Files
lone-services/pkg/utils/tools.go
T
2026-08-31 11:21:04 +08:00

637 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package utils
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"math"
"math/rand"
"reflect"
"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 // 保证全局只初始化一次,并发安全
)
// 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转指定类型Ttrue→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: 源 mapkey 为字符串,value 为任意类型)
// target: 目标 struct 指针(必须传指针,否则无法赋值
func MapToStruct(data map[string]interface{}, target interface{}) error {
jsonData, err := jsoniter.Marshal(data) // 将 map 转换为 JSON 字节切片
if err != nil {
Logger.Error("Error marshalling map to JSON:", err)
return err
}
err = jsoniter.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 := jsoniter.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 := jsoniter.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
}
// Float 泛型约束,只允许 float32 / float64
type Float interface {
~float32 | ~float64
}
// RoundFloat 四舍五入保留n位小数,泛型版本
func RoundFloat[T Float](val T) T {
shift := math.Pow10(NumberTwo)
f := float64(val)
res := math.Round(f*shift) / shift
return T(res)
}