go-zero 进阶与项目
本节介绍 go-zero 的进阶特性,包括服务注册、RPC 调用、分布式事务、缓存策略等,并提供完整的项目示例。
2.1 服务注册与发现
2.1.1 基于 Consul 的服务注册
go
// config.yaml
Name: user-api
Host: 0.0.0.0
Port: 8080
Consul:
Host: localhost:8500
Key: user-api
Port: 8500go
// main.go
package main
import (
"flag"
"github.com/zeromicro/go-zero/core/discovery"
"github.com/zeromicro/go-zero/core/conf"
)
var configFile = flag.String("f", "user-api.yaml", "the config file")
type Config struct {
rest.RestConf
Consul discovery.ConsulConf `json:",optional"`
}
func main() {
flag.Parse()
var c Config
conf.MustLoad(*flag.Parse(), &c)
// 启动服务注册
if c.Consul.Host != "" {
discovery.RegisterService(c.Consul)
}
// 启动服务
// ...
}2.1.2 服务发现
go
import "github.com/zeromicro/go-zero/core/discovery"
// 发现服务
endpoints, err := discovery.GetNodes(discovery.NewEtcd(
[]string{"localhost:2379"},
), "user-rpc")2.2 RPC 调用
2.2.1 定义 RPC 服务
创建 user.proto:
protobuf
syntax = "proto3";
package user;
option go_package = "./user";
service User {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
}
message GetUserRequest {
int64 id = 1;
}
message CreateUserRequest {
string username = 1;
string email = 2;
string phone = 3;
string password = 4;
}
message DeleteUserRequest {
int64 id = 1;
}
message User {
int64 id = 1;
string username = 2;
string email = 3;
string phone = 4;
int32 status = 5;
string created_at = 6;
string updated_at = 7;
}2.2.2 生成 RPC 代码
bash
# 生成 RPC 代码
goctl rpc proto -src user.proto -dir ./rpc -style gozero2.2.3 RPC 客户端
go
import "github.com/zeromicro/go-zero/zrpc"
type Config struct {
UserRpc zrpc.RpcClientConf `json:",optional"`
}
// 创建 RPC 客户端
client := zrpc.MustNewClient(c.UserRpc)
// 获取 User 服务
userClient := user.NewUser(client.Conn(), nil)
// 调用 RPC
resp, err := userClient.GetUser(context.Background(), &user.GetUserRequest{
Id: 1,
})2.3 分布式事务
2.3.1 TCC 模式
go
import "github.com/zeromicro/go-zero/core/transaction"
// 定义 TCC 事务
type OrderTCC struct{}
// Try 预留资源
func (t *OrderTCC) Try(ctx context.Context, req OrderRequest) error {
// 1. 预留库存
err := reserveStock(req.Items)
if err != nil {
return err
}
// 2. 冻结金额
err = freezeAmount(req.UserID, req.TotalAmount)
if err != nil {
releaseStock(req.Items)
return err
}
return nil
}
// Confirm 确认执行
func (t *OrderTCC) Confirm(ctx context.Context, req OrderRequest) error {
// 1. 扣减库存
err := deductStock(req.Items)
if err != nil {
return err
}
// 2. 扣减金额
err = chargeAmount(req.UserID, req.TotalAmount)
if err != nil {
// 补偿
restoreStock(req.Items)
return err
}
return nil
}
// Cancel 取消
func (t *OrderTCC) Cancel(ctx context.Context, req OrderRequest) error {
// 1. 释放库存
releaseStock(req.Items)
// 2. 释放金额
unfreezeAmount(req.UserID, req.TotalAmount)
return nil
}
// 执行事务
txn := transaction.NewTCC(&OrderTCC{})
err := txn.Execute(ctx, req)2.3.2 消息事务
go
import "github.com/zeromicro/go-zero/core/stores/sqlx"
type OrderMessage struct {
ID int64
Status string
Payload string
}
// 发送消息
func sendMessage(orderID int64, action string) error {
msg := OrderMessage{
ID: orderID,
Status: "pending",
Payload: fmt.Sprintf(`{"order_id":%d,"action":"%s"}`, orderID, action),
}
// 保存到消息表
_, err := db.Exec("INSERT INTO messages (id, status, payload) VALUES (?, ?, ?)",
msg.ID, msg.Status, msg.Payload)
return err
}
// 确认消息
func confirmMessage(orderID int64) error {
_, err := db.Exec("UPDATE messages SET status='confirmed' WHERE id=?", orderID)
return err
}2.4 缓存策略
2.4.1 多级缓存
go
import (
"context"
"time"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/redis"
)
// 本地缓存
localCache, _ := cache.NewCache(
cache.NewMemory(),
cache.WithExpiry(time.Minute * 5),
)
// Redis 缓存
redisCache, _ := cache.NewCache(
cache.NewRedis(redis.New("localhost:6379")),
cache.WithExpiry(time.Hour),
)
// 多级缓存
multiCache := cache.NewMultiCache(localCache, redisCache)
// 使用
var user User
err := multiCache.Take(&user, func(v interface{}) error {
return db.First(v, id).Error
}, "user:%d", id)2.4.2 缓存模式
go
// Cache-Aside 模式
func GetUser(id int64) (*User, error) {
// 1. 先查缓存
key := fmt.Sprintf("user:%d", id)
cached, err := redis.Get(key)
if err == nil {
var user User
json.Unmarshal([]byte(cached), &user)
return &user, nil
}
// 2. 缓存未命中,查数据库
var user User
err = db.First(&user, id).Error
if err != nil {
return nil, err
}
// 3. 写入缓存
data, _ := json.Marshal(user)
redis.Setex(key, string(data), 3600)
return &user, nil
}
// Write-Through 模式
func CreateUser(user *User) error {
// 1. 写入数据库
err := db.Create(user).Error
if err != nil {
return err
}
// 2. 写入缓存
key := fmt.Sprintf("user:%d", user.ID)
data, _ := json.Marshal(user)
redis.Setex(key, string(data), 3600)
return nil
}2.5 限流与熔断
2.5.1 手动限流
go
import (
"github.com/zeromicro/go-zero/core/limit"
"github.com/zeromicro/go-zero/core/stores/redis"
)
// 令牌桶限流
tokenLimiter := limit.NewTokenLimiter(
1000, // 容量
1000, // 速率
redis.New("localhost:6379"),
)
// 在请求处理中使用
func handleRequest(w http.ResponseWriter, r *http.Request) {
if !tokenLimiter.Allow() {
httpx.Error(w, errorx.New("rate limit exceeded"))
return
}
// 处理请求
}2.5.2 手动熔断
go
import "github.com/zeromicro/go-zero/core/breaker"
// 获取熔断器
br := breaker.GetBreaker("user-service")
// 执行受保护的操作
err := br.Do(func() error {
return callRemoteService()
}, func(err error) bool {
// 判断是否需要重试
return err != nil
})2.5.3 HTTP 客户端熔断
go
import "github.com/zeromicro/go-zero/core/breaker"
type HttpClient struct {
client *http.Client
breaker *breaker.Breaker
}
func NewHttpClient() *HttpClient {
return &HttpClient{
client: &http.Client{},
breaker: breaker.GetBreaker("http-client"),
}
}
func (c *HttpClient) Get(url string) ([]byte, error) {
var result []byte
err := c.breaker.Do(func() error {
resp, err := c.client.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
result, err = io.ReadAll(resp.Body)
return err
})
return result, err
}2.6 项目示例:用户服务
2.6.1 项目结构
user-service/
├── api/
│ └── user.api
├── cmd/
│ └── user-api/
│ └── main.go
├── configs/
│ └── user-api.yaml
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── handler/
│ │ ├── user.go
│ │ └── routes.go
│ ├── logic/
│ │ └── user.go
│ ├── model/
│ │ └── user.go
│ ├── svc/
│ │ └── service.go
│ └── types/
│ └── types.go
├── go.mod
└── go.sum2.6.2 API 定义
yaml
# user.api
type (
User {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Phone string `json:"phone"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Status int `json:"status"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
CreateUserRequest {
Username string `json:"username"`
Email string `json:"email"`
Phone string `json:"phone"`
Password string `json:"password"`
Nickname string `json:"nickname"`
}
UpdateUserRequest {
Id int64 `json:"id"`
Email string `json:"email"`
Phone string `json:"phone"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
}
GetUserRequest {
Id int64 `json:"id"`
}
ListUserRequest {
Page int `json:"page,default=1"`
PageSize int `json:"page_size,default=10"`
Keyword string `json:"keyword,optional"`
}
ListUserResponse {
Total int64 `json:"total"`
Users []User `json:"users"`
}
DeleteUserRequest {
Id int64 `json:"id"`
}
LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
LoginResponse {
Token string `json:"token"`
User User `json:"user"`
}
)
service user-api {
@handler CreateUserHandler
post /api/user/create (CreateUserRequest) returns (User)
@handler UpdateUserHandler
put /api/user/update (UpdateUserRequest) returns (User)
@handler GetUserHandler
get /api/user/get (GetUserRequest) returns (User)
@handler ListUserHandler
get /api/user/list (ListUserRequest) returns (ListUserResponse)
@handler DeleteUserHandler
delete /api/user/delete (DeleteUserRequest) returns (string)
@handler LoginHandler
post /api/user/login (LoginRequest) returns (LoginResponse)
}2.6.3 配置文件
yaml
# configs/user-api.yaml
Name: user-api
Host: 0.0.0.0
Port: 8080
Log:
Mode: console
Level: info
Database:
Type: mysql
DataSource: root:root123@tcp(localhost:3306)/user_db?charset=utf8mb4&parseTime=True&loc=Local
Redis:
Host: localhost:6379
Pass: ""
Type: node
Auth:
Secret: your-secret-key
TokenExpire: 864002.6.4 配置结构
go
// internal/config/config.go
package config
import (
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type Config struct {
rest.RestConf
Database struct {
Type string
DataSource string
}
Redis struct {
Host string
Pass string
Type string
}
Auth struct {
Secret string
TokenExpire int
}
}2.6.5 数据模型
go
// internal/model/user.go
package model
import (
"time"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var (
ErrNotFound = sqlx.ErrNotFound
)
type User struct {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Phone string `json:"phone"`
Password string `json:"-"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Status int `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type UserModel interface {
Insert(user *User) (int64, error)
FindOne(id int64) (*User, error)
FindOneByUsername(username string) (*User, error)
Update(user *User) error
Delete(id int64) error
List(keyword string, page, pageSize int) ([]*User, int64, error)
}
type userModel struct {
conn sqlx.SqlConn
}
func NewUserModel(conn sqlx.SqlConn) UserModel {
return &userModel{conn: conn}
}
func (m *userModel) Insert(user *User) (int64, error) {
query := `INSERT INTO users (username, email, phone, password, nickname, avatar, status)
VALUES (?, ?, ?, ?, ?, ?, ?)`
result, err := m.conn.Exec(query, user.Username, user.Email, user.Phone,
user.Password, user.Nickname, user.Avatar, user.Status)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
func (m *userModel) FindOne(id int64) (*User, error) {
query := `SELECT id, username, email, phone, password, nickname, avatar, status, created_at, updated_at
FROM users WHERE id = ?`
var user User
err := m.conn.QueryRow(&user, query, id)
return &user, err
}
func (m *userModel) FindOneByUsername(username string) (*User, error) {
query := `SELECT id, username, email, phone, password, nickname, avatar, status, created_at, updated_at
FROM users WHERE username = ?`
var user User
err := m.conn.QueryRow(&user, query, username)
return &user, err
}
func (m *userModel) Update(user *User) error {
query := `UPDATE users SET email=?, phone=?, nickname=?, avatar=?, status=?, updated_at=? WHERE id=?`
_, err := m.conn.Exec(query, user.Email, user.Phone, user.Nickname,
user.Avatar, user.Status, time.Now(), user.Id)
return err
}
func (m *userModel) Delete(id int64) error {
query := `DELETE FROM users WHERE id = ?`
_, err := m.conn.Exec(query, id)
return err
}
func (m *userModel) List(keyword string, page, pageSize int) ([]*User, int64, error) {
var users []*User
var total int64
where := ""
if keyword != "" {
where = " WHERE username LIKE '%" + keyword + "%' OR email LIKE '%" + keyword + "%'"
}
countQuery := "SELECT COUNT(*) FROM users" + where
err := m.conn.QueryRow(&total, countQuery)
if err != nil {
return nil, 0, err
}
query := "SELECT id, username, email, phone, password, nickname, avatar, status, created_at, updated_at FROM users" + where
query += " LIMIT " + string(rune(pageSize)) + " OFFSET " + string(rune((page-1)*pageSize))
err = m.conn.QueryRows(&users, query)
return users, total, err
}2.6.6 服务上下文
go
// internal/svc/service.go
package svc
import (
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"user-service/internal/config"
"user-service/internal/model"
)
type ServiceContext struct {
UserModel model.UserModel
Redis *redis.Redis
Config *config.Config
}
func NewServiceContext(c *config.Config) *ServiceContext {
conn := sqlx.NewMysql(c.Database.DataSource)
return &ServiceContext{
UserModel: model.NewUserModel(conn),
Redis: redis.New(c.Redis.Host),
Config: c,
}
}2.6.7 业务逻辑
go
// internal/logic/user.go
package logic
import (
"context"
"errors"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/logx"
"user-service/internal/svc"
"user-service/internal/types"
)
type UserLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLogic {
return &UserLogic{
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UserLogic) CreateUser(req *types.CreateUserRequest) (*types.User, error) {
// 检查用户名是否存在
existing, err := l.svcCtx.UserModel.FindOneByUsername(req.Username)
if err == nil && existing != nil {
return nil, errors.New("用户名已存在")
}
// 创建用户
user := &types.User{
Username: req.Username,
Email: req.Email,
Phone: req.Phone,
Nickname: req.Nickname,
Status: 1,
}
id, err := l.svcCtx.UserModel.Insert(user)
if err != nil {
logx.Error("创建用户失败", "error", err)
return nil, errorx.New("创建用户失败")
}
user.Id = id
return user, nil
}
func (l *UserLogic) GetUser(req *types.GetUserRequest) (*types.User, error) {
user, err := l.svcCtx.UserModel.FindOne(req.Id)
if err != nil {
return nil, errorx.New("用户不存在")
}
return user, nil
}
func (l *UserLogic) ListUser(req *types.ListUserRequest) (*types.ListUserResponse, error) {
users, total, err := l.svcCtx.UserModel.List(req.Keyword, req.Page, req.PageSize)
if err != nil {
return nil, errorx.New("获取用户列表失败")
}
return &types.ListUserResponse{
Total: total,
Users: users,
}, nil
}
func (l *UserLogic) UpdateUser(req *types.UpdateUserRequest) (*types.User, error) {
existing, err := l.svcCtx.UserModel.FindOne(req.Id)
if err != nil {
return nil, errorx.New("用户不存在")
}
existing.Email = req.Email
existing.Phone = req.Phone
existing.Nickname = req.Nickname
existing.Avatar = req.Avatar
err = l.svcCtx.UserModel.Update(existing)
if err != nil {
return nil, errorx.New("更新用户失败")
}
return existing, nil
}
func (l *UserLogic) DeleteUser(req *types.DeleteUserRequest) (string, error) {
err := l.svcCtx.UserModel.Delete(req.Id)
if err != nil {
return "", errorx.New("删除用户失败")
}
return "删除成功", nil
}
func (l *UserLogic) Login(req *types.LoginRequest) (*types.LoginResponse, error) {
user, err := l.svcCtx.UserModel.FindOneByUsername(req.Username)
if err != nil {
return nil, errorx.New("用户名或密码错误")
}
// 验证密码(实际应使用 bcrypt)
if user.Password != req.Password {
return nil, errorx.New("用户名或密码错误")
}
// 生成 JWT Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.Id,
"username": user.Username,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})
tokenString, _ := token.SignedString([]byte(l.svcCtx.Config.Auth.Secret))
return &types.LoginResponse{
Token: tokenString,
User: user,
}, nil
}2.6.8 Handler
go
// internal/handler/user.go
package handler
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"user-service/internal/logic"
"user-service/internal/svc"
"user-service/internal/types"
)
func CreateUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateUserRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewUserLogic(r.Context(), svcCtx)
resp, err := l.CreateUser(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}
func GetUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetUserRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewUserLogic(r.Context(), svcCtx)
resp, err := l.GetUser(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}
func ListUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListUserRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewUserLogic(r.Context(), svcCtx)
resp, err := l.ListUser(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}
func UpdateUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdateUserRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewUserLogic(r.Context(), svcCtx)
resp, err := l.UpdateUser(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}
func DeleteUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DeleteUserRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewUserLogic(r.Context(), svcCtx)
resp, err := l.DeleteUser(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.LoginRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
l := logic.NewUserLogic(r.Context(), svcCtx)
resp, err := l.Login(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}2.6.9 路由配置
go
// internal/handler/routes.go
package handler
import (
"net/http"
"github.com/zeromicro/go-zero/rest"
"user-service/internal/svc"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodPost,
Path: "/api/user/create",
Handler: CreateUserHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/api/user/get",
Handler: GetUserHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/api/user/list",
Handler: ListUserHandler(serverCtx),
},
{
Method: http.MethodPut,
Path: "/api/user/update",
Handler: UpdateUserHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/api/user/delete",
Handler: DeleteUserHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/api/user/login",
Handler: LoginHandler(serverCtx),
},
},
)
}2.6.10 入口文件
go
// cmd/user-api/main.go
package main
import (
"flag"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest"
"user-service/internal/config"
"user-service/internal/handler"
"user-service/internal/svc"
)
var configFile = flag.String("f", "configs/user-api.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*flag.Parse(), &c)
logx.MustSetup(logx.Config{
Mode: c.Log.Mode,
Level: c.Log.Level,
})
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
handler.RegisterHandlers(server, svc.NewServiceContext(&c))
logx.Infof("Starting server at %s:%d...", c.Host, c.Port)
server.Start()
}2.7 运行项目
2.7.1 生成代码
bash
# 进入项目目录
cd user-service
# 生成代码
goctl api go -api api/user.api -dir . -style gozero2.7.2 启动服务
bash
# 启动服务
go run cmd/user-api/main.go -f configs/user-api.yaml2.7.3 测试 API
bash
# 创建用户
curl -X POST http://localhost:8080/api/user/create \
-H "Content-Type: application/json" \
-d '{"username":"test","email":"test@example.com","password":"123456"}'
# 获取用户
curl http://localhost:8080/api/user/get?id=1
# 用户列表
curl http://localhost:8080/api/user/list?page=1&page_size=10
# 登录
curl -X POST http://localhost:8080/api/user/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"123456"}'2.8 扩展练习
- 添加 Redis 缓存:为用户查询添加缓存
- 添加 JWT 认证:保护需要认证的接口
- 添加分页:完善分页逻辑
- 添加日志:集成结构化日志
- 添加监控:集成 Prometheus