Kaynağa Gözat

fix(architecture): 修复后端模块包规范文档并添加到主索引

- 修复GenericCrudService构造函数示例,移除错误的@InjectRepository注解
- 将backend-module-package-standards.md添加到架构文档主索引
- 规范文档包含11个类别的开发规范,基于史诗007系列经验总结

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 6 gün önce
ebeveyn
işleme
94301f837c

+ 502 - 0
docs/architecture/backend-module-package-standards.md

@@ -0,0 +1,502 @@
+# 后端模块包规范
+
+## 版本信息
+| 版本 | 日期 | 描述 | 作者 |
+|------|------|------|------|
+| 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<Channel> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Channel);
+  }
+}
+```
+
+### 4.2 方法覆盖模式
+```typescript
+// 覆盖create方法:添加唯一性检查
+async create(data: CreateChannelDto): Promise<Channel> {
+  // 业务逻辑:检查名称唯一性
+  const existing = await this.repository.findOne({
+    where: { channelName: data.channelName }
+  });
+
+  if (existing) {
+    throw new Error('渠道名称已存在');
+  }
+
+  return super.create(data);
+}
+
+// 覆盖findAll方法:返回标准格式
+async findAll(options?: FindManyOptions<Channel>): Promise<{ data: Channel[], total: number }> {
+  const [data, total] = await this.repository.findAndCount(options);
+  return { data, total };
+}
+```
+
+### 4.3 自定义业务方法
+```typescript
+// 按名称搜索
+async searchByName(name: string): Promise<Channel[]> {
+  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验证完整
+- [ ] 测试数据完整且不违反约束
+- [ ] 类型检查通过
+- [ ] 所有测试通过
+- [ ] 错误处理规范
+- [ ] 注释完整清晰

+ 12 - 0
docs/architecture/index.md

@@ -43,6 +43,18 @@
     - [现有标准合规性](./coding-standards.md#现有标准合规性)
     - [增强特定标准](./coding-standards.md#增强特定标准)
     - [关键集成规则](./coding-standards.md#关键集成规则)
+  - [后端模块包开发规范](./backend-module-package-standards.md)
+    - [包结构规范](./backend-module-package-standards.md#包结构规范)
+    - [实体设计规范](./backend-module-package-standards.md#实体设计规范)
+    - [数据库类型规范](./backend-module-package-standards.md#数据库类型规范)
+    - [服务层规范](./backend-module-package-standards.md#服务层规范)
+    - [路由层规范](./backend-module-package-standards.md#路由层规范)
+    - [验证系统规范](./backend-module-package-standards.md#验证系统规范)
+    - [模块集成规范](./backend-module-package-standards.md#模块集成规范)
+    - [测试规范](./backend-module-package-standards.md#测试规范)
+    - [开发流程规范](./backend-module-package-standards.md#开发流程规范)
+    - [错误处理规范](./backend-module-package-standards.md#错误处理规范)
+    - [参考实现](./backend-module-package-standards.md#参考实现)
   - [安全集成](./security-integration.md)
     - [现有安全措施](./security-integration.md#现有安全措施)
     - [增强安全要求](./security-integration.md#增强安全要求)