|
|
@@ -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的文件集成。"
|