mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 16:30:25 +08:00 
			
		
		
		
	
		
			
	
	
		
			190 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			190 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								package api
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								import (
							 | 
						|||
| 
								 | 
							
									"context"
							 | 
						|||
| 
								 | 
							
									"fmt"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/internal/redis/api/form"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/internal/redis/api/vo"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/internal/redis/domain/entity"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/biz"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/ginx"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/req"
							 | 
						|||
| 
								 | 
							
									"strconv"
							 | 
						|||
| 
								 | 
							
									"strings"
							 | 
						|||
| 
								 | 
							
									"sync"
							 | 
						|||
| 
								 | 
							
									"time"
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									"github.com/redis/go-redis/v9"
							 | 
						|||
| 
								 | 
							
								)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								// scan获取redis的key列表信息
							 | 
						|||
| 
								 | 
							
								func (r *Redis) Scan(rc *req.Ctx) {
							 | 
						|||
| 
								 | 
							
									ri := r.getRedisIns(rc)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									form := &form.RedisScanForm{}
							 | 
						|||
| 
								 | 
							
									ginx.BindJsonAndValid(rc.GinCtx, form)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									cmd := ri.GetCmdable()
							 | 
						|||
| 
								 | 
							
									ctx := context.Background()
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									kis := make([]*vo.KeyInfo, 0)
							 | 
						|||
| 
								 | 
							
									var cursorRes map[string]uint64 = make(map[string]uint64)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									mode := ri.Info.Mode
							 | 
						|||
| 
								 | 
							
									if mode == "" || mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel {
							 | 
						|||
| 
								 | 
							
										redisAddr := ri.Cli.Options().Addr
							 | 
						|||
| 
								 | 
							
										// 汇总所有的查询出来的键值
							 | 
						|||
| 
								 | 
							
										var keys []string
							 | 
						|||
| 
								 | 
							
										// 有通配符或空时使用scan,非模糊匹配直接匹配key
							 | 
						|||
| 
								 | 
							
										if form.Match == "" || strings.ContainsAny(form.Match, "*") {
							 | 
						|||
| 
								 | 
							
											cursorRes[redisAddr] = form.Cursor[redisAddr]
							 | 
						|||
| 
								 | 
							
											for {
							 | 
						|||
| 
								 | 
							
												ks, cursor := ri.Scan(cursorRes[redisAddr], form.Match, form.Count)
							 | 
						|||
| 
								 | 
							
												cursorRes[redisAddr] = cursor
							 | 
						|||
| 
								 | 
							
												if len(ks) > 0 {
							 | 
						|||
| 
								 | 
							
													// 返回了数据则追加总集合中
							 | 
						|||
| 
								 | 
							
													keys = append(keys, ks...)
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
												// 匹配的数量满足用户需求退出
							 | 
						|||
| 
								 | 
							
												if int32(len(keys)) >= int32(form.Count) {
							 | 
						|||
| 
								 | 
							
													break
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
												// 匹配到最后退出
							 | 
						|||
| 
								 | 
							
												if cursor == 0 {
							 | 
						|||
| 
								 | 
							
													break
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
										} else {
							 | 
						|||
| 
								 | 
							
											// 精确匹配
							 | 
						|||
| 
								 | 
							
											keys = append(keys, form.Match)
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										var keyInfoSplit []string
							 | 
						|||
| 
								 | 
							
										if len(keys) > 0 {
							 | 
						|||
| 
								 | 
							
											keyInfosLua := `local result = {}
							 | 
						|||
| 
								 | 
							
															-- KEYS[1]为第1个参数,lua数组下标从1开始
							 | 
						|||
| 
								 | 
							
															for i = 1, #KEYS do
							 | 
						|||
| 
								 | 
							
																local ttl = redis.call('ttl', KEYS[i]);
							 | 
						|||
| 
								 | 
							
																local keyType = redis.call('type', KEYS[i]);
							 | 
						|||
| 
								 | 
							
																table.insert(result, string.format("%d,%s", ttl, keyType['ok']));
							 | 
						|||
| 
								 | 
							
															end;
							 | 
						|||
| 
								 | 
							
															return table.concat(result, ".");`
							 | 
						|||
| 
								 | 
							
											// 通过lua获取 ttl,type.ttl2,type2格式,以便下面切割获取ttl和type。避免多次调用ttl和type函数
							 | 
						|||
| 
								 | 
							
											keyInfos, err := cmd.Eval(ctx, keyInfosLua, keys).Result()
							 | 
						|||
| 
								 | 
							
											biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
							 | 
						|||
| 
								 | 
							
											keyInfoSplit = strings.Split(keyInfos.(string), ".")
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										for i, k := range keys {
							 | 
						|||
| 
								 | 
							
											ttlType := strings.Split(keyInfoSplit[i], ",")
							 | 
						|||
| 
								 | 
							
											ttl, _ := strconv.Atoi(ttlType[0])
							 | 
						|||
| 
								 | 
							
											// 没有存在该key,则跳过
							 | 
						|||
| 
								 | 
							
											if ttl == -2 {
							 | 
						|||
| 
								 | 
							
												continue
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
											ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
							 | 
						|||
| 
								 | 
							
											kis = append(kis, ki)
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									} else if mode == entity.RedisModeCluster {
							 | 
						|||
| 
								 | 
							
										var keys []string
							 | 
						|||
| 
								 | 
							
										// 有通配符或空时使用scan,非模糊匹配直接匹配key
							 | 
						|||
| 
								 | 
							
										if form.Match == "" || strings.ContainsAny(form.Match, "*") {
							 | 
						|||
| 
								 | 
							
											mu := &sync.Mutex{}
							 | 
						|||
| 
								 | 
							
											// 遍历所有master节点,并执行scan命令,合并keys
							 | 
						|||
| 
								 | 
							
											ri.ClusterCli.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
							 | 
						|||
| 
								 | 
							
												redisAddr := client.Options().Addr
							 | 
						|||
| 
								 | 
							
												nowCursor := form.Cursor[redisAddr]
							 | 
						|||
| 
								 | 
							
												for {
							 | 
						|||
| 
								 | 
							
													ks, cursor, _ := client.Scan(ctx, nowCursor, form.Match, form.Count).Result()
							 | 
						|||
| 
								 | 
							
													// 遍历节点的内部回调函数使用异步调用,如不加锁会导致集合并发错误
							 | 
						|||
| 
								 | 
							
													mu.Lock()
							 | 
						|||
| 
								 | 
							
													cursorRes[redisAddr] = cursor
							 | 
						|||
| 
								 | 
							
													nowCursor = cursor
							 | 
						|||
| 
								 | 
							
													if len(ks) > 0 {
							 | 
						|||
| 
								 | 
							
														// 返回了数据则追加总集合中
							 | 
						|||
| 
								 | 
							
														keys = append(keys, ks...)
							 | 
						|||
| 
								 | 
							
													}
							 | 
						|||
| 
								 | 
							
													mu.Unlock()
							 | 
						|||
| 
								 | 
							
													// 匹配的数量满足用户需求退出
							 | 
						|||
| 
								 | 
							
													if int32(len(keys)) >= int32(form.Count) {
							 | 
						|||
| 
								 | 
							
														break
							 | 
						|||
| 
								 | 
							
													}
							 | 
						|||
| 
								 | 
							
													// 匹配到最后退出
							 | 
						|||
| 
								 | 
							
													if cursor == 0 {
							 | 
						|||
| 
								 | 
							
														break
							 | 
						|||
| 
								 | 
							
													}
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
												return nil
							 | 
						|||
| 
								 | 
							
											})
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										} else {
							 | 
						|||
| 
								 | 
							
											// 精确匹配
							 | 
						|||
| 
								 | 
							
											keys = append(keys, form.Match)
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
										// 因为redis集群模式执行lua脚本key必须位于同一slot中,故单机获取的方式不适合
							 | 
						|||
| 
								 | 
							
										// 使用lua获取key的ttl以及类型,减少网络调用
							 | 
						|||
| 
								 | 
							
										keyInfoLua := `local ttl = redis.call('ttl', KEYS[1]);
							 | 
						|||
| 
								 | 
							
													   local keyType = redis.call('type', KEYS[1]);
							 | 
						|||
| 
								 | 
							
													   return string.format("%d,%s", ttl, keyType['ok'])`
							 | 
						|||
| 
								 | 
							
										for _, k := range keys {
							 | 
						|||
| 
								 | 
							
											keyInfo, err := cmd.Eval(ctx, keyInfoLua, []string{k}).Result()
							 | 
						|||
| 
								 | 
							
											biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
							 | 
						|||
| 
								 | 
							
											ttlType := strings.Split(keyInfo.(string), ",")
							 | 
						|||
| 
								 | 
							
											ttl, _ := strconv.Atoi(ttlType[0])
							 | 
						|||
| 
								 | 
							
											// 没有存在该key,则跳过
							 | 
						|||
| 
								 | 
							
											if ttl == -2 {
							 | 
						|||
| 
								 | 
							
												continue
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
											ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
							 | 
						|||
| 
								 | 
							
											kis = append(kis, ki)
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									size, _ := cmd.DBSize(context.TODO()).Result()
							 | 
						|||
| 
								 | 
							
									rc.ResData = &vo.Keys{Cursor: cursorRes, Keys: kis, DbSize: size}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								func (r *Redis) TtlKey(rc *req.Ctx) {
							 | 
						|||
| 
								 | 
							
									ri, key := r.checkKeyAndGetRedisIns(rc)
							 | 
						|||
| 
								 | 
							
									ttl, err := ri.GetCmdable().TTL(context.Background(), key).Result()
							 | 
						|||
| 
								 | 
							
									biz.ErrIsNil(err, "ttl失败: %s")
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									if ttl == -1 {
							 | 
						|||
| 
								 | 
							
										rc.ResData = -1
							 | 
						|||
| 
								 | 
							
									} else {
							 | 
						|||
| 
								 | 
							
										rc.ResData = ttl.Seconds()
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								func (r *Redis) DeleteKey(rc *req.Ctx) {
							 | 
						|||
| 
								 | 
							
									ri, key := r.checkKeyAndGetRedisIns(rc)
							 | 
						|||
| 
								 | 
							
									rc.ReqParam = fmt.Sprintf("%s -> 删除key: %s", ri.Info.GetLogDesc(), key)
							 | 
						|||
| 
								 | 
							
									ri.GetCmdable().Del(context.Background(), key)
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								func (r *Redis) RenameKey(rc *req.Ctx) {
							 | 
						|||
| 
								 | 
							
									form := &form.Rename{}
							 | 
						|||
| 
								 | 
							
									ginx.BindJsonAndValid(rc.GinCtx, form)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									ri := r.getRedisIns(rc)
							 | 
						|||
| 
								 | 
							
									rc.ReqParam = fmt.Sprintf("%s -> 重命名key[%s] -> [%s]", ri.Info.GetLogDesc(), form.Key, form.NewKey)
							 | 
						|||
| 
								 | 
							
									ri.GetCmdable().Rename(context.Background(), form.Key, form.NewKey)
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								func (r *Redis) ExpireKey(rc *req.Ctx) {
							 | 
						|||
| 
								 | 
							
									form := &form.Expire{}
							 | 
						|||
| 
								 | 
							
									ginx.BindJsonAndValid(rc.GinCtx, form)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									ri := r.getRedisIns(rc)
							 | 
						|||
| 
								 | 
							
									rc.ReqParam = fmt.Sprintf("%s -> 重置key[%s]过期时间为%d", ri.Info.GetLogDesc(), form.Key, form.Seconds)
							 | 
						|||
| 
								 | 
							
									ri.GetCmdable().Expire(context.Background(), form.Key, time.Duration(form.Seconds)*time.Second)
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								// 移除过期时间
							 | 
						|||
| 
								 | 
							
								func (r *Redis) PersistKey(rc *req.Ctx) {
							 | 
						|||
| 
								 | 
							
									ri, key := r.checkKeyAndGetRedisIns(rc)
							 | 
						|||
| 
								 | 
							
									rc.ReqParam = fmt.Sprintf("%s -> 移除key[%s]的过期时间", ri.Info.GetLogDesc(), key)
							 | 
						|||
| 
								 | 
							
									ri.GetCmdable().Persist(context.Background(), key)
							 | 
						|||
| 
								 | 
							
								}
							 |