Procházet zdrojové kódy

docs(coding-standards): 更新后端模块包开发规范章节

- 扩展后端模块包开发章节,添加详细的Entity、Service、路由、Schema规范
- 添加实际代码示例展示正确的实现方式
- 添加常见错误避免清单
- 更新参考实现列表,包含史诗007系列的模块

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname před 3 týdny
rodič
revize
a56533cf2b
1 změnil soubory, kde provedl 139 přidání a 6 odebrání
  1. 139 6
      docs/architecture/coding-standards.md

+ 139 - 6
docs/architecture/coding-standards.md

@@ -130,13 +130,146 @@
 - 共享组件包:`mini-ui-packages/mini-shared-ui-components`
 
 ### 后端模块包开发
-开发后端模块包时,**必须**参考并遵循[后端模块包开发规范](./backend-module-package-standards.md)。
+开发后端模块包时,**必须**参考并遵循[后端模块包开发规范](./backend-module-package-standards.md),该规范基于史诗007系列(渠道、平台、公司、残疾管理等模块)的实际实施经验总结
 
-**核心要求**:
-1. 模块化架构: entities / services / schemas / routes
-2. 依赖注入: 使用构造函数注入
-3. 错误处理: 统一的错误处理机制
-4. 日志记录: 使用结构化日志
+**关键检查点**:
+
+#### 1. Entity定义规范
+- **完整的列定义**: 必须包含 `type`, `length`, `nullable`, `comment` 等属性
+- **使用索引装饰器**: 使用 `@Index` 定义唯一索引和普通索引
+- **时间戳字段**: 使用 `timestamp` 类型,设置 `default: () => 'CURRENT_TIMESTAMP'`
+- **主键定义**: 使用 `@PrimaryGeneratedColumn`,包含 `unsigned: true` 和 `comment`
+
+**正确示例**:
+```typescript
+@Entity('channel_info')
+export class Channel {
+  @PrimaryGeneratedColumn({
+    name: 'channel_id',
+    type: 'int',
+    unsigned: true,
+    comment: '渠道ID'
+  })
+  id!: number;
+
+  @Column({
+    name: 'channel_name',
+    type: 'varchar',
+    length: 100,
+    nullable: false,
+    comment: '渠道名称'
+  })
+  @Index('idx_channel_name', { unique: true })
+  channelName!: string;
+
+  @Column({
+    name: 'create_time',
+    type: 'timestamp',
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间'
+  })
+  createTime!: Date;
+}
+```
+
+#### 2. Service层规范
+- **继承 `GenericCrudService`**: 使用基类提供的CRUD能力
+- **使用 `override` 关键字**: 明确标识覆盖父类方法
+- **软删除实现**: 使用 `status` 字段而非物理删除
+- **业务逻辑检查**: 在调用父类方法前进行验证
+
+**正确示例**:
+```typescript
+export class ChannelService extends GenericCrudService<Channel> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, Channel);
+  }
+
+  override async create(data: Partial<Channel>, userId?: string | number): Promise<Channel> {
+    // 业务逻辑检查
+    const existingChannel = await this.repository.findOne({
+      where: { channelName: data.channelName, status: 1 }
+    });
+    if (existingChannel) {
+      throw new Error('渠道名称已存在');
+    }
+
+    const channelData = {
+      ...data,
+      status: 1,
+      createTime: new Date(),
+      updateTime: new Date()
+    };
+
+    return super.create(channelData, userId);
+  }
+}
+```
+
+#### 3. 路由层规范
+- **使用 `OpenAPIHono`**: 而非普通的 `Hono`
+- **使用 `AuthContext` 泛型**: 提供类型安全的认证上下文
+- **自定义路由必须使用 `parseWithAwait`**: 验证响应数据符合Schema定义
+- **使用 `createZodErrorResponse`**: 处理Zod验证错误
+
+**正确示例**:
+```typescript
+import { parseWithAwait, createZodErrorResponse } from '@d8d/shared-utils';
+
+channelCustomRoutes.get('/statistics/:id', async (c) => {
+  try {
+    const result = await channelService.getStatistics(id);
+
+    // ✅ 必须:使用 parseWithAwait 验证和转换响应数据
+    const validatedResult = await parseWithAwait(ChannelSchema, result);
+    return c.json(validatedResult, 200);
+  } catch (error) {
+    if (error instanceof z.ZodError) {
+      return c.json(createZodErrorResponse(error), 400);
+    }
+    return c.json({ code: 500, message: error.message }, 500);
+  }
+});
+```
+
+#### 4. Schema规范
+- **使用 `.openapi()` 装饰器**: 添加描述和示例
+- **使用 `z.coerce.date<Date>()` 和 `z.coerce.number<number>()`**: Zod 4.0需要添加泛型参数
+- **不导出推断类型**: 类型由RPC自动推断,不需要手动导出 `z.infer<typeof Schema>`
+
+**正确示例**:
+```typescript
+export const ChannelSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '渠道ID',
+    example: 1
+  }),
+  channelName: z.string().max(100).openapi({
+    description: '渠道名称',
+    example: '微信小程序'
+  }),
+  createTime: z.coerce.date<Date>().openapi({
+    description: '创建时间',
+    example: '2024-01-01T00:00:00Z'
+  })
+});
+```
+
+**常见错误避免**:
+- ❌ Entity列定义不要省略 `type`, `comment`, `nullable` 等属性
+- ❌ Service覆盖方法不要忘记使用 `override` 关键字
+- ❌ 自定义路由不要省略 `parseWithAwait` 验证
+- ❌ Schema中不要使用 `z.coerce.date()` 或 `z.coerce.number()`(必须添加泛型)
+- ❌ Schema不要导出 `z.infer` 推断的类型(类型由RPC自动推断)
+- ❌ 不要使用物理删除(应使用 `status` 字段实现软删除)
+
+**参考实现**:
+- 渠道模块:`allin-packages/channel-module`
+- 平台模块:`allin-packages/platform-module`
+- 公司模块:`allin-packages/company-module`
+- 残疾管理模块:`allin-packages/disability-module`
+- 认证模块:`packages/core-module/auth-module`
+- 用户模块:`packages/core-module/user-module`
 
 ## 类型安全