2025-11-25 11:51:16 +08:00
|
|
|
|
package utils
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-12-02 09:07:21 +08:00
|
|
|
|
"context"
|
2026-01-08 19:09:47 +08:00
|
|
|
|
"encoding/json"
|
2026-03-27 09:49:43 +08:00
|
|
|
|
"errors"
|
2025-11-25 11:51:16 +08:00
|
|
|
|
"fmt"
|
2026-01-04 10:35:15 +08:00
|
|
|
|
"net"
|
2025-12-03 16:39:55 +08:00
|
|
|
|
"reflect"
|
2025-12-04 17:38:34 +08:00
|
|
|
|
"sort"
|
2026-01-04 10:35:15 +08:00
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync/atomic"
|
2025-12-03 16:39:55 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-06-10 15:01:13 +08:00
|
|
|
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
2026-03-27 09:49:43 +08:00
|
|
|
|
"github.com/gogf/gf/v2/container/gvar"
|
2025-12-02 09:07:21 +08:00
|
|
|
|
"github.com/gogf/gf/v2/database/gredis"
|
2025-11-25 11:51:16 +08:00
|
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
2025-12-02 09:07:21 +08:00
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
2026-03-27 09:49:43 +08:00
|
|
|
|
"github.com/gogf/gf/v2/os/glog"
|
2026-03-17 16:09:19 +08:00
|
|
|
|
"github.com/gogf/gf/v2/os/gtime"
|
2025-12-02 09:07:21 +08:00
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
|
|
|
|
"github.com/tiger1103/gfast-token/gftoken"
|
2025-11-25 11:51:16 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
2026-01-04 10:35:15 +08:00
|
|
|
|
|
2026-03-17 16:09:19 +08:00
|
|
|
|
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
|
2026-01-21 16:33:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 16:09:19 +08:00
|
|
|
|
// 2. 从请求头中获取(gateway 转发时设置)
|
|
|
|
|
|
if req := g.RequestFromCtx(ctx); req != nil {
|
|
|
|
|
|
userInfoHeader := req.Header.Get("X-User-Info")
|
|
|
|
|
|
if userInfoHeader != "" {
|
|
|
|
|
|
err = gconv.Struct(userInfoHeader, &user)
|
2026-01-21 16:33:37 +08:00
|
|
|
|
if err != nil {
|
2026-03-17 16:09:19 +08:00
|
|
|
|
return user, gerror.Wrap(err, "请求头用户信息解析失败")
|
2026-01-21 16:33:37 +08:00
|
|
|
|
}
|
2026-03-17 16:09:19 +08:00
|
|
|
|
return
|
2025-12-30 10:52:12 +08:00
|
|
|
|
}
|
2026-03-17 16:09:19 +08:00
|
|
|
|
}
|
2025-12-03 16:39:55 +08:00
|
|
|
|
|
2026-03-17 16:09:19 +08:00
|
|
|
|
// 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 解析失败")
|
2025-12-30 10:52:12 +08:00
|
|
|
|
}
|
2026-03-17 16:09:19 +08:00
|
|
|
|
} else if g.RequestFromCtx(ctx) != nil {
|
|
|
|
|
|
// 解析 token
|
|
|
|
|
|
data, err = gft.ParseToken(g.RequestFromCtx(ctx))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return user, gerror.Wrap(err, "token 解析失败")
|
2025-12-30 10:52:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-21 16:33:37 +08:00
|
|
|
|
|
2026-03-17 16:09:19 +08:00
|
|
|
|
// 检查 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, "用户信息转换失败")
|
2025-12-30 10:52:12 +08:00
|
|
|
|
}
|
2025-12-02 09:07:21 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-21 16:33:37 +08:00
|
|
|
|
|
2026-01-06 17:01:10 +08:00
|
|
|
|
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:
|
2026-01-04 10:35:15 +08:00
|
|
|
|
|
2026-01-06 17:01:10 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-04 17:38:34 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-01-04 10:35:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2026-01-21 16:33:37 +08:00
|
|
|
|
// 检查context是否已取消
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
|
return 0, ""
|
|
|
|
|
|
default:
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-04 10:35:15 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-08 19:09:47 +08:00
|
|
|
|
|
|
|
|
|
|
func Struct(params any, pointer any) error {
|
|
|
|
|
|
b, err := json.Marshal(params)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-01-21 16:33:37 +08:00
|
|
|
|
err = json.Unmarshal(b, pointer)
|
2026-01-08 19:09:47 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2026-02-26 17:47:19 +08:00
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|
2026-03-24 16:17:22 +08:00
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 09:49:43 +08:00
|
|
|
|
// GetBucketName 获取bucket名称
|
2026-03-24 16:17:22 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-03-27 09:49:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|
2026-04-10 15:42:48 +08:00
|
|
|
|
|
2026-04-13 14:50:25 +08:00
|
|
|
|
// IsLocalIP 判断是否是本地IP
|
|
|
|
|
|
func IsLocalIP(ip string) bool {
|
|
|
|
|
|
addrs, err := net.InterfaceAddrs()
|
2026-04-10 15:42:48 +08:00
|
|
|
|
if err != nil {
|
2026-04-13 14:50:25 +08:00
|
|
|
|
return false
|
2026-04-10 15:42:48 +08:00
|
|
|
|
}
|
2026-04-13 14:50:25 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-04-10 15:42:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
2026-06-02 19:52:07 +08:00
|
|
|
|
|
|
|
|
|
|
// GetLocalIP 获取本机有效的局域网 IPv4 地址
|
|
|
|
|
|
func GetLocalIP() string {
|
|
|
|
|
|
addrs, err := net.InterfaceAddrs()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "127.0.0.1"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var validIPs []string
|
|
|
|
|
|
|
|
|
|
|
|
for _, addr := range addrs {
|
|
|
|
|
|
ipnet, ok := addr.(*net.IPNet)
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ip := ipnet.IP
|
|
|
|
|
|
|
|
|
|
|
|
if isIPValid(ip) {
|
|
|
|
|
|
validIPs = append(validIPs, ip.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 优先返回非 169.254.x.x 的 IP
|
|
|
|
|
|
for _, ip := range validIPs {
|
|
|
|
|
|
if !strings.HasPrefix(ip, "169.254.") {
|
|
|
|
|
|
return ip
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 其次返回 169.254.x.x(最后的选择)
|
|
|
|
|
|
if len(validIPs) > 0 {
|
|
|
|
|
|
return validIPs[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return "127.0.0.1"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// isIPValid 判断 IP 是否有效
|
|
|
|
|
|
func isIPValid(ip net.IP) bool {
|
|
|
|
|
|
// 不是 loopback (127.0.0.1)
|
|
|
|
|
|
if ip.IsLoopback() {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 是 IPv4
|
|
|
|
|
|
if ip.To4() == nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不是链路本地地址 (169.254.0.0/16)
|
|
|
|
|
|
if ip[0] == 169 && ip[1] == 254 {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不是组播地址
|
|
|
|
|
|
if ip.IsMulticast() {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不是未指定地址 (0.0.0.0)
|
|
|
|
|
|
if ip.IsUnspecified() {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func GetServerPort(ctx context.Context) string {
|
|
|
|
|
|
address := g.Cfg().MustGet(ctx, "server.address", ":8080").String()
|
|
|
|
|
|
// address 格式如 ":3009",去掉冒号
|
|
|
|
|
|
if strings.HasPrefix(address, ":") {
|
|
|
|
|
|
return address[1:]
|
|
|
|
|
|
}
|
|
|
|
|
|
return "8080"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetLocalAddress 获取局域网地址(IP:端口)
|
|
|
|
|
|
func GetLocalAddress(ctx context.Context) string {
|
|
|
|
|
|
ip := GetLocalIP()
|
|
|
|
|
|
port := GetServerPort(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
if port == "80" || port == "443" {
|
|
|
|
|
|
return ip
|
|
|
|
|
|
}
|
|
|
|
|
|
return ip + ":" + port
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetSchemaFromRequest 从当前请求中获取协议(http/https)
|
|
|
|
|
|
func GetSchemaFromRequest(ctx context.Context) string {
|
|
|
|
|
|
r := g.RequestFromCtx(ctx)
|
|
|
|
|
|
if r == nil {
|
|
|
|
|
|
return "http"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 代理场景:X-Forwarded-Proto
|
|
|
|
|
|
if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
|
|
|
|
|
|
return proto
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 代理场景:X-Forwarded-Scheme
|
|
|
|
|
|
if proto := r.Header.Get("X-Forwarded-Scheme"); proto != "" {
|
|
|
|
|
|
return proto
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. TLS 连接(直接 HTTPS)
|
|
|
|
|
|
if r.TLS != nil {
|
|
|
|
|
|
return "https"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 默认 HTTP(这行很重要!)
|
|
|
|
|
|
return "http" // ← 确保有这行
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetLocalBaseURL 获取局域网基础 URL(动态协议 + IP + 端口)
|
|
|
|
|
|
func GetLocalBaseURL(ctx context.Context) string {
|
|
|
|
|
|
schema := GetSchemaFromRequest(ctx)
|
|
|
|
|
|
addr := GetLocalAddress(ctx)
|
|
|
|
|
|
return schema + "://" + addr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetCallbackURL 获取回调地址(完整 URL)
|
|
|
|
|
|
func GetCallbackURL(ctx context.Context, path string) string {
|
|
|
|
|
|
baseURL := GetLocalBaseURL(ctx)
|
|
|
|
|
|
// 确保 path 以 / 开头
|
|
|
|
|
|
if !strings.HasPrefix(path, "/") {
|
|
|
|
|
|
path = "/" + path
|
|
|
|
|
|
}
|
|
|
|
|
|
return baseURL + path
|
|
|
|
|
|
}
|