序文ファイルのアップロードは、フロントエンド開発者が開発中によく遭遇する問題です。関連する機能を実装することはできるかもしれませんが、完成後、コードの実装が少し「不十分」だと感じたことはありませんか?ファイルのアップロードについて本当に理解していますか?大きなファイルをアップロードし、停電中にアップロードを再開するにはどうすればよいですか? フロントエンドとバックエンドの通信で一般的に使用される形式は何ですか? ファイルアップロードの進行状況はどのように制御され、サーバー上でどのように実装されていますか?次は、手対手シリーズの学習を始めましょう! ! !何か不足な点がありましたら、遠慮なくアドバイスをください。次に、次の図に従って検討し、議論してください。 準備は完了です。始めましょう! ! ! フロントエンド構造ページ表示 プロジェクトの依存関係 バックエンド構造(ノード+エクスプレス)ディレクトリ構造 Axiosのシンプルなカプセル化 インスタンスを axios.create() にします。 インスタンスのデフォルトベースURL = 'http://127.0.0.1:8888'; instance.defaults.headers['Content-Type'] = 'multipart/form-data'; instance.defaults.transformRequest = (データ、ヘッダー) => { const contentType = headers['Content-Type']; contentType === "application/x-www-form-urlencoded" の場合、Qs.stringify(data) を返します。 データを返します。 }; インスタンス.インターセプター.レスポンス.use(レスポンス => { 応答データを返します。 }); ファイルのアップロードは一般的にFormDataとBase64の2つの方法に基づいています。 FormDataに基づくファイルアップロード//フロントエンドコード// 主にForDataに基づいたアップロードのコアコードを示します upload_button_upload.addEventListener('click', function () { upload_button_upload.classList.contains('disable') || upload_button_upload.classList.contains('loading') の場合、戻り値: if (!_file) { alert('まずアップロードするファイルを選択してください~~'); 戻る; } 無効にします(true); // ファイルをサーバーに渡す: FormData formData を新しい FormData() にします。 // 背景のニーズに応じてフィールドを追加します formData.append('file', _file); formData.append('ファイル名', _file.name); instance.post('/upload_single', formData).then(data => { (+データコード === 0)の場合{ alert(`ファイルは正常にアップロードされました~~、${data.servicePath}に基づいてこのリソースにアクセスできます~~`); 戻る; } Promise.reject(data.codeText) を返します。 }).catch(理由 => { alert('ファイルのアップロードに失敗しました。後でもう一度お試しください~~'); }).finally(() => { ハンドルをクリアします。 変更無効(false); }); }); BASE64に基づくファイルアップロードBASE64特有のメソッドエクスポートchangeBASE64(ファイル) => { 新しいPromiseを返します(resolve => { fileReader を新しい FileReader() にします。 fileReader.readAsDataURL(ファイル); ファイルリーダー.onload = ev => { ev.target.result を解決します。 }; }); }; 具体的な実装 upload_inp.addEventListener('change'、非同期関数() { ファイルをupload_inp.files[0]とします。 BASE64、 データ; if (!file) 戻り値; ファイルサイズが 2 * 1024 * 1024 より大きい場合 alert('アップロードできるファイルは2MBを超えることはできません~~'); 戻る; } upload_button_select.classList.add('読み込み中'); // Base64 を取得 BASE64 = changeBASE64(ファイル)を待機します。 試す { データ = インスタンス.post('/upload_single_base64', { // encodeURIComponent(BASE64) は、送信中に特殊文字が文字化けするのを防ぎます。同時に、バックエンドは decodeURIComponent を使用してファイルをデコードする必要があります: encodeURIComponent(BASE64)、 ファイル名: file.name }, { ヘッダー: { 'コンテンツタイプ': 'application/x-www-form-urlencoded' } }); (+データコード === 0)の場合{ alert(`おめでとうございます。ファイルが正常にアップロードされました。${data.servicePath} アドレスに基づいてアクセスできます~~`); 戻る; } data.codeText をスローします。 } キャッチ (エラー) { alert('残念ながら、ファイルのアップロードに失敗しました。しばらくしてからもう一度お試しください~~'); ついに upload_button_select.classList.remove('ロード中'); } **});** 上記の例では、バックエンドはフロントエンドから受信したファイルにランダムな名前を生成して保存します。ただし、一部の企業では、この手順をフロントエンドで実行し、生成された名前をバックエンドに送信します。次に、この機能を実装しましょう。 フロントエンドはファイル名を生成し、それをバックエンドに渡しますここでは、前述の SparkMD5 プラグインを使用する必要があります。使い方の詳細については説明しません。ドキュメントを参照してください。 ファイルストリームの読み取りメソッドをカプセル化します const changeBuffer = ファイル => { 新しいPromiseを返します(resolve => { fileReader を新しい FileReader() にします。 fileReader.readAsArrayBuffer(ファイル); ファイルリーダー.onload = ev => { バッファ = ev.target.result とします。 スパーク = 新しい SparkMD5.ArrayBuffer()、 ハッシュ、 サフィックス; spark.append(バッファ); // ファイル名を取得します HASH = spark.end(); // サフィックス名を取得します suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1]; 解決する({ バッファ、 ハッシュ、 サフィックス、 ファイル名: `${HASH}.${suffix}` }); }; }); }; サーバー関連コードをアップロードする upload_button_upload.addEventListener('click', 非同期関数() { checkIsDisable(this) の場合、戻り値: if (!_file) { alert('まずアップロードするファイルを選択してください~~'); 戻る; } 無効にします(true); // ファイルのHASH名を生成する let { ファイル名 } = changeBuffer(_file) を待機します。 formData を新しい FormData() にします。 formData.append('ファイル', _file); formData.append('ファイル名', ファイル名); instance.post('/upload_single_name', formData).then(data => { (+データコード === 0)の場合{ alert(`ファイルは正常にアップロードされました~~、${data.servicePath}に基づいてこのリソースにアクセスできます~~`); 戻る; } Promise.reject(data.codeText) を返します。 }).catch(理由 => { alert('ファイルのアップロードに失敗しました。後でもう一度お試しください~~'); }).finally(() => { 変更無効(false); upload_abbre.style.display = 'なし'; アップロードした画像をアップロードします。 _file = null; }); }); アップロード進捗管理この機能は比較的シンプルです。この記事で使用しているリクエストライブラリは axios です。進捗管理は主に axios が提供する onUploadProgress 関数をベースに実装されています。この関数の実装原理を見てみましょう。 xhr.upload.onprogress をリッスンしています ファイルがアップロードされた後に取得されるオブジェクト 具体的な実装 (関数 () { アップロード = document.querySelector('#upload4') とします。 upload_inp = upload.querySelector('.upload_inp'), upload_button_select = upload.querySelector('.upload_button.select'), アップロードの進行状況 = upload.querySelector('.upload_progress'), upload_progress_value = upload_progress.querySelector('.value'); // 操作可能な状態であるかどうかを確認する const checkIsDisable = element => { classList を element.classList とします。 classList.contains('disable') を返します || classList.contains('loading'); }; upload_inp.addEventListener('change'、非同期関数() { ファイルをupload_inp.files[0]とします。 データ; if (!file) 戻り値: upload_button_select.classList.add('読み込み中'); 試す { formData を新しい FormData() にします。 formData.append('ファイル', ファイル); formData.append('ファイル名', ファイル名); データ = インスタンス.post('/upload_single', フォームデータ, { //ファイルアップロード時のコールバック関数 xhr.upload.onprogress onUploadProgress(ev) { させて { ロード済み、 合計 } = ev; upload_progress.style.display = 'ブロック'; upload_progress_value.style.width = `${loaded/total*100}%`; } }); (+データコード === 0)の場合{ upload_progress_value.style.width = `100%`; alert(`おめでとうございます。ファイルが正常にアップロードされました。${data.servicePath} に基づいてファイルにアクセスできます~~`); 戻る; } data.codeText をスローします。 } キャッチ (エラー) { alert('残念ながら、ファイルのアップロードに失敗しました。しばらくしてからもう一度お試しください~~'); ついに upload_button_select.classList.remove('ロード中'); upload_progress.style.display = 'なし'; アップロードの進行状況の値.style.width = `0%`; } }); upload_button_select.addEventListener('click', 関数() { checkIsDisable(this) の場合、戻り値: アップロード }); })(); 大きなファイルのアップロード大きなファイルは通常、スライスでアップロードされます。これにより、ファイルのアップロード速度が向上します。フロントエンドはファイルストリームをスライスし、バックエンドと通信して送信します。通常は、ブレークポイント送信と組み合わせます。このとき、バックエンドは通常、3 つのインターフェイスを提供します。最初のインターフェイスはアップロードされたスライス情報を取得し、2 番目のインターフェイスはフロントエンドのスライスファイルを送信し、3 番目のインターフェイスは、すべてのスライスがアップロードされた後にファイルをマージするようにバックエンドに指示します。 スライスは、固定数と固定サイズの 2 つの方法で実行できます。ここでは、この 2 つを組み合わせます。 // ファイルスライス処理「固定数 & 固定サイズ」を実装 最大値を1024 * 100とします。 count = Math.ceil(ファイルサイズ / 最大値)、 インデックス = 0、 チャンク = []; (カウント>100)の場合{ 最大値 = ファイルサイズ / 100; カウント = 100; } while (インデックス < カウント) { チャンク.push({ // ファイル自体にはスライスメソッドがあります。次の図を参照してください。file.slice(index * max, (index + 1) * max), ファイル名: `${HASH}_${index+1}.${suffix}` }); インデックス++; } サーバーに送信 チャンク.forEach(チャンク => { fm = new FormData; とします。 fm.append('ファイル', chunk.file); fm.append('ファイル名', chunk.ファイル名); instance.post('/upload_chunk', fm).then(データ => { (+データコード === 0)の場合{ 補完(); 戻る; } Promise.reject(data.codeText) を返します。 }).catch(() => { alert('現在のスライスのアップロードに失敗しました。後でもう一度お試しください~~'); クリア(); }); }); ファイルアップロード + 電源オフ再開 + 進行状況管理 upload_inp.addEventListener('change'、非同期関数() { ファイルをupload_inp.files[0]とします。 if (!file) 戻り値; upload_button_select.classList.add('読み込み中'); upload_progress.style.display = 'ブロック'; // ファイルのハッシュを取得する すでに = [] とします。 データ = null、 { ハッシュ、 サフィックス } = changeBuffer(ファイル)を待機します。 // アップロードされたスライスの情報を取得する try { データ = インスタンス.get('/upload_already', { パラメータ: { ハッシュ } }); (+データコード === 0)の場合{ すでに = data.fileList; } } キャッチ (エラー) {} // ファイルスライス処理「固定数 & 固定サイズ」を実装 最大値を1024 * 100とします。 count = Math.ceil(ファイルサイズ / 最大値)、 インデックス = 0、 チャンク = []; (カウント>100)の場合{ 最大値 = ファイルサイズ / 100; カウント = 100; } while (インデックス < カウント) { チャンク.push({ ファイル: file.slice(インデックス * 最大値、(インデックス + 1) * 最大値)、 ファイル名: `${HASH}_${index+1}.${suffix}` }); インデックス++; } // アップロード処理が成功しました。index = 0; 定数クリア = () => { upload_button_select.classList.remove('ロード中'); upload_progress.style.display = 'なし'; アップロードの進行状況の値.style.width = '0%'; }; 定数コンプリート = 非同期() => { // プログレスバーのインデックスを制御します++; upload_progress_value.style.width = `${index/count*100}%`; // すべてのスライスが正常にアップロードされたら、スライスをマージします。if (index < count) return; upload_progress_value.style.width = `100%`; 試す { データ = インスタンス.post('/upload_merge', { ハッシュ、 カウント }, { ヘッダー: { 'コンテンツタイプ': 'application/x-www-form-urlencoded' } }); (+データコード === 0)の場合{ alert(`おめでとうございます。ファイルが正常にアップロードされました。${data.servicePath} に基づいてファイルにアクセスできます~~`); クリア(); 戻る; } data.codeText をスローします。 } キャッチ (エラー) { alert('スライスのマージに失敗しました。後でもう一度お試しください~~'); クリア(); } }; // 各スライスをサーバーにアップロードする chunks.forEach(chunk => { // すでにアップロードされているため、再度アップロードする必要はありません if (already.length > 0 && already.includes(chunk.filename)) { 補完(); 戻る; } fm = new FormData; とします。 fm.append('ファイル', chunk.file); fm.append('ファイル名', chunk.ファイル名); instance.post('/upload_chunk', fm).then(データ => { (+データコード === 0)の場合{ 補完(); 戻る; } Promise.reject(data.codeText) を返します。 }).catch(() => { alert('現在のスライスのアップロードに失敗しました。後でもう一度お試しください~~'); クリア(); }); }); }); サーバー コード (大きなファイルのアップロード + ブレークポイントの再開)// 大きなファイルのスライスをアップロードしてスライスをマージする const merge = function merge(HASH, count) { 新しい Promise(async (resolve, reject) => { を返します。 パスを `${uploadDir}/${HASH}` とします。 ファイルリスト = [], サフィックス、 存在する; isExists = exists(パス) を待機します。 存在する場合 拒否('HASHパスが見つかりません!'); 戻る; } ファイルリスト = fs.readdirSync(パス); if (fileList.length < count) { 拒否('スライスがアップロードされていません!'); 戻る; } ファイルリスト.sort((a, b) => { reg = /_(\d+)/ とします。 reg.exec(a)[1] - reg.exec(b)[1]を返します。 }).forEach(項目 => { !サフィックス ? サフィックス = /\.([0-9a-zA-Z]+)$/.exec(item)[1] : null; fs.appendFileSync(`${uploadDir}/${HASH}.${suffix}`、fs.readFileSync(`${path}/${item}`)); fs.unlinkSync(`${path}/${item}`); }); fs.rmdirSync(パス); 解決する({ パス: `${uploadDir}/${HASH}.${suffix}`, ファイル名: `${HASH}.${suffix}` }); }); }; app.post('/upload_chunk', 非同期(req, res) => { 試す { させて { フィールド、 ファイル } = multiparty_upload(req) を待機します。 ファイルを (files.file && files.file[0]) とします || {}, ファイル名 = (fields.filename && fields.filename[0]) || "", パス = ''、 isExists = false; // スライスを保存するための一時ディレクトリを作成します。let [, HASH] = /^([^_]+)_(\d+)/.exec(filename); パス = `${uploadDir}/${HASH}`; !fs.existsSync(パス) ? fs.mkdirSync(パス) : null; // スライスを一時ディレクトリに保存します。path = `${uploadDir}/${HASH}/${filename}`; isExists = 存在することを待機します(パス); 存在する場合 res.send({ コード: 0, コードテキスト: 'ファイルが存在します', 元のファイル名: ファイル名、 サービスパス: path.replace(__dirname, HOSTNAME) }); 戻る; } writeFile(res、パス、ファイル、ファイル名、true); } キャッチ (エラー) { res.send({ コード: 1, コードテキスト: エラー }); } }); app.post('/upload_merge', 非同期(req, res) => { させて { ハッシュ、 カウント } = 要求本体; 試す { させて { ファイル名、 パス } = マージを待機します(HASH、カウント); res.send({ コード: 0, コードテキスト: 'マージ成功'、 元のファイル名: ファイル名、 サービスパス: path.replace(__dirname, HOSTNAME) }); } キャッチ (エラー) { res.send({ コード: 1, コードテキスト: エラー }); } }); app.get('/upload_already', 非同期(req, res) => { させて { ハッシュ } = 要求クエリ; パスを `${uploadDir}/${HASH}` とします。 ファイルリスト = []; 試す { ファイルリスト = fs.readdirSync(パス); ファイルリスト = ファイルリスト.sort((a, b) => { reg = /_(\d+)/ とします。 reg.exec(a)[1] - reg.exec(b)[1]を返します。 }); res.send({ コード: 0, コードテキスト: ''、 ファイルリスト: ファイルリスト }); } キャッチ (エラー) { res.send({ コード: 0, コードテキスト: ''、 ファイルリスト: ファイルリスト }); } }); 要約するjs ベースの大容量ファイルのアップロードとブレークポイント再開を管理する方法については、これで終わりです。より関連性の高い js 大容量ファイルのアップロードとブレークポイント再開のコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
これは私の最初のブログ投稿です。時間の制約があるため、どのようにフォーマットすればよいかわかりません...
XHTML 言語では、ul タグに li が含まれ、dl タグに dt と dd が含まれることは誰...
この記事では、CentOS 7 で高可用性 Hadoop 2.10 クラスターを構築する方法を紹介し...
MySQL 論理アーキテクチャの概要他のデータベースと比較すると、MySQL は、そのアーキテクチャ...
Unicode 署名 BOM - BOM とは何ですか? BOM は Byte Order Mark...
目次1. ウィンドウ関数とは何ですか? 1. ウィンドウをどのように理解しますか? 2. ウィンドウ...
この効果を実現するには、まず CSS のプロパティを知っておく必要があります。 text-shado...
目次序文: 1. データ移行について2. 移行計画と留意点要約:序文:日常業務では、テーブル、データ...
目次1. ログ関連サービス2. システム内の共通ログファイル1. ログ関連サービスCentOS 6....
この記事では、参考までに、簡単な英中辞典を実装するためのjsの具体的なコードを紹介します。具体的な内...
docker exec コマンドは、実行中のコンテナ内でコマンドを実行できます。 docker ex...
この記事の例では、Vueプロジェクトでのトークン検証ログインの具体的なコードを参考までに共有していま...
序文システムにファイル システムを作成し、それを永続的または非永続的にマウントする方法を学習します。...
この記事では、例を使用して、MySQL スライディング順序問題の原理と解決方法を説明します。ご参考ま...
<br />インターネットは絶えず変化しており、BusinessWeek.com は専門...