# 后端模块包规范 ## 版本信息 | 版本 | 日期 | 描述 | 作者 | |------|------|------|------| | 1.0 | 2025-12-02 | 基于史诗007系列移植经验创建 | Claude Code | ## 概述 本文档定义了后端模块包的设计、开发和集成规范,基于史诗007系列(Allin系统模块移植)的实际经验总结。这些规范旨在确保模块包的一致性、可维护性和可集成性。 ## 1. 包结构规范 ### 1.1 目录结构 ``` allin-packages/{module-name}-module/ ├── package.json # 包配置 ├── tsconfig.json # TypeScript配置 ├── vitest.config.ts # 测试配置 ├── src/ │ ├── entities/ # 实体定义 │ │ └── {entity-name}.entity.ts │ ├── services/ # 服务层 │ │ └── {service-name}.service.ts │ ├── routes/ # 路由层 │ │ ├── {module}-custom.routes.ts # 自定义路由 │ │ ├── {module}-crud.routes.ts # CRUD路由 │ │ └── {module}.routes.ts # 主路由 │ ├── schemas/ # 验证Schema │ │ └── {schema-name}.schema.ts │ ├── types/ # 类型定义 │ │ └── index.ts │ └── index.ts # 包入口 └── tests/ └── integration/ # 集成测试 └── {module}.integration.test.ts ``` ### 1.2 包命名规范 - **前缀**: `@d8d/allin-` - **后缀**: `-module` - **示例**: `@d8d/allin-channel-module`, `@d8d/allin-platform-module` ### 1.3 workspace配置 ```yaml # pnpm-workspace.yaml packages: - 'allin-packages/*' # 自动包含所有allin包 - 'allin-packages/{module-name}-module' # 或显式指定 ``` ## 2. 实体设计规范 ### 2.1 主键命名 ```typescript // ✅ 正确:使用id作为主键名 @PrimaryGeneratedColumn({ name: 'channel_id' }) id!: number; // ❌ 错误:使用特定名称 @PrimaryGeneratedColumn({ name: 'channel_id' }) channelId!: number; ``` ### 2.2 字段命名转换 ```typescript // 数据库下划线命名 → TypeScript驼峰命名 @Column({ name: 'channel_name' }) channelName!: string; @Column({ name: 'contact_person' }) contactPerson!: string; @Column({ name: 'create_time' }) createTime!: Date; ``` ### 2.3 唯一性约束 ```typescript // 单字段唯一性 @Unique(['channelName']) // 复合字段唯一性(如公司名称在同一平台下唯一) @Unique(['companyName', 'platformId']) ``` ### 2.4 关联关系配置 ```typescript // 多对一关系(如公司关联平台) @ManyToOne(() => Platform, { eager: true }) @JoinColumn({ name: 'platform_id' }) platform!: Platform; // 一对多关系 @OneToMany(() => Company, company => company.platform) companies!: Company[]; ``` ### 2.5 软删除实现 ```typescript @Column({ name: 'status', type: 'tinyint', default: 1, comment: '状态:0-删除,1-正常' }) status!: number; ``` ## 3. 数据库类型规范 ### 3.1 PostgreSQL类型兼容 ```typescript // 源类型 → 目标类型 @Column({ name: 'some_flag', type: 'tinyint' }) // tinyint → smallint someFlag!: number; @Column({ name: 'create_time', type: 'datetime' }) // datetime → timestamp createTime!: Date; ``` ### 3.2 Decimal字段处理 ```typescript // 实体定义 @Column({ name: 'total_amount', type: 'decimal', precision: 10, scale: 2 }) totalAmount!: number; // Schema验证(使用z.coerce.number()处理字符串) const CreateSchema = z.object({ totalAmount: z.coerce.number().min(0), }); ``` ### 3.3 枚举值一致性 ```typescript // 保持与数据库值一致(小写字符串,下划线分隔) enum OrderStatus { DRAFT = 'draft', CONFIRMED = 'confirmed', IN_PROGRESS = 'in_progress', COMPLETED = 'completed', CANCELLED = 'cancelled' } // 数字枚举 enum DisabilityLevel { ONE = 1, TWO = 2, THREE = 3, FOUR = 4 } ``` ## 4. 服务层规范 ### 4.1 GenericCrudService继承 ```typescript export class ChannelService extends GenericCrudService { constructor(dataSource: DataSource) { super(dataSource, Channel); } } ``` ### 4.2 方法覆盖模式 ```typescript // 覆盖create方法:添加唯一性检查 async create(data: CreateChannelDto): Promise { // 业务逻辑:检查名称唯一性 const existing = await this.repository.findOne({ where: { channelName: data.channelName } }); if (existing) { throw new Error('渠道名称已存在'); } return super.create(data); } // 覆盖findAll方法:返回标准格式 async findAll(options?: FindManyOptions): Promise<{ data: Channel[], total: number }> { const [data, total] = await this.repository.findAndCount(options); return { data, total }; } ``` ### 4.3 自定义业务方法 ```typescript // 按名称搜索 async searchByName(name: string): Promise { return this.repository.find({ where: { channelName: Like(`%${name}%`) } }); } ``` ## 5. 路由层规范 ### 5.1 路由聚合模式 ```typescript // 主路由文件:聚合自定义路由和CRUD路由 import customRoutes from './channel-custom.routes'; import crudRoutes from './channel-crud.routes'; const channelRoutes = new Hono() .basePath('/channels') .route('/', customRoutes) // 自定义业务逻辑路由 .route('/', crudRoutes); // 标准CRUD路由 export default channelRoutes; ``` ### 5.2 API兼容性 ```typescript // 保持与原始NestJS API相同的端点路径和功能 // 原始:POST /channel/createChannel // 新:POST /channels/createChannel app.post('/createChannel', async (c) => { // 实现逻辑 }); ``` ### 5.3 布尔返回值处理 ```typescript // 正确处理布尔返回值 app.post('/create', async (c) => { try { const result = await service.create(data); return c.json({ success: true }, 200); } catch (error) { return c.json({ success: false, message: error.message || '创建失败' }, 400); } }); ``` ### 5.4 错误信息明确 ```typescript // 提供明确的错误信息 app.put('/update/:id', async (c) => { const id = parseInt(c.req.param('id')); const data = await c.req.json(); try { const result = await service.update(id, data); return c.json({ success: true }); } catch (error) { return c.json({ success: false, message: error.message || '平台不存在或名称重复' }, 400); } }); ``` ## 6. 验证系统规范 ### 6.1 Zod Schema定义 ```typescript // 创建Schema const CreateChannelSchema = z.object({ channelName: z.string().min(1).max(100), contactPerson: z.string().max(50).optional(), contactPhone: z.string().max(20).optional(), status: z.number().int().min(0).max(1).default(1), }); // 更新Schema(id通过路径参数传递,不在body中) const UpdateChannelSchema = CreateChannelSchema.partial(); // 查询Schema const QueryChannelSchema = z.object({ channelName: z.string().optional(), skip: z.coerce.number().int().min(0).default(0), take: z.coerce.number().int().min(1).max(100).default(10), }); ``` ### 6.2 枚举验证 ```typescript import { OrderStatus, WorkStatus } from '@d8d/allin-enums'; const CreateOrderSchema = z.object({ orderStatus: z.nativeEnum(OrderStatus).default(OrderStatus.DRAFT), workStatus: z.nativeEnum(WorkStatus).default(WorkStatus.NOT_WORKING), }); ``` ## 7. 模块集成规范 ### 7.1 文件模块集成 ```typescript // 使用fileId字段而非URL字段 @Column({ name: 'file_id', type: 'int', nullable: false, comment: '文件ID,引用files表' }) fileId!: number; @ManyToOne(() => File) @JoinColumn({ name: 'file_id' }) file!: File; ``` ### 7.2 循环依赖处理 ```typescript // 原代码(有循环依赖): // @ManyToOne(() => DisabledPerson) // person!: DisabledPerson; // 新代码(解耦): @Column({ name: 'person_id', type: 'int', nullable: false, comment: '人员ID' }) personId!: number; ``` ### 7.3 依赖配置 ```json { "name": "@d8d/allin-company-module", "dependencies": { "@d8d/allin-platform-module": "workspace:*", "@d8d/file-module": "workspace:*", "@d8d/allin-enums": "workspace:*" } } ``` ### 7.4 基础包设计 - **基础包**(如platform-module):不需要依赖其他allin模块 - **业务包**(如company-module):可以依赖基础包和其他业务包 - **工具包**(如enums包):提供共享常量,被其他模块依赖 ## 8. 测试规范 ### 8.1 测试数据完整性 ```typescript // 确保测试数据包含所有必填字段 const testData = { channelName: '测试渠道', contactPerson: '测试联系人', contactPhone: '13800138000', status: 1, // 不要遗漏任何必填字段 }; ``` ### 8.2 唯一性约束测试 ```typescript // 避免测试数据违反唯一性约束 test('创建重复名称的渠道应失败', async () => { // 使用不同的测试数据避免冲突 const data1 = { channelName: '渠道A', ...otherFields }; const data2 = { channelName: '渠道B', ...otherFields }; // 使用不同的名称 await service.create(data1); await expect(service.create(data1)).rejects.toThrow(); // 第二次创建应失败 }); ``` ### 8.3 调试信息 ```typescript // 在关键路由中添加调试信息 app.post('/create', async (c) => { const body = await c.req.json(); console.debug('创建请求:', body); // ...处理逻辑 }); ``` ### 8.4 测试覆盖率标准 - **单元测试**:核心业务逻辑 > 80% - **集成测试**:API端点覆盖 ≥ 60% - **错误场景**:覆盖各种错误场景和边界条件 ## 9. 开发流程规范 ### 9.1 workspace配置 ```bash # 创建新包后,确保在pnpm-workspace.yaml中添加 # 或在根目录配置'allin-packages/*'自动包含 ``` ### 9.2 类型检查 ```bash # 开发过程中运行类型检查 pnpm typecheck # 针对特定包 cd allin-packages/channel-module && pnpm typecheck ``` ### 9.3 包配置优化 ```json { "name": "@d8d/allin-enums", "type": "module", "main": "src/index.ts", # workspace中直接引用源码 "types": "src/index.ts", # 类型定义直接使用源码 "scripts": { "test": "vitest run", "typecheck": "tsc --noEmit" # 不需要"build"脚本(workspace中直接引用源码) } } ``` ### 9.4 注释规范 ```typescript /** * 订单状态枚举 * - draft: 草稿状态,可编辑 * - confirmed: 已确认,不可编辑 * - in_progress: 进行中,有工作人员处理 * - completed: 已完成,可归档 * - cancelled: 已取消,用户主动取消 */ enum OrderStatus { DRAFT = 'draft', CONFIRMED = 'confirmed', IN_PROGRESS = 'in_progress', COMPLETED = 'completed', CANCELLED = 'cancelled' } ``` ## 10. 错误处理规范 ### 10.1 HTTP状态码 - `200 OK`:操作成功 - `201 Created`:创建成功 - `400 Bad Request`:请求参数错误 - `404 Not Found`:资源不存在 - `409 Conflict`:资源冲突(如唯一性冲突) - `500 Internal Server Error`:服务器内部错误 ### 10.2 错误响应格式 ```json { "success": false, "message": "渠道名称已存在", "code": "CHANNEL_NAME_EXISTS", "timestamp": "2025-12-02T10:30:00Z" } ``` ### 10.3 DELETE操作响应 ```typescript // DELETE成功应返回200,而不是404 app.delete('/:id', async (c) => { const result = await service.delete(id); return c.json({ success: true }, 200); // ✅ 正确 // return c.json({ success: true }, 404); // ❌ 错误 }); ``` ## 11. 参考实现 ### 11.1 已完成模块参考 - **channel-module** (007.001):基础CRUD模块实现 - **company-module** (007.002):模块间依赖处理 - **platform-module** (007.006):基础依赖包设计 - **disability-module** (007.004):文件模块集成 - **salary-module** (007.007):外部包集成(geo-areas) ### 11.2 最佳实践总结 1. **保持API兼容性**:移植时保持原始API功能 2. **统一错误处理**:提供明确的错误信息 3. **避免循环依赖**:使用ID引用解耦模块 4. **完整测试覆盖**:覆盖成功和失败场景 5. **及时类型检查**:开发过程中持续运行类型检查 ## 附录:检查清单 ### 新模块包创建检查清单 - [ ] 包名符合规范:`@d8d/allin-{name}-module` - [ ] 目录结构完整:entities, services, routes, schemas, tests - [ ] workspace配置:在pnpm-workspace.yaml中添加或配置通配符 - [ ] 实体主键命名为`id` - [ ] 字段命名正确转换(下划线→驼峰) - [ ] 唯一性约束正确配置 - [ ] 服务层继承GenericCrudService - [ ] 路由聚合模式正确 - [ ] Schema验证完整 - [ ] 测试数据完整且不违反约束 - [ ] 类型检查通过 - [ ] 所有测试通过 - [ ] 错误处理规范 - [ ] 注释完整清晰