package utils import ( "crypto/md5" "encoding/hex" "encoding/json" "errors" "fmt" "math" "math/rand" "reflect" "regexp" "strconv" "strings" "sync" "time" jsoniter "github.com/json-iterator/go" "github.com/gin-gonic/gin" "github.com/xuri/excelize/v2" ) var ( randGen *rand.Rand once sync.Once // 保证全局只初始化一次,并发安全 ) func IsMobile(mobile string) bool { // 手机号验证正则表达式(支持国内手机号) phoneRegex := regexp.MustCompile(`^1[3-9]\d{9}$`) return phoneRegex.MatchString(mobile) } // 初始化随机数生成器(内部方法,外部无需调用) func initRandGenerator() { // 创建随机数源 source := rand.NewSource(time.Now().UnixNano()) // 基于源创建生成器 randGen = rand.New(source) } // GetRandNum 工程化生成四位随机数(0000-9999) func GetRandNum(number int) int { // 首次调用时初始化,后续调用直接复用,无重复初始化开销 once.Do(initRandGenerator) // 生成0-9999随机数,格式化补零为4位 return randGen.Intn(number) } func UniqueId(typeStr string, id int) string { // 格式化补零:%04d 表示数字占4位,不足左侧补0 return fmt.Sprintf("%s%010d%s%s", typeStr, id, time.Now().Format("20060102150405"), fmt.Sprintf("%04d", GetRandNum(10000))) } // GenerateNickname 生成风格1:形容词+名词+随机数字(例:帅气小猫668) func GenerateNickname() string { var ( // 形容词(修饰昵称) adjectives = []string{ "帅气", "可爱", "温柔", "霸气", "呆萌", "高冷", "暖心", "潇洒", "元气", "佛系", "硬核", "软萌", "酷飒", "慵懒", "清爽", } // 名词(核心昵称) nouns = []string{ "小猫", "小狗", "星辰", "大海", "清风", "明月", "云朵", "小鹿", "枫叶", "晚风", "暖阳", "星辰", "鲸鱼", "兔子", "小熊", } ) adj := adjectives[rand.Intn(len(adjectives))] noun := nouns[rand.Intn(len(nouns))] var numStr strings.Builder for i := 0; i < 3; i++ { numStr.WriteByte(byte('0' + rand.Intn(10))) } num := numStr.String() // 2位随机数字 return fmt.Sprintf("%s%s%s", adj, noun, num) } // MD5Encrypt 封装MD5加密函数:输入字符串,返回32位小写MD5哈希值 func MD5Encrypt(str string) string { // 1. 创建MD5哈希对象 hash := md5.New() // 2. 写入需要加密的字节流(MD5处理的是字节,需将字符串转[]byte) hash.Write([]byte(str)) // 3. 计算哈希值(返回[]byte类型的二进制结果) sum := hash.Sum(nil) // 4. 将二进制结果转为16进制字符串(32位小写) return hex.EncodeToString(sum) } func GetEncrypt(key string, nums ...int8) string { original := GetConfigString(key) str := original //TODO 后台切换到新版时开启 //for _, num := range nums { // str = str[num:] + str[:num] //} return str } func InArray[K int | int8 | uint8 | int64 | string, V any](val K, arr map[K]V) bool { _, ok := arr[val] return ok } // ConvertTo 通用类型转换方法 // 入参:val 任意类型的值 // 出参:T 指定类型的结果,error 转换失败原因(成功时为nil) // 支持类型:int/int64/uint/string/float64/bool 所有基础类型双向转换 func ConvertTo[T int | int64 | uint | string | float64 | bool](val interface{}) (T, error) { // 定义返回值的零值,用于转换失败时返回 var zero T // 入参为nil,直接返回错误 if val == nil { return zero, errors.New("convert failed: val is nil") } // 核心:类型断言+匹配,实现任意类型转指定类型T switch t := val.(type) { // 入参类型与返回类型一致,直接返回 case T: return t, nil // 入参为int,转换为目标类型T case int: return convertIntTo[T](t) // 入参为int64,转换为目标类型T case int64: return convertInt64To[T](t) // 入参为string,转换为目标类型T case string: return convertStringTo[T](t) // 入参为float64,转换为目标类型T case float64: return convertFloat64To[T](t) // 入参为bool,转换为目标类型T case bool: return convertBoolTo[T](t) // 入参为uint,转换为目标类型T case uint: return convertUintTo[T](t) // 不支持的入参类型 default: return zero, fmt.Errorf("convert failed: unsupport input type %T, target type %T", val, zero) } } // ------------------------------ 基础类型转换子方法 ------------------------------ // convertIntTo int转指定类型T func convertIntTo[T int | int64 | uint | string | float64 | bool](val int) (T, error) { var zero T switch any(zero).(type) { case int: return any(val).(T), nil case int64: return any(int64(val)).(T), nil case uint: return any(uint(val)).(T), nil case string: return any(strconv.Itoa(val)).(T), nil case float64: return any(float64(val)).(T), nil case bool: return any(val != 0).(T), nil default: return zero, fmt.Errorf("unsupport convert int to %T", zero) } } // convertInt64To int64转指定类型T func convertInt64To[T int | int64 | uint | string | float64 | bool](val int64) (T, error) { var zero T switch any(zero).(type) { case int: return any(int(val)).(T), nil case int64: return any(val).(T), nil case uint: return any(uint(val)).(T), nil case string: return any(strconv.FormatInt(val, 10)).(T), nil case float64: return any(float64(val)).(T), nil case bool: return any(val != 0).(T), nil default: return zero, fmt.Errorf("unsupport convert int64 to %T", zero) } } // convertUintTo uint转指定类型T func convertUintTo[T int | int64 | uint | string | float64 | bool](val uint) (T, error) { var zero T switch any(zero).(type) { case int: return any(int(val)).(T), nil case int64: return any(int64(val)).(T), nil case uint: return any(val).(T), nil case string: return any(strconv.FormatUint(uint64(val), 10)).(T), nil case float64: return any(float64(val)).(T), nil case bool: return any(val != 0).(T), nil default: return zero, fmt.Errorf("unsupport convert uint to %T", zero) } } // convertFloat64To float64转指定类型T(注意:转整型会截断小数) func convertFloat64To[T int | int64 | uint | string | float64 | bool](val float64) (T, error) { var zero T switch any(zero).(type) { case int: return any(int(val)).(T), nil case int64: return any(int64(val)).(T), nil case uint: return any(uint(val)).(T), nil case string: return any(strconv.FormatFloat(val, 'f', -1, 64)).(T), nil case float64: return any(val).(T), nil case bool: return any(val != 0).(T), nil default: return zero, fmt.Errorf("unsupport convert float64 to %T", zero) } } // convertBoolTo bool转指定类型T(true→1/"true",false→0/"false") func convertBoolTo[T int | int64 | uint | string | float64 | bool](val bool) (T, error) { var zero T var intVal int = 0 var strVal string = "false" if val { intVal = 1 strVal = "true" } switch any(zero).(type) { case int: return any(intVal).(T), nil case int64: return any(int64(intVal)).(T), nil case uint: return any(uint(intVal)).(T), nil case string: return any(strVal).(T), nil case float64: return any(float64(intVal)).(T), nil case bool: return any(val).(T), nil default: return zero, fmt.Errorf("unsupport convert bool to %T", zero) } } // convertStringTo string转指定类型T(需保证字符串格式合法,否则转换失败) func convertStringTo[T int | int64 | uint | string | float64 | bool](val string) (T, error) { var zero T switch any(zero).(type) { case int: num, err := strconv.Atoi(val) return any(num).(T), err case int64: num, err := strconv.ParseInt(val, 10, 64) return any(num).(T), err case uint: num, err := strconv.ParseUint(val, 10, 64) return any(uint(num)).(T), err case string: return any(val).(T), nil case float64: num, err := strconv.ParseFloat(val, 64) return any(num).(T), err case bool: b, err := strconv.ParseBool(val) return any(b).(T), err default: return zero, fmt.Errorf("unsupport convert string to %T", zero) } } func StringToFloat64(val string) float64 { n, _ := convertStringTo[float64](strings.TrimSpace(val)) return n } func RoundMoney(amount float64) float64 { return math.Round(amount*100) / 100 } func Decrypt(str string) (string, Error) { str, err := Crypto{}.AESDecryptECB(str) if err != nil { Logger.Error("decrypt str error", str, err) return str, err } // 去掉乱码 str = strings.TrimRight(str, "\x00\x01\x02\x03\x04\x05\x06\x07\x08") return str, nil } func DecryptMobile(mobile string) string { mobile, err := Decrypt(mobile) if err != nil { Logger.Error("decrypt mobile error", mobile, err) return mobile } if len(mobile) < 11 { return mobile } return mobile[:3] + "****" + mobile[7:11] } // 接收 [startDate, endDate](格式 "YYYY-MM-DD"), // 返回 [2006-01-02 00:00:00, 2006-01-02 23:59:59] func DateRangeWithTime(dates []string) [2]string { var result [2]string if len(dates) != 2 { return result } layout := "2006-01-02" startDate, err1 := time.Parse(layout, dates[0]) endDate, err2 := time.Parse(layout, dates[1]) if err1 != nil || err2 != nil { // 出错就原样返回前端输入 result[0] = dates[0] result[1] = dates[1] return result } result[0] = startDate.Format("2006-01-02") + " 00:00:00" result[1] = endDate.Format("2006-01-02") + " 23:59:59" return result } // CalcAge 根据出生日期计算年龄 func CalcAgeStr(birthday string) string { if birthday == "" { return "" } layout := "2006-01-02" birth, err := time.Parse(layout, birthday) if err != nil { return "" } now := time.Now() age := now.Year() - birth.Year() if now.Month() < birth.Month() || (now.Month() == birth.Month() && now.Day() < birth.Day()) { age-- } if age < 0 { return "" } return strconv.Itoa(age) } func MaskData(data string, first, end uint8) string { // 2. 截取前3位 + **** + 后4位 // Go字符串按字节截取,数字字符单字节,直接切片即可 return data[:first] + "****" + data[end:] } func RandString(num uint8) string { chars := "23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ" rand.Seed(time.Now().UnixNano()) res := make([]byte, num) for i := range res { res[i] = chars[rand.Intn(len(chars))] } return string(res) } // ExcelColumn 定义导出的列:表头 + 对应结构体字段 type ExcelColumn struct { Header string // 表头显示名称 Field string // 结构体字段名(必须大写) Width float64 //宽 IsImage bool //是否是图片 } func ExcelExport(c *gin.Context, fileName string, columns []ExcelColumn, dataList interface{}) error { f := excelize.NewFile() sheet := "Sheet1" f.SetSheetName("Sheet1", sheet) // 自动换行样式 wrapStyle, _ := f.NewStyle(&excelize.Style{ Alignment: &excelize.Alignment{ WrapText: true, Vertical: "top", }, }) // 写入表头 for i, col := range columns { colName, _ := excelize.ColumnNumberToName(i + 1) _ = f.SetCellValue(sheet, colName+"1", col.Header) } items := reflect.ValueOf(dataList) if items.Kind() != reflect.Slice { return f.Write(c.Writer) } // 遍历行 for rowIdx := 0; rowIdx < items.Len(); rowIdx++ { rowNum := rowIdx + 2 item := items.Index(rowIdx).Interface() val := reflect.ValueOf(item) if val.Kind() == reflect.Ptr { val = val.Elem() } // 遍历列 for colIdx, col := range columns { fieldVal := getFieldValue(val, col.Field) if !fieldVal.IsValid() { continue } cellName, _ := excelize.ColumnNumberToName(colIdx + 1) cellPos := cellName + strconv.Itoa(rowNum) // ====================================== // 图片URL → 改为【点击查看图片】链接 // ====================================== if col.IsImage { imgURL := toStr(fieldVal.Interface()) if imgURL != "" { // 设置超链接(显示文字:点击查看图片,真正链接:imgURL) _ = f.SetCellHyperLink(sheet, cellPos, imgURL, "External") // 单元格显示文字 _ = f.SetCellValue(sheet, cellPos, "点击查看") } continue } // ====================================== // 文本/数组 自动换行 // ====================================== var cellValue interface{} kind := fieldVal.Kind() if kind == reflect.Slice || kind == reflect.Array { var lines []string for i := 0; i < fieldVal.Len(); i++ { lines = append(lines, toStr(fieldVal.Index(i).Interface())) } cellValue = strings.Join(lines, "\n") } else { cellValue = fieldVal.Interface() } _ = f.SetCellValue(sheet, cellPos, cellValue) _ = f.SetCellStyle(sheet, cellPos, cellPos, wrapStyle) } _ = f.SetRowHeight(sheet, rowNum, 30) } // 设置列宽 for colIdx, col := range columns { if col.Width <= 0 { continue } colName, _ := excelize.ColumnNumberToName(colIdx + 1) _ = f.SetColWidth(sheet, colName, colName, col.Width) } // 下载 c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") c.Header("Content-Disposition", "attachment; filename="+fileName+".xlsx") return f.Write(c.Writer) } // 嵌套字段取值 func getFieldValue(val reflect.Value, field string) reflect.Value { fields := strings.Split(field, ".") current := val for _, f := range fields { if current.Kind() == reflect.Ptr { current = current.Elem() } if !current.IsValid() { return reflect.Value{} } current = current.FieldByName(f) } return current } // 通用转字符串 func toStr(v interface{}) string { if v == nil { return "" } return strings.TrimSpace(fmt.Sprintf("%v", v)) } // Unique 去重函数:传入切片,返回去重后的新切片 func Unique(s []string) []string { tempMap := make(map[string]bool) // 用map记录是否存在 res := []string{} for _, item := range s { if !tempMap[item] { // 不存在才添加 tempMap[item] = true res = append(res, item) } } return res } // MapToStruct 将 map 转换为 struct(支持 map[string]interface{} 转任意 struct) // 参数: // // data: 源 map(key 为字符串,value 为任意类型) // target: 目标 struct 指针(必须传指针,否则无法赋值 func MapToStruct(data map[string]interface{}, target interface{}) error { jsonData, err := json.Marshal(data) // 将 map 转换为 JSON 字节切片 if err != nil { Logger.Error("Error marshalling map to JSON:", err) return err } err = json.Unmarshal(jsonData, &target) // 将 JSON 字节切片解析到结构体中 if err != nil { Logger.Error("Error unmarshalling JSON:", err) return err } return nil } func JsonStringToStruct(jsonStr string, v interface{}) bool { // 1. 校验入参:确保v是指针类型(否则json.Unmarshal无法工作) if v == nil { Logger.Error("传入的结构体指针不能为nil") return false } // 2. 将JSON字符串转为字节数组,执行解析 err := jsoniter.Unmarshal([]byte(jsonStr), v) if err != nil { Logger.Error("JSON解析失败: ", err, jsonStr) return false } return true } func TimeToStartAndEnd(timeString string, day int) (string, string) { if len(timeString) > 0 { tmpStr := strings.Split(timeString, " - ") if len(tmpStr) > 1 { startTime := TimeStart(day, tmpStr[0]) endTime := TimeEnd(day, tmpStr[1]) if len(startTime) > 0 && len(endTime) > 0 { return startTime, endTime } } } return "", "" } func StructToJson(data interface{}) string { bytes, err := jsoniter.Marshal(data) if err != nil { return "" } return string(bytes) } func GetStringByMap(m map[string]interface{}, key string) string { if v, ok := m[key].(string); ok { return v } return "" } func BuildImageURL(data interface{}) interface{} { switch v := data.(type) { case map[string]interface{}: for key, val := range v { v[key] = BuildImageURL(val) } return v case []interface{}: for i, val := range v { v[i] = BuildImageURL(val) } return v case string: if v == "" { return v } base := GetConfigString("upload.image_base_url") return fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(v, "/")) default: return v } } func ParseIntArray(value string) ([]int, error) { var result []int value = strings.TrimSpace(value) if value == "" { return result, nil } err := json.Unmarshal( []byte(value), &result, ) return result, err } func ParseStringArray(value string) ([]string, error) { var result []string value = strings.TrimSpace(value) if value == "" { return result, nil } err := json.Unmarshal( []byte(value), &result, ) return result, err } func UniqueStr(arr []string) []string { m := make(map[string]struct{}) res := make([]string, 0, len(arr)) for _, v := range arr { // 修正这里:用ok接收是否存在 if _, ok := m[v]; !ok { m[v] = struct{}{} res = append(res, v) } } return res }