feat: 调整单个数据库资源可配置多个数据库

This commit is contained in:
meilin.huang
2022-05-08 14:10:57 +08:00
parent 483f5b7604
commit 9db3db31be
26 changed files with 329 additions and 143 deletions

View File

@@ -1,14 +1,14 @@
<template> <template>
<div> <div>
<el-form class="search-form" label-position="right" :inline="true" label-width="60px"> <el-form class="search-form" label-position="right" :inline="true">
<el-form-item prop="project" label="项目" label-width="40px"> <el-form-item prop="project" label="项目" label-width="40px">
<el-select v-model="projectId" placeholder="请选择项目" @change="changeProject" filterable> <el-select v-model="projectId" placeholder="请选择项目" @change="changeProject" filterable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"></el-option> <el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="env" label="环境" label-width="40px"> <el-form-item prop="env" label="env" label-width="33px">
<el-select style="width: 100px" v-model="envId" placeholder="环境" @change="changeEnv" filterable> <el-select style="width: 80px" v-model="envId" placeholder="环境" @change="changeEnv" filterable>
<el-option v-for="item in envs" :key="item.id" :label="item.name" :value="item.id"> <el-option v-for="item in envs" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span> <span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.remark }}</span> <span style="float: right; color: #8492a6; font-size: 13px">{{ item.remark }}</span>
@@ -22,7 +22,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { toRefs, reactive, watch, defineComponent, onMounted } from 'vue'; import { toRefs, reactive, defineComponent, onMounted } from 'vue';
import { projectApi } from '../project/api'; import { projectApi } from '../project/api';
export default defineComponent({ export default defineComponent({
@@ -52,8 +52,6 @@ export default defineComponent({
envId: null, envId: null,
}); });
watch(props, (newValue, oldValue) => {});
onMounted(async () => { onMounted(async () => {
state.projects = await projectApi.accountProjects.request(null); state.projects = await projectApi.accountProjects.request(null);
}); });

View File

@@ -83,6 +83,9 @@ export default defineComponent({
dbId: { dbId: {
type: Number, type: Number,
}, },
db: {
type: String,
}
}, },
setup(props: any, { emit }) { setup(props: any, { emit }) {
const formRef: any = ref(); const formRef: any = ref();
@@ -202,6 +205,7 @@ export default defineComponent({
SqlExecBox({ SqlExecBox({
sql: sql, sql: sql,
dbId: props.dbId as any, dbId: props.dbId as any,
db: props.db,
runSuccessCallback: () => { runSuccessCallback: () => {
ElMessage.success('创建成功'); ElMessage.success('创建成功');
proxy.$parent.tableInfo({ id: props.dbId }); proxy.$parent.tableInfo({ id: props.dbId });

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="35%"> <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="35%">
<el-form :model="form" ref="dbForm" :rules="rules" label-width="85px"> <el-form :model="form" ref="dbForm" :rules="rules" label-width="85px">
<el-form-item prop="projectId" label="项目:" required> <el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable> <el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
@@ -30,24 +30,45 @@
<el-form-item prop="username" label="用户名:" required> <el-form-item prop="username" label="用户名:" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input> <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password" label="密码:" required> <el-form-item prop="password" label="密码:">
<el-input <el-input
type="password" type="password"
show-password show-password
v-model.trim="form.password" v-model.trim="form.password"
placeholder="请输入密码" placeholder="请输入密码,新增为必填项"
autocomplete="new-password" autocomplete="new-password"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="database" label="数据库名:" required> <el-form-item prop="database" label="数据库名:" required>
<el-input v-model.trim="form.database" placeholder="请输入数据库名"></el-input> <el-tag
v-for="db in databaseList"
:key="db"
class="ml5 mt5"
type="success"
effect="plain"
closable
:disable-transitions="false"
@close="handleClose(db)"
>
{{ db }}
</el-tag>
<el-input
v-if="inputDbVisible"
ref="InputDbRef"
v-model="inputDbValue"
style="width: 120px; margin-left: 5px; margin-top: 5px"
size="small"
@keyup.enter="handleInputDbConfirm"
@blur="handleInputDbConfirm"
/>
<el-button v-else class="ml5 mt5" size="small" @click="showInputDb"> + 添加数据库 </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button @click="cancel()"> </el-button> <el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -55,10 +76,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue'; import { toRefs, reactive, nextTick, watch, defineComponent, ref } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import { projectApi } from '../project/api.ts'; import { projectApi } from '../project/api.ts';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import type { ElInput } from 'element-plus';
export default defineComponent({ export default defineComponent({
name: 'DbEdit', name: 'DbEdit',
@@ -78,16 +100,22 @@ export default defineComponent({
}, },
setup(props: any, { emit }) { setup(props: any, { emit }) {
const dbForm: any = ref(null); const dbForm: any = ref(null);
const InputDbRef = ref<InstanceType<typeof ElInput>>();
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
projects: [], projects: [],
envs: [], envs: [],
databaseList: [] as any,
inputDbVisible: false,
inputDbValue: '',
form: { form: {
id: null, id: null,
name: null, name: null,
port: 3306, port: 3306,
username: null, username: null,
password: null, password: null,
database: '',
project: null, project: null,
projectId: null, projectId: null,
envId: null, envId: null,
@@ -144,17 +172,17 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
password: [ // password: [
{ // {
required: true, // required: true,
message: '请输入密码', // message: '请输入密码',
trigger: ['change', 'blur'], // trigger: ['change', 'blur'],
}, // },
], // ],
database: [ database: [
{ {
required: true, required: true,
message: '请输入数据库', message: '请添加数据库',
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
@@ -162,17 +190,47 @@ export default defineComponent({
}); });
watch(props, async (newValue) => { watch(props, async (newValue) => {
state.dialogVisible = newValue.visible;
state.projects = newValue.projects; state.projects = newValue.projects;
if (newValue.db) { if (newValue.db) {
getEnvs(newValue.db.projectId); getEnvs(newValue.db.projectId);
state.form = { ...newValue.db }; state.form = { ...newValue.db };
// 将数据库名使用空格切割,获取所有数据库列表
state.databaseList = newValue.db.database.split(' ');
} else { } else {
state.envs = []; state.envs = [];
state.form = { port: 3306 } as any; state.form = { port: 3306 } as any;
} }
state.dialogVisible = newValue.visible;
}); });
const handleClose = (db: string) => {
state.databaseList.splice(state.databaseList.indexOf(db), 1);
changeDatabase();
};
const showInputDb = () => {
state.inputDbVisible = true;
nextTick(() => {
InputDbRef.value!.input!.focus();
});
};
const handleInputDbConfirm = () => {
if (state.inputDbValue) {
state.databaseList.push(state.inputDbValue);
changeDatabase();
}
state.inputDbVisible = false;
state.inputDbValue = '';
};
/**
* 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加数据库
*/
const changeDatabase = () => {
state.form.database = state.databaseList.length == 0 ? '' : state.databaseList.join(' ');
};
const getEnvs = async (projectId: any) => { const getEnvs = async (projectId: any) => {
state.envs = await projectApi.projectEnvs.request({ projectId }); state.envs = await projectApi.projectEnvs.request({ projectId });
}; };
@@ -216,10 +274,17 @@ export default defineComponent({
}); });
}; };
const resetInputDb = () => {
state.inputDbVisible = false;
state.databaseList = [];
state.inputDbValue = '';
};
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
setTimeout(() => { setTimeout(() => {
resetInputDb();
dbForm.value.resetFields(); dbForm.value.resetFields();
// 重置对象属性为null // 重置对象属性为null
state.form = {} as any; state.form = {} as any;
@@ -229,6 +294,10 @@ export default defineComponent({
return { return {
...toRefs(state), ...toRefs(state),
dbForm, dbForm,
InputDbRef,
handleClose,
showInputDb,
handleInputDbConfirm,
changeProject, changeProject,
changeEnv, changeEnv,
btnOk, btnOk,

View File

@@ -14,9 +14,6 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item>
<el-input v-model="query.database" placeholder="请输入数据库" auto-complete="off" clearable></el-input>
</el-form-item>
<el-form-item> <el-form-item>
<el-button v-waves type="primary" icon="search" @click="search()">查询</el-button> <el-button v-waves type="primary" icon="search" @click="search()">查询</el-button>
</el-form-item> </el-form-item>
@@ -39,7 +36,20 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="type" label="类型" min-width="80"></el-table-column> <el-table-column prop="type" label="类型" min-width="80"></el-table-column>
<el-table-column prop="database" label="数据库" min-width="120"></el-table-column> <el-table-column prop="database" label="数据库" min-width="120">
<template #default="scope">
<el-tag
@click="showTableInfo(scope.row, db)"
effect="plain"
type="success"
size="small"
v-for="db in scope.row.dbs"
:key="db"
style="cursor: pointer"
>{{ db }}</el-tag
>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column> <el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column> <el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
@@ -49,11 +59,8 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column fixed="right" label="更多信息" min-width="100"> <!-- <el-table-column fixed="right" label="更多信息" min-width="100">
<template #default="scope"> </el-table-column> -->
<el-link @click.prevent="tableInfo(scope.row)" type="success">表信息</el-link>
</template>
</el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination
@@ -67,12 +74,7 @@
</el-row> </el-row>
</el-card> </el-card>
<el-dialog <el-dialog width="75%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
width="75%"
:title="`${chooseData ? chooseData.database : ''} 表信息`"
:before-close="closeTableInfo"
v-model="tableInfoDialog.visible"
>
<el-row class="mb10"> <el-row class="mb10">
<el-button type="primary" size="small" @click="tableCreateDialog.visible = true">创建表</el-button> <el-button type="primary" size="small" @click="tableCreateDialog.visible = true">创建表</el-button>
</el-row> </el-row>
@@ -173,6 +175,7 @@ export default defineComponent({
setup() { setup() {
const state = reactive({ const state = reactive({
dbId: 0, dbId: 0,
db: '',
permissions: { permissions: {
saveDb: 'db:save', saveDb: 'db:save',
delDb: 'db:del', delDb: 'db:del',
@@ -235,6 +238,11 @@ export default defineComponent({
const search = async () => { const search = async () => {
let res: any = await dbApi.dbs.request(state.query); let res: any = await dbApi.dbs.request(state.query);
// 切割数据库
res.list.forEach((e: any) => {
e.popoverSelectDbVisible = false;
e.dbs = e.database.split(' ');
});
state.datas = res.list; state.datas = res.list;
state.total = res.total; state.total = res.total;
}; };
@@ -247,10 +255,10 @@ export default defineComponent({
const editDb = (isAdd = false) => { const editDb = (isAdd = false) => {
if (isAdd) { if (isAdd) {
state.dbEditDialog.data = null; state.dbEditDialog.data = null;
state.dbEditDialog.title = '新增数据库'; state.dbEditDialog.title = '新增数据库资源';
} else { } else {
state.dbEditDialog.data = state.chooseData; state.dbEditDialog.data = state.chooseData;
state.dbEditDialog.title = '修改数据库'; state.dbEditDialog.title = '修改数据库资源';
} }
state.dbEditDialog.visible = true; state.dbEditDialog.visible = true;
}; };
@@ -274,9 +282,10 @@ export default defineComponent({
} catch (err) {} } catch (err) {}
}; };
const tableInfo = async (row: any) => { const showTableInfo = async (row: any, db: string) => {
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id }); state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
state.dbId = row.id; state.dbId = row.id;
state.db = db;
state.tableInfoDialog.visible = true; state.tableInfoDialog.visible = true;
}; };
@@ -289,6 +298,7 @@ export default defineComponent({
state.chooseTableName = row.tableName; state.chooseTableName = row.tableName;
state.columnDialog.columns = await dbApi.columnMetadata.request({ state.columnDialog.columns = await dbApi.columnMetadata.request({
id: state.chooseId, id: state.chooseId,
db: state.db,
tableName: row.tableName, tableName: row.tableName,
}); });
@@ -299,6 +309,7 @@ export default defineComponent({
state.chooseTableName = row.tableName; state.chooseTableName = row.tableName;
state.indexDialog.indexs = await dbApi.tableIndex.request({ state.indexDialog.indexs = await dbApi.tableIndex.request({
id: state.chooseId, id: state.chooseId,
db: state.db,
tableName: row.tableName, tableName: row.tableName,
}); });
@@ -309,6 +320,7 @@ export default defineComponent({
state.chooseTableName = row.tableName; state.chooseTableName = row.tableName;
const res = await dbApi.tableDdl.request({ const res = await dbApi.tableDdl.request({
id: state.chooseId, id: state.chooseId,
db: state.db,
tableName: row.tableName, tableName: row.tableName,
}); });
state.ddlDialog.ddl = res[0]['Create Table']; state.ddlDialog.ddl = res[0]['Create Table'];
@@ -329,8 +341,9 @@ export default defineComponent({
SqlExecBox({ SqlExecBox({
sql: `DROP TABLE ${tableName}`, sql: `DROP TABLE ${tableName}`,
dbId: state.chooseId, dbId: state.chooseId,
db: state.db,
runSuccessCallback: async () => { runSuccessCallback: async () => {
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId }); state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId, db: state.db });
}, },
}); });
} catch (err) {} } catch (err) {}
@@ -345,7 +358,7 @@ export default defineComponent({
editDb, editDb,
valChange, valChange,
deleteDb, deleteDb,
tableInfo, showTableInfo,
closeTableInfo, closeTableInfo,
showColumns, showColumns,
showTableIndex, showTableIndex,

View File

@@ -7,21 +7,33 @@
<div class="toolbar"> <div class="toolbar">
<el-row type="flex" justify="space-between"> <el-row type="flex" justify="space-between">
<el-col :span="24"> <el-col :span="24">
<project-env-select @changeProjectEnv="changeProjectEnv" @clear="clearDb"> <project-env-select @changeProjectEnv="changeProjectEnv">
<template #default> <template #default>
<el-form-item label="数据库"> <el-form-item label="资源">
<el-select v-model="dbId" placeholder="请选择数据库" @change="changeDb" @clear="clearDb" clearable filterable> <el-select
<el-option v-for="item in dbs" :key="item.id" :label="item.database" :value="item.id"> v-model="dbId"
<span style="float: left">{{ item.database }}</span> placeholder="请选择资源实例"
@change="changeDbInstance"
filterable
style="width: 150px"
>
<el-option v-for="item in dbs" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ <span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{
`${item.name} [${item.type}]` `${item.host}:${item.port} ${item.type}`
}}</span> }}</span>
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label-width="40" label=""> <el-form-item label="数据库">
<el-select v-model="tableName" placeholder="选择表查看表数据" @change="changeTable" filterable style="width: 300px"> <el-select v-model="db" placeholder="选择数据" @change="changeDb" @clear="clearDb" clearable filterable style="width: 150px">
<el-option v-for="item in databaseList" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
<el-form-item label-width="20" label="表">
<el-select v-model="tableName" placeholder="选择表查看表数据" @change="changeTable" filterable style="width: 250px">
<el-option <el-option
v-for="item in tableMetadata" v-for="item in tableMetadata"
:key="item.tableName" :key="item.tableName"
@@ -139,7 +151,12 @@
</el-tooltip> </el-tooltip>
</el-row> </el-row>
<el-row class="mt5"> <el-row class="mt5">
<el-input v-model="dt.condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可" clearable size="small"> <el-input
v-model="dt.condition"
placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可"
clearable
size="small"
>
<template #prepend> <template #prepend>
<el-popover trigger="click" :width="270" placement="right"> <el-popover trigger="click" :width="270" placement="right">
<template #reference> <template #reference>
@@ -245,9 +262,11 @@ export default defineComponent({
const state = reactive({ const state = reactive({
token: token, token: token,
defalutLimit: 25, // 默认查询数量 defalutLimit: 25, // 默认查询数量
dbs: [], dbs: [], // 数据库实例列表
databaseList: [], // 数据库实例拥有的数据库列表1数据库实例 -> 多数据库
db: '', // 当前操作的数据库
tables: [], tables: [],
dbId: null, dbId: null, // 当前选中操作的数据库实例
tableName: '', tableName: '',
tableMetadata: [], tableMetadata: [],
columnMetadata: [], columnMetadata: [],
@@ -344,6 +363,8 @@ export default defineComponent({
const changeProjectEnv = (projectId: any, envId: any) => { const changeProjectEnv = (projectId: any, envId: any) => {
state.dbs = []; state.dbs = [];
state.dbId = null; state.dbId = null;
state.db = '';
state.databaseList = [];
clearDb(); clearDb();
if (envId != null) { if (envId != null) {
state.params.envId = envId; state.params.envId = envId;
@@ -403,6 +424,7 @@ export default defineComponent({
const runSql = (sql: string) => { const runSql = (sql: string) => {
return dbApi.sqlExec.request({ return dbApi.sqlExec.request({
id: state.dbId, id: state.dbId,
db: state.db,
sql: sql.trim(), sql: sql.trim(),
}); });
}; };
@@ -540,26 +562,35 @@ export default defineComponent({
return selectSql; return selectSql;
}; };
/**
* 选择数据库实例事件
*/
const changeDbInstance = (dbId: any) => {
state.db = '';
state.databaseList = (state.dbs.find((e: any) => e.id == dbId) as any).database.split(' ');
clearDb();
};
/** /**
* 更改数据库事件 * 更改数据库事件
*/ */
const changeDb = (id: number) => { const changeDb = (db: string) => {
if (!id) { if (!db) {
return; return;
} }
clearDb(); clearDb();
dbApi.tableMetadata.request({ id }).then((res) => { dbApi.tableMetadata.request({ id: state.dbId, db }).then((res) => {
state.tableMetadata = res; state.tableMetadata = res;
}); });
dbApi.hintTables dbApi.hintTables
.request({ .request({
id: state.dbId, id: state.dbId,
db,
}) })
.then((res) => { .then((res) => {
state.cmOptions.hintOptions.tables = res; state.cmOptions.hintOptions.tables = res;
}); });
getSqlNames(); getSqlNames();
}; };
@@ -631,6 +662,7 @@ export default defineComponent({
} }
columns = await dbApi.columnMetadata.request({ columns = await dbApi.columnMetadata.request({
id: state.dbId, id: state.dbId,
db: state.db,
tableName: tableName, tableName: tableName,
}); });
tableMap.set(tableName, columns); tableMap.set(tableName, columns);
@@ -709,7 +741,7 @@ export default defineComponent({
*/ */
const getUserSql = () => { const getUserSql = () => {
notBlank(state.dbId, '请先选择数据库'); notBlank(state.dbId, '请先选择数据库');
dbApi.getSql.request({ id: state.dbId, type: 1, name: state.sqlName }).then((res) => { dbApi.getSql.request({ id: state.dbId, type: 1, name: state.sqlName, db: state.db }).then((res) => {
if (res) { if (res) {
setCodermirrorValue(res.sql); setCodermirrorValue(res.sql);
} else { } else {
@@ -733,6 +765,7 @@ export default defineComponent({
dbApi.getSqlNames dbApi.getSqlNames
.request({ .request({
id: state.dbId, id: state.dbId,
db: state.db,
}) })
.then((res) => { .then((res) => {
if (res && res.length > 0) { if (res && res.length > 0) {
@@ -750,13 +783,14 @@ export default defineComponent({
const saveSql = async () => { const saveSql = async () => {
const sql = codemirror.getValue(); const sql = codemirror.getValue();
notEmpty(sql, 'sql内容不能为空'); notEmpty(sql, 'sql内容不能为空');
notBlank(state.dbId, '请先选择数据库'); notBlank(state.dbId, '请先选择数据库实例');
await dbApi.saveSql.request({ id: state.dbId, sql: sql, type: 1, name: state.sqlName }); await dbApi.saveSql.request({ id: state.dbId, db: state.db, sql: sql, type: 1, name: state.sqlName });
ElMessage.success('保存成功'); ElMessage.success('保存成功');
dbApi.getSqlNames dbApi.getSqlNames
.request({ .request({
id: state.dbId, id: state.dbId,
db: state.db,
}) })
.then((res) => { .then((res) => {
if (res) { if (res) {
@@ -773,7 +807,7 @@ export default defineComponent({
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}); });
await dbApi.deleteDbSql.request({ id: state.dbId, name: state.sqlName }); await dbApi.deleteDbSql.request({ id: state.dbId, name: state.sqlName, db: state.db });
ElMessage.success('删除成功'); ElMessage.success('删除成功');
getSqlNames(); getSqlNames();
} catch (err) {} } catch (err) {}
@@ -909,6 +943,7 @@ export default defineComponent({
SqlExecBox({ SqlExecBox({
sql: sql, sql: sql,
dbId: state.dbId as any, dbId: state.dbId as any,
db: state.db,
runSuccessCallback: successFunc, runSuccessCallback: successFunc,
cancelCallback: cancelFunc, cancelCallback: cancelFunc,
}); });
@@ -991,6 +1026,7 @@ export default defineComponent({
changeSqlTemplate, changeSqlTemplate,
deleteSql, deleteSql,
saveSql, saveSql,
changeDbInstance,
changeDb, changeDb,
clearDb, clearDb,
formatSql, formatSql,

View File

@@ -4,6 +4,7 @@ import SqlExecDialog from './SqlExecDialog.vue'
export type SqlExecProps = { export type SqlExecProps = {
sql: string sql: string
dbId: number, dbId: number,
db: string,
runSuccessCallback?: Function, runSuccessCallback?: Function,
cancelCallback?: Function cancelCallback?: Function
} }

View File

@@ -40,6 +40,9 @@ export default defineComponent({
dbId: { dbId: {
type: [Number], type: [Number],
}, },
db: {
type: String,
},
sql: { sql: {
type: String, type: String,
}, },
@@ -49,6 +52,7 @@ export default defineComponent({
dialogVisible: false, dialogVisible: false,
sqlValue: '', sqlValue: '',
dbId: 0, dbId: 0,
db: '',
btnLoading: false, btnLoading: false,
cmOptions: { cmOptions: {
tabSize: 4, tabSize: 4,
@@ -77,6 +81,7 @@ export default defineComponent({
state.btnLoading = true; state.btnLoading = true;
await dbApi.sqlExec.request({ await dbApi.sqlExec.request({
id: state.dbId, id: state.dbId,
db: state.db,
sql: state.sqlValue.trim(), sql: state.sqlValue.trim(),
}); });
runSuccess = true; runSuccess = true;
@@ -110,6 +115,7 @@ export default defineComponent({
cancelCallback = props.cancelCallback; cancelCallback = props.cancelCallback;
state.sqlValue = sqlFormatter(props.sql); state.sqlValue = sqlFormatter(props.sql);
state.dbId = props.dbId; state.dbId = props.dbId;
state.db = props.db;
state.dialogVisible = true; state.dialogVisible = true;
}; };

View File

@@ -32,8 +32,8 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button @click="cancel()"> </el-button> <el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -326,8 +326,6 @@ export default defineComponent({
return { return {
...toRefs(state), ...toRefs(state),
choose, choose,
// monitor,
// closeMonitor,
showTerminal, showTerminal,
openFormDialog, openFormDialog,
deleteMachine, deleteMachine,

View File

@@ -35,6 +35,7 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="cancel()" :disabled="submitDisabled" size="small"> </el-button>
<el-button <el-button
v-auth="'machine:script:save'" v-auth="'machine:script:save'"
type="primary" type="primary"
@@ -44,7 +45,6 @@
:disabled="submitDisabled" :disabled="submitDisabled"
> </el-button > </el-button
> >
<el-button @click="cancel()" :disabled="submitDisabled" size="small"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -71,8 +71,8 @@
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="addProject" type="primary"> </el-button>
<el-button @click="cancelAddProject()"> </el-button> <el-button @click="cancelAddProject()"> </el-button>
<el-button @click="addProject" type="primary"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -103,8 +103,8 @@
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button v-auth="permissions.saveEnv" @click="addEnv" type="primary" :loading="btnLoading"> </el-button>
<el-button @click="cancelAddEnv()"> </el-button> <el-button @click="cancelAddEnv()"> </el-button>
<el-button v-auth="permissions.saveEnv" @click="addEnv" type="primary" :loading="btnLoading"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -163,8 +163,8 @@
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button v-auth="permissions.saveMember" @click="addMember" type="primary" :loading="btnLoading"> </el-button>
<el-button @click="cancelAddMember()"> </el-button> <el-button @click="cancelAddMember()"> </el-button>
<el-button v-auth="permissions.saveMember" @click="addMember" type="primary" :loading="btnLoading"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -32,8 +32,8 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button @click="cancel()"> </el-button> <el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -8,8 +8,8 @@
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="saveValue" type="primary"> </el-button>
<el-button @click="cancel()"> </el-button> <el-button @click="cancel()"> </el-button>
<el-button @click="saveValue" type="primary"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -82,10 +82,12 @@
</el-row> </el-row>
</el-form> </el-form>
<div style="text-align: center" class="dialog-footer mt10"> <template #footer>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button> <div class="dialog-footer mt10">
<el-button @click="cancel()"> </el-button> <el-button @click="cancel()"> </el-button>
</div> <el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>

View File

@@ -19,8 +19,8 @@
</el-tree> </el-tree>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" @click="btnOk"> </el-button>
<el-button @click="cancel"> </el-button> <el-button @click="cancel"> </el-button>
<el-button type="primary" @click="btnOk"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -14,8 +14,8 @@
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button @click="cancel()"> </el-button> <el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@@ -31,7 +31,6 @@ func (d *Db) Dbs(rc *ctx.ReqCtx) {
g := rc.GinCtx g := rc.GinCtx
m := &entity.Db{EnvId: uint64(ginx.QueryInt(g, "envId", 0)), m := &entity.Db{EnvId: uint64(ginx.QueryInt(g, "envId", 0)),
ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)), ProjectId: uint64(ginx.QueryInt(g, "projectId", 0)),
Database: g.Query("database"),
} }
m.CreatorId = rc.LoginAccount.Id m.CreatorId = rc.LoginAccount.Id
rc.ResData = d.DbApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO)) rc.ResData = d.DbApp.GetPageList(m, ginx.GetPageParam(rc.GinCtx), new([]vo.SelectDataDbVO))
@@ -54,37 +53,38 @@ func (d *Db) DeleteDb(rc *ctx.ReqCtx) {
} }
func (d *Db) TableInfos(rc *ctx.ReqCtx) { func (d *Db) TableInfos(rc *ctx.ReqCtx) {
rc.ResData = d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).GetTableInfos() rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetTableInfos()
} }
func (d *Db) TableIndex(rc *ctx.ReqCtx) { func (d *Db) TableIndex(rc *ctx.ReqCtx) {
tn := rc.GinCtx.Query("tableName") tn := rc.GinCtx.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空") biz.NotEmpty(tn, "tableName不能为空")
rc.ResData = d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).GetTableIndex(tn) rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetTableIndex(tn)
} }
func (d *Db) GetCreateTableDdl(rc *ctx.ReqCtx) { func (d *Db) GetCreateTableDdl(rc *ctx.ReqCtx) {
tn := rc.GinCtx.Query("tableName") tn := rc.GinCtx.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空") biz.NotEmpty(tn, "tableName不能为空")
rc.ResData = d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)).GetCreateTableDdl(tn) rc.ResData = d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx)).GetCreateTableDdl(tn)
} }
// @router /api/db/:dbId/exec-sql [get] // @router /api/db/:dbId/exec-sql [get]
func (d *Db) ExecSql(rc *ctx.ReqCtx) { func (d *Db) ExecSql(rc *ctx.ReqCtx) {
g := rc.GinCtx g := rc.GinCtx
dbInstance := d.DbApp.GetDbInstance(GetDbId(g)) id, db := GetIdAndDb(g)
biz.IsTrue(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbInstance.ProjectId), "您无权操作该资源") dbInstance := d.DbApp.GetDbInstance(id, db)
biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbInstance.ProjectId), "%s")
// 去除前后空格及换行符 // 去除前后空格及换行符
sql := strings.TrimFunc(g.Query("sql"), func(r rune) bool { sql := strings.TrimFunc(g.Query("sql"), func(r rune) bool {
s := string(r) s := string(r)
return s == " " || s == "\n" return s == " " || s == "\n"
}) })
rc.ReqParam = sql rc.ReqParam = fmt.Sprintf("db: %d:%s | sql: %s", id, db, sql)
biz.NotEmpty(sql, "sql不能为空") biz.NotEmpty(sql, "sql不能为空")
if strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select") { if strings.HasPrefix(sql, "SELECT") || strings.HasPrefix(sql, "select") || strings.HasPrefix(sql, "show") {
colNames, res, err := dbInstance.SelectData(sql) colNames, res, err := dbInstance.SelectData(sql)
biz.ErrIsNilAppendErr(err, "查询失败: %s") biz.ErrIsNilAppendErr(err, "查询失败: %s")
colAndRes := make(map[string]interface{}) colAndRes := make(map[string]interface{})
@@ -119,10 +119,10 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
bytes, _ := ioutil.ReadAll(file) bytes, _ := ioutil.ReadAll(file)
sqlContent := string(bytes) sqlContent := string(bytes)
sqls := strings.Split(sqlContent, ";") sqls := strings.Split(sqlContent, ";")
dbId := GetDbId(g) dbId, db := GetIdAndDb(g)
go func() { go func() {
db := d.DbApp.GetDbInstance(dbId) db := d.DbApp.GetDbInstance(dbId, db)
dbEntity := d.DbApp.GetById(dbId) dbEntity := d.DbApp.GetById(dbId)
dbInfo := fmt.Sprintf("于%s的%s环境", dbEntity.Name, dbEntity.Env) dbInfo := fmt.Sprintf("于%s的%s环境", dbEntity.Name, dbEntity.Env)
@@ -136,7 +136,7 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
} }
}() }()
biz.IsTrue(d.ProjectApp.CanAccess(rc.LoginAccount.Id, db.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, db.ProjectId), "%s")
for _, sql := range sqls { for _, sql := range sqls {
sql = strings.Trim(sql, " ") sql = strings.Trim(sql, " ")
@@ -155,8 +155,8 @@ func (d *Db) ExecSqlFile(rc *ctx.ReqCtx) {
// @router /api/db/:dbId/t-metadata [get] // @router /api/db/:dbId/t-metadata [get]
func (d *Db) TableMA(rc *ctx.ReqCtx) { func (d *Db) TableMA(rc *ctx.ReqCtx) {
dbi := d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)) dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.IsTrue(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
rc.ResData = dbi.GetTableMetedatas() rc.ResData = dbi.GetTableMetedatas()
} }
@@ -166,15 +166,15 @@ func (d *Db) ColumnMA(rc *ctx.ReqCtx) {
tn := g.Query("tableName") tn := g.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空") biz.NotEmpty(tn, "tableName不能为空")
dbi := d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)) dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.IsTrue(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
rc.ResData = dbi.GetColumnMetadatas(tn) rc.ResData = dbi.GetColumnMetadatas(tn)
} }
// @router /api/db/:dbId/hint-tables [get] // @router /api/db/:dbId/hint-tables [get]
func (d *Db) HintTables(rc *ctx.ReqCtx) { func (d *Db) HintTables(rc *ctx.ReqCtx) {
dbi := d.DbApp.GetDbInstance(GetDbId(rc.GinCtx)) dbi := d.DbApp.GetDbInstance(GetIdAndDb(rc.GinCtx))
biz.IsTrue(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbi.ProjectId), "%s")
// 获取所有表 // 获取所有表
tables := dbi.GetTableMetedatas() tables := dbi.GetTableMetedatas()
@@ -225,7 +225,7 @@ func (d *Db) SaveSql(rc *ctx.ReqCtx) {
biz.ErrIsNil(err, "该数据库信息不存在") biz.ErrIsNil(err, "该数据库信息不存在")
// 获取用于是否有该dbsql的保存记录有则更改否则新增 // 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &entity.DbSql{Type: dbSqlForm.Type, DbId: dbId, Name: dbSqlForm.Name} dbSql := &entity.DbSql{Type: dbSqlForm.Type, DbId: dbId, Name: dbSqlForm.Name, Db: dbSqlForm.Db}
dbSql.CreatorId = account.Id dbSql.CreatorId = account.Id
e := model.GetBy(dbSql) e := model.GetBy(dbSql)
@@ -241,8 +241,9 @@ func (d *Db) SaveSql(rc *ctx.ReqCtx) {
// 获取所有保存的sql names // 获取所有保存的sql names
func (d *Db) GetSqlNames(rc *ctx.ReqCtx) { func (d *Db) GetSqlNames(rc *ctx.ReqCtx) {
id, db := GetIdAndDb(rc.GinCtx)
// 获取用于是否有该dbsql的保存记录有则更改否则新增 // 获取用于是否有该dbsql的保存记录有则更改否则新增
dbSql := &entity.DbSql{Type: 1, DbId: GetDbId(rc.GinCtx)} dbSql := &entity.DbSql{Type: 1, DbId: id, Db: db}
dbSql.CreatorId = rc.LoginAccount.Id dbSql.CreatorId = rc.LoginAccount.Id
var sqls []entity.DbSql var sqls []entity.DbSql
model.ListBy(dbSql, &sqls, "id", "name") model.ListBy(dbSql, &sqls, "id", "name")
@@ -262,8 +263,9 @@ func (d *Db) DeleteSql(rc *ctx.ReqCtx) {
// @router /api/db/:dbId/sql [get] // @router /api/db/:dbId/sql [get]
func (d *Db) GetSql(rc *ctx.ReqCtx) { func (d *Db) GetSql(rc *ctx.ReqCtx) {
id, db := GetIdAndDb(rc.GinCtx)
// 根据创建者id 数据库id以及sql模板名称查询保存的sql信息 // 根据创建者id 数据库id以及sql模板名称查询保存的sql信息
dbSql := &entity.DbSql{Type: 1, DbId: GetDbId(rc.GinCtx)} dbSql := &entity.DbSql{Type: 1, DbId: id, Db: db}
dbSql.CreatorId = rc.LoginAccount.Id dbSql.CreatorId = rc.LoginAccount.Id
dbSql.Name = rc.GinCtx.Query("name") dbSql.Name = rc.GinCtx.Query("name")
@@ -279,3 +281,9 @@ func GetDbId(g *gin.Context) uint64 {
biz.IsTrue(dbId > 0, "dbId错误") biz.IsTrue(dbId > 0, "dbId错误")
return uint64(dbId) return uint64(dbId)
} }
func GetIdAndDb(g *gin.Context) (uint64, string) {
db := g.Query("db")
biz.NotEmpty(db, "db不能为空")
return GetDbId(g), db
}

View File

@@ -7,7 +7,7 @@ type DbForm struct {
Host string `binding:"required" json:"host"` Host string `binding:"required" json:"host"`
Port int `binding:"required" json:"port"` Port int `binding:"required" json:"port"`
Username string `binding:"required" json:"username"` Username string `binding:"required" json:"username"`
Password string `binding:"required" json:"password"` Password string `json:"password"`
Database string `binding:"required" json:"database"` Database string `binding:"required" json:"database"`
ProjectId uint64 `binding:"required" json:"projectId"` ProjectId uint64 `binding:"required" json:"projectId"`
Project string `json:"project"` Project string `json:"project"`

View File

@@ -41,6 +41,7 @@ type DbSqlSaveForm struct {
Name string Name string
Sql string `binding:"required"` Sql string `binding:"required"`
Type int `binding:"required"` Type int `binding:"required"`
Db string `binding:"required"`
} }
type MachineFileUpdateForm struct { type MachineFileUpdateForm struct {

View File

@@ -104,7 +104,7 @@ func (m *Machine) GetProcess(rc *ctx.ReqCtx) {
cmd += "| head -n " + count cmd += "| head -n " + count
cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx)) cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.IsTrue(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
res, err := cli.Run(cmd) res, err := cli.Run(cmd)
biz.ErrIsNilAppendErr(err, "获取进程信息失败: %s") biz.ErrIsNilAppendErr(err, "获取进程信息失败: %s")
@@ -117,7 +117,7 @@ func (m *Machine) KillProcess(rc *ctx.ReqCtx) {
biz.NotEmpty(pid, "进程id不能为空") biz.NotEmpty(pid, "进程id不能为空")
cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx)) cli := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.IsTrue(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
_, err := cli.Run("kill -9 " + pid) _, err := cli.Run("kill -9 " + pid)
biz.ErrIsNilAppendErr(err, "终止进程失败: %s") biz.ErrIsNilAppendErr(err, "终止进程失败: %s")
@@ -145,7 +145,7 @@ func (m *Machine) WsSSH(g *gin.Context) {
rows := ginx.QueryInt(g, "rows", 40) rows := ginx.QueryInt(g, "rows", 40)
cli := m.MachineApp.GetCli(GetMachineId(g)) cli := m.MachineApp.GetCli(GetMachineId(g))
biz.IsTrue(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
sws, err := machine.NewLogicSshWsSession(cols, rows, cli, wsConn) sws, err := machine.NewLogicSshWsSession(cols, rows, cli, wsConn)
biz.ErrIsNilAppendErr(err, "连接失败:%s") biz.ErrIsNilAppendErr(err, "连接失败:%s")

View File

@@ -64,7 +64,7 @@ func (m *MachineScript) RunMachineScript(rc *ctx.ReqCtx) {
script = utils.TemplateParse(ms.Script, utils.Json2Map(params)) script = utils.TemplateParse(ms.Script, utils.Json2Map(params))
} }
cli := m.MachineApp.GetCli(machineId) cli := m.MachineApp.GetCli(machineId)
biz.IsTrue(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(m.ProjectApp.CanAccess(rc.LoginAccount.Id, cli.GetMachine().ProjectId), "%s")
res, err := cli.Run(script) res, err := cli.Run(script)
// 记录请求参数 // 记录请求参数

View File

@@ -86,7 +86,7 @@ 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")))
biz.IsTrue(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
keys, cursor := ri.Scan(uint64(ginx.PathParamInt(g, "cursor")), g.Query("match"), int64(ginx.PathParamInt(g, "count"))) keys, cursor := ri.Scan(uint64(ginx.PathParamInt(g, "cursor")), g.Query("match"), int64(ginx.PathParamInt(g, "count")))
@@ -126,7 +126,7 @@ func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
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")))
biz.IsTrue(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
rc.ReqParam = key rc.ReqParam = key
ri.Cli.Del(key) ri.Cli.Del(key)
@@ -138,7 +138,7 @@ func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
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")))
biz.IsTrue(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
return ri, key return ri, key
} }
@@ -163,7 +163,7 @@ func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
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")))
biz.IsTrue(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
str, err := ri.Cli.Set(keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result() str, err := ri.Cli.Set(keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s") biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
@@ -176,7 +176,7 @@ func (r *Redis) SetHashValue(rc *ctx.ReqCtx) {
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")))
biz.IsTrue(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
key := hashValue.Key key := hashValue.Key
// 简单处理->先删除,后新增 // 简单处理->先删除,后新增
@@ -203,7 +203,7 @@ func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
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")))
biz.IsTrue(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "您无权操作该资源") biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
key := keyvalue.Key key := keyvalue.Key
// 简单处理->先删除,后新增 // 简单处理->先删除,后新增

View File

@@ -8,6 +8,7 @@ import (
"mayfly-go/base/cache" "mayfly-go/base/cache"
"mayfly-go/base/global" "mayfly-go/base/global"
"mayfly-go/base/model" "mayfly-go/base/model"
"mayfly-go/base/utils"
"mayfly-go/server/devops/domain/entity" "mayfly-go/server/devops/domain/entity"
"mayfly-go/server/devops/domain/repository" "mayfly-go/server/devops/domain/repository"
"mayfly-go/server/devops/infrastructure/persistence" "mayfly-go/server/devops/infrastructure/persistence"
@@ -35,7 +36,9 @@ type Db interface {
Delete(id uint64) Delete(id uint64)
// 获取数据库连接实例 // 获取数据库连接实例
GetDbInstance(id uint64) *DbInstance // @param id 数据库实例id
// @param db 数据库
GetDbInstance(id uint64, db string) *DbInstance
} }
type dbAppImpl struct { type dbAppImpl struct {
@@ -71,29 +74,60 @@ func (d *dbAppImpl) Save(dbEntity *entity.Db) {
// 默认tcp连接 // 默认tcp连接
dbEntity.Network = "tcp" dbEntity.Network = "tcp"
// 测试连接 // 测试连接
TestConnection(dbEntity) if dbEntity.Password != "" {
TestConnection(*dbEntity)
}
// 查找是否存在该库 // 查找是否存在该库
oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, Database: dbEntity.Database} oldDb := &entity.Db{Host: dbEntity.Host, Port: dbEntity.Port, EnvId: dbEntity.EnvId}
err := d.GetDbBy(oldDb) err := d.GetDbBy(oldDb)
if dbEntity.Id == 0 { if dbEntity.Id == 0 {
biz.IsTrue(err != nil, "该库已存在") biz.NotEmpty(dbEntity.Password, "密码不能为空")
biz.IsTrue(err != nil, "该数据库实例已存在")
d.dbRepo.Insert(dbEntity) d.dbRepo.Insert(dbEntity)
} else { return
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldDb.Id == dbEntity.Id, "该库已存在")
}
// 先关闭数据库连接
CloseDb(dbEntity.Id)
d.dbRepo.Update(dbEntity)
} }
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
biz.IsTrue(oldDb.Id == dbEntity.Id, "该数据库实例已存在")
}
dbId := dbEntity.Id
old := d.GetById(dbId)
var oldDbs []interface{}
for _, v := range strings.Split(old.Database, " ") {
oldDbs = append(oldDbs, v)
}
var newDbs []interface{}
for _, v := range strings.Split(dbEntity.Database, " ") {
newDbs = append(newDbs, v)
}
// 比较新旧数据库列表,需要将移除的数据库相关联的信息删除
_, delDb, _ := utils.ArrayCompare(newDbs, oldDbs, func(i1, i2 interface{}) bool {
return i1.(string) == i2.(string)
})
for _, v := range delDb {
// 先关闭数据库连接
CloseDb(dbEntity.Id, v.(string))
// 删除该库关联的所有sql记录
d.dbSqlRepo.DeleteBy(&entity.DbSql{DbId: dbId, Db: v.(string)})
}
d.dbRepo.Update(dbEntity)
} }
func (d *dbAppImpl) Delete(id uint64) { func (d *dbAppImpl) Delete(id uint64) {
// 关闭连接 db := d.GetById(id)
CloseDb(id) dbs := strings.Split(db.Database, " ")
for _, v := range dbs {
// 关闭连接
CloseDb(id, v)
}
d.dbRepo.Delete(id) d.dbRepo.Delete(id)
// 删除该库下用户保存的所有sql信息 // 删除该库下用户保存的所有sql信息
d.dbSqlRepo.DeleteBy(&entity.DbSql{DbId: id}) d.dbSqlRepo.DeleteBy(&entity.DbSql{DbId: id})
@@ -101,13 +135,13 @@ func (d *dbAppImpl) Delete(id uint64) {
var mutex sync.Mutex var mutex sync.Mutex
func (da *dbAppImpl) GetDbInstance(id uint64) *DbInstance { func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
// Id不为0则为需要缓存 // Id不为0则为需要缓存
needCache := id != 0 needCache := id != 0
if needCache { if needCache {
load, ok := dbCache.Get(id) load, ok := dbCache.Get(GetDbCacheKey(id, db))
if ok { if ok {
return load.(*DbInstance) return load.(*DbInstance)
} }
@@ -115,8 +149,11 @@ func (da *dbAppImpl) GetDbInstance(id uint64) *DbInstance {
d := da.GetById(id) d := da.GetById(id)
biz.NotNil(d, "数据库信息不存在") biz.NotNil(d, "数据库信息不存在")
global.Log.Infof("连接db: %s:%d/%s", d.Host, d.Port, d.Database) biz.IsTrue(strings.Contains(d.Database, db), "未配置该库的操作权限")
global.Log.Infof("连接db: %s:%d/%s", d.Host, d.Port, db)
// 将数据库替换为要访问的数据库,原本数据库为空格拼接的所有库
d.Database = db
DB, err := sql.Open(d.Type, getDsn(d)) DB, err := sql.Open(d.Type, getDsn(d))
biz.ErrIsNil(err, fmt.Sprintf("Open %s failed, err:%v\n", d.Type, err)) biz.ErrIsNil(err, fmt.Sprintf("Open %s failed, err:%v\n", d.Type, err))
perr := DB.Ping() perr := DB.Ping()
@@ -131,33 +168,39 @@ func (da *dbAppImpl) GetDbInstance(id uint64) *DbInstance {
// 设置闲置连接数 // 设置闲置连接数
DB.SetMaxIdleConns(1) DB.SetMaxIdleConns(1)
dbi := &DbInstance{Id: id, Type: d.Type, ProjectId: d.ProjectId, db: DB} cacheKey := GetDbCacheKey(id, db)
dbi := &DbInstance{Id: cacheKey, Type: d.Type, ProjectId: d.ProjectId, db: DB}
if needCache { if needCache {
dbCache.Put(id, dbi) dbCache.Put(cacheKey, dbi)
} }
return dbi return dbi
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// 客户端连接缓存30分钟内没有访问则会被关闭 // 客户端连接缓存30分钟内没有访问则会被关闭, key为数据库实例id:数据库
var dbCache = cache.NewTimedCache(30*time.Minute, 5*time.Second). var dbCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
WithUpdateAccessTime(true). WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) { OnEvicted(func(key interface{}, value interface{}) {
global.Log.Info(fmt.Sprintf("删除db连接缓存 id: %d", key)) global.Log.Info(fmt.Sprintf("删除db连接缓存 id: %s", key))
value.(*DbInstance).Close() value.(*DbInstance).Close()
}) })
func GetDbInstanceByCache(id uint64) *DbInstance { func GetDbCacheKey(dbId uint64, db string) string {
if load, ok := dbCache.Get(fmt.Sprint(id)); ok { return fmt.Sprintf("%d:%s", dbId, db)
}
func GetDbInstanceByCache(id string) *DbInstance {
if load, ok := dbCache.Get(id); ok {
return load.(*DbInstance) return load.(*DbInstance)
} }
return nil return nil
} }
func TestConnection(d *entity.Db) { func TestConnection(d entity.Db) {
biz.NotNil(d, "数据库信息不存在") // 验证第一个库是否可以连接即可
DB, err := sql.Open(d.Type, getDsn(d)) d.Database = strings.Split(d.Database, " ")[0]
DB, err := sql.Open(d.Type, getDsn(&d))
biz.ErrIsNil(err, "Open %s failed, err:%v\n", d.Type, err) biz.ErrIsNil(err, "Open %s failed, err:%v\n", d.Type, err)
defer DB.Close() defer DB.Close()
perr := DB.Ping() perr := DB.Ping()
@@ -166,7 +209,7 @@ func TestConnection(d *entity.Db) {
// db实例 // db实例
type DbInstance struct { type DbInstance struct {
Id uint64 Id string
Type string Type string
ProjectId uint64 ProjectId uint64
db *sql.DB db *sql.DB
@@ -264,7 +307,9 @@ func getDsn(d *entity.Db) string {
return "" return ""
} }
func CloseDb(id uint64) { // 关闭该数据库所有连接
func CloseDb(dbId uint64, db string) {
id := GetDbCacheKey(dbId, db)
if di := GetDbInstanceByCache(id); di != nil { if di := GetDbInstanceByCache(id); di != nil {
di.Close() di.Close()
dbCache.Delete(id) dbCache.Delete(id)

View File

@@ -37,7 +37,7 @@ type Project interface {
DeleteMember(projectId, accountId uint64) DeleteMember(projectId, accountId uint64)
// 账号是否有权限访问该项目关联的资源信息 // 账号是否有权限访问该项目关联的资源信息
CanAccess(accountId, projectId uint64) bool CanAccess(accountId, projectId uint64) error
} }
type projectAppImpl struct { type projectAppImpl struct {
@@ -120,6 +120,9 @@ func (p *projectAppImpl) DeleteMember(projectId, accountId uint64) {
p.projectMemberRepo.DeleteByPidMid(projectId, accountId) p.projectMemberRepo.DeleteByPidMid(projectId, accountId)
} }
func (p *projectAppImpl) CanAccess(accountId, projectId uint64) bool { func (p *projectAppImpl) CanAccess(accountId, projectId uint64) error {
return p.projectMemberRepo.IsExist(projectId, accountId) if p.projectMemberRepo.IsExist(projectId, accountId) {
return nil
}
return biz.NewBizErr("您无权操作该资源")
} }

View File

@@ -8,6 +8,7 @@ type DbSql struct {
model.Model `orm:"-"` model.Model `orm:"-"`
DbId uint64 `json:"dbId"` DbId uint64 `json:"dbId"`
Db string `json:"db"`
Type int `json:"type"` // 类型 Type int `json:"type"` // 类型
Sql string `json:"sql"` Sql string `json:"sql"`
Name string `json:"name"` Name string `json:"name"`

View File

@@ -21,13 +21,13 @@ SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `t_db`; DROP TABLE IF EXISTS `t_db`;
CREATE TABLE `t_db` ( CREATE TABLE `t_db` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库名称', `name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
`host` varchar(20) COLLATE utf8mb4_bin NOT NULL, `host` varchar(20) COLLATE utf8mb4_bin NOT NULL,
`port` int(8) NOT NULL, `port` int(8) NOT NULL,
`username` varchar(255) COLLATE utf8mb4_bin NOT NULL, `username` varchar(255) COLLATE utf8mb4_bin NOT NULL,
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL, `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库类型(mysql...)', `type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
`database` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL, `database` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
`network` varchar(8) COLLATE utf8mb4_bin DEFAULT NULL, `network` varchar(8) COLLATE utf8mb4_bin DEFAULT NULL,
`project_id` bigint(20) DEFAULT NULL, `project_id` bigint(20) DEFAULT NULL,
`project` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL, `project` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
@@ -41,7 +41,7 @@ CREATE TABLE `t_db` (
`modifier_id` bigint(20) DEFAULT NULL, `modifier_id` bigint(20) DEFAULT NULL,
`modifier` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL, `modifier` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库信息表'; ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库资源信息表';
-- ---------------------------- -- ----------------------------
-- Records of t_db -- Records of t_db
@@ -55,7 +55,8 @@ COMMIT;
DROP TABLE IF EXISTS `t_db_sql`; DROP TABLE IF EXISTS `t_db_sql`;
CREATE TABLE `t_db_sql` ( CREATE TABLE `t_db_sql` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, `id` bigint(20) NOT NULL AUTO_INCREMENT,
`db_id` bigint(20) NOT NULL COMMENT '数据库id', `db_id` bigint(20) NOT NULL COMMENT '数据库实例id',
`db` varchar(125) NOT NULL COMMENT '数据库',
`name` varchar(60) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'sql模板名', `name` varchar(60) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'sql模板名',
`sql` text COLLATE utf8mb4_bin, `sql` text COLLATE utf8mb4_bin,
`type` tinyint(8) NOT NULL, `type` tinyint(8) NOT NULL,