diff --git a/mayfly_go_web/src/views/ops/redis/DataOperation.vue b/mayfly_go_web/src/views/ops/redis/DataOperation.vue index 8633fdd8..45e072c0 100644 --- a/mayfly_go_web/src/views/ops/redis/DataOperation.vue +++ b/mayfly_go_web/src/views/ops/redis/DataOperation.vue @@ -8,12 +8,12 @@ @@ -61,7 +61,7 @@ {{ scope.row.type }} - + @@ -83,6 +83,7 @@ :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" + :db="scanParam.db" @cancel="onCancelDataEdit" @valChange="searchKey" /> @@ -93,6 +94,7 @@ :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" + :db="scanParam.db" @cancel="onCancelDataEdit" @valChange="searchKey" /> @@ -102,6 +104,7 @@ :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" + :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey" @cancel="onCancelDataEdit" @@ -112,6 +115,7 @@ :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" + :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey" @cancel="onCancelDataEdit" @@ -143,11 +147,13 @@ export default defineComponent({ const state = reactive({ loading: false, redisList: [], + dbList: [], query: { envId: 0, }, scanParam: { id: null, + db: null, match: null, count: 10, cursor: {}, @@ -194,6 +200,14 @@ export default defineComponent({ const changeRedis = (id: number) => { resetScanParam(id); + state.scanParam.db = null; + state.dbList = (state.redisList.find((x: any) => x.id == id) as any).db.split(','); + state.keys = []; + state.dbsize = 0; + }; + + const changeDb = () => { + resetScanParam(state.scanParam.id as any); state.keys = []; state.dbsize = 0; searchKey(); @@ -230,6 +244,7 @@ export default defineComponent({ state.redisList = []; state.scanParam.id = null; resetScanParam(); + state.scanParam.db = null; state.keys = []; state.dbsize = 0; }; @@ -247,7 +262,7 @@ export default defineComponent({ const redis: any = state.redisList.find((x: any) => x.id == id); // 集群模式count设小点,因为后端会从所有master节点scan一遍然后合并结果 if (redis && redis.mode == 'cluster') { - state.scanParam.count = 5; + state.scanParam.count = 4; } } state.scanParam.match = null; @@ -310,6 +325,7 @@ export default defineComponent({ .request({ key, id: state.scanParam.id, + db: state.scanParam.db, }) .then(() => { ElMessage.success('删除成功!'); @@ -371,6 +387,7 @@ export default defineComponent({ ...toRefs(state), changeProjectEnv, changeRedis, + changeDb, clearRedis, searchKey, scan, diff --git a/mayfly_go_web/src/views/ops/redis/HashValue.vue b/mayfly_go_web/src/views/ops/redis/HashValue.vue index 17079d2c..14e95e9d 100644 --- a/mayfly_go_web/src/views/ops/redis/HashValue.vue +++ b/mayfly_go_web/src/views/ops/redis/HashValue.vue @@ -81,6 +81,10 @@ export default defineComponent({ type: [Number], require: true, }, + db: { + type: [Number], + require: true, + }, keyInfo: { type: [Object], }, @@ -94,6 +98,7 @@ export default defineComponent({ dialogVisible: false, operationType: 1, redisId: 0, + db: 0, key: { key: '', type: 'hash', @@ -102,6 +107,7 @@ export default defineComponent({ scanParam: { key: '', id: 0, + db: 0, cursor: 0, match: '', count: 10, @@ -127,6 +133,7 @@ export default defineComponent({ watch(props, async (newValue) => { const visible = newValue.visible; state.redisId = newValue.redisId; + state.db = newValue.db; state.key = newValue.keyInfo; state.operationType = newValue.operationType; @@ -141,6 +148,7 @@ export default defineComponent({ const reHscan = async () => { state.scanParam.id = state.redisId; + state.scanParam.db = state.db; state.scanParam.cursor = 0; hscan(); }; @@ -186,6 +194,7 @@ export default defineComponent({ }); await redisApi.hdel.request({ id: state.redisId, + db: state.db, key: state.key.key, field, }); @@ -196,6 +205,7 @@ export default defineComponent({ const hset = async (row: any) => { await redisApi.saveHashValue.request({ id: state.redisId, + db: state.db, key: state.key.key, timed: state.key.timed, value: [ @@ -215,7 +225,7 @@ export default defineComponent({ const saveValue = async () => { notEmpty(state.key.key, 'key不能为空'); isTrue(state.hashValues.length > 0, 'hash内容不能为空'); - const sv = { value: state.hashValues, id: state.redisId }; + const sv = { value: state.hashValues, id: state.redisId, db: state.db }; Object.assign(sv, state.key); await redisApi.saveHashValue.request(sv); ElMessage.success('保存成功'); diff --git a/mayfly_go_web/src/views/ops/redis/ListValue.vue b/mayfly_go_web/src/views/ops/redis/ListValue.vue index 0c039805..384f4349 100644 --- a/mayfly_go_web/src/views/ops/redis/ListValue.vue +++ b/mayfly_go_web/src/views/ops/redis/ListValue.vue @@ -74,6 +74,10 @@ export default defineComponent({ type: [Number], require: true, }, + db: { + type: [Number], + require: true, + }, keyInfo: { type: [Object], }, @@ -91,6 +95,7 @@ export default defineComponent({ dialogVisible: false, operationType: 1, redisId: '', + db: 0, key: { key: '', type: 'string', @@ -121,6 +126,7 @@ export default defineComponent({ state.dialogVisible = newValue.visible; state.key = newValue.key; state.redisId = newValue.redisId; + state.db = newValue.db; state.key = newValue.keyInfo; state.operationType = newValue.operationType; // 如果是查看编辑操作,则获取值 @@ -134,6 +140,7 @@ export default defineComponent({ const pageSize = state.pageSize; const res = await redisApi.getListValue.request({ id: state.redisId, + db: state.db, key: state.key.key, start: (pageNum - 1) * pageSize, stop: pageNum * pageSize - 1, @@ -149,6 +156,7 @@ export default defineComponent({ const lset = async (row: any, rowIndex: number) => { await redisApi.setListValue.request({ id: state.redisId, + db: state.db, key: state.key.key, index: (state.pageNum - 1) * state.pageSize + rowIndex, value: row.value, diff --git a/mayfly_go_web/src/views/ops/redis/RedisEdit.vue b/mayfly_go_web/src/views/ops/redis/RedisEdit.vue index a5429b1e..995b312a 100644 --- a/mayfly_go_web/src/views/ops/redis/RedisEdit.vue +++ b/mayfly_go_web/src/views/ops/redis/RedisEdit.vue @@ -45,7 +45,9 @@ > - + + + @@ -116,6 +118,7 @@ export default defineComponent({ mode: 'standalone', host: '', password: null, + db: '', project: null, projectId: null, envId: null, @@ -124,6 +127,7 @@ export default defineComponent({ enableSshTunnel: null, sshTunnelMachineId: null, }, + dbList: [0], pwd: '', btnLoading: false, rules: { @@ -151,14 +155,14 @@ export default defineComponent({ db: [ { required: true, - message: '请输入库号', + message: '请选择库号', trigger: ['change', 'blur'], }, ], mode: [ { required: true, - message: '请输入模式', + message: '请选择模式', trigger: ['change', 'blur'], }, ], @@ -174,13 +178,26 @@ export default defineComponent({ if (newValue.redis) { getEnvs(newValue.redis.projectId); state.form = { ...newValue.redis }; + convertDb(state.form.db); } else { state.envs = []; - state.form = { db: 0, enableSshTunnel: -1 } as any; + state.form = { db: '0', enableSshTunnel: -1 } as any; + state.dbList = []; } getSshTunnelMachines(); }); + const convertDb = (db: string) => { + state.dbList = db.split(',').map((x) => Number.parseInt(x)); + }; + + /** + * 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加库号 + */ + const changeDb = () => { + state.form.db = state.dbList.length == 0 ? '' : state.dbList.join(','); + }; + const getSshTunnelMachines = async () => { if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) { const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 }); @@ -250,6 +267,7 @@ export default defineComponent({ return { ...toRefs(state), redisForm, + changeDb, getSshTunnelMachines, getPwd, changeProject, diff --git a/mayfly_go_web/src/views/ops/redis/SetValue.vue b/mayfly_go_web/src/views/ops/redis/SetValue.vue index 8aef6314..3213b948 100644 --- a/mayfly_go_web/src/views/ops/redis/SetValue.vue +++ b/mayfly_go_web/src/views/ops/redis/SetValue.vue @@ -20,7 +20,7 @@ @@ -53,6 +53,10 @@ export default defineComponent({ type: [Number], require: true, }, + db: { + type: [Number], + require: true, + }, keyInfo: { type: [Object], }, @@ -70,6 +74,7 @@ export default defineComponent({ dialogVisible: false, operationType: 1, redisId: '', + db: 0, key: { key: '', type: 'string', @@ -95,6 +100,7 @@ export default defineComponent({ state.dialogVisible = newValue.visible; state.key = newValue.key; state.redisId = newValue.redisId; + state.db = newValue.db; state.key = newValue.keyInfo; state.operationType = newValue.operationType; // 如果是查看编辑操作,则获取值 @@ -106,6 +112,7 @@ export default defineComponent({ const getSetValue = async () => { const res = await redisApi.getSetValue.request({ id: state.redisId, + db: state.db, key: state.key.key, }); state.value = res.map((x: any) => { @@ -118,7 +125,7 @@ export default defineComponent({ const saveValue = async () => { notEmpty(state.key.key, 'key不能为空'); isTrue(state.value.length > 0, 'set内容不能为空'); - const sv = { value: state.value.map((x) => x.value), id: state.redisId }; + const sv = { value: state.value.map((x) => x.value), id: state.redisId, db: state.db }; Object.assign(sv, state.key); await redisApi.saveSetValue.request(sv); diff --git a/mayfly_go_web/src/views/ops/redis/StringValue.vue b/mayfly_go_web/src/views/ops/redis/StringValue.vue index b7f8b927..50d2be5c 100644 --- a/mayfly_go_web/src/views/ops/redis/StringValue.vue +++ b/mayfly_go_web/src/views/ops/redis/StringValue.vue @@ -48,6 +48,10 @@ export default defineComponent({ type: [Number], require: true, }, + db: { + type: [Number], + require: true, + }, keyInfo: { type: [Object], }, @@ -62,6 +66,7 @@ export default defineComponent({ dialogVisible: false, operationType: 1, redisId: '', + db: 0, key: { key: '', type: 'string', @@ -101,12 +106,20 @@ export default defineComponent({ } ); + watch( + () => props.db, + (val) => { + state.db = val; + } + ); + watch(props, async (newValue) => { state.dialogVisible = newValue.visible; state.key = newValue.key; state.redisId = newValue.redisId; + state.db = newValue.db; state.key = newValue.keyInfo; - state.operationType = newValue.operationType + state.operationType = newValue.operationType; // 如果是查看编辑操作,则获取值 if (state.dialogVisible && state.operationType == 2) { getStringValue(); @@ -116,6 +129,7 @@ export default defineComponent({ const getStringValue = async () => { state.string.value = await redisApi.getStringValue.request({ id: state.redisId, + db: state.db, key: state.key.key, }); }; @@ -124,7 +138,7 @@ export default defineComponent({ notEmpty(state.key.key, 'key不能为空'); notEmpty(state.string.value, 'value不能为空'); - const sv = { value: formatJsonString(state.string.value, true), id: state.redisId }; + const sv = { value: formatJsonString(state.string.value, true), id: state.redisId, db: state.db }; Object.assign(sv, state.key); await redisApi.saveStringValue.request(sv); ElMessage.success('数据保存成功'); diff --git a/mayfly_go_web/src/views/ops/redis/api.ts b/mayfly_go_web/src/views/ops/redis/api.ts index afb3c058..4fb6d37a 100644 --- a/mayfly_go_web/src/views/ops/redis/api.ts +++ b/mayfly_go_web/src/views/ops/redis/api.ts @@ -8,19 +8,19 @@ export const redisApi = { saveRedis: Api.create("/redis", 'post'), delRedis: Api.create("/redis/{id}", 'delete'), // 获取权限列表 - scan: Api.create("/redis/{id}/scan", 'post'), - getStringValue: Api.create("/redis/{id}/string-value", 'get'), - saveStringValue: Api.create("/redis/{id}/string-value", 'post'), - getHashValue: Api.create("/redis/{id}/hash-value", 'get'), - hscan: Api.create("/redis/{id}/hscan", 'get'), - hget: Api.create("/redis/{id}/hget", 'get'), - hdel: Api.create("/redis/{id}/hdel", 'delete'), - saveHashValue: Api.create("/redis/{id}/hash-value", 'post'), - getSetValue: Api.create("/redis/{id}/set-value", 'get'), - saveSetValue: Api.create("/redis/{id}/set-value", 'post'), - del: Api.create("/redis/{id}/scan/{cursor}/{count}", 'delete'), - delKey: Api.create("/redis/{id}/key", 'delete'), - getListValue: Api.create("/redis/{id}/list-value", 'get'), - saveListValue: Api.create("/redis/{id}/list-value", 'post'), - setListValue: Api.create("/redis/{id}/list-value/lset", 'post'), + scan: Api.create("/redis/{id}/{db}/scan", 'post'), + getStringValue: Api.create("/redis/{id}/{db}/string-value", 'get'), + saveStringValue: Api.create("/redis/{id}/{db}/string-value", 'post'), + getHashValue: Api.create("/redis/{id}/{db}/hash-value", 'get'), + hscan: Api.create("/redis/{id}/{db}/hscan", 'get'), + hget: Api.create("/redis/{id}/{db}/hget", 'get'), + hdel: Api.create("/redis/{id}/{db}/hdel", 'delete'), + saveHashValue: Api.create("/redis/{id}/{db}/hash-value", 'post'), + getSetValue: Api.create("/redis/{id}/{db}/set-value", 'get'), + saveSetValue: Api.create("/redis/{id}/{db}/set-value", 'post'), + del: Api.create("/redis/{id}/{db}/scan/{cursor}/{count}", 'delete'), + delKey: Api.create("/redis/{id}/{db}/key", 'delete'), + getListValue: Api.create("/redis/{id}/{db}/list-value", 'get'), + saveListValue: Api.create("/redis/{id}/{db}/list-value", 'post'), + setListValue: Api.create("/redis/{id}/{db}/list-value/lset", 'post'), } \ No newline at end of file diff --git a/server/internal/redis/api/form/redis.go b/server/internal/redis/api/form/redis.go index ad043561..e0a23b41 100644 --- a/server/internal/redis/api/form/redis.go +++ b/server/internal/redis/api/form/redis.go @@ -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"` diff --git a/server/internal/redis/api/redis.go b/server/internal/redis/api/redis.go index 1f37c13d..b5990aea 100644 --- a/server/internal/redis/api/redis.go +++ b/server/internal/redis/api/redis.go @@ -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() diff --git a/server/internal/redis/api/vo/redis.go b/server/internal/redis/api/vo/redis.go index 4efdded3..3a56833f 100644 --- a/server/internal/redis/api/vo/redis.go +++ b/server/internal/redis/api/vo/redis.go @@ -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"` diff --git a/server/internal/redis/application/redis_app.go b/server/internal/redis/application/redis_app.go index db771607..1d4be6fe 100644 --- a/server/internal/redis/application/redis_app.go +++ b/server/internal/redis/application/redis_app.go @@ -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 diff --git a/server/internal/redis/domain/entity/redis.go b/server/internal/redis/domain/entity/redis.go index 305f49e3..6ce8be6e 100644 --- a/server/internal/redis/domain/entity/redis.go +++ b/server/internal/redis/domain/entity/redis.go @@ -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 diff --git a/server/internal/redis/router/redis.go b/server/internal/redis/router/redis.go index 8b0db517..cd5c9f5a 100644 --- a/server/internal/redis/router/redis.go +++ b/server/internal/redis/router/redis.go @@ -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) }) } diff --git a/server/mayfly-go.sql b/server/mayfly-go.sql index ccd0349f..1ccb4fa7 100644 --- a/server/mayfly-go.sql +++ b/server/mayfly-go.sql @@ -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', diff --git a/server/pkg/config/app.go b/server/pkg/config/app.go index d1489c6e..8e1f997a 100644 --- a/server/pkg/config/app.go +++ b/server/pkg/config/app.go @@ -4,7 +4,7 @@ import "fmt" const ( AppName = "mayfly-go" - Version = "v1.2.11" + Version = "v1.2.12" ) func GetAppInfo() string {