@@ -160,6 +160,8 @@ import { dispposeCompletionItemProvider } from '@/components/monaco/completionIt
import SvgIcon from '@/components/svgIcon/index.vue';
import { ContextmenuItem } from '@/components/contextmenu';
import { getDbDialect } from './dialect/index';
+import { sleep } from '@/common/utils/loading';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
@@ -213,11 +215,16 @@ const nodeClickChangeDb = (nodeData: TagTreeNode) => {
// tagpath 节点类型
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
- const dbInfos = instMap.get(parentNode.key);
+ const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
+ const dbInfos = dbInfoRes.list;
if (!dbInfos) {
return [];
}
+
+ // 防止过快加载会出现一闪而过,对眼睛不好
+ await sleep(100);
return dbInfos?.map((x: any) => {
+ x.tagPath = parentNode.key;
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
});
});
@@ -396,35 +403,6 @@ const setHeight = () => {
state.tablesOpHeight = window.innerHeight - 220 + 'px';
};
-/**
- * instmap; tagPaht -> info[]
- */
-const instMap: Map = new Map();
-
-const getInsts = async () => {
- const res = await dbApi.dbs.request({ pageNum: 1, pageSize: 1000 });
- if (!res.total) return;
- for (const db of res.list) {
- const tagPath = db.tagPath;
- let dbInsts = instMap.get(tagPath) || [];
- dbInsts.push(db);
- instMap.set(tagPath, dbInsts?.sort());
- }
-};
-
-/**
- * 加载标签树节点
- */
-const loadTags = async () => {
- await getInsts();
- const tagPaths = instMap.keys();
- const tagNodes = [];
- for (let tagPath of tagPaths) {
- tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
- }
- return tagNodes;
-};
-
// 选择数据库,改变当前正在操作的数据库信息
const changeDb = (db: any, dbName: string) => {
state.nowDbInst = DbInst.getOrNewInst(db);
diff --git a/mayfly_go_web/src/views/ops/db/component/table/DbTableData.vue b/mayfly_go_web/src/views/ops/db/component/table/DbTableData.vue
index 95c76b09..484ee032 100644
--- a/mayfly_go_web/src/views/ops/db/component/table/DbTableData.vue
+++ b/mayfly_go_web/src/views/ops/db/component/table/DbTableData.vue
@@ -71,7 +71,7 @@
:ref="(el: any) => el?.focus()"
@blur="onExitEditMode(rowData, column, rowIndex)"
class="w100"
- input-style="text-align: center; height: 27px;"
+ input-style="text-align: center; height: 26px;"
size="small"
v-model="rowData[column.dataKey!]"
>
diff --git a/mayfly_go_web/src/views/ops/machine/MachineEdit.vue b/mayfly_go_web/src/views/ops/machine/MachineEdit.vue
index edcbeea2..4780658e 100644
--- a/mayfly_go_web/src/views/ops/machine/MachineEdit.vue
+++ b/mayfly_go_web/src/views/ops/machine/MachineEdit.vue
@@ -4,8 +4,19 @@
-
-
+
+ {
+ form.tagId = tagIds;
+ tagSelectRef.validate();
+ }
+ "
+ :resource-code="form.code"
+ :resource-type="TagResourceTypeEnum.Machine.value"
+ style="width: 100%"
+ />
@@ -71,9 +82,10 @@
import { toRefs, reactive, watch, ref } from 'vue';
import { machineApi } from './api';
import { ElMessage } from 'element-plus';
-import TagSelect from '../component/TagSelect.vue';
+import TagTreeSelect from '../component/TagTreeSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import AuthCertSelect from './authcert/AuthCertSelect.vue';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({
visible: {
@@ -95,7 +107,7 @@ const rules = {
{
required: true,
message: '请选择标签',
- trigger: ['blur', 'change'],
+ trigger: ['change'],
},
],
name: [
@@ -126,17 +138,11 @@ const rules = {
trigger: ['change', 'blur'],
},
],
- password: [
- {
- required: true,
- message: '请输入授权密码',
- trigger: ['change', 'blur'],
- },
- ],
};
const machineForm: any = ref(null);
const authCertSelectRef: any = ref(null);
+const tagSelectRef: any = ref(null);
const state = reactive({
dialogVisible: false,
@@ -146,14 +152,14 @@ const state = reactive({
authType: 1,
form: {
id: null,
+ code: '',
ip: null,
port: 22,
name: null,
authCertId: null as any,
username: '',
password: '',
- tagId: null as any,
- tagPath: null as any,
+ tagId: [],
remark: '',
sshTunnelMachineId: null as any,
enableRecorder: -1,
@@ -173,6 +179,7 @@ watch(props, async (newValue: any) => {
state.tabActiveName = 'basic';
if (newValue.machine) {
state.form = { ...newValue.machine };
+
// 如果凭证类型为公共的,则表示使用授权凭证认证
const authCertId = (state.form as any).authCertId;
if (authCertId > 0) {
@@ -181,7 +188,7 @@ watch(props, async (newValue: any) => {
state.authType = 1;
}
} else {
- state.form = { port: 22 } as any;
+ state.form = { port: 22, tagId: [] } as any;
state.authType = 1;
}
});
diff --git a/mayfly_go_web/src/views/ops/machine/MachineList.vue b/mayfly_go_web/src/views/ops/machine/MachineList.vue
index 171ad46e..61355ee3 100644
--- a/mayfly_go_web/src/views/ops/machine/MachineList.vue
+++ b/mayfly_go_web/src/views/ops/machine/MachineList.vue
@@ -24,13 +24,6 @@
删除
-
-
-
- {{ data.tagPath }}
-
-
-
{{ `${data.ip}:${data.port}` }}
@@ -82,6 +75,10 @@
>
+
+
+
+
@@ -190,15 +187,17 @@
diff --git a/mayfly_go_web/src/views/ops/machine/api.ts b/mayfly_go_web/src/views/ops/machine/api.ts
index 9cc5a369..55e94fdb 100644
--- a/mayfly_go_web/src/views/ops/machine/api.ts
+++ b/mayfly_go_web/src/views/ops/machine/api.ts
@@ -43,7 +43,10 @@ export const machineApi = {
// 删除配置的文件or目录
delConf: Api.newDelete('/machines/{machineId}/files/{id}'),
terminal: Api.newGet('/api/machines/{id}/terminal'),
- recDirNames: Api.newGet('/machines/rec/names'),
+ // 机器终端操作记录列表
+ termOpRecs: Api.newGet('/machines/{id}/term-recs'),
+ // 机器终端操作记录详情
+ termOpRec: Api.newGet('/machines/{id}/term-recs/{recId}'),
};
export const authCertApi = {
@@ -59,6 +62,7 @@ export const cronJobApi = {
relateCronJobIds: Api.newGet('/machine-cronjobs/cronjob-ids'),
save: Api.newPost('/machine-cronjobs'),
delete: Api.newDelete('/machine-cronjobs/{id}'),
+ run: Api.newPost('/machine-cronjobs/run/{key}'),
execList: Api.newGet('/machine-cronjobs/execs'),
};
diff --git a/mayfly_go_web/src/views/ops/machine/cronjob/CronJobExecList.vue b/mayfly_go_web/src/views/ops/machine/cronjob/CronJobExecList.vue
index 4054bc6d..575754d1 100644
--- a/mayfly_go_web/src/views/ops/machine/cronjob/CronJobExecList.vue
+++ b/mayfly_go_web/src/views/ops/machine/cronjob/CronJobExecList.vue
@@ -13,9 +13,9 @@
ref="pageTableRef"
:query="queryConfig"
v-model:query-form="params"
- :data="data.list"
+ :data="state.data.list"
:columns="columns"
- :total="data.total"
+ :total="state.data.total"
v-model:page-size="params.pageSize"
v-model:page-num="params.pageNum"
@pageChange="search()"
@@ -88,7 +88,7 @@ const state = reactive({
const machineMap: Map = new Map();
-const { dialogVisible, params, data } = toRefs(state);
+const { dialogVisible, params } = toRefs(state);
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
diff --git a/mayfly_go_web/src/views/ops/machine/cronjob/CronJobList.vue b/mayfly_go_web/src/views/ops/machine/cronjob/CronJobList.vue
index 36389953..3756d10e 100644
--- a/mayfly_go_web/src/views/ops/machine/cronjob/CronJobList.vue
+++ b/mayfly_go_web/src/views/ops/machine/cronjob/CronJobList.vue
@@ -24,6 +24,9 @@
+ 执行
编辑
执行记录
@@ -111,6 +114,11 @@ const openFormDialog = async (data: any) => {
state.cronJobEdit.visible = true;
};
+const runCronJob = async (data: any) => {
+ await cronJobApi.run.request({ key: data.key });
+ ElMessage.success('执行成功');
+};
+
const deleteCronJob = async () => {
try {
await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】计划任务信息? 该操作将同时删除执行记录`, '提示', {
diff --git a/mayfly_go_web/src/views/ops/mongo/MongoDataOp.vue b/mayfly_go_web/src/views/ops/mongo/MongoDataOp.vue
index 67d3d8b7..5e5c6ff6 100644
--- a/mayfly_go_web/src/views/ops/mongo/MongoDataOp.vue
+++ b/mayfly_go_web/src/views/ops/mongo/MongoDataOp.vue
@@ -2,7 +2,7 @@
-
+
@@ -172,6 +172,8 @@ import { isTrue, notBlank } from '@/common/assert';
import { TagTreeNode, NodeType } from '../component/tag';
import TagTree from '../component/TagTree.vue';
import { formatByteSize } from '@/common/utils/format';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
+import { sleep } from '@/common/utils/loading';
const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue'));
@@ -192,12 +194,15 @@ class MongoNodeType {
// tagpath 节点类型
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
- // 点击标签 -> 显示mongo信息列表
- const mongoInfos = instMap.get(parentNode.key);
- if (!mongoInfos) {
+ const res = await mongoApi.mongoList.request({ tagPath: parentNode.key });
+ if (!res.total) {
return [];
}
+
+ const mongoInfos = res.list;
+ await sleep(100);
return mongoInfos?.map((x: any) => {
+ x.tagPath = parentNode.key;
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeMongo).withParams(x);
});
});
@@ -278,35 +283,6 @@ const nowColl = computed(() => {
return getNowDataTab();
});
-/**
- * instmap; tagPaht -> mongo info[]
- */
-const instMap: Map = new Map();
-
-const getInsts = async () => {
- const res = await mongoApi.mongoList.request({ pageNum: 1, pageSize: 1000 });
- if (!res.total) return;
- for (const mongoInfo of res.list) {
- const tagPath = mongoInfo.tagPath;
- let mongoInsts = instMap.get(tagPath) || [];
- mongoInsts.push(mongoInfo);
- instMap.set(tagPath, mongoInsts);
- }
-};
-
-/**
- * 加载标签树树节点
- */
-const loadTags = async () => {
- await getInsts();
- const tagPaths = instMap.keys();
- const tagNodes = [];
- for (let tagPath of tagPaths) {
- tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
- }
- return tagNodes;
-};
-
const changeCollection = async (id: any, schema: string, collection: string) => {
const label = `${id}:\`${schema}\`.${collection}`;
let dataTab = state.dataTabs[label];
diff --git a/mayfly_go_web/src/views/ops/mongo/MongoEdit.vue b/mayfly_go_web/src/views/ops/mongo/MongoEdit.vue
index 17a90f1d..d03951c2 100644
--- a/mayfly_go_web/src/views/ops/mongo/MongoEdit.vue
+++ b/mayfly_go_web/src/views/ops/mongo/MongoEdit.vue
@@ -4,8 +4,19 @@
-
-
+
+ {
+ form.tagId = tagIds;
+ tagSelectRef.validate();
+ }
+ "
+ multiple
+ :resource-code="form.code"
+ :resource-type="TagResourceTypeEnum.Mongo.value"
+ style="width: 100%"
+ />
@@ -45,8 +56,9 @@
import { toRefs, reactive, watch, ref } from 'vue';
import { mongoApi } from './api';
import { ElMessage } from 'element-plus';
-import TagSelect from '../component/TagSelect.vue';
+import TagTreeSelect from '../component/TagTreeSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({
visible: {
@@ -88,16 +100,18 @@ const rules = {
};
const mongoForm: any = ref(null);
+const tagSelectRef: any = ref(null);
+
const state = reactive({
dialogVisible: false,
tabActiveName: 'basic',
form: {
id: null,
+ code: '',
name: null,
uri: null,
sshTunnelMachineId: null as any,
- tagId: null as any,
- tagPath: null as any,
+ tagId: [],
},
btnLoading: false,
testConnBtnLoading: false,
@@ -146,7 +160,7 @@ const testConn = async () => {
const btnOk = async () => {
mongoForm.value.validate(async (valid: boolean) => {
if (valid) {
- mongoApi.saveMongo.request(getReqForm).then(() => {
+ mongoApi.saveMongo.request(getReqForm()).then(() => {
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
diff --git a/mayfly_go_web/src/views/ops/mongo/MongoList.vue b/mayfly_go_web/src/views/ops/mongo/MongoList.vue
index 73e758cc..6604bbf0 100644
--- a/mayfly_go_web/src/views/ops/mongo/MongoList.vue
+++ b/mayfly_go_web/src/views/ops/mongo/MongoList.vue
@@ -25,10 +25,7 @@
-
-
- {{ data.tagPath }}
-
+
@@ -57,21 +54,25 @@
import { mongoApi } from './api';
import { defineAsyncComponent, ref, toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
-import TagInfo from '../component/TagInfo.vue';
+import ResourceTag from '../component/ResourceTag.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
+import { tagApi } from '../tag/api';
+import { useRoute } from 'vue-router';
const MongoEdit = defineAsyncComponent(() => import('./MongoEdit.vue'));
const MongoDbs = defineAsyncComponent(() => import('./MongoDbs.vue'));
const MongoRunCommand = defineAsyncComponent(() => import('./MongoRunCommand.vue'));
const pageTableRef: any = ref(null);
+const route = useRoute();
const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect')];
const columns = ref([
- TableColumn.new('tagPath', '标签路径').isSlot().setAddWidth(20),
TableColumn.new('name', '名称'),
TableColumn.new('uri', '连接uri'),
+ TableColumn.new('tagPath', '关联标签').isSlot().setAddWidth(20).alignCenter(),
TableColumn.new('createTime', '创建时间').isTime(),
TableColumn.new('creator', '创建人'),
TableColumn.new('action', '操作').isSlot().setMinWidth(170).fixedRight().alignCenter(),
@@ -89,7 +90,7 @@ const state = reactive({
query: {
pageNum: 1,
pageSize: 0,
- tagPath: null,
+ tagPath: '',
},
mongoEditDialog: {
visible: false,
@@ -134,6 +135,11 @@ const deleteMongo = async () => {
const search = async () => {
try {
pageTableRef.value.loading(true);
+
+ if (route.query.tagPath) {
+ state.query.tagPath = route.query.tagPath as string;
+ }
+
const res = await mongoApi.mongoList.request(state.query);
state.list = res.list;
state.total = res.total;
@@ -143,7 +149,7 @@ const search = async () => {
};
const getTags = async () => {
- state.tags = await mongoApi.mongoTags.request(null);
+ state.tags = await tagApi.getResourceTagPaths.request({ resourceType: TagResourceTypeEnum.Mongo.value });
};
const editMongo = async (data: any) => {
diff --git a/mayfly_go_web/src/views/ops/redis/DataOperation.vue b/mayfly_go_web/src/views/ops/redis/DataOperation.vue
index 137d0a11..e81ffe3f 100644
--- a/mayfly_go_web/src/views/ops/redis/DataOperation.vue
+++ b/mayfly_go_web/src/views/ops/redis/DataOperation.vue
@@ -4,7 +4,7 @@
-
+
@@ -181,6 +181,8 @@ import { TagTreeNode, NodeType } from '../component/tag';
import TagTree from '../component/TagTree.vue';
import { keysToTree, sortByTreeNodes, keysToList } from './utils';
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
+import { sleep } from '../../../common/utils/loading';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
@@ -212,11 +214,15 @@ class RedisNodeType {
// tagpath 节点类型
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
- const redisInfos = instMap.get(parentNode.key);
- if (!redisInfos) {
+ const res = await redisApi.redisList.request({ tagPath: parentNode.key });
+ if (!res.total) {
return [];
}
+
+ const redisInfos = res.list;
+ await sleep(100);
return redisInfos.map((x: any) => {
+ x.tagPath = parentNode.key;
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeRedis).withParams(x);
});
});
@@ -321,34 +327,34 @@ const setHeight = () => {
state.keyTreeHeight = window.innerHeight - 174 + 'px';
};
-/**
- * instmap; tagPaht -> redis info[]
- */
-const instMap: Map = new Map();
+// /**
+// * instmap; tagPaht -> redis info[]
+// */
+// const instMap: Map = new Map();
-const getInsts = async () => {
- const res = await redisApi.redisList.request({ pageNum: 1, pageSize: 1000 });
- if (!res.total) return;
- for (const redisInfo of res.list) {
- const tagPath = redisInfo.tagPath;
- let redisInsts = instMap.get(tagPath) || [];
- redisInsts.push(redisInfo);
- instMap.set(tagPath, redisInsts);
- }
-};
+// const getInsts = async () => {
+// const res = await redisApi.redisList.request({ pageNum: 1, pageSize: 1000 });
+// if (!res.total) return;
+// for (const redisInfo of res.list) {
+// const tagPath = redisInfo.tagPath;
+// let redisInsts = instMap.get(tagPath) || [];
+// redisInsts.push(redisInfo);
+// instMap.set(tagPath, redisInsts);
+// }
+// };
-/**
- * 加载标签树节点
- */
-const loadTags = async () => {
- await getInsts();
- const tagPaths = instMap.keys();
- const tagNodes = [];
- for (let tagPath of tagPaths) {
- tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
- }
- return tagNodes;
-};
+// /**
+// * 加载标签树节点
+// */
+// const loadTags = async () => {
+// await getInsts();
+// const tagPaths = instMap.keys();
+// const tagNodes = [];
+// for (let tagPath of tagPaths) {
+// tagNodes.push(new TagTreeNode(tagPath, tagPath, NodeTypeTagPath));
+// }
+// return tagNodes;
+// };
const scan = async (appendKey = false) => {
isTrue(state.scanParam.id != null, '请先选择redis');
diff --git a/mayfly_go_web/src/views/ops/redis/RedisEdit.vue b/mayfly_go_web/src/views/ops/redis/RedisEdit.vue
index a174a8d7..c00e1165 100644
--- a/mayfly_go_web/src/views/ops/redis/RedisEdit.vue
+++ b/mayfly_go_web/src/views/ops/redis/RedisEdit.vue
@@ -4,8 +4,19 @@
-
-
+
+ {
+ form.tagId = tagIds;
+ tagSelectRef.validate();
+ }
+ "
+ multiple
+ :resource-code="form.code"
+ :resource-type="TagResourceTypeEnum.Redis.value"
+ style="width: 100%"
+ />
@@ -87,8 +98,9 @@ import { toRefs, reactive, watch, ref } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { RsaEncrypt } from '@/common/rsa';
-import TagSelect from '../component/TagSelect.vue';
+import TagTreeSelect from '../component/TagTreeSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
const props = defineProps({
visible: {
@@ -143,13 +155,15 @@ const rules = {
};
const redisForm: any = ref(null);
+const tagSelectRef: any = ref(null);
+
const state = reactive({
dialogVisible: false,
tabActiveName: 'basic',
form: {
id: null,
- tagId: null as any,
- tagPath: null as any,
+ code: '',
+ tagId: [],
name: null,
mode: 'standalone',
host: '',
diff --git a/mayfly_go_web/src/views/ops/redis/RedisList.vue b/mayfly_go_web/src/views/ops/redis/RedisList.vue
index 0637e25c..1b405891 100644
--- a/mayfly_go_web/src/views/ops/redis/RedisList.vue
+++ b/mayfly_go_web/src/views/ops/redis/RedisList.vue
@@ -25,10 +25,7 @@
-
-
- {{ data.tagPath }}
-
+
@@ -167,18 +164,22 @@ import { ref, toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import RedisEdit from './RedisEdit.vue';
import { dateFormat } from '@/common/utils/date';
-import TagInfo from '../component/TagInfo.vue';
+import ResourceTag from '../component/ResourceTag.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable';
+import { tagApi } from '../tag/api';
+import { TagResourceTypeEnum } from '@/common/commonEnum';
+import { useRoute } from 'vue-router';
const pageTableRef: any = ref(null);
+const route = useRoute();
const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect')];
const columns = ref([
- TableColumn.new('tagPath', '标签路径').isSlot().setAddWidth(20),
TableColumn.new('name', '名称'),
TableColumn.new('host', 'host:port'),
TableColumn.new('mode', 'mode'),
+ TableColumn.new('tagPath', '关联标签').isSlot().setAddWidth(10).alignCenter(),
TableColumn.new('remark', '备注'),
TableColumn.new('action', '操作').isSlot().setMinWidth(200).fixedRight().alignCenter(),
]);
@@ -189,7 +190,7 @@ const state = reactive({
total: 0,
selectionData: [],
query: {
- tagPath: null,
+ tagPath: '',
pageNum: 1,
pageSize: 0,
},
@@ -269,6 +270,11 @@ const onShowClusterInfo = async (redis: any) => {
const search = async () => {
try {
pageTableRef.value.loading(true);
+
+ if (route.query.tagPath) {
+ state.query.tagPath = route.query.tagPath as string;
+ }
+
const res = await redisApi.redisList.request(state.query);
state.redisTable = res.list;
state.total = res.total;
@@ -278,7 +284,7 @@ const search = async () => {
};
const getTags = async () => {
- state.tags = await redisApi.redisTags.request(null);
+ state.tags = await tagApi.getResourceTagPaths.request({ resourceType: TagResourceTypeEnum.Redis.value });
};
const editRedis = async (data: any) => {
diff --git a/mayfly_go_web/src/views/ops/tag/TagTreeList.vue b/mayfly_go_web/src/views/ops/tag/TagTreeList.vue
index 6700ee98..5e72bfa8 100644
--- a/mayfly_go_web/src/views/ops/tag/TagTreeList.vue
+++ b/mayfly_go_web/src/views/ops/tag/TagTreeList.vue
@@ -79,6 +79,24 @@
+
+
+
+
+ {{ EnumValue.getLabelByValue(TagResourceTypeEnum, scope.row.resourceType) }}
+
+
+
+
+
+
+
+ 查看
+
+
+
+
+
@@ -89,6 +107,9 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from './api';
import { dateFormat } from '@/common/utils/date';
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu/index';
+import { TagResourceTypeEnum } from '../../../common/commonEnum';
+import EnumValue from '@/common/Enum';
+import { useRouter } from 'vue-router';
interface Tree {
id: number;
@@ -97,6 +118,8 @@ interface Tree {
children?: Tree[];
}
+const router = useRouter();
+
const tagForm: any = ref(null);
const tagTreeRef: any = ref(null);
const filterTag = ref('');
@@ -123,6 +146,14 @@ const contextmenuDel = new ContextmenuItem('delete', '删除')
})
.withOnClick((data: any) => deleteTag(data));
+const contextmenuShowRelateResource = new ContextmenuItem('showRelateResources', '查看关联资源')
+ .withIcon('view')
+ .withHideFunc((data: any) => {
+ // 存在子标签,则不允许查看关联资源
+ return data.children;
+ })
+ .withOnClick((data: any) => showRelateResource(data));
+
const state = reactive({
data: [],
saveTabDialog: {
@@ -136,6 +167,12 @@ const state = reactive({
// 资源类型选择是否选
data: null as any,
},
+ resourceDialog: {
+ title: '',
+ visible: false,
+ tagPath: '',
+ data: null as any,
+ },
// 展开的节点
defaultExpandedKeys: [] as any,
contextmenu: {
@@ -143,11 +180,11 @@ const state = reactive({
x: 0,
y: 0,
},
- items: [contextmenuInfo, contextmenuEdit, contextmenuAdd, contextmenuDel],
+ items: [contextmenuInfo, contextmenuEdit, contextmenuAdd, contextmenuDel, contextmenuShowRelateResource],
},
});
-const { data, saveTabDialog, infoDialog, defaultExpandedKeys } = toRefs(state);
+const { data, saveTabDialog, infoDialog, resourceDialog, defaultExpandedKeys } = toRefs(state);
const props = {
label: 'name',
@@ -205,7 +242,7 @@ const info = async (data: any) => {
const showSaveTagDialog = (data: any) => {
if (data) {
state.saveTabDialog.form.pid = data.id;
- state.saveTabDialog.title = `新增 [${data.codePath}] 子标签信息`;
+ state.saveTabDialog.title = `新增[ ${data.codePath} ]子标签信息`;
} else {
state.saveTabDialog.title = '新增根标签信息';
}
@@ -221,6 +258,49 @@ const showEditTagDialog = (data: any) => {
state.saveTabDialog.visible = true;
};
+const showRelateResource = async (data: any) => {
+ const resourceMap = new Map();
+ state.resourceDialog.tagPath = data.codePath;
+ const tagResources = await tagApi.getTagResources.request({ tagId: data.id });
+ for (let tagResource of tagResources) {
+ const resourceType = tagResource.resourceType;
+ const exist = resourceMap.get(resourceType);
+ if (exist) {
+ exist.count = exist.count + 1;
+ } else {
+ resourceMap.set(resourceType, { resourceType, count: 1, tagPath: tagResource.tagPath });
+ }
+ }
+ state.resourceDialog.data = Array.from(resourceMap.values());
+ state.resourceDialog.visible = true;
+};
+
+const showResources = (resourceType: any, tagPath: string) => {
+ state.resourceDialog.visible = false;
+ setTimeout(() => {
+ let toPath = '';
+ if (resourceType == TagResourceTypeEnum.Machine.value) {
+ toPath = '/machine/machines';
+ }
+ if (resourceType == TagResourceTypeEnum.Db.value) {
+ toPath = '/dbms/dbs';
+ }
+ if (resourceType == TagResourceTypeEnum.Redis.value) {
+ toPath = '/redis/manage';
+ }
+ if (resourceType == TagResourceTypeEnum.Mongo.value) {
+ toPath = '/mongo/mongo-manage';
+ }
+
+ router.push({
+ path: toPath,
+ query: {
+ tagPath,
+ },
+ });
+ }, 350);
+};
+
const saveTag = async () => {
tagForm.value.validate(async (valid: any) => {
if (valid) {
diff --git a/mayfly_go_web/src/views/ops/tag/api.ts b/mayfly_go_web/src/views/ops/tag/api.ts
index 8fa550e9..1e229ef2 100644
--- a/mayfly_go_web/src/views/ops/tag/api.ts
+++ b/mayfly_go_web/src/views/ops/tag/api.ts
@@ -1,12 +1,14 @@
import Api from '@/common/Api';
export const tagApi = {
- getAccountTags: Api.newGet('/tag-trees/account-has'),
listByQuery: Api.newGet('/tag-trees/query'),
getTagTrees: Api.newGet('/tag-trees'),
saveTagTree: Api.newPost('/tag-trees'),
delTagTree: Api.newDelete('/tag-trees/{id}'),
+ getResourceTagPaths: Api.newGet('/tag-trees/resources/{resourceType}/tag-paths'),
+ getTagResources: Api.newGet('/tag-trees/resources'),
+
getTeams: Api.newGet('/teams'),
saveTeam: Api.newPost('/teams'),
delTeam: Api.newDelete('/teams/{id}'),
diff --git a/mayfly_go_web/yarn.lock b/mayfly_go_web/yarn.lock
index 23a4a67e..ad357175 100644
--- a/mayfly_go_web/yarn.lock
+++ b/mayfly_go_web/yarn.lock
@@ -7,10 +7,10 @@
resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
-"@babel/parser@^7.23.3":
- version "7.23.4"
- resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.4.tgz#409fbe690c333bb70187e2de4021e1e47a026661"
- integrity sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==
+"@babel/parser@^7.23.5":
+ version "7.23.5"
+ resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563"
+ integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==
"@babel/runtime@^7.21.0":
version "7.21.5"
@@ -440,6 +440,16 @@
resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz#8ae96573236cdb12de6850a6d929b5537ec85390"
integrity sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==
+"@vue/compiler-core@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.10.tgz#9ca4123a1458df43db641aaa8b7d1e636aa22545"
+ integrity sha512-doe0hODR1+i1menPkRzJ5MNR6G+9uiZHIknK3Zn5OcIztu6GGw7u0XUzf3AgB8h/dfsZC9eouzoLo3c3+N/cVA==
+ dependencies:
+ "@babel/parser" "^7.23.5"
+ "@vue/shared" "3.3.10"
+ estree-walker "^2.0.2"
+ source-map-js "^1.0.2"
+
"@vue/compiler-core@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128"
@@ -450,15 +460,13 @@
estree-walker "^2.0.2"
source-map-js "^1.0.2"
-"@vue/compiler-core@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.9.tgz#df1fc7947dcef5c2e12d257eae540057707f47d1"
- integrity sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==
+"@vue/compiler-dom@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.10.tgz#183811252be6aff4ac923f783124bb1590301907"
+ integrity sha512-NCrqF5fm10GXZIK0GrEAauBqdy+F2LZRt3yNHzrYjpYBuRssQbuPLtSnSNjyR9luHKkWSH8we5LMB3g+4z2HvA==
dependencies:
- "@babel/parser" "^7.23.3"
- "@vue/shared" "3.3.9"
- estree-walker "^2.0.2"
- source-map-js "^1.0.2"
+ "@vue/compiler-core" "3.3.10"
+ "@vue/shared" "3.3.10"
"@vue/compiler-dom@3.3.4":
version "3.3.4"
@@ -468,28 +476,20 @@
"@vue/compiler-core" "3.3.4"
"@vue/shared" "3.3.4"
-"@vue/compiler-dom@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.9.tgz#67315ea4193d9d18c7a710889b8f90f7aa3914d2"
- integrity sha512-nfWubTtLXuT4iBeDSZ5J3m218MjOy42Vp2pmKVuBKo2/BLcrFUX8nCSr/bKRFiJ32R8qbdnnnBgRn9AdU5v0Sg==
+"@vue/compiler-sfc@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.10.tgz#8eb97d42f276089ec58fd0565ef3a813bceeaa87"
+ integrity sha512-xpcTe7Rw7QefOTRFFTlcfzozccvjM40dT45JtrE3onGm/jBLZ0JhpKu3jkV7rbDFLeeagR/5RlJ2Y9SvyS0lAg==
dependencies:
- "@vue/compiler-core" "3.3.9"
- "@vue/shared" "3.3.9"
-
-"@vue/compiler-sfc@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.9.tgz#5900906baba1a90389200d81753ad0f7ceb98a83"
- integrity sha512-wy0CNc8z4ihoDzjASCOCsQuzW0A/HP27+0MDSSICMjVIFzk/rFViezkR3dzH+miS2NDEz8ywMdbjO5ylhOLI2A==
- dependencies:
- "@babel/parser" "^7.23.3"
- "@vue/compiler-core" "3.3.9"
- "@vue/compiler-dom" "3.3.9"
- "@vue/compiler-ssr" "3.3.9"
- "@vue/reactivity-transform" "3.3.9"
- "@vue/shared" "3.3.9"
+ "@babel/parser" "^7.23.5"
+ "@vue/compiler-core" "3.3.10"
+ "@vue/compiler-dom" "3.3.10"
+ "@vue/compiler-ssr" "3.3.10"
+ "@vue/reactivity-transform" "3.3.10"
+ "@vue/shared" "3.3.10"
estree-walker "^2.0.2"
magic-string "^0.30.5"
- postcss "^8.4.31"
+ postcss "^8.4.32"
source-map-js "^1.0.2"
"@vue/compiler-sfc@^3.3.4":
@@ -508,6 +508,14 @@
postcss "^8.1.10"
source-map-js "^1.0.2"
+"@vue/compiler-ssr@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.10.tgz#5a1b14a358cb3960a4edbce0ade90548e452fcaa"
+ integrity sha512-12iM4jA4GEbskwXMmPcskK5wImc2ohKm408+o9iox3tfN9qua8xL0THIZtoe9OJHnXP4eOWZpgCAAThEveNlqQ==
+ dependencies:
+ "@vue/compiler-dom" "3.3.10"
+ "@vue/shared" "3.3.10"
+
"@vue/compiler-ssr@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777"
@@ -516,19 +524,22 @@
"@vue/compiler-dom" "3.3.4"
"@vue/shared" "3.3.4"
-"@vue/compiler-ssr@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.9.tgz#3b3dbfa5368165fa4ff74c060503b4087ec1beed"
- integrity sha512-NO5oobAw78R0G4SODY5A502MGnDNiDjf6qvhn7zD7TJGc8XDeIEw4fg6JU705jZ/YhuokBKz0A5a/FL/XZU73g==
- dependencies:
- "@vue/compiler-dom" "3.3.9"
- "@vue/shared" "3.3.9"
-
"@vue/devtools-api@^6.5.0":
version "6.5.0"
resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
+"@vue/reactivity-transform@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.10.tgz#b045776cc954bb57883fd305db7a200d42993768"
+ integrity sha512-0xBdk+CKHWT+Gev8oZ63Tc0qFfj935YZx+UAynlutnrDZ4diFCVFMWixn65HzjE3S1iJppWOo6Tt1OzASH7VEg==
+ dependencies:
+ "@babel/parser" "^7.23.5"
+ "@vue/compiler-core" "3.3.10"
+ "@vue/shared" "3.3.10"
+ estree-walker "^2.0.2"
+ magic-string "^0.30.5"
+
"@vue/reactivity-transform@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929"
@@ -540,59 +551,48 @@
estree-walker "^2.0.2"
magic-string "^0.30.0"
-"@vue/reactivity-transform@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.9.tgz#5d894dd9a42a422a2db309babb385f9a2529b52f"
- integrity sha512-HnUFm7Ry6dFa4Lp63DAxTixUp8opMtQr6RxQCpDI1vlh12rkGIeYqMvJtK+IKyEfEOa2I9oCkD1mmsPdaGpdVg==
+"@vue/reactivity@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.10.tgz#78fe3da319276d9e6d0f072037532928c472a287"
+ integrity sha512-H5Z7rOY/JLO+e5a6/FEXaQ1TMuOvY4LDVgT+/+HKubEAgs9qeeZ+NhADSeEtrNQeiKLDuzeKc8v0CUFpB6Pqgw==
dependencies:
- "@babel/parser" "^7.23.3"
- "@vue/compiler-core" "3.3.9"
- "@vue/shared" "3.3.9"
- estree-walker "^2.0.2"
- magic-string "^0.30.5"
+ "@vue/shared" "3.3.10"
-"@vue/reactivity@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.9.tgz#e28e8071bd74edcdd9c87b667ad00e8fbd8d6920"
- integrity sha512-VmpIqlNp+aYDg2X0xQhJqHx9YguOmz2UxuUJDckBdQCNkipJvfk9yA75woLWElCa0Jtyec3lAAt49GO0izsphw==
+"@vue/runtime-core@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.10.tgz#d7b78c5c0500b856cf9447ef81d4a1b1438fd5bb"
+ integrity sha512-DZ0v31oTN4YHX9JEU5VW1LoIVgFovWgIVb30bWn9DG9a7oA415idcwsRNNajqTx8HQJyOaWfRKoyuP2P2TYIag==
dependencies:
- "@vue/shared" "3.3.9"
+ "@vue/reactivity" "3.3.10"
+ "@vue/shared" "3.3.10"
-"@vue/runtime-core@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.9.tgz#c835b77f7dc7ae5f251e93f277b54963ea1b5c31"
- integrity sha512-xxaG9KvPm3GTRuM4ZyU8Tc+pMVzcu6eeoSRQJ9IE7NmCcClW6z4B3Ij6L4EDl80sxe/arTtQ6YmgiO4UZqRc+w==
+"@vue/runtime-dom@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.10.tgz#130dfffb8fee8051671aaf80c5104d2020544950"
+ integrity sha512-c/jKb3ny05KJcYk0j1m7Wbhrxq7mZYr06GhKykDMNRRR9S+/dGT8KpHuNQjv3/8U4JshfkAk6TpecPD3B21Ijw==
dependencies:
- "@vue/reactivity" "3.3.9"
- "@vue/shared" "3.3.9"
-
-"@vue/runtime-dom@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.9.tgz#68081d981695a229d72f431fed0b0cdd9161ce53"
- integrity sha512-e7LIfcxYSWbV6BK1wQv9qJyxprC75EvSqF/kQKe6bdZEDNValzeRXEVgiX7AHI6hZ59HA4h7WT5CGvm69vzJTQ==
- dependencies:
- "@vue/runtime-core" "3.3.9"
- "@vue/shared" "3.3.9"
+ "@vue/runtime-core" "3.3.10"
+ "@vue/shared" "3.3.10"
csstype "^3.1.2"
-"@vue/server-renderer@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.9.tgz#ffb41bc9c7afafcc608d0c500e9d6b0af7d68fad"
- integrity sha512-w0zT/s5l3Oa3ZjtLW88eO4uV6AQFqU8X5GOgzq7SkQQu6vVr+8tfm+OI2kDBplS/W/XgCBuFXiPw6T5EdwXP0A==
+"@vue/server-renderer@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.10.tgz#f23d151f0e5021ebdc730052d9934c9178486742"
+ integrity sha512-0i6ww3sBV3SKlF3YTjSVqKQ74xialMbjVYGy7cOTi7Imd8ediE7t72SK3qnvhrTAhOvlQhq6Bk6nFPdXxe0sAg==
dependencies:
- "@vue/compiler-ssr" "3.3.9"
- "@vue/shared" "3.3.9"
+ "@vue/compiler-ssr" "3.3.10"
+ "@vue/shared" "3.3.10"
+
+"@vue/shared@3.3.10":
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.10.tgz#1583a8d85a957d8b819078c465d2a11db7914b2f"
+ integrity sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==
"@vue/shared@3.3.4":
version "3.3.4"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz#06e83c5027f464eef861c329be81454bc8b70780"
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
-"@vue/shared@3.3.9":
- version "3.3.9"
- resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.9.tgz#df740d26d338faf03e09ca662a8031acf66051db"
- integrity sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==
-
"@vueuse/core@^9.1.0":
version "9.2.0"
resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.2.0.tgz"
@@ -1518,10 +1518,10 @@ nanoid@^3.1.30:
resolved "https://registry.npmmirror.com/nanoid/download/nanoid-3.1.30.tgz"
integrity sha1-Y/k8xUjSoRPcXfvGO/oJ4rm2Q2I=
-nanoid@^3.3.6:
- version "3.3.6"
- resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
- integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+nanoid@^3.3.7:
+ version "3.3.7"
+ resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
natural-compare@^1.4.0:
version "1.4.0"
@@ -1660,12 +1660,12 @@ postcss@^8.1.10:
picocolors "^1.0.0"
source-map-js "^1.0.1"
-postcss@^8.4.31:
- version "8.4.31"
- resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
- integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
+postcss@^8.4.32:
+ version "8.4.32"
+ resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9"
+ integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==
dependencies:
- nanoid "^3.3.6"
+ nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.0.2"
@@ -1938,13 +1938,13 @@ uuid@^9.0.1:
resolved "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
-vite@^5.0.3:
- version "5.0.3"
- resolved "https://registry.npmmirror.com/vite/-/vite-5.0.3.tgz#febf6801604c618234de331bd04382cf9a149ec6"
- integrity sha512-WgEq8WEKpZ8c0DL4M1+E+kBZEJyjBmGVrul6z8Ljfhv+PPbNF4aGq014DwNYxGz2FGq6NKL0N8usdiESWd2l2w==
+vite@^5.0.5:
+ version "5.0.5"
+ resolved "https://registry.npmmirror.com/vite/-/vite-5.0.5.tgz#3eebe3698e3b32cea36350f58879258fec858a3c"
+ integrity sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==
dependencies:
esbuild "^0.19.3"
- postcss "^8.4.31"
+ postcss "^8.4.32"
rollup "^4.2.0"
optionalDependencies:
fsevents "~2.3.3"
@@ -1979,16 +1979,16 @@ vue-router@^4.2.5:
dependencies:
"@vue/devtools-api" "^6.5.0"
-vue@^3.3.9:
- version "3.3.9"
- resolved "https://registry.npmmirror.com/vue/-/vue-3.3.9.tgz#219a2ec68e8d4d0b0180460af0f5b9299b3f3f1f"
- integrity sha512-sy5sLCTR8m6tvUk1/ijri3Yqzgpdsmxgj6n6yl7GXXCXqVbmW2RCXe9atE4cEI6Iv7L89v5f35fZRRr5dChP9w==
+vue@^3.3.10:
+ version "3.3.10"
+ resolved "https://registry.npmmirror.com/vue/-/vue-3.3.10.tgz#6e19c1982ee655a14babe1610288b90005f02ab1"
+ integrity sha512-zg6SIXZdTBwiqCw/1p+m04VyHjLfwtjwz8N57sPaBhEex31ND0RYECVOC1YrRwMRmxFf5T1dabl6SGUbMKKuVw==
dependencies:
- "@vue/compiler-dom" "3.3.9"
- "@vue/compiler-sfc" "3.3.9"
- "@vue/runtime-dom" "3.3.9"
- "@vue/server-renderer" "3.3.9"
- "@vue/shared" "3.3.9"
+ "@vue/compiler-dom" "3.3.10"
+ "@vue/compiler-sfc" "3.3.10"
+ "@vue/runtime-dom" "3.3.10"
+ "@vue/server-renderer" "3.3.10"
+ "@vue/shared" "3.3.10"
which@^2.0.1:
version "2.0.2"
diff --git a/server/internal/common/api/index.go b/server/internal/common/api/index.go
index 4f9ce1e0..7b245801 100644
--- a/server/internal/common/api/index.go
+++ b/server/internal/common/api/index.go
@@ -1,14 +1,11 @@
package api
import (
+ "mayfly-go/internal/common/consts"
dbapp "mayfly-go/internal/db/application"
- dbentity "mayfly-go/internal/db/domain/entity"
machineapp "mayfly-go/internal/machine/application"
- machineentity "mayfly-go/internal/machine/domain/entity"
mongoapp "mayfly-go/internal/mongo/application"
- mongoentity "mayfly-go/internal/mongo/domain/entity"
redisapp "mayfly-go/internal/redis/application"
- redisentity "mayfly-go/internal/redis/domain/entity"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
@@ -24,19 +21,12 @@ type Index struct {
func (i *Index) Count(rc *req.Ctx) {
accountId := rc.GetLoginAccount().Id
- tagIds := i.TagApp.ListTagIdByAccountId(accountId)
- var mongoNum int64
- var redisNum int64
- var dbNum int64
- var machienNum int64
+ mongoNum := len(i.TagApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMongo, ""))
+ machienNum := len(i.TagApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMachine, ""))
+ dbNum := len(i.TagApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeDb, ""))
+ redisNum := len(i.TagApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeRedis, ""))
- if len(tagIds) > 0 {
- mongoNum = i.MongoApp.Count(&mongoentity.MongoQuery{TagIds: tagIds})
- machienNum = i.MachineApp.Count(&machineentity.MachineQuery{TagIds: tagIds})
- dbNum = i.DbApp.Count(&dbentity.DbQuery{TagIds: tagIds})
- redisNum = i.RedisApp.Count(&redisentity.RedisQuery{TagIds: tagIds})
- }
rc.ResData = collx.M{
"mongoNum": mongoNum,
"machineNum": machienNum,
diff --git a/server/internal/common/consts/consts.go b/server/internal/common/consts/consts.go
index a3521dd8..28fba258 100644
--- a/server/internal/common/consts/consts.go
+++ b/server/internal/common/consts/consts.go
@@ -10,9 +10,14 @@ const (
RedisConnExpireTime = 30 * time.Minute
MongoConnExpireTime = 30 * time.Minute
-/**** 开发测试使用 ****/
-// MachineConnExpireTime = 4 * time.Minute
-// DbConnExpireTime = 2 * time.Minute
-// RedisConnExpireTime = 2 * time.Minute
-// MongoConnExpireTime = 2 * time.Minute
+ /**** 开发测试使用 ****/
+ // MachineConnExpireTime = 4 * time.Minute
+ // DbConnExpireTime = 2 * time.Minute
+ // RedisConnExpireTime = 2 * time.Minute
+ // MongoConnExpireTime = 2 * time.Minute
+
+ TagResourceTypeMachine = 1
+ TagResourceTypeDb = 2
+ TagResourceTypeRedis = 3
+ TagResourceTypeMongo = 4
)
diff --git a/server/internal/db/api/db.go b/server/internal/db/api/db.go
index d951b10f..a12546ae 100644
--- a/server/internal/db/api/db.go
+++ b/server/internal/db/api/db.go
@@ -3,6 +3,7 @@ package api
import (
"fmt"
"io"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/db/api/form"
"mayfly-go/internal/db/api/vo"
"mayfly-go/internal/db/application"
@@ -41,29 +42,25 @@ func (d *Db) Dbs(rc *req.Ctx) {
queryCond, page := ginx.BindQueryAndPage[*entity.DbQuery](rc.GinCtx, new(entity.DbQuery))
// 不存在可访问标签id,即没有可操作数据
- tagIds := d.TagApp.ListTagIdByAccountId(rc.GetLoginAccount().Id)
- if len(tagIds) == 0 {
+ codes := d.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeDb, queryCond.TagPath)
+ if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
+ queryCond.Codes = codes
- queryCond.TagIds = tagIds
res, err := d.DbApp.GetPageList(queryCond, page, new([]vo.DbListVO))
biz.ErrIsNil(err)
rc.ResData = res
}
-func (d *Db) DbTags(rc *req.Ctx) {
- rc.ResData = d.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Db))
-}
-
func (d *Db) Save(rc *req.Ctx) {
form := &form.DbForm{}
db := ginx.BindJsonAndCopyTo[*entity.Db](rc.GinCtx, form, new(entity.Db))
rc.ReqParam = form
- biz.ErrIsNil(d.DbApp.Save(rc.MetaCtx, db))
+ biz.ErrIsNil(d.DbApp.Save(rc.MetaCtx, db, form.TagId...))
}
func (d *Db) DeleteDb(rc *req.Ctx) {
@@ -90,7 +87,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
dbId := getDbId(g)
dbConn, err := d.DbApp.GetDbConn(dbId, form.Db)
biz.ErrIsNil(err)
- biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath...), "%s")
rc.ReqParam = fmt.Sprintf("%s\n-> %s", dbConn.Info.GetLogDesc(), form.Sql)
biz.NotEmpty(form.Sql, "sql不能为空")
@@ -161,7 +158,7 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
dbConn, err := d.DbApp.GetDbConn(dbId, dbName)
biz.ErrIsNil(err)
- biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath...), "%s")
rc.ReqParam = fmt.Sprintf("filename: %s -> %s", filename, dbConn.Info.GetLogDesc())
defer func() {
@@ -230,7 +227,7 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
}
dbConn, err = d.DbApp.GetDbConn(dbId, stmtUse.DBName.String())
biz.ErrIsNil(err)
- biz.ErrIsNilAppendErr(d.TagApp.CanAccess(laId, dbConn.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(d.TagApp.CanAccess(laId, dbConn.Info.TagPath...), "%s")
execReq.DbConn = dbConn
}
// 需要记录执行记录
@@ -270,7 +267,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
la := rc.GetLoginAccount()
db, err := d.DbApp.GetById(new(entity.Db), dbId)
biz.ErrIsNil(err, "该数据库不存在")
- biz.ErrIsNilAppendErr(d.TagApp.CanAccess(la.Id, db.TagPath), "%s")
+ biz.ErrIsNilAppendErr(d.TagApp.CanAccess(la.Id, d.TagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...), "%s")
now := time.Now()
filename := fmt.Sprintf("%s.%s.sql%s", db.Name, now.Format("20060102150405"), extName)
diff --git a/server/internal/db/api/form/db.go b/server/internal/db/api/form/db.go
index 081b251e..c679ca0f 100644
--- a/server/internal/db/api/form/db.go
+++ b/server/internal/db/api/form/db.go
@@ -1,13 +1,12 @@
package form
type DbForm struct {
- Id uint64 `json:"id"`
- Name string `binding:"required" json:"name"`
- Database string `json:"database"`
- Remark string `json:"remark"`
- TagId uint64 `binding:"required" json:"tagId"`
- TagPath string `binding:"required" json:"tagPath"`
- InstanceId uint64 `binding:"required" json:"instanceId"`
+ Id uint64 `json:"id"`
+ Name string `binding:"required" json:"name"`
+ Database string `json:"database"`
+ Remark string `json:"remark"`
+ TagId []uint64 `binding:"required" json:"tagId"`
+ InstanceId uint64 `binding:"required" json:"instanceId"`
}
type DbSqlSaveForm struct {
diff --git a/server/internal/db/api/vo/db.go b/server/internal/db/api/vo/db.go
index 4827d8b6..ac02688d 100644
--- a/server/internal/db/api/vo/db.go
+++ b/server/internal/db/api/vo/db.go
@@ -4,11 +4,10 @@ import "time"
type DbListVO struct {
Id *int64 `json:"id"`
+ Code string `json:"code"`
Name *string `json:"name"`
Database *string `json:"database"`
Remark *string `json:"remark"`
- TagId *int64 `json:"tagId"`
- TagPath *string `json:"tagPath"`
InstanceId *int64 `json:"instanceId"`
InstanceName *string `json:"instanceName"`
diff --git a/server/internal/db/application/application.go b/server/internal/db/application/application.go
index 578c7e65..8c45aeb7 100644
--- a/server/internal/db/application/application.go
+++ b/server/internal/db/application/application.go
@@ -2,11 +2,12 @@ package application
import (
"mayfly-go/internal/db/infrastructure/persistence"
+ tagapp "mayfly-go/internal/tag/application"
)
var (
instanceApp Instance = newInstanceApp(persistence.GetInstanceRepo())
- dbApp Db = newDbApp(persistence.GetDbRepo(), persistence.GetDbSqlRepo(), instanceApp)
+ dbApp Db = newDbApp(persistence.GetDbRepo(), persistence.GetDbSqlRepo(), instanceApp, tagapp.GetTagTreeApp())
dbSqlExecApp DbSqlExec = newDbSqlExecApp(persistence.GetDbSqlExecRepo())
dbSqlApp DbSql = newDbSqlApp(persistence.GetDbSqlRepo())
)
diff --git a/server/internal/db/application/db.go b/server/internal/db/application/db.go
index 8dd1f2d3..cd478a44 100644
--- a/server/internal/db/application/db.go
+++ b/server/internal/db/application/db.go
@@ -2,13 +2,16 @@ package application
import (
"context"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/db/dbm"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
+ tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
+ "mayfly-go/pkg/utils/stringx"
"mayfly-go/pkg/utils/structx"
"strings"
)
@@ -21,7 +24,7 @@ type Db interface {
Count(condition *entity.DbQuery) int64
- Save(ctx context.Context, entity *entity.Db) error
+ Save(ctx context.Context, entity *entity.Db, tagIds ...uint64) error
// 删除数据库信息
Delete(ctx context.Context, id uint64) error
@@ -32,10 +35,11 @@ type Db interface {
GetDbConn(dbId uint64, dbName string) (*dbm.DbConn, error)
}
-func newDbApp(dbRepo repository.Db, dbSqlRepo repository.DbSql, dbInstanceApp Instance) Db {
+func newDbApp(dbRepo repository.Db, dbSqlRepo repository.DbSql, dbInstanceApp Instance, tagApp tagapp.TagTree) Db {
app := &dbAppImpl{
dbSqlRepo: dbSqlRepo,
dbInstanceApp: dbInstanceApp,
+ tagApp: tagApp,
}
app.Repo = dbRepo
return app
@@ -46,6 +50,7 @@ type dbAppImpl struct {
dbSqlRepo repository.DbSql
dbInstanceApp Instance
+ tagApp tagapp.TagTree
}
// 分页获取数据库信息列表
@@ -57,7 +62,7 @@ func (d *dbAppImpl) Count(condition *entity.DbQuery) int64 {
return d.GetRepo().Count(condition)
}
-func (d *dbAppImpl) Save(ctx context.Context, dbEntity *entity.Db) error {
+func (d *dbAppImpl) Save(ctx context.Context, dbEntity *entity.Db, tagIds ...uint64) error {
// 查找是否存在
oldDb := &entity.Db{Name: dbEntity.Name, InstanceId: dbEntity.InstanceId}
err := d.GetBy(oldDb)
@@ -66,7 +71,15 @@ func (d *dbAppImpl) Save(ctx context.Context, dbEntity *entity.Db) error {
if err == nil {
return errorx.NewBiz("该实例下数据库名已存在")
}
- return d.Insert(ctx, dbEntity)
+
+ resouceCode := stringx.Rand(16)
+ dbEntity.Code = resouceCode
+
+ return d.Tx(ctx, func(ctx context.Context) error {
+ return d.Insert(ctx, dbEntity)
+ }, func(ctx context.Context) error {
+ return d.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeDb, tagIds)
+ })
}
// 如果存在该库,则校验修改的库是否为该库
@@ -94,7 +107,11 @@ func (d *dbAppImpl) Save(ctx context.Context, dbEntity *entity.Db) error {
d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: dbId, Db: v})
}
- return d.UpdateById(ctx, dbEntity)
+ return d.Tx(ctx, func(ctx context.Context) error {
+ return d.UpdateById(ctx, dbEntity)
+ }, func(ctx context.Context) error {
+ return d.tagApp.RelateResource(ctx, oldDb.Code, consts.TagResourceTypeDb, tagIds)
+ })
}
func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
@@ -138,11 +155,11 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbm.DbConn, error) {
// 密码解密
instance.PwdDecrypt()
- return toDbInfo(instance, dbId, dbName, db.TagPath), nil
+ return toDbInfo(instance, dbId, dbName, d.tagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...), nil
})
}
-func toDbInfo(instance *entity.DbInstance, dbId uint64, database string, tagPath string) *dbm.DbInfo {
+func toDbInfo(instance *entity.DbInstance, dbId uint64, database string, tagPath ...string) *dbm.DbInfo {
di := new(dbm.DbInfo)
di.Id = dbId
di.Database = database
diff --git a/server/internal/db/application/instance.go b/server/internal/db/application/instance.go
index 8082c517..22253ea1 100644
--- a/server/internal/db/application/instance.go
+++ b/server/internal/db/application/instance.go
@@ -48,6 +48,7 @@ func (app *instanceAppImpl) Count(condition *entity.InstanceQuery) int64 {
}
func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance) error {
+ instanceEntity.Network = instanceEntity.GetNetwork()
dbConn, err := toDbInfo(instanceEntity, 0, "", "").Conn()
if err != nil {
return err
diff --git a/server/internal/db/dbm/info.go b/server/internal/db/dbm/info.go
index 95219d25..ef0c6ef4 100644
--- a/server/internal/db/dbm/info.go
+++ b/server/internal/db/dbm/info.go
@@ -20,7 +20,7 @@ type DbInfo struct {
Params string
Database string
- TagPath string
+ TagPath []string
SshTunnelMachineId int
}
diff --git a/server/internal/db/domain/entity/db.go b/server/internal/db/domain/entity/db.go
index dc80022a..ef895142 100644
--- a/server/internal/db/domain/entity/db.go
+++ b/server/internal/db/domain/entity/db.go
@@ -7,10 +7,9 @@ import (
type Db struct {
model.Model
+ Code string `orm:"column(code)" json:"code"`
Name string `orm:"column(name)" json:"name"`
Database string `orm:"column(database)" json:"database"`
Remark string `json:"remark"`
- TagId uint64
- TagPath string
InstanceId uint64
}
diff --git a/server/internal/db/domain/entity/query.go b/server/internal/db/domain/entity/query.go
index d1a4fa7a..ebc1347b 100644
--- a/server/internal/db/domain/entity/query.go
+++ b/server/internal/db/domain/entity/query.go
@@ -17,6 +17,7 @@ type DbQuery struct {
Database string `orm:"column(database)" json:"database"`
Remark string `json:"remark"`
+ Codes []string
TagIds []uint64 `orm:"column(tag_id)"`
TagPath string `form:"tagPath"`
diff --git a/server/internal/db/infrastructure/persistence/db.go b/server/internal/db/infrastructure/persistence/db.go
index b14ea7d6..5d39fdc0 100644
--- a/server/internal/db/infrastructure/persistence/db.go
+++ b/server/internal/db/infrastructure/persistence/db.go
@@ -23,11 +23,9 @@ func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageP
Joins("JOIN t_db_instance inst ON db.instance_id = inst.id").
Eq("db.instance_id", condition.InstanceId).
Like("db.database", condition.Database).
- In("db.tag_id", condition.TagIds).
- RLike("db.tag_path", condition.TagPath).
+ In("db.code", condition.Codes).
Eq0("db."+model.DeletedColumn, model.ModelUndeleted).
- Eq0("inst."+model.DeletedColumn, model.ModelUndeleted).
- OrderByAsc("db.tag_path")
+ Eq0("inst."+model.DeletedColumn, model.ModelUndeleted)
return gormx.PageQuery(qd, pageParam, toEntity)
}
diff --git a/server/internal/db/router/db.go b/server/internal/db/router/db.go
index a5928c95..989c154c 100644
--- a/server/internal/db/router/db.go
+++ b/server/internal/db/router/db.go
@@ -25,8 +25,6 @@ func InitDbRouter(router *gin.RouterGroup) {
// 获取数据库列表
req.NewGet("", d.Dbs),
- req.NewGet("/tags", d.DbTags),
-
req.NewPost("", d.Save).Log(req.NewLogSave("db-保存数据库信息")),
req.NewDelete(":dbId", d.DeleteDb).Log(req.NewLogSave("db-删除数据库信息")),
diff --git a/server/internal/machine/api/form/form.go b/server/internal/machine/api/form/form.go
index 3021c3c9..0293d526 100644
--- a/server/internal/machine/api/form/form.go
+++ b/server/internal/machine/api/form/form.go
@@ -7,11 +7,10 @@ type MachineForm struct {
Port int `json:"port" binding:"required"` // 端口号
// 资产授权凭证信息列表
- AuthCertId int `json:"authCertId"`
- TagId uint64 `json:"tagId" binding:"required"`
- TagPath string `json:"tagPath" binding:"required"`
- Username string `json:"username"`
- Password string `json:"password"`
+ AuthCertId int `json:"authCertId"`
+ TagId []uint64 `json:"tagId" binding:"required"`
+ Username string `json:"username"`
+ Password string `json:"password"`
Remark string `json:"remark"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
diff --git a/server/internal/machine/api/machine.go b/server/internal/machine/api/machine.go
index 57e26944..f53721e6 100644
--- a/server/internal/machine/api/machine.go
+++ b/server/internal/machine/api/machine.go
@@ -3,6 +3,7 @@ package api
import (
"encoding/base64"
"fmt"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application"
@@ -17,34 +18,32 @@ import (
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
- "mayfly-go/pkg/utils/stringx"
"mayfly-go/pkg/ws"
"os"
"path"
- "sort"
"strconv"
"strings"
- "time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
type Machine struct {
- MachineApp application.Machine
- TagApp tagapp.TagTree
+ MachineApp application.Machine
+ MachineTermOpApp application.MachineTermOp
+ TagApp tagapp.TagTree
}
func (m *Machine) Machines(rc *req.Ctx) {
condition, pageParam := ginx.BindQueryAndPage(rc.GinCtx, new(entity.MachineQuery))
// 不存在可访问标签id,即没有可操作数据
- tagIds := m.TagApp.ListTagIdByAccountId(rc.GetLoginAccount().Id)
- if len(tagIds) == 0 {
+ codes := m.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeMachine, condition.TagPath)
+ if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
- condition.TagIds = tagIds
+ condition.Codes = codes
res, err := m.MachineApp.GetMachineList(condition, pageParam, new([]*vo.MachineVO))
biz.ErrIsNil(err)
@@ -67,10 +66,6 @@ func (m *Machine) Machines(rc *req.Ctx) {
rc.ResData = res
}
-func (m *Machine) MachineTags(rc *req.Ctx) {
- rc.ResData = m.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Machine))
-}
-
func (m *Machine) MachineStats(rc *req.Ctx) {
cli, err := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
@@ -85,7 +80,7 @@ func (m *Machine) SaveMachine(rc *req.Ctx) {
machineForm.Password = "******"
rc.ReqParam = machineForm
- biz.ErrIsNil(m.MachineApp.Save(rc.MetaCtx, me))
+ biz.ErrIsNil(m.MachineApp.Save(rc.MetaCtx, me, machineForm.TagId...))
}
func (m *Machine) TestConn(rc *req.Ctx) {
@@ -140,7 +135,7 @@ func (m *Machine) GetProcess(rc *req.Ctx) {
cli, err := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
- biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
res, err := cli.Run(cmd)
biz.ErrIsNilAppendErr(err, "获取进程信息失败: %s")
@@ -154,7 +149,7 @@ func (m *Machine) KillProcess(rc *req.Ctx) {
cli, err := m.MachineApp.GetCli(GetMachineId(rc.GinCtx))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
- biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
res, err := cli.Run("sudo kill -9 " + pid)
biz.ErrIsNil(err, "终止进程失败: %s", res)
@@ -180,59 +175,35 @@ func (m *Machine) WsSSH(g *gin.Context) {
cli, err := m.MachineApp.GetCli(GetMachineId(g))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
- biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
cols := ginx.QueryInt(g, "cols", 80)
rows := ginx.QueryInt(g, "rows", 40)
- var recorder *mcm.Recorder
- if cli.Info.EnableRecorder == 1 {
- now := time.Now()
- // 回放文件路径为: 基础配置路径/机器id/操作日期/操作者账号/操作时间.cast
- recPath := fmt.Sprintf("%s/%d/%s/%s", config.GetMachine().TerminalRecPath, cli.Info.Id, now.Format("20060102"), rc.GetLoginAccount().Username)
- os.MkdirAll(recPath, 0766)
- fileName := path.Join(recPath, fmt.Sprintf("%s.cast", now.Format("20060102_150405")))
- f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
- biz.ErrIsNilAppendErr(err, "创建终端回放记录文件失败: %s")
- defer f.Close()
- recorder = mcm.NewRecorder(f)
- }
-
- mts, err := mcm.NewTerminalSession(stringx.Rand(16), wsConn, cli, rows, cols, recorder)
- biz.ErrIsNilAppendErr(err, "\033[1;31m连接失败: %s\033[0m")
-
// 记录系统操作日志
rc.WithLog(req.NewLogSave("机器-终端操作"))
rc.ReqParam = cli.Info
req.LogHandler(rc)
- mts.Start()
- defer mts.Stop()
+ err = m.MachineTermOpApp.TermConn(rc.MetaCtx, cli, wsConn, rows, cols)
+ biz.ErrIsNilAppendErr(err, "\033[1;31m连接失败: %s\033[0m")
}
-// 获取机器终端回放记录的相应文件夹名或文件内容
-func (m *Machine) MachineRecDirNames(rc *req.Ctx) {
- readPath := rc.GinCtx.Query("path")
- biz.NotEmpty(readPath, "path不能为空")
- path_ := path.Join(config.GetMachine().TerminalRecPath, readPath)
+func (m *Machine) MachineTermOpRecords(rc *req.Ctx) {
+ mid := GetMachineId(rc.GinCtx)
+ res, err := m.MachineTermOpApp.GetPageList(&entity.MachineTermOp{MachineId: mid}, ginx.GetPageParam(rc.GinCtx), new([]entity.MachineTermOp))
+ biz.ErrIsNil(err)
+ rc.ResData = res
+}
- // 如果是读取文件内容,则读取对应回放记录文件内容,否则读取文件夹名列表。小小偷懒一会不想再加个接口
- isFile := rc.GinCtx.Query("isFile")
- if isFile == "1" {
- bytes, err := os.ReadFile(path_)
- biz.ErrIsNilAppendErr(err, "还未有相应终端操作记录: %s")
- rc.ResData = base64.StdEncoding.EncodeToString(bytes)
- return
- }
+func (m *Machine) MachineTermOpRecord(rc *req.Ctx) {
+ recId, _ := strconv.Atoi(rc.GinCtx.Param("recId"))
+ termOp, err := m.MachineTermOpApp.GetById(new(entity.MachineTermOp), uint64(recId))
+ biz.ErrIsNil(err)
- files, err := os.ReadDir(path_)
- biz.ErrIsNilAppendErr(err, "还未有相应终端操作记录: %s")
- var names []string
- for _, f := range files {
- names = append(names, f.Name())
- }
- sort.Sort(sort.Reverse(sort.StringSlice(names)))
- rc.ResData = names
+ bytes, err := os.ReadFile(path.Join(config.GetMachine().TerminalRecPath, termOp.RecordFilePath))
+ biz.ErrIsNilAppendErr(err, "读取终端操作记录失败: %s")
+ rc.ResData = base64.StdEncoding.EncodeToString(bytes)
}
func GetMachineId(g *gin.Context) uint64 {
diff --git a/server/internal/machine/api/machine_cronjob.go b/server/internal/machine/api/machine_cronjob.go
index 90c1af8a..a9080c13 100644
--- a/server/internal/machine/api/machine_cronjob.go
+++ b/server/internal/machine/api/machine_cronjob.go
@@ -63,6 +63,12 @@ func (m *MachineCronJob) GetRelateCronJobIds(rc *req.Ctx) {
rc.ResData = m.MachineCronJobApp.GetRelateMachineIds(uint64(ginx.QueryInt(rc.GinCtx, "machineId", -1)))
}
+func (m *MachineCronJob) RunCronJob(rc *req.Ctx) {
+ cronJobKey := ginx.PathParam(rc.GinCtx, "key")
+ biz.NotEmpty(cronJobKey, "cronJob key不能为空")
+ m.MachineCronJobApp.RunCronJob(cronJobKey)
+}
+
func (m *MachineCronJob) CronJobExecs(rc *req.Ctx) {
cond, pageParam := ginx.BindQueryAndPage[*entity.MachineCronJobExec](rc.GinCtx, new(entity.MachineCronJobExec))
res, err := m.MachineCronJobApp.GetExecPageList(cond, pageParam, new([]entity.MachineCronJobExec))
diff --git a/server/internal/machine/api/machine_script.go b/server/internal/machine/api/machine_script.go
index 78b17255..3733ec08 100644
--- a/server/internal/machine/api/machine_script.go
+++ b/server/internal/machine/api/machine_script.go
@@ -69,7 +69,7 @@ func (m *MachineScript) RunMachineScript(rc *req.Ctx) {
}
cli, err := m.MachineApp.GetCli(machineId)
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
- biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.TagPath...), "%s")
res, err := cli.Run(script)
// 记录请求参数
diff --git a/server/internal/machine/api/vo/vo.go b/server/internal/machine/api/vo/vo.go
index bc00a651..992bccb3 100644
--- a/server/internal/machine/api/vo/vo.go
+++ b/server/internal/machine/api/vo/vo.go
@@ -13,6 +13,7 @@ type AuthCertBaseVO struct {
type MachineVO struct {
Id uint64 `json:"id"`
+ Code string `json:"code"`
Name string `json:"name"`
Ip string `json:"ip"`
Port int `json:"port"`
@@ -28,8 +29,8 @@ type MachineVO struct {
ModifierId *int64 `json:"modifierId"`
Remark *string `json:"remark"`
EnableRecorder int8 `json:"enableRecorder"`
- TagId uint64 `json:"tagId"`
- TagPath string `json:"tagPath"`
+ // TagId uint64 `json:"tagId"`
+ // TagPath string `json:"tagPath"`
HasCli bool `json:"hasCli" gorm:"-"`
Stat map[string]any `json:"stat" gorm:"-"`
diff --git a/server/internal/machine/application/application.go b/server/internal/machine/application/application.go
index 62311a65..c66efcf1 100644
--- a/server/internal/machine/application/application.go
+++ b/server/internal/machine/application/application.go
@@ -2,12 +2,14 @@ package application
import (
"mayfly-go/internal/machine/infrastructure/persistence"
+ tagapp "mayfly-go/internal/tag/application"
)
var (
machineApp Machine = newMachineApp(
persistence.GetMachineRepo(),
GetAuthCertApp(),
+ tagapp.GetTagTreeApp(),
)
machineFileApp MachineFile = newMachineFileApp(
@@ -28,6 +30,8 @@ var (
persistence.GetMachineCronJobExecRepo(),
GetMachineApp(),
)
+
+ machineTermOpApp MachineTermOp = newMachineTermOpApp(persistence.GetMachineTermOpRepo())
)
func GetMachineApp() Machine {
@@ -49,3 +53,7 @@ func GetAuthCertApp() AuthCert {
func GetMachineCronJobApp() MachineCronJob {
return machineCropJobApp
}
+
+func GetMachineTermOpApp() MachineTermOp {
+ return machineTermOpApp
+}
diff --git a/server/internal/machine/application/machine.go b/server/internal/machine/application/machine.go
index 6ef8623c..6892568f 100644
--- a/server/internal/machine/application/machine.go
+++ b/server/internal/machine/application/machine.go
@@ -3,17 +3,20 @@ package application
import (
"context"
"fmt"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/internal/machine/infrastructure/cache"
"mayfly-go/internal/machine/mcm"
+ tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/gormx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/scheduler"
+ "mayfly-go/pkg/utils/stringx"
"time"
"gorm.io/gorm"
@@ -22,7 +25,7 @@ import (
type Machine interface {
base.App[*entity.Machine]
- Save(ctx context.Context, m *entity.Machine) error
+ Save(ctx context.Context, m *entity.Machine, tagIds ...uint64) error
// 测试机器连接
TestConn(me *entity.Machine) error
@@ -30,8 +33,6 @@ type Machine interface {
// 调整机器状态
ChangeStatus(ctx context.Context, id uint64, status int8) error
- Count(condition *entity.MachineQuery) int64
-
Delete(ctx context.Context, id uint64) error
// 分页获取机器信息列表
@@ -50,9 +51,10 @@ type Machine interface {
GetMachineStats(machineId uint64) (*mcm.Stats, error)
}
-func newMachineApp(machineRepo repository.Machine, authCertApp AuthCert) Machine {
+func newMachineApp(machineRepo repository.Machine, authCertApp AuthCert, tagApp tagapp.TagTree) Machine {
app := &machineAppImpl{
authCertApp: authCertApp,
+ tagApp: tagApp,
}
app.Repo = machineRepo
return app
@@ -62,6 +64,8 @@ type machineAppImpl struct {
base.AppImpl[*entity.Machine, repository.Machine]
authCertApp AuthCert
+
+ tagApp tagapp.TagTree
}
// 分页获取机器信息列表
@@ -69,11 +73,7 @@ func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pagePara
return m.GetRepo().GetMachineList(condition, pageParam, toEntity, orderBy...)
}
-func (m *machineAppImpl) Count(condition *entity.MachineQuery) int64 {
- return m.GetRepo().Count(condition)
-}
-
-func (m *machineAppImpl) Save(ctx context.Context, me *entity.Machine) error {
+func (m *machineAppImpl) Save(ctx context.Context, me *entity.Machine, tagIds ...uint64) error {
oldMachine := &entity.Machine{Ip: me.Ip, Port: me.Port, Username: me.Username}
if me.SshTunnelMachineId > 0 {
oldMachine.SshTunnelMachineId = me.SshTunnelMachineId
@@ -85,9 +85,16 @@ func (m *machineAppImpl) Save(ctx context.Context, me *entity.Machine) error {
if err == nil {
return errorx.NewBiz("该机器信息已存在")
}
+ resouceCode := stringx.Rand(16)
+ me.Code = resouceCode
// 新增机器,默认启用状态
me.Status = entity.MachineStatusEnable
- return m.Insert(ctx, me)
+
+ return m.Tx(ctx, func(ctx context.Context) error {
+ return m.Insert(ctx, me)
+ }, func(ctx context.Context) error {
+ return m.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeMachine, tagIds)
+ })
}
// 如果存在该库,则校验修改的库是否为该库
@@ -97,7 +104,11 @@ func (m *machineAppImpl) Save(ctx context.Context, me *entity.Machine) error {
// 关闭连接
mcm.DeleteCli(me.Id)
- return m.UpdateById(ctx, me)
+ return m.Tx(ctx, func(ctx context.Context) error {
+ return m.UpdateById(ctx, me)
+ }, func(ctx context.Context) error {
+ return m.tagApp.RelateResource(ctx, oldMachine.Code, consts.TagResourceTypeMachine, tagIds)
+ })
}
func (m *machineAppImpl) TestConn(me *entity.Machine) error {
@@ -214,7 +225,7 @@ func (m *machineAppImpl) toMachineInfo(me *entity.Machine) (*mcm.MachineInfo, er
mi.Ip = me.Ip
mi.Port = me.Port
mi.Username = me.Username
- mi.TagPath = me.TagPath
+ mi.TagPath = m.tagApp.ListTagPathByResource(consts.TagResourceTypeMachine, me.Code)
mi.EnableRecorder = me.EnableRecorder
if me.UseAuthCert() {
diff --git a/server/internal/machine/application/machine_cronjob.go b/server/internal/machine/application/machine_cronjob.go
index e5b111c5..a718b215 100644
--- a/server/internal/machine/application/machine_cronjob.go
+++ b/server/internal/machine/application/machine_cronjob.go
@@ -44,6 +44,10 @@ type MachineCronJob interface {
// 初始化计划任务
InitCronJob()
+
+ // 执行cron job
+ // @param key cron job key
+ RunCronJob(key string)
}
type machineCropJobAppImpl struct {
@@ -183,30 +187,7 @@ func (m *machineCropJobAppImpl) InitCronJob() {
}
}
-func (m *machineCropJobAppImpl) addCronJob(mcj *entity.MachineCronJob) {
- var key string
- isDisable := mcj.Status == entity.MachineCronJobStatusDisable
- if mcj.Id == 0 {
- key = stringx.Rand(16)
- mcj.Key = key
- if isDisable {
- return
- }
- } else {
- key = mcj.Key
- }
-
- if isDisable {
- scheduler.RemoveByKey(key)
- return
- }
-
- scheduler.AddFunByKey(key, mcj.Cron, func() {
- go m.runCronJob(key)
- })
-}
-
-func (m *machineCropJobAppImpl) runCronJob(key string) {
+func (m *machineCropJobAppImpl) RunCronJob(key string) {
// 简单使用redis分布式锁防止多实例同一时刻重复执行
if lock := rediscli.NewLock(key, 30*time.Second); lock != nil {
if !lock.Lock() {
@@ -229,6 +210,28 @@ func (m *machineCropJobAppImpl) runCronJob(key string) {
}
}
+func (m *machineCropJobAppImpl) addCronJob(mcj *entity.MachineCronJob) {
+ var key string
+ isDisable := mcj.Status == entity.MachineCronJobStatusDisable
+ if mcj.Id == 0 {
+ key = stringx.Rand(16)
+ mcj.Key = key
+ if isDisable {
+ return
+ }
+ } else {
+ key = mcj.Key
+ }
+
+ if isDisable {
+ scheduler.RemoveByKey(key)
+ return
+ }
+
+ scheduler.AddFunByKey(key, mcj.Cron, func() {
+ go m.RunCronJob(key)
+ })
+}
func (m *machineCropJobAppImpl) runCronJob0(mid uint64, cronJob *entity.MachineCronJob) {
defer func() {
if err := recover(); err != nil {
diff --git a/server/internal/machine/application/machine_term_op.go b/server/internal/machine/application/machine_term_op.go
new file mode 100644
index 00000000..bf8d0a37
--- /dev/null
+++ b/server/internal/machine/application/machine_term_op.go
@@ -0,0 +1,93 @@
+package application
+
+import (
+ "context"
+ "fmt"
+ "mayfly-go/internal/machine/config"
+ "mayfly-go/internal/machine/domain/entity"
+ "mayfly-go/internal/machine/domain/repository"
+ "mayfly-go/internal/machine/mcm"
+ "mayfly-go/pkg/base"
+ "mayfly-go/pkg/contextx"
+ "mayfly-go/pkg/errorx"
+ "mayfly-go/pkg/model"
+ "mayfly-go/pkg/utils/stringx"
+ "os"
+ "path"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+type MachineTermOp interface {
+ base.App[*entity.MachineTermOp]
+
+ // 终端连接操作
+ TermConn(ctx context.Context, cli *mcm.Cli, wsConn *websocket.Conn, rows, cols int) error
+
+ GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
+}
+
+func newMachineTermOpApp(machineTermOpRepo repository.MachineTermOp) MachineTermOp {
+ return &machineTermOpAppImpl{
+ base.AppImpl[*entity.MachineTermOp, repository.MachineTermOp]{Repo: machineTermOpRepo},
+ }
+}
+
+type machineTermOpAppImpl struct {
+ base.AppImpl[*entity.MachineTermOp, repository.MachineTermOp]
+}
+
+func (a *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsConn *websocket.Conn, rows, cols int) error {
+ var recorder *mcm.Recorder
+ var termOpRecord *entity.MachineTermOp
+
+ // 开启终端操作记录
+ if cli.Info.EnableRecorder == 1 {
+ now := time.Now()
+ la := contextx.GetLoginAccount(ctx)
+
+ termOpRecord = new(entity.MachineTermOp)
+
+ termOpRecord.CreateTime = &now
+ termOpRecord.Creator = la.Username
+ termOpRecord.CreatorId = la.Id
+
+ termOpRecord.MachineId = cli.Info.Id
+ termOpRecord.Username = cli.Info.Username
+
+ // 回放文件路径为: 基础配置路径/操作日期(202301)/day/hour/randstr.cast
+ recRelPath := path.Join(now.Format("200601"), fmt.Sprintf("%d", now.Day()), fmt.Sprintf("%d", now.Hour()))
+ // 文件绝对路径
+ recAbsPath := path.Join(config.GetMachine().TerminalRecPath, recRelPath)
+ os.MkdirAll(recAbsPath, 0766)
+ filename := fmt.Sprintf("%s.cast", stringx.RandByChars(18, stringx.LowerChars))
+ f, err := os.OpenFile(path.Join(recAbsPath, filename), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
+ if err != nil {
+ return errorx.NewBiz("创建终端回放记录文件失败: %s", err.Error())
+ }
+ defer f.Close()
+
+ termOpRecord.RecordFilePath = path.Join(recRelPath, filename)
+ recorder = mcm.NewRecorder(f)
+ }
+
+ mts, err := mcm.NewTerminalSession(stringx.Rand(16), wsConn, cli, rows, cols, recorder)
+ if err != nil {
+ return err
+ }
+
+ mts.Start()
+ defer mts.Stop()
+
+ if termOpRecord != nil {
+ now := time.Now()
+ termOpRecord.EndTime = &now
+ return a.Insert(ctx, termOpRecord)
+ }
+ return nil
+}
+
+func (a *machineTermOpAppImpl) GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
+ return a.GetRepo().GetPageList(condition, pageParam, toEntity)
+}
diff --git a/server/internal/machine/domain/entity/machine.go b/server/internal/machine/domain/entity/machine.go
index 1d594247..d701f08c 100644
--- a/server/internal/machine/domain/entity/machine.go
+++ b/server/internal/machine/domain/entity/machine.go
@@ -8,14 +8,13 @@ import (
type Machine struct {
model.Model
+ Code string `json:"code"`
Name string `json:"name"`
- Ip string `json:"ip"` // IP地址
- Port int `json:"port"` // 端口号
- Username string `json:"username"` // 用户名
- Password string `json:"password"` // 密码
- AuthCertId int `json:"authCertId"` // 授权凭证id
- TagId uint64
- TagPath string
+ Ip string `json:"ip"` // IP地址
+ Port int `json:"port"` // 端口号
+ Username string `json:"username"` // 用户名
+ Password string `json:"password"` // 密码
+ AuthCertId int `json:"authCertId"` // 授权凭证id
Status int8 `json:"status"` // 状态 1:启用;2:停用
Remark string `json:"remark"` // 备注
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
diff --git a/server/internal/machine/domain/entity/machine_term_op.go b/server/internal/machine/domain/entity/machine_term_op.go
new file mode 100644
index 00000000..1f741b11
--- /dev/null
+++ b/server/internal/machine/domain/entity/machine_term_op.go
@@ -0,0 +1,19 @@
+package entity
+
+import (
+ "mayfly-go/pkg/model"
+ "time"
+)
+
+type MachineTermOp struct {
+ model.DeletedModel
+
+ MachineId uint64 `json:"machineId"`
+ Username string `json:"username"`
+ RecordFilePath string `json:"recordFilePath"` // 回放文件路径
+
+ CreateTime *time.Time `json:"createTime"`
+ CreatorId uint64 `json:"creatorId"`
+ Creator string `json:"creator"`
+ EndTime *time.Time `json:"endTime"`
+}
diff --git a/server/internal/machine/domain/entity/query.go b/server/internal/machine/domain/entity/query.go
index 398165cf..ed288c84 100644
--- a/server/internal/machine/domain/entity/query.go
+++ b/server/internal/machine/domain/entity/query.go
@@ -6,7 +6,8 @@ type MachineQuery struct {
Status int8 `json:"status" form:"status"`
Ip string `json:"ip" form:"ip"` // IP地址
TagPath string `json:"tagPath" form:"tagPath"`
- TagIds []uint64
+
+ Codes []string
}
type AuthCertQuery struct {
diff --git a/server/internal/machine/domain/repository/machine.go b/server/internal/machine/domain/repository/machine.go
index a6bb27c9..4925ead1 100644
--- a/server/internal/machine/domain/repository/machine.go
+++ b/server/internal/machine/domain/repository/machine.go
@@ -12,6 +12,4 @@ type Machine interface {
// 分页获取机器信息列表
GetMachineList(condition *entity.MachineQuery, pageParam *model.PageParam, toEntity *[]*vo.MachineVO, orderBy ...string) (*model.PageResult[*[]*vo.MachineVO], error)
-
- Count(condition *entity.MachineQuery) int64
}
diff --git a/server/internal/machine/domain/repository/machine_term_op.go b/server/internal/machine/domain/repository/machine_term_op.go
new file mode 100644
index 00000000..5a12ee64
--- /dev/null
+++ b/server/internal/machine/domain/repository/machine_term_op.go
@@ -0,0 +1,14 @@
+package repository
+
+import (
+ "mayfly-go/internal/machine/domain/entity"
+ "mayfly-go/pkg/base"
+ "mayfly-go/pkg/model"
+)
+
+type MachineTermOp interface {
+ base.Repo[*entity.MachineTermOp]
+
+ // 分页获取机器终端执行记录列表
+ GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
+}
diff --git a/server/internal/machine/infrastructure/persistence/machine.go b/server/internal/machine/infrastructure/persistence/machine.go
index 6e126ba8..c7e7a3e2 100644
--- a/server/internal/machine/infrastructure/persistence/machine.go
+++ b/server/internal/machine/infrastructure/persistence/machine.go
@@ -26,9 +26,7 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
Eq("status", condition.Status).
Like("ip", condition.Ip).
Like("name", condition.Name).
- In("tag_id", condition.TagIds).
- RLike("tag_path", condition.TagPath).
- OrderByAsc("tag_path")
+ In("code", condition.Codes)
if condition.Ids != "" {
// ,分割id转为id数组
@@ -40,12 +38,3 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
return gormx.PageQuery(qd, pageParam, toEntity)
}
-
-func (m *machineRepoImpl) Count(condition *entity.MachineQuery) int64 {
- where := make(map[string]any)
- if len(condition.TagIds) > 0 {
- where["tag_id"] = condition.TagIds
- }
-
- return m.CountByCond(where)
-}
diff --git a/server/internal/machine/infrastructure/persistence/machine_term_op.go b/server/internal/machine/infrastructure/persistence/machine_term_op.go
new file mode 100644
index 00000000..fe168d24
--- /dev/null
+++ b/server/internal/machine/infrastructure/persistence/machine_term_op.go
@@ -0,0 +1,22 @@
+package persistence
+
+import (
+ "mayfly-go/internal/machine/domain/entity"
+ "mayfly-go/internal/machine/domain/repository"
+ "mayfly-go/pkg/base"
+ "mayfly-go/pkg/gormx"
+ "mayfly-go/pkg/model"
+)
+
+type machineTermOpRepoImpl struct {
+ base.RepoImpl[*entity.MachineTermOp]
+}
+
+func newMachineTermOpRepoImpl() repository.MachineTermOp {
+ return &machineTermOpRepoImpl{base.RepoImpl[*entity.MachineTermOp]{M: new(entity.MachineTermOp)}}
+}
+
+func (m *machineTermOpRepoImpl) GetPageList(condition *entity.MachineTermOp, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
+ qd := gormx.NewQuery(condition).WithCondModel(condition).WithOrderBy(orderBy...)
+ return gormx.PageQuery(qd, pageParam, toEntity)
+}
diff --git a/server/internal/machine/infrastructure/persistence/persistence.go b/server/internal/machine/infrastructure/persistence/persistence.go
index 984a5155..6509fdaa 100644
--- a/server/internal/machine/infrastructure/persistence/persistence.go
+++ b/server/internal/machine/infrastructure/persistence/persistence.go
@@ -10,6 +10,7 @@ var (
machineCropJobRepo repository.MachineCronJob = newMachineCronJobRepo()
machineCropJobExecRepo repository.MachineCronJobExec = newMachineCronJobExecRepo()
machineCronJobRelateRepo repository.MachineCronJobRelate = newMachineCropJobRelateRepo()
+ machineTermOpRepo repository.MachineTermOp = newMachineTermOpRepoImpl()
)
func GetMachineRepo() repository.Machine {
@@ -39,3 +40,7 @@ func GetMachineCronJobExecRepo() repository.MachineCronJobExec {
func GetMachineCronJobRelateRepo() repository.MachineCronJobRelate {
return machineCronJobRelateRepo
}
+
+func GetMachineTermOpRepo() repository.MachineTermOp {
+ return machineTermOpRepo
+}
diff --git a/server/internal/machine/mcm/machine.go b/server/internal/machine/mcm/machine.go
index 28b9fb57..99d72969 100644
--- a/server/internal/machine/mcm/machine.go
+++ b/server/internal/machine/mcm/machine.go
@@ -25,7 +25,7 @@ type MachineInfo struct {
SshTunnelMachine *MachineInfo `json:"-"` // ssh隧道机器
EnableRecorder int8 `json:"-"` // 是否启用终端回放记录
- TagPath string `json:"tagPath"`
+ TagPath []string `json:"tagPath"`
}
func (m *MachineInfo) UseSshTunnel() bool {
diff --git a/server/internal/machine/mcm/terminal_session.go b/server/internal/machine/mcm/terminal_session.go
index 49728d74..3ad41b6f 100644
--- a/server/internal/machine/mcm/terminal_session.go
+++ b/server/internal/machine/mcm/terminal_session.go
@@ -140,6 +140,7 @@ type WsMsg struct {
Rows int `json:"rows"`
}
+// 接收客户端ws发送过来的消息,并写入终端会话中。
func (ts *TerminalSession) receiveWsMsg() {
wsConn := ts.wsConn
for {
diff --git a/server/internal/machine/router/machine.go b/server/internal/machine/router/machine.go
index f9c3415c..e3a6adb6 100644
--- a/server/internal/machine/router/machine.go
+++ b/server/internal/machine/router/machine.go
@@ -11,8 +11,9 @@ import (
func InitMachineRouter(router *gin.RouterGroup) {
m := &api.Machine{
- MachineApp: application.GetMachineApp(),
- TagApp: tagapp.GetTagTreeApp(),
+ MachineApp: application.GetMachineApp(),
+ MachineTermOpApp: application.GetMachineTermOpApp(),
+ TagApp: tagapp.GetTagTreeApp(),
}
machines := router.Group("machines")
@@ -22,8 +23,6 @@ func InitMachineRouter(router *gin.RouterGroup) {
reqs := [...]*req.Conf{
req.NewGet("", m.Machines),
- req.NewGet("/tags", m.MachineTags),
-
req.NewGet(":machineId/stats", m.MachineStats),
req.NewGet(":machineId/process", m.GetProcess),
@@ -40,8 +39,11 @@ func InitMachineRouter(router *gin.RouterGroup) {
req.NewDelete(":machineId/close-cli", m.CloseCli).Log(req.NewLogSave("关闭机器客户端")).RequiredPermissionCode("machine:close-cli"),
- // 获取机器终端回放记录的相应文件夹名或文件名,目前具有保存机器信息的权限标识才有权限查看终端回放
- req.NewGet("rec/names", m.MachineRecDirNames).RequiredPermission(saveMachineP),
+ // 获取机器终端回放记录列表,目前具有保存机器信息的权限标识才有权限查看终端回放
+ req.NewGet(":machineId/term-recs", m.MachineTermOpRecords).RequiredPermission(saveMachineP),
+
+ // 获取机器终端回放记录
+ req.NewGet(":machineId/term-recs/:recId", m.MachineTermOpRecord).RequiredPermission(saveMachineP),
}
req.BatchSetGroup(machines, reqs[:])
diff --git a/server/internal/machine/router/machine_cronjob.go b/server/internal/machine/router/machine_cronjob.go
index dd093bb3..4ef918b3 100644
--- a/server/internal/machine/router/machine_cronjob.go
+++ b/server/internal/machine/router/machine_cronjob.go
@@ -26,6 +26,8 @@ func InitMachineCronJobRouter(router *gin.RouterGroup) {
req.NewDelete(":ids", cj.Delete).Log(req.NewLogSave("删除机器计划任务")),
+ req.NewPost("/run/:key", cj.RunCronJob).Log(req.NewLogSave("手动执行计划任务")),
+
req.NewGet("/execs", cj.CronJobExecs),
}
diff --git a/server/internal/mongo/api/form/mongo.go b/server/internal/mongo/api/form/mongo.go
index e38b35c2..c369afa6 100644
--- a/server/internal/mongo/api/form/mongo.go
+++ b/server/internal/mongo/api/form/mongo.go
@@ -1,12 +1,11 @@
package form
type Mongo struct {
- Id uint64 `json:"id"`
- Uri string `binding:"required" json:"uri"`
- SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
- Name string `binding:"required" json:"name"`
- TagId uint64 `binding:"required" json:"tagId"`
- TagPath string `binding:"required" json:"tagPath"`
+ Id uint64 `json:"id"`
+ Uri string `binding:"required" json:"uri"`
+ SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
+ Name string `binding:"required" json:"name"`
+ TagId []uint64 `binding:"required" json:"tagId"`
}
type MongoCommand struct {
diff --git a/server/internal/mongo/api/mongo.go b/server/internal/mongo/api/mongo.go
index 7f6695d8..da0ccc21 100644
--- a/server/internal/mongo/api/mongo.go
+++ b/server/internal/mongo/api/mongo.go
@@ -2,6 +2,7 @@ package api
import (
"context"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/mongo/api/form"
"mayfly-go/internal/mongo/application"
"mayfly-go/internal/mongo/domain/entity"
@@ -30,22 +31,18 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
queryCond, page := ginx.BindQueryAndPage[*entity.MongoQuery](rc.GinCtx, new(entity.MongoQuery))
// 不存在可访问标签id,即没有可操作数据
- tagIds := m.TagApp.ListTagIdByAccountId(rc.GetLoginAccount().Id)
- if len(tagIds) == 0 {
+ codes := m.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeMongo, queryCond.TagPath)
+ if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
- queryCond.TagIds = tagIds
+ queryCond.Codes = codes
res, err := m.MongoApp.GetPageList(queryCond, page, new([]entity.Mongo))
biz.ErrIsNil(err)
rc.ResData = res
}
-func (m *Mongo) MongoTags(rc *req.Ctx) {
- rc.ResData = m.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Mongo))
-}
-
func (m *Mongo) TestConn(rc *req.Ctx) {
form := &form.Mongo{}
mongo := ginx.BindJsonAndCopyTo[*entity.Mongo](rc.GinCtx, form, new(entity.Mongo))
@@ -63,7 +60,7 @@ func (m *Mongo) Save(rc *req.Ctx) {
}(form.Uri)
rc.ReqParam = form
- biz.ErrIsNil(m.MongoApp.Save(rc.MetaCtx, mongo))
+ biz.ErrIsNil(m.MongoApp.Save(rc.MetaCtx, mongo, form.TagId...))
}
func (m *Mongo) DeleteMongo(rc *req.Ctx) {
diff --git a/server/internal/mongo/application/application.go b/server/internal/mongo/application/application.go
index beff37bc..1e201171 100644
--- a/server/internal/mongo/application/application.go
+++ b/server/internal/mongo/application/application.go
@@ -1,9 +1,12 @@
package application
-import "mayfly-go/internal/mongo/infrastructure/persistence"
+import (
+ "mayfly-go/internal/mongo/infrastructure/persistence"
+ tagapp "mayfly-go/internal/tag/application"
+)
var (
- mongoApp Mongo = newMongoAppImpl(persistence.GetMongoRepo())
+ mongoApp Mongo = newMongoAppImpl(persistence.GetMongoRepo(), tagapp.GetTagTreeApp())
)
func GetMongoApp() Mongo {
diff --git a/server/internal/mongo/application/mongo.go b/server/internal/mongo/application/mongo.go
index 70f894a5..50e01375 100644
--- a/server/internal/mongo/application/mongo.go
+++ b/server/internal/mongo/application/mongo.go
@@ -2,12 +2,15 @@ package application
import (
"context"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/mongo/domain/entity"
"mayfly-go/internal/mongo/domain/repository"
"mayfly-go/internal/mongo/mgm"
+ tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
+ "mayfly-go/pkg/utils/stringx"
)
type Mongo interface {
@@ -16,11 +19,9 @@ type Mongo interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
- Count(condition *entity.MongoQuery) int64
-
TestConn(entity *entity.Mongo) error
- Save(ctx context.Context, entity *entity.Mongo) error
+ Save(ctx context.Context, entity *entity.Mongo, tagIds ...uint64) error
// 删除数据库信息
Delete(ctx context.Context, id uint64) error
@@ -30,14 +31,18 @@ type Mongo interface {
GetMongoConn(id uint64) (*mgm.MongoConn, error)
}
-func newMongoAppImpl(mongoRepo repository.Mongo) Mongo {
- return &mongoAppImpl{
- base.AppImpl[*entity.Mongo, repository.Mongo]{Repo: mongoRepo},
+func newMongoAppImpl(mongoRepo repository.Mongo, tagApp tagapp.TagTree) Mongo {
+ app := &mongoAppImpl{
+ tagApp: tagApp,
}
+ app.Repo = mongoRepo
+ return app
}
type mongoAppImpl struct {
base.AppImpl[*entity.Mongo, repository.Mongo]
+
+ tagApp tagapp.TagTree
}
// 分页获取数据库信息列表
@@ -45,10 +50,6 @@ func (d *mongoAppImpl) GetPageList(condition *entity.MongoQuery, pageParam *mode
return d.GetRepo().GetList(condition, pageParam, toEntity, orderBy...)
}
-func (d *mongoAppImpl) Count(condition *entity.MongoQuery) int64 {
- return d.GetRepo().Count(condition)
-}
-
func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
mgm.CloseConn(id)
return d.GetRepo().DeleteById(ctx, id)
@@ -63,22 +64,45 @@ func (d *mongoAppImpl) TestConn(me *entity.Mongo) error {
return nil
}
-func (d *mongoAppImpl) Save(ctx context.Context, m *entity.Mongo) error {
+func (d *mongoAppImpl) Save(ctx context.Context, m *entity.Mongo, tagIds ...uint64) error {
+ oldMongo := &entity.Mongo{Name: m.Name}
+ err := d.GetBy(oldMongo)
+
if m.Id == 0 {
- return d.GetRepo().Insert(ctx, m)
+ if err == nil {
+ return errorx.NewBiz("该名称已存在")
+ }
+
+ resouceCode := stringx.Rand(16)
+ m.Code = resouceCode
+
+ return d.Tx(ctx, func(ctx context.Context) error {
+ return d.Insert(ctx, m)
+ }, func(ctx context.Context) error {
+ return d.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeMongo, tagIds)
+ })
+ }
+
+ // 如果存在该库,则校验修改的库是否为该库
+ if err == nil && oldMongo.Id != m.Id {
+ return errorx.NewBiz("该名称已存在")
}
// 先关闭连接
mgm.CloseConn(m.Id)
- return d.GetRepo().UpdateById(ctx, m)
+ return d.Tx(ctx, func(ctx context.Context) error {
+ return d.UpdateById(ctx, m)
+ }, func(ctx context.Context) error {
+ return d.tagApp.RelateResource(ctx, oldMongo.Code, consts.TagResourceTypeMongo, tagIds)
+ })
}
func (d *mongoAppImpl) GetMongoConn(id uint64) (*mgm.MongoConn, error) {
return mgm.GetMongoConn(id, func() (*mgm.MongoInfo, error) {
- mongo, err := d.GetById(new(entity.Mongo), id)
+ me, err := d.GetById(new(entity.Mongo), id)
if err != nil {
return nil, errorx.NewBiz("mongo信息不存在")
}
- return mongo.ToMongoInfo(), nil
+ return me.ToMongoInfo(d.tagApp.ListTagPathByResource(consts.TagResourceTypeMongo, me.Code)...), nil
})
}
diff --git a/server/internal/mongo/domain/entity/mongo.go b/server/internal/mongo/domain/entity/mongo.go
index 332bd174..e1a91359 100644
--- a/server/internal/mongo/domain/entity/mongo.go
+++ b/server/internal/mongo/domain/entity/mongo.go
@@ -9,16 +9,16 @@ import (
type Mongo struct {
model.Model
+ Code string `orm:"column(code)" json:"code"`
Name string `orm:"column(name)" json:"name"`
Uri string `orm:"column(uri)" json:"uri"`
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
- TagId uint64 `json:"tagId"`
- TagPath string `json:"tagPath"`
}
// 转换为mongoInfo进行连接
-func (me *Mongo) ToMongoInfo() *mgm.MongoInfo {
+func (me *Mongo) ToMongoInfo(tagPath ...string) *mgm.MongoInfo {
mongoInfo := new(mgm.MongoInfo)
structx.Copy(mongoInfo, me)
+ mongoInfo.TagPath = tagPath
return mongoInfo
}
diff --git a/server/internal/mongo/domain/entity/query.go b/server/internal/mongo/domain/entity/query.go
index e0772b54..4af11afa 100644
--- a/server/internal/mongo/domain/entity/query.go
+++ b/server/internal/mongo/domain/entity/query.go
@@ -10,5 +10,5 @@ type MongoQuery struct {
SshTunnelMachineId uint64 // ssh隧道机器id
TagPath string `json:"tagPath" form:"tagPath"`
- TagIds []uint64
+ Codes []string
}
diff --git a/server/internal/mongo/domain/repository/mongo.go b/server/internal/mongo/domain/repository/mongo.go
index d10829b2..266027f8 100644
--- a/server/internal/mongo/domain/repository/mongo.go
+++ b/server/internal/mongo/domain/repository/mongo.go
@@ -11,6 +11,4 @@ type Mongo interface {
// 分页获取列表
GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
-
- Count(condition *entity.MongoQuery) int64
}
diff --git a/server/internal/mongo/infrastructure/persistence/mongo.go b/server/internal/mongo/infrastructure/persistence/mongo.go
index 23734263..8cc89027 100644
--- a/server/internal/mongo/infrastructure/persistence/mongo.go
+++ b/server/internal/mongo/infrastructure/persistence/mongo.go
@@ -20,16 +20,6 @@ func newMongoRepo() repository.Mongo {
func (d *mongoRepoImpl) GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQuery(new(entity.Mongo)).
Like("name", condition.Name).
- In("tag_id", condition.TagIds).
- RLike("tag_path", condition.TagPath).
- OrderByAsc("tag_path")
+ In("code", condition.Codes)
return gormx.PageQuery(qd, pageParam, toEntity)
}
-
-func (d *mongoRepoImpl) Count(condition *entity.MongoQuery) int64 {
- where := make(map[string]any)
- if len(condition.TagIds) > 0 {
- where["tag_id"] = condition.TagIds
- }
- return gormx.CountByCond(new(entity.Mongo), where)
-}
diff --git a/server/internal/mongo/mgm/info.go b/server/internal/mongo/mgm/info.go
index 29de78b7..bf101eba 100644
--- a/server/internal/mongo/mgm/info.go
+++ b/server/internal/mongo/mgm/info.go
@@ -21,8 +21,8 @@ type MongoInfo struct {
Uri string `json:"-"`
- TagPath string `json:"tagPath"`
- SshTunnelMachineId int `json:"-"` // ssh隧道机器id
+ TagPath []string `json:"tagPath"`
+ SshTunnelMachineId int `json:"-"` // ssh隧道机器id
}
func (mi *MongoInfo) Conn() (*MongoConn, error) {
diff --git a/server/internal/mongo/router/mongo.go b/server/internal/mongo/router/mongo.go
index f5cdd10b..5463fab6 100644
--- a/server/internal/mongo/router/mongo.go
+++ b/server/internal/mongo/router/mongo.go
@@ -23,8 +23,6 @@ func InitMongoRouter(router *gin.RouterGroup) {
// 获取所有mongo列表
req.NewGet("", ma.Mongos),
- req.NewGet("/tags", ma.MongoTags),
-
req.NewPost("/test-conn", ma.TestConn),
req.NewPost("", ma.Save).Log(req.NewLogSave("mongo-保存信息")),
diff --git a/server/internal/redis/api/form/redis.go b/server/internal/redis/api/form/redis.go
index 543bdb07..ff1132ec 100644
--- a/server/internal/redis/api/form/redis.go
+++ b/server/internal/redis/api/form/redis.go
@@ -1,17 +1,16 @@
package form
type Redis struct {
- Id uint64 `json:"id"`
- Name string `json:"name"`
- Host string `json:"host" binding:"required"`
- Username string `json:"username"`
- Password string `json:"password"`
- Mode string `json:"mode"`
- Db string `json:"db"`
- SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
- TagId uint64 `binding:"required" json:"tagId"`
- TagPath string `binding:"required" json:"tagPath"`
- Remark string `json:"remark"`
+ Id uint64 `json:"id"`
+ Name string `json:"name"`
+ Host string `json:"host" binding:"required"`
+ Username string `json:"username"`
+ Password string `json:"password"`
+ Mode string `json:"mode"`
+ Db string `json:"db"`
+ SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
+ TagId []uint64 `binding:"required" json:"tagId"`
+ Remark string `json:"remark"`
}
type Rename struct {
diff --git a/server/internal/redis/api/redis.go b/server/internal/redis/api/redis.go
index f778937f..4fedee5d 100644
--- a/server/internal/redis/api/redis.go
+++ b/server/internal/redis/api/redis.go
@@ -2,6 +2,7 @@ package api
import (
"context"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/redis/api/form"
"mayfly-go/internal/redis/api/vo"
"mayfly-go/internal/redis/application"
@@ -31,22 +32,18 @@ func (r *Redis) RedisList(rc *req.Ctx) {
queryCond, page := ginx.BindQueryAndPage[*entity.RedisQuery](rc.GinCtx, new(entity.RedisQuery))
// 不存在可访问标签id,即没有可操作数据
- tagIds := r.TagApp.ListTagIdByAccountId(rc.GetLoginAccount().Id)
- if len(tagIds) == 0 {
+ codes := r.TagApp.GetAccountResourceCodes(rc.GetLoginAccount().Id, consts.TagResourceTypeRedis, queryCond.TagPath)
+ if len(codes) == 0 {
rc.ResData = model.EmptyPageResult[any]()
return
}
- queryCond.TagIds = tagIds
+ queryCond.Codes = codes
res, err := r.RedisApp.GetPageList(queryCond, page, new([]vo.Redis))
biz.ErrIsNil(err)
rc.ResData = res
}
-func (r *Redis) RedisTags(rc *req.Ctx) {
- rc.ResData = r.TagApp.ListTagByAccountIdAndResource(rc.GetLoginAccount().Id, new(entity.Redis))
-}
-
func (r *Redis) TestConn(rc *req.Ctx) {
form := &form.Redis{}
redis := ginx.BindJsonAndCopyTo[*entity.Redis](rc.GinCtx, form, new(entity.Redis))
@@ -72,7 +69,7 @@ func (r *Redis) Save(rc *req.Ctx) {
form.Password = "****"
rc.ReqParam = form
- biz.ErrIsNil(r.RedisApp.Save(rc.MetaCtx, redis))
+ biz.ErrIsNil(r.RedisApp.Save(rc.MetaCtx, redis, form.TagId...))
}
// 获取redis实例密码,由于数据库是加密存储,故提供该接口展示原文密码
@@ -229,7 +226,7 @@ func (r *Redis) checkKeyAndGetRedisConn(rc *req.Ctx) (*rdm.RedisConn, string) {
func (r *Redis) getRedisConn(rc *req.Ctx) *rdm.RedisConn {
ri, err := r.RedisApp.GetRedisConn(getIdAndDbNum(rc.GinCtx))
biz.ErrIsNil(err)
- biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.GetLoginAccount().Id, ri.Info.TagPath), "%s")
+ biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.GetLoginAccount().Id, ri.Info.TagPath...), "%s")
return ri
}
diff --git a/server/internal/redis/api/vo/redis.go b/server/internal/redis/api/vo/redis.go
index 6f0ff385..43d2d806 100644
--- a/server/internal/redis/api/vo/redis.go
+++ b/server/internal/redis/api/vo/redis.go
@@ -4,14 +4,13 @@ import "time"
type Redis struct {
Id *int64 `json:"id"`
+ Code *string `json:"code"`
Name *string `json:"name"`
Host *string `json:"host"`
Db string `json:"db"`
Mode *string `json:"mode"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
Remark *string `json:"remark"`
- TagId *uint64 `json:"tagId"`
- TagPath *string `json:"tagPath"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`
diff --git a/server/internal/redis/application/application.go b/server/internal/redis/application/application.go
index fef2c356..7c369355 100644
--- a/server/internal/redis/application/application.go
+++ b/server/internal/redis/application/application.go
@@ -1,9 +1,12 @@
package application
-import "mayfly-go/internal/redis/infrastructure/persistence"
+import (
+ "mayfly-go/internal/redis/infrastructure/persistence"
+ tagapp "mayfly-go/internal/tag/application"
+)
var (
- redisApp Redis = newRedisApp(persistence.GetRedisRepo())
+ redisApp Redis = newRedisApp(persistence.GetRedisRepo(), tagapp.GetTagTreeApp())
)
func GetRedisApp() Redis {
diff --git a/server/internal/redis/application/redis.go b/server/internal/redis/application/redis.go
index 9124dcef..4b0283e5 100644
--- a/server/internal/redis/application/redis.go
+++ b/server/internal/redis/application/redis.go
@@ -2,12 +2,15 @@ package application
import (
"context"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/redis/domain/entity"
"mayfly-go/internal/redis/domain/repository"
"mayfly-go/internal/redis/rdm"
+ tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
+ "mayfly-go/pkg/utils/stringx"
"strconv"
"strings"
)
@@ -18,12 +21,10 @@ type Redis interface {
// 分页获取机器脚本信息列表
GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
- Count(condition *entity.RedisQuery) int64
-
// 测试连接
TestConn(re *entity.Redis) error
- Save(ctx context.Context, re *entity.Redis) error
+ Save(ctx context.Context, re *entity.Redis, tagIds ...uint64) error
// 删除数据库信息
Delete(ctx context.Context, id uint64) error
@@ -34,14 +35,18 @@ type Redis interface {
GetRedisConn(id uint64, db int) (*rdm.RedisConn, error)
}
-func newRedisApp(redisRepo repository.Redis) Redis {
- return &redisAppImpl{
- base.AppImpl[*entity.Redis, repository.Redis]{Repo: redisRepo},
+func newRedisApp(redisRepo repository.Redis, tagApp tagapp.TagTree) Redis {
+ app := &redisAppImpl{
+ tagApp: tagApp,
}
+ app.Repo = redisRepo
+ return app
}
type redisAppImpl struct {
base.AppImpl[*entity.Redis, repository.Redis]
+
+ tagApp tagapp.TagTree
}
// 分页获取redis列表
@@ -49,10 +54,6 @@ func (r *redisAppImpl) GetPageList(condition *entity.RedisQuery, pageParam *mode
return r.GetRepo().GetRedisList(condition, pageParam, toEntity, orderBy...)
}
-func (r *redisAppImpl) Count(condition *entity.RedisQuery) int64 {
- return r.GetRepo().Count(condition)
-}
-
func (r *redisAppImpl) TestConn(re *entity.Redis) error {
db := 0
if re.Db != "" {
@@ -67,7 +68,7 @@ func (r *redisAppImpl) TestConn(re *entity.Redis) error {
return nil
}
-func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis) error {
+func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis, tagIds ...uint64) error {
// 查找是否存在该库
oldRedis := &entity.Redis{Host: re.Host}
if re.SshTunnelMachineId > 0 {
@@ -80,7 +81,15 @@ func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis) error {
return errorx.NewBiz("该实例已存在")
}
re.PwdEncrypt()
- return r.Insert(ctx, re)
+
+ resouceCode := stringx.Rand(16)
+ re.Code = resouceCode
+
+ return r.Tx(ctx, func(ctx context.Context) error {
+ return r.Insert(ctx, re)
+ }, func(ctx context.Context) error {
+ return r.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeRedis, tagIds)
+ })
}
// 如果存在该库,则校验修改的库是否为该库
@@ -94,8 +103,13 @@ func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis) error {
rdm.CloseConn(re.Id, db)
}
}
+
re.PwdEncrypt()
- return r.UpdateById(ctx, re)
+ return r.Tx(ctx, func(ctx context.Context) error {
+ return r.UpdateById(ctx, re)
+ }, func(ctx context.Context) error {
+ return r.tagApp.RelateResource(ctx, oldRedis.Code, consts.TagResourceTypeRedis, tagIds)
+ })
}
// 删除Redis信息
@@ -122,6 +136,6 @@ func (r *redisAppImpl) GetRedisConn(id uint64, db int) (*rdm.RedisConn, error) {
}
re.PwdDecrypt()
- return re.ToRedisInfo(db), nil
+ return re.ToRedisInfo(db, r.tagApp.ListTagPathByResource(consts.TagResourceTypeRedis, re.Code)...), nil
})
}
diff --git a/server/internal/redis/domain/entity/query.go b/server/internal/redis/domain/entity/query.go
index 86c2c7ab..6840cab6 100644
--- a/server/internal/redis/domain/entity/query.go
+++ b/server/internal/redis/domain/entity/query.go
@@ -13,6 +13,6 @@ type RedisQuery struct {
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
- TagIds []uint64
+ Codes []string
TagPath string `form:"tagPath"`
}
diff --git a/server/internal/redis/domain/entity/redis.go b/server/internal/redis/domain/entity/redis.go
index a88cadc8..c62ae209 100644
--- a/server/internal/redis/domain/entity/redis.go
+++ b/server/internal/redis/domain/entity/redis.go
@@ -10,6 +10,7 @@ import (
type Redis struct {
model.Model
+ Code string `orm:"column(code)" json:"code"`
Name string `orm:"column(name)" json:"name"`
Host string `orm:"column(host)" json:"host"`
Mode string `json:"mode"`
@@ -18,8 +19,6 @@ type Redis struct {
Db string `orm:"column(database)" json:"db"`
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
- TagId uint64
- TagPath string
}
func (r *Redis) PwdEncrypt() {
@@ -33,9 +32,10 @@ func (r *Redis) PwdDecrypt() {
}
// 转换为redisInfo进行连接
-func (re *Redis) ToRedisInfo(db int) *rdm.RedisInfo {
+func (re *Redis) ToRedisInfo(db int, tagPath ...string) *rdm.RedisInfo {
redisInfo := new(rdm.RedisInfo)
structx.Copy(redisInfo, re)
redisInfo.Db = db
+ redisInfo.TagPath = tagPath
return redisInfo
}
diff --git a/server/internal/redis/domain/repository/redis.go b/server/internal/redis/domain/repository/redis.go
index 937d79dd..b9842c4d 100644
--- a/server/internal/redis/domain/repository/redis.go
+++ b/server/internal/redis/domain/repository/redis.go
@@ -11,6 +11,4 @@ type Redis interface {
// 分页获取机器信息列表
GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
-
- Count(condition *entity.RedisQuery) int64
}
diff --git a/server/internal/redis/infrastructure/persistence/redis_repo.go b/server/internal/redis/infrastructure/persistence/redis_repo.go
index 14209e36..3380a459 100644
--- a/server/internal/redis/infrastructure/persistence/redis_repo.go
+++ b/server/internal/redis/infrastructure/persistence/redis_repo.go
@@ -20,17 +20,6 @@ func newRedisRepo() repository.Redis {
func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQuery(new(entity.Redis)).
Like("host", condition.Host).
- In("tag_id", condition.TagIds).
- RLike("tag_path", condition.TagPath).
- OrderByAsc("tag_path")
+ In("code", condition.Codes)
return gormx.PageQuery(qd, pageParam, toEntity)
}
-
-func (r *redisRepoImpl) Count(condition *entity.RedisQuery) int64 {
- where := make(map[string]any)
- if len(condition.TagIds) > 0 {
- where["tag_id"] = condition.TagIds
- }
-
- return gormx.CountByCond(new(entity.Redis), where)
-}
diff --git a/server/internal/redis/rdm/info.go b/server/internal/redis/rdm/info.go
index 113f70f0..1d1dd502 100644
--- a/server/internal/redis/rdm/info.go
+++ b/server/internal/redis/rdm/info.go
@@ -31,9 +31,9 @@ type RedisInfo struct {
Username string `json:"-"`
Password string `json:"-"`
- Name string `json:"-"`
- TagPath string `json:"tagPath"`
- SshTunnelMachineId int `json:"-"`
+ Name string `json:"-"`
+ TagPath []string `json:"tagPath"`
+ SshTunnelMachineId int `json:"-"`
}
func (r *RedisInfo) Conn() (*RedisConn, error) {
diff --git a/server/internal/redis/router/redis.go b/server/internal/redis/router/redis.go
index 5b71d05a..80311079 100644
--- a/server/internal/redis/router/redis.go
+++ b/server/internal/redis/router/redis.go
@@ -26,8 +26,6 @@ func InitRedisRouter(router *gin.RouterGroup) {
// 获取redis list
req.NewGet("", rs.RedisList),
- req.NewGet("/tags", rs.RedisTags),
-
req.NewPost("/test-conn", rs.TestConn),
req.NewPost("", rs.Save).Log(req.NewLogSave("redis-保存信息")),
diff --git a/server/internal/sys/application/syslog.go b/server/internal/sys/application/syslog.go
index 50b5d002..74c74519 100644
--- a/server/internal/sys/application/syslog.go
+++ b/server/internal/sys/application/syslog.go
@@ -43,9 +43,10 @@ func (m *syslogAppImpl) SaveFromReq(req *req.Ctx) {
syslog.CreateTime = time.Now()
syslog.Creator = lg.Username
syslog.CreatorId = lg.Id
- syslog.Description = req.GetLogInfo().Description
- if req.GetLogInfo().LogResp {
+ logInfo := req.GetLogInfo()
+ syslog.Description = logInfo.Description
+ if logInfo.LogResp {
respB, _ := json.Marshal(req.ResData)
syslog.Resp = string(respB)
}
diff --git a/server/internal/tag/api/tag_tree.go b/server/internal/tag/api/tag_tree.go
index b8cc1810..383cb23e 100644
--- a/server/internal/tag/api/tag_tree.go
+++ b/server/internal/tag/api/tag_tree.go
@@ -2,34 +2,62 @@ package api
import (
"fmt"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ginx"
"mayfly-go/pkg/req"
+ "mayfly-go/pkg/utils/collx"
+ "sort"
"strings"
+
+ "golang.org/x/exp/maps"
)
type TagTree struct {
- TagTreeApp application.TagTree
-}
-
-func (p *TagTree) GetAccountTags(rc *req.Ctx) {
- tagPaths := p.TagTreeApp.ListTagByAccountId(rc.GetLoginAccount().Id)
- allTagPath := make([]string, 0)
- if len(tagPaths) > 0 {
- tags := p.TagTreeApp.ListTagByPath(tagPaths...)
- for _, v := range tags {
- allTagPath = append(allTagPath, v.CodePath)
- }
- }
- rc.ResData = allTagPath
+ TagTreeApp application.TagTree
+ TagResourceApp application.TagResource
}
func (p *TagTree) GetTagTree(rc *req.Ctx) {
- var tagTrees vo.TagTreeVOS
- p.TagTreeApp.ListByQuery(new(entity.TagTreeQuery), &tagTrees)
+ // 超管返回所有标签树
+ if rc.GetLoginAccount().Id == consts.AdminId {
+ var tagTrees vo.TagTreeVOS
+ p.TagTreeApp.ListByQuery(new(entity.TagTreeQuery), &tagTrees)
+ rc.ResData = tagTrees.ToTrees(0)
+ return
+ }
+
+ // 获取用户可以操作访问的标签路径
+ tagPaths := p.TagTreeApp.ListTagByAccountId(rc.GetLoginAccount().Id)
+
+ rootTag := make(map[string][]string, 0)
+ for _, accountTagPath := range tagPaths {
+ root := strings.Split(accountTagPath, "/")[0] + entity.CodePathSeparator
+ tags := rootTag[root]
+ tags = append(tags, accountTagPath)
+ rootTag[root] = tags
+
+ }
+
+ // 获取所有以root标签开头的子标签
+ tags := p.TagTreeApp.ListTagByPath(maps.Keys(rootTag)...)
+ tagTrees := make(vo.TagTreeVOS, 0)
+ for _, tag := range tags {
+ tagPath := tag.CodePath
+ root := strings.Split(tagPath, "/")[0] + entity.CodePathSeparator
+ // 获取用户可操作的标签路径列表
+ accountTagPaths := rootTag[root]
+ for _, accountTagPath := range accountTagPaths {
+ if strings.HasPrefix(tagPath, accountTagPath) || strings.HasPrefix(accountTagPath, tagPath) {
+ tagTrees = append(tagTrees, tag)
+ break
+ }
+ }
+ }
+
rc.ResData = tagTrees.ToTrees(0)
}
@@ -46,7 +74,7 @@ func (p *TagTree) SaveTagTree(rc *req.Ctx) {
tagTree := &entity.TagTree{}
ginx.BindJsonAndValid(rc.GinCtx, tagTree)
- rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, codePath: %s", tagTree.Id, tagTree.Name, tagTree.CodePath)
+ rc.ReqParam = fmt.Sprintf("tagTreeId: %d, tagName: %s, code: %s", tagTree.Id, tagTree.Name, tagTree.Code)
biz.ErrIsNil(p.TagTreeApp.Save(rc.MetaCtx, tagTree))
}
@@ -54,3 +82,23 @@ func (p *TagTree) SaveTagTree(rc *req.Ctx) {
func (p *TagTree) DelTagTree(rc *req.Ctx) {
biz.ErrIsNil(p.TagTreeApp.Delete(rc.MetaCtx, uint64(ginx.PathParamInt(rc.GinCtx, "id"))))
}
+
+// 获取用户可操作的资源标签路径
+func (p *TagTree) TagResources(rc *req.Ctx) {
+ resourceType := int8(ginx.PathParamInt(rc.GinCtx, "rtype"))
+ tagResources := p.TagTreeApp.GetAccountTagResources(rc.GetLoginAccount().Id, resourceType, "")
+ tagPath2Resource := collx.ArrayToMap[entity.TagResource, string](tagResources, func(tagResource entity.TagResource) string {
+ return tagResource.TagPath
+ })
+
+ tagPaths := maps.Keys(tagPath2Resource)
+ sort.Strings(tagPaths)
+ rc.ResData = tagPaths
+}
+
+// 资源标签关联信息查询
+func (p *TagTree) QueryTagResources(rc *req.Ctx) {
+ var trs []*entity.TagResource
+ p.TagResourceApp.ListByQuery(ginx.BindQuery(rc.GinCtx, new(entity.TagResourceQuery)), &trs)
+ rc.ResData = trs
+}
diff --git a/server/internal/tag/api/vo/tag_tree.go b/server/internal/tag/api/vo/tag_tree.go
index 3dcc60fa..3b340ac6 100644
--- a/server/internal/tag/api/vo/tag_tree.go
+++ b/server/internal/tag/api/vo/tag_tree.go
@@ -1,28 +1,17 @@
package vo
-import "time"
+import (
+ "mayfly-go/internal/tag/domain/entity"
+)
-type TagTreeVO struct {
- Id int `json:"id"`
- Pid int `json:"pid"`
- Name string `json:"name"`
- Code string `json:"code"`
- CodePath string `json:"codePath"`
- Remark string `json:"remark"`
- Creator string `json:"creator"`
- CreateTime time.Time `json:"createTime"`
- Modifier string `json:"modifier"`
- UpdateTime time.Time `json:"updateTime"`
-}
-
-type TagTreeVOS []TagTreeVO
+type TagTreeVOS []*entity.TagTree
type TagTreeItem struct {
- TagTreeVO
+ *entity.TagTree
Children []TagTreeItem `json:"children"`
}
-func (m *TagTreeVOS) ToTrees(pid int) []TagTreeItem {
+func (m *TagTreeVOS) ToTrees(pid uint64) []TagTreeItem {
var resourceTree []TagTreeItem
list := m.findChildren(pid)
@@ -31,15 +20,15 @@ func (m *TagTreeVOS) ToTrees(pid int) []TagTreeItem {
}
for _, v := range list {
- Children := m.ToTrees(int(v.Id))
+ Children := m.ToTrees(v.Id)
resourceTree = append(resourceTree, TagTreeItem{v, Children})
}
return resourceTree
}
-func (m *TagTreeVOS) findChildren(pid int) []TagTreeVO {
- child := []TagTreeVO{}
+func (m *TagTreeVOS) findChildren(pid uint64) []*entity.TagTree {
+ child := []*entity.TagTree{}
for _, v := range *m {
if v.Pid == pid {
diff --git a/server/internal/tag/application/application.go b/server/internal/tag/application/application.go
index 3615125a..dd988289 100644
--- a/server/internal/tag/application/application.go
+++ b/server/internal/tag/application/application.go
@@ -1,21 +1,14 @@
package application
import (
- dbapp "mayfly-go/internal/db/application"
- machineapp "mayfly-go/internal/machine/application"
- mongoapp "mayfly-go/internal/mongo/application"
- redisapp "mayfly-go/internal/redis/application"
"mayfly-go/internal/tag/infrastructure/persistence"
)
var (
tagTreeApp TagTree = newTagTreeApp(
persistence.GetTagTreeRepo(),
+ GetTagResourceApp(),
persistence.GetTagTreeTeamRepo(),
- machineapp.GetMachineApp(),
- redisapp.GetRedisApp(),
- dbapp.GetDbApp(),
- mongoapp.GetMongoApp(),
)
teamApp Team = newTeamApp(
@@ -23,6 +16,8 @@ var (
persistence.GetTeamMemberRepo(),
persistence.GetTagTreeTeamRepo(),
)
+
+ tagResourceApp TagResource = newTagResourceApp(persistence.GetTagResourceRepo())
)
func GetTagTreeApp() TagTree {
@@ -32,3 +27,7 @@ func GetTagTreeApp() TagTree {
func GetTeamApp() Team {
return teamApp
}
+
+func GetTagResourceApp() TagResource {
+ return tagResourceApp
+}
diff --git a/server/internal/tag/application/tag_resource.go b/server/internal/tag/application/tag_resource.go
new file mode 100644
index 00000000..00aea752
--- /dev/null
+++ b/server/internal/tag/application/tag_resource.go
@@ -0,0 +1,27 @@
+package application
+
+import (
+ "mayfly-go/internal/tag/domain/entity"
+ "mayfly-go/internal/tag/domain/repository"
+ "mayfly-go/pkg/base"
+)
+
+type TagResource interface {
+ base.App[*entity.TagResource]
+
+ ListByQuery(condition *entity.TagResourceQuery, toEntity any)
+}
+
+func newTagResourceApp(tagResourceRepo repository.TagResource) TagResource {
+ tagResourceApp := &tagResourceAppImpl{}
+ tagResourceApp.Repo = tagResourceRepo
+ return tagResourceApp
+}
+
+type tagResourceAppImpl struct {
+ base.AppImpl[*entity.TagResource, repository.TagResource]
+}
+
+func (tr *tagResourceAppImpl) ListByQuery(condition *entity.TagResourceQuery, toEntity any) {
+ tr.Repo.SelectByCondition(condition, toEntity)
+}
diff --git a/server/internal/tag/application/tag_tree.go b/server/internal/tag/application/tag_tree.go
index 1418c7c3..a38266e4 100644
--- a/server/internal/tag/application/tag_tree.go
+++ b/server/internal/tag/application/tag_tree.go
@@ -2,21 +2,16 @@ package application
import (
"context"
- dbapp "mayfly-go/internal/db/application"
- dbentity "mayfly-go/internal/db/domain/entity"
- machineapp "mayfly-go/internal/machine/application"
- machineentity "mayfly-go/internal/machine/domain/entity"
- mongoapp "mayfly-go/internal/mongo/application"
- mongoentity "mayfly-go/internal/mongo/domain/entity"
- redisapp "mayfly-go/internal/redis/application"
- redisentity "mayfly-go/internal/redis/domain/entity"
+ "mayfly-go/internal/common/consts"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/base"
+ "mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx"
- "mayfly-go/pkg/global"
- "mayfly-go/pkg/gormx"
+ "mayfly-go/pkg/utils/collx"
"strings"
+
+ "golang.org/x/exp/maps"
)
type TagTree interface {
@@ -28,38 +23,41 @@ type TagTree interface {
Delete(ctx context.Context, id uint64) error
- // 获取账号id拥有的可访问的标签id
- ListTagIdByAccountId(accountId uint64) []uint64
+ // 获取指定账号有权限操作的资源信息列表
+ // @param accountId 账号id
+ // @param resourceType 资源类型
+ // @param tagPath 访问指定的标签路径下关联的资源
+ GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagResource
- // 获取以指定tagPath数组开头的所有标签id
- ListTagIdByPath(tagPath ...string) []uint64
+ // 获取指定账号有权限操作的资源codes
+ GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string
+
+ // 关联资源
+ // @resourceCode 资源唯一编号
+ // @resourceType 资源类型
+ // @tagIds 资源关联的标签
+ RelateResource(ctx context.Context, resourceCode string, resourceType int8, tagIds []uint64) error
+
+ // 根据资源信息获取对应的标签路径列表
+ ListTagPathByResource(resourceType int8, resourceCode string) []string
// 根据tagPath获取自身及其所有子标签信息
- ListTagByPath(tagPath ...string) []entity.TagTree
+ ListTagByPath(tagPath ...string) []*entity.TagTree
// 根据账号id获取其可访问标签信息
ListTagByAccountId(accountId uint64) []string
- // 查询账号id可访问的资源相关联的标签信息
- // @param model对应资源的实体信息,如Machinie、Db等等
- ListTagByAccountIdAndResource(accountId uint64, model any) []string
-
// 账号是否有权限访问该标签关联的资源信息
- CanAccess(accountId uint64, tagPath string) error
+ CanAccess(accountId uint64, tagPath ...string) error
}
func newTagTreeApp(tagTreeRepo repository.TagTree,
+ tagResourceApp TagResource,
tagTreeTeamRepo repository.TagTreeTeam,
- machineApp machineapp.Machine,
- redisApp redisapp.Redis,
- dbApp dbapp.Db,
- mongoApp mongoapp.Mongo) TagTree {
+) TagTree {
tagTreeApp := &tagTreeAppImpl{
tagTreeTeamRepo: tagTreeTeamRepo,
- machineApp: machineApp,
- redisApp: redisApp,
- dbApp: dbApp,
- mongoApp: mongoApp,
+ tagResourceApp: tagResourceApp,
}
tagTreeApp.Repo = tagTreeRepo
return tagTreeApp
@@ -69,13 +67,11 @@ type tagTreeAppImpl struct {
base.AppImpl[*entity.TagTree, repository.TagTree]
tagTreeTeamRepo repository.TagTreeTeam
- machineApp machineapp.Machine
- redisApp redisapp.Redis
- mongoApp mongoapp.Mongo
- dbApp dbapp.Db
+ tagResourceApp TagResource
}
func (p *tagTreeAppImpl) Save(ctx context.Context, tag *entity.TagTree) error {
+ accountId := contextx.GetLoginAccount(ctx).Id
// 新建项目树节点信息
if tag.Id == 0 {
if strings.Contains(tag.Code, entity.CodePathSeparator) {
@@ -86,10 +82,18 @@ func (p *tagTreeAppImpl) Save(ctx context.Context, tag *entity.TagTree) error {
if err != nil {
return errorx.NewBiz("父节点不存在")
}
+ if p.tagResourceApp.CountByCond(&entity.TagResource{TagId: tag.Pid}) > 0 {
+ return errorx.NewBiz("该父标签已关联资源, 无法添加子标签")
+ }
+
tag.CodePath = parentTag.CodePath + tag.Code + entity.CodePathSeparator
} else {
tag.CodePath = tag.Code + entity.CodePathSeparator
}
+ if err := p.CanAccess(accountId, tag.CodePath); err != nil {
+ return errorx.NewBiz("无权添加该标签")
+ }
+
// 判断该路径是否存在
var hasLikeTags []entity.TagTree
p.GetRepo().SelectByCondition(&entity.TagTreeQuery{CodePathLike: tag.CodePath}, &hasLikeTags)
@@ -110,52 +114,114 @@ func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity an
p.GetRepo().SelectByCondition(condition, toEntity)
}
-func (p *tagTreeAppImpl) ListTagIdByAccountId(accountId uint64) []uint64 {
- // 获取该账号可操作的标签路径
- return p.ListTagIdByPath(p.ListTagByAccountId(accountId)...)
+func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagResource {
+ tagResourceQuery := &entity.TagResourceQuery{
+ ResourceType: resourceType,
+ }
+
+ var tagResources []entity.TagResource
+ var accountTagPaths []string
+
+ if accountId != consts.AdminId {
+ // 获取账号有权限操作的标签路径列表
+ accountTagPaths = p.ListTagByAccountId(accountId)
+ if len(accountTagPaths) == 0 {
+ return tagResources
+ }
+ }
+
+ tagResourceQuery.TagPath = tagPath
+ tagResourceQuery.TagPathLikes = accountTagPaths
+ p.tagResourceApp.ListByQuery(tagResourceQuery, &tagResources)
+ return tagResources
}
-func (p *tagTreeAppImpl) ListTagByPath(tagPaths ...string) []entity.TagTree {
- var tags []entity.TagTree
+func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string {
+ tagResources := p.GetAccountTagResources(accountId, resourceType, tagPath)
+ // resouce code去重
+ code2Resource := collx.ArrayToMap[entity.TagResource, string](tagResources, func(val entity.TagResource) string {
+ return val.ResourceCode
+ })
+
+ return maps.Keys(code2Resource)
+}
+
+func (p *tagTreeAppImpl) RelateResource(ctx context.Context, resourceCode string, resourceType int8, tagIds []uint64) error {
+ var oldTagResources []*entity.TagResource
+ p.tagResourceApp.ListByQuery(&entity.TagResourceQuery{ResourceType: resourceType, ResourceCode: resourceCode}, &oldTagResources)
+
+ var addTagIds, delTagIds []uint64
+ if len(oldTagResources) == 0 {
+ addTagIds = tagIds
+ } else {
+ oldTagIds := collx.ArrayMap[*entity.TagResource, uint64](oldTagResources, func(tr *entity.TagResource) uint64 {
+ return tr.TagId
+ })
+ addTagIds, delTagIds, _ = collx.ArrayCompare[uint64](tagIds, oldTagIds, func(u1, u2 uint64) bool { return u1 == u2 })
+ }
+
+ return p.Tx(ctx, func(ctx context.Context) error {
+ if len(addTagIds) > 0 {
+ addTagResource := make([]*entity.TagResource, 0)
+ for _, tagId := range addTagIds {
+ tag, err := p.GetById(new(entity.TagTree), tagId)
+ if err != nil {
+ return errorx.NewBiz("存在错误标签id")
+ }
+ addTagResource = append(addTagResource, &entity.TagResource{
+ ResourceCode: resourceCode,
+ ResourceType: resourceType,
+ TagId: tagId,
+ TagPath: tag.CodePath,
+ })
+ }
+ if err := p.tagResourceApp.BatchInsert(ctx, addTagResource); err != nil {
+ return err
+ }
+ }
+
+ if len(delTagIds) > 0 {
+ for _, tagId := range delTagIds {
+ cond := &entity.TagResource{ResourceCode: resourceCode, ResourceType: resourceType, TagId: tagId}
+ if err := p.tagResourceApp.DeleteByCond(ctx, cond); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+ })
+}
+
+func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string {
+ var trs []*entity.TagResource
+ p.tagResourceApp.ListByQuery(&entity.TagResourceQuery{ResourceType: resourceType, ResourceCode: resourceCode}, &trs)
+ return collx.ArrayMap(trs, func(tr *entity.TagResource) string {
+ return tr.TagPath
+ })
+}
+
+func (p *tagTreeAppImpl) ListTagByPath(tagPaths ...string) []*entity.TagTree {
+ var tags []*entity.TagTree
p.GetRepo().SelectByCondition(&entity.TagTreeQuery{CodePathLikes: tagPaths}, &tags)
return tags
}
-func (p *tagTreeAppImpl) ListTagIdByPath(tagPaths ...string) []uint64 {
- tagIds := make([]uint64, 0)
- if len(tagPaths) == 0 {
- return tagIds
- }
-
- tags := p.ListTagByPath(tagPaths...)
- for _, v := range tags {
- tagIds = append(tagIds, v.Id)
- }
- return tagIds
-}
-
func (p *tagTreeAppImpl) ListTagByAccountId(accountId uint64) []string {
return p.tagTreeTeamRepo.SelectTagPathsByAccountId(accountId)
}
-func (p *tagTreeAppImpl) ListTagByAccountIdAndResource(accountId uint64, entity any) []string {
- var res []string
-
- tagIds := p.ListTagIdByAccountId(accountId)
- if len(tagIds) == 0 {
- return res
+func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath ...string) error {
+ if accountId == consts.AdminId {
+ return nil
}
-
- global.Db.Model(entity).Distinct("tag_path").Where("tag_id in ?", tagIds).Scopes(gormx.UndeleteScope).Order("tag_path asc").Find(&res)
- return res
-}
-
-func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath string) error {
tagPaths := p.ListTagByAccountId(accountId)
// 判断该资源标签是否为该账号拥有的标签或其子标签
for _, v := range tagPaths {
- if strings.HasPrefix(tagPath, v) {
- return nil
+ for _, tp := range tagPath {
+ if strings.HasPrefix(tp, v) {
+ return nil
+ }
}
}
@@ -163,21 +229,23 @@ func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath string) error {
}
func (p *tagTreeAppImpl) Delete(ctx context.Context, id uint64) error {
- tagIds := [1]uint64{id}
- if p.machineApp.Count(&machineentity.MachineQuery{TagIds: tagIds[:]}) > 0 {
- return errorx.NewBiz("请先删除该标签关联的机器信息")
+ accountId := contextx.GetLoginAccount(ctx).Id
+ tag, err := p.GetById(new(entity.TagTree), id)
+ if err != nil {
+ return errorx.NewBiz("该标签不存在")
}
- if p.redisApp.Count(&redisentity.RedisQuery{TagIds: tagIds[:]}) > 0 {
- return errorx.NewBiz("请先删除该标签关联的redis信息")
- }
- if p.dbApp.Count(&dbentity.DbQuery{TagIds: tagIds[:]}) > 0 {
- return errorx.NewBiz("请先删除该标签关联的数据库信息")
- }
- if p.mongoApp.Count(&mongoentity.MongoQuery{TagIds: tagIds[:]}) > 0 {
- return errorx.NewBiz("请先删除该标签关联的Mongo信息")
+ if err := p.CanAccess(accountId, tag.CodePath); err != nil {
+ return errorx.NewBiz("您无权删除该标签")
}
- p.DeleteById(ctx, id)
- // 删除该标签关联的团队信息
- return p.tagTreeTeamRepo.DeleteByCond(ctx, &entity.TagTreeTeam{TagId: id})
+ if p.tagResourceApp.CountByCond(&entity.TagResource{TagId: id}) > 0 {
+ return errorx.NewBiz("请先移除该标签关联的资源")
+ }
+
+ return p.Tx(ctx, func(ctx context.Context) error {
+ return p.DeleteById(ctx, id)
+ }, func(ctx context.Context) error {
+ // 删除该标签关联的团队信息
+ return p.tagTreeTeamRepo.DeleteByCond(ctx, &entity.TagTreeTeam{TagId: id})
+ })
}
diff --git a/server/internal/tag/domain/entity/query.go b/server/internal/tag/domain/entity/query.go
index a3ab90dd..d392b2b7 100644
--- a/server/internal/tag/domain/entity/query.go
+++ b/server/internal/tag/domain/entity/query.go
@@ -13,3 +13,16 @@ type TagTreeQuery struct {
CodePathLike string // 标识符路径模糊查询
CodePathLikes []string
}
+
+type TagResourceQuery struct {
+ model.Model
+
+ TagPath string `json:"string"` // 标签路径
+ TagId uint64 `json:"tagId" form:"tagId"`
+ ResourceType int8 `json:"resourceType" form:"resourceType"` // 资源编码
+ ResourceCode string `json:"resourceCode" form:"resourceCode"` // 资源编码
+ ResourceCodes []string // 资源编码列表
+
+ TagPathLike string // 标签路径模糊查询
+ TagPathLikes []string
+}
diff --git a/server/internal/tag/domain/entity/tag_resource.go b/server/internal/tag/domain/entity/tag_resource.go
new file mode 100644
index 00000000..7cdafa8c
--- /dev/null
+++ b/server/internal/tag/domain/entity/tag_resource.go
@@ -0,0 +1,15 @@
+package entity
+
+import (
+ "mayfly-go/pkg/model"
+)
+
+// 标签资源关联
+type TagResource struct {
+ model.Model
+
+ TagId uint64 `json:"tagId"`
+ TagPath string `json:"tagPath"` // 标签路径
+ ResourceCode string `json:"resourceCode"` // 资源标识
+ ResourceType int8 `json:"resourceType"` // 资源类型
+}
diff --git a/server/internal/tag/domain/repository/tag_resource.go b/server/internal/tag/domain/repository/tag_resource.go
new file mode 100644
index 00000000..0c0b23f9
--- /dev/null
+++ b/server/internal/tag/domain/repository/tag_resource.go
@@ -0,0 +1,12 @@
+package repository
+
+import (
+ "mayfly-go/internal/tag/domain/entity"
+ "mayfly-go/pkg/base"
+)
+
+type TagResource interface {
+ base.Repo[*entity.TagResource]
+
+ SelectByCondition(condition *entity.TagResourceQuery, toEntity any, orderBy ...string)
+}
diff --git a/server/internal/tag/infrastructure/persistence/persistence.go b/server/internal/tag/infrastructure/persistence/persistence.go
index 1241cbb4..fdce24b4 100644
--- a/server/internal/tag/infrastructure/persistence/persistence.go
+++ b/server/internal/tag/infrastructure/persistence/persistence.go
@@ -5,6 +5,7 @@ import "mayfly-go/internal/tag/domain/repository"
var (
tagTreeRepo repository.TagTree = newTagTreeRepo()
tagTreeTeamRepo repository.TagTreeTeam = newTagTreeTeamRepo()
+ tagResourceRepo repository.TagResource = newTagResourceRepo()
teamRepo repository.Team = newTeamRepo()
teamMemberRepo repository.TeamMember = newTeamMemberRepo()
)
@@ -17,6 +18,10 @@ func GetTagTreeTeamRepo() repository.TagTreeTeam {
return tagTreeTeamRepo
}
+func GetTagResourceRepo() repository.TagResource {
+ return tagResourceRepo
+}
+
func GetTeamRepo() repository.Team {
return teamRepo
}
diff --git a/server/internal/tag/infrastructure/persistence/tag_resource.go b/server/internal/tag/infrastructure/persistence/tag_resource.go
new file mode 100644
index 00000000..f00cd307
--- /dev/null
+++ b/server/internal/tag/infrastructure/persistence/tag_resource.go
@@ -0,0 +1,65 @@
+package persistence
+
+import (
+ "mayfly-go/internal/tag/domain/entity"
+ "mayfly-go/internal/tag/domain/repository"
+ "mayfly-go/pkg/base"
+ "mayfly-go/pkg/gormx"
+)
+
+type tagResourceRepoImpl struct {
+ base.RepoImpl[*entity.TagResource]
+}
+
+func newTagResourceRepo() repository.TagResource {
+ return &tagResourceRepoImpl{base.RepoImpl[*entity.TagResource]{M: new(entity.TagResource)}}
+}
+
+func (p *tagResourceRepoImpl) SelectByCondition(condition *entity.TagResourceQuery, toEntity any, orderBy ...string) {
+ sql := "SELECT tr.resource_type, tr.resource_code, tr.tag_id, tr.tag_path FROM t_tag_resource tr WHERE tr.is_deleted = 0 "
+
+ params := make([]any, 0)
+
+ if condition.ResourceType != 0 {
+ sql = sql + " AND tr.resource_type = ?"
+ params = append(params, condition.ResourceType)
+ }
+
+ if condition.ResourceCode != "" {
+ sql = sql + " AND tr.resource_code = ?"
+ params = append(params, condition.ResourceCode)
+ }
+
+ if len(condition.ResourceCodes) > 0 {
+ sql = sql + " AND tr.resource_code IN (?)"
+ params = append(params, condition.ResourceCodes)
+ }
+
+ if condition.TagId != 0 {
+ sql = sql + " AND tr.tag_id = ?"
+ params = append(params, condition.TagId)
+ }
+ if condition.TagPath != "" {
+ sql = sql + " AND tr.tag_path = ?"
+ params = append(params, condition.TagPath)
+ }
+ if condition.TagPathLike != "" {
+ sql = sql + " AND tr.tag_path LIKE ?"
+ params = append(params, condition.TagPathLike+"%")
+ }
+ if len(condition.TagPathLikes) > 0 {
+ sql = sql + " AND ("
+ for i, v := range condition.TagPathLikes {
+ if i == 0 {
+ sql = sql + "tr.tag_path LIKE ?"
+ } else {
+ sql = sql + " OR tr.tag_path LIKE ?"
+ }
+ params = append(params, v+"%")
+ }
+ sql = sql + ")"
+ }
+
+ sql = sql + " ORDER BY tr.tag_path"
+ gormx.GetListBySql2Model(sql, toEntity, params...)
+}
diff --git a/server/internal/tag/infrastructure/persistence/tag_tree.go b/server/internal/tag/infrastructure/persistence/tag_tree.go
index abec9e48..67edaf17 100644
--- a/server/internal/tag/infrastructure/persistence/tag_tree.go
+++ b/server/internal/tag/infrastructure/persistence/tag_tree.go
@@ -1,12 +1,10 @@
package persistence
import (
- "fmt"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/base"
"mayfly-go/pkg/gormx"
- "strings"
)
type tagTreeRepoImpl struct {
@@ -19,37 +17,40 @@ func newTagTreeRepo() repository.TagTree {
func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEntity any, orderBy ...string) {
sql := "SELECT DISTINCT(p.id), p.pid, p.code, p.code_path, p.name, p.remark, p.create_time, p.creator, p.update_time, p.modifier FROM t_tag_tree p WHERE p.is_deleted = 0 "
+
+ params := make([]any, 0)
if condition.Name != "" {
- sql = sql + " AND p.name LIKE '%" + condition.Name + "%'"
+ sql = sql + " AND p.name LIKE ?"
+ params = append(params, "%"+condition.Name+"%")
}
if condition.CodePath != "" {
- sql = fmt.Sprintf("%s AND p.code_path = '%s'", sql, condition.CodePath)
+ sql = sql + " AND p.code_path = ?"
+ params = append(params, condition.CodePath)
}
if len(condition.CodePaths) > 0 {
- strCodePaths := make([]string, 0)
- // 将字符串用''包裹
- for _, v := range condition.CodePaths {
- strCodePaths = append(strCodePaths, fmt.Sprintf("'%s'", v))
- }
- sql = fmt.Sprintf("%s AND p.code_path IN (%s)", sql, strings.Join(strCodePaths, ","))
+ sql = sql + " AND p.code_path IN (?)"
+ params = append(params, condition.CodePaths)
}
if condition.CodePathLike != "" {
- sql = fmt.Sprintf("%s AND p.code_path LIKE '%s'", sql, condition.CodePathLike+"%")
+ sql = sql + " AND p.code_path LIKE ?"
+ params = append(params, condition.CodePathLike+"%")
}
if condition.Pid != 0 {
- sql = fmt.Sprintf("%s AND p.pid = %d ", sql, condition.Pid)
+ sql = sql + " AND p.pid = ?"
+ params = append(params, condition.Pid)
}
if len(condition.CodePathLikes) > 0 {
sql = sql + " AND ("
for i, v := range condition.CodePathLikes {
if i == 0 {
- sql = sql + fmt.Sprintf("p.code_path LIKE '%s'", v+"%")
+ sql = sql + "p.code_path LIKE ?"
} else {
- sql = sql + fmt.Sprintf(" OR p.code_path LIKE '%s'", v+"%")
+ sql = sql + " OR p.code_path LIKE ?"
}
+ params = append(params, v+"%")
}
sql = sql + ")"
}
sql = sql + " ORDER BY p.code_path"
- gormx.GetListBySql2Model(sql, toEntity)
+ gormx.GetListBySql2Model(sql, toEntity, params...)
}
diff --git a/server/internal/tag/router/tag_tree.go b/server/internal/tag/router/tag_tree.go
index 78f7a59a..81663ae4 100644
--- a/server/internal/tag/router/tag_tree.go
+++ b/server/internal/tag/router/tag_tree.go
@@ -10,7 +10,8 @@ import (
func InitTagTreeRouter(router *gin.RouterGroup) {
m := &api.TagTree{
- TagTreeApp: application.GetTagTreeApp(),
+ TagTreeApp: application.GetTagTreeApp(),
+ TagResourceApp: application.GetTagResourceApp(),
}
tagTree := router.Group("/tag-trees")
@@ -22,12 +23,13 @@ func InitTagTreeRouter(router *gin.RouterGroup) {
// 根据条件获取标签
req.NewGet("query", m.ListByQuery),
- // 获取登录账号拥有的标签信息
- req.NewGet("account-has", m.GetAccountTags),
-
req.NewPost("", m.SaveTagTree).Log(req.NewLogSave("标签树-保存信息")).RequiredPermissionCode("tag:save"),
req.NewDelete(":id", m.DelTagTree).Log(req.NewLogSave("标签树-删除信息")).RequiredPermissionCode("tag:del"),
+
+ req.NewGet("/resources/:rtype/tag-paths", m.TagResources),
+
+ req.NewGet("/resources", m.QueryTagResources),
}
req.BatchSetGroup(tagTree, reqs[:])
diff --git a/server/pkg/base/app.go b/server/pkg/base/app.go
index 3168f3c8..cca4e607 100644
--- a/server/pkg/base/app.go
+++ b/server/pkg/base/app.go
@@ -2,6 +2,9 @@ package base
import (
"context"
+ "fmt"
+ "mayfly-go/pkg/contextx"
+ "mayfly-go/pkg/global"
"mayfly-go/pkg/model"
"gorm.io/gorm"
@@ -62,6 +65,9 @@ type App[T model.ModelI] interface {
// 根据指定条件统计model表的数量, cond为条件可以为map等
CountByCond(cond any) int64
+
+ // 执行事务操作
+ Tx(ctx context.Context, funcs ...func(context.Context) error) (err error)
}
// 基础application接口实现
@@ -162,3 +168,28 @@ func (ai *AppImpl[T, R]) ListByCondOrder(cond any, list any, order ...string) er
func (ai *AppImpl[T, R]) CountByCond(cond any) int64 {
return ai.GetRepo().CountByCond(cond)
}
+
+// 执行事务操作
+func (ai *AppImpl[T, R]) Tx(ctx context.Context, funcs ...func(context.Context) error) (err error) {
+ tx := global.Db.Begin()
+ dbCtx := contextx.WithDb(ctx, tx)
+
+ defer func() {
+ // 移除当前已执行完成的的数据库事务实例
+ contextx.RmDb(ctx)
+ if r := recover(); r != nil {
+ tx.Rollback()
+ err = fmt.Errorf("%v", err)
+ }
+ }()
+
+ for _, f := range funcs {
+ err = f(dbCtx)
+ if err != nil {
+ tx.Rollback()
+ return
+ }
+ }
+ err = tx.Commit().Error
+ return
+}
diff --git a/server/pkg/base/repo.go b/server/pkg/base/repo.go
index d4901524..da4ae218 100644
--- a/server/pkg/base/repo.go
+++ b/server/pkg/base/repo.go
@@ -6,6 +6,7 @@ import (
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/gormx"
"mayfly-go/pkg/model"
+ "mayfly-go/pkg/utils/anyx"
"gorm.io/gorm"
)
@@ -70,10 +71,13 @@ type Repo[T model.ModelI] interface {
// 基础repo接口
type RepoImpl[T model.ModelI] struct {
- M any // 模型实例
+ M T // 模型实例
}
func (br *RepoImpl[T]) Insert(ctx context.Context, e T) error {
+ if db := contextx.GetDb(ctx); db != nil {
+ return br.InsertWithDb(ctx, db, e)
+ }
return gormx.Insert(br.setBaseInfo(ctx, e))
}
@@ -82,6 +86,10 @@ func (br *RepoImpl[T]) InsertWithDb(ctx context.Context, db *gorm.DB, e T) error
}
func (br *RepoImpl[T]) BatchInsert(ctx context.Context, es []T) error {
+ if db := contextx.GetDb(ctx); db != nil {
+ return br.BatchInsertWithDb(ctx, db, es)
+ }
+
for _, e := range es {
br.setBaseInfo(ctx, e)
}
@@ -97,6 +105,10 @@ func (br *RepoImpl[T]) BatchInsertWithDb(ctx context.Context, db *gorm.DB, es []
}
func (br *RepoImpl[T]) UpdateById(ctx context.Context, e T) error {
+ if db := contextx.GetDb(ctx); db != nil {
+ return br.UpdateByIdWithDb(ctx, db, e)
+ }
+
return gormx.UpdateById(br.setBaseInfo(ctx, e))
}
@@ -109,6 +121,10 @@ func (br *RepoImpl[T]) Updates(cond any, udpateFields map[string]any) error {
}
func (br *RepoImpl[T]) DeleteById(ctx context.Context, id uint64) error {
+ if db := contextx.GetDb(ctx); db != nil {
+ return br.DeleteByIdWithDb(ctx, db, id)
+ }
+
return gormx.DeleteById(br.getModel(), id)
}
@@ -117,6 +133,10 @@ func (br *RepoImpl[T]) DeleteByIdWithDb(ctx context.Context, db *gorm.DB, id uin
}
func (br *RepoImpl[T]) DeleteByCond(ctx context.Context, cond any) error {
+ if db := contextx.GetDb(ctx); db != nil {
+ return br.DeleteByCondWithDb(ctx, db, cond)
+ }
+
return gormx.DeleteByCond(br.getModel(), cond)
}
@@ -152,8 +172,8 @@ func (br *RepoImpl[T]) CountByCond(cond any) int64 {
}
// 获取表的模型实例
-func (br *RepoImpl[T]) getModel() any {
- biz.IsTrue(br.M != nil, "base.RepoImpl的M字段不能为空")
+func (br *RepoImpl[T]) getModel() T {
+ biz.IsTrue(!anyx.IsBlank(br.M), "base.RepoImpl的M字段不能为空")
return br.M
}
diff --git a/server/pkg/config/app.go b/server/pkg/config/app.go
index d48e12c9..0f11c613 100644
--- a/server/pkg/config/app.go
+++ b/server/pkg/config/app.go
@@ -4,7 +4,7 @@ import "fmt"
const (
AppName = "mayfly-go"
- Version = "v1.5.4"
+ Version = "v1.6.0"
)
func GetAppInfo() string {
diff --git a/server/pkg/contextx/contextx.go b/server/pkg/contextx/contextx.go
index bf5f53a6..c51f9331 100644
--- a/server/pkg/contextx/contextx.go
+++ b/server/pkg/contextx/contextx.go
@@ -3,7 +3,10 @@ package contextx
import (
"context"
"mayfly-go/pkg/model"
+ "mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/stringx"
+
+ "gorm.io/gorm"
)
type CtxKey string
@@ -11,6 +14,7 @@ type CtxKey string
const (
LoginAccountKey CtxKey = "loginAccount"
TraceIdKey CtxKey = "traceId"
+ DbKey CtxKey = "db"
)
func NewLoginAccount(la *model.LoginAccount) context.Context {
@@ -44,3 +48,30 @@ func GetTraceId(ctx context.Context) string {
}
return ""
}
+
+// 将事务db放置context中,使用stack保存。以便多个方法调用实现方法内部各自的事务操作
+func WithDb(ctx context.Context, db *gorm.DB) context.Context {
+ if dbStack, ok := ctx.Value(DbKey).(*collx.Stack[*gorm.DB]); ok {
+ dbStack.Push(db)
+ return ctx
+ }
+ dbStack := new(collx.Stack[*gorm.DB])
+ dbStack.Push(db)
+
+ return context.WithValue(ctx, DbKey, dbStack)
+}
+
+// 获取当前操作的栈顶事务数据库实例
+func GetDb(ctx context.Context) *gorm.DB {
+ if dbStack, ok := ctx.Value(DbKey).(*collx.Stack[*gorm.DB]); ok {
+ return dbStack.Top()
+ }
+ return nil
+}
+
+func RmDb(ctx context.Context) *gorm.DB {
+ if dbStack, ok := ctx.Value(DbKey).(*collx.Stack[*gorm.DB]); ok {
+ return dbStack.Pop()
+ }
+ return nil
+}
diff --git a/server/pkg/ginx/ginx.go b/server/pkg/ginx/ginx.go
index 04233458..77e068a7 100644
--- a/server/pkg/ginx/ginx.go
+++ b/server/pkg/ginx/ginx.go
@@ -103,12 +103,8 @@ func ErrorRes(g *gin.Context, err any) {
switch t := err.(type) {
case errorx.BizError:
g.JSON(http.StatusOK, model.Error(t))
- case error:
- g.JSON(http.StatusOK, model.ServerError())
- case string:
- g.JSON(http.StatusOK, model.ServerError())
default:
- logx.Errorf("未知错误: %v", t)
+ logx.ErrorTrace("服务器错误", t)
g.JSON(http.StatusOK, model.ServerError())
}
}
diff --git a/server/pkg/logx/logx.go b/server/pkg/logx/logx.go
index 43bd3807..1643bfda 100644
--- a/server/pkg/logx/logx.go
+++ b/server/pkg/logx/logx.go
@@ -106,8 +106,17 @@ func Errorf(format string, args ...any) {
}
// 错误记录,并将堆栈信息添加至msg里,默认记录10个堆栈信息
-func ErrorTrace(msg string, err error) {
- Log(context.Background(), slog.LevelError, fmt.Sprintf(msg+" %s\n%s", err.Error(), runtimex.StatckStr(2, 10)))
+func ErrorTrace(msg string, err any) {
+ errMsg := ""
+ switch t := err.(type) {
+ case error:
+ errMsg = t.Error()
+ case string:
+ errMsg = t
+ default:
+ errMsg = fmt.Sprintf("%v", t)
+ }
+ Log(context.Background(), slog.LevelError, fmt.Sprintf(msg+"\n%s\n%s", errMsg, runtimex.StatckStr(2, 10)))
}
func ErrorWithFields(ctx context.Context, msg string, mapFields map[string]any) {
diff --git a/server/pkg/model/model.go b/server/pkg/model/model.go
index 6a5f4d0b..cb2150bb 100644
--- a/server/pkg/model/model.go
+++ b/server/pkg/model/model.go
@@ -5,11 +5,12 @@ import (
)
const (
- IdColumn = "id"
- DeletedColumn = "is_deleted" // 删除字段
- DeleteTimeColumn = "delete_time"
- ModelDeleted int8 = 1
- ModelUndeleted int8 = 0
+ IdColumn = "id"
+ DeletedColumn = "is_deleted" // 删除字段
+ DeleteTimeColumn = "delete_time"
+
+ ModelDeleted int8 = 1
+ ModelUndeleted int8 = 0
)
// 实体接口
diff --git a/server/pkg/req/req_ctx.go b/server/pkg/req/req_ctx.go
index c03630d5..c7a71c02 100644
--- a/server/pkg/req/req_ctx.go
+++ b/server/pkg/req/req_ctx.go
@@ -21,6 +21,7 @@ type Ctx struct {
GinCtx *gin.Context // gin context
ReqParam any // 请求参数,主要用于记录日志
+ LogExtra any // 日志额外参数,主要用于系统日志定制化展示
ResData any // 响应结果
Err any // 请求错误
timed int64 // 执行时间
diff --git a/server/pkg/utils/collx/stack.go b/server/pkg/utils/collx/stack.go
new file mode 100644
index 00000000..650f5509
--- /dev/null
+++ b/server/pkg/utils/collx/stack.go
@@ -0,0 +1,41 @@
+package collx
+
+type Stack[T any] struct {
+ items []T
+}
+
+// 入栈
+func (s *Stack[T]) Push(item T) {
+ s.items = append(s.items, item)
+}
+
+// 出栈
+func (s *Stack[T]) Pop() T {
+ var item T
+ if len(s.items) == 0 {
+ return item
+ }
+ lastIndex := len(s.items) - 1
+ item = s.items[lastIndex]
+ s.items = s.items[:lastIndex]
+ return item
+}
+
+// 获取栈顶元素
+func (s *Stack[T]) Top() T {
+ var item T
+ if len(s.items) == 0 {
+ return item
+ }
+ return s.items[len(s.items)-1]
+}
+
+// 检查栈是否为空
+func (s *Stack[T]) IsEmpty() bool {
+ return len(s.items) == 0
+}
+
+// 返回栈的大小
+func (s *Stack[T]) Size() int {
+ return len(s.items)
+}
diff --git a/server/resources/data/mayfly-go.sqlite b/server/resources/data/mayfly-go.sqlite
index 616eda293a18f990532791dc6dcadf84f61cde83..ae6548ede9a3e2f338e5cb2414d8c578b42fb216 100644
GIT binary patch
delta 814
zcma)4T}TvB6rMBV&g{<4j=QFfxt8Nk+LC{iP)eyFk*Ju(7Kp;kxI4LvyF2T)mX90$
zBod(waUrrG#Frp978>Yj?LkN)*AUYl(xSJb?4^1MoAHnIsmr~Wd(QdJx!?Kjjm507
zq;<^cD8MkRTwgn*YrOO%6g(5h(n%x4u%_0$%tT)o
zfJ>xiY9E@hI4d#F8BTTB?`v@phG-lHG#yw4dJb_9DFHaIO{_o@F%^c&yP_3x5{|a-
z>Emst$D&=IIX24#MiLDe)oDr+4eTUy-SUK;WFKH(^rDN}w-ij9vvo==S62r^LR9Js
z3^f)|oYM)T-CA=yeg{x|5sFdl1LUIEd$6H!2mYBJJf2T+Sy?brjn4MKo5ZJ?E1dOgQYhP~GuK2TwWgB_Go
delta 355
zcmZoTz}?WmJwaMfoPmKs2Z&)He4>sqqxi;z3H)rbKrwNN&ENR>HZXHC9GuL4U>8?Q
zi4eQEs3>En_w^66FiVOX
z8Zvg-Pd|{uD8Bt-0^<}$4mJfguot&4xyKk=$O~jMb2>8cZr};wblfZ`V9d$VCZNDR
z-8GWQX8OF#j6l7KjDpkmWH9ng@4m<=G`%l`NeJZ9YaloJP7lapRGj>Qk$ro?ZALam
YkfAY*EG@or?AyOzVDw>HmcWz%0AT%W)c^nh
diff --git a/server/resources/script/sql/mayfly-go.sql b/server/resources/script/sql/mayfly-go.sql
index de36dc34..205178a7 100644
--- a/server/resources/script/sql/mayfly-go.sql
+++ b/server/resources/script/sql/mayfly-go.sql
@@ -43,8 +43,6 @@ CREATE TABLE `t_db` (
`name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
`database` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
`remark` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注,描述等',
- `tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
- `tag_path` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '标签路径',
`instance_id` bigint(20) NOT NULL COMMENT '数据库实例 ID',
`create_time` datetime DEFAULT NULL,
`creator_id` bigint(20) DEFAULT NULL,
@@ -157,8 +155,6 @@ CREATE TABLE `t_machine` (
`enable_recorder` tinyint(2) DEFAULT NULL COMMENT '是否启用终端回放记录',
`status` tinyint(2) NOT NULL COMMENT '状态: 1:启用; -1:禁用',
`remark` varchar(255) DEFAULT NULL,
- `tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
- `tag_path` varchar(255) DEFAULT NULL COMMENT '标签路径',
`need_monitor` tinyint(2) DEFAULT NULL,
`create_time` datetime NOT NULL,
`creator` varchar(16) DEFAULT NULL,
@@ -168,8 +164,7 @@ CREATE TABLE `t_machine` (
`modifier_id` bigint(32) DEFAULT NULL,
`is_deleted` tinyint(8) NOT NULL DEFAULT 0,
`delete_time` datetime DEFAULT NULL,
- PRIMARY KEY (`id`),
- KEY `idx_path` (`tag_path`) USING BTREE
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器信息';
-- ----------------------------
@@ -309,6 +304,21 @@ CREATE TABLE `t_machine_cron_job_relate` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器计划任务关联表';
+DROP TABLE IF EXISTS `t_machine_term_op`;
+CREATE TABLE `t_machine_term_op` (
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+ `machine_id` bigint NOT NULL COMMENT '机器id',
+ `username` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '登录用户名',
+ `record_file_path` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '终端回放文件路径',
+ `creator_id` bigint unsigned DEFAULT NULL,
+ `creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
+ `create_time` datetime NOT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `is_deleted` tinyint DEFAULT '0',
+ `delete_time` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器终端操作记录表';
+
-- ----------------------------
-- Table structure for t_mongo
-- ----------------------------
@@ -318,8 +328,6 @@ CREATE TABLE `t_mongo` (
`name` varchar(36) NOT NULL COMMENT '名称',
`uri` varchar(255) NOT NULL COMMENT '连接uri',
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
- `tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
- `tag_path` varchar(255) DEFAULT NULL COMMENT '标签路径',
`create_time` datetime NOT NULL,
`creator_id` bigint(20) DEFAULT NULL,
`creator` varchar(36) DEFAULT NULL,
@@ -351,8 +359,6 @@ CREATE TABLE `t_redis` (
`mode` varchar(32) DEFAULT NULL,
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`remark` varchar(125) DEFAULT NULL,
- `tag_id` bigint(20) DEFAULT NULL COMMENT '标签id',
- `tag_path` varchar(255) DEFAULT NULL COMMENT '标签路径',
`creator` varchar(32) DEFAULT NULL,
`creator_id` bigint(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
@@ -361,8 +367,7 @@ CREATE TABLE `t_redis` (
`update_time` datetime DEFAULT NULL,
`is_deleted` tinyint(8) NOT NULL DEFAULT 0,
`delete_time` datetime DEFAULT NULL,
- PRIMARY KEY (`id`),
- KEY `idx_tag_path` (`tag_path`)
+ PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='redis信息';
-- ----------------------------
@@ -763,6 +768,26 @@ CREATE TABLE `t_tag_tree_team` (
KEY `idx_tag_id` (`tag_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签树团队关联信息';
+DROP TABLE IF EXISTS `t_tag_resource`;
+CREATE TABLE `t_tag_resource` (
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+ `tag_id` bigint NOT NULL,
+ `tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标签路径',
+ `resource_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '资源编码',
+ `resource_type` tinyint NOT NULL COMMENT '资源类型',
+ `create_time` datetime NOT NULL,
+ `creator_id` bigint NOT NULL,
+ `creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `update_time` datetime NOT NULL,
+ `modifier_id` bigint NOT NULL,
+ `modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `is_deleted` tinyint DEFAULT '0',
+ `delete_time` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_tag_path` (`tag_path`(100)) USING BTREE,
+ KEY `idx_resource_code` (`resource_code`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签资源关联表';
+
-- ----------------------------
-- Records of t_tag_tree_team
-- ----------------------------
diff --git a/server/resources/script/sql/v1.6.0.sql b/server/resources/script/sql/v1.6.0.sql
new file mode 100644
index 00000000..8fd9d5ee
--- /dev/null
+++ b/server/resources/script/sql/v1.6.0.sql
@@ -0,0 +1,194 @@
+begin;
+
+DROP TABLE IF EXISTS `t_tag_resource`;
+CREATE TABLE `t_tag_resource` (
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+ `tag_id` bigint NOT NULL,
+ `tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标签路径',
+ `resource_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '资源编码',
+ `resource_type` tinyint NOT NULL COMMENT '资源类型',
+ `create_time` datetime NOT NULL,
+ `creator_id` bigint NOT NULL,
+ `creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `update_time` datetime NOT NULL,
+ `modifier_id` bigint NOT NULL,
+ `modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
+ `is_deleted` tinyint DEFAULT '0',
+ `delete_time` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_tag_path` (`tag_path`(100)) USING BTREE,
+ KEY `idx_resource_code` (`resource_code`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签资源关联表';
+
+ALTER TABLE t_machine ADD COLUMN code varchar(32) NULL AFTER id;
+CREATE INDEX idx_code USING BTREE ON t_machine (code);
+UPDATE t_machine SET code = id;
+INSERT
+ INTO
+ t_tag_resource (`tag_id`,
+ `tag_path`,
+ `resource_code`,
+ `resource_type`,
+ `create_time`,
+ `creator_id`,
+ `creator`,
+ `update_time`,
+ `modifier_id`,
+ `modifier`,
+ `is_deleted`,
+ `delete_time`)
+SELECT
+ `tag_id`,
+ `tag_path`,
+ `code`,
+ "1",
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ 0,
+ NULL
+FROM
+ t_machine
+WHERE
+ is_deleted = 0;
+
+
+ALTER TABLE t_db ADD COLUMN code varchar(32) NULL AFTER id;
+CREATE INDEX idx_code USING BTREE ON t_db (code);
+UPDATE t_db SET code = id;
+INSERT
+ INTO
+ t_tag_resource (`tag_id`,
+ `tag_path`,
+ `resource_code`,
+ `resource_type`,
+ `create_time`,
+ `creator_id`,
+ `creator`,
+ `update_time`,
+ `modifier_id`,
+ `modifier`,
+ `is_deleted`,
+ `delete_time`)
+SELECT
+ `tag_id`,
+ `tag_path`,
+ `code`,
+ "2",
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ 0,
+ NULL
+FROM
+ t_db
+WHERE
+ is_deleted = 0;
+
+
+ALTER TABLE t_redis ADD COLUMN code varchar(32) NULL AFTER id;
+CREATE INDEX idx_code USING BTREE ON t_redis (code);
+UPDATE t_redis SET code = id;
+INSERT
+ INTO
+ t_tag_resource (`tag_id`,
+ `tag_path`,
+ `resource_code`,
+ `resource_type`,
+ `create_time`,
+ `creator_id`,
+ `creator`,
+ `update_time`,
+ `modifier_id`,
+ `modifier`,
+ `is_deleted`,
+ `delete_time`)
+SELECT
+ `tag_id`,
+ `tag_path`,
+ `code`,
+ "3",
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ 0,
+ NULL
+FROM
+ t_redis
+WHERE
+ is_deleted = 0;
+
+
+ALTER TABLE t_mongo ADD COLUMN code varchar(32) NULL AFTER id;
+CREATE INDEX idx_code USING BTREE ON t_mongo (code);
+UPDATE t_mongo SET code = id;
+INSERT
+ INTO
+ t_tag_resource (`tag_id`,
+ `tag_path`,
+ `resource_code`,
+ `resource_type`,
+ `create_time`,
+ `creator_id`,
+ `creator`,
+ `update_time`,
+ `modifier_id`,
+ `modifier`,
+ `is_deleted`,
+ `delete_time`)
+SELECT
+ `tag_id`,
+ `tag_path`,
+ `code`,
+ "4",
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ '2023-08-30 15:04:07',
+ 1,
+ 'admin',
+ 0,
+ NULL
+FROM
+ t_mongo
+WHERE
+ is_deleted = 0;
+
+ALTER TABLE t_machine DROP COLUMN tag_id;
+ALTER TABLE t_machine DROP COLUMN tag_path;
+
+ALTER TABLE t_db DROP COLUMN tag_id;
+ALTER TABLE t_db DROP COLUMN tag_path;
+
+ALTER TABLE t_redis DROP COLUMN tag_id;
+ALTER TABLE t_redis DROP COLUMN tag_path;
+
+ALTER TABLE t_mongo DROP COLUMN tag_id;
+ALTER TABLE t_mongo DROP COLUMN tag_path;
+
+-- 机器终端操作记录表
+DROP TABLE IF EXISTS `t_machine_term_op`;
+CREATE TABLE `t_machine_term_op` (
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+ `machine_id` bigint NOT NULL COMMENT '机器id',
+ `username` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '登录用户名',
+ `record_file_path` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '终端回放文件路径',
+ `creator_id` bigint unsigned DEFAULT NULL,
+ `creator` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
+ `create_time` datetime NOT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `is_deleted` tinyint DEFAULT '0',
+ `delete_time` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='机器终端操作记录表';
+
+commit;
\ No newline at end of file