【OSS】前端直传 oss
2024年12月3日 / 1月1日
在上传文件时,为了降低后端服务器压力、提高上传速度等。可由前端直接上传至oss。
当使用 npm 的方式使用 oss sdk 时,build 可能会异常。可以采用 cdn 的方式使用。
Github:ali-oss
实现逻辑
如果是批量上传,则需控制上传队列,单个依次执行上传。
单个文件上传简单逻辑如下:
- 获取 oss config
- 初始化 oss sdk
- 执行分片上传
- 这里不论文件大小都采用分片上传的方式,目的是为了简化,统一逻辑。
- 可针对不同文件指定不同的分片数量(例如:文件小于1MB则分1片)
- 上传成功后上报 oss path
异常处理
- 初始化 oss sdk 需要有 token,由于token直接输出至前端,出于安全考虑 token 必须设置有效期,例如 5 分钟。因此客户端需 在 token 过期之前,提前刷新 token。防止 token 过期导致上传失败。
- 为什么 5 分钟有效期还不够用?客户端网络不可控
- 在进行分片上传时,如果上传失败,可设置自动重试。重试次数可根据业务自定。
- 断点续传?可根据业务自行实现。
能力扩展
- 上传速度
- 上传开始前 记录当前时间为开始时间
- 通过上传的 progress 回调。可以获得上传进度,进而可以获取已上传的文件大小。如果以此刻为结束时间的话,那么就可大约计算出上传速度
- 由于 progress 回调频率不够高,如果需要更高频率显示,可自行实现加速逻辑。比如在一次 progress 回调中计算多次结果。
- 单文件上传进度
- 通过 progress 回调根据已上传文件大小,计算得出当前上传进度。
- 整体上传任务进度
- 如果需要显示批量的进度,则可在每个文件上传后或者每个文件的 progress 回调(更精细的进度显示)中计算。
- 失败重试
- 在上传失败时可设置自动重试逻辑。超过最大重试次数则最终上传失败。
- 针对不同文件设置不同分片
- 合理的分片能够更好的利用带宽,增加上传速度。
- 同域名下的浏览器并发限制为6个(不同浏览器,不同版本可能限制存在差异,主要目的是需要合理的限制并发数)
- 根据文件大小,以及并发数量,计算出分片数量以及每片大小。
- 使用加速域名
- 如果客户端由于地域原因导致上传慢或者失败。可在上传失败时启用加速域名进行加速上传(修改oss config)。
- 文件检查
- 在上传之前可简单进行文件类型,大小等检查。如果不符合业务则直接给出提示即可。
代码实现
- refreshSTSTokenInterval 可根据业务情况进行修改。
- calcPart 可根据业务情况自行实现。
1let client; 2 3const uploadConfig = { 4 fileList: [], 5 params: new Map(), 6 data: {}, 7}; 8 9const multipartUpload = (file) => { 10 return new Promise(async (resolve, reject) => { 11 const res = await getOssConfig(); 12 13 const startTime = Date.now(); 14 15 client = new OSS({ 16 ...getStsConfig(uploadConfig.data.sts_config), 17 refreshSTSToken: async () => { 18 const info = await getOssConfig(); 19 return { 20 accessKeyId: info.accessKeyId, 21 accessKeySecret: info.accessKeySecret, 22 stsToken: info.stsToken, 23 }; 24 }, 25 refreshSTSTokenInterval: interval * 60 * 1000, 26 }); 27 28 client 29 .multipartUpload(dir, file.file, { 30 progress: (p) => { 31 const costTime = (Date.now() - startTime) / 1000; 32 const uploadedSize = ((file.file?.size || 0) * p) / 1024 / 1024; 33 const fileSpeed = (uploadedSize / costTime).toFixed(2); 34 35 console.table({ 36 '0-name': file.name, 37 '1-fileSize': (file.file?.size || 0) / 1024 / 1024 + 'M', 38 '2-progress': p * 100 + '%', 39 '3-costTime': costTime + 's', 40 '4-uploadedSize': uploadedSize + 'M', 41 '5-fileSpeed': fileSpeed + 'M/s', 42 '6-partSize': calcPart(file.file?.size || 0).partSize / 1024 / 1024 + 'M', 43 }); 44 45 uploadConfig.speedMap[file.name] = fileSpeed; 46 }, 47 ...calcPart(file.file?.size || 0), 48 }) 49 .then((res) => { 50 uploadConfig.retryMap[file.name] = 0; 51 delete uploadConfig.speedMap[file.name]; 52 resolve(res); 53 }) 54 .catch(async (err) => { 55 if (err?.message?.includes('expired') && uploadConfig.retryMap[file.name] < 3) { 56 try { 57 uploadConfig.retryMap[file.name] += 1; 58 uploadConfig.data = await getOssConfig(); 59 resolve(await multipartUpload(file)); 60 } catch (e) { 61 reject(e); 62 } 63 } else { 64 uploadConfig.retryMap[file.name] = 0; 65 reject(err?.message || err?.name); 66 } 67 }); 68 }); 69}; 70 71function calcPart(size) { 72 const mbSize = size / 1024 / 1024; 73 74 const config = { 75 parallel: 6, 76 partSize: size, 77 }; 78 79 if (mbSize > 12 && mbSize <= 50) { 80 config.partSize = Math.ceil(mbSize / 6) * 1024 * 1024; 81 } 82 83 if (mbSize > 50 && mbSize <= 100) { 84 config.partSize = Math.ceil(mbSize / 12) * 1024 * 1024; 85 } 86 87 if (mbSize > 100) { 88 config.partSize = Math.ceil(mbSize / 18) * 1024 * 1024; 89 } 90 91 return config; 92}
cd ..