package utils import ( "context" "encoding/json" "errors" "fmt" "net" "reflect" "sort" "strconv" "strings" "sync/atomic" "time" "gitea.com/red-future/common/beans" "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/database/gredis" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" "github.com/tiger1103/gfast-token/gftoken" ) // ValidStructPtr 验证是否为结构体指针 func ValidStructPtr(req any) (err error) { //验证请求参数必须为指针 var ( reflectValue reflect.Value reflectKind reflect.Kind ) if v, ok := req.(reflect.Value); ok { reflectValue = v } else { reflectValue = reflect.ValueOf(req) } reflectKind = reflectValue.Kind() if reflectKind != reflect.Ptr { err = gerror.NewCode(gcode.CodeInvalidParameter, `the parameter "req" for function Find should type of *struct/*[]struct`) } return } // GetMonthToday 获取N个月前的某日 func GetMonthToday(t time.Time, month int) time.Time { // today fmt.Printf("today: [%s]\n", t) // 判断天数范围 小于等于28天的计算,覆盖大多数情况 if t.Day() <= 28 { return t.AddDate(0, -month, 0) } // 月份的天数数组 monthDay := [13]int{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} // 计算目标所在日期 target := t.AddDate(0, 0, 1-t.Day()).AddDate(0, -month, 0) // 计算当月最大天数 targetDay := monthDay[target.Month()] // 计算闰年 if target.Month() == time.February && (target.Year()%400 == 0 || (target.Year()%100 != 0 && target.Year()%4 == 0)) { targetDay++ } if t.Day() > targetDay { return target.AddDate(0, 0, targetDay-1) } return target.AddDate(0, 0, t.Day()-1) } func GetUserInfo(ctx context.Context) (user *beans.User, err error) { // 1. 优先从 context 中获取 if !g.IsNil(ctx.Value("user")) { err = gconv.Struct(ctx.Value("user"), &user) if err != nil { return user, gerror.Wrap(err, "用户信息转换失败") } return } // 2. 从请求头中获取(gateway 转发时设置) if req := g.RequestFromCtx(ctx); req != nil { userInfoHeader := req.Header.Get("X-User-Info") if userInfoHeader != "" { err = gconv.Struct(userInfoHeader, &user) if err != nil { return user, gerror.Wrap(err, "请求头用户信息解析失败") } return } } // 3. 从 token 解析 redisAddr := g.Cfg().MustGet(ctx, "redis.default.address").String() gft := gftoken.NewGfToken( gftoken.WithCacheKey("gfToken:"), gftoken.WithTimeout(20), gftoken.WithMaxRefresh(10), gftoken.WithMultiLogin(true), //gftoken.WithExcludePaths(g.SliceStr{"/excludeDemo"}), gftoken.WithGRedisConfig(&gredis.Config{ Address: redisAddr, Db: 1, })) var data *gftoken.CustomClaims if !g.IsNil(ctx.Value("token")) { var tokenData *gftoken.TokenData tokenData, _, err = gft.GetTokenData(ctx, ctx.Value("token").(string)) if err != nil { return user, gerror.Wrap(err, "ctx token 解析失败") } var code int if data, code = gft.IsNotExpired(tokenData.JwtToken); code != gftoken.JwtTokenOK { return user, gerror.New("token jwt 解析失败") } } else if g.RequestFromCtx(ctx) != nil { // 解析 token data, err = gft.ParseToken(g.RequestFromCtx(ctx)) if err != nil { return user, gerror.Wrap(err, "token 解析失败") } } // 检查 data 是否为 nil if data == nil { return user, gerror.New("token 数据为空") } // 检查 data.Data 是否为 nil if data.Data == nil { return user, gerror.New("用户信息为空") } err = gconv.Struct(data.Data, &user) if err != nil { return user, gerror.Wrap(err, "用户信息转换失败") } return } func SetValue(ctx context.Context, result any, key string, value any) { // 检查context是否已取消 select { case <-ctx.Done(): return default: // 使用反射设置result的Total属性 resultValue := reflect.ValueOf(result) if resultValue.Kind() == reflect.Ptr { resultValue = resultValue.Elem() totalField := resultValue.FieldByName(key) if totalField.IsValid() && totalField.CanSet() { totalField.Set(reflect.ValueOf(value)) } } return } } func Struts(ctx context.Context, pointer any, mapping ...map[string]string) { // 检查context是否已取消 select { case <-ctx.Done(): return default: return } } func OrderMap(m map[string]interface{}) map[string]interface{} { // 提取所有key keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } // 使用标准排序算法对key进行排序 // 使用strings.Sort确保排序结果永远一致 sort.Strings(keys) // 创建有序map orderedMap := make(map[string]interface{}, len(m)) for _, k := range keys { orderedMap[k] = m[k] } return orderedMap } // ParseIntSlice 解析整数切片 - 通用字符串处理工具 func ParseIntSlice(str string) []int { parts := strings.Split(str, ",") result := make([]int, 0, len(parts)) for _, part := range parts { if val, err := strconv.Atoi(strings.TrimSpace(part)); err == nil { result = append(result, val) } } return result } // ParseStrings 解析字符串切片 - 通用字符串处理工具 func ParseStrings(str string) []string { if str == "" { return nil } parts := strings.Split(str, ",") result := make([]string, 0, len(parts)) for _, part := range parts { if trimmed := strings.TrimSpace(part); trimmed != "" { result = append(result, trimmed) } } return result } // FilterServiceNames 过滤服务名 - 通用映射处理工具 func FilterServiceNames(services map[string]interface{}, excludeKeys ...string) []string { excludeMap := make(map[string]bool) for _, key := range excludeKeys { excludeMap[key] = true } result := make([]string, 0, len(services)) for key := range services { if !excludeMap[key] { result = append(result, key) } } return result } // FormatUnixTime 格式化Unix时间戳 - 通用时间处理工具 func FormatUnixTime(timestamp int64) string { if timestamp <= 0 { return "" } return time.Unix(timestamp, 0).Format("2006-01-02 15:04:05") } // ParseDurationWithDefault 解析持续时间,失败时使用默认值 - 通用时间处理工具 func ParseDurationWithDefault(ctx context.Context, durationStr, defaultStr, fieldName string) (time.Duration, string) { // 检查context是否已取消 select { case <-ctx.Done(): return 0, "" default: } durationParsed, err := time.ParseDuration(durationStr) if err != nil { // 这里不能直接使用g.Log(),因为这是utils包,没有直接的日志访问 // 调用方应该处理日志 // g.Log().Warningf(ctx, "解析%s失败: %s, 使用默认值 %s, error: %v", fieldName, durationStr, defaultStr, err) durationParsed, _ = time.ParseDuration(defaultStr) return durationParsed, defaultStr } return durationParsed, durationStr } // AtomicUpdateMin 原子更新最小值 - 通用数值处理工具 func AtomicUpdateMin(minValue *atomic.Int64, newValue int64) { for { currentMin := minValue.Load() if newValue >= currentMin { break } if minValue.CompareAndSwap(currentMin, newValue) { break } } } // AtomicUpdateMax 原子更新最大值 - 通用数值处理工具 func AtomicUpdateMax(maxValue *atomic.Int64, newValue int64) { for { currentMax := maxValue.Load() if newValue <= currentMax { break } if maxValue.CompareAndSwap(currentMax, newValue) { break } } } // ParseCIDRs 解析CIDR列表 - 通用网络处理工具 func ParseCIDRs(strs []string) ([]*net.IPNet, error) { nets := make([]*net.IPNet, 0, len(strs)) for _, s := range strs { if s == "*" { if _, ipv4Net, err := net.ParseCIDR("0.0.0.0/0"); err == nil { nets = append(nets, ipv4Net) } if _, ipv6Net, err := net.ParseCIDR("::/0"); err == nil { nets = append(nets, ipv6Net) } continue } if _, ipNet, err := net.ParseCIDR(s); err == nil { nets = append(nets, ipNet) } } return nets, nil } // UrlDecode 简单的URL解码 - 通用编码解码工具 func UrlDecode(s string) string { result := make([]byte, 0, len(s)) for i := 0; i < len(s); i++ { if s[i] == '%' && i+2 < len(s) { if high := HexDigit(s[i+1]); high != 0xFF { if low := HexDigit(s[i+2]); low != 0xFF { result = append(result, (high<<4)|low) i += 2 continue } } } result = append(result, s[i]) } return string(result) } // HexDigit 十六进制字符转数字 - 通用编码解码工具 func HexDigit(c byte) byte { switch { case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 default: return 0xFF } } func Struct(params any, pointer any) error { b, err := json.Marshal(params) if err != nil { return err } err = json.Unmarshal(b, pointer) if err != nil { return err } return nil } // IncrSequence 自增序列号 - 通用序列号工具 func IncrSequence(ctx context.Context, prefix string, incrLen int, seqSep string) (string, error) { // 1. 校验incrLen合法性(至少1位,最多10位,避免位数过大) if incrLen < 1 || incrLen > 8 { g.Log().Warningf(ctx, "自增数位数[%d]不合法,默认使用8位", incrLen) incrLen = 8 // 兜底默认8位 } // 2. 获取当前时间,格式化为"年月日"(如20260226) timeStr := gtime.Now().Format("Ymd") // 3. 拼接Redis的key(seq:前缀:时间字符串),确保每天一个独立的自增序列 redisKey := fmt.Sprintf("%s:%s:%s", "SEQ", prefix, timeStr) // 4. 调用Redis的Incr做自增,失败则生成兜底序列号 seqNum, err := g.Redis().Incr(ctx, redisKey) if err != nil { return "", fmt.Errorf("redis自增失败: %w", err) } // 5. 自动清理过期key,定时任务 // 6. 校验自增数是否超过incrLen位上限,超过则取模(避免格式混乱) maxSeq := intPow10(incrLen) - 1 if seqNum > int64(maxSeq) { return "", fmt.Errorf("自增数[%d]超过%d位上限[%d]", seqNum, incrLen, maxSeq) } // 7. 格式化自增数为指定位数,不足补零 seqFormat := fmt.Sprintf("%%0%dd", incrLen) // 动态生成格式化字符串 seqStr := fmt.Sprintf(seqFormat, seqNum) // 8. 拼接最终序列号 finalSeq := fmt.Sprintf("%s%s%s%s%s", prefix, seqSep, timeStr, seqSep, seqStr) return finalSeq, nil } // intPow10 计算10的n次方(辅助函数,如intPow10(6)=1000000) func intPow10(n int) int { result := 1 for i := 0; i < n; i++ { result *= 10 } return result } // GetFileAddressPrefix 拼接图片前缀地址 func GetFileAddressPrefix(ctx context.Context) (imageUrl string, err error) { // 拼接图片前缀地址 bucketName, err := GetBucketName(ctx) if err != nil { return } imageUrl = fmt.Sprintf("%s/%s", g.Cfg().MustGet(ctx, "filePrefix").String(), bucketName) return } // GetBucketName 获取bucket名称 func GetBucketName(ctx context.Context) (bucketName string, err error) { user, err := GetUserInfo(ctx) if err != nil { return } bucketName = fmt.Sprintf("tenantid-%d", user.TenantId) return } // Lock 分布式锁 func Lock(ctx context.Context, key string, expireSeconds int64, fn func(ctx context.Context) error) (success bool, err error) { limit := 3 LOOP: if limit < 0 { return false, errors.New("锁重试次数耗尽") } limit-- var val *gvar.Var if val, err = g.Redis().Set(ctx, key, true, gredis.SetOption{ TTLOption: gredis.TTLOption{ EX: &expireSeconds, }, NX: true, }); err != nil { return false, err } if val.Bool() { defer func(ctx context.Context, key string) { if _, err = g.Redis().Del(ctx, key); err != nil { glog.Errorf(ctx, "redis client Del error: %v", err) } }(ctx, key) if err = fn(ctx); err != nil { return false, err } return true, nil } time.Sleep(time.Second) goto LOOP } // GetLocalIP 获取本地IP ✅ 阿里云 ECS✅ 腾讯云 CVM✅ 华为云✅ 物理机✅ Docker 容器✅ K8s Pod✅ 虚拟机 func GetLocalIP() (string, error) { // 先获取所有网卡 ifaces, err := net.Interfaces() if err != nil { return "", err } // 遍历网卡,找符合条件的 for _, iface := range ifaces { // 跳过 禁用、回环、虚拟网卡 if iface.Flags&net.FlagUp == 0 || // 网卡未启用 iface.Flags&net.FlagLoopback != 0 || // 回环地址 strings.Contains(iface.Name, "docker") || // docker 网卡 strings.Contains(iface.Name, "veth") || // 容器虚拟网卡 strings.Contains(iface.Name, "bridge") || // 网桥 strings.Contains(iface.Name, "lo") { // 本地回环 continue } // 获取网卡地址 addrs, err := iface.Addrs() if err != nil { continue } for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok || ipNet.IP.IsLoopback() { continue } ip := ipNet.IP if ip.To4() != nil && isPrivateIP(ip) { // 只取内网 IPv4 return ip.String(), nil } } } return "", errors.New("cannot find valid local private IP") } // 判断是否内网IP(生产必须) func isPrivateIP(ip net.IP) bool { privateIPBlocks := []string{ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", } for _, block := range privateIPBlocks { _, ipNet, err := net.ParseCIDR(block) if err == nil && ipNet.Contains(ip) { return true } } return false }