# 编码标准 ## 版本信息 | 版本 | 日期 | 描述 | 作者 | |------|------|------|------| | 3.0 | 2025-12-26 | 拆分测试策略到独立文档,保留编码标准 | James (Claude Code) | | 2.5 | 2025-12-26 | 添加Mini UI包开发规范章节 | Bob (Scrum Master) | | 2.4 | 2025-09-20 | 与主架构文档版本一致 | Winston | ## 现有标准合规性 ### 代码风格 - **TypeScript严格模式**: 所有项目必须启用严格类型检查 - **一致的缩进**: 使用2个空格缩进 - **命名约定**: - 文件名: kebab-case (如: `user.service.ts`) - 类名: PascalCase (如: `UserService`) - 函数/变量: camelCase (如: `getUserById`) - 常量: UPPER_SNAKE_CASE (如: `API_BASE_URL`) - 接口: PascalCase,无I前缀 (如: `User`) ### Linting规则 - **ESLint**: 已配置ESLint,支持TypeScript和React - **Prettier**: 统一代码格式化 - **提交前检查**: 使用husky进行pre-commit钩子检查 ### 文档风格 - **代码注释**: 关键逻辑必须添加注释说明 - **JSDoc**: 公共API必须包含JSDoc注释 - **README**: 每个包必须有独立的README说明用途和使用方法 ## 开发规范引用 ### UI包开发 开发Web UI包时,**必须**参考并遵循[UI包开发规范](./ui-package-standards.md),该规范基于史诗008(AllIn UI模块移植)的经验总结。 **关键检查点**: 1. **API路径映射验证**: 开发前必须验证故事中的API路径映射与实际后端路由定义的一致性 2. **类型推断最佳实践**: 必须使用RPC推断类型,而不是直接导入schema类型 3. **测试选择器优化**: 必须为关键交互元素添加`data-testid`属性 4. **表单组件模式**: 必须使用条件渲染两个独立的Form组件 5. **API调用一致性**: 必须根据实际路由名称修正API调用 **常见错误避免**: - ❌ 不要直接导入schema类型(可能导致Date/string类型不匹配) - ❌ 不要使用`getByText()`查找可能重复的文本元素 - ❌ 不要在单个Form组件上动态切换props - ❌ 不要使用故事中描述但实际不存在的路由名称 **参考实现**: - 广告管理UI包:`packages/advertisement-management-ui` - 平台管理UI包:`allin-packages/platform-management-ui` - 渠道管理UI包:`allin-packages/channel-management-ui`(史诗008.002) ### Mini UI包开发 开发Mini UI包(Taro小程序UI包)时,**必须**参考并遵循[Mini UI包开发规范](./mini-ui-package-standards.md),该规范基于史诗011(用人方小程序)和史诗017(人才小程序)的实施经验总结。 **关键检查点**: #### 1. Taro小程序布局规范 - **View组件默认横向布局**: View容器内的子元素默认是横向布局(`flex-row`),必须显式添加 `flex flex-col` 类才能实现垂直布局 - **Text组件默认内联显示**: Text组件默认是内联显示(类似span),需要使用`flex flex-col`强制垂直排列 **正确示例**: ```typescript // ✅ 正确: 使用 flex flex-col 实现垂直布局 姓名: 张三 性别: 男 年龄: 35 // ❌ 错误: 缺少 flex flex-col,子元素会横向排列 姓名: 张三 性别: 男 年龄: 35 ``` #### 2. 图标使用规范 - **必须使用Heroicons图标类**: 不要使用emoji或文本符号 - **图标类命名格式**: `i-heroicons-{icon-name}-{size}-{style}` - **必须添加尺寸类**: 如 `w-5 h-5`、`text-lg` 等 **正确示例**: ```typescript // ✅ 正确: 使用Heroicons图标类 // ❌ 错误: 使用emoji 🔔 👤 ``` **常用图标**: - `chevron-left-20-solid` - 左箭头(返回按钮) - `user-20-solid` - 用户 - `bell-20-solid` - 通知铃 - `document-text-20-solid` - 文档 - `chart-bar-20-solid` - 图表 - `calendar-20-solid` - 日历 - `phone-20-solid` - 电话 - `lock-closed-20-solid` - 锁 - `qr-code-20-solid` - 二维码 #### 3. Navbar导航栏集成规范 - **TabBar页面(一级)**: 使用Navbar无返回按钮(`leftIcon=""`、`leftText=""`) - **非TabBar页面(二级)**: 使用Navbar带返回按钮(`leftIcon="i-heroicons-chevron-left-20-solid"`) - **Navbar组件来源**: `@d8d/mini-shared-ui-components/components/navbar` #### 4. API客户端模式 - **每个UI包独立管理**: 每个Mini UI包包含自己的API客户端和RPC类型 - **使用相对路径导入**: UI包内部必须使用相对路径,不要使用别名 - **RPC推断类型**: 必须使用RPC推断类型,而不是直接导入schema类型 **常见错误避免**: - ❌ 不要忘记添加 `flex flex-col` 实现垂直布局 - ❌ 不要使用emoji代替Heroicons图标 - ❌ 不要忘记为图标添加尺寸类(`w-5 h-5`、`text-lg`等) - ❌ 不要在Mini UI包内部导入中使用别名(`@/`、`~/`等) - ❌ 不要使用Vitest作为Mini项目的测试框架(应使用Jest) **参考实现**: - 用人方小程序UI包:`mini-ui-packages/yongren-dashboard-ui` - 人才小程序UI包:`mini-ui-packages/rencai-dashboard-ui` - 共享组件包:`mini-ui-packages/mini-shared-ui-components` ### 后端模块包开发 开发后端模块包时,**必须**参考并遵循[后端模块包开发规范](./backend-module-package-standards.md),该规范基于史诗007系列(渠道、平台、公司、残疾管理等模块)的实际实施经验总结。 **关键检查点**: #### 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 { constructor(dataSource: DataSource) { super(dataSource, Channel); } override async create(data: Partial, userId?: string | number): Promise { // 业务逻辑检查 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()` 和 `z.coerce.number()`**: Zod 4.0需要添加泛型参数 - **不导出推断类型**: 类型由RPC自动推断,不需要手动导出 `z.infer` **正确示例**: ```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().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` ## 类型安全 ### TypeScript配置 ```json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true } } ``` ### 类型定义 ```typescript // ✅ 推荐: 使用interface定义对象形状 interface User { id: number; username: string; email: string; createdAt: Date; } // ✅ 推荐: 使用type定义联合类型 type Status = 'pending' | 'active' | 'inactive'; // ✅ 推荐: 使用泛型增强类型复用 interface ApiResponse { status: number; data: T; message?: string; } ``` ## 错误处理 ### 统一错误处理 ```typescript // 定义自定义错误类 class ValidationError extends Error { constructor(public errors: Record) { super('验证失败'); this.name = 'ValidationError'; } } // 使用自定义错误 function validateUser(data: unknown): User { const result = userSchema.safeParse(data); if (!result.success) { throw new ValidationError(result.error.flatten().fieldErrors); } return result.data; } ``` ### 错误日志 ```typescript import { logger } from '@d8d/shared-utils/logger'; try { await userService.createUser(userData); } catch (error) { logger.error('创建用户失败', { error: error.message, stack: error.stack, userData: JSON.stringify(userData) }); throw error; } ``` ## 安全最佳实践 ### 输入验证 ```typescript // ✅ 使用Schema验证 import { userSchema } from './schemas/user.schema'; const validatedData = await userSchema.parseAsync(inputData); // ❌ 不要直接信任用户输入 const user = { username: req.body.username }; // 不安全 ``` ### 敏感数据处理 ```typescript // ✅ 从响应中排除敏感字段 function sanitizeUser(user: User): Partial { const { password, ...sanitized } = user; return sanitized; } // ✅ 日志中不记录敏感信息 logger.info('用户登录', { userId: user.id }); // 正确 logger.info('用户登录', { user }); // 错误 - 会记录密码 ``` ### SQL注入防护 ```typescript // ✅ 使用TypeORM参数化查询 const users = await userRepo.find({ where: { username: username } }); // ❌ 不要拼接SQL字符串 const query = `SELECT * FROM users WHERE username = '${username}'`; // 危险 ``` ## 性能优化 ### 数据库查询优化 ```typescript // ✅ 只查询需要的字段 const users = await userRepo.find({ select: ['id', 'username', 'email'] }); // ✅ 使用索引字段查询 const user = await userRepo.findOne({ where: { email: userEmail } // email字段应有索引 }); // ❌ 避免N+1查询 const orders = await orderRepo.find({ relations: ['user', 'products'] // 使用join而不是循环查询 }); ``` ### 缓存策略 ```typescript // ✅ 使用Redis缓存 import { cacheGet, cacheSet } from '@d8d/shared-utils/redis.util'; async function getUserById(id: number) { const cached = await cacheGet(`user:${id}`); if (cached) return JSON.parse(cached); const user = await userRepo.findOne({ where: { id } }); await cacheSet(`user:${id}`, JSON.stringify(user), 3600); return user; } ``` ## 代码组织 ### 文件结构 ``` packages/user-module/ ├── src/ │ ├── entities/ # 数据实体 │ ├── services/ # 业务逻辑 │ ├── schemas/ # 验证Schema │ ├── routes/ # API路由 │ ├── middleware/ # 中间件 │ ├── utils/ # 工具函数 │ └── index.ts # 包入口 ├── tests/ # 测试文件 ├── README.md # 包说明 └── package.json ``` ### 导出规范 ```typescript // index.ts - 统一导出 export * from './entities'; export * from './services'; export { userRoutes } from './routes'; export { userSchema } from './schemas'; ``` ## 相关文档 ### 测试规范 - [测试策略概述](./testing-strategy.md) - [Web UI包测试规范](./web-ui-testing-standards.md) - [Web Server包测试规范](./web-server-testing-standards.md) - [后端模块包测试规范](./backend-module-testing-standards.md) - [Mini UI包测试规范](./mini-ui-testing-standards.md) ### 开发规范 - [UI包开发规范](./ui-package-standards.md) - [Mini UI包开发规范](./mini-ui-package-standards.md) - [后端模块包开发规范](./backend-module-package-standards.md) - [API设计规范](./api-design-integration.md) --- **文档状态**: 正式版 **下次评审**: 2026-01-26