github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/validate/README.md (about)

     1  ## validate
     2  
     3  validate包核查模块,用于对入参的校验
     4  
     5  ## 快速使用
     6  这里举个例子,快速使用
     7  
     8  ### 基于isc-gobase的web的项目的示例:
     9  ```go
    10  // main.go 文件
    11  package main
    12  
    13  import (
    14    "bytes"
    15      "encoding/json"
    16      "github.com/gin-gonic/gin"
    17      "github.com/isyscore/isc-gobase/http"
    18      "github.com/isyscore/isc-gobase/isc"
    19      "github.com/isyscore/isc-gobase/logger"
    20      "github.com/isyscore/isc-gobase/server"
    21      "github.com/isyscore/isc-gobase/server/rsp"
    22      "github.com/isyscore/isc-gobase/validate"
    23      "io/ioutil"
    24      "strings"
    25  )
    26  
    27  func main() {
    28      server.Post("test/insert", InsertData)
    29      server.Run()
    30  }
    31  
    32  // InsertData 数据插入
    33  func InsertData(c *gin.Context) {
    34      insertReq := InsertReq{}
    35  
    36      // 读取body数据,可以采用isc提供的工具
    37      err := isc.DataToObject(c.Request.Body, &insertReq)
    38      if err != nil {
    39          // ... 省略异常日志
    40          return
    41      }
    42  
    43      // api示例:核查入参
    44      if result, msg := validate.Check(insertReq); !result {
    45          // 参数异常
    46          rsp.FailedOfStandard(c, 53, msg)
    47          logger.Error(msg)
    48          return
    49      }
    50  
    51      rsp.SuccessOfStandard(c, "ok")
    52  }
    53  
    54  type InsertReq struct {
    55      Name    string `match:"value={zhou, chen}"`
    56      Profile string `match:"range=[0, 10)"`
    57  }
    58  ```
    59  ```yaml
    60  # application.yml 文件
    61  api-module: app/sample
    62  
    63  base:
    64    api:
    65      # api前缀
    66      prefix: /api
    67    application:
    68      # 应用名称
    69      name: sample
    70    server:
    71      # 是否启用,默认:true
    72      enable: true
    73      # 端口号
    74      port: 8080
    75      # web框架gin的配置
    76      gin:
    77        # 有三种模式:debug/release/test
    78        mode: release
    79  
    80  ```
    81  
    82  请求
    83  ```shell
    84  curl -X POST \
    85    http://localhost:8080/api/app/sample/test/insert \
    86    -H 'Content-Type: application/json' \
    87    -d '{
    88  	"name": "zhou",
    89  	"profile": "abcde-abcde"
    90  }'
    91  ```
    92  返回异常
    93  ```json
    94  {
    95      "code": 53,
    96      "data": null,
    97      "message": "长度不合法"
    98  }
    99  ```
   100  
   101  ### 非web的普通类核查示例:
   102  
   103  ```go
   104  package main
   105  
   106  import (
   107      "github.com/isyscore/isc-gobase/validate"
   108      "github.com/isyscore/isc-gobase/logger"
   109  )
   110  
   111  type DemoInsert struct {
   112      // 对属性修饰 
   113      Name string `match:"value=zhou"`
   114      Age  int
   115  }
   116  
   117  func main() {
   118      var value DemoInsert
   119      var result bool
   120      var errMsg string
   121  
   122      value = DemoInsert{Name: "chen"}
   123      // 核查
   124      result, errMsg = validate.Check(value)
   125      if !result {
   126          // 属性 Name 的值 chen 不在只可用列表 [zhou] 中 
   127          logger.Error(errMsg)
   128      }
   129  }
   130  ```
   131  说明:<br/>
   132  
   133  1. 这里提供方法Check,用于核查是否符合条件
   134  2. 提供标签match,标签内容中提供匹配器:value,该匹配器表示匹配的具体的一些值
   135  
   136  ## api说明
   137  只提供两个Api,`Check` 和 `CheckWithParameter`
   138  ```go
   139  // 入参:
   140  //  @any            待核查对象
   141  //  @fieldNames     待核查对象的核查属性;不指定则核查所有属性名
   142  // 返回值:
   143  //  bool    是否匹配合法:true-合法,false-不合法
   144  //  string  不合法对应的说明
   145  func Check(object any, fieldNames ...string) (bool, string) {}
   146  
   147  // 入参:
   148  //  @parameterMap   额外的参数,用于在自定义函数中进行使用,可见下面的customize的用法
   149  //  @any            待核查对象
   150  //  @fieldNames     待核查对象的核查属性;不指定则核查所有属性名
   151  // 返回值:
   152  //  bool    是否匹配合法:true-合法,false-不合法
   153  //  string  不合法对应的说明
   154  func CheckWithParameter(parameterMap map[string]interface{}, object interface{}, fieldNames ...string) (bool, string) {}
   155  ```
   156  
   157  ## 更多功能
   158  
   159  这里将核查部分分为匹配和处理两部分,匹配可以有多种的匹配器,核查的逻辑是只要有任何一个匹配器匹配上则认为匹配上,处理模块用于对匹配上的结果进行处理,比如返回指定的异常,或者匹配后接受还是拒绝对应的值,或者匹配后将某个值更改掉(待支持)
   160  
   161  #### 匹配模块
   162  
   163  - value:匹配指定的值
   164  - isBlank:值是否为空字符
   165  - isUnBlank:值是否为非空字符
   166  - range:匹配数值的范围(最大值和最小值,用法是数学表达式):数值(整数和浮点数)的大小、字符串的长度、数组的长度、时间的范围、时间的移动
   167  - model:匹配指定的类型:
   168      - id_card:身份证
   169      - phone: 手机号
   170      - fixed_phone:固定电话
   171      - mail: 邮件地址
   172      - ip: ip地址
   173  - condition:修饰的属性的表达式的匹配,提供#current和#root占位符,用于获取相邻属性的值
   174  - regex:匹配正则表达式
   175  - customize:匹配自定义的回调函数
   176  
   177  #### 处理模块
   178  
   179  - errMsg: 自定义的异常
   180  - accept: 匹配后接受还是拒绝
   181  - disable: 是否启用匹配,默认启用
   182  
   183  
   184  ## 匹配模块
   185  
   186  匹配器可以有多个一起修饰,只要匹配上一个,则认为匹配上
   187  
   188  ### 1. 值匹配器:value
   189  匹配指定的一些值,可以修饰一个,也可以修饰多个值,可以修饰字符,也可修饰整数(int、int8、int16、int32、int64)、无符号整数(uint、uint8、uint16、uint32、uint64)、浮点数(float32、float64)、bool类型和string类型。<br/>
   190  
   191  提示:
   192   - 中间逗号也可以为中文,为了防止某些手误写错为中文字符
   193  
   194  
   195  ```go
   196  // 修饰一个值
   197  type ValueBaseEntityOne struct {
   198      Name string `match:"value=zhou"`
   199      Age  int    `match:"value=12"`
   200  }
   201  
   202  // 修饰一个值
   203  type ValueBaseEntity struct {
   204      Name string `match:"value={zhou, 宋江}"`
   205      Age  int    `match:"value={12, 13}"`
   206  }
   207  ```
   208  
   209  如果有自定义类型嵌套,则可以使用标签`check`,用于解析复杂结构
   210  ```go
   211  type ValueInnerEntity struct {
   212      InnerName string `match:"value={inner_zhou, inner_宋江}"`
   213      InnerAge  int    `match:"value={2212, 2213}"`
   214  }
   215  
   216  type ValueStructEntity struct {
   217      Name string `match:"value={zhou, 宋江}"`
   218      Age  int    `match:"value={12, 13}"`
   219  
   220      Inner ValueInnerEntity `match:"check"`
   221  }
   222  ```
   223  修饰的结构可以有如下
   224  - 自定义结构
   225  - 数组/分片:对应类型只有为复杂结构才会核查
   226  - map:其中的key和value类型只有是复杂结构才会核查
   227  
   228  ### 2. 空值匹配器:isBlank
   229  匹配string类型的值是否为空字符,false:字符不为空则匹配上,true:字符为空则匹配上
   230  ```go
   231  // 默认为true
   232  type IsBlankEntity2 struct {
   233      Name string `match:"isBlank"`
   234      Age  int
   235  }
   236  
   237  // 同上
   238  type IsBlankEntity3 struct {
   239      Name string `match:"isBlank=true"`
   240      Age  int
   241  }
   242  
   243  type IsBlankEntity1 struct {
   244      Name string `match:"isBlank=false"`
   245      Age  int
   246  }
   247  
   248  ```
   249  
   250  ### 3. 非空匹配器:isUnBlank
   251  匹配string类型的值是否为非空字符,true:字符非空则匹配上,false:字符为空则匹配上
   252  ```go
   253  // 默认为true
   254  type IsBlankEntity2 struct {
   255      Name string `match:"isBlank"`
   256      Age  int
   257  }
   258  
   259  // 同上
   260  type IsBlankEntity3 struct {
   261      Name string `match:"isBlank=true"`
   262      Age  int
   263  }
   264  
   265  type IsBlankEntity1 struct {
   266      Name string `match:"isBlank=false"`
   267      Age  int
   268  }
   269  
   270  ```
   271  
   272  ### 4. 范围匹配器:range
   273  匹配类型的指定范围,方式使用数学表达式"["、"]"、"("、")",使用数学表达式的开闭符号
   274  - [:表示大于等于
   275  - ]:表示小于等于
   276  - (:表示大于
   277  - ):表示小于
   278  
   279  比如:<br/>
   280  - [1, 10):表示大于等于1而且小于10
   281  - (1, 10):表示大于1而且小于10
   282  - (1,):表示大于1
   283  - (,10]:表示小于等于10,也可以[,10]
   284  
   285  修饰的类型有
   286  - 整数:比较大小,int、int8、int16、int32、int64
   287  - 无符号整数:比较大小,uint、uint8、uint16、uint32、uint64
   288  - 浮点数:比较大小,float32、float64
   289  - 分片:匹配分片的长度
   290  - 字符串:匹配字符串的长度
   291  - 时间类型(time.Time):时间的范围,时间格式支持如下
   292    - yyyy
   293    - yyyy-MM
   294    - yyyy-MM-dd
   295    - yyyy-MM-dd HH
   296    - yyyy-MM-dd HH:mm
   297    - yyyy-MM-dd HH:mm:ss
   298    - yyyy-MM-dd HH:mm:ss.SSS
   299    - now:表示当前时间
   300  - 变量时间:除了使用数学表达式外,还支持past、future这两个关键字,用于表示过去和未来的时间
   301  - 时间计算:用于计算当前的时间向前或者向后推几个小时或者几分钟这种;(-1M, ):表示最近一个月的时间;(-1M3d, ):表示最近一个月零3天的时间,表示大于时间向前推一个月零三天的时间<br/>
   302    - -/+:表示往前推还是往后推
   303    - y:年
   304    - M:月
   305    - d:日
   306    - h:小时
   307    - m:分钟
   308    - s:秒
   309  
   310  ```go
   311  // 整数类型1
   312  type RangeIntEntity1 struct {
   313      Age  int `match:"range=[1, 2]"`
   314  }
   315  
   316  // 整数类型2
   317  type RangeIntEntity2 struct {
   318      Age  int `match:"range=[3,]"`
   319  }
   320  
   321  // 整数类型3
   322  type RangeIntEntity3 struct {
   323      Age  int `match:"range=[3,)"`
   324  }
   325  
   326  // 浮点数类型
   327  type RangeFloatEntity struct {
   328      Money float32 `match:"range=[10.37, 20.31]"`
   329  }
   330  
   331  // 字符类型
   332  type RangeStringEntity struct {
   333      Name string `match:"range=[2, 12]"`
   334  }
   335  
   336  // 分片类型
   337  type RangeSliceEntity struct {
   338      Age  []int `match:"range=[2, 6]"`
   339  }
   340  
   341  // 时间类型1
   342  type RangeTimeEntity1 struct {
   343      CreateTime time.Time `match:"range=[2019-07-13 12:00:23.321, 2019-08-23 12:00:23.321]"`
   344  }
   345  
   346  // 时间类型2
   347  type RangeTimeEntity2 struct {
   348      CreateTime time.Time `match:"range=[2019-07-13 12:00:23.321, ]"`
   349  }
   350  
   351  // 时间类型3
   352  type RangeTimeEntity3 struct {
   353      CreateTime time.Time `match:"range=(, 2019-07-23 12:00:23.321]"`
   354  }
   355  
   356  // 时间类型4
   357  type RangeTimeEntity4 struct {
   358      CreateTime time.Time `match:"range=[2019-07-23 12:00:23.321, now)"`
   359  }
   360  
   361  // 时间类型4
   362  type RangeTimeEntity5 struct {
   363      CreateTime time.Time `match:"range=past"`
   364  }
   365  
   366  // 时间类型4
   367  type RangeTimeEntity6 struct {
   368      CreateTime time.Time `match:"range=future"`
   369  }
   370  
   371  // 时间计算:年
   372  type RangeTimeCalEntity1 struct {
   373      CreateTime time.Time `match:"range=(-1y, )"`
   374  }
   375  
   376  // 时间计算:月
   377  type RangeTimeCalEntity2 struct {
   378      CreateTime time.Time `match:"range=(-1M, )"`
   379  }
   380  
   381  // 时间计算:月日
   382  // 顺序不能乱:yMdhms,中间可以为空,比如:-1y3d
   383  type RangeTimeCalEntity2And1 struct {
   384      CreateTime time.Time `match:"range=(-1M3d, )"`
   385  }
   386  
   387  // 时间计算:日
   388  type RangeTimeCalEntity3 struct {
   389      CreateTime time.Time `match:"range=(-3d, )"`
   390  }
   391  
   392  // 时间计算:时
   393  type RangeTimeCalEntity4 struct {
   394      CreateTime time.Time `match:"range=(-4h, )"`
   395  }
   396  
   397  // 时间计算:分
   398  type RangeTimeCalEntity5 struct {
   399      CreateTime time.Time `match:"range=(-12m, )"`
   400  }
   401  
   402  // 时间计算:秒
   403  type RangeTimeCalEntity6 struct {
   404      CreateTime time.Time `match:"range=(-120s, )"`
   405  }
   406  
   407  // 时间计算:正负号
   408  type RangeTimeCalEntity7 struct {
   409      CreateTime time.Time `match:"range=(2h, )"`
   410  }
   411  ```
   412  
   413  ### 5. 类型匹配器:model
   414  类型匹配器:指定的几种内置类型进行匹配
   415  - id_card:身份证
   416  - phone: 手机号
   417  - fixed_phone:固定电话
   418  - mail: 邮件地址
   419  - ip: ip地址
   420  
   421  ```go
   422  type ValueModelIdCardEntity struct {
   423      Data string `match:"model=id_card"`
   424  }
   425  
   426  type ValueModelPhone struct {
   427      Data string `match:"model=phone"`
   428  }
   429  
   430  type ValueModelFixedPhoneEntity struct {
   431      Data string `match:"model=fixed_phone"`
   432  }
   433  
   434  type ValueModelEmailEntity struct {
   435      Data string `match:"model=mail"`
   436  }
   437  
   438  type ValueModelIpAddressEntity struct {
   439      Data string `match:"model=ip"`
   440  }
   441  ```
   442  
   443  ### 6. 表达式匹配器:condition
   444  表达式匹配器:用于数学计算表达式进行计算,表达式是返回bool类型的表达式。提供两个占位符
   445  - \#current:当前修饰的值
   446  - \#root:当前属性所在的对象,比如:#root.Age,表示当前对象中的其他属性Age的值
   447  
   448  ```go
   449  // 测试基本表达式
   450  type ValueConditionEntity1 struct {
   451      Data1 int `match:"condition=#current + #root.Data2 > 100"`
   452      Data2 int `match:"condition=#current < 20"`
   453      Data3 int `match:"condition=(++#current) >31"`
   454  }
   455  
   456  // 测试表达式
   457  type ValueConditionEntity2 struct {
   458      Age   int `match:"condition=#root.Judge"`
   459      Judge bool
   460  }
   461  ```
   462  
   463  ### 7. 正则表达式匹配器:regex
   464  正则表达式匹配器:用于匹配自定义的正则表达式
   465  
   466  ```go
   467  type ValueRegexEntity struct {
   468      Name string `match:"regex=^zhou.*zhen$"`
   469      Age  int    `match:"regex=^\\d+$"`
   470  }
   471  ```
   472  
   473  ### 8. 自定义回调匹配器:customize
   474  该匹配器可以用于自定义扩展,比如实际业务场景,某个字段在数据库中存在,这种情况就需要用户自定义扩展
   475  
   476  比如:
   477  ```go
   478  package fun
   479  
   480  type CustomizeEntity1 struct {
   481      // fun.Judge1是对应的函数
   482      Name string `match:"customize=judge1Name"`
   483  }
   484  
   485  func JudgeString1(name string) bool {
   486      if name == "zhou" || name == "宋江" {
   487          return true
   488      }
   489  
   490      return false
   491  }
   492  
   493  // 由于go反射功能没那么强,因此需要用户自己先将函数和name进行注册
   494  func init() {
   495    validate.RegisterCustomize("judge1Name", JudgeString1)
   496  }
   497  ```
   498  ##### 说明:
   499  
   500  其中自定义的函数有相关的要求,参数可以为一个,也可以为两个,也可以为三个<br/>
   501  其中的参数类型有严格限制
   502  - 属性类型
   503  - 属性所在对象类型
   504  - 外部参数类型`map[string]interface{}`
   505  
   506  
   507  参数:<br/>
   508  - 一个参数:
   509    - 1:属性类型
   510    - 2:属性所在对象类型
   511  - 两个参数
   512    - 1:属性所在对象类型,2:属性类型
   513    - 1:属性所在对象类型,2:外部参数类型`map[string]interface{}`
   514    - 1:属性类型,2:属性所在对象类型
   515    - 1:属性类型,2:外部参数类型`map[string]interface{}`
   516  - 三个参数:前两个参数为:属性类型和所在对象类型的组合
   517    - 1:属性类型,2:属性所在对象类型,3:外部参数类型`map[string]interface{}`
   518    - 1:属性所在对象类型,2:属性类型,3:外部参数类型`map[string]interface{}`
   519  
   520  返回值:<br/>
   521  - 一个值:则为bool类型(表示是否匹配上)
   522  - 两个值:第一个为bool类型(表示是否匹配上),第二个为string类型(匹配或者没有匹配上的自定义错误)
   523  
   524  更多详情请见测试类`customize_test.go` <br/><br/>
   525  示例:
   526  ```go
   527  package fun
   528  
   529  import (
   530      "fmt"
   531      "github.com/isyscore/isc-gobase/validate"
   532  )
   533  
   534  type CustomizeEntity2 struct {
   535      Name string `match:"customize=judge2Name"`
   536  }
   537  
   538  type CustomizeEntity3 struct {
   539      Name string `match:"customize=judge3Name"`
   540      Age  int
   541  }
   542  
   543  func JudgeString2(name string) (bool, string) {
   544      if name == "zhou" || name == "宋江" {
   545          return true, ""
   546      }
   547  
   548      return false, "没有命中可用的值'zhou'和'宋江'"
   549  }
   550  
   551  func JudgeString3(customize CustomizeEntity3, name string) (bool, string) {
   552      if name == "zhou" || name == "宋江" {
   553      if customize.Age > 12 {
   554          return true, ""
   555      } else {
   556          return false, "用户[" + name + "]" + "没有满足年龄age > 12," + "当前年龄为:" + fmt.Sprintf("%v", customize.Age)
   557      }
   558  
   559      } else {
   560          return false, "没有命中可用的值'zhou'和'宋江'"
   561      }
   562  }
   563  
   564  // 由于go反射功能没那么强,因此需要用户自己先将函数和name进行注册
   565  func init() {
   566      validate.RegisterCustomize("judge2Name", JudgeString2)
   567      validate.RegisterCustomize("judge3Name", JudgeString3)
   568  }
   569  ```
   570  
   571  ## 处理模块
   572  当匹配后如何处理,这里分为了如下几种处理
   573  
   574  ### 1. 匹配上接受/拒绝:accept
   575  匹配后是接收还是拒绝,目前业内的处理方式都是匹配后接收,在概念上其实叫白名单,对于黑名单的处理,业内是没有,而我们这里用accept实现白名单和黑名单的概念
   576  
   577  ```go
   578  type AcceptEntity1 struct {
   579      // 表示只要匹配上name为zhou的,则拒绝
   580      Name string `match:"value=zhou" accept:"false"`
   581      Age  int
   582  }
   583  
   584  type AcceptEntity2 struct {
   585      // 表示name为空,则拒绝,表示需要非空才行,以下同`match:"isUnBlank"`
   586      Name string `match:"isBlank" accept:"false"`
   587      Age  int
   588  }
   589  
   590  // 只要任何一个匹配上,则接受
   591  type AcceptEntity3 struct {
   592      Name string `match:"isBlank=true value=zhou" accept:"true"`
   593      Age  int
   594  }
   595  ```
   596  
   597  ### 2. 自定义异常:errMsg
   598  匹配后返回自定义的异常,提供了两个占位符#current表示修饰的当前属性的值,#root当前属性所在的结构的值,#root.Age表示当前结构中的属性Age对应的值
   599  
   600  ```go
   601  type ErrMsgEntity1 struct {
   602      Name string `match:"value=zhou" errMsg:"对应的值不合法"`
   603      Age  int
   604  }
   605  
   606  type ErrMsgEntity2 struct {
   607      Name string `match:"value=zhou"`
   608      Age  int    `match:"condition=#current > 10" errMsg:"当前的值不合法,应该大于10,当前值为#current,对应的名字为#root.Name"`
   609  }
   610  ```
   611  
   612  ### 3. 启用:disable
   613  表示是否启用整个核查
   614  ```go
   615  type DisableEntity1 struct {
   616      Name string `match:"value=zhou" disable:"true"`
   617      Age  int
   618  }
   619  
   620  ```