1 Commits

Author SHA1 Message Date
meilin.huang
c4cb4234fd fix: 问题修复 2024-05-16 17:26:32 +08:00
46 changed files with 416 additions and 262 deletions

View File

@@ -14,7 +14,7 @@ services:
restart: always restart: always
server: server:
image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:v1.8.3 image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:v1.8.5
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile

View File

@@ -17,7 +17,7 @@
"cropperjs": "^1.6.1", "cropperjs": "^1.6.1",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-plus": "^2.7.2", "element-plus": "^2.7.3",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -55,7 +55,7 @@
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-plugin-vue": "^9.25.0", "eslint-plugin-vue": "^9.25.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"sass": "^1.76.0", "sass": "^1.77.1",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"vite": "^5.2.11", "vite": "^5.2.11",
"vue-eslint-parser": "^9.4.2" "vue-eslint-parser": "^9.4.2"

View File

@@ -15,7 +15,7 @@ const config = {
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`, baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本 // 系统版本
version: 'v1.8.4', version: 'v1.8.5',
}; };
export default config; export default config;

View File

@@ -39,6 +39,11 @@
/> />
<el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" /> <el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
<el-option
:key="TagResourceTypeEnum.Redis.value"
:label="TagResourceTypeEnum.Redis.label"
:value="TagResourceTypeEnum.Redis.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="resourceCode" label="资源编号" required> <el-form-item prop="resourceCode" label="资源编号" required>

View File

@@ -23,7 +23,7 @@
</el-form-item> </el-form-item>
<el-form-item prop="authCertName" label="授权凭证" required> <el-form-item prop="authCertName" label="授权凭证" required>
<el-select @change="changeAuthCert" v-model="form.authCertName" placeholder="请选择授权凭证" filterable> <el-select v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
<el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name"> <el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name">
{{ item.name }} {{ item.name }}
@@ -39,8 +39,15 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="getDatabaseMode" label="获库方式" required>
<el-select v-model="form.getDatabaseMode" @change="onChangeGetDatabaseMode" placeholder="请选择库名获取方式">
<el-option v-for="item in DbGetDbNamesMode" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
<el-form-item prop="database" label="数据库名"> <el-form-item prop="database" label="数据库名">
<el-select <el-select
:disabled="form.getDatabaseMode == DbGetDbNamesMode.Auto.value || !form.authCertName"
v-model="dbNamesSelected" v-model="dbNamesSelected"
multiple multiple
clearable clearable
@@ -49,8 +56,9 @@
filterable filterable
:filter-method="filterDbNames" :filter-method="filterDbNames"
allow-create allow-create
placeholder="请确保数据库实例信息填写完整后获取库名" placeholder="获库方式为‘指定库名’时,可选择"
style="width: 100%" @focus="getAllDatabase(form.authCertName)"
:loading="state.loadingDbNames"
> >
<template #header> <template #header>
<el-checkbox v-model="checkAllDbNames" :indeterminate="indeterminateDbNames" @change="handleCheckAll"> 全选 </el-checkbox> <el-checkbox v-model="checkAllDbNames" :indeterminate="indeterminateDbNames" @change="handleCheckAll"> 全选 </el-checkbox>
@@ -75,7 +83,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { toRefs, reactive, watch, ref, watchEffect } from 'vue'; import { toRefs, reactive, watch, ref } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import type { CheckboxValueType } from 'element-plus'; import type { CheckboxValueType } from 'element-plus';
@@ -86,6 +94,7 @@ import EnumTag from '@/components/enumtag/EnumTag.vue';
import { AuthCertCiphertextTypeEnum } from '../tag/enums'; import { AuthCertCiphertextTypeEnum } from '../tag/enums';
import { resourceAuthCertApi } from '../tag/api'; import { resourceAuthCertApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum'; import { TagResourceTypeEnum } from '@/common/commonEnum';
import { DbGetDbNamesMode } from './enums';
const props = defineProps({ const props = defineProps({
visible: { visible: {
@@ -147,10 +156,10 @@ const rules = {
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
database: [ getDatabaseMode: [
{ {
required: true, required: true,
message: '请添加数据库', message: '请选择库名获取方式',
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
@@ -172,38 +181,45 @@ const state = reactive({
authCerts: [] as any, authCerts: [] as any,
form: { form: {
id: null, id: null,
// tagId: [],
name: null, name: null,
code: '', code: '',
getDatabaseMode: DbGetDbNamesMode.Auto.value,
database: '', database: '',
remark: '', remark: '',
instanceId: null as any, instanceId: null as any,
authCertName: '', authCertName: '',
}, },
instances: [] as any, instances: [] as any,
loadingDbNames: false,
}); });
const { dialogVisible, allDatabases, form, dbNamesSelected } = toRefs(state); const { dialogVisible, allDatabases, form, dbNamesSelected } = toRefs(state);
watchEffect(() => { watch(
state.dialogVisible = props.visible; () => props.visible,
if (!state.dialogVisible) { () => {
return; state.dialogVisible = props.visible;
if (!state.dialogVisible) {
return;
}
const db: any = props.db;
if (db.code) {
state.form = { ...db };
if (db.getDatabaseMode == DbGetDbNamesMode.Assign.value) {
// 将数据库名使用空格切割,获取所有数据库列表
state.dbNamesSelected = db.database.split(' ');
}
} else {
state.form = { getDatabaseMode: DbGetDbNamesMode.Auto.value } as any;
state.dbNamesSelected = [];
}
} }
const db: any = props.db; );
if (db.code) {
state.form = { ...db }; const onChangeGetDatabaseMode = (val: any) => {
// state.form.tagId = newValue.db.tags.map((t: any) => t.tagId); if (val == DbGetDbNamesMode.Auto.value) {
// 将数据库名使用空格切割,获取所有数据库列表
state.dbNamesSelected = db.database.split(' ');
} else {
state.form = {} as any;
state.dbNamesSelected = []; state.dbNamesSelected = [];
} }
});
const changeAuthCert = (val: string) => {
getAllDatabase(val);
}; };
const getAuthCerts = async () => { const getAuthCerts = async () => {
@@ -217,15 +233,20 @@ const getAuthCerts = async () => {
}; };
const getAllDatabase = async (authCertName: string) => { const getAllDatabase = async (authCertName: string) => {
const req = { ...(props.instance as any) }; try {
req.authCert = state.authCerts?.find((x: any) => x.name == authCertName); state.loadingDbNames = true;
let dbs = await dbApi.getAllDatabase.request(req); const req = { ...(props.instance as any) };
state.allDatabases = dbs; req.authCert = state.authCerts?.find((x: any) => x.name == authCertName);
let dbs = await dbApi.getAllDatabase.request(req);
state.allDatabases = dbs;
// 如果是oracle且没查出数据库列表则取实例sid // 如果是oracle且没查出数据库列表则取实例sid
let instance = state.instances.find((item: any) => item.id === state.form.instanceId); let instance = state.instances.find((item: any) => item.id === state.form.instanceId);
if (instance && instance.type === DbType.oracle && dbs.length === 0) { if (instance && instance.type === DbType.oracle && dbs.length === 0) {
state.allDatabases = [instance.sid]; state.allDatabases = [instance.sid];
}
} finally {
state.loadingDbNames = false;
} }
}; };

View File

@@ -35,9 +35,9 @@
<template #database="{ data }"> <template #database="{ data }">
<el-popover placement="bottom" :width="200" trigger="click"> <el-popover placement="bottom" :width="200" trigger="click">
<template #reference> <template #reference>
<el-button @click="state.currentDbs = data.database" type="primary" link>查看库</el-button> <el-button @click="getDbNames(data)" type="primary" link>查看库</el-button>
</template> </template>
<el-table :data="filterDbs" size="small"> <el-table :data="filterDbs" v-loading="state.loadingDbNames" size="small">
<el-table-column prop="dbName" label="数据库"> <el-table-column prop="dbName" label="数据库">
<template #header> <template #header>
<el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable /> <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
@@ -221,6 +221,8 @@ import DbBackupHistoryList from './DbBackupHistoryList.vue';
import DbRestoreList from './DbRestoreList.vue'; import DbRestoreList from './DbRestoreList.vue';
import ResourceTags from '../component/ResourceTags.vue'; import ResourceTags from '../component/ResourceTags.vue';
import { sleep } from '@/common/utils/loading'; import { sleep } from '@/common/utils/loading';
import { DbGetDbNamesMode } from './enums';
import { DbInst } from './db';
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue')); const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
@@ -237,6 +239,7 @@ const columns = ref([
TableColumn.new('instanceName', '实例名'), TableColumn.new('instanceName', '实例名'),
TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40), TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
TableColumn.new('authCertName', '授权凭证'), TableColumn.new('authCertName', '授权凭证'),
TableColumn.new('getDatabaseMode', '获库方式').typeTag(DbGetDbNamesMode),
TableColumn.new('database', '库').isSlot().setMinWidth(80), TableColumn.new('database', '库').isSlot().setMinWidth(80),
TableColumn.new('remark', '备注'), TableColumn.new('remark', '备注'),
TableColumn.new('code', '编号'), TableColumn.new('code', '编号'),
@@ -258,7 +261,8 @@ const state = reactive({
row: {} as any, row: {} as any,
dbId: 0, dbId: 0,
db: '', db: '',
currentDbs: '', loadingDbNames: false,
currentDbNames: [],
dbNameSearch: '', dbNameSearch: '',
instances: [] as any, instances: [] as any,
/** /**
@@ -344,15 +348,26 @@ onMounted(async () => {
search(); search();
}); });
const getDbNames = async (db: any) => {
try {
state.loadingDbNames = true;
state.currentDbNames = await DbInst.getDbNames(db);
} finally {
state.loadingDbNames = false;
}
};
const filterDbs = computed(() => { const filterDbs = computed(() => {
const dbsStr = state.currentDbs; const dbNames = state.currentDbNames;
if (!dbsStr) { if (!dbNames) {
return []; return [];
} }
const dbs = dbsStr.split(' ').map((db: any) => { const dbNameObjs = dbNames.map((x) => {
return { dbName: db }; return {
dbName: x,
};
}); });
return dbs.filter((db: any) => { return dbNameObjs.filter((db: any) => {
return db.dbName.includes(state.dbNameSearch); return db.dbName.includes(state.dbNameSearch);
}); });
}); });

View File

@@ -8,13 +8,18 @@
<el-table :data="state.dbs" stripe> <el-table :data="state.dbs" stripe>
<el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100"> </el-table-column> <el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100"> </el-table-column>
<el-table-column prop="authCertName" label="授权凭证" min-width="120" show-overflow-tooltip> </el-table-column> <el-table-column prop="authCertName" label="授权凭证" min-width="120" show-overflow-tooltip> </el-table-column>
<el-table-column prop="getDatabaseMode" label="获库方式" min-width="80">
<template #default="scope">
<EnumTag :enums="DbGetDbNamesMode" :value="scope.row.getDatabaseMode" />
</template>
</el-table-column>
<el-table-column prop="database" label="库" min-width="80"> <el-table-column prop="database" label="库" min-width="80">
<template #default="scope"> <template #default="scope">
<el-popover placement="bottom" :width="200" trigger="click"> <el-popover placement="bottom" :width="200" trigger="click">
<template #reference> <template #reference>
<el-button @click="state.currentDbs = scope.row.database" type="primary" link>查看库</el-button> <el-button @click="getDbNames(scope.row)" type="primary" link>查看库</el-button>
</template> </template>
<el-table :data="filterDbs" size="small"> <el-table :data="filterDbs" size="small" v-loading="state.loadingDbNames">
<el-table-column prop="dbName" label="数据库"> <el-table-column prop="dbName" label="数据库">
<template #header> <template #header>
<el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable /> <el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
@@ -57,6 +62,9 @@ import { dbApi } from './api';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue'; import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import DbEdit from './DbEdit.vue'; import DbEdit from './DbEdit.vue';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { DbGetDbNamesMode } from './enums';
import { DbInst } from './db';
const props = defineProps({ const props = defineProps({
visible: { visible: {
@@ -83,7 +91,8 @@ const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
dbs: [] as any, dbs: [] as any,
currentDbs: '', // 当前数据库名,空格分割库名 loadingDbNames: false,
currentDbNames: [], // 当前数据库名
dbNameSearch: '', dbNameSearch: '',
dbEditDialog: { dbEditDialog: {
visible: false, visible: false,
@@ -103,15 +112,26 @@ watchEffect(() => {
getDbs(); getDbs();
}); });
const getDbNames = async (db: any) => {
try {
state.loadingDbNames = true;
state.currentDbNames = await DbInst.getDbNames(db);
} finally {
state.loadingDbNames = false;
}
};
const filterDbs = computed(() => { const filterDbs = computed(() => {
const dbsStr = state.currentDbs; const dbNames = state.currentDbNames;
if (!dbsStr) { if (!dbNames) {
return []; return [];
} }
const dbs = dbsStr.split(' ').map((db: any) => { const dbNameObjs = dbNames.map((x) => {
return { dbName: db }; return {
dbName: x,
};
}); });
return dbs.filter((db: any) => { return dbNameObjs.filter((db: any) => {
return db.dbName.includes(state.dbNameSearch); return db.dbName.includes(state.dbNameSearch);
}); });
}); });

View File

@@ -272,7 +272,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
// 数据库实例节点类型 // 数据库实例节点类型
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => { const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const params = parentNode.params; const params = parentNode.params;
const dbs = params.database.split(' ')?.sort(); const dbs = (await DbInst.getDbNames(params))?.sort();
const flowProcdef = await procdefApi.getByResource.request({ resourceType: TagResourceTypeEnum.DbName.value, resourceCode: params.code }); const flowProcdef = await procdefApi.getByResource.request({ resourceType: TagResourceTypeEnum.DbName.value, resourceCode: params.code });
return dbs.map((x: any) => { return dbs.map((x: any) => {

View File

@@ -40,6 +40,7 @@ export const dbApi = {
instances: Api.newGet('/instances'), instances: Api.newGet('/instances'),
getInstance: Api.newGet('/instances/{instanceId}'), getInstance: Api.newGet('/instances/{instanceId}'),
getAllDatabase: Api.newPost('/instances/databases'), getAllDatabase: Api.newPost('/instances/databases'),
getDbNamesByAc: Api.newGet('/instances/databases/{authCertName}'),
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'),

View File

@@ -25,6 +25,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect'; import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue'; import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { DbInst } from '../db';
const props = defineProps({ const props = defineProps({
dbId: { dbId: {
@@ -101,9 +102,9 @@ const noSchemaType = (type: string) => {
}; };
// 数据库实例节点类型 // 数据库实例节点类型
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => { const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
const params = parentNode.params; const params = parentNode.params;
const dbs = params.database.split(' ')?.sort(); const dbs = (await DbInst.getDbNames(params))?.sort();
let fn: NodeType; let fn: NodeType;
if (noSchemaType(params.type)) { if (noSchemaType(params.type)) {
fn = MysqlNodeTypes; fn = MysqlNodeTypes;

View File

@@ -3,7 +3,7 @@
<el-row class="mb5"> <el-row class="mb5">
<el-popover v-model:visible="state.dumpInfo.visible" trigger="click" :width="470" placement="right"> <el-popover v-model:visible="state.dumpInfo.visible" trigger="click" :width="470" placement="right">
<template #reference> <template #reference>
<el-button class="ml5" type="success" size="small">导出</el-button> <el-button :disabled="state.dumpInfo.tables?.length == 0" class="ml5" type="success" size="small">导出</el-button>
</template> </template>
<el-form-item label="导出内容: "> <el-form-item label="导出内容: ">
<el-radio-group v-model="dumpInfo.type"> <el-radio-group v-model="dumpInfo.type">

View File

@@ -8,6 +8,7 @@ import { editor, languages, Position } from 'monaco-editor';
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider'; import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect'; import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
import { type RemovableRef, useLocalStorage } from '@vueuse/core'; import { type RemovableRef, useLocalStorage } from '@vueuse/core';
import { DbGetDbNamesMode } from './enums';
const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map()); const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map()); const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
@@ -503,6 +504,19 @@ export class DbInst {
col.columnType = col.dataType; col.columnType = col.dataType;
} }
} }
/**
* 根据数据库配置信息获取对应的库名列表
* @param db db配置信息
* @returns 库名列表
*/
static async getDbNames(db: any) {
if (db.getDatabaseMode == DbGetDbNamesMode.Assign.value) {
return db.database.split(' ');
}
return await dbApi.getDbNamesByAc.request({ authCertName: db.authCertName });
}
} }
/** /**

View File

@@ -1,5 +1,10 @@
import { EnumValue } from '@/common/Enum'; import { EnumValue } from '@/common/Enum';
export const DbGetDbNamesMode = {
Auto: EnumValue.of(-1, '实时获取').setTagType('warning'),
Assign: EnumValue.of(1, '指定库名').setTagType('primary'),
};
// 数据库sql执行类型 // 数据库sql执行类型
export const DbSqlExecTypeEnum = { export const DbSqlExecTypeEnum = {
Update: EnumValue.of(1, 'UPDATE').setTagColor('#E4F5EB'), Update: EnumValue.of(1, 'UPDATE').setTagColor('#E4F5EB'),

View File

@@ -0,0 +1,69 @@
<template>
<div>
<el-popover placement="right" width="auto" title="机器详情" trigger="click">
<template #reference>
<el-link @click="getMachineDetail" type="primary">{{ props.code }}</el-link>
</template>
<el-descriptions v-loading="state.loading" :column="3" border>
<el-descriptions-item :span="1" label="机器id">{{ state.machineDetail.id }}</el-descriptions-item>
<el-descriptions-item :span="1" label="编号">{{ state.machineDetail.code }}</el-descriptions-item>
<el-descriptions-item :span="1" label="名称">{{ state.machineDetail.name }}</el-descriptions-item>
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="state.machineDetail.tags" /></el-descriptions-item>
<el-descriptions-item :span="2" label="IP">{{ state.machineDetail.ip }}</el-descriptions-item>
<el-descriptions-item :span="1" label="端口">{{ state.machineDetail.port }}</el-descriptions-item>
<el-descriptions-item :span="3" label="备注">{{ state.machineDetail.remark }}</el-descriptions-item>
<el-descriptions-item :span="1.5" label="SSH隧道">{{ state.machineDetail.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="1.5" label="终端回放">{{ state.machineDetail.enableRecorder == 1 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ formatDate(state.machineDetail.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="创建者">{{ state.machineDetail.creator }}</el-descriptions-item>
<el-descriptions-item :span="2" label="更新时间">{{ formatDate(state.machineDetail.updateTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="修改者">{{ state.machineDetail.modifier }}</el-descriptions-item>
</el-descriptions>
</el-popover>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { machineApi } from '../api';
import { formatDate } from '@/common/utils/format';
import ResourceTags from '../../component/ResourceTags.vue';
const props = defineProps({
code: {
type: [String],
requierd: true,
},
});
const state = reactive({
loading: false,
machineDetail: {} as any,
});
const getMachineDetail = async () => {
try {
state.machineDetail = {};
state.loading = true;
const res = await machineApi.list.request({
code: props.code,
});
if (res.total == 0) {
return;
}
state.machineDetail = res.list?.[0];
} finally {
state.loading = false;
}
};
</script>
<style></style>

View File

@@ -13,20 +13,13 @@
ref="pageTableRef" ref="pageTableRef"
:page-api="cronJobApi.execList" :page-api="cronJobApi.execList"
:lazy="true" :lazy="true"
:data-handler-fn="parseData"
:search-items="searchItems" :search-items="searchItems"
v-model:query-form="params" v-model:query-form="params"
:data="state.data.list" :data="state.data.list"
:columns="columns" :columns="columns"
> >
<template #machineSelect> <template #machineCode="{ data }">
<el-select v-model="params.machineId" filterable placeholder="选择机器查询" clearable> <MachineDetail :code="data.machineCode" />
<el-option v-for="ac in machineMap.values()" :key="ac.id" :value="ac.id" :label="ac.ip">
{{ ac.ip }}
<el-divider direction="vertical" border-style="dashed" />
{{ ac.tagPath }}{{ ac.name }}
</el-option>
</el-select>
</template> </template>
</page-table> </page-table>
</el-dialog> </el-dialog>
@@ -35,11 +28,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { watch, ref, toRefs, reactive, Ref } from 'vue'; import { watch, ref, toRefs, reactive, Ref } from 'vue';
import { cronJobApi, machineApi } from '../api'; import { cronJobApi } from '../api';
import PageTable from '@/components/pagetable/PageTable.vue'; import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable'; import { TableColumn } from '@/components/pagetable';
import { CronJobExecStatusEnum } from '../enums'; import { CronJobExecStatusEnum } from '../enums';
import { SearchItem } from '@/components/SearchForm'; import { SearchItem } from '@/components/SearchForm';
import MachineDetail from '../component/MachineDetail.vue';
const props = defineProps({ const props = defineProps({
visible: { visible: {
@@ -55,11 +49,10 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'update:data', 'cancel']); const emit = defineEmits(['update:visible', 'update:data', 'cancel']);
const searchItems = [SearchItem.slot('machineId', '机器', 'machineSelect'), SearchItem.select('status', '状态').withEnum(CronJobExecStatusEnum)]; const searchItems = [SearchItem.input('machineCode', '机器编号'), SearchItem.select('status', '状态').withEnum(CronJobExecStatusEnum)];
const columns = ref([ const columns = ref([
TableColumn.new('machineIp', '机器IP').setMinWidth(120), TableColumn.new('machineCode', '机器编号').isSlot(),
TableColumn.new('machineName', '机器名称').setMinWidth(100),
TableColumn.new('status', '状态').typeTag(CronJobExecStatusEnum).setMinWidth(70), TableColumn.new('status', '状态').typeTag(CronJobExecStatusEnum).setMinWidth(70),
TableColumn.new('res', '执行结果').setMinWidth(250).canBeautify(), TableColumn.new('res', '执行结果').setMinWidth(250).canBeautify(),
TableColumn.new('execTime', '执行时间').isTime().setMinWidth(150), TableColumn.new('execTime', '执行时间').isTime().setMinWidth(150),
@@ -75,7 +68,7 @@ const state = reactive({
pageSize: 10, pageSize: 10,
cronJobId: 0, cronJobId: 0,
status: null, status: null,
machineId: null, machineCode: '',
}, },
// 列表数据 // 列表数据
data: { data: {
@@ -85,8 +78,6 @@ const state = reactive({
machines: [], machines: [],
}); });
const machineMap: Map<number, any> = new Map();
const { dialogVisible, params } = toRefs(state); const { dialogVisible, params } = toRefs(state);
watch(props, async (newValue: any) => { watch(props, async (newValue: any) => {
@@ -95,52 +86,14 @@ watch(props, async (newValue: any) => {
return; return;
} }
const machineIds = await cronJobApi.relateMachineIds.request({
cronJobId: props.data?.id,
});
const res = await machineApi.list.request({
ids: machineIds?.join(','),
});
res.list?.forEach((x: any) => {
machineMap.set(x.id, x);
});
state.params.cronJobId = props.data?.id; state.params.cronJobId = props.data?.id;
search(); setTimeout(() => search(), 300);
}); });
const search = async () => { const search = async () => {
pageTableRef.value.search(); pageTableRef.value.search();
}; };
const parseData = async (res: any) => {
const dataList = res.list;
// 填充机器信息
for (let x of dataList) {
const machineId = x.machineId;
let machine = machineMap.get(machineId);
// 如果未找到,则可能被移除,则调接口查询机器信息
if (!machine) {
const machineRes = await machineApi.list.request({ ids: machineId });
if (!machineRes.list) {
machine = {
id: machineId,
ip: machineId,
name: '该机器已被删除',
};
} else {
machine = machineRes.list[0];
}
machineMap.set(machineId, machine);
}
x.machineIp = machine?.ip;
x.machineName = machine?.name;
}
return res;
};
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
setTimeout(() => { setTimeout(() => {
@@ -152,7 +105,7 @@ const initData = () => {
state.data.list = []; state.data.list = [];
state.data.total = 0; state.data.total = 0;
state.params.pageNum = 1; state.params.pageNum = 1;
state.params.machineId = null; state.params.machineCode = '';
state.params.status = null; state.params.status = null;
}; };
</script> </script>

View File

@@ -11,6 +11,14 @@
<el-button v-auth="'authcert:save'" type="primary" icon="plus" @click="edit(false)">添加</el-button> <el-button v-auth="'authcert:save'" type="primary" icon="plus" @click="edit(false)">添加</el-button>
</template> </template>
<template #resourceCode="{ data }">
<SvgIcon
:name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.resourceType)?.extra.icon"
:color="EnumValue.getEnumByValue(TagResourceTypeEnum, data.resourceType)?.extra.iconColor"
/>
{{ data.resourceCode }}
</template>
<template #action="{ data }"> <template #action="{ data }">
<el-button v-auth="'authcert:save'" @click="edit(data)" type="primary" link>编辑</el-button> <el-button v-auth="'authcert:save'" @click="edit(data)" type="primary" link>编辑</el-button>
@@ -41,6 +49,7 @@ import { SearchItem } from '@/components/SearchForm';
import { AuthCertCiphertextTypeEnum, AuthCertTypeEnum } from './enums'; import { AuthCertCiphertextTypeEnum, AuthCertTypeEnum } from './enums';
import { ResourceTypeEnum, TagResourceTypeEnum } from '@/common/commonEnum'; import { ResourceTypeEnum, TagResourceTypeEnum } from '@/common/commonEnum';
import ResourceAuthCertEdit from '../component/ResourceAuthCertEdit.vue'; import ResourceAuthCertEdit from '../component/ResourceAuthCertEdit.vue';
import EnumValue from '@/common/Enum';
const pageTableRef: Ref<any> = ref(null); const pageTableRef: Ref<any> = ref(null);
const state = reactive({ const state = reactive({
@@ -50,6 +59,7 @@ const state = reactive({
name: null, name: null,
}, },
searchItems: [ searchItems: [
SearchItem.input('resourceCode', '资源编号'),
SearchItem.input('name', '凭证名称'), SearchItem.input('name', '凭证名称'),
SearchItem.select('resourceType', '资源类型').withEnum(ResourceTypeEnum), SearchItem.select('resourceType', '资源类型').withEnum(ResourceTypeEnum),
SearchItem.select('type', '凭证类型').withEnum(AuthCertTypeEnum), SearchItem.select('type', '凭证类型').withEnum(AuthCertTypeEnum),
@@ -60,8 +70,7 @@ const state = reactive({
TableColumn.new('type', '凭证类型').typeTag(AuthCertTypeEnum), TableColumn.new('type', '凭证类型').typeTag(AuthCertTypeEnum),
TableColumn.new('username', '用户名'), TableColumn.new('username', '用户名'),
TableColumn.new('ciphertextType', '密文类型').typeTag(AuthCertCiphertextTypeEnum), TableColumn.new('ciphertextType', '密文类型').typeTag(AuthCertCiphertextTypeEnum),
TableColumn.new('resourceType', '资源类型').typeTag(TagResourceTypeEnum), TableColumn.new('resourceCode', '资源编号').isSlot().setAddWidth(30),
TableColumn.new('resourceCode', '资源编号'),
TableColumn.new('remark', '备注'), TableColumn.new('remark', '备注'),
TableColumn.new('creator', '创建人'), TableColumn.new('creator', '创建人'),
TableColumn.new('createTime', '创建时间').isTime(), TableColumn.new('createTime', '创建时间').isTime(),

View File

@@ -123,9 +123,8 @@ func (a *AccountLogin) RefreshToken(rc *req.Ctx) {
biz.NotEmpty(refreshToken, "refresh_token不能为空") biz.NotEmpty(refreshToken, "refresh_token不能为空")
accountId, username, err := req.ParseToken(refreshToken) accountId, username, err := req.ParseToken(refreshToken)
if err != nil { biz.IsTrueBy(err == nil, errorx.PermissionErr)
panic(errorx.PermissionErr)
}
token, refreshToken, err := req.CreateToken(accountId, username) token, refreshToken, err := req.CreateToken(accountId, username)
biz.ErrIsNil(err) biz.ErrIsNil(err)
rc.ResData = collx.Kvs("token", token, "refresh_token", refreshToken) rc.ResData = collx.Kvs("token", token, "refresh_token", refreshToken)

View File

@@ -114,6 +114,12 @@ func (d *Instance) GetDatabaseNames(rc *req.Ctx) {
rc.ResData = res rc.ResData = res
} }
func (d *Instance) GetDatabaseNamesByAc(rc *req.Ctx) {
res, err := d.InstanceApp.GetDatabasesByAc(rc.PathParam("ac"))
biz.ErrIsNil(err)
rc.ResData = res
}
// 获取数据库实例server信息 // 获取数据库实例server信息
func (d *Instance) GetDbServer(rc *req.Ctx) { func (d *Instance) GetDbServer(rc *req.Ctx) {
instanceId := getInstanceId(rc) instanceId := getInstanceId(rc)

View File

@@ -1,14 +1,16 @@
package form package form
import "mayfly-go/internal/db/domain/entity"
type DbForm struct { type DbForm struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
Code string `binding:"required" json:"code"` Code string `binding:"required" json:"code"`
Name string `binding:"required" json:"name"` Name string `binding:"required" json:"name"`
Database string `json:"database"` GetDatabaseMode entity.DbGetDatabaseMode `json:"getDatabaseMode"` // 获取数据库方式
Remark string `json:"remark"` Database string `json:"database"`
InstanceId uint64 `binding:"required" json:"instanceId"` Remark string `json:"remark"`
AuthCertName string `json:"authCertName"` InstanceId uint64 `binding:"required" json:"instanceId"`
FlowProcdefKey string `json:"flowProcdefKey"` AuthCertName string `json:"authCertName"`
} }
type DbSqlSaveForm struct { type DbSqlSaveForm struct {

View File

@@ -1,6 +1,7 @@
package vo package vo
import ( import (
"mayfly-go/internal/db/domain/entity"
tagentity "mayfly-go/internal/tag/domain/entity" tagentity "mayfly-go/internal/tag/domain/entity"
"time" "time"
) )
@@ -8,11 +9,12 @@ import (
type DbListVO struct { type DbListVO struct {
tagentity.ResourceTags tagentity.ResourceTags
Id *int64 `json:"id"` Id *int64 `json:"id"`
Code string `json:"code"` Code string `json:"code"`
Name *string `json:"name"` Name *string `json:"name"`
Database *string `json:"database"` GetDatabaseMode entity.DbGetDatabaseMode `json:"getDatabaseMode"` // 获取数据库方式
Remark *string `json:"remark"` Database *string `json:"database"`
Remark *string `json:"remark"`
InstanceId *int64 `json:"instanceId"` InstanceId *int64 `json:"instanceId"`
AuthCertName string `json:"authCertName"` AuthCertName string `json:"authCertName"`

View File

@@ -194,7 +194,7 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
di.Id = db.Id di.Id = db.Id
checkDb := di.GetDatabase() checkDb := di.GetDatabase()
if !strings.Contains(" "+db.Database+" ", " "+checkDb+" ") { if db.GetDatabaseMode == entity.DbGetDatabaseModeAssign && !strings.Contains(" "+db.Database+" ", " "+checkDb+" ") {
return nil, errorx.NewBiz("未配置数据库【%s】的操作权限", dbName) return nil, errorx.NewBiz("未配置数据库【%s】的操作权限", dbName)
} }

View File

@@ -410,7 +410,7 @@ func (app *dataSyncAppImpl) InitCronJob() {
}() }()
// 修改执行中状态为待执行 // 修改执行中状态为待执行
_ = app.UpdateByCond(context.TODO(), &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateRunning}, &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateReady}) _ = app.UpdateByCond(context.TODO(), &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateReady}, &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateRunning})
// 把所有正常任务添加到定时任务中 // 把所有正常任务添加到定时任务中
pageParam := &model.PageParam{ pageParam := &model.PageParam{

View File

@@ -39,6 +39,9 @@ type Instance interface {
// GetDatabases 获取数据库实例的所有数据库列表 // GetDatabases 获取数据库实例的所有数据库列表
GetDatabases(entity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) ([]string, error) GetDatabases(entity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) ([]string, error)
// GetDatabasesByAc 根据授权凭证名获取所有数据库名称列表
GetDatabasesByAc(acName string) ([]string, error)
// ToDbInfo 根据实例与授权凭证返回对应的DbInfo // ToDbInfo 根据实例与授权凭证返回对应的DbInfo
ToDbInfo(instance *entity.DbInstance, authCertName string, database string) (*dbi.DbInfo, error) ToDbInfo(instance *entity.DbInstance, authCertName string, database string) (*dbi.DbInfo, error)
} }
@@ -53,6 +56,8 @@ type instanceAppImpl struct {
restoreApp *DbRestoreApp `inject:"DbRestoreApp"` restoreApp *DbRestoreApp `inject:"DbRestoreApp"`
} }
var _ (Instance) = (*instanceAppImpl)(nil)
// 注入DbInstanceRepo // 注入DbInstanceRepo
func (app *instanceAppImpl) InjectDbInstanceRepo(repo repository.Instance) { func (app *instanceAppImpl) InjectDbInstanceRepo(repo repository.Instance) {
app.Repo = repo app.Repo = repo
@@ -118,7 +123,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
return instanceEntity.Id, app.Tx(ctx, func(ctx context.Context) error { return instanceEntity.Id, app.Tx(ctx, func(ctx context.Context) error {
return app.Insert(ctx, instanceEntity) return app.Insert(ctx, instanceEntity)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: instanceEntity.Code, ResourceCode: instanceEntity.Code,
ResourceType: tagentity.TagType(resourceType), ResourceType: tagentity.TagType(resourceType),
AuthCerts: authCerts, AuthCerts: authCerts,
@@ -147,7 +152,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
return oldInstance.Id, app.Tx(ctx, func(ctx context.Context) error { return oldInstance.Id, app.Tx(ctx, func(ctx context.Context) error {
return app.UpdateById(ctx, instanceEntity) return app.UpdateById(ctx, instanceEntity)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: oldInstance.Code, ResourceCode: oldInstance.Code,
ResourceType: tagentity.TagType(resourceType), ResourceType: tagentity.TagType(resourceType),
AuthCerts: authCerts, AuthCerts: authCerts,
@@ -205,7 +210,7 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
return app.DeleteById(ctx, instanceId) return app.DeleteById(ctx, instanceId)
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
// 删除该实例关联的授权凭证信息 // 删除该实例关联的授权凭证信息
return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return app.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: instance.Code, ResourceCode: instance.Code,
ResourceType: tagentity.TagType(consts.ResourceTypeDb), ResourceType: tagentity.TagType(consts.ResourceTypeDb),
}) })
@@ -239,16 +244,22 @@ func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance, authCert *tagent
} }
} }
ed.Network = ed.GetNetwork() return app.getDatabases(ed, authCert)
dbi := app.toDbInfoByAc(ed, authCert, "") }
dbConn, err := dbm.Conn(dbi) func (app *instanceAppImpl) GetDatabasesByAc(acName string) ([]string, error) {
ac, err := app.resourceAuthCertApp.GetAuthCert(acName)
if err != nil { if err != nil {
return nil, err return nil, errorx.NewBiz("该授权凭证不存在")
} }
defer dbConn.Close()
return dbConn.GetMetaData().GetDbNames() instance := &entity.DbInstance{Code: ac.ResourceCode}
err = app.GetByCond(instance)
if err != nil {
return nil, errorx.NewBiz("不存在该授权凭证对应的数据库实例信息")
}
return app.getDatabases(instance, ac)
} }
func (app *instanceAppImpl) ToDbInfo(instance *entity.DbInstance, authCertName string, database string) (*dbi.DbInfo, error) { func (app *instanceAppImpl) ToDbInfo(instance *entity.DbInstance, authCertName string, database string) (*dbi.DbInfo, error) {
@@ -260,6 +271,19 @@ func (app *instanceAppImpl) ToDbInfo(instance *entity.DbInstance, authCertName s
return app.toDbInfoByAc(instance, ac, database), nil return app.toDbInfoByAc(instance, ac, database), nil
} }
func (app *instanceAppImpl) getDatabases(instance *entity.DbInstance, ac *tagentity.ResourceAuthCert) ([]string, error) {
instance.Network = instance.GetNetwork()
dbi := app.toDbInfoByAc(instance, ac, "")
dbConn, err := dbm.Conn(dbi)
if err != nil {
return nil, err
}
defer dbConn.Close()
return dbConn.GetMetaData().GetDbNames()
}
func (app *instanceAppImpl) toDbInfoByAc(instance *entity.DbInstance, ac *tagentity.ResourceAuthCert, database string) *dbi.DbInfo { func (app *instanceAppImpl) toDbInfoByAc(instance *entity.DbInstance, ac *tagentity.ResourceAuthCert, database string) *dbi.DbInfo {
di := new(dbi.DbInfo) di := new(dbi.DbInfo)
di.InstanceId = instance.Id di.InstanceId = instance.Id

View File

@@ -7,10 +7,18 @@ import (
type Db struct { type Db struct {
model.Model model.Model
Code string `orm:"column(code)" json:"code"` Code string `orm:"column(code)" json:"code"`
Name string `orm:"column(name)" json:"name"` Name string `orm:"column(name)" json:"name"`
Database string `orm:"column(database)" json:"database"` GetDatabaseMode DbGetDatabaseMode `json:"getDatabaseMode"` // 获取数据库方式
Remark string `json:"remark"` Database string `orm:"column(database)" json:"database"`
InstanceId uint64 Remark string `json:"remark"`
AuthCertName string `json:"authCertName"` InstanceId uint64
AuthCertName string `json:"authCertName"`
} }
type DbGetDatabaseMode int8
const (
DbGetDatabaseModeAuto DbGetDatabaseMode = -1 // 自动获取(根据凭证获取对应所有库名)
DbGetDatabaseModeAssign DbGetDatabaseMode = 1 // 指定库名
)

View File

@@ -6,7 +6,7 @@ type InstanceQuery struct {
Name string `json:"name" form:"name"` Name string `json:"name" form:"name"`
Code string `json:"code" form:"code"` Code string `json:"code" form:"code"`
Host string `json:"host" form:"host"` Host string `json:"host" form:"host"`
TagPath string `json:"host" form:"tagPath"` TagPath string `json:"tagPath" form:"tagPath"`
Codes []string Codes []string
} }

View File

@@ -28,6 +28,9 @@ func InitInstanceRouter(router *gin.RouterGroup) {
// 获取数据库实例的所有数据库名 // 获取数据库实例的所有数据库名
req.NewPost("/databases", d.GetDatabaseNames), req.NewPost("/databases", d.GetDatabaseNames),
// 根据授权凭证名获取其所有库名
req.NewGet("/databases/:ac", d.GetDatabaseNamesByAc),
req.NewGet(":instanceId/server-info", d.GetDbServer), req.NewGet(":instanceId/server-info", d.GetDbServer),
req.NewDelete(":instanceId", d.DeleteInstance).Log(req.NewLogSave("db-删除数据库实例")), req.NewDelete(":instanceId", d.DeleteInstance).Log(req.NewLogSave("db-删除数据库实例")),

View File

@@ -8,13 +8,14 @@ import (
"mayfly-go/internal/machine/domain/entity" "mayfly-go/internal/machine/domain/entity"
tagapp "mayfly-go/internal/tag/application" tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity" tagentity "mayfly-go/internal/tag/domain/entity"
"strconv"
"strings" "strings"
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
"mayfly-go/pkg/req" "mayfly-go/pkg/req"
"mayfly-go/pkg/scheduler" "mayfly-go/pkg/scheduler"
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
"github.com/may-fly/cast"
) )
type MachineCronJob struct { type MachineCronJob struct {
@@ -58,9 +59,7 @@ func (m *MachineCronJob) Delete(rc *req.Ctx) {
ids := strings.Split(idsStr, ",") ids := strings.Split(idsStr, ",")
for _, v := range ids { for _, v := range ids {
value, err := strconv.Atoi(v) m.MachineCronJobApp.Delete(rc.MetaCtx, cast.ToUint64(v))
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
m.MachineCronJobApp.Delete(rc.MetaCtx, uint64(value))
} }
} }

View File

@@ -11,8 +11,9 @@ import (
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/jsonx" "mayfly-go/pkg/utils/jsonx"
"mayfly-go/pkg/utils/stringx" "mayfly-go/pkg/utils/stringx"
"strconv"
"strings" "strings"
"github.com/may-fly/cast"
) )
type MachineScript struct { type MachineScript struct {
@@ -42,9 +43,7 @@ func (m *MachineScript) DeleteMachineScript(rc *req.Ctx) {
ids := strings.Split(idsStr, ",") ids := strings.Split(idsStr, ",")
for _, v := range ids { for _, v := range ids {
value, err := strconv.Atoi(v) m.MachineScriptApp.Delete(rc.MetaCtx, cast.ToUint64(v))
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
m.MachineScriptApp.Delete(rc.MetaCtx, uint64(value))
} }
} }

View File

@@ -108,7 +108,7 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
return m.Tx(ctx, func(ctx context.Context) error { return 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.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: me.Code, ResourceCode: me.Code,
ResourceType: resourceType, ResourceType: resourceType,
AuthCerts: authCerts, AuthCerts: authCerts,
@@ -138,7 +138,7 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
return m.Tx(ctx, func(ctx context.Context) error { return 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.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: oldMachine.Code, ResourceCode: oldMachine.Code,
ResourceType: resourceType, ResourceType: resourceType,
AuthCerts: authCerts, AuthCerts: authCerts,
@@ -219,7 +219,7 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
}, },
}) })
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return m.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: machine.Code, ResourceCode: machine.Code,
ResourceType: resourceType, ResourceType: resourceType,
}) })
@@ -334,6 +334,7 @@ func (m *machineAppImpl) getMachineAndAuthCert(machineId uint64) (*entity.Machin
func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAuthCert) (*mcm.MachineInfo, error) { func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAuthCert) (*mcm.MachineInfo, error) {
mi := new(mcm.MachineInfo) mi := new(mcm.MachineInfo)
mi.Id = me.Id mi.Id = me.Id
mi.Code = me.Code
mi.Name = me.Name mi.Name = me.Name
mi.Ip = me.Ip mi.Ip = me.Ip
mi.Port = me.Port mi.Port = me.Port

View File

@@ -8,13 +8,11 @@ import (
tagapp "mayfly-go/internal/tag/application" tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity" tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base" "mayfly-go/pkg/base"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx" "mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx" "mayfly-go/pkg/logx"
"mayfly-go/pkg/model" "mayfly-go/pkg/model"
"mayfly-go/pkg/rediscli" "mayfly-go/pkg/rediscli"
"mayfly-go/pkg/scheduler" "mayfly-go/pkg/scheduler"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/stringx" "mayfly-go/pkg/utils/stringx"
"time" "time"
@@ -178,43 +176,35 @@ func (m *machineCronJobAppImpl) addCronJob(mcj *entity.MachineCronJob) {
} }
func (m *machineCronJobAppImpl) runCronJob0(mid uint64, cronJob *entity.MachineCronJob) { func (m *machineCronJobAppImpl) runCronJob0(mid uint64, cronJob *entity.MachineCronJob) {
defer func() { execRes := &entity.MachineCronJobExec{
if err := recover(); err != nil { CronJobId: cronJob.Id,
res := anyx.ToString(err) ExecTime: time.Now(),
m.machineCronJobExecRepo.Insert(context.TODO(), &entity.MachineCronJobExec{ }
MachineId: mid,
CronJobId: cronJob.Id,
ExecTime: time.Now(),
Status: entity.MachineCronJobExecStatusError,
Res: res,
})
logx.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
}
}()
machineCli, err := m.machineApp.GetCli(uint64(mid)) machineCli, err := m.machineApp.GetCli(uint64(mid))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s") res := ""
res, err := machineCli.Run(cronJob.Script)
if err != nil { if err != nil {
if res == "" { machine, _ := m.machineApp.GetById(mid)
res = err.Error() execRes.MachineCode = machine.Code
}
logx.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
} else { } else {
logx.Debugf("机器:[%d]执行[%s]计划任务成功, 执行结果: %s", mid, cronJob.Name, res) execRes.MachineCode = machineCli.Info.Code
res, err = machineCli.Run(cronJob.Script)
if err != nil {
if res == "" {
res = err.Error()
}
logx.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
} else {
logx.Debugf("机器:[%d]执行[%s]计划任务成功, 执行结果: %s", mid, cronJob.Name, res)
}
} }
execRes.Res = res
if cronJob.SaveExecResType == entity.SaveExecResTypeNo || if cronJob.SaveExecResType == entity.SaveExecResTypeNo ||
(cronJob.SaveExecResType == entity.SaveExecResTypeOnError && err == nil) { (cronJob.SaveExecResType == entity.SaveExecResTypeOnError && err == nil) {
return return
} }
execRes := &entity.MachineCronJobExec{
MachineId: mid,
CronJobId: cronJob.Id,
ExecTime: time.Now(),
Res: res,
}
if err == nil { if err == nil {
execRes.Status = entity.MachineCronJobExecStatusSuccess execRes.Status = entity.MachineCronJobExecStatusSuccess
} else { } else {

View File

@@ -63,8 +63,8 @@ func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon
termOpRecord.MachineId = cli.Info.Id termOpRecord.MachineId = cli.Info.Id
termOpRecord.Username = cli.Info.Username termOpRecord.Username = cli.Info.Username
// 回放文件路径为: 基础配置路径/操作日期(202301)/day/hour/randstr.cast // 回放文件路径为: 基础配置路径/机器编号/操作日期(202301)/day/hour/randstr.cast
recRelPath := path.Join(now.Format("200601"), fmt.Sprintf("%d", now.Day()), fmt.Sprintf("%d", now.Hour())) recRelPath := path.Join(cli.Info.Code, now.Format("200601"), fmt.Sprintf("%d", now.Day()), fmt.Sprintf("%d", now.Hour()))
// 文件绝对路径 // 文件绝对路径
recAbsPath := path.Join(config.GetMachine().TerminalRecPath, recRelPath) recAbsPath := path.Join(config.GetMachine().TerminalRecPath, recRelPath)
os.MkdirAll(recAbsPath, 0766) os.MkdirAll(recAbsPath, 0766)

View File

@@ -23,11 +23,11 @@ type MachineCronJob struct {
type MachineCronJobExec struct { type MachineCronJobExec struct {
model.DeletedModel model.DeletedModel
CronJobId uint64 `json:"cronJobId" form:"cronJobId"` CronJobId uint64 `json:"cronJobId" form:"cronJobId"`
MachineId uint64 `json:"machineId" form:"machineId"` MachineCode string `json:"machineCode" form:"machineCode"`
Status int `json:"status" form:"status"` // 执行状态 Status int `json:"status" form:"status"` // 执行状态
Res string `json:"res"` // 执行结果 Res string `json:"res"` // 执行结果
ExecTime time.Time `json:"execTime"` ExecTime time.Time `json:"execTime"`
} }
const ( const (

View File

@@ -3,7 +3,7 @@ package entity
import "time" import "time"
type MachineQuery struct { type MachineQuery struct {
Ids string `json:"ids" form:"ids"` Id uint64 `json:"id" form:"id"`
Code string `json:"code" form:"code"` Code string `json:"code" form:"code"`
Name string `json:"name" form:"name"` Name string `json:"name" form:"name"`
Status int8 `json:"status" form:"status"` Status int8 `json:"status" form:"status"`

View File

@@ -5,10 +5,6 @@ import (
"mayfly-go/internal/machine/domain/repository" "mayfly-go/internal/machine/domain/repository"
"mayfly-go/pkg/base" "mayfly-go/pkg/base"
"mayfly-go/pkg/model" "mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
"strings"
"github.com/may-fly/cast"
) )
type machineRepoImpl struct { type machineRepoImpl struct {
@@ -22,6 +18,7 @@ func newMachineRepo() repository.Machine {
// 分页获取机器信息列表 // 分页获取机器信息列表
func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) { func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := model.NewCond(). qd := model.NewCond().
Eq("id", condition.Id).
Eq("status", condition.Status). Eq("status", condition.Status).
Like("ip", condition.Ip). Like("ip", condition.Ip).
Like("name", condition.Name). Like("name", condition.Name).
@@ -29,11 +26,5 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
Like("code", condition.Code). Like("code", condition.Code).
Eq("protocol", condition.Protocol) Eq("protocol", condition.Protocol)
if condition.Ids != "" {
qd.In("id", collx.ArrayMap[string, uint64](strings.Split(condition.Ids, ","), func(val string) uint64 {
return cast.ToUint64(val)
}))
}
return m.PageByCondToAny(qd, pageParam, toEntity) return m.PageByCondToAny(qd, pageParam, toEntity)
} }

View File

@@ -9,7 +9,7 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// 机器客户端 // Cli 机器客户端
type Cli struct { type Cli struct {
Info *MachineInfo // 机器信息 Info *MachineInfo // 机器信息
@@ -17,7 +17,7 @@ type Cli struct {
sftpClient *sftp.Client // sftp客户端 sftpClient *sftp.Client // sftp客户端
} }
// 获取sftp client // GetSftpCli 获取sftp client
func (c *Cli) GetSftpCli() (*sftp.Client, error) { func (c *Cli) GetSftpCli() (*sftp.Client, error) {
if c.sshClient == nil { if c.sshClient == nil {
return nil, errorx.NewBiz("请先进行机器客户端连接") return nil, errorx.NewBiz("请先进行机器客户端连接")
@@ -36,7 +36,7 @@ func (c *Cli) GetSftpCli() (*sftp.Client, error) {
return sftpclient, nil return sftpclient, nil
} }
// 获取session // GetSession 获取session
func (c *Cli) GetSession() (*ssh.Session, error) { func (c *Cli) GetSession() (*ssh.Session, error) {
if c.sshClient == nil { if c.sshClient == nil {
return nil, errorx.NewBiz("请先进行机器客户端连接") return nil, errorx.NewBiz("请先进行机器客户端连接")
@@ -49,7 +49,7 @@ func (c *Cli) GetSession() (*ssh.Session, error) {
return session, nil return session, nil
} }
// 执行shell // Run 执行shell
// @param shell shell脚本命令 // @param shell shell脚本命令
// @return 返回执行成功或错误的消息 // @return 返回执行成功或错误的消息
func (c *Cli) Run(shell string) (string, error) { func (c *Cli) Run(shell string) (string, error) {
@@ -58,14 +58,15 @@ func (c *Cli) Run(shell string) (string, error) {
return "", err return "", err
} }
defer session.Close() defer session.Close()
buf, err := session.CombinedOutput(shell) // 将可能存在的windows换行符替换为linux格式
buf, err := session.CombinedOutput(strings.ReplaceAll(shell, "\r\n", "\n"))
if err != nil { if err != nil {
return string(buf), err return string(buf), err
} }
return string(buf), nil return string(buf), nil
} }
// 获取机器的所有状态信息 // GetAllStats 获取机器的所有状态信息
func (c *Cli) GetAllStats() *Stats { func (c *Cli) GetAllStats() *Stats {
stats := new(Stats) stats := new(Stats)
res, err := c.Run(StatsShell) res, err := c.Run(StatsShell)
@@ -89,7 +90,7 @@ func (c *Cli) GetAllStats() *Stats {
return stats return stats
} }
// 关闭client并从缓存中移除如果使用隧道则也关闭 // Close 关闭client并从缓存中移除如果使用隧道则也关闭
func (c *Cli) Close() { func (c *Cli) Close() {
m := c.Info m := c.Info
logx.Debugf("close machine cli -> id=%d, name=%s, ip=%s", m.Id, m.Name, m.Ip) logx.Debugf("close machine cli -> id=%d, name=%s, ip=%s", m.Id, m.Name, m.Ip)

View File

@@ -15,6 +15,7 @@ import (
type MachineInfo struct { type MachineInfo struct {
Key string `json:"key"` // 缓存key Key string `json:"key"` // 缓存key
Id uint64 `json:"id"` Id uint64 `json:"id"`
Code string `json:"code"`
Name string `json:"name"` Name string `json:"name"`
Protocol int `json:"protocol"` Protocol int `json:"protocol"`

View File

@@ -128,7 +128,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, param *dto.SaveRedis) erro
ParentTagCodePaths: tagCodePaths, ParentTagCodePaths: tagCodePaths,
}) })
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: re.Code, ResourceCode: re.Code,
ResourceType: tagentity.TagTypeRedis, ResourceType: tagentity.TagTypeRedis,
AuthCerts: []*tagentity.ResourceAuthCert{param.AuthCert}, AuthCerts: []*tagentity.ResourceAuthCert{param.AuthCert},
@@ -170,7 +170,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, param *dto.SaveRedis) erro
ParentTagCodePaths: tagCodePaths, ParentTagCodePaths: tagCodePaths,
}) })
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: oldRedis.Code, ResourceCode: oldRedis.Code,
ResourceType: tagentity.TagTypeRedis, ResourceType: tagentity.TagTypeRedis,
AuthCerts: []*tagentity.ResourceAuthCert{param.AuthCert}, AuthCerts: []*tagentity.ResourceAuthCert{param.AuthCert},
@@ -200,7 +200,7 @@ func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
}, },
}) })
}, func(ctx context.Context) error { }, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{ return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagdto.RelateAuthCert{
ResourceCode: re.Code, ResourceCode: re.Code,
ResourceType: tagentity.TagTypeRedis, ResourceType: tagentity.TagTypeRedis,
}) })

View File

@@ -2,7 +2,6 @@ package vo
import ( import (
"mayfly-go/internal/tag/application/dto" "mayfly-go/internal/tag/application/dto"
"mayfly-go/pkg/utils/collx"
) )
type TagTreeVOS []*dto.SimpleTagTree type TagTreeVOS []*dto.SimpleTagTree
@@ -18,14 +17,21 @@ func (m *TagTreeVOS) ToTrees(pid uint64) []*TagTreeItem {
return ttis return ttis
} }
ttis = collx.ArrayMap(*m, func(tr *dto.SimpleTagTree) *TagTreeItem { return &TagTreeItem{SimpleTagTree: tr} }) tagMap := make(map[string]*TagTreeItem)
tagMap := collx.ArrayToMap(ttis, func(item *TagTreeItem) string { var roots []*TagTreeItem
return item.CodePath for _, tag := range *m {
}) tti := &TagTreeItem{SimpleTagTree: tag}
tagMap[tag.CodePath] = tti
ttis = append(ttis, tti)
if tti.IsRoot() {
roots = append(roots, tti)
tti.Root = true
}
}
for _, node := range ttis { for _, node := range ttis {
// 根节点 // 根节点
if node.IsRoot() { if node.Root {
continue continue
} }
parentCodePath := node.GetParentPath(0) parentCodePath := node.GetParentPath(0)
@@ -35,5 +41,5 @@ func (m *TagTreeVOS) ToTrees(pid uint64) []*TagTreeItem {
} }
} }
return collx.ArrayFilter(ttis, func(tti *TagTreeItem) bool { return tti.IsRoot() }) return roots
} }

View File

@@ -0,0 +1,13 @@
package dto
import "mayfly-go/internal/tag/domain/entity"
type RelateAuthCert struct {
ResourceCode string
// 资源标签类型
ResourceType entity.TagType
// 空数组则为删除该资源绑定的授权凭证
AuthCerts []*entity.ResourceAuthCert
}

View File

@@ -43,6 +43,7 @@ type SimpleTagTree struct {
CodePath string `json:"codePath"` // 标识路径tag1/tag2/tagType1|tagCode/tagType2|yyycode/,非普通标签类型段含有标签类型 CodePath string `json:"codePath"` // 标识路径tag1/tag2/tagType1|tagCode/tagType2|yyycode/,非普通标签类型段含有标签类型
Name string `json:"name"` // 名称 Name string `json:"name"` // 名称
Remark string `json:"remark"` Remark string `json:"remark"`
Root bool `json:"-" gorm:"-"`
} }
func (pt *SimpleTagTree) IsRoot() bool { func (pt *SimpleTagTree) IsRoot() bool {

View File

@@ -12,21 +12,11 @@ import (
"mayfly-go/pkg/utils/collx" "mayfly-go/pkg/utils/collx"
) )
type RelateAuthCertParam struct {
ResourceCode string
// 资源标签类型
ResourceType entity.TagType
// 空数组则为删除该资源绑定的授权凭证
AuthCerts []*entity.ResourceAuthCert
}
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 *dto.RelateAuthCert) error
// SaveAuthCert 保存授权凭证信息 // SaveAuthCert 保存授权凭证信息
SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error SaveAuthCert(ctx context.Context, rac *entity.ResourceAuthCert) error
@@ -64,7 +54,7 @@ func (r *resourceAuthCertAppImpl) InjectResourceAuthCertRepo(resourceAuthCertRep
r.Repo = resourceAuthCertRepo r.Repo = resourceAuthCertRepo
} }
func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *RelateAuthCertParam) error { func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *dto.RelateAuthCert) error {
resourceCode := params.ResourceCode resourceCode := params.ResourceCode
resourceType := int8(params.ResourceType) resourceType := int8(params.ResourceType)
resourceAuthCerts := params.AuthCerts resourceAuthCerts := params.AuthCerts
@@ -92,11 +82,6 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
resourceAuthCert.ResourceType = int8(resourceType) resourceAuthCert.ResourceType = int8(resourceType)
name2AuthCert[resourceAuthCert.Name] = resourceAuthCert name2AuthCert[resourceAuthCert.Name] = resourceAuthCert
existNameAc := &entity.ResourceAuthCert{Name: resourceAuthCert.Name}
if resourceAuthCert.Id == 0 && r.GetByCond(existNameAc) == nil && existNameAc.ResourceCode != resourceCode {
return errorx.NewBiz("授权凭证的名称不能重复[%s]", resourceAuthCert.Name)
}
// 公共授权凭证,则无需进行密文加密,密文即为公共授权凭证名 // 公共授权凭证,则无需进行密文加密,密文即为公共授权凭证名
if resourceAuthCert.CiphertextType == entity.AuthCertCiphertextTypePublic { if resourceAuthCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
continue continue
@@ -111,63 +96,66 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
oldAuthCert, _ := r.ListByCond(&entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}) oldAuthCert, _ := r.ListByCond(&entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType})
// 新增、删除以及不变的授权凭证名 // 新增、删除以及不变的授权凭证名
var adds, dels, unmodifys []string var addAcNames, delAcNames, unmodifyAcNames []string
if len(oldAuthCert) == 0 { if len(oldAuthCert) == 0 {
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-不存在已有的授权凭证信息, 为新增资源授权凭证", resourceType, resourceCode) logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-不存在已有的授权凭证信息, 为新增资源授权凭证", resourceType, resourceCode)
adds = collx.MapKeys(name2AuthCert) addAcNames = collx.MapKeys(name2AuthCert)
} else { } else {
oldNames := collx.ArrayMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string { oldNames := collx.ArrayMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string {
return ac.Name return ac.Name
}) })
adds, dels, unmodifys = collx.ArrayCompare[string](collx.MapKeys(name2AuthCert), oldNames) addAcNames, delAcNames, unmodifyAcNames = collx.ArrayCompare[string](collx.MapKeys(name2AuthCert), oldNames)
} }
addAuthCerts := make([]*entity.ResourceAuthCert, 0) addAuthCerts := make([]*entity.ResourceAuthCert, 0)
for _, add := range adds { for _, addAcName := range addAcNames {
addAc := name2AuthCert[add] addAc := name2AuthCert[addAcName]
addAc.Id = 0 addAc.Id = 0
existNameAc := &entity.ResourceAuthCert{Name: addAcName}
if r.GetByCond(existNameAc) == nil && existNameAc.ResourceCode != resourceCode {
return errorx.NewBiz("授权凭证的名称不能重复[%s]", addAcName)
}
addAuthCerts = append(addAuthCerts, addAc) addAuthCerts = append(addAuthCerts, addAc)
} }
// 处理新增的授权凭证 // 处理新增的授权凭证
if len(addAuthCerts) > 0 { if len(addAuthCerts) > 0 {
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-新增授权凭证-[%v]", resourceType, resourceCode, adds) logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-新增授权凭证-[%v]", resourceType, resourceCode, addAcNames)
if err := r.BatchInsert(ctx, addAuthCerts); err != nil { if err := r.BatchInsert(ctx, addAuthCerts); err != nil {
return err return err
} }
} }
for _, del := range dels { for _, delAcName := range delAcNames {
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-删除授权凭证-[%v]", resourceType, resourceCode, del) logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-删除授权凭证-[%v]", resourceType, resourceCode, delAcName)
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: delAcName}); err != nil {
return err return err
} }
} }
if len(unmodifys) > 0 { if len(unmodifyAcNames) > 0 {
// 旧凭证名 -> 旧凭证 // 旧凭证名 -> 旧凭证
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) acTagType := GetResourceAuthCertTagType(params.ResourceType)
for _, unmodify := range unmodifys { for _, unmodifyAcName := range unmodifyAcNames {
unmodifyAc := name2AuthCert[unmodify] unmodifyAc := name2AuthCert[unmodifyAcName]
if unmodifyAc.Id == 0 {
continue
}
oldAuthCert := oldName2AuthCert[unmodify] oldAuthCert := oldName2AuthCert[unmodifyAcName]
if !unmodifyAc.HasChanged(oldAuthCert) { if !unmodifyAc.HasChanged(oldAuthCert) {
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-授权凭证[%s]未发生字段变更", resourceType, resourceCode, unmodify) logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-授权凭证[%s]未发生字段变更", resourceType, resourceCode, unmodifyAcName)
continue continue
} }
// 如果修改了用户名且该凭证关联至标签则需要更新对应的标签名资源授权凭证类型的标签名为username // 如果修改了用户名且该凭证关联至标签则需要更新对应的标签名资源授权凭证类型的标签名为username
if oldAuthCert.Username != unmodifyAc.Username && acTagType != 0 { if oldAuthCert.Username != unmodifyAc.Username && acTagType != 0 {
r.tagTreeApp.UpdateTagName(ctx, acTagType, unmodify, unmodifyAc.Username) r.tagTreeApp.UpdateTagName(ctx, acTagType, unmodifyAcName, unmodifyAc.Username)
} }
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-更新授权凭证-[%v]", resourceType, resourceCode, unmodify) logx.DebugfContext(ctx, "RelateAuthCert[%d-%s]-更新授权凭证-[%v]", resourceType, resourceCode, unmodifyAcName)
if err := r.UpdateById(ctx, unmodifyAc); err != nil { if err := r.UpdateByCond(ctx, unmodifyAc, &entity.ResourceAuthCert{Name: unmodifyAcName, ResourceCode: resourceCode, ResourceType: resourceType}); err != nil {
return err return err
} }
} }

View File

@@ -103,7 +103,7 @@ func (m *ResourceAuthCert) HasChanged(rac *ResourceAuthCert) bool {
return true return true
} }
return m.Username != rac.Username || return m.Username != rac.Username ||
m.Ciphertext != rac.Ciphertext || (m.Ciphertext != "" && rac.Ciphertext != "" && m.Ciphertext != rac.Ciphertext) ||
m.CiphertextType != rac.CiphertextType || m.CiphertextType != rac.CiphertextType ||
m.Remark != rac.Remark || m.Remark != rac.Remark ||
m.Type != rac.Type || m.Type != rac.Type ||

View File

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

View File

@@ -36,6 +36,7 @@ CREATE TABLE `t_db` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL, `code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
`name` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL, `name` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL,
`get_database_mode` tinyint NULL COMMENT '库名获取方式(-1.实时获取、1.指定库名)',
`database` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL, `database` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL,
`remark` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL, `remark` varchar(191) COLLATE utf8mb4_bin DEFAULT NULL,
`instance_id` bigint unsigned NOT NULL, `instance_id` bigint unsigned NOT NULL,
@@ -464,9 +465,9 @@ DROP TABLE IF EXISTS `t_machine_cron_job_exec`;
CREATE TABLE `t_machine_cron_job_exec` ( CREATE TABLE `t_machine_cron_job_exec` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `id` bigint unsigned NOT NULL AUTO_INCREMENT,
`cron_job_id` bigint DEFAULT NULL, `cron_job_id` bigint DEFAULT NULL,
`machine_id` bigint DEFAULT NULL, `machine_code` varchar(36) DEFAULT NULL,
`status` tinyint DEFAULT NULL COMMENT '状态', `status` tinyint DEFAULT NULL COMMENT '状态',
`res` varchar(1000) DEFAULT NULL COMMENT '执行结果', `res` varchar(4000) DEFAULT NULL COMMENT '执行结果',
`exec_time` datetime DEFAULT NULL COMMENT '执行时间', `exec_time` datetime DEFAULT NULL COMMENT '执行时间',
`is_deleted` tinyint NOT NULL DEFAULT 0, `is_deleted` tinyint NOT NULL DEFAULT 0,
`delete_time` datetime DEFAULT NULL, `delete_time` datetime DEFAULT NULL,

View File

@@ -0,0 +1,6 @@
ALTER TABLE t_db ADD get_database_mode tinyint NULL COMMENT '库名获取方式(-1.实时获取、1.指定库名)';
UPDATE t_db SET get_database_mode = 1;
ALTER TABLE t_machine_cron_job_exec ADD machine_code varchar(36) NULL COMMENT '机器编号';
ALTER TABLE t_machine_cron_job_exec DROP COLUMN machine_id;