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