init: 初始系统模板
This commit is contained in:
195
src/api/tool/file.ts
Normal file
195
src/api/tool/file.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { request } 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 === 200 && 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 !== 200) {
|
||||
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 === 200) {
|
||||
uploadedSize += chunk.size;
|
||||
uploadProgress = (uploadedSize / size) * 100;
|
||||
console.log(`上传进度:${uploadProgress}%`);
|
||||
} else {
|
||||
// 上传失败处理
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 上传数据完整后合并数据块
|
||||
if (uploadedSize === size) {
|
||||
return await chunkMerge(fileIdentifier, name, subPath);
|
||||
}
|
||||
return { code: 500, msg: '上传出错,请重试' };
|
||||
}
|
||||
|
||||
/**
|
||||
* 切片文件检查
|
||||
* @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',
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user