可以看到,通过项目脚手架工具,很多与项目业务逻辑无关的代码都已经预先生成好,我们只需要关注业务逻辑实现即可。我们接下来看看如何实现CURD具体逻辑吧。

创建接口

创建逻辑实现

internal/controller/user/user_v1_create.go

  1. package user
  2. import (
  3. "context"
  4. "demo/api/user/v1"
  5. "demo/internal/dao"
  6. "demo/internal/model/do"
  7. )
  8. func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
  9. insertId, err := dao.User.Ctx(ctx).Data(do.User{
  10. Name: req.Name,
  11. Status: v1.StatusOK,
  12. Age: req.Age,
  13. }).InsertAndGetId()
  14. if err != nil {
  15. return nil, err
  16. }
  17. res = &v1.CreateRes{
  18. Id: insertId,
  19. }
  20. return
  21. }

Create实现方法中:

  • 我们通过dao.User通过dao组件操作user表。
  • 每个dao操作都需要传递ctx参数,因此我们通过Ctx(ctx)方法创建一个gdb.Model对象,该对象是框架的模型对象,用于操作特定的数据表。
  • 通过Data传递需要写入数据表的数据,我们这里使用do转换模型对象输入我们的数据。do转换模型会自动过滤nil数据,并在底层自动转换为对应的数据表字段类型。在绝大部分时候,我们都使用do转换模型来给数据库操作对象传递写入/更新参数、查询条件等数据。
  • 通过InsertAndGetId方法将Data的参数写入数据库,并返回新创建的记录主键id

参数校验实现

等等,大家可能会问,为什么这里没有校验逻辑呢?因为校验逻辑都已经配置到请求参数对象CreateReq上了。还记得前面介绍的v标签吗?我们再来看看这个请求参数对象:

api/user/v1/user.go

  1. type CreateReq struct {
  2. g.Meta `path:"/user" method:"put" tags:"User" summary:"Create user"`
  3. Name string `v:"required|length:3,10" dc:"user name"`
  4. Age uint `v:"required|between:18,200" dc:"user age"`
  5. }
  6. type CreateRes struct {
  7. Id int64 `json:"id" dc:"user id"`
  8. }

这里的required/length/between校验规则在调用路由函数Create之前就已经由GoFrame框架的Server自动执行了。 如果请求参数校验失败,会立即返回错误,不会进入到路由函数。GoFrame框架的这种机制极大地简便了开发流程, 开发者在这个路由函数中,仅需要关注业务逻辑实现即可。

Step5 - 完成接口逻辑实现 - 图1信息

当然,如果有一些额外的、定制化的业务逻辑校验,是需要在路由函数中自行实现的哟。

删除接口

internal/controller/user/user_v1_delete.go

  1. package user
  2. import (
  3. "context"
  4. "demo/api/user/v1"
  5. "demo/internal/dao"
  6. )
  7. func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) {
  8. _, err = dao.User.Ctx(ctx).WherePri(req.Id).Delete()
  9. return
  10. }

删除逻辑比较简单,我们这里用到一个WherePri方法,该方法会将给定的参数req.Id作为主键进行Where条件限制。

更新接口

internal/controller/user/user_v1_update.go

  1. package user
  2. import (
  3. "context"
  4. "demo/api/user/v1"
  5. "demo/internal/dao"
  6. "demo/internal/model/do"
  7. )
  8. func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
  9. _, err = dao.User.Ctx(ctx).Data(do.User{
  10. Name: req.Name,
  11. Status: req.Status,
  12. Age: req.Age,
  13. }).WherePri(req.Id).Update()
  14. return
  15. }

更新接口也比较简单,除了已经介绍过的WherePri方法,在更新数据时也需要通过Data方法传递更新的数据。

查询接口(单个)

internal/controller/user/user_v1_get_one.go

  1. package user
  2. import (
  3. "context"
  4. "demo/api/user/v1"
  5. "demo/internal/dao"
  6. )
  7. func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) {
  8. res = &v1.GetOneRes{}
  9. err = dao.User.Ctx(ctx).WherePri(req.Id).Scan(&res.User)
  10. return
  11. }

数据查询接口中,我们使用了Scan方法,该方法可以将查询到的单条数据表记录智能地映射到结构体对象上。大家需要注意这里的&res.User中的User属性对象其实是没有初始化的,其值为nil。如果查询到了数据,Scan方法会对其做初始化并赋值,如果查询不到数据,那么Scan方法什么都不会做,其值还是nil

查询接口(列表)

internal/controller/user/user_v1_get_list.go

  1. package user
  2. import (
  3. "context"
  4. "demo/api/user/v1"
  5. "demo/internal/dao"
  6. "demo/internal/model/do"
  7. )
  8. func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
  9. res = &v1.GetListRes{}
  10. err = dao.User.Ctx(ctx).Where(do.User{
  11. Age: req.Age,
  12. Status: req.Status,
  13. }).Scan(&res.List)
  14. return
  15. }

查询列表数据我们同样使用到了Scan方法,这个方法是非常强大的。同查询单条数据的逻辑一样,它仅会在查询的数据时才会初始化这里的&res.List

学习小结

本章节的示例源码:https://github.com/gogf/quick-demo/tree/main/internal/controller/user

可以看到,使用GoFrame数据库ORM组件可以非常快速、高效地完成接口开发工作。整个CURD接口开发下来,开发者需要实现的业务逻辑仅需要几行代码😼。

开发效率的提升,除了归功于脚手架工具自动生成的daocontroller代码之外,强大的数据库ORM组件也是功不可没。可以看到,我们在对数据库表进行操作时,代码量非常简洁优雅,但在数据库ORM组件的内部设计中,涉及很多精细的设计、严格的代码测试、年复一年的功能迭代的沉淀结果。

接口逻辑开发完了,在下一步,我们需要做一些数据库配置和路由注册的操作,同样也是非常简便,一起看看吧。