9.0 KiB
新增 model 流程
项目 models 定义于 backend/database/models 文件中。 每个 model 对应数据库中的一张表。 新增 model 的步骤如下:
步骤
- 运行
atomctl migrate create [alter|create_table]创建迁移文件。 - 编辑生成的迁移文件,定义数据库表结构变更。不需要声明
BEGIN和COMMIT,框架会自动处理。table 名称使用复数形式,例如tenants。 - 执行
atomctl migrate up应用迁移,更新数据库结构。 - 对于
JSONARRAYENUM等复杂字段类型,编辑database/.transform.yaml文件,在field_type.[table_name]定义字段与 Go 类型的映射关系。支持定义的数据类型参考数据类型章节 - 运行
atomctl gen model生成或更新models代码,确保代码与数据库结构同步。
数据类型
Enum 类型
不使用数据库原生 ENUM 类型,使用业务代码来声明枚举类型,步骤如下:
- 在
pkg/consts/[table].go文件中,定义枚举字段的 Go 类型。例如:
// swagger:enum UserStatus
// ENUM(pending_verify, verified, banned, )
type UserStatus string
- 执行
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/timetypes.Money:moneytypes.URL:URL(通常落库为text/varchar,由类型负责解析/序列化)types.XML:xmltypes.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):
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 关系标签。
示例:
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:<pivot>。
- 多对多中间表名称,对应 GORM 标签
-
foreign_key(按关系含义不同)
- 对应 GORM 标签
foreignKey:<Field>。 - belongs_to:当前表上的外键列(例如
students.class_id),会映射为当前模型上的字段(如ClassID)。 - has_one / has_many:外键在对端表上(例如
credit_cards.user_id)。配置时仍在当前表的配置块里填“外键列名”,生成时会正确落到 GORM 标签中。
- 对应 GORM 标签
-
references(按关系含义不同)
- 对应 GORM 标签
references:<Field>。 - belongs_to:对端表被引用的列(一般是
id),映射为对端模型字段名(如ID)。 - has_one / has_many:被对端外键引用的当前模型列(一般是当前模型的
ID字段)。
- 对应 GORM 标签
-
join_foreign_key(仅 many_to_many)
- 对应 GORM 标签
joinForeignKey:<Field>,指中间表里“指向当前模型”的列(如class_teacher.class_id)。
- 对应 GORM 标签
-
join_references(仅 many_to_many)
- 对应 GORM 标签
joinReferences:<Field>,指中间表里“指向关联模型”的列(如class_teacher.teacher_id)。
- 对应 GORM 标签
说明:生成器会结合数据库的 NamingStrategy 将列名(如 class_id、teacher_id)转换为结构体字段名(如 ClassID、TeacherID),并据此写入正确的 GORM 标签。
与 GORM 标签的对应关系
-
belongs_to 示例(students → classes)
- YAML:
foreign_key: class_idreferences: id
- 生成的模型字段(示意):
Class Class gorm:"foreignKey:ClassID;references:ID"
- YAML:
-
has_many 示例(users → credit_cards)
- YAML(在
users下配置CreditCards关系):relation: has_manytable: credit_cardsforeign_key: user_id(对端表上的外键列)references: id(当前模型被引用的列)
- 生成的模型字段(示意):
CreditCards []CreditCard gorm:"foreignKey:UserID;references:ID"
- YAML(在
-
many_to_many 示例(students ⇄ teachers,经由 class_teacher)
- YAML(在
students下配置Teachers关系):relation: many_to_manytable: teacherspivot: class_teacherforeign_key: class_idjoin_foreign_key: class_idreferences: idjoin_references: teacher_id
- 生成的模型字段(示意):
Teachers []Teacher gorm:"many2many:class_teacher;foreignKey:ClassID;references:ID;joinForeignKey:ClassID;joinReferences:TeacherID"
- YAML(在
提示:GORM 在 many2many 下允许省略部分键,生成器也支持“只给必要字段”。若不确定,建议显式全部写出,避免命名不一致导致推断失败。
model 功能扩展
模型生成完后可以创建 database/models/[table].go 文件,添加自定义方法、GORM Hook 来扩展 model 的使用。例如:
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 层调用,不需要与表进行一一对应。 示例:
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 示例:
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
}
···