Files
common/utils/utils.go

463 lines
12 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 (
"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的keyseq:前缀:时间字符串),确保每天一个独立的自增序列
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
}
// IsLocalIP 判断是否是本地IP
func IsLocalIP(ip string) bool {
addrs, err := net.InterfaceAddrs()
if err != nil {
return false
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
if ipNet.IP.String() == ip {
return true
}
}
}
return false
}