package tools import ( "crypto/md5" "encoding/hex" "errors" "fmt" "math/rand" "regexp" "strconv" "strings" "time" "unicode/utf8" "github.com/google/uuid" "github.com/shopspring/decimal" ) const ( defaultSaltLen = 4 ) func Uuid() string { u7, _ := uuid.NewV7() return strings.ReplaceAll(u7.String(), "-", "") } func SafePStr(p *string) string { if p == nil { return "" } return *p } func SafeI32(p *int32) int32 { if p == nil { return 0 } return *p } func StrFromInt(i int) string { return strconv.Itoa(i) } func StrFromInt32(i int32) string { return strconv.Itoa(int(i)) } func StrFromInt64(i int64) string { return strconv.FormatInt(i, 10) } func StrFromFloat32(f float32) string { return strconv.FormatFloat(float64(f), 'f', -1, 32) } func StrFromFloat64(f float64) string { return strconv.FormatFloat(f, 'f', -1, 64) } func MD5(str string) string { h := md5.New() h.Write([]byte(str)) return hex.EncodeToString(h.Sum(nil)) } func MD5WithSalt(str, salt string) string { return MD5(str + salt) } func RandSalt() string { return RandSaltN(defaultSaltLen) } func RandSaltN(n uint8) string { if n == 0 { return "" } var ans = make([]byte, n) ans[0] = byte(rand.Intn(9)+1) + '0' for i := byte(1); i < n; i++ { ans[i] = byte(rand.Intn(10)) + '0' } return string(ans) } func StrToInt(str string) int { return int(StrToFloat(str)) } func StrToInt64(str string) int64 { return int64(StrToFloat(str)) } func StrToInt32(str string) int32 { return int32(StrToFloat(str)) } func StrToFloat(str string) float64 { if s, err := strconv.ParseFloat(str, 64); err != nil { return 0 } else { return s } } // GetWeekEnd 根据年份和ISO周数结束日期(周日) func GetWeekEnd(year, week int) (endWeek time.Time) { // 1月4日至少是该年的第一周 // 这是计算ISO周的一个参考点 t := time.Date(year, time.January, 4, 0, 0, 0, 0, time.UTC) // 找到这一天所在的周一是哪一天 // ISO周中周一是一周的第一天 weekday := t.Weekday() if weekday == time.Sunday { weekday = 7 // 调整周日为7,以便正确计算偏移量 } // 计算到周一的偏移量 offset := int(time.Monday - weekday) if offset > 0 { offset -= 7 } t = t.AddDate(0, 0, offset) // 计算目标周的起始日期(加上周数-1周) t = t.AddDate(0, 0, (week-1)*7) return t.AddDate(0, 0, 6) } // GetWeekRange 根据年份和ISO周数获取该周的开始日期(周一)和结束日期(周日) func GetWeekRange(year, week int) (start, end string) { // 1月4日至少是该年的第一周 // 这是计算ISO周的一个参考点 t := time.Date(year, time.January, 4, 0, 0, 0, 0, time.UTC) // 找到这一天所在的周一是哪一天 // ISO周中周一是一周的第一天 weekday := t.Weekday() if weekday == time.Sunday { weekday = 7 // 调整周日为7,以便正确计算偏移量 } // 计算到周一的偏移量 offset := int(time.Monday - weekday) if offset > 0 { offset -= 7 } t = t.AddDate(0, 0, offset) // 计算目标周的起始日期(加上周数-1周) t = t.AddDate(0, 0, (week-1)*7) // 开始日期是周一 start = t.Format("2006-01-02") // 结束日期是周日(加6天) end = t.AddDate(0, 0, 6).Format("2006-01-02") return } func DaysBetween(date1, date2 time.Time) int { // 只比较日期部分,忽略时间 y1, m1, d1 := date1.Date() y2, m2, d2 := date2.Date() date1 = time.Date(y1, m1, d1, 0, 0, 0, 0, date1.Location()) date2 = time.Date(y2, m2, d2, 0, 0, 0, 0, date2.Location()) // 计算时间差(纳秒) diff := date1.Sub(date2) if diff < 0 { diff = -diff } // 转换为天数 return int(diff.Hours() / 24) } func StrToDate(str string) time.Time { layout := "2006-01-02" t, _ := time.ParseInLocation(layout, str, time.Local) return t } func StrToDateTime(str string) time.Time { layout := "2006-01-02 15:04:05" t, _ := time.ParseInLocation(layout, str, time.Local) return t } func SecMobile(src string) string { start, end := 3, 7 bytes := []rune(src) for start < end && start < len(bytes) { bytes[start] = '*' start++ } return string(bytes) } const ( lenMin = 4 lenMax = 6 defaultTimeout = time.Minute * 5 ) func RandomCode() string { code, _ := RandomCodeByLen(lenMin) return code } func RandomCodeWithTimeout() (code string, validTo time.Time) { code, _ = RandomCodeByLen(lenMin) validTo = time.Now().Add(defaultTimeout) return } func RandomCodeByLen(len int) (code string, e error) { if len < lenMin || len > lenMax { e = errors.New("只能生成4~6位验证码") return } switch len { case 4: code = fmt.Sprintf("%4d", r.Intn(8999)+1000) case 5: code = fmt.Sprintf("%5d", r.Intn(89999)+10000) case 6: code = fmt.Sprintf("%6d", r.Intn(899999)+100000) } return } func MaskIDCardFlexible(id string) string { length := utf8.RuneCountInString(id) runes := []rune(id) switch length { case 18: // 18位:前6后4 return string(runes[:6]) + "**********" + string(runes[16:]) case 15: // 15位:前6后3(老身份证) return string(runes[:6]) + "******" + string(runes[12:]) case 0: return "" default: return "****" // 或返回原值 / 错误提示 } } // 判断decimal是否是0.5的倍数 func IsHalfMultiple(d decimal.Decimal) bool { // 乘以2后检查是否为整数 multiplied := d.Mul(decimal.NewFromInt(2)) // 将整数部分转换为decimal类型后再比较 intPart := decimal.NewFromInt(multiplied.IntPart()) return multiplied.Equal(intPart) } // IsChineseIDCard 检查输入是否为中国大陆合法的 18 位身份证号码 func IsChineseIDCard(id string) bool { // 1. 检查长度 if utf8.RuneCountInString(id) != 18 { return false } // 2. 使用正则表达式校验格式 // 第1-17位:数字;第18位:数字或X(大小写均可) matched, err := regexp.MatchString(`^\d{17}[\dXx]$`, id) if err != nil || !matched { return false } // 3. 校验最后一位校验码 return validateCheckDigit(id) } // validateCheckDigit 验证身份证最后一位校验码是否正确 func validateCheckDigit(id string) bool { // 权重因子 factor := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2} // 校验码对照表 checkCode := []byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'} sum := 0 for i := 0; i < 17; i++ { if id[i] < '0' || id[i] > '9' { return false } sum += int(id[i]-'0') * factor[i] } // 计算校验码 mod := sum % 11 expected := checkCode[mod] // 比较最后一位(忽略大小写) lastChar := id[17] if lastChar >= 'a' && lastChar <= 'z' { lastChar -= 32 // 转大写 } if lastChar >= 'A' && lastChar <= 'Z' { lastChar += 32 // 转小写再比?不,直接比 'X' } return lastChar == expected || (lastChar == 'x' && expected == 'X') } func IsMultipleOfHalf(d decimal.Decimal) bool { // 乘以 2 times2 := d.Mul(decimal.NewFromInt(2)) // 检查是否为整数(小数位为 0) return times2.Equal(times2.Truncate(0)) } func NumToChinese(n int) string { chineseDigits := []string{"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"} return chineseDigits[n] } // 将切片按每组10个元素拆分 // 参数:原切片、每组元素数量(此处固定为10) // 返回:拆分后的二维切片 func PaginateList[T any](slice []T, groupSize int) [][]T { var groups [][]T length := len(slice) // 循环截取,每次取 groupSize 个元素 for i := 0; i < length; i += groupSize { end := i + groupSize // 处理最后一组可能不足 groupSize 的情况 if end > length { end = length } // 截取当前组并添加到结果中 groups = append(groups, slice[i:end]) } return groups }