# 数据库实体规范 ## 常见不规范问题 1. nullable 字段用了? 而不是!, 类型没加 null 错误示例: ```ts @Column({ name: 'description', type: 'text', nullable: true, comment: '容器配置描述' }) description?: string; ``` 正确示例: ```ts @Column({ name: 'description', type: 'text', nullable: true, comment: '容器配置描述' }) description!: string | null; ``` ## 1. 实体基础结构 ```typescript import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity('table_name') // 使用小写下划线命名表名 export class EntityName { // 字段定义... } ``` ## 2. 主键定义规范 ```typescript @PrimaryGeneratedColumn({ unsigned: true }) // 必须使用无符号整数 id!: number; // 使用非空断言(!)和明确类型 ``` ## 3. 列定义规范 ```typescript @Column({ name: '字段名称', // 必须添加并与数据表字段名称一致 type: 'varchar', // 明确指定数据库类型 length: 255, // 字符串必须指定长度 nullable: true, // 明确是否可为空 default: undefined, // 默认值(可选) comment: '字段说明' // 必须添加中文注释 }) fieldName!: FieldType; // 类型必须明确 ``` ## 4. 数据类型规范 | 业务类型 | 数据库类型 | TypeScript 类型 | |----------------|-------------------------|-----------------------| | 主键ID | int unsigned | number | | 短文本 | varchar(length) | string | | 长文本 | text | string | | 整数 | int | number | | 小数 | decimal(precision,scale)| number | | 布尔状态 | tinyint | number (0/1) | | 日期时间 | timestamp | Date | ## 5. 状态字段规范 ```typescript // 禁用状态 (0启用 1禁用) @Column({ name: 'is_disabled', type: 'tinyint', default: 1 }) isDisabled!: number; // 删除状态 (0未删除 1已删除) @Column({ name: 'is_deleted',type: 'tinyint', default: 0 }) isDeleted!: number; ``` ## 6. 时间字段规范 ```typescript @CreateDateColumn({ name: 'created_at', comment: '创建时间' }) createdAt!: Date; @UpdateDateColumn({ name: 'updated_at', comment: '更新时间' }) updatedAt!: Date; ``` ## 7. Zod Schema 规范 ### 7.1 基础类型规范 ```typescript import { z } from '@hono/zod-openapi'; export const EntitySchema = z.object({ id: z.number().int('必须是整数').positive('必须是正整数').openapi({ description: 'ID说明' }), // 字符串字段 fieldName: z.string() .min(1, '不能为空') .max(255, '最多255个字符') .nullable() .openapi({ description: '字段说明', example: '示例值' }), // 数字字段 numberField: z.number() .int('必须是整数') .positive('必须是正数') .min(1, '最小值为1') .max(9999, '最大值为9999') .default(默认值) .openapi({...}), // 日期字段 dateField: z.coerce.date('日期格式不正确').openapi({...}) }); ``` ### 7.2 数据类型转换规范 #### 7.2.1 数字类型转换 对于URL参数或表单数据中的数字类型,必须使用`z.coerce.number()`进行类型转换,以确保字符串到数字的正确转换: ```typescript // 整数类型 z.coerce.number().int('必须是整数').positive('必须是正整数').openapi({ description: '正整数ID', example: 1 }); // 小数类型 z.coerce.number().multipleOf(0.01, '最多保留两位小数').openapi({ description: '金额,保留两位小数', example: 19.99 }); // 状态类型(0/1) z.coerce.number().int('必须是整数').min(0, '最小值为0').max(1, '最大值为1').openapi({ description: '状态(0-禁用,1-启用)', example: 1 }); ``` #### 7.2.2 日期类型转换 对于日期时间类型,必须使用`z.coerce.date()`进行类型转换: ```typescript // 日期时间类型 z.coerce.date('日期格式不正确').openapi({ description: '创建时间', example: '2023-10-01T12:00:00Z' }); // 日期范围查询 const DateRangeSchema = z.object({ startDate: z.coerce.date('开始日期格式不正确').openapi({ description: '开始日期', example: '2023-10-01T00:00:00Z' }), endDate: z.coerce.date('结束日期格式不正确').openapi({ description: '结束日期', example: '2023-10-31T23:59:59Z' }) }); ``` #### 7.2.3 布尔类型转换 对于布尔类型参数,必须使用`z.coerce.boolean()`进行类型转换: ```typescript // 布尔类型 z.coerce.boolean('必须是布尔值').openapi({ description: '是否启用', example: true }); ``` ### 7.3 创建/更新Schema特殊规范 创建(Create)和更新(Update)Schema必须遵循以下额外规范: 1. **创建Schema**: - 不包含`id`字段(由数据库自动生成) - 所有必填字段必须显式定义,不得为`optional()` - 如字段为选填项(包括数据库允许为null的字段),必须显式添加`optional()` - 必须使用适当的coerce方法处理非字符串类型 ```typescript export const CreateEntityDto = z.object({ name: z.string().min(1, '名称不能为空').max(255, '名称最多255个字符').openapi({ description: '名称', example: '示例名称' }), quantity: z.coerce.number().int('数量必须是整数').min(1, '数量最小为1').max(9999, '数量最大为9999').openapi({ description: '数量', example: 10 }), price: z.coerce.number().multipleOf(0.01, '价格最多保留两位小数').min(0.01, '价格必须大于0').max(999999.99, '价格不能超过999999.99').openapi({ description: '价格', example: 99.99 }), isActive: z.coerce.boolean('是否激活必须是布尔值').default(true).openapi({ description: '是否激活', example: true }), expireDate: z.coerce.date('过期日期格式不正确').min(new Date(), '过期日期不能早于当前时间').openapi({ description: '过期日期', example: '2024-12-31T23:59:59Z' }), // 选填字段示例 // nullable字段必须显式添加optional() description: z.string().max(500, '描述最多500个字符').nullable().optional().openapi({ description: '商品描述(选填)', example: '这是一个可选的商品描述信息' }) }); ``` 2. **更新Schema**: - 不包含`id`字段(通过URL参数传递) - 所有字段必须为`optional()` - 必须使用适当的coerce方法处理非字符串类型 ```typescript export const UpdateEntityDto = z.object({ name: z.string().min(1, '名称不能为空').max(255, '名称最多255个字符').optional().openapi({ description: '名称', example: '更新后的名称' }), quantity: z.coerce.number().int('数量必须是整数').min(1, '数量最小为1').max(9999, '数量最大为9999').optional().openapi({ description: '数量', example: 20 }), price: z.coerce.number().multipleOf(0.01, '价格最多保留两位小数').min(0.01, '价格必须大于0').max(999999.99, '价格不能超过999999.99').optional().openapi({ description: '价格', example: 89.99 }), isActive: z.coerce.boolean('是否激活必须是布尔值').optional().openapi({ description: '是否激活', example: false }), expireDate: z.coerce.date('过期日期格式不正确').optional().openapi({ description: '过期日期', example: '2025-12-31T23:59:59Z' }) }); ``` ``` ## 8. 命名规范 - 实体类名:PascalCase (如 RackInfo) - 表名:snake_case (如 rack_info) - 字段名:camelCase (如 rackName) - 数据库列名:snake_case (如 rack_name) ## 9. 最佳实践 1. 所有字段必须添加字段名称(name)及注释(comment) 2. 必须明确指定 nullable 属性 3. 状态字段使用 tinyint 并注明取值含义 4. 必须包含 createdAt/updatedAt 时间字段 5. 每个实体必须配套 Zod Schema 定义 6. Schema 必须包含 OpenAPI 元数据(description/example)