Files
knowai/api/upload-api.ts
tobegold574 382e3aff21 feat(reset): 以构造器模式重构
- 加了大文件传输自定义分片协议

BREAKING CHANGES: 0.1.0(latest)
2025-11-30 20:27:53 +08:00

133 lines
3.9 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 { 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();