243 lines
6.2 KiB
TypeScript
243 lines
6.2 KiB
TypeScript
import {
|
||
RESULT_CODE_ERROR,
|
||
RESULT_CODE_SUCCESS,
|
||
RESULT_MSG_ERROR,
|
||
} from '@/constants/result-constants';
|
||
import { request, language } from '@/plugins/http-fetch';
|
||
import { encode } from 'js-base64';
|
||
|
||
/**
|
||
* 下载文件
|
||
* @param filePath 文件路径带/,如:/upload/default/2023/06/xx.png
|
||
* @param range 断点续传标识,填入字符串 `bytes=${startByte}-${endByte}`
|
||
* @returns object
|
||
*/
|
||
export async function downloadFile(filePath: string, range?: string) {
|
||
return request({
|
||
url: `/file/download/${encode(filePath)}`,
|
||
method: 'get',
|
||
headers: range ? { range } : {},
|
||
responseType: 'blob',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 下载文件切片
|
||
* @param filePath 文件路径带/,如:/upload/default/2023/06/xx.png
|
||
* @param chunkSize 数据块大小MB,默认1MB
|
||
* @returns bolb
|
||
*/
|
||
export async function downloadFileChunk(
|
||
filePath: string,
|
||
chunkSize: number = 1
|
||
): Promise<Blob> {
|
||
chunkSize = chunkSize * 1024 * 1024;
|
||
let start = 0; // 文件块的起始字节
|
||
let end = chunkSize - 1; // 文件块的结束字节
|
||
let totalSize = 0; // 文件总大小
|
||
let downloadedSize = 0; // 已下载的文件大小
|
||
let filePart: Blob[] = []; // 文件数据块内容
|
||
|
||
// 发送带有 Range 请求头的 HTTP 请求
|
||
async function sendRequest() {
|
||
const range = `bytes=${start}-${end}`;
|
||
const res = await downloadFile(filePath, range);
|
||
if (res.code === RESULT_CODE_SUCCESS && res.status === 206) {
|
||
// 总大小
|
||
const contentRange = res.headers.get('content-range') || '0/0';
|
||
totalSize = parseInt(contentRange.split('/')[1]);
|
||
// 已下载大小
|
||
const contentLength = res.headers.get('content-length') || '0';
|
||
const chunkSize = parseInt(contentLength);
|
||
// 下一段数据块区间
|
||
start += chunkSize;
|
||
end = Math.min(start + chunkSize - 1, totalSize - 1);
|
||
// 记录下载结果
|
||
filePart.push(res.data);
|
||
downloadedSize += chunkSize;
|
||
// 小于总大小继续下载后续数据
|
||
if (downloadedSize < totalSize) {
|
||
await sendRequest();
|
||
}
|
||
} else {
|
||
return res;
|
||
}
|
||
}
|
||
|
||
await sendRequest();
|
||
return new Blob(filePart, { type: 'application/octet-stream' });
|
||
}
|
||
|
||
/**
|
||
* 上传文件
|
||
* @param data 表单数据对象
|
||
* @returns object
|
||
*/
|
||
export function uploadFile(data: FormData) {
|
||
return request({
|
||
url: '/file/upload',
|
||
method: 'post',
|
||
data,
|
||
dataType: 'form-data',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 上传切片文件
|
||
* @param file 文件对象
|
||
* @param chunkSize 数据块大小MB,默认1MB
|
||
* @param subPath 归属子路径, 默认default
|
||
* @returns
|
||
*/
|
||
export async function uploadFileChunk(
|
||
fileData: File,
|
||
chunkSize: number = 1,
|
||
subPath: string = 'default'
|
||
) {
|
||
const { name, size } = fileData;
|
||
const chunkSizeInBytes = chunkSize * 1024 * 1024;
|
||
// 文件标识使用唯一编码 MD5(文件名+文件大小)
|
||
const fileIdentifier = `${name}-${size}`;
|
||
// 文件切分为多少份进行上传
|
||
const chunksCount = Math.ceil(size / chunkSizeInBytes);
|
||
// 切块的数据数据用于上传
|
||
const fileChunks: { index: number; chunk: Blob }[] = [];
|
||
|
||
for (let i = 0; i < chunksCount; i++) {
|
||
const start = i * chunkSizeInBytes;
|
||
const end = Math.min(start + chunkSizeInBytes, size);
|
||
fileChunks.push({
|
||
index: i,
|
||
chunk: fileData.slice(start, end),
|
||
});
|
||
}
|
||
|
||
// 检查是否已上传部分数据块
|
||
const resCheck = await chunkCheck(fileIdentifier, name);
|
||
if (resCheck.code !== RESULT_CODE_SUCCESS) {
|
||
return resCheck;
|
||
}
|
||
|
||
let uploadedSize = 0;
|
||
let uploadProgress = 0;
|
||
|
||
for (const { index, chunk } of fileChunks) {
|
||
const chunksIndex = `${index}`;
|
||
// 跳过已上传块
|
||
if (resCheck.data.includes(chunksIndex)) {
|
||
continue;
|
||
}
|
||
|
||
// 上传数据块
|
||
const formData = new FormData();
|
||
formData.append('file', chunk, name);
|
||
formData.append('index', chunksIndex);
|
||
formData.append('identifier', fileIdentifier);
|
||
|
||
const resUpload = await chunkUpload(formData);
|
||
if (resUpload.code === RESULT_CODE_SUCCESS) {
|
||
uploadedSize += chunk.size;
|
||
uploadProgress = (uploadedSize / size) * 100;
|
||
console.log(`上传进度:${uploadProgress}%`);
|
||
} else {
|
||
// 上传失败处理
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 上传数据完整后合并数据块
|
||
if (uploadedSize === size) {
|
||
return await chunkMerge(fileIdentifier, name, subPath);
|
||
}
|
||
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language] };
|
||
}
|
||
|
||
/**
|
||
* 切片文件检查
|
||
* @param identifier 文件标识
|
||
* @param fileName 原文件名称
|
||
* @returns object
|
||
*/
|
||
export function chunkCheck(identifier: string, fileName: string) {
|
||
return request({
|
||
url: '/file/chunkCheck',
|
||
method: 'post',
|
||
data: { identifier, fileName },
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 切片文件合并
|
||
* @param identifier 文件标识
|
||
* @param fileName 原文件名称
|
||
* @param subPath 文件归属
|
||
* @returns object
|
||
*/
|
||
export function chunkMerge(
|
||
identifier: string,
|
||
fileName: string,
|
||
subPath: string = 'default'
|
||
) {
|
||
return request({
|
||
url: '/file/chunkMerge',
|
||
method: 'post',
|
||
data: { identifier, fileName, subPath },
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 切片文件上传
|
||
* @param data 表单数据对象
|
||
* @returns object
|
||
*/
|
||
export function chunkUpload(data: FormData) {
|
||
return request({
|
||
url: '/file/chunkUpload',
|
||
method: 'post',
|
||
data,
|
||
dataType: 'form-data',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 转存上传文件到静态资源
|
||
* @returns object
|
||
*/
|
||
export function transferStaticFile(data: Record<string, any>) {
|
||
return request({
|
||
url: `/file/transferStaticFile`,
|
||
method: 'post',
|
||
data,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 上传切片文件并发送文件到网元端
|
||
* @param neType 网元类型, UPF
|
||
* @param neId 网元标识, 001
|
||
* @param fileData 文件对象
|
||
* @param chunkSize 数据块大小MB,默认1MB
|
||
* @returns
|
||
*/
|
||
export async function uploadFileToNE(
|
||
neType: string,
|
||
neId: string,
|
||
fileData: File,
|
||
chunkSize: number = 1
|
||
) {
|
||
const uploadChunkRes = await uploadFileChunk(fileData, chunkSize, 'import');
|
||
if (uploadChunkRes.code === RESULT_CODE_SUCCESS) {
|
||
const transferToNeFileRes = await request({
|
||
url: `/ne/action/pushFile`,
|
||
method: 'post',
|
||
data: {
|
||
uploadPath: uploadChunkRes.data.fileName,
|
||
neType,
|
||
neId,
|
||
},
|
||
});
|
||
return transferToNeFileRes;
|
||
}
|
||
return uploadChunkRes;
|
||
}
|