API 服务开发
go-zero 使用自研的 API DSL(领域特定语言)描述 HTTP 服务。.api 文件既是 goctl 的输入,也是接口文档和参数校验的载体。
API DSL 文件结构
syntax = "v1" // 必须的版本声明
info ( // 可选元数据(纯文档,不参与代码生成)
title: "用户 API"
version: "1.0"
author: "dev-team"
)
import "base.api" // 导入其他 .api 文件(支持相对/绝对路径)
type (...) // 类型定义块
service name-api { // 服务定义块
@server (...)
@handler HandlerName
method /path (RequestType) returns (ResponseType)
}类型定义(type)
类型定义直接映射为 Go 结构体,使用标准的 Go struct tag:
type (
// JSON body 参数
LoginReq {
Username string `json:"username"`
Password string `json:"password"`
}
// URL 路径参数 → /user/:id
UserReq {
Id int64 `path:"id"`
}
// URL 查询参数 → /search?keyword=foo&page=1
SearchReq {
Keyword string `form:"keyword"`
Page int `form:"page,default=1"`
PageSize int `form:"page_size,optional"`
}
// 请求头参数
AuthReq {
Token string `header:"Authorization"`
}
)🚨 一个字段只能有一个 tag,不支持多 tag 接收同一参数。
标签类型速查
| 标签 | 参数来源 | 适用方法 | 示例 |
|---|---|---|---|
json |
请求/响应体 | POST/PUT/PATCH | json:"username" |
path |
URL 路径片段 | 所有方法 | path:"id" → /user/:id |
form |
URL 查询字符串 | GET/DELETE | form:"page,default=1" |
header |
HTTP 请求头 | 所有方法 | header:"Authorization" |
参数校验规则
校验规则只对请求体有效:
| 规则 | 说明 | 示例 |
|---|---|---|
optional |
可选字段,允许零值 | json:"age,optional" |
options |
枚举值限制,用 | 分隔 |
json:"gender,options=male|female" |
default |
默认值 | json:"gender,default=male" |
range |
数值范围(仅数值类型) | json:"age,range=[0:120]" |
range 区间规则
| 区间写法 | 含义 | 示例 |
|---|---|---|
(min:max] |
左开右闭 | range=(0:100] → 0 < x ≤ 100 |
[min:max) |
左闭右开 | range=[0:100) → 0 ≤ x < 100 |
[min:max] |
闭区间 | range=[0:100] → 0 ≤ x ≤ 100 |
(min:max) |
开区间 | range=(0:100) → 0 < x < 100 |
💡 min 或 max 可缺省:
range=[0:]表示 ≥0,range=[:100]表示 ≤100。
服务块(service)
service user-api {
@server (
jwt: Auth // 启用 JWT 中间件
middleware: AccessLog,Cors // 应用自定义中间件
prefix: /v1 // URL 前缀
group: user // 路由分组(按文件夹组织代码)
timeout: 3s // 请求超时
maxBytes: 1048576 // 请求体大小限制(字节)
signature: true // 启用请求签名
sse: true // 启用 SSE(服务端推送)
)
@handler Login
post /user/login (LoginReq) returns (LoginResp)
@handler GetUser
get /user/:id (UserReq) returns (UserResp)
}@server 装饰器一览
| 装饰器 | 说明 | 示例 |
|---|---|---|
jwt: Auth |
启用 JWT 认证,Auth 对应 Config 中的字段名 | jwt: Auth |
middleware: A,B |
声明中间件,逗号分隔,按声明顺序执行 | middleware: Log,Cors |
prefix: /v1 |
该组路由的统一前缀 | prefix: /v1 |
group: foo |
路由分组,代码按文件夹组织 | group: user |
timeout: 3s |
请求超时时间 | timeout: 5s |
maxBytes: 1048576 |
请求体最大字节数(goctl ≥ 1.5.0) | maxBytes: 1048576 |
signature: true |
启用请求签名校验 | signature: true |
sse: true |
启用 SSE(Server-Sent Events) | sse: true |
🚨 @server 的作用域:每个
@server只影响它下面紧跟的service块。如果需要部分接口有 JWT、部分没有,必须拆分为两个service块。
支持的 HTTP 方法
get / post / put / patch / delete / head
路由规则
- 必须用
/开头 - 每个路由节点用
/分割 :开头的节点为路径参数,须在请求体中有对应的pathtag- 路由节点命名:字母、数字、下划线、中划线
service Demo {
// /foo
@handler demoPath1
get /foo (DemoReq) returns (DemoResp)
// /foo/bar
@handler demoPath2
get /foo/bar (DemoReq) returns (DemoResp)
// /foo/bar/:id
@handler demoPath3
get /foo/bar/:id (DemoPath3Req) returns (DemoResp)
// /foo/bar/:id/:name
@handler demoPath4
get /foo/bar/:id/:name (DemoPath4Req) returns (DemoResp)
// /foo/bar/baz-qux
@handler demoPath6
get /foo/bar/baz-qux (DemoReq) returns (DemoResp)
// /foo/bar_baz/123 (goctl ≥ 1.5.1)
@handler demoPath7
get /foo/bar_baz/123 (DemoReq) returns (DemoResp)
}路由分组(group)
用 group 关键字依据业务将 handler 和 logic 散落到各自的文件夹中:
@server (
prefix: /v1
group: user
)
service user-api {
@handler UserLogin
post /user/login (UserLoginReq) returns (UserLoginResp)
@handler UserInfo
post /user/info (UserInfoReq) returns (UserInfoResp)
}
@server (
prefix: /v1
group: role
)
service user-api {
@handler UserRoleList
get /user/role/list returns ([]UserRoleResp)
@handler UserRoleAdd
get /user/role/add (UserRoleAddReq) returns (UserRoleAddResp)
}
@server (
prefix: /v1
group: class
)
service user-api {
@handler UserClassList
get /user/class/list returns ([]UserClassResp)
}生成的目录结构按组组织:
internal/
├── handler/
│ ├── user/ # group: user
│ ├── role/ # group: role
│ └── class/ # group: class
├── logic/
│ ├── user/
│ ├── role/
│ └── class/
└── types/
├── user.go
├── role.go
└── class.go💡 当接口数量超过 10 个时强烈建议使用
group分组,否则 logic 文件夹会变得难以维护。
路由前缀(prefix)
通过 prefix 区分 API 版本:
@server(prefix: /v1)
service user-api {
@handler usersv1
get /users returns ([]UserV1)
}
@server(prefix: /v2)
service user-api {
@handler usersv2
get /users returns ([]UserV2)
}生成的路由:
/v1/users→usersv1/v2/users→usersv2
中间件声明(middleware)
@server(
middleware: UserAgentMiddleware,LogMiddleware
)
service user-api {
@handler userInfo
get /user/info/:id (UserInfoReq) returns (UserInfoResp)
}goctl 会自动生成中间件骨架文件到 internal/middleware/,你需要自己填充逻辑:
// internal/middleware/useragentmiddleware.go
package middleware
import "net/http"
type UserAgentMiddleware struct{}
func NewUserAgentMiddleware() *UserAgentMiddleware {
return &UserAgentMiddleware{}
}
func (m *UserAgentMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
val := r.Header.Get("User-Agent")
ctx := context.WithValue(r.Context(), "User-Agent", val)
next(w, r.WithContext(ctx))
}
}然后在 ServiceContext 中注册:
type ServiceContext struct {
Config config.Config
UserAgentMiddleware rest.Middleware
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
UserAgentMiddleware: middleware.NewUserAgentMiddleware().Handle,
}
}JWT 鉴权
@server(jwt: Auth)
service user-api {
@handler userInfo
post /user/info (UserInfoReq) returns (UserInfoResp)
}需要在配置中添加:
type Config struct {
rest.RestConf
Auth struct {
AccessSecret string
AccessExpire int64
}
}# etc/user-api.yaml
Auth:
AccessSecret: your-secret-key
AccessExpire: 86400🚨 go-zero 的 JWT 中间件只做服务端 Token 验证,Token 的生成和 refresh token 需要你自行实现。
获取 JWT payload 信息
func (l *UserInfoLogic) UserInfo(req *types.UserInfoReq) (*types.UserInfoResp, error) {
// 从 context 获取 JWT 中的自定义字段
userId := l.ctx.Value("userId")
return &types.UserInfoResp{Id: userId.(int64)}, nil
}请求签名
请求签名是一种加密安全机制,确保请求未被篡改。go-zero 内置了基于 RSA 的签名校验。
声明
@server(signature: true)
service sign-api {
@handler SignDemo
post /sign/demo (SignDemoReq) returns (SignDemoResp)
}配置
Signature:
Strict: true # true: 签名无效返回 403;false: 仅记录日志
Expiry: 1h # 签名有效期(防重放攻击)
PrivateKeys:
- Fingerprint: "your-service-id" # 密钥标识
KeyFile: ./secret/id_rsa # RSA 私钥路径客户端请求头
X-Content-Security: <key> <secret> <signature>key:对应服务端的Fingerprintsecret:加密后的签名signature:签名原文
工作流程
- 服务端生成 RSA 密钥对,保留私钥,公钥下发给客户端
- 客户端用公钥对请求体加密生成签名,放入
X-Content-Security头 - 服务端用私钥解密验证,不通过则拒绝请求
SSE 路由(Server-Sent Events)
SSE 让服务端能单向向客户端推送实时事件流:
syntax = "v1"
type EventMessage {
Type string `json:"type"`
Data string `json:"data"`
}
@server (
sse: true
prefix: /api/v1
)
service EventApi {
@handler StreamEvents
get /events returns (EventMessage)
}rest.WithSSE() 自动:
- 设置
Content-Type: text/event-stream、Cache-Control: no-cache - 移除写入超时以允许长时间连接
- 启用连接保持活动
💡 SSE 路由通常使用 GET 方法,可与 JWT 认证和中间件配合使用。
导入与复用(import)
可以 import 其他 .api 文件中定义的类型:
// base.api
syntax = "v1"
type Base {
Code int `json:"code"`
Msg string `json:"msg"`
}// user.api
syntax = "v1"
import "base.api"
type UserInfoResp {
Base // 嵌入公共响应体
Data UserInfo `json:"data"`
}🚨 规则:
- 使用相对路径或绝对路径导入
- 被导入的
.api文件不允许出现service语法块- 所有
service声明放在主api文件中- 不支持循环导入
自定义 goctl 模板
goctl 支持自定义代码生成模板,实现统一的响应格式等:
# 模板路径
~/.goctl/<version>/api/
├── handler.tpl # handler 模板
├── logic.tpl # logic 模板
├── routes.tpl # 路由模板
└── ...你可以修改这些模板,例如在 handler 模板中引入统一错误处理:
func {{.HandlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
{{if .HasRequest}}
var req types.{{.RequestType}}
if err := httpx.Parse(r, &req); err != nil {
xhttp.ParamErrorResult(r, w, err) // 统一参数错误处理
return
}
{{end}}
l := logic.New{{.LogicType}}(r.Context(), ctx)
resp, err := l.{{.Call}}(req)
xhttp.HttpResult(r, w, resp, err) // 统一响应格式化
}
}💡 团队内部统一
.api规范 + 自定义模板 = 一键生成符合团队标准的代码。
HTTP 测试文件(.http)
利用 IDE 的 RestClient 插件,可以直接在 .http 文件中调试接口:
### 用户登录
POST http://localhost:8888/user/login
Content-Type: application/json
{
"username": "admin",
"password": "123456"
}
### 获取用户信息
GET http://localhost:8888/user/1
Authorization: Bearer admin
### 用户搜索
GET http://localhost:8888/user/search?keyword=alice&page=1常见陷阱
| 陷阱 | 说明 |
|---|---|
🚨 类型定义需要 type 关键字 |
不需要写 struct 关键字,直接写字段 |
🚨 optional 与零值冲突 |
int 类型 optional 时,0 值会被判定为未提供 |
🚨 range 只对数值有效 |
range 不能用于 string 类型 |
🚨 @server 作用域是按 service 块的 |
需要不同中间件组合 = 需要拆分 service 块 |
| 🚨 不支持多 tag | 一个字段不能同时用 json 和 form 接收参数 |
| 🚨 JWT 中间件默认 leeway=0 | NTP 时钟漂移可能导致合法 Token 被拒,需要手动设置 jwt.WithLeeway() |