React+Koa によるファイルアップロードの実装例

React+Koa によるファイルアップロードの実装例

背景

最近、最終プロジェクトを書いていたときに、通常のファイルのアップロード、大きなファイルのアップロード、ブレークポイントの再開など、いくつかのファイルアップロード機能に携わりました。

サーバーの依存関係

  • koa(node.jsフレームワーク)
  • koa-router (Koa ルーティング)
  • koa-body (Koa ボディ解析ミドルウェア。POST リクエスト コンテンツを解析するために使用できます)
  • koa-static-cache (Koa 静的リソース ミドルウェア。静的リソース要求の処理に使用)
  • koa-bodyparser (request.body の内容を解析する)

バックエンド構成クロスドメイン

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>
   )
 }

その他のオプション

  • input+form フォームのアクションをバックエンドページに設定します。enctype="multipart/form-data", type='post'
  • アップロード用のファイルデータを読み取るためにfileReaderを使用することは互換性があまりありません

大きなファイルのアップロード

ファイルをアップロードする際、ファイルが大きすぎるためにリクエストがタイムアウトになる場合があります。この場合、断片化方式を使用できます。簡単に言うと、ファイルは小さな断片に分割され、サーバーに送信されます。これらの小さな断片は、どのファイルに属し、どの位置にあるかを識別します。すべての小さな断片が送信された後、バックエンドはマージを実行してこれらのファイルを完全なファイルにマージし、送信プロセス全体を完了します。

フロントエンド

  • ファイルの取得方法は前と同じなので、詳細は省略します。
  • デフォルトのフラグメント サイズ、ファイル スライスを設定します。各スライス名は filename.index.ext で、ファイル全体が送信され、要求がマージされるまで再帰要求が行われます。
  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の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Alibaba Cloud OSSにファイルをアップロードするReact実装例
  • React quill の画像アップロードは、サーバーにアップロードするためにデフォルトの変換から base64 に変更されました。
  • React Nativeはfetchを使って写真をアップロードします
  • ReactでQiniuに写真をアップロードするためのサンプルコード
  • 画像アップロード用の React+react-dropzone+node.js サンプルコード
  • React Nativeはネットワーク上の写真をサーバーにアップロードする例を実装します
  • 画像アップロード機能を実装するためのReactNativeサンプルコード
  • React+ajax+javaで写真のアップロードとプレビューの機能を実現
  • ノードベースの React 画像アップロード コンポーネント実装のサンプル コード
  • ファイルのアップロードの進行状況を示す React の例

<<:  上位Nを見つけるためのMySQLグループソートの詳細な説明

>>:  CentOS7 で Jenkins+Maven+Git 継続的インテグレーション環境を構築する方法

推薦する

Dockerのインストール方法とDockerの4つのネットワークモードの詳細説明

1. Dockerをインストールするyum -y install docker-ioインストールが完...

Nginx リクエスト制限の設定方法

Nginx は、多くの優れた機能を備えた強力で高性能な Web およびリバース プロキシ サーバーで...

WeChatアプレットがフォーム検証を実装

WeChatアプレットフォームの検証、参考までに具体的な内容は次のとおりです。プラグインWxVali...

NGINXがウェブサイトのPV、UV、独立IPをカウントする方法の詳細な説明

Nginx: PV、UV、独立IPウェブサイトを作成する人なら誰でも、ウェブサイトのPV、UV、その...

2列の水平タイムラインを実装するためのVueサンプルコード

目次1.コンポーネントtimelineH.vueを実装する2. コンポーネントの呼び出しこの記事では...

HTMLポップアップ透明レイヤーインスタンスのサイズを設定でき、比例することができます

コードをコピーコードは次のとおりです。 <!DOCTYPE html PUBLIC "...

IE6 で PNG-24 形式の画像を正常に表示させる 2 つの方法

方法1: </html>の後に次のコードを追加してください。コードをコピーコードは次のと...

CSSは、閉じることができるマスクレイヤーを備えたポップアップウィンドウ効果を実装します。

実際の開発ではポップアップウィンドウがよく使われます。CSS3を勉強していたときに、閉じることができ...

MySQLのデッドロックとログに関する詳細な説明

最近、MySQL オンラインでいくつかのデータ異常が発生しましたが、すべて早朝に発生しました。ビジネ...

MySQL 5.7.17無料インストール版のインストールと設定

MYSQLバージョン:MySQL Community Server 5.7.17、インストール不要版...

フロントエンドタスク構築のための強力なツールであるGulp.jsの使い方を詳しく説明します

目次概要Gulp.jsをインストールするGulp.jsを使用してプロジェクトを作成するgulpfil...

crontab の実行結果を電子メールでユーザーに通知する方法

症状Centos7 ホストに crontab タスクを設定しましたが、時間が来るとメールを実行して「...

tinyMCEの使い方と体験の詳細な説明

tinyMCE の使用方法の詳細な説明初期化TinyMCE を初期化するときは、ページの HEAD ...

JavaScript を使用して簡単なアルゴリズムを実行する方法

目次質問1件2つの方法3 実験結果と考察質問1件ご存知のとおり、 Pycharm 、 IDLE 、 ...

UTF-8 および GB2312 ウェブエンコーディング

最近、多くの学生から Web ページのエンコーディングについて質問を受けています。gb2312 と ...