Skip to content
Go
API 服务开发

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


路由规则

  1. 必须用 / 开头
  2. 每个路由节点用 / 分割
  3. : 开头的节点为路径参数,须在请求体中有对应的 path tag
  4. 路由节点命名:字母、数字、下划线、中划线
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/usersusersv1
  • /v2/usersusersv2

中间件声明(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:对应服务端的 Fingerprint
  • secret:加密后的签名
  • signature:签名原文

工作流程

  1. 服务端生成 RSA 密钥对,保留私钥,公钥下发给客户端
  2. 客户端用公钥对请求体加密生成签名,放入 X-Content-Security
  3. 服务端用私钥解密验证,不通过则拒绝请求

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() 自动:

  1. 设置 Content-Type: text/event-streamCache-Control: no-cache
  2. 移除写入超时以允许长时间连接
  3. 启用连接保持活动

💡 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 一个字段不能同时用 jsonform 接收参数
🚨 JWT 中间件默认 leeway=0 NTP 时钟漂移可能导致合法 Token 被拒,需要手动设置 jwt.WithLeeway()