# 校验体系设计优化 ## 1. 校验层次重新设计 ### Handler 层(client-api) ```go // 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) ```go // 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 层的作用 ```go // 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 ``` ### 业务校验器实现 ```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 提供的参数绑定函数,它会: 1. **自动绑定参数**:根据 struct tag 从 HTTP 请求中解析参数 2. **支持多种来源**:query 参数、form 数据、JSON body 等 3. **类型转换**:自动进行基础类型转换 ```go // 示例: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. 最佳实践总结 1. **Handler 层**:只做格式校验,使用 validator tag 2. **Logic 层**:参数转换和聚合,添加业务上下文 3. **RPC 层**:业务规则校验,放在对应的业务域内 4. **校验器分离**:格式校验器放 shared,业务校验器放各自域内 5. **参数绑定**:使用 go-zero 的 httpx.Parse 自动绑定