github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/validate/matcher/range.go (about)

     1  package matcher
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/isyscore/isc-gobase/constants"
     6  	"reflect"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  	t0 "time"
    11  	"unicode/utf8"
    12  
    13  	"github.com/antonmedv/expr"
    14  	"github.com/antonmedv/expr/compiler"
    15  	"github.com/antonmedv/expr/parser"
    16  	"github.com/antonmedv/expr/vm"
    17  	"github.com/isyscore/isc-gobase/logger"
    18  	"github.com/isyscore/isc-gobase/time"
    19  )
    20  
    21  type RangeMatch struct {
    22  	BlackWhiteMatch
    23  
    24  	RangeExpress string
    25  	Script       string
    26  	Begin        any
    27  	End          any
    28  	BeginNow     bool
    29  	EndNow       bool
    30  	Program      *vm.Program
    31  }
    32  
    33  type RangeEntity struct {
    34  	beginAli    string
    35  	begin       any
    36  	end         any
    37  	endAli      string
    38  	dateFlag    bool
    39  	dynamicTime bool
    40  	beginNow    bool
    41  	endNow      bool
    42  }
    43  
    44  type DynamicTimeNum struct {
    45  	plusOrMinus bool
    46  	years       int
    47  	months      int
    48  	days        int
    49  	hours       int
    50  	minutes     int
    51  	seconds     int
    52  }
    53  
    54  type Predicate func(subCondition string) bool
    55  
    56  // []:时间或者数字范围匹配
    57  var rangeRegex = regexp.MustCompile("^([(\\[])(.*)([,,])(\\s)*(.*)([)\\]])$")
    58  
    59  // digitRegex 全是数字匹配(整数,浮点数,0,负数)
    60  var digitRegex = regexp.MustCompile("^(0)|^[-+]?([1-9]+\\d*|0\\.(\\d*)|[1-9]\\d*\\.(\\d*))$")
    61  
    62  // 时间的前后计算匹配:(-|+)yMd(h|H)msS
    63  var timePlusRegex = regexp.MustCompile("^([-+])?(\\d*y)?(\\d*M)?(\\d*d)?(\\d*H|\\d*h)?(\\d*m)?(\\d*s)?$")
    64  
    65  func (rangeMatch *RangeMatch) Match(_ map[string]interface{}, _ any, field reflect.StructField, fieldValue any) bool {
    66  	env := map[string]any{
    67  		"begin": rangeMatch.Begin,
    68  		"end":   rangeMatch.End,
    69  	}
    70  
    71  	fieldKind := field.Type.Kind()
    72  	if IsCheckNumber(fieldKind) {
    73  		env["value"] = fieldValue
    74  	} else if fieldKind == reflect.String {
    75  		env["value"] = utf8.RuneCountInString(fmt.Sprintf("%v", fieldValue))
    76  	} else if fieldKind == reflect.Slice {
    77  		env["value"] = reflect.ValueOf(fieldValue).Len()
    78  	} else if field.Type.String() == "time.Time" {
    79  		env["value"] = fieldValue.(t0.Time).UnixNano()
    80  		if rangeMatch.BeginNow {
    81  			env["begin"] = time.Now().UnixNano()
    82  		} else if rangeMatch.EndNow {
    83  			env["end"] = time.Now().UnixNano()
    84  		}
    85  	} else {
    86  		return true
    87  	}
    88  
    89  	output, err := expr.Run(rangeMatch.Program, env)
    90  	if err != nil {
    91  		logger.Error("脚本 %v 执行失败: %v", rangeMatch.Script, err.Error())
    92  		return false
    93  	}
    94  
    95  	result, err := CastBool(fmt.Sprintf("%v", output))
    96  	if err != nil {
    97  		return false
    98  	}
    99  
   100  	if result {
   101  		if field.Type.Kind() == reflect.String {
   102  			if len(fmt.Sprintf("%v", fieldValue)) > 1024 {
   103  				rangeMatch.SetBlackMsg("属性 [%v] 值 [%v] 字符串长度位于禁用的范围 [%v] 中", field.Name, fieldValue, rangeMatch.RangeExpress)
   104  			} else {
   105  				rangeMatch.SetBlackMsg("属性 [%v] 值 [%v] 字符串长度位于禁用的范围 [%v] 中", field.Name, fieldValue, rangeMatch.RangeExpress)
   106  			}
   107  		} else if IsCheckNumber(field.Type.Kind()) {
   108  			rangeMatch.SetBlackMsg("属性 [%v] 值 [%v] 位于禁用的范围 [%v] 中", field.Name, fieldValue, rangeMatch.RangeExpress)
   109  		} else if field.Type.Kind() == reflect.Slice {
   110  			if reflect.ValueOf(fieldValue).Len() > 1024 {
   111  				rangeMatch.SetBlackMsg("属性 [%v] 值 [%v] 数组长度位于禁用的范围 [%v] 中", field.Name, fieldValue, rangeMatch.RangeExpress)
   112  			} else {
   113  				rangeMatch.SetBlackMsg("属性 [%v] 值 [%v] 数组长度位于禁用的范围 [%v] 中", field.Name, fieldValue, rangeMatch.RangeExpress)
   114  			}
   115  		} else if field.Type.String() == "time.Time" {
   116  			rangeMatch.SetBlackMsg("属性 [%v] 值 [%v] 时间位于禁用时间段 [%v] 中", field.Name, fieldValue, rangeMatch.RangeExpress)
   117  		} else {
   118  			return true
   119  		}
   120  		return true
   121  	} else {
   122  		if field.Type.Kind() == reflect.String {
   123  			if len(fmt.Sprintf("%v", fieldValue)) > 1024 {
   124  				rangeMatch.SetWhiteMsg("属性 [%v] 值 [%v] 长度没有命中只允许的范围 [%v]", field.Name, fieldValue, rangeMatch.RangeExpress)
   125  			} else {
   126  				rangeMatch.SetWhiteMsg("属性 [%v] 值 [%v] 长度没有命中只允许的范围 [%v]", field.Name, fieldValue, rangeMatch.RangeExpress)
   127  			}
   128  		} else if IsCheckNumber(field.Type.Kind()) {
   129  			rangeMatch.SetWhiteMsg("属性 [%v] 值 [%v] 没有命中只允许的范围 [%v]", field.Name, fieldValue, rangeMatch.RangeExpress)
   130  		} else if field.Type.Kind() == reflect.Slice {
   131  			if reflect.ValueOf(fieldValue).Len() > 1024 {
   132  				rangeMatch.SetWhiteMsg("属性 [%v] 值 [%v] 数组长度没有命中只允许的范围 [%v]", field.Name, fieldValue, rangeMatch.RangeExpress)
   133  			} else {
   134  				rangeMatch.SetWhiteMsg("属性 [%v] 值 [%v] 数组长度没有命中只允许的范围 [%v]", field.Name, fieldValue, rangeMatch.RangeExpress)
   135  			}
   136  		} else if field.Type.String() == "time.Time" {
   137  			rangeMatch.SetWhiteMsg("属性 [%v] 值 [%v] 时间没有命中只允许的时间段 [%v] 中", field.Name, fieldValue, rangeMatch.RangeExpress)
   138  		} else {
   139  			return true
   140  		}
   141  		return false
   142  	}
   143  }
   144  
   145  func (rangeMatch *RangeMatch) IsEmpty() bool {
   146  	return rangeMatch.Script == ""
   147  }
   148  
   149  func BuildRangeMatcher(objectTypeFullName string, fieldKind reflect.Kind, objectFieldName string, tagName string, subCondition string, errMsg string) {
   150  	if constants.MATCH != tagName {
   151  		return
   152  	}
   153  
   154  	if !strings.Contains(subCondition, constants.Range) || !strings.Contains(subCondition, constants.EQUAL) {
   155  		return
   156  	}
   157  
   158  	index := strings.Index(subCondition, "=")
   159  	value := subCondition[index+1:]
   160  
   161  	rangeEntity := parseRange(fieldKind, value)
   162  	if rangeEntity == nil {
   163  		return
   164  	}
   165  
   166  	beginAli := rangeEntity.beginAli
   167  	begin := rangeEntity.begin
   168  	end := rangeEntity.end
   169  	endAli := rangeEntity.endAli
   170  	beginNow := rangeEntity.beginNow
   171  	endNow := rangeEntity.endNow
   172  
   173  	var script string
   174  	if begin == nil {
   175  		if end == nil {
   176  			if beginNow {
   177  				if constants.LeftEqual == beginAli {
   178  					script = "begin <= value"
   179  				} else if constants.LeftUnEqual == beginAli {
   180  					script = "begin < value"
   181  				}
   182  			} else if endNow {
   183  				if constants.RightEqual == endAli {
   184  					script = "value <= end"
   185  				} else if constants.RightUnEqual == endAli {
   186  					script = "value < end"
   187  				}
   188  			} else {
   189  				return
   190  			}
   191  		} else {
   192  			if beginNow {
   193  				if constants.LeftEqual == beginAli && constants.RightEqual == endAli {
   194  					script = "begin <= value && value <= end"
   195  				} else if constants.LeftEqual == beginAli && constants.RightUnEqual == endAli {
   196  					script = "begin <= value && value < end"
   197  				} else if constants.LeftUnEqual == beginAli && constants.RightEqual == endAli {
   198  					script = "begin < value && value <= end"
   199  				} else if constants.LeftUnEqual == beginAli && constants.RightUnEqual == endAli {
   200  					script = "begin < value && value < end"
   201  				}
   202  			} else {
   203  				if constants.RightEqual == endAli {
   204  					script = "value <= end"
   205  				} else if constants.RightUnEqual == endAli {
   206  					script = "value < end"
   207  				}
   208  			}
   209  		}
   210  	} else {
   211  		if end == nil {
   212  			if endNow {
   213  				if constants.LeftEqual == beginAli && constants.RightEqual == endAli {
   214  					script = "begin <= value && value <= end"
   215  				} else if constants.LeftEqual == beginAli && constants.RightUnEqual == endAli {
   216  					script = "begin <= value && value < end"
   217  				} else if constants.LeftUnEqual == beginAli && constants.RightEqual == endAli {
   218  					script = "begin < value && value <= end"
   219  				} else if constants.LeftUnEqual == beginAli && constants.RightUnEqual == endAli {
   220  					script = "begin < value && value < end"
   221  				}
   222  			} else {
   223  				if constants.LeftEqual == beginAli {
   224  					script = "begin <= value"
   225  				} else if constants.LeftUnEqual == beginAli {
   226  					script = "begin < value"
   227  				}
   228  			}
   229  		} else {
   230  			if constants.LeftEqual == beginAli && constants.RightEqual == endAli {
   231  				script = "begin <= value && value <= end"
   232  			} else if constants.LeftEqual == beginAli && constants.RightUnEqual == endAli {
   233  				script = "begin <= value && value < end"
   234  			} else if constants.LeftUnEqual == beginAli && constants.RightEqual == endAli {
   235  				script = "begin < value && value <= end"
   236  			} else if constants.LeftUnEqual == beginAli && constants.RightUnEqual == endAli {
   237  				script = "begin < value && value < end"
   238  			}
   239  		}
   240  	}
   241  
   242  	tree, err := parser.Parse(script)
   243  	if err != nil {
   244  		logger.Error("脚本:%v 解析异常:%v", script, err.Error())
   245  		return
   246  	}
   247  
   248  	program, err := compiler.Compile(tree, nil)
   249  	if err != nil {
   250  		logger.Error("脚本: %v 编译异常:%v", script, err.Error())
   251  		return
   252  	}
   253  
   254  	addMatcher(objectTypeFullName, objectFieldName, &RangeMatch{Program: program, Begin: begin, End: end, Script: script, RangeExpress: value, BeginNow: beginNow, EndNow: endNow}, errMsg, true)
   255  }
   256  
   257  func parseRange(fieldKind reflect.Kind, subCondition string) *RangeEntity {
   258  	subData := rangeRegex.FindAllStringSubmatch(subCondition, -1)
   259  	if len(subData) > 0 {
   260  		beginAli := subData[0][1]
   261  		begin := subData[0][2]
   262  		end := subData[0][5]
   263  		endAli := subData[0][6]
   264  
   265  		if (begin == "nil" || begin == "") && (end == "nil" || end == "") {
   266  			logger.Error("range匹配器格式输入错误,start和end不可都为null或者空字符, input=%v", subCondition)
   267  			return nil
   268  		} else if begin == "past" || begin == "future" {
   269  			logger.Error("range匹配器格式输入错误, start不可含有past或者future, input=%v", subCondition)
   270  			return nil
   271  		} else if end == "past" || end == "future" {
   272  			logger.Error("range匹配器格式输入错误, end不可含有past或者future, input=%v", subCondition)
   273  			return nil
   274  		}
   275  
   276  		// 如果是数字,则按照数字解析
   277  		if (begin != "" && digitRegex.MatchString(begin)) || (end != "" && digitRegex.MatchString(end)) {
   278  			beginNum := parseNum(fieldKind, begin)
   279  			endNum := parseNum(fieldKind, end)
   280  
   281  			return &RangeEntity{beginAli: beginAli, begin: beginNum, end: endNum, endAli: endAli, dateFlag: true}
   282  		} else if (begin != "" && timePlusRegex.MatchString(begin)) || (end != "" && timePlusRegex.MatchString(end)) {
   283  			// 解析动态时间
   284  			dynamicBegin := parseDynamicTime(begin)
   285  			dynamicEnd := parseDynamicTime(end)
   286  			if dynamicBegin == time.EmptyTime && dynamicEnd == time.EmptyTime {
   287  				return nil
   288  			}
   289  
   290  			if dynamicBegin == time.EmptyTime {
   291  				return &RangeEntity{beginAli: beginAli, begin: nil, end: dynamicEnd.UnixNano(), endAli: endAli, dateFlag: true}
   292  			} else if dynamicEnd == time.EmptyTime {
   293  				return &RangeEntity{beginAli: beginAli, begin: dynamicBegin.UnixNano(), end: nil, endAli: endAli, dateFlag: true}
   294  			} else {
   295  				return &RangeEntity{beginAli: beginAli, begin: dynamicBegin.UnixNano(), end: dynamicEnd.UnixNano(), endAli: endAli, dateFlag: true}
   296  			}
   297  		} else {
   298  			var beginNow bool
   299  			var endNow bool
   300  			var beginTime t0.Time
   301  			var endTime t0.Time
   302  			if begin == constants.Now {
   303  				beginNow = true
   304  			} else {
   305  				beginTime = time.ParseTime(begin)
   306  			}
   307  
   308  			if end == constants.Now {
   309  				endNow = true
   310  			} else {
   311  				endTime = time.ParseTime(end)
   312  			}
   313  
   314  			beginTimeIsEmpty := time.IsTimeEmpty(beginTime)
   315  			endTimeIsEmpty := time.IsTimeEmpty(endTime)
   316  
   317  			if !beginTimeIsEmpty && !endTimeIsEmpty {
   318  				if beginTime.After(endTime) {
   319  					logger.Error("时间的范围起始点不正确,起点时间不应该大于终点时间")
   320  					return nil
   321  				}
   322  				return &RangeEntity{beginAli: beginAli, begin: beginTime.UnixNano(), end: endTime.UnixNano(), endAli: endAli, dateFlag: true, beginNow: beginNow, endNow: endNow}
   323  			} else if beginTimeIsEmpty && endTimeIsEmpty {
   324  				logger.Error("range 匹配器格式输入错误,解析数字或者日期失败, time: %v", subData)
   325  			} else {
   326  				if !beginTimeIsEmpty {
   327  					return &RangeEntity{beginAli: beginAli, begin: beginTime.UnixNano(), end: nil, endAli: endAli, dateFlag: true, beginNow: beginNow, endNow: endNow}
   328  				} else if !endTimeIsEmpty {
   329  					return &RangeEntity{beginAli: beginAli, begin: nil, end: endTime.UnixNano(), endAli: endAli, dateFlag: true, beginNow: beginNow, endNow: endNow}
   330  				} else {
   331  					return nil
   332  				}
   333  			}
   334  		}
   335  	} else {
   336  		// 匹配过去和未来的时间
   337  		if subCondition == constants.Past {
   338  			// 过去,则范围为(null, now)
   339  			return &RangeEntity{beginAli: constants.LeftUnEqual, begin: nil, end: nil, endAli: constants.RightUnEqual, dateFlag: true, endNow: true}
   340  		} else if subCondition == constants.Future {
   341  			// 未来,则范围为(now, null)
   342  			return &RangeEntity{beginAli: constants.LeftUnEqual, begin: nil, end: nil, endAli: constants.RightUnEqual, dateFlag: true, beginNow: true}
   343  		}
   344  		return nil
   345  	}
   346  	return nil
   347  }
   348  
   349  func parseNum(fieldKind reflect.Kind, valueStr string) any {
   350  	if IsCheckNumber(fieldKind) {
   351  		result, err := Cast(fieldKind, valueStr)
   352  		if err != nil {
   353  			return nil
   354  		}
   355  		return result
   356  	} else if fieldKind == reflect.String || fieldKind == reflect.Slice {
   357  		result, err := strconv.Atoi(valueStr)
   358  		if err != nil {
   359  			return nil
   360  		}
   361  		return result
   362  	} else {
   363  		return nil
   364  	}
   365  }
   366  
   367  func parseDynamicTime(valueStr string) t0.Time {
   368  	valueStr = strings.TrimSpace(valueStr)
   369  	if valueStr == "" {
   370  		return time.EmptyTime
   371  	}
   372  	subData := timePlusRegex.FindAllStringSubmatch(valueStr, -1)
   373  	if len(subData) > 0 {
   374  		plusOrMinus := subData[0][1]
   375  		var years, months, days int
   376  		yearStr := subData[0][2]
   377  		monthStr := subData[0][3]
   378  		dayStr := subData[0][4]
   379  		if yearStr != "" {
   380  			yearStr = yearStr[:len(yearStr)-1]
   381  		}
   382  		if monthStr != "" {
   383  			monthStr = monthStr[:len(monthStr)-1]
   384  		}
   385  		if dayStr != "" {
   386  			dayStr = dayStr[:len(dayStr)-1]
   387  		}
   388  		years, _ = strconv.Atoi(fmt.Sprintf("%v%v", plusOrMinus, yearStr))
   389  		months, _ = strconv.Atoi(fmt.Sprintf("%v%v", plusOrMinus, monthStr))
   390  		days, _ = strconv.Atoi(fmt.Sprintf("%v%v", plusOrMinus, dayStr))
   391  
   392  		hours := subData[0][5]
   393  		minutes := subData[0][6]
   394  		seconds := subData[0][7]
   395  
   396  		resultTime := time.AddYears(time.Now(), years)
   397  		resultTime = time.AddMonths(resultTime, months)
   398  		resultTime = time.AddDays(resultTime, days)
   399  		resultTime = time.AddHour(resultTime, plusOrMinus, hours)
   400  		resultTime = time.AddMinutes(resultTime, plusOrMinus, minutes)
   401  		resultTime = time.AddSeconds(resultTime, plusOrMinus, seconds)
   402  
   403  		return resultTime
   404  	}
   405  	return time.EmptyTime
   406  }