mirror of
https://gitee.com/dromara/mayfly-go
synced 2026-03-14 05:55:49 +08:00
feat: redis支持设置多库操作
This commit is contained in:
@@ -5,7 +5,7 @@ type Redis struct {
|
||||
Host string `binding:"required" json:"host"`
|
||||
Password string `json:"password"`
|
||||
Mode string `json:"mode"`
|
||||
Db int `json:"db"`
|
||||
Db string `json:"db"`
|
||||
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
|
||||
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
ProjectId uint64 `binding:"required" json:"projectId"`
|
||||
|
||||
@@ -66,7 +66,8 @@ func (r *Redis) DeleteRedis(rc *ctx.ReqCtx) {
|
||||
}
|
||||
|
||||
func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
|
||||
g := rc.GinCtx
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), 0)
|
||||
|
||||
var res string
|
||||
var err error
|
||||
@@ -137,7 +138,7 @@ func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
|
||||
|
||||
func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), 0)
|
||||
biz.IsEquals(ri.Mode, entity.RedisModeCluster, "非集群模式")
|
||||
info, _ := ri.ClusterCli.ClusterInfo(context.Background()).Result()
|
||||
nodesStr, _ := ri.ClusterCli.ClusterNodes(context.Background()).Result()
|
||||
@@ -182,7 +183,7 @@ func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
|
||||
// scan获取redis的key列表信息
|
||||
func (r *Redis) Scan(rc *ctx.ReqCtx) {
|
||||
g := rc.GinCtx
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
|
||||
form := &form.RedisScanForm{}
|
||||
@@ -261,7 +262,7 @@ func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
|
||||
key := g.Query("key")
|
||||
biz.NotEmpty(key, "key不能为空")
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
|
||||
rc.ReqParam = key
|
||||
@@ -273,7 +274,7 @@ func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
|
||||
key := g.Query("key")
|
||||
biz.NotEmpty(key, "key不能为空")
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
|
||||
return ri, key
|
||||
@@ -291,7 +292,7 @@ func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
|
||||
keyValue := new(form.StringValue)
|
||||
ginx.BindJsonAndValid(g, keyValue)
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
|
||||
str, err := ri.GetCmdable().Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
|
||||
@@ -343,7 +344,7 @@ func (r *Redis) SetHashValue(rc *ctx.ReqCtx) {
|
||||
hashValue := new(form.HashValue)
|
||||
ginx.BindJsonAndValid(g, hashValue)
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
|
||||
cmd := ri.GetCmdable()
|
||||
@@ -370,7 +371,7 @@ func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
|
||||
keyvalue := new(form.SetValue)
|
||||
ginx.BindJsonAndValid(g, keyvalue)
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
cmd := ri.GetCmdable()
|
||||
|
||||
@@ -409,7 +410,7 @@ func (r *Redis) SaveListValue(rc *ctx.ReqCtx) {
|
||||
listValue := new(form.ListValue)
|
||||
ginx.BindJsonAndValid(g, listValue)
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
cmd := ri.GetCmdable()
|
||||
|
||||
@@ -429,7 +430,7 @@ func (r *Redis) SetListValue(rc *ctx.ReqCtx) {
|
||||
listSetValue := new(form.ListSetValue)
|
||||
ginx.BindJsonAndValid(g, listSetValue)
|
||||
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
|
||||
ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
|
||||
biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
|
||||
|
||||
_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()
|
||||
|
||||
@@ -6,7 +6,7 @@ type Redis struct {
|
||||
Id *int64 `json:"id"`
|
||||
// Name *string `json:"name"`
|
||||
Host *string `json:"host"`
|
||||
Db int `json:"db"`
|
||||
Db string `json:"db"`
|
||||
ProjectId *int64 `json:"projectId"`
|
||||
Project *string `json:"project"`
|
||||
Mode *string `json:"mode"`
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -38,7 +39,9 @@ type Redis interface {
|
||||
Delete(id uint64)
|
||||
|
||||
// 获取数据库连接实例
|
||||
GetRedisInstance(id uint64) *RedisInstance
|
||||
// id: 数据库实例id
|
||||
// db: 库号
|
||||
GetRedisInstance(id uint64, db int) *RedisInstance
|
||||
}
|
||||
|
||||
func newRedisApp(redisRepo repository.Redis) Redis {
|
||||
@@ -77,20 +80,25 @@ func (r *redisAppImpl) Save(re *entity.Redis) {
|
||||
}
|
||||
|
||||
// 查找是否存在该库
|
||||
oldRedis := &entity.Redis{Host: re.Host, Db: re.Db}
|
||||
oldRedis := &entity.Redis{Host: re.Host}
|
||||
err := r.GetRedisBy(oldRedis)
|
||||
|
||||
if re.Id == 0 {
|
||||
biz.IsTrue(err != nil, "该库已存在")
|
||||
biz.IsTrue(err != nil, "该实例已存在")
|
||||
re.PwdEncrypt()
|
||||
r.redisRepo.Insert(re)
|
||||
} else {
|
||||
// 如果存在该库,则校验修改的库是否为该库
|
||||
if err == nil {
|
||||
biz.IsTrue(oldRedis.Id == re.Id, "该库已存在")
|
||||
biz.IsTrue(oldRedis.Id == re.Id, "该实例已存在")
|
||||
}
|
||||
// 如果修改了redis实例的库信息,则关闭旧库的连接
|
||||
if oldRedis.Db != re.Db {
|
||||
for _, dbStr := range strings.Split(oldRedis.Db, ",") {
|
||||
db, _ := strconv.Atoi(dbStr)
|
||||
CloseRedis(re.Id, db)
|
||||
}
|
||||
}
|
||||
// 先关闭数据库连接
|
||||
CloseRedis(re.Id)
|
||||
re.PwdEncrypt()
|
||||
r.redisRepo.Update(re)
|
||||
}
|
||||
@@ -98,16 +106,22 @@ func (r *redisAppImpl) Save(re *entity.Redis) {
|
||||
|
||||
// 删除Redis信息
|
||||
func (r *redisAppImpl) Delete(id uint64) {
|
||||
CloseRedis(id)
|
||||
re := r.GetById(id)
|
||||
biz.NotNil(re, "该redis信息不存在")
|
||||
// 如果存在连接,则关闭所有库连接信息
|
||||
for _, dbStr := range strings.Split(re.Db, ",") {
|
||||
db, _ := strconv.Atoi(dbStr)
|
||||
CloseRedis(re.Id, db)
|
||||
}
|
||||
r.redisRepo.Delete(id)
|
||||
}
|
||||
|
||||
// 获取数据库连接实例
|
||||
func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
|
||||
func (r *redisAppImpl) GetRedisInstance(id uint64, db int) *RedisInstance {
|
||||
// Id不为0,则为需要缓存
|
||||
needCache := id != 0
|
||||
if needCache {
|
||||
load, ok := redisCache.Get(id)
|
||||
load, ok := redisCache.Get(getRedisCacheKey(id, db))
|
||||
if ok {
|
||||
return load.(*RedisInstance)
|
||||
}
|
||||
@@ -120,7 +134,7 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
|
||||
redisMode := re.Mode
|
||||
var ri *RedisInstance
|
||||
if redisMode == "" || redisMode == entity.RedisModeStandalone {
|
||||
ri = getRedisCient(re)
|
||||
ri = getRedisCient(re, db)
|
||||
// 测试连接
|
||||
_, e := ri.Cli.Ping(context.Background()).Result()
|
||||
if e != nil {
|
||||
@@ -136,7 +150,7 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
|
||||
panic(biz.NewBizErr(fmt.Sprintf("redis集群连接失败: %s", e.Error())))
|
||||
}
|
||||
} else if redisMode == entity.RedisModeSentinel {
|
||||
ri = getRedisSentinelCient(re)
|
||||
ri = getRedisSentinelCient(re, db)
|
||||
// 测试连接
|
||||
_, e := ri.Cli.Ping(context.Background()).Result()
|
||||
if e != nil {
|
||||
@@ -147,18 +161,23 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
|
||||
|
||||
global.Log.Infof("连接redis: %s", re.Host)
|
||||
if needCache {
|
||||
redisCache.Put(re.Id, ri)
|
||||
redisCache.Put(getRedisCacheKey(id, db), ri)
|
||||
}
|
||||
return ri
|
||||
}
|
||||
|
||||
func getRedisCient(re *entity.Redis) *RedisInstance {
|
||||
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode}
|
||||
// 生成redis连接缓存key
|
||||
func getRedisCacheKey(id uint64, db int) string {
|
||||
return fmt.Sprintf("%d/%d", id, db)
|
||||
}
|
||||
|
||||
func getRedisCient(re *entity.Redis, db int) *RedisInstance {
|
||||
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, db), ProjectId: re.ProjectId, Mode: re.Mode}
|
||||
|
||||
redisOptions := &redis.Options{
|
||||
Addr: re.Host,
|
||||
Password: re.Password, // no password set
|
||||
DB: re.Db, // use default DB
|
||||
DB: db, // use default DB
|
||||
DialTimeout: 8 * time.Second,
|
||||
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
|
||||
WriteTimeout: -1,
|
||||
@@ -172,7 +191,7 @@ func getRedisCient(re *entity.Redis) *RedisInstance {
|
||||
}
|
||||
|
||||
func getRedisClusterClient(re *entity.Redis) *RedisInstance {
|
||||
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode}
|
||||
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, 0), ProjectId: re.ProjectId, Mode: re.Mode}
|
||||
|
||||
redisClusterOptions := &redis.ClusterOptions{
|
||||
Addrs: strings.Split(re.Host, ","),
|
||||
@@ -187,15 +206,15 @@ func getRedisClusterClient(re *entity.Redis) *RedisInstance {
|
||||
return ri
|
||||
}
|
||||
|
||||
func getRedisSentinelCient(re *entity.Redis) *RedisInstance {
|
||||
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode}
|
||||
func getRedisSentinelCient(re *entity.Redis, db int) *RedisInstance {
|
||||
ri := &RedisInstance{Id: getRedisCacheKey(re.Id, db), ProjectId: re.ProjectId, Mode: re.Mode}
|
||||
// sentinel模式host为 masterName=host:port,host:port
|
||||
masterNameAndHosts := strings.Split(re.Host, "=")
|
||||
sentinelOptions := &redis.FailoverOptions{
|
||||
MasterName: masterNameAndHosts[0],
|
||||
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
|
||||
Password: re.Password, // no password set
|
||||
DB: re.Db, // use default DB
|
||||
DB: db, // use default DB
|
||||
DialTimeout: 8 * time.Second,
|
||||
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
|
||||
WriteTimeout: -1,
|
||||
@@ -231,8 +250,8 @@ var redisCache = cache.NewTimedCache(constant.RedisConnExpireTime, 5*time.Second
|
||||
})
|
||||
|
||||
// 移除redis连接缓存并关闭redis连接
|
||||
func CloseRedis(id uint64) {
|
||||
redisCache.Delete(id)
|
||||
func CloseRedis(id uint64, db int) {
|
||||
redisCache.Delete(getRedisCacheKey(id, db))
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -250,8 +269,11 @@ func init() {
|
||||
|
||||
func TestRedisConnection(re *entity.Redis) {
|
||||
var cmd redis.Cmdable
|
||||
// 取第一个库测试连接即可
|
||||
dbStr := strings.Split(re.Db, ",")[0]
|
||||
db, _ := strconv.Atoi(dbStr)
|
||||
if re.Mode == "" || re.Mode == entity.RedisModeStandalone {
|
||||
rcli := getRedisCient(re)
|
||||
rcli := getRedisCient(re, db)
|
||||
defer rcli.Close()
|
||||
cmd = rcli.Cli
|
||||
} else if re.Mode == entity.RedisModeCluster {
|
||||
@@ -259,7 +281,7 @@ func TestRedisConnection(re *entity.Redis) {
|
||||
defer ccli.Close()
|
||||
cmd = ccli.ClusterCli
|
||||
} else if re.Mode == entity.RedisModeSentinel {
|
||||
rcli := getRedisSentinelCient(re)
|
||||
rcli := getRedisSentinelCient(re, db)
|
||||
defer rcli.Close()
|
||||
cmd = rcli.Cli
|
||||
}
|
||||
@@ -271,7 +293,7 @@ func TestRedisConnection(re *entity.Redis) {
|
||||
|
||||
// redis实例
|
||||
type RedisInstance struct {
|
||||
Id uint64
|
||||
Id string
|
||||
ProjectId uint64
|
||||
Mode string
|
||||
Cli *redis.Client
|
||||
|
||||
@@ -11,7 +11,7 @@ type Redis struct {
|
||||
Host string `orm:"column(host)" json:"host"`
|
||||
Mode string `json:"mode"`
|
||||
Password string `orm:"column(password)" json:"-"`
|
||||
Db int `orm:"column(database)" json:"db"`
|
||||
Db string `orm:"column(database)" json:"db"`
|
||||
EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
|
||||
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
Remark string
|
||||
|
||||
@@ -45,64 +45,64 @@ func InitRedisRouter(router *gin.RouterGroup) {
|
||||
})
|
||||
|
||||
// 获取指定redis keys
|
||||
redis.POST(":id/scan", func(c *gin.Context) {
|
||||
redis.POST(":id/:db/scan", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.Scan)
|
||||
})
|
||||
|
||||
// 删除key
|
||||
deleteKeyL := ctx.NewLogInfo("redis删除key").WithSave(true)
|
||||
redis.DELETE(":id/key", func(c *gin.Context) {
|
||||
redis.DELETE(":id/:db/key", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).WithLog(deleteKeyL).Handle(rs.DeleteKey)
|
||||
})
|
||||
|
||||
// 获取string类型值
|
||||
redis.GET(":id/string-value", func(c *gin.Context) {
|
||||
redis.GET(":id/:db/string-value", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.GetStringValue)
|
||||
})
|
||||
|
||||
// 设置string类型值
|
||||
redis.POST(":id/string-value", func(c *gin.Context) {
|
||||
redis.POST(":id/:db/string-value", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.SetStringValue)
|
||||
})
|
||||
|
||||
// hscan
|
||||
redis.GET(":id/hscan", func(c *gin.Context) {
|
||||
redis.GET(":id/:db/hscan", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.Hscan)
|
||||
})
|
||||
|
||||
redis.GET(":id/hget", func(c *gin.Context) {
|
||||
redis.GET(":id/:db/hget", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.Hget)
|
||||
})
|
||||
|
||||
redis.DELETE(":id/hdel", func(c *gin.Context) {
|
||||
redis.DELETE(":id/:db/hdel", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.Hdel)
|
||||
})
|
||||
|
||||
// 设置hash类型值
|
||||
redis.POST(":id/hash-value", func(c *gin.Context) {
|
||||
redis.POST(":id/:db/hash-value", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.SetHashValue)
|
||||
})
|
||||
|
||||
// 获取set类型值
|
||||
redis.GET(":id/set-value", func(c *gin.Context) {
|
||||
redis.GET(":id/:db/set-value", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.GetSetValue)
|
||||
})
|
||||
|
||||
// 设置set类型值
|
||||
redis.POST(":id/set-value", func(c *gin.Context) {
|
||||
redis.POST(":id/:db/set-value", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.SetSetValue)
|
||||
})
|
||||
|
||||
// 获取list类型值
|
||||
redis.GET(":id/list-value", func(c *gin.Context) {
|
||||
redis.GET(":id/:db/list-value", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.GetListValue)
|
||||
})
|
||||
|
||||
redis.POST(":id/list-value", func(c *gin.Context) {
|
||||
redis.POST(":id/:db/list-value", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.SaveListValue)
|
||||
})
|
||||
|
||||
redis.POST(":id/list-value/lset", func(c *gin.Context) {
|
||||
redis.POST(":id/:db/list-value/lset", func(c *gin.Context) {
|
||||
ctx.NewReqCtxWithGin(c).Handle(rs.SetListValue)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ CREATE TABLE `t_redis` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`host` varchar(255) COLLATE utf8mb4_bin NOT NULL,
|
||||
`password` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`db` int(32) DEFAULT NULL,
|
||||
`db` varchar(64) DEFAULT NULL COMMENT '库号: 多个库用,分割',
|
||||
`mode` varchar(32) DEFAULT NULL,
|
||||
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
|
||||
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
|
||||
|
||||
@@ -4,7 +4,7 @@ import "fmt"
|
||||
|
||||
const (
|
||||
AppName = "mayfly-go"
|
||||
Version = "v1.2.11"
|
||||
Version = "v1.2.12"
|
||||
)
|
||||
|
||||
func GetAppInfo() string {
|
||||
|
||||
Reference in New Issue
Block a user