feat 初始化

This commit is contained in:
2026-10-29 10:12:22 +08:00
parent 332d812347
commit 09c1df8a22
81 changed files with 2211 additions and 7821 deletions

6
utils/admin_info.go Normal file
View File

@@ -0,0 +1,6 @@
package utils
type AdminInfo struct {
Usn int64 `redis:"usn"`
RoleCode string `redis:"roleCode"`
}

13
utils/channel.go Normal file
View File

@@ -0,0 +1,13 @@
package utils
import (
"fmt"
"git.hlsq.asia/alex/proto/proto/ss/sspb"
"strings"
)
// GetChannelName 获得频道名称
func GetChannelName(t sspb.ChannelType, sn int64) string {
s := sspb.ChannelType_name[int32(t)]
return fmt.Sprintf("%v-%v", strings.TrimPrefix(s, "CHANNEL_TYPE_"), sn)
}

View File

@@ -9,8 +9,13 @@ import (
)
type WrapErrors struct {
message string
trace []string
original error
message string
trace []string
}
func (e *WrapErrors) Original() error {
return e.original
}
func (e *WrapErrors) Error() string {
@@ -30,7 +35,9 @@ func ErrorsWrap(err error, message ...string) error {
}
var e *WrapErrors
if !errors.As(err, &e) {
e = &WrapErrors{}
e = &WrapErrors{
original: err,
}
}
if len(message) > 0 {
e.message = fmt.Sprintf("%v: %v", message[0], err.Error())
@@ -47,7 +54,9 @@ func ErrorsWrapF(err error, message string, a ...any) error {
}
var e *WrapErrors
if !errors.As(err, &e) {
e = &WrapErrors{}
e = &WrapErrors{
original: err,
}
}
e.message = fmt.Sprintf("%v: %v", fmt.Sprintf(message, a...), err.Error())
e.trace = append(e.trace, getErrorPath())

View File

@@ -37,3 +37,35 @@ func ParseToken(tokenString string, secret string) (*Claims, error) {
}
return claims, nil
}
type PhoneClaims struct {
Phone string `json:"phone"`
jwt.RegisteredClaims
}
func GenPhoneToken(phone string, secret string, expires time.Duration) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, PhoneClaims{
Phone: phone,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expires)),
},
})
return token.SignedString([]byte(secret))
}
func ParsePhoneToken(tokenString string, secret string) (*PhoneClaims, error) {
if tokenString == "" {
return nil, errors.New("invalid token")
}
claims := &PhoneClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(_ *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}

82
utils/locator/locator.go Normal file
View File

@@ -0,0 +1,82 @@
package locator
import (
"context"
"fmt"
"git.hlsq.asia/alex/service-common/db/redis"
"git.hlsq.asia/alex/service-common/log"
)
// 玩家定位器
const (
KeyGatewayInfo = "gateway:info:%v"
HFieldInfoGatewaySID = "gateway_sid_%v"
)
var (
scriptBindGatewaySha1 string
scriptBindGateway = `
local gateway_key = KEYS[1]
local field_key = ARGV[1]
local new_sid = ARGV[2]
local existing_sid = redis.call("HGET", gateway_key, field_key)
if existing_sid then
return 0
end
redis.call("HSET", gateway_key, field_key, new_sid)
return 1
`
)
func NewLocator(ctx context.Context, usn int64, platform int32) *Locator {
return &Locator{
ctx: ctx,
usn: usn,
platform: platform,
}
}
type Locator struct {
ctx context.Context
usn int64
platform int32
}
// BindGateway 绑定网关
func (l *Locator) BindGateway(gatewaySID int64) bool {
if scriptBindGatewaySha1 == "" {
scriptBindGatewaySha1 = redis.GetClient().ScriptLoad(l.ctx, scriptBindGateway).Val()
}
result, err := redis.GetClient().EvalSha(
l.ctx,
scriptBindGatewaySha1,
[]string{fmt.Sprintf(KeyGatewayInfo, l.usn)},
fmt.Sprintf(HFieldInfoGatewaySID, l.platform),
gatewaySID,
).Int64()
if err != nil {
return false
}
if result == 0 {
return false
}
return true
}
// UnbindGateway 解绑网关
func (l *Locator) UnbindGateway() {
err := redis.GetClient().HDel(l.ctx, fmt.Sprintf(KeyGatewayInfo, l.usn), fmt.Sprintf(HFieldInfoGatewaySID, l.platform)).Err()
if err != nil {
log.Errorf("UnbindGateway Failed %v-%v, err: %v", l.usn, l.platform, err)
}
}
// LocateGateway 定位网关
func (l *Locator) LocateGateway() int64 {
sid, _ := redis.GetClient().HGet(l.ctx, fmt.Sprintf(KeyGatewayInfo, l.usn), fmt.Sprintf(HFieldInfoGatewaySID, l.platform)).Int64()
return sid
}

View File

@@ -1,6 +1,7 @@
package utils
import (
"github.com/shopspring/decimal"
"math/rand"
"strconv"
)
@@ -26,3 +27,86 @@ func StringToInt64(s string) int64 {
func Int64ToString(i int64) string {
return strconv.FormatInt(i, 10)
}
// AddFloat64 对浮点数求和
func AddFloat64(places int32, number ...float64) float64 {
if len(number) == 0 {
return 0
}
result := decimal.NewFromFloat(0)
for _, n := range number {
result = result.Add(decimal.NewFromFloat(n))
}
return result.Round(places).InexactFloat64()
}
// SubFloat64 对浮点数求差
func SubFloat64(places int32, number ...float64) float64 {
if len(number) == 0 {
return 0
}
result := decimal.NewFromFloat(number[0])
for i := 1; i < len(number); i++ {
result = result.Sub(decimal.NewFromFloat(number[i]))
}
return result.Round(places).InexactFloat64()
}
// MulFloat64 对浮点数求积
func MulFloat64(places int32, number ...float64) float64 {
if len(number) == 0 {
return 0
}
result := decimal.NewFromFloat(1)
for _, n := range number {
result = result.Mul(decimal.NewFromFloat(n))
}
return result.Round(places).InexactFloat64()
}
// DivFloat64 对浮点数求商
func DivFloat64(places int32, number ...float64) float64 {
if len(number) == 0 {
return 0
}
result := decimal.NewFromFloat(number[0])
for i := 1; i < len(number); i++ {
if number[i] == 0 {
return 0 // 避免除以零
}
result = result.Div(decimal.NewFromFloat(number[i]))
}
return result.Round(places).InexactFloat64()
}
// MaxInt32 返回两个 int32 中的较大值
func MaxInt32(a, b int32) int32 {
if a > b {
return a
}
return b
}
// MinInt32 返回两个 int32 中的较小值
func MinInt32(a, b int32) int32 {
if a < b {
return a
}
return b
}
// MaxFloat64 返回两个 float64 中的较大值
func MaxFloat64(a, b float64) float64 {
if a > b {
return a
}
return b
}
// MinFloat64 返回两个 float64 中的较小值
func MinFloat64(a, b float64) float64 {
if a < b {
return a
}
return b
}

62
utils/page.go Normal file
View File

@@ -0,0 +1,62 @@
package utils
import (
"math"
"git.hlsq.asia/alex/proto/proto/rs/rscommon"
"gorm.io/gen/field"
)
func PageGetOffSet(page *rscommon.PageArgs) int {
if page == nil || page.PageSize <= 0 {
return 0
}
return int((page.PageIndex - 1) * page.PageSize)
}
func CalcTotalPage(totalCount, pageSize int32) int32 {
if pageSize <= 0 {
return 0
}
return int32(math.Ceil(float64(totalCount) / float64(pageSize)))
}
func BuildPageInfoReply(page *rscommon.PageArgs, totalCount int32) *rscommon.PageInfoReply {
if page == nil {
page = &rscommon.PageArgs{PageIndex: 1, PageSize: 10}
}
if page.PageSize <= 0 {
page.PageSize = 10
}
if page.PageIndex <= 0 {
page.PageIndex = 1
}
if totalCount <= 0 {
totalCount = 0
return &rscommon.PageInfoReply{
PageIndex: page.PageIndex,
PageSize: page.PageSize,
TotalCount: totalCount,
}
}
return &rscommon.PageInfoReply{
PageIndex: page.PageIndex,
PageSize: page.PageSize,
TotalCount: totalCount,
}
}
func GetOrderInfo(orderList []*rscommon.OrderInfo, getField func(field string) field.OrderExpr) []field.Expr {
orders := make([]field.Expr, 0, len(orderList))
for _, order := range orderList {
expr := getField(order.Field)
if expr != nil {
if order.Asc {
orders = append(orders, expr.Asc())
} else {
orders = append(orders, expr.Desc())
}
}
}
return orders
}

33
utils/pool_object.go Normal file
View File

@@ -0,0 +1,33 @@
package utils
import (
"sync"
)
type ObjectPool[T any] struct {
pool sync.Pool
}
func NewObjectPool[T any]() *ObjectPool[T] {
return &ObjectPool[T]{
pool: sync.Pool{
New: func() any {
return new(T)
},
},
}
}
func (p *ObjectPool[T]) Get() *T {
return p.pool.Get().(*T)
}
func (p *ObjectPool[T]) Put(obj *T) {
*obj = zero[T]()
p.pool.Put(obj)
}
func zero[T any]() T {
var z T
return z
}

View File

@@ -1,6 +1,7 @@
package utils
import (
"git.hlsq.asia/alex/service-common/log"
"sync"
)
@@ -27,6 +28,11 @@ func WorkerPool(tasks []interface{}, taskFunc func(interface{}), maxWorkers ...i
wg.Add(1)
go func() {
defer wg.Done()
defer func() {
if e := recover(); e != nil {
log.Errorf("WorkerPool panic: %v", e)
}
}()
for task := range jobs {
taskFunc(task)
}

19
utils/qr.go Normal file
View File

@@ -0,0 +1,19 @@
package utils
import (
"encoding/base64"
"github.com/skip2/go-qrcode"
)
func GetQRCodeIO(content string, size ...int) string {
s := 260 // 写死尺寸 260*260
if len(size) > 0 {
s = size[0]
}
png, err := qrcode.Encode(content, qrcode.High, s)
if err != nil {
return ""
}
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(png)
}

59
utils/replay.go Normal file
View File

@@ -0,0 +1,59 @@
package utils
import (
"context"
"fmt"
"git.hlsq.asia/alex/service-common/db/redis"
"git.hlsq.asia/alex/service-common/net/http/httpresp"
"google.golang.org/grpc/metadata"
"time"
)
const (
replayTTL = 2 * time.Minute // 防重放缓存有效期
lockTTL = 10 * time.Second // 分布式锁有效期
)
// WrapPreventReplay 防止重放请求
func WrapPreventReplay[T any](ctx context.Context, usn int64, fun func() (T, error)) (T, error) {
var zero T
key := ""
if md, ok := metadata.FromIncomingContext(ctx); ok {
ridArr := md.Get("Request-Id")
if len(ridArr) == 0 || ridArr[0] == "" {
return fun()
}
key = fmt.Sprintf("cache:replay:%v:%v", usn, ridArr[0])
} else {
return fun()
}
// 先查缓存,命中直接返回
if val := getReplayCache[T](ctx, key); val != nil {
return *val, nil
}
// 尝试抢分布式锁,抢到就执行,没抢到报错
lockKey := fmt.Sprintf("%s:lock", key)
locked, err := redis.GetClient().SetNX(ctx, lockKey, "1", lockTTL).Result()
if err == nil && locked {
defer redis.GetClient().Del(ctx, lockKey)
result, err := fun()
// 这里报错了是没有缓存的
if err != nil {
return zero, err
}
redis.GetCacheClient().Set(ctx, key, result, replayTTL)
return result, nil
}
return zero, httpresp.RequestTooFast
}
// getReplayCache 从 Redis 读取重放缓存
func getReplayCache[T any](ctx context.Context, key string) *T {
var dst T
if redis.GetCacheClient().Get(ctx, key, &dst) {
return &dst
}
return nil
}

View File

@@ -3,6 +3,8 @@ package utils
import (
"context"
"google.golang.org/grpc/metadata"
"strconv"
"strings"
)
type UserSession struct {
@@ -17,6 +19,17 @@ func (us *UserSession) GetUsnKey() string {
return "usn"
}
func (us *UserSession) GetAccessTokenKey() string {
return "at"
}
func (us *UserSession) GetRefreshTokenKey() string {
return "rt"
}
// Deprecated: 请使用 ShouldGetUsn 替代,示例:
// usn := utils.ShouldGetUsn(ctx)
// 不需要判断 usn 是否为 0,只有不鉴权接口的 usn 才可能是 0。
func ShouldBindUsn(ctx context.Context, usn *int64) bool {
if md, ok := metadata.FromIncomingContext(ctx); ok {
usnArr := md.Get("X-Usn")
@@ -27,3 +40,30 @@ func ShouldBindUsn(ctx context.Context, usn *int64) bool {
}
return *usn != 0
}
func ShouldGetUsn(ctx context.Context) int64 {
if md, ok := metadata.FromIncomingContext(ctx); ok {
usnArr := md.Get("X-Usn")
if len(usnArr) == 0 || usnArr[0] == "" {
return 0
}
return StringToInt64(usnArr[0])
}
return 0
}
func GetPlatformAndUsn(token string) (int32, int64, bool) {
str := strings.Split(token, ":")
if len(str) != 3 {
return 0, 0, false
}
platform, err := strconv.Atoi(str[0])
if err != nil {
return 0, 0, false
}
usn, err := strconv.Atoi(str[1])
if err != nil {
return 0, 0, false
}
return int32(platform), int64(usn), true
}

View File

@@ -1,14 +1,19 @@
package utils
import (
"github.com/bwmarrin/snowflake"
"github.com/sony/sonyflake/v2"
"log"
)
var instance *snowflake.Node
var instance *sonyflake.Sonyflake
func InitSnowflake(node int64) *snowflake.Node {
n, err := snowflake.NewNode(node)
func InitSnowflake(node int64) *sonyflake.Sonyflake {
n, err := sonyflake.New(sonyflake.Settings{
BitsMachineID: 10,
MachineID: func() (int, error) {
return int(node), nil
},
})
if err != nil {
log.Fatalf("create snowflake's node failed: %v", err)
}
@@ -16,9 +21,10 @@ func InitSnowflake(node int64) *snowflake.Node {
return instance
}
func SnowflakeInstance() *snowflake.Node {
func SnowflakeInt64() int64 {
if instance == nil {
log.Fatal("snowflake's not init")
}
return instance
id, _ := instance.NextID()
return id
}

49
utils/string.go Normal file
View File

@@ -0,0 +1,49 @@
package utils
import (
"crypto/md5"
"encoding/hex"
"math/rand"
)
// GetMD5Encode 获取Md5编码
func GetMD5Encode(data string) string {
h := md5.New()
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
const (
StringChars1 = "0123456789"
StringChars2 = "abcdefghijklmnopqrstuvwxyz"
StringChars3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
StringChars4 = "0123456789abcdefghijklmnopqrstuvwxyz"
StringChars5 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
StringChars6 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
StringChars7 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
// RandomString 获取随机字符串
func RandomString(chars string, length int8) string {
result := make([]byte, length)
for i := int8(0); i < length; i++ {
result[i] = chars[rand.Intn(len(chars))]
if i == 0 && result[i] == '0' {
i--
}
}
return string(result)
}
// GetDisplayWidth 获取字符串长度
func GetDisplayWidth(text string) int {
width := 0
for _, r := range text {
if r <= 0x7F {
width += 1 // ASCII,半角
} else {
width += 2 // CJK、日文假名等,全角
}
}
return width
}

26
utils/time.go Normal file
View File

@@ -0,0 +1,26 @@
package utils
import (
"math"
"time"
)
// CalcDaysDiff 计算两个日期相差多少天
func CalcDaysDiff(one, two time.Time) int32 {
oneZero := time.Date(
one.Year(),
one.Month(),
one.Day(),
0, 0, 0, 0,
time.Local,
)
twoZero := time.Date(
two.Year(),
two.Month(),
two.Day(),
0, 0, 0, 0,
time.Local,
)
// 计算相差天数
return int32(math.Abs(oneZero.Sub(twoZero).Hours() / 24))
}