|
@@ -130,13 +130,146 @@
|
|
|
- 共享组件包:`mini-ui-packages/mini-shared-ui-components`
|
|
- 共享组件包:`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`
|
|
|
|
|
|
|
|
## 类型安全
|
|
## 类型安全
|
|
|
|
|
|