refactor: 数据库授权凭证迁移

This commit is contained in:
meilin.huang
2024-04-12 13:24:20 +08:00
parent 4ef8d27b1e
commit abc015aec0
47 changed files with 1011 additions and 558 deletions

View File

@@ -5,10 +5,10 @@ export const TagResourceTypeEnum = {
AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }), AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }), Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor' }), Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor' }).tagTypeSuccess(),
Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }), Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }).tagTypeWarning(),
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }), Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }).tagTypeInfo(),
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }), Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }).tagTypeDanger(),
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket' }), MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket' }),
}; };

View File

@@ -4,6 +4,6 @@ export const AccountUsernamePattern = {
}; };
export const ResourceCodePattern = { export const ResourceCodePattern = {
pattern: /^[a-zA-Z0-9_]{1,32}$/g, pattern: /^[a-zA-Z0-9_.:]{1,32}$/g,
message: '只允许输入1-32位大小写字母、数字、下划线', message: '只允许输入1-32位大小写字母、数字、_.:',
}; };

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="auth-cert-edit"> <div class="auth-cert-edit">
<el-dialog title="凭证保存" v-model="dialogVisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false"> <el-dialog :title="props.title" v-model="dialogVisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
<el-form ref="acForm" :model="state.form" label-width="auto" :rules="rules"> <el-form ref="acForm" :model="state.form" label-width="auto" :rules="rules">
<el-form-item prop="type" label="凭证类型" required> <el-form-item prop="type" label="凭证类型" required>
<el-select @change="changeType" v-model="form.type" placeholder="请选择凭证类型"> <el-select @change="changeType" v-model="form.type" placeholder="请选择凭证类型">
@@ -118,6 +118,10 @@ import { ResourceCodePattern } from '@/common/pattern';
import { TagResourceTypeEnum } from '@/common/commonEnum'; import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({ const props = defineProps({
title: {
type: String,
default: '凭证保存',
},
authCert: { authCert: {
type: Object, type: Object,
}, },
@@ -162,7 +166,7 @@ const rules = {
], ],
}; };
const emit = defineEmits(['confirm']); const emit = defineEmits(['confirm', 'cancel']);
const dialogVisible = defineModel<boolean>('visible', { default: false }); const dialogVisible = defineModel<boolean>('visible', { default: false });
@@ -238,9 +242,10 @@ const getCiphertext = async () => {
const cancelEdit = () => { const cancelEdit = () => {
dialogVisible.value = false; dialogVisible.value = false;
emit('cancel');
setTimeout(() => { setTimeout(() => {
state.form = { ...DefaultForm };
acForm.value?.resetFields(); acForm.value?.resetFields();
state.form = { ...DefaultForm };
}, 300); }, 300);
}; };

View File

@@ -34,9 +34,17 @@
<EnumTag :value="scope.row.type" :enums="AuthCertTypeEnum" /> <EnumTag :value="scope.row.type" :enums="AuthCertTypeEnum" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip width="120px"> </el-table-column>
</el-table> </el-table>
<ResourceAuthCertEdit v-model:visible="state.dvisible" :auth-cert="state.form" @confirm="btnOk" :disable-type="[AuthCertTypeEnum.Public.value]" /> <ResourceAuthCertEdit
v-model:visible="state.dvisible"
:auth-cert="state.form"
@confirm="btnOk"
@cancel="cancelEdit"
:disable-type="[AuthCertTypeEnum.Public.value]"
:disable-ciphertext-type="props.disableCiphertextType"
/>
</div> </div>
</template> </template>
@@ -51,6 +59,9 @@ import { ElMessage } from 'element-plus';
const props = defineProps({ const props = defineProps({
resourceType: { type: Number }, resourceType: { type: Number },
resourceCode: { type: String }, resourceCode: { type: String },
disableCiphertextType: {
type: Array,
},
testConnBtnLoading: { type: Boolean }, testConnBtnLoading: { type: Boolean },
}); });
@@ -109,7 +120,19 @@ const cancelEdit = () => {
const btnOk = async (authCert: any) => { const btnOk = async (authCert: any) => {
const isEdit = authCert.id; const isEdit = authCert.id;
if (isEdit || state.idx > 0) { if (!isEdit) {
const res = await resourceAuthCertApi.listByQuery.request({
name: authCert.name,
pageNum: 1,
pageSize: 100,
});
if (res.total) {
ElMessage.error('该授权凭证名称已存在');
return;
}
}
if (isEdit || state.idx >= 0) {
authCerts.value[state.idx] = authCert; authCerts.value[state.idx] = authCert;
cancelEdit(); cancelEdit();
return; return;
@@ -119,15 +142,6 @@ const btnOk = async (authCert: any) => {
ElMessage.error('该名称或用户名已存在于该账号列表中'); ElMessage.error('该名称或用户名已存在于该账号列表中');
return; return;
} }
const res = await resourceAuthCertApi.listByQuery.request({
name: authCert.name,
pageNum: 1,
pageSize: 100,
});
if (res.total) {
ElMessage.error('该授权凭证名称已存在');
return;
}
authCerts.value.push(authCert); authCerts.value.push(authCert);
cancelEdit(); cancelEdit();

View File

@@ -30,13 +30,14 @@
remote remote
:remote-method="getInstances" :remote-method="getInstances"
@change="changeInstance" @change="changeInstance"
v-model="form.instanceId" v-model="state.selectInstalce"
value-key="id"
placeholder="请输入实例名称搜索并选择实例" placeholder="请输入实例名称搜索并选择实例"
filterable filterable
clearable clearable
class="w100" class="w100"
> >
<el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id"> <el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item">
{{ item.name }} {{ item.name }}
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
@@ -47,6 +48,23 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="authCertName" label="授权凭证" required>
<el-select @focus="getAuthCerts" @change="changeAuthCert" v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
<el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name">
{{ item.name }}
<el-divider direction="vertical" border-style="dashed" />
{{ item.username }}
<el-divider direction="vertical" border-style="dashed" />
<EnumTag :value="item.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
<el-divider direction="vertical" border-style="dashed" />
{{ item.remark }}
</el-option>
</el-select>
</el-form-item>
<el-form-item prop="code" label="编号" required> <el-form-item prop="code" label="编号" required>
<el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input> <el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input>
</el-form-item> </el-form-item>
@@ -100,6 +118,10 @@ import type { CheckboxValueType } from 'element-plus';
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue'; import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
import { DbType } from '@/views/ops/db/dialect'; import { DbType } from '@/views/ops/db/dialect';
import { ResourceCodePattern } from '@/common/pattern'; import { ResourceCodePattern } from '@/common/pattern';
import { resourceAuthCertApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
const props = defineProps({ const props = defineProps({
visible: { visible: {
@@ -172,6 +194,8 @@ const state = reactive({
dbNamesSelected: [] as any, dbNamesSelected: [] as any,
dbNamesFiltered: [] as any, dbNamesFiltered: [] as any,
filterString: '', filterString: '',
selectInstalce: {} as any,
authCerts: [] as any,
form: { form: {
id: null, id: null,
tagId: [], tagId: [],
@@ -180,6 +204,7 @@ const state = reactive({
database: '', database: '',
remark: '', remark: '',
instanceId: null as any, instanceId: null as any,
authCertName: '',
flowProcdefKey: '', flowProcdefKey: '',
}, },
instances: [] as any, instances: [] as any,
@@ -205,14 +230,27 @@ watch(props, async (newValue: any) => {
} }
}); });
const changeInstance = () => { const changeInstance = async () => {
state.dbNamesSelected = []; state.dbNamesSelected = [];
getAllDatabase(); state.form.instanceId = state.selectInstalce.id;
}; };
const getAllDatabase = async () => { const getAuthCerts = async () => {
const res = await resourceAuthCertApi.listByQuery.request({
resourceCode: state.selectInstalce.code,
resourceType: TagResourceTypeEnum.Db.value,
pageSize: 100,
});
state.authCerts = res.list || [];
};
const changeAuthCert = (val: string) => {
getAllDatabase(val);
};
const getAllDatabase = async (authCertName: string) => {
if (state.form.instanceId > 0) { if (state.form.instanceId > 0) {
let dbs = await dbApi.getAllDatabase.request({ instanceId: state.form.instanceId }); let dbs = await dbApi.getAllDatabase.request({ instanceId: state.form.instanceId, authCertName });
state.allDatabases = dbs; state.allDatabases = dbs;
// 如果是oracle且没查出数据库列表则取实例sid // 如果是oracle且没查出数据库列表则取实例sid
@@ -238,8 +276,9 @@ const open = async () => {
if (state.form.instanceId) { if (state.form.instanceId) {
// 根据id获取因为需要回显实例名称 // 根据id获取因为需要回显实例名称
await getInstances('', state.form.instanceId); await getInstances('', state.form.instanceId);
state.selectInstalce = state.instances[0];
await getAllDatabase(state.form.authCertName);
} }
await getAllDatabase();
}; };
const btnOk = async () => { const btnOk = async () => {

View File

@@ -194,7 +194,7 @@
</el-descriptions> </el-descriptions>
</el-dialog> </el-dialog>
<db-edit @val-change="search" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit> <db-edit @val-change="search()" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
</div> </div>
</template> </template>

View File

@@ -1,94 +1,111 @@
<template> <template>
<div> <div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%"> <el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
<template #header>
<DrawerHeader :header="title" :back="cancel" />
</template>
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto"> <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
<el-tabs v-model="tabActiveName"> <el-divider content-position="left">基本</el-divider>
<el-tab-pane label="基础信息" name="basic"> <el-form-item prop="code" label="编号" required>
<el-form-item prop="name" label="别名" required> <el-input :disabled="form.id" v-model.trim="form.code" placeholder="请输入编号 (数字字母下划线), 不可修改" auto-complete="off"></el-input>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input> </el-form-item>
</el-form-item> <el-form-item prop="name" label="名称" required>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="type" label="类型" required> <el-form-item prop="type" label="类型" required>
<el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型"> <el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
<el-option <el-option
v-for="(dbTypeAndDialect, key) in getDbDialectMap()" v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
:key="key" :key="key"
:value="dbTypeAndDialect[0]" :value="dbTypeAndDialect[0]"
:label="dbTypeAndDialect[1].getInfo().name" :label="dbTypeAndDialect[1].getInfo().name"
> >
<SvgIcon :name="dbTypeAndDialect[1].getInfo().icon" :size="20" /> <SvgIcon :name="dbTypeAndDialect[1].getInfo().icon" :size="20" />
{{ dbTypeAndDialect[1].getInfo().name }} {{ dbTypeAndDialect[1].getInfo().name }}
</el-option> </el-option>
<template #prefix> <template #prefix>
<SvgIcon :name="getDbDialect(form.type).getInfo().icon" :size="20" /> <SvgIcon :name="getDbDialect(form.type).getInfo().icon" :size="20" />
</template>
</el-select>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="host" label="host" required>
<el-col :span="18">
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
</el-col>
</el-form-item>
<el-form-item v-if="form.type === DbType.sqlite" prop="host" label="sqlite地址">
<el-input v-model.trim="form.host" placeholder="请输入sqlite文件在服务器的绝对地址"></el-input>
</el-form-item>
<el-form-item v-if="form.type === DbType.oracle" label="SID|服务名">
<el-col :span="5">
<el-select
@change="
() => {
state.extra.serviceName = '';
state.extra.sid = '';
}
"
v-model="state.extra.stype"
placeholder="请选择"
>
<el-option label="服务名" :value="1" />
<el-option label="SID" :value="2" />
</el-select>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="18">
<el-input v-if="state.extra.stype == 1" v-model="state.extra.serviceName" placeholder="请输入服务名"> </el-input>
<el-input v-else v-model="state.extra.sid" placeholder="请输入SID"> </el-input>
</el-col>
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-input v-model="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<template v-if="form.type !== DbType.sqlite">
<el-divider content-position="left">账号</el-divider>
<div>
<ResourceAuthCertTableEdit
v-model="form.authCerts"
:resource-code="form.code"
:resource-type="TagResourceTypeEnum.Db.value"
:test-conn-btn-loading="testConnBtnLoading"
@test-conn="testConn"
:disable-ciphertext-type="[AuthCertCiphertextTypeEnum.PrivateKey.value]"
/>
</div>
</template>
<!--
<el-form-item v-if="form.type !== DbType.sqlite" prop="username" label="用户名" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="password" label="密码">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<template #reference>
<el-link v-auth="'db:instance:save'" @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码 </el-link>
</template> </template>
</el-select> </el-popover>
</el-form-item> </template>
</el-input>
</el-form-item> -->
<el-form-item v-if="form.type !== DbType.sqlite" prop="host" label="host" required> <el-divider content-position="left">其他</el-divider>
<el-col :span="18"> <el-form-item prop="params" label="连接参数">
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input> <el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
</el-col> <!-- <template #suffix>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
</el-col>
</el-form-item>
<el-form-item v-if="form.type === DbType.sqlite" prop="host" label="sqlite地址">
<el-input v-model.trim="form.host" placeholder="请输入sqlite文件在服务器的绝对地址"></el-input>
</el-form-item>
<el-form-item v-if="form.type === DbType.oracle" label="SID|服务名">
<el-col :span="5">
<el-select
@change="
() => {
state.extra.serviceName = '';
state.extra.sid = '';
}
"
v-model="state.extra.stype"
placeholder="请选择"
>
<el-option label="服务名" :value="1" />
<el-option label="SID" :value="2" />
</el-select>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="18">
<el-input v-if="state.extra.stype == 1" v-model="state.extra.serviceName" placeholder="请输入服务名"> </el-input>
<el-input v-else v-model="state.extra.sid" placeholder="请输入SID"> </el-input>
</el-col>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="username" label="用户名" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="password" label="密码">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<template #reference>
<el-link v-auth="'db:instance:save'" @click="getDbPwd" :underline="false" type="primary" class="mr5"
>原密码
</el-link>
</template>
</el-popover>
</template>
</el-input>
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-input v-model="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="其他配置" name="other">
<el-form-item prop="params" label="连接参数">
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
<!-- <template #suffix>
<el-link <el-link
target="_blank" target="_blank"
href="https://github.com/go-sql-driver/mysql#parameters" href="https://github.com/go-sql-driver/mysql#parameters"
@@ -98,24 +115,21 @@
>参数参考</el-link >参数参考</el-link
> >
</template> --> </template> -->
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="sshTunnelMachineId" label="SSH隧道"> <el-form-item prop="sshTunnelMachineId" label="SSH隧道">
<ssh-tunnel-select v-model="form.sshTunnelMachineId" /> <ssh-tunnel-select v-model="form.sshTunnelMachineId" />
</el-form-item> </el-form-item>
</el-tab-pane>
</el-tabs>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="cancel()"> </el-button> <el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button> <el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-drawer>
</div> </div>
</template> </template>
@@ -123,11 +137,14 @@
import { reactive, ref, toRefs, watch } from 'vue'; import { reactive, ref, toRefs, watch } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import SshTunnelSelect from '../component/SshTunnelSelect.vue'; import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import { DbType, getDbDialect, getDbDialectMap } from './dialect'; import { DbType, getDbDialect, getDbDialectMap } from './dialect';
import SvgIcon from '@/components/svgIcon/index.vue'; import SvgIcon from '@/components/svgIcon/index.vue';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import { ResourceCodePattern } from '@/common/pattern';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import ResourceAuthCertTableEdit from '../component/ResourceAuthCertTableEdit.vue';
import { AuthCertCiphertextTypeEnum } from '../tag/enums';
const props = defineProps({ const props = defineProps({
visible: { visible: {
@@ -145,6 +162,18 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'cancel', 'val-change']); const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
const rules = { const rules = {
code: [
{
required: true,
message: '请输入编码',
trigger: ['change', 'blur'],
},
{
pattern: ResourceCodePattern.pattern,
message: ResourceCodePattern.message,
trigger: ['blur'],
},
],
name: [ name: [
{ {
required: true, required: true,
@@ -166,13 +195,6 @@ const rules = {
trigger: ['blur'], trigger: ['blur'],
}, },
], ],
username: [
{
required: true,
message: '请输入用户名',
trigger: ['change', 'blur'],
},
],
sid: [ sid: [
{ {
required: true, required: true,
@@ -186,29 +208,24 @@ const dbForm: any = ref(null);
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
tabActiveName: 'basic',
extra: {} as any, // 连接需要的额外参数json extra: {} as any, // 连接需要的额外参数json
form: { form: {
id: null, id: null,
type: '', type: '',
code: '',
name: null, name: null,
host: '', host: '',
port: null, port: null,
username: null, authCerts: [],
extra: '', // 连接需要的额外参数json字符串 extra: '', // 连接需要的额外参数json字符串
password: null,
params: null, params: null,
remark: '', remark: '',
sshTunnelMachineId: null as any, sshTunnelMachineId: null as any,
}, },
submitForm: {}, submitForm: {} as any,
// 原密码
pwd: '',
// 原用户名
oldUserName: null,
}); });
const { dialogVisible, tabActiveName, form, submitForm, pwd } = toRefs(state); const { dialogVisible, form, submitForm } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(submitForm); const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(submitForm);
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(submitForm); const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(submitForm);
@@ -218,14 +235,12 @@ watch(props, (newValue: any) => {
if (!state.dialogVisible) { if (!state.dialogVisible) {
return; return;
} }
state.tabActiveName = 'basic';
if (newValue.data) { if (newValue.data) {
state.form = { ...newValue.data }; state.form = { ...newValue.data };
state.oldUserName = state.form.username;
state.extra = JSON.parse(state.form.extra); state.extra = JSON.parse(state.form.extra);
} else { } else {
state.form = { port: null, type: DbType.mysql } as any; state.form = { port: null, type: DbType.mysql } as any;
state.oldUserName = null; state.form.authCerts = [];
} }
}); });
@@ -236,13 +251,8 @@ const changeDbType = (val: string) => {
state.extra = {}; state.extra = {};
}; };
const getDbPwd = async () => {
state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id });
};
const getReqForm = async () => { const getReqForm = async () => {
const reqForm = { ...state.form }; const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password);
if (!state.form.sshTunnelMachineId) { if (!state.form.sshTunnelMachineId) {
reqForm.sshTunnelMachineId = -1; reqForm.sshTunnelMachineId = -1;
} }
@@ -252,7 +262,7 @@ const getReqForm = async () => {
return reqForm; return reqForm;
}; };
const testConn = async () => { const testConn = async (authCert: any) => {
dbForm.value.validate(async (valid: boolean) => { dbForm.value.validate(async (valid: boolean) => {
if (!valid) { if (!valid) {
ElMessage.error('请正确填写信息'); ElMessage.error('请正确填写信息');
@@ -260,20 +270,13 @@ const testConn = async () => {
} }
state.submitForm = await getReqForm(); state.submitForm = await getReqForm();
state.submitForm.authCerts = [authCert];
await testConnExec(); await testConnExec();
ElMessage.success('连接成功'); ElMessage.success('连接成功');
}); });
}; };
const btnOk = async () => { const btnOk = async () => {
if (state.form.type !== DbType.sqlite) {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
} else if (state.form.username != state.oldUserName) {
notBlank(state.form.password, '已修改用户名,请输入密码');
}
}
dbForm.value.validate(async (valid: boolean) => { dbForm.value.validate(async (valid: boolean) => {
if (!valid) { if (!valid) {
ElMessage.error('请正确填写信息'); ElMessage.error('请正确填写信息');

View File

@@ -3,6 +3,7 @@
<page-table <page-table
ref="pageTableRef" ref="pageTableRef"
:page-api="dbApi.instances" :page-api="dbApi.instances"
:data-handler-fn="handleData"
:searchItems="searchItems" :searchItems="searchItems"
v-model:query-form="query" v-model:query-form="query"
:show-selection="true" :show-selection="true"
@@ -16,6 +17,10 @@
> >
</template> </template>
<template #authCert="{ data }">
<ResourceAuthCert v-model:select-auth-cert="data.selectAuthCert" :auth-certs="data.authCerts" />
</template>
<template #type="{ data }"> <template #type="{ data }">
<el-tooltip :content="getDbDialect(data.type).getInfo().name" placement="top"> <el-tooltip :content="getDbDialect(data.type).getInfo().name" placement="top">
<SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" /> <SvgIcon :name="getDbDialect(data.type).getInfo().icon" :size="20" />
@@ -35,7 +40,6 @@
<el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</el-descriptions-item> <el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</el-descriptions-item>
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item> <el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
<el-descriptions-item :span="1" label="类型">{{ infoDialog.data.type }}</el-descriptions-item> <el-descriptions-item :span="1" label="类型">{{ infoDialog.data.type }}</el-descriptions-item>
<el-descriptions-item :span="3" label="连接参数">{{ infoDialog.data.params }}</el-descriptions-item> <el-descriptions-item :span="3" label="连接参数">{{ infoDialog.data.params }}</el-descriptions-item>
@@ -71,6 +75,7 @@ import { hasPerms } from '@/components/auth/auth';
import SvgIcon from '@/components/svgIcon/index.vue'; import SvgIcon from '@/components/svgIcon/index.vue';
import { getDbDialect } from './dialect'; import { getDbDialect } from './dialect';
import { SearchItem } from '@/components/SearchForm'; import { SearchItem } from '@/components/SearchForm';
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue')); const InstanceEdit = defineAsyncComponent(() => import('./InstanceEdit.vue'));
@@ -79,13 +84,14 @@ const perms = {
delInstance: 'db:instance:del', delInstance: 'db:instance:del',
}; };
const searchItems = [SearchItem.input('name', '名称')]; const searchItems = [SearchItem.input('code', '编号'), SearchItem.input('name', '名称')];
const columns = ref([ const columns = ref([
TableColumn.new('code', '编号'),
TableColumn.new('name', '名称'), TableColumn.new('name', '名称'),
TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(), TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
TableColumn.new('host', 'host:port').setFormatFunc((data: any) => `${data.host}:${data.port}`), TableColumn.new('host', 'host:port').setFormatFunc((data: any) => `${data.host}:${data.port}`),
TableColumn.new('username', '用户名'), TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(10),
TableColumn.new('params', '连接参数'), TableColumn.new('params', '连接参数'),
TableColumn.new('remark', '备注'), TableColumn.new('remark', '备注'),
]); ]);
@@ -134,6 +140,15 @@ const search = () => {
pageTableRef.value.search(); pageTableRef.value.search();
}; };
const handleData = (res: any) => {
const dataList = res.list;
// 赋值授权凭证
for (let x of dataList) {
x.selectAuthCert = x.authCerts[0];
}
return res;
};
const showInfo = (info: any) => { const showInfo = (info: any) => {
state.infoDialog.data = info; state.infoDialog.data = info;
state.infoDialog.visible = true; state.infoDialog.visible = true;

View File

@@ -43,7 +43,6 @@ export const dbApi = {
getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'), getInstanceServerInfo: Api.newGet('/instances/{instanceId}/server-info'),
testConn: Api.newPost('/instances/test-conn'), testConn: Api.newPost('/instances/test-conn'),
saveInstance: Api.newPost('/instances'), saveInstance: Api.newPost('/instances'),
getInstancePwd: Api.newGet('/instances/{id}/pwd'),
deleteInstance: Api.newDelete('/instances/{id}'), deleteInstance: Api.newDelete('/instances/{id}'),
// 获取数据库备份列表 // 获取数据库备份列表

View File

@@ -287,7 +287,7 @@ const columns = [
TableColumn.new('ipPort', 'ip:port').isSlot().setAddWidth(50), TableColumn.new('ipPort', 'ip:port').isSlot().setAddWidth(50),
TableColumn.new('stat', '运行状态').isSlot().setAddWidth(55), TableColumn.new('stat', '运行状态').isSlot().setAddWidth(55),
TableColumn.new('fs', '磁盘(挂载点=>可用/总)').isSlot().setAddWidth(25), TableColumn.new('fs', '磁盘(挂载点=>可用/总)').isSlot().setAddWidth(25),
TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(20), TableColumn.new('authCerts[0].username', '授权凭证').isSlot('authCert').setAddWidth(10),
TableColumn.new('status', '状态').isSlot().setMinWidth(85), TableColumn.new('status', '状态').isSlot().setMinWidth(85),
TableColumn.new('remark', '备注'), TableColumn.new('remark', '备注'),
TableColumn.new('action', '操作').isSlot().setMinWidth(238).fixedRight().alignCenter(), TableColumn.new('action', '操作').isSlot().setMinWidth(238).fixedRight().alignCenter(),

View File

@@ -19,9 +19,11 @@
</page-table> </page-table>
<ResourceAuthCertEdit <ResourceAuthCertEdit
:title="editor.title"
v-model:visible="editor.visible" v-model:visible="editor.visible"
:auth-cert="editor.authcert" :auth-cert="editor.authcert"
@confirm="confirmSave" @confirm="confirmSave"
@cancel="editor.authcert = {}"
:disable-type="state.disableAuthCertType" :disable-type="state.disableAuthCertType"
:disable-ciphertext-type="state.disableAuthCertCiphertextType" :disable-ciphertext-type="state.disableAuthCertCiphertextType"
:resource-edit="false" :resource-edit="false"
@@ -73,7 +75,7 @@ const state = reactive({
paramsFormItem: [] as any, paramsFormItem: [] as any,
}, },
editor: { editor: {
title: '授权凭证保存', title: '添加授权凭证',
visible: false, visible: false,
authcert: {}, authcert: {},
}, },
@@ -93,6 +95,7 @@ const edit = (data: any) => {
state.disableAuthCertType = []; state.disableAuthCertType = [];
state.disableAuthCertCiphertextType = []; state.disableAuthCertCiphertextType = [];
if (data) { if (data) {
state.editor.title = `编辑授权凭证-[${data.name}]`;
state.editor.authcert = data; state.editor.authcert = data;
// 如果数据为公共授权凭证,则不允许修改凭证类型 // 如果数据为公共授权凭证,则不允许修改凭证类型
if (data.type == AuthCertTypeEnum.Public.value) { if (data.type == AuthCertTypeEnum.Public.value) {
@@ -103,6 +106,7 @@ const edit = (data: any) => {
state.disableAuthCertType = [AuthCertTypeEnum.Public.value]; state.disableAuthCertType = [AuthCertTypeEnum.Public.value];
} }
} else { } else {
state.editor.title = '添加授权凭证';
state.editor.authcert = { state.editor.authcert = {
type: AuthCertTypeEnum.Public.value, type: AuthCertTypeEnum.Public.value,
ciphertextType: AuthCertCiphertextTypeEnum.Password.value, ciphertextType: AuthCertCiphertextTypeEnum.Password.value,

View File

@@ -65,9 +65,16 @@
<el-descriptions-item label="类型"> <el-descriptions-item label="类型">
<EnumTag :enums="TagResourceTypeEnum" :value="currentTag.type" /> <EnumTag :enums="TagResourceTypeEnum" :value="currentTag.type" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="code">{{ currentTag.code }}</el-descriptions-item> <el-descriptions-item label="code">{{ currentTag.code }}</el-descriptions-item>
<el-descriptions-item label="code路径">{{ currentTag.codePath }}</el-descriptions-item>
<el-descriptions-item label="路径" :span="2">
<span v-for="item in parseTagPath(currentTag.codePath)" :key="item.code">
<SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, item.type)?.extra.icon" class="mr2" />
<span> {{ item.code }}</span>
<SvgIcon v-if="!item.isEnd" class="mr5 ml5" name="arrow-right" />
</span>
</el-descriptions-item>
<el-descriptions-item label="名称">{{ currentTag.name }}</el-descriptions-item> <el-descriptions-item label="名称">{{ currentTag.name }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item> <el-descriptions-item label="备注">{{ currentTag.remark }}</el-descriptions-item>
@@ -255,6 +262,38 @@ watch(
} }
); );
const parseTagPath = (tagPath: string) => {
if (!tagPath) {
return [];
}
const res = [] as any;
const codes = tagPath.split('/');
for (let code of codes) {
const typeAndCode = code.split('|');
if (typeAndCode.length == 1) {
const tagCode = typeAndCode[0];
if (!tagCode) {
continue;
}
res.push({
type: TagResourceTypeEnum.Tag.value,
code: typeAndCode[0],
});
continue;
}
res.push({
type: typeAndCode[0],
code: typeAndCode[1],
});
}
res[res.length - 1].isEnd = true;
return res;
};
const tabChange = () => { const tabChange = () => {
setNowTabData(); setNowTabData();
}; };

View File

@@ -28,9 +28,15 @@
</template> </template>
</page-table> </page-table>
<el-drawer title="团队编辑" v-model="addTeamDialog.visible" :before-close="cancelSaveTeam" :destroy-on-close="true" :close-on-click-modal="false"> <el-drawer
:title="addTeamDialog.form.id ? '编辑团队' : '添加团队'"
v-model="addTeamDialog.visible"
:before-close="cancelSaveTeam"
:destroy-on-close="true"
:close-on-click-modal="false"
>
<template #header> <template #header>
<DrawerHeader header="团队编辑" :back="cancelSaveTeam" /> <DrawerHeader :header="addTeamDialog.form.id ? '编辑团队' : '添加团队'" :back="cancelSaveTeam" />
</template> </template>
<el-form ref="teamForm" :model="addTeamDialog.form" label-width="auto"> <el-form ref="teamForm" :model="addTeamDialog.form" label-width="auto">
@@ -58,6 +64,7 @@
value: 'id', value: 'id',
label: 'codePath', label: 'codePath',
children: 'children', children: 'children',
disabled: 'disabled',
}" }"
@check="tagTreeNodeCheck" @check="tagTreeNodeCheck"
> >
@@ -152,7 +159,6 @@ const state = reactive({
currentEditPermissions: false, currentEditPermissions: false,
tags: [], tags: [],
addTeamDialog: { addTeamDialog: {
title: '新增团队',
visible: false, visible: false,
form: { id: 0, name: '', remark: '', tags: [] }, form: { id: 0, name: '', remark: '', tags: [] },
}, },
@@ -201,16 +207,22 @@ const search = async () => {
}; };
const showSaveTeamDialog = async (data: any) => { const showSaveTeamDialog = async (data: any) => {
if (state.tags.length == 0) { state.tags = await tagApi.getTagTrees.request(null);
state.tags = await tagApi.getTagTrees.request(null);
}
if (data) { if (data) {
state.addTeamDialog.form.id = data.id; state.addTeamDialog.form.id = data.id;
state.addTeamDialog.form.name = data.name; state.addTeamDialog.form.name = data.name;
state.addTeamDialog.form.remark = data.remark; state.addTeamDialog.form.remark = data.remark;
state.addTeamDialog.title = `修改 [${data.codePath}] 信息`;
state.addTeamDialog.form.tags = await tagApi.getTeamTagIds.request({ teamId: data.id }); state.addTeamDialog.form.tags = await tagApi.getTeamTagIds.request({ teamId: data.id });
setTimeout(() => {
const checkedNodes = tagTreeRef.value.getCheckedNodes();
console.log('check nodes: ', checkedNodes);
// 禁用选中节点的所有父节点,不可选中
for (let checkNodeData of checkedNodes) {
disableParentNodes(tagTreeRef.value.getNode(checkNodeData.id).parent);
}
}, 200);
} }
state.addTeamDialog.visible = true; state.addTeamDialog.visible = true;
@@ -231,8 +243,10 @@ const saveTeam = async () => {
const cancelSaveTeam = () => { const cancelSaveTeam = () => {
state.addTeamDialog.visible = false; state.addTeamDialog.visible = false;
state.addTeamDialog.form = {} as any;
teamForm.value.resetFields(); teamForm.value.resetFields();
setTimeout(() => {
state.addTeamDialog.form = {} as any;
}, 500);
}; };
const deleteTeam = () => { const deleteTeam = () => {
@@ -289,34 +303,47 @@ const cancelAddMember = () => {
state.showMemDialog.addVisible = false; state.showMemDialog.addVisible = false;
}; };
const tagTreeNodeCheck = () => { const tagTreeNodeCheck = (data: any) => {
// const node = tagTreeRef.value.getNode(data.id); const node = tagTreeRef.value.getNode(data.id);
// console.log(node); console.log('check node: ', node);
// // state.showTagDialog.tagTreeTeams = [16]
// if (node.checked) { if (node.checked) {
// if (node.parent) { // 如果选中了子节点,则需要将父节点全部取消选中,并禁用父节点
// console.log(node.parent); unCheckParentNodes(node.parent);
// // removeCheckedTagId(node.parent.key); disableParentNodes(node.parent);
// tagTreeRef.value.setChecked(node.parent, false, false); } else {
// } // 如果取消了选中,则需要根据条件恢复父节点的选中状态
// // // parentNode = node.parent disableParentNodes(node.parent, false);
// // for (let parentNode of node.parent) { }
// // parentNode.setChecked(false);
// // }
// }
// console.log(data);
// console.log(checkInfo);
}; };
// function removeCheckedTagId(id: any) { const unCheckParentNodes = (node: any) => {
// console.log(state.showTagDialog.tagTreeTeams); if (!node) {
// for (let i = 0; i < state.showTagDialog.tagTreeTeams.length; i++) { return;
// if (state.showTagDialog.tagTreeTeams[i] == id) { }
// console.log('has id', id); tagTreeRef.value.setChecked(node, false, false);
// state.showTagDialog.tagTreeTeams.splice(i, 1); unCheckParentNodes(node.parent);
// } };
// }
// console.log(state.showTagDialog.tagTreeTeams); /**
// } * 禁用该节点以及所有父节点
* @param node 节点
* @param disable 是否禁用
*/
const disableParentNodes = (node: any, disable = true) => {
if (!node) {
return;
}
if (!disable) {
// 恢复为非禁用状态时,若同层级存在一个选中状态或者禁用状态,则继续禁用 不恢复非禁用状态。
for (let oneLevelNodes of node.childNodes) {
if (oneLevelNodes.checked || oneLevelNodes.data.disabled) {
return;
}
}
}
node.data.disabled = disable;
disableParentNodes(node.parent, disable);
};
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -2,10 +2,10 @@ import { EnumValue } from '@/common/Enum';
// 授权凭证类型 // 授权凭证类型
export const AuthCertTypeEnum = { export const AuthCertTypeEnum = {
Public: EnumValue.of(2, '公共凭证').tagTypeSuccess(), Public: EnumValue.of(2, '公共凭证').tagTypeSuccess().tagTypeSuccess(),
Private: EnumValue.of(1, '普通账号').tagTypeSuccess(), Private: EnumValue.of(1, '普通账号'),
Privileged: EnumValue.of(11, '特权账号').tagTypeSuccess(), Privileged: EnumValue.of(11, '特权账号').tagTypeDanger(),
PrivateDefault: EnumValue.of(12, '默认账号').tagTypeSuccess(), PrivateDefault: EnumValue.of(12, '默认账号').tagTypeWarning(),
}; };
// 授权凭证密文类型 // 授权凭证密文类型

View File

@@ -16,10 +16,10 @@ const (
// RedisConnExpireTime = 2 * time.Minute // RedisConnExpireTime = 2 * time.Minute
// MongoConnExpireTime = 2 * time.Minute // MongoConnExpireTime = 2 * time.Minute
TagResourceTypeMachine int8 = 1 ResourceTypeMachine int8 = 1
TagResourceTypeDb int8 = 2 ResourceTypeDb int8 = 2
TagResourceTypeRedis int8 = 3 ResourceTypeRedis int8 = 3
TagResourceTypeMongo int8 = 4 ResourceTypeMongo int8 = 4
// 删除机器的事件主题名 // 删除机器的事件主题名
DeleteMachineEventTopic = "machine:delete" DeleteMachineEventTopic = "machine:delete"

View File

@@ -15,7 +15,7 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) { func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id accountId := rc.GetLoginAccount().Id
dbNum := len(m.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeDb, "")) dbNum := len(m.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeDb, ""))
rc.ResData = collx.M{ rc.ResData = collx.M{
"dbNum": dbNum, "dbNum": dbNum,

View File

@@ -44,7 +44,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery)) queryCond, page := req.BindQueryAndPage[*entity.DbQuery](rc, new(entity.DbQuery))
// 不存在可访问标签id即没有可操作数据 // 不存在可访问标签id即没有可操作数据
codes := d.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeDb, queryCond.TagPath) codes := d.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeDb, queryCond.TagPath)
if len(codes) == 0 { if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]() rc.ResData = model.EmptyPageResult[any]()
return return
@@ -56,7 +56,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
biz.ErrIsNil(err) biz.ErrIsNil(err)
// 填充标签信息 // 填充标签信息
d.TagApp.FillTagInfo(collx.ArrayMap(dbvos, func(dbvo *vo.DbListVO) tagentity.ITagResource { d.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeDb), collx.ArrayMap(dbvos, func(dbvo *vo.DbListVO) tagentity.ITagResource {
return dbvo return dbvo
})...) })...)
@@ -280,7 +280,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
la := rc.GetLoginAccount() la := rc.GetLoginAccount()
db, err := d.DbApp.GetById(new(entity.Db), dbId) db, err := d.DbApp.GetById(new(entity.Db), dbId)
biz.ErrIsNil(err, "该数据库不存在") biz.ErrIsNil(err, "该数据库不存在")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(la.Id, d.TagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...), "%s") biz.ErrIsNilAppendErr(d.TagApp.CanAccess(la.Id, d.TagApp.ListTagPathByTypeAndCode(consts.ResourceTypeDb, db.Code)...), "%s")
now := time.Now() now := time.Now()
filename := fmt.Sprintf("%s-%s.%s.sql%s", db.Name, dbName, now.Format("20060102150405"), extName) filename := fmt.Sprintf("%s-%s.%s.sql%s", db.Name, dbName, now.Format("20060102150405"), extName)

View File

@@ -1,28 +1,41 @@
package api package api
import ( import (
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/api/form" "mayfly-go/internal/db/api/form"
"mayfly-go/internal/db/api/vo" "mayfly-go/internal/db/api/vo"
"mayfly-go/internal/db/application" "mayfly-go/internal/db/application"
"mayfly-go/internal/db/domain/entity" "mayfly-go/internal/db/domain/entity"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
"mayfly-go/pkg/req" "mayfly-go/pkg/req"
"mayfly-go/pkg/utils/cryptox" "mayfly-go/pkg/utils/collx"
"strconv" "strconv"
"strings" "strings"
) )
type Instance struct { type Instance struct {
InstanceApp application.Instance `inject:"DbInstanceApp"` InstanceApp application.Instance `inject:"DbInstanceApp"`
DbApp application.Db `inject:""` DbApp application.Db `inject:""`
ResourceAuthCertApp tagapp.ResourceAuthCert `inject:""`
} }
// Instances 获取数据库实例信息 // Instances 获取数据库实例信息
// @router /api/instances [get] // @router /api/instances [get]
func (d *Instance) Instances(rc *req.Ctx) { func (d *Instance) Instances(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery)) queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery))
res, err := d.InstanceApp.GetPageList(queryCond, page, new([]vo.InstanceListVO))
var instvos []*vo.InstanceListVO
res, err := d.InstanceApp.GetPageList(queryCond, page, &instvos)
biz.ErrIsNil(err) biz.ErrIsNil(err)
// 填充授权凭证信息
d.ResourceAuthCertApp.FillAuthCert(consts.ResourceTypeDb, collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
return vos
})...)
rc.ResData = res rc.ResData = res
} }
@@ -30,12 +43,7 @@ func (d *Instance) TestConn(rc *req.Ctx) {
form := &form.InstanceForm{} form := &form.InstanceForm{}
instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance)) instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance))
// 密码解密,并使用解密后的赋值 biz.ErrIsNil(d.InstanceApp.TestConn(instance, form.AuthCerts[0]))
originPwd, err := cryptox.DefaultRsaDecrypt(form.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
instance.Password = originPwd
biz.ErrIsNil(d.InstanceApp.TestConn(instance))
} }
// SaveInstance 保存数据库实例信息 // SaveInstance 保存数据库实例信息
@@ -44,15 +52,11 @@ func (d *Instance) SaveInstance(rc *req.Ctx) {
form := &form.InstanceForm{} form := &form.InstanceForm{}
instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance)) instance := req.BindJsonAndCopyTo[*entity.DbInstance](rc, form, new(entity.DbInstance))
// 密码解密,并使用解密后的赋值
originPwd, err := cryptox.DefaultRsaDecrypt(form.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
instance.Password = originPwd
// 密码脱敏记录日志
form.Password = "****"
rc.ReqParam = form rc.ReqParam = form
biz.ErrIsNil(d.InstanceApp.Save(rc.MetaCtx, instance)) biz.ErrIsNil(d.InstanceApp.SaveDbInstance(rc.MetaCtx, &application.SaveDbInstanceParam{
DbInstance: instance,
AuthCerts: form.AuthCerts,
}))
} }
// GetInstance 获取数据库实例密码,由于数据库是加密存储,故提供该接口展示原文密码 // GetInstance 获取数据库实例密码,由于数据库是加密存储,故提供该接口展示原文密码
@@ -61,20 +65,9 @@ func (d *Instance) GetInstance(rc *req.Ctx) {
dbId := getInstanceId(rc) dbId := getInstanceId(rc)
dbEntity, err := d.InstanceApp.GetById(new(entity.DbInstance), dbId) dbEntity, err := d.InstanceApp.GetById(new(entity.DbInstance), dbId)
biz.ErrIsNil(err, "获取数据库实例错误") biz.ErrIsNil(err, "获取数据库实例错误")
dbEntity.Password = ""
rc.ResData = dbEntity rc.ResData = dbEntity
} }
// GetInstancePwd 获取数据库实例密码,由于数据库是加密存储,故提供该接口展示原文密码
// @router /api/instances/:instance/pwd [GET]
func (d *Instance) GetInstancePwd(rc *req.Ctx) {
instanceId := getInstanceId(rc)
instanceEntity, err := d.InstanceApp.GetById(new(entity.DbInstance), instanceId, "Password")
biz.ErrIsNil(err, "获取数据库实例错误")
biz.ErrIsNil(instanceEntity.PwdDecrypt())
rc.ResData = instanceEntity.Password
}
// DeleteInstance 删除数据库实例信息 // DeleteInstance 删除数据库实例信息
// @router /api/instances/:instance [DELETE] // @router /api/instances/:instance [DELETE]
func (d *Instance) DeleteInstance(rc *req.Ctx) { func (d *Instance) DeleteInstance(rc *req.Ctx) {
@@ -94,10 +87,13 @@ func (d *Instance) DeleteInstance(rc *req.Ctx) {
// 获取数据库实例的所有数据库名 // 获取数据库实例的所有数据库名
func (d *Instance) GetDatabaseNames(rc *req.Ctx) { func (d *Instance) GetDatabaseNames(rc *req.Ctx) {
instanceId := getInstanceId(rc) instanceId := getInstanceId(rc)
instance, err := d.InstanceApp.GetById(new(entity.DbInstance), instanceId, "Password") authCertName := rc.Query("authCertName")
biz.NotEmpty(authCertName, "授权凭证名不能为空")
instance, err := d.InstanceApp.GetById(new(entity.DbInstance), instanceId)
biz.ErrIsNil(err, "获取数据库实例错误") biz.ErrIsNil(err, "获取数据库实例错误")
biz.ErrIsNil(instance.PwdDecrypt())
res, err := d.InstanceApp.GetDatabases(instance) res, err := d.InstanceApp.GetDatabases(instance, authCertName)
biz.ErrIsNil(err) biz.ErrIsNil(err)
rc.ResData = res rc.ResData = res
} }

View File

@@ -1,15 +1,18 @@
package form package form
import tagentity "mayfly-go/internal/tag/domain/entity"
type InstanceForm struct { type InstanceForm struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
Code string `binding:"pattern=resource_code" json:"code"`
Name string `binding:"required" json:"name"` Name string `binding:"required" json:"name"`
Type string `binding:"required" json:"type"` // 类型mysql oracle等 Type string `binding:"required" json:"type"` // 类型mysql oracle等
Host string `binding:"required" json:"host"` Host string `binding:"required" json:"host"`
Port int `json:"port"` Port int `json:"port"`
Extra string `json:"extra"` Extra string `json:"extra"`
Username string `json:"username"`
Password string `json:"password"`
Params string `json:"params"` Params string `json:"params"`
Remark string `json:"remark"` Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` SshTunnelMachineId int `json:"sshTunnelMachineId"`
AuthCerts []*tagentity.ResourceAuthCert `json:"authCerts" binding:"required"` // 资产授权凭证信息列表
} }

View File

@@ -15,11 +15,12 @@ type DbListVO struct {
Remark *string `json:"remark"` Remark *string `json:"remark"`
InstanceId *int64 `json:"instanceId"` InstanceId *int64 `json:"instanceId"`
AuthCertName string `json:"authCertName"`
Username string `json:"username"`
InstanceName *string `json:"instanceName"` InstanceName *string `json:"instanceName"`
InstanceType *string `json:"type"` InstanceType *string `json:"type"`
Host string `json:"host"` Host string `json:"host"`
Port int `json:"port"` Port int `json:"port"`
Username string `json:"username"`
FlowProcdefKey string `json:"flowProcdefKey"` FlowProcdefKey string `json:"flowProcdefKey"`

View File

@@ -1,16 +1,21 @@
package vo package vo
import "time" import (
tagentity "mayfly-go/internal/tag/domain/entity"
"time"
)
type InstanceListVO struct { type InstanceListVO struct {
tagentity.AuthCerts // 授权凭证信息
Id *int64 `json:"id"` Id *int64 `json:"id"`
Code string `json:"code"`
Name *string `json:"name"` Name *string `json:"name"`
Host *string `json:"host"` Host *string `json:"host"`
Port *int `json:"port"` Port *int `json:"port"`
Type *string `json:"type"` Type *string `json:"type"`
Params string `json:"params"` Params string `json:"params"`
Extra string `json:"extra"` Extra string `json:"extra"`
Username *string `json:"username"`
Remark *string `json:"remark"` Remark *string `json:"remark"`
CreateTime *time.Time `json:"createTime"` CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"` Creator *string `json:"creator"`
@@ -22,3 +27,7 @@ type InstanceListVO struct {
SshTunnelMachineId int `json:"sshTunnelMachineId"` SshTunnelMachineId int `json:"sshTunnelMachineId"`
} }
func (i *InstanceListVO) GetCode() string {
return i.Code
}

View File

@@ -15,7 +15,6 @@ import (
"mayfly-go/pkg/errorx" "mayfly-go/pkg/errorx"
"mayfly-go/pkg/model" "mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/structx"
"sort" "sort"
"strings" "strings"
"time" "time"
@@ -86,10 +85,10 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
return d.Tx(ctx, func(ctx context.Context) error { return d.Tx(ctx, func(ctx context.Context) error {
return d.Insert(ctx, dbEntity) return d.Insert(ctx, dbEntity)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: dbEntity.Code, Code: dbEntity.Code,
ResourceType: tagentity.TagTypeDb, Type: tagentity.TagTypeDb,
TagIds: tagIds, ParentTagIds: tagIds,
}) })
}) })
} }
@@ -126,10 +125,10 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
return d.Tx(ctx, func(ctx context.Context) error { return d.Tx(ctx, func(ctx context.Context) error {
return d.UpdateById(ctx, dbEntity) return d.UpdateById(ctx, dbEntity)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: old.Code, Code: old.Code,
ResourceType: tagentity.TagTypeDb, Type: tagentity.TagTypeDb,
TagIds: tagIds, ParentTagIds: tagIds,
}) })
}) })
} }
@@ -153,9 +152,9 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
// 删除该库下用户保存的所有sql信息 // 删除该库下用户保存的所有sql信息
return d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: id}) return d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: id})
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: db.Code, Code: db.Code,
ResourceType: tagentity.TagTypeDb, Type: tagentity.TagTypeDb,
}) })
}) })
} }
@@ -172,11 +171,13 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
return nil, errorx.NewBiz("数据库实例不存在") return nil, errorx.NewBiz("数据库实例不存在")
} }
// 密码解密 di, err := d.dbInstanceApp.ToDbInfo(instance, db.AuthCertName, dbName)
if err := instance.PwdDecrypt(); err != nil { if err != nil {
return nil, errorx.NewBiz(err.Error()) return nil, err
} }
di := toDbInfo(instance, dbId, dbName, d.tagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...) di.TagPath = d.tagApp.ListTagPathByTypeAndCode(consts.ResourceTypeDb, db.Code)
di.Id = db.Id
if db.FlowProcdefKey != nil { if db.FlowProcdefKey != nil {
di.FlowProcdefKey = *db.FlowProcdefKey di.FlowProcdefKey = *db.FlowProcdefKey
} }
@@ -323,14 +324,3 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *DumpDbReq) error {
return nil return nil
} }
func toDbInfo(instance *entity.DbInstance, dbId uint64, database string, tagPath ...string) *dbi.DbInfo {
di := new(dbi.DbInfo)
di.InstanceId = instance.Id
di.Id = dbId
di.Database = database
di.TagPath = tagPath
structx.Copy(di, instance)
return di
}

View File

@@ -3,18 +3,27 @@ package application
import ( import (
"context" "context"
"errors" "errors"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/db/dbm" "mayfly-go/internal/db/dbm"
"mayfly-go/internal/db/dbm/dbi" "mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity" "mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository" "mayfly-go/internal/db/domain/repository"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base" "mayfly-go/pkg/base"
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx" "mayfly-go/pkg/errorx"
"mayfly-go/pkg/model" "mayfly-go/pkg/model"
"mayfly-go/pkg/utils/structx"
"gorm.io/gorm" "gorm.io/gorm"
) )
type SaveDbInstanceParam struct {
DbInstance *entity.DbInstance
AuthCerts []*tagentity.ResourceAuthCert
}
type Instance interface { type Instance interface {
base.App[*entity.DbInstance] base.App[*entity.DbInstance]
@@ -23,23 +32,27 @@ type Instance interface {
Count(condition *entity.InstanceQuery) int64 Count(condition *entity.InstanceQuery) int64
TestConn(instanceEntity *entity.DbInstance) error TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error
Save(ctx context.Context, instanceEntity *entity.DbInstance) error SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) error
// Delete 删除数据库信息 // Delete 删除数据库信息
Delete(ctx context.Context, id uint64) error Delete(ctx context.Context, id uint64) error
// GetDatabases 获取数据库实例的所有数据库列表 // GetDatabases 获取数据库实例的所有数据库列表
GetDatabases(entity *entity.DbInstance) ([]string, error) GetDatabases(entity *entity.DbInstance, authCertName string) ([]string, error)
// ToDbInfo 根据实例与授权凭证返回对应的DbInfo
ToDbInfo(instance *entity.DbInstance, authCertName string, database string) (*dbi.DbInfo, error)
} }
type instanceAppImpl struct { type instanceAppImpl struct {
base.AppImpl[*entity.DbInstance, repository.Instance] base.AppImpl[*entity.DbInstance, repository.Instance]
dbApp Db `inject:"DbApp"` resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
backupApp *DbBackupApp `inject:"DbBackupApp"` dbApp Db `inject:"DbApp"`
restoreApp *DbRestoreApp `inject:"DbRestoreApp"` backupApp *DbBackupApp `inject:"DbBackupApp"`
restoreApp *DbRestoreApp `inject:"DbRestoreApp"`
} }
// 注入DbInstanceRepo // 注入DbInstanceRepo
@@ -56,9 +69,23 @@ func (app *instanceAppImpl) Count(condition *entity.InstanceQuery) int64 {
return app.CountByCond(condition) return app.CountByCond(condition)
} }
func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance) error { func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error {
instanceEntity.Network = instanceEntity.GetNetwork() instanceEntity.Network = instanceEntity.GetNetwork()
dbConn, err := dbm.Conn(toDbInfo(instanceEntity, 0, "", ""))
if authCert.Id != 0 {
// 密文可能被清除,故需要重新获取
authCert, _ = app.resourceAuthCertApp.GetAuthCert(authCert.Name)
} else {
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
publicAuthCert, err := app.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
if err != nil {
return err
}
authCert = publicAuthCert
}
}
dbConn, err := dbm.Conn(app.toDbInfoByAc(instanceEntity, authCert, ""))
if err != nil { if err != nil {
return err return err
} }
@@ -66,47 +93,65 @@ func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance) error {
return nil return nil
} }
func (app *instanceAppImpl) Save(ctx context.Context, instanceEntity *entity.DbInstance) error { func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *SaveDbInstanceParam) error {
instanceEntity := instance.DbInstance
// 默认tcp连接 // 默认tcp连接
instanceEntity.Network = instanceEntity.GetNetwork() instanceEntity.Network = instanceEntity.GetNetwork()
resourceType := consts.ResourceTypeDb
authCerts := instance.AuthCerts
if len(authCerts) == 0 {
return errorx.NewBiz("授权凭证信息不能为空")
}
// 查找是否存在该库 // 查找是否存在该库
oldInstance := &entity.DbInstance{ oldInstance := &entity.DbInstance{
Host: instanceEntity.Host, Host: instanceEntity.Host,
Port: instanceEntity.Port, Port: instanceEntity.Port,
Username: instanceEntity.Username,
SshTunnelMachineId: instanceEntity.SshTunnelMachineId, SshTunnelMachineId: instanceEntity.SshTunnelMachineId,
} }
err := app.GetBy(oldInstance) err := app.GetBy(oldInstance)
if instanceEntity.Id == 0 { if instanceEntity.Id == 0 {
if instanceEntity.Type != string(dbi.DbTypeSqlite) && instanceEntity.Password == "" {
return errorx.NewBiz("密码不能为空")
}
if err == nil { if err == nil {
return errorx.NewBiz("该数据库实例已存在") return errorx.NewBiz("该数据库实例已存在")
} }
if err := instanceEntity.PwdEncrypt(); err != nil { if app.CountByCond(&entity.DbInstance{Code: instanceEntity.Code}) > 0 {
return errorx.NewBiz(err.Error()) return errorx.NewBiz("该编码已存在")
} }
return app.Insert(ctx, instanceEntity)
return app.Tx(ctx, func(ctx context.Context) error {
return app.Insert(ctx, instanceEntity)
}, func(ctx context.Context) error {
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: instanceEntity.Code,
ResourceType: tagentity.TagType(resourceType),
AuthCerts: authCerts,
})
})
} }
// 如果存在该库,则校验修改的库是否为该库 // 如果存在该库,则校验修改的库是否为该库
if err == nil && oldInstance.Id != instanceEntity.Id { if err == nil && oldInstance.Id != instanceEntity.Id {
return errorx.NewBiz("该数据库实例已存在") return errorx.NewBiz("该数据库实例已存在")
} }
if err := instanceEntity.PwdEncrypt(); err != nil { return app.Tx(ctx, func(ctx context.Context) error {
return errorx.NewBiz(err.Error()) return app.UpdateById(ctx, instanceEntity)
} }, func(ctx context.Context) error {
return app.UpdateById(ctx, instanceEntity) return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: oldInstance.Code,
ResourceType: tagentity.TagType(resourceType),
AuthCerts: authCerts,
})
})
} }
func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error { func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error {
instance, err := app.GetById(new(entity.DbInstance), instanceId, "name") instance, err := app.GetById(new(entity.DbInstance), instanceId, "name")
biz.ErrIsNil(err, "获取数据库实例错误数据库实例ID为: %d", instance.Id) if err != nil {
return errorx.NewBiz("获取数据库实例错误数据库实例ID为: %d", instance.Id)
}
restore := &entity.DbRestore{ restore := &entity.DbRestore{
DbInstanceId: instanceId, DbInstanceId: instanceId,
@@ -147,13 +192,25 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
biz.ErrIsNil(err, "删除数据库实例失败: %v", err) biz.ErrIsNil(err, "删除数据库实例失败: %v", err)
} }
return app.DeleteById(ctx, instanceId) return app.Tx(ctx, func(ctx context.Context) error {
return app.DeleteById(ctx, instanceId)
}, func(ctx context.Context) error {
// 删除该实例关联的授权凭证信息
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: instance.Code,
ResourceType: tagentity.TagType(consts.ResourceTypeDb),
})
})
} }
func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance) ([]string, error) { func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance, authCertName string) ([]string, error) {
ed.Network = ed.GetNetwork() ed.Network = ed.GetNetwork()
dbi, err := app.ToDbInfo(ed, authCertName, "")
if err != nil {
return nil, err
}
dbConn, err := dbm.Conn(toDbInfo(ed, 0, "", "")) dbConn, err := dbm.Conn(dbi)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -161,3 +218,23 @@ func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance) ([]string, error
return dbConn.GetMetaData().GetDbNames() return dbConn.GetMetaData().GetDbNames()
} }
func (app *instanceAppImpl) ToDbInfo(instance *entity.DbInstance, authCertName string, database string) (*dbi.DbInfo, error) {
ac, err := app.resourceAuthCertApp.GetAuthCert(authCertName)
if err != nil {
return nil, err
}
return app.toDbInfoByAc(instance, ac, database), nil
}
func (app *instanceAppImpl) toDbInfoByAc(instance *entity.DbInstance, ac *tagentity.ResourceAuthCert, database string) *dbi.DbInfo {
di := new(dbi.DbInfo)
di.InstanceId = instance.Id
di.Database = database
structx.Copy(di, instance)
di.Username = ac.Username
di.Password = ac.Ciphertext
return di
}

View File

@@ -12,5 +12,6 @@ type Db struct {
Database string `orm:"column(database)" json:"database"` Database string `orm:"column(database)" json:"database"`
Remark string `json:"remark"` Remark string `json:"remark"`
InstanceId uint64 InstanceId uint64
AuthCertName string `json:"authCertName"`
FlowProcdefKey *string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批) FlowProcdefKey *string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批)
} }

View File

@@ -1,24 +1,21 @@
package entity package entity
import ( import (
"errors"
"fmt" "fmt"
"mayfly-go/internal/common/utils"
"mayfly-go/pkg/model" "mayfly-go/pkg/model"
) )
type DbInstance struct { type DbInstance struct {
model.Model model.Model
Code string `json:"code"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` // 类型mysql oracle等 Type string `json:"type"` // 类型mysql oracle等
Host string `json:"host"` Host string `json:"host"`
Port int `json:"port"` Port int `json:"port"`
Network string `json:"network"` Network string `json:"network"`
Extra *string `json:"extra"` // 连接需要的其他额外参数json格式, 如oracle需要sid等 Extra *string `json:"extra"` // 连接需要的其他额外参数json格式, 如oracle需要sid等
Username string `json:"username"` Params *string `json:"params"` // 使用指针类型,可更新为零值(空字符串)
Password string `json:"-"`
Params *string `json:"params"`
Remark string `json:"remark"` Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
} }
@@ -39,23 +36,3 @@ func (d *DbInstance) GetNetwork() string {
} }
return fmt.Sprintf("%s+ssh:%d", d.Type, d.SshTunnelMachineId) return fmt.Sprintf("%s+ssh:%d", d.Type, d.SshTunnelMachineId)
} }
func (d *DbInstance) PwdEncrypt() error {
// 密码替换为加密后的密码
password, err := utils.PwdAesEncrypt(d.Password)
if err != nil {
return errors.New("加密数据库密码失败")
}
d.Password = password
return nil
}
func (d *DbInstance) PwdDecrypt() error {
// 密码替换为解密后的密码
password, err := utils.PwdAesDecrypt(d.Password)
if err != nil {
return errors.New("解密数据库密码失败")
}
d.Password = password
return nil
}

View File

@@ -4,6 +4,7 @@ package entity
type InstanceQuery struct { type InstanceQuery struct {
Id uint64 `json:"id" form:"id"` Id uint64 `json:"id" form:"id"`
Name string `json:"name" form:"name"` Name string `json:"name" form:"name"`
Code string `json:"code" form:"code"`
Host string `json:"host" form:"host"` Host string `json:"host" form:"host"`
} }

View File

@@ -19,8 +19,8 @@ func newDbRepo() repository.Db {
// 分页获取数据库信息列表 // 分页获取数据库信息列表
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) { func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQueryWithTableName("t_db db"). qd := gormx.NewQueryWithTableName("t_db db").
Select("db.*, inst.name instance_name, inst.type instance_type, inst.host, inst.port, inst.username "). Select("db.*, inst.name instance_name, inst.type instance_type, inst.host, inst.port, rac.username ").
Joins("JOIN t_db_instance inst ON db.instance_id = inst.id"). Joins("JOIN t_db_instance inst ON db.instance_id = inst.id JOIN t_resource_auth_cert rac ON inst.code = rac.resource_code AND rac.resource_type = 2").
Eq("db.instance_id", condition.InstanceId). Eq("db.instance_id", condition.InstanceId).
Eq("db.id", condition.Id). Eq("db.id", condition.Id).
Like("db.database", condition.Database). Like("db.database", condition.Database).

View File

@@ -21,6 +21,7 @@ func (d *instanceRepoImpl) GetInstanceList(condition *entity.InstanceQuery, page
qd := gormx.NewQuery(new(entity.DbInstance)). qd := gormx.NewQuery(new(entity.DbInstance)).
Eq("id", condition.Id). Eq("id", condition.Id).
Eq("host", condition.Host). Eq("host", condition.Host).
Like("name", condition.Name) Like("name", condition.Name).
Like("code", condition.Code)
return gormx.PageQuery(qd, pageParam, toEntity) return gormx.PageQuery(qd, pageParam, toEntity)
} }

View File

@@ -25,8 +25,6 @@ func InitInstanceRouter(router *gin.RouterGroup) {
req.NewGet(":instanceId", d.GetInstance), req.NewGet(":instanceId", d.GetInstance),
req.NewGet(":instanceId/pwd", d.GetInstancePwd),
// 获取数据库实例的所有数据库名 // 获取数据库实例的所有数据库名
req.NewGet(":instanceId/databases", d.GetDatabaseNames), req.NewGet(":instanceId/databases", d.GetDatabaseNames),

View File

@@ -5,13 +5,13 @@ import tagentity "mayfly-go/internal/tag/domain/entity"
type MachineForm struct { type MachineForm struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
Protocol int `json:"protocol" binding:"required"` Protocol int `json:"protocol" binding:"required"`
Code string `json:"code" binding:"required"` Code string `json:"code" binding:"pattern=resource_code"`
Name string `json:"name" binding:"required"` Name string `json:"name" binding:"required"`
Ip string `json:"ip" binding:"required"` // IP地址 Ip string `json:"ip" binding:"required"` // IP地址
Port int `json:"port" binding:"required"` // 端口号 Port int `json:"port" binding:"required"` // 端口号
TagId []uint64 `json:"tagId" binding:"required"` TagId []uint64 `json:"tagId" binding:"required"`
AuthCerts []*tagentity.ResourceAuthCert // 资产授权凭证信息列表 AuthCerts []*tagentity.ResourceAuthCert `json:"authCerts" binding:"required"` // 资产授权凭证信息列表
Remark string `json:"remark"` Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id

View File

@@ -3,6 +3,7 @@ package api
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/api/form" "mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/api/vo" "mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application" "mayfly-go/internal/machine/application"
@@ -62,12 +63,12 @@ func (m *Machine) Machines(rc *req.Ctx) {
} }
// 填充标签信息 // 填充标签信息
m.TagApp.FillTagInfo(collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.ITagResource { m.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeMachine), collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.ITagResource {
return mvo return mvo
})...) })...)
// 填充授权凭证信息 // 填充授权凭证信息
m.ResourceAuthCertApp.FillAuthCert(authCerts, collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert { m.ResourceAuthCertApp.FillAuthCertByAcs(authCerts, collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
return mvo return mvo
})...) })...)

View File

@@ -17,7 +17,6 @@ import (
"mayfly-go/pkg/logx" "mayfly-go/pkg/logx"
"mayfly-go/pkg/model" "mayfly-go/pkg/model"
"mayfly-go/pkg/scheduler" "mayfly-go/pkg/scheduler"
"time"
) )
type SaveMachineParam struct { type SaveMachineParam struct {
@@ -66,7 +65,6 @@ type Machine interface {
type machineAppImpl struct { type machineAppImpl struct {
base.AppImpl[*entity.Machine, repository.Machine] base.AppImpl[*entity.Machine, repository.Machine]
// authCertApp AuthCert `inject:"AuthCertApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"` tagApp tagapp.TagTree `inject:"TagTreeApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"` resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
} }
@@ -87,6 +85,10 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
authCerts := param.AuthCerts authCerts := param.AuthCerts
resourceType := tagentity.TagTypeMachine resourceType := tagentity.TagTypeMachine
if len(authCerts) == 0 {
return errorx.NewBiz("授权凭证信息不能为空")
}
oldMachine := &entity.Machine{ oldMachine := &entity.Machine{
Ip: me.Ip, Ip: me.Ip,
Port: me.Port, Port: me.Port,
@@ -94,7 +96,6 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
} }
err := m.GetBy(oldMachine) err := m.GetBy(oldMachine)
if me.Id == 0 { if me.Id == 0 {
if err == nil { if err == nil {
return errorx.NewBiz("该机器信息已存在") return errorx.NewBiz("该机器信息已存在")
@@ -109,16 +110,23 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
if err := m.Tx(ctx, func(ctx context.Context) error { if err := m.Tx(ctx, func(ctx context.Context) error {
return m.Insert(ctx, me) return m.Insert(ctx, me)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: me.Code,
Type: resourceType,
ParentTagIds: tagIds,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: me.Code, ResourceCode: me.Code,
ResourceType: resourceType, ResourceType: resourceType,
TagIds: tagIds, AuthCerts: authCerts,
}) })
}); err != nil { }); err != nil {
return err return err
} }
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ // 事务问题,导致可能删除或新增的标签数据不可见,造成数据错误,故单独执行。若万一失败,可授权凭证管理处添加
return m.resourceAuthCertApp.RelateAuthCert2ResourceTag(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: me.Code, ResourceCode: me.Code,
ResourceType: resourceType, ResourceType: resourceType,
AuthCerts: authCerts, AuthCerts: authCerts,
@@ -141,31 +149,44 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *SaveMachinePara
if err := m.Tx(ctx, func(ctx context.Context) error { if err := m.Tx(ctx, func(ctx context.Context) error {
return m.UpdateById(ctx, me) return m.UpdateById(ctx, me)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceCode: oldMachine.Code, Code: oldMachine.Code,
ResourceType: resourceType, Type: resourceType,
TagIds: tagIds, ParentTagIds: tagIds,
}) })
}); err != nil { }); err != nil {
return err return err
} }
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return m.Tx(ctx, func(ctx context.Context) error {
ResourceCode: oldMachine.Code, return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceType: resourceType, ResourceCode: oldMachine.Code,
AuthCerts: authCerts, ResourceType: resourceType,
AuthCerts: authCerts,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert2ResourceTag(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: oldMachine.Code,
ResourceType: resourceType,
AuthCerts: authCerts,
})
}) })
} }
func (m *machineAppImpl) TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error { func (m *machineAppImpl) TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error {
me.Id = 0 me.Id = 0
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic { if authCert.Id != 0 {
publicAuthCert, err := m.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext) // 密文可能被清除,故需要重新获取
if err != nil { authCert, _ = m.resourceAuthCertApp.GetAuthCert(authCert.Name)
return err } else {
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
publicAuthCert, err := m.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
if err != nil {
return err
}
authCert = publicAuthCert
} }
authCert = publicAuthCert
} }
mi, err := m.toMi(me, authCert) mi, err := m.toMi(me, authCert)
@@ -208,12 +229,17 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
func(ctx context.Context) error { func(ctx context.Context) error {
return m.DeleteById(ctx, id) return m.DeleteById(ctx, id)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return m.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Code: machine.Code,
Type: resourceType,
})
}, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: machine.Code, ResourceCode: machine.Code,
ResourceType: resourceType, ResourceType: resourceType,
}) })
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return m.resourceAuthCertApp.RelateAuthCert2ResourceTag(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: machine.Code, ResourceCode: machine.Code,
ResourceType: resourceType, ResourceType: resourceType,
}) })
@@ -267,13 +293,9 @@ func (m *machineAppImpl) TimerUpdateStats() {
}() }()
logx.Debugf("定时获取机器[id=%d]状态信息开始", mid) logx.Debugf("定时获取机器[id=%d]状态信息开始", mid)
cli, err := m.GetCli(mid) cli, err := m.GetCli(mid)
// ssh获取客户端失败则更新机器状态为禁用
if err != nil { if err != nil {
updateMachine := &entity.Machine{Status: entity.MachineStatusDisable} logx.Errorf("定时获取机器[id=%d]状态信息失败, 获取机器cli失败: %s", mid, err.Error())
updateMachine.Id = mid return
now := time.Now()
updateMachine.UpdateTime = &now
m.UpdateById(context.TODO(), updateMachine)
} }
cache.SaveMachineStats(mid, cli.GetAllStats()) cache.SaveMachineStats(mid, cli.GetAllStats())
logx.Debugf("定时获取机器[id=%d]状态信息结束", mid) logx.Debugf("定时获取机器[id=%d]状态信息结束", mid)
@@ -336,7 +358,7 @@ func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAu
mi.Name = me.Name mi.Name = me.Name
mi.Ip = me.Ip mi.Ip = me.Ip
mi.Port = me.Port mi.Port = me.Port
mi.TagPath = m.tagApp.ListTagPathByResource(int8(tagentity.TagTypeMachineAuthCert), authCert.Name) mi.TagPath = m.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeMachineAuthCert), authCert.Name)
mi.EnableRecorder = me.EnableRecorder mi.EnableRecorder = me.EnableRecorder
mi.Protocol = me.Protocol mi.Protocol = me.Protocol

View File

@@ -28,7 +28,7 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
Like("ip", condition.Ip). Like("ip", condition.Ip).
Like("name", condition.Name). Like("name", condition.Name).
In("code", condition.Codes). In("code", condition.Codes).
Eq("code", condition.Code) Like("code", condition.Code)
// 只查询ssh服务器 // 只查询ssh服务器
if condition.Ssh == entity.MachineProtocolSsh { if condition.Ssh == entity.MachineProtocolSsh {

View File

@@ -13,7 +13,7 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) { func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id accountId := rc.GetLoginAccount().Id
mongoNum := len(m.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMongo, "")) mongoNum := len(m.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeMongo, ""))
rc.ResData = collx.M{ rc.ResData = collx.M{
"mongoNum": mongoNum, "mongoNum": mongoNum,

View File

@@ -31,7 +31,7 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.MongoQuery](rc, new(entity.MongoQuery)) queryCond, page := req.BindQueryAndPage[*entity.MongoQuery](rc, new(entity.MongoQuery))
// 不存在可访问标签id即没有可操作数据 // 不存在可访问标签id即没有可操作数据
codes := m.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeMongo, queryCond.TagPath) codes := m.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeMongo, queryCond.TagPath)
if len(codes) == 0 { if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]() rc.ResData = model.EmptyPageResult[any]()
return return
@@ -43,7 +43,7 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
biz.ErrIsNil(err) biz.ErrIsNil(err)
// 填充标签信息 // 填充标签信息
m.TagApp.FillTagInfo(collx.ArrayMap(mongovos, func(mvo *vo.Mongo) tagentity.ITagResource { m.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeMongo), collx.ArrayMap(mongovos, func(mvo *vo.Mongo) tagentity.ITagResource {
return mvo return mvo
})...) })...)

View File

@@ -59,9 +59,9 @@ func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
return d.DeleteById(ctx, id) return d.DeleteById(ctx, id)
}, },
func(ctx context.Context) error { func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceType: tagentity.TagTypeMongo, Type: tagentity.TagTypeMongo,
ResourceCode: mongoEntity.Code, Code: mongoEntity.Code,
}) })
}) })
} }
@@ -90,10 +90,10 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
return d.Tx(ctx, func(ctx context.Context) error { return d.Tx(ctx, func(ctx context.Context) error {
return d.Insert(ctx, m) return d.Insert(ctx, m)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceType: tagentity.TagTypeMongo, Type: tagentity.TagTypeMongo,
ResourceCode: m.Code, Code: m.Code,
TagIds: tagIds, ParentTagIds: tagIds,
}) })
}) })
} }
@@ -113,10 +113,10 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
return d.Tx(ctx, func(ctx context.Context) error { return d.Tx(ctx, func(ctx context.Context) error {
return d.UpdateById(ctx, m) return d.UpdateById(ctx, m)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return d.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceType: tagentity.TagTypeMongo, Type: tagentity.TagTypeMongo,
ResourceCode: oldMongo.Code, Code: oldMongo.Code,
TagIds: tagIds, ParentTagIds: tagIds,
}) })
}) })
} }
@@ -127,6 +127,6 @@ func (d *mongoAppImpl) GetMongoConn(id uint64) (*mgm.MongoConn, error) {
if err != nil { if err != nil {
return nil, errorx.NewBiz("mongo信息不存在") return nil, errorx.NewBiz("mongo信息不存在")
} }
return me.ToMongoInfo(d.tagApp.ListTagPathByResource(consts.TagResourceTypeMongo, me.Code)...), nil return me.ToMongoInfo(d.tagApp.ListTagPathByTypeAndCode(consts.ResourceTypeMongo, me.Code)...), nil
}) })
} }

View File

@@ -13,7 +13,7 @@ type Dashbord struct {
func (m *Dashbord) Dashbord(rc *req.Ctx) { func (m *Dashbord) Dashbord(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id accountId := rc.GetLoginAccount().Id
redisNum := len(m.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeRedis, "")) redisNum := len(m.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeRedis, ""))
rc.ResData = collx.M{ rc.ResData = collx.M{
"redisNum": redisNum, "redisNum": redisNum,

View File

@@ -31,7 +31,7 @@ func (r *Redis) RedisList(rc *req.Ctx) {
queryCond, page := req.BindQueryAndPage[*entity.RedisQuery](rc, new(entity.RedisQuery)) queryCond, page := req.BindQueryAndPage[*entity.RedisQuery](rc, new(entity.RedisQuery))
// 不存在可访问标签id即没有可操作数据 // 不存在可访问标签id即没有可操作数据
codes := r.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeRedis, queryCond.TagPath) codes := r.TagApp.GetAccountTagCodes(rc.GetLoginAccount().Id, consts.ResourceTypeRedis, queryCond.TagPath)
if len(codes) == 0 { if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]() rc.ResData = model.EmptyPageResult[any]()
return return
@@ -43,7 +43,7 @@ func (r *Redis) RedisList(rc *req.Ctx) {
biz.ErrIsNil(err) biz.ErrIsNil(err)
// 填充标签信息 // 填充标签信息
r.TagApp.FillTagInfo(collx.ArrayMap(redisvos, func(rvo *vo.Redis) tagentity.ITagResource { r.TagApp.FillTagInfo(tagentity.TagType(consts.ResourceTypeRedis), collx.ArrayMap(redisvos, func(rvo *vo.Redis) tagentity.ITagResource {
return rvo return rvo
})...) })...)

View File

@@ -107,10 +107,10 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
return r.Tx(ctx, func(ctx context.Context) error { return r.Tx(ctx, func(ctx context.Context) error {
return r.Insert(ctx, re) return r.Insert(ctx, re)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceType: tagenttiy.TagTypeRedis, Type: tagenttiy.TagTypeRedis,
ResourceCode: re.Code, Code: re.Code,
TagIds: tagIds, ParentTagIds: tagIds,
}) })
}) })
} }
@@ -138,10 +138,10 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
return r.Tx(ctx, func(ctx context.Context) error { return r.Tx(ctx, func(ctx context.Context) error {
return r.UpdateById(ctx, re) return r.UpdateById(ctx, re)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceType: tagenttiy.TagTypeRedis, Type: tagenttiy.TagTypeRedis,
ResourceCode: oldRedis.Code, Code: oldRedis.Code,
TagIds: tagIds, ParentTagIds: tagIds,
}) })
}) })
} }
@@ -161,9 +161,9 @@ func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
return r.Tx(ctx, func(ctx context.Context) error { return r.Tx(ctx, func(ctx context.Context) error {
return r.DeleteById(ctx, id) return r.DeleteById(ctx, id)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{ return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
ResourceType: tagenttiy.TagTypeRedis, Type: tagenttiy.TagTypeRedis,
ResourceCode: re.Code, Code: re.Code,
}) })
}) })
} }
@@ -179,7 +179,7 @@ func (r *redisAppImpl) GetRedisConn(id uint64, db int) (*rdm.RedisConn, error) {
if err := re.PwdDecrypt(); err != nil { if err := re.PwdDecrypt(); err != nil {
return nil, errorx.NewBiz(err.Error()) return nil, errorx.NewBiz(err.Error())
} }
return re.ToRedisInfo(db, r.tagApp.ListTagPathByResource(consts.TagResourceTypeRedis, re.Code)...), nil return re.ToRedisInfo(db, r.tagApp.ListTagPathByTypeAndCode(consts.ResourceTypeRedis, re.Code)...), nil
}) })
} }

View File

@@ -87,10 +87,10 @@ func (p *TagTree) DelTagTree(rc *req.Ctx) {
func (p *TagTree) TagResources(rc *req.Ctx) { func (p *TagTree) TagResources(rc *req.Ctx) {
resourceType := int8(rc.PathParamInt("rtype")) resourceType := int8(rc.PathParamInt("rtype"))
accountId := rc.GetLoginAccount().Id accountId := rc.GetLoginAccount().Id
tagResources := p.TagTreeApp.GetAccountTagResources(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType)}) tagResources := p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType)})
tagPath2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(tagResource *entity.TagTree) string { tagPath2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(tagResource *entity.TagTree) string {
return tagResource.GetParentPath(1) return tagResource.GetTagPath()
}) })
tagPaths := collx.MapKeys(tagPath2Resource) tagPaths := collx.MapKeys(tagPath2Resource)
@@ -110,8 +110,8 @@ func (p *TagTree) CountTagResource(rc *req.Ctx) {
rc.ResData = collx.M{ rc.ResData = collx.M{
"machine": len(collx.ArrayDeduplicate(machineCodes)), "machine": len(collx.ArrayDeduplicate(machineCodes)),
"db": len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeDb, tagPath)), "db": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeDb, tagPath)),
"redis": len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeRedis, tagPath)), "redis": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeRedis, tagPath)),
"mongo": len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMongo, tagPath)), "mongo": len(p.TagTreeApp.GetAccountTagCodes(accountId, consts.ResourceTypeMongo, tagPath)),
} }
} }

View File

@@ -23,9 +23,12 @@ type RelateAuthCertParam struct {
type ResourceAuthCert interface { type ResourceAuthCert interface {
base.App[*entity.ResourceAuthCert] base.App[*entity.ResourceAuthCert]
// RelateAuthCert 保存资源授权凭证信息,不可放于事务中 // RelateAuthCert 关联资源授权凭证信息
RelateAuthCert(ctx context.Context, param *RelateAuthCertParam) error RelateAuthCert(ctx context.Context, param *RelateAuthCertParam) error
// RelateAuthCert2ResourceTag 关联授权凭证标签至指定资源标签下
RelateAuthCert2ResourceTag(ctx context.Context, param *RelateAuthCertParam) error
// SaveAuthCert 保存授权凭证信息 // SaveAuthCert 保存授权凭证信息
SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error
@@ -40,9 +43,15 @@ type ResourceAuthCert interface {
// GetAccountAuthCert 获取账号有权限操作的授权凭证信息 // GetAccountAuthCert 获取账号有权限操作的授权凭证信息
GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert GetAccountAuthCert(accountId uint64, authCertTagType entity.TagType, tagPath ...string) []*entity.ResourceAuthCert
// FillAuthCert 填充资源的授权凭证信息 // FillAuthCertByAcs 根据授权凭证列表填充资源的授权凭证信息
// @param authCerts 授权凭证列表
// @param resources 实现了entity.IAuthCert接口的资源信息 // @param resources 实现了entity.IAuthCert接口的资源信息
FillAuthCert(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert) FillAuthCertByAcs(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert)
// FillAuthCert 填充资源对应的授权凭证信息
// @param resourceType 资源类型
// @param resources 实现了entity.IAuthCert接口的资源信息
FillAuthCert(resourceType int8, resources ...entity.IAuthCert)
} }
type resourceAuthCertAppImpl struct { type resourceAuthCertAppImpl struct {
@@ -60,11 +69,7 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
resourceCode := params.ResourceCode resourceCode := params.ResourceCode
resourceType := int8(params.ResourceType) resourceType := int8(params.ResourceType)
resourceAuthCerts := params.AuthCerts resourceAuthCerts := params.AuthCerts
authCertTagType := getResourceAuthCertTagType(entity.TagType(resourceType))
if authCertTagType == 0 {
return errorx.NewBiz("资源授权凭证所属标签类型不能为空")
}
if resourceCode == "" { if resourceCode == "" {
return errorx.NewBiz("资源授权凭证的资源编号不能为空") return errorx.NewBiz("资源授权凭证的资源编号不能为空")
} }
@@ -79,15 +84,6 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
return err return err
} }
// 删除该资源下的所有授权凭证资源标签
if err := r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{
ResourceCode: resourceCode,
ResourceType: params.ResourceType,
ChildType: authCertTagType,
}); err != nil {
return err
}
return nil return nil
} }
@@ -98,7 +94,7 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
name2AuthCert[resourceAuthCert.Name] = resourceAuthCert name2AuthCert[resourceAuthCert.Name] = resourceAuthCert
existNameAc := &entity.ResourceAuthCert{Name: resourceAuthCert.Name} existNameAc := &entity.ResourceAuthCert{Name: resourceAuthCert.Name}
if r.GetBy(existNameAc) == nil && existNameAc.ResourceCode != resourceCode { if resourceAuthCert.Id == 0 && r.GetBy(existNameAc) == nil && existNameAc.ResourceCode != resourceCode {
return errorx.NewBiz("授权凭证的名称不能重复[%s]", resourceAuthCert.Name) return errorx.NewBiz("授权凭证的名称不能重复[%s]", resourceAuthCert.Name)
} }
@@ -141,29 +137,6 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
if err := r.BatchInsert(ctx, addAuthCerts); err != nil { if err := r.BatchInsert(ctx, addAuthCerts); err != nil {
return err return err
} }
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
r.tagTreeApp.ListByCond(&entity.TagTree{Type: params.ResourceType, Code: resourceCode}, &resourceTags)
// 资源标签id相当于父tag id
resourceTagIds := collx.ArrayMap(resourceTags, func(tag *entity.TagTree) uint64 {
return tag.Id
})
if len(resourceTagIds) > 0 {
// 保存授权凭证类型的资源标签
for _, authCert := range addAuthCerts {
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, authCert.Name, resourceTagIds)
if err := r.tagTreeApp.SaveResource(ctx, &SaveResourceTagParam{
ResourceCode: authCert.Name,
ResourceType: authCertTagType,
ResourceName: authCert.Username,
TagIds: resourceTagIds,
}); err != nil {
return err
}
}
}
} }
for _, del := range dels { for _, del := range dels {
@@ -171,13 +144,6 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType, Name: del}); err != nil { if err := r.DeleteByCond(ctx, &entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType, Name: del}); err != nil {
return err return err
} }
// 删除对应授权凭证资源标签
if err := r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{
ResourceCode: del,
ResourceType: authCertTagType,
}); err != nil {
return err
}
} }
if len(unmodifys) > 0 { if len(unmodifys) > 0 {
@@ -185,6 +151,7 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
oldName2AuthCert := collx.ArrayToMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string { oldName2AuthCert := collx.ArrayToMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string {
return ac.Name return ac.Name
}) })
acTagType := GetResourceAuthCertTagType(params.ResourceType)
for _, unmodify := range unmodifys { for _, unmodify := range unmodifys {
unmodifyAc := name2AuthCert[unmodify] unmodifyAc := name2AuthCert[unmodify]
if unmodifyAc.Id == 0 { if unmodifyAc.Id == 0 {
@@ -197,13 +164,11 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
continue continue
} }
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-更新授权凭证-[%v]", resourceType, resourceCode, unmodify) // 如果修改了用户名且该凭证关联至标签则需要更新对应的标签名资源授权凭证类型的标签名为username
if oldAuthCert.Username != unmodifyAc.Username { if oldAuthCert.Username != unmodifyAc.Username && acTagType != 0 {
if err := r.updateAuthCertTagName(ctx, unmodify, authCertTagType, unmodifyAc.Username); err != nil { r.updateAuthCertTagName(ctx, unmodify, acTagType, unmodifyAc.Username)
logx.WarnfContext(ctx, "授权凭证[%s]修改了用户名-同步更新授权凭证标签名失败", unmodify)
}
} }
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-更新授权凭证-[%v]", resourceType, resourceCode, unmodify)
if err := r.UpdateById(ctx, unmodifyAc); err != nil { if err := r.UpdateById(ctx, unmodifyAc); err != nil {
return err return err
} }
@@ -213,6 +178,17 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
return nil return nil
} }
func (r *resourceAuthCertAppImpl) RelateAuthCert2ResourceTag(ctx context.Context, param *RelateAuthCertParam) error {
return r.tagTreeApp.RelateTagsByCodeAndType(ctx, &RelateTagsByCodeAndTypeParam{
ParentTagCode: param.ResourceCode,
ParentTagType: param.ResourceType,
TagType: GetResourceAuthCertTagType(param.ResourceType),
Tags: collx.ArrayMap(param.AuthCerts, func(rac *entity.ResourceAuthCert) ITag {
return rac
}),
})
}
func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error { func (r *resourceAuthCertAppImpl) SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error {
if rac.Id == 0 { if rac.Id == 0 {
return r.addAuthCert(ctx, rac) return r.addAuthCert(ctx, rac)
@@ -242,7 +218,7 @@ func (r *resourceAuthCertAppImpl) DeleteAuthCert(ctx context.Context, id uint64)
return r.Tx(ctx, return r.Tx(ctx,
func(ctx context.Context) error { func(ctx context.Context) error {
// 删除对应授权凭证标签 // 删除对应授权凭证标签
return r.tagTreeApp.DeleteResource(ctx, &DelResourceTagParam{ return r.tagTreeApp.DeleteTagByParam(ctx, &DelResourceTagParam{
ResourceCode: rac.Name, ResourceCode: rac.Name,
}) })
}, },
@@ -288,7 +264,7 @@ func (r *resourceAuthCertAppImpl) GetAccountAuthCert(accountId uint64, authCertT
Type: authCertTagType, Type: authCertTagType,
CodePathLikes: tagPath, CodePathLikes: tagPath,
} }
authCertTags := r.tagTreeApp.GetAccountTagResources(accountId, tagQuery) authCertTags := r.tagTreeApp.GetAccountTags(accountId, tagQuery)
// 获取所有授权凭证名称 // 获取所有授权凭证名称
authCertNames := collx.ArrayMap(authCertTags, func(tag *entity.TagTree) string { authCertNames := collx.ArrayMap(authCertTags, func(tag *entity.TagTree) string {
@@ -303,7 +279,7 @@ func (r *resourceAuthCertAppImpl) GetAccountAuthCert(accountId uint64, authCertT
return authCerts return authCerts
} }
func (r *resourceAuthCertAppImpl) FillAuthCert(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert) { func (r *resourceAuthCertAppImpl) FillAuthCertByAcs(authCerts []*entity.ResourceAuthCert, resources ...entity.IAuthCert) {
if len(resources) == 0 || len(authCerts) == 0 { if len(resources) == 0 || len(authCerts) == 0 {
return return
} }
@@ -329,6 +305,19 @@ func (r *resourceAuthCertAppImpl) FillAuthCert(authCerts []*entity.ResourceAuthC
} }
} }
func (r *resourceAuthCertAppImpl) FillAuthCert(resourceType int8, resources ...entity.IAuthCert) {
if len(resources) == 0 {
return
}
resourceCodes := collx.ArrayMap(resources, func(ac entity.IAuthCert) string {
return ac.GetCode()
})
var acs []*entity.ResourceAuthCert
r.Repo.ListByWheres(collx.M{"resource_code in ?": resourceCodes, "resource_type = ?": resourceType}, &acs)
r.FillAuthCertByAcs(acs, resources...)
}
// addAuthCert 添加授权凭证 // addAuthCert 添加授权凭证
func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error { func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error {
if r.CountByCond(&entity.ResourceAuthCert{Name: rac.Name}) > 0 { if r.CountByCond(&entity.ResourceAuthCert{Name: rac.Name}) > 0 {
@@ -344,32 +333,45 @@ func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.R
resourceCode := rac.ResourceCode resourceCode := rac.ResourceCode
resourceType := rac.ResourceType resourceType := rac.ResourceType
// 资源对应的授权凭证标签类型若为0则说明该资源不需要关联至资源tagTree
authCertTagType := GetResourceAuthCertTagType(entity.TagType(resourceType))
// 获取资源编号对应的资源标签信息 var resourceTagIds []uint64
var resourceTags []*entity.TagTree // 如果该资源存在对应的授权凭证标签类型则说明需要关联至tagTree否则直接从授权凭证库中验证资源编号是否正确即可一个资源最少有一个授权凭证
r.tagTreeApp.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &resourceTags) if authCertTagType != 0 {
// 资源标签id相当于父tag id // 获取资源编号对应的资源标签信息
resourceTagIds := collx.ArrayMap(resourceTags, func(tag *entity.TagTree) uint64 { var resourceTags []*entity.TagTree
return tag.Id r.tagTreeApp.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &resourceTags)
}) // 资源标签id相当于父tag id
if len(resourceTagIds) == 0 { resourceTagIds = collx.ArrayMap(resourceTags, func(tag *entity.TagTree) uint64 {
return errorx.NewBiz("资源标签不存在[%s], 请检查资源编号是否正确", resourceCode) return tag.Id
})
if len(resourceTagIds) == 0 {
return errorx.NewBiz("资源标签不存在[%s], 请检查资源编号是否正确", resourceCode)
}
} else {
if r.CountByCond(&entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}) == 0 {
return errorx.NewBiz("该授权凭证关联的资源信息不存在, 请检查资源编号")
}
} }
authCertTagType := getResourceAuthCertTagType(entity.TagType(resourceType))
// 如果密文类型不为公共凭证,则进行加密。公共凭证密文内容存的是明文的公共凭证名 // 如果密文类型不为公共凭证,则进行加密。公共凭证密文内容存的是明文的公共凭证名
if rac.CiphertextType != entity.AuthCertCiphertextTypePublic { if rac.CiphertextType != entity.AuthCertCiphertextTypePublic {
rac.CiphertextEncrypt() rac.CiphertextEncrypt()
} }
return r.Tx(ctx, func(ctx context.Context) error { return r.Tx(ctx, func(ctx context.Context) error {
logx.DebugfContext(ctx, "[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, rac.Name, resourceTagIds) // 若存在需要关联到的资源标签,则关联到对应的资源标签下
return r.tagTreeApp.SaveResource(ctx, &SaveResourceTagParam{ if len(resourceTagIds) > 0 {
ResourceCode: rac.Name, logx.DebugfContext(ctx, "[%d-%s]-授权凭证标签[%d-%s]关联至所属资源标签下[%v]", resourceType, resourceCode, authCertTagType, rac.Name, resourceTagIds)
ResourceType: authCertTagType, return r.tagTreeApp.SaveResourceTag(ctx, &SaveResourceTagParam{
ResourceName: rac.Username, Code: rac.Name,
TagIds: resourceTagIds, Type: GetResourceAuthCertTagType(entity.TagType(resourceType)),
}) Name: rac.Username,
ParentTagIds: resourceTagIds,
})
}
return nil
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return r.Insert(ctx, rac) return r.Insert(ctx, rac)
}) })
@@ -401,9 +403,12 @@ func (r *resourceAuthCertAppImpl) updateAuthCert(ctx context.Context, rac *entit
} }
// 修改了用户名,则需要同步更新对应授权凭证标签里的名称 // 修改了用户名,则需要同步更新对应授权凭证标签里的名称
if rac.Username != oldRac.Username { if rac.Username != oldRac.Username && rac.ResourceType == int8(entity.TagTypeMachine) {
if err := r.updateAuthCertTagName(ctx, oldRac.Name, getResourceAuthCertTagType(entity.TagType(oldRac.ResourceType)), rac.Username); err != nil { authCertTagType := GetResourceAuthCertTagType(entity.TagType(oldRac.ResourceType))
return errorx.NewBiz("同步更新授权凭证标签名称失败") if authCertTagType != 0 {
if err := r.updateAuthCertTagName(ctx, oldRac.Name, authCertTagType, rac.Username); err != nil {
return errorx.NewBiz("同步更新授权凭证标签名称失败")
}
} }
} }
} }
@@ -420,18 +425,6 @@ func (r *resourceAuthCertAppImpl) updateAuthCertTagName(ctx context.Context, aut
return r.tagTreeApp.UpdateByWheres(ctx, &entity.TagTree{Name: newTagName}, collx.M{"code = ?": authCertName, "type = ?": autyCertTagType}) return r.tagTreeApp.UpdateByWheres(ctx, &entity.TagTree{Name: newTagName}, collx.M{"code = ?": authCertName, "type = ?": autyCertTagType})
} }
// getResourceAuthCertTagType 根据资源类型,获取对应的授权凭证标签类型
func getResourceAuthCertTagType(resourceType entity.TagType) entity.TagType {
if resourceType == entity.TagTypeMachine {
return entity.TagTypeMachineAuthCert
}
if resourceType == entity.TagTypeDb {
return entity.TagTypeDbAuthCert
}
return -2
}
// 解密授权凭证信息 // 解密授权凭证信息
func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) { func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) {
if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic { if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
@@ -457,3 +450,13 @@ func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthC
} }
return authCert, nil return authCert, nil
} }
// GetResourceAuthCertTagType 根据资源类型获取对应的授权凭证标签类型return 0 说明该资源授权凭证不关联至tagTree
func GetResourceAuthCertTagType(resourceType entity.TagType) entity.TagType {
if resourceType == entity.TagTypeMachine {
return entity.TagTypeMachineAuthCert
}
// 该资源不存在对应的授权凭证标签即tag_tree不关联该资源的授权凭证
return 0
}

View File

@@ -2,26 +2,46 @@ package application
import ( import (
"context" "context"
"fmt"
"mayfly-go/internal/common/consts" "mayfly-go/internal/common/consts"
"mayfly-go/internal/tag/domain/entity" "mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository" "mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/base" "mayfly-go/pkg/base"
"mayfly-go/pkg/contextx" "mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx" "mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
"strings" "strings"
) )
// 保存资源标签参数 // 标签接口,实现了该接口的结构体默认都可以当成标签树的一种标签
type SaveResourceTagParam struct { type ITag interface {
ResourceCode string // 资源标签code
ResourceName string GetCode() string
ResourceType entity.TagType
TagIds []uint64 // 关联标签,相当于父标签 pid空数组则为删除该资源绑定的标签 // 资源标签
GetName() string
}
// 保存标签参数
type SaveResourceTagParam struct {
Code string
Name string
Type entity.TagType
ParentTagIds []uint64 // 关联标签,空数组则为删除该资源绑定的标签
}
type RelateTagsByCodeAndTypeParam struct {
ParentTagCode string // 父标签编号
ParentTagType entity.TagType // 父标签类型
TagType entity.TagType // 要关联的标签类型
Tags []ITag // 要关联的标签数组
} }
type DelResourceTagParam struct { type DelResourceTagParam struct {
Id uint64
ResourceCode string ResourceCode string
ResourceType entity.TagType ResourceType entity.TagType
@@ -40,23 +60,25 @@ type TagTree interface {
Delete(ctx context.Context, id uint64) error Delete(ctx context.Context, id uint64) error
// 获取指定账号有权限操作的资源信息列表 // 获取指定账号有权限操作的标签列表
// @param accountId 账号id // @param accountId 账号id
// @param resourceType 资源类型 // @param query 查询条件
// @param tagPath 访问指定的标签路径下关联的资源 GetAccountTags(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree
GetAccountTagResources(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree
// 获取指定账号有权限操作的资源codes // 获取指定账号有权限操作的标签codes
GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string GetAccountTagCodes(accountId uint64, resourceType int8, tagPath string) []string
// SaveResource 保存资源标签 // SaveResourceTag 保存资源类型标签
SaveResource(ctx context.Context, req *SaveResourceTagParam) error SaveResourceTag(ctx context.Context, param *SaveResourceTagParam) error
// DeleteResource 删除资源标签,会删除该资源下所有子节点信息 // RelateTagsByCodeAndType 将指定标签数组关联至满足指定标签类型和标签code的标签下
DeleteResource(ctx context.Context, param *DelResourceTagParam) error RelateTagsByCodeAndType(ctx context.Context, param *RelateTagsByCodeAndTypeParam) error
// 根据资源信息获取对应的标签路径列表 // DeleteTagByParam 删除标签,会删除该标签下所有子标签信息以及团队关联的标签信息
ListTagPathByResource(resourceType int8, resourceCode string) []string DeleteTagByParam(ctx context.Context, param *DelResourceTagParam) error
// 根据标签类型和标签code获取对应的标签路径列表
ListTagPathByTypeAndCode(resourceType int8, resourceCode string) []string
// 根据账号id获取其可访问标签信息 // 根据账号id获取其可访问标签信息
ListTagByAccountId(accountId uint64) []string ListTagByAccountId(accountId uint64) []string
@@ -65,7 +87,7 @@ type TagTree interface {
CanAccess(accountId uint64, tagPath ...string) error CanAccess(accountId uint64, tagPath ...string) error
// 填充资源的标签信息 // 填充资源的标签信息
FillTagInfo(resources ...entity.ITagResource) FillTagInfo(resourceTagType entity.TagType, resources ...entity.ITagResource)
} }
type tagTreeAppImpl struct { type tagTreeAppImpl struct {
@@ -114,7 +136,7 @@ func (p *tagTreeAppImpl) Save(ctx context.Context, tag *entity.TagTree) error {
} }
// 普通标签类型 // 普通标签类型
tag.Type = -1 tag.Type = entity.TagTypeTag
return p.Insert(ctx, tag) return p.Insert(ctx, tag)
} }
@@ -128,7 +150,7 @@ func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity an
p.GetRepo().SelectByCondition(condition, toEntity) p.GetRepo().SelectByCondition(condition, toEntity)
} }
func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree { func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQuery) []*entity.TagTree {
tagResourceQuery := &entity.TagTreeQuery{ tagResourceQuery := &entity.TagTreeQuery{
Type: query.Type, Type: query.Type,
} }
@@ -182,8 +204,8 @@ func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, query *entity.
return tagResources return tagResources
} }
func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string { func (p *tagTreeAppImpl) GetAccountTagCodes(accountId uint64, resourceType int8, tagPath string) []string {
tagResources := p.GetAccountTagResources(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType), CodePathLikes: []string{tagPath}}) tagResources := p.GetAccountTags(accountId, &entity.TagTreeQuery{Type: entity.TagType(resourceType), CodePathLikes: []string{tagPath}})
// resouce code去重 // resouce code去重
code2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(val *entity.TagTree) string { code2Resource := collx.ArrayToMap[*entity.TagTree, string](tagResources, func(val *entity.TagTree) string {
return val.Code return val.Code
@@ -192,34 +214,34 @@ func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType
return collx.MapKeys(code2Resource) return collx.MapKeys(code2Resource)
} }
func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagParam) error { func (p *tagTreeAppImpl) SaveResourceTag(ctx context.Context, param *SaveResourceTagParam) error {
resourceCode := req.ResourceCode code := param.Code
resourceType := entity.TagType(req.ResourceType) tagType := entity.TagType(param.Type)
resourceName := req.ResourceName name := param.Name
tagIds := req.TagIds tagIds := param.ParentTagIds
if resourceCode == "" { if code == "" {
return errorx.NewBiz("资源编号不能为空") return errorx.NewBiz("资源编号不能为空")
} }
if resourceType == 0 { if tagType == 0 {
return errorx.NewBiz("资源类型不能为空") return errorx.NewBiz("资源类型不能为空")
} }
// 如果tagIds为空数组则为删除该资源标签 // 如果tagIds为空数组则为删除该资源标签
if len(tagIds) == 0 { if len(tagIds) == 0 {
return p.DeleteResource(ctx, &DelResourceTagParam{ return p.DeleteTagByParam(ctx, &DelResourceTagParam{
ResourceType: resourceType, ResourceType: tagType,
ResourceCode: resourceCode, ResourceCode: code,
}) })
} }
if resourceName == "" { if name == "" {
resourceName = resourceCode name = code
} }
// 该资源对应的旧资源标签信息 // 该资源对应的旧资源标签信息
var oldTagTree []*entity.TagTree var oldTagTree []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: resourceType, Code: resourceCode}, &oldTagTree) p.ListByCond(&entity.TagTree{Type: tagType, Code: code}, &oldTagTree)
var addTagIds, delTagIds []uint64 var addTagIds, delTagIds []uint64
if len(oldTagTree) == 0 { if len(oldTagTree) == 0 {
@@ -240,11 +262,12 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
} }
addTagResource = append(addTagResource, &entity.TagTree{ addTagResource = append(addTagResource, &entity.TagTree{
Pid: tagId, Pid: tagId,
Code: resourceCode, Code: code,
Type: resourceType, Type: tagType,
Name: resourceName, Name: name,
CodePath: tag.CodePath + resourceCode + entity.CodePathSeparator, CodePath: fmt.Sprintf("%s%d%s%s%s", tag.CodePath, tagType, entity.CodePathResourceSeparator, code, entity.CodePathSeparator), // tag1/tag2/1|resourceCode1/11|resourceCode2
}) })
} }
if err := p.BatchInsert(ctx, addTagResource); err != nil { if err := p.BatchInsert(ctx, addTagResource); err != nil {
return err return err
@@ -253,9 +276,9 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
if len(delTagIds) > 0 { if len(delTagIds) > 0 {
for _, tagId := range delTagIds { for _, tagId := range delTagIds {
if err := p.DeleteResource(ctx, &DelResourceTagParam{ if err := p.DeleteTagByParam(ctx, &DelResourceTagParam{
ResourceType: resourceType, ResourceType: tagType,
ResourceCode: resourceCode, ResourceCode: code,
Pid: tagId, Pid: tagId,
}); err != nil { }); err != nil {
return err return err
@@ -267,29 +290,136 @@ func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagP
return nil return nil
} }
func (p *tagTreeAppImpl) DeleteResource(ctx context.Context, param *DelResourceTagParam) error { func (p *tagTreeAppImpl) RelateTagsByCodeAndType(ctx context.Context, param *RelateTagsByCodeAndTypeParam) error {
// 获取资源编号对应的资源标签信息 parentTagCode := param.ParentTagCode
var resourceTags []*entity.TagTree parentTagType := param.ParentTagType
p.ListByCond(&entity.TagTree{Type: param.ResourceType, Code: param.ResourceCode, Pid: param.Pid}, &resourceTags) tagType := param.TagType
if len(resourceTags) == 0 {
return nil // 如果资源为,则表示清楚关联
if len(param.Tags) == 0 {
// 删除该资源下的所有指定类型的资源
return p.DeleteTagByParam(ctx, &DelResourceTagParam{
ResourceCode: parentTagCode,
ResourceType: param.ParentTagType,
ChildType: tagType,
})
} }
delTagType := param.ChildType // 获取满足指定编号与类型的所有标签信息
for _, resourceTag := range resourceTags { var parentTags []*entity.TagTree
// 删除所有code_path下的子标签 p.ListByCond(&entity.TagTree{Type: parentTagType, Code: parentTagCode}, &parentTags)
if err := p.DeleteByWheres(ctx, collx.M{ // 标签id相当于需要关联的标签数组的父tag id
"code_path LIKE ?": resourceTag.CodePath + "%", parentTagIds := collx.ArrayMap(parentTags, func(tag *entity.TagTree) uint64 {
"type = ?": delTagType, return tag.Id
}); err != nil { })
if len(parentTagIds) == 0 {
return errorx.NewBiz("不存在满足[type=%d, code=%s]的标签", parentTagType, parentTagCode)
}
var oldChildrenTags []*entity.TagTree
// 获取该资源的所有旧的该类型的子标签
p.ListByQuery(&entity.TagTreeQuery{
CodePathLikes: collx.ArrayMap[*entity.TagTree, string](parentTags, func(val *entity.TagTree) string { return val.CodePath }),
Type: tagType,
}, &oldChildrenTags)
// 组合新的授权凭证资源标签
newTags := make([]*entity.TagTree, 0)
for _, resourceTag := range parentTags {
for _, resource := range param.Tags {
tagCode := resource.GetCode()
newTags = append(newTags, &entity.TagTree{
Pid: resourceTag.Id,
Type: tagType,
Code: tagCode,
CodePath: fmt.Sprintf("%s%d%s%s%s", resourceTag.CodePath, tagType, entity.CodePathResourceSeparator, tagCode, entity.CodePathSeparator),
Name: resource.GetName(),
})
}
}
// 旧的codePath -> tag
oldCodePath2Tag := collx.ArrayToMap[*entity.TagTree, string](oldChildrenTags, func(val *entity.TagTree) string { return val.CodePath })
// 新的codePath -> tag
newCodePath2Tag := collx.ArrayToMap[*entity.TagTree, string](newTags, func(val *entity.TagTree) string { return val.CodePath })
var addCodePaths, delCodePaths []string
addCodePaths, delCodePaths, _ = collx.ArrayCompare(collx.MapKeys(newCodePath2Tag), collx.MapKeys(oldCodePath2Tag))
if len(addCodePaths) > 0 {
logx.DebugfContext(ctx, "RelateTags2CodeAndType[%d-%s]-新增标签[%v]", parentTagType, parentTagCode, addCodePaths)
addTags := make([]*entity.TagTree, 0)
for _, addCodePath := range addCodePaths {
addTags = append(addTags, newCodePath2Tag[addCodePath])
}
if err := p.BatchInsert(ctx, addTags); err != nil {
return err return err
} }
} }
if len(delCodePaths) > 0 {
logx.DebugfContext(ctx, "RelateTags2CodeAndType[%d-%s]-删除标签[%v]", parentTagType, parentTagCode, delCodePaths)
for _, delCodePath := range delCodePaths {
oldTag := oldCodePath2Tag[delCodePath]
if oldTag == nil {
continue
}
if err := p.DeleteTagByParam(ctx, &DelResourceTagParam{
Id: oldTag.Id,
}); err != nil {
return err
}
}
}
return nil return nil
} }
func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string { func (p *tagTreeAppImpl) DeleteTagByParam(ctx context.Context, param *DelResourceTagParam) error {
// 获取资源编号对应的资源标签信息
var resourceTags []*entity.TagTree
cond := &entity.TagTree{Type: param.ResourceType, Code: param.ResourceCode, Pid: param.Pid}
cond.Id = param.Id
p.ListByCond(cond, &resourceTags)
if len(resourceTags) == 0 {
logx.DebugfContext(ctx, "TagTreeApp.DeleteResource[%d-%s]不存在可删除的标签", param.ResourceType, param.ResourceCode)
return nil
}
delTagType := param.ChildType
for _, resourceTag := range resourceTags {
// 获取所有关联的子标签
var childrenTag []*entity.TagTree
p.Repo.ListByWheres(collx.M{
"code_path LIKE ?": resourceTag.CodePath + "%",
"type = ?": delTagType,
}, &childrenTag)
if len(childrenTag) == 0 {
continue
}
childrenTagIds := collx.ArrayMap(childrenTag, func(item *entity.TagTree) uint64 {
return item.Id
})
// 删除所有code_path下的子标签
if err := p.DeleteByWheres(ctx, collx.M{
"id in ?": childrenTagIds,
}); err != nil {
return err
}
// 删除team关联的标签
return p.tagTreeTeamRepo.DeleteByWheres(ctx, collx.M{"tag_id in ?": childrenTagIds})
}
return nil
}
func (p *tagTreeAppImpl) ListTagPathByTypeAndCode(resourceType int8, resourceCode string) []string {
var trs []*entity.TagTree var trs []*entity.TagTree
p.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &trs) p.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode}, &trs)
return collx.ArrayMap(trs, func(tr *entity.TagTree) string { return collx.ArrayMap(trs, func(tr *entity.TagTree) string {
@@ -318,7 +448,7 @@ func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath ...string) error {
return errorx.NewBiz("您无权操作该资源") return errorx.NewBiz("您无权操作该资源")
} }
func (p *tagTreeAppImpl) FillTagInfo(resources ...entity.ITagResource) { func (p *tagTreeAppImpl) FillTagInfo(resourceTagType entity.TagType, resources ...entity.ITagResource) {
if len(resources) == 0 { if len(resources) == 0 {
return return
} }
@@ -330,11 +460,11 @@ func (p *tagTreeAppImpl) FillTagInfo(resources ...entity.ITagResource) {
// 获取所有资源code关联的标签列表信息 // 获取所有资源code关联的标签列表信息
var tagResources []*entity.TagTree var tagResources []*entity.TagTree
p.ListByQuery(&entity.TagTreeQuery{Codes: collx.MapKeys(resourceCode2Resouce)}, &tagResources) p.ListByQuery(&entity.TagTreeQuery{Codes: collx.MapKeys(resourceCode2Resouce), Type: resourceTagType}, &tagResources)
for _, tr := range tagResources { for _, tr := range tagResources {
// 赋值标签信息 // 赋值标签信息
resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetParentPath(0)}) resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetTagPath()})
} }
} }

View File

@@ -97,6 +97,16 @@ func (m *ResourceAuthCert) GetExtraString(key string) string {
return cast.ToString(m.Extra[key]) return cast.ToString(m.Extra[key])
} }
// IResourceTag接口 授权凭证名 -> 资源标签code
func (m *ResourceAuthCert) GetCode() string {
return m.Name
}
// IResourceTag接口 授权凭证用户名 -> 资源标签名
func (m *ResourceAuthCert) GetName() string {
return m.Username
}
// HasChanged 与指定授权凭证比较是否有变更 // HasChanged 与指定授权凭证比较是否有变更
func (m *ResourceAuthCert) HasChanged(rac *ResourceAuthCert) bool { func (m *ResourceAuthCert) HasChanged(rac *ResourceAuthCert) bool {
if rac == nil { if rac == nil {

View File

@@ -23,17 +23,18 @@ type TagType int8
const ( const (
// 标识路径分隔符 // 标识路径分隔符
CodePathSeparator = "/" CodePathSeparator = "/"
// 标签路径资源段分隔符
CodePathResourceSeparator = "|"
TagTypeTag TagType = -1 TagTypeTag TagType = -1
TagTypeMachine TagType = TagType(consts.TagResourceTypeMachine) TagTypeMachine TagType = TagType(consts.ResourceTypeMachine)
TagTypeDb TagType = TagType(consts.TagResourceTypeDb) TagTypeDb TagType = TagType(consts.ResourceTypeDb)
TagTypeRedis TagType = TagType(consts.TagResourceTypeRedis) TagTypeRedis TagType = TagType(consts.ResourceTypeRedis)
TagTypeMongo TagType = TagType(consts.TagResourceTypeMongo) TagTypeMongo TagType = TagType(consts.ResourceTypeMongo)
// ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证) // ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证)
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证 TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
TagTypeDbAuthCert TagType = 21 // DB-授权凭证
) )
// GetRootCode 获取根路径信息 // GetRootCode 获取根路径信息
@@ -62,6 +63,30 @@ func (pt *TagTree) GetParentPath(index int) string {
return parentPath + "/" return parentPath + "/"
} }
// GetTagPath 获取标签段路径,不获取对应资源相关路径
func (pt *TagTree) GetTagPath() string {
codePath := pt.CodePath
// 以 资源分隔符"|" 符号对字符串进行分割
parts := strings.Split(codePath, CodePathResourceSeparator)
if len(parts) < 2 {
return codePath
}
// 从分割后的第一个子串中提取所需部分
substringBeforeNumber := parts[0]
// 找到最后一个 "/" 的位置
lastSlashIndex := strings.LastIndex(substringBeforeNumber, CodePathSeparator)
// 如果找到最后一个 "/" 符号,则截取子串
if lastSlashIndex != -1 {
return substringBeforeNumber[:lastSlashIndex+1]
}
return codePath
}
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口 // 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
type ITagResource interface { type ITagResource interface {
// 获取资源code // 获取资源code

View File

@@ -18,6 +18,7 @@ var (
func RegisterCustomPatterns() { func RegisterCustomPatterns() {
// 账号用户名校验 // 账号用户名校验
RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允许输入5-20位大小写字母、数字、下划线") RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允许输入5-20位大小写字母、数字、下划线")
RegisterPattern("resource_code", "^[a-zA-Z0-9_.:]{1,32}$", "只允许输入1-32位大小写字母、数字、_.:")
} }
// 注册自定义正则表达式 // 注册自定义正则表达式

View File

@@ -873,7 +873,7 @@ CREATE TABLE `t_tag_tree` (
`pid` bigint(20) NOT NULL DEFAULT '0', `pid` bigint(20) NOT NULL DEFAULT '0',
`type` tinyint NOT NULL DEFAULT '-1' COMMENT '类型: -1.普通标签; 其他值则为对应的资源类型', `type` tinyint NOT NULL DEFAULT '-1' COMMENT '类型: -1.普通标签; 其他值则为对应的资源类型',
`code` varchar(36) NOT NULL COMMENT '标识符', `code` varchar(36) NOT NULL COMMENT '标识符',
`code_path` varchar(255) NOT NULL COMMENT '标识符路径', `code_path` varchar(555) NOT NULL COMMENT '标识符路径',
`name` varchar(36) DEFAULT NULL COMMENT '名称', `name` varchar(36) DEFAULT NULL COMMENT '名称',
`remark` varchar(255) DEFAULT NULL, `remark` varchar(255) DEFAULT NULL,
`create_time` datetime NOT NULL, `create_time` datetime NOT NULL,

View File

@@ -76,7 +76,7 @@ INSERT
select select
tag_id, tag_id,
resource_code, resource_code,
CONCAT(tag_path , resource_code, '/'), CONCAT(tag_path ,resource_type , '|', resource_code, '/'),
resource_type, resource_type,
resource_code, resource_code,
DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'), DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),
@@ -175,7 +175,7 @@ INSERT
SELECT SELECT
tt.id, tt.id,
rac.`name`, rac.`name`,
CONCAT(tt.code_path, rac.`name`, '/'), CONCAT(tt.code_path, '11|' ,rac.`name`, '/'),
11, 11,
rac.`username`, rac.`username`,
DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'), DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),
@@ -188,9 +188,9 @@ SELECT
FROM FROM
`t_tag_tree` tt `t_tag_tree` tt
JOIN `t_resource_auth_cert` rac ON tt.`code` = rac.`resource_code` JOIN `t_resource_auth_cert` rac ON tt.`code` = rac.`resource_code`
AND tt.`type` = rac.`resource_type` AND tt.`type` = rac.`resource_type` AND rac.type = 1
WHERE WHERE
tt.`is_deleted` = 0 tt.`is_deleted` = 0;
-- 删除机器表 账号相关字段 -- 删除机器表 账号相关字段
ALTER TABLE t_machine DROP COLUMN username; ALTER TABLE t_machine DROP COLUMN username;
@@ -205,3 +205,65 @@ INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1712717337, 1712717290, 'tLb8TKLB/m2abQkA8/', 2, 1, '授权凭证密文查看', 'authcert:showciphertext', 1712717337, 'null', 1, 'admin', 1, 'admin', '2024-04-10 10:48:58', '2024-04-10 10:48:58', 0, NULL); INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1712717337, 1712717290, 'tLb8TKLB/m2abQkA8/', 2, 1, '授权凭证密文查看', 'authcert:showciphertext', 1712717337, 'null', 1, 'admin', 1, 'admin', '2024-04-10 10:48:58', '2024-04-10 10:48:58', 0, NULL);
commit; commit;
-- 关联数据库账号至授权凭证表
begin;
ALTER TABLE t_db_instance ADD code varchar(36) NULL COMMENT '唯一编号';
ALTER TABLE t_db_instance CHANGE code code varchar(36) NULL COMMENT '唯一编号' AFTER id;
UPDATE t_db_instance SET code = CONCAT('db_code_', id);
INSERT
INTO
t_resource_auth_cert (name,
resource_code,
resource_type,
type,
username,
ciphertext,
ciphertext_type,
create_time,
creator_id,
creator,
update_time,
modifier_id,
modifier,
is_deleted)
select
CONCAT(code, '_', username),
code,
2,
1,
username,
password,
1,
DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),
1,
'admin',
DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),
1,
'admin',
0
from
t_db_instance
WHERE
is_deleted = 0;
ALTER TABLE t_db ADD auth_cert_name varchar(36) NULL COMMENT '授权凭证名';
ALTER TABLE t_db CHANGE auth_cert_name auth_cert_name varchar(36) NULL COMMENT '授权凭证名' AFTER instance_id;
UPDATE
t_db d
SET
d.auth_cert_name = (
SELECT
rac.name
FROM
t_resource_auth_cert rac
join t_db_instance di on
rac.resource_code = di.code
and rac.resource_type = 2
WHERE
di.id = d.instance_id);
ALTER TABLE t_tag_tree MODIFY COLUMN code_path varchar(555) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标识符路径';
commit;