把「写 .api 定义接口 → 建表 → goctl 生成 Model → 装配 svc → 写 logic 业务逻辑 → 跑起来」这条端到端链路一次讲清,并专门说清楚每一步对应的 goctl 自动化命令。
先把最关键的说在前面。
go-zero 的后端开发是「声明式契约 + 代码生成」模式:你用 .api(HTTP)/ .proto(RPC)描述接口、用 .sql 或现成的库描述表,goctl 把骨架代码(handler / logic / types / model / svc 骨架)一次性生成出来,你只在 logic 里填业务。大部分重复性代码(路由注册、参数绑定、CRUD、缓存)都不用你手写。
左到右:你在哪写、命令在哪跑、产物去哪、最终谁干活。
.api 契约这是「接口」的源头——你描述接口长什么样,不写实现。
// user.api syntax = "v1" info ( title: "user api" desc: "用户服务" version: "1.0" ) // 数据结构:入参与出参 type RegisterReq { Username string `json:"username"` Password string `json:"password"` } type RegisterResp { Id int64 `json:"id"` } // 分组配置:统一前缀 + jwt @server ( jwt: Auth prefix: /user ) // 路由声明 service user-api { @handler Register post /register (RegisterReq) returns (RegisterResp) @handler Login post /login (LoginReq) returns (LoginResp) }
type 定义数据形状、@handler + 方法/路径定义路由、service 把它们归到一个服务。RPC 接口同理,只是换成 .proto。这一份文件决定了三件事:① 会生成哪些 handler(RegisterHandler / LoginHandler);② 会生成哪些 types(RegisterReq 等);③ 路由怎么挂(/user/register 走 POST,且整组要 jwt)。 详细的「API 怎么长出来」见 go-zero API 定义全流程。
go-zero 不帮你建表,它「读表生成 Model」。所以表要你先准备好。
CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, username VARCHAR(64) NOT NULL DEFAULT '', password VARCHAR(128) NOT NULL DEFAULT '', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_username (username) -- 二级索引 → 生成 FindOneByUsername ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
然后你(或 DBA / 迁移工具)把表建进库:mysql -uroot -p test < user.sql。也可以用 golang-migrate 管理建表。
username 加 KEY idx_username 是为了让 goctl 自动生成 FindOneByUsername,少写自定义 SQL。这一步是自动化的核心:两条命令,把 .api 和 .sql 翻译成可编译工程。
goctl api go -api user.api -dir . # 在当前目录生成工程
生成后目录多出来:
goctl model mysql ddl \ --src user.sql \ # 你的建表 SQL --dir ./internal/model \ --cache # 开 Redis 缓存(主键/唯一索引自动缓存)
生成的 model 包:
现在你有了一个能编译但逻辑空的工程:路由、参数绑定、CRUD 全有了,只差往 logic 里塞业务,以及把 Model 接进 svc。
go-zero 的依赖注入就发生在这:config 声明要什么,svc 把它们建出来一次、供所有 logic 复用。
type Config struct { rest.RestConf Mysql sqlx.MysqlConf // 来自 etc/*.yaml 的 DataSource CacheRedis redis.RedisConf // 开了 --cache 就必须配 Auth struct { AccessSecret string AccessExpire int64 } }
type ServiceContext struct { Config config.Config UserModel model.UserModel // 注入 Model } func NewServiceContext(c config.Config) *ServiceContext { conn := sqlx.NewMysql(c.Mysql.DataSource) // 一个连接池 return &ServiceContext{ Config: c, UserModel: model.NewUserModel(conn, c.CacheRedis), // 共享同一个 conn } }
骨架已就位,你只实现每个接口的 Login/Register 方法,依赖全从 svcCtx 取。
func (l *RegisterLogic) Register(req *types.RegisterReq) (*types.RegisterResp, error) { // 1) 查重:直接用生成的 FindOneByUsername if _, err := l.svcCtx.UserModel.FindOneByUsername(l.ctx, req.Username); err == nil { return nil, errors.New("用户已存在") } else if !errors.Is(err, model.ErrNotFound) { return nil, err } // 2) 加密密码(包级工具放 internal 或 pkg,见下方说明) hash := utils.HashPassword(req.Password) // 3) 入库:生成的 Insert res, err := l.svcCtx.UserModel.Insert(l.ctx, &model.User{ Username: req.Username, Password: hash, }) if err != nil { return nil, err } id, _ := res.LastInsertId() return &types.RegisterResp{Id: id}, nil }
httpc(见 内/外部服务调用)。这些都是在 svc 装配、logic 调用,模式完全一致。工程齐全,跑起来即可。
go mod tidy # 拉依赖(首次) go run user.go -f etc/user-api.yaml # -f 指定配置文件(main 自带该 flag) # 验证 curl -X POST localhost:8888/user/register \ -H 'Content-Type: application/json' \ -d '{"username":"alice","password":"123"}'
这是你问的「自动化使用命令的流程」——把每类命令、触发时机、产物一次列清。
| 命令 | 何时用 | 生成/做了啥 | 常见参数 |
|---|---|---|---|
goctl api go | 写好 .api 后,新建/整体生成 HTTP 工程 | handler / logic 骨架 / types / routes / svc 骨架 / config / main / etc yaml | -api -dir -style |
goctl rpc protoc | 写好 .proto 后,生成 RPC 服务 | pb.go / 服务端骨架 / 客户端 stub(zrpc 封装) | --go_out --go-grpc_out --zrpc_out |
goctl model mysql ddl | 有建表 SQL 时生成 Model | model 包(CRUD + 类型 + 缓存逻辑) | --src --dir --cache |
goctl model mysql datasource | 表已在库里,直接反查生成 Model | 同 ddl(信息源是活库,不用写 SQL) | --url --table --dir --cache |
goctl api doc | 想出接口文档时 | 从 .api 生成 markdown / openapi 文档 | --dir |
goctl docker | 部署前,生成 Dockerfile | 多阶段构建 Dockerfile(含 -f 启动) | --go -o |
goctl kube deploy | 上 K8s 前,生成 yaml | Deployment / Service yaml | --o |
goctl template init | 想改生成代码风格时 | 拉取可编辑的代码模板(自定义生成产物) | --home |
典型「首次开发」命令序列:
① goctl api go -api user.api -dir . (出 HTTP 骨架)
② goctl model mysql ddl --src user.sql --dir ./internal/model --cache (出 Model)
③ 手改 config.go + servicecontext.go 把 Model 接进 svc
④ 在 logic/*.go 填业务
⑤ goctl docker 出 Dockerfile → 部署
goctl model 重新生成 _gen.go,自定义 SQL 写在 usermodel.go 不会被覆盖。改了 .api?重跑 goctl api go 会刷新对应 handler/logic/types,但已填的 logic 内容一般在合并时保留(建议小步多次生成、别大改 .api 后整体覆盖)。把上面五步用一条 Register 请求串起来,看数据流怎么走。
请求 POST /user/register → routes.go 匹配到 RegisterHandler(goctl 生成的)→ 解析进 RegisterReq(types,goctl 生成)→ 调 RegisterLogic.Register(你写的)→ 用 l.svcCtx.UserModel.Insert(goctl 生成的 Model)写库 → 返回 RegisterResp JSON。全程只有 logic 是手写,其余皆生成。
照着做,流程不翻车。
| 该做 / 别做 | 说明 |
|---|---|
| ✅ 先写契约(.api/.proto/.sql)再生成 | 声明式开发,生成产物以契约为准 |
| ✅ 业务只写在 logic | handler / _gen.go / routes.go 都不手改,留给 goctl |
| ✅ 自定义 Model 方法写 usermodel.go | 别碰 _gen.go,重生成会被覆盖 |
| ✅ 依赖在 svc 一次性装配 | 连接池/客户端只建一份,logic 通过 svcCtx 取 |
| ✅ 建表顺序先于 goctl model | 表不存在 → 连库生成 Model 直接报错 |
| ⚠️ 别在 logic 里现 new 连接 | 破坏连接池复用,且难测试;统一走 svc |
| ⚠️ 改表/改 .api 后重跑对应 goctl | 否则代码与契约/表不一致,运行时才暴露 |
| ❌ 别把环境差异写回代码 | 环境靠 -f + ${ENV} + Mode,不在 logic 散 if env |
目标都是写接口,但「谁生成代码、谁建表」机制不同。
| 环节 | go-zero(本文) | Gin + GORM(手写) |
|---|---|---|
| 定义接口 | 写 .api → goctl 生成 handler/route/types | 手写 route + struct + 绑定 |
| 建表 | 你先建表,goctl 读表生成 Model | AutoMigrate 运行时自动建/改表 |
| 数据访问 | goctl 生成 sqlx Model(可选缓存) | 手写 struct + GORM 调用,或自己封装 |
| 依赖注入 | svc 容器集中装配(编译期) | 通常 wire / 手动 / 全局 var |
| 重复性代码 | 少(大量生成) | 多(路由、绑定、CRUD 多手写) |
| 代价 | 多学一套 .api DSL + 生成流程 | 灵活、自由,但样板代码多 |