校正ファイル形式
ファイルのアップロードを決定する方法のほとんどは、まだ主に最初のものですが、しかし、拡張子は完全に自由に変更することができます。
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です。しかし、ファイルは考慮されなければなりません:
- アップロードするファイルが大きすぎます。
- ファイルのアップロード中にネットワークエラーが発生してアップロードに失敗した場合はどうすればよいですか?
- セカンドパスなどの問題
つまり、ファイルは多くの小さな塊に分割され、それぞれの塊が個別にアップロードされるため、どの塊がアップロードされたかを知る必要があります。
ファイルがサーバーにアップロードされたかどうかを知るには?アップロードされたファイルのスライス数は?
これは、計算されたファイルの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リクエストを送信します。存在しない場合、スライスされたファイルパッケージが存在する可能性もあります。
- もしそれが存在すれば、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回目です。





