Browse Source

✨ feat(files): 添加分片上传完成接口

- 创建multipart-complete/post.ts文件实现分片上传完成功能
- 定义完成分片上传请求和响应Schema
- 实现分片上传完成处理逻辑,整合FileService服务
- 在文件路由中注册multipart-complete路由
yourname 8 months ago
parent
commit
a53bac2780
2 changed files with 121 additions and 0 deletions
  1. 2 0
      src/server/api/files/index.ts
  2. 119 0
      src/server/api/files/multipart-complete/post.ts

+ 2 - 0
src/server/api/files/index.ts

@@ -1,6 +1,7 @@
 import { OpenAPIHono } from '@hono/zod-openapi';
 import uploadPolicyRoute from './upload-policy/post';
 import multipartPolicyRoute from './multipart-policy/post';
+import completeMultipartRoute from './multipart-complete/post';
 import getUrlRoute from './[id]/get-url';
 import deleteRoute from './[id]/delete';
 import { AuthContext } from '@/server/types/context';
@@ -25,6 +26,7 @@ const fileRoutes = createCrudRoutes({
 const app = new OpenAPIHono<AuthContext>()
   .route('/upload-policy', uploadPolicyRoute)
   .route('/multipart-policy', multipartPolicyRoute)
+  .route('/multipart-complete', completeMultipartRoute)
   .route('/{id}', getUrlRoute)
   .route('/', fileRoutes)
   .route('/{id}', deleteRoute)

+ 119 - 0
src/server/api/files/multipart-complete/post.ts

@@ -0,0 +1,119 @@
+import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
+import { FileService } from '@/server/modules/files/file.service';
+import { ErrorSchema } from '@/server/utils/errorHandler';
+import { AppDataSource } from '@/server/data-source';
+import { AuthContext } from '@/server/types/context';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+// 完成分片上传请求Schema
+const CompleteMultipartUploadDto = z.object({
+  uploadId: z.string().openapi({
+    description: '分片上传ID',
+    example: '123e4567-e89b-12d3-a456-426614174000'
+  }),
+  bucket: z.string().openapi({
+    description: '存储桶名称',
+    example: 'my-bucket'
+  }),
+  key: z.string().openapi({
+    description: '文件键名',
+    example: 'documents/report.pdf'
+  }),
+  parts: z.array(
+    z.object({
+      partNumber: z.coerce.number().int().positive().openapi({
+        description: '分片序号',
+        example: 1
+      }),
+      etag: z.string().openapi({
+        description: '分片ETag值',
+        example: 'd41d8cd98f00b204e9800998ecf8427e'
+      })
+    })
+  ).openapi({
+    description: '分片信息列表',
+    example: [
+      { partNumber: 1, etag: 'd41d8cd98f00b204e9800998ecf8427e' },
+      { partNumber: 2, etag: '5f4dcc3b5aa765d61d8327deb882cf99' }
+    ]
+  })
+});
+
+// 完成分片上传响应Schema
+const CompleteMultipartUploadResponse = z.object({
+  fileId: z.string().openapi({
+    description: '文件ID',
+    example: 'file_123456'
+  }),
+  url: z.string().openapi({
+    description: '文件访问URL',
+    example: 'https://minio.example.com/my-bucket/documents/report.pdf'
+  }),
+  host: z.string().openapi({
+    description: 'MinIO主机地址',
+    example: 'minio.example.com'
+  }),
+  bucket: z.string().openapi({
+    description: '存储桶名称',
+    example: 'my-bucket'
+  }),
+  key: z.string().openapi({
+    description: '文件键名',
+    example: 'documents/report.pdf'
+  })
+});
+
+// 创建完成分片上传路由定义
+const completeMultipartUploadRoute = createRoute({
+  method: 'post',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: CompleteMultipartUploadDto }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '完成分片上传成功',
+      content: {
+        'application/json': { schema: CompleteMultipartUploadResponse }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+// 初始化FileService
+const fileService = new FileService(AppDataSource);
+
+// 创建路由实例并实现处理逻辑
+const app = new OpenAPIHono<AuthContext>().openapi(completeMultipartUploadRoute, async (c) => {
+  try {
+    const data = await c.req.json();
+    const result = await fileService.completeMultipartUpload(data);
+    
+    // 构建完整的响应包含host和bucket信息
+    const response = {
+      ...result,
+      host: `${process.env.MINIO_USE_SSL ? 'https' : 'http'}://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}`,
+      bucket: data.bucket
+    };
+    
+    return c.json(response, 200);
+  } catch (error) {
+    const message = error instanceof Error ? error.message : '完成分片上传失败';
+    return c.json({ code: 500, message }, 500);
+  }
+});
+
+export default app;