重构:完善 IoT 模型架构规范和数据库设计
- 完善 GORM 模型规范:货币字段使用 int64(分为单位)、JSONB 字段规范、模型结构规范 - 修复所有 IoT 模型的架构违规问题 - 更新 CLAUDE.md 开发指南,补充完整的数据库设计规范和模型示例 - 添加数据库迁移脚本(000006)用于架构重构 - 归档 OpenSpec 变更文档(2026-01-12-fix-iot-models-violations) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
124
CLAUDE.md
124
CLAUDE.md
@@ -310,28 +310,126 @@ internal/
|
||||
- 短文本(名称、标题等):`VARCHAR(255)` 或 `VARCHAR(100)`
|
||||
- 中等文本(描述、备注等):`VARCHAR(500)` 或 `VARCHAR(1000)`
|
||||
- 长文本(内容、详情等):`TEXT` 类型
|
||||
- **货币金额字段必须使用整数类型存储(分为单位)**:
|
||||
- Go 类型:`int64`(不是 `float64`)
|
||||
- 数据库类型:`BIGINT`(不是 `DECIMAL` 或 `NUMERIC`)
|
||||
- 示例:`CostPrice int64 gorm:"column:cost_price;type:bigint;default:0;comment:成本价(分为单位)" json:"cost_price"`
|
||||
- 理由:避免浮点数精度问题,确保货币计算的准确性
|
||||
- 显示时转换:金额除以 100 转换为元(如 10000 分 = 100.00 元)
|
||||
- 数值字段精度必须明确定义:
|
||||
- 货币金额:`DECIMAL(10, 2)` 或 `DECIMAL(18, 2)`(根据业务需求)
|
||||
- 百分比:`DECIMAL(5, 2)` 或 `DECIMAL(5, 4)`
|
||||
- 百分比:使用 `int64` 存储千分比或万分比(如 2000 表示 20%,避免浮点精度问题)
|
||||
- 计数器:`INTEGER` 或 `BIGINT`
|
||||
- 流量数据:`BIGINT`(如 MB、KB 为单位的流量使用量)
|
||||
- 所有字段必须添加中文注释,说明字段用途和业务含义
|
||||
- 必填字段必须在 GORM 标签中指定 `not null`
|
||||
- 唯一字段必须在 GORM 标签中指定 `unique` 或通过数据库索引保证唯一性
|
||||
- 枚举字段应该使用 `VARCHAR` 或 `INTEGER` 类型,并在代码中定义常量映射
|
||||
- JSONB 字段必须使用 `datatypes.JSON` 类型(从 `gorm.io/datatypes` 包导入)
|
||||
- 示例:`AccountInfo datatypes.JSON gorm:"column:account_info;type:jsonb;comment:收款账户信息" json:"account_info"`
|
||||
- 不使用 `pq.StringArray` 或其他 PostgreSQL 特定类型
|
||||
|
||||
**字段命名示例:**
|
||||
**GORM 模型结构规范:**
|
||||
|
||||
- **所有业务实体模型必须嵌入 `gorm.Model` 和 `BaseModel`**:
|
||||
- `gorm.Model` 提供:`ID`(主键)、`CreatedAt`、`UpdatedAt`、`DeletedAt`(软删除支持)
|
||||
- `BaseModel` 提供:`Creator`、`Updater`(审计字段)
|
||||
- 禁止手动定义 `ID`、`CreatedAt`、`UpdatedAt`、`DeletedAt` 字段
|
||||
- 示例:
|
||||
```go
|
||||
type IotCard struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
// 业务字段...
|
||||
}
|
||||
```
|
||||
- **日志表和只追加(append-only)表不需要软删除和审计字段**:
|
||||
- 这类表只定义 `ID` 和 `CreatedAt`,不嵌入 `gorm.Model` 或 `BaseModel`
|
||||
- 示例:`DataUsageRecord`(流量使用记录)
|
||||
- 示例:
|
||||
```go
|
||||
type DataUsageRecord struct {
|
||||
ID uint `gorm:"column:id;primaryKey;comment:流量使用记录ID" json:"id"`
|
||||
IotCardID uint `gorm:"column:iot_card_id;index;not null;comment:IoT卡ID" json:"iot_card_id"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime;comment:创建时间" json:"created_at"`
|
||||
}
|
||||
```
|
||||
|
||||
**表名命名规范:**
|
||||
|
||||
- **所有表名必须使用 `tb_` 前缀 + 单数形式**:
|
||||
- 示例:`tb_iot_card`(不是 `iot_cards`)
|
||||
- 示例:`tb_package`(不是 `packages`)
|
||||
- 示例:`tb_order`(不是 `orders`)
|
||||
- **必须实现 `TableName()` 方法显式指定表名**:
|
||||
```go
|
||||
func (IotCard) TableName() string {
|
||||
return "tb_iot_card"
|
||||
}
|
||||
```
|
||||
- 禁止依赖 GORM 的自动表名推断(避免复数形式导致的不一致)
|
||||
|
||||
**索引和约束规范:**
|
||||
|
||||
- **外键字段必须添加 `index` 标签**:
|
||||
- 示例:`CarrierID uint gorm:"column:carrier_id;index;not null;comment:运营商ID" json:"carrier_id"`
|
||||
- 提高关联查询性能
|
||||
- **唯一索引必须支持软删除兼容性**:
|
||||
- 添加 `where:deleted_at IS NULL` 条件,确保软删除后的记录不影响唯一性约束
|
||||
- 示例:`gorm:"column:iccid;type:varchar(50);uniqueIndex:idx_iot_card_iccid,where:deleted_at IS NULL;not null;comment:ICCID" json:"iccid"`
|
||||
- 这样同一个 ICCID 的卡可以多次创建/删除而不违反唯一性约束
|
||||
- **复合索引命名规范**:
|
||||
- 使用 `idx_{table}_{field1}_{field2}` 格式
|
||||
- 示例:`uniqueIndex:idx_device_sim_binding_device_slot,where:deleted_at IS NULL`
|
||||
- 禁止定义数据库级别的外键约束(Foreign Key Constraints)
|
||||
|
||||
**完整模型示例:**
|
||||
|
||||
标准业务实体模型(带软删除和审计字段):
|
||||
```go
|
||||
type User struct {
|
||||
ID uint `gorm:"column:id;primaryKey;comment:用户 ID" json:"id"`
|
||||
UserID string `gorm:"column:user_id;type:varchar(100);uniqueIndex;not null;comment:用户唯一标识" json:"user_id"`
|
||||
Email string `gorm:"column:email;type:varchar(255);uniqueIndex;not null;comment:用户邮箱" json:"email"`
|
||||
Phone string `gorm:"column:phone;type:varchar(20);comment:手机号码" json:"phone"`
|
||||
Nickname string `gorm:"column:nickname;type:varchar(100);comment:用户昵称" json:"nickname"`
|
||||
Balance int64 `gorm:"column:balance;type:bigint;default:0;comment:账户余额(分为单位)" json:"balance"`
|
||||
Status int `gorm:"column:status;type:int;default:1;comment:用户状态 1-正常 2-禁用" json:"status"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime;comment:创建时间" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime;comment:更新时间" json:"updated_at"`
|
||||
type IotCard struct {
|
||||
gorm.Model // 提供 ID, CreatedAt, UpdatedAt, DeletedAt
|
||||
BaseModel `gorm:"embedded"` // 提供 Creator, Updater
|
||||
ICCID string `gorm:"column:iccid;type:varchar(50);uniqueIndex:idx_iot_card_iccid,where:deleted_at IS NULL;not null;comment:ICCID(唯一标识)" json:"iccid"`
|
||||
CarrierID uint `gorm:"column:carrier_id;index;not null;comment:运营商ID" json:"carrier_id"`
|
||||
CostPrice int64 `gorm:"column:cost_price;type:bigint;default:0;comment:成本价(分为单位)" json:"cost_price"`
|
||||
DistributePrice int64 `gorm:"column:distribute_price;type:bigint;default:0;comment:分销价(分为单位)" json:"distribute_price"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-未激活 2-已激活 3-已停用" json:"status"`
|
||||
}
|
||||
|
||||
func (IotCard) TableName() string {
|
||||
return "tb_iot_card"
|
||||
}
|
||||
```
|
||||
|
||||
日志表模型(不需要软删除和审计):
|
||||
```go
|
||||
type DataUsageRecord struct {
|
||||
ID uint `gorm:"column:id;primaryKey;comment:流量使用记录ID" json:"id"`
|
||||
IotCardID uint `gorm:"column:iot_card_id;index;not null;comment:IoT卡ID" json:"iot_card_id"`
|
||||
DataUsageMB int64 `gorm:"column:data_usage_mb;type:bigint;not null;comment:流量使用量(MB)" json:"data_usage_mb"`
|
||||
DataIncreaseMB int64 `gorm:"column:data_increase_mb;type:bigint;default:0;comment:相比上次的增量(MB)" json:"data_increase_mb"`
|
||||
CheckTime time.Time `gorm:"column:check_time;not null;comment:检查时间" json:"check_time"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime;comment:创建时间" json:"created_at"`
|
||||
}
|
||||
|
||||
func (DataUsageRecord) TableName() string {
|
||||
return "tb_data_usage_record"
|
||||
}
|
||||
```
|
||||
|
||||
包含 JSONB 字段的模型:
|
||||
```go
|
||||
type Order struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
OrderNo string `gorm:"column:order_no;type:varchar(100);uniqueIndex:idx_order_no,where:deleted_at IS NULL;not null;comment:订单号" json:"order_no"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:订单金额(分为单位)" json:"amount"`
|
||||
CarrierOrderData datatypes.JSON `gorm:"column:carrier_order_data;type:jsonb;comment:运营商订单原始数据" json:"carrier_order_data"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-待支付 2-已支付 3-已完成" json:"status"`
|
||||
}
|
||||
|
||||
func (Order) TableName() string {
|
||||
return "tb_order"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user