一、大文件分片上传
原型
大文件如果直接上传的话由于nginx的限制会导致响应500报错,或者响应时间过长导致响应超时
并且大文件上传有如下缺点
- 上传时间长: 对于大文件,直接上传可能需要较长时间,特别是在网络速度较慢或不稳定的情况下。这可能会导致用户等待时间过长,影响用户体验。
- 内存占用: 直接上传大文件可能会占用大量内存,特别是在客户端进行文件读取和上传的情况下。这可能会导致客户端程序消耗大量内存资源,甚至在上传大型文件时出现内存溢出的情况。
- 网络传输中断: 在网络不稳定的环境下,上传大文件可能会遇到网络传输中断的问题。这会导致上传过程中断,需要重新开始上传,从而增加了额外的时间和网络流量。
- 服务器端资源消耗: 直接上传大文件可能会对服务器端资源造成较大负担,特别是在高并发情况下。大文件上传会占用服务器的网络带宽和处理能力,可能会影响服务器的稳定性和性能。
解决方案
根据这种情况我们往往采用分片上传 来进行解决
分片上传:将一个大文件在前端进行分片,并将每个小分片进行编码异步上传至后端服务器中。后端收到这些分片后解码并按顺序进行重组
优点:
- 提高上传速度。在网络带宽允许的情况下,可以同时上传多个文件片段,从而加快了整个文件的上传速度
- 方便前端做上传进度条处理,提高用户体验
- 断点续传,分片上传过程中若出现了网络问题或其他问题导致上传中断,只需要上传中断的部分而不需要上传整个文件,提高了上传的可靠性
由于公司采用的是阿里云的oss对象存储,oss中已经我们提供了分片上传这样一项服务,因此我们只需要在前端简单的配置便可以实现分片上传并返回进度条,前端甚至不需要手动分片
typescript
import OSS from "ali-oss";
interface OSSConfig {
accessKeyId: string;
accessKeySecret: string;
region: string;
bucket: string;
}
export class OssService {
private client: any;
constructor(ossConfig: OSSConfig) {
this.client = new OSS({
accessKeyId: ossConfig.accessKeyId,
accessKeySecret: ossConfig.accessKeySecret,
region: ossConfig.region,
bucket: ossConfig.bucket,
});
}
// 开始分片上传。
async multipartUpload(
ossPath: string,
file: File,
progressCallback: (progress: number) => void
) {
return new Promise(async (resolve, reject) => {
try {
const result = await this.client.multipartUpload(ossPath, file, {
// 回调返回进度条
progress: async (p: number) => {
progressCallback(p);
},
});
resolve(result);
} catch (e: any) {
if (e.code === "ConnectionTimeoutError") {
reject("TimeoutError");
}
reject(e);
}
});
}
}
typescript
import { OssService } from "@/util/OSS/index";
// OSS 配置
const ossConfig = {
accessKeyId: "accessKeyId",
accessKeySecret: "accessKeySecret",
endpoint: "endpoint",
bucket: "bucket",
region: "region",
};
const oss = new OssService(ossConfig);
// 以naiveui UI框架 的自定义上传 举例
async function customRequest({
file,
data,
onFinish,
onError,
}: UploadCustomRequestOptions) {
try {
// 工具类引用
const res = await oss.multipartUpload(
`/file/${file.file?.name}`,
file.file as File,
(e) => {
// 返回进度条,做进度条处理
console.log(e);
}
);
onFinish();
message.success("上传成功");
} catch (e) {
onError();
message.error("上传失败");
}
}
大致效果
缺点
目前的OSS配置项是保存在前端,因此安全性不高。即使通过后端加密后返回最终也需要在前端进行解密,对于懂点逆向的人来说,需要破解难度不高
因此,需要将OSS的配置项存储到后端,前端通过接口请求后端返回OSS的一个临时授权凭证(STS),根据这个临时授权凭证便可以进行前端直传到OSS服务器
实现方法
- 通过官网进行配置
使用STS临时访问凭证访问OSS_对象存储(OSS)-阿里云帮助中心
- 编写生成sts临时凭证接口
typescript
import * as OSS from 'ali-oss';
import * as STS from '@alicloud/sts-sdk';
import ossConfig, { STSConfig } from 'src/config/oss';
/**
* 获取签名
*/
async getSignature() {
const stsEndpoint = STSConfig.stsEndpoint;
const accessKeyId = STSConfig.accessKeyId;
const accessKeySecret = STSConfig.accessKeySecret;
const roleArn = STSConfig.roleArn;
// 生成签名,策略等信息
const sts = new STS({
endpoint: stsEndpoint,
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret,
});
// 响应给客户端的签名和策略等信息
return {
...(await sts.assumeRole(roleArn, 'RamOssTest')),
};
}
- 前端拿到临时凭证后再进行直传
typescript
import OSS from "ali-oss";
import { oss_Config } from "@/config";
import { getSignature } from "@/api/common/upload";
export class OssService {
private client: any;
// 定义中断点。
protected abortCheckpoint: any;
constructor() {
this.initOss();
}
// 新增凭证请求
async initOss() {
const { Credentials } = (await getSignature()).data;
this.client = new OSS({
accessKeyId: Credentials.AccessKeyId,
accessKeySecret: Credentials.AccessKeySecret,
stsToken: Credentials.SecurityToken,
region: oss_Config.region,
bucket: oss_Config.bucket,
});
}
// 开始分片上传。
async multipartUpload(
ossPath: string,
file: File,
progressCallback: (progress: number) => void
) {
return new Promise(async (resolve, reject) => {
try {
const result = await this.client.multipartUpload(ossPath, file, {
checkpoint: this.abortCheckpoint,
progress: async (p: number, cpt: any) => {
// 为中断点赋值。
this.abortCheckpoint = cpt;
progressCallback(p);
},
});
resolve(result);
} catch (e: any) {
// 捕获超时异常。
if (e.code === "ConnectionTimeoutError") {
reject("TimeoutError");
}
reject(e);
}
});
}
// 暂停上传。
pause() {
this.client.cancel();
}
}
typescript
// 自定义文件上传 & 续传
async function customRequest({
file,
onFinish,
onError,
}: UploadCustomRequestOptions) {
try {
// 文件重命名
const fileExtension = getFileExtension(file.file?.name as string);
// const newFileName = (await calculateFileHash(file.file as File)) + fileExtension;
const newFileName = uuid() + fileExtension;
// 设置上传进度
processing.value = true;
const res: any = await oss.multipartUpload(
`/file/${newFileName}`,
file.file as File,
(e) => {
percentage.value = Math.floor(e * 100);
}
);
processing.value = false;
file.url = `http://${oss_Config.bucket}.${oss_Config.endpoint}${res.name}`;
fileList.value.push(file);
onFinish();
emit("update:value", fileList.value);
message.success("上传成功");
} catch (e) {
onError();
processing.value = false;
message.error("上传失败");
}
}
原理
二、断点续传
断点续传是一种网络传输技术,用于在文件传输过程中实现中断后的续传操作。其核心思想是在文件传输过程中保存传输的断点信息,以便在传输中断或失败后能够在之后的某个时间点恢复传输,而不需要重新开始传输整个文件
结合阿里云OSS我们也很容易可以实现断点续传
只需要在OSS工具类中新加两个地方即可实现!
typescript
async multipartUpload(
ossPath: string,
file: File,
progressCallback: (progress: number) => void
) {
return new Promise(async (resolve, reject) => {
try {
const result = await this.client.multipartUpload(ossPath, file, {
checkpoint: this.abortCheckpoint, // 新加
progress: async (p: number, cpt: any) => {
// 为中断点赋值。
this.abortCheckpoint = cpt; // 新加
progressCallback(p);
},
});
resolve(result);
} catch (e: any) {
// 捕获超时异常。
if (e.code === "ConnectionTimeoutError") {
reject("TimeoutError");
}
reject(e);
}
});
}