reset backend
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
# 新增 HTTP 接口流程
|
||||
|
||||
项目 `controller` 定义于 `backend/app/http/[module_name]/[controller].go` 文件中。 每个 `controller` 负责处理一组相关的 HTTP 请求。 例如,`backend/app/http/super/tenant.go` 负责处理与租户相关的请求。
|
||||
|
||||
## 新增接口步骤
|
||||
|
||||
1. 在 `controller` 中新增方法以处理特定的 HTTP 请求。 例如,在 `tenant.go` 新增一个 `list` 方法来处理列出租户的请求。
|
||||
2. 定义相关 `swagger` 注解以生成 API 文档。 这些注解通常位于方法上方,描述了请求路径、参数和响应格式。
|
||||
3. 在模块 `dto/`(数据传输对象)目录中定义请求和响应的数据结构, 以确保数据的一致性和类型安全。
|
||||
4. 运行 `atomctl gen route` 生成路由文件,确保新接口被正确注册。
|
||||
5. 运行 `atomctl gen provider` 生成路由文件,确保新接口被正确注册。
|
||||
|
||||
## 接口定义示例:
|
||||
|
||||
1. 实现需要返回数据的接口。
|
||||
|
||||
```go
|
||||
func (*tenant) list(ctx fiber.Ctx, filter *dto.TenantFilter) (*requests.Pager, error) {
|
||||
return nil,nil
|
||||
}
|
||||
```
|
||||
|
||||
2. 实现不需要返回数据的接口
|
||||
|
||||
```go
|
||||
func (*tenant) update(ctx fiber.Ctx, tenantID int64, form *dto.TenantExpireUpdateForm) error {
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## swagger 注解说明
|
||||
|
||||
- **@Summary**: 接口的简要描述。
|
||||
- **@Description**: 接口的详细描述。
|
||||
- **@Tags**: 接口所属的分类标签。
|
||||
- **@Accept**: 接口接受的数据格式。通常为 `json`
|
||||
- **@Produce**: 接口返回的数据格式。通常为 `json`
|
||||
- **@Param**: 定义接口的参数,包括参数名称、数据来源位置、数据类型、是否必须、和描述。如:`// @Param form body dto.LoginForm true "form"`
|
||||
- **@Success**: 定义接口成功时的响应格式。如:
|
||||
- 返回分页列表 `// @Success 200 {object} requests.Pager{items=dto.Item}`
|
||||
- 直接返回对象 `// @Success 200 {object} dto.Item`
|
||||
- 返回数据对象列表 `// @Success 200 {array} dto.Item`
|
||||
- **@Router**: 定义接口的路由信息,包括路径和请求方法。如:`// @Router /super/tenants [get|post|put|delete|patch]`, 如果需要定义 path 参数,使用 `:paramName` 语法表示,如:`/super/tenants/:tenantID`
|
||||
- **@Bind**: 定义参数绑定方式,格式: `@Bind <paramName> <position> [key(<key>)] [model(<field>|<model>[:<field>])]`
|
||||
- `paramName` 与方法参数名一致(大小写敏感)
|
||||
- `position`:`path`、`query`、`body`、`header`、`cookie`、`local`、`file`
|
||||
- 可选:
|
||||
- `key()` 覆盖默认键名;
|
||||
- `model()` 详见“模型绑定”。
|
||||
|
||||
### 参数绑定
|
||||
|
||||
- query:标量用 `QueryParam[T]("key")`,非标量用 `Query[T]("key")`
|
||||
- path:标量用 `PathParam[T]("key")`,非标量用 `Path[T]("key")`
|
||||
- 若使用 `model()`(仅在 path 有效),会按字段值查询并绑定为 `T`,详见下文
|
||||
- header:`Header[T]("key")`
|
||||
- body:`Body[T]("key")`
|
||||
- cookie:`string` 用 `CookieParam("key")`,其他用 `Cookie[T]("key")`
|
||||
- file:`File[multipart.FileHeader]("key")`
|
||||
- local:`Local[T]("key")`
|
||||
|
||||
说明:
|
||||
|
||||
- 标量类型集合:`string`、`int`、`int32`、`int64`、`float32`、`float64`、`bool`
|
||||
- `key` 默认等于 `paramName`;设置 `key(...)` 后以其为准
|
||||
- `file` 使用固定类型 `multipart.FileHeader`
|
||||
|
||||
### 类型与指针处理
|
||||
|
||||
- 支持 `T`、`*T`、`pkg.T`、`*pkg.T`;会正确收集选择子表达式对应 import
|
||||
- 忽略结尾为 `Context` 或 `Ctx` 的参数(框架上下文)
|
||||
- 指针处理:除 `local` 外会去掉前导 `*` 作为泛型实参;`local` 保留指针(便于写回)
|
||||
|
||||
### 模型绑定
|
||||
|
||||
当 `@Bind ... model(...)` 配合 `position=path` 使用时,将根据路径参数值查询模型并绑定为方法参数类型的实例(`T` 来自方法参数)。
|
||||
|
||||
- 语法:
|
||||
- 仅字段:`model(id)`(推荐)
|
||||
- 指定字段与类型:`model(id:int)`、`model(code:string)`(用于非字符串路径参数)
|
||||
- 指定类型与字段:`model(pkg.Type:field)` 或 `model(pkg.Type)`(字段缺省为 `id`)
|
||||
- 行为:
|
||||
- 生成的绑定器会按给定字段构造查询条件并返回首条记录
|
||||
- 自动注入 import:`field "go.ipao.vip/gen/field"`,用于构造字段条件表达式
|
||||
|
||||
示例:
|
||||
|
||||
```go
|
||||
// @Router /users/:id [get]
|
||||
// @Bind user path key(id) model(id)
|
||||
func (uc *UserController) Show(ctx context.Context, user *models.User) (*UserDTO, error)
|
||||
```
|
||||
@@ -1,226 +0,0 @@
|
||||
# 新增 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:<pivot>`。
|
||||
|
||||
- foreign_key(按关系含义不同)
|
||||
|
||||
- 对应 GORM 标签 `foreignKey:<Field>`。
|
||||
- belongs_to:当前表上的外键列(例如 `students.class_id`),会映射为当前模型上的字段(如 `ClassID`)。
|
||||
- has_one / has_many:外键在对端表上(例如 `credit_cards.user_id`)。配置时仍在当前表的配置块里填“外键列名”,生成时会正确落到 GORM 标签中。
|
||||
|
||||
- references(按关系含义不同)
|
||||
|
||||
- 对应 GORM 标签 `references:<Field>`。
|
||||
- belongs_to:对端表被引用的列(一般是 `id`),映射为对端模型字段名(如 `ID`)。
|
||||
- has_one / has_many:被对端外键引用的当前模型列(一般是当前模型的 `ID` 字段)。
|
||||
|
||||
- join_foreign_key(仅 many_to_many)
|
||||
|
||||
- 对应 GORM 标签 `joinForeignKey:<Field>`,指中间表里“指向当前模型”的列(如 `class_teacher.class_id`)。
|
||||
|
||||
- join_references(仅 many_to_many)
|
||||
- 对应 GORM 标签 `joinReferences:<Field>`,指中间表里“指向关联模型”的列(如 `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
|
||||
}
|
||||
···
|
||||
```
|
||||
Reference in New Issue
Block a user