# 新增 model 流程 项目 `models` 定义于 `backend/database/models` 文件中。 每个 `model` 对应数据库中的一张表。 新增 `model` 的步骤如下: ## 步骤 1. 运行 `atomctl migrate create [alter|create_table]` 创建迁移文件。 2. 编辑生成的迁移文件,定义数据库表结构变更。不需要声明 `BEGIN` 和 `COMMIT`,框架会自动处理。table 名称使用复数形式,例如 `tenants`。 3. 执行 `atomctl migrate up` 应用迁移,更新数据库结构。 4. 对于 `JSON` `ARRAY` `ENUM` 等复杂字段类型,编辑 `database/.transform.yaml` 文件,在 `field_type.[table_name]` 定义字段与 Go 类型的映射关系。支持定义的数据类型参考数据类型章节 5. 运行 `atomctl gen model` 生成或更新 `models` 代码,确保代码与数据库结构同步。 ## 数据类型 ### Enum 类型 不使用数据库原生 `ENUM` 类型,使用业务代码来声明枚举类型,步骤如下: 1. 在 `pkg/consts/[table].go` 文件中,定义枚举字段的 Go 类型。例如: ```go // swagger:enum UserStatus // ENUM(pending_verify, verified, banned, ) type UserStatus string ``` 2. 执行 `atomctl gen enum`,生成 `pkg/consts/[table].gen.go` ### 其它支持的数据类型 `database/.transform.yaml` 中 `field_type` 支持将表字段映射为 `go.ipao.vip/gen/types` 提供的 PostgreSQL 扩展类型(在 `.transform.yaml` 的 `imports` 中引入 `go.ipao.vip/gen` 后,通常可直接使用 `types.*`)。 常用类型清单(对应 `gen/types/`): - `types.JSON`:`json/jsonb`(建议列类型用 `jsonb`) - `types.JSONMap`:`json/jsonb` 的 `map[string]any` 形态 - `types.JSONType[T]` / `types.JSONSlice[T]`:强类型 JSON(读写用,不提供 JSON 路径查询能力) - `types.Array[T]`:PostgreSQL 数组(如 `text[]/int[]` 等) - `types.UUID` / `types.BinUUID`:`uuid`(`BinUUID` 主要用于二进制存储场景) - `types.Date` / `types.Time`:`date` / `time` - `types.Money`:`money` - `types.URL`:URL(通常落库为 `text/varchar`,由类型负责解析/序列化) - `types.XML`:`xml` - `types.HexBytes`:`bytea`(hex 表示) - `types.BitString`:`bit/varbit` - 网络类型:`types.Inet`(`inet`)、`types.CIDR`(`cidr`)、`types.MACAddr`(`macaddr`) - 范围类型: - `types.Int4Range`(`int4range`) - `types.Int8Range`(`int8range`) - `types.NumRange`(`numrange`) - `types.TsRange`(`tsrange`) - `types.TstzRange`(`tstzrange`) - `types.DateRange`(`daterange`) - 几何类型:`types.Point` / `types.Polygon` / `types.Box` / `types.Circle` / `types.Path` - 全文检索:`types.TSQuery` / `types.TSVector` - 可空类型:`types.Null[T]` 以及别名 `types.NullString/NullInt64/...`(需要字段允许 NULL) 示例(`database/.transform.yaml`): ```yaml imports: - go.ipao.vip/gen - quyun/v2/pkg/consts field_type: users: roles: types.Array[consts.Role] meta: types.JSON home_ip: types.Inet profile: types.JSONType[Profile] tenants: uuid: types.UUID ``` ### 关联关系字段说明(对齐 GORM) 支持在 `database/.transform.yaml` 中为模型定义关联关系字段,生成对应的 GORM 关系标签。 示例: ```yaml field_relate: students: Class: # belong_to, has_one, has_many, many_to_many relation: belongs_to table: classes references: id # 关联表的主键/被引用键(通常是 id) foreign_key: class_id # 当前表上的外键列(如 students.class_id) json: class Teachers: # belong_to, has_one, has_many, many_to_many relation: many_to_many table: teachers pivot: class_teacher foreign_key: class_id # 当前表(students)用于关联的键(转为结构体字段名 ClassID) join_foreign_key: class_id # 中间表中指向当前表的列(class_teacher.class_id) references: id # 关联表(teachers)被引用的列(转为结构体字段名 ID) join_references: teacher_id # 中间表中指向关联表的列(class_teacher.teacher_id) json: teachers teachers: Classes: relation: many_to_many table: classes pivot: class_teacher classes: Teachers: relation: many_to_many table: teachers pivot: class_teacher ``` 关联关系配置项如下: - relation - 取值:`belongs_to`、`has_one`、`has_many`、`many_to_many`。 - 对应 GORM 的四种关系:Belongs To、Has One、Has Many、Many2Many。 - table - 关联的目标表名(即另一侧模型对应的表)。 - pivot(仅 many_to_many) - 多对多中间表名称,对应 GORM 标签 `many2many:`。 - foreign_key(按关系含义不同) - 对应 GORM 标签 `foreignKey:`。 - belongs_to:当前表上的外键列(例如 `students.class_id`),会映射为当前模型上的字段(如 `ClassID`)。 - has_one / has_many:外键在对端表上(例如 `credit_cards.user_id`)。配置时仍在当前表的配置块里填“外键列名”,生成时会正确落到 GORM 标签中。 - references(按关系含义不同) - 对应 GORM 标签 `references:`。 - belongs_to:对端表被引用的列(一般是 `id`),映射为对端模型字段名(如 `ID`)。 - has_one / has_many:被对端外键引用的当前模型列(一般是当前模型的 `ID` 字段)。 - join_foreign_key(仅 many_to_many) - 对应 GORM 标签 `joinForeignKey:`,指中间表里“指向当前模型”的列(如 `class_teacher.class_id`)。 - join_references(仅 many_to_many) - 对应 GORM 标签 `joinReferences:`,指中间表里“指向关联模型”的列(如 `class_teacher.teacher_id`)。 说明:生成器会结合数据库的 NamingStrategy 将列名(如 `class_id`、`teacher_id`)转换为结构体字段名(如 `ClassID`、`TeacherID`),并据此写入正确的 GORM 标签。 ### 与 GORM 标签的对应关系 - belongs_to 示例(students → classes) - YAML: - `foreign_key: class_id` - `references: id` - 生成的模型字段(示意): - `Class Class gorm:"foreignKey:ClassID;references:ID"` - has_many 示例(users → credit_cards) - YAML(在 `users` 下配置 `CreditCards` 关系): - `relation: has_many` - `table: credit_cards` - `foreign_key: user_id` (对端表上的外键列) - `references: id` (当前模型被引用的列) - 生成的模型字段(示意): - `CreditCards []CreditCard gorm:"foreignKey:UserID;references:ID"` - many_to_many 示例(students ⇄ teachers,经由 class_teacher) - YAML(在 `students` 下配置 `Teachers` 关系): - `relation: many_to_many` - `table: teachers` - `pivot: class_teacher` - `foreign_key: class_id` - `join_foreign_key: class_id` - `references: id` - `join_references: teacher_id` - 生成的模型字段(示意): - `Teachers []Teacher gorm:"many2many:class_teacher;foreignKey:ClassID;references:ID;joinForeignKey:ClassID;joinReferences:TeacherID"` 提示:GORM 在 many2many 下允许省略部分键,生成器也支持“只给必要字段”。若不确定,建议显式全部写出,避免命名不一致导致推断失败。 ## model 功能扩展 模型生成完后可以创建 `database/models/[table].go` 文件,添加自定义方法、GORM Hook 来扩展 model 的使用。例如: ```go func (m *User) ComparePassword(ctx context.Context, password string) bool { err := bcrypt.CompareHashAndPassword([]byte(m.Password), []byte(password)) return err == nil } ``` ## 生成模型的使用 模型通常在 service 中使用,service 定义于 `app/services`, 通常用于对一类功能进行封装,方便 controller 层调用,不需要与表进行一一对应。 示例: ```go package services import "context" // @provider type test struct{ app *app.Config } func (t *test) Test(ctx context.Context) (string, error) { return "Test", nil } ``` struct 中可以定义一个多上需要注入的 provider 对象,示例中的 app 会自动注入对应的 app.Config 实例。 service 文件创建完成后需要运行 `atomctl gen service` 和 `atomctl gen provider` 完成依赖对象注入。 service 调用 model 示例: ```go func (t *test) FindByID(ctx context.Context, userID int64) (*models.User, error) { tbl, query := models.UserQuery.QueryContext(ctx) model, err := query.Preload(tbl.OwnedTenant, tbl.Tenants).Where(tbl.ID.Eq(userID)).First() if err != nil { return nil, errors.Wrapf(err, "FindByID failed, %d", userID) } return model, nil } ··· ```