背景最近、最終プロジェクトを書いていたときに、通常のファイルのアップロード、大きなファイルのアップロード、ブレークポイントの再開など、いくつかのファイルアップロード機能に携わりました。 サーバーの依存関係
バックエンド構成クロスドメインapp.use(async (ctx, next) => { ctx.set('アクセス制御許可オリジン', '*'); ctx.set( 「アクセス制御許可ヘッダー」、 'Content-Type、Content-Length、Authorization、Accept、X-Requested-With、yourHeaderFeild'、 ); ctx.set('アクセス制御許可メソッド', 'PUT、POST、GET、DELETE、OPTIONS'); ctx.method == 'OPTIONS'の場合{ ctx.body = 200; } それ以外 { 次の()を待ちます。 } }); バックエンド構成の静的リソースアクセスではkoa-static-cacheを使用します。// 静的リソース処理 app.use( KoaStaticCache('./public', { プレフィックス: '/public'、 動的: true、 gzip: 真、 })、 ); バックエンド構成リクエストボディ解析はkoa-bodyparserを使用しますbodyParser は 'koa-bodyparser' を必要とします。 app.use(bodyParser()); フロントエンドの依存関係
通常のファイルアップロード後部バックエンドは、ミドルウェアとして koa-body を使用してオプションを設定し、それを router.post('url',middleware,callback) に渡すだけです。 バックエンドコード // アップロード設定 const uploadOptions = { // ファイル形式をサポートする multipart: true, 恐ろしい: // ディレクトリをパブリックフォルダに直接アップロードします。簡単にアクセスできるように、フォルダの後に / を追加することを忘れないでください アップロードディレクトリ: path.join(__dirname, '../../pulbic/'), // ファイル拡張子を保持する keepExtensions: true, }, }; router.post('/upload', 新しい KoaBody(uploadOptions), (ctx, next) => { // アップロードされたファイルを取得します。const file = ctx.request.files.file; 定数 fileName = file.path.split('/')[file.path.split('/').length-1]; ctx.body = { コード:0, データ:{ url:`public/${fileName}` }, メッセージ: '成功' } }); フロントエンドここでは、formData 送信メソッドを使用します。フロントエンドは、<input type='file'/> を通じてファイルセレクターにアクセスし、onChange イベント e.target.files[0] を通じて選択されたファイルを取得します。次に、取得したファイルを取得するための FormData オブジェクトを作成します。formData.append('file',targetFile) フロントエンドコード const アップロード = () => { const [url, setUrl] = useState<文字列>('') const handleClickUpload = () => { const fileLoader = document.querySelector('#btnFile') を HTMLInputElement として定義します。 if (isNil(fileLoader)) { 戻る; } ファイルローダーをクリックします。 } const handleUpload = async (e: any) => { //アップロードされたファイルを取得します。const file = e.target.files[0]; const フォームデータ = 新しいフォームデータ() formData.append('ファイル', ファイル); // ファイルをアップロード const { data } = await uploadSmallFile(formData); コンソールにログ出力します。 setUrl(`${baseURL}${data.url}`); } 戻る ( <div> <input type="file" id="btnFile" onChange={handleUpload} style={{ display: 'none' }} /> <Button onClick={handleClickUpload}>小さいファイルをアップロード</Button> <画像ソース={url} /> </div> ) } その他のオプション
大きなファイルのアップロードファイルをアップロードする際、ファイルが大きすぎるためにリクエストがタイムアウトになる場合があります。この場合、断片化方式を使用できます。簡単に言うと、ファイルは小さな断片に分割され、サーバーに送信されます。これらの小さな断片は、どのファイルに属し、どの位置にあるかを識別します。すべての小さな断片が送信された後、バックエンドはマージを実行してこれらのファイルを完全なファイルにマージし、送信プロセス全体を完了します。 フロントエンド
const handleUploadLarge = async (e: any) => { //アップロードされたファイルを取得します。const file = e.target.files[0]; // ファイル セグメントの場合、await uploadEveryChunk(file, 0); } 定数アップロード毎チャンク = ( ファイル: ファイル、 インデックス: 番号、 ) => { コンソールログ(インデックス); const chunkSize = 512; // スライス幅 // [ファイル名、ファイルサフィックス] const [fname, fext] = file.name.split('.'); // 現在のスライスの開始バイトを取得します。const start = index * chunkSize; if (start > file.size) { // ファイルサイズを超えたら再帰アップロードを停止します return mergeLargeFile(file.name); } const blob = file.slice(start, start + chunkSize); // 各ピースに名前を付けます const blobName = `${fname}.${index}.${fext}`; const blobFile = 新しいファイル([blob]、blobName); フォームデータを作成します。 formData.append('file', blobFile); アップロードLargeFile(formData).then((res) => { // 再帰アップロード uploadEveryChunk(file, ++index); }); }; 後部バックエンドは2つのインターフェースを提供する必要がある アップロードアップロードした各チャンクを、後で簡単にマージできるように、対応する名前のフォルダに保存します。 const アップロードステンシルプレビューオプション = { マルチパート: true、 恐ろしい: uploadDir: path.resolve(__dirname, '../../temp/'), // ファイル保存アドレス keepExtensions: true, 最大フィールドサイズ: 2 * 1024 * 1024、 }, }; router.post('/upload_chunk', 新しい KoaBody(uploadStencilPreviewOptions), 非同期 (ctx) => { 試す { const ファイル = ctx.request.files.file; // [ name, index, ext ] - ファイル名を分割します const fileNameArr = file.name.split('.'); const UPLOAD_DIR = path.resolve(__dirname, '../../temp'); // スライスを保存するディレクトリ const chunkDir = `${UPLOAD_DIR}/${fileNameArr[0]}`; if (!fse.existsSync(chunkDir)) { // ディレクトリが存在しない場合はディレクトリを作成します // 大きなファイル用の一時ディレクトリを作成します await fse.mkdirs(chunkDir); } // 元のファイル名.index - 各シャードの特定のアドレスと名前 const dPath = path.join(chunkDir, fileNameArr[1]); // 断片化されたファイルを temp から今回アップロードした大きなファイルの一時ディレクトリに移動します。await fse.move(file.path, dPath, { overwrite: true }); ctx.body = { コード: 0, メッセージ: 「ファイルが正常にアップロードされました」 }; } キャッチ (e) { ctx.body = { コード: -1、 メッセージ: `ファイルのアップロードに失敗しました: ${e.toString()}`、 }; } }); マージフロントエンドからのマージ要求に従って、運ばれた名前を使用して、大きなファイルブロックの一時キャッシュ内でその名前に属するフォルダーを検索します。チャンクをインデックス順に読み取った後、fse.appendFileSync(path,data) を使用してファイルをマージし (順番に追加とはマージを意味します)、一時保存フォルダーを削除してメモリ領域を解放します。 router.post('/merge_chunk', 非同期(ctx) => { 試す { const { ファイル名 } = ctx.request.body; 定数 fname = ファイル名.split('.')[0]; TEMP_DIR は path.resolve(__dirname, '../../temp'); const static_preview_url = '/public/previews'; STORAGE_DIR は path.resolve(__dirname, `../..${static_preview_url}`); TEMP_DIR を path に結合します。 const チャンク = fse.readdir(chunkDir); チャンク .sort((a, b) => a - b) .map((チャンクパス) => { // ファイルをマージする fse.appendFileSync( パス.join(STORAGE_DIR, ファイル名), fse.readFileSync(`${chunkDir}/${chunkPath}`)、 ); }); // 一時フォルダを削除します fse.removeSync(chunkDir); // 画像にアクセスするためのURL const url = `http://${ctx.request.header.host}${static_preview_url}/${fileName}`; ctx.body = { コード: 0, データ: { url }, メッセージ: '成功'、 }; } キャッチ (e) { ctx.body = { コード: -1、メッセージ: `マージに失敗しました: ${e.toString()}` }; } }); 履歴書のダウンロード大きなファイルの送信中に、ページの更新や一時的な障害により送信が失敗した場合、ファイルを最初から再度送信する必要があり、ユーザーにとって非常に不快な体験となります。そのため、転送に失敗した場所をマークしておき、次回はここに直接転送する必要があります。localStorage で読み書きする方法を採用しています。 const handleUploadLarge = async (e: any) => { //アップロードされたファイルを取得します。const file = e.target.files[0]; const レコード = JSON.parse(localStorage.getItem('uploadRecord') を any として); if (!isNil(レコード)) { // 便宜上、ここでは衝突の問題は考慮しません。ファイルが同じかどうかを判断するには、ハッシュファイル方式を使用できます。 // 大きなファイルの場合は、ハッシュ(ファイル + ファイルサイズ)方式を使用して、2 つのファイルが同じかどうかを判断できます if (record.name === file.name) { 戻り値 uploadEveryChunk(file, record.index); } } // ファイル セグメントの場合、await uploadEveryChunk(file, 0); } 定数アップロード毎チャンク = ( ファイル: ファイル、 インデックス: 番号、 ) => { const chunkSize = 512; // スライス幅 // [ファイル名、ファイルサフィックス] const [fname, fext] = file.name.split('.'); // 現在のスライスの開始バイトを取得します。const start = index * chunkSize; if (start > file.size) { // ファイルサイズを超えたら再帰アップロードを停止します return mergeLargeFile(file.name).then(()=>{ // マージが成功したらレコードを削除します localStorage.removeItem('uploadRecord') }); } const blob = file.slice(start, start + chunkSize); // 各ピースに名前を付けます const blobName = `${fname}.${index}.${fext}`; const blobFile = 新しいファイル([blob]、blobName); フォームデータを作成します。 formData.append('file', blobFile); アップロードLargeFile(formData).then((res) => { // 各データが正常に転送された後、レコードの場所が記録されます localStorage.setItem('uploadRecord',JSON.stringify({ 名前:ファイル名、 インデックス:インデックス+1 })) // 再帰アップロード uploadEveryChunk(file, ++index); }); }; ファイル識別ファイルの MD5、ハッシュなどを計算できます。ファイルが大きすぎる場合、ハッシュ化に時間がかかる場合があります。 ファイルの一部を取り出してファイルサイズでハッシュし、ローカルサンプリング比較を行うことができます。crypto-jsライブラリを使用してmd5を計算し、FileReaderでファイルを読み取るコードは次のとおりです。 // md5 を計算して、すでに存在するかどうかを確認します。const sign = tempFile.slice(0, 512); const signFile = 新しいファイル( [sign、(tempFile.size は不明) は BlobPart]、 ''、 ); const リーダー = 新しい FileReader(); reader.onload = 関数 (イベント) { const バイナリ = イベント?.ターゲット?.結果; const md5 = binary && CryptoJs.MD5(binary を文字列として).toString(); 定数レコード = localStorage.getItem('upLoadMD5'); if (isNil(md5)) { const file = blobToFile(blob, `${getRandomFileName()}.png`); uploadPreview(ファイル、0、md5)を返します。 } const file = blobToFile(blob, `${md5}.png`); if (isNil(レコード)) { // このmd5を直接アップロードして記録します uploadPreview(ファイル、0、md5)を返します。 } レコードを解析します。 (recordObj.md5 == md5)の場合{ // 記録された位置からアップロードを開始します // ブレークポイントからアップロードを再開します return uploadPreview(file, recordObj.index, md5); } uploadPreview(ファイル、0、md5)を返します。 }; リーダー。バイナリ文字列として読み取ります。(signFile) 要約するこれまでファイルのアップロードについてはあまり知りませんでした。卒業プロジェクトのこの機能を通じて、ファイルをアップロードするためのフロントエンドとバックエンドのコードについて予備的な理解が得られました。これらの方法はオプションの一部にすぎず、すべてを網羅しているわけではないかもしれません。今後の学習でさらに向上していきたいと思います。 上記はReact+Koaでファイルアップロードを実装した例の詳細です。React+Koaでファイルアップロードを実装する詳細については、123WORDPRESS.COMの他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: 上位Nを見つけるためのMySQLグループソートの詳細な説明
>>: CentOS7 で Jenkins+Maven+Git 継続的インテグレーション環境を構築する方法
01. コマンドの概要Linux には充実したヘルプ マニュアルが用意されています。コマンドのパラメ...
目次1. Node.jsとVue 2. ローカル開発環境でフロントエンドのVueプロジェクトを実行す...
XMeter API は、以下のサービスを含む、JMeter に基づくワンストップのオンライン イン...
一部の Web ページは大きく見えなくても開くのに非常に時間がかかる場合があります。一方、他の We...
目次父から息子へ:息子から父へ: Vuex を使用せずにコンポーネント間で値を渡す方法は、親から子、...
<div align="center"> <table sty...
この記事では、VMware仮想マシンのNAT構成プロセスを詳しく説明します。具体的な内容は次のとおり...
1. 構造部品1. フォームには、入力コントロール、標準フォーム フィールド、ラベル、ドロップダウン...
1. ネットワーク接続方法がブリッジされていることを確認する物理ネットワーク接続ステータスのコピーを...
まずSQLを書く SELECT DISTINCT from_id タラから cod.from_id ...
公式ドキュメント:したがって、mysql は次のように起動する必要があります。 docker run...
ユーザーとグループの管理1. ユーザーとグループの基本概念ユーザーとグループ:システム上のすべてのプ...
この記事では、USBバーコードスキャナデータを取得するjsの具体的なプロセスを参考までに紹介します。...
例示するDML(データ操作言語)とは、データベースの追加、削除、変更を行うための操作命令のことです。...
序文ご存知のとおり、ブラウザの相同性戦略とクロスドメイン方式も、フロントエンド面接で頻繁に遭遇する問...