★ 实战问答:规范路由实现用户 CRUD 的完整套路

面试问法:现场写一个“用户模块 CRUD”,你会怎么组织 GoFrame 代码? 一句话结论:八步套路:建表 → gf gen dao 生成数据层 → api 定义 Req/Res(含 g.Meta 路由与 v 校验)→ controller 薄接线 → service 实现业务 → 分组绑定 + 统一响应中间件 → 补分页/错误码 → 测试与文档。关键在“controller 薄、service 厚、api 即文档”。

八步套路

步骤产出要点
1 建表表结构字段注释写清楚,会进生成代码
2 gen daodao/do/entityhack/config.yaml 配 gfcli.gen.dao
3 定义 apiReq/Resg.Meta 声明 path/method,v 标签校验
4 生成/手写 controller接口骨架签名 (ctx, req) (res, err)
5 service 实现业务逻辑CRUD、密码哈希、校验业务规则
6 注册绑定路由生效group.Bind + 统一响应中间件
7 公共能力分页/错误码Page+ScanAndCount、gerror.NewCode
8 验证文档/测试openapiPath 出文档,接口测试回归

骨架代码

// api/user/v1/user.go —— 接口即定义
type UserCreateReq struct {
    g.Meta   `path:"/user" method:"post" tags:"User" summary:"create user"`
    Name     string `v:"required|length:2,30" json:"name"`
    Password string `v:"required|length:6,64" json:"password"`
}
type UserCreateRes struct{ Id int64 `json:"id"` }

type UserGetOneReq struct {
    g.Meta `path:"/user/{id}" method:"get" tags:"User" summary:"get user"`
    Id     int64 `v:"required" in:"path" json:"id"`
}
type UserGetOneRes struct {
    Id   int64  `json:"id"`
    Name string `json:"name"`
}
// internal/service(或 logic)—— 业务实现
func Create(ctx context.Context, in *v1.UserCreateReq) (out *v1.UserCreateRes, err error) {
    hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost) // 框架未内置哈希,选型以官方为准
    if err != nil { return nil, err }
    id, err := dao.User.Ctx(ctx).Data(do.User{Name: in.Name, Password: string(hash)}).InsertAndGetId()
    if err != nil { return nil, err }
    return &v1.UserCreateRes{Id: id}, nil
}

func GetOne(ctx context.Context, in *v1.UserGetOneReq) (out *v1.UserGetOneRes, err error) {
    var user entity.User
    if err = dao.User.Ctx(ctx).Where("id", in.Id).Scan(&user); err != nil { return nil, err }
    if user.Id == 0 { return nil, gerror.NewCode(code.UserNotExist) } // 业务错误码 >1000
    return &v1.UserGetOneRes{Id: user.Id, Name: user.Name}, nil
}
// main:统一响应 + 分组绑定;大量接口时用 controller 包 + gen ctrl 生成骨架
s := g.Server()
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/api", func(group *ghttp.RouterGroup) {
    group.Bind(new(controller.User))
})
s.Run()

常见追问

  • 追问:为什么 controller 要薄?——参数绑定/校验/路由由规范路由承接,controller 只做编排;业务放 service 才能被多个入口复用(HTTP/gRPC/定时任务)。
  • 追问:校验失败返回什么?——规范路由自动校验失败即停并返回错误,经统一响应中间件输出非零 code;前端按统一结构解析即可。
  • 追问:列表带条件怎么加?——Where 链按需拼接,分页用 Page+ScanAndCount(见分页章节),排序字段白名单化。

记忆点

  • 口诀:表→dao→api→controller→service→Bind→公共能力→文档测试;api 即文档、controller 薄、service 厚。
笔记加载中…