需求:由于上传文件过大需要对文件进行切割 然后分段上传。
优点:此处使用临时url操作,前端完全不需要使用obs提供的jdk, obs账号密码由后端保存,大大提高了安全性。
流程:后台提供两个接口,分别是:
1.从后台获取临时操作url 用于初始化 并获取uploadId
2.后台获取临时分段操作url 用于分段上传
/**
* 临时url上传 分段
*/
export async function uploadMulFileToObsTemp(
obsType: string,
file: File,
filePath: string,
callback: Function
) {
const uploadId = await getTemMulObsSignedUrlUploadId(obsType, filePath);
// 第一步 分段
const chunks = await sliceFile(file);
const promiseList: any = [];
if (chunks.length) {
chunks.forEach((f, i) => {
const itemApi = mulPartUpload(filePath, f, uploadId, i + 1);
promiseList.push(itemApi);
});
}
// 等待分段上传完成 发送合并合并段
Promise.all(promiseList).then(async (result) => {
if (result.length) {
// 第二 分段上完成后 合并处理
const obsSigned = await getTmpObsMergeSignedUrl(
filePath,
'POST',
uploadId,
'application/xml'
);
// 后台要求上传格式xml
let content = `<CompleteMultipartUpload>`;
result.forEach((f) => {
content += `<Part>`;
content += `<PartNumber>${f.partNum}</PartNumber>`;
content += `<ETag>${f.etag}</ETag>`;
content += `</Part>`;
});
content += `</CompleteMultipartUpload>`;
mergePartUpload(obsSigned, content).then(async (res: any) => {
if (res.status === 200) {
const url = window.URL.createObjectURL(file);
callback(url);
} else {
callback(false);
}
});
}
});
}
// 分段 上传
export async function mulPartUpload(
filePath: string,
file: File | Blob,
uploadId: string,
partNumber: string | number,
t = 2
) {
const obsSigned: any = await getTmpObsMulSignedUrl(
filePath,
'PUT',
partNumber,
uploadId
);
return new Promise((resolve, reject) => {
if (obsSigned.actualSignedRequestHeaders.Host) {
delete obsSigned.actualSignedRequestHeaders.Host;
}
const reopt: any = {
method: 'PUT',
url: obsSigned.signedUrl,
withCredentials: false,
headers: obsSigned.actualSignedRequestHeaders || {},
headersType: 'application/x-www-form-urlencoded',
maxRedirects: 0,
responseType: 'text',
data: file,
validateStatus: function (status: number) {
return status >= 200;
},
};
axios
.request(reopt)
.then(function (response) {
console.log(response);
if (response.status < 300) {
const part = {
partNum: Number(partNumber),
etag: JSON.parse(response.headers.etag),
};
resolve(part);
} else {
reject(response);
}
})
.catch(function (err) {
console.log('Upload Object using temporary signature failed!');
console.log(err);
t--;
if (t > 0) {
// 失败后 重传2次
mulPartUpload(filePath, file, uploadId, partNumber, t);
} else {
// 重传2次后还是失败 就取消上传任务
cancelPartUpload(filePath, uploadId);
}
reject(err);
});
});
}
// 取消分段上传任务
export async function cancelPartUpload(filePath: string, uploadId: string) {
const obsSigned: any = await getTmpObsMergeSignedUrl(
filePath,
'DELETE',
uploadId
);
return new Promise((resolve, reject) => {
const reopt: any = {
method: 'DELETE',
url: obsSigned.signedUrl,
withCredentials: false,
headers: obsSigned.actualSignedRequestHeaders || {},
maxRedirects: 0,
data: null,
responseType: 'text',
validateStatus: function (status: number) {
return status >= 200;
},
};
axios
.request(reopt)
.then(function (response) {
if (response.status < 300) {
resolve(response);
} else {
reject(response);
}
})
.catch(function (err) {
console.log(err);
reject(err);
});
});
}
// 发送合并分段请求
export async function mergePartUpload(obsSigned: any, content: string) {
return new Promise((resolve, reject) => {
const reopt: any = {
method: 'POST',
url: obsSigned.signedUrl,
withCredentials: false,
headers: obsSigned.actualSignedRequestHeaders || {},
maxRedirects: 0,
responseType: 'text',
data: content,
validateStatus: function (status: number) {
return status >= 200;
},
};
axios
.request(reopt)
.then(function (response) {
if (response.status < 300) {
resolve(response);
} else {
reject(response);
}
})
.catch(function (err) {
console.log(err);
reject(err);
});
});
}
// 分段大小 50M
export const sliceFile = (file: File | Blob, chunkSize = FILESIZE) => {
// 50Mb
const chunkCount = Math.ceil(file.size / chunkSize);
return new Array(chunkCount).fill(null).map((_, i) => {
return file.slice(i * chunkSize, (i + 1) * chunkSize);
});
};
/**
* 获取临时URL 分段上传
* @param obsType
* @param objectKey
* @param operationType
*/
export async function getTmpObsMulSignedUrl(
objectKey: string,
operationType: string,
partNumber?: string | number,
uploadId?: string
) {
const config = {};
await postTemporarySignature({
data: {
path: objectKey,
method: operationType,
queryParams: { partNumber, uploadId },
},
}).then(async (res: any) => {
if (res.data.success) {
await Object.assign(config, res.data.data);
}
});
return config;
}
/**
* 获取 合并段 url 分段
*/
export async function getTmpObsMergeSignedUrl(
objectKey: string,
operationType: string,
uploadId: string,
contentType?: string
) {
const config = {};
await postTemporarySignature({
data: {
path: objectKey,
method: operationType,
contentType: contentType,
queryParams: { uploadId },
},
}).then(async (res: any) => {
if (res.data.success) {
await Object.assign(config, res.data.data);
}
});
return config;
}
// 获取临时 分段签名 url 后台接口二
export const postTemporarySignature = ({ data }: any) => {
// 权限判断
return post({
url: 'open/admin/obs/postTemporarySignature',
method: 'post',
data,
headers: {
headersType: 'application/x-www-form-urlencoded',
},
});
};
// 获取 obs 分段 uploadid
export async function getTemMulObsSignedUrlUploadId(
obsType: string,
objectKey: string
) {
let uploadid = '';
await initMultipartUpload({
data: { path: objectKey },
}).then(async (res: any) => {
if (res.data.success) {
uploadid = res.data.data;
}
});
return uploadid;
}
/**
* 获取POST临时签名 分段 后台接口一
* @param data
*/
export const initMultipartUpload = ({ data }: any) => {
// 权限判断
return post({
url: 'open/admin/obs/initMultipartUpload',
data,
headers: {
headersType: 'application/x-www-form-urlencoded',
},
});
};