2021-09-11 14:04:09 +08:00
|
|
|
|
package api
|
2021-07-28 18:03:19 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
2022-07-10 12:14:06 +08:00
|
|
|
|
"context"
|
2025-04-18 22:07:37 +08:00
|
|
|
|
"mayfly-go/internal/pkg/consts"
|
|
|
|
|
|
"mayfly-go/internal/pkg/utils"
|
2022-09-09 18:26:08 +08:00
|
|
|
|
"mayfly-go/internal/redis/api/form"
|
|
|
|
|
|
"mayfly-go/internal/redis/api/vo"
|
|
|
|
|
|
"mayfly-go/internal/redis/application"
|
2024-05-09 21:29:34 +08:00
|
|
|
|
"mayfly-go/internal/redis/application/dto"
|
2022-09-09 18:26:08 +08:00
|
|
|
|
"mayfly-go/internal/redis/domain/entity"
|
2024-12-16 23:29:18 +08:00
|
|
|
|
"mayfly-go/internal/redis/imsg"
|
2023-10-27 17:41:45 +08:00
|
|
|
|
"mayfly-go/internal/redis/rdm"
|
2022-10-26 20:49:29 +08:00
|
|
|
|
tagapp "mayfly-go/internal/tag/application"
|
2024-02-29 22:12:50 +08:00
|
|
|
|
tagentity "mayfly-go/internal/tag/domain/entity"
|
2022-06-02 17:41:11 +08:00
|
|
|
|
"mayfly-go/pkg/biz"
|
2022-10-26 20:49:29 +08:00
|
|
|
|
"mayfly-go/pkg/model"
|
2023-01-14 16:29:52 +08:00
|
|
|
|
"mayfly-go/pkg/req"
|
2023-10-12 12:14:56 +08:00
|
|
|
|
"mayfly-go/pkg/utils/collx"
|
2023-07-21 17:07:04 +08:00
|
|
|
|
"mayfly-go/pkg/utils/stringx"
|
2021-07-28 18:03:19 +08:00
|
|
|
|
"strings"
|
2022-07-10 12:14:06 +08:00
|
|
|
|
|
2024-11-20 22:43:53 +08:00
|
|
|
|
"github.com/may-fly/cast"
|
2023-03-17 09:46:41 +08:00
|
|
|
|
"github.com/redis/go-redis/v9"
|
2021-07-28 18:03:19 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type Redis struct {
|
2024-12-16 23:29:18 +08:00
|
|
|
|
redisApp application.Redis `inject:"T"`
|
|
|
|
|
|
tagApp tagapp.TagTree `inject:"T"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (rs *Redis) ReqConfs() *req.Confs {
|
|
|
|
|
|
reqs := [...]*req.Conf{
|
|
|
|
|
|
// 获取redis list
|
|
|
|
|
|
req.NewGet("", rs.RedisList),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewPost("/test-conn", rs.TestConn),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewPost("", rs.Save).Log(req.NewLogSaveI(imsg.LogRedisSave)),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewDelete(":id", rs.DeleteRedis).Log(req.NewLogSaveI(imsg.LogRedisDelete)),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewGet("/:id/info", rs.RedisInfo),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewGet(":id/cluster-info", rs.ClusterInfo),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewPost(":id/:db/run-cmd", rs.RunCmd).Log(req.NewLogSaveI(imsg.LogRedisRunCmd)),
|
|
|
|
|
|
|
|
|
|
|
|
// 获取指定redis keys
|
|
|
|
|
|
req.NewPost(":id/:db/scan", rs.ScanKeys),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewGet(":id/:db/key-info", rs.KeyInfo),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewGet(":id/:db/key-ttl", rs.TtlKey),
|
|
|
|
|
|
|
|
|
|
|
|
req.NewGet(":id/:db/key-memuse", rs.MemoryUsage),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return req.NewConfs("/redis", reqs[:]...)
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-14 16:29:52 +08:00
|
|
|
|
func (r *Redis) RedisList(rc *req.Ctx) {
|
2025-05-20 21:04:47 +08:00
|
|
|
|
queryCond := req.BindQuery(rc, new(entity.RedisQuery))
|
2022-10-26 20:49:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 不存在可访问标签id,即没有可操作数据
|
2024-12-16 23:29:18 +08:00
|
|
|
|
tags := r.tagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
2024-11-26 17:32:44 +08:00
|
|
|
|
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeRedis)),
|
2024-11-23 17:23:18 +08:00
|
|
|
|
CodePathLikes: collx.AsArray(queryCond.TagPath),
|
|
|
|
|
|
})
|
|
|
|
|
|
if len(tags) == 0 {
|
2025-05-20 21:04:47 +08:00
|
|
|
|
rc.ResData = model.NewEmptyPageResult[any]()
|
2022-10-26 20:49:29 +08:00
|
|
|
|
return
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
2024-11-23 17:23:18 +08:00
|
|
|
|
queryCond.Codes = tags.GetCodes()
|
2023-07-08 20:05:55 +08:00
|
|
|
|
|
2025-05-20 21:04:47 +08:00
|
|
|
|
res, err := r.redisApp.GetPageList(queryCond)
|
2023-10-26 17:15:49 +08:00
|
|
|
|
biz.ErrIsNil(err)
|
2025-05-20 21:04:47 +08:00
|
|
|
|
resVo := model.PageResultConv[*entity.Redis, *vo.Redis](res)
|
|
|
|
|
|
redisvos := resVo.List
|
2024-02-29 22:12:50 +08:00
|
|
|
|
|
|
|
|
|
|
// 填充标签信息
|
2024-12-16 23:29:18 +08:00
|
|
|
|
r.tagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeRedis), collx.ArrayMap(redisvos, func(rvo *vo.Redis) tagentity.ITagResource {
|
2024-02-29 22:12:50 +08:00
|
|
|
|
return rvo
|
|
|
|
|
|
})...)
|
|
|
|
|
|
|
2025-05-20 21:04:47 +08:00
|
|
|
|
rc.ResData = resVo
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-12 20:14:44 +08:00
|
|
|
|
func (r *Redis) TestConn(rc *req.Ctx) {
|
|
|
|
|
|
form := &form.Redis{}
|
2024-02-24 16:30:29 +08:00
|
|
|
|
redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis))
|
2023-11-12 20:14:44 +08:00
|
|
|
|
|
2024-11-26 17:32:44 +08:00
|
|
|
|
authCert := &tagentity.ResourceAuthCert{
|
|
|
|
|
|
Username: form.Username,
|
|
|
|
|
|
Ciphertext: form.Password,
|
|
|
|
|
|
CiphertextType: tagentity.AuthCertCiphertextTypePassword,
|
|
|
|
|
|
Type: tagentity.AuthCertTypePrivate,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if form.Mode == string(rdm.SentinelMode) {
|
|
|
|
|
|
encPwd, err := utils.PwdAesEncrypt(form.RedisNodePassword)
|
|
|
|
|
|
biz.ErrIsNil(err)
|
|
|
|
|
|
authCert.SetExtraValue("redisNodePassword", encPwd)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-16 23:29:18 +08:00
|
|
|
|
biz.ErrIsNil(r.redisApp.TestConn(&dto.SaveRedis{
|
2024-11-26 17:32:44 +08:00
|
|
|
|
Redis: redis,
|
|
|
|
|
|
AuthCert: authCert,
|
2024-04-13 17:01:12 +08:00
|
|
|
|
}))
|
2023-11-12 20:14:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-14 16:29:52 +08:00
|
|
|
|
func (r *Redis) Save(rc *req.Ctx) {
|
2021-07-28 18:03:19 +08:00
|
|
|
|
form := &form.Redis{}
|
2024-02-24 16:30:29 +08:00
|
|
|
|
redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis))
|
2022-07-18 20:36:31 +08:00
|
|
|
|
|
2024-05-09 21:29:34 +08:00
|
|
|
|
redisParam := &dto.SaveRedis{
|
2024-04-17 21:28:28 +08:00
|
|
|
|
Redis: redis,
|
|
|
|
|
|
TagCodePaths: form.TagCodePaths,
|
2024-01-05 08:55:34 +08:00
|
|
|
|
}
|
2024-10-16 17:24:50 +08:00
|
|
|
|
authCert := &tagentity.ResourceAuthCert{
|
|
|
|
|
|
Username: form.Username,
|
|
|
|
|
|
Ciphertext: form.Password,
|
|
|
|
|
|
CiphertextType: tagentity.AuthCertCiphertextTypePassword,
|
|
|
|
|
|
Type: tagentity.AuthCertTypePrivate,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if form.Mode == string(rdm.SentinelMode) {
|
|
|
|
|
|
encPwd, err := utils.PwdAesEncrypt(form.RedisNodePassword)
|
|
|
|
|
|
biz.ErrIsNil(err)
|
|
|
|
|
|
authCert.SetExtraValue("redisNodePassword", encPwd)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
redisParam.AuthCert = authCert
|
2024-04-13 17:01:12 +08:00
|
|
|
|
|
2024-04-18 20:50:14 +08:00
|
|
|
|
// 密码脱敏记录日志
|
|
|
|
|
|
form.Password = "****"
|
|
|
|
|
|
rc.ReqParam = form
|
|
|
|
|
|
|
2024-12-16 23:29:18 +08:00
|
|
|
|
biz.ErrIsNil(r.redisApp.SaveRedis(rc.MetaCtx, redisParam))
|
2022-08-02 21:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-14 16:29:52 +08:00
|
|
|
|
func (r *Redis) DeleteRedis(rc *req.Ctx) {
|
2024-02-25 12:46:18 +08:00
|
|
|
|
idsStr := rc.PathParam("id")
|
2023-07-01 14:34:42 +08:00
|
|
|
|
rc.ReqParam = idsStr
|
|
|
|
|
|
ids := strings.Split(idsStr, ",")
|
|
|
|
|
|
|
|
|
|
|
|
for _, v := range ids {
|
2024-12-16 23:29:18 +08:00
|
|
|
|
r.redisApp.Delete(rc.MetaCtx, cast.ToUint64(v))
|
2023-07-01 14:34:42 +08:00
|
|
|
|
}
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-14 16:29:52 +08:00
|
|
|
|
func (r *Redis) RedisInfo(rc *req.Ctx) {
|
2024-12-16 23:29:18 +08:00
|
|
|
|
ri, err := r.redisApp.GetRedisConn(uint64(rc.PathParamInt("id")), 0)
|
2023-10-26 17:15:49 +08:00
|
|
|
|
biz.ErrIsNil(err)
|
2025-05-21 04:42:30 +00:00
|
|
|
|
defer rdm.PutRedisConn(ri)
|
2022-07-10 12:14:06 +08:00
|
|
|
|
|
2024-02-25 12:46:18 +08:00
|
|
|
|
section := rc.Query("section")
|
2022-11-18 17:52:30 +08:00
|
|
|
|
mode := ri.Info.Mode
|
2022-07-10 12:14:06 +08:00
|
|
|
|
ctx := context.Background()
|
2023-02-07 16:54:44 +08:00
|
|
|
|
var redisCli *redis.Client
|
|
|
|
|
|
|
2023-10-27 17:41:45 +08:00
|
|
|
|
if mode == "" || mode == rdm.StandaloneMode || mode == rdm.SentinelMode {
|
2023-02-07 16:54:44 +08:00
|
|
|
|
redisCli = ri.Cli
|
2023-10-27 17:41:45 +08:00
|
|
|
|
} else if mode == rdm.ClusterMode {
|
2024-02-25 12:46:18 +08:00
|
|
|
|
host := rc.Query("host")
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.NotEmpty(host, "the cluster mode host info cannot be empty")
|
2022-07-10 12:14:06 +08:00
|
|
|
|
clusterClient := ri.ClusterCli
|
|
|
|
|
|
// 遍历集群的master节点找到该redis client
|
|
|
|
|
|
clusterClient.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
|
|
|
|
|
|
if host == client.Options().Addr {
|
2023-02-07 16:54:44 +08:00
|
|
|
|
redisCli = client
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
2023-02-07 16:54:44 +08:00
|
|
|
|
if redisCli == nil {
|
2022-07-10 12:14:06 +08:00
|
|
|
|
// 遍历集群的slave节点找到该redis client
|
|
|
|
|
|
clusterClient.ForEachSlave(ctx, func(ctx context.Context, client *redis.Client) error {
|
|
|
|
|
|
if host == client.Options().Addr {
|
2023-02-07 16:54:44 +08:00
|
|
|
|
redisCli = client
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.NotNil(redisCli, "the instance is not in the cluster")
|
2023-02-07 16:54:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var res string
|
|
|
|
|
|
if section == "" {
|
2023-07-05 22:06:32 +08:00
|
|
|
|
res, err = redisCli.Info(ctx).Result()
|
2023-02-07 16:54:44 +08:00
|
|
|
|
} else {
|
2023-07-05 22:06:32 +08:00
|
|
|
|
res, err = redisCli.Info(ctx, section).Result()
|
2022-07-10 12:14:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.ErrIsNilAppendErr(err, "get redis info error: %s")
|
2021-07-28 18:03:19 +08:00
|
|
|
|
|
|
|
|
|
|
datas := strings.Split(res, "\r\n")
|
|
|
|
|
|
i := 0
|
|
|
|
|
|
length := len(datas)
|
|
|
|
|
|
|
2021-09-08 17:55:57 +08:00
|
|
|
|
parseMap := make(map[string]map[string]string)
|
2021-07-28 18:03:19 +08:00
|
|
|
|
for {
|
|
|
|
|
|
if i >= length {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
if strings.Contains(datas[i], "#") {
|
2023-07-21 17:07:04 +08:00
|
|
|
|
key := stringx.SubString(datas[i], strings.Index(datas[i], "#")+1, stringx.Len(datas[i]))
|
2021-07-28 18:03:19 +08:00
|
|
|
|
i++
|
|
|
|
|
|
key = strings.Trim(key, " ")
|
|
|
|
|
|
|
2021-09-08 17:55:57 +08:00
|
|
|
|
sectionMap := make(map[string]string)
|
2021-07-28 18:03:19 +08:00
|
|
|
|
for {
|
|
|
|
|
|
if i >= length || !strings.Contains(datas[i], ":") {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
pair := strings.Split(datas[i], ":")
|
|
|
|
|
|
i++
|
|
|
|
|
|
if len(pair) != 2 {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
sectionMap[pair[0]] = pair[1]
|
|
|
|
|
|
}
|
|
|
|
|
|
parseMap[key] = sectionMap
|
|
|
|
|
|
} else {
|
|
|
|
|
|
i++
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
rc.ResData = parseMap
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-14 16:29:52 +08:00
|
|
|
|
func (r *Redis) ClusterInfo(rc *req.Ctx) {
|
2024-12-16 23:29:18 +08:00
|
|
|
|
ri, err := r.redisApp.GetRedisConn(uint64(rc.PathParamInt("id")), 0)
|
2023-10-26 17:15:49 +08:00
|
|
|
|
biz.ErrIsNil(err)
|
2025-05-21 04:42:30 +00:00
|
|
|
|
defer rdm.PutRedisConn(ri)
|
|
|
|
|
|
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.IsEquals(ri.Info.Mode, rdm.ClusterMode, "non-cluster mode")
|
2022-07-10 12:14:06 +08:00
|
|
|
|
info, _ := ri.ClusterCli.ClusterInfo(context.Background()).Result()
|
|
|
|
|
|
nodesStr, _ := ri.ClusterCli.ClusterNodes(context.Background()).Result()
|
|
|
|
|
|
|
|
|
|
|
|
nodesRes := make([]map[string]string, 0)
|
|
|
|
|
|
nodes := strings.Split(nodesStr, "\n")
|
|
|
|
|
|
for _, node := range nodes {
|
|
|
|
|
|
if node == "" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
nodeInfos := strings.Split(node, " ")
|
|
|
|
|
|
node := make(map[string]string)
|
|
|
|
|
|
node["nodeId"] = nodeInfos[0]
|
|
|
|
|
|
// ip:port1@port2:port1指redis服务器与客户端通信的端口,port2则是集群内部节点间通信的端口
|
|
|
|
|
|
node["ip"] = nodeInfos[1]
|
|
|
|
|
|
node["flags"] = nodeInfos[2]
|
|
|
|
|
|
// 如果节点是slave,并且已知master节点,则为master节点ID;否则为符号"-"
|
|
|
|
|
|
node["masterSlaveRelation"] = nodeInfos[3]
|
|
|
|
|
|
// 最近一次发送ping的时间,这个时间是一个unix毫秒时间戳,0代表没有发送过
|
|
|
|
|
|
node["pingSent"] = nodeInfos[4]
|
|
|
|
|
|
// 最近一次收到pong的时间,使用unix时间戳表示
|
|
|
|
|
|
node["pongRecv"] = nodeInfos[5]
|
|
|
|
|
|
// 节点的epoch值(如果该节点是从节点,则为其主节点的epoch值)。每当节点发生失败切换时,都会创建一个新的,独特的,递增的epoch。
|
|
|
|
|
|
// 如果多个节点竞争同一个哈希槽时,epoch值更高的节点会抢夺到
|
|
|
|
|
|
node["configEpoch"] = nodeInfos[6]
|
|
|
|
|
|
// node-to-node集群总线使用的链接的状态,我们使用这个链接与集群中其他节点进行通信.值可以是 connected 和 disconnected
|
|
|
|
|
|
node["linkState"] = nodeInfos[7]
|
|
|
|
|
|
// slave节点没有插槽信息
|
|
|
|
|
|
if len(nodeInfos) > 8 {
|
|
|
|
|
|
// slot:master节点第9位为哈希槽值或者一个哈希槽范围,代表当前节点可以提供服务的所有哈希槽值。如果只是一个值,那就是只有一个槽会被使用。
|
|
|
|
|
|
// 如果是一个范围,这个值表示为起始槽-结束槽,节点将处理包括起始槽和结束槽在内的所有哈希槽。
|
|
|
|
|
|
node["slot"] = nodeInfos[8]
|
|
|
|
|
|
}
|
|
|
|
|
|
nodesRes = append(nodesRes, node)
|
|
|
|
|
|
}
|
2023-10-12 12:14:56 +08:00
|
|
|
|
rc.ResData = collx.M{
|
2022-07-10 12:14:06 +08:00
|
|
|
|
"clusterInfo": info,
|
|
|
|
|
|
"clusterNodes": nodesRes,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-16 00:50:36 +08:00
|
|
|
|
// 校验查询参数中的key为必填项,并返回redis实例
|
2023-10-27 17:41:45 +08:00
|
|
|
|
func (r *Redis) checkKeyAndGetRedisConn(rc *req.Ctx) (*rdm.RedisConn, string) {
|
2024-02-25 12:46:18 +08:00
|
|
|
|
key := rc.Query("key")
|
2024-11-20 22:43:53 +08:00
|
|
|
|
biz.NotEmpty(key, "key cannot be empty")
|
2023-10-27 17:41:45 +08:00
|
|
|
|
return r.getRedisConn(rc), key
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-27 17:41:45 +08:00
|
|
|
|
func (r *Redis) getRedisConn(rc *req.Ctx) *rdm.RedisConn {
|
2024-12-16 23:29:18 +08:00
|
|
|
|
ri, err := r.redisApp.GetRedisConn(getIdAndDbNum(rc))
|
2023-10-26 17:15:49 +08:00
|
|
|
|
biz.ErrIsNil(err)
|
2025-05-21 04:42:30 +00:00
|
|
|
|
defer rdm.PutRedisConn(ri)
|
|
|
|
|
|
|
2024-12-16 23:29:18 +08:00
|
|
|
|
biz.ErrIsNilAppendErr(r.tagApp.CanAccess(rc.GetLoginAccount().Id, ri.Info.CodePath...), "%s")
|
2023-04-16 00:50:36 +08:00
|
|
|
|
return ri
|
2021-07-28 18:03:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-16 00:50:36 +08:00
|
|
|
|
// 获取redis id与要操作的库号(统一路径)
|
2024-02-24 16:30:29 +08:00
|
|
|
|
func getIdAndDbNum(rc *req.Ctx) (uint64, int) {
|
2024-02-25 12:46:18 +08:00
|
|
|
|
return uint64(rc.PathParamInt("id")), rc.PathParamInt("db")
|
2022-09-22 11:56:21 +08:00
|
|
|
|
}
|