# 编码标准

## 版本信息
| 版本 | 日期 | 描述 | 作者 |
|------|------|------|------|
| 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 实现垂直布局
<View className="flex flex-col">
  <Text>姓名: 张三</Text>
  <Text>性别: 男</Text>
  <Text>年龄: 35</Text>
</View>

// ❌ 错误: 缺少 flex flex-col，子元素会横向排列
<View>
  <Text>姓名: 张三</Text>
  <Text>性别: 男</Text>
  <Text>年龄: 35</Text>
</View>
```

#### 2. 图标使用规范
- **必须使用Heroicons图标类**: 不要使用emoji或文本符号
- **图标类命名格式**: `i-heroicons-{icon-name}-{size}-{style}`
- **必须添加尺寸类**: 如 `w-5 h-5`、`text-lg` 等

**正确示例**：
```typescript
// ✅ 正确: 使用Heroicons图标类
<View className="i-heroicons-chevron-left-20-solid w-5 h-5 text-gray-600" />
<View className="i-heroicons-user-20-solid w-6 h-6 text-blue-500" />
<View className="i-heroicons-bell-20-solid w-4 h-4 text-gray-400" />

// ❌ 错误: 使用emoji
<Text>🔔</Text>
<Text>👤</Text>
<View>←</View>
```

**常用图标**：
- `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<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`

## 类型安全

### 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<T> {
  status: number;
  data: T;
  message?: string;
}
```

## 错误处理

### 统一错误处理
```typescript
// 定义自定义错误类
class ValidationError extends Error {
  constructor(public errors: Record<string, string[]>) {
    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<User> {
  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
