# 编码标准
## 版本信息
| 版本 | 日期 | 描述 | 作者 |
|------|------|------|------|
| 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