feat 初始化
This commit is contained in:
59
utils/replay.go
Normal file
59
utils/replay.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user