| 版本 | 日期 | 描述 | 作者 |
|---|---|---|---|
| 3.0 | 2025-12-26 | 拆分测试策略到独立文档,保留编码标准 | James (Claude Code) |
| 2.5 | 2025-12-26 | 添加Mini UI包开发规范章节 | Bob (Scrum Master) |
| 2.4 | 2025-09-20 | 与主架构文档版本一致 | Winston |
user.service.ts)UserService)getUserById)API_BASE_URL)User)开发Web UI包时,必须参考并遵循UI包开发规范,该规范基于史诗008(AllIn UI模块移植)的经验总结。
关键检查点:
data-testid属性常见错误避免:
getByText()查找可能重复的文本元素参考实现:
packages/advertisement-management-uiallin-packages/platform-management-uiallin-packages/channel-management-ui(史诗008.002)开发Mini UI包(Taro小程序UI包)时,必须参考并遵循Mini UI包开发规范,该规范基于史诗011(用人方小程序)和史诗017(人才小程序)的实施经验总结。
关键检查点:
flex-row),必须显式添加 flex flex-col 类才能实现垂直布局flex flex-col强制垂直排列正确示例:
// ✅ 正确: 使用 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>
i-heroicons-{icon-name}-{size}-{style}w-5 h-5、text-lg 等正确示例:
// ✅ 正确: 使用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 - 二维码leftIcon=""、leftText="")leftIcon="i-heroicons-chevron-left-20-solid")@d8d/mini-shared-ui-components/components/navbar常见错误避免:
flex flex-col 实现垂直布局w-5 h-5、text-lg等)@/、~/等)参考实现:
mini-ui-packages/yongren-dashboard-uimini-ui-packages/rencai-dashboard-uimini-ui-packages/mini-shared-ui-components开发后端模块包时,必须参考并遵循后端模块包开发规范,该规范基于史诗007系列(渠道、平台、公司、残疾管理等模块)的实际实施经验总结。
关键检查点:
type, length, nullable, comment 等属性@Index 定义唯一索引和普通索引timestamp 类型,设置 default: () => 'CURRENT_TIMESTAMP'@PrimaryGeneratedColumn,包含 unsigned: true 和 comment正确示例:
@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;
}
GenericCrudService: 使用基类提供的CRUD能力override 关键字: 明确标识覆盖父类方法status 字段而非物理删除正确示例:
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);
}
}
OpenAPIHono: 而非普通的 HonoAuthContext 泛型: 提供类型安全的认证上下文parseWithAwait: 验证响应数据符合Schema定义createZodErrorResponse: 处理Zod验证错误正确示例:
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);
}
});
.openapi() 装饰器: 添加描述和示例z.coerce.date<Date>() 和 z.coerce.number<number>(): Zod 4.0需要添加泛型参数z.infer<typeof Schema>正确示例:
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'
})
});
常见错误避免:
type, comment, nullable 等属性override 关键字parseWithAwait 验证z.coerce.date() 或 z.coerce.number()(必须添加泛型)z.infer 推断的类型(类型由RPC自动推断)status 字段实现软删除)参考实现:
allin-packages/channel-moduleallin-packages/platform-moduleallin-packages/company-moduleallin-packages/disability-modulepackages/core-module/auth-modulepackages/core-module/user-module{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
// ✅ 推荐: 使用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;
}
// 定义自定义错误类
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;
}
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;
}
// ✅ 使用Schema验证
import { userSchema } from './schemas/user.schema';
const validatedData = await userSchema.parseAsync(inputData);
// ❌ 不要直接信任用户输入
const user = { username: req.body.username }; // 不安全
// ✅ 从响应中排除敏感字段
function sanitizeUser(user: User): Partial<User> {
const { password, ...sanitized } = user;
return sanitized;
}
// ✅ 日志中不记录敏感信息
logger.info('用户登录', { userId: user.id }); // 正确
logger.info('用户登录', { user }); // 错误 - 会记录密码
// ✅ 使用TypeORM参数化查询
const users = await userRepo.find({
where: { username: username }
});
// ❌ 不要拼接SQL字符串
const query = `SELECT * FROM users WHERE username = '${username}'`; // 危险
// ✅ 只查询需要的字段
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而不是循环查询
});
// ✅ 使用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
// index.ts - 统一导出
export * from './entities';
export * from './services';
export { userRoutes } from './routes';
export { userSchema } from './schemas';
文档状态: 正式版 下次评审: 2026-01-26