blog

ファイルのアップロード

1.ファイルの接尾辞が一致するかどうかを判断します。この校正方法、不正確。 最初の呼び出し。指定された範囲のデータ、blobオブジェクトを返します。 このblobオブジェクトの内容を読み取るためのイン...

Jul 28, 2020 · 6 min. read
シェア

校正ファイル形式

ファイルのアップロードを決定する方法のほとんどは、まだ主に最初のものですが、しかし、拡張子は完全に自由に変更することができます。

1.ファイルのサフィックスが一致するかどうかを判断します。この校正方法、不正確。

2.かどうかは、イメージ、ビデオ、およびその他のファイルは、バイナリ形式で格納されているファイルのヘッダー情報の各タイプが異なっている、すべてのヘッダー情報によって判断することができますファイルの種類です。

入力のイベントを通じて、渡されたファイルは、コンストラクタはFileですが、FileもBlobのプロパティメソッドを継承します。

例えば、gif形式を判断するには、最初の6ビットを判断するだけです。

  • 最初にfile.slice(0,6)を呼び出します。指定された範囲のデータをblobオブジェクトとして返します。
async blobToString(blob) {
 return new Promise(resolve => {
 const reader = new FileReader()
 reader.onload = function() {
 // 1.結果をユニコードに変換する, 2.16進数に変換した後、大文字に変換する。
 let res = reader.result.split('')
 .map(i => i.charCodeAt())
 .map(j => j.toString(16).toUpperCase())
 .join(' ')
 // 変換された結果を返す
 resolve(res)
 }
 // ファイル読み込みメソッドを呼び出す
 reader.readAsBinaryString(blob)
 })
},
  • このblobオブジェクト(この時点では「GIF89a」)のコンテンツを読み取るためのFileReaderインスタンスを作成します。

  • 最初に文字を分割し、charCodeAt: は文字の Unicode コードを返し、戻り値は 0 から 65535 までの整数です。

  • 16進数に変換し、次に大文字に変換し、最後にスペースを含む文字列に変換します。最終的な比較結果が "47 49 46 38 37 61 "であるか、res === "47 49 46 38 39 61 "であるか。

ファイルのアップロード

通常のファイルのアップロードは、formDateにファイルを追加し、バックエンドに渡すだけでOKです。しかし、ファイルは考慮されなければなりません:

  1. アップロードするファイルが大きすぎます。
  2. ファイルのアップロード中にネットワークエラーが発生してアップロードに失敗した場合はどうすればよいですか?
  3. セカンドパスなどの問題

つまり、ファイルは多くの小さな塊に分割され、それぞれの塊が個別にアップロードされるため、どの塊がアップロードされたかを知る必要があります。

ファイルがサーバーにアップロードされたかどうかを知るには?アップロードされたファイルのスライス数は?

これは、計算されたファイルのmd5値である一意の識別子を必要とし、ファイルの内容が同じである限り、md5値は変更されません。

ファイルの md5 を計算

まず最初に、ファイルをスライスします。つまり、サイズを定義し、file.slice(0, size)を使ってファイルを同じサイズのスライスにスライスします。

md5値を計算するのは非常に時間がかかります。

new Worker

Worker インスタンスを作成し、受け渡し用に 2 つのリスナー関数を登録します。

スライスの配列を渡すと、ワーカーはスライスの計算が終わるたびに、あるいはすべてのスライスの計算が終わるたびに、メインスレッドにシグナルを送ります。Worker はメインスレッドにシグナルを送ります。このシグナルは md5 の計算の進捗を持ち、フロントエンドでプログレスバーとして使用できます。

requestIdleCallback

react16のFilberアーキテクチャは、ブラウザのアイドル時間を使ってこれを計算します。

実行関数のwindow.requestIdleCallbackパスにあり、この計算関数の実行時にアイドル時間が1msを超えた場合

ハッシュ計算例

例えば2Mといったサイズを設定することです。

  • ファイルの先頭をsizeの長さでスライスするには、slice(0, size)です。

  • それに続く長さのスライスのサイズは、それぞれ前、中、後ろから2つのサブセクションです。つまり、おおよそslice(start, start + 2)、slice(midd, midd + 2)、slice(end - 2, end)となります。

  • 残りは一切れの大きさ以下です。

その後、md5値の計算を置き、実際には、これは計算します:前面と背面のスライスのサンプリングのmd5値、およびスライスの中央セクター。

計算結果は、上記の2つの計算とは異なる必要がありますが、このメソッドは、おそらく4Mかそこらのファイルのmd5の値として、どのように大きなファイルは、オーバーサイズのファイルのようなものに適していません。

この計算は、高い効率性と引き換えに、その精度の一部を失っています。ファイルのサブセクションの1つが変更された場合、計算されるmd5値は異なるかもしれません。もちろん、これは非常にまれなことなので、無視してもかまいませんが...。

イメージをアップロードするスライス

上記のステップの後、スライス、ファイルハッシュが利用可能になります。

  • 各スライスにインデックスと名前を付けます。インデックスはスライスの順番で、名前はハッシュとインデックスを足したものです。
  • 配列の各項目がスライスのアップロード要求である配列を作成します。つまり、各メンバーはPromiseオブジェクトです。
  • Promise.all(約束)、同時実行。ここで問題がある、それは同時にn個の複数の要求を実行されますが、同時にn個の複数のTcp接続を確立するだけで、同時にアップロードされないことです。パフォーマンスの問題を引き起こす、嫌悪への解決策は、次の制御の同時実行です。
  • スライスがアップロードされたら、ファイル名やハッシュ値などを渡してバックエンドのマージインターフェイスをリクエストします。
async mergeRequest() {
 let data = {
 ext: this.file.name.split(".").pop(),
 size: this.chunkSize,
 hash: this.hash
 }
 let res = await this.$http.post("/mergeUploadedSliceFile", data)
 if (res && res.success) {
 this.$message({
 message: res.message,
 type: 'success'
 });
 }
},
  • バックエンドインターフェイスがそれを受け取ると、おそらく書き込みストリームを作成し、スライスのインデックスに従って順番に読み込みストリームを指示します。すべてのスライスを削除します。
async mergeFile(filePath, filehash, size) {
 // スライスがあるフォルダ
 let chunkdDir = path.resolve(this.config.UPLOAD_DIR, filehash)
 // すべてのスライスをソートする
 try {
 let chunks = await fse.readdir(chunkdDir) 
 chunks.sort((a, b) => a.split('-')[1] - b.split('-')[1])
 chunks = chunks.map(cp => path.resolve(chunkdDir, cp))
 await this.mergeChunks(chunks, filePath, size)
 } catch(err) {
 console.log("err", err);
 }
}
async mergeChunks(files, dest, size) { 
 const pipStream = (filePath, writeStream) => new Promise(resolve => {
 const readStream = fse.createReadStream(filePath)
 readStream.on('end', () => {
 fse.unlinkSync(filePath)
 resolve()
 })
 readStream.pipe(writeStream)
 })
 // 書き込みストリームを作成する。
 await Promise.all(
 files.map((file, index) => {
 // size 整数でなければならない
 let option = {
 start: index * size,
 end: (index + 1) * size
 } 
 pipStream(file, fse.createWriteStream(dest, option))
 })
 )
}

秒で切断、転送

ファイルが存在することを確認するために、ファイルのmd5値でchecked interfaceリクエストを送信します。存在しない場合、スライスされたファイルパッケージが存在する可能性もあります。

  1. もしそれが存在すれば、2回目の移籍が成功したことを示すプロンプトが直接表示されます。
  2. が存在しない場合、バックエンドはスライスの配列を返します。スライスの配列に従って、フロントエンドは各スライスのプログレスバーの状態を変更し、既存のスライスをフィルタリングします。アップロードされたスライスのみをアップロードし、前と同じようにスライスをアップロードしてマージします。

同時アップロードの制御

上記のPromise.allが一度に複数のtcpコネクションを確立する問題を解決します。例えば、limitが3の場合、実行関数を3回呼び出します。

ループのため、3つの実行の開始点は異なります。毎回スライスがポップアップされ、現在のスライスがアップロードされた後、次のスライスが実行されます。常に3つの同時実行となります。

return new Promise((resolve, reject) => {
 let cur = 0
 let limit = this.limit
 let len = requets.lengthz
 const startUpload = async () => {
 let task = requets.shift();
 if (task) {
 let res = await this.$http.post('/uploadSliceFile', task.formData, {
 onUploadProgress: e => {
 this.chunks[task.index]['progress'] = Number(
 ((e.loaded / e.total) * 100).toFixed(2),
 )
 },
 })
 if (cur === (len - 1)) {
 resolve()
 } else {
 cur ++
 startUpload()
 }
 }
 }
 while(limit>0) {
 startUpload()
 limit --
 }
})

エラーレポート再試行アップロード

1スライスあたりのアップロードは3回までとします。

各スライスは、現在のスライスがアップロードに失敗した回数を記録するために使用される0のerr属性を追加し、スライスがインターフェイスにエラーを返したときに、上記の並行処理の外側のレイヤーにtry catchのレイヤーを追加します。

  • err+1が3回以下になったら、現在のタスクをスライス配列の先頭に追加し、プログレスバーを更新しながら再実行します。
  • もう3回目です。

Read next

vue +要素の日付と時刻のコントロールは、この瞬間以降にしか選択できない。

``` ``` ``` data() { return { "", endTime: "", dataTime: { // 開始時間の範囲制限 (ti

Jul 28, 2020 · 3 min read