go-zero 规范手册

目录结构怎么摆、项目怎么组织、代码怎么写。把约定记牢,团队协作和代码生成才顺。

目录结构规范 项目规范 代码规范 api 文件规范 命名对照

代码目录结构规范

go-zero 生成的服务有固定骨架。无论 API 服务还是 RPC 服务,都遵循「配置 / handler(或 pb) / logic / svc / model」的分层。

user-api/ # HTTP 服务 ├── etc/ │ └── user-api.yaml # 运行配置(端口、etcd、限流…) ├── internal/ │ ├── config/ # 配置结构体定义 │ │ └── config.go │ ├── handler/ # 路由 + 请求适配(薄层) │ │ ├── routes.go │ │ └── loginhandler.go │ ├── logic/ # 业务逻辑(核心) │ │ └── loginlogic.go │ ├── svc/ # 依赖装配(ServiceContext) │ │ └── servicecontext.go │ └── types/ # 请求/响应结构体 │ └── types.go ├── user.go # main 入口 ├── user.api # 接口定义(契约) └── go.mod user-rpc/ # RPC 服务 ├── etc/user-rpc.yaml ├── internal/ │ ├── config/ │ ├── logic/ │ ├── svc/ │ └── server/ # RPC 方法注册(等效 handler) ├── user/ # 生成的 pb.go + 客户端 │ ├── user.pb.go │ └── user_grpc.pb.go ├── user.proto └── user.go

分层职责一眼记

目录 / 文件职责能不能写业务
handler / server解析请求、调用 logic、组装响应❌ 不要写业务逻辑
logic真正的业务编排,可调用 RPC / DB / 缓存✅ 业务都在这
svc装配所有外部依赖(DB、RPC client、配置)❌ 只做注入
config声明 yaml 配置对应的结构体
types / pb数据传输对象,由工具生成❌ 不手改
微服务整体工程常见两种摆法:单仓库多服务(每个服务一个文件夹,共享 go.mod)或 一服务一仓库。小团队推荐前者,大团队按服务边界拆仓库。

项目规范

一个 go-zero 服务从「配置 → 启动 → 装配依赖 → 跑」有一套固定仪式,别自己发明。

  1. 配置外置:所有环境相关参数(端口、MySQL、Redis、Etcd、限流阈值)都放 etc/*.yaml,绝不硬编码。
  2. main 三步走conf.MustLoad 加载配置 → NewServer 建服务 → svc.NewServiceContext 装配依赖 → server.Start 启动,并 defer server.Stop()
  3. 依赖进 ServiceContext:DB、RPC 客户端、缓存都挂到 svc.ServiceContext 上,logic 通过构造函数拿到,不用全局变量。
  4. 单体用 API,内部用 RPC:对外暴露走 rest,服务之间调用走 zrpc,不要跨服务直连数据库。
  5. go.mod 锁版本:go-zero 与 protobuf 等依赖版本要统一,提交前 go mod tidy
  6. 配置示例与说明etc/ 下保留一份可运行的 yaml,提交到仓库作为模板。

标准 main 入口长这样

package main

import (
    "github.com/zeromicro/go-zero/core/conf"
    "github.com/zeromicro/go-zero/rest"
    "user-api/internal/config"
    "user-api/internal/handler"
    "user-api/internal/svc"
)

var configFile = "etc/user-api.yaml"

func main() {
    var c config.Config
    conf.MustLoad(configFile, &c)              // 1. 加载配置

    server := rest.MustNewServer(c.RestConf)  // 2. 建 HTTP 服务
    defer server.Stop()

    ctx := svc.NewServiceContext(c)           // 3. 装配依赖
    handler.RegisterHandlers(server, ctx)     // 4. 注册路由

    server.Start()                            // 5. 启动
}

代码规范

go-zero 风格的几条硬规矩,团队统一后就很少出现「这个逻辑该放哪」的争论。

1. 错误处理:不吞、不裸返

// ✅ 明确返回错误并带上上下文
if err != nil {
    return nil, fmt.Errorf("查询用户失败: %w", err)
}
// ❌ 吞掉错误
if err != nil { return nil, nil }

2. context 必须透传

所有 RPC / DB / 缓存调用都带 ctx,用于超时、链路追踪与取消。logic 的入参第一位是 ctx

resp, err := l.userRpc.Login(l.ctx, req)   // 用注入的 ctx

3. 不在 logic 里建连接

MySQL / Redis / RPC client 都在 svc 建好,logic 通过 l.svcCtx 取用,避免重复连接。

func (l *LoginLogic) Login(req *types.LoginReq) (*types.LoginResp, error) {
    u, err := l.svcCtx.UserModel.FindOneByUsername(l.ctx, req.Username)
    if err != nil {
        return nil, err
    }
    _ = u
    return &types.LoginResp{Token: "..."}, nil
}

4. 配置结构体要完整

type Config struct {
    rest.RestConf               // 内嵌,获得 Host/Port/Timeout 等
    UserRpc     zrpc.RpcClientConf  // 下游 RPC 配置
    Mysql      sqlx.Config         // 数据库
    CacheRedis cache.CacheConf     // 缓存集群
}

.api 文件编写规范

.api 是契约也是文档,写得规范,生成的代码才规范。

命名与分组

// 结构体用大驼峰,字段小驼峰,json tag 同名
type CreateOrderReq {
    ProductId int64  `json:"productId"`
    Count     int32  `json:"count"`
}

// 一个 service 聚合一组接口,handler 名用大驼峰动词
service order-api {
    @handler CreateOrder
    post /order/create (CreateOrderReq) returns (CreateOrderResp)

    @handler GetOrder
    get  /order/:id (GetOrderReq) returns (GetOrderResp)
}
路径里的 :id 是路径参数,对应请求结构体的同名字段,框架自动解析。

命名对照速查

对象规则示例
服务小写下划线或中划线,带角色后缀user-api / user-rpc
结构体大驼峰LoginReq
handler大驼峰动词 + Handler 后缀LoginHandler
logic大驼峰动词 + Logic 后缀LoginLogic
配置文件etc/服务名.yamletc/user-api.yaml
接口方法大驼峰动词CreateOrder
最容易踩的坑:手改了生成的 types.go / pb.go。这些文件由 goctl 维护,下次生成会覆盖。要扩展结构体,改 .api / .proto 重新生成,而不是手改生成物。