2
0
Эх сурвалжийг харах

📝 docs(epic-007): 更新文件实体集成方案

- 更新故事4(disability-module)验收标准,明确文件实体集成要求
- 更新故事5(order-module)验收标准,明确文件实体集成要求
- 将集成方案从"统一使用file-module"改为"实体重构 + UI层统一上传"
- 详细说明架构设计原则:UI层统一处理文件上传,后端只接收文件ID
- 提供完整的实施步骤:数据库迁移、实体重构、API接口设计、服务层实现
- 更新文件上传流程说明,明确UI层与后端的分工
- 更新影响的故事说明,明确各模块需要调整的内容
- 更新实施注意事项,强调UI层组件复用和后端简化
- 更新总结部分,反映新的集成方案
yourname 2 долоо хоног өмнө
parent
commit
a863da6040
1 өөрчлөгдсөн 164 нэмэгдсэн , 72 устгасан
  1. 164 72
      docs/epic-007.md

+ 164 - 72
docs/epic-007.md

@@ -361,44 +361,56 @@ export type CreateChannelDto = z.infer<typeof CreateChannelSchema>;
 - 包含复杂查询场景测试
 
 ### 故事4:移植残疾人管理模块(disability_person → @d8d/allin-disability-module)
-**目标**:移植最复杂的模块,处理5个实体和跨模块依赖
+**目标**:移植最复杂的模块,处理5个实体和跨模块依赖,完成文件实体集成
 
 **验收标准**:
 1. ✅ 创建`allin-packages/disability-module`目录结构
 2. ✅ 完成实体转换:5个实体(DisabledPerson、DisabledBankCard等)转换
-3. ✅ 处理跨模块依赖:引用order、platform、company、channel模块实体
-4. ✅ 完成服务层转换:复杂业务逻辑处理
-5. ✅ 完成路由层转换:多个控制器转换为Hono路由
-6. ✅ 完成验证系统转换:复杂数据验证
-7. ✅ 配置package.json:处理多个依赖
-8. ✅ 编写API集成测试:覆盖所有5个实体的管理功能
-9. ✅ 通过类型检查和基本测试验证
-10. ✅ 解决与order-module的循环依赖问题
+3. ✅ **文件实体集成**:修改`DisabledPhoto`实体,添加`fileId`字段引用`File`实体
+4. ✅ 处理跨模块依赖:引用order、platform、company、channel模块实体
+5. ✅ 完成服务层转换:复杂业务逻辑处理,包含文件ID验证逻辑
+6. ✅ 完成路由层转换:多个控制器转换为Hono路由,API接收`fileId`参数
+7. ✅ 完成验证系统转换:复杂数据验证,包含文件ID验证
+8. ✅ 配置package.json:处理多个依赖,包含对`@d8d/file-module`的依赖
+9. ✅ 编写API集成测试:覆盖所有5个实体的管理功能,包含文件ID关联测试
+10. ✅ 通过类型检查和基本测试验证
+11. ✅ 解决与order-module的循环依赖问题
 
 **API集成测试要求**:
 - 测试文件:`tests/integration/disability.integration.test.ts`
 - 测试覆盖:残疾人信息、银行卡、照片、备注、走访记录
-- 验证:多实体关联、文件上传、复杂业务规则
+- **文件测试重点**:
+  - 验证`fileId`参数接收和处理
+  - 测试文件不存在时的错误处理
+  - 验证返回数据包含文件URL
+  - 测试文件关联的业务规则
+- 验证:多实体关联、复杂业务规则
 - 包含性能测试:大数据量查询
 
 ### 故事5:移植订单管理模块(order → @d8d/allin-order-module)
-**目标**:移植订单模块,处理与disability-module的循环依赖
+**目标**:移植订单模块,处理与disability-module的循环依赖,完成文件实体集成
 
 **验收标准**:
 1. ✅ 创建`allin-packages/order-module`目录结构
 2. ✅ 完成实体转换:3个实体(EmploymentOrder、OrderPerson等)转换
-3. ✅ 解决循环依赖:与disability-module的相互引用处理
-4. ✅ 完成服务层转换:订单业务逻辑
-5. ✅ 完成路由层转换:Hono路由实现
-6. ✅ 完成验证系统转换:订单相关验证
-7. ✅ 配置package.json:依赖管理
-8. ✅ 编写API集成测试:覆盖订单全生命周期
-9. ✅ 通过类型检查和基本测试验证
-10. ✅ 验证循环依赖解决方案的有效性
+3. ✅ **文件实体集成**:修改`OrderPersonAsset`实体,添加`fileId`字段引用`File`实体
+4. ✅ 解决循环依赖:与disability-module的相互引用处理
+5. ✅ 完成服务层转换:订单业务逻辑,包含资产文件关联处理
+6. ✅ 完成路由层转换:Hono路由实现,API接收`fileId`参数
+7. ✅ 完成验证系统转换:订单相关验证,包含文件类型验证
+8. ✅ 配置package.json:依赖管理,包含对`@d8d/file-module`的依赖
+9. ✅ 编写API集成测试:覆盖订单全生命周期,包含资产文件关联测试
+10. ✅ 通过类型检查和基本测试验证
+11. ✅ 验证循环依赖解决方案的有效性
 
 **API集成测试要求**:
 - 测试文件:`tests/integration/order.integration.test.ts`
 - 测试覆盖:订单创建、人员分配、资产关联、状态流转
+- **文件测试重点**:
+  - 测试不同资产类型(tax、salary、contract等)的文件关联
+  - 验证文件类型(image/video)与资产类型的匹配
+  - 测试资产文件的上传、查看、删除流程
+  - 验证文件关联的权限控制
 - 验证:与disability-module的集成、业务规则执行
 - 包含工作流测试:订单状态机
 
@@ -464,81 +476,127 @@ export type CreateChannelDto = z.infer<typeof CreateChannelSchema>;
    - 字段:`op_id`, `order_id`, `person_id`, `asset_type`, `asset_file_type`, `asset_url`, `related_time`
    - 存储图片/视频URL在`asset_url`字段
 
-### 集成方案(推荐:统一使用file-module
+### 集成方案(采用:实体重构 + UI层统一上传
 
-#### 方案A:实体重构(推荐)
-**核心思想**:将文件URL存储转换为引用`File`实体,统一文件管理
+#### 架构设计原则
+**核心思想**:文件上传在UI层通过文件选择器组件统一处理,后端只负责文件ID的关联管理
 
-**实施步骤**:
+**架构分层**:
+1. **UI层**:管理后台使用文件选择器组件上传文件,获取文件ID
+2. **API层**:接收文件ID,与业务实体关联
+3. **服务层**:处理文件ID与业务实体的关联逻辑
+4. **实体层**:通过`file_id`引用`File`实体
+
+#### 实施步骤
 1. **数据库迁移**:
    ```sql
    -- 1. 添加file_id字段
    ALTER TABLE disabled_photo ADD COLUMN file_id INT;
    ALTER TABLE order_person_asset ADD COLUMN file_id INT;
 
-   -- 2. 创建files表记录(可选,根据现有URL数据)
-   -- 3. 建立外键关系
+   -- 2. 建立外键关系
    ALTER TABLE disabled_photo ADD FOREIGN KEY (file_id) REFERENCES files(id);
    ALTER TABLE order_person_asset ADD FOREIGN KEY (file_id) REFERENCES files(id);
+
+   -- 3. 可选:迁移现有URL数据到files表
+   -- 根据现有photo_url/asset_url创建files记录,更新file_id
    ```
 
 2. **实体重构**:
    ```typescript
-   // DisabledPhoto实体
+   // 重构后的DisabledPhoto实体
    @Entity('disabled_photo')
    export class DisabledPhoto {
-     @Column({ name: 'photo_url' })
-     photoUrl: string; // 直接存储URL
-   }
+     @PrimaryGeneratedColumn({ name: 'photo_id' })
+     photoId!: number;
+
+     @Column({ name: 'person_id' })
+     personId!: number;
 
-   // 重构后
-   @Entity('disabled_photo')
-   export class DisabledPhoto {
      @Column({ name: 'file_id' })
-     fileId: number;
+     fileId!: number; // 引用File实体的ID
 
      @ManyToOne(() => File)
      @JoinColumn({ name: 'file_id' })
-     file: File; // 引用File实体
+     file!: File; // 关联File实体
 
-     // 保持原有字段
+     // 业务字段保持不变
      @Column({ name: 'photo_type' })
-     photoType: string;
+     photoType!: string;
 
      @Column({ name: 'can_download' })
-     canDownload: number;
+     canDownload!: number;
 
-     // 兼容性属性
+     @CreateDateColumn({ name: 'upload_time' })
+     uploadTime!: Date;
+
+     // 计算属性:获取文件URL
      get photoUrl(): Promise<string> {
        return this.file?.fullUrl || Promise.resolve('');
      }
+
+     // 关联残疾人实体
+     @ManyToOne(() => DisabledPerson, person => person.photos)
+     @JoinColumn({ name: 'person_id' })
+     person!: DisabledPerson;
    }
    ```
 
-3. **服务层调整**:
+3. **API接口设计**:
    ```typescript
-   // 使用FileService处理文件上传
-   import { FileService } from '@d8d/file-module';
+   // 创建残疾人照片的API接口
+   // UI层已通过文件选择器上传文件,这里只接收文件ID
+   export const CreateDisabledPhotoSchema = z.object({
+     personId: z.number().int().positive(),
+     fileId: z.number().int().positive(), // 文件ID,由UI层提供
+     photoType: z.string().min(1).max(50),
+     canDownload: z.number().int().min(0).max(1).default(1)
+   });
+
+   // 路由实现
+   channelRoutes.post('/photos', async (c) => {
+     const data = await c.req.valid('json'); // 包含fileId
+     const photo = await disabilityService.createPhoto(data);
+     return c.json(photo, 201);
+   });
+   ```
 
+4. **服务层实现**:
+   ```typescript
    export class DisabilityService {
-     async uploadPhoto(personId: number, fileData: Buffer, filename: string): Promise<DisabledPhoto> {
-       // 1. 使用FileService上传文件
-       const file = await this.fileService.uploadFile({
-         name: filename,
-         data: fileData,
-         uploadUserId: currentUser.id
+     async createPhoto(data: CreateDisabledPhotoDto): Promise<DisabledPhoto> {
+       // 验证文件是否存在
+       const file = await this.fileRepository.findOne({
+         where: { id: data.fileId }
        });
 
-       // 2. 创建DisabledPhoto记录
+       if (!file) {
+         throw new Error('文件不存在');
+       }
+
+       // 创建照片记录(只关联文件ID)
        const photo = this.photoRepository.create({
-         personId,
-         fileId: file.id,
-         photoType: this.getPhotoType(filename),
-         canDownload: 1
+         personId: data.personId,
+         fileId: data.fileId, // 使用UI层提供的文件ID
+         photoType: data.photoType,
+         canDownload: data.canDownload
        });
 
        return this.photoRepository.save(photo);
      }
+
+     async getPhotoWithUrl(photoId: number): Promise<DisabledPhoto> {
+       const photo = await this.photoRepository.findOne({
+         where: { photoId },
+         relations: ['file'] // 加载关联的File实体
+       });
+
+       if (!photo) {
+         throw new Error('照片不存在');
+       }
+
+       return photo;
+     }
    }
    ```
 
@@ -565,27 +623,55 @@ export type CreateChannelDto = z.infer<typeof CreateChannelSchema>;
    }
    ```
 
-### 推荐方案A的理由
+### 采用方案A(实体重构)的理由
 
-1. **标准化管理**:统一使用项目现有的文件管理架构
-2. **功能复用**:利用file-module的完整功能(权限、元数据、存储)
-3. **扩展性**:便于未来添加文件版本、缩略图、水印等功能
-4. **一致性**:与项目其他模块保持相同的技术栈和模式
-5. **维护性**:集中文件逻辑,减少重复代码
+1. **架构清晰**:UI层统一处理文件上传,后端专注业务逻辑
+2. **职责分离**:
+   - UI层:文件选择、上传、进度显示
+   - API层:验证文件ID、关联业务实体
+   - 服务层:业务逻辑处理
+   - 实体层:数据关系定义
+3. **复用现有组件**:利用项目已有的文件选择器组件
+4. **简化后端逻辑**:不需要处理文件上传的复杂逻辑(分片、重试、进度等)
+5. **一致性**:与项目其他模块保持相同的文件处理模式
+6. **安全性**:文件上传由统一的组件处理,避免安全漏洞
+
+### 文件上传流程
+```
+UI层(管理后台):
+1. 用户点击"上传照片"按钮
+2. 打开文件选择器组件(复用现有组件)
+3. 选择文件 → 组件调用file-module上传接口
+4. 获取文件ID → 填充到表单的fileId字段
+5. 提交表单(包含fileId和其他业务数据)
+
+API层(disability-module):
+1. 接收包含fileId的请求
+2. 验证fileId对应的文件是否存在
+3. 创建DisabledPhoto记录,关联fileId
+4. 返回创建结果
+
+服务层:
+1. 验证业务规则(如照片类型限制)
+2. 处理文件与业务的关联逻辑
+3. 返回包含文件URL的完整数据
+```
 
 ### 影响的故事
 
 需要调整以下故事的文件实体处理:
 
 1. **故事4(disability-module)**:
-   - 修改`DisabledPhoto`实体,引用`File`实体
-   - 更新照片上传/下载服务,使用`FileService`
-   - 调整API集成测试,验证文件功能
+   - 修改`DisabledPhoto`实体,添加`fileId`字段引用`File`实体
+   - 更新API接口,接收`fileId`参数(而非文件内容)
+   - 更新服务层,验证文件存在性并关联业务
+   - 调整API集成测试,验证文件ID关联功能
 
 2. **故事5(order-module)**:
-   - 修改`OrderPersonAsset`实体,引用`File`实体
-   - 更新资产上传服务,使用`FileService`
-   - 调整测试用例
+   - 修改`OrderPersonAsset`实体,添加`fileId`字段引用`File`实体
+   - 更新API接口,接收`fileId`参数
+   - 更新服务层,处理资产文件关联逻辑
+   - 调整测试用例,模拟文件ID关联场景
 
 ### 实施注意事项
 
@@ -689,16 +775,22 @@ export type CreateChannelDto = z.infer<typeof CreateChannelSchema>;
 **文件实体集成方案**:
 发现allin_system-master中有文件相关实体(DisabledPhoto、OrderPersonAsset),需要与现有file-module集成。
 
-**推荐方案**:统一使用file-module,重构文件实体引用
-1. **故事4(disability-module)**:修改`DisabledPhoto`实体,添加`fileId`字段引用`File`实体
-2. **故事5(order-module)**:修改`OrderPersonAsset`实体,添加`fileId`字段引用`File`实体
-3. **服务层**:使用`FileService`处理文件上传/下载
+**采用方案**:实体重构 + UI层统一上传
+1. **架构原则**:文件上传在UI层通过文件选择器组件处理,后端只接收文件ID
+2. **故事4(disability-module)**:
+   - 修改`DisabledPhoto`实体,添加`fileId`字段引用`File`实体
+   - API接口接收`fileId`参数(而非文件内容)
+   - 服务层验证文件存在性并关联业务
+3. **故事5(order-module)**:
+   - 修改`OrderPersonAsset`实体,添加`fileId`字段引用`File`实体
+   - API接口接收`fileId`参数
+   - 服务层处理资产文件关联逻辑
 4. **数据库**:需要迁移现有URL数据到files表
 
 **实施要点**:
-- 保持API兼容性
-- 制定数据迁移方案
-- 注意N+1查询性能问题
-- 处理文件服务错误情况
+- UI层使用现有文件选择器组件上传文件
+- 后端API只接收文件ID,不处理文件上传
+- 保持API兼容性,制定数据迁移方案
+- 注意N+1查询性能,合理使用数据加载策略
 
 史诗应在保持系统完整性的同时实现将有实体模块从NestJS架构移植到Hono架构的标准化独立包,每个模块都要有完整的API集成测试验证,并完成与现有file-module的文件集成。"