国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務器之家 - 編程語言 - JavaScript - React - React+Koa實現文件上傳的示例

React+Koa實現文件上傳的示例

2022-02-23 16:07孤雨隨風zz React

這篇文章主要介紹了React+Koa實現文件上傳的示例,幫助大家更好的理解和學習使用React,感興趣的朋友可以了解下

背景

最近在寫畢設的時候,涉及到了一些文件上傳的功能,其中包括了普通文件上傳,大文件上傳,斷點續傳等等

服務端依賴

  • koa(node.js框架)
  • koa-router(Koa路由)
  • koa-body(Koa body 解析中間件,可以用于解析post請求內容)
  • koa-static-cache(Koa 靜態資源中間件,用于處理靜態資源請求)
  • koa-bodyparser(解析 request.body 的內容)

后端配置跨域

?
1
2
3
4
5
6
7
8
9
10
11
12
13
app.use(async (ctx, next) => {
 ctx.set('Access-Control-Allow-Origin', '*');
 ctx.set(
  'Access-Control-Allow-Headers',
  'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild',
 );
 ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
 if (ctx.method == 'OPTIONS') {
  ctx.body = 200;
 } else {
  await next();
 }
});

后端配置靜態資源訪問 使用 koa-static-cache

?
1
2
3
4
5
6
7
8
// 靜態資源處理
app.use(
 KoaStaticCache('./pulbic', {
  prefix: '/public',
  dynamic: true,
  gzip: true,
 }),
);

后端配置requst body parse 使用 koa-bodyparser

?
1
2
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());

前端依賴

  • React
  • Antd
  • axios

正常文件上傳

后端

后端只需要使用 koa-body 配置好options,作為中間件,傳入router.post('url',middleware,callback)即可

后端代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 上傳配置
const uploadOptions = {
// 支持文件格式
 multipart: true,
 formidable: {
  // 上傳目錄 這邊直接上傳到public文件夾,方便訪問 文件夾后面要記得加/
  uploadDir: path.join(__dirname, '../../pulbic/'),
  // 保留文件擴展名
  keepExtensions: true,
 },
};
router.post('/upload', new KoaBody(uploadOptions), (ctx, next) => {
 // 獲取上傳的文件
 const file = ctx.request.files.file;
 const fileName = file.path.split('/')[file.path.split('/').length-1];
 ctx.body = {
   code:0,
   data:{
    url:`public/${fileName}`
   },
   message:'success'
 
 }
});

前端

  我這里使用的是formData傳遞的方式,前端通過<input type='file'/> 來訪問文件選擇器,通過onChange事件 e.target.files[0] 即可獲取選擇的文件,而后創建FormData 對象將獲取的文件formData.append('file',targetFile)即可

前端代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  const Upload = () => {
  const [url, setUrl] = useState<string>('')
  const handleClickUpload = () => {
    const fileLoader = document.querySelector('#btnFile') as HTMLInputElement;
    if (isNil(fileLoader)) {
      return;
    }
    fileLoader.click();
  }
  const handleUpload = async (e: any) => {
    //獲取上傳文件
    const file = e.target.files[0];
    const formData = new FormData()
    formData.append('file', file);
    // 上傳文件
    const { data } = await uploadSmallFile(formData);
    console.log(data.url);
    setUrl(`${baseURL}${data.url}`);
  }
  return (
    <div>
      <input type="file" id="btnFile" onChange={handleUpload} style={{ display: 'none' }} />
      <Button onClick={handleClickUpload}>上傳小文件</Button>
      <img src={url} />
    </div>
  )
}

其他可選方法

  • input+form 設置form的aciton為后端頁面,enctype="multipart/form-data",type=‘post'
  • 使用fileReader讀取文件數據進行上傳 兼容性不是特別好

大文件上傳

  文件上傳的時候,可能會因為文件過大,導致請求超時,這時候就可以采取分片的方式,簡單來說就是將文件拆分為一個個小塊,傳給服務器,這些小塊標識了自己屬于哪一個文件的哪一個位置,在所有小塊傳遞完畢后,后端執行merge 將這些文件合并了完整文件,完成整個傳輸過程

前端

  • 獲取文件和前面一樣,不再贅述
  • 設置默認分片大小,文件切片,每一片名字為 filename.index.ext,遞歸請求直到整個文件發送完請求合并
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const handleUploadLarge = async (e: any) => {
   //獲取上傳文件
   const file = e.target.files[0];
   // 對于文件分片
   await uploadEveryChunk(file, 0);
 }
 const uploadEveryChunk = (
   file: File,
   index: number,
 ) => {
   console.log(index);
   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 = new File([blob], blobName);
   const formData = new FormData();
   formData.append('file', blobFile);
   uploadLargeFile(formData).then((res) => {
     // 遞歸分片上傳
     uploadEveryChunk(file, ++index);
   });
 };

后端

后端需要提供兩個接口

上傳

將上傳的每一個分塊存儲到對應name 的文件夾,便于之后合并

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const uploadStencilPreviewOptions = {
multipart: true,
formidable: {
 uploadDir: path.resolve(__dirname, '../../temp/'), // 文件存放地址
 keepExtensions: true,
 maxFieldsSize: 2 * 1024 * 1024,
},
};
 
router.post('/upload_chunk', new KoaBody(uploadStencilPreviewOptions), async (ctx) => {
try {
 const file = 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 = {
  code: 0,
  message: '文件上傳成功',
 };
} catch (e) {
 ctx.body = {
  code: -1,
  message: `文件上傳失敗:${e.toString()}`,
 };
}
});

合并

  根據前端傳來合并請求,攜帶的name去臨時緩存大文件分塊的文件夾找到屬于該name的文件夾,根據index順序讀取chunks后,合并文件fse.appendFileSync(path,data) (按順序追加寫即合并),然后刪除臨時存儲的文件夾釋放內存空間

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
router.post('/merge_chunk', async (ctx) => {
 try {
  const { fileName } = ctx.request.body;
  const fname = fileName.split('.')[0];
  const TEMP_DIR = path.resolve(__dirname, '../../temp');
  const static_preview_url = '/public/previews';
  const STORAGE_DIR = path.resolve(__dirname, `../..${static_preview_url}`);
  const chunkDir = path.join(TEMP_DIR, fname);
  const chunks = await fse.readdir(chunkDir);
  chunks
   .sort((a, b) => a - b)
   .map((chunkPath) => {
    // 合并文件
    fse.appendFileSync(
     path.join(STORAGE_DIR, fileName),
     fse.readFileSync(`${chunkDir}/${chunkPath}`),
    );
   });
  // 刪除臨時文件夾
  fse.removeSync(chunkDir);
  // 圖片訪問的url
  const url = `http://${ctx.request.header.host}${static_preview_url}/${fileName}`;
  ctx.body = {
   code: 0,
   data: { url },
   message: 'success',
  };
 } catch (e) {
  ctx.body = { code: -1, message: `合并失敗:${e.toString()}` };
 }
});

斷點續傳

  大文件在傳輸過程中,如果刷新頁面或者臨時的失敗導致傳輸失敗,又需要從頭傳輸對于用戶的體驗是很不好的。因此就需要在傳輸失敗的位置,做好標記,下一次直接在這里進行傳輸即可,我采取的是在localStorage讀寫的方式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const handleUploadLarge = async (e: any) => {
  //獲取上傳文件
  const file = e.target.files[0];
  const record = JSON.parse(localStorage.getItem('uploadRecord') as any);
  if (!isNil(record)) {
    // 這里為了便于展示,先不考慮碰撞問題, 判斷文件是否是同一個可以使用hash文件的方式
    // 對于大文件可以采用hash(一塊文件+文件size)的方式來判斷兩文件是否相同
    if(record.name === file.name){
      return await uploadEveryChunk(file, record.index);
    }
  }
  // 對于文件分片
  await uploadEveryChunk(file, 0);
}
const uploadEveryChunk = (
  file: File,
  index: number,
) => {
  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 = new File([blob], blobName);
  const formData = new FormData();
  formData.append('file', blobFile);
  uploadLargeFile(formData).then((res) => {
    // 傳輸成功每一塊的返回后記錄位置
    localStorage.setItem('uploadRecord',JSON.stringify({
      name:file.name,
      index:index+1
    }))
    // 遞歸分片上傳
    uploadEveryChunk(file, ++index);
  });
};

文件相同判斷

  通過計算文件MD5,hash等方式均可,當文件過大時,進行hash可能會花費較大的時間。 可取文件的一塊chunk與文件的大小進行hash,進行局部的采樣比對, 這里展示 通過 crypto-js庫進行計算md5,FileReader讀取文件的代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 計算md5 看是否已經存在
   const sign = tempFile.slice(0, 512);
   const signFile = new File(
    [sign, (tempFile.size as unknown) as BlobPart],
    '',
   );
   const reader = new FileReader();
   reader.onload = function (event) {
    const binary = event?.target?.result;
    const md5 = binary && CryptoJs.MD5(binary as string).toString();
    const record = localStorage.getItem('upLoadMD5');
    if (isNil(md5)) {
     const file = blobToFile(blob, `${getRandomFileName()}.png`);
     return uploadPreview(file, 0, md5);
    }
    const file = blobToFile(blob, `${md5}.png`);
    if (isNil(record)) {
     // 直接從頭傳 記錄這個md5
     return uploadPreview(file, 0, md5);
    }
    const recordObj = JSON.parse(record);
    if (recordObj.md5 == md5) {
     // 從記錄位置開始傳
     //斷點續傳
     return uploadPreview(file, recordObj.index, md5);
    }
    return uploadPreview(file, 0, md5);
   };
   reader.readAsBinaryString(signFile);

總結

  之前一直對于上傳文件沒有過太多的了解,通過畢設的這個功能,對于上傳文件的前后端代碼有了初步的認識,可能這些方法也只是其中的選項并不包括所有,希望未來的學習中能夠不斷的完善。
  第一次在掘金寫博客,在參加實習以后,發現自己的知識體量的不足,希望能夠通過堅持寫博客的方式,來梳理自己的知識體系,記錄自己的學習歷程,也希望各位大神在發現問題時不吝賜教,thx

以上就是React+Koa實現文件上傳的示例的詳細內容,更多關于React+Koa實現文件上傳的資料請關注服務器之家其它相關文章!

原文鏈接:https://juejin.cn/post/6947613143141089287

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 亚洲最新无码中文字幕久久 | 中文在线日韩 | 日本中文字幕一区 | 精品一区二区三区视频 | 色偷偷888欧美精品久久久 | 国产视频1区 | 在线不卡视频 | 色中色综合 | 一区二区三区回区在观看免费视频 | 日韩精品在线视频 | 亚洲日韩中文字幕一区 | 精品天堂 | 精品9999 | 一区中文 | 亚洲国产高清在线 | 亚洲电影一区 | 国产精品久久久久久久久久久久午夜片 | 欧美另类专区 | 国产福利在线视频 | 91精品国产综合久久婷婷香蕉 | 一区视频在线 | 欧美精品久久 | 欧美一区在线视频 | 午夜国产视频 | 中国a毛片 | 久久99精品久久久久久 | 日韩看片 | 亚洲天堂网站 | 北条麻妃99| 国产精品日产欧美久久久久 | 一区二区三区 | 欧洲亚洲精品久久久久 | 欧美日韩一区二区三 | 亚洲精品久久久蜜桃 | 91亚洲精品一区 | 久久综合久久88 | 亚洲色综合 | www.99| 成人免费网站 | 亚洲国产精品久久久久秋霞蜜臀 | 日韩av怡红院 |