go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。
- 强大的工具支持:尽可能少的代码编写
- 极简的接口:完全兼容 net/http
- 高性能:面向故障编程,弹性设计
- 内建服务发现、负载均衡
- 内建限流、熔断、降载:且自动触发,自动恢复
- API 参数自动校验
- 超时级联控制
- 自动缓存控制
- 链路跟踪、统计报警
- 高并发支撑:稳定保障了疫情期间每天的流量洪峰
确保你的系统已经安装了 Go 1.21 或更高版本:
go versiongoctl 是 go-zero 的代码生成工具,可以快速生成项目代码:
# 方式一:通过 go install 安装(推荐)
go install github.com/zeromicro/go-zero/tools/goctl@latest
# 方式二:通过下载二进制文件安装
# 访问 https://github.com/zeromicro/go-zero/releases 下载对应平台的 goctl
# 验证安装
goctl --version# 安装 protoc
# macOS
brew install protobuf
# 安装 protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest# 创建项目目录
mkdir quickstart && cd quickstart
# 使用 goctl 快速创建项目
goctl quickstart按照提示输入:
- 选择服务类型:选择
api(HTTP 服务) - 输入服务名称:例如
user - 输入模块名称:例如
quickstart
生成的项目结构如下:
user/
├── etc/ # 配置文件目录
│ └── user.yaml # 服务配置文件
├── internal/ # 内部代码,不对外暴露
│ ├── config/ # 配置定义
│ ├── handler/ # 路由和handler定义
│ ├── logic/ # 业务逻辑
│ ├── svc/ # 服务依赖
│ └── types/ # 请求响应类型定义
├── user.api # API定义文件
└── user.go # 程序入口
编辑 user.api 文件,定义你的 API 接口:
syntax = "v1"
info (
title: "用户服务API"
desc: "用户服务的API接口"
author: "your-name"
email: "[email protected]"
version: "v1"
)
type (
// 登录请求
LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
// 登录响应
LoginResponse {
Id int64 `json:"id"`
Username string `json:"username"`
Token string `json:"token"`
}
// 用户信息请求
GetUserRequest {
Id int64 `path:"id"`
}
// 用户信息响应
GetUserResponse {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
)
// 不需要鉴权的接口
@server (
prefix: /api/user
)
service user {
@doc "用户登录"
@handler login
post /login (LoginRequest) returns (LoginResponse)
}
// 需要鉴权的接口
@server (
prefix: /api/user
jwt: Auth
middleware: AuthMiddleware
)
service user {
@doc "获取用户信息"
@handler getUserInfo
get /info/:id (GetUserRequest) returns (GetUserResponse)
}# 在项目根目录执行
goctl api go -api user.api -dir .这个命令会根据 API 定义生成对应的代码结构。
在实现业务逻辑之前,我们需要安装 JWT 库来生成真实的 JWT token:
go get -u github.com/golang-jwt/jwt/v4编辑 internal/logic/loginlogic.go,实现真实的 JWT token 生成:
package logic
import (
"context"
"errors"
"time"
"quickstart/user/internal/svc"
"quickstart/user/internal/types"
"github.com/golang-jwt/jwt/v4"
"github.com/zeromicro/go-zero/core/logx"
)
type LoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
return &LoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) {
// 简单的用户名密码验证
if req.Username == "admin" && req.Password == "123456" {
// 生成真实的 JWT token
now := time.Now().Unix()
accessExpire := l.svcCtx.Config.Auth.AccessExpire
claims := make(jwt.MapClaims)
claims["exp"] = now + accessExpire
claims["iat"] = now
claims["userId"] = int64(1)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(l.svcCtx.Config.Auth.AccessSecret))
if err != nil {
return nil, err
}
return &types.LoginResponse{
Id: 1,
Username: req.Username,
Token: tokenString,
}, nil
}
return nil, errors.New("用户名或密码错误")
}JWT Token 说明:
生成的 JWT token 包含以下信息:
exp:过期时间(从配置文件中的Auth.AccessExpire读取)iat:签发时间userId:用户ID(可以添加更多自定义字段,如角色、权限等)
go-zero 的 JWT 中间件会自动:
- 从 HTTP Header 的
Authorization字段获取 token - 使用配置的
AccessSecret验证 token 签名 - 检查 token 是否过期
- 验证通过后将请求转发到业务逻辑
编辑 `internal/logic/getuserinfologic.go`:
```go
package logic
import (
"context"
"quickstart/user/internal/svc"
"quickstart/user/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUserInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoLogic {
return &GetUserInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserRequest) (resp *types.GetUserResponse, err error) {
// 这里实现获取用户信息的逻辑
// 示例:返回模拟数据
return &types.GetUserResponse{
Id: req.Id,
Username: "admin",
Email: "[email protected]",
}, nil
}
编辑 etc/user.yaml:
Name: user
Host: 0.0.0.0
Port: 8888
Timeout: 30000
Auth:
AccessSecret: your-secret-key-here
AccessExpire: 7200
Log:
ServiceName: user
Mode: console
Level: infogo run user.go -f etc/user.yaml你会看到类似以下的输出:
Starting server at 0.0.0.0:8888...
# 测试登录接口 - 成功场景
curl -X POST http://localhost:8888/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'响应示例:
{
"id": 1,
"username": "admin",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzMwNzEyMDAsImlhdCI6MTczMzA2NDAwMCwidXNlcklkIjoxfQ..."
}# 错误的密码(应返回 400)
curl -X POST http://localhost:8888/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"wrong"}'
# 缺少参数(应返回 400)
curl -X POST http://localhost:8888/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"admin"}'
# 不带 token 访问受保护接口(应返回 401)
curl -X GET http://localhost:8888/api/user/info/1# 先获取 token
TOKEN=$(curl -s -X POST http://localhost:8888/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}' | jq -r '.token')
# 使用 token 访问受保护的接口
curl -X GET http://localhost:8888/api/user/info/1 \
-H "Authorization: $TOKEN"响应示例:
{
"id": 1,
"username": "admin",
"email": "[email protected]"
}在 go-zero 中,不同类型的错误会返回不同的 HTTP 状态码:
| 状态码 | 场景 | 说明 |
|---|---|---|
| 200 | 成功 | 正常的业务响应 |
| 400 | 参数错误或业务错误 | 参数验证失败或业务逻辑返回 error |
| 401 | 未授权 | JWT token 缺失或验证失败 |
| 404 | 未找到 | 路由不存在 |
| 405 | 方法不允许 | HTTP 方法与定义不匹配 |
| 500 | 服务器错误 | 服务内部错误 |
注意:当业务逻辑返回 error 时,go-zero 默认返回 HTTP 400。如果需要自定义错误码和消息,可以使用自定义错误类型。
mkdir user-rpc && cd user-rpc创建 user.proto:
syntax = "proto3";
package user;
option go_package = "./user";
message GetUserRequest {
int64 id = 1;
}
message GetUserResponse {
int64 id = 1;
string username = 2;
string email = 3;
}
service User {
rpc GetUser(GetUserRequest) returns(GetUserResponse);
}goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.编辑 internal/logic/getuserlogic.go:
package logic
import (
"context"
"user-rpc/internal/svc"
"user-rpc/user"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUserLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserLogic {
return &GetUserLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *GetUserLogic) GetUser(in *user.GetUserRequest) (*user.GetUserResponse, error) {
// 实现获取用户逻辑
return &user.GetUserResponse{
Id: in.Id,
Username: "admin",
Email: "[email protected]",
}, nil
}go run user.go -f etc/user.yamlgo get -u gorm.io/gorm
go get -u gorm.io/driver/mysql在 etc/user.yaml 中添加数据库配置:
Name: user
Host: 0.0.0.0
Port: 8888
MySQL:
DataSource: root:password@tcp(127.0.0.1:3306)/user_db?charset=utf8mb4&parseTime=True&loc=Local创建 internal/model/usermodel.go:
package model
import (
"context"
"database/sql"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserModel = (*customUserModel)(nil)
type (
UserModel interface {
Insert(ctx context.Context, data *User) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*User, error)
Update(ctx context.Context, data *User) error
Delete(ctx context.Context, id int64) error
}
customUserModel struct {
*defaultUserModel
}
User struct {
Id int64 `db:"id"`
Username string `db:"username"`
Password string `db:"password"`
Email string `db:"email"`
}
)
func NewUserModel(conn sqlx.SqlConn) UserModel {
return &customUserModel{
defaultUserModel: newUserModel(conn),
}
}在 etc/user.yaml 中添加 Redis 配置:
Redis:
Host: 127.0.0.1:6379
Type: node
Pass: ""package logic
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/stores/redis"
)
func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserRequest) (resp *types.GetUserResponse, err error) {
// 从缓存获取
key := fmt.Sprintf("user:info:%d", req.Id)
val, err := l.svcCtx.Redis.Get(key)
if err == nil {
var user types.GetUserResponse
json.Unmarshal([]byte(val), &user)
return &user, nil
}
// 从数据库获取
user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, err
}
resp = &types.GetUserResponse{
Id: user.Id,
Username: user.Username,
Email: user.Email,
}
// 写入缓存
data, _ := json.Marshal(resp)
l.svcCtx.Redis.Setex(key, string(data), 3600)
return resp, nil
}创建 internal/middleware/authmiddleware.go:
package middleware
import (
"net/http"
)
type AuthMiddleware struct {
}
func NewAuthMiddleware() *AuthMiddleware {
return &AuthMiddleware{}
}
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 这里实现认证逻辑
// 例如:验证 JWT token
// 如果验证通过,继续处理
next(w, r)
// 如果验证失败,返回错误
// http.Error(w, "Unauthorized", http.StatusUnauthorized)
}
}在 internal/svc/servicecontext.go 中注册:
package svc
import (
"quickstart/user/internal/config"
"quickstart/user/internal/middleware"
"github.com/zeromicro/go-zero/rest"
)
type ServiceContext struct {
Config config.Config
AuthMiddleware rest.Middleware
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
AuthMiddleware: middleware.NewAuthMiddleware().Handle,
}
}# 创建 API 服务
goctl quickstart
# 从 API 文件生成代码
goctl api go -api user.api -dir .
# 从 Proto 文件生成 RPC 代码
goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
# 从 SQL DDL 生成 Model 代码
goctl model mysql ddl -src user.sql -dir ./model
# 从数据库生成 Model 代码
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*" -dir="./model"
# 生成 Dockerfile
goctl docker -go user.go
# 生成 Kubernetes 部署文件
goctl kube deploy -name user -namespace default -image user:v1 -o user.yaml -port 8888
# 查看 goctl 版本
goctl --version
# 查看帮助
goctl --helpproject/
├── api/ # API 服务
│ └── user/
├── rpc/ # RPC 服务
│ └── user/
├── model/ # 数据模型(可共享)
├── common/ # 公共代码
│ ├── errorx/ # 错误定义
│ └── utils/ # 工具函数
└── docker-compose.yml
创建统一的错误处理:
package errorx
import "fmt"
const (
OK = 0
ERROR = 1
INVALID_PARAMS = 400
UNAUTHORIZED = 401
FORBIDDEN = 403
NOT_FOUND = 404
INTERNAL_ERROR = 500
)
type CodeError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func NewCodeError(code int, msg string) error {
return &CodeError{Code: code, Msg: msg}
}
func (e *CodeError) Error() string {
return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
func Success(data interface{}) *Response {
return &Response{
Code: 0,
Msg: "success",
Data: data,
}
}
func Error(code int, msg string) *Response {
return &Response{
Code: code,
Msg: msg,
}
}// 使用结构化日志
logx.WithContext(ctx).Infow("user login",
logx.Field("username", req.Username),
logx.Field("ip", r.RemoteAddr),
)
// 记录错误
logx.WithContext(ctx).Errorw("database error",
logx.Field("error", err.Error()),
logx.Field("sql", sqlStr),
)- 使用连接池管理数据库连接
- 合理使用缓存减少数据库查询
- 开启 gRPC 连接复用
- 使用批量操作减少网络往返
- 合理设置超时时间
- 使用熔断器保护下游服务
# 生成 Dockerfile
goctl docker -go user.go
# 构建镜像
docker build -t user:v1 .
# 运行容器
docker run -d --name user -p 8888:8888 user:v1# 生成 k8s 部署文件
goctl kube deploy -name user -namespace default -image user:v1 -o user.yaml -port 8888
# 应用配置
kubectl apply -f user.yaml创建 docker-compose.yml:
version: '3'
services:
user-api:
build: ./api/user
ports:
- "8888:8888"
depends_on:
- mysql
- redis
- user-rpc
user-rpc:
build: ./rpc/user
ports:
- "9000:9000"
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: user_db
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
mysql-data:症状: command not found: goctl
解决方案:
# 确保 GOPATH/bin 在 PATH 中
export PATH=$PATH:$(go env GOPATH)/bin
# 或者重新安装 goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest症状: bind: address already in use
解决方案:
# macOS/Linux: 查找占用端口的进程
lsof -i :8888
# 或者修改配置文件中的端口
vim etc/user.yaml
# 将 Port 改为其他值,如 8889症状: 所有受保护的接口都返回 401
可能原因:
- AccessSecret 配置不正确
- Token 生成和验证使用的密钥不一致
- Token 格式不正确(需要使用 JWT 标准格式)
- Token 已过期
解决方案:
# 检查配置文件中的 AccessSecret
cat etc/user.yaml
# 确保代码中使用相同的 AccessSecret
# 检查 loginlogic.go 中的 token 生成代码症状: 请求返回 field "xxx" is not set
原因: API 定义中的必填参数未传递
解决方案: 确保请求中包含所有必填参数,参数名称和类型与 API 定义一致
症状: go mod tidy 或 go run 时下载依赖失败
解决方案:
# 设置 Go 代理(中国大陆)
go env -w GOPROXY=https://goproxy.cn,direct
# 或者使用其他代理
go env -w GOPROXY=https://goproxy.io,direct- 官方文档: https://go-zero.dev
- GitHub: https://github.com/zeromicro/go-zero
- 示例项目: https://github.com/zeromicro/go-zero-examples
- 视频教程: 在 B 站搜索 "go-zero"
- 社区支持: 加入微信群
通过本教程,你已经学会了:
- ✅ 安装和配置 go-zero 开发环境
- ✅ 创建 API 服务和 RPC 服务
- ✅ 定义 API 接口和 Proto 文件
- ✅ 实现业务逻辑(包括真实的 JWT token 生成)
- ✅ 配置和使用 JWT 认证
- ✅ 理解 API 响应和错误处理
- ✅ 集成数据库和缓存
- ✅ 使用中间件
- ✅ 项目部署
- JWT 认证: 使用
github.com/golang-jwt/jwt/v4生成真实的 JWT token - 参数验证: go-zero 自动验证 API 定义中的必填参数
- 错误处理: 业务逻辑返回 error 时默认返回 HTTP 400
- 中间件: JWT 中间件自动验证 token 的有效性和过期时间
go-zero 提供了完整的微服务开发解决方案,让你可以专注于业务逻辑的实现,而不必担心底层的技术细节。开始构建你的第一个 go-zero 项目吧!
- 学习更多关于 API 网关的知识
- 了解服务治理和监控
- 探索分布式事务处理
- 学习性能优化技巧
- 参与社区贡献
祝你在 go-zero 的学习之旅中取得成功!🚀