【Go】参数验证,对象数组核验-validator

news2025/2/3 17:54:58

文章目录

  • 背景
  • 功能介绍
    • 范围比较验证
    • 标记之间特殊符号说明
    • 字符串验证
    • 特殊字符串验证
    • 例子
  • 扩展问题
    • 我的问题
      • 验证slice
        • 举例
        • 输出
        • 说明
        • 详细举例
      • 二维slice
        • 举例:
        • 说明
      • map核验
        • 举例
        • 输出
        • 说明
      • 嵌套map核验
        • 举例
        • 说明
  • 参考

背景

一直做的 go 项目中想要方便简洁的对接口参数字段进行核验,选择与 gin 框架适配的validator库作为验证组件,基础用法都比较简单,对字符数字型值校验,对切片、数组、map 类型的范围检验,基础使用起来都比较简单易懂好上手,但近期突然遇到一个意外错误,本来该校验的对象数组,却没有按照我预计的核验这个数组里每个对象所属字段的值是否符合要求,由此记录一下。

功能介绍

范围比较验证

范围验证: 切片、数组和map、字符串,验证其长度;数值,验证大小范围

  • lte:小于等于参数值,validate:“lte=3” (小于等于3)
  • gte:大于等于参数值,validate:“lte=120,gte=0” (大于等于0小于等于120)
  • lt:小于参数值,validate:“lt=3” (小于3)
  • gt:大于参数值,validate:“lt=120,gt=0” (大于0小于120)
  • len:等于参数值,validate:“len=2”,字符串长度必须为n,或者是数组、切片、map的len的值
  • max:最大值,小于等于参数值,validate:“max=20” (小于等于20)
  • min:最小值,大于等于参数值,validate:“min=2,max=20” (大于等于2小于等于20)
  • ne:不等于,validate:“ne=2” (不等于2)
  • oneof:只能是列举出的值其中一个,这些值必须是数值或字符串,以空格分隔,如果字符串中有空格,将字符串用单引号包围,validate:“oneof=red green”

标记之间特殊符号说明

  • 逗号( ,):把多个验证标记隔开。注意:隔开逗号之间不能有空格, validate:“lt=0,gt=100”,逗号那里不能有空格,否则panic
  • 横线( - ):跳过该字段不验证
  • 竖线( | ):使用多个验证标记,但是只需满足其中一个即可
  • required:表示该字段值必输设置,且不能为默认值
  • omitempty:如果字段未设置,则忽略它

字符串验证

  • contains:包含参数子串,validate:“contains=tom” (字段的字符串值包含tom)
  • excludes:包含参数子串,validate:“excludes=tom” (字段的字符串值不包含tom)
  • startswith:以参数子串为前缀,validate:“startswith=golang”
  • endswith:以参数子串为后缀,validate:“startswith=world”

特殊字符串验证

  • email:验证字符串是email格式。默认为必填
  • url:验证字符串是URL格式。默认为必填
  • uri:字段值是否包含有效的uri,validate:“uri”
  • ip:字段值是否包含有效的IP地址,validate:“ip”
  • ipv4:字段值是否包含有效的ipv4地址,validate:“ipv4”
  • ipv6:字段值是否包含有效的ipv6地址,validate:“ipv6”

例子

简单举例,可自行测试

type UserInfo struct {
	ID   int    `validate:"gt=0"`
	Age  int    `validate:"gt=0"`
	Name string `validate:"required"`
	Sex  string `validate:"required"`
}
func InitUserInfo(id, age int, name, sex string) *UserInfo {
	// new一个校验器
	valid := validator.New()
	// 初始化UserInfo
	userInfo := &UserInfo{
		ID:   id,
		Age:  age,
		Name: name,
		Sex:  sex,
	}
	if err := valid.Struct(userInfo); err != nil {
		fmt.Println("参数校验不通过", err)
	}
	return userInfo
}
func TestValidate(t *testing.T) {
	InitUserInfo(1, 2, "kevin", "男") // 参数校验通过
	InitUserInfo(0, 2, "kevin", "男") // 参数校验不通过 Key: 'UserInfo.ID' Error:Field validation for 'ID' failed on the 'gt' tag
	InitUserInfo(1, 2, "kevin", "")  // 参数校验不通过 Key: 'UserInfo.Sex' Error:Field validation for 'Sex' failed on the 'required' tag
}

扩展问题

我的问题

验证slice

因为项目的接口参数的结构体是复杂,往往嵌套着对象数组或切片,不是简单由一个结构体就可以概括接收,在项目后续突然发现一个本该检验的对象数组里的某个参数,因对方方也疏忽未传值,导致后面检验出现问题,排查之前虽然问题不在我这边,但的却这个校验字段没有生效,后续排查了一下对于数组或切片或 Map 类型的检验需要使用 dive 才能核验到下一层级的字段里。

举例

sliceone := []string{"123", "onetwothree", "myslicetest", "four", "five"} 
validate.Var(sliceone, "max=15,dive,min=4")

输出

Key: '[0]' Error:Field validation for '[0]' failed on the 'min' tag
Key: '' Error:Field validation for '' failed on the 'min' tag

说明

第二个参数中tag关键字 dive 前面的 max=15,验证 [] , 也就是验证slice的长度,
dive 后面的 min=4,验证slice里的值长度,也就是说 dive 后面的 tag 验证 slice 的值

详细举例

结合上面简单使用的例子使用 dive 核验切片数组对象字段实现


import (
	"fmt"
	"github.com/go-playground/validator/v10"
	"testing"
)

type UserInfo struct {
	ID   int    `validate:"gt=0"`
	Age  int    `validate:"gt=0"`
	Name string `validate:"required"`
	Sex  string `validate:"required"`
}

type Teacher struct {
	Member []*UserInfo `validate:"max=15,dive,required"`
}

func InitUserInfo(id, age int, name, sex string) *UserInfo {
	// new一个校验器
	valid := validator.New()
	// 初始化UserInfo
	userInfo := &UserInfo{
		ID:   id,
		Age:  age,
		Name: name,
		Sex:  sex,
	}
	var menber []*UserInfo
	menber = append(menber, userInfo)
	teacher := &Teacher{
		Member: menber,
	}
	if err := valid.Struct(teacher); err != nil {
		fmt.Println("参数校验不通过", err)
	}
	return userInfo
}

func TestValidate(t *testing.T) {
	InitUserInfo(1, 2, "kevin", "男") // 参数校验通过
	InitUserInfo(0, 2, "kevin", "男") // 参数校验不通过 Key: 'UserInfo.ID' Error:Field validation for 'ID' failed on the 'gt' tag
	InitUserInfo(1, 2, "kevin", "")  // 参数校验不通过 Key: 'UserInfo.Sex' Error:Field validation for 'Sex' failed on the 'required' tag
}

当教师结构体中的参数内包含用户信息结构体,需要核验用户信息的字段时候就需要加上validate:"max=15,dive,required",这样就很核验用户信息结构体字段是否也符合要求
输出如下
在这里插入图片描述
也可不加前面的长度,直接validate:"dive,required"使用

二维slice

当然二维切片也差不多原理

举例:

slicethree := [][]string{} validate.Var(slicethree, "min=2,dive,len=2,dive,required") 
validate.Var(slicethree, "min=2,dive,dive,required")

说明

这里有2个 dive,刚好深入到二维slice,但他们也有不同之处,第二个表达式的第一个dive后没有设置tag。
第一个验证表达式:
min=2:验证第一个 [] 方括号的值长度 ;
len=2:验证第二个 []string 长度;
required:验证slice里的值
第二个验证表达式:
min=2:验证第一个 [] 方括号的值长度 ;
dive: 后没有设置tag值,不验证第二个 []string ;
required: 验证slice里的值

map核验

map的验证中也需要tag关键字 dive, 另外,它还有 keys 和 endkeys 两tag,验证这2个tag之间map的 key,而不是value值。

举例

var mapone map[string]string mapone = map[string]string{"one": "jimmmy", "two": "tom", "three": ""}
 validate := validator.New() 
 err := validate.Var(mapone, "gte=3,dive,keys,eq=1|eq=2,endkeys,required")

输出

Key: '[three]' Error:Field validation for '[three]' failed on the 'eq=1|eq=3' tag
Key: '[three]' Error:Field validation for '[three]' failed on the 'required' tag
Key: '[one]' Error:Field validation for '[one]' failed on the 'eq=1|eq=3' tag
Key: '[two]' Error:Field validation for '[two]' failed on the 'eq=1|eq=3' tag

说明

gte=3:验证map自己的长度;
dive后的 keys,eq=1|eq=2,endkeys:验证map的keys个数,也就是验证 [] 里值。上例中定义了一个string,所以明显报了3个错误。
required:验证 map的值value

嵌套map核验

如:map[[3]string]string,和上面slice差不多,使用多个 dive

举例

var maptwo map[[3]string]string{} validate.Var(maptwo, "gte=3,dive,keys,dive,eq=1|eq=3,endkeys,required")

说明

gte=3: 验证map的长度;
keys,dive,eq=1|eq=3,endkeys:keys和endkeys中有一个dive(深入一级),验证map中key的数组每一个值
required: 验证map的值

参考

golang常用库:字段参数验证库-validator使用
validator代码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/103704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTP模块的数据结构

1.ngx_module_t 1.1数据定义 背景: ngx_module_t结构体作为所有模块的通用接口,它只定义了init_master、init_module、init_process、init_thread、ext_thread、exit_process、exit_master这7个回调方法(事实上,init_master、i…

DEJA_VU3D - Cesium功能集 之 090-台风过境实时动画

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小130个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(尽可能把代码简洁一些)。博文内容…

内网穿透软件对比——cpolar : 花生壳(上)

系列文章 内网穿透软件对比——cpolar : 花生壳(上)内网穿透软件对比——cpolar : 花生壳(中)内网穿透软件对比——cpolar : 花生壳(下) 文章目录系列文章1. 前言2. 对比内容2.1 官网主页对比2.2 用户注册…

JS中的事件循环机制(Event Loop)

JS中的事件循环机制(Event Loop) javascript是单线程的非阻塞的脚本语言 单线程 只有一个主线程来处理任务。非阻塞 JS引擎执行异步任务时,不会一直等待返回结果,主线程会挂起(pending)这个任务,继续执行其他任务&am…

微三云(陈志坤):共享购商业模式概念、框架、基础制度

各位企业家及创业者朋友们,你们好。我是微三云(陈志坤),在你打开这个文章的时候,先不要急,因为任何一个能够长久、安稳、盈利的平台,背后肯定有一位看准宏观方向且耐心的人。这是一个极具颠覆性…

php宝塔搭建部署实战零起飞OA办公管理系统源码

大家好啊,我是测评君,欢迎来到web测评。 本期给大家带来一套php开发的零起飞OA办公管理系统源码,感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板文字搭建教程 下载源码,宝…

Mybatis对MySQL中BLOB字段的读取

一、使用TEXT还是BLOB&#xff1f; 1、TEXT和BLOB主要差别 主要差别就是BLOB保存二进制数据&#xff0c;TEXT保存字符数据。 目前几乎博客内容里图片都不是二进制存储在数据库的&#xff0c;而是把图片上传到服务器&#xff0c;然后正文里使用<img>标签引用&#xff0c…

电脑系统下载的镜像文件在哪里图解

如果你在小白系统上面下载了镜像文件&#xff0c;但是不知道下载完成怎么找到它的话&#xff0c;下面就和小编一起来看一下小白系统下载的镜像文件知道方法。 工具/原料&#xff1a; 系统版本&#xff1a;windows10系统 品牌型号&#xff1a;惠普战66五代 方法/步骤&#x…

C/C++程序的断点调试 - Microsoft Visual Studio

本文以Microsoft Visual Studio为例&#xff0c;简述C/C程序断点调试的基本方法和过程。其它的IDE环境&#xff0c;大同小异。 本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载&#xff0c;但需要注明原作者"海洋饼干叔 叔"&#xff…

【BFS】八数码问题(c++基础算法)

目录 一.读题 二.在做题之前 1.康拓展开 2.DFS和BFS的区别 3.栈和队列的区别 三.做题 1.算法原理 2.算法实现 ①队列 ②康托展开 ③标记 四.AC代码 一.读题 作为最经典的一道宽度优先搜索题&#xff0c;它的题面并不是很难懂。 【宽搜&#xff08;难度&#xff1a;6&a…

爆款短视频拍摄技巧之摇、移、跟拍等,这样拍的视频才更有吸引力,速收藏

爆款短视频拍摄技巧之拍摄手法,这样拍的视频才更有吸引力。 拍摄技巧主要分为两个部分&#xff0c;一个是构图&#xff0c;一个是拍摄手法。上一篇我们聊过了两种构图手法&#xff0c;接下来咱们聊一下拍摄手法&#xff0c;也就是我们常说的推、拉、摇、移、跟这五种手法。 其…

NNDL 2022秋

第一届AI专业&#xff0c;很多课程都是第一次开课&#xff0c;老师和学生都在“摸着石头过河”。 好处是所学内容比较新&#xff0c;跟得上“潮流”&#xff0c;学习意愿比较强。 难处是教学资料相对欠缺&#xff0c;需要学的内容较多&#xff0c;难度较大。 大家经过一学期…

leetcode:1494. 并行课程 II【dfs记忆化 + 状态压缩】

目录题目截图题目分析ac code总结题目截图 题目分析 这道题很像toposort&#xff0c;但实际不是因为那些indeg为先为0的&#xff0c;先选不一定好的考虑到n很小我们使用状态压缩dfs(state)表示当前state下&#xff0c;还需要多少个学期结束用pre数组存一下每个idx对应的前置条…

基于C++11 实现的线程池

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 基于C11 实现的线程池基于C11 实现的线程池1、线程池原理2、线程池的设计思路&#xff1f;3、一个基于C11的优秀的线程池3.1 头文件3.2 线程池类3.3 构造函数实现3.4 入队—添…

ModStartBlog v6.4.0 升级输入过滤、多文件组件,修复已知问题

V6.4.0版本更新 2022年12月20日ModStartBlog发布v6.4.0版本&#xff0c;增加了以下14个特性&#xff1a; [新功能] 富文本过滤规则调整优化[新功能] ArrayPackage数组输入数据包处理器[新功能] 请求输入组件新增多文件路径类型[新功能] 多文件上传组件[新功能] 所有组件新增t…

RCE代码及命令执行(详解)

RCE代码及命令执行1.RCE漏洞1.1.漏洞原理1.2.漏洞产生条件1.3.漏洞挖掘1.4.漏洞分类1.4.1.命令执行1.4.1.1.漏洞原理1.4.1.2.命令执行危险函数1.4.1.3.漏洞检测1.4.2.代码执行1.4.2.1.漏洞原理1.4.2.2.代码执行危险函数1.4.2.3.漏洞检测1.5.命令执行和代码执行区别2.命令执行2.…

QT学习记录(二)最基础的工程

文件 工程新建后会有这几个文件&#xff0c;自动生成的 main.cpp #include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);MainWindow w;w.show();return a.exec(); }QApplication a(argc, argv);这里…

Mentor-dft 学习笔记 day45-MTFI

Using MTFI Files此节介绍MTFI&#xff08;Mentor Tessend Fault Information&#xff09;功能&#xff0c;可用于ATPG工具和Tessent LogicBIST。MTFI是用于存储故障状态信息的通用且可扩展的文件格式。MTFI File Format MTFI是在“dft-edt”和“patterns-scan”上下文中读取和…

运用大O来给代码提速(冒泡排序)

本文内容借鉴一本我非常喜欢的书——《数据结构与算法图解》。学习之余&#xff0c;我决定把这本书精彩的部分摘录出来与大家分享。 本章内容 写在前面 1.冒泡排序 2.冒泡排序实战 3.冒泡排序的实现 4.冒泡排序的效率 5.二次问题 6.线性解决 7.总结 写在前面 大 O记…

Diffusion Model合集 part2

扩散模型原理介绍2五&#xff0c;逆扩散过程(Reverse Process)六&#xff0c;扩散过程中的后验的条件概率q(xt−1∣xt,x0)q(x_{t-1}|x_{t},x_{0})q(xt−1​∣xt​,x0​)七&#xff0c;目标数据分布的似然函数八&#xff0c;Diffusion Probabilistic Model的算法代码五&#xff…