# 数据库实体规范

## 常见不规范问题
  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)