基于 go-zero 的企业级微服务框架底座,提供统一的启动方式、响应结构、错误码、中间件等基础能力。
# 从远程仓库安装
go install github.com/addls/go-base/cmd/go-base@latest
# 或本地开发安装
cd go-base
go install ./cmd/go-base
# 验证安装
go-base --version# 一键初始化标准业务项目(包含 gateway 和 services/ping)
go-base init demo生成的项目结构:
demo/
├── go.mod
├── gateway/ # Gateway 服务
│ ├── etc/
│ │ └── config.yaml
│ ├── pb/ # proto descriptor 文件(自动生成)
│ │ └── ping.pb
│ └── main.go
└── services/
└── ping/ # Ping RPC 服务
├── ping.proto # Proto 定义
├── etc/
│ └── config.yaml
├── pb/ # 生成的 proto 代码
└── ping.go
运行服务:
# 1. 运行 RPC 服务
cd demo/services/ping
go run ping.go
# 2. 运行 Gateway 服务(在另一个终端)
cd demo/gateway
go run main.go# 1. 进入项目根目录
cd demo
# 2. 创建服务目录
mkdir -p services/user
cd services/user
# 3. 生成 proto 模板文件
goctl rpc -o user.proto
# 4. 编辑 user.proto 定义服务接口
# syntax = "proto3";
# package user;
# option go_package="./user";
#
# message GetUserReq {
# int64 id = 1;
# }
#
# message GetUserResp {
# int64 id = 1;
# string name = 2;
# }
#
# service UserService {
# rpc GetUser(GetUserReq) returns(GetUserResp);
# }
# 5. 生成 RPC 服务代码
goctl rpc protoc user.proto \
--go_out=./pb \
--go-grpc_out=./pb \
--zrpc_out=. \
--client=true \
--style=go_zero \
-m
# 6. 重命名配置文件(如果生成了其他名称)
mv etc/user.yaml etc/config.yaml 2>/dev/null || true
# 7. 运行服务
go run user.go重要:Gateway 需要 .pb descriptor 文件来解析 gRPC 服务定义,不能直接使用 .proto 源文件。
自动生成(推荐):
- 使用
go-base init初始化项目时,会自动生成gateway/pb/ping.pb - 如果后续添加了新服务,需要手动生成
手动生成:
# 在项目根目录执行
cd demo
# 生成 ping 服务的 descriptor 文件
protoc --descriptor_set_out=gateway/pb/ping.pb \
--include_imports \
services/ping/ping.proto
# 如果添加了新服务(如 user),也需要生成
protoc --descriptor_set_out=gateway/pb/user.pb \
--include_imports \
services/user/user.proto参数说明:
--descriptor_set_out:输出 descriptor 文件路径--include_imports:包含所有导入的 proto 文件(重要!Gateway 需要完整依赖)- 最后一个参数:proto 源文件路径
在 gateway/etc/config.yaml 中配置路由:
RPC 服务配置:
Upstreams:
- Grpc:
Target: localhost:8080 # gRPC 服务地址
ProtoSets:
- pb/ping.pb # proto descriptor 文件路径(相对于 gateway 目录)
Mappings:
- Method: GET
Path: /ping
RpcPath: ping.Ping/Ping # 格式:package.Service/MethodRpcPath 格式说明:
- 格式:
package.Service/Method package:proto 文件中的package声明(如package ping;)Service:服务名称(如service Ping {)Method:RPC 方法名(如rpc Ping(...))
示例:
package user;
service UserService {
rpc GetUser(...) returns (...);
}对应的 RpcPath:user.UserService/GetUser
HTTP 服务配置:
Upstreams:
- Name: orderapi
Http:
Target: localhost:8888
Prefix: /api
Timeout: 3000
Mappings:
- Method: GET
Path: /order在 gateway/etc/config.yaml 中开启 Auth 配置后,Gateway 会在转发前做 JWT 校验;校验通过后,会把用户信息透传给后端 gRPC 服务。
1) 开启 JWT
Auth:
AccessSecret: a-string-secret-at-least-256-bits-long
AccessExpire: 3600
SkipPaths:
- /health
- /ping2) Token 里需要包含的字段
当前实现基于 go-zero 的 handler.Authorize:它会把 非标准 claims 写入 context(标准字段如 sub/exp/iat/... 会被忽略)。
因此建议在 JWT payload 里使用非标准字段:
uid:用户 ID(字符串)name:用户名(字符串)
示例 payload(仅示例):
{
"uid": "1234567890",
"name": "John Doe"
}3) Gateway 如何透传到 gRPC
Gateway 会设置以下 Header(grpc-gateway 约定,带 Grpc-Metadata- 前缀的 Header 会进入 gRPC metadata):
Grpc-Metadata-x-jwt-user-id: <uid>Grpc-Metadata-x-jwt-user-name: <name>
4) gRPC 服务里如何获取
在 RPC 逻辑中使用 pkg/auth:
userID := auth.GetUserID(ctx)
userName := auth.GetUserName(ctx)
// 调试用:拿到整个 metadata(包含系统字段)
claims := auth.GetClaims(ctx)package main
import (
"github.com/zeromicro/go-zero/rest"
"github.com/addls/go-base/pkg/bootstrap"
"demo-project/internal/config"
"demo-project/internal/handler"
"demo-project/internal/svc"
)
func main() {
bootstrap.RunHttp(bootstrap.WithHttpRoutes(func(server *rest.Server) {
handler.RegisterHandlers(server, svc.NewServiceContext(*bootstrap.MustLoadConfig[config.Config]()))
}))
}package main
import (
"github.com/addls/go-base/pkg/bootstrap"
"google.golang.org/grpc"
"demo-project/internal/config"
"demo-project/internal/server"
"demo-project/internal/svc"
)
func main() {
bootstrap.RunRpc(bootstrap.WithRpcService(func(grpcServer *grpc.Server) {
server.RegisterServices(grpcServer, svc.NewServiceContext(*bootstrap.MustLoadConfig[config.Config]()))
}))
}package main
import "github.com/addls/go-base/pkg/bootstrap"
func main() {
// Gateway 配置通过配置文件中的 Upstreams 定义路由规则
bootstrap.RunGateway()
}所有服务统一使用 -f flag 指定配置文件(默认 etc/config.yaml):
# 使用默认配置
go run main.go
# 指定配置文件
go run main.go -f etc/config.prod.yaml配置结构:
// HTTP 服务配置
type Config struct {
bootstrap.HttpConfig // 嵌入 bootstrap.HttpConfig
// 业务配置...
}
// gRPC 服务配置
type Config struct {
bootstrap.RpcConfig // 嵌入 bootstrap.RpcConfig
// 业务配置...
}
// Gateway 服务配置
type Config struct {
bootstrap.GatewayConfig // 嵌入 bootstrap.GatewayConfig
// Gateway 通过 Upstreams 配置路由
}import "github.com/addls/go-base/pkg/response"
// 成功响应
response.Ok(w) // 无数据
response.OkWithData(w, data) // 带数据
response.HandleResult(w, resp, err) // 自动处理结果
// 错误响应
response.Error(w, err) // 自动识别错误码
response.ErrorWithCode(w, 20001, "自定义错误") // 指定错误码响应格式:
{
"code": 0,
"msg": "success",
"data": {},
"traceId": "xxx"
}import "github.com/addls/go-base/pkg/errcode"
// 预定义错误码
errcode.ErrInvalidParam // 20001 - 参数错误
errcode.ErrNotFound // 20002 - 资源不存在
errcode.ErrUnauthorized // 20004 - 未授权
// 自定义业务错误码
var ErrUserNotFound = errcode.New(30101, "用户不存在")原因:Gateway 需要 proto descriptor 文件(.pb),而不是 proto 源文件(.proto)
解决:
# 生成 descriptor 文件
protoc --descriptor_set_out=gateway/pb/ping.pb \
--include_imports \
services/ping/ping.proto查看 proto 文件:
package ping;→ package 名是pingservice Ping {→ 服务名是Pingrpc Ping(...)→ 方法名是Ping
RpcPath = ping.Ping/Ping(格式:package.Service/Method)
ProtoSets 中的路径是相对于 gateway 目录的:
ProtoSets:
- pb/ping.pb # 相对于 gateway 目录MIT License