889 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			889 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package helper
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"errors"
 | ||
| 	"fmt"
 | ||
| 	"log"
 | ||
| 	"math"
 | ||
| 	"reflect"
 | ||
| 	"strconv"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/bytedance/sonic"
 | ||
| 	"github.com/go-redis/redis/v8"
 | ||
| 	"github.com/shopspring/decimal"
 | ||
| )
 | ||
| 
 | ||
| // RedisHelper 结构体封装了 Redis 客户端及上下文
 | ||
| type RedisHelper struct {
 | ||
| 	client          *redis.Client   // Redis 客户端
 | ||
| 	ctx             context.Context // 上下文
 | ||
| 	emptyCacheValue string          // 缓存空值的标志
 | ||
| }
 | ||
| 
 | ||
| var DefaultRedis *RedisHelper
 | ||
| 
 | ||
| // 初始化默认链接
 | ||
| func InitDefaultRedis(addr, password string, db int) {
 | ||
| 	if DefaultRedis == nil {
 | ||
| 		DefaultRedis = NewRedisHelper(addr, password, db)
 | ||
| 	}
 | ||
| 
 | ||
| 	log.Printf("初始化redis链接")
 | ||
| }
 | ||
| 
 | ||
| // NewRedisHelper 创建一个新的 RedisHelper 实例
 | ||
| func NewRedisHelper(addr, password string, db int) *RedisHelper {
 | ||
| 	rdb := redis.NewClient(&redis.Options{
 | ||
| 		Addr:         addr,     // Redis 服务器地址
 | ||
| 		Password:     password, // Redis 密码
 | ||
| 		DB:           db,       // 使用的数据库编号
 | ||
| 		PoolSize:     50,
 | ||
| 		MinIdleConns: 10,
 | ||
| 		DialTimeout:  10 * time.Second, // 调整连接超时时间
 | ||
| 		ReadTimeout:  10 * time.Second, // 调整读超时时间
 | ||
| 		WriteTimeout: 10 * time.Second, // 调整写超时时间
 | ||
| 	})
 | ||
| 
 | ||
| 	return &RedisHelper{
 | ||
| 		client: rdb,
 | ||
| 		ctx:    context.Background(), // 创建背景上下文
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 测试连接
 | ||
| func (r *RedisHelper) Ping() error {
 | ||
| 	return r.client.Ping(r.ctx).Err()
 | ||
| }
 | ||
| 
 | ||
| // SetString 设置字符串值
 | ||
| func (r *RedisHelper) SetString(key, value string) error {
 | ||
| 	return r.client.Set(r.ctx, key, value, 0).Err() // 将值存储到指定的键
 | ||
| }
 | ||
| 
 | ||
| // 批量设置
 | ||
| func (r *RedisHelper) BatchSet(maps *map[string]string) error {
 | ||
| 	pipe := r.client.Pipeline()
 | ||
| 
 | ||
| 	for key, val := range *maps {
 | ||
| 		pipe.Set(r.ctx, key, val, 0)
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err := pipe.Exec(r.ctx)
 | ||
| 
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // SetString 设置字符串值
 | ||
| func (r *RedisHelper) SetStringExpire(key, value string, expireTime time.Duration) error {
 | ||
| 	return r.client.Set(r.ctx, key, value, expireTime).Err() // 将值存储到指定的键
 | ||
| }
 | ||
| 
 | ||
| // SetString 设置字符串值
 | ||
| func (r *RedisHelper) SetAdd(key, value string, expireTime time.Duration) error {
 | ||
| 	// 存储到 SET 中
 | ||
| 	result, err := r.client.SAdd(r.ctx, key, value).Result()
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	if result == 1 {
 | ||
| 		// 设置 SET 的过期时间
 | ||
| 		err = r.client.Expire(r.ctx, key, expireTime).Err()
 | ||
| 		if err != nil {
 | ||
| 			return errors.New("设置过期时间失败:" + err.Error())
 | ||
| 		}
 | ||
| 
 | ||
| 		return nil
 | ||
| 	} else {
 | ||
| 		return errors.New("key已存在")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 设置对象
 | ||
| func SetObjString[T any](r *RedisHelper, key string, value T) error {
 | ||
| 	keyValue, err := sonic.Marshal(value)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	return r.SetString(key, string(keyValue))
 | ||
| }
 | ||
| 
 | ||
| // 获取对象
 | ||
| func GetObjString[T any](r *RedisHelper, key string) (T, error) {
 | ||
| 	var result T
 | ||
| 	value, err := r.GetString(key)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return result, err
 | ||
| 	}
 | ||
| 
 | ||
| 	err = sonic.Unmarshal([]byte(value), &result)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return result, err
 | ||
| 	}
 | ||
| 
 | ||
| 	return result, nil
 | ||
| }
 | ||
| 
 | ||
| func (r *RedisHelper) Get(key string) *redis.StringCmd {
 | ||
| 	return r.client.Get(r.ctx, key)
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取剩余时间
 | ||
|   - @key redis key
 | ||
| */
 | ||
| func (r *RedisHelper) TTL(key string) *redis.DurationCmd {
 | ||
| 	return r.client.TTL(r.ctx, key)
 | ||
| }
 | ||
| 
 | ||
| // GetString 获取字符串值
 | ||
| func (r *RedisHelper) GetString(key string) (string, error) {
 | ||
| 	return r.client.Get(r.ctx, key).Result() // 从指定的键获取值
 | ||
| }
 | ||
| 
 | ||
| // DeleteString 删除字符串键
 | ||
| func (r *RedisHelper) DeleteString(key string) error {
 | ||
| 	return r.client.Del(r.ctx, key).Err() // 删除指定的键
 | ||
| }
 | ||
| 
 | ||
| // DeleteString 删除目录下所有key
 | ||
| func (r *RedisHelper) DeleteAll(key string) error {
 | ||
| 	keys, err := r.ScanKeys(key)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err = r.BatchDeleteKeys(keys)
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 递增
 | ||
|   - @key rediskey
 | ||
| */
 | ||
| func (r *RedisHelper) Incr(key string) *redis.IntCmd {
 | ||
| 	return r.client.Incr(r.ctx, key)
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 设置过期时间
 | ||
|   - @key redis key
 | ||
|   - @expiration 过期时间
 | ||
| */
 | ||
| func (r *RedisHelper) Expire(key string, expiration time.Duration) *redis.BoolCmd {
 | ||
| 	return r.client.Expire(r.ctx, key, expiration)
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 批量删除
 | ||
| 
 | ||
|   - @keys 键数组
 | ||
| */
 | ||
| func (r *RedisHelper) BatchDeleteKeys(keys []string) (int, error) {
 | ||
| 	if r.client == nil {
 | ||
| 		return 0, errors.New("Redis client is nil")
 | ||
| 	}
 | ||
| 	if len(keys) == 0 {
 | ||
| 		return 0, nil
 | ||
| 	}
 | ||
| 
 | ||
| 	deletedCount := 0
 | ||
| 	batchSize := 1000 // 每批次删除的键数量
 | ||
| 	for i := 0; i < len(keys); i += batchSize {
 | ||
| 		end := i + batchSize
 | ||
| 		if end > len(keys) {
 | ||
| 			end = len(keys)
 | ||
| 		}
 | ||
| 		batch := keys[i:end]
 | ||
| 
 | ||
| 		_, err := r.client.Pipelined(r.ctx, func(pipe redis.Pipeliner) error {
 | ||
| 			for _, key := range batch {
 | ||
| 				pipe.Del(r.ctx, key)
 | ||
| 				deletedCount++
 | ||
| 			}
 | ||
| 			return nil
 | ||
| 		})
 | ||
| 		if err != nil {
 | ||
| 			return deletedCount, fmt.Errorf("failed to delete keys in batch: %v", err)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return deletedCount, nil
 | ||
| }
 | ||
| 
 | ||
| // DeleteKeysByPrefix 删除指定前缀的键
 | ||
| func (r *RedisHelper) DeleteKeysByPrefix(prefixes ...string) error {
 | ||
| 	ctx := context.Background()
 | ||
| 	// 遍历每个前缀
 | ||
| 	for _, prefix := range prefixes {
 | ||
| 		var cursor uint64
 | ||
| 		var keys []string
 | ||
| 
 | ||
| 		// 使用 SCAN 命令查找匹配的键
 | ||
| 		for {
 | ||
| 			var err error
 | ||
| 			keys, cursor, err = r.client.Scan(ctx, cursor, prefix+"*", 1000).Result()
 | ||
| 			if err != nil {
 | ||
| 				return err
 | ||
| 			}
 | ||
| 
 | ||
| 			// 删除匹配的键
 | ||
| 			if len(keys) > 0 {
 | ||
| 				_, err := r.client.Del(ctx, keys...).Result()
 | ||
| 				if err != nil {
 | ||
| 					return err
 | ||
| 				}
 | ||
| 				fmt.Printf("Deleted keys with prefix '%s': %v\n", prefix, keys)
 | ||
| 			}
 | ||
| 
 | ||
| 			// 如果游标为 0,表示迭代结束
 | ||
| 			if cursor == 0 {
 | ||
| 				break
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 查找所有value
 | ||
| func (r *RedisHelper) GetAllKeysAndValues(pattern string) ([]string, error) {
 | ||
| 	var cursor uint64
 | ||
| 	var result = []string{}
 | ||
| 
 | ||
| 	for {
 | ||
| 		// 使用 SCAN 命令获取匹配的键
 | ||
| 		keys, nextCursor, err := r.client.Scan(r.ctx, cursor, pattern+"*", 1000).Result()
 | ||
| 		if err != nil {
 | ||
| 			log.Printf("Error scanning keys: %v", err)
 | ||
| 			return nil, err
 | ||
| 		}
 | ||
| 
 | ||
| 		// 处理匹配到的键
 | ||
| 		for _, key := range keys {
 | ||
| 			value, err := r.client.Get(r.ctx, key).Result()
 | ||
| 			if err != nil {
 | ||
| 				if err == redis.Nil {
 | ||
| 					fmt.Printf("Key %s does not exist\n", key)
 | ||
| 				} else {
 | ||
| 					fmt.Printf("Error getting value for key %s: %v", key, err)
 | ||
| 				}
 | ||
| 			} else {
 | ||
| 				result = append(result, value)
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		// 如果 cursor 为 0,表示扫描完成
 | ||
| 		if nextCursor == 0 {
 | ||
| 			break
 | ||
| 		}
 | ||
| 		cursor = nextCursor
 | ||
| 	}
 | ||
| 
 | ||
| 	return result, nil
 | ||
| }
 | ||
| 
 | ||
| // LPushList 将一个或多个值插入到列表的头部
 | ||
| func (r *RedisHelper) LPushList(key string, values ...string) error {
 | ||
| 	return r.client.LPush(r.ctx, key, values).Err() // 将值插入到列表的头部
 | ||
| }
 | ||
| 
 | ||
| // RPushList 将一个或多个值插入到列表的尾部
 | ||
| func (r *RedisHelper) RPushList(key string, values ...string) error {
 | ||
| 	return r.client.RPush(r.ctx, key, values).Err() // 将值插入到列表的尾部
 | ||
| }
 | ||
| 
 | ||
| // LPopList 从列表的头部弹出一个元素
 | ||
| func (r *RedisHelper) LPopList(key string) (string, error) {
 | ||
| 	return r.client.LPop(r.ctx, key).Result() // 从列表的头部移除并返回第一个元素
 | ||
| }
 | ||
| 
 | ||
| // RPopList 从列表的尾部弹出一个元素
 | ||
| func (r *RedisHelper) RPopList(key string) (string, error) {
 | ||
| 	return r.client.RPop(r.ctx, key).Result() // 从列表的尾部移除并返回最后一个元素
 | ||
| }
 | ||
| 
 | ||
| // LRangeList 获取列表中指定范围的元素
 | ||
| func (r *RedisHelper) LRangeList(key string, start, stop int64) ([]string, error) {
 | ||
| 	return r.client.LRange(r.ctx, key, start, stop).Result() // 获取列表中指定范围的元素
 | ||
| }
 | ||
| 
 | ||
| // GetAllList 获取列表中的所有元素
 | ||
| func (r *RedisHelper) GetAllList(key string) ([]string, error) {
 | ||
| 	values, err := r.client.LRange(r.ctx, key, 0, -1).Result()
 | ||
| 	if err == redis.Nil {
 | ||
| 		return nil, nil
 | ||
| 	} else if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	// 检查是否包含空值标志
 | ||
| 	if len(values) == 1 && values[0] == r.emptyCacheValue {
 | ||
| 		return nil, nil
 | ||
| 	}
 | ||
| 
 | ||
| 	return values, nil
 | ||
| }
 | ||
| 
 | ||
| func (r *RedisHelper) LRem(key, val string) (int64, error) {
 | ||
| 	count := 0 // 删除所有与 valueToRemove 相等的元素
 | ||
| 	result, err := r.client.LRem(r.ctx, key, int64(count), val).Result()
 | ||
| 	if err != nil {
 | ||
| 		fmt.Printf("删除元素失败: %v\n", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	return result, nil
 | ||
| }
 | ||
| 
 | ||
| func (r *RedisHelper) IsElementInList(key string, element string) (bool, error) {
 | ||
| 	var cursor int64 = 0
 | ||
| 	const batchSize int64 = 1000 // 每批次获取的元素数量
 | ||
| 
 | ||
| 	for {
 | ||
| 		// 分批次获取列表元素
 | ||
| 		elements, err := r.client.LRange(r.ctx, key, cursor, cursor+batchSize-1).Result()
 | ||
| 		if err != nil {
 | ||
| 			return false, err
 | ||
| 		}
 | ||
| 		if len(elements) == 0 {
 | ||
| 			break // 没有更多数据
 | ||
| 		}
 | ||
| 
 | ||
| 		// 遍历当前批次的元素
 | ||
| 		for _, e := range elements {
 | ||
| 			if e == element {
 | ||
| 				return true, nil
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		cursor += batchSize // 移动到下一批次
 | ||
| 	}
 | ||
| 
 | ||
| 	return false, nil
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| SetListCache 重新设置列表缓存
 | ||
|   - @expiration 0-过期 1-过期时间
 | ||
| */
 | ||
| func (r *RedisHelper) SetListCache(key string, expiration time.Duration, values ...string) error {
 | ||
| 	tempKey := key + ":temp"
 | ||
| 
 | ||
| 	// 使用事务来确保操作的原子性
 | ||
| 	pipe := r.client.TxPipeline()
 | ||
| 
 | ||
| 	// 将新数据插入到临时列表中
 | ||
| 	pipe.RPush(r.ctx, tempKey, values)
 | ||
| 
 | ||
| 	// 重命名临时列表为目标列表
 | ||
| 	pipe.Rename(r.ctx, tempKey, key)
 | ||
| 
 | ||
| 	if expiration > 0 {
 | ||
| 		// 设置目标列表的过期时间
 | ||
| 		pipe.Expire(r.ctx, key, expiration)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 执行事务
 | ||
| 	_, err := pipe.Exec(r.ctx)
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // SetEmptyListCache 设置空值缓存
 | ||
| func (r *RedisHelper) SetEmptyListCache(key string, expiration time.Duration) error {
 | ||
| 	// 使用一个特殊标志值表示列表为空
 | ||
| 	_, err := r.client.RPush(r.ctx, key, r.emptyCacheValue).Result()
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	// 设置列表的过期时间
 | ||
| 	return r.client.Expire(r.ctx, key, expiration).Err()
 | ||
| }
 | ||
| 
 | ||
| // scanKeys 使用 SCAN 命令获取所有匹配的键
 | ||
| func (r *RedisHelper) ScanKeys(pattern string) ([]string, error) {
 | ||
| 
 | ||
| 	var cursor uint64
 | ||
| 	var keys []string
 | ||
| 	for {
 | ||
| 		var newKeys []string
 | ||
| 		var err error
 | ||
| 
 | ||
| 		// SCAN 命令每次返回部分匹配的键
 | ||
| 		newKeys, cursor, err = r.client.Scan(r.ctx, cursor, pattern, 1000).Result()
 | ||
| 		if err != nil {
 | ||
| 			return nil, err
 | ||
| 		}
 | ||
| 		keys = append(keys, newKeys...)
 | ||
| 		if cursor == 0 {
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return keys, nil
 | ||
| }
 | ||
| 
 | ||
| // 泛型函数,用于获取所有键的列表数据并合并为一个数组
 | ||
| func GetAndMergeLists[T any](r *RedisHelper, keys []string) ([]T, error) {
 | ||
| 	var combinedList []T
 | ||
| 	for _, key := range keys {
 | ||
| 		// 获取每个键的列表数据
 | ||
| 		listData, err := r.client.LRange(r.ctx, key, 0, -1).Result()
 | ||
| 		if err != nil {
 | ||
| 			return nil, err
 | ||
| 		}
 | ||
| 
 | ||
| 		// 解码每个数据项为类型 T,并添加到结果列表中
 | ||
| 		for _, data := range listData {
 | ||
| 			var item T
 | ||
| 			if err := sonic.Unmarshal([]byte(data), &item); err != nil {
 | ||
| 				return nil, err
 | ||
| 			}
 | ||
| 			combinedList = append(combinedList, item)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return combinedList, nil
 | ||
| }
 | ||
| 
 | ||
| // SetNX 实现类似于 Redis 的 SETNX 命令
 | ||
| func (r *RedisHelper) SetNX(key string, value interface{}, expiration time.Duration) (bool, error) {
 | ||
| 	result, err := r.client.Set(r.ctx, key, value, expiration).Result()
 | ||
| 	if err != nil {
 | ||
| 		return false, err
 | ||
| 	}
 | ||
| 
 | ||
| 	// 如果键不存在则 result 会等于 "OK"
 | ||
| 	return result == "OK", nil
 | ||
| }
 | ||
| 
 | ||
| // SetHashWithTags 改进版:支持 struct 或 map 输入
 | ||
| func (r *RedisHelper) SetHashWithTags(key string, obj interface{}) error {
 | ||
| 	fields, err := getFieldsFromStruct(obj)
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err = r.client.HSet(r.ctx, key, fields).Result()
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // getFieldsFromStruct 改进版:处理指针并进行类型检查,返回 error
 | ||
| func getFieldsFromStruct(obj interface{}) (map[string]interface{}, error) {
 | ||
| 	fields := make(map[string]interface{})
 | ||
| 	val := reflect.ValueOf(obj)
 | ||
| 
 | ||
| 	// 如果 obj 是指针,则解引用
 | ||
| 	if val.Kind() == reflect.Ptr {
 | ||
| 		val = val.Elem()
 | ||
| 	}
 | ||
| 
 | ||
| 	// 确保我们正在处理的是一个结构体
 | ||
| 	if val.Kind() != reflect.Struct {
 | ||
| 		return nil, fmt.Errorf("期望一个结构体或结构体指针,但得到的是 %s", val.Kind())
 | ||
| 	}
 | ||
| 
 | ||
| 	typ := val.Type() // 在解引用后获取类型
 | ||
| 
 | ||
| 	for i := 0; i < val.NumField(); i++ {
 | ||
| 		field := typ.Field(i)
 | ||
| 		tag := field.Tag.Get("redis") // 获取 redis tag
 | ||
| 		if tag != "" {
 | ||
| 			fieldVal := val.Field(i)
 | ||
| 			// 检查字段是否可导出,不可导出的字段无法直接通过 Interface() 访问
 | ||
| 			if !fieldVal.CanInterface() {
 | ||
| 				continue // 跳过不可导出字段
 | ||
| 			}
 | ||
| 
 | ||
| 			switch fieldVal.Kind() {
 | ||
| 			case reflect.Slice, reflect.Map:
 | ||
| 				// 处理切片或映射类型
 | ||
| 				if fieldVal.Kind() == reflect.Slice {
 | ||
| 					for j := 0; j < fieldVal.Len(); j++ {
 | ||
| 						elem := fieldVal.Index(j).Interface()
 | ||
| 						fields[fmt.Sprintf("%s_%d", tag, j)] = elem
 | ||
| 					}
 | ||
| 				} else if fieldVal.Kind() == reflect.Map {
 | ||
| 					for _, key := range fieldVal.MapKeys() {
 | ||
| 						elem := fieldVal.MapIndex(key).Interface()
 | ||
| 						fields[fmt.Sprintf("%s_%v", tag, key.Interface())] = elem
 | ||
| 					}
 | ||
| 				}
 | ||
| 			case reflect.Struct:
 | ||
| 				if fieldVal.Type() == reflect.TypeOf(decimal.Decimal{}) {
 | ||
| 					if decVal, ok := fieldVal.Interface().(decimal.Decimal); ok {
 | ||
| 						// 将 decimal.Decimal 直接转换为字符串来保留精度
 | ||
| 						fields[tag] = decVal.String()
 | ||
| 					} else {
 | ||
| 						// 理论上不应该发生,但作为回退
 | ||
| 						fields[tag] = fieldVal.Interface()
 | ||
| 					}
 | ||
| 				} else {
 | ||
| 					fields[tag] = fieldVal.Interface()
 | ||
| 				}
 | ||
| 			default:
 | ||
| 				fields[tag] = fieldVal.Interface()
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return fields, nil
 | ||
| }
 | ||
| 
 | ||
| // HSetField 设置哈希中的一个字段
 | ||
| func (r *RedisHelper) HSetField(key, field string, value interface{}) error {
 | ||
| 	_, err := r.client.HSet(r.ctx, key, field, value).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error setting field %s in hash %s: %v", field, key, err)
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // HSetMultipleFields 设置哈希中的多个字段
 | ||
| func (r *RedisHelper) HSetMultipleFields(key string, fields map[string]interface{}) error {
 | ||
| 	_, err := r.client.HSet(r.ctx, key, fields).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error setting multiple fields in hash %s: %v", key, err)
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // HGetField 获取哈希中某个字段的值
 | ||
| func (r *RedisHelper) HGetField(key, field string) (string, error) {
 | ||
| 	val, err := r.client.HGet(r.ctx, key, field).Result()
 | ||
| 	if err != nil {
 | ||
| 		if err == redis.Nil {
 | ||
| 			return "", nil // Field does not exist
 | ||
| 		}
 | ||
| 		log.Printf("Error getting field %s from hash %s: %v", field, key, err)
 | ||
| 		return "", err
 | ||
| 	}
 | ||
| 	return val, nil
 | ||
| }
 | ||
| 
 | ||
| // HGetAllFields 获取哈希中所有字段的值
 | ||
| func (r *RedisHelper) HGetAllFields(key string) (map[string]string, error) {
 | ||
| 	fields, err := r.client.HGetAll(r.ctx, key).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error getting all fields from hash %s: %v", key, err)
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 	return fields, nil
 | ||
| }
 | ||
| 
 | ||
| // HGetAsObject 获取哈希中所有字段的值并反序列化为对象
 | ||
| func (r *RedisHelper) HGetAsObject(key string, out interface{}) error {
 | ||
| 	data, err := r.HGetAllFields(key)
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	val := reflect.ValueOf(out)
 | ||
| 	if val.Kind() != reflect.Ptr || val.IsNil() {
 | ||
| 		return fmt.Errorf("output must be a non-nil pointer to a struct")
 | ||
| 	}
 | ||
| 	val = val.Elem()
 | ||
| 	typ := val.Type()
 | ||
| 
 | ||
| 	for i := 0; i < typ.NumField(); i++ {
 | ||
| 		field := typ.Field(i)
 | ||
| 		tag := field.Tag.Get("redis")
 | ||
| 		if tag == "" {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		strVal, ok := data[tag]
 | ||
| 		if !ok {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		fieldVal := val.Field(i)
 | ||
| 		if !fieldVal.CanSet() {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		switch fieldVal.Kind() {
 | ||
| 		case reflect.String:
 | ||
| 			fieldVal.SetString(strVal)
 | ||
| 		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | ||
| 			if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil {
 | ||
| 				fieldVal.SetInt(intVal)
 | ||
| 			}
 | ||
| 		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | ||
| 			if uintVal, err := strconv.ParseUint(strVal, 10, 64); err == nil {
 | ||
| 				fieldVal.SetUint(uintVal)
 | ||
| 			}
 | ||
| 		case reflect.Float32, reflect.Float64:
 | ||
| 			if floatVal, err := strconv.ParseFloat(strVal, 64); err == nil {
 | ||
| 				fieldVal.SetFloat(floatVal)
 | ||
| 			}
 | ||
| 		case reflect.Bool:
 | ||
| 			if boolVal, err := strconv.ParseBool(strVal); err == nil {
 | ||
| 				fieldVal.SetBool(boolVal)
 | ||
| 			}
 | ||
| 		case reflect.Struct:
 | ||
| 			// 针对 time.Time 特别处理
 | ||
| 			if fieldVal.Type() == reflect.TypeOf(time.Time{}) {
 | ||
| 				if t, err := time.Parse(time.RFC3339, strVal); err == nil {
 | ||
| 					fieldVal.Set(reflect.ValueOf(t))
 | ||
| 				}
 | ||
| 			} else if fieldVal.Type() == reflect.TypeOf(decimal.Decimal{}) {
 | ||
| 				if t, err := decimal.NewFromString(strVal); err == nil {
 | ||
| 					fieldVal.Set(reflect.ValueOf(t))
 | ||
| 				}
 | ||
| 			} else {
 | ||
| 				// 其他 struct 尝试用 json 反序列化
 | ||
| 				ptr := reflect.New(fieldVal.Type()).Interface()
 | ||
| 				if err := sonic.Unmarshal([]byte(strVal), ptr); err == nil {
 | ||
| 					fieldVal.Set(reflect.ValueOf(ptr).Elem())
 | ||
| 				}
 | ||
| 			}
 | ||
| 		case reflect.Slice, reflect.Map:
 | ||
| 			ptr := reflect.New(fieldVal.Type()).Interface()
 | ||
| 			if err := sonic.Unmarshal([]byte(strVal), ptr); err == nil {
 | ||
| 				fieldVal.Set(reflect.ValueOf(ptr).Elem())
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // HDelField 删除哈希中的某个字段
 | ||
| func (r *RedisHelper) HDelField(key, field string) error {
 | ||
| 	_, err := r.client.HDel(r.ctx, key, field).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error deleting field %s from hash %s: %v", field, key, err)
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 删除哈希
 | ||
| func (r *RedisHelper) HDelAll(key string) error {
 | ||
| 	_, err := r.client.Del(r.ctx, key).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error deleting from hash %s: %v", key, err)
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // HKeys 获取哈希中所有字段的名字
 | ||
| func (r *RedisHelper) HKeys(key string) ([]string, error) {
 | ||
| 	fields, err := r.client.HKeys(r.ctx, key).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error getting keys from hash %s: %v", key, err)
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 	return fields, nil
 | ||
| }
 | ||
| 
 | ||
| func (r *RedisHelper) HExists(key, field, value string) (bool, error) {
 | ||
| 	exists, err := r.client.HExists(r.ctx, key, field).Result()
 | ||
| 	if err != nil {
 | ||
| 		return false, fmt.Errorf("check existence failed: %v", err)
 | ||
| 	}
 | ||
| 	if !exists {
 | ||
| 		return false, nil
 | ||
| 	}
 | ||
| 
 | ||
| 	storedValue, err := r.client.HGet(r.ctx, key, field).Result()
 | ||
| 	if err != nil {
 | ||
| 		return false, fmt.Errorf("get value failed: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 如果值是 JSON,比较前反序列化
 | ||
| 	var storedObj, inputObj interface{}
 | ||
| 	if err := sonic.UnmarshalString(storedValue, &storedObj); err != nil {
 | ||
| 		return false, fmt.Errorf("unmarshal stored value failed: %v", err)
 | ||
| 	}
 | ||
| 	if err := sonic.UnmarshalString(value, &inputObj); err != nil {
 | ||
| 		return false, fmt.Errorf("unmarshal input value failed: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 比较两个对象(需要根据实际类型调整)
 | ||
| 	return fmt.Sprintf("%v", storedObj) == fmt.Sprintf("%v", inputObj), nil
 | ||
| }
 | ||
| 
 | ||
| // DelSet 从集合中删除元素
 | ||
| func (r *RedisHelper) DelSet(key string, value string) error {
 | ||
| 	_, err := r.client.SRem(r.ctx, key, value).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error del value from set %s: %v", key, err)
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| func (r *RedisHelper) Sismember(key string, value string) (bool, error) {
 | ||
| 	result, err := r.client.SIsMember(r.ctx, key, value).Result()
 | ||
| 	if err != nil {
 | ||
| 		log.Printf("Error Sismember value from set %s: %v", key, err)
 | ||
| 	}
 | ||
| 	return result, err
 | ||
| }
 | ||
| 
 | ||
| // sort set start
 | ||
| // 批量添加
 | ||
| func (r *RedisHelper) BatchSortSet(key string, array []*redis.Z) error {
 | ||
| 	pipe := r.client.Pipeline()
 | ||
| 	for _, val := range array {
 | ||
| 		pipe.ZAdd(r.ctx, key, val)
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err := pipe.Exec(r.ctx)
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // 单一写入 sort set
 | ||
| func (e *RedisHelper) SignelAdd(key string, score float64, member string) error {
 | ||
| 	// 先删除具有相同 score 的所有成员
 | ||
| 	scoreStr := strconv.FormatFloat(score, 'g', -1, 64)
 | ||
| 	_, err := e.client.ZRemRangeByScore(e.ctx, key, scoreStr, scoreStr).Result()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		fmt.Printf("删除score失败,err:%s", err.Error())
 | ||
| 	}
 | ||
| 	_, err = e.client.ZAdd(e.ctx, key, &redis.Z{
 | ||
| 		Score:  score,
 | ||
| 		Member: member,
 | ||
| 	}).Result()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 写入数据
 | ||
| func (e *RedisHelper) AddSortSet(key string, score float64, member string) error {
 | ||
| 	_, err := e.client.ZAdd(e.ctx, key, &redis.Z{
 | ||
| 		Score:  score,
 | ||
| 		Member: member,
 | ||
| 	}).Result()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 删除指定元素
 | ||
| func (e *RedisHelper) DelSortSet(key, member string) error {
 | ||
| 	return e.client.ZRem(e.ctx, key, member).Err()
 | ||
| }
 | ||
| 
 | ||
| // RemoveBeforeScore 移除 Sorted Set 中分数小于等于指定值的数据
 | ||
| // key: Sorted Set 的键
 | ||
| // score: 分数上限,所有小于等于此分数的元素将被移除
 | ||
| // 返回值: 移除的元素数量和可能的错误
 | ||
| func (e *RedisHelper) RemoveBeforeScore(key string, score float64) (int64, error) {
 | ||
| 	if key == "" {
 | ||
| 		return 0, errors.New("key 不能为空")
 | ||
| 	}
 | ||
| 	if math.IsNaN(score) || math.IsInf(score, 0) {
 | ||
| 		return 0, errors.New("score 必须是有效数字")
 | ||
| 	}
 | ||
| 
 | ||
| 	// 使用 ZRemRangeByScore 移除数据
 | ||
| 	count, err := e.client.ZRemRangeByScore(e.ctx, key, "-inf", strconv.FormatFloat(score, 'f', -1, 64)).Result()
 | ||
| 	if err != nil {
 | ||
| 		return 0, fmt.Errorf("移除 Sorted Set 数据失败, key: %s, score: %f, err: %v", key, score, err)
 | ||
| 	}
 | ||
| 
 | ||
| 	return count, nil
 | ||
| }
 | ||
| 
 | ||
| // GetNextAfterScore 获取指定分数及之后的第一条数据(包含指定分数)
 | ||
| func (e *RedisHelper) GetNextAfterScore(key string, score float64) (string, error) {
 | ||
| 	// 使用 ZRangeByScore 获取大于等于 score 的第一条数据
 | ||
| 	zs, err := e.client.ZRangeByScoreWithScores(e.ctx, key, &redis.ZRangeBy{
 | ||
| 		Min:    fmt.Sprintf("%f", score), // 包含指定分数
 | ||
| 		Max:    "+inf",                   // 上限为正无穷
 | ||
| 		Offset: 0,                        // 从第 0 条开始
 | ||
| 		Count:  1,                        // 只取 1 条
 | ||
| 	}).Result()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return "", fmt.Errorf("获取数据失败: %v", err)
 | ||
| 	}
 | ||
| 	if len(zs) == 0 {
 | ||
| 		return "", nil // 没有符合条件的元素
 | ||
| 	}
 | ||
| 	return zs[0].Member.(string), nil
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取sort set 所有数据
 | ||
| */
 | ||
| func (e *RedisHelper) GetAllSortSet(key string) ([]string, error) {
 | ||
| 	return e.client.ZRange(e.ctx, key, 0, -1).Result()
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取sort set 所有数据和score
 | ||
| */
 | ||
| func (e *RedisHelper) GetRevRangeScoresSortSet(key string) ([]redis.Z, error) {
 | ||
| 	return e.client.ZRevRangeWithScores(e.ctx, key, 0, -1).Result()
 | ||
| }
 | ||
| 
 | ||
| // 获取最后一条数据
 | ||
| func (e *RedisHelper) GetLastSortSet(key string) ([]redis.Z, error) {
 | ||
| 	// 获取最后一个元素及其分数
 | ||
| 	results, err := e.client.ZRevRangeWithScores(e.ctx, key, 0, 0).Result()
 | ||
| 	if err != nil {
 | ||
| 		return nil, fmt.Errorf("failed to get last member: %w", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 如果没有数据,返回空
 | ||
| 	if len(results) == 0 {
 | ||
| 		return []redis.Z{}, nil
 | ||
| 	}
 | ||
| 
 | ||
| 	return results, nil
 | ||
| }
 | ||
| 
 | ||
| // 获取指定区间数据
 | ||
| func (e *RedisHelper) GetSortSetMembers(key string, start, stop int64) ([]string, error) {
 | ||
| 	return e.client.ZRange(e.ctx, key, start, stop).Result()
 | ||
| }
 | ||
| 
 | ||
| // 获取最后N条数据
 | ||
| func (e *RedisHelper) GetLastSortSetMembers(key string, num int64) ([]string, error) {
 | ||
| 	return e.client.ZRevRange(e.ctx, key, 0, num).Result()
 | ||
| }
 | ||
| 
 | ||
| // func (e *RedisHelper) DelSortSet(key,)
 | ||
| 
 | ||
| // 根据索引范围删除
 | ||
| func (e *RedisHelper) DelByRank(key string, start, stop int64) error {
 | ||
| 	return e.client.ZRemRangeByRank(e.ctx, key, start, stop).Err()
 | ||
| }
 | ||
| 
 | ||
| // sort set end
 | ||
| 
 | ||
| // GetUserLoginPwdErrFre 获取用户登录密码错误频次
 | ||
| func (e *RedisHelper) GetUserLoginPwdErrFre(key string) (total int, wait time.Duration, err error) {
 | ||
| 	total, _ = e.client.Get(e.ctx, key).Int()
 | ||
| 	wait = e.client.TTL(e.ctx, key).Val()
 | ||
| 	return
 | ||
| }
 | ||
| 
 | ||
| // SetUserLoginPwdErrFre 设置用户登录密码错误频次
 | ||
| func (e *RedisHelper) SetUserLoginPwdErrFre(key string, expire time.Duration) (val int64, err error) {
 | ||
| 	val, err = e.client.Incr(e.ctx, key).Result()
 | ||
| 	if err != nil {
 | ||
| 		return
 | ||
| 	}
 | ||
| 	if err = e.client.Expire(e.ctx, key, expire).Err(); err != nil {
 | ||
| 		return
 | ||
| 	}
 | ||
| 	return
 | ||
| }
 |