当前位置: 首页 > news >正文

Golang Model 字段自动化校验设计

背景

在我们日常开发中,不可避免的总要去进行各种参数校验,但是如果在某个场景中,要校验的字段非常多,并且在其中还有耦合关系,那么我们手写校验逻辑就变得非常的低效且难以维护。本篇文档就基于 DDD 领域模型设计的思想下,提供自动化的校验模型字段。

常见的字段校验方式

数据校验在业务逻辑代码中有着至关重要的作用,关系到整个后续业务是否可以正常运行。对参数的校验根据其具体业务逻辑与场景,可以分为字段校验、依赖校验、功能校验与逻辑校验四个部分。

字段校验

字段校验是最常见的校验类型。例如:商品名称不能超过多少个字符,商品状态必须是有效等。

func (e *Shop) ValidateShopName() error {
  	if e.Name != nil && e.Name == "" {
    		return errors.New("商品名称不能为空。")
 		}
  
  	if e.Name != nil && utf8.RuneCountInString(e.Name) > constant.MaxShopNameLength {
    		return errors.Errorf("商品名称长度为 %d, 不能超过 %d ", utf8.RuneCountInString(e.Name), constant.MaxShopNameLength)
  	}
  
  	return nil
}

依赖校验

依赖校验,顾名思义是在业务逻辑中依赖了其他模块。例如,在创建商品信息时,要校验一下商品依赖的商家或供应商等信息是否合法。

func (e *Shop) ValidateMerchant() error {
		// 在此方法中可能需要进行外部调用或者查询 DB 的操作。
  	if e.HasInvalidMerchant() {
    		return errors.New("商家信息存在异常")
  	}
  
  	return nil
}

功能校验

功能校验例如用户是否有权限发布商品、商品信息是否与其他商品存在冲突等。

func (e *Shop) ValidateUserPermission() error {
  	if e.UserCreateShopWithoutPermission() {
    		return errors.New("用户无权限创建商品")
  	}
  
  	return nil
}

逻辑校验

逻辑校验主要是一些具体的业务逻辑。例如在下架商品时,校验是否有新用户下单等。

func (e *Shop) ValidateCloseShop() error{
  	if e.InvalidShopStatus() {
    		return errors.New("商品已下架")
  	}
  	if e.ExistShopTicket() {
    		return errors.New("有正在进行的订单信息,无法下架")
  	}
  
  	return nil
}

上面我们列出来常见的四种校验方式,当我们在一个复杂且庞大的业务场景需要把各种各样的校验放在一起去校验时,我们不得不编写一个庞大的校验函数,把这些单点的校验函数聚合起来,更有甚者都没有进行子逻辑校验的函数区分,就是第一个大函数,把各种各样的校验逻辑代码写到一个函数中,那么长此以往,校验逻辑就会非常复杂,无法迭代。

func (e *Shop) ValidateCreateShop() error {
  	if err = e.ValidateShopName(); err != nil {
    		return err
  	}
  	if err = e.ValidateDescrption(); err != nil {
    		return err
  	}
  	if err = e.ValidateImage(); err != nil {
    		return err
  	}
  	if err = e.ValidateMerchant(); err != nil {
    		return err
  	}
  	if err = e.ValidateUserPermission(); err != nil {
    		return err
  	}
  	if err = e.ValidateCloseShop(); err != nil {
    		return err
  	}
  
  	return nil
}

自动化校验

image-20250215164708771



type Validator struct {
    FieldNames			[]string		// 需要更新的字段
    ValidateNames		[]string		// 需要校验的字段列表
    ValidateFuncList	[]Func() error	// 校验函数列表
}

func (v *Validator) Validate() error {
    for _, validate := range v.validateFuncList {
        if err := validate(); err != nil {
            return err
        }
    }
    return nil
}

// GetFields2ValidateFuncMap 各个字段的校验函数在这里扩展,在调用 register 函数时,会自动注册
func (a *Aggregate) GetFields2ValidateFuncMap() map[string]func() error {
    return map[string]func() error {
        constant.ShopForCreate:		a.Shop.ValidateCreateShop,
        constant.ShopForUpdate: 	a.Shop.ValidateUpdateShop,
        constant.ShopCanStart:  	a.Shop.CanStart,
        // ... 等等各种校验都可以在这里定义一个聚合函数列表
    }
}


func DTOToAgg(dto *DTO.Shop) (*shop.Aggregate, error) {
    baseShop := base.NewBaseShop()
    // 先把传参 model 转化成领域数据
    if err = copier.Copy(baseShop, dto); err != nil {
        return nil, errors.Wrap(err, err.Error())
    }
    
    // New 一个聚合类
    shopAgg := shop.NewShopAggregate(baseShop)
    
    // 获取本次传给领域对象的字段,以及加载要校验的字段
    setFields := GetSetOptionalFields(*dto)
    var validateName []string
    for _, field := range setFields {
        validateName = append(validateName, field)
    }
    
    shopAgg.SetUpdateFields(setFields)
    // 注册 validate 函数
    shopAgg.RegisterValidator(validateName)
    
    return shopAgg, nil   
}

// 执行校验函数
func (v *Validator) ValidateMultipleFields(ctx context.Context) error {
	for _, validate := range v.validateFuncList {
		if err := validate(); err != nil {
			return err
		}
	}
	return
}
image-20250215165659841

简单来描述自动校验分为以下几个步骤:

  1. 在接收传参的转换函数中,先把本次请求传入的字段拿到,并且注册这些字段对应的校验函数。
  2. 进入到业务逻辑处理的函数中,再次增加一些当前业务场景需要的特殊校验函数。
  3. 依次执行校验函数,观察是否有报错。

相关文章:

  • WEB安全--SQL注入--常见的注入手段
  • cv2.Sobel
  • 构建现代微服务安全体系:Spring Security、JWT 与 Spring Cloud Gateway 实践
  • 如何在 IntelliJ IDEA 中使用 Bito AI 插件
  • C++编程,#include <iostream>详解,以及using namespace std;作用
  • Android的Activity生命周期知识点总结,详情
  • ML.Net二元分类
  • spring boot 对接aws 的S3 服务,实现上传和查询
  • vue3.x 自定义hook函数详细解读
  • CAS单点登录(第7版)18.日志和审计
  • Java:单例模式(Singleton Pattern)及实现方式
  • sql sqlserver的特殊函数COALESCE和PIVOT的用法分析
  • 理解 WebGPU 中的 navigator.gpu 和 adapter:从浏览器到显卡的旅程
  • 视频编码标准(H.264/AVC、H.265/HEVC、AV1、MPEG-2 和 MPEG-4 Part 2)
  • Python常见面试题的详解6
  • vscode/cursor 写注释时候出现框框解决办法
  • 【UE】快速的搓一个基于贴图的假渲染
  • 【Linux】Ext2文件系统、软硬链接
  • Linux: 调整套接字缓冲区大小相关内核参数
  • verilog程序设计及SystemVerilog验证
  • 从《缶翁的世界》看吴昌硕等湖州籍书画家对海派的影响
  • 香港今年新股集资额已超600亿港元,暂居全球首位
  • 广西壮族自治区政府主席蓝天立任上被查
  • 国家统计局:2024年城镇单位就业人员工资平稳增长
  • 梅花奖在上海|话剧《主角》:艺术与人生的交错
  • 大环线呼之欲出,“金三角”跑起来了