从一份 .api 文件,到路由注册、handler、logic —— 定义路由、注册路由、定义接口、handler 这一整套流程,图文 + 代码一次讲清。
go-zero 的 API 开发是「声明式」的:你用一份 DSL(.api)描述接口长什么样,goctl 把骨架代码一次性生成出来,你只在 logic 里填业务。
logic 是你自由发挥的地方。理解这条,下面每步都顺了。一份 .api 文件由三块组成:type(数据结构)、@server(分组配置)、service(路由与 handler 声明)。
// user.api syntax = "v1" info ( title: "user api" desc: "用户服务接口" version: "1.0" ) // 1) 数据结构:请求与响应的「形状」 type LoginReq { Username string `json:"username"` Password string `json:"password"` } type LoginResp { Token string `json:"token"` ExpireAt int64 `json:"expireAt"` } // 2) 分组配置:这一组接口共用 jwt 鉴权 + 路径前缀 @server ( jwt: Auth prefix: /user ) // 3) service 块:声明路由与对应的 handler 名 service user-api { @handler Login post /login (LoginReq) returns (LoginResp) @handler GetProfile get /profile }
internal/types/types.go(见 types 解析篇)。Login)。goctl 据此生成 LoginHandler 函数和 NewLoginLogic。(LoginReq) returns (LoginResp) 绑定数据类型。一条命令,把 .api 翻译成可编译的 Go 工程。
goctl api go -api user.api -dir . # 在当前目录生成工程
生成后多出这些关键文件(和 types 篇目录树对应):
你不用手写 mux.HandleFunc。路由的「定义」就是 .api 里那两行:@handler 决定处理函数名,post /login 决定方法 + 路径。
回顾 .api 里的声明:
@handler Login → 生成函数 LoginHandler
post /login (LoginReq) returns (LoginResp) → 方法 POST、路径 /login、绑定入参/出参类型。
上面 prefix: /user 会让最终路径变成 /user/login(前缀 + 路由路径拼接);jwt: Auth 会让这组路由自动要求 JWT 鉴权。这些是「路由定义」的一部分,写在 .api,由生成代码落地。
goctl 生成的 internal/handler/routes.go 把所有路由集中注册到 rest server。你通常不用动它。
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { server.AddRoutes( []rest.Route{ { Method: http.MethodPost, Path: "/user/login", // prefix + 路由路径 Handler: LoginHandler(serverCtx), // 指向生成的 handler }, { Method: http.MethodGet, Path: "/user/profile", Handler: GetProfileHandler(serverCtx), }, }, rest.WithJwt(serverCtx.Config.Auth.AccessSecret), // @server jwt 落地 rest.WithPrefix("/user"), // @server prefix 落地 ) }
main.go 调用 handler.RegisterHandlers(restServer, svcCtx),把路由挂到 server 上。@server 分组对应一次 AddRoutes 调用,用 WithJwt/WithPrefix/WithMiddleware 携带分组配置。客户端真正看到的「接口」= .api 里的路由 + 类型;而 handler 是把 HTTP 请求翻译成对 logic 调用的胶水函数,由 goctl 生成。
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req types.LoginReq if err := httpx.Parse(r, &req); err != nil { // 解析路径/query/body httpx.ErrorCtx(r.Context(), w, err) return } l := logic.NewLoginLogic(r.Context(), svcCtx) // 拿到 logic resp, err := l.Login(&req) // 调业务逻辑 if err != nil { httpx.ErrorCtx(r.Context(), w, err) return } httpx.OkJsonCtx(r.Context(), w, resp) // 写 JSON 响应 } }
handler 三件事:① 用 httpx.Parse 把请求绑定到 types.LoginReq;② 用 NewXxxLogic(ctx, svcCtx) 造 logic;③ 调 logic 方法并写回 JSON。 它不写业务 —— 业务在 logic。
type LoginLogic struct { ctx context.Context svcCtx *svc.ServiceContext // 所有依赖从这里取 } func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { return &LoginLogic{ctx: ctx, svcCtx: svcCtx} } func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) { // TODO: 查库、校验密码、签发 token,用 l.svcCtx.UserModel / l.svcCtx.UserRpc ... return }
把「定义路由 → 注册路由 → handler → logic」连成一条线,看一个 POST /user/login 是怎么走完的。
请求打到 rest server,按方法 + 路径 POST /user/login 匹配到 LoginHandler。
httpx.Parse 把 body 解析进 types.LoginReq;若开启 jwt,先校验 token(@server jwt 生效)。
NewLoginLogic(ctx, svcCtx).Login(&req),业务逻辑从这里开始,通过 svcCtx 取 DB/RPC/Cache 等依赖。
logic 返回 *types.LoginResp,handler 用 httpx.OkJsonCtx 序列化成 JSON 返回客户端。
| 环节 | 你在哪写 | 谁生成的 | 作用 |
|---|---|---|---|
| 定义路由 | .api 的 @handler + 方法/路径 | 你手写 | 声明接口契约 |
| 数据形状 | .api 的 type | goctl → types.go | 请求/响应结构体 |
| 注册路由 | 不用写 | goctl → routes.go | 把路由挂到 rest server |
| 定义接口(handler) | 不用写 | goctl → handler/*.go | 解析请求、调 logic、写响应 |
| 业务逻辑 | logic/*.go | goctl 出骨架,你填 | 真正干活的地方 |
| 依赖装配 | svc/servicecontext.go | goctl 出骨架,你补 | DB/RPC/Cache 注入 logic |
.api 里「声明」、由 goctl「生成注册」;业务在 logic 里「手写」。 想加一个接口?先在 .api 加两行,再跑 goctl,最后去 logic 填代码——三步完事。