6.8 KiB
6.8 KiB
校验体系设计优化
1. 校验层次重新设计
Handler 层(client-api)
// types.go - 只做格式校验
type GetProductListReq struct {
CategoryId int64 `form:"category_id" validate:"min=1"` // 格式校验:必须大于0
PageNum int64 `form:"page_num" validate:"min=1,max=1000"` // 格式校验:分页范围
PageSize int64 `form:"page_size" validate:"min=1,max=100"` // 格式校验:每页数量
Keyword string `form:"keyword" validate:"max=50"` // 格式校验:关键词长度
}
// handler.go - 只做参数绑定和格式校验
func (l *GetProductListHandler) GetProductList(w http.ResponseWriter, r *http.Request) {
var req types.GetProductListReq
// 1. 参数绑定
if err := httpx.Parse(r, &req); err != nil {
response.ParamErrorResponse(w, err)
return
}
// 2. 格式校验(只校验格式,不校验业务)
if err := validator.Validate(&req); err != nil {
response.ParamErrorResponse(w, err)
return
}
// 3. 调用Logic层
resp, err := l.svcCtx.GetProductListLogic(l.ctx, &req)
// ...
}
RPC 层(product-rpc)
// product.proto - RPC接口定义
message GetProductListReq {
int64 category_id = 1;
int64 page_num = 2;
int64 page_size = 3;
string keyword = 4;
int64 user_id = 5; // 业务相关:用户ID
string user_role = 6; // 业务相关:用户角色
}
// logic.go - 业务校验
func (l *GetProductListLogic) GetProductList(req *product.GetProductListReq) (*product.GetProductListResp, error) {
// 1. 业务校验(在相关业务域内)
if err := l.validateBusinessRules(req); err != nil {
return nil, err
}
// 2. 业务逻辑处理
// ...
}
func (l *GetProductListLogic) validateBusinessRules(req *product.GetProductListReq) error {
// 业务规则校验:用户是否有权限查看该分类
if !l.svcCtx.UserModel.HasCategoryPermission(req.UserId, req.CategoryId) {
return errors.New("用户无权限查看该分类")
}
// 业务规则校验:分类是否存在且启用
category, err := l.svcCtx.CategoryModel.FindOne(req.CategoryId)
if err != nil || category.Status != 1 {
return errors.New("分类不存在或已禁用")
}
return nil
}
2. 参数传递和转换
Logic 层的作用
// client/internal/logic/product/getproductlistlogic.go
func (l *GetProductListLogic) GetProductList(req *types.GetProductListReq) (*types.GetProductListResp, error) {
// 1. 获取用户信息(从JWT中解析)
userInfo := l.ctx.Value("userInfo").(auth.UserInfo)
// 2. 转换为RPC请求(添加业务上下文)
rpcReq := &product.GetProductListReq{
CategoryId: req.CategoryId,
PageNum: req.PageNum,
PageSize: req.PageSize,
Keyword: req.Keyword,
UserId: userInfo.UserId, // 添加业务上下文
UserRole: userInfo.Role, // 添加业务上下文
}
// 3. 调用RPC服务
rpcResp, err := l.svcCtx.ProductRpc.GetProductList(l.ctx, rpcReq)
if err != nil {
return nil, err
}
// 4. 转换响应格式
return &types.GetProductListResp{
List: convertToApiFormat(rpcResp.List),
Total: rpcResp.Total,
}, nil
}
3. 校验规则组织优化
目录结构调整
shared/
├── validator/ # 通用格式校验器
│ ├── validator.go # 校验器初始化
│ ├── rules.go # 通用校验规则
│ └── messages.go # 错误消息
├── errcode/ # 错误码定义
│ ├── api_errors.go # API层错误
│ └── business_errors.go # 业务层错误
└── utils/ # 工具函数
└── converter.go # 数据转换
domains/
├── product/
│ ├── rpc/
│ │ └── internal/
│ │ └── logic/
│ │ └── validator/ # 产品域业务校验
│ │ ├── product_validator.go
│ │ └── category_validator.go
├── user/
│ └── rpc/
│ └── internal/
│ └── logic/
│ └── validator/ # 用户域业务校验
│ ├── user_validator.go
│ └── auth_validator.go
业务校验器实现
// domains/product/rpc/internal/logic/validator/product_validator.go
package validator
import (
"context"
"tianyuan/domains/product/rpc/internal/svc"
"tianyuan/domains/product/rpc/product"
)
type ProductValidator struct {
svcCtx *svc.ServiceContext
}
func NewProductValidator(svcCtx *svc.ServiceContext) *ProductValidator {
return &ProductValidator{
svcCtx: svcCtx,
}
}
// 校验用户是否有权限查看产品列表
func (v *ProductValidator) ValidateGetProductListPermission(ctx context.Context, req *product.GetProductListReq) error {
// 检查用户权限
hasPermission, err := v.svcCtx.UserRpc.CheckPermission(ctx, &user.CheckPermissionReq{
UserId: req.UserId,
Permission: "product:list",
})
if err != nil {
return err
}
if !hasPermission.HasPermission {
return errors.New("用户无权限查看产品列表")
}
return nil
}
// 校验分类相关业务规则
func (v *ProductValidator) ValidateCategoryAccess(ctx context.Context, req *product.GetProductListReq) error {
// 检查分类是否存在且用户有访问权限
// ...
return nil
}
4. 关于 httpx.Parse 的说明
httpx.Parse
是 go-zero 提供的参数绑定函数,它会:
- 自动绑定参数:根据 struct tag 从 HTTP 请求中解析参数
- 支持多种来源:query 参数、form 数据、JSON body 等
- 类型转换:自动进行基础类型转换
// 示例:httpx.Parse 的使用
type GetProductListReq struct {
CategoryId int64 `form:"category_id"` // 从form或query中获取
PageNum int64 `form:"page_num"`
PageSize int64 `form:"page_size"`
Keyword string `form:"keyword"`
}
func Handler(w http.ResponseWriter, r *http.Request) {
var req GetProductListReq
// 这一行会自动从 r *http.Request 中解析参数到 req 结构体
if err := httpx.Parse(r, &req); err != nil {
// 参数解析失败
return
}
// 此时 req 已经包含了解析后的参数值
}
5. 最佳实践总结
- Handler 层:只做格式校验,使用 validator tag
- Logic 层:参数转换和聚合,添加业务上下文
- RPC 层:业务规则校验,放在对应的业务域内
- 校验器分离:格式校验器放 shared,业务校验器放各自域内
- 参数绑定:使用 go-zero 的 httpx.Parse 自动绑定