feat(reset): 以构造器模式重构

- 加了大文件传输自定义分片协议

BREAKING CHANGES: 0.1.0(latest)
This commit is contained in:
tobegold574
2025-11-30 20:27:53 +08:00
parent c5853847ae
commit 382e3aff21
82 changed files with 1421 additions and 7010 deletions

132
api/upload-api.ts Normal file
View File

@@ -0,0 +1,132 @@
import { ApiClientBound } from '../decorators';
import { API_ENDPOINTS } from './common';
import type {
UploadChunkRequest,
UploadChunkResponse,
MergeChunksRequest,
MergeChunksResponse,
CheckUploadStatusRequest,
CheckUploadStatusResponse
} from '../types/upload/api';
import type { AxiosHttpClient } from '../client';
/**
* 上传API服务类
* 使用自定义实现而非@Request装饰器以支持分片上传的复杂headers
*/
@ApiClientBound
export class UploadApiService {
/**
* 客户端实例由ApiClientBound装饰器注入
*/
declare client: AxiosHttpClient;
/**
* 上传文件分片
* @param chunkRequest 分片上传请求参数
* @param chunkBlob 文件分片数据
* @returns 分片上传响应
*/
async uploadChunk(
chunkRequest: UploadChunkRequest,
chunkBlob: Blob
): Promise<UploadChunkResponse> {
// 创建FormData以支持文件上传
const formData = new FormData();
formData.append('chunk', chunkBlob);
formData.append('chunkIndex', chunkRequest.chunkIndex.toString());
formData.append('totalChunks', chunkRequest.totalChunks.toString());
formData.append('fileName', chunkRequest.fileName);
formData.append('fileType', chunkRequest.fileType);
formData.append('fileId', chunkRequest.fileId);
// 自定义headers不设置Content-Type让浏览器自动设置multipart/form-data
const headers = {
'X-Chunk-Index': chunkRequest.chunkIndex.toString(),
'X-Total-Chunks': chunkRequest.totalChunks.toString(),
'X-File-Id': chunkRequest.fileId,
'X-Chunk-Size': chunkRequest.chunkSize.toString(),
'X-Total-Size': chunkRequest.totalSize.toString(),
// 不设置Content-Type让浏览器自动处理
};
// 直接使用client实例发送请求而不通过@Request装饰器
return await this.client.request({
method: 'POST',
url: API_ENDPOINTS.UPLOAD_CHUNK,
body: formData,
headers,
// 允许跨域携带凭证
withCredentials: true,
// 禁用默认的Content-Type设置禁止axios的jsonify处理
transformRequest: [(data: any) => data]
});
}
/**
* 合并文件分片
* @param mergeRequest 合并分片请求参数
* @returns 合并分片响应
*/
async mergeChunks(mergeRequest: MergeChunksRequest): Promise<MergeChunksResponse> {
const headers = {
'Content-Type': 'application/json',
'X-File-Id': mergeRequest.fileId,
'X-Total-Chunks': mergeRequest.totalChunks.toString()
};
return await this.client.request({
method: 'POST',
url: API_ENDPOINTS.MERGE_CHUNKS,
body: mergeRequest,
headers,
withCredentials: true
});
}
/**
* 检查文件上传状态
* @param checkRequest 检查上传状态请求参数
* @returns 上传状态响应
*/
async checkUploadStatus(
checkRequest: CheckUploadStatusRequest
): Promise<CheckUploadStatusResponse> {
const headers = {
'X-File-Id': checkRequest.fileId
};
return await this.client.request({
method: 'POST',
url: API_ENDPOINTS.CHECK_UPLOAD_STATUS,
body: checkRequest,
headers,
withCredentials: true
});
}
/**
* 生成文件唯一ID
* @param _fileName 文件名(未使用,保留参数位置)
* @param fileSize 文件大小
* @returns 文件唯一ID
*/
generateFileId(_fileName: string, fileSize: number): string {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substring(2, 9);
return `${timestamp}_${random}_${fileSize}`;
}
/**
* 计算分片数量
* @param fileSize 文件大小
* @param chunkSize 分片大小
* @returns 分片数量
*/
calculateChunks(fileSize: number, chunkSize: number = 5 * 1024 * 1024): number {
return Math.ceil(fileSize / chunkSize);
}
}
// 导出单例实例
export const uploadApi = new UploadApiService();