tianyuan-api-server/校验体系设计优化.md
2025-07-13 20:37:12 +08:00

230 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 校验体系设计优化
## 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 自动绑定