add
This commit is contained in:
229
校验体系设计优化.md
Normal file
229
校验体系设计优化.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# 校验体系设计优化
|
||||
|
||||
## 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 自动绑定
|
||||
Reference in New Issue
Block a user