|
@@ -0,0 +1,913 @@
|
|
|
|
|
+# 史诗007 - Allin System有实体模块移植到独立包
|
|
|
|
|
+
|
|
|
|
|
+## 史诗目标
|
|
|
|
|
+
|
|
|
|
|
+将allin_system-master/server/src目录下有对应数据库实体的模块移植到packages目录下,作为非多租户独立包,遵循项目现有的独立包结构和命名规范,实现模块的现代化重构和标准化管理。
|
|
|
|
|
+
|
|
|
|
|
+## 史诗描述
|
|
|
|
|
+
|
|
|
|
|
+### 现有系统上下文
|
|
|
|
|
+
|
|
|
|
|
+- **当前相关功能**:allin_system-master是一个基于NestJS的后端系统,根据allin_2025-11-25.sql分析,有以下有实体的模块:
|
|
|
|
|
+ 1. channel_info模块 - 对应channel_info表
|
|
|
|
|
+ 2. company模块 - 对应employer_company表
|
|
|
|
|
+ 3. dict_management模块 - 对应sys_dict表
|
|
|
|
|
+ 4. disability_person模块 - 对应disabled_person、disabled_bank_card、disabled_photo、disabled_remark、disabled_visit表
|
|
|
|
|
+ 5. order模块 - 对应employment_order、order_person、order_person_asset表
|
|
|
|
|
+ 6. platform模块 - 对应employer_platform表
|
|
|
|
|
+ 7. salary模块 - 对应salary_level表
|
|
|
|
|
+
|
|
|
|
|
+- **无实体模块(本次不移植)**:admin、auth、users模块
|
|
|
|
|
+- **技术栈**:TypeScript、NestJS、PostgreSQL、Redis、MinIO
|
|
|
|
|
+- **集成点**:模块间通过NestJS模块系统集成,共享数据库连接和配置
|
|
|
|
|
+
|
|
|
|
|
+### 增强详情
|
|
|
|
|
+
|
|
|
|
|
+- **新增/变更内容**:将7个有实体的现有模块从allin_system-master/server/src移植到packages目录下的独立包
|
|
|
|
|
+- **集成方式**:每个模块将重构为独立的npm包,遵循@d8d命名规范,通过workspace依赖管理
|
|
|
|
|
+- **成功标准**:
|
|
|
|
|
+ 1. 7个有实体模块成功移植为独立包
|
|
|
|
|
+ 2. 保持原有功能完整性
|
|
|
|
|
+ 3. 遵循现有独立包结构模式
|
|
|
|
|
+ 4. 通过类型检查和基本测试验证
|
|
|
|
|
+
|
|
|
|
|
+## 模块分析结果
|
|
|
|
|
+
|
|
|
|
|
+### 1. 模块依赖关系分析
|
|
|
|
|
+
|
|
|
|
|
+根据对每个模块`module.ts`文件的分析,依赖关系如下:
|
|
|
|
|
+
|
|
|
|
|
+1. **channel_info模块** (`channel.module.ts`)
|
|
|
|
|
+ - 依赖:`@nestjs/common`, `@nestjs/typeorm`
|
|
|
|
|
+ - 实体:`Channel` (对应`channel_info`表)
|
|
|
|
|
+ - 无其他模块依赖
|
|
|
|
|
+
|
|
|
|
|
+2. **company模块** (`company.module.ts`)
|
|
|
|
|
+ - 依赖:`@nestjs/common`, `@nestjs/typeorm`, `PlatformModule`
|
|
|
|
|
+ - 实体:`Company` (对应`employer_company`表)
|
|
|
|
|
+ - 依赖模块:`platform`
|
|
|
|
|
+
|
|
|
|
|
+3. **dict_management模块** (`dict.module.ts`)
|
|
|
|
|
+ - 依赖:`@nestjs/common`, `@nestjs/typeorm`
|
|
|
|
|
+ - 实体:`Dict` (对应`sys_dict`表)
|
|
|
|
|
+ - 无其他模块依赖
|
|
|
|
|
+
|
|
|
|
|
+4. **disability_person模块** (`disability_person.module.ts`)
|
|
|
|
|
+ - 依赖:`@nestjs/common`, `@nestjs/typeorm`
|
|
|
|
|
+ - 实体:`DisabledPerson`, `DisabledBankCard`, `DisabledPhoto`, `DisabledRemark`, `DisabledVisit`
|
|
|
|
|
+ - 引用其他模块实体:`EmploymentOrder`, `OrderPerson` (order模块), `Platform` (platform模块), `Company` (company模块), `Channel` (channel_info模块)
|
|
|
|
|
+ - 复杂度:高(5个实体,多个控制器和服务)
|
|
|
|
|
+
|
|
|
|
|
+5. **order模块** (`order.module.ts`)
|
|
|
|
|
+ - 依赖:`@nestjs/common`, `@nestjs/typeorm`
|
|
|
|
|
+ - 实体:`EmploymentOrder`, `OrderPerson`, `OrderPersonAsset`
|
|
|
|
|
+ - 引用其他模块实体:`DisabledPerson` (disability_person模块), `Platform` (platform模块), `Company` (company模块), `Channel` (channel_info模块)
|
|
|
|
|
+ - 复杂度:中高(3个实体,跨模块依赖)
|
|
|
|
|
+
|
|
|
|
|
+6. **platform模块** (`platform.module.ts`)
|
|
|
|
|
+ - 依赖:`@nestjs/common`, `@nestjs/typeorm`
|
|
|
|
|
+ - 实体:`Platform` (对应`employer_platform`表)
|
|
|
|
|
+ - 无其他模块依赖
|
|
|
|
|
+ - 被依赖:`company`模块依赖此模块
|
|
|
|
|
+
|
|
|
|
|
+7. **salary模块** (`salary.module.ts`)
|
|
|
|
|
+ - 依赖:`@nestjs/common`, `@nestjs/typeorm`
|
|
|
|
|
+ - 实体:`SalaryLevel` (对应`salary_level`表)
|
|
|
|
|
+ - 无其他模块依赖
|
|
|
|
|
+
|
|
|
|
|
+### 2. 依赖关系图
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+platform ───┐
|
|
|
|
|
+ ↓
|
|
|
|
|
+company ────┐
|
|
|
|
|
+ ↓
|
|
|
|
|
+channel_info ─┐
|
|
|
|
|
+ ↓ ↓
|
|
|
|
|
+order ───────┘
|
|
|
|
|
+ ↑
|
|
|
|
|
+disability_person
|
|
|
|
|
+ ↑
|
|
|
|
|
+dict_management (独立)
|
|
|
|
|
+salary (独立)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键发现**:
|
|
|
|
|
+- `platform`模块是基础依赖,被`company`模块直接依赖
|
|
|
|
|
+- `company`、`channel_info`模块被`order`和`disability_person`模块引用
|
|
|
|
|
+- `order`和`disability_person`模块相互引用实体,形成循环依赖
|
|
|
|
|
+- `dict_management`和`salary`模块完全独立
|
|
|
|
|
+
|
|
|
|
|
+### 3. 模块到独立包的映射方案
|
|
|
|
|
+
|
|
|
|
|
+基于现有项目命名规范,并考虑这是Allin系统专属模块,制定以下映射方案:
|
|
|
|
|
+
|
|
|
|
|
+| 原模块名 | 独立包名 | 目录名 | 说明 |
|
|
|
|
|
+|---------|---------|--------|------|
|
|
|
|
|
+| channel_info | `@d8d/allin-channel-module` | `channel-module` | 渠道管理模块 |
|
|
|
|
|
+| company | `@d8d/allin-company-module` | `company-module` | 公司管理模块,依赖platform-module |
|
|
|
|
|
+| dict_management | `@d8d/allin-dict-module` | `dict-module` | 字典管理模块 |
|
|
|
|
|
+| disability_person | `@d8d/allin-disability-module` | `disability-module` | 残疾人管理模块(简化名称) |
|
|
|
|
|
+| order | `@d8d/allin-order-module` | `order-module` | 订单管理模块 |
|
|
|
|
|
+| platform | `@d8d/allin-platform-module` | `platform-module` | 平台管理模块 |
|
|
|
|
|
+| salary | `@d8d/allin-salary-module` | `salary-module` | 薪资管理模块 |
|
|
|
|
|
+
|
|
|
|
|
+**命名原则**:
|
|
|
|
|
+1. **包名前缀**:使用`@d8d/allin-`前缀,明确表明是Allin系统专属包
|
|
|
|
|
+2. **目录名**:使用简洁的`{模块名}-module`格式,便于在`allin-packages`目录中管理
|
|
|
|
|
+3. **模块名**:使用单数形式 + `-module`后缀
|
|
|
|
|
+4. **名称简洁**:反映模块核心功能
|
|
|
|
|
+5. **非多租户**:不使用`-mt`后缀(本次移植为非多租户版本)
|
|
|
|
|
+
|
|
|
|
|
+### 4. 目录结构和包规范
|
|
|
|
|
+
|
|
|
|
|
+#### 目录结构决策
|
|
|
|
|
+由于这些模块是`allin_system-master`项目独有的业务模块(非通用模块),将在项目根目录创建独立目录:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+项目根目录/
|
|
|
|
|
+├── allin-packages/ # Allin系统专属包目录
|
|
|
|
|
+│ ├── channel-module/ # 渠道管理模块
|
|
|
|
|
+│ ├── company-module/ # 公司管理模块
|
|
|
|
|
+│ ├── dict-module/ # 字典管理模块
|
|
|
|
|
+│ ├── disability-module/ # 残疾人管理模块
|
|
|
|
|
+│ ├── order-module/ # 订单管理模块
|
|
|
|
|
+│ ├── platform-module/ # 平台管理模块
|
|
|
|
|
+│ └── salary-module/ # 薪资管理模块
|
|
|
|
|
+├── packages/ # 现有通用包目录(保持不变)
|
|
|
|
|
+└── allin_system-master/ # 原始代码目录(移植源)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**目录命名说明**:`allin-packages`清晰表明这是Allin系统专属的包目录,与`allin_system-master`源目录对应,区别于通用的`packages`目录。
|
|
|
|
|
+
|
|
|
|
|
+#### 包结构规范
|
|
|
|
|
+参考现有`auth-module`结构,每个独立包应包含:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+allin-packages/{module-name}/
|
|
|
|
|
+├── package.json # 包配置,workspace依赖管理
|
|
|
|
|
+├── tsconfig.json # TypeScript配置
|
|
|
|
|
+├── vitest.config.ts # 测试配置(如需要)
|
|
|
|
|
+├── src/
|
|
|
|
|
+│ ├── index.ts # 主导出文件
|
|
|
|
|
+│ ├── schemas/ # Zod模式定义(如需要)
|
|
|
|
|
+│ │ └── index.ts
|
|
|
|
|
+│ ├── entities/ # 数据库实体
|
|
|
|
|
+│ ├── services/ # 业务服务
|
|
|
|
|
+│ ├── controllers/ # API控制器
|
|
|
|
|
+│ ├── dtos/ # 数据传输对象
|
|
|
|
|
+│ └── types/ # TypeScript类型定义
|
|
|
|
|
+└── dist/ # 构建输出
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键配置要求**:
|
|
|
|
|
+1. `package.json`中设置`"type": "module"`
|
|
|
|
|
+2. 主入口为`src/index.ts`
|
|
|
|
|
+3. 使用workspace依赖:`"@d8d/core-module": "workspace:*"`
|
|
|
|
|
+4. 包名使用`@d8d/allin-`前缀,如`@d8d/allin-channel-module`
|
|
|
|
|
+5. 导出必要的服务和实体
|
|
|
|
|
+
|
|
|
|
|
+### 5. 技术栈差异分析与移植方案
|
|
|
|
|
+
|
|
|
|
|
+#### 技术栈对比分析
|
|
|
|
|
+
|
|
|
|
|
+| 方面 | 当前项目(目标) | allin_system-master(源) | 差异程度 |
|
|
|
|
|
+|------|------------------|---------------------------|----------|
|
|
|
|
|
+| **Web框架** | Hono(轻量级) | NestJS(重量级) | **重大** |
|
|
|
|
|
+| **路由系统** | Hono路由 | NestJS装饰器路由 | **重大** |
|
|
|
|
|
+| **验证库** | Zod + @hono/zod-openapi | class-validator + class-transformer | **中等** |
|
|
|
|
|
+| **服务架构** | GenericCrudService继承 | 自定义Service类 | **中等** |
|
|
|
|
|
+| **实体配置** | 详细TypeORM装饰器 | 简单TypeORM装饰器 | **轻微** |
|
|
|
|
|
+| **命名规范** | 驼峰命名(channelId) | 下划线命名(channel_id) | **轻微** |
|
|
|
|
|
+| **模块系统** | npm包 + workspace | NestJS模块系统 | **重大** |
|
|
|
|
|
+| **API风格** | RESTful + OpenAPI | 自定义端点 | **中等** |
|
|
|
|
|
+
|
|
|
|
|
+#### 关键依赖对比
|
|
|
|
|
+- **当前项目**:`@d8d/core-module`、`@d8d/shared-crud`、`@d8d/shared-utils`、TypeORM、Hono、Zod
|
|
|
|
|
+- **allin_system**:`@nestjs/*`、TypeORM、class-validator、Passport/JWT
|
|
|
|
|
+
|
|
|
|
|
+#### 移植调整方案
|
|
|
|
|
+
|
|
|
|
|
+##### A. 架构转换策略
|
|
|
|
|
+1. **NestJS控制器 → Hono路由**:重写路由层
|
|
|
|
|
+2. **自定义Service → GenericCrudService继承**:重构服务层
|
|
|
|
|
+3. **class-validator DTO → Zod Schema**:转换验证逻辑
|
|
|
|
|
+4. **NestJS模块 → npm包**:重新组织模块结构
|
|
|
|
|
+
|
|
|
|
|
+##### B. 实体层转换示例
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 原allin实体(下划线命名)
|
|
|
|
|
+@Entity('channel_info')
|
|
|
|
|
+export class Channel {
|
|
|
|
|
+ @PrimaryGeneratedColumn('increment')
|
|
|
|
|
+ channel_id: number; // 下划线
|
|
|
|
|
+ @Column()
|
|
|
|
|
+ channel_name: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 转换后实体(驼峰命名,详细配置)
|
|
|
|
|
+@Entity('channel_info')
|
|
|
|
|
+export class Channel {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ name: 'channel_id', type: 'int' })
|
|
|
|
|
+ channelId!: number; // 驼峰
|
|
|
|
|
+ @Column({ name: 'channel_name', type: 'varchar', length: 100 })
|
|
|
|
|
+ channelName!: string;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+##### C. 服务层转换示例
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 原allin服务(NestJS风格)
|
|
|
|
|
+@Injectable()
|
|
|
|
|
+export class ChannelService {
|
|
|
|
|
+ constructor(@InjectRepository(Channel) private repo: Repository<Channel>) {}
|
|
|
|
|
+ async createChannel(data: CreateChannelDto): Promise<boolean> {
|
|
|
|
|
+ // 自定义逻辑
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 转换后服务(Hono + GenericCrudService)
|
|
|
|
|
+export class ChannelService extends GenericCrudService<Channel> {
|
|
|
|
|
+ constructor(dataSource: DataSource) {
|
|
|
|
|
+ super(dataSource, Channel, {
|
|
|
|
|
+ searchFields: ['channelName', 'contactPerson'],
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ // 保留特殊业务方法
|
|
|
|
|
+ async createChannel(data: CreateChannelDto): Promise<boolean> {
|
|
|
|
|
+ // 复用或重写逻辑
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+##### D. 路由层转换示例
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 原NestJS控制器
|
|
|
|
|
+@Controller('channel')
|
|
|
|
|
+@UseGuards(JwtAuthGuard)
|
|
|
|
|
+export class ChannelController {
|
|
|
|
|
+ @Post('createChannel')
|
|
|
|
|
+ createChannel(@Body() req: CreateChannelDto) {
|
|
|
|
|
+ return this.service.createChannel(req);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 转换后Hono路由
|
|
|
|
|
+import { OpenAPIHono } from '@hono/zod-openapi';
|
|
|
|
|
+const channelRoutes = new OpenAPIHono()
|
|
|
|
|
+ .post('/createChannel', async (c) => {
|
|
|
|
|
+ const data = await c.req.json();
|
|
|
|
|
+ const result = await channelService.createChannel(data);
|
|
|
|
|
+ return c.json({ success: result });
|
|
|
|
|
+ });
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+##### E. 验证系统转换
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 原class-validator DTO
|
|
|
|
|
+export class CreateChannelDto {
|
|
|
|
|
+ @IsString()
|
|
|
|
|
+ channel_name: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 转换后Zod Schema
|
|
|
|
|
+export const CreateChannelSchema = z.object({
|
|
|
|
|
+ channelName: z.string().min(1).max(100),
|
|
|
|
|
+});
|
|
|
|
|
+export type CreateChannelDto = z.infer<typeof CreateChannelSchema>;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 依赖关系解决方案
|
|
|
|
|
+
|
|
|
|
|
+针对发现的循环依赖问题(order ↔ disability_person),解决方案:
|
|
|
|
|
+
|
|
|
|
|
+1. **创建共享实体包**:将相互引用的实体提取到共享包
|
|
|
|
|
+2. **接口抽象**:使用接口而非具体实体引用
|
|
|
|
|
+3. **依赖注入调整**:重构服务层减少直接实体依赖
|
|
|
|
|
+4. **移植顺序策略**:按依赖顺序移植,先移植基础模块
|
|
|
|
|
+
|
|
|
|
|
+**建议移植顺序**:
|
|
|
|
|
+1. `allin-platform-module`(基础)
|
|
|
|
|
+2. `allin-channel-module`、`allin-dict-module`、`allin-salary-module`(独立)
|
|
|
|
|
+3. `allin-company-module`(依赖platform)
|
|
|
|
|
+4. `allin-order-module`和`allin-disability-module`(最后处理,解决循环依赖)
|
|
|
|
|
+
|
|
|
|
|
+#### 兼容性保证
|
|
|
|
|
+- **保持**:数据库表结构、核心业务逻辑、数据关系
|
|
|
|
|
+- **调整**:API端点路径、请求/响应格式、错误处理
|
|
|
|
|
+- **新增**:OpenAPI文档、标准化CRUD操作、类型安全验证
|
|
|
|
|
+
|
|
|
|
|
+## 故事分解(按模块拆分)
|
|
|
|
|
+
|
|
|
|
|
+**注**:每个故事对应一个模块的完整移植,包括技术栈转换和API集成测试。
|
|
|
|
|
+
|
|
|
|
|
+### 故事1:移植渠道管理模块(channel_info → @d8d/allin-channel-module)✅ **已完成**
|
|
|
|
|
+**目标**:将channel_info模块移植为独立包,完成技术栈转换并验证功能完整性
|
|
|
|
|
+
|
|
|
|
|
+**验收标准**:
|
|
|
|
|
+1. ✅ 创建`allin-packages/channel-module`目录结构
|
|
|
|
|
+2. ✅ 完成实体转换:`Channel`实体从下划线命名转换为驼峰命名
|
|
|
|
|
+3. ✅ 完成服务层转换:从NestJS自定义Service转换为GenericCrudService继承
|
|
|
|
|
+4. ✅ 完成路由层转换:从NestJS控制器转换为Hono路由
|
|
|
|
|
+5. ✅ 完成验证系统转换:从class-validator DTO转换为Zod Schema
|
|
|
|
|
+6. ✅ 配置package.json:使用`@d8d/allin-channel-module`包名,workspace依赖
|
|
|
|
|
+7. ✅ 编写API集成测试:覆盖所有路由端点,验证CRUD操作
|
|
|
|
|
+8. ✅ 通过类型检查和基本测试验证
|
|
|
|
|
+
|
|
|
|
|
+**API集成测试要求**:
|
|
|
|
|
+- 测试文件:`tests/integration/channel.integration.test.ts`
|
|
|
|
|
+- 测试覆盖:GET/POST/PUT/DELETE所有端点
|
|
|
|
|
+- 验证:认证、授权、数据验证、错误处理
|
|
|
|
|
+- 遵循现有集成测试模式(参考advertisements-module)
|
|
|
|
|
+
|
|
|
|
|
+**完成情况**:
|
|
|
|
|
+- ✅ 已创建完整的模块结构和配置文件
|
|
|
|
|
+- ✅ 已完成实体转换(主键属性名从channelId改为id以遵循GenericCrudService约定)
|
|
|
|
|
+- ✅ 已完成服务层转换,继承GenericCrudService并覆盖关键方法
|
|
|
|
|
+- ✅ 已完成路由层转换,实现所有6个API端点
|
|
|
|
|
+- ✅ 已完成验证系统转换,使用Zod Schema
|
|
|
|
|
+- ✅ 已编写11个集成测试,全部通过
|
|
|
|
|
+- ✅ 类型检查通过,无任何错误
|
|
|
|
|
+- ✅ 修复了workspace配置问题(添加allin-packages/*到pnpm-workspace.yaml)
|
|
|
|
|
+- ✅ 修复了测试依赖问题(添加@d8d/user-module和@d8d/file-module依赖)
|
|
|
|
|
+
|
|
|
|
|
+### 故事2:移植公司管理模块(company → @d8d/allin-company-module)
|
|
|
|
|
+**目标**:将company模块移植为独立包,处理对platform-module的依赖
|
|
|
|
|
+
|
|
|
|
|
+**验收标准**:
|
|
|
|
|
+1. ✅ 创建`allin-packages/company-module`目录结构
|
|
|
|
|
+2. ✅ 完成实体转换:`Company`实体转换
|
|
|
|
|
+3. ✅ 处理模块依赖:正确配置对`@d8d/allin-platform-module`的依赖
|
|
|
|
|
+4. ✅ 完成服务层转换:处理跨模块业务逻辑
|
|
|
|
|
+5. ✅ 完成路由层转换:Hono路由实现
|
|
|
|
|
+6. ✅ 完成验证系统转换:Zod Schema定义
|
|
|
|
|
+7. ✅ 配置package.json:workspace依赖管理
|
|
|
|
|
+8. ✅ 编写API集成测试:验证模块间依赖正常工作
|
|
|
|
|
+9. ✅ 通过类型检查和基本测试验证
|
|
|
|
|
+
|
|
|
|
|
+**API集成测试要求**:
|
|
|
|
|
+- 测试文件:`tests/integration/company.integration.test.ts`
|
|
|
|
|
+- 测试覆盖:公司管理的所有业务场景
|
|
|
|
|
+- 验证:与platform-module的集成、数据关联查询
|
|
|
|
|
+- 包含跨模块数据一致性测试
|
|
|
|
|
+
|
|
|
|
|
+## 设计决策:字典管理模块重构为枚举常量包
|
|
|
|
|
+
|
|
|
|
|
+### 背景分析
|
|
|
|
|
+经过对`dict_management`模块的深入分析,发现其存储的数据主要分为两类:
|
|
|
|
|
+
|
|
|
|
|
+1. **固定业务枚举数据**(适合用TypeScript枚举):
|
|
|
|
|
+ - `disability_type`(残疾类型):7种固定类型
|
|
|
|
|
+ - `disability_level`(残疾等级):4个固定等级
|
|
|
|
|
+ - `order_status`(订单状态):5种固定状态
|
|
|
|
|
+ - `work_status`(工作状态):4种固定状态
|
|
|
|
|
+
|
|
|
|
|
+2. **动态配置数据**(但已有替代方案):
|
|
|
|
|
+ - 行政区划数据:已有专门的`@d8d/geo-areas`包管理
|
|
|
|
|
+ - 其他动态配置:可根据需要单独处理
|
|
|
|
|
+
|
|
|
|
|
+### 设计决策
|
|
|
|
|
+**放弃移植`dict_management`模块,改为创建枚举常量包**,原因如下:
|
|
|
|
|
+
|
|
|
|
|
+1. **类型安全**:枚举常量提供编译时类型检查
|
|
|
|
|
+2. **性能优化**:无需数据库查询,减少IO操作
|
|
|
|
|
+3. **代码清晰**:在代码中直接引用,提高可读性
|
|
|
|
|
+4. **维护简单**:修改枚举值即可,无需数据库操作
|
|
|
|
|
+5. **避免重复**:行政区划功能已有`@d8d/geo-areas`包
|
|
|
|
|
+
|
|
|
|
|
+### 故事3:创建Allin系统枚举常量包(@d8d/allin-enums)
|
|
|
|
|
+**目标**:创建专门供Allin系统使用的枚举常量包,替换原有的数据库字典管理
|
|
|
|
|
+
|
|
|
|
|
+**验收标准**:
|
|
|
|
|
+1. ✅ 创建`allin-packages/enums`目录结构
|
|
|
|
|
+2. ✅ 定义残疾类型枚举:`DisabilityType`(vision, hearing, speech, physical, intellectual, mental, multiple)
|
|
|
|
|
+3. ✅ 定义残疾等级枚举:`DisabilityLevel`(1, 2, 3, 4)
|
|
|
|
|
+4. ✅ 定义订单状态枚举:`OrderStatus`(draft, confirmed, in_progress, completed, cancelled)
|
|
|
|
|
+5. ✅ 定义工作状态枚举:`WorkStatus`(not_working, pre_working, working, resigned)
|
|
|
|
|
+6. ✅ 配置package.json:包名`@d8d/allin-enums`,workspace依赖
|
|
|
|
|
+7. ✅ 编写类型定义测试:验证枚举值正确性
|
|
|
|
|
+8. ✅ 通过类型检查和导出验证
|
|
|
|
|
+9. ✅ 更新后续模块依赖:确保disability-module和order-module使用枚举常量
|
|
|
|
|
+
|
|
|
|
|
+**技术实现要求**:
|
|
|
|
|
+- 包位置:`allin-packages/enums/`
|
|
|
|
|
+- 包名:`@d8d/allin-enums`
|
|
|
|
|
+- 导出方式:通过`src/index.ts`统一导出所有枚举
|
|
|
|
|
+- 类型定义:使用TypeScript `enum`或`const enum`定义
|
|
|
|
|
+- 值映射:保持与原始数据库值一致(如`disability_type='vision'` → `DisabilityType.VISION = 'vision'`)
|
|
|
|
|
+
|
|
|
|
|
+**对后续模块的影响**:
|
|
|
|
|
+1. **disability-module**:使用`DisabilityType`和`DisabilityLevel`枚举
|
|
|
|
|
+2. **order-module**:使用`OrderStatus`和`WorkStatus`枚举
|
|
|
|
|
+3. **其他模块**:根据需要使用相应枚举
|
|
|
|
|
+
|
|
|
|
|
+**优势**:
|
|
|
|
|
+- ✅ 类型安全:编译时检查,避免无效值
|
|
|
|
|
+- ✅ 性能优化:无需数据库查询
|
|
|
|
|
+- ✅ 代码提示:IDE自动补全枚举值
|
|
|
|
|
+- ✅ 维护简单:集中管理,一处修改处处生效
|
|
|
|
|
+- ✅ 版本控制:枚举定义与代码一起版本控制
|
|
|
|
|
+
|
|
|
|
|
+### 故事4:移植残疾人管理模块(disability_person → @d8d/allin-disability-module)
|
|
|
|
|
+**目标**:移植最复杂的模块,处理5个实体和跨模块依赖,完成文件实体集成,使用枚举常量
|
|
|
|
|
+
|
|
|
|
|
+**验收标准**:
|
|
|
|
|
+1. ✅ 创建`allin-packages/disability-module`目录结构
|
|
|
|
|
+2. ✅ 完成实体转换:5个实体(DisabledPerson、DisabledBankCard等)转换
|
|
|
|
|
+3. ✅ **枚举常量集成**:使用`@d8d/allin-enums`包中的`DisabilityType`和`DisabilityLevel`枚举
|
|
|
|
|
+4. ✅ **文件实体集成**:修改`DisabledPhoto`实体,添加`fileId`字段引用`File`实体
|
|
|
|
|
+5. ✅ 处理跨模块依赖:引用order、platform、company、channel模块实体
|
|
|
|
|
+6. ✅ 完成服务层转换:复杂业务逻辑处理,包含文件ID验证逻辑
|
|
|
|
|
+7. ✅ 完成路由层转换:多个控制器转换为Hono路由,API接收`fileId`参数
|
|
|
|
|
+8. ✅ 完成验证系统转换:复杂数据验证,包含文件ID验证和枚举值验证
|
|
|
|
|
+9. ✅ 配置package.json:处理多个依赖,包含对`@d8d/file-module`和`@d8d/allin-enums`的依赖
|
|
|
|
|
+10. ✅ 编写API集成测试:覆盖所有5个实体的管理功能,包含文件ID关联测试和枚举值测试
|
|
|
|
|
+11. ✅ 通过类型检查和基本测试验证
|
|
|
|
|
+12. ✅ 解决与order-module的循环依赖问题
|
|
|
|
|
+
|
|
|
|
|
+**API集成测试要求**:
|
|
|
|
|
+- 测试文件:`tests/integration/disability.integration.test.ts`
|
|
|
|
|
+- 测试覆盖:残疾人信息、银行卡、照片、备注、走访记录
|
|
|
|
|
+- **枚举测试重点**:
|
|
|
|
|
+ - 验证残疾类型枚举值正确性
|
|
|
|
|
+ - 验证残疾等级枚举值正确性
|
|
|
|
|
+ - 测试无效枚举值的错误处理
|
|
|
|
|
+- **文件测试重点**:
|
|
|
|
|
+ - 验证`fileId`参数接收和处理
|
|
|
|
|
+ - 测试文件不存在时的错误处理
|
|
|
|
|
+ - 验证返回数据包含文件URL
|
|
|
|
|
+ - 测试文件关联的业务规则
|
|
|
|
|
+- 验证:多实体关联、复杂业务规则
|
|
|
|
|
+- 包含性能测试:大数据量查询
|
|
|
|
|
+
|
|
|
|
|
+### 故事5:移植订单管理模块(order → @d8d/allin-order-module)
|
|
|
|
|
+**目标**:移植订单模块,处理与disability-module的循环依赖,完成文件实体集成
|
|
|
|
|
+
|
|
|
|
|
+**验收标准**:
|
|
|
|
|
+1. ✅ 创建`allin-packages/order-module`目录结构
|
|
|
|
|
+2. ✅ 完成实体转换:3个实体(EmploymentOrder、OrderPerson等)转换
|
|
|
|
|
+3. ✅ **枚举常量集成**:使用`@d8d/allin-enums`包中的`OrderStatus`和`WorkStatus`枚举
|
|
|
|
|
+4. ✅ **文件实体集成**:修改`OrderPersonAsset`实体,添加`fileId`字段引用`File`实体
|
|
|
|
|
+5. ✅ 解决循环依赖:与disability-module的相互引用处理
|
|
|
|
|
+6. ✅ 完成服务层转换:订单业务逻辑,包含资产文件关联处理
|
|
|
|
|
+7. ✅ 完成路由层转换:Hono路由实现,API接收`fileId`参数
|
|
|
|
|
+8. ✅ 完成验证系统转换:订单相关验证,包含文件类型验证和枚举值验证
|
|
|
|
|
+9. ✅ 配置package.json:依赖管理,包含对`@d8d/file-module`和`@d8d/allin-enums`的依赖
|
|
|
|
|
+10. ✅ 编写API集成测试:覆盖订单全生命周期,包含资产文件关联测试和枚举值测试
|
|
|
|
|
+11. ✅ 通过类型检查和基本测试验证
|
|
|
|
|
+12. ✅ 验证循环依赖解决方案的有效性
|
|
|
|
|
+
|
|
|
|
|
+**API集成测试要求**:
|
|
|
|
|
+- 测试文件:`tests/integration/order.integration.test.ts`
|
|
|
|
|
+- 测试覆盖:订单创建、人员分配、资产关联、状态流转
|
|
|
|
|
+- **枚举测试重点**:
|
|
|
|
|
+ - 验证订单状态枚举值正确性
|
|
|
|
|
+ - 验证工作状态枚举值正确性
|
|
|
|
|
+ - 测试无效枚举值的错误处理
|
|
|
|
|
+ - 验证状态流转的业务规则
|
|
|
|
|
+- **文件测试重点**:
|
|
|
|
|
+ - 测试不同资产类型(tax、salary、contract等)的文件关联
|
|
|
|
|
+ - 验证文件类型(image/video)与资产类型的匹配
|
|
|
|
|
+ - 测试资产文件的上传、查看、删除流程
|
|
|
|
|
+ - 验证文件关联的权限控制
|
|
|
|
|
+- 验证:与disability-module的集成、业务规则执行
|
|
|
|
|
+- 包含工作流测试:订单状态机
|
|
|
|
|
+
|
|
|
|
|
+### 故事6:移植平台管理模块(platform → @d8d/allin-platform-module)
|
|
|
|
|
+**目标**:移植基础模块,作为其他模块的依赖
|
|
|
|
|
+
|
|
|
|
|
+**验收标准**:
|
|
|
|
|
+1. ✅ 创建`allin-packages/platform-module`目录结构
|
|
|
|
|
+2. ✅ 完成实体转换:`Platform`实体转换
|
|
|
|
|
+3. ✅ 完成服务层转换:基础CRUD服务
|
|
|
|
|
+4. ✅ 完成路由层转换:Hono路由实现
|
|
|
|
|
+5. ✅ 完成验证系统转换:Zod Schema定义
|
|
|
|
|
+6. ✅ 配置package.json:作为基础依赖包
|
|
|
|
|
+7. ✅ 编写API集成测试:验证基础功能
|
|
|
|
|
+8. ✅ 通过类型检查和基本测试验证
|
|
|
|
|
+9. ✅ 验证作为依赖包被其他模块引用的能力
|
|
|
|
|
+
|
|
|
|
|
+**API集成测试要求**:
|
|
|
|
|
+- 测试文件:`tests/integration/platform.integration.test.ts`
|
|
|
|
|
+- 测试覆盖:平台CRUD操作
|
|
|
|
|
+- 验证:作为基础数据的完整性和一致性
|
|
|
|
|
+- 包含被引用场景的模拟测试
|
|
|
|
|
+
|
|
|
|
|
+### 故事7:移植薪资管理模块(salary → @d8d/allin-salary-module)
|
|
|
|
|
+**目标**:移植独立模块,完成所有模块移植
|
|
|
|
|
+
|
|
|
|
|
+**验收标准**:
|
|
|
|
|
+1. ✅ 创建`allin-packages/salary-module`目录结构
|
|
|
|
|
+2. ✅ 完成实体转换:`SalaryLevel`实体转换
|
|
|
|
|
+3. ✅ 完成服务层转换:薪资业务逻辑
|
|
|
|
|
+4. ✅ 完成路由层转换:Hono路由实现
|
|
|
|
|
+5. ✅ 完成验证系统转换:Zod Schema定义
|
|
|
|
|
+6. ✅ 配置package.json:独立包配置
|
|
|
|
|
+7. ✅ 编写API集成测试:验证薪资管理功能
|
|
|
|
|
+8. ✅ 通过类型检查和基本测试验证
|
|
|
|
|
+9. ✅ 整体验证:所有7个模块的集成测试
|
|
|
|
|
+
|
|
|
|
|
+**API集成测试要求**:
|
|
|
|
|
+- 测试文件:`tests/integration/salary.integration.test.ts`
|
|
|
|
|
+- 测试覆盖:薪资等级管理、关联查询
|
|
|
|
|
+- 验证:数值计算、等级规则
|
|
|
|
|
+- 包含整体集成测试:验证所有模块协同工作
|
|
|
|
|
+
|
|
|
|
|
+## 文件实体集成方案
|
|
|
|
|
+
|
|
|
|
|
+### 现状分析
|
|
|
|
|
+
|
|
|
|
|
+#### 1. 现有file-module结构
|
|
|
|
|
+- **实体**:`File`(对应`files`表)
|
|
|
|
|
+- **核心功能**:
|
|
|
|
|
+ - MinIO对象存储集成
|
|
|
|
|
+ - 预签名URL生成(`fullUrl`属性)
|
|
|
|
|
+ - 完整的文件元数据管理(名称、类型、大小、描述等)
|
|
|
|
|
+ - 用户关联(上传用户、时间)
|
|
|
|
|
+- **表结构**:`id`, `name`, `type`, `size`, `path`, `description`, `upload_user_id`, `upload_time`等
|
|
|
|
|
+
|
|
|
|
|
+#### 2. allin_system-master中的文件相关实体
|
|
|
|
|
+1. **DisabledPhoto**(`disabled_photo`表):残疾人照片管理
|
|
|
|
|
+ - 字段:`photo_id`, `person_id`, `photo_type`, `photo_url`, `upload_time`, `can_download`
|
|
|
|
|
+ - 直接存储文件URL在`photo_url`字段
|
|
|
|
|
+
|
|
|
|
|
+2. **OrderPersonAsset**(`order_person_asset`表):订单人员资产管理
|
|
|
|
|
+ - 字段:`op_id`, `order_id`, `person_id`, `asset_type`, `asset_file_type`, `asset_url`, `related_time`
|
|
|
|
|
+ - 存储图片/视频URL在`asset_url`字段
|
|
|
|
|
+
|
|
|
|
|
+### 集成方案(采用:实体重构 + UI层统一上传)
|
|
|
|
|
+
|
|
|
|
|
+#### 架构设计原则
|
|
|
|
|
+**核心思想**:文件上传在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. 建立外键关系
|
|
|
|
|
+ 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实体
|
|
|
|
|
+ @Entity('disabled_photo')
|
|
|
|
|
+ export class DisabledPhoto {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ name: 'photo_id' })
|
|
|
|
|
+ photoId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'person_id' })
|
|
|
|
|
+ personId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'file_id' })
|
|
|
|
|
+ fileId!: number; // 引用File实体的ID
|
|
|
|
|
+
|
|
|
|
|
+ @ManyToOne(() => File)
|
|
|
|
|
+ @JoinColumn({ name: 'file_id' })
|
|
|
|
|
+ file!: File; // 关联File实体
|
|
|
|
|
+
|
|
|
|
|
+ // 业务字段保持不变
|
|
|
|
|
+ @Column({ name: 'photo_type' })
|
|
|
|
|
+ photoType!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'can_download' })
|
|
|
|
|
+ canDownload!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'upload_time' })
|
|
|
|
|
+ uploadTime!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ // 关联残疾人实体
|
|
|
|
|
+ @ManyToOne(() => DisabledPerson, person => person.photos)
|
|
|
|
|
+ @JoinColumn({ name: 'person_id' })
|
|
|
|
|
+ person!: DisabledPerson;
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+3. **API接口设计**:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ // 创建残疾人照片的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);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 获取照片详情(包含文件URL)
|
|
|
|
|
+ channelRoutes.get('/photos/:id', async (c) => {
|
|
|
|
|
+ const { id } = c.req.param();
|
|
|
|
|
+ const photo = await disabilityService.getPhotoWithFile(Number(id));
|
|
|
|
|
+
|
|
|
|
|
+ // 构建响应数据,包含文件URL
|
|
|
|
|
+ const response = {
|
|
|
|
|
+ ...photo,
|
|
|
|
|
+ photoUrl: await photo.file.fullUrl, // 通过关联实体获取文件URL
|
|
|
|
|
+ fileName: photo.file.name,
|
|
|
|
|
+ fileSize: photo.file.size,
|
|
|
|
|
+ fileType: photo.file.type
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return c.json(response);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 获取残疾人的所有照片(包含文件信息)
|
|
|
|
|
+ channelRoutes.get('/persons/:personId/photos', async (c) => {
|
|
|
|
|
+ const { personId } = c.req.param();
|
|
|
|
|
+ const photos = await disabilityService.getPersonPhotosWithFiles(Number(personId));
|
|
|
|
|
+
|
|
|
|
|
+ // 为每张照片添加文件URL
|
|
|
|
|
+ const photosWithUrls = await Promise.all(
|
|
|
|
|
+ photos.map(async (photo) => ({
|
|
|
|
|
+ ...photo,
|
|
|
|
|
+ photoUrl: await photo.file.fullUrl,
|
|
|
|
|
+ fileName: photo.file.name,
|
|
|
|
|
+ fileSize: photo.file.size
|
|
|
|
|
+ }))
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return c.json(photosWithUrls);
|
|
|
|
|
+ });
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+4. **服务层实现**:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ export class DisabilityService {
|
|
|
|
|
+ async createPhoto(data: CreateDisabledPhotoDto): Promise<DisabledPhoto> {
|
|
|
|
|
+ // 验证文件是否存在
|
|
|
|
|
+ const file = await this.fileRepository.findOne({
|
|
|
|
|
+ where: { id: data.fileId }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!file) {
|
|
|
|
|
+ throw new Error('文件不存在');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建照片记录(只关联文件ID)
|
|
|
|
|
+ const photo = this.photoRepository.create({
|
|
|
|
|
+ personId: data.personId,
|
|
|
|
|
+ fileId: data.fileId, // 使用UI层提供的文件ID
|
|
|
|
|
+ photoType: data.photoType,
|
|
|
|
|
+ canDownload: data.canDownload
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return this.photoRepository.save(photo);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getPhotoWithFile(photoId: number): Promise<DisabledPhoto> {
|
|
|
|
|
+ // 查询照片时加载关联的File实体
|
|
|
|
|
+ const photo = await this.photoRepository.findOne({
|
|
|
|
|
+ where: { photoId },
|
|
|
|
|
+ relations: ['file'] // 加载关联的File实体
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!photo) {
|
|
|
|
|
+ throw new Error('照片不存在');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return photo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getPhotoUrl(photoId: number): Promise<string> {
|
|
|
|
|
+ const photo = await this.getPhotoWithFile(photoId);
|
|
|
|
|
+
|
|
|
|
|
+ // 通过关联的file实体访问fullUrl属性
|
|
|
|
|
+ // file.fullUrl是File实体中的Promise属性
|
|
|
|
|
+ return await photo.file.fullUrl;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getPersonPhotosWithFiles(personId: number): Promise<DisabledPhoto[]> {
|
|
|
|
|
+ // 查询某个残疾人的所有照片,并加载关联的File实体
|
|
|
|
|
+ return this.photoRepository.find({
|
|
|
|
|
+ where: { personId },
|
|
|
|
|
+ relations: ['file'], // 加载关联的File实体
|
|
|
|
|
+ order: { uploadTime: 'DESC' }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+#### 方案B:服务层集成(备选)
|
|
|
|
|
+**核心思想**:保持表结构不变,在服务层集成FileService
|
|
|
|
|
+
|
|
|
|
|
+**实施步骤**:
|
|
|
|
|
+1. **保持表结构**:不修改`disabled_photo`和`order_person_asset`表
|
|
|
|
|
+2. **服务层集成**:
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ export class DisabilityService {
|
|
|
|
|
+ async getPhotoUrl(photoId: number): Promise<string> {
|
|
|
|
|
+ const photo = await this.getPhoto(photoId);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果photo_url是MinIO路径,使用FileService生成预签名URL
|
|
|
|
|
+ if (this.isMinioPath(photo.photoUrl)) {
|
|
|
|
|
+ const path = this.extractMinioPath(photo.photoUrl);
|
|
|
|
|
+ return this.fileService.getPresignedUrl(path);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 否则返回原始URL
|
|
|
|
|
+ return photo.photoUrl;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+### 采用方案A(实体重构)的理由
|
|
|
|
|
+
|
|
|
|
|
+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`实体,添加`fileId`字段引用`File`实体
|
|
|
|
|
+ - 更新API接口,接收`fileId`参数(而非文件内容)
|
|
|
|
|
+ - 更新服务层,验证文件存在性并关联业务
|
|
|
|
|
+ - 调整API集成测试,验证文件ID关联功能
|
|
|
|
|
+
|
|
|
|
|
+2. **故事5(order-module)**:
|
|
|
|
|
+ - 修改`OrderPersonAsset`实体,添加`fileId`字段引用`File`实体
|
|
|
|
|
+ - 更新API接口,接收`fileId`参数
|
|
|
|
|
+ - 更新服务层,处理资产文件关联逻辑
|
|
|
|
|
+ - 调整测试用例,模拟文件ID关联场景
|
|
|
|
|
+
|
|
|
|
|
+### 实施注意事项
|
|
|
|
|
+
|
|
|
|
|
+1. **数据迁移**:需要制定现有URL数据的迁移方案
|
|
|
|
|
+2. **兼容性**:保持API兼容,逐步迁移
|
|
|
|
|
+3. **性能优化**:
|
|
|
|
|
+ - **关联查询**:使用`relations: ['file']`加载关联实体,避免N+1查询
|
|
|
|
|
+ - **选择性加载**:根据需要选择加载的关联字段
|
|
|
|
|
+ - **分页查询**:大数据量时使用分页,避免一次性加载所有关联数据
|
|
|
|
|
+4. **查询模式**:
|
|
|
|
|
+ - 通过关联的`file`实体访问文件属性(`file.fullUrl`、`file.name`等)
|
|
|
|
|
+ - 不需要在业务实体中定义重复的文件URL属性
|
|
|
|
|
+ - 使用TypeORM的`relations`选项加载关联数据
|
|
|
|
|
+5. **错误处理**:
|
|
|
|
|
+ - 验证`fileId`对应的文件是否存在
|
|
|
|
|
+ - 处理文件服务不可用的情况
|
|
|
|
|
+ - 处理关联实体加载失败的情况
|
|
|
|
|
+
|
|
|
|
|
+## 兼容性要求
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 现有API接口保持不变
|
|
|
|
|
+- [ ] 数据库schema保持向后兼容(通过迁移方案)
|
|
|
|
|
+- [ ] 遵循现有TypeScript配置和构建模式
|
|
|
|
|
+- [ ] 性能影响最小化
|
|
|
|
|
+- [ ] 文件功能与现有file-module集成
|
|
|
|
|
+
|
|
|
|
|
+## 风险缓解
|
|
|
|
|
+
|
|
|
|
|
+- **主要风险**:模块间依赖关系复杂,移植过程中可能破坏现有功能
|
|
|
|
|
+- **缓解措施**:逐个模块移植,每个模块完成后进行功能验证
|
|
|
|
|
+- **回滚计划**:保留原始allin_system-master目录作为备份,可随时恢复
|
|
|
|
|
+
|
|
|
|
|
+## 完成定义
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 所有4个故事完成,验收标准满足
|
|
|
|
|
+- [ ] 现有功能通过测试验证
|
|
|
|
|
+- [ ] 集成点正常工作
|
|
|
|
|
+- [ ] 文档更新适当
|
|
|
|
|
+- [ ] 现有功能无回归
|
|
|
|
|
+
|
|
|
|
|
+## 验证清单
|
|
|
|
|
+
|
|
|
|
|
+### 范围验证
|
|
|
|
|
+- [ ] 史诗可在4个故事内完成
|
|
|
|
|
+- [ ] 不需要架构文档变更
|
|
|
|
|
+- [ ] 增强遵循现有模式
|
|
|
|
|
+- [ ] 集成复杂度可管理
|
|
|
|
|
+
|
|
|
|
|
+### 风险评估
|
|
|
|
|
+- [ ] 对现有系统风险较低
|
|
|
|
|
+- [ ] 回滚计划可行
|
|
|
|
|
+- [ ] 测试方法覆盖现有功能
|
|
|
|
|
+- [ ] 团队对集成点有足够了解
|
|
|
|
|
+
|
|
|
|
|
+### 完整性检查
|
|
|
|
|
+- [ ] 史诗目标清晰可实现
|
|
|
|
|
+- [ ] 故事范围适当
|
|
|
|
|
+- [ ] 成功标准可衡量
|
|
|
|
|
+- [ ] 依赖关系已识别
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 故事经理交接说明
|
|
|
|
|
+
|
|
|
|
|
+"模块分析和技术栈分析已完成,关键发现:
|
|
|
|
|
+
|
|
|
|
|
+1. **依赖关系分析完成**:7个模块的依赖关系已明确,发现order和disability_person模块存在循环依赖
|
|
|
|
|
+2. **技术栈差异分析完成**:源系统使用NestJS,目标系统使用Hono,存在重大架构差异
|
|
|
|
|
+3. **命名方案确定**:使用`@d8d/allin-`前缀,`-module`后缀,非多租户版本
|
|
|
|
|
+4. **目录结构**:在根目录创建`allin-packages/`目录存放专属包
|
|
|
|
|
+5. **移植顺序建议**:platform → channel/dict/salary → company → order/disability
|
|
|
|
|
+6. **技术栈转换方案**:已制定从NestJS到Hono的详细转换策略
|
|
|
|
|
+7. **故事拆分完成**:按模块拆分为7个故事,每个故事包含API集成测试要求
|
|
|
|
|
+
|
|
|
|
|
+**新的故事拆分方案**:
|
|
|
|
|
+- **故事1**:移植渠道管理模块(channel_info → @d8d/allin-channel-module)
|
|
|
|
|
+- **故事2**:移植公司管理模块(company → @d8d/allin-company-module)
|
|
|
|
|
+- **故事3**:移植字典管理模块(dict_management → @d8d/allin-dict-module)
|
|
|
|
|
+- **故事4**:移植残疾人管理模块(disability_person → @d8d/allin-disability-module)
|
|
|
|
|
+- **故事5**:移植订单管理模块(order → @d8d/allin-order-module)
|
|
|
|
|
+- **故事6**:移植平台管理模块(platform → @d8d/allin-platform-module)
|
|
|
|
|
+- **故事7**:移植薪资管理模块(salary → @d8d/allin-salary-module)
|
|
|
|
|
+
|
|
|
|
|
+**每个故事的关键要求**:
|
|
|
|
|
+1. **技术栈转换**:必须完成实体、服务、路由、验证的完整转换
|
|
|
|
|
+2. **API集成测试**:必须编写`tests/integration/{module}.integration.test.ts`文件
|
|
|
|
|
+3. **测试覆盖**:必须覆盖所有路由端点,验证CRUD操作
|
|
|
|
|
+4. **遵循现有模式**:参考advertisements-module的集成测试模式
|
|
|
|
|
+5. **验证要求**:认证、授权、数据验证、错误处理
|
|
|
|
|
+
|
|
|
|
|
+**执行顺序建议**:
|
|
|
|
|
+1. 先执行**故事6**(platform-module):基础依赖模块
|
|
|
|
|
+2. 然后执行**故事1、3、7**(channel、dict、salary):独立模块
|
|
|
|
|
+3. 接着执行**故事2**(company-module):依赖platform
|
|
|
|
|
+4. 最后执行**故事4、5**(disability、order):处理循环依赖
|
|
|
|
|
+
|
|
|
|
|
+**技术栈转换关键点**:
|
|
|
|
|
+- **NestJS控制器 → Hono路由**:使用OpenAPIHono
|
|
|
|
|
+- **class-validator DTO → Zod Schema**:使用z.object()定义
|
|
|
|
|
+- **自定义Service → GenericCrudService继承**:复用现有CRUD模式
|
|
|
|
|
+- **下划线命名 → 驼峰命名**:实体字段名转换
|
|
|
|
|
+- **模块间依赖**:通过workspace依赖管理
|
|
|
|
|
+
|
|
|
|
|
+**API集成测试模板参考**:
|
|
|
|
|
+参考`/packages/advertisements-module/tests/integration/advertisements.integration.test.ts`
|
|
|
|
|
+- 使用`testClient`创建测试客户端
|
|
|
|
|
+- 使用`setupIntegrationDatabaseHooksWithEntities`设置测试数据库
|
|
|
|
|
+- 包含认证测试、数据验证测试、错误处理测试
|
|
|
|
|
+- 每个端点都要有成功和失败场景测试
|
|
|
|
|
+
|
|
|
|
|
+**文件实体集成方案**:
|
|
|
|
|
+发现allin_system-master中有文件相关实体(DisabledPhoto、OrderPersonAsset),需要与现有file-module集成。
|
|
|
|
|
+
|
|
|
|
|
+**采用方案**:实体重构 + 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表
|
|
|
|
|
+
|
|
|
|
|
+**实施要点**:
|
|
|
|
|
+- UI层使用现有文件选择器组件上传文件
|
|
|
|
|
+- 后端API只接收文件ID,不处理文件上传
|
|
|
|
|
+- 保持API兼容性,制定数据迁移方案
|
|
|
|
|
+- 注意N+1查询性能,合理使用数据加载策略
|
|
|
|
|
+
|
|
|
|
|
+史诗应在保持系统完整性的同时实现将有实体模块从NestJS架构移植到Hono架构的标准化独立包,每个模块都要有完整的API集成测试验证,并完成与现有file-module的文件集成。"
|