# 史诗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) {} async createChannel(data: CreateChannelDto): Promise { // 自定义逻辑 } } // 转换后服务(Hono + GenericCrudService) export class ChannelService extends GenericCrudService { constructor(dataSource: DataSource) { super(dataSource, Channel, { searchFields: ['channelName', 'contactPerson'], }); } // 保留特殊业务方法 async createChannel(data: CreateChannelDto): Promise { // 复用或重写逻辑 } } ``` ##### 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; ``` #### 依赖关系解决方案 针对发现的循环依赖问题(order ↔ disability_person),解决方案: 1. **创建共享实体包**:将相互引用的实体提取到共享包 2. **接口抽象**:使用接口而非具体实体引用 3. **依赖注入调整**:重构服务层减少直接实体依赖 4. **移植顺序策略**:按依赖顺序移植,先移植基础模块 **建议移植顺序**: 1. ✅ `allin-platform-module`(基础) - **已完成** 2. ✅ `allin-channel-module`(独立) - **已完成** 3. ✅ `allin-enums`(枚举常量包) - **已完成** 4. ✅ `allin-company-module`(依赖platform) - **已完成** 5. `allin-salary-module`(独立) 6. `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的集成、数据关联查询 - 包含跨模块数据一致性测试 **完成情况**: - ✅ 已创建完整的模块结构和配置文件 - ✅ 已完成实体转换:`Company`实体,配置复合唯一索引`idx_company_name_platform` - ✅ 已完成服务层转换:`CompanyService`继承`GenericCrudService`,覆盖`create`、`update`方法实现业务逻辑 - ✅ 已完成路由层转换:实现7个API端点,保持原始布尔返回值格式 - ✅ 已完成验证系统转换:从class-validator DTO转换为Zod Schema - ✅ 已编写15个集成测试,全部通过,特别测试与platform-module的集成 - ✅ 类型检查通过,修复了导出冲突、GenericCrudService配置等问题 - ✅ 修复了复合索引配置错误(从字段级别移到实体级别) - ✅ 修复了服务层错误处理逻辑(重新抛出错误让路由层处理) - ✅ 修复了测试类型安全问题(避免使用`as any`) - ✅ 测试性能:所有测试在18.58秒内完成 ## 设计决策:字典管理模块重构为枚举常量包 ### 背景分析 经过对`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`目录结构 - ✅ 已定义4个枚举类型,值与数据库`sys_dict`表完全一致: - `DisabilityType`:7种残疾类型(vision, hearing, speech, physical, intellectual, mental, multiple) - `DisabilityLevel`:4个残疾等级(1, 2, 3, 4) - `OrderStatus`:5种订单状态(draft, confirmed, in_progress, completed, cancelled) - `WorkStatus`:4种工作状态(not_working, pre_working, working, resigned) - ✅ 已添加辅助函数和标签映射: - `DISABILITY_TYPES`:残疾类型数组 - `DISABILITY_LEVELS`:残疾等级数组(过滤掉反向映射值) - `ORDER_STATUSES`:订单状态数组 - `WORK_STATUSES`:工作状态数组 - `DisabilityTypeLabelMap`:残疾类型中文标签映射 - `DisabilityLevelLabelMap`:残疾等级中文标签映射 - `OrderStatusLabelMap`:订单状态中文标签映射 - `WorkStatusLabelMap`:工作状态中文标签映射 - ✅ 已配置`package.json`:包名`@d8d/allin-enums`,`"type": "module"`,workspace依赖 - ✅ 已编写17个单元测试,覆盖所有枚举和辅助函数,测试通过率100% - ✅ 已修复测试问题:处理数字枚举的反向映射(`Object.values()`返回键和值) - ✅ 已添加`@vitest/coverage-v8`依赖,测试覆盖率100% - ✅ 类型检查通过,无任何错误 - ✅ 已更新故事文件状态为"Ready for Review" **技术实现要求**: - 包位置:`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自动补全枚举值 - ✅ 维护简单:集中管理,一处修改处处生效 - ✅ 版本控制:枚举定义与代码一起版本控制 ## 设计决策:区域数据统一使用@d8d/geo-areas包 ### 背景分析 经过对Allin系统模块的分析,发现以下模块使用行政区划数据: 1. **残疾人管理模块** (`disability_person`): - 存储`province`、`city`、`district`字段(字符串类型) - 用于记录残疾人的户籍地址和居住地址 2. **薪资管理模块** (`salary`): - 存储`province`、`city`、`district`字段(字符串类型) - 用于按地区管理薪资水平,有唯一性约束`(province, city)` ### 问题分析 当前实现存在以下问题: 1. **数据不一致**:字符串存储,容易输入错误或格式不一致 2. **缺乏验证**:没有验证区域数据的有效性 3. **维护困难**:区域变更需要手动更新所有相关记录 4. **重复功能**:与已有的`@d8d/geo-areas`包功能重叠 ### 设计决策 **采用方案A:完全依赖区域包**,将所有区域数据改为引用`@d8d/geo-areas`包中的`AreaEntity`。 ### 改造方案 1. **残疾人管理模块改造**: - 移除`province`、`city`、`district`字符串字段 - 添加`provinceId`、`cityId`、`districtId`字段(外键引用`AreaEntity`) - 使用`AreaService`验证区域数据的有效性和层级关系 2. **薪资管理模块改造**: - 移除`province`、`city`、`district`字符串字段 - 添加`provinceId`、`cityId`、`districtId`字段(外键引用`AreaEntity`) - 修改唯一性约束为`(provinceId, cityId)` - 使用区域包API获取区域完整信息 3. **API兼容性处理**: - 创建DTO转换器,支持前端传递区域名称自动转换为ID - 在响应中返回区域完整信息(名称+ID+层级) - 提供区域查询API,支持按名称搜索区域 ### 优势 - ✅ **数据一致性**:统一使用区域包数据,避免数据不一致 - ✅ **类型安全**:外键引用提供编译时检查 - ✅ **维护简单**:区域变更只需更新区域包数据 - ✅ **功能完整**:利用区域包的树形结构、层级验证等功能 - ✅ **性能优化**:减少重复数据存储,提高查询效率 ### 故事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. ✅ **银行卡文件集成**:修改`DisabledBankCard`实体,添加`fileId`字段引用`File`实体 6. ✅ 处理跨模块依赖:引用order、platform、company、channel模块实体 7. ✅ 完成服务层转换:复杂业务逻辑处理,包含文件ID验证逻辑 8. ✅ 完成路由层转换:多个控制器转换为Hono路由,API接收`fileId`参数 9. ✅ 完成验证系统转换:复杂数据验证,包含文件ID验证、枚举值验证 10. ✅ 配置package.json:处理多个依赖,包含对`@d8d/file-module`、`@d8d/allin-enums`的依赖 11. ✅ 编写API集成测试:覆盖所有5个实体的管理功能,包含文件ID关联测试、枚举值测试 12. ✅ 通过类型检查和基本测试验证 13. ✅ 修复TypeScript类型错误和PostgreSQL类型兼容问题 14. ✅ 修复schema验证错误(实体字段与schema定义匹配) 15. ✅ 修复测试数据完整性和错误处理逻辑 **完成情况**: - ✅ 已创建完整的模块结构和配置文件 - ✅ 已完成5个实体转换:`DisabledPerson`、`DisabledBankCard`、`DisabledPhoto`、`DisabledRemark`、`DisabledVisit` - ✅ 已完成文件模块集成:照片和银行卡实体使用`fileId`字段引用`File`实体 - ✅ 已完成服务层转换:`DisabledPersonService`继承`GenericCrudService`,`AggregatedService`处理聚合业务逻辑 - ✅ 已完成路由层转换:实现22个API端点,包含聚合创建和查询功能 - ✅ 已完成验证系统转换:从class-validator DTO转换为Zod Schema,包含文件ID验证 - ✅ 已编写22个集成测试,全部通过,覆盖所有API端点和业务逻辑 - ✅ 修复了TypeScript类型错误:PostgreSQL类型兼容问题(tinyint → smallint,datetime → timestamp) - ✅ 修复了schema验证错误:实体字段与schema定义不匹配(createTime/updateTime → uploadTime/remarkTime/visitTime) - ✅ 修复了测试数据问题:缺少subBankName、operatorId等必填字段 - ✅ 修复了错误处理:DELETE端点返回404而不是200,GET聚合端点返回404而不是500 - ✅ 修复了文件模块集成:银行卡实体使用fileId字段而不是cardPhotoUrl - ✅ 移除不存在的education字段,保持与原始数据表结构一致 - ✅ 类型检查通过,所有22个测试通过 - ✅ 清理了调试信息,恢复了干净的测试代码 **API集成测试要求**: - 测试文件:`tests/integration/disability.integration.test.ts` - 测试覆盖:残疾人信息、银行卡、照片、备注、走访记录 - **枚举测试重点**: - 验证残疾类型枚举值正确性 - 验证残疾等级枚举值正确性 - 测试无效枚举值的错误处理 - **区域测试重点**: - 验证区域ID的有效性检查 - 测试区域层级关系验证(省→市→区) - 验证区域不存在时的错误处理 - 测试区域数据在响应中的完整性 - **文件测试重点**: - 验证`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. ✅ 验证循环依赖解决方案的有效性 **完成情况**: - ✅ 已创建完整的模块结构和配置文件 - ✅ 已完成3个实体转换:`EmploymentOrder`、`OrderPerson`、`OrderPersonAsset` - ✅ 已完成文件模块集成:`OrderPersonAsset`实体使用`fileId`字段引用`File`实体 - ✅ 已完成服务层转换:`OrderService`继承`GenericCrudService`,处理订单业务逻辑和文件关联 - ✅ 已完成路由层转换:实现31个API端点,包含订单全生命周期管理 - ✅ 已完成验证系统转换:从class-validator DTO转换为Zod Schema,包含文件ID验证和枚举验证 - ✅ 已编写31个集成测试,全部通过,覆盖所有API端点和业务逻辑 - ✅ 修复了循环依赖问题:使用`personId: number`代替`DisabledPerson`直接引用 - ✅ 修复了类型检查问题:处理枚举值类型、实体创建类型、Schema设计等问题 - ✅ 修复了测试问题:GET /order/detail/:id返回500错误、错误处理顺序问题、资产类型枚举值不匹配问题 - ✅ 创建了测试数据工厂`OrderTestDataFactory`,减少测试代码重复 - ✅ 类型检查通过,所有31个测试通过 - ✅ 代码已提交,故事文档已更新为"Completed"状态 **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操作 - 验证:作为基础数据的完整性和一致性 - 包含被引用场景的模拟测试 **完成情况**: - ✅ 已创建完整的模块结构和配置文件 - ✅ 已完成实体转换:`Platform`实体从下划线命名转换为驼峰命名,添加详细TypeORM配置 - ✅ 已完成服务层转换:继承`GenericCrudService`,覆盖create、update、delete方法,添加平台名称唯一性检查 - ✅ 已完成路由层转换:实现6个自定义Hono路由,保持原始API端点路径和功能 - ✅ 已完成验证系统转换:从class-validator DTO转换为Zod Schema,添加详细验证规则 - ✅ 已编写16个集成测试,全部通过,覆盖所有API端点和业务逻辑 - ✅ 类型检查通过,修复了Platform类型重复定义、relationFields配置错误等问题 - ✅ 测试性能:所有测试在32.52秒内完成 - ✅ 验证了作为基础依赖包的能力 ### 故事7:移植薪资管理模块(salary → @d8d/allin-salary-module)✅ **已完成** **目标**:移植独立模块,完成所有模块移植 **验收标准**: 1. ✅ 创建`allin-packages/salary-module`目录结构 2. ✅ 完成实体转换:`SalaryLevel`实体转换,添加区域ID字段 3. ✅ **区域包集成**:使用`@d8d/geo-areas`包管理区域数据 4. ✅ 完成服务层转换:薪资业务逻辑,包含区域数据验证 5. ✅ 完成路由层转换:Hono路由实现,支持区域ID参数 6. ✅ 完成验证系统转换:Zod Schema定义,包含区域ID验证 7. ✅ 配置package.json:依赖管理,包含对`@d8d/geo-areas`的依赖 8. ✅ 编写API集成测试:验证薪资管理功能,包含区域数据测试 9. ✅ 通过类型检查和基本测试验证 10. ✅ 整体验证:所有7个模块的集成测试 **API集成测试要求**: - 测试文件:`tests/integration/salary.integration.test.ts` - 测试覆盖:薪资等级管理、关联查询 - **区域测试重点**: - 验证区域ID的有效性检查 - 测试区域层级关系验证(省→市→区) - 验证区域不存在时的错误处理 - 测试区域唯一性约束`(provinceId, cityId)` - 验证区域数据在响应中的完整性 - 验证:数值计算、等级规则 - 包含整体集成测试:验证所有模块协同工作 **完成情况**: - ✅ 已创建完整的模块结构和配置文件 - ✅ 已完成实体转换:`SalaryLevel`实体,配置复合唯一索引`UQ_51cd5d2613663c210df246f183c`(provinceId, cityId) - ✅ 已完成服务层转换:`SalaryService`继承`GenericCrudService`,覆盖`create`、`update`方法实现业务逻辑,包含区域验证和唯一性检查 - ✅ 已完成路由层转换:实现6个API端点,支持区域ID参数和查询 - ✅ 已完成验证系统转换:从class-validator DTO转换为Zod Schema,包含区域ID验证 - ✅ 已编写16个集成测试,全部通过,特别测试与geo-areas包的集成和区域唯一性约束 - ✅ 类型检查通过,修复了Schema验证问题、Decimal字段处理、路由冲突等问题 - ✅ 修复了测试数据问题:添加第二个省份和城市测试数据,避免唯一性约束冲突 - ✅ 修复了计算逻辑问题:在`calculateTotalSalary`方法中添加字符串转换逻辑,正确处理数据库返回的decimal字符串格式 - ✅ 修复了Schema验证问题:将`basicSalary`、`totalSalary`、`allowance`、`insurance`、`housingFund`字段改为`z.coerce.number()`,正确处理字符串到数字的转换 - ✅ 修复了区域字段验证:将`districtId`字段改为`.nullable().optional()`,`district`字段改为`.nullable().optional()`,处理null值 - ✅ 测试性能:所有测试在21.07秒内完成 - ✅ 清理了调试信息,恢复了CRUD路由聚合 ### 故事8:将Allin系统模块集成到packages/server(已完成) **目标**:将所有移植的Allin模块集成到主server中,配置路由和依赖 **验收标准**: 1. ✅ 在server的package.json中添加所有Allin模块的workspace依赖 2. ✅ 在server/src/index.ts中导入并注册所有Allin模块的路由 3. ✅ 在数据库初始化中注册所有Allin模块的实体 4. ✅ 验证所有API端点可以通过server访问 5. ✅ 在server/tests/integration/中添加Allin模块的集成测试,只测试API路由连通性(不重复测试业务逻辑) 6. ✅ 通过类型检查和基本测试验证 7. ✅ 确保与现有模块的兼容性 **API集成测试要求**: - 测试文件:`packages/server/tests/integration/allin-modules.integration.test.ts` - 测试覆盖:只测试API路由连通性,不重复测试业务逻辑 - 验证:路由注册正确性、数据库实体初始化、API端点连通性 - 测试范围:6个Allin模块的关键列表端点连通性 **完成情况**: - ✅ **依赖配置**:在`packages/server/package.json`中添加了所有7个Allin模块的workspace依赖 - ✅ **路由注册**:在`packages/server/src/index.ts`中导入并注册了所有Allin模块的路由: ```typescript export const channelApiRoutes = api.route('/api/v1/channel', channelRoutes) export const companyApiRoutes = api.route('/api/v1/company', companyRoutes) export const disabilityApiRoutes = api.route('/api/v1/disability', disabledPersonRoutes) export const orderApiRoutes = api.route('/api/v1/order', orderRoutes) export const platformApiRoutes = api.route('/api/v1/platform', platformRoutes) export const salaryApiRoutes = api.route('/api/v1/salary', salaryRoutes) ``` - ✅ **实体注册**:在数据库初始化中添加了所有Allin模块的实体 - ✅ **集成测试**:创建了`allin-modules.integration.test.ts`文件,包含6个模块的路由连通性测试 - ✅ **测试通过**:所有6个集成测试全部通过,验证了API端点连通性 - ✅ **类型检查**:集成测试文件无类型错误(项目整体依赖问题不影响Allin模块集成) - ✅ **兼容性**:与现有模块兼容,不影响现有功能 **修复的问题**: 1. **依赖导入错误**:`disabilityRoutes`不存在,修复为`disabledPersonRoutes` 2. **实体导入错误**:`Company`被当作类型使用,修复为从`/entities`路径导入 3. **测试语法错误**:`$get()`需要`query: {}`参数,修复所有模块的测试调用 4. **端点名称错误**:`getAllOrders`和`getAllSalaryLevels`端点不存在,修复为`list`端点 **测试结果**: - 渠道管理模块路由连通性测试 ✅ - 公司管理模块路由连通性测试 ✅ - 残疾人管理模块路由连通性测试 ✅ - 订单管理模块路由连通性测试 ✅ - 平台管理模块路由连通性测试 ✅ - 薪资管理模块路由连通性测试 ✅ ## 文件实体集成方案 ### 现状分析 #### 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 { // 验证文件是否存在 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 { // 查询照片时加载关联的File实体 const photo = await this.photoRepository.findOne({ where: { photoId }, relations: ['file'] // 加载关联的File实体 }); if (!photo) { throw new Error('照片不存在'); } return photo; } async getPhotoUrl(photoId: number): Promise { const photo = await this.getPhotoWithFile(photoId); // 通过关联的file实体访问fullUrl属性 // file.fullUrl是File实体中的Promise属性 return await photo.file.fullUrl; } async getPersonPhotosWithFiles(personId: number): Promise { // 查询某个残疾人的所有照片,并加载关联的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 { 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目录作为备份,可随时恢复 ## 完成定义 - [x] 所有7个模块移植故事完成,验收标准满足 - ✅ 故事1:渠道管理模块(channel-module)已完成 - ✅ 故事2:公司管理模块(company-module)已完成 - ✅ 故事3:枚举常量包(allin-enums)已完成 - ✅ 故事6:平台管理模块(platform-module)已完成 - ✅ 故事4:残疾人管理模块(disability-module)已完成 - ✅ 故事5:订单管理模块(order-module)已完成 - ✅ 故事7:薪资管理模块(salary-module)已完成 - [x] 故事8:集成到packages/server(已完成) - [x] 现有功能通过测试验证 - [x] 集成点正常工作 - [x] 文档更新适当 - [x] 现有功能无回归 ## 验证清单 ### 范围验证 - [ ] 史诗可在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) - **故事8**:将Allin系统模块集成到packages/server **每个故事的关键要求**: 1. **技术栈转换**:必须完成实体、服务、路由、验证的完整转换 2. **API集成测试**:必须编写`tests/integration/{module}.integration.test.ts`文件 3. **测试覆盖**:必须覆盖所有路由端点,验证CRUD操作 4. **遵循现有模式**:参考advertisements-module的集成测试模式 5. **验证要求**:认证、授权、数据验证、错误处理 **执行顺序建议**: 1. ✅ 先执行**故事6**(platform-module):基础依赖模块 - **已完成** 2. ✅ 然后执行**故事1**(channel-module):独立模块 - **已完成** 3. ✅ 然后执行**故事3**(allin-enums):枚举常量包 - **已完成** 4. ✅ 接着执行**故事2**(company-module):依赖platform - **已完成** 5. ✅ 然后执行**故事7**(salary-module):独立模块 - **已完成** 6. ✅ 最后执行**故事4、5**(disability、order):处理循环依赖 - **已完成** 7. ✅ 最后执行**故事8**(集成到server):将所有模块集成到packages/server - **已完成** **技术栈转换关键点**: - **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查询性能,合理使用数据加载策略 **新增故事8说明**: - **故事8**:将Allin系统模块集成到packages/server - **目标**:将所有移植的Allin模块集成到主server中,配置路由和依赖 - **关键任务**: 1. 在server的package.json中添加所有Allin模块的workspace依赖 2. 在server/src/index.ts中导入并注册所有Allin模块的路由 3. 在数据库初始化中注册所有Allin模块的实体 4. 验证所有API端点可以通过server访问 - **集成路径**:所有模块使用`/api/v1/{module}`路径前缀 - **认证要求**:所有端点通过现有认证中间件保护 史诗应在保持系统完整性的同时实现将有实体模块从NestJS架构移植到Hono架构的标准化独立包,每个模块都要有完整的API集成测试验证,并完成与现有file-module的文件集成,最后将所有模块集成到packages/server中。"