60 lines
1.5 KiB
Go
60 lines
1.5 KiB
Go
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
|
|
}
|