mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2deb3109c2 | ||
| 
						 | 
					a80221a950 | ||
| 
						 | 
					10630847df | 
@@ -10,20 +10,20 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@element-plus/icons-vue": "^2.3.1",
 | 
			
		||||
    "@vueuse/core": "^10.10.0",
 | 
			
		||||
    "asciinema-player": "^3.7.1",
 | 
			
		||||
    "@vueuse/core": "^10.11.0",
 | 
			
		||||
    "asciinema-player": "^3.8.0",
 | 
			
		||||
    "axios": "^1.6.2",
 | 
			
		||||
    "clipboard": "^2.0.11",
 | 
			
		||||
    "cropperjs": "^1.6.1",
 | 
			
		||||
    "dayjs": "^1.11.11",
 | 
			
		||||
    "echarts": "^5.5.0",
 | 
			
		||||
    "element-plus": "^2.7.4",
 | 
			
		||||
    "echarts": "^5.5.1",
 | 
			
		||||
    "element-plus": "^2.7.7",
 | 
			
		||||
    "js-base64": "^3.7.7",
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "monaco-editor": "^0.49.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.12.0",
 | 
			
		||||
    "monaco-editor": "^0.50.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.12.2",
 | 
			
		||||
    "monaco-themes": "^0.4.4",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.1.7",
 | 
			
		||||
@@ -34,8 +34,8 @@
 | 
			
		||||
    "sql-formatter": "^15.0.2",
 | 
			
		||||
    "trzsz": "^1.1.5",
 | 
			
		||||
    "uuid": "^9.0.1",
 | 
			
		||||
    "vue": "^3.4.27",
 | 
			
		||||
    "vue-router": "^4.3.2",
 | 
			
		||||
    "vue": "^3.4.32",
 | 
			
		||||
    "vue-router": "^4.4.0",
 | 
			
		||||
    "xterm": "^5.3.0",
 | 
			
		||||
    "xterm-addon-fit": "^0.8.0",
 | 
			
		||||
    "xterm-addon-search": "^0.13.0",
 | 
			
		||||
@@ -48,16 +48,16 @@
 | 
			
		||||
    "@types/sortablejs": "^1.15.8",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
			
		||||
    "@typescript-eslint/parser": "^6.7.4",
 | 
			
		||||
    "@vitejs/plugin-vue": "^5.0.4",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.4.27",
 | 
			
		||||
    "@vitejs/plugin-vue": "^5.0.5",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.4.32",
 | 
			
		||||
    "code-inspector-plugin": "^0.4.5",
 | 
			
		||||
    "dotenv": "^16.3.1",
 | 
			
		||||
    "eslint": "^8.35.0",
 | 
			
		||||
    "eslint-plugin-vue": "^9.25.0",
 | 
			
		||||
    "prettier": "^3.2.5",
 | 
			
		||||
    "sass": "^1.77.1",
 | 
			
		||||
    "typescript": "^5.4.5",
 | 
			
		||||
    "vite": "^5.2.12",
 | 
			
		||||
    "sass": "^1.77.8",
 | 
			
		||||
    "typescript": "^5.5.3",
 | 
			
		||||
    "vite": "^5.3.4",
 | 
			
		||||
    "vue-eslint-parser": "^9.4.2"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ const config = {
 | 
			
		||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
			
		||||
 | 
			
		||||
    // 系统版本
 | 
			
		||||
    version: 'v1.8.7',
 | 
			
		||||
    version: 'v1.8.8',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,19 @@ export function getValueByPath(obj: any, path: string) {
 | 
			
		||||
    const keys = path.split('.');
 | 
			
		||||
    let result = obj;
 | 
			
		||||
    for (let key of keys) {
 | 
			
		||||
        if (!result || typeof result !== 'object') {
 | 
			
		||||
        if (!result) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
        // 如果是字符串,则尝试使用json解析
 | 
			
		||||
        if (typeof result == 'string') {
 | 
			
		||||
            try {
 | 
			
		||||
                result = JSON.parse(result);
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                return undefined;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof result !== 'object') {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +35,18 @@ export function getValueByPath(obj: any, path: string) {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const index = parseInt(matchIndex[1]);
 | 
			
		||||
            result = Array.isArray(result[arrayKey]) ? result[arrayKey][index] : undefined;
 | 
			
		||||
 | 
			
		||||
            let arrValue = result[arrayKey];
 | 
			
		||||
            if (typeof arrValue == 'string') {
 | 
			
		||||
                try {
 | 
			
		||||
                    arrValue = JSON.parse(arrValue);
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    result = undefined;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            result = Array.isArray(arrValue) ? arrValue[index] : undefined;
 | 
			
		||||
        } else {
 | 
			
		||||
            result = result[key];
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,13 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-descriptions :column="3" border>
 | 
			
		||||
            <el-descriptions-item :span="2" label="名称">{{ db?.name }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="id">{{ db?.id }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="db.tags" /></el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="db.codePaths" /></el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="1" label="名称">{{ db?.name }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="主机">{{ `${db?.host}:${db?.port}` }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="类型">
 | 
			
		||||
                <SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />{{ db?.type }}
 | 
			
		||||
            </el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="用户名">{{ db?.username }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item label="数据库">{{ sqlExec.db }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item label="表">
 | 
			
		||||
@@ -33,7 +30,9 @@ import { dbApi } from '@/views/ops/db/api';
 | 
			
		||||
import { DbSqlExecTypeEnum } from '@/views/ops/db/enums';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
import { getDbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
 | 
			
		||||
import { tagApi } from '@/views/ops/tag/api';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import TagCodePath from '@/views/ops/component/TagCodePath.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    // 业务key
 | 
			
		||||
@@ -74,6 +73,10 @@ const getDbSqlExec = async (bizKey: string) => {
 | 
			
		||||
    state.sqlExec = res.list?.[0];
 | 
			
		||||
    const dbRes = await dbApi.dbs.request({ id: state.sqlExec.dbId });
 | 
			
		||||
    state.db = dbRes.list?.[0];
 | 
			
		||||
 | 
			
		||||
    tagApi.listByQuery.request({ type: TagResourceTypeEnum.DbName.value, codes: state.db.code }).then((res) => {
 | 
			
		||||
        state.db.codePaths = res.map((item: any) => item.codePath);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-descriptions :column="3" border>
 | 
			
		||||
            <el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="id">{{ redis?.id }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="用户名">{{ redis?.username }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="3" label="标签"><TagCodePath :path="redis.codePaths" /></el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="redis.tags" /></el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="2" label="编号">{{ redis?.code }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
            <el-descriptions-item :span="1" label="主机">{{ `${redis?.host}` }}</el-descriptions-item>
 | 
			
		||||
            <el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
 | 
			
		||||
@@ -22,8 +21,10 @@
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { toRefs, reactive, watch, onMounted } from 'vue';
 | 
			
		||||
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
 | 
			
		||||
import { redisApi } from '@/views/ops/redis/api';
 | 
			
		||||
import TagCodePath from '@/views/ops/component/TagCodePath.vue';
 | 
			
		||||
import { tagApi } from '@/views/ops/tag/api';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    // 业务表单
 | 
			
		||||
@@ -75,6 +76,10 @@ const parseRunCmdForm = async (bizForm: string) => {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    state.redis = res.list?.[0];
 | 
			
		||||
 | 
			
		||||
    tagApi.listByQuery.request({ type: TagResourceTypeEnum.Redis.value, codes: state.redis.code }).then((res) => {
 | 
			
		||||
        state.redis.codePaths = res.map((item: any) => item.codePath);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -222,7 +222,7 @@
 | 
			
		||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { formatByteSize } from '@/common/utils/format';
 | 
			
		||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
			
		||||
import { DbInst, DbThemeConfig, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
 | 
			
		||||
import { NodeType, TagTreeNode, getTagTypeCodeByPath } from '../component/tag';
 | 
			
		||||
import TagTree from '../component/TagTree.vue';
 | 
			
		||||
import { dbApi } from './api';
 | 
			
		||||
@@ -505,7 +505,7 @@ const state = reactive({
 | 
			
		||||
 | 
			
		||||
const { nowDbInst, tableCreateDialog } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false, locationTreeNode: false });
 | 
			
		||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
			
		||||
 | 
			
		||||
const serverInfoReqParam = ref({
 | 
			
		||||
    instanceId: 0,
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        type="Date"
 | 
			
		||||
        value-format="YYYY-MM-DD"
 | 
			
		||||
        placeholder="选择日期"
 | 
			
		||||
        :placeholder="`选择日期-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-date-picker
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        type="datetime"
 | 
			
		||||
        value-format="YYYY-MM-DD HH:mm:ss"
 | 
			
		||||
        placeholder="选择日期时间"
 | 
			
		||||
        :placeholder="`选择日期时间-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
 | 
			
		||||
    <el-time-picker
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
        v-model="itemValue"
 | 
			
		||||
        :clearable="false"
 | 
			
		||||
        value-format="HH:mm:ss"
 | 
			
		||||
        placeholder="选择时间"
 | 
			
		||||
        :placeholder="`选择时间-${placeholder}`"
 | 
			
		||||
    />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -156,7 +156,7 @@
 | 
			
		||||
import { onBeforeUnmount, onMounted, reactive, ref, toRefs, watch } from 'vue';
 | 
			
		||||
import { ElInput, ElMessage } from 'element-plus';
 | 
			
		||||
import { copyToClipboard } from '@/common/utils/string';
 | 
			
		||||
import { DbInst } from '@/views/ops/db/db';
 | 
			
		||||
import { DbInst, DbThemeConfig } from '@/views/ops/db/db';
 | 
			
		||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { exportCsv, exportFile } from '@/common/utils/export';
 | 
			
		||||
@@ -258,12 +258,10 @@ const cmDataDel = new ContextmenuItem('deleteData', '删除')
 | 
			
		||||
        return state.table == '';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
const cmDataEdit = new ContextmenuItem('editData', '编辑行')
 | 
			
		||||
    .withIcon('edit')
 | 
			
		||||
    .withOnClick(() => onEditRowData())
 | 
			
		||||
    .withHideFunc(() => {
 | 
			
		||||
        return state.table == '';
 | 
			
		||||
    });
 | 
			
		||||
const cmFormView = new ContextmenuItem('formView', '表单视图').withIcon('Document').withOnClick(() => onEditRowData());
 | 
			
		||||
// .withHideFunc(() => {
 | 
			
		||||
//     return state.table == '';
 | 
			
		||||
// });
 | 
			
		||||
 | 
			
		||||
const cmDataGenInsertSql = new ContextmenuItem('genInsertSql', 'Insert SQL')
 | 
			
		||||
    .withIcon('tickets')
 | 
			
		||||
@@ -363,7 +361,7 @@ const state = reactive({
 | 
			
		||||
 | 
			
		||||
const { tableHeight, datas } = toRefs(state);
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
 | 
			
		||||
const dbConfig = useStorage('dbConfig', DbThemeConfig);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 行号字段列
 | 
			
		||||
@@ -595,7 +593,7 @@ const dataContextmenuClick = (event: any, rowIndex: number, column: any, data: a
 | 
			
		||||
    const { clientX, clientY } = event;
 | 
			
		||||
    state.contextmenu.dropdown.x = clientX;
 | 
			
		||||
    state.contextmenu.dropdown.y = clientY;
 | 
			
		||||
    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmDataEdit, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
			
		||||
    state.contextmenu.items = [cmDataCopyCell, cmDataDel, cmFormView, cmDataGenInsertSql, cmDataGenJson, cmDataExportCsv, cmDataExportSql];
 | 
			
		||||
    contextmenuRef.value.openContextmenu({ column, rowData: data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -627,12 +625,12 @@ const onDeleteData = async () => {
 | 
			
		||||
const onEditRowData = () => {
 | 
			
		||||
    const selectionDatas = Array.from(selectionRowsMap.values());
 | 
			
		||||
    if (selectionDatas.length > 1) {
 | 
			
		||||
        ElMessage.warning('只能编辑一行数据');
 | 
			
		||||
        ElMessage.warning('只能选择一行数据');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const data = selectionDatas[0];
 | 
			
		||||
    state.tableDataFormDialog.data = { ...data };
 | 
			
		||||
    state.tableDataFormDialog.title = `编辑表'${props.table}'数据`;
 | 
			
		||||
    state.tableDataFormDialog.title = state.table ? `'${props.table}'表单数据` : '表单视图';
 | 
			
		||||
    state.tableDataFormDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -648,7 +646,7 @@ const onGenerateJson = async () => {
 | 
			
		||||
    // 按列字段重新排序对象key
 | 
			
		||||
    const jsonObj = [];
 | 
			
		||||
    for (let selectionData of selectionDatas) {
 | 
			
		||||
        let obj = {};
 | 
			
		||||
        let obj: any = {};
 | 
			
		||||
        for (let column of state.columns) {
 | 
			
		||||
            if (column.show) {
 | 
			
		||||
                obj[column.title] = selectionData[column.dataKey];
 | 
			
		||||
@@ -752,7 +750,7 @@ const submitUpdateFields = async () => {
 | 
			
		||||
 | 
			
		||||
    for (let updateRow of cellUpdateMap.values()) {
 | 
			
		||||
        const rowData = { ...updateRow.rowData };
 | 
			
		||||
        let updateColumnValue = {};
 | 
			
		||||
        let updateColumnValue: any = {};
 | 
			
		||||
 | 
			
		||||
        for (let k of updateRow.columnsMap.keys()) {
 | 
			
		||||
            const v = updateRow.columnsMap.get(k);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@
 | 
			
		||||
                :key="column.columnName"
 | 
			
		||||
                class="w100 mb5"
 | 
			
		||||
                :prop="column.columnName"
 | 
			
		||||
                :required="!column.nullable && !column.isPrimaryKey && !column.isIdentity"
 | 
			
		||||
                :required="props.tableName != '' && !column.nullable && !column.isPrimaryKey && !column.isIdentity"
 | 
			
		||||
            >
 | 
			
		||||
                <template #label>
 | 
			
		||||
                    <span class="pointer" :title="`${column.columnType} | ${column.columnComment}`">
 | 
			
		||||
                    <span class="pointer" :title="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType">
 | 
			
		||||
                        {{ column.columnName }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                </template>
 | 
			
		||||
@@ -17,13 +17,13 @@
 | 
			
		||||
                <ColumnFormItem
 | 
			
		||||
                    v-model="modelValue[`${column.columnName}`]"
 | 
			
		||||
                    :data-type="dbInst.getDialect().getDataType(column.dataType)"
 | 
			
		||||
                    :placeholder="`${column.columnType}  ${column.columnComment}`"
 | 
			
		||||
                    :placeholder="column?.columnComment ? `${column.columnType} | ${column.columnComment}` : column.columnType"
 | 
			
		||||
                    :column-name="column.columnName"
 | 
			
		||||
                    :disabled="column.isIdentity"
 | 
			
		||||
                />
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <template #footer>
 | 
			
		||||
        <template #footer v-if="props.tableName">
 | 
			
		||||
            <span class="dialog-footer">
 | 
			
		||||
                <el-button @click="closeDialog">取消</el-button>
 | 
			
		||||
                <el-button type="primary" @click="confirm">确定</el-button>
 | 
			
		||||
@@ -99,7 +99,7 @@ const confirm = async () => {
 | 
			
		||||
 | 
			
		||||
    let sql = '';
 | 
			
		||||
    if (oldValue) {
 | 
			
		||||
        const updateColumnValue = {};
 | 
			
		||||
        const updateColumnValue: any = {};
 | 
			
		||||
        Object.keys(oldValue).forEach((key) => {
 | 
			
		||||
            // 如果新旧值不相等,则为需要更新的字段
 | 
			
		||||
            if (oldValue[key] !== modelValue.value[key]) {
 | 
			
		||||
 
 | 
			
		||||
@@ -50,22 +50,6 @@
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <!-- 表数据展示配置 -->
 | 
			
		||||
                    <el-popover
 | 
			
		||||
                        popper-style="max-height: 550px; overflow: auto; max-width: 450px"
 | 
			
		||||
                        placement="bottom"
 | 
			
		||||
                        width="auto"
 | 
			
		||||
                        title="展示配置"
 | 
			
		||||
                        trigger="click"
 | 
			
		||||
                    >
 | 
			
		||||
                        <el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-value="true" :false-value="false" size="small" />
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link type="primary" icon="setting" :underline="false"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popover>
 | 
			
		||||
 | 
			
		||||
                    <el-divider direction="vertical" border-style="dashed" />
 | 
			
		||||
 | 
			
		||||
                    <el-tooltip :show-after="500" v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
 | 
			
		||||
                        <el-link @click="submitUpdateFields()" type="success" :underline="false" class="font12">提交</el-link>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
@@ -258,7 +242,7 @@ import { DbInst } from '@/views/ops/db/db';
 | 
			
		||||
import DbTableData from './DbTableData.vue';
 | 
			
		||||
import { DbDialect } from '@/views/ops/db/dialect';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { useEventListener, useStorage } from '@vueuse/core';
 | 
			
		||||
import { useEventListener } from '@vueuse/core';
 | 
			
		||||
import { copyToClipboard, fuzzyMatchField } from '@/common/utils/string';
 | 
			
		||||
import DbTableDataForm from './DbTableDataForm.vue';
 | 
			
		||||
 | 
			
		||||
@@ -288,8 +272,6 @@ const condDialogInputRef: Ref = ref(null);
 | 
			
		||||
 | 
			
		||||
const defaultPageSize = DbInst.DefaultLimit;
 | 
			
		||||
 | 
			
		||||
const dbConfig = useStorage('dbConfig', { showColumnComment: false });
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    datas: [],
 | 
			
		||||
    sql: '', // 当前数据tab执行的sql
 | 
			
		||||
 
 | 
			
		||||
@@ -842,3 +842,18 @@ function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string)
 | 
			
		||||
        return tables.length > 0 ? tables[0] : undefined;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 数据库主题配置
 | 
			
		||||
 */
 | 
			
		||||
export const DbThemeConfig = {
 | 
			
		||||
    /**
 | 
			
		||||
     * 表数据表头是否显示备注
 | 
			
		||||
     */
 | 
			
		||||
    showColumnComment: true,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否自动定位至树节点
 | 
			
		||||
     */
 | 
			
		||||
    locationTreeNode: true,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ export enum DataType {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 列数据类型角标 */
 | 
			
		||||
export const ColumnTypeSubscript = {
 | 
			
		||||
export const ColumnTypeSubscript: any = {
 | 
			
		||||
    /** 字符串 */
 | 
			
		||||
    string: 'ab',
 | 
			
		||||
    /** 数字 */
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,8 @@ module mayfly-go
 | 
			
		||||
go 1.22
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	gitee.com/chunanyong/dm v1.8.14
 | 
			
		||||
	gitee.com/chunanyong/dm v1.8.15
 | 
			
		||||
	gitee.com/liuzongyang/libpq v1.0.9
 | 
			
		||||
	github.com/buger/jsonparser v1.1.1
 | 
			
		||||
	github.com/emirpasic/gods v1.18.1
 | 
			
		||||
	github.com/gin-gonic/gin v1.10.0
 | 
			
		||||
	github.com/glebarez/sqlite v1.11.0
 | 
			
		||||
@@ -17,29 +16,30 @@ require (
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.8.1
 | 
			
		||||
	github.com/golang-jwt/jwt/v5 v5.2.1
 | 
			
		||||
	github.com/google/uuid v1.6.0
 | 
			
		||||
	github.com/gorilla/websocket v1.5.1
 | 
			
		||||
	github.com/gorilla/websocket v1.5.3
 | 
			
		||||
	github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231018071450-ac8d9f0167e9
 | 
			
		||||
	github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230712084735-068dc2aee82d
 | 
			
		||||
	github.com/may-fly/cast v1.6.1
 | 
			
		||||
	github.com/microsoft/go-mssqldb v1.7.1
 | 
			
		||||
	github.com/microsoft/go-mssqldb v1.7.2
 | 
			
		||||
	github.com/mojocn/base64Captcha v1.3.6 // 验证码
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
	github.com/pkg/sftp v1.13.6
 | 
			
		||||
	github.com/pquerna/otp v1.4.0
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.5.1
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.5.3
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
			
		||||
	github.com/sijms/go-ora/v2 v2.8.19
 | 
			
		||||
	github.com/stretchr/testify v1.9.0
 | 
			
		||||
	github.com/tidwall/gjson v1.17.1
 | 
			
		||||
	github.com/veops/go-ansiterm v0.0.5
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.15.0 // mongo
 | 
			
		||||
	golang.org/x/crypto v0.24.0 // ssh
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.16.0 // mongo
 | 
			
		||||
	golang.org/x/crypto v0.25.0 // ssh
 | 
			
		||||
	golang.org/x/oauth2 v0.21.0
 | 
			
		||||
	golang.org/x/sync v0.7.0
 | 
			
		||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1
 | 
			
		||||
	// gorm
 | 
			
		||||
	gorm.io/driver/mysql v1.5.6
 | 
			
		||||
	gorm.io/gorm v1.25.10
 | 
			
		||||
	gorm.io/driver/mysql v1.5.7
 | 
			
		||||
	gorm.io/gorm v1.25.11
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
@@ -77,12 +77,14 @@ require (
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.17 // indirect
 | 
			
		||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
			
		||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
			
		||||
	github.com/montanaflynn/stats v0.7.0 // indirect
 | 
			
		||||
	github.com/montanaflynn/stats v0.7.1 // indirect
 | 
			
		||||
	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 | 
			
		||||
	github.com/rivo/uniseg v0.4.3 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
	github.com/tidwall/match v1.1.1 // indirect
 | 
			
		||||
	github.com/tidwall/pretty v1.2.1 // indirect
 | 
			
		||||
	github.com/tjfoc/gmsm v1.4.1 // indirect
 | 
			
		||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
			
		||||
	github.com/ugorji/go/codec v1.2.12 // indirect
 | 
			
		||||
@@ -94,7 +96,7 @@ require (
 | 
			
		||||
	golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
 | 
			
		||||
	golang.org/x/image v0.13.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.25.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.21.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.16.0 // indirect
 | 
			
		||||
	google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
 | 
			
		||||
	google.golang.org/grpc v1.52.3 // indirect
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
 | 
			
		||||
	for _, dbvo := range dbvos {
 | 
			
		||||
		di := instancesMap[dbvo.InstanceId]
 | 
			
		||||
		if di != nil {
 | 
			
		||||
			dbvo.InstanceCode = di.Code
 | 
			
		||||
			dbvo.InstanceType = di.Type
 | 
			
		||||
			dbvo.Host = di.Host
 | 
			
		||||
			dbvo.Port = di.Port
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ type DbListVO struct {
 | 
			
		||||
	InstanceId      uint64                   `json:"instanceId"`
 | 
			
		||||
	AuthCertName    string                   `json:"authCertName"`
 | 
			
		||||
 | 
			
		||||
	InstanceCode string `json:"instanceCode" gorm:"-"`
 | 
			
		||||
	InstanceType string `json:"type" gorm:"-"`
 | 
			
		||||
	Host         string `json:"host" gorm:"-"`
 | 
			
		||||
	Port         int    `json:"port" gorm:"-"`
 | 
			
		||||
 
 | 
			
		||||
@@ -67,17 +67,9 @@ func (app *instanceAppImpl) GetPageList(condition *entity.InstanceQuery, pagePar
 | 
			
		||||
func (app *instanceAppImpl) TestConn(instanceEntity *entity.DbInstance, authCert *tagentity.ResourceAuthCert) error {
 | 
			
		||||
	instanceEntity.Network = instanceEntity.GetNetwork()
 | 
			
		||||
 | 
			
		||||
	if authCert.Id != 0 {
 | 
			
		||||
		// 密文可能被清除,故需要重新获取
 | 
			
		||||
		authCert, _ = app.resourceAuthCertApp.GetAuthCert(authCert.Name)
 | 
			
		||||
	} else {
 | 
			
		||||
		if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
 | 
			
		||||
			publicAuthCert, err := app.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			authCert = publicAuthCert
 | 
			
		||||
		}
 | 
			
		||||
	authCert, err := app.resourceAuthCertApp.GetRealAuthCert(authCert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbConn, err := dbm.Conn(app.toDbInfoByAc(instanceEntity, authCert, ""))
 | 
			
		||||
@@ -176,7 +168,7 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
 | 
			
		||||
		DbInstanceId: instanceId,
 | 
			
		||||
	}
 | 
			
		||||
	err = app.restoreApp.restoreRepo.GetByCond(restore)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库恢复任务。", instance.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -184,7 +176,7 @@ func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error
 | 
			
		||||
		DbInstanceId: instanceId,
 | 
			
		||||
	}
 | 
			
		||||
	err = app.backupApp.backupRepo.GetByCond(backup)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库备份任务。", instance.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,17 +28,19 @@ type Dialect interface {
 | 
			
		||||
	// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
 | 
			
		||||
	GetDbProgram() (DbProgram, error)
 | 
			
		||||
 | 
			
		||||
	// 批量保存数据
 | 
			
		||||
	// BatchInsert 批量insert数据
 | 
			
		||||
	BatchInsert(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error)
 | 
			
		||||
 | 
			
		||||
	// 拷贝表
 | 
			
		||||
	// CopyTable 拷贝表
 | 
			
		||||
	CopyTable(copy *DbCopyTable) error
 | 
			
		||||
 | 
			
		||||
	// CreateTable 创建表
 | 
			
		||||
	CreateTable(columns []Column, tableInfo Table, dropOldTable bool) (int, error)
 | 
			
		||||
 | 
			
		||||
	// CreateIndex 创建索引
 | 
			
		||||
	CreateIndex(tableInfo Table, indexs []Index) error
 | 
			
		||||
 | 
			
		||||
	// 有些数据库迁移完数据之后,需要更新表自增序列为当前表最大值
 | 
			
		||||
	// UpdateSequence 有些数据库迁移完数据之后,需要更新表自增序列为当前表最大值
 | 
			
		||||
	UpdateSequence(tableName string, columns []Column)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,37 +13,36 @@ import (
 | 
			
		||||
type MetaData interface {
 | 
			
		||||
	BaseMetaData
 | 
			
		||||
 | 
			
		||||
	// 获取数据库服务实例信息
 | 
			
		||||
	// GetDbServer 获取数据库服务实例信息
 | 
			
		||||
	GetDbServer() (*DbServer, error)
 | 
			
		||||
 | 
			
		||||
	// 获取数据库名称列表
 | 
			
		||||
	// GetDbNames 获取数据库名称列表
 | 
			
		||||
	GetDbNames() ([]string, error)
 | 
			
		||||
 | 
			
		||||
	// 获取表信息
 | 
			
		||||
	// GetTables 获取表信息
 | 
			
		||||
	GetTables(tableNames ...string) ([]Table, error)
 | 
			
		||||
 | 
			
		||||
	// 获取指定表名的所有列元信息
 | 
			
		||||
	// GetColumns 获取指定表名的所有列元信息
 | 
			
		||||
	GetColumns(tableNames ...string) ([]Column, error)
 | 
			
		||||
 | 
			
		||||
	// 根据数据库类型修复字段长度、精度等
 | 
			
		||||
	// FixColumn(column *Column)
 | 
			
		||||
 | 
			
		||||
	// 获取表主键字段名,没有主键标识则默认第一个字段
 | 
			
		||||
	// GetPrimaryKey 获取表主键字段名,没有主键标识则默认第一个字段
 | 
			
		||||
	GetPrimaryKey(tableName string) (string, error)
 | 
			
		||||
 | 
			
		||||
	// 获取表索引信息
 | 
			
		||||
	// GetTableIndex 获取表索引信息
 | 
			
		||||
	GetTableIndex(tableName string) ([]Index, error)
 | 
			
		||||
 | 
			
		||||
	// 获取建表ddl
 | 
			
		||||
	// GetTableDDL 获取建表ddl
 | 
			
		||||
	GetTableDDL(tableName string, dropBeforeCreate bool) (string, error)
 | 
			
		||||
 | 
			
		||||
	// GenerateTableDDL 生成建表ddl
 | 
			
		||||
	GenerateTableDDL(columns []Column, tableInfo Table, dropBeforeCreate bool) []string
 | 
			
		||||
 | 
			
		||||
	// GenerateIndexDDL 生成索引ddl
 | 
			
		||||
	GenerateIndexDDL(indexs []Index, tableInfo Table) []string
 | 
			
		||||
 | 
			
		||||
	GetSchemas() ([]string, error)
 | 
			
		||||
 | 
			
		||||
	// 获取数据处理助手 用于解析格式化列数据等
 | 
			
		||||
	// GetDataHelper 获取数据处理助手 用于解析格式化列数据等
 | 
			
		||||
	GetDataHelper() DataHelper
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ type DbInstance struct {
 | 
			
		||||
	Network            string  `json:"network"`
 | 
			
		||||
	Extra              *string `json:"extra"`  // 连接需要的其他额外参数(json格式), 如oracle需要sid等
 | 
			
		||||
	Params             *string `json:"params"` // 使用指针类型,可更新为零值(空字符串)
 | 
			
		||||
	Remark             string  `json:"remark"`
 | 
			
		||||
	Remark             *string `json:"remark"`
 | 
			
		||||
	SshTunnelMachineId int     `json:"sshTunnelMachineId"` // ssh隧道机器id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,6 @@ func newDbRepo() repository.Db {
 | 
			
		||||
 | 
			
		||||
// 分页获取数据库信息列表
 | 
			
		||||
func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	pd := model.NewCond().Eq("instance_id", condition.InstanceId).In("code", condition.Codes)
 | 
			
		||||
	pd := model.NewCond().Eq("instance_id", condition.InstanceId).In("code", condition.Codes).Eq("id", condition.Id)
 | 
			
		||||
	return d.PageByCondToAny(pd, pageParam, toEntity)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -159,17 +159,9 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
 | 
			
		||||
func (m *machineAppImpl) TestConn(me *entity.Machine, authCert *tagentity.ResourceAuthCert) error {
 | 
			
		||||
	me.Id = 0
 | 
			
		||||
 | 
			
		||||
	if authCert.Id != 0 {
 | 
			
		||||
		// 密文可能被清除,故需要重新获取
 | 
			
		||||
		authCert, _ = m.resourceAuthCertApp.GetAuthCert(authCert.Name)
 | 
			
		||||
	} else {
 | 
			
		||||
		if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
 | 
			
		||||
			publicAuthCert, err := m.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			authCert = publicAuthCert
 | 
			
		||||
		}
 | 
			
		||||
	authCert, err := m.resourceAuthCertApp.GetRealAuthCert(authCert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mi, err := m.toMi(me, authCert)
 | 
			
		||||
 
 | 
			
		||||
@@ -98,18 +98,19 @@ func NewParserByteStream(width, height int) *ansiterm.ByteStream {
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	enterMarks = [][]byte{
 | 
			
		||||
		[]byte("\x1b[?1049h"),
 | 
			
		||||
		[]byte("\x1b[?1049h"), // 从备用屏幕缓冲区恢复屏幕内容
 | 
			
		||||
		[]byte("\x1b[?1048h"),
 | 
			
		||||
		[]byte("\x1b[?1047h"),
 | 
			
		||||
		[]byte("\x1b[?47h"),
 | 
			
		||||
		[]byte("\x1b[?25l"),
 | 
			
		||||
		[]byte("\x1b[?25l"), // 隐藏光标
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exitMarks = [][]byte{
 | 
			
		||||
		[]byte("\x1b[?1049l"),
 | 
			
		||||
		[]byte("\x1b[?1049l"), // 从备用屏幕缓冲区恢复屏幕内容
 | 
			
		||||
		[]byte("\x1b[?1048l"),
 | 
			
		||||
		[]byte("\x1b[?1047l"),
 | 
			
		||||
		[]byte("\x1b[?47l"),
 | 
			
		||||
		[]byte("\x1b[?25h"), // 显示光标
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	screenMarks = [][]byte{
 | 
			
		||||
 
 | 
			
		||||
@@ -76,18 +76,9 @@ func (r *redisAppImpl) TestConn(param *dto.SaveRedis) error {
 | 
			
		||||
		db = cast.ToInt(strings.Split(re.Db, ",")[0])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	authCert := param.AuthCert
 | 
			
		||||
	if authCert.Id != 0 {
 | 
			
		||||
		// 密文可能被清除,故需要重新获取
 | 
			
		||||
		authCert, _ = r.resourceAuthCertApp.GetAuthCert(authCert.Name)
 | 
			
		||||
	} else {
 | 
			
		||||
		if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
 | 
			
		||||
			publicAuthCert, err := r.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			authCert = publicAuthCert
 | 
			
		||||
		}
 | 
			
		||||
	authCert, err := r.resourceAuthCertApp.GetRealAuthCert(param.AuthCert)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc, err := re.ToRedisInfo(db, authCert).Conn()
 | 
			
		||||
 
 | 
			
		||||
@@ -112,6 +112,7 @@ func (re *RedisInfo) connSentinel() (*RedisConn, error) {
 | 
			
		||||
		SentinelAddrs:    strings.Split(masterNameAndHosts[1], ","),
 | 
			
		||||
		Username:         re.Username,
 | 
			
		||||
		Password:         re.Password, // no password set
 | 
			
		||||
		SentinelUsername: re.Username,
 | 
			
		||||
		SentinelPassword: re.Password, // 哨兵节点密码需与redis节点密码一致
 | 
			
		||||
		DB:               re.Db,       // use default DB
 | 
			
		||||
		DialTimeout:      8 * time.Second,
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,11 @@ func (p *TagTree) ListByQuery(rc *req.Ctx) {
 | 
			
		||||
		cond.CodePaths = strings.Split(tagPaths, ",")
 | 
			
		||||
	}
 | 
			
		||||
	cond.Id = uint64(rc.QueryInt("id"))
 | 
			
		||||
	cond.Type = entity.TagType(rc.QueryInt("type"))
 | 
			
		||||
	codes := rc.Query("codes")
 | 
			
		||||
	if codes != "" {
 | 
			
		||||
		cond.Codes = strings.Split(codes, ",")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tagTrees []entity.TagTree
 | 
			
		||||
	p.TagTreeApp.ListByQuery(cond, &tagTrees)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,9 @@ type ResourceAuthCert interface {
 | 
			
		||||
	// GetAuthCert 根据授权凭证名称获取授权凭证
 | 
			
		||||
	GetAuthCert(authCertName string) (*entity.ResourceAuthCert, error)
 | 
			
		||||
 | 
			
		||||
	//GetRealAuthCert 获取真实可连接鉴权的授权凭证,主要用于资源测试连接时
 | 
			
		||||
	GetRealAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error)
 | 
			
		||||
 | 
			
		||||
	// GetResourceAuthCert 获取资源授权凭证,优先获取默认账号,若不存在默认账号则返回特权账号,都不存在则返回第一个
 | 
			
		||||
	GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error)
 | 
			
		||||
 | 
			
		||||
@@ -211,6 +214,25 @@ func (r *resourceAuthCertAppImpl) GetAuthCert(authCertName string) (*entity.Reso
 | 
			
		||||
	return r.decryptAuthCert(authCert)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *resourceAuthCertAppImpl) GetRealAuthCert(authCert *entity.ResourceAuthCert) (*entity.ResourceAuthCert, error) {
 | 
			
		||||
	// 如果使用的是公共授权凭证,则密文为凭证名称
 | 
			
		||||
	if authCert.CiphertextType == entity.AuthCertCiphertextTypePublic {
 | 
			
		||||
		return r.GetAuthCert(authCert.Ciphertext)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if authCert.Id != 0 && authCert.Ciphertext == "" {
 | 
			
		||||
		// 密文可能被清除,故需要重新获取
 | 
			
		||||
		ac, err := r.GetAuthCert(authCert.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		authCert.Ciphertext = ac.Ciphertext
 | 
			
		||||
		return authCert, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return authCert, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *resourceAuthCertAppImpl) GetResourceAuthCert(resourceType entity.TagType, resourceCode string) (*entity.ResourceAuthCert, error) {
 | 
			
		||||
	resourceAuthCerts, err := r.ListByCond(&entity.ResourceAuthCert{
 | 
			
		||||
		ResourceType: int8(resourceType),
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import "fmt"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	AppName = "mayfly-go"
 | 
			
		||||
	Version = "v1.8.7"
 | 
			
		||||
	Version = "v1.8.8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetAppInfo() string {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,8 @@ package jsonx
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"mayfly-go/pkg/logx"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/buger/jsonparser"
 | 
			
		||||
	"github.com/tidwall/gjson"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// json字符串转map
 | 
			
		||||
@@ -42,35 +41,35 @@ func ToStr(val any) string {
 | 
			
		||||
//
 | 
			
		||||
// @param fieldPath字段路径。如user.username等
 | 
			
		||||
func GetStringByBytes(bytes []byte, fieldPath string) (string, error) {
 | 
			
		||||
	return jsonparser.GetString(bytes, strings.Split(fieldPath, ".")...)
 | 
			
		||||
	return gjson.GetBytes(bytes, fieldPath).String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据json字符串获取对应字段路径的string类型值
 | 
			
		||||
//
 | 
			
		||||
// @param fieldPath字段路径。如user.username等
 | 
			
		||||
func GetString(jsonStr string, fieldPath string) (string, error) {
 | 
			
		||||
	return GetStringByBytes([]byte(jsonStr), fieldPath)
 | 
			
		||||
	return gjson.Get(jsonStr, fieldPath).String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据json字节数组获取对应字段路径的int类型值
 | 
			
		||||
//
 | 
			
		||||
// @param fieldPath字段路径。如user.age等
 | 
			
		||||
func GetIntByBytes(bytes []byte, fieldPath string) (int64, error) {
 | 
			
		||||
	return jsonparser.GetInt(bytes, strings.Split(fieldPath, ".")...)
 | 
			
		||||
	return gjson.GetBytes(bytes, fieldPath).Int(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据json字符串获取对应字段路径的int类型值
 | 
			
		||||
//
 | 
			
		||||
// @param fieldPath字段路径。如user.age等
 | 
			
		||||
func GetInt(jsonStr string, fieldPath string) (int64, error) {
 | 
			
		||||
	return GetIntByBytes([]byte(jsonStr), fieldPath)
 | 
			
		||||
	return gjson.Get(jsonStr, fieldPath).Int(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据json字节数组获取对应字段路径的bool类型值
 | 
			
		||||
//
 | 
			
		||||
// @param fieldPath字段路径。如user.isDeleted等
 | 
			
		||||
func GetBoolByBytes(bytes []byte, fieldPath string) (bool, error) {
 | 
			
		||||
	return jsonparser.GetBoolean(bytes, strings.Split(fieldPath, ".")...)
 | 
			
		||||
	return gjson.GetBytes(bytes, fieldPath).Bool(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据json字符串获取对应字段路径的bool类型值
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,6 @@ package jsonx
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/buger/jsonparser"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const jsonStr = `{
 | 
			
		||||
@@ -36,7 +34,7 @@ func TestGetString(t *testing.T) {
 | 
			
		||||
	// val, err := GetString(jsonStr, "username1")
 | 
			
		||||
 | 
			
		||||
	// 含有数组的
 | 
			
		||||
	val, err := GetString(jsonStr, "person.avatars.[0].url")
 | 
			
		||||
	val, err := GetString(jsonStr, "person.avatars.0.url")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("error: ", err.Error())
 | 
			
		||||
@@ -50,60 +48,3 @@ func TestGetInt(t *testing.T) {
 | 
			
		||||
	val2, _ := GetInt(jsonStr, "person.github.followers")
 | 
			
		||||
	fmt.Println(val, ",", val2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 官方demo
 | 
			
		||||
func TestJsonParser(t *testing.T) {
 | 
			
		||||
	data := []byte(jsonStr)
 | 
			
		||||
	// You can specify key path by providing arguments to Get function
 | 
			
		||||
	jsonparser.Get(data, "person", "name", "fullName")
 | 
			
		||||
 | 
			
		||||
	// There is `GetInt` and `GetBoolean` helpers if you exactly know key data type
 | 
			
		||||
	jsonparser.GetInt(data, "person", "github", "followers")
 | 
			
		||||
 | 
			
		||||
	// When you try to get object, it will return you []byte slice pointer to data containing it
 | 
			
		||||
	// In `company` it will be `{"name": "Acme"}`
 | 
			
		||||
	jsonparser.Get(data, "company")
 | 
			
		||||
 | 
			
		||||
	// If the key doesn't exist it will throw an error
 | 
			
		||||
	var size int64
 | 
			
		||||
	if value, err := jsonparser.GetInt(data, "company", "size"); err == nil {
 | 
			
		||||
		size = value
 | 
			
		||||
		fmt.Println(size)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// You can use `ArrayEach` helper to iterate items [item1, item2 .... itemN]
 | 
			
		||||
	jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
 | 
			
		||||
		fmt.Println(jsonparser.Get(value, "url"))
 | 
			
		||||
	}, "person", "avatars")
 | 
			
		||||
 | 
			
		||||
	// Or use can access fields by index!
 | 
			
		||||
	jsonparser.GetString(data, "person", "avatars", "[0]", "url")
 | 
			
		||||
 | 
			
		||||
	// You can use `ObjectEach` helper to iterate objects { "key1":object1, "key2":object2, .... "keyN":objectN }
 | 
			
		||||
	jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
 | 
			
		||||
		fmt.Printf("Key: '%s'\n Value: '%s'\n Type: %s\n", string(key), string(value), dataType)
 | 
			
		||||
		return nil
 | 
			
		||||
	}, "person", "name")
 | 
			
		||||
 | 
			
		||||
	// The most efficient way to extract multiple keys is `EachKey`
 | 
			
		||||
 | 
			
		||||
	paths := [][]string{
 | 
			
		||||
		[]string{"person", "name", "fullName"},
 | 
			
		||||
		[]string{"person", "avatars", "[0]", "url"},
 | 
			
		||||
		[]string{"company", "url"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jsonparser.EachKey(data, func(idx int, value []byte, vt jsonparser.ValueType, err error) {
 | 
			
		||||
		switch idx {
 | 
			
		||||
		case 0: // []string{"person", "name", "fullName"}
 | 
			
		||||
			{
 | 
			
		||||
			}
 | 
			
		||||
		case 1: // []string{"person", "avatars", "[0]", "url"}
 | 
			
		||||
			{
 | 
			
		||||
			}
 | 
			
		||||
		case 2: // []string{"company", "url"},
 | 
			
		||||
			{
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}, paths...)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user