mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 16:30:25 +08:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4836a770c4 | ||
| 
						 | 
					e6c89fad1b | ||
| 
						 | 
					dba19b1e66 | ||
| 
						 | 
					4e30bdb7cc | ||
| 
						 | 
					4ac57cd140 | ||
| 
						 | 
					c4d52ce47a | ||
| 
						 | 
					54d0688571 | 
@@ -11,40 +11,40 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
        "@element-plus/icons-vue": "^2.3.2",
 | 
					        "@element-plus/icons-vue": "^2.3.2",
 | 
				
			||||||
        "@logicflow/core": "^2.1.1",
 | 
					        "@logicflow/core": "^2.1.3",
 | 
				
			||||||
        "@logicflow/extension": "^2.1.2",
 | 
					        "@logicflow/extension": "^2.1.5",
 | 
				
			||||||
        "@vueuse/core": "^13.9.0",
 | 
					        "@vueuse/core": "^13.9.0",
 | 
				
			||||||
        "@xterm/addon-fit": "^0.10.0",
 | 
					        "@xterm/addon-fit": "^0.10.0",
 | 
				
			||||||
        "@xterm/addon-search": "^0.15.0",
 | 
					        "@xterm/addon-search": "^0.15.0",
 | 
				
			||||||
        "@xterm/addon-web-links": "^0.11.0",
 | 
					        "@xterm/addon-web-links": "^0.11.0",
 | 
				
			||||||
        "@xterm/xterm": "^5.5.0",
 | 
					        "@xterm/xterm": "^5.5.0",
 | 
				
			||||||
        "asciinema-player": "^3.10.0",
 | 
					        "asciinema-player": "^3.11.1",
 | 
				
			||||||
        "axios": "^1.6.2",
 | 
					        "axios": "^1.6.2",
 | 
				
			||||||
        "clipboard": "^2.0.11",
 | 
					        "clipboard": "^2.0.11",
 | 
				
			||||||
        "crypto-js": "^4.2.0",
 | 
					        "crypto-js": "^4.2.0",
 | 
				
			||||||
        "dayjs": "^1.11.18",
 | 
					        "dayjs": "^1.11.18",
 | 
				
			||||||
        "echarts": "^6.0.0",
 | 
					        "echarts": "^6.0.0",
 | 
				
			||||||
        "element-plus": "^2.11.2",
 | 
					        "element-plus": "^2.11.4",
 | 
				
			||||||
        "js-base64": "^3.7.7",
 | 
					        "js-base64": "^3.7.8",
 | 
				
			||||||
        "jsencrypt": "^3.3.2",
 | 
					        "jsencrypt": "^3.5.4",
 | 
				
			||||||
        "monaco-editor": "^0.52.2",
 | 
					        "monaco-editor": "^0.54.0",
 | 
				
			||||||
        "monaco-sql-languages": "^0.15.1",
 | 
					        "monaco-sql-languages": "^0.15.1",
 | 
				
			||||||
        "monaco-themes": "^0.4.6",
 | 
					        "monaco-themes": "^0.4.7",
 | 
				
			||||||
        "nprogress": "^0.2.0",
 | 
					        "nprogress": "^0.2.0",
 | 
				
			||||||
        "pinia": "^3.0.3",
 | 
					        "pinia": "^3.0.3",
 | 
				
			||||||
        "qrcode.vue": "^3.6.0",
 | 
					        "qrcode.vue": "^3.6.0",
 | 
				
			||||||
        "screenfull": "^6.0.2",
 | 
					        "screenfull": "^6.0.2",
 | 
				
			||||||
        "sortablejs": "^1.15.6",
 | 
					        "sortablejs": "^1.15.6",
 | 
				
			||||||
        "sql-formatter": "^15.6.5",
 | 
					        "sql-formatter": "^15.6.8",
 | 
				
			||||||
        "trzsz": "^1.1.5",
 | 
					        "trzsz": "^1.1.5",
 | 
				
			||||||
        "uuid": "^11.1.0",
 | 
					        "uuid": "^13.0.0",
 | 
				
			||||||
        "vue": "^v3.6.0-alpha.2",
 | 
					        "vue": "^v3.5.22",
 | 
				
			||||||
        "vue-i18n": "^11.1.11",
 | 
					        "vue-i18n": "^11.1.12",
 | 
				
			||||||
        "vue-router": "^4.5.1",
 | 
					        "vue-router": "^4.6.3",
 | 
				
			||||||
        "vuedraggable": "^4.1.0"
 | 
					        "vuedraggable": "^4.1.0"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "devDependencies": {
 | 
					    "devDependencies": {
 | 
				
			||||||
        "@tailwindcss/vite": "^4.1.12",
 | 
					        "@tailwindcss/vite": "^4.1.14",
 | 
				
			||||||
        "@types/crypto-js": "^4.2.2",
 | 
					        "@types/crypto-js": "^4.2.2",
 | 
				
			||||||
        "@types/node": "^22.13.14",
 | 
					        "@types/node": "^22.13.14",
 | 
				
			||||||
        "@types/nprogress": "^0.2.0",
 | 
					        "@types/nprogress": "^0.2.0",
 | 
				
			||||||
@@ -56,11 +56,11 @@
 | 
				
			|||||||
        "autoprefixer": "^10.4.21",
 | 
					        "autoprefixer": "^10.4.21",
 | 
				
			||||||
        "code-inspector-plugin": "^1.0.4",
 | 
					        "code-inspector-plugin": "^1.0.4",
 | 
				
			||||||
        "eslint": "^9.29.0",
 | 
					        "eslint": "^9.29.0",
 | 
				
			||||||
        "eslint-plugin-vue": "^10.4.0",
 | 
					        "eslint-plugin-vue": "^10.5.0",
 | 
				
			||||||
        "postcss": "^8.5.6",
 | 
					        "postcss": "^8.5.6",
 | 
				
			||||||
        "prettier": "^3.6.1",
 | 
					        "prettier": "^3.6.1",
 | 
				
			||||||
        "sass": "^1.92.1",
 | 
					        "sass": "^1.93.2",
 | 
				
			||||||
        "tailwindcss": "^4.1.13",
 | 
					        "tailwindcss": "^4.1.14",
 | 
				
			||||||
        "typescript": "^5.9.2",
 | 
					        "typescript": "^5.9.2",
 | 
				
			||||||
        "vite": "npm:rolldown-vite@latest",
 | 
					        "vite": "npm:rolldown-vite@latest",
 | 
				
			||||||
        "vite-plugin-progress": "0.0.7",
 | 
					        "vite-plugin-progress": "0.0.7",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ const config = {
 | 
				
			|||||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
					    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 系统版本
 | 
					    // 系统版本
 | 
				
			||||||
    version: 'v1.10.3',
 | 
					    version: 'v1.10.4',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default config;
 | 
					export default config;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,16 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <el-form-item v-bind="$attrs">
 | 
					    <el-form-item v-bind="$attrs">
 | 
				
			||||||
        <template #label>
 | 
					        <template #label>
 | 
				
			||||||
            {{ props.label }}
 | 
					            <div class="flex items-center">
 | 
				
			||||||
 | 
					                {{ props.label }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-tooltip :placement="props.placement">
 | 
					                <el-tooltip :placement="props.placement">
 | 
				
			||||||
                <template #content>
 | 
					                    <template #content>
 | 
				
			||||||
                    <span v-html="props.tooltip"></span>
 | 
					                        <span v-html="props.tooltip"></span>
 | 
				
			||||||
                </template>
 | 
					                    </template>
 | 
				
			||||||
                <SvgIcon name="QuestionFilled" />
 | 
					                    <SvgIcon name="QuestionFilled" class="ml-1" />
 | 
				
			||||||
            </el-tooltip>
 | 
					                </el-tooltip>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- 遍历父组件传入的 solts 透传给子组件 -->
 | 
					        <!-- 遍历父组件传入的 solts 透传给子组件 -->
 | 
				
			||||||
@@ -24,11 +26,11 @@ import { useSlots } from 'vue';
 | 
				
			|||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    label: {
 | 
					    label: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
        require: true,
 | 
					        required: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tooltip: {
 | 
					    tooltip: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
        require: true,
 | 
					        required: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    placement: {
 | 
					    placement: {
 | 
				
			||||||
        type: String,
 | 
					        type: String,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -155,6 +155,7 @@ const defaultOptions = {
 | 
				
			|||||||
    scrollBeyondLastLine: false,
 | 
					    scrollBeyondLastLine: false,
 | 
				
			||||||
    lineNumbers: 'on',
 | 
					    lineNumbers: 'on',
 | 
				
			||||||
    lineNumbersMinChars: 3,
 | 
					    lineNumbersMinChars: 3,
 | 
				
			||||||
 | 
					    fixedOverflowWidgets: true, // 使弹出层不被容器限制
 | 
				
			||||||
} as editor.IStandaloneEditorConstructionOptions;
 | 
					} as editor.IStandaloneEditorConstructionOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const monacoTextareaRef: Ref<any> = useTemplateRef('monacoTextareaRef');
 | 
					const monacoTextareaRef: Ref<any> = useTemplateRef('monacoTextareaRef');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
export default {
 | 
					export default {
 | 
				
			||||||
    es: {
 | 
					    es: {
 | 
				
			||||||
        keywordPlaceholder: 'host / name / code',
 | 
					        keywordPlaceholder: 'host / name / code',
 | 
				
			||||||
 | 
					        protocol: 'Protocol',
 | 
				
			||||||
        port: 'Port',
 | 
					        port: 'Port',
 | 
				
			||||||
        size: 'size',
 | 
					        size: 'size',
 | 
				
			||||||
        docs: 'docs',
 | 
					        docs: 'docs',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@ export default {
 | 
				
			|||||||
        personalCenter: 'Personal Center',
 | 
					        personalCenter: 'Personal Center',
 | 
				
			||||||
        myResource: 'Resource',
 | 
					        myResource: 'Resource',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tag: 'Tag',
 | 
					        tag: 'Resource',
 | 
				
			||||||
        tagTree: 'Tag Tree',
 | 
					        tagTree: 'Resource Tree',
 | 
				
			||||||
        tagSave: 'Save Tag',
 | 
					        tagSave: 'Save Tag',
 | 
				
			||||||
        tagDelete: 'Delete Tag',
 | 
					        tagDelete: 'Delete Tag',
 | 
				
			||||||
        authorization: 'Authorization',
 | 
					        authorization: 'Authorization',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ export default {
 | 
				
			|||||||
        tagTips1: '1. Used to group assets',
 | 
					        tagTips1: '1. Used to group assets',
 | 
				
			||||||
        tagTips2: '2. Can be allocated in team management for resource isolation',
 | 
					        tagTips2: '2. Can be allocated in team management for resource isolation',
 | 
				
			||||||
        tagTips3: '3. Team members who own a parent tag have access to resources that manipulate their own or child tag associations',
 | 
					        tagTips3: '3. Team members who own a parent tag have access to resources that manipulate their own or child tag associations',
 | 
				
			||||||
 | 
					        tagTips4: '4. Right-click nodes to edit or add child tags',
 | 
				
			||||||
        machine: 'Machine',
 | 
					        machine: 'Machine',
 | 
				
			||||||
        db: 'Db',
 | 
					        db: 'Db',
 | 
				
			||||||
        code: 'Code',
 | 
					        code: 'Code',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
export default {
 | 
					export default {
 | 
				
			||||||
    es: {
 | 
					    es: {
 | 
				
			||||||
        keywordPlaceholder: 'host / 名称 / 编号',
 | 
					        keywordPlaceholder: 'host / 名称 / 编号',
 | 
				
			||||||
 | 
					        protocol: '协议',
 | 
				
			||||||
        port: '端口',
 | 
					        port: '端口',
 | 
				
			||||||
        size: '存储大小',
 | 
					        size: '存储大小',
 | 
				
			||||||
        docs: '文档数',
 | 
					        docs: '文档数',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@ export default {
 | 
				
			|||||||
        personalCenter: '个人中心',
 | 
					        personalCenter: '个人中心',
 | 
				
			||||||
        myResource: '我的资源',
 | 
					        myResource: '我的资源',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tag: '标签',
 | 
					        tag: '资源',
 | 
				
			||||||
        tagTree: '标签树',
 | 
					        tagTree: '资源树',
 | 
				
			||||||
        tagSave: '保存标签',
 | 
					        tagSave: '保存标签',
 | 
				
			||||||
        tagDelete: '删除标签',
 | 
					        tagDelete: '删除标签',
 | 
				
			||||||
        authorization: '授权凭证',
 | 
					        authorization: '授权凭证',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ export default {
 | 
				
			|||||||
        tagTips1: '1. 用于将资产进行归类',
 | 
					        tagTips1: '1. 用于将资产进行归类',
 | 
				
			||||||
        tagTips2: '2. 可在团队管理中进行分配,用于资源隔离',
 | 
					        tagTips2: '2. 可在团队管理中进行分配,用于资源隔离',
 | 
				
			||||||
        tagTips3: '3. 拥有父标签的团队成员可访问操作其自身或子标签关联的资源',
 | 
					        tagTips3: '3. 拥有父标签的团队成员可访问操作其自身或子标签关联的资源',
 | 
				
			||||||
 | 
					        tagTips4: '4. 右击节点可进行编辑或添加子标签操作',
 | 
				
			||||||
        machine: '机器',
 | 
					        machine: '机器',
 | 
				
			||||||
        db: '数据库',
 | 
					        db: '数据库',
 | 
				
			||||||
        es: 'ES',
 | 
					        es: 'ES',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -654,8 +654,9 @@ const onCopyConfigClick = (target: any) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const checkClientWidth = () => {
 | 
					const checkClientWidth = () => {
 | 
				
			||||||
    const oldLayout = getLocal('oldLayout');
 | 
					    let oldLayout = getLocal('oldLayout');
 | 
				
			||||||
    if (!oldLayout) {
 | 
					    if (!oldLayout) {
 | 
				
			||||||
 | 
					        oldLayout = themeConfig.value.layout;
 | 
				
			||||||
        setLocal('oldLayout', themeConfig.value.layout);
 | 
					        setLocal('oldLayout', themeConfig.value.layout);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (width.value < 1000) {
 | 
					    if (width.value < 1000) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -138,9 +138,8 @@ onBeforeRouteUpdate((to) => {
 | 
				
			|||||||
.horizontal-menu :deep(.el-sub-menu__title) {
 | 
					.horizontal-menu :deep(.el-sub-menu__title) {
 | 
				
			||||||
    margin: 0 5px !important;
 | 
					    margin: 0 5px !important;
 | 
				
			||||||
    justify-content: center;
 | 
					    justify-content: center;
 | 
				
			||||||
    max-width: 150px;
 | 
					    width: fit-content;
 | 
				
			||||||
    min-width: 120px; // 统一最小宽度
 | 
					 | 
				
			||||||
    text-align: center; // 使文字居中对齐
 | 
					    text-align: center; // 使文字居中对齐
 | 
				
			||||||
    padding: 0 8px !important; // 统一内边距
 | 
					    padding: 0 16px !important; // 统一内边距
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
 | 
					    <el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
 | 
				
			||||||
        <el-form-item prop="id" label="DB" required>
 | 
					        <el-form-item prop="id" label="DB" required>
 | 
				
			||||||
            <TagTreeResourceSelect
 | 
					            <ResourceSelect
 | 
				
			||||||
                v-bind="$attrs"
 | 
					                v-bind="$attrs"
 | 
				
			||||||
                v-model="selectRedis"
 | 
					                v-model="selectRedis"
 | 
				
			||||||
                @change="changeRedis"
 | 
					                @change="changeRedis"
 | 
				
			||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
                :tag-path-node-type="NodeTypeTagPath"
 | 
					                :tag-path-node-type="NodeTypeTagPath"
 | 
				
			||||||
                :placeholder="$t('flow.selectRedisPlaceholder')"
 | 
					                :placeholder="$t('flow.selectRedisPlaceholder')"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
            </TagTreeResourceSelect>
 | 
					            </ResourceSelect>
 | 
				
			||||||
        </el-form-item>
 | 
					        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <el-form-item prop="cmd" label="CMD" required>
 | 
					        <el-form-item prop="cmd" label="CMD" required>
 | 
				
			||||||
@@ -21,12 +21,13 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { computed, ref } from 'vue';
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
import TagTreeResourceSelect from '@/views/ops/component/TagTreeResourceSelect.vue';
 | 
					import ResourceSelect from '@/views/ops/resource/ResourceSelect.vue';
 | 
				
			||||||
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
					import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
				
			||||||
import { redisApi } from '@/views/ops/redis/api';
 | 
					import { redisApi } from '@/views/ops/redis/api';
 | 
				
			||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
import { useI18n } from 'vue-i18n';
 | 
					import { useI18n } from 'vue-i18n';
 | 
				
			||||||
import { Rules } from '@/common/rule';
 | 
					import { Rules } from '@/common/rule';
 | 
				
			||||||
 | 
					import { RedisIcon } from '@/views/ops/redis/resource';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { t } = useI18n();
 | 
					const { t } = useI18n();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,7 +53,7 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
 | 
				
			|||||||
    await sleep(100);
 | 
					    await sleep(100);
 | 
				
			||||||
    return redisInfos.map((x: any) => {
 | 
					    return redisInfos.map((x: any) => {
 | 
				
			||||||
        x.tagPath = parentNode.key;
 | 
					        x.tagPath = parentNode.key;
 | 
				
			||||||
        return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x);
 | 
					        return new TagTreeNode(`${x.code}`, x.name, NodeTypeRedis).withParams(x).withIcon(RedisIcon);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,15 +62,18 @@ const NodeTypeRedis = new NodeType(1).withLoadNodesFunc(async (parentNode: TagTr
 | 
				
			|||||||
    const redisInfo = parentNode.params;
 | 
					    const redisInfo = parentNode.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
 | 
					    let dbs: TagTreeNode[] = redisInfo.db.split(',').map((x: string) => {
 | 
				
			||||||
        return new TagTreeNode(x, `db${x}`, 2 as any).withIsLeaf(true).withParams({
 | 
					        return new TagTreeNode(x, `db${x}`, 2 as any)
 | 
				
			||||||
            id: redisInfo.id,
 | 
					            .withIsLeaf(true)
 | 
				
			||||||
            db: x,
 | 
					            .withParams({
 | 
				
			||||||
            name: `db${x}`,
 | 
					                id: redisInfo.id,
 | 
				
			||||||
            keys: 0,
 | 
					                db: x,
 | 
				
			||||||
            tagPath: redisInfo.tagPath,
 | 
					                name: `db${x}`,
 | 
				
			||||||
            redisName: redisInfo.name,
 | 
					                keys: 0,
 | 
				
			||||||
            code: redisInfo.code,
 | 
					                tagPath: redisInfo.tagPath,
 | 
				
			||||||
        });
 | 
					                redisName: redisInfo.name,
 | 
				
			||||||
 | 
					                code: redisInfo.code,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .withIcon({ name: 'Coin', color: '#67c23a' });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (redisInfo.mode == 'cluster') {
 | 
					    if (redisInfo.mode == 'cluster') {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,10 +37,9 @@
 | 
				
			|||||||
                                :label="$t(TagResourceTypeEnum.Machine.label)"
 | 
					                                :label="$t(TagResourceTypeEnum.Machine.label)"
 | 
				
			||||||
                                :value="TagResourceTypeEnum.Machine.value"
 | 
					                                :value="TagResourceTypeEnum.Machine.value"
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
 | 
					 | 
				
			||||||
                            <el-option
 | 
					                            <el-option
 | 
				
			||||||
                                :key="TagResourceTypeEnum.DbInstance.value"
 | 
					                                :key="TagResourceTypeEnum.DbInstance.value"
 | 
				
			||||||
                                :label="TagResourceTypeEnum.DbInstance.label"
 | 
					                                :label="$t(TagResourceTypeEnum.DbInstance.label)"
 | 
				
			||||||
                                :value="TagResourceTypeEnum.DbInstance.value"
 | 
					                                :value="TagResourceTypeEnum.DbInstance.value"
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
                            <el-option
 | 
					                            <el-option
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,38 +5,36 @@
 | 
				
			|||||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
					            <el-form :model="form" ref="dbForm" :rules="rules" label-position="top" label-width="auto">
 | 
				
			||||||
                <el-divider content-position="left">{{ $t('common.basic') }}</el-divider>
 | 
					                <el-divider content-position="left">{{ $t('common.basic') }}</el-divider>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="taskName" :label="$t('db.taskName')" required>
 | 
					                <el-form-item prop="taskName" :label="$t('db.taskName')" required>
 | 
				
			||||||
                    <el-input v-model.trim="form.taskName" auto-complete="off" />
 | 
					                    <el-input v-model.trim="form.taskName" auto-complete="off" />
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item>
 | 
					                <el-row class="!w-full">
 | 
				
			||||||
                    <el-row class="!w-full">
 | 
					                    <el-col :span="12">
 | 
				
			||||||
                        <el-col :span="12">
 | 
					                        <el-form-item prop="status" :label="$t('common.status')" label-position="left">
 | 
				
			||||||
                            <el-form-item prop="status" :label="$t('common.status')">
 | 
					                            <el-switch
 | 
				
			||||||
                                <el-switch
 | 
					                                v-model="form.status"
 | 
				
			||||||
                                    v-model="form.status"
 | 
					                                inline-prompt
 | 
				
			||||||
                                    inline-prompt
 | 
					                                :active-text="$t('common.enable')"
 | 
				
			||||||
                                    :active-text="$t('common.enable')"
 | 
					                                :inactive-text="$t('common.disable')"
 | 
				
			||||||
                                    :inactive-text="$t('common.disable')"
 | 
					                                :active-value="1"
 | 
				
			||||||
                                    :active-value="1"
 | 
					                                :inactive-value="-1"
 | 
				
			||||||
                                    :inactive-value="-1"
 | 
					                            />
 | 
				
			||||||
                                />
 | 
					                        </el-form-item>
 | 
				
			||||||
                            </el-form-item>
 | 
					                    </el-col>
 | 
				
			||||||
                        </el-col>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-col :span="12">
 | 
					                    <el-col :span="12">
 | 
				
			||||||
                            <el-form-item prop="cronAble" :label="$t('db.cronAble')" required>
 | 
					                        <el-form-item prop="cronAble" :label="$t('db.cronAble')" required label-position="left">
 | 
				
			||||||
                                <el-radio-group v-model="form.cronAble">
 | 
					                            <el-radio-group v-model="form.cronAble">
 | 
				
			||||||
                                    <el-radio :label="$t('common.yes')" :value="1" />
 | 
					                                <el-radio :label="$t('common.yes')" :value="1" />
 | 
				
			||||||
                                    <el-radio :label="$t('common.no')" :value="-1" />
 | 
					                                <el-radio :label="$t('common.no')" :value="-1" />
 | 
				
			||||||
                                </el-radio-group>
 | 
					                            </el-radio-group>
 | 
				
			||||||
                            </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
                        </el-col>
 | 
					                    </el-col>
 | 
				
			||||||
                    </el-row>
 | 
					                </el-row>
 | 
				
			||||||
                </el-form-item>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="cron" label="cron" :required="form.cronAble == 1">
 | 
					                <el-form-item prop="cron" label="cron" :required="form.cronAble == 1">
 | 
				
			||||||
                    <CrontabInput v-model="form.cron" />
 | 
					                    <CrontabInput v-model="form.cron" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,10 @@
 | 
				
			|||||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
					                <DrawerHeader :header="title" :back="cancel" />
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
 | 
					            <el-form :model="form" ref="dbForm" :rules="rules" label-position="top" label-width="auto">
 | 
				
			||||||
                <el-tabs v-model="tabActiveName">
 | 
					                <el-tabs v-model="tabActiveName">
 | 
				
			||||||
                    <el-tab-pane :label="$t('common.basic')" :name="basicTab">
 | 
					                    <el-tab-pane :label="$t('common.basic')" :name="basicTab">
 | 
				
			||||||
                        <el-row>
 | 
					                        <el-row :gutter="10">
 | 
				
			||||||
                            <el-col :span="12">
 | 
					                            <el-col :span="12">
 | 
				
			||||||
                                <el-form-item prop="taskName" :label="$t('db.taskName')" required>
 | 
					                                <el-form-item prop="taskName" :label="$t('db.taskName')" required>
 | 
				
			||||||
                                    <el-input v-model.trim="form.taskName" auto-complete="off" />
 | 
					                                    <el-input v-model.trim="form.taskName" auto-complete="off" />
 | 
				
			||||||
@@ -22,7 +22,7 @@
 | 
				
			|||||||
                            </el-col>
 | 
					                            </el-col>
 | 
				
			||||||
                        </el-row>
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-form-item prop="status" :label="$t('common.status')" label-width="60" required>
 | 
					                        <el-form-item prop="status" :label="$t('common.status')" label-position="left" label-width="60" required>
 | 
				
			||||||
                            <el-switch
 | 
					                            <el-switch
 | 
				
			||||||
                                v-model="form.status"
 | 
					                                v-model="form.status"
 | 
				
			||||||
                                inline-prompt
 | 
					                                inline-prompt
 | 
				
			||||||
@@ -59,7 +59,7 @@
 | 
				
			|||||||
                            <monaco-editor height="200px" class="task-sql" language="sql" v-model="form.dataSql" />
 | 
					                            <monaco-editor height="200px" class="task-sql" language="sql" v-model="form.dataSql" />
 | 
				
			||||||
                        </el-form-item>
 | 
					                        </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-row>
 | 
					                        <el-row :gutter="10">
 | 
				
			||||||
                            <el-col :span="12">
 | 
					                            <el-col :span="12">
 | 
				
			||||||
                                <el-form-item prop="targetTableName" :label="$t('db.targetDbTable')" required>
 | 
					                                <el-form-item prop="targetTableName" :label="$t('db.targetDbTable')" required>
 | 
				
			||||||
                                    <el-select v-model="form.targetTableName" filterable>
 | 
					                                    <el-select v-model="form.targetTableName" filterable>
 | 
				
			||||||
@@ -80,7 +80,7 @@
 | 
				
			|||||||
                            </el-col>
 | 
					                            </el-col>
 | 
				
			||||||
                        </el-row>
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-row>
 | 
					                        <el-row :gutter="10">
 | 
				
			||||||
                            <el-col :span="12">
 | 
					                            <el-col :span="12">
 | 
				
			||||||
                                <FormItemTooltip :label="$t('db.updateField')" prop="updField" :tooltip="$t('db.updateFieldTips')">
 | 
					                                <FormItemTooltip :label="$t('db.updateField')" prop="updField" :tooltip="$t('db.updateFieldTips')">
 | 
				
			||||||
                                    <el-input v-model.trim="form.updField" :placeholder="$t('db.updateFiledPlaceholder')" auto-complete="off" />
 | 
					                                    <el-input v-model.trim="form.updField" :placeholder="$t('db.updateFiledPlaceholder')" auto-complete="off" />
 | 
				
			||||||
@@ -94,7 +94,7 @@
 | 
				
			|||||||
                            </el-col>
 | 
					                            </el-col>
 | 
				
			||||||
                        </el-row>
 | 
					                        </el-row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <el-row>
 | 
					                        <el-row :gutter="10">
 | 
				
			||||||
                            <el-col :span="12">
 | 
					                            <el-col :span="12">
 | 
				
			||||||
                                <FormItemTooltip :label="$t('db.fieldValueSrc')" prop="updFieldSrc" :tooltip="$t('db.fieldValueSrcTips')">
 | 
					                                <FormItemTooltip :label="$t('db.fieldValueSrc')" prop="updFieldSrc" :tooltip="$t('db.fieldValueSrcTips')">
 | 
				
			||||||
                                    <el-input v-model.trim="form.updFieldSrc" :placeholder="$t('db.fieldValueSrcPlaceholder')" auto-complete="off" />
 | 
					                                    <el-input v-model.trim="form.updFieldSrc" :placeholder="$t('db.fieldValueSrcPlaceholder')" auto-complete="off" />
 | 
				
			||||||
@@ -105,17 +105,32 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    <el-tab-pane :label="$t('db.fieldMap')" :name="fieldTab" :disabled="!baseFieldCompleted">
 | 
					                    <el-tab-pane :label="$t('db.fieldMap')" :name="fieldTab" :disabled="!baseFieldCompleted">
 | 
				
			||||||
                        <el-form-item prop="fieldMap" :label="$t('db.fieldMap')" required>
 | 
					                        <el-form-item prop="fieldMap" :label="$t('db.fieldMap')" required>
 | 
				
			||||||
                            <el-table :data="form.fieldMap" :max-height="fieldMapTableHeight" size="small">
 | 
					                            <el-table :data="form.fieldMap" :max-height="fieldMapTableHeight">
 | 
				
			||||||
                                <el-table-column prop="src" :label="$t('db.srcField')" :width="200" />
 | 
					                                <el-table-column prop="src" :label="$t('db.srcField')" :width="200"></el-table-column>
 | 
				
			||||||
                                <el-table-column prop="target" :label="$t('db.targetField')">
 | 
					                                <el-table-column prop="target" :label="$t('db.targetField')">
 | 
				
			||||||
                                    <template #default="scope">
 | 
					                                    <template #default="scope">
 | 
				
			||||||
                                        <el-select v-model="scope.row.target" allow-create filterable>
 | 
					                                        <el-select v-model="scope.row.target" allow-create filterable>
 | 
				
			||||||
 | 
					                                            <template #label="{ label, value }">
 | 
				
			||||||
 | 
					                                                <div class="flex justify-between">
 | 
				
			||||||
 | 
					                                                    <el-text tag="b">{{ value }}</el-text>
 | 
				
			||||||
 | 
					                                                    <el-text size="small">{{ label }}</el-text>
 | 
				
			||||||
 | 
					                                                </div>
 | 
				
			||||||
 | 
					                                            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                            <el-option
 | 
					                                            <el-option
 | 
				
			||||||
                                                v-for="item in state.targetColumnList"
 | 
					                                                v-for="item in state.targetColumnList"
 | 
				
			||||||
                                                :key="item.columnName"
 | 
					                                                :key="item.columnName"
 | 
				
			||||||
                                                :label="item.columnName + ` ${item.columnType}` + (item.columnComment && ' - ' + item.columnComment)"
 | 
					                                                :label="`${item.columnType}${item.columnComment && ' - ' + item.columnComment}`"
 | 
				
			||||||
                                                :value="item.columnName"
 | 
					                                                :value="item.columnName"
 | 
				
			||||||
                                            />
 | 
					                                            >
 | 
				
			||||||
 | 
					                                                <div class="flex justify-between">
 | 
				
			||||||
 | 
					                                                    {{ item.columnName }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                                    <el-text size="small">
 | 
				
			||||||
 | 
					                                                        {{ item.columnType }}{{ item.columnComment && ' - ' + item.columnComment }}
 | 
				
			||||||
 | 
					                                                    </el-text>
 | 
				
			||||||
 | 
					                                                </div>
 | 
				
			||||||
 | 
					                                            </el-option>
 | 
				
			||||||
                                        </el-select>
 | 
					                                        </el-select>
 | 
				
			||||||
                                    </template>
 | 
					                                    </template>
 | 
				
			||||||
                                </el-table-column>
 | 
					                                </el-table-column>
 | 
				
			||||||
@@ -305,7 +320,7 @@ watch(dialogVisible, async (newValue: boolean) => {
 | 
				
			|||||||
    state.tabActiveName = 'basic';
 | 
					    state.tabActiveName = 'basic';
 | 
				
			||||||
    const propsData = props.data as any;
 | 
					    const propsData = props.data as any;
 | 
				
			||||||
    if (!propsData?.id) {
 | 
					    if (!propsData?.id) {
 | 
				
			||||||
        let d = {} as FormData;
 | 
					        let d = { taskCron: '' } as FormData;
 | 
				
			||||||
        Object.assign(d, basicFormData);
 | 
					        Object.assign(d, basicFormData);
 | 
				
			||||||
        state.form = d;
 | 
					        state.form = d;
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
@@ -401,6 +416,7 @@ const refreshPreviewInsertSql = () => {
 | 
				
			|||||||
const onSelectSrcDb = async (params: any) => {
 | 
					const onSelectSrcDb = async (params: any) => {
 | 
				
			||||||
    //  初始化数据源
 | 
					    //  初始化数据源
 | 
				
			||||||
    params.databases = params.dbs; // 数据源里需要这个值
 | 
					    params.databases = params.dbs; // 数据源里需要这个值
 | 
				
			||||||
 | 
					    console.log(params.dbs);
 | 
				
			||||||
    state.srcDbInst = await DbInst.getOrNewInst(params);
 | 
					    state.srcDbInst = await DbInst.getOrNewInst(params);
 | 
				
			||||||
    registerDbCompletionItemProvider(params.id, params.db, params.dbs, params.type);
 | 
					    registerDbCompletionItemProvider(params.id, params.db, params.dbs, params.type);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +1,23 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <TagTreeResourceSelect
 | 
					    <ResourceSelect v-bind="$attrs" v-model="selectNode" @change="changeNode" :resource-type="TagResourceTypePath.Db" :tag-path-node-type="NodeTypeDbInst">
 | 
				
			||||||
        v-bind="$attrs"
 | 
					 | 
				
			||||||
        v-model="selectNode"
 | 
					 | 
				
			||||||
        @change="changeNode"
 | 
					 | 
				
			||||||
        :resource-type="TagResourceTypePath.Db"
 | 
					 | 
				
			||||||
        :tag-path-node-type="NodeTypeTagPath"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
        <template #iconPrefix>
 | 
					        <template #iconPrefix>
 | 
				
			||||||
            <SvgIcon v-if="dbType && getDbDialect(dbType)" :name="getDbDialect(dbType).getInfo().icon" :size="18" />
 | 
					            <SvgIcon v-if="dbType && getDbDialect(dbType)" :name="getDbDialect(dbType).getInfo().icon" :size="18" />
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
        <template #prefix="{ data }">
 | 
					    </ResourceSelect>
 | 
				
			||||||
            <SvgIcon v-if="data.type.value == SqlExecNodeType.DbInst" :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
 | 
					 | 
				
			||||||
            <SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
 | 
					 | 
				
			||||||
        </template>
 | 
					 | 
				
			||||||
    </TagTreeResourceSelect>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { computed } from 'vue';
 | 
				
			||||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
 | 
				
			||||||
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
					import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
				
			||||||
import { dbApi } from '@/views/ops/db/api';
 | 
					import { dbApi } from '@/views/ops/db/api';
 | 
				
			||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import { getDbDialect, schemaDbTypes } from '@/views/ops/db/dialect';
 | 
				
			||||||
import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
 | 
					import ResourceSelect from '@/views/ops/resource/ResourceSelect.vue';
 | 
				
			||||||
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
 | 
					import NodeDbInst from '@/views/ops/db/resource/NodeDbInst.vue';
 | 
				
			||||||
import { computed } from 'vue';
 | 
					import NodeDb from '@/views/ops/db/resource/NodeDb.vue';
 | 
				
			||||||
import { DbInst } from '../db';
 | 
					import { DbIcon, SchemaIcon } from '@/views/ops/db/resource';
 | 
				
			||||||
 | 
					import { DbInst } from '@/views/ops/db/db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dbId = defineModel<number>('dbId');
 | 
					const dbId = defineModel<number>('dbId');
 | 
				
			||||||
const instName = defineModel<string>('instName');
 | 
					const instName = defineModel<string>('instName');
 | 
				
			||||||
@@ -35,20 +27,6 @@ const dbType = defineModel<string>('dbType');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(['selectDb']);
 | 
					const emits = defineEmits(['selectDb']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 树节点类型
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class SqlExecNodeType {
 | 
					 | 
				
			||||||
    static DbInst = 1;
 | 
					 | 
				
			||||||
    static Db = 2;
 | 
					 | 
				
			||||||
    static TableMenu = 3;
 | 
					 | 
				
			||||||
    static SqlMenu = 4;
 | 
					 | 
				
			||||||
    static Table = 5;
 | 
					 | 
				
			||||||
    static Sql = 6;
 | 
					 | 
				
			||||||
    static PgSchemaMenu = 7;
 | 
					 | 
				
			||||||
    static PgSchema = 8;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const selectNode = computed({
 | 
					const selectNode = computed({
 | 
				
			||||||
    get: () => {
 | 
					    get: () => {
 | 
				
			||||||
        return dbName.value ? `${tagPath.value} > ${instName.value} > ${dbName.value}` : '';
 | 
					        return dbName.value ? `${tagPath.value} > ${instName.value} > ${dbName.value}` : '';
 | 
				
			||||||
@@ -58,90 +36,94 @@ const selectNode = computed({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbIcon = {
 | 
					const NodeTypeDbInst = new NodeType(TagResourceTypeEnum.DbInstance.value).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
    name: 'Coin',
 | 
					    const tagPath = parentNode.key;
 | 
				
			||||||
    color: '#67c23a',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// pgsql schema icon
 | 
					    const dbInstancesRes = await dbApi.instances.request({ tagPath, pageSize: 100 });
 | 
				
			||||||
const SchemaIcon = {
 | 
					    const dbInstances = dbInstancesRes.list;
 | 
				
			||||||
    name: 'List',
 | 
					    if (!dbInstances) {
 | 
				
			||||||
    color: '#67c23a',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					 | 
				
			||||||
    const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
 | 
					 | 
				
			||||||
    const dbInfos = dbInfoRes.list;
 | 
					 | 
				
			||||||
    if (!dbInfos) {
 | 
					 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 防止过快加载会出现一闪而过,对眼睛不好
 | 
					    // 防止过快加载会出现一闪而过,对眼睛不好
 | 
				
			||||||
    await sleep(100);
 | 
					    await sleep(100);
 | 
				
			||||||
    return dbInfos?.map((x: any) => {
 | 
					    return dbInstances?.map((x: any) => {
 | 
				
			||||||
        x.tagPath = parentNode.key;
 | 
					        x.tagPath = tagPath;
 | 
				
			||||||
        return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
 | 
					        return TagTreeNode.new(parentNode, `${x.code}`, x.name, NodeTypeDbConf).withParams(x).withNodeComponent(NodeDbInst);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**  mysql类型的数据库,没有schema层 */
 | 
					const NodeTypeDbConf = new NodeType(TagResourceTypeEnum.Db.value).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
const noSchemaType = (type: string) => {
 | 
					    const params = parentNode.params;
 | 
				
			||||||
    return noSchemaTypes.includes(type);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库实例节点类型
 | 
					    const tagPath = params.tagPath;
 | 
				
			||||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					    const authCerts = {} as any;
 | 
				
			||||||
 | 
					    for (let authCert of params.authCerts) {
 | 
				
			||||||
 | 
					        authCerts[authCert.name] = authCert;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dbInfoRes = await dbApi.dbs.request({
 | 
				
			||||||
 | 
					        tagPath: `${tagPath}${TagResourceTypeEnum.DbInstance.value}|${params.code}`,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const dbInfos = dbInfoRes.list;
 | 
				
			||||||
 | 
					    if (!dbInfos) {
 | 
				
			||||||
 | 
					        return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return dbInfos?.map((x: any) => {
 | 
				
			||||||
 | 
					        x.tagPath = tagPath;
 | 
				
			||||||
 | 
					        x.username = authCerts[x.authCertName]?.username;
 | 
				
			||||||
 | 
					        return TagTreeNode.new(parentNode, `${x.code}`, x.name, NodeTypeDbs).withParams(x).withIcon(DbIcon).withNodeComponent(NodeDb);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 数据库列表名类型
 | 
				
			||||||
 | 
					const NodeTypeDbs = new NodeType(222).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
    const params = parentNode.params;
 | 
					    const params = parentNode.params;
 | 
				
			||||||
    const dbs = (await DbInst.getDbNames(params))?.sort();
 | 
					    const dbs = (await DbInst.getDbNames(params))?.sort();
 | 
				
			||||||
    let fn: NodeType;
 | 
					    const hasSchema = schemaDbTypes.includes(params.type);
 | 
				
			||||||
    if (noSchemaType(params.type)) {
 | 
					    const nodeType = hasSchema ? NodeTypeDbSchema : NodeTypeNoSchemaDb;
 | 
				
			||||||
        fn = MysqlNodeTypes;
 | 
					
 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        fn = PgNodeTypes;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return dbs.map((x: any) => {
 | 
					    return dbs.map((x: any) => {
 | 
				
			||||||
        let tagTreeNode = new TagTreeNode(`${parentNode.key}.${x}`, `${x}`, fn)
 | 
					        return TagTreeNode.new(parentNode, `${parentNode.key}.${x}`, x, nodeType)
 | 
				
			||||||
            .withParams({
 | 
					            .withParams({
 | 
				
			||||||
                tagPath: params.tagPath,
 | 
					                tagPath: params.tagPath,
 | 
				
			||||||
                id: params.id,
 | 
					                id: params.id,
 | 
				
			||||||
                code: params.code,
 | 
					 | 
				
			||||||
                instanceId: params.instanceId,
 | 
					 | 
				
			||||||
                name: params.name,
 | 
					                name: params.name,
 | 
				
			||||||
                type: params.type,
 | 
					                type: params.type,
 | 
				
			||||||
                host: `${params.host}:${params.port}`,
 | 
					                host: `${params.host}:${params.port}`,
 | 
				
			||||||
                dbs: dbs,
 | 
					                dbs: dbs,
 | 
				
			||||||
                db: x,
 | 
					                db: x,
 | 
				
			||||||
 | 
					                code: params.code,
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .withIcon(DbIcon);
 | 
					            .withIcon(DbIcon)
 | 
				
			||||||
        if (noSchemaType(params.type)) {
 | 
					            .withIsLeaf(!hasSchema);
 | 
				
			||||||
            tagTreeNode.isLeaf = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return tagTreeNode;
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 数据库节点
 | 
					// 数据库节点
 | 
				
			||||||
const PgNodeTypes = new NodeType(SqlExecNodeType.Db).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
					const NodeTypeDbSchema = new NodeType(2).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
 | 
				
			||||||
    // pg类数据库会多一层schema
 | 
					 | 
				
			||||||
    const params = parentNode.params;
 | 
					    const params = parentNode.params;
 | 
				
			||||||
 | 
					    params.parentKey = parentNode.key;
 | 
				
			||||||
    const { id, db } = params;
 | 
					    const { id, db } = params;
 | 
				
			||||||
    const schemaNames = await dbApi.pgSchemas.request({ id, db });
 | 
					    const schemaNames = await dbApi.pgSchemas.request({ id, db });
 | 
				
			||||||
 | 
					    const dbs = schemaNames.map((x: any) => `${db}/${x}`);
 | 
				
			||||||
    return schemaNames.map((sn: any) => {
 | 
					    return schemaNames.map((sn: any) => {
 | 
				
			||||||
        // 将db变更为  db/schema;
 | 
					        // 将db变更为  db/schema;
 | 
				
			||||||
        const nParams = { ...params };
 | 
					        const nParams = { ...params };
 | 
				
			||||||
        nParams.schema = sn;
 | 
					        nParams.schema = sn;
 | 
				
			||||||
        nParams.db = nParams.db + '/' + sn;
 | 
					        nParams.db = nParams.db + '/' + sn;
 | 
				
			||||||
        nParams.dbs = schemaNames;
 | 
					        nParams.dbs = dbs;
 | 
				
			||||||
        let tagTreeNode = new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresSchema).withParams(nParams).withIcon(SchemaIcon);
 | 
					        return TagTreeNode.new(parentNode, `${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresSchema)
 | 
				
			||||||
        tagTreeNode.isLeaf = true;
 | 
					            .withParams(nParams)
 | 
				
			||||||
        return tagTreeNode;
 | 
					            .withIcon(SchemaIcon)
 | 
				
			||||||
 | 
					            .withIsLeaf(true);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MysqlNodeTypes = new NodeType(SqlExecNodeType.Db);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// postgres schema模式
 | 
					// postgres schema模式
 | 
				
			||||||
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema);
 | 
					const NodeTypePostgresSchema = new NodeType(99);
 | 
				
			||||||
 | 
					const NodeTypeNoSchemaDb = new NodeType(99);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const changeNode = (nodeData: TagTreeNode) => {
 | 
					const changeNode = (nodeData: TagTreeNode) => {
 | 
				
			||||||
    const params = nodeData.params;
 | 
					    const params = nodeData.params;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,7 +88,7 @@
 | 
				
			|||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <el-row>
 | 
					                            <el-row>
 | 
				
			||||||
                                <span v-if="dt.hasUpdatedFileds" class="mt-1">
 | 
					                                <span v-if="dt.hasUpdatedFields" class="mt-1">
 | 
				
			||||||
                                    <span>
 | 
					                                    <span>
 | 
				
			||||||
                                        <el-link type="success" underline="never" @click="submitUpdateFields(dt)"
 | 
					                                        <el-link type="success" underline="never" @click="submitUpdateFields(dt)"
 | 
				
			||||||
                                            ><span style="font-size: 12px">{{ $t('common.submit') }}</span></el-link
 | 
					                                            ><span style="font-size: 12px">{{ $t('common.submit') }}</span></el-link
 | 
				
			||||||
@@ -200,7 +200,7 @@ class ExecResTab {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 是否有更新字段
 | 
					     * 是否有更新字段
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    hasUpdatedFileds: boolean;
 | 
					    hasUpdatedFields: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    errorMsg: string;
 | 
					    errorMsg: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -783,7 +783,7 @@ const getUploadSqlFileUrl = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const changeUpdatedField = (updatedFields: any, dt: ExecResTab) => {
 | 
					const changeUpdatedField = (updatedFields: any, dt: ExecResTab) => {
 | 
				
			||||||
    // 如果存在要更新字段,则显示提交和取消按钮
 | 
					    // 如果存在要更新字段,则显示提交和取消按钮
 | 
				
			||||||
    dt.hasUpdatedFileds = updatedFields && updatedFields.size > 0;
 | 
					    dt.hasUpdatedFields = updatedFields && updatedFields.size > 0;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,18 +16,18 @@ const NodeDbInst = defineAsyncComponent(() => import('./NodeDbInst.vue'));
 | 
				
			|||||||
const NodeDb = defineAsyncComponent(() => import('./NodeDb.vue'));
 | 
					const NodeDb = defineAsyncComponent(() => import('./NodeDb.vue'));
 | 
				
			||||||
const NodeDbTable = defineAsyncComponent(() => import('./NodeDbTable.vue'));
 | 
					const NodeDbTable = defineAsyncComponent(() => import('./NodeDbTable.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DbIcon = {
 | 
					export const DbIcon = {
 | 
				
			||||||
    name: ResourceTypeEnum.Db.extra.icon,
 | 
					    name: ResourceTypeEnum.Db.extra.icon,
 | 
				
			||||||
    color: ResourceTypeEnum.Db.extra.iconColor,
 | 
					    color: ResourceTypeEnum.Db.extra.iconColor,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// pgsql schema icon
 | 
					// pgsql schema icon
 | 
				
			||||||
const SchemaIcon = {
 | 
					export const SchemaIcon = {
 | 
				
			||||||
    name: 'List',
 | 
					    name: 'List',
 | 
				
			||||||
    color: '#67c23a',
 | 
					    color: '#67c23a',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TableIcon = {
 | 
					export const TableIcon = {
 | 
				
			||||||
    name: 'icon db/table',
 | 
					    name: 'icon db/table',
 | 
				
			||||||
    color: '#409eff',
 | 
					    color: '#409eff',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,13 +33,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="3" :label="$t('tag.relateTag')"><ResourceTags :tags="detailDialog.data.tags" /></el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" :label="$t('tag.relateTag')"><ResourceTags :tags="detailDialog.data.tags" /></el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="3" label="Host">{{ detailDialog.data.host }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" :label="$t('docker.addr')">{{ detailDialog.data.addr }}</el-descriptions-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="3" label="DB">{{ detailDialog.data.db }}</el-descriptions-item>
 | 
					 | 
				
			||||||
                <el-descriptions-item :span="3" :label="$t('common.remark')">{{ detailDialog.data.remark }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="3" :label="$t('common.remark')">{{ detailDialog.data.remark }}</el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="3" :label="$t('machine.sshTunnel')">
 | 
					 | 
				
			||||||
                    {{ detailDialog.data.sshTunnelMachineId > 0 ? $t('common.yes') : $t('common.no') }}
 | 
					 | 
				
			||||||
                </el-descriptions-item>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-descriptions-item :span="2" :label="$t('common.createTime')">{{ formatDate(detailDialog.data.createTime) }} </el-descriptions-item>
 | 
					                <el-descriptions-item :span="2" :label="$t('common.createTime')">{{ formatDate(detailDialog.data.createTime) }} </el-descriptions-item>
 | 
				
			||||||
                <el-descriptions-item :span="1" :label="$t('common.creator')">{{ detailDialog.data.creator }}</el-descriptions-item>
 | 
					                <el-descriptions-item :span="1" :label="$t('common.creator')">{{ detailDialog.data.creator }}</el-descriptions-item>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,13 @@
 | 
				
			|||||||
                <el-form-item prop="version" :label="t('common.version')">
 | 
					                <el-form-item prop="version" :label="t('common.version')">
 | 
				
			||||||
                    <el-input v-model.trim="form.version" auto-complete="off" disabled></el-input>
 | 
					                    <el-input v-model.trim="form.version" auto-complete="off" disabled></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <!-- 增加协议下拉框 http和https,默认http-->
 | 
				
			||||||
 | 
					                 <el-form-item prop="protocol" :label="t('es.protocol')">
 | 
				
			||||||
 | 
					                    <el-select v-model="form.protocol" placeholder="http">
 | 
				
			||||||
 | 
					                        <el-option label="http" value="http"></el-option>
 | 
				
			||||||
 | 
					                        <el-option label="https" value="https"></el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="host" label="Host" required>
 | 
					                <el-form-item prop="host" label="Host" required>
 | 
				
			||||||
                    <el-col :span="18">
 | 
					                    <el-col :span="18">
 | 
				
			||||||
@@ -105,6 +112,7 @@ const DefaultForm = {
 | 
				
			|||||||
    id: null,
 | 
					    id: null,
 | 
				
			||||||
    code: '',
 | 
					    code: '',
 | 
				
			||||||
    name: null,
 | 
					    name: null,
 | 
				
			||||||
 | 
					    protocol: 'http',
 | 
				
			||||||
    host: '',
 | 
					    host: '',
 | 
				
			||||||
    version: '',
 | 
					    version: '',
 | 
				
			||||||
    port: 9200,
 | 
					    port: 9200,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
            <el-row class="mb-2 ml-4">
 | 
					            <el-row class="mb-2 ml-4">
 | 
				
			||||||
                <el-breadcrumb separator-icon="ArrowRight">
 | 
					                <el-breadcrumb separator-icon="ArrowRight">
 | 
				
			||||||
                    <el-breadcrumb-item v-for="path in filePathNav" :key="path">
 | 
					                    <el-breadcrumb-item v-for="path in filePathNav" :key="path">
 | 
				
			||||||
                        <el-link @click="setFiles(path.path)" style="font-weight: bold">{{ path.name }}</el-link>
 | 
					                        <el-link @click="setFiles(path.path)" class="!cursor-pointer !font-bold">{{ path.name }}</el-link>
 | 
				
			||||||
                    </el-breadcrumb-item>
 | 
					                    </el-breadcrumb-item>
 | 
				
			||||||
                </el-breadcrumb>
 | 
					                </el-breadcrumb>
 | 
				
			||||||
            </el-row>
 | 
					            </el-row>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,7 @@
 | 
				
			|||||||
                    </el-popover>
 | 
					                    </el-popover>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div :ref="(el: any) => setTerminalWrapperRef(el, dt.key)" class="terminal-wrapper flex-1 h-[calc(100vh-155px)]">
 | 
					                <div class="terminal-wrapper flex-1 h-[calc(100vh-155px)]">
 | 
				
			||||||
                    <TerminalBody
 | 
					                    <TerminalBody
 | 
				
			||||||
                        v-if="dt.params.protocol == MachineProtocolEnum.Ssh.value"
 | 
					                        v-if="dt.params.protocol == MachineProtocolEnum.Ssh.value"
 | 
				
			||||||
                        :mount-init="false"
 | 
					                        :mount-init="false"
 | 
				
			||||||
@@ -161,11 +161,6 @@ const actionBtns = hasPerms([perms.updateMachine, perms.closeCli]);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(['init']);
 | 
					const emits = defineEmits(['init']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MachineNodeType {
 | 
					 | 
				
			||||||
    static Machine = 1;
 | 
					 | 
				
			||||||
    static AuthCert = 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const resourceOpCtx: ResourceOpCtx | undefined = inject(ResourceOpCtxKey);
 | 
					const resourceOpCtx: ResourceOpCtx | undefined = inject(ResourceOpCtxKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
@@ -294,10 +289,11 @@ const openTerminal = (machine: any, ex?: boolean) => {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state.tabs.set(key, tab);
 | 
					    state.tabs.set(key, tab);
 | 
				
			||||||
    state.activeTermName = key;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    nextTick(() => {
 | 
					    nextTick(() => {
 | 
				
			||||||
        handleReconnect(tab);
 | 
					        handleReconnect(tab);
 | 
				
			||||||
 | 
					        state.activeTermName = key;
 | 
				
			||||||
 | 
					        setTimeout(() => fitTerminal(), 300);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -398,17 +394,6 @@ const setTerminalRef = (el: any, key: any) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const terminalWrapperRefs: any = {};
 | 
					 | 
				
			||||||
const setTerminalWrapperRef = (el: any, key: any) => {
 | 
					 | 
				
			||||||
    if (key) {
 | 
					 | 
				
			||||||
        terminalWrapperRefs[key] = el;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const onResizeTagTree = () => {
 | 
					 | 
				
			||||||
    fitTerminal();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const fitTerminal = () => {
 | 
					const fitTerminal = () => {
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
        let info = state.tabs.get(state.activeTermName);
 | 
					        let info = state.tabs.get(state.activeTermName);
 | 
				
			||||||
@@ -419,9 +404,7 @@ const fitTerminal = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handleReconnect = (tab: any, force = false) => {
 | 
					const handleReconnect = (tab: any, force = false) => {
 | 
				
			||||||
    let width = terminalWrapperRefs[tab.key]?.offsetWidth;
 | 
					    terminalRefs[tab.key]?.init();
 | 
				
			||||||
    let height = terminalWrapperRefs[tab.key]?.offsetHeight;
 | 
					 | 
				
			||||||
    terminalRefs[tab.key]?.init(width, height, force);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineExpose({
 | 
					defineExpose({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -111,8 +111,6 @@
 | 
				
			|||||||
            </el-splitter-panel>
 | 
					            </el-splitter-panel>
 | 
				
			||||||
        </el-splitter>
 | 
					        </el-splitter>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div style="text-align: center; margin-top: 10px"></div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <el-dialog :title="$t('redis.addKey')" v-model="newKeyDialog.visible" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
					        <el-dialog :title="$t('redis.addKey')" v-model="newKeyDialog.visible" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
 | 
				
			||||||
            <el-form ref="keyForm" label-width="auto" :rules="keyFormRules" :model="newKeyDialog.keyInfo">
 | 
					            <el-form ref="keyForm" label-width="auto" :rules="keyFormRules" :model="newKeyDialog.keyInfo">
 | 
				
			||||||
                <el-form-item prop="key" label="Key" required>
 | 
					                <el-form-item prop="key" label="Key" required>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ import { ResourceTypeEnum, TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			|||||||
import { redisApi } from '../api';
 | 
					import { redisApi } from '../api';
 | 
				
			||||||
import { sleep } from '@/common/utils/loading';
 | 
					import { sleep } from '@/common/utils/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const RedisIcon = {
 | 
					export const RedisIcon = {
 | 
				
			||||||
    name: ResourceTypeEnum.Redis.extra.icon,
 | 
					    name: ResourceTypeEnum.Redis.extra.icon,
 | 
				
			||||||
    color: ResourceTypeEnum.Redis.extra.iconColor,
 | 
					    color: ResourceTypeEnum.Redis.extra.iconColor,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
    <div
 | 
					    <div
 | 
				
			||||||
        :id="props.node.key"
 | 
					        :id="props.node.key"
 | 
				
			||||||
        class="w-full node-container flex items-center cursor-pointer select-none"
 | 
					        class="w-full node-container flex items-center cursor-pointer select-none"
 | 
				
			||||||
        :class="props.data.type.nodeDblclickFunc ? 'select-none' : ''"
 | 
					        :class="props.data.type?.nodeDblclickFunc ? 'select-none' : ''"
 | 
				
			||||||
        @mouseenter="showActions = true"
 | 
					        @mouseenter="showActions = true"
 | 
				
			||||||
        @mouseleave="showActions = false"
 | 
					        @mouseleave="showActions = false"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
@@ -55,7 +55,7 @@ import { ContextmenuItem } from '@/components/contextmenu';
 | 
				
			|||||||
import { ResourceOpCtx, TagTreeNode } from '@/views/ops/component/tag';
 | 
					import { ResourceOpCtx, TagTreeNode } from '@/views/ops/component/tag';
 | 
				
			||||||
import { ResourceOpCtxKey } from '@/views/ops/resource/resource';
 | 
					import { ResourceOpCtxKey } from '@/views/ops/resource/resource';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const resourceOpCtx: ResourceOpCtx | undefined = inject(ResourceOpCtxKey);
 | 
					const resourceOpCtx: ResourceOpCtx | undefined = inject(ResourceOpCtxKey, undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    node: {
 | 
					    node: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,6 +44,7 @@
 | 
				
			|||||||
                            :filter-node-method="filterNode"
 | 
					                            :filter-node-method="filterNode"
 | 
				
			||||||
                            @node-click="treeNodeClick"
 | 
					                            @node-click="treeNodeClick"
 | 
				
			||||||
                            @node-expand="treeNodeClick"
 | 
					                            @node-expand="treeNodeClick"
 | 
				
			||||||
 | 
					                            @node-contextmenu="onNodeContextmenu"
 | 
				
			||||||
                            :default-expanded-keys="state.defaultExpandedKeys"
 | 
					                            :default-expanded-keys="state.defaultExpandedKeys"
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
                            <template #default="{ node, data }">
 | 
					                            <template #default="{ node, data }">
 | 
				
			||||||
@@ -65,12 +66,15 @@
 | 
				
			|||||||
                </el-card>
 | 
					                </el-card>
 | 
				
			||||||
            </el-splitter-panel>
 | 
					            </el-splitter-panel>
 | 
				
			||||||
        </el-splitter>
 | 
					        </el-splitter>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { markRaw, nextTick, provide, reactive, ref, toRefs, useTemplateRef, watch } from 'vue';
 | 
					import { markRaw, nextTick, provide, reactive, ref, toRefs, useTemplateRef, watch } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Contextmenu } from '@/components/contextmenu';
 | 
				
			||||||
import { isPrefixSubsequence } from '@/common/utils/string';
 | 
					import { isPrefixSubsequence } from '@/common/utils/string';
 | 
				
			||||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
					import SvgIcon from '@/components/svgIcon/index.vue';
 | 
				
			||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
					import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
				
			||||||
@@ -108,6 +112,7 @@ const { t } = useI18n();
 | 
				
			|||||||
const emit = defineEmits(['nodeClick', 'currentContextmenuClick']);
 | 
					const emit = defineEmits(['nodeClick', 'currentContextmenuClick']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const treeRef: any = useTemplateRef('treeRef');
 | 
					const treeRef: any = useTemplateRef('treeRef');
 | 
				
			||||||
 | 
					const contextmenuRef: any = useTemplateRef('contextmenuRef');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 存储所有注册的资源组件引用,key -> 组件名称
 | 
					// 存储所有注册的资源组件引用,key -> 组件名称
 | 
				
			||||||
const resourceComponents = ref<Record<string, ResourceComponentConfig>>({});
 | 
					const resourceComponents = ref<Record<string, ResourceComponentConfig>>({});
 | 
				
			||||||
@@ -134,6 +139,11 @@ const setResourceComponentRefs = async (name: string, ref: any) => {
 | 
				
			|||||||
const state = reactive({
 | 
					const state = reactive({
 | 
				
			||||||
    defaultExpandedKeys: [] as string[],
 | 
					    defaultExpandedKeys: [] as string[],
 | 
				
			||||||
    filterText: '',
 | 
					    filterText: '',
 | 
				
			||||||
 | 
					    contextmenuItems: [],
 | 
				
			||||||
 | 
					    dropdown: {
 | 
				
			||||||
 | 
					        x: 0,
 | 
				
			||||||
 | 
					        y: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { filterText } = toRefs(state);
 | 
					const { filterText } = toRefs(state);
 | 
				
			||||||
@@ -216,6 +226,9 @@ const loadNode = async (node: any, resolve: (data: any) => void, reject: () => v
 | 
				
			|||||||
let lastNodeClickTime = 0;
 | 
					let lastNodeClickTime = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const treeNodeClick = async (data: any, node: any) => {
 | 
					const treeNodeClick = async (data: any, node: any) => {
 | 
				
			||||||
 | 
					    // 关闭可能存在的右击菜单
 | 
				
			||||||
 | 
					    contextmenuRef.value?.closeContextmenu();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const currentClickNodeTime = Date.now();
 | 
					    const currentClickNodeTime = Date.now();
 | 
				
			||||||
    // 双击节点
 | 
					    // 双击节点
 | 
				
			||||||
    if (currentClickNodeTime - lastNodeClickTime < 300) {
 | 
					    if (currentClickNodeTime - lastNodeClickTime < 300) {
 | 
				
			||||||
@@ -248,6 +261,29 @@ const treeNodeDblclick = async (data: any, node: any) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 树节点右击事件
 | 
				
			||||||
 | 
					const onNodeContextmenu = (event: any, data: any) => {
 | 
				
			||||||
 | 
					    if (data.disabled) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 加载当前节点是否需要显示右击菜单
 | 
				
			||||||
 | 
					    let items = data.type.contextMenuItems;
 | 
				
			||||||
 | 
					    if (!items || items.length == 0) {
 | 
				
			||||||
 | 
					        if (props.loadContextmenuItems) {
 | 
				
			||||||
 | 
					            items = props.loadContextmenuItems(data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!items) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state.contextmenuItems = items;
 | 
				
			||||||
 | 
					    const { clientX, clientY } = event;
 | 
				
			||||||
 | 
					    state.dropdown.x = clientX;
 | 
				
			||||||
 | 
					    state.dropdown.y = clientY;
 | 
				
			||||||
 | 
					    contextmenuRef.value.openContextmenu(data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 初始化资源组件ref
 | 
					// 初始化资源组件ref
 | 
				
			||||||
const initResourceComp = (val: any) => {
 | 
					const initResourceComp = (val: any) => {
 | 
				
			||||||
    if (!val.ref || resourceComponentRefs.value[val.name]) {
 | 
					    if (!val.ref || resourceComponentRefs.value[val.name]) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,28 +18,18 @@
 | 
				
			|||||||
            <slot name="iconPrefix" :node="node" :data="data" />
 | 
					            <slot name="iconPrefix" :node="node" :data="data" />
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
        <template #default="{ node, data }">
 | 
					        <template #default="{ node, data }">
 | 
				
			||||||
            <span>
 | 
					            <component v-if="data.nodeComponent" :is="data.nodeComponent" :node="node" :data="data" />
 | 
				
			||||||
                <span v-if="data.type.value == TagTreeNode.TagPath">
 | 
					            <BaseTreeNode v-else :node="node" :data="data" />
 | 
				
			||||||
                    <tag-info :tag-path="data.label" />
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <slot v-else :node="node" :data="data" name="prefix"></slot>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <span class="ml-0.5" :title="data.labelRemark">
 | 
					 | 
				
			||||||
                    <slot name="label" :data="data"> {{ data.label }}</slot>
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <slot :node="node" :data="data" name="suffix"></slot>
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
    </el-tree-select>
 | 
					    </el-tree-select>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
					import { onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
				
			||||||
import { NodeType, TagTreeNode } from './tag';
 | 
					
 | 
				
			||||||
import TagInfo from './TagInfo.vue';
 | 
					import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
 | 
				
			||||||
import { tagApi } from '../tag/api';
 | 
					import { tagApi } from '@/views/ops/tag/api';
 | 
				
			||||||
 | 
					import BaseTreeNode from '@/views/ops/resource/BaseTreeNode.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
    resourceType: {
 | 
					    resourceType: {
 | 
				
			||||||
@@ -16,8 +16,11 @@
 | 
				
			|||||||
                            <template #content>
 | 
					                            <template #content>
 | 
				
			||||||
                                {{ $t('tag.tagTips1') }}
 | 
					                                {{ $t('tag.tagTips1') }}
 | 
				
			||||||
                                <br />
 | 
					                                <br />
 | 
				
			||||||
                                {{ $t('tag.tagTips2') }} <br />
 | 
					                                {{ $t('tag.tagTips2') }}
 | 
				
			||||||
 | 
					                                <br />
 | 
				
			||||||
                                {{ $t('tag.tagTips3') }}
 | 
					                                {{ $t('tag.tagTips3') }}
 | 
				
			||||||
 | 
					                                <br />
 | 
				
			||||||
 | 
					                                {{ $t('tag.tagTips4') }}
 | 
				
			||||||
                            </template>
 | 
					                            </template>
 | 
				
			||||||
                            <SvgIcon class="ml-1" name="question-filled" />
 | 
					                            <SvgIcon class="ml-1" name="question-filled" />
 | 
				
			||||||
                        </el-tooltip>
 | 
					                        </el-tooltip>
 | 
				
			||||||
@@ -37,10 +40,6 @@
 | 
				
			|||||||
                        @node-contextmenu="onNodeContextmenu"
 | 
					                        @node-contextmenu="onNodeContextmenu"
 | 
				
			||||||
                        @node-click="onTreeNodeClick"
 | 
					                        @node-click="onTreeNodeClick"
 | 
				
			||||||
                        :default-expanded-keys="defaultExpandedKeys"
 | 
					                        :default-expanded-keys="defaultExpandedKeys"
 | 
				
			||||||
                        draggable
 | 
					 | 
				
			||||||
                        :allow-drop="allowDrop"
 | 
					 | 
				
			||||||
                        :allow-drag="allowDrag"
 | 
					 | 
				
			||||||
                        @node-drop="onNodeDrop"
 | 
					 | 
				
			||||||
                        :expand-on-click-node="false"
 | 
					                        :expand-on-click-node="false"
 | 
				
			||||||
                        :filter-node-method="filterNode"
 | 
					                        :filter-node-method="filterNode"
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
@@ -279,7 +278,7 @@ watch(filterTag, (val) => {
 | 
				
			|||||||
watch(
 | 
					watch(
 | 
				
			||||||
    () => state.currentTag,
 | 
					    () => state.currentTag,
 | 
				
			||||||
    (val: any) => {
 | 
					    (val: any) => {
 | 
				
			||||||
        if (val.type == TagResourceTypeEnum.Tag.value) {
 | 
					        if (val?.type == TagResourceTypeEnum.Tag.value) {
 | 
				
			||||||
            tagApi.countTagResource.request({ tagPath: val.codePath }).then((res: any) => {
 | 
					            tagApi.countTagResource.request({ tagPath: val.codePath }).then((res: any) => {
 | 
				
			||||||
                state.resourceCount = res;
 | 
					                state.resourceCount = res;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@@ -289,82 +288,6 @@ watch(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const allowDrop = (draggingNode: any, dropNode: any, type: any) => {
 | 
					 | 
				
			||||||
    // 不允许同层级移动
 | 
					 | 
				
			||||||
    if (type != 'inner') {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const dropNodeData = dropNode.data;
 | 
					 | 
				
			||||||
    const draggingNodeData = draggingNode.data;
 | 
					 | 
				
			||||||
    const dropTagType = dropNodeData.type;
 | 
					 | 
				
			||||||
    const draggingTagType = draggingNodeData.type;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 目标节点只允许为标签类型
 | 
					 | 
				
			||||||
    if (dropTagType != TagResourceTypeEnum.Tag.value) {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 目标节点下没有子节点
 | 
					 | 
				
			||||||
    if (!dropNodeData.children) {
 | 
					 | 
				
			||||||
        // 都为标签类型允许移动
 | 
					 | 
				
			||||||
        if (dropTagType == draggingTagType && dropTagType == TagResourceTypeEnum.Tag.value) {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 目标节点为标签,允许移动
 | 
					 | 
				
			||||||
        if (dropTagType == TagResourceTypeEnum.Tag.value) {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (let child of dropNodeData.children) {
 | 
					 | 
				
			||||||
        // 当前移动节点若在目标节点下有相同code,则不允许移动
 | 
					 | 
				
			||||||
        if (draggingNodeData.code == child.code) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const childType = child.type;
 | 
					 | 
				
			||||||
        // 移动节点非标签类型时(资源标签),并且子节点存在标签类型,则不允许移动,因为资源只允许放在叶子标签类型下
 | 
					 | 
				
			||||||
        if (draggingTagType != TagResourceTypeEnum.Tag.value && childType == TagResourceTypeEnum.Tag.value) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 移动节点为标签类型时(资源标签),并且子节点存在资源类型,则不允许移动
 | 
					 | 
				
			||||||
        if (draggingTagType == TagResourceTypeEnum.Tag.value && childType != TagResourceTypeEnum.Tag.value) {
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const allowDrag = (node: any) => {
 | 
					 | 
				
			||||||
    const tagType = node.data.type;
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        tagType == TagResourceTypeEnum.Tag.value ||
 | 
					 | 
				
			||||||
        tagType == TagResourceTypeEnum.DbInstance.value ||
 | 
					 | 
				
			||||||
        tagType == TagResourceTypeEnum.Redis.value ||
 | 
					 | 
				
			||||||
        tagType == TagResourceTypeEnum.Machine.value ||
 | 
					 | 
				
			||||||
        tagType == TagResourceTypeEnum.Mongo.value
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const onNodeDrop = async (draggingNode: any, dropNode: any) => {
 | 
					 | 
				
			||||||
    const draggingData = draggingNode.data;
 | 
					 | 
				
			||||||
    const dropData = dropNode.data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        await tagApi.movingTag.request({
 | 
					 | 
				
			||||||
            fromPath: draggingData.codePath,
 | 
					 | 
				
			||||||
            toPath: dropData.codePath,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
        search();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const onTabChange = () => {
 | 
					const onTabChange = () => {
 | 
				
			||||||
    setNowTabData();
 | 
					    setNowTabData();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,42 +6,42 @@ require (
 | 
				
			|||||||
	gitee.com/chunanyong/dm v1.8.20
 | 
						gitee.com/chunanyong/dm v1.8.20
 | 
				
			||||||
	gitee.com/liuzongyang/libpq v1.10.11
 | 
						gitee.com/liuzongyang/libpq v1.10.11
 | 
				
			||||||
	github.com/antlr4-go/antlr/v4 v4.13.1
 | 
						github.com/antlr4-go/antlr/v4 v4.13.1
 | 
				
			||||||
	github.com/docker/docker v28.3.3+incompatible
 | 
						github.com/docker/docker v28.5.0+incompatible
 | 
				
			||||||
	github.com/docker/go-connections v0.6.0
 | 
						github.com/docker/go-connections v0.6.0
 | 
				
			||||||
	github.com/gin-gonic/gin v1.10.1
 | 
						github.com/gin-gonic/gin v1.11.0
 | 
				
			||||||
	github.com/glebarez/sqlite v1.11.0
 | 
						github.com/glebarez/sqlite v1.11.0
 | 
				
			||||||
	github.com/go-gormigrate/gormigrate/v2 v2.1.4
 | 
						github.com/go-gormigrate/gormigrate/v2 v2.1.5
 | 
				
			||||||
	github.com/go-ldap/ldap/v3 v3.4.11
 | 
						github.com/go-ldap/ldap/v3 v3.4.12
 | 
				
			||||||
	github.com/go-playground/locales v0.14.1
 | 
						github.com/go-playground/locales v0.14.1
 | 
				
			||||||
	github.com/go-playground/universal-translator v0.18.1
 | 
						github.com/go-playground/universal-translator v0.18.1
 | 
				
			||||||
	github.com/go-playground/validator/v10 v10.27.0
 | 
						github.com/go-playground/validator/v10 v10.28.0
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.9.3
 | 
						github.com/go-sql-driver/mysql v1.9.3
 | 
				
			||||||
	github.com/golang-jwt/jwt/v5 v5.3.0
 | 
						github.com/golang-jwt/jwt/v5 v5.3.0
 | 
				
			||||||
	github.com/google/uuid v1.6.0
 | 
						github.com/google/uuid v1.6.0
 | 
				
			||||||
	github.com/gorilla/websocket v1.5.3
 | 
						github.com/gorilla/websocket v1.5.3
 | 
				
			||||||
	github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250630080345-f9402614f6ba
 | 
						github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250930013652-2d71241a3bb9
 | 
				
			||||||
	github.com/microsoft/go-mssqldb v1.9.2
 | 
						github.com/microsoft/go-mssqldb v1.9.3
 | 
				
			||||||
	github.com/mojocn/base64Captcha v1.3.8 // 验证码
 | 
						github.com/mojocn/base64Captcha v1.3.8 // 验证码
 | 
				
			||||||
	github.com/opencontainers/image-spec v1.1.1
 | 
						github.com/opencontainers/image-spec v1.1.1
 | 
				
			||||||
	github.com/pkg/errors v0.9.1
 | 
						github.com/pkg/errors v0.9.1
 | 
				
			||||||
	github.com/pkg/sftp v1.13.9
 | 
						github.com/pkg/sftp v1.13.9
 | 
				
			||||||
	github.com/pquerna/otp v1.5.0
 | 
						github.com/pquerna/otp v1.5.0
 | 
				
			||||||
	github.com/redis/go-redis/v9 v9.12.1
 | 
						github.com/redis/go-redis/v9 v9.14.0
 | 
				
			||||||
	github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
						github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
				
			||||||
	github.com/sijms/go-ora/v2 v2.9.0
 | 
						github.com/sijms/go-ora/v2 v2.9.0
 | 
				
			||||||
	github.com/spf13/cast v1.9.2
 | 
						github.com/spf13/cast v1.10.0
 | 
				
			||||||
	github.com/stretchr/testify v1.10.0
 | 
						github.com/stretchr/testify v1.11.1
 | 
				
			||||||
	github.com/tidwall/gjson v1.18.0
 | 
						github.com/tidwall/gjson v1.18.0
 | 
				
			||||||
	github.com/veops/go-ansiterm v0.0.5
 | 
						github.com/veops/go-ansiterm v0.0.5
 | 
				
			||||||
	go.mongodb.org/mongo-driver/v2 v2.3.0 // mongo
 | 
						go.mongodb.org/mongo-driver/v2 v2.3.0 // mongo
 | 
				
			||||||
	golang.org/x/crypto v0.41.0 // ssh
 | 
						golang.org/x/crypto v0.43.0 // ssh
 | 
				
			||||||
	golang.org/x/oauth2 v0.30.0
 | 
						golang.org/x/oauth2 v0.32.0
 | 
				
			||||||
	golang.org/x/sync v0.16.0
 | 
						golang.org/x/sync v0.17.0
 | 
				
			||||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
						gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1
 | 
						gopkg.in/yaml.v3 v3.0.1
 | 
				
			||||||
	// gorm
 | 
						// gorm
 | 
				
			||||||
	gorm.io/driver/mysql v1.6.0
 | 
						gorm.io/driver/mysql v1.6.0
 | 
				
			||||||
	gorm.io/gorm v1.30.3
 | 
						gorm.io/gorm v1.31.0
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
@@ -49,10 +49,12 @@ require (
 | 
				
			|||||||
	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 | 
						github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 | 
				
			||||||
	github.com/Microsoft/go-winio v0.6.2 // indirect
 | 
						github.com/Microsoft/go-winio v0.6.2 // indirect
 | 
				
			||||||
	github.com/boombuler/barcode v1.1.0 // indirect
 | 
						github.com/boombuler/barcode v1.1.0 // indirect
 | 
				
			||||||
	github.com/bytedance/sonic v1.14.0 // indirect
 | 
						github.com/bytedance/gopkg v0.1.3 // indirect
 | 
				
			||||||
 | 
						github.com/bytedance/sonic v1.14.1 // indirect
 | 
				
			||||||
	github.com/bytedance/sonic/loader v0.3.0 // indirect
 | 
						github.com/bytedance/sonic/loader v0.3.0 // indirect
 | 
				
			||||||
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 | 
						github.com/cespare/xxhash/v2 v2.3.0 // indirect
 | 
				
			||||||
	github.com/cloudwego/base64x v0.1.5 // indirect
 | 
						github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/cloudwego/base64x v0.1.6 // indirect
 | 
				
			||||||
	github.com/containerd/errdefs v1.0.0 // indirect
 | 
						github.com/containerd/errdefs v1.0.0 // indirect
 | 
				
			||||||
	github.com/containerd/errdefs/pkg v0.3.0 // indirect
 | 
						github.com/containerd/errdefs/pkg v0.3.0 // indirect
 | 
				
			||||||
	github.com/containerd/log v0.1.0 // indirect
 | 
						github.com/containerd/log v0.1.0 // indirect
 | 
				
			||||||
@@ -62,14 +64,14 @@ require (
 | 
				
			|||||||
	github.com/docker/go-units v0.5.0 // indirect
 | 
						github.com/docker/go-units v0.5.0 // indirect
 | 
				
			||||||
	github.com/dustin/go-humanize v1.0.1 // indirect
 | 
						github.com/dustin/go-humanize v1.0.1 // indirect
 | 
				
			||||||
	github.com/felixge/httpsnoop v1.0.4 // indirect
 | 
						github.com/felixge/httpsnoop v1.0.4 // indirect
 | 
				
			||||||
	github.com/gabriel-vasile/mimetype v1.4.9 // indirect
 | 
						github.com/gabriel-vasile/mimetype v1.4.10 // indirect
 | 
				
			||||||
	github.com/gin-contrib/sse v1.1.0 // indirect
 | 
						github.com/gin-contrib/sse v1.1.0 // indirect
 | 
				
			||||||
	github.com/glebarez/go-sqlite v1.22.0 // indirect
 | 
						github.com/glebarez/go-sqlite v1.22.0 // indirect
 | 
				
			||||||
	github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
 | 
						github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
 | 
				
			||||||
	github.com/go-logr/logr v1.4.3 // indirect
 | 
						github.com/go-logr/logr v1.4.3 // indirect
 | 
				
			||||||
	github.com/go-logr/stdr v1.2.2 // indirect
 | 
						github.com/go-logr/stdr v1.2.2 // indirect
 | 
				
			||||||
	github.com/goccy/go-json v0.10.5 // indirect
 | 
						github.com/goccy/go-json v0.10.5 // indirect
 | 
				
			||||||
	github.com/gogo/protobuf v1.3.2 // indirect
 | 
						github.com/goccy/go-yaml v1.18.0 // indirect
 | 
				
			||||||
	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
 | 
						github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
 | 
				
			||||||
	github.com/golang-sql/sqlexp v0.1.0 // indirect
 | 
						github.com/golang-sql/sqlexp v0.1.0 // indirect
 | 
				
			||||||
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
						github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
				
			||||||
@@ -82,7 +84,7 @@ require (
 | 
				
			|||||||
	github.com/kr/fs v0.1.0 // indirect
 | 
						github.com/kr/fs v0.1.0 // indirect
 | 
				
			||||||
	github.com/leodido/go-urn v1.4.0 // indirect
 | 
						github.com/leodido/go-urn v1.4.0 // indirect
 | 
				
			||||||
	github.com/mattn/go-isatty v0.0.20 // indirect
 | 
						github.com/mattn/go-isatty v0.0.20 // indirect
 | 
				
			||||||
	github.com/mattn/go-runewidth v0.0.16 // indirect
 | 
						github.com/mattn/go-runewidth v0.0.19 // indirect
 | 
				
			||||||
	github.com/moby/docker-image-spec v1.3.1 // indirect
 | 
						github.com/moby/docker-image-spec v1.3.1 // indirect
 | 
				
			||||||
	github.com/moby/sys/atomicwriter v0.1.0 // indirect
 | 
						github.com/moby/sys/atomicwriter v0.1.0 // indirect
 | 
				
			||||||
	github.com/moby/term v0.5.2 // indirect
 | 
						github.com/moby/term v0.5.2 // indirect
 | 
				
			||||||
@@ -93,9 +95,10 @@ require (
 | 
				
			|||||||
	github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
						github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
				
			||||||
	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
 | 
						github.com/pelletier/go-toml/v2 v2.2.4 // indirect
 | 
				
			||||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/quic-go/qpack v0.5.1 // indirect
 | 
				
			||||||
 | 
						github.com/quic-go/quic-go v0.55.0 // indirect
 | 
				
			||||||
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 | 
						github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 | 
				
			||||||
	github.com/rivo/uniseg v0.4.7 // indirect
 | 
						github.com/tidwall/match v1.2.0 // indirect
 | 
				
			||||||
	github.com/tidwall/match v1.1.1 // indirect
 | 
					 | 
				
			||||||
	github.com/tidwall/pretty v1.2.1 // indirect
 | 
						github.com/tidwall/pretty v1.2.1 // indirect
 | 
				
			||||||
	github.com/tjfoc/gmsm v1.4.1 // indirect
 | 
						github.com/tjfoc/gmsm v1.4.1 // indirect
 | 
				
			||||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
						github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
				
			||||||
@@ -104,24 +107,26 @@ require (
 | 
				
			|||||||
	github.com/xdg-go/scram v1.1.2 // indirect
 | 
						github.com/xdg-go/scram v1.1.2 // indirect
 | 
				
			||||||
	github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
						github.com/xdg-go/stringprep v1.0.4 // indirect
 | 
				
			||||||
	github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
 | 
						github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
 | 
				
			||||||
	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
 | 
						go.opentelemetry.io/auto/sdk v1.2.1 // indirect
 | 
				
			||||||
	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
 | 
						go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel v1.37.0 // indirect
 | 
						go.opentelemetry.io/otel v1.38.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
 | 
						go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel/metric v1.37.0 // indirect
 | 
						go.opentelemetry.io/otel/metric v1.38.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel/sdk v1.37.0 // indirect
 | 
						go.opentelemetry.io/otel/trace v1.38.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel/trace v1.37.0 // indirect
 | 
						go.uber.org/mock v0.6.0 // indirect
 | 
				
			||||||
	golang.org/x/arch v0.19.0 // indirect
 | 
						golang.org/x/arch v0.21.0 // indirect
 | 
				
			||||||
	golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
 | 
						golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect
 | 
				
			||||||
	golang.org/x/image v0.29.0 // indirect
 | 
						golang.org/x/image v0.31.0 // indirect
 | 
				
			||||||
	golang.org/x/net v0.42.0 // indirect
 | 
						golang.org/x/mod v0.28.0 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.35.0 // indirect
 | 
						golang.org/x/net v0.45.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.28.0 // indirect
 | 
						golang.org/x/sys v0.37.0 // indirect
 | 
				
			||||||
	google.golang.org/protobuf v1.36.6 // indirect
 | 
						golang.org/x/text v0.30.0 // indirect
 | 
				
			||||||
	modernc.org/libc v1.66.4 // indirect
 | 
						golang.org/x/tools v0.37.0 // indirect
 | 
				
			||||||
 | 
						google.golang.org/protobuf v1.36.10 // indirect
 | 
				
			||||||
 | 
						modernc.org/libc v1.66.10 // indirect
 | 
				
			||||||
	modernc.org/mathutil v1.7.1 // indirect
 | 
						modernc.org/mathutil v1.7.1 // indirect
 | 
				
			||||||
	modernc.org/memory v1.11.0 // indirect
 | 
						modernc.org/memory v1.11.0 // indirect
 | 
				
			||||||
	modernc.org/sqlite v1.38.1 // indirect
 | 
						modernc.org/sqlite v1.39.0 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
replace google.golang.org/genproto => google.golang.org/genproto v0.0.0-20250603155806-513f23925822
 | 
					replace google.golang.org/genproto => google.golang.org/genproto v0.0.0-20250603155806-513f23925822
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -209,7 +209,7 @@ func (d *dbAppImpl) GetDbConnByInstanceId(ctx context.Context, instanceId uint64
 | 
				
			|||||||
		return nil, errorx.NewBiz("failed to get database list")
 | 
							return nil, errorx.NewBiz("failed to get database list")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(dbs) == 0 {
 | 
						if len(dbs) == 0 {
 | 
				
			||||||
		return nil, errorx.NewBiz("DB instance [%d] database is not configured, please configure it first", instanceId)
 | 
							return nil, errorx.NewBizf("DB instance [%d] database is not configured, please configure it first", instanceId)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 使用该实例关联的已配置数据库中的第一个库进行连接并返回
 | 
						// 使用该实例关联的已配置数据库中的第一个库进行连接并返回
 | 
				
			||||||
@@ -308,7 +308,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		if len(tbs) <= 0 {
 | 
							if len(tbs) <= 0 {
 | 
				
			||||||
			log(fmt.Sprintf("failed to get table [%s] information: No table information was retrieved", tableName))
 | 
								log(fmt.Sprintf("failed to get table [%s] information: No table information was retrieved", tableName))
 | 
				
			||||||
			return errorx.NewBiz("Failed to get table information: %s", tableName)
 | 
								return errorx.NewBizf("Failed to get table information: %s", tableName)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tableInfo := tbs[0]
 | 
							tableInfo := tbs[0]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -94,7 +94,7 @@ func (app *dataSyncAppImpl) Delete(ctx context.Context, id uint64) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (app *dataSyncAppImpl) Run(ctx context.Context, id uint64) error {
 | 
					func (app *dataSyncAppImpl) Run(ctx context.Context, id uint64) error {
 | 
				
			||||||
	if app.IsRunning(id) {
 | 
						if app.IsRunning(id) {
 | 
				
			||||||
		logx.Warn("[%d] the db sync task is running...", id)
 | 
							logx.Warnf("[%d] the db sync task is running...", id)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -114,7 +114,7 @@ func (app *dataSyncAppImpl) Run(ctx context.Context, id uint64) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	updateStateTask.Id = id
 | 
						updateStateTask.Id = id
 | 
				
			||||||
	if err := app.UpdateById(ctx, updateStateTask); err != nil {
 | 
						if err := app.UpdateById(ctx, updateStateTask); err != nil {
 | 
				
			||||||
		return errorx.NewBiz("failed to update task running state: %s", err.Error())
 | 
							return errorx.NewBizf("failed to update task running state: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 标记该任务运行中
 | 
						// 标记该任务运行中
 | 
				
			||||||
@@ -136,7 +136,7 @@ func (app *dataSyncAppImpl) Run(ctx context.Context, id uint64) error {
 | 
				
			|||||||
				logx.ErrorfContext(ctx, "data source connection unavailable: %s", err.Error())
 | 
									logx.ErrorfContext(ctx, "data source connection unavailable: %s", err.Error())
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			srcConn, err := app.dbApp.GetDbConn(ctx, uint64(task.SrcDbId), task.SrcDbName)
 | 
								srcConn, err := app.dbApp.GetDbConn(context.Background(), uint64(task.SrcDbId), task.SrcDbName)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logx.ErrorfContext(ctx, "failed to connect to the source database: %s", err.Error())
 | 
									logx.ErrorfContext(ctx, "failed to connect to the source database: %s", err.Error())
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
@@ -184,20 +184,20 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
 | 
				
			|||||||
	srcConn, err := app.dbApp.GetDbConn(ctx, uint64(task.SrcDbId), task.SrcDbName)
 | 
						srcConn, err := app.dbApp.GetDbConn(ctx, uint64(task.SrcDbId), task.SrcDbName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return errorx.NewBiz("failed to connect to the source database: %s", err.Error())
 | 
							return errorx.NewBizf("failed to connect to the source database: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 获取目标数据库连接
 | 
						// 获取目标数据库连接
 | 
				
			||||||
	targetConn, err := app.dbApp.GetDbConn(ctx, uint64(task.TargetDbId), task.TargetDbName)
 | 
						targetConn, err := app.dbApp.GetDbConn(ctx, uint64(task.TargetDbId), task.TargetDbName)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return errorx.NewBiz("failed to connect to the target database: %s", err.Error())
 | 
							return errorx.NewBizf("failed to connect to the target database: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// task.FieldMap为json数组字符串 [{"src":"id","target":"id"}],转为map
 | 
						// task.FieldMap为json数组字符串 [{"src":"id","target":"id"}],转为map
 | 
				
			||||||
	var fieldMap []map[string]string
 | 
						var fieldMap []map[string]string
 | 
				
			||||||
	err = json.Unmarshal([]byte(task.FieldMap), &fieldMap)
 | 
						err = json.Unmarshal([]byte(task.FieldMap), &fieldMap)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return errorx.NewBiz("there was an error parsing the field map json: %s", err.Error())
 | 
							return errorx.NewBizf("there was an error parsing the field map json: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 记录本次同步数据总数
 | 
						// 记录本次同步数据总数
 | 
				
			||||||
@@ -213,7 +213,7 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	targetTableColumns, err := targetConn.GetMetadata().GetColumns(task.TargetTableName)
 | 
						targetTableColumns, err := targetConn.GetMetadata().GetColumns(task.TargetTableName)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return errorx.NewBiz("failed to get target table columns: %s", err.Error())
 | 
							return errorx.NewBizf("failed to get target table columns: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	targetColumnName2Column := collx.ArrayToMap(targetTableColumns, func(column dbi.Column) string {
 | 
						targetColumnName2Column := collx.ArrayToMap(targetTableColumns, func(column dbi.Column) string {
 | 
				
			||||||
		return column.ColumnName
 | 
							return column.ColumnName
 | 
				
			||||||
@@ -300,7 +300,7 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
 | 
				
			|||||||
	// 开启本批次执行事务
 | 
						// 开启本批次执行事务
 | 
				
			||||||
	targetDbTx, err := targetDbConn.Begin()
 | 
						targetDbTx, err := targetDbConn.Begin()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return errorx.NewBiz("failed to start the target database transaction: %s", err.Error())
 | 
							return errorx.NewBizf("failed to start the target database transaction: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		if r := recover(); r != nil {
 | 
							if r := recover(); r != nil {
 | 
				
			||||||
@@ -320,7 +320,7 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
 | 
				
			|||||||
	// 如果是mssql,暂不手动提交事务,否则报错 mssql: The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
 | 
						// 如果是mssql,暂不手动提交事务,否则报错 mssql: The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
 | 
				
			||||||
	if err := targetDbTx.Commit(); err != nil {
 | 
						if err := targetDbTx.Commit(); err != nil {
 | 
				
			||||||
		if targetDbConn.Info.Type != dbi.ToDbType("mssql") {
 | 
							if targetDbConn.Info.Type != dbi.ToDbType("mssql") {
 | 
				
			||||||
			return errorx.NewBiz("data synchronization - The target database transaction failed to commit: %s", err.Error())
 | 
								return errorx.NewBizf("data synchronization - The target database transaction failed to commit: %s", err.Error())
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -370,9 +370,11 @@ func (app *dataSyncAppImpl) saveLog(log *entity.DataSyncLog) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (app *dataSyncAppImpl) InitCronJob() {
 | 
					func (app *dataSyncAppImpl) InitCronJob() {
 | 
				
			||||||
 | 
						ctx := contextx.NewTraceId()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		if err := recover(); err != nil {
 | 
							if err := recover(); err != nil {
 | 
				
			||||||
			logx.ErrorTrace("the data synchronization task failed to initialize", err)
 | 
								logx.ErrorTraceContext(ctx, "the data synchronization task failed to initialize", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -380,10 +382,11 @@ func (app *dataSyncAppImpl) InitCronJob() {
 | 
				
			|||||||
	_ = app.UpdateByCond(context.TODO(), &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateReady}, &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateRunning})
 | 
						_ = app.UpdateByCond(context.TODO(), &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateReady}, &entity.DataSyncTask{RunningState: entity.DataSyncTaskRunStateRunning})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := app.CursorByCond(&entity.DataSyncTaskQuery{Status: entity.DataSyncTaskStatusEnable}, func(dst *entity.DataSyncTask) error {
 | 
						if err := app.CursorByCond(&entity.DataSyncTaskQuery{Status: entity.DataSyncTaskStatusEnable}, func(dst *entity.DataSyncTask) error {
 | 
				
			||||||
		app.addCronJob(contextx.NewTraceId(), dst)
 | 
							app.MarkStop(dst.Id)
 | 
				
			||||||
 | 
							app.addCronJob(ctx, dst)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		logx.ErrorTrace("the db data sync task failed to initialize: %v", err)
 | 
							logx.ErrorTraceContext(ctx, "the db data sync task failed to initialize: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -414,13 +417,13 @@ func (app *dataSyncAppImpl) addCronJob(ctx context.Context, taskEntity *entity.D
 | 
				
			|||||||
	// 根据状态添加新的任务
 | 
						// 根据状态添加新的任务
 | 
				
			||||||
	if taskEntity.Status == entity.DataSyncTaskStatusEnable {
 | 
						if taskEntity.Status == entity.DataSyncTaskStatusEnable {
 | 
				
			||||||
		taskId := taskEntity.Id
 | 
							taskId := taskEntity.Id
 | 
				
			||||||
		logx.Infof("start add the data sync task job: %s, cron[%s]", taskEntity.TaskName, taskEntity.TaskCron)
 | 
							logx.InfofContext(ctx, "start add the data sync task job: %s, cron[%s]", taskEntity.TaskName, taskEntity.TaskCron)
 | 
				
			||||||
		if err := scheduler.AddFunByKey(key, taskEntity.TaskCron, func() {
 | 
							if err := scheduler.AddFunByKey(key, taskEntity.TaskCron, func() {
 | 
				
			||||||
			if err := app.Run(context.Background(), taskId); err != nil {
 | 
								if err := app.Run(context.Background(), taskId); err != nil {
 | 
				
			||||||
				logx.Errorf("the data sync task failed to execute at a scheduled time: %s", err.Error())
 | 
									logx.ErrorfContext(ctx, "the data sync task failed to execute at a scheduled time: %s", err.Error())
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}); err != nil {
 | 
							}); err != nil {
 | 
				
			||||||
			logx.ErrorTrace("add db data sync job failed", err)
 | 
								logx.ErrorTraceContext(ctx, "add db data sync job failed", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -300,7 +300,7 @@ func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *fl
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	execSqlBizForm, err := jsonx.To[*FlowDbExecSqlBizForm](procinst.BizForm)
 | 
						execSqlBizForm, err := jsonx.To[*FlowDbExecSqlBizForm](procinst.BizForm)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, errorx.NewBiz("failed to parse the business form information: %s", err.Error())
 | 
							return nil, errorx.NewBizf("failed to parse the business form information: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dbConn, err := d.dbApp.GetDbConn(ctx, execSqlBizForm.DbId, execSqlBizForm.DbName)
 | 
						dbConn, err := d.dbApp.GetDbConn(ctx, execSqlBizForm.DbId, execSqlBizForm.DbName)
 | 
				
			||||||
@@ -471,7 +471,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
 | 
				
			|||||||
		nowRec++
 | 
							nowRec++
 | 
				
			||||||
		res = append(res, row)
 | 
							res = append(res, row)
 | 
				
			||||||
		if nowRec == maxRec {
 | 
							if nowRec == maxRec {
 | 
				
			||||||
			return errorx.NewBiz("update SQL - the maximum number of updated queries is exceeded: %d", maxRec)
 | 
								return errorx.NewBizf("update SQL - the maximum number of updated queries is exceeded: %d", maxRec)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -122,12 +122,12 @@ func (app *dbTransferAppImpl) InitCronJob() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64) (uint64, error) {
 | 
					func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64) (uint64, error) {
 | 
				
			||||||
	if app.IsRunning(taskId) {
 | 
						if app.IsRunning(taskId) {
 | 
				
			||||||
		return 0, errorx.NewBiz("the db transfer task [%d] is running, please do not repeat the operation", taskId)
 | 
							return 0, errorx.NewBizf("the db transfer task [%d] is running, please do not repeat the operation", taskId)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	task, err := app.GetById(taskId)
 | 
						task, err := app.GetById(taskId)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return 0, errorx.NewBiz("db transfer task [%d] not found", taskId)
 | 
							return 0, errorx.NewBizf("db transfer task [%d] not found", taskId)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logId, _ := app.CreateLog(ctx, taskId)
 | 
						logId, _ := app.CreateLog(ctx, taskId)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import (
 | 
				
			|||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/pkg/utils/collx"
 | 
						"mayfly-go/pkg/utils/collx"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -111,6 +112,12 @@ func ClearNumScale(column *Column) {
 | 
				
			|||||||
	column.CharMaxLength = 0
 | 
						column.CharMaxLength = 0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ClearNumPrecision(column *Column) {
 | 
				
			||||||
 | 
						column.NumScale = 0
 | 
				
			||||||
 | 
						column.NumPrecision = 0
 | 
				
			||||||
 | 
						column.CharMaxLength = 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DataType 数据类型, 对应于go类型,如int int64等。可自定义其他类型
 | 
					// DataType 数据类型, 对应于go类型,如int int64等。可自定义其他类型
 | 
				
			||||||
type DataType struct {
 | 
					type DataType struct {
 | 
				
			||||||
	Name string //  类型名
 | 
						Name string //  类型名
 | 
				
			||||||
@@ -173,7 +180,13 @@ func SQLValueString(val any) string {
 | 
				
			|||||||
		return fmt.Sprintf("%v", val)
 | 
							return fmt.Sprintf("%v", val)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return fmt.Sprintf("'%s'", strings.ReplaceAll(strings.ReplaceAll(strVal, "'", "''"), `\`, `\\`))
 | 
						// 使用 strconv.Quote 来处理所有特殊字符
 | 
				
			||||||
 | 
						quoted := strconv.Quote(strVal)
 | 
				
			||||||
 | 
						// 去掉 strconv.Quote 添加的外层引号,因为会在最后添加 SQL 的单引号
 | 
				
			||||||
 | 
						quoted = quoted[1 : len(quoted)-1]
 | 
				
			||||||
 | 
						// 处理 SQL 中的单引号
 | 
				
			||||||
 | 
						quoted = strings.ReplaceAll(quoted, "'", "''")
 | 
				
			||||||
 | 
						return fmt.Sprintf("'%s'", quoted)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,6 +41,15 @@ func (d *DbConn) Close() error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *DbConn) Ping() error {
 | 
					func (d *DbConn) Ping() error {
 | 
				
			||||||
 | 
						// 首先检查d是否为nil
 | 
				
			||||||
 | 
						if d == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("d is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 然后检查d.db是否为nil,这是避免空指针异常的关键
 | 
				
			||||||
 | 
						if d.db == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("db is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return d.db.Ping()
 | 
						return d.db.Ping()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,13 +70,13 @@ func (di *DbInfo) Conn(ctx context.Context, meta Meta) (*DbConn, error) {
 | 
				
			|||||||
	conn, err := meta.GetSqlDb(ctx, di)
 | 
						conn, err := meta.GetSqlDb(ctx, di)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logx.Errorf("db connection failed: %s:%d/%s, err:%s", di.Host, di.Port, database, err.Error())
 | 
							logx.Errorf("db connection failed: %s:%d/%s, err:%s", di.Host, di.Port, database, err.Error())
 | 
				
			||||||
		return nil, errorx.NewBiz("db connection failed: %s", err.Error())
 | 
							return nil, errorx.NewBizf("db connection failed: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = conn.Ping()
 | 
						err = conn.Ping()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logx.Errorf("db ping failed: %s:%d/%s, err:%s", di.Host, di.Port, database, err.Error())
 | 
							logx.Errorf("db ping failed: %s:%d/%s, err:%s", di.Host, di.Port, database, err.Error())
 | 
				
			||||||
		return nil, errorx.NewBiz("db connection failed: %s", err.Error())
 | 
							return nil, errorx.NewBizf("db connection failed: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dbc := &DbConn{Id: GetDbConnId(di.Id, database), Info: di}
 | 
						dbc := &DbConn{Id: GetDbConnId(di.Id, database), Info: di}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,17 +24,16 @@ type Meta struct {
 | 
				
			|||||||
func (dm *Meta) GetSqlDb(ctx context.Context, d *dbi.DbInfo) (*sql.DB, error) {
 | 
					func (dm *Meta) GetSqlDb(ctx context.Context, d *dbi.DbInfo) (*sql.DB, error) {
 | 
				
			||||||
	driverName := "dm"
 | 
						driverName := "dm"
 | 
				
			||||||
	db := d.Database
 | 
						db := d.Database
 | 
				
			||||||
	var dbParam string
 | 
						dbParam := "?escapeProcess=true"
 | 
				
			||||||
	if db != "" {
 | 
						if db != "" {
 | 
				
			||||||
		// dm database可以使用db/schema表示,方便连接指定schema, 若不存在schema则使用默认schema
 | 
							// dm database可以使用db/schema表示,方便连接指定schema, 若不存在schema则使用默认schema
 | 
				
			||||||
		ss := strings.Split(db, "/")
 | 
							ss := strings.Split(db, "/")
 | 
				
			||||||
		if len(ss) > 1 {
 | 
							if len(ss) > 1 {
 | 
				
			||||||
			dbParam = fmt.Sprintf("%s?schema=\"%s\"&escapeProcess=true", ss[0], ss[len(ss)-1])
 | 
								dbParam = fmt.Sprintf("%s&schema=\"%s\"", dbParam, ss[len(ss)-1])
 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			dbParam = db + "?escapeProcess=true"
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						}
 | 
				
			||||||
		dbParam = "?escapeProcess=true"
 | 
						if d.Params != "" {
 | 
				
			||||||
 | 
							dbParam += "&" + d.Params
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := d.IfUseSshTunnelChangeIpPort(ctx)
 | 
						err := d.IfUseSshTunnelChangeIpPort(ctx)
 | 
				
			||||||
@@ -42,7 +41,7 @@ func (dm *Meta) GetSqlDb(ctx context.Context, d *dbi.DbInfo) (*sql.DB, error) {
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dsn := fmt.Sprintf("dm://%s:%s@%s:%d/%s", d.Username, url.PathEscape(d.Password), d.Host, d.Port, dbParam)
 | 
						dsn := fmt.Sprintf("dm://%s:%s@%s:%d%s", d.Username, url.PathEscape(d.Password), d.Host, d.Port, dbParam)
 | 
				
			||||||
	return sql.Open(driverName, dsn)
 | 
						return sql.Open(driverName, dsn)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -134,7 +134,7 @@ func (dd *DMMetadata) GetPrimaryKey(tablename string) (string, error) {
 | 
				
			|||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(columns) == 0 {
 | 
						if len(columns) == 0 {
 | 
				
			||||||
		return "", errorx.NewBiz("[%s] 表不存在", tablename)
 | 
							return "", errorx.NewBizf("[%s] 表不存在", tablename)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, v := range columns {
 | 
						for _, v := range columns {
 | 
				
			||||||
		if v.IsPrimaryKey {
 | 
							if v.IsPrimaryKey {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,7 +129,7 @@ func (md *MssqlMetadata) GetPrimaryKey(tablename string) (string, error) {
 | 
				
			|||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(columns) == 0 {
 | 
						if len(columns) == 0 {
 | 
				
			||||||
		return "", errorx.NewBiz("[%s] 表不存在", tablename)
 | 
							return "", errorx.NewBizf("[%s] 表不存在", tablename)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, v := range columns {
 | 
						for _, v := range columns {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ var (
 | 
				
			|||||||
	UnsignedMediumint = dbi.NewDbDataType("unsigned mediumint", dbi.DTInt64).WithCT(dbi.CTUnsignedInt4).WithFixColumn(dbi.ClearNumScale)
 | 
						UnsignedMediumint = dbi.NewDbDataType("unsigned mediumint", dbi.DTInt64).WithCT(dbi.CTUnsignedInt4).WithFixColumn(dbi.ClearNumScale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Decimal = dbi.NewDbDataType("decimal", dbi.DTDecimal).WithCT(dbi.CTDecimal)
 | 
						Decimal = dbi.NewDbDataType("decimal", dbi.DTDecimal).WithCT(dbi.CTDecimal)
 | 
				
			||||||
	Double  = dbi.NewDbDataType("double", dbi.DTNumeric).WithCT(dbi.CTNumeric)
 | 
						Double  = dbi.NewDbDataType("double", dbi.DTNumeric).WithCT(dbi.CTNumeric).WithFixColumn(dbi.ClearNumPrecision)
 | 
				
			||||||
	Float   = dbi.NewDbDataType("float", dbi.DTNumeric).WithCT(dbi.CTNumeric)
 | 
						Float   = dbi.NewDbDataType("float", dbi.DTNumeric).WithCT(dbi.CTNumeric)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Varchar    = dbi.NewDbDataType("varchar", dbi.DTString).WithCT(dbi.CTVarchar)
 | 
						Varchar    = dbi.NewDbDataType("varchar", dbi.DTString).WithCT(dbi.CTVarchar)
 | 
				
			||||||
@@ -40,9 +40,9 @@ var (
 | 
				
			|||||||
	Enum = dbi.NewDbDataType("enum", dbi.DTString).WithCT(dbi.CTEnum)
 | 
						Enum = dbi.NewDbDataType("enum", dbi.DTString).WithCT(dbi.CTEnum)
 | 
				
			||||||
	Set  = dbi.NewDbDataType("set", dbi.DTString).WithCT(dbi.CTVarchar)
 | 
						Set  = dbi.NewDbDataType("set", dbi.DTString).WithCT(dbi.CTVarchar)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Blob       = dbi.NewDbDataType("blob", dbi.DTBytes).WithCT(dbi.CTBlob)
 | 
						Blob       = dbi.NewDbDataType("blob", dbi.DTBytes).WithCT(dbi.CTBlob).WithFixColumn(dbi.ClearNumScale)
 | 
				
			||||||
	Mediumblob = dbi.NewDbDataType("mediumblob", dbi.DTBytes).WithCT(dbi.CTMediumblob)
 | 
						Mediumblob = dbi.NewDbDataType("mediumblob", dbi.DTBytes).WithCT(dbi.CTMediumblob).WithFixColumn(dbi.ClearNumScale)
 | 
				
			||||||
	Longblob   = dbi.NewDbDataType("longblob", dbi.DTBytes).WithCT(dbi.CTLongblob)
 | 
						Longblob   = dbi.NewDbDataType("longblob", dbi.DTBytes).WithCT(dbi.CTLongblob).WithFixColumn(dbi.ClearNumScale)
 | 
				
			||||||
	Binary     = dbi.NewDbDataType("binary", dbi.DTBytes).WithCT(dbi.CTBinary)
 | 
						Binary     = dbi.NewDbDataType("binary", dbi.DTBytes).WithCT(dbi.CTBinary)
 | 
				
			||||||
	Varbinary  = dbi.NewDbDataType("varbinary", dbi.DTBytes).WithCT(dbi.CTVarbinary)
 | 
						Varbinary  = dbi.NewDbDataType("varbinary", dbi.DTBytes).WithCT(dbi.CTVarbinary)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -125,7 +125,7 @@ func (md *MysqlMetadata) GetPrimaryKey(tablename string) (string, error) {
 | 
				
			|||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(columns) == 0 {
 | 
						if len(columns) == 0 {
 | 
				
			||||||
		return "", errorx.NewBiz("[%s] 表不存在", tablename)
 | 
							return "", errorx.NewBizf("[%s] 表不存在", tablename)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, v := range columns {
 | 
						for _, v := range columns {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,7 +148,7 @@ func (od *OracleMetadata) GetPrimaryKey(tablename string) (string, error) {
 | 
				
			|||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(columns) == 0 {
 | 
						if len(columns) == 0 {
 | 
				
			||||||
		return "", errorx.NewBiz("[%s] 表不存在", tablename)
 | 
							return "", errorx.NewBizf("[%s] 表不存在", tablename)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, v := range columns {
 | 
						for _, v := range columns {
 | 
				
			||||||
		if v.IsPrimaryKey {
 | 
							if v.IsPrimaryKey {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,7 +124,7 @@ func (pd *PgsqlMetadata) GetPrimaryKey(tablename string) (string, error) {
 | 
				
			|||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(columns) == 0 {
 | 
						if len(columns) == 0 {
 | 
				
			||||||
		return "", errorx.NewBiz("[%s] 表不存在", tablename)
 | 
							return "", errorx.NewBizf("[%s] 表不存在", tablename)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, v := range columns {
 | 
						for _, v := range columns {
 | 
				
			||||||
		if v.IsPrimaryKey {
 | 
							if v.IsPrimaryKey {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,14 +148,14 @@ func (d *Container) ContainerCreate(rc *req.Ctx) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		_ = cli.DockerClient.ContainerRemove(ctx, containerCreate.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
 | 
							_ = cli.DockerClient.ContainerRemove(ctx, containerCreate.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
 | 
				
			||||||
		panic(errorx.NewBiz("create container failed, err: %v", err))
 | 
							panic(errorx.NewBizf("create container failed, err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logx.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", containerCreate.Name)
 | 
						logx.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", containerCreate.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := cli.DockerClient.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
 | 
						if err := cli.DockerClient.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
 | 
				
			||||||
		_ = cli.DockerClient.ContainerRemove(ctx, containerCreate.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
 | 
							_ = cli.DockerClient.ContainerRemove(ctx, containerCreate.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
 | 
				
			||||||
		panic(errorx.NewBiz("create successful but start failed, err: %v", err))
 | 
							panic(errorx.NewBizf("create successful but start failed, err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type InstanceForm struct {
 | 
					type InstanceForm struct {
 | 
				
			||||||
	Id                 uint64  `json:"id"`
 | 
						Id                 uint64  `json:"id"`
 | 
				
			||||||
 | 
						Protocol           string  `binding:"required" json:"protocol"`
 | 
				
			||||||
	Name               string  `binding:"required" json:"name"`
 | 
						Name               string  `binding:"required" json:"name"`
 | 
				
			||||||
	Host               string  `binding:"required" json:"host"`
 | 
						Host               string  `binding:"required" json:"host"`
 | 
				
			||||||
	Port               int     `binding:"required" json:"port"`
 | 
						Port               int     `binding:"required" json:"port"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,13 +9,14 @@ type InstanceListVO struct {
 | 
				
			|||||||
	tagentity.AuthCerts // 授权凭证信息
 | 
						tagentity.AuthCerts // 授权凭证信息
 | 
				
			||||||
	tagentity.ResourceTags
 | 
						tagentity.ResourceTags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Id      *int64  `json:"id"`
 | 
						Id       *int64  `json:"id"`
 | 
				
			||||||
	Code    string  `json:"code"`
 | 
						Code     string  `json:"code"`
 | 
				
			||||||
	Name    *string `json:"name"`
 | 
						Name     *string `json:"name"`
 | 
				
			||||||
	Host    *string `json:"host"`
 | 
						Protocol *string `json:"protocol"`
 | 
				
			||||||
	Port    *int    `json:"port"`
 | 
						Host     *string `json:"host"`
 | 
				
			||||||
	Version *string `json:"version"`
 | 
						Port     *int    `json:"port"`
 | 
				
			||||||
	Remark  *string `json:"remark"`
 | 
						Version  *string `json:"version"`
 | 
				
			||||||
 | 
						Remark   *string `json:"remark"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CreateTime *time.Time `json:"createTime"`
 | 
						CreateTime *time.Time `json:"createTime"`
 | 
				
			||||||
	Creator    *string    `json:"creator"`
 | 
						Creator    *string    `json:"creator"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ type EsInstance struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	Code               string  `json:"code" gorm:"size:32;not null;"`
 | 
						Code               string  `json:"code" gorm:"size:32;not null;"`
 | 
				
			||||||
	Name               string  `json:"name" gorm:"size:32;not null;"`
 | 
						Name               string  `json:"name" gorm:"size:32;not null;"`
 | 
				
			||||||
 | 
						Protocol           string  `json:"protocol" gorm:"size:10;not null;"`
 | 
				
			||||||
	Host               string  `json:"host" gorm:"size:255;not null;"`
 | 
						Host               string  `json:"host" gorm:"size:255;not null;"`
 | 
				
			||||||
	Port               int     `json:"port"`
 | 
						Port               int     `json:"port"`
 | 
				
			||||||
	Network            string  `json:"network" gorm:"size:20;"`
 | 
						Network            string  `json:"network" gorm:"size:20;"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package esi
 | 
					package esi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/internal/machine/mcm"
 | 
						"mayfly-go/internal/machine/mcm"
 | 
				
			||||||
	"mayfly-go/pkg/logx"
 | 
						"mayfly-go/pkg/logx"
 | 
				
			||||||
@@ -27,6 +28,15 @@ func (d *EsConn) Close() error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *EsConn) Ping() error {
 | 
					func (d *EsConn) Ping() error {
 | 
				
			||||||
 | 
						// 首先检查d是否为nil
 | 
				
			||||||
 | 
						if d == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("es connection is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 然后检查d.Info是否为nil,这是避免空指针异常的关键
 | 
				
			||||||
 | 
						if d.Info == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("es Info is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	_, err := d.Info.Ping()
 | 
						_, err := d.Info.Ping()
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -43,6 +53,16 @@ func (d *EsConn) StartProxy() error {
 | 
				
			|||||||
	d.proxy = httputil.NewSingleHostReverseProxy(targetURL)
 | 
						d.proxy = httputil.NewSingleHostReverseProxy(targetURL)
 | 
				
			||||||
	// 设置 proxy buffer pool
 | 
						// 设置 proxy buffer pool
 | 
				
			||||||
	d.proxy.BufferPool = NewBufferPool()
 | 
						d.proxy.BufferPool = NewBufferPool()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Configure TLS to skip certificate verification for non-compliant certificates
 | 
				
			||||||
 | 
						if targetURL.Scheme == "https" {
 | 
				
			||||||
 | 
							d.proxy.Transport = &http.Transport{
 | 
				
			||||||
 | 
								TLSClientConfig: &tls.Config{
 | 
				
			||||||
 | 
									InsecureSkipVerify: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ type EsInfo struct {
 | 
				
			|||||||
	InstanceId uint64 // 实例id
 | 
						InstanceId uint64 // 实例id
 | 
				
			||||||
	Name       string
 | 
						Name       string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Protocol string // 协议,默认http
 | 
				
			||||||
	Host     string
 | 
						Host     string
 | 
				
			||||||
	Port     int
 | 
						Port     int
 | 
				
			||||||
	Network  string
 | 
						Network  string
 | 
				
			||||||
@@ -58,14 +59,14 @@ func (di *EsInfo) Conn(ctx context.Context) (*EsConn, map[string]any, error) {
 | 
				
			|||||||
	err := di.IfUseSshTunnelChangeIpPort(ctx)
 | 
						err := di.IfUseSshTunnelChangeIpPort(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logx.Errorf("es ssh failed: %s, err:%s", di.baseUrl, err.Error())
 | 
							logx.Errorf("es ssh failed: %s, err:%s", di.baseUrl, err.Error())
 | 
				
			||||||
		return nil, nil, errorx.NewBiz("es ssh failed: %s", err.Error())
 | 
							return nil, nil, errorx.NewBizf("es ssh failed: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 尝试获取es版本信息,调用接口:get /
 | 
						// 尝试获取es版本信息,调用接口:get /
 | 
				
			||||||
	res, err := di.Ping()
 | 
						res, err := di.Ping()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logx.Errorf("es ping failed: %s, err:%s", di.baseUrl, err.Error())
 | 
							logx.Errorf("es ping failed: %s, err:%s", di.baseUrl, err.Error())
 | 
				
			||||||
		return nil, nil, errorx.NewBiz("es ping failed: %s", err.Error())
 | 
							return nil, nil, errorx.NewBizf("es ping failed: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	esc := &EsConn{Id: di.InstanceId, Info: di}
 | 
						esc := &EsConn{Id: di.InstanceId, Info: di}
 | 
				
			||||||
@@ -90,7 +91,14 @@ func (di *EsInfo) Ping() (map[string]any, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ExecApi 执行api
 | 
					// ExecApi 执行api
 | 
				
			||||||
func (di *EsInfo) ExecApi(method, path string, data any, timeoutSecond ...int) (map[string]any, error) {
 | 
					func (di *EsInfo) ExecApi(method, path string, data any, timeoutSecond ...int) (map[string]any, error) {
 | 
				
			||||||
	request := httpx.NewReq(di.baseUrl + path)
 | 
						var request *httpx.Req
 | 
				
			||||||
 | 
						// Use insecure TLS client for HTTPS connections to handle non-compliant certificates
 | 
				
			||||||
 | 
						if di.Protocol == "https" {
 | 
				
			||||||
 | 
							request = httpx.NewReqWithInsecureTLS(di.baseUrl + path)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							request = httpx.NewReq(di.baseUrl + path)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if di.authorization != "" {
 | 
						if di.authorization != "" {
 | 
				
			||||||
		request.Header("Authorization", di.authorization)
 | 
							request.Header("Authorization", di.authorization)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -111,12 +119,17 @@ func (di *EsInfo) ExecApi(method, path string, data any, timeoutSecond ...int) (
 | 
				
			|||||||
		return request.PutObj(data).BodyToMap()
 | 
							return request.PutObj(data).BodyToMap()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil, errorx.NewBiz("不支持的请求方法: %s", method)
 | 
						return nil, errorx.NewBizf("不支持的请求方法: %s", method)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 如果使用了ssh隧道,将其host port改变其本地映射host port
 | 
					// 如果使用了ssh隧道,将其host port改变其本地映射host port
 | 
				
			||||||
func (di *EsInfo) IfUseSshTunnelChangeIpPort(ctx context.Context) error {
 | 
					func (di *EsInfo) IfUseSshTunnelChangeIpPort(ctx context.Context) error {
 | 
				
			||||||
 | 
						// 设置默认协议
 | 
				
			||||||
 | 
						if di.Protocol == "" {
 | 
				
			||||||
 | 
							di.Protocol = "http"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 开启ssh隧道
 | 
						// 开启ssh隧道
 | 
				
			||||||
	if di.SshTunnelMachineId > 0 {
 | 
						if di.SshTunnelMachineId > 0 {
 | 
				
			||||||
		stm, err := GetSshTunnel(ctx, di.SshTunnelMachineId)
 | 
							stm, err := GetSshTunnel(ctx, di.SshTunnelMachineId)
 | 
				
			||||||
@@ -130,9 +143,9 @@ func (di *EsInfo) IfUseSshTunnelChangeIpPort(ctx context.Context) error {
 | 
				
			|||||||
		di.Host = exposedIp
 | 
							di.Host = exposedIp
 | 
				
			||||||
		di.Port = exposedPort
 | 
							di.Port = exposedPort
 | 
				
			||||||
		di.useSshTunnel = true
 | 
							di.useSshTunnel = true
 | 
				
			||||||
		di.baseUrl = fmt.Sprintf("http://%s:%d", exposedIp, exposedPort)
 | 
							di.baseUrl = fmt.Sprintf("%s://%s:%d", di.Protocol, exposedIp, exposedPort)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		di.baseUrl = fmt.Sprintf("http://%s:%d", di.Host, di.Port)
 | 
							di.baseUrl = fmt.Sprintf("%s://%s:%d", di.Protocol, di.Host, di.Port)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -312,10 +312,10 @@ func (m *machineAppImpl) ToMachineInfoById(machineId uint64) (*mcm.MachineInfo,
 | 
				
			|||||||
func (m *machineAppImpl) getMachineAndAuthCert(machineId uint64) (*entity.Machine, *tagentity.ResourceAuthCert, error) {
 | 
					func (m *machineAppImpl) getMachineAndAuthCert(machineId uint64) (*entity.Machine, *tagentity.ResourceAuthCert, error) {
 | 
				
			||||||
	me, err := m.GetById(machineId)
 | 
						me, err := m.GetById(machineId)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, nil, errorx.NewBiz("[%d] machine not found", machineId)
 | 
							return nil, nil, errorx.NewBizf("[%d] machine not found", machineId)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if me.Status != entity.MachineStatusEnable && me.Protocol == 1 {
 | 
						if me.Status != entity.MachineStatusEnable && me.Protocol == 1 {
 | 
				
			||||||
		return nil, nil, errorx.NewBiz("[%s] machine has been disable", me.Code)
 | 
							return nil, nil, errorx.NewBizf("[%s] machine has been disable", me.Code)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	authCert, err := m.resourceAuthCertApp.GetResourceAuthCert(tagentity.TagTypeMachine, me.Code)
 | 
						authCert, err := m.resourceAuthCertApp.GetResourceAuthCert(tagentity.TagTypeMachine, me.Code)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -180,7 +180,7 @@ func (m *machineFileAppImpl) GetDirSize(ctx context.Context, opParam *dto.Machin
 | 
				
			|||||||
		//du: cannot access ‘/proc/19087/fdinfo/3’: No such file or directory\n
 | 
							//du: cannot access ‘/proc/19087/fdinfo/3’: No such file or directory\n
 | 
				
			||||||
		//18G     /\n
 | 
							//18G     /\n
 | 
				
			||||||
		if res == "" {
 | 
							if res == "" {
 | 
				
			||||||
			return "", errorx.NewBiz("failed to get directory size: %s", err.Error())
 | 
								return "", errorx.NewBizf("failed to get directory size: %s", err.Error())
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		strs := strings.Split(res, "\n")
 | 
							strs := strings.Split(res, "\n")
 | 
				
			||||||
		res = strs[len(strs)-2]
 | 
							res = strs[len(strs)-2]
 | 
				
			||||||
@@ -247,7 +247,7 @@ func (m *machineFileAppImpl) CreateFile(ctx context.Context, opParam *dto.Machin
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	file, err := sftpCli.Create(path)
 | 
						file, err := sftpCli.Create(path)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, errorx.NewBiz("failed to create file: %s", err.Error())
 | 
							return nil, errorx.NewBizf("failed to create file: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer file.Close()
 | 
						defer file.Close()
 | 
				
			||||||
	return mi, err
 | 
						return mi, err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,7 +63,7 @@ func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		fileKey, wc, saveFileFunc, err := m.fileApp.NewWriter(ctx, "", fmt.Sprintf("mto_%d_%s.cast", termOpRecord.MachineId, timex.TimeNo()))
 | 
							fileKey, wc, saveFileFunc, err := m.fileApp.NewWriter(ctx, "", fmt.Sprintf("mto_%d_%s.cast", termOpRecord.MachineId, timex.TimeNo()))
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return errorx.NewBiz("failed to create a terminal playback log file: %v", err)
 | 
								return errorx.NewBizf("failed to create a terminal playback log file: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		defer saveFileFunc(&err)
 | 
							defer saveFileFunc(&err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,7 +63,7 @@ func (c *Cli) GetSftpCli() (*sftp.Client, error) {
 | 
				
			|||||||
	if sftpclient == nil {
 | 
						if sftpclient == nil {
 | 
				
			||||||
		sc, serr := sftp.NewClient(c.sshClient)
 | 
							sc, serr := sftp.NewClient(c.sshClient)
 | 
				
			||||||
		if serr != nil {
 | 
							if serr != nil {
 | 
				
			||||||
			return nil, errorx.NewBiz("failed to obtain the sftp client: %s", serr.Error())
 | 
								return nil, errorx.NewBizf("failed to obtain the sftp client: %s", serr.Error())
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		sftpclient = sc
 | 
							sftpclient = sc
 | 
				
			||||||
		c.sftpClient = sftpclient
 | 
							c.sftpClient = sftpclient
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,7 +56,7 @@ func (mi *MachineInfo) Conn(ctx context.Context) (*Cli, error) {
 | 
				
			|||||||
	// 如果使用了ssh隧道,则修改机器ip port为暴露的ip port
 | 
						// 如果使用了ssh隧道,则修改机器ip port为暴露的ip port
 | 
				
			||||||
	err := mi.IfUseSshTunnelChangeIpPort(ctx, false)
 | 
						err := mi.IfUseSshTunnelChangeIpPort(ctx, false)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, errorx.NewBiz("ssh tunnel connection failed: %s", err.Error())
 | 
							return nil, errorx.NewBizf("ssh tunnel connection failed: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cli := &Cli{Info: mi}
 | 
						cli := &Cli{Info: mi}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ package mgm
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/pkg/logx"
 | 
						"mayfly-go/pkg/logx"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"go.mongodb.org/mongo-driver/v2/mongo"
 | 
						"go.mongodb.org/mongo-driver/v2/mongo"
 | 
				
			||||||
@@ -28,5 +29,14 @@ func (mc *MongoConn) Close() error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (mc *MongoConn) Ping() error {
 | 
					func (mc *MongoConn) Ping() error {
 | 
				
			||||||
 | 
						// 首先检查mc是否为nil
 | 
				
			||||||
 | 
						if mc == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("mc connection is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 然后检查mc.Cli是否为nil,这是避免空指针异常的关键
 | 
				
			||||||
 | 
						if mc.Cli == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("mc client is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return mc.Cli.Ping(context.Background(), nil)
 | 
						return mc.Cli.Ping(context.Background(), nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ import "fmt"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	AppName = "mayfly-go"
 | 
						AppName = "mayfly-go"
 | 
				
			||||||
	Version = "v1.10.3"
 | 
						Version = "v1.10.4"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetAppInfo() string {
 | 
					func GetAppInfo() string {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -254,7 +254,7 @@ func (r *redisAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *flowap
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	runCmdParam, err := jsonx.To[*FlowRedisRunCmdBizForm](procinst.BizForm)
 | 
						runCmdParam, err := jsonx.To[*FlowRedisRunCmdBizForm](procinst.BizForm)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, errorx.NewBiz("failed to parse the business form information: %s", err.Error())
 | 
							return nil, errorx.NewBizf("failed to parse the business form information: %s", err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	redisConn, err := r.GetRedisConn(ctx, runCmdParam.Id, runCmdParam.Db)
 | 
						redisConn, err := r.GetRedisConn(ctx, runCmdParam.Id, runCmdParam.Db)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ package rdm
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/pkg/errorx"
 | 
						"mayfly-go/pkg/errorx"
 | 
				
			||||||
	"mayfly-go/pkg/logx"
 | 
						"mayfly-go/pkg/logx"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,7 +42,19 @@ func (r *RedisConn) Close() error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *RedisConn) Ping() error {
 | 
					func (r *RedisConn) Ping() error {
 | 
				
			||||||
	_, err := r.Cli.Ping(context.Background()).Result()
 | 
						// 首先检查r是否为nil
 | 
				
			||||||
 | 
						if r == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("redis connection is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 然后检查r.Cli是否为nil,这是避免空指针异常的关键
 | 
				
			||||||
 | 
						if r.Cli == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("redis client is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cmd := r.Cli.Ping(context.Background())
 | 
				
			||||||
 | 
						if cmd == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("the ping cmd is nil")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err := cmd.Result()
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,7 +70,7 @@ func (re *RedisInfo) connStandalone() (*RedisConn, error) {
 | 
				
			|||||||
	_, e := cli.Ping(context.Background()).Result()
 | 
						_, e := cli.Ping(context.Background()).Result()
 | 
				
			||||||
	if e != nil {
 | 
						if e != nil {
 | 
				
			||||||
		cli.Close()
 | 
							cli.Close()
 | 
				
			||||||
		return nil, errorx.NewBiz("redis standalone connection failed: %s", e.Error())
 | 
							return nil, errorx.NewBizf("redis standalone connection failed: %s", e.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logx.Infof("redis standalone connection: %s/%d", re.Host, re.Db)
 | 
						logx.Infof("redis standalone connection: %s/%d", re.Host, re.Db)
 | 
				
			||||||
@@ -95,7 +95,7 @@ func (re *RedisInfo) connCluster() (*RedisConn, error) {
 | 
				
			|||||||
	_, e := cli.Ping(context.Background()).Result()
 | 
						_, e := cli.Ping(context.Background()).Result()
 | 
				
			||||||
	if e != nil {
 | 
						if e != nil {
 | 
				
			||||||
		cli.Close()
 | 
							cli.Close()
 | 
				
			||||||
		return nil, errorx.NewBiz("redis cluster connection failed: %s", e.Error())
 | 
							return nil, errorx.NewBizf("redis cluster connection failed: %s", e.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logx.Infof("redis cluster connection: %s/%d", re.Host, re.Db)
 | 
						logx.Infof("redis cluster connection: %s/%d", re.Host, re.Db)
 | 
				
			||||||
@@ -128,7 +128,7 @@ func (re *RedisInfo) connSentinel() (*RedisConn, error) {
 | 
				
			|||||||
	_, e := cli.Ping(context.Background()).Result()
 | 
						_, e := cli.Ping(context.Background()).Result()
 | 
				
			||||||
	if e != nil {
 | 
						if e != nil {
 | 
				
			||||||
		cli.Close()
 | 
							cli.Close()
 | 
				
			||||||
		return nil, errorx.NewBiz("redis sentinel connection failed: %s", e.Error())
 | 
							return nil, errorx.NewBizf("redis sentinel connection failed: %s", e.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logx.Infof("redis sentinel connection: %s/%d", re.Host, re.Db)
 | 
						logx.Infof("redis sentinel connection: %s/%d", re.Host, re.Db)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,6 +87,7 @@ func (a *Account) GetPermissions(rc *req.Ctx) {
 | 
				
			|||||||
	var resources vo.AccountResourceVOList
 | 
						var resources vo.AccountResourceVOList
 | 
				
			||||||
	// 获取账号菜单资源
 | 
						// 获取账号菜单资源
 | 
				
			||||||
	biz.ErrIsNil(a.resourceApp.GetAccountResources(account.Id, &resources))
 | 
						biz.ErrIsNil(a.resourceApp.GetAccountResources(account.Id, &resources))
 | 
				
			||||||
 | 
						biz.IsTrue(len(resources) > 0, "no permission")
 | 
				
			||||||
	// 菜单树与权限code数组
 | 
						// 菜单树与权限code数组
 | 
				
			||||||
	var menus vo.AccountResourceVOList
 | 
						var menus vo.AccountResourceVOList
 | 
				
			||||||
	var permissions []string
 | 
						var permissions []string
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,7 +121,7 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *dt
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		existNameAc := &entity.ResourceAuthCert{Name: addAcName}
 | 
							existNameAc := &entity.ResourceAuthCert{Name: addAcName}
 | 
				
			||||||
		if r.GetByCond(existNameAc) == nil && existNameAc.ResourceCode != resourceCode {
 | 
							if r.GetByCond(existNameAc) == nil && existNameAc.ResourceCode != resourceCode {
 | 
				
			||||||
			return errorx.NewBiz("The name of the authorization credential cannot be repeated: [%s]", addAcName)
 | 
								return errorx.NewBizf("The name of the authorization credential cannot be repeated: [%s]", addAcName)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		addAuthCerts = append(addAuthCerts, addAc)
 | 
							addAuthCerts = append(addAuthCerts, addAc)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -210,7 +210,7 @@ func (p *tagTreeAppImpl) RelateTagsByCodeAndType(ctx context.Context, param *dto
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if len(parentTagCodePaths) == 0 {
 | 
						if len(parentTagCodePaths) == 0 {
 | 
				
			||||||
		// 不满足满足条件的标签
 | 
							// 不满足满足条件的标签
 | 
				
			||||||
		return errorx.NewBiz("There is no tag that satisfies [type=%d, code=%s]", parentTagType, parentTagCode)
 | 
							return errorx.NewBizf("There is no tag that satisfies [type=%d, code=%s]", parentTagType, parentTagCode)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tag := range param.Tags {
 | 
						for _, tag := range param.Tags {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ func V1_10() []*gormigrate.Migration {
 | 
				
			|||||||
	migrations = append(migrations, V1_10_1()...)
 | 
						migrations = append(migrations, V1_10_1()...)
 | 
				
			||||||
	migrations = append(migrations, V1_10_2()...)
 | 
						migrations = append(migrations, V1_10_2()...)
 | 
				
			||||||
	migrations = append(migrations, V1_10_3()...)
 | 
						migrations = append(migrations, V1_10_3()...)
 | 
				
			||||||
 | 
						migrations = append(migrations, V1_10_4()...)
 | 
				
			||||||
	return migrations
 | 
						return migrations
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -326,3 +327,28 @@ func V1_10_3() []*gormigrate.Migration {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func V1_10_4() []*gormigrate.Migration {
 | 
				
			||||||
 | 
						return []*gormigrate.Migration{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								ID: "20251023-v1.10.4",
 | 
				
			||||||
 | 
								Migrate: func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
									// 给EsInstance表添加protocol列,默认值为http, 20251023,fudawei
 | 
				
			||||||
 | 
									if !tx.Migrator().HasColumn(&esentity.EsInstance{}, "protocol") {
 | 
				
			||||||
 | 
										// 先添加可为空的列
 | 
				
			||||||
 | 
										if err := tx.Exec("ALTER TABLE t_es_instance ADD COLUMN protocol VARCHAR(10) DEFAULT 'http'").Error; err != nil {
 | 
				
			||||||
 | 
											return err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// 更新所有现有记录为默认值http
 | 
				
			||||||
 | 
										if err := tx.Exec("UPDATE t_es_instance SET protocol = 'http' WHERE protocol IS NULL OR protocol = ''").Error; err != nil {
 | 
				
			||||||
 | 
											return err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Rollback: func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ func ErrIsNil(err error, msgAndParams ...any) {
 | 
				
			|||||||
			panic(errorx.NewBiz(err.Error()))
 | 
								panic(errorx.NewBiz(err.Error()))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		panic(errorx.NewBiz(msgAndParams[0].(string), msgAndParams[1:]...))
 | 
							panic(errorx.NewBizf(msgAndParams[0].(string), msgAndParams[1:]...))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,7 +43,7 @@ func ErrIsNilI(ctx context.Context, err error, msgId i18n.MsgId, attrs ...any) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func ErrNotNil(err error, msg string, params ...any) {
 | 
					func ErrNotNil(err error, msg string, params ...any) {
 | 
				
			||||||
	if err == nil {
 | 
						if err == nil {
 | 
				
			||||||
		panic(errorx.NewBiz(msg, params...))
 | 
							panic(errorx.NewBizf(msg, params...))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,7 +53,7 @@ func ErrNotNil(err error, msg string, params ...any) {
 | 
				
			|||||||
//	biz.ErrIsNilAppendErr(err, "xxxx: %s")
 | 
					//	biz.ErrIsNilAppendErr(err, "xxxx: %s")
 | 
				
			||||||
func ErrIsNilAppendErr(err error, msg string) {
 | 
					func ErrIsNilAppendErr(err error, msg string) {
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic(errorx.NewBiz(msg, err.Error()))
 | 
							panic(errorx.NewBizf(msg, err.Error()))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,13 +63,13 @@ func ErrIsNilAppendErr(err error, msg string) {
 | 
				
			|||||||
//	biz.ErrIsNilAppendErr(err, "xxxx: %s")
 | 
					//	biz.ErrIsNilAppendErr(err, "xxxx: %s")
 | 
				
			||||||
func ErrIsNilAppendErrI(ctx context.Context, err error, msgId i18n.MsgId) {
 | 
					func ErrIsNilAppendErrI(ctx context.Context, err error, msgId i18n.MsgId) {
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic(errorx.NewBiz(i18n.TC(ctx, msgId), err.Error()))
 | 
							panic(errorx.NewBizf(i18n.TC(ctx, msgId), err.Error()))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func IsTrue(exp bool, msg string, params ...any) {
 | 
					func IsTrue(exp bool, msg string, params ...any) {
 | 
				
			||||||
	if !exp {
 | 
						if !exp {
 | 
				
			||||||
		panic(errorx.NewBiz(msg, params...))
 | 
							panic(errorx.NewBizf(msg, params...))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,19 +87,19 @@ func IsTrueBy(exp bool, err *errorx.BizError) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func NotEmpty(str string, msg string, params ...any) {
 | 
					func NotEmpty(str string, msg string, params ...any) {
 | 
				
			||||||
	if str == "" {
 | 
						if str == "" {
 | 
				
			||||||
		panic(errorx.NewBiz(msg, params...))
 | 
							panic(errorx.NewBizf(msg, params...))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NotNil(data any, msg string, params ...any) {
 | 
					func NotNil(data any, msg string, params ...any) {
 | 
				
			||||||
	if reflect.ValueOf(data).IsNil() {
 | 
						if reflect.ValueOf(data).IsNil() {
 | 
				
			||||||
		panic(errorx.NewBiz(msg, params...))
 | 
							panic(errorx.NewBizf(msg, params...))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NotBlank(data any, msg string, params ...any) {
 | 
					func NotBlank(data any, msg string, params ...any) {
 | 
				
			||||||
	if anyx.IsBlank(data) {
 | 
						if anyx.IsBlank(data) {
 | 
				
			||||||
		panic(errorx.NewBiz(msg, params...))
 | 
							panic(errorx.NewBizf(msg, params...))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,8 +35,13 @@ func (e BizError) String() string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewBiz 创建业务逻辑错误结构体,默认为业务逻辑错误
 | 
					// NewBiz 创建业务逻辑错误结构体,默认为业务逻辑错误
 | 
				
			||||||
func NewBiz(msg string, formatValues ...any) *BizError {
 | 
					func NewBiz(msg string) *BizError {
 | 
				
			||||||
	return &BizError{code: BizErr.code, err: fmt.Sprintf(msg, formatValues...)}
 | 
						return &BizError{code: BizErr.code, err: msg}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewBizf 创建业务逻辑错误结构体,可设置格式化参数
 | 
				
			||||||
 | 
					func NewBizf(format string, formatValues ...any) *BizError {
 | 
				
			||||||
 | 
						return NewBiz(fmt.Sprintf(format, formatValues...))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewBizI 使用i18n的msgId创建业务逻辑错误结构体,默认为业务逻辑错误 (使用ctx中的国际化语言)
 | 
					// NewBizI 使用i18n的msgId创建业务逻辑错误结构体,默认为业务逻辑错误 (使用ctx中的国际化语言)
 | 
				
			||||||
@@ -47,7 +52,12 @@ func NewBizI(ctx context.Context, msgId i18n.MsgId, attrs ...any) *BizError {
 | 
				
			|||||||
	return &BizError{code: BizErr.code, err: i18n.TC(ctx, msgId, attrs...)}
 | 
						return &BizError{code: BizErr.code, err: i18n.TC(ctx, msgId, attrs...)}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 创建业务逻辑错误结构体,可设置指定错误code
 | 
					// NewBizCode 创建业务逻辑错误结构体,可设置指定错误code
 | 
				
			||||||
func NewBizCode(code int16, msg string, formats ...any) *BizError {
 | 
					func NewBizCode(code int16, msg string) *BizError {
 | 
				
			||||||
	return &BizError{code: code, err: fmt.Sprintf(msg, formats...)}
 | 
						return &BizError{code: code, err: msg}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewBizCodef 创建业务逻辑错误结构体,可设置指定错误code,并且支持格式化参数
 | 
				
			||||||
 | 
					func NewBizCodef(code int16, format string, formats ...any) *BizError {
 | 
				
			||||||
 | 
						return NewBizCode(code, fmt.Sprintf(format, formats...))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ package httpx
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@@ -41,6 +42,16 @@ func NewReq(url string) *Req {
 | 
				
			|||||||
	return &Req{url: url, client: http.Client{}}
 | 
						return &Req{url: url, client: http.Client{}}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 创建一个请求(不验证TLS证书)
 | 
				
			||||||
 | 
					func NewReqWithInsecureTLS(url string) *Req {
 | 
				
			||||||
 | 
						transport := &http.Transport{
 | 
				
			||||||
 | 
							TLSClientConfig: &tls.Config{
 | 
				
			||||||
 | 
								InsecureSkipVerify: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &Req{url: url, client: http.Client{Transport: transport}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Req) Url(url string) *Req {
 | 
					func (r *Req) Url(url string) *Req {
 | 
				
			||||||
	r.url = url
 | 
						r.url = url
 | 
				
			||||||
	return r
 | 
						return r
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,8 +121,8 @@ func Errorf(format string, args ...any) {
 | 
				
			|||||||
	Log(context.Background(), slog.LevelError, fmt.Sprintf(format, args...))
 | 
						Log(context.Background(), slog.LevelError, fmt.Sprintf(format, args...))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 错误记录,并将堆栈信息添加至msg里,默认记录10个堆栈信息
 | 
					// ErrorTraceContext 错误记录,并将堆栈信息添加至msg里,默认记录10个堆栈信息
 | 
				
			||||||
func ErrorTrace(msg string, err any) {
 | 
					func ErrorTraceContext(ctx context.Context, msg string, err any) {
 | 
				
			||||||
	errMsg := ""
 | 
						errMsg := ""
 | 
				
			||||||
	switch t := err.(type) {
 | 
						switch t := err.(type) {
 | 
				
			||||||
	case error:
 | 
						case error:
 | 
				
			||||||
@@ -132,7 +132,12 @@ func ErrorTrace(msg string, err any) {
 | 
				
			|||||||
	default:
 | 
						default:
 | 
				
			||||||
		errMsg = fmt.Sprintf("%v", t)
 | 
							errMsg = fmt.Sprintf("%v", t)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	Log(context.Background(), slog.LevelError, fmt.Sprintf(msg+"\n%s\n%s", errMsg, runtimex.StackStr(2, 20)))
 | 
						Log(ctx, slog.LevelError, fmt.Sprintf(msg+"\n%s\n%s", errMsg, runtimex.StackStr(2, 20)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorTrace 错误记录,并将堆栈信息添加至msg里,默认记录10个堆栈信息
 | 
				
			||||||
 | 
					func ErrorTrace(msg string, err any) {
 | 
				
			||||||
 | 
						ErrorTraceContext(context.Background(), msg, err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ErrorWithFields(ctx context.Context, msg string, mapFields map[string]any) {
 | 
					func ErrorWithFields(ctx context.Context, msg string, mapFields map[string]any) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,8 +49,9 @@ func Error(bizerr *errorx.BizError) *Result {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 返回服务器错误Result
 | 
					// 返回服务器错误Result
 | 
				
			||||||
func ServerError() *Result {
 | 
					func ServerError(msg string) *Result {
 | 
				
			||||||
	return Error(errorx.ServerError)
 | 
						serverErr := errorx.NewBizCode(errorx.ServerError.Code(), msg)
 | 
				
			||||||
 | 
						return Error(serverErr)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TokenError() *Result {
 | 
					func TokenError() *Result {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
package req
 | 
					package req
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"cmp"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/pkg/biz"
 | 
						"mayfly-go/pkg/biz"
 | 
				
			||||||
	"mayfly-go/pkg/contextx"
 | 
						"mayfly-go/pkg/contextx"
 | 
				
			||||||
	"mayfly-go/pkg/errorx"
 | 
						"mayfly-go/pkg/errorx"
 | 
				
			||||||
@@ -109,8 +111,8 @@ func (rc *Ctx) res() {
 | 
				
			|||||||
		case *errorx.BizError:
 | 
							case *errorx.BizError:
 | 
				
			||||||
			rc.JSONRes(http.StatusOK, model.Error(t))
 | 
								rc.JSONRes(http.StatusOK, model.Error(t))
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			logx.ErrorTrace("服务器错误", t)
 | 
								logx.ErrorTrace("server error", t)
 | 
				
			||||||
			rc.JSONRes(http.StatusOK, model.ServerError())
 | 
								rc.JSONRes(http.StatusOK, model.ServerError(fmt.Sprintf("server error [%d-%s]", errorx.ServerError.Code(), cmp.Or(contextx.GetTraceId(rc.MetaCtx), "none"))))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,7 +53,7 @@ func Ip2Region(ip string) string {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 2、用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
 | 
						// 2、用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
 | 
				
			||||||
	searcher, err := xdb.NewWithVectorIndex(ip2RegionDbPath, vectorIndex)
 | 
						searcher, err := xdb.NewWithVectorIndex(xdb.IPv4, ip2RegionDbPath, vectorIndex)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logx.Errorf("failed to create searcher with vector index: %s\n", err)
 | 
							logx.Errorf("failed to create searcher with vector index: %s\n", err)
 | 
				
			||||||
		return ""
 | 
							return ""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,17 +30,16 @@ func (ucs UserClients) Count() int {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 连接管理
 | 
					// 连接管理
 | 
				
			||||||
type ClientManager struct {
 | 
					type ClientManager struct {
 | 
				
			||||||
	UserClientsMap map[UserId]UserClients // 全部的用户连接, key->userid, value->UserClients
 | 
						UserClientsMap sync.Map // 全部的用户连接, key->userid, value->UserClients
 | 
				
			||||||
	RwLock         sync.RWMutex           // 读写锁
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ConnectChan    chan *Client // 连接处理
 | 
						ConnectChan    chan *Client // 连接处理
 | 
				
			||||||
	DisConnectChan chan *Client // 断开连接处理
 | 
						DisConnectChan chan *Client // 断开连接处理
 | 
				
			||||||
	MsgChan        chan *Msg    //  消息信息channel通道
 | 
						MsgChan        chan *Msg    // 消息信息channel通道
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewClientManager() (clientManager *ClientManager) {
 | 
					func NewClientManager() (clientManager *ClientManager) {
 | 
				
			||||||
	return &ClientManager{
 | 
						return &ClientManager{
 | 
				
			||||||
		UserClientsMap: make(map[UserId]UserClients),
 | 
							UserClientsMap: sync.Map{},
 | 
				
			||||||
		ConnectChan:    make(chan *Client, 10),
 | 
							ConnectChan:    make(chan *Client, 10),
 | 
				
			||||||
		DisConnectChan: make(chan *Client, 10),
 | 
							DisConnectChan: make(chan *Client, 10),
 | 
				
			||||||
		MsgChan:        make(chan *Msg, 100),
 | 
							MsgChan:        make(chan *Msg, 100),
 | 
				
			||||||
@@ -78,24 +77,30 @@ func (manager *ClientManager) CloseClient(client *Client) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 根据用户id关闭客户端连接
 | 
					// 根据用户id关闭客户端连接
 | 
				
			||||||
func (manager *ClientManager) CloseByUid(userId UserId) {
 | 
					func (manager *ClientManager) CloseByUid(userId UserId) {
 | 
				
			||||||
	for _, client := range manager.GetByUid(userId) {
 | 
						userClients := manager.GetByUid(userId)
 | 
				
			||||||
 | 
						for _, client := range userClients {
 | 
				
			||||||
		manager.CloseClient(client)
 | 
							manager.CloseClient(client)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获取所有的客户端
 | 
					// 获取所有的客户端
 | 
				
			||||||
func (manager *ClientManager) AllUserClient() map[UserId]UserClients {
 | 
					func (manager *ClientManager) AllUserClient() map[UserId]UserClients {
 | 
				
			||||||
	manager.RwLock.RLock()
 | 
						result := make(map[UserId]UserClients)
 | 
				
			||||||
	defer manager.RwLock.RUnlock()
 | 
						manager.UserClientsMap.Range(func(key, value any) bool {
 | 
				
			||||||
 | 
							userId := key.(UserId)
 | 
				
			||||||
	return manager.UserClientsMap
 | 
							userClients := value.(UserClients)
 | 
				
			||||||
 | 
							result[userId] = userClients
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 通过userId获取用户所有客户端信息
 | 
					// 通过userId获取用户所有客户端信息
 | 
				
			||||||
func (manager *ClientManager) GetByUid(userId UserId) UserClients {
 | 
					func (manager *ClientManager) GetByUid(userId UserId) UserClients {
 | 
				
			||||||
	manager.RwLock.RLock()
 | 
						if value, ok := manager.UserClientsMap.Load(userId); ok {
 | 
				
			||||||
	defer manager.RwLock.RUnlock()
 | 
							return value.(UserClients)
 | 
				
			||||||
	return manager.UserClientsMap[userId]
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 通过userId和clientId获取客户端信息
 | 
					// 通过userId和clientId获取客户端信息
 | 
				
			||||||
@@ -108,9 +113,12 @@ func (manager *ClientManager) GetByUidAndCid(uid UserId, clientId string) *Clien
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 客户端数量
 | 
					// 客户端数量
 | 
				
			||||||
func (manager *ClientManager) Count() int {
 | 
					func (manager *ClientManager) Count() int {
 | 
				
			||||||
	manager.RwLock.RLock()
 | 
						count := 0
 | 
				
			||||||
	defer manager.RwLock.RUnlock()
 | 
						manager.UserClientsMap.Range(func(key, value any) bool {
 | 
				
			||||||
	return len(manager.UserClientsMap)
 | 
							count++
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return count
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 发送json数据给指定用户
 | 
					// 发送json数据给指定用户
 | 
				
			||||||
@@ -139,7 +147,8 @@ func (manager *ClientManager) WriteMessage() {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// cid为空,则向该用户所有客户端发送该消息
 | 
								// cid为空,则向该用户所有客户端发送该消息
 | 
				
			||||||
			for _, cli := range manager.GetByUid(uid) {
 | 
								userClients := manager.GetByUid(uid)
 | 
				
			||||||
 | 
								for _, cli := range userClients {
 | 
				
			||||||
				if err := cli.WriteMsg(msg); err != nil {
 | 
									if err := cli.WriteMsg(msg); err != nil {
 | 
				
			||||||
					logx.Warnf("ws send message failed - [uid=%d, cid=%s]: %s", uid, cli.ClientId, err.Error())
 | 
										logx.Warnf("ws send message failed - [uid=%d, cid=%s]: %s", uid, cli.ClientId, err.Error())
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -156,21 +165,23 @@ func (manager *ClientManager) HeartbeatTimer() {
 | 
				
			|||||||
		for {
 | 
							for {
 | 
				
			||||||
			<-ticker.C
 | 
								<-ticker.C
 | 
				
			||||||
			//发送心跳
 | 
								//发送心跳
 | 
				
			||||||
			for userId, clis := range manager.AllUserClient() {
 | 
								manager.UserClientsMap.Range(func(key, value any) bool {
 | 
				
			||||||
 | 
									userId := key.(UserId)
 | 
				
			||||||
 | 
									clis := value.(UserClients)
 | 
				
			||||||
				for _, cli := range clis {
 | 
									for _, cli := range clis {
 | 
				
			||||||
					if cli == nil || cli.WsConn == nil {
 | 
										if cli == nil || cli.WsConn == nil {
 | 
				
			||||||
						continue
 | 
											continue
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					if err := cli.Ping(); err != nil {
 | 
										if err := cli.Ping(); err != nil {
 | 
				
			||||||
						manager.CloseClient(cli)
 | 
											manager.CloseClient(cli)
 | 
				
			||||||
						logx.Debugf("WS - failed to send heartbeat: uid=%v, cid=%s, usercount=%d", userId, cli.ClientId, Manager.Count())
 | 
											logx.Debugf("WS - failed to send heartbeat: uid=%v, cid=%s, usercount=%d", userId, cli.ClientId, manager.Count())
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						logx.Debugf("WS - send heartbeat successfully: uid=%v, cid=%s", userId, cli.ClientId)
 | 
											logx.Debugf("WS - send heartbeat successfully: uid=%v, cid=%s", userId, cli.ClientId)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
									return true
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -192,31 +203,29 @@ func (manager *ClientManager) doDisconnect(client *Client) {
 | 
				
			|||||||
		client.WsConn = nil
 | 
							client.WsConn = nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	manager.delUserClient4Map(client)
 | 
						manager.delUserClient4Map(client)
 | 
				
			||||||
	logx.Debugf("WS client disconnected: uid=%d, cid=%s, usercount=%d", client.UserId, client.ClientId, Manager.Count())
 | 
						logx.Debugf("WS client disconnected: uid=%d, cid=%s, usercount=%d", client.UserId, client.ClientId, manager.Count())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (manager *ClientManager) addUserClient2Map(client *Client) {
 | 
					func (manager *ClientManager) addUserClient2Map(client *Client) {
 | 
				
			||||||
	manager.RwLock.Lock()
 | 
						// 先尝试加载现有的UserClients
 | 
				
			||||||
	defer manager.RwLock.Unlock()
 | 
						if value, ok := manager.UserClientsMap.Load(client.UserId); ok {
 | 
				
			||||||
 | 
							userClients := value.(UserClients)
 | 
				
			||||||
	userClients := manager.UserClientsMap[client.UserId]
 | 
							userClients.AddClient(client)
 | 
				
			||||||
	if userClients == nil {
 | 
						} else {
 | 
				
			||||||
		userClients = make(UserClients)
 | 
							// 创建新的UserClients
 | 
				
			||||||
		manager.UserClientsMap[client.UserId] = userClients
 | 
							userClients := make(UserClients)
 | 
				
			||||||
 | 
							userClients.AddClient(client)
 | 
				
			||||||
 | 
							manager.UserClientsMap.Store(client.UserId, userClients)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	userClients.AddClient(client)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (manager *ClientManager) delUserClient4Map(client *Client) {
 | 
					func (manager *ClientManager) delUserClient4Map(client *Client) {
 | 
				
			||||||
	manager.RwLock.Lock()
 | 
						if value, ok := manager.UserClientsMap.Load(client.UserId); ok {
 | 
				
			||||||
	defer manager.RwLock.Unlock()
 | 
							userClients := value.(UserClients)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	userClients := manager.UserClientsMap[client.UserId]
 | 
					 | 
				
			||||||
	if userClients != nil {
 | 
					 | 
				
			||||||
		userClients.DeleteByCid(client.ClientId)
 | 
							userClients.DeleteByCid(client.ClientId)
 | 
				
			||||||
		// 如果用户所有客户端都关闭,则移除manager中的UserClientsMap值
 | 
							// 如果用户所有客户端都关闭,则移除manager中的UserClientsMap值
 | 
				
			||||||
		if userClients.Count() == 0 {
 | 
							if userClients.Count() == 0 {
 | 
				
			||||||
			delete(manager.UserClientsMap, client.UserId)
 | 
								manager.UserClientsMap.Delete(client.UserId)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user