网站Logo 90的blog

go-zero学习

root
2
2025-07-04

GoZero 是一个偏向微服务开发的 Go 框架。它不仅能写 HTTP 接口,还提供了 RPC、服务治理、代码生成、配置管理、链路追踪等能力。它最大的特点是:通过统一的目录结构和工具生成代码,让项目更容易保持规范。

GoZero 适合解决什么问题

假设要写一个用户接口:

GET /api/users/:id

然后文件:

func GetUser(w http.ResponseWriter, r *http.Request) {
	// 获取参数、查询数据库、返回数据
}

但当接口数量变多时,会逐渐混在一起:

  • 路由定义放在哪里;

  • 请求参数如何校验;

  • 数据库连接在哪里初始化;

  • 业务逻辑应该写在哪一层;

  • 配置文件如何管理;

  • API 服务如何调用另一个服务;

  • 多个服务如何统一日志和错误处理。

GoZero 提供了一套较明确的组织方式,减少每个项目都从头设计目录和基础设施的情况。

GoZero 中的两个重要部分

GoZero 中比较常见的两个开发方向是:

  • API 服务:对外提供 HTTP 接口,通常由前端、移动端或第三方调用;

  • RPC 服务:服务之间通过 RPC 通信。

可以把它理解为:

前端
  ↓ HTTP
API 服务
  ↓ RPC
用户服务 / 订单服务 / 商品服务
  ↓
MySQL、Redis、消息队列

例如用户访问订单详情页面:

  1. 前端请求订单 API;

  2. API 服务负责接收 HTTP 参数、鉴权和返回 JSON;

  3. API 服务通过 RPC 调用订单服务;

  4. 订单服务查询 MySQL、Redis;

  5. 最后把结果逐层返回给前端。

在小项目中,API 服务和业务逻辑也可以放在同一个服务里。只有当系统复杂度提高时,再拆成多个 RPC 服务。

.api 文件开始

GoZero 的一个核心体验是 API 描述文件。

例如创建 user.api

syntax = "v1"

type UserResponse {
	Id   int64  `json:"id"`
	Name string `json:"name"`
}

service user-api {
	@handler GetUser
	get /api/users/:id returns (UserResponse)
}

这份文件描述了:

  • 接口路径;

  • 请求方式;

  • 参数位置;

  • 返回结构;

  • 对应的处理器名称。

然后可以使用 goctl 根据 .api 文件生成基础代码。

goctl api go -api user.api -dir .

生成后,通常会得到类似这样的结构:

user-api
├── etc
│   └── user-api.yaml
├── internal
│   ├── config
│   ├── handler
│   ├── logic
│   ├── svc
│   └── types
└── user.go

这样做的价值不只是“少写代码”,更重要的是让接口定义、路由、参数类型和目录职责保持一致。

各个目录是做什么的

我目前对 GoZero 常见目录的理解如下:

目录

主要职责

etc

配置文件,例如端口、MySQL、Redis 地址

internal/config

配置结构体定义

internal/handler

接收 HTTP 请求、调用 Logic、返回响应

internal/logic

编写核心业务逻辑

internal/svc

初始化并集中管理数据库、Redis、RPC 客户端等依赖

internal/types

请求和响应结构体

*.api

接口定义文件

user.go

服务启动入口

其中,我认为最重要的分层是:

Handler:处理 HTTP
Logic:处理业务
ServiceContext:提供依赖

一个用户查询接口的例子

生成接口后,Handler 通常不需要写太多业务代码,它主要负责接收请求并调用 Logic。

业务代码一般写在 logic 层:

func (l *GetUserLogic) GetUser(req *types.Request) (*types.UserResponse, error) {
	id, err := strconv.ParseInt(req.Id, 10, 64)
	if err != nil || id <= 0 {
		return nil, errorx.NewCodeError(400, "用户 ID 不合法")
	}

	// 实际项目中,这里可以查询 MySQL 或调用 RPC 服务。
	return &types.UserResponse{
		Id:   id,
		Name: "Alice",
	}, nil
}

这里可以看到,Logic 层不需要关心 HTTP 响应怎么写回浏览器,它只需要处理:

  • 参数是否合法;

  • 要查询什么数据;

  • 业务规则是否满足;

  • 返回什么结果或错误。

这种分层的好处是业务逻辑更容易测试,也更容易复用。

ServiceContext:统一管理依赖

项目中常常需要 MySQL、Redis、RPC 客户端等资源。如果每个接口都自己创建连接,代码会变乱,也可能造成资源浪费。

GoZero 通常通过 ServiceContext 统一管理依赖。

例如:

type ServiceContext struct {
	Config config.Config
	Redis  *redis.Redis
}

初始化后,Logic 可以通过:

l.svcCtx.Redis

访问 Redis。

如果后面接入 MySQL、用户 RPC 服务或消息队列,也可以统一放在这里。这样依赖关系比较清楚:Logic 需要什么资源,就从 svcCtx 中获取。

配置文件的作用

GoZero 通常使用 YAML 配置服务信息:

Name: user-api
Host: 0.0.0.0
Port: 8888

Redis:
  Host: 127.0.0.1:6379
  Type: node

代码和配置分开后,开发、测试、生产环境可以使用不同配置,而不需要把数据库地址、密码或端口直接写死在代码里。

实际部署时,敏感配置例如数据库密码、Token 密钥,通过环境变量、密钥管理服务或部署平台配置注入,不要提交到 Git 仓库。

GoZero 中的 RPC

当服务需要拆分时,可以使用 .proto 文件定义 RPC 接口。

例如用户服务提供根据 ID 查询用户的能力:

syntax = "proto3";

package user;

service User {
  rpc GetUser(UserRequest) returns (UserResponse);
}

message UserRequest {
  int64 id = 1;
}

message UserResponse {
  int64 id = 1;
  string name = 2;
}

通过 goctl rpc protoc 生成代码后,订单服务或 API 服务就可以调用用户服务。

订单 API
  ↓
订单 RPC
  ↓
用户 RPC

这种方式适合服务之间高频、结构化的调用。但对刚学习 GoZero 的人来说,我觉得可以先把 API 服务、数据库和 Redis 用熟,再逐步理解 RPC,不必一开始就拆很多服务。

GoZero 的优点

我目前觉得 GoZero 有几个比较明显的优点:

  • API 和 RPC 都有比较完整的支持;

  • goctl 可以减少大量重复代码;

  • 项目结构相对统一,团队协作更容易;

  • 内置了日志、配置、服务发现、限流等工程化能力;

  • 很适合学习一个后端服务从接口到部署的大致流程。

尤其是代码生成,对刚开始做稍大一点项目的人很友好。先定义接口,再生成骨架,最后专注写业务逻辑,会比直接堆代码更清晰。

GoZero注意

1. 不要修改自动生成的路由代码

goctl 生成的文件有自己的职责。业务逻辑应该尽量写在 logic 层,而不是随意把代码塞到 handler 或生成文件中。

否则下一次重新生成代码时,可能会覆盖自己的修改。

2. 不要一开始就拆成很多微服务

微服务会增加 RPC 调用、服务发现、配置管理、部署和排错的复杂度。

刚学习时,可以先完成一个单体 API 项目:

用户接口 + MySQL + Redis + JWT 鉴权

等项目稳定后,再尝试拆出用户 RPC、订单 RPC 等服务。

3. 数据库操作仍然需要自己保证正确性

框架能帮忙组织代码,但不能自动保证库存、余额、订单等关键数据的并发安全。

事务、索引、隔离级别、条件更新、唯一约束这些数据库基础,仍然非常重要。

4. 接口定义修改后要重新生成

修改了 .api.proto 文件后,通常需要重新运行对应的 goctl 命令生成代码。

因此,接口定义文件应该当作项目的重要源文件维护,而不是生成一次后就不再关注。

我对 GoZero 的理解是:

它不是只帮我们“快速写接口”的框架,而是一套帮助 Go 项目按规范组织 API、RPC、配置和依赖的工程化工具。

动物装饰