一、前置环境准备
先安装所有必需的工具,确保版本匹配:
# 1. 安装Thrift代码生成核心
go install github.com/cloudwego/thriftgo@latest
# 2. 安装Kitex RPC代码生成工具
go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
# 3. 安装Hertz HTTP代码生成工具
go install github.com/cloudwego/hertz/cmd/hz@latest
# 4. 安装参数验证代码生成插件(给Kitex用,与Hertz验证注解兼容)
go install github.com/cloudwego/thrift-gen-validator@latest
初始化项目(替换为你自己的模块名):
mkdir user-demo && cd user-demo
go mod init github.com/yourname/user-demo
二、编写共用 Thrift IDL(用户模块示例)
创建 idl/user.thrift,一份文件同时包含三层定义:
- RPC 服务接口定义(给 Kitex 用)
- HTTP 路由与参数绑定规则(给 Hertz 用,api.*注解)
- 统一参数验证规则(api.vd注解,两边工具都可识别)
namespace go user
// 用户实体
struct User {
1: i64 id (api.vd="min=1,message=用户ID必须大于0")
2: string username (api.vd="required,min=3,max=20,message=用户名长度3-20位")
3: string email (api.vd="required,email,message=邮箱格式不正确")
}
// 查询用户请求
struct GetUserReq {
// api.query:HTTP层从query参数取值,RPC层正常传参
1: i64 user_id (api.query="user_id", api.vd="min=1,message=用户ID必须大于0")
}
// 创建用户请求
struct CreateUserReq {
// api.body:HTTP层从JSON Body取值
1: string username (api.body="username", api.vd="required,min=3,max=20")
2: string password (api.body="password", api.vd="required,min=6,message=密码至少6位")
3: string email (api.body="email", api.vd="required,email")
}
struct CreateUserResp {
1: User user
}
// 统一服务定义:Kitex生成RPC服务,Hertz生成HTTP路由
service UserService {
// HTTP GET /user/get?user_id=123
GetUser(1: GetUserReq req) (api.get="/user/get")
// HTTP POST /user/create,Body为JSON
CreateUser(1: CreateUserReq req) (api.post="/user/create")
}
三、基于同一份 IDL 生成两层代码
1. 生成 Kitex RPC 层代码(带验证标签)
执行命令生成 RPC 服务骨架、客户端 SDK、带validate标签的结构体:
kitex -module github.com/yourname/user-demo \
-service user-rpc \
-plugin validator \
-out ./cmd/user-rpc \
idl/user.thrift
- module:必须和 go.mod 的模块名完全一致,避免导入路径错误
- plugin validator:启用验证插件,自动给结构体生成validate标签
- out:RPC 服务骨架输出目录,kitex_gen代码会自动生成在项目根目录
2. 生成 Hertz HTTP 层代码(带验证标签)
执行命令生成 HTTP 网关的路由、Handler 骨架、带vd标签的结构体:
hz new -module github.com/yourname/user-demo \
-out ./cmd/gateway \
-idl ../../idl/user.thrift
后续修改 IDL 后,用hz update -idl ../../idl/user.thrift增量更新代码,不会覆盖你写的业务逻辑。
最终项目结构
user-demo/
├── idl/
│ └── user.thrift # 唯一的接口定义文件
├── kitex_gen/ # Kitex生成的RPC结构体、客户端、服务端代码
│ └── user/
├── cmd/
│ ├── user-rpc/ # Kitex RPC服务端入口与Handler
│ │ ├── main.go
│ │ └── handler.go
│ └── gateway/ # Hertz HTTP网关入口与路由
│ ├── main.go
│ └── biz/
│ ├── handler/ # HTTP业务Handler
│ └── router/ # 自动生成的路由
└── go.mod
四、实现 Kitex RPC 服务端(带全局参数验证)
1. 编写全局验证中间件
RPC 层做兜底校验,避免内部调用绕过网关导致非法参数。创建 cmd/user-rpc/middleware/validator.go:
package middleware
import (
"context"
"github.com/cloudwego/kitex/pkg/endpoint"
"github.com/go-playground/validator/v10"
)
// 全局单例validator,避免重复初始化
var validate = validator.New()
// ValidatorMiddleware RPC服务端参数验证中间件
func ValidatorMiddleware() endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, req, resp interface{}) (err error) {
// 自动读取结构体的validate标签验证
if err = validate.Struct(req); err != nil {
return err
}
return next(ctx, req, resp)
}
}
}
2. 注册中间件并实现业务 Handler
修改 cmd/user-rpc/main.go,注册验证中间件:
package main
import (
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/cloudwego/kitex/server"
"github.com/yourname/user-demo/cmd/user-rpc/middleware"
"github.com/yourname/user-demo/kitex_gen/user/userservice"
"log"
)
func main() {
svr := userservice.NewServer(
new(UserServiceImpl),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "user-rpc"}),
server.WithMiddleware(middleware.ValidatorMiddleware()), // 注册全局验证中间件
)
err := svr.Run()
if err != nil {
log.Println(err.Error())
}
}
修改 cmd/user-rpc/handler.go 实现业务逻辑(验证已经由中间件完成,Handler 只处理业务):
package main
import (
"context"
"github.com/yourname/user-demo/kitex_gen/user"
)
type UserServiceImpl struct{}
func (s *UserServiceImpl) GetUser(ctx context.Context, req *user.GetUserReq) (resp *user.User, err error) {
// 业务逻辑:查询数据库等,参数已经验证完成
return &user.User{
Id: req.UserId,
Username: "test_user",
Email: "test@example.com",
}, nil
}
func (s *UserServiceImpl) CreateUser(ctx context.Context, req *user.CreateUserReq) (resp *user.CreateUserResp, err error) {
// 业务逻辑:创建用户,参数已经验证完成
return &user.CreateUserResp{
User: &user.User{
Id: 1001,
Username: req.Username,
Email: req.Email,
},
}, nil
}
五、实现 Hertz HTTP 网关(带参数验证 + RPC 调用)
1. 初始化 RPC 客户端
创建 cmd/gateway/biz/rpc/user.go,封装 Kitex RPC 客户端:
package rpc
import (
"github.com/cloudwego/kitex/client"
"github.com/yourname/user-demo/kitex_gen/user/userservice"
"log"
)
var UserClient userservice.Client
func InitRPC() {
c, err := userservice.NewClient(
"user-rpc",
client.WithHostPorts("127.0.0.1:8888"), // RPC服务地址,生产用服务发现
)
if err != nil {
log.Fatalf("RPC客户端初始化失败: %v", err)
}
UserClient = c
}
2. 实现 HTTP Handler(入口验证 + RPC 调用)
修改 cmd/gateway/biz/handler/user_service.go,Hertz 层做入口参数校验,拦截非法请求:
package handler
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
"github.com/yourname/user-demo/cmd/gateway/biz/rpc"
"github.com/yourname/user-demo/cmd/gateway/hertz_gen/user"
kitexUser "github.com/yourname/user-demo/kitex_gen/user"
)
type UserServiceHandler struct{}
func NewUserServiceHandler() *UserServiceHandler {
return &UserServiceHandler{}
}
func (h *UserServiceHandler) GetUser(ctx context.Context, c *app.RequestContext) {
// 1. HTTP层自动绑定参数+验证(读取vd标签)
var req user.GetUserReq
if err := c.BindAndValidate(&req); err != nil {
c.JSON(consts.StatusBadRequest, utils.H{"code": 400, "msg": err.Error()})
return
}
// 2. 调用RPC服务(同一份IDL,字段一一对应直接转换)
rpcResp, err := rpc.UserClient.GetUser(ctx, &kitexUser.GetUserReq{UserId: req.UserId})
if err != nil {
c.JSON(consts.StatusInternalServerError, utils.H{"code": 500, "msg": err.Error()})
return
}
c.JSON(consts.StatusOK, utils.H{"code": 0, "data": rpcResp})
}
func (h *UserServiceHandler) CreateUser(ctx context.Context, c *app.RequestContext) {
var req user.CreateUserReq
if err := c.BindAndValidate(&req); err != nil {
c.JSON(consts.StatusBadRequest, utils.H{"code": 400, "msg": err.Error()})
return
}
rpcResp, err := rpc.UserClient.CreateUser(ctx, &kitexUser.CreateUserReq{
Username: req.Username,
Password: req.Password,
Email: req.Email,
})
if err != nil {
c.JSON(consts.StatusInternalServerError, utils.H{"code": 500, "msg": err.Error()})
return
}
c.JSON(consts.StatusOK, utils.H{"code": 0, "data": rpcResp})
}
3. 网关启动入口初始化 RPC
修改 cmd/gateway/main.go,启动前初始化 RPC 客户端:
package main
import (
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/yourname/user-demo/cmd/gateway/biz/rpc"
"github.com/yourname/user-demo/cmd/gateway/biz/router"
)
func main() {
rpc.InitRPC() // 初始化RPC客户端
h := server.Default(server.WithHostPorts(":8080"))
router.Register(h)
h.Spin()
}
六、运行与验证
- 启动 RPC 服务:go run ./cmd/user-rpc(默认监听 8888 端口)
- 启动 HTTP 网关:go run ./cmd/gateway(监听 8080 端口)
- 测试验证效果:
# 测试参数校验失败(user_id为0不符合min=1规则)
curl "http://localhost:8080/user/get?user_id=0"
# 返回:{"code":400,"msg":"Key: 'GetUserReq.UserId' Error:Field validation for 'UserId' failed on the 'min' tag"}
# 测试正常请求
curl "http://localhost:8080/user/get?user_id=123"
七、最佳实践
- IDL 唯一原则:接口变更只修改 Thrift 文件,重新生成代码,彻底避免 HTTP 与 RPC 接口不一致的问题。
- 分层验证职责:HTTP 层做格式校验(必填、长度、正则),RPC 层做业务校验(如用户唯一性、权限),边界清晰且双重兜底。
- 自定义验证规则:复杂验证逻辑(跨字段、业务规则)编写统一的 validator 自定义函数,在两层同时注册,保证规则完全一致。
- 生产环境优化:RPC 服务注册到服务发现中心(如 etcd、nacos),网关通过服务名发现实例,不要硬编码地址。