Files
fe.ems.vue3/src/api/tool/file.ts
2023-12-12 19:29:49 +08:00

243 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}