Browse Source

✨ feat(file): 实现分片上传完成功能

- 客户端:更新API端点和策略访问路径,从policy改为uploadPolicy
- 客户端:修正文件URL和bucket名称的获取方式
- 服务端:添加completeMultipartUpload方法处理分片上传完成逻辑
- 服务端:实现分片上传完成后更新文件记录和生成访问URL的功能

🐛 fix(file): 修复上传策略相关路径错误

- 修复客户端获取上传策略的API端点路径
- 修复客户端构建上传地址时的host获取逻辑
- 修正multipart-complete API端点的调用路径
yourname 8 months ago
parent
commit
37807c6b5d
2 changed files with 51 additions and 7 deletions
  1. 7 7
      src/client/utils/minio.ts
  2. 44 0
      src/server/modules/files/file.service.ts

+ 7 - 7
src/client/utils/minio.ts

@@ -196,7 +196,7 @@ export class MinIOXHRMultipartUploader {
     key: string,
     uploadedParts: UploadPart[]
   ): Promise<void> {
-    const response = await fileClient["complete-multipart-upload"].$post({
+    const response = await fileClient["multipart-complete"].$post({
         json:{
             bucket: policy.bucket,
             key,
@@ -268,9 +268,9 @@ export class MinIOXHRUploader {
                     }
                     callbacks?.onComplete?.();
                     resolve({
-                        fileUrl:`${policy.host}/${key}`,
+                        fileUrl:`${policy.uploadPolicy.host}/${key}`,
                         fileKey: key,
-                        bucketName: policy.bucket
+                        bucketName: policy.uploadPolicy.bucket
                     });
                 } else {
                     const error = new Error(`上传失败: ${xhr.status} ${xhr.statusText}`);
@@ -296,9 +296,9 @@ export class MinIOXHRUploader {
 
             // 根据当前页面协议和 host 配置决定最终的上传地址
             const currentProtocol = typeof window !== 'undefined' ? window.location.protocol : 'https:';
-            const host = policy.host?.startsWith('http')
-                ? policy.host
-                : `${currentProtocol}//${policy.host}`;
+            const host = policy.uploadPolicy.host?.startsWith('http')
+                ? policy.uploadPolicy.host
+                : `${currentProtocol}//${policy.uploadPolicy.host}`;
             // 开始上传
             xhr.open('POST', host);
             xhr.send(formData);
@@ -315,7 +315,7 @@ export class MinIOXHRUploader {
 } 
 
 export async function getUploadPolicy(key: string): Promise<MinioUploadPolicy> {
-  const policyResponse = await fileClient.policy.$post({
+  const policyResponse = await fileClient["upload-policy"].$post({
     json: { key }
   });
   if (!policyResponse.ok) {

+ 44 - 0
src/server/modules/files/file.service.ts

@@ -135,4 +135,48 @@ export class FileService extends GenericCrudService<File> {
       throw new Error('创建多部分上传策略失败');
     }
   }
+
+  /**
+   * 完成分片上传
+   */
+  async completeMultipartUpload(data: {
+    uploadId: string;
+    bucket: string;
+    key: string;
+    parts: Array<{ partNumber: number; etag: string }>;
+  }) {
+    try {
+      // 完成MinIO分片上传
+      const result = await this.minioService.completeMultipartUpload(
+        data.bucket,
+        data.key,
+        data.uploadId,
+        data.parts
+      );
+      
+      // 查找文件记录并更新
+      const file = await this.repository.findOneBy({ path: data.key });
+      if (!file) {
+        throw new Error('文件记录不存在');
+      }
+      
+      // 更新文件大小等信息
+      file.size = result.size;
+      file.updatedAt = new Date();
+      await this.repository.save(file);
+      
+      // 生成文件访问URL
+      const url = this.minioService.getFileUrl(data.bucket, data.key);
+      
+      return {
+        fileId: file.id,
+        url,
+        key: data.key,
+        size: result.size
+      };
+    } catch (error) {
+      logger.error('Failed to complete multipart upload:', error);
+      throw new Error('完成分片上传失败');
+    }
+  }
 }