feat: redis支持zset、redis数据操作界面优化

This commit is contained in:
meilin.huang
2023-04-16 00:50:36 +08:00
parent 1d858118d5
commit af55193591
40 changed files with 2362 additions and 1332 deletions

View File

@@ -14,7 +14,7 @@ func InitCommonRouter(router *gin.RouterGroup) {
// 获取公钥
common.GET("public-key", func(g *gin.Context) {
req.NewCtxWithGin(g).
WithNeedToken(false).
DontNeedToken().
Handle(c.RasPublicKey)
})
}

View File

@@ -13,6 +13,16 @@ type Redis struct {
Remark string `json:"remark"`
}
type Rename struct {
Key string `binding:"required" json:"key"`
NewKey string `binding:"required" json:"newKey"`
}
type Expire struct {
Key string `binding:"required" json:"key"`
Seconds int64 `binding:"required" json:"seconds"`
}
type KeyInfo struct {
Key string `binding:"required" json:"key"`
Timed int64
@@ -50,3 +60,27 @@ type RedisScanForm struct {
Match string `json:"match"`
Count int64 `json:"count"`
}
type ScanForm struct {
Key string `json:"key"`
Cursor uint64 `json:"cursor"`
Match string `json:"match"`
Count int64 `json:"count"`
}
type SmemberOption struct {
Key string `json:"key"`
Member any `json:"member"`
}
type LRemOption struct {
Key string `json:"key"`
Count int64 `json:"count"`
Member any `json:"member"`
}
type ZAddOption struct {
Key string `json:"key"`
Score float64 `json:"score"`
Member any `json:"member"`
}

View File

@@ -0,0 +1,79 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"time"
)
func (r *Redis) Hscan(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
g := rc.GinCtx
count := ginx.QueryInt(g, "count", 10)
match := g.Query("match")
cursor := ginx.QueryInt(g, "cursor", 0)
contextTodo := context.TODO()
cmdable := ri.GetCmdable()
keys, nextCursor, err := cmdable.HScan(contextTodo, key, uint64(cursor), match, int64(count)).Result()
biz.ErrIsNilAppendErr(err, "hcan err: %s")
keySize, err := cmdable.HLen(contextTodo, key).Result()
biz.ErrIsNilAppendErr(err, "hlen err: %s")
rc.ResData = map[string]interface{}{
"keys": keys,
"cursor": nextCursor,
"keySize": keySize,
}
}
func (r *Redis) Hdel(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
field := rc.GinCtx.Query("field")
delRes, err := ri.GetCmdable().HDel(context.TODO(), key, field).Result()
biz.ErrIsNilAppendErr(err, "hdel err: %s")
rc.ResData = delRes
}
func (r *Redis) Hget(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
field := rc.GinCtx.Query("field")
res, err := ri.GetCmdable().HGet(context.TODO(), key, field).Result()
biz.ErrIsNilAppendErr(err, "hget err: %s")
rc.ResData = res
}
func (r *Redis) Hset(rc *req.Ctx) {
g := rc.GinCtx
hashValue := new(form.HashValue)
ginx.BindJsonAndValid(g, hashValue)
hv := hashValue.Value[0]
res, err := r.getRedisIns(rc).GetCmdable().HSet(context.TODO(), hashValue.Key, hv["field"].(string), hv["value"]).Result()
biz.ErrIsNilAppendErr(err, "hset失败: %s")
rc.ResData = res
}
func (r *Redis) SetHashValue(rc *req.Ctx) {
g := rc.GinCtx
hashValue := new(form.HashValue)
ginx.BindJsonAndValid(g, hashValue)
ri := r.getRedisIns(rc)
cmd := ri.GetCmdable()
key := hashValue.Key
contextTodo := context.TODO()
for _, v := range hashValue.Value {
res := cmd.HSet(contextTodo, key, v["field"].(string), v["value"])
biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
}
if hashValue.Timed != 0 && hashValue.Timed != -1 {
cmd.Expire(context.TODO(), key, time.Second*time.Duration(hashValue.Timed))
}
}

View File

@@ -0,0 +1,189 @@
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)
}

View File

@@ -0,0 +1,65 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
)
func (r *Redis) GetListValue(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
ctx := context.TODO()
cmdable := ri.GetCmdable()
len, err := cmdable.LLen(ctx, key).Result()
biz.ErrIsNilAppendErr(err, "获取list长度失败: %s")
g := rc.GinCtx
start := ginx.QueryInt(g, "start", 0)
stop := ginx.QueryInt(g, "stop", 10)
res, err := cmdable.LRange(ctx, key, int64(start), int64(stop)).Result()
biz.ErrIsNilAppendErr(err, "获取list值失败: %s")
rc.ResData = map[string]interface{}{
"len": len,
"list": res,
}
}
func (r *Redis) Lrem(rc *req.Ctx) {
g := rc.GinCtx
option := new(form.LRemOption)
ginx.BindJsonAndValid(g, option)
cmd := r.getRedisIns(rc).GetCmdable()
res, err := cmd.LRem(context.TODO(), option.Key, int64(option.Count), option.Member).Result()
biz.ErrIsNilAppendErr(err, "lrem失败: %s")
rc.ResData = res
}
func (r *Redis) SaveListValue(rc *req.Ctx) {
g := rc.GinCtx
listValue := new(form.ListValue)
ginx.BindJsonAndValid(g, listValue)
cmd := r.getRedisIns(rc).GetCmdable()
key := listValue.Key
ctx := context.TODO()
for _, v := range listValue.Value {
cmd.RPush(ctx, key, v)
}
}
func (r *Redis) SetListValue(rc *req.Ctx) {
g := rc.GinCtx
listSetValue := new(form.ListSetValue)
ginx.BindJsonAndValid(g, listSetValue)
ri := r.getRedisIns(rc)
_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()
biz.ErrIsNilAppendErr(err, "list set失败: %s")
}

View File

@@ -2,7 +2,6 @@ package api
import (
"context"
"fmt"
"mayfly-go/internal/redis/api/form"
"mayfly-go/internal/redis/api/vo"
"mayfly-go/internal/redis/application"
@@ -13,11 +12,9 @@ import (
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"strconv"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
)
@@ -194,314 +191,20 @@ func (r *Redis) ClusterInfo(rc *req.Ctx) {
}
}
// scan获取redis的key列表信息
func (r *Redis) Scan(rc *req.Ctx) {
g := rc.GinCtx
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
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) DeleteKey(rc *req.Ctx) {
g := rc.GinCtx
key := g.Query("key")
// 校验查询参数中的key为必填项并返回redis实例
func (r *Redis) checkKeyAndGetRedisIns(rc *req.Ctx) (*application.RedisInstance, string) {
key := rc.GinCtx.Query("key")
biz.NotEmpty(key, "key不能为空")
return r.getRedisIns(rc), key
}
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
func (r *Redis) getRedisIns(rc *req.Ctx) *application.RedisInstance {
ri := r.RedisApp.GetRedisInstance(getIdAndDbNum(rc.GinCtx))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
rc.ReqParam = fmt.Sprintf("%s -> 删除key: %s", ri.Info.GetLogDesc(), key)
ri.GetCmdable().Del(context.Background(), key)
return ri
}
func (r *Redis) checkKey(rc *req.Ctx) (*application.RedisInstance, string) {
g := rc.GinCtx
key := g.Query("key")
biz.NotEmpty(key, "key不能为空")
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
return ri, key
}
func (r *Redis) GetStringValue(rc *req.Ctx) {
ri, key := r.checkKey(rc)
str, err := ri.GetCmdable().Get(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
rc.ResData = str
}
func (r *Redis) SetStringValue(rc *req.Ctx) {
g := rc.GinCtx
keyValue := new(form.StringValue)
ginx.BindJsonAndValid(g, keyValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
rc.ReqParam = fmt.Sprintf("%s -> %s", ri.Info.GetLogDesc(), utils.ToString(keyValue))
str, err := ri.GetCmdable().Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
rc.ResData = str
}
func (r *Redis) Hscan(rc *req.Ctx) {
ri, key := r.checkKey(rc)
g := rc.GinCtx
count := ginx.QueryInt(g, "count", 10)
match := g.Query("match")
cursor := ginx.QueryInt(g, "cursor", 0)
contextTodo := context.TODO()
cmdable := ri.GetCmdable()
keys, nextCursor, err := cmdable.HScan(contextTodo, key, uint64(cursor), match, int64(count)).Result()
biz.ErrIsNilAppendErr(err, "hcan err: %s")
keySize, err := cmdable.HLen(contextTodo, key).Result()
biz.ErrIsNilAppendErr(err, "hlen err: %s")
rc.ResData = map[string]interface{}{
"keys": keys,
"cursor": nextCursor,
"keySize": keySize,
}
}
func (r *Redis) Hdel(rc *req.Ctx) {
ri, key := r.checkKey(rc)
field := rc.GinCtx.Query("field")
delRes, err := ri.GetCmdable().HDel(context.TODO(), key, field).Result()
biz.ErrIsNilAppendErr(err, "hdel err: %s")
rc.ResData = delRes
}
func (r *Redis) Hget(rc *req.Ctx) {
ri, key := r.checkKey(rc)
field := rc.GinCtx.Query("field")
res, err := ri.GetCmdable().HGet(context.TODO(), key, field).Result()
biz.ErrIsNilAppendErr(err, "hget err: %s")
rc.ResData = res
}
func (r *Redis) SetHashValue(rc *req.Ctx) {
g := rc.GinCtx
hashValue := new(form.HashValue)
ginx.BindJsonAndValid(g, hashValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
cmd := ri.GetCmdable()
key := hashValue.Key
contextTodo := context.TODO()
for _, v := range hashValue.Value {
res := cmd.HSet(contextTodo, key, v["field"].(string), v["value"])
biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
}
if hashValue.Timed != 0 && hashValue.Timed != -1 {
cmd.Expire(context.TODO(), key, time.Second*time.Duration(hashValue.Timed))
}
}
func (r *Redis) GetSetValue(rc *req.Ctx) {
ri, key := r.checkKey(rc)
res, err := ri.GetCmdable().SMembers(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
rc.ResData = res
}
func (r *Redis) SetSetValue(rc *req.Ctx) {
g := rc.GinCtx
keyvalue := new(form.SetValue)
ginx.BindJsonAndValid(g, keyvalue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
cmd := ri.GetCmdable()
key := keyvalue.Key
// 简单处理->先删除,后新增
cmd.Del(context.TODO(), key)
cmd.SAdd(context.TODO(), key, keyvalue.Value...)
if keyvalue.Timed != -1 {
cmd.Expire(context.TODO(), key, time.Second*time.Duration(keyvalue.Timed))
}
}
func (r *Redis) GetListValue(rc *req.Ctx) {
ri, key := r.checkKey(rc)
ctx := context.TODO()
cmdable := ri.GetCmdable()
len, err := cmdable.LLen(ctx, key).Result()
biz.ErrIsNilAppendErr(err, "获取list长度失败: %s")
g := rc.GinCtx
start := ginx.QueryInt(g, "start", 0)
stop := ginx.QueryInt(g, "stop", 10)
res, err := cmdable.LRange(ctx, key, int64(start), int64(stop)).Result()
biz.ErrIsNilAppendErr(err, "获取list值失败: %s")
rc.ResData = map[string]interface{}{
"len": len,
"list": res,
}
}
func (r *Redis) SaveListValue(rc *req.Ctx) {
g := rc.GinCtx
listValue := new(form.ListValue)
ginx.BindJsonAndValid(g, listValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
cmd := ri.GetCmdable()
key := listValue.Key
ctx := context.TODO()
for _, v := range listValue.Value {
cmd.RPush(ctx, key, v)
}
if listValue.Timed != -1 {
cmd.Expire(context.TODO(), key, time.Second*time.Duration(listValue.Timed))
}
}
func (r *Redis) SetListValue(rc *req.Ctx) {
g := rc.GinCtx
listSetValue := new(form.ListSetValue)
ginx.BindJsonAndValid(g, listSetValue)
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()
biz.ErrIsNilAppendErr(err, "list set失败: %s")
// 获取redis id与要操作的库号统一路径
func getIdAndDbNum(g *gin.Context) (uint64, int) {
return uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db")
}

View File

@@ -0,0 +1,78 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"time"
)
func (r *Redis) GetSetValue(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
res, err := ri.GetCmdable().SMembers(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
rc.ResData = res
}
func (r *Redis) SetSetValue(rc *req.Ctx) {
g := rc.GinCtx
keyvalue := new(form.SetValue)
ginx.BindJsonAndValid(g, keyvalue)
cmd := r.getRedisIns(rc).GetCmdable()
key := keyvalue.Key
// 简单处理->先删除,后新增
cmd.Del(context.TODO(), key)
cmd.SAdd(context.TODO(), key, keyvalue.Value...)
if keyvalue.Timed != -1 {
cmd.Expire(context.TODO(), key, time.Second*time.Duration(keyvalue.Timed))
}
}
func (r *Redis) Scard(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
total, err := ri.GetCmdable().SCard(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "scard失败: %s")
rc.ResData = total
}
func (r *Redis) Sscan(rc *req.Ctx) {
g := rc.GinCtx
scan := new(form.ScanForm)
ginx.BindJsonAndValid(g, scan)
cmd := r.getRedisIns(rc).GetCmdable()
keys, cursor, err := cmd.SScan(context.TODO(), scan.Key, scan.Cursor, scan.Match, scan.Count).Result()
biz.ErrIsNilAppendErr(err, "sscan失败: %s")
rc.ResData = map[string]interface{}{
"keys": keys,
"cursor": cursor,
}
}
func (r *Redis) Sadd(rc *req.Ctx) {
g := rc.GinCtx
option := new(form.SmemberOption)
ginx.BindJsonAndValid(g, option)
cmd := r.getRedisIns(rc).GetCmdable()
res, err := cmd.SAdd(context.TODO(), option.Key, option.Member).Result()
biz.ErrIsNilAppendErr(err, "sadd失败: %s")
rc.ResData = res
}
func (r *Redis) Srem(rc *req.Ctx) {
g := rc.GinCtx
option := new(form.SmemberOption)
ginx.BindJsonAndValid(g, option)
cmd := r.getRedisIns(rc).GetCmdable()
res, err := cmd.SRem(context.TODO(), option.Key, option.Member).Result()
biz.ErrIsNilAppendErr(err, "srem失败: %s")
rc.ResData = res
}

View File

@@ -0,0 +1,33 @@
package api
import (
"context"
"fmt"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils"
"time"
)
func (r *Redis) GetStringValue(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
str, err := ri.GetCmdable().Get(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
rc.ResData = str
}
func (r *Redis) SetStringValue(rc *req.Ctx) {
g := rc.GinCtx
keyValue := new(form.StringValue)
ginx.BindJsonAndValid(g, keyValue)
ri := r.getRedisIns(rc)
cmd := ri.GetCmdable()
rc.ReqParam = fmt.Sprintf("%s -> %s", ri.Info.GetLogDesc(), utils.ToString(keyValue))
str, err := cmd.Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
rc.ResData = str
}

View File

@@ -0,0 +1,72 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
"github.com/redis/go-redis/v9"
)
func (r *Redis) ZCard(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisIns(rc)
total, err := ri.GetCmdable().ZCard(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "zcard失败: %s")
rc.ResData = total
}
func (r *Redis) ZScan(rc *req.Ctx) {
g := rc.GinCtx
ri, key := r.checkKeyAndGetRedisIns(rc)
cursor := uint64(ginx.QueryInt(g, "cursor", 0))
match := ginx.Query(g, "match", "*")
count := ginx.QueryInt(g, "count", 50)
keys, cursor, err := ri.GetCmdable().ZScan(context.TODO(), key, cursor, match, int64(count)).Result()
biz.ErrIsNilAppendErr(err, "sscan失败: %s")
rc.ResData = map[string]interface{}{
"keys": keys,
"cursor": cursor,
}
}
func (r *Redis) ZRevRange(rc *req.Ctx) {
g := rc.GinCtx
ri, key := r.checkKeyAndGetRedisIns(rc)
start := ginx.QueryInt(g, "start", 0)
stop := ginx.QueryInt(g, "stop", 50)
res, err := ri.GetCmdable().ZRevRangeWithScores(context.TODO(), key, int64(start), int64(stop)).Result()
biz.ErrIsNilAppendErr(err, "ZRevRange失败: %s")
rc.ResData = res
}
func (r *Redis) ZRem(rc *req.Ctx) {
g := rc.GinCtx
option := new(form.SmemberOption)
ginx.BindJsonAndValid(g, option)
cmd := r.getRedisIns(rc).GetCmdable()
res, err := cmd.ZRem(context.TODO(), option.Key, option.Member).Result()
biz.ErrIsNilAppendErr(err, "zrem失败: %s")
rc.ResData = res
}
func (r *Redis) ZAdd(rc *req.Ctx) {
g := rc.GinCtx
option := new(form.ZAddOption)
ginx.BindJsonAndValid(g, option)
cmd := r.getRedisIns(rc).GetCmdable()
zm := redis.Z{
Score: option.Score,
Member: option.Member,
}
res, err := cmd.ZAdd(context.TODO(), option.Key, zm).Result()
biz.ErrIsNilAppendErr(err, "zadd失败: %s")
rc.ResData = res
}

View File

@@ -49,15 +49,47 @@ func InitRedisRouter(router *gin.RouterGroup) {
req.NewCtxWithGin(c).Handle(rs.Scan)
})
// 删除key
deleteKeyL := req.NewLogInfo("redis-删除key").WithSave(true)
deleteKeyP := req.NewPermission("redis:data:del")
redis.DELETE(":id/:db/key", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(deleteKeyL).WithRequiredPermission(deleteKeyP).Handle(rs.DeleteKey)
redis.GET(":id/:db/key-ttl", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.TtlKey)
})
// 保存数据权限
saveDataP := req.NewPermission("redis:data:save")
// 删除数据权限
deleteDataP := req.NewPermission("redis:data:del")
// 删除key
deleteKeyL := req.NewLogInfo("redis-删除key").WithSave(true)
redis.DELETE(":id/:db/key", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(deleteKeyL).
WithRequiredPermission(deleteDataP).
Handle(rs.DeleteKey)
})
renameKeyL := req.NewLogInfo("redis-重命名key").WithSave(true)
redis.POST(":id/:db/rename-key", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(renameKeyL).
WithRequiredPermission(saveDataP).
Handle(rs.RenameKey)
})
expireKeyL := req.NewLogInfo("redis-设置key过期时间").WithSave(true)
redis.POST(":id/:db/expire-key", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(expireKeyL).
WithRequiredPermission(saveDataP).
Handle(rs.ExpireKey)
})
persistKeyL := req.NewLogInfo("redis-移除key过期时间").WithSave(true)
redis.DELETE(":id/:db/persist-key", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(persistKeyL).
WithRequiredPermission(saveDataP).
Handle(rs.PersistKey)
})
// 获取string类型值
redis.GET(":id/:db/string-value", func(c *gin.Context) {
@@ -67,7 +99,10 @@ func InitRedisRouter(router *gin.RouterGroup) {
// 设置string类型值
setStringL := req.NewLogInfo("redis-setString").WithSave(true)
redis.POST(":id/:db/string-value", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(setStringL).WithRequiredPermission(saveDataP).Handle(rs.SetStringValue)
req.NewCtxWithGin(c).
WithLog(setStringL).
WithRequiredPermission(saveDataP).
Handle(rs.SetStringValue)
})
// hscan
@@ -79,25 +114,60 @@ func InitRedisRouter(router *gin.RouterGroup) {
req.NewCtxWithGin(c).Handle(rs.Hget)
})
hsetL := req.NewLogInfo("redis-hset").WithSave(true)
redis.POST(":id/:db/hset", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithLog(hsetL).
WithRequiredPermission(saveDataP).
Handle(rs.Hset)
})
hdelL := req.NewLogInfo("redis-hdel").WithSave(true)
redis.DELETE(":id/:db/hdel", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(hdelL).WithRequiredPermission(deleteKeyP).Handle(rs.Hdel)
req.NewCtxWithGin(c).
WithLog(hdelL).
WithRequiredPermission(deleteDataP).
Handle(rs.Hdel)
})
// 设置hash类型值
setHashValueL := req.NewLogInfo("redis-setHashValue").WithSave(true)
redis.POST(":id/:db/hash-value", func(c *gin.Context) {
req.NewCtxWithGin(c).WithLog(setHashValueL).WithRequiredPermission(saveDataP).Handle(rs.SetHashValue)
req.NewCtxWithGin(c).
WithLog(setHashValueL).
WithRequiredPermission(saveDataP).
Handle(rs.SetHashValue)
})
// 获取set类型值
// set操作
redis.GET(":id/:db/set-value", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.GetSetValue)
})
// 设置set类型值
redis.POST(":id/:db/set-value", func(c *gin.Context) {
req.NewCtxWithGin(c).WithRequiredPermission(saveDataP).Handle(rs.SetSetValue)
req.NewCtxWithGin(c).
WithRequiredPermission(saveDataP).
Handle(rs.SetSetValue)
})
redis.GET(":id/:db/scard", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.Scard)
})
redis.POST(":id/:db/sscan", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.Sscan)
})
redis.POST(":id/:db/sadd", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithRequiredPermission(saveDataP).
Handle(rs.Sadd)
})
redis.POST(":id/:db/srem", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithRequiredPermission(deleteDataP).
Handle(rs.Srem)
})
// 获取list类型值
@@ -112,5 +182,36 @@ func InitRedisRouter(router *gin.RouterGroup) {
redis.POST(":id/:db/list-value/lset", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.SetListValue)
})
redis.POST(":id/:db/lrem", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithRequiredPermission(deleteDataP).
Handle(rs.Lrem)
})
// zset操作
redis.GET(":id/:db/zcard", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.ZCard)
})
redis.GET(":id/:db/zscan", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.ZScan)
})
redis.GET(":id/:db/zrevrange", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(rs.ZRevRange)
})
redis.POST(":id/:db/zrem", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithRequiredPermission(deleteDataP).
Handle(rs.ZRem)
})
redis.POST(":id/:db/zadd", func(c *gin.Context) {
req.NewCtxWithGin(c).
WithRequiredPermission(saveDataP).
Handle(rs.ZAdd)
})
}
}

View File

@@ -22,7 +22,7 @@ func InitAccountRouter(router *gin.RouterGroup) {
loginLog := req.NewLogInfo("用户登录").WithSave(true)
account.POST("login", func(g *gin.Context) {
req.NewCtxWithGin(g).
WithNeedToken(false).
DontNeedToken().
WithLog(loginLog).
Handle(a.Login)
})
@@ -35,7 +35,7 @@ func InitAccountRouter(router *gin.RouterGroup) {
changePwdLog := req.NewLogInfo("用户修改密码").WithSave(true)
account.POST("change-pwd", func(g *gin.Context) {
req.NewCtxWithGin(g).
WithNeedToken(false).
DontNeedToken().
WithLog(changePwdLog).
Handle(a.ChangePassword)
})

View File

@@ -11,7 +11,7 @@ func InitCaptchaRouter(router *gin.RouterGroup) {
captcha := router.Group("sys/captcha")
{
captcha.GET("", func(c *gin.Context) {
req.NewCtxWithGin(c).WithNeedToken(false).Handle(api.GenerateCaptcha)
req.NewCtxWithGin(c).DontNeedToken().Handle(api.GenerateCaptcha)
})
}
}

View File

@@ -17,7 +17,7 @@ func InitSysConfigRouter(router *gin.RouterGroup) {
})
db.GET("/value", func(c *gin.Context) {
req.NewCtxWithGin(c).WithNeedToken(false).Handle(r.GetConfigValueByKey)
req.NewCtxWithGin(c).DontNeedToken().Handle(r.GetConfigValueByKey)
})
saveConfig := req.NewLogInfo("保存系统配置信息").WithSave(true)