feat: redis支持设置多库操作

This commit is contained in:
meilin.huang
2022-09-29 13:14:50 +08:00
parent ac62767a18
commit e8f3671ffb
15 changed files with 181 additions and 84 deletions

View File

@@ -8,12 +8,12 @@
<template #default> <template #default>
<el-form-item label="redis" label-width="40px"> <el-form-item label="redis" label-width="40px">
<el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis" @clear="clearRedis" clearable> <el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis" @clear="clearRedis" clearable>
<el-option v-for="item in redisList" :key="item.id" :label="item.host" :value="item.id"> <el-option v-for="item in redisList" :key="item.id" :label="item.host" :value="item.id"> </el-option>
<span style="float: left">{{ item.host }}</span> </el-select>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ </el-form-item>
`库: [${item.db}]` <el-form-item label="库" label-width="20px">
}}</span> <el-select v-model="scanParam.db" @change="changeDb" placeholder="库" style="width: 70px">
</el-option> <el-option v-for="db in dbList" :key="db" :label="db" :value="db"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</template> </template>
@@ -61,7 +61,7 @@
<el-tag :color="getTypeColor(scope.row.type)" size="small">{{ scope.row.type }}</el-tag> <el-tag :color="getTypeColor(scope.row.type)" size="small">{{ scope.row.type }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="ttl" label="ttl(过期时间)" width="130"> <el-table-column prop="ttl" label="ttl(过期时间)" width="140">
<template #default="scope"> <template #default="scope">
{{ ttlConveter(scope.row.ttl) }} {{ ttlConveter(scope.row.ttl) }}
</template> </template>
@@ -83,6 +83,7 @@
:title="dataEdit.title" :title="dataEdit.title"
:keyInfo="dataEdit.keyInfo" :keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id" :redisId="scanParam.id"
:db="scanParam.db"
@cancel="onCancelDataEdit" @cancel="onCancelDataEdit"
@valChange="searchKey" @valChange="searchKey"
/> />
@@ -93,6 +94,7 @@
:title="dataEdit.title" :title="dataEdit.title"
:keyInfo="dataEdit.keyInfo" :keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id" :redisId="scanParam.id"
:db="scanParam.db"
@cancel="onCancelDataEdit" @cancel="onCancelDataEdit"
@valChange="searchKey" @valChange="searchKey"
/> />
@@ -102,6 +104,7 @@
:title="dataEdit.title" :title="dataEdit.title"
:keyInfo="dataEdit.keyInfo" :keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id" :redisId="scanParam.id"
:db="scanParam.db"
:operationType="dataEdit.operationType" :operationType="dataEdit.operationType"
@valChange="searchKey" @valChange="searchKey"
@cancel="onCancelDataEdit" @cancel="onCancelDataEdit"
@@ -112,6 +115,7 @@
:title="dataEdit.title" :title="dataEdit.title"
:keyInfo="dataEdit.keyInfo" :keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id" :redisId="scanParam.id"
:db="scanParam.db"
:operationType="dataEdit.operationType" :operationType="dataEdit.operationType"
@valChange="searchKey" @valChange="searchKey"
@cancel="onCancelDataEdit" @cancel="onCancelDataEdit"
@@ -143,11 +147,13 @@ export default defineComponent({
const state = reactive({ const state = reactive({
loading: false, loading: false,
redisList: [], redisList: [],
dbList: [],
query: { query: {
envId: 0, envId: 0,
}, },
scanParam: { scanParam: {
id: null, id: null,
db: null,
match: null, match: null,
count: 10, count: 10,
cursor: {}, cursor: {},
@@ -194,6 +200,14 @@ export default defineComponent({
const changeRedis = (id: number) => { const changeRedis = (id: number) => {
resetScanParam(id); 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.keys = [];
state.dbsize = 0; state.dbsize = 0;
searchKey(); searchKey();
@@ -230,6 +244,7 @@ export default defineComponent({
state.redisList = []; state.redisList = [];
state.scanParam.id = null; state.scanParam.id = null;
resetScanParam(); resetScanParam();
state.scanParam.db = null;
state.keys = []; state.keys = [];
state.dbsize = 0; state.dbsize = 0;
}; };
@@ -247,7 +262,7 @@ export default defineComponent({
const redis: any = state.redisList.find((x: any) => x.id == id); const redis: any = state.redisList.find((x: any) => x.id == id);
// 集群模式count设小点因为后端会从所有master节点scan一遍然后合并结果 // 集群模式count设小点因为后端会从所有master节点scan一遍然后合并结果
if (redis && redis.mode == 'cluster') { if (redis && redis.mode == 'cluster') {
state.scanParam.count = 5; state.scanParam.count = 4;
} }
} }
state.scanParam.match = null; state.scanParam.match = null;
@@ -310,6 +325,7 @@ export default defineComponent({
.request({ .request({
key, key,
id: state.scanParam.id, id: state.scanParam.id,
db: state.scanParam.db,
}) })
.then(() => { .then(() => {
ElMessage.success('删除成功!'); ElMessage.success('删除成功!');
@@ -371,6 +387,7 @@ export default defineComponent({
...toRefs(state), ...toRefs(state),
changeProjectEnv, changeProjectEnv,
changeRedis, changeRedis,
changeDb,
clearRedis, clearRedis,
searchKey, searchKey,
scan, scan,

View File

@@ -81,6 +81,10 @@ export default defineComponent({
type: [Number], type: [Number],
require: true, require: true,
}, },
db: {
type: [Number],
require: true,
},
keyInfo: { keyInfo: {
type: [Object], type: [Object],
}, },
@@ -94,6 +98,7 @@ export default defineComponent({
dialogVisible: false, dialogVisible: false,
operationType: 1, operationType: 1,
redisId: 0, redisId: 0,
db: 0,
key: { key: {
key: '', key: '',
type: 'hash', type: 'hash',
@@ -102,6 +107,7 @@ export default defineComponent({
scanParam: { scanParam: {
key: '', key: '',
id: 0, id: 0,
db: 0,
cursor: 0, cursor: 0,
match: '', match: '',
count: 10, count: 10,
@@ -127,6 +133,7 @@ export default defineComponent({
watch(props, async (newValue) => { watch(props, async (newValue) => {
const visible = newValue.visible; const visible = newValue.visible;
state.redisId = newValue.redisId; state.redisId = newValue.redisId;
state.db = newValue.db;
state.key = newValue.keyInfo; state.key = newValue.keyInfo;
state.operationType = newValue.operationType; state.operationType = newValue.operationType;
@@ -141,6 +148,7 @@ export default defineComponent({
const reHscan = async () => { const reHscan = async () => {
state.scanParam.id = state.redisId; state.scanParam.id = state.redisId;
state.scanParam.db = state.db;
state.scanParam.cursor = 0; state.scanParam.cursor = 0;
hscan(); hscan();
}; };
@@ -186,6 +194,7 @@ export default defineComponent({
}); });
await redisApi.hdel.request({ await redisApi.hdel.request({
id: state.redisId, id: state.redisId,
db: state.db,
key: state.key.key, key: state.key.key,
field, field,
}); });
@@ -196,6 +205,7 @@ export default defineComponent({
const hset = async (row: any) => { const hset = async (row: any) => {
await redisApi.saveHashValue.request({ await redisApi.saveHashValue.request({
id: state.redisId, id: state.redisId,
db: state.db,
key: state.key.key, key: state.key.key,
timed: state.key.timed, timed: state.key.timed,
value: [ value: [
@@ -215,7 +225,7 @@ export default defineComponent({
const saveValue = async () => { const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空'); notEmpty(state.key.key, 'key不能为空');
isTrue(state.hashValues.length > 0, 'hash内容不能为空'); 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); Object.assign(sv, state.key);
await redisApi.saveHashValue.request(sv); await redisApi.saveHashValue.request(sv);
ElMessage.success('保存成功'); ElMessage.success('保存成功');

View File

@@ -74,6 +74,10 @@ export default defineComponent({
type: [Number], type: [Number],
require: true, require: true,
}, },
db: {
type: [Number],
require: true,
},
keyInfo: { keyInfo: {
type: [Object], type: [Object],
}, },
@@ -91,6 +95,7 @@ export default defineComponent({
dialogVisible: false, dialogVisible: false,
operationType: 1, operationType: 1,
redisId: '', redisId: '',
db: 0,
key: { key: {
key: '', key: '',
type: 'string', type: 'string',
@@ -121,6 +126,7 @@ export default defineComponent({
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
state.key = newValue.key; state.key = newValue.key;
state.redisId = newValue.redisId; state.redisId = newValue.redisId;
state.db = newValue.db;
state.key = newValue.keyInfo; state.key = newValue.keyInfo;
state.operationType = newValue.operationType; state.operationType = newValue.operationType;
// 如果是查看编辑操作,则获取值 // 如果是查看编辑操作,则获取值
@@ -134,6 +140,7 @@ export default defineComponent({
const pageSize = state.pageSize; const pageSize = state.pageSize;
const res = await redisApi.getListValue.request({ const res = await redisApi.getListValue.request({
id: state.redisId, id: state.redisId,
db: state.db,
key: state.key.key, key: state.key.key,
start: (pageNum - 1) * pageSize, start: (pageNum - 1) * pageSize,
stop: pageNum * pageSize - 1, stop: pageNum * pageSize - 1,
@@ -149,6 +156,7 @@ export default defineComponent({
const lset = async (row: any, rowIndex: number) => { const lset = async (row: any, rowIndex: number) => {
await redisApi.setListValue.request({ await redisApi.setListValue.request({
id: state.redisId, id: state.redisId,
db: state.db,
key: state.key.key, key: state.key.key,
index: (state.pageNum - 1) * state.pageSize + rowIndex, index: (state.pageNum - 1) * state.pageSize + rowIndex,
value: row.value, value: row.value,

View File

@@ -45,7 +45,9 @@
> >
</el-form-item> </el-form-item>
<el-form-item prop="db" label="库号:" required> <el-form-item prop="db" label="库号:" required>
<el-input v-model.number="form.db" placeholder="请输入库号"></el-input> <el-select @change="changeDb" v-model="dbList" multiple placeholder="请选择可操作库号" style="width: 100%">
<el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db" :label="db" :value="db" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item prop="remark" label="备注:"> <el-form-item prop="remark" label="备注:">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input> <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
@@ -116,6 +118,7 @@ export default defineComponent({
mode: 'standalone', mode: 'standalone',
host: '', host: '',
password: null, password: null,
db: '',
project: null, project: null,
projectId: null, projectId: null,
envId: null, envId: null,
@@ -124,6 +127,7 @@ export default defineComponent({
enableSshTunnel: null, enableSshTunnel: null,
sshTunnelMachineId: null, sshTunnelMachineId: null,
}, },
dbList: [0],
pwd: '', pwd: '',
btnLoading: false, btnLoading: false,
rules: { rules: {
@@ -151,14 +155,14 @@ export default defineComponent({
db: [ db: [
{ {
required: true, required: true,
message: '请输入库号', message: '请选择库号',
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
mode: [ mode: [
{ {
required: true, required: true,
message: '请输入模式', message: '请选择模式',
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
@@ -174,13 +178,26 @@ export default defineComponent({
if (newValue.redis) { if (newValue.redis) {
getEnvs(newValue.redis.projectId); getEnvs(newValue.redis.projectId);
state.form = { ...newValue.redis }; state.form = { ...newValue.redis };
convertDb(state.form.db);
} else { } else {
state.envs = []; state.envs = [];
state.form = { db: 0, enableSshTunnel: -1 } as any; state.form = { db: '0', enableSshTunnel: -1 } as any;
state.dbList = [];
} }
getSshTunnelMachines(); 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 () => { const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) { if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 }); const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
@@ -250,6 +267,7 @@ export default defineComponent({
return { return {
...toRefs(state), ...toRefs(state),
redisForm, redisForm,
changeDb,
getSshTunnelMachines, getSshTunnelMachines,
getPwd, getPwd,
changeProject, changeProject,

View File

@@ -20,7 +20,7 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="90"> <el-table-column label="操作" width="90">
<template #default="scope"> <template #default="scope">
<el-button type="danger" @click="set.value.splice(scope.$index, 1)" icon="delete" size="small" plain>删除</el-button> <el-button type="danger" @click="value.splice(scope.$index, 1)" icon="delete" size="small" plain>删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -53,6 +53,10 @@ export default defineComponent({
type: [Number], type: [Number],
require: true, require: true,
}, },
db: {
type: [Number],
require: true,
},
keyInfo: { keyInfo: {
type: [Object], type: [Object],
}, },
@@ -70,6 +74,7 @@ export default defineComponent({
dialogVisible: false, dialogVisible: false,
operationType: 1, operationType: 1,
redisId: '', redisId: '',
db: 0,
key: { key: {
key: '', key: '',
type: 'string', type: 'string',
@@ -95,6 +100,7 @@ export default defineComponent({
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
state.key = newValue.key; state.key = newValue.key;
state.redisId = newValue.redisId; state.redisId = newValue.redisId;
state.db = newValue.db;
state.key = newValue.keyInfo; state.key = newValue.keyInfo;
state.operationType = newValue.operationType; state.operationType = newValue.operationType;
// 如果是查看编辑操作,则获取值 // 如果是查看编辑操作,则获取值
@@ -106,6 +112,7 @@ export default defineComponent({
const getSetValue = async () => { const getSetValue = async () => {
const res = await redisApi.getSetValue.request({ const res = await redisApi.getSetValue.request({
id: state.redisId, id: state.redisId,
db: state.db,
key: state.key.key, key: state.key.key,
}); });
state.value = res.map((x: any) => { state.value = res.map((x: any) => {
@@ -118,7 +125,7 @@ export default defineComponent({
const saveValue = async () => { const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空'); notEmpty(state.key.key, 'key不能为空');
isTrue(state.value.length > 0, 'set内容不能为空'); 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); Object.assign(sv, state.key);
await redisApi.saveSetValue.request(sv); await redisApi.saveSetValue.request(sv);

View File

@@ -48,6 +48,10 @@ export default defineComponent({
type: [Number], type: [Number],
require: true, require: true,
}, },
db: {
type: [Number],
require: true,
},
keyInfo: { keyInfo: {
type: [Object], type: [Object],
}, },
@@ -62,6 +66,7 @@ export default defineComponent({
dialogVisible: false, dialogVisible: false,
operationType: 1, operationType: 1,
redisId: '', redisId: '',
db: 0,
key: { key: {
key: '', key: '',
type: 'string', type: 'string',
@@ -101,12 +106,20 @@ export default defineComponent({
} }
); );
watch(
() => props.db,
(val) => {
state.db = val;
}
);
watch(props, async (newValue) => { watch(props, async (newValue) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
state.key = newValue.key; state.key = newValue.key;
state.redisId = newValue.redisId; state.redisId = newValue.redisId;
state.db = newValue.db;
state.key = newValue.keyInfo; state.key = newValue.keyInfo;
state.operationType = newValue.operationType state.operationType = newValue.operationType;
// 如果是查看编辑操作,则获取值 // 如果是查看编辑操作,则获取值
if (state.dialogVisible && state.operationType == 2) { if (state.dialogVisible && state.operationType == 2) {
getStringValue(); getStringValue();
@@ -116,6 +129,7 @@ export default defineComponent({
const getStringValue = async () => { const getStringValue = async () => {
state.string.value = await redisApi.getStringValue.request({ state.string.value = await redisApi.getStringValue.request({
id: state.redisId, id: state.redisId,
db: state.db,
key: state.key.key, key: state.key.key,
}); });
}; };
@@ -124,7 +138,7 @@ export default defineComponent({
notEmpty(state.key.key, 'key不能为空'); notEmpty(state.key.key, 'key不能为空');
notEmpty(state.string.value, 'value不能为空'); 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); Object.assign(sv, state.key);
await redisApi.saveStringValue.request(sv); await redisApi.saveStringValue.request(sv);
ElMessage.success('数据保存成功'); ElMessage.success('数据保存成功');

View File

@@ -8,19 +8,19 @@ export const redisApi = {
saveRedis: Api.create("/redis", 'post'), saveRedis: Api.create("/redis", 'post'),
delRedis: Api.create("/redis/{id}", 'delete'), delRedis: Api.create("/redis/{id}", 'delete'),
// 获取权限列表 // 获取权限列表
scan: Api.create("/redis/{id}/scan", 'post'), scan: Api.create("/redis/{id}/{db}/scan", 'post'),
getStringValue: Api.create("/redis/{id}/string-value", 'get'), getStringValue: Api.create("/redis/{id}/{db}/string-value", 'get'),
saveStringValue: Api.create("/redis/{id}/string-value", 'post'), saveStringValue: Api.create("/redis/{id}/{db}/string-value", 'post'),
getHashValue: Api.create("/redis/{id}/hash-value", 'get'), getHashValue: Api.create("/redis/{id}/{db}/hash-value", 'get'),
hscan: Api.create("/redis/{id}/hscan", 'get'), hscan: Api.create("/redis/{id}/{db}/hscan", 'get'),
hget: Api.create("/redis/{id}/hget", 'get'), hget: Api.create("/redis/{id}/{db}/hget", 'get'),
hdel: Api.create("/redis/{id}/hdel", 'delete'), hdel: Api.create("/redis/{id}/{db}/hdel", 'delete'),
saveHashValue: Api.create("/redis/{id}/hash-value", 'post'), saveHashValue: Api.create("/redis/{id}/{db}/hash-value", 'post'),
getSetValue: Api.create("/redis/{id}/set-value", 'get'), getSetValue: Api.create("/redis/{id}/{db}/set-value", 'get'),
saveSetValue: Api.create("/redis/{id}/set-value", 'post'), saveSetValue: Api.create("/redis/{id}/{db}/set-value", 'post'),
del: Api.create("/redis/{id}/scan/{cursor}/{count}", 'delete'), del: Api.create("/redis/{id}/{db}/scan/{cursor}/{count}", 'delete'),
delKey: Api.create("/redis/{id}/key", 'delete'), delKey: Api.create("/redis/{id}/{db}/key", 'delete'),
getListValue: Api.create("/redis/{id}/list-value", 'get'), getListValue: Api.create("/redis/{id}/{db}/list-value", 'get'),
saveListValue: Api.create("/redis/{id}/list-value", 'post'), saveListValue: Api.create("/redis/{id}/{db}/list-value", 'post'),
setListValue: Api.create("/redis/{id}/list-value/lset", 'post'), setListValue: Api.create("/redis/{id}/{db}/list-value/lset", 'post'),
} }

View File

@@ -5,7 +5,7 @@ type Redis struct {
Host string `binding:"required" json:"host"` Host string `binding:"required" json:"host"`
Password string `json:"password"` Password string `json:"password"`
Mode string `json:"mode"` Mode string `json:"mode"`
Db int `json:"db"` Db string `json:"db"`
EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道 EnableSshTunnel int8 `json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id SshTunnelMachineId uint64 `json:"sshTunnelMachineId"` // ssh隧道机器id
ProjectId uint64 `binding:"required" json:"projectId"` ProjectId uint64 `binding:"required" json:"projectId"`

View File

@@ -66,7 +66,8 @@ func (r *Redis) DeleteRedis(rc *ctx.ReqCtx) {
} }
func (r *Redis) RedisInfo(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 res string
var err error var err error
@@ -137,7 +138,7 @@ func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) { func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
g := rc.GinCtx 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, "非集群模式") biz.IsEquals(ri.Mode, entity.RedisModeCluster, "非集群模式")
info, _ := ri.ClusterCli.ClusterInfo(context.Background()).Result() info, _ := ri.ClusterCli.ClusterInfo(context.Background()).Result()
nodesStr, _ := ri.ClusterCli.ClusterNodes(context.Background()).Result() nodesStr, _ := ri.ClusterCli.ClusterNodes(context.Background()).Result()
@@ -182,7 +183,7 @@ func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
// scan获取redis的key列表信息 // scan获取redis的key列表信息
func (r *Redis) Scan(rc *ctx.ReqCtx) { func (r *Redis) Scan(rc *ctx.ReqCtx) {
g := rc.GinCtx 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") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
form := &form.RedisScanForm{} form := &form.RedisScanForm{}
@@ -261,7 +262,7 @@ func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
key := g.Query("key") key := g.Query("key")
biz.NotEmpty(key, "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") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
rc.ReqParam = key rc.ReqParam = key
@@ -273,7 +274,7 @@ func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
key := g.Query("key") key := g.Query("key")
biz.NotEmpty(key, "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") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
return ri, key return ri, key
@@ -291,7 +292,7 @@ func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
keyValue := new(form.StringValue) keyValue := new(form.StringValue)
ginx.BindJsonAndValid(g, keyValue) 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") 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() 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) hashValue := new(form.HashValue)
ginx.BindJsonAndValid(g, 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") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
cmd := ri.GetCmdable() cmd := ri.GetCmdable()
@@ -370,7 +371,7 @@ func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
keyvalue := new(form.SetValue) keyvalue := new(form.SetValue)
ginx.BindJsonAndValid(g, keyvalue) 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") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
cmd := ri.GetCmdable() cmd := ri.GetCmdable()
@@ -409,7 +410,7 @@ func (r *Redis) SaveListValue(rc *ctx.ReqCtx) {
listValue := new(form.ListValue) listValue := new(form.ListValue)
ginx.BindJsonAndValid(g, 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") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
cmd := ri.GetCmdable() cmd := ri.GetCmdable()
@@ -429,7 +430,7 @@ func (r *Redis) SetListValue(rc *ctx.ReqCtx) {
listSetValue := new(form.ListSetValue) listSetValue := new(form.ListSetValue)
ginx.BindJsonAndValid(g, 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") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result() _, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()

View File

@@ -6,7 +6,7 @@ type Redis struct {
Id *int64 `json:"id"` Id *int64 `json:"id"`
// Name *string `json:"name"` // Name *string `json:"name"`
Host *string `json:"host"` Host *string `json:"host"`
Db int `json:"db"` Db string `json:"db"`
ProjectId *int64 `json:"projectId"` ProjectId *int64 `json:"projectId"`
Project *string `json:"project"` Project *string `json:"project"`
Mode *string `json:"mode"` Mode *string `json:"mode"`

View File

@@ -14,6 +14,7 @@ import (
"mayfly-go/pkg/model" "mayfly-go/pkg/model"
"mayfly-go/pkg/utils" "mayfly-go/pkg/utils"
"net" "net"
"strconv"
"strings" "strings"
"time" "time"
@@ -38,7 +39,9 @@ type Redis interface {
Delete(id uint64) Delete(id uint64)
// 获取数据库连接实例 // 获取数据库连接实例
GetRedisInstance(id uint64) *RedisInstance // id: 数据库实例id
// db: 库号
GetRedisInstance(id uint64, db int) *RedisInstance
} }
func newRedisApp(redisRepo repository.Redis) Redis { 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) err := r.GetRedisBy(oldRedis)
if re.Id == 0 { if re.Id == 0 {
biz.IsTrue(err != nil, "该已存在") biz.IsTrue(err != nil, "该实例已存在")
re.PwdEncrypt() re.PwdEncrypt()
r.redisRepo.Insert(re) r.redisRepo.Insert(re)
} else { } else {
// 如果存在该库,则校验修改的库是否为该库 // 如果存在该库,则校验修改的库是否为该库
if err == nil { 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() re.PwdEncrypt()
r.redisRepo.Update(re) r.redisRepo.Update(re)
} }
@@ -98,16 +106,22 @@ func (r *redisAppImpl) Save(re *entity.Redis) {
// 删除Redis信息 // 删除Redis信息
func (r *redisAppImpl) Delete(id uint64) { 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) r.redisRepo.Delete(id)
} }
// 获取数据库连接实例 // 获取数据库连接实例
func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance { func (r *redisAppImpl) GetRedisInstance(id uint64, db int) *RedisInstance {
// Id不为0则为需要缓存 // Id不为0则为需要缓存
needCache := id != 0 needCache := id != 0
if needCache { if needCache {
load, ok := redisCache.Get(id) load, ok := redisCache.Get(getRedisCacheKey(id, db))
if ok { if ok {
return load.(*RedisInstance) return load.(*RedisInstance)
} }
@@ -120,7 +134,7 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
redisMode := re.Mode redisMode := re.Mode
var ri *RedisInstance var ri *RedisInstance
if redisMode == "" || redisMode == entity.RedisModeStandalone { if redisMode == "" || redisMode == entity.RedisModeStandalone {
ri = getRedisCient(re) ri = getRedisCient(re, db)
// 测试连接 // 测试连接
_, e := ri.Cli.Ping(context.Background()).Result() _, e := ri.Cli.Ping(context.Background()).Result()
if e != nil { if e != nil {
@@ -136,7 +150,7 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
panic(biz.NewBizErr(fmt.Sprintf("redis集群连接失败: %s", e.Error()))) panic(biz.NewBizErr(fmt.Sprintf("redis集群连接失败: %s", e.Error())))
} }
} else if redisMode == entity.RedisModeSentinel { } else if redisMode == entity.RedisModeSentinel {
ri = getRedisSentinelCient(re) ri = getRedisSentinelCient(re, db)
// 测试连接 // 测试连接
_, e := ri.Cli.Ping(context.Background()).Result() _, e := ri.Cli.Ping(context.Background()).Result()
if e != nil { if e != nil {
@@ -147,18 +161,23 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
global.Log.Infof("连接redis: %s", re.Host) global.Log.Infof("连接redis: %s", re.Host)
if needCache { if needCache {
redisCache.Put(re.Id, ri) redisCache.Put(getRedisCacheKey(id, db), ri)
} }
return ri return ri
} }
func getRedisCient(re *entity.Redis) *RedisInstance { // 生成redis连接缓存key
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode} 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{ redisOptions := &redis.Options{
Addr: re.Host, Addr: re.Host,
Password: re.Password, // no password set Password: re.Password, // no password set
DB: re.Db, // use default DB DB: db, // use default DB
DialTimeout: 8 * time.Second, DialTimeout: 8 * time.Second,
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines. ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
WriteTimeout: -1, WriteTimeout: -1,
@@ -172,7 +191,7 @@ func getRedisCient(re *entity.Redis) *RedisInstance {
} }
func getRedisClusterClient(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{ redisClusterOptions := &redis.ClusterOptions{
Addrs: strings.Split(re.Host, ","), Addrs: strings.Split(re.Host, ","),
@@ -187,15 +206,15 @@ func getRedisClusterClient(re *entity.Redis) *RedisInstance {
return ri return ri
} }
func getRedisSentinelCient(re *entity.Redis) *RedisInstance { func getRedisSentinelCient(re *entity.Redis, db int) *RedisInstance {
ri := &RedisInstance{Id: re.Id, ProjectId: re.ProjectId, Mode: re.Mode} ri := &RedisInstance{Id: getRedisCacheKey(re.Id, db), ProjectId: re.ProjectId, Mode: re.Mode}
// sentinel模式host为 masterName=host:port,host:port // sentinel模式host为 masterName=host:port,host:port
masterNameAndHosts := strings.Split(re.Host, "=") masterNameAndHosts := strings.Split(re.Host, "=")
sentinelOptions := &redis.FailoverOptions{ sentinelOptions := &redis.FailoverOptions{
MasterName: masterNameAndHosts[0], MasterName: masterNameAndHosts[0],
SentinelAddrs: strings.Split(masterNameAndHosts[1], ","), SentinelAddrs: strings.Split(masterNameAndHosts[1], ","),
Password: re.Password, // no password set Password: re.Password, // no password set
DB: re.Db, // use default DB DB: db, // use default DB
DialTimeout: 8 * time.Second, DialTimeout: 8 * time.Second,
ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines. ReadTimeout: -1, // Disable timeouts, because SSH does not support deadlines.
WriteTimeout: -1, WriteTimeout: -1,
@@ -231,8 +250,8 @@ var redisCache = cache.NewTimedCache(constant.RedisConnExpireTime, 5*time.Second
}) })
// 移除redis连接缓存并关闭redis连接 // 移除redis连接缓存并关闭redis连接
func CloseRedis(id uint64) { func CloseRedis(id uint64, db int) {
redisCache.Delete(id) redisCache.Delete(getRedisCacheKey(id, db))
} }
func init() { func init() {
@@ -250,8 +269,11 @@ func init() {
func TestRedisConnection(re *entity.Redis) { func TestRedisConnection(re *entity.Redis) {
var cmd redis.Cmdable var cmd redis.Cmdable
// 取第一个库测试连接即可
dbStr := strings.Split(re.Db, ",")[0]
db, _ := strconv.Atoi(dbStr)
if re.Mode == "" || re.Mode == entity.RedisModeStandalone { if re.Mode == "" || re.Mode == entity.RedisModeStandalone {
rcli := getRedisCient(re) rcli := getRedisCient(re, db)
defer rcli.Close() defer rcli.Close()
cmd = rcli.Cli cmd = rcli.Cli
} else if re.Mode == entity.RedisModeCluster { } else if re.Mode == entity.RedisModeCluster {
@@ -259,7 +281,7 @@ func TestRedisConnection(re *entity.Redis) {
defer ccli.Close() defer ccli.Close()
cmd = ccli.ClusterCli cmd = ccli.ClusterCli
} else if re.Mode == entity.RedisModeSentinel { } else if re.Mode == entity.RedisModeSentinel {
rcli := getRedisSentinelCient(re) rcli := getRedisSentinelCient(re, db)
defer rcli.Close() defer rcli.Close()
cmd = rcli.Cli cmd = rcli.Cli
} }
@@ -271,7 +293,7 @@ func TestRedisConnection(re *entity.Redis) {
// redis实例 // redis实例
type RedisInstance struct { type RedisInstance struct {
Id uint64 Id string
ProjectId uint64 ProjectId uint64
Mode string Mode string
Cli *redis.Client Cli *redis.Client

View File

@@ -11,7 +11,7 @@ type Redis struct {
Host string `orm:"column(host)" json:"host"` Host string `orm:"column(host)" json:"host"`
Mode string `json:"mode"` Mode string `json:"mode"`
Password string `orm:"column(password)" json:"-"` 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隧道 EnableSshTunnel int8 `orm:"column(enable_ssh_tunnel)" json:"enableSshTunnel"` // 是否启用ssh隧道
SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id SshTunnelMachineId uint64 `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string Remark string

View File

@@ -45,64 +45,64 @@ func InitRedisRouter(router *gin.RouterGroup) {
}) })
// 获取指定redis keys // 获取指定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) ctx.NewReqCtxWithGin(c).Handle(rs.Scan)
}) })
// 删除key // 删除key
deleteKeyL := ctx.NewLogInfo("redis删除key").WithSave(true) 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) ctx.NewReqCtxWithGin(c).WithLog(deleteKeyL).Handle(rs.DeleteKey)
}) })
// 获取string类型值 // 获取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) ctx.NewReqCtxWithGin(c).Handle(rs.GetStringValue)
}) })
// 设置string类型值 // 设置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) ctx.NewReqCtxWithGin(c).Handle(rs.SetStringValue)
}) })
// hscan // hscan
redis.GET(":id/hscan", func(c *gin.Context) { redis.GET(":id/:db/hscan", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).Handle(rs.Hscan) 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) 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) ctx.NewReqCtxWithGin(c).Handle(rs.Hdel)
}) })
// 设置hash类型值 // 设置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) ctx.NewReqCtxWithGin(c).Handle(rs.SetHashValue)
}) })
// 获取set类型值 // 获取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) ctx.NewReqCtxWithGin(c).Handle(rs.GetSetValue)
}) })
// 设置set类型值 // 设置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) ctx.NewReqCtxWithGin(c).Handle(rs.SetSetValue)
}) })
// 获取list类型值 // 获取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) 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) 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) ctx.NewReqCtxWithGin(c).Handle(rs.SetListValue)
}) })
} }

View File

@@ -262,7 +262,7 @@ CREATE TABLE `t_redis` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`host` varchar(255) COLLATE utf8mb4_bin NOT NULL, `host` varchar(255) COLLATE utf8mb4_bin NOT NULL,
`password` varchar(100) COLLATE utf8mb4_bin DEFAULT 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, `mode` varchar(32) DEFAULT NULL,
`enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道', `enable_ssh_tunnel` tinyint(2) DEFAULT NULL COMMENT '是否启用ssh隧道',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id', `ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',

View File

@@ -4,7 +4,7 @@ import "fmt"
const ( const (
AppName = "mayfly-go" AppName = "mayfly-go"
Version = "v1.2.11" Version = "v1.2.12"
) )
func GetAppInfo() string { func GetAppInfo() string {