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 { // 创建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 { 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 { 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();