Files
kami_backend/utility/cache/cache.go
danial 15e2426e85 feat(camel_oil): 新增骆驼加油账号管理模块
- 实现账号增删改查接口和逻辑
- 支持账号状态更新及状态历史记录功能
- 提供账号列表、历史和统计信息查询API
- 实现账号轮询机制,支持按使用时间轮询获取账号
- 增加账号登录流程及批量登录功能,集成接码平台和平台API
- 管理账号订单容量,支持容量检查与账号登录触发
- 提供账号池状态统计接口
- 账号历史记录查询支持多种变更类型文本展示
- 密码等敏感信息采用脱敏展示
- 完善日志记录和错误处理机制,保证业务稳定运行
2025-11-21 00:49:50 +08:00

220 lines
6.4 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 cache
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"reflect"
"strings"
"sync"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/glog"
"github.com/duke-git/lancet/v2/slice"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/guid"
"go.opentelemetry.io/otel/trace"
)
var (
cache *Cache
)
type Cache struct {
*gcache.Cache
}
type TraceContext struct {
TraceID string
SpanID string
ParentID string
Attributes map[string]string
}
type PrefixEnum string
const (
PrefixRedeemType PrefixEnum = "redeem_type"
PrefixRedeemWithPaymentType PrefixEnum = "redeem_with_payment_type"
PrefixRedeemAppleAccountLimitedType PrefixEnum = "redeem_apple_account_limited_type"
PrefixAccountLimiterType PrefixEnum = "account_limiter_type"
PrefixJDAccountQueryCache PrefixEnum = "jd_account_query_cache"
PrefixJDAccountQueryBalanceWithCookie PrefixEnum = "jd_account_query_cache_with_cookie"
PrefixWalmartAccountQueryCache PrefixEnum = "walmart_account_query_cache"
PrefixWalmartAccountQueryBalanceWithCookie PrefixEnum = "walmart_account_query_cache_with_cookie"
PrefixAppleMachineAccount PrefixEnum = "MachineCurrentAccountId"
PrefixAppleAccount PrefixEnum = "apple_account"
PrefixAppleDuplicatedOrder PrefixEnum = "apple_duplicated_order"
PrefixJdPaymentCheck PrefixEnum = "jd_payment_check" // 支付状态检查缓存
PrefixJdCardExtract PrefixEnum = "jd_card_extract" // 卡密提取锁定缓存
PrefixTrace PrefixEnum = "trace"
PrefixTracePigAccount PrefixEnum = "trace_pig_account"
)
func (e PrefixEnum) Key(key interface{}) string {
return gconv.String(e) + ":" + gconv.String(key)
}
func NewCache() *Cache {
if cache == nil {
sync.OnceFunc(func() {
cache = &Cache{
Cache: gcache.New(),
}
cache.SetAdapter(gcache.NewAdapterRedis(g.Redis()))
})()
}
return cache
}
// Incr 设置计数器缓存
func (i *Cache) Incr(ctx context.Context, key string, duration time.Duration) (err error) {
result, err := i.Get(ctx, key)
if result == nil || result.IsNil() {
_ = i.Set(ctx, key, 1, duration)
return
}
_, _, err = i.Update(ctx, key, result.Int()+1)
return
}
// Decr 递减缓存
func (i *Cache) Decr(ctx context.Context, key string, duration time.Duration) (err error) {
result, err := i.Get(ctx, key)
if result == nil || result.IsNil() {
_ = i.Set(ctx, key, 0, duration)
return
}
_, _, err = i.Update(ctx, key, result.Int()-1)
return
}
// GetPrefixKeyNum 获取指定以键为开头的键值对个数
func (i *Cache) GetPrefixKeyNum(ctx context.Context, key interface{}) (count int) {
keys, _ := i.Keys(ctx)
count = slice.CountBy(keys, func(index int, item interface{}) bool {
return strings.HasPrefix(item.(string), key.(string))
})
return
}
// GetPrefixKey 获取指定以键为开头的键值对个数
func (i *Cache) GetPrefixKey(ctx context.Context, key interface{}) (keys []interface{}) {
keys, _ = i.Keys(ctx)
keys = slice.Filter(keys, func(index int, item interface{}) bool {
return strings.HasPrefix(item.(string), key.(string))
})
return keys
}
// SaveTrace 存储追踪信息到Redis中
// SaveTraceToRedis 保存追踪信息到Redis
func (i *Cache) SaveTrace(ctx context.Context, key string) error {
spanCtx := trace.SpanContextFromContext(ctx)
if spanCtx.IsValid() {
return i.Set(ctx, PrefixTrace.Key(key), spanCtx, time.Minute*10)
}
return nil
}
// GetTrace 获取追踪信息(恢复追踪能力)
func (i *Cache) GetTrace(ctx context.Context, key string) context.Context {
value, err := i.Get(ctx, PrefixTrace.Key(key))
if err != nil || value == nil || value.IsNil() || reflect.TypeOf(value.Interface()) != reflect.TypeOf(trace.SpanContext{}) {
return ctx
}
spanCtx, ok := value.Interface().(trace.SpanContext)
if !ok {
glog.Error(ctx, "获取追踪信息失败,类型错误")
return ctx
}
return trace.ContextWithRemoteSpanContext(ctx, spanCtx)
}
// GetTraceID 获取追踪的唯一id
func (i *Cache) GetTraceID(ctx context.Context) string {
return gtrace.GetSpanID(ctx) + "-" + guid.S()
}
// DeleteTrace 删除追踪信息
func (i *Cache) DeleteTrace(ctx context.Context, key string) {
_, _ = i.Remove(ctx, PrefixTrace.Key(key))
}
// TryLock 尝试获取分布式锁
// key: 锁的键名
// ttl: 锁的过期时间
// 返回值: 是否获取成功,锁的唯一标识,错误信息
func (i *Cache) TryLock(ctx context.Context, key string, ttl time.Duration) (bool, string, error) {
lockValue := guid.S() // 生成唯一标识
// 使用自定义cache模块的SetIfNotExist方法实现分布式锁
// SetIfNotExist返回true表示设置成功key不存在false表示key已存在
success, err := i.SetIfNotExist(ctx, key, lockValue, ttl)
if err != nil {
return false, "", err // 其他错误
}
if !success {
return false, "", nil // key已存在获取锁失败
}
return true, lockValue, nil
}
// Lock 获取分布式锁(阻塞式)
// key: 锁的键名
// ttl: 锁的过期时间
// timeout: 获取锁的超时时间
// 返回值: 锁的唯一标识,错误信息
func (i *Cache) Lock(ctx context.Context, key string, ttl time.Duration, timeout time.Duration) (string, error) {
startTime := time.Now()
for {
success, lockValue, err := i.TryLock(ctx, key, ttl)
if err != nil {
return "", err
}
if success {
return lockValue, nil
}
// 检查是否超时
if time.Since(startTime) > timeout {
return "", gerror.New("获取分布式锁超时")
}
// 短暂等待后重试
time.Sleep(time.Millisecond * 10)
}
}
// Unlock 释放分布式锁
// key: 锁的键名
// lockValue: 锁的唯一标识(用于确保只有锁的持有者才能释放锁)
func (i *Cache) Unlock(ctx context.Context, key string, lockValue string) error {
// 获取当前锁的值
currentValue, err := i.Get(ctx, key)
if err != nil {
return err
}
if currentValue == nil || currentValue.IsNil() {
// 锁不存在,无需释放
return nil
}
// 检查锁的值是否匹配,确保只有锁的持有者才能释放锁
if currentValue.String() != lockValue {
return gerror.New("无权释放分布式锁,锁的持有者不匹配")
}
// 删除锁
_, err = i.Remove(ctx, key)
return err
}