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 { 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' ) { let { name, size } = fileData; // 去除非法字符 const cleanedFilename = name.replace(/[\\/:*?"<>|]/g, ''); // 去除空格 name = cleanedFilename.replace(/\s/g, '_'); // 数据块大小 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)) { uploadedSize += chunk.size; 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) { 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; }