mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
feat: tag refactor & other
This commit is contained in:
@@ -30,7 +30,12 @@ export function isTrue(condition: boolean, msg: string) {
|
||||
* @param msg 错误消息
|
||||
*/
|
||||
export function notBlank(obj: any, msg: string) {
|
||||
isTrue(obj, msg);
|
||||
if (obj == null || obj == undefined || obj == '') {
|
||||
throw new AssertError(msg);
|
||||
}
|
||||
if (Array.isArray(obj) && obj.length == 0) {
|
||||
throw new AssertError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,15 +19,22 @@ export const ResourceTypeEnum = {
|
||||
|
||||
// 标签关联的资源类型
|
||||
export const TagResourceTypeEnum = {
|
||||
AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
|
||||
PublicAuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
|
||||
Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
|
||||
|
||||
Machine: ResourceTypeEnum.Machine,
|
||||
Db: ResourceTypeEnum.Db,
|
||||
DbInstance: ResourceTypeEnum.Db,
|
||||
Redis: ResourceTypeEnum.Redis,
|
||||
Mongo: ResourceTypeEnum.Mongo,
|
||||
AuthCert: EnumValue.of(5, '授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
|
||||
MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
DbAuthCert: EnumValue.of(21, '数据库-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
|
||||
DbName: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
|
||||
Db: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
|
||||
};
|
||||
|
||||
// 标签关联的资源类型路径
|
||||
export const TagResourceTypePath = {
|
||||
MachineAuthCert: `${TagResourceTypeEnum.Machine.value}/${TagResourceTypeEnum.AuthCert.value}`,
|
||||
|
||||
DbInstanceAuthCert: `${TagResourceTypeEnum.DbInstance.value}/${TagResourceTypeEnum.AuthCert.value}`,
|
||||
Db: `${TagResourceTypeEnum.DbInstance.value}/${TagResourceTypeEnum.AuthCert.value}/${TagResourceTypeEnum.Db.value}`,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ const config = {
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.9.1',
|
||||
version: 'v1.9.2',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item ref="tagSelectRef" prop="codePaths" :label="$t('tag.relateTag')">
|
||||
<tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypeEnum.DbName.value, TagResourceTypeEnum.Redis.value]" />
|
||||
<tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypePath.Db, TagResourceTypeEnum.Redis.value]" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">{{ $t('flow.approvalNode') }}</el-divider>
|
||||
@@ -94,7 +94,7 @@ import Sortable from 'sortablejs';
|
||||
import { randomUuid } from '../../common/utils/string';
|
||||
import { ProcdefStatus } from './enums';
|
||||
import TagTreeCheck from '../ops/component/TagTreeCheck.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
|
||||
import { useI18nFormValidate, useI18nPleaseInput, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -95,7 +95,7 @@ const parseBizForm = async (bizFormStr: string) => {
|
||||
const dbRes = await dbApi.dbs.request({ id: bizForm.dbId });
|
||||
state.db = dbRes.list?.[0];
|
||||
|
||||
tagApi.listByQuery.request({ type: TagResourceTypeEnum.DbName.value, codes: state.db.code }).then((res) => {
|
||||
tagApi.listByQuery.request({ type: TagResourceTypeEnum.Db.value, codes: state.db.code }).then((res) => {
|
||||
state.db.codePaths = res.map((item: any) => item.codePath);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@ watch(
|
||||
);
|
||||
|
||||
const changeResourceCode = async (db: any) => {
|
||||
emit('changeResourceCode', TagResourceTypeEnum.DbName.value, db.code);
|
||||
emit('changeResourceCode', TagResourceTypeEnum.Db.value, db.code);
|
||||
};
|
||||
|
||||
const validateBizForm = async () => {
|
||||
|
||||
@@ -122,7 +122,12 @@
|
||||
<template #header>
|
||||
<el-row justify="center">
|
||||
<div class="resource-num pointer-icon" @click="toPage('db')">
|
||||
<SvgIcon class="mb5 mr5" :size="28" :name="TagResourceTypeEnum.Db.extra.icon" :color="TagResourceTypeEnum.Db.extra.iconColor" />
|
||||
<SvgIcon
|
||||
class="mb5 mr5"
|
||||
:size="28"
|
||||
:name="TagResourceTypeEnum.DbInstance.extra.icon"
|
||||
:color="TagResourceTypeEnum.DbInstance.extra.iconColor"
|
||||
/>
|
||||
<span class="">{{ state.db.num }}</span>
|
||||
</div>
|
||||
</el-row>
|
||||
@@ -388,7 +393,7 @@ const handleAvatarSuccess = (response: any, uploadFile: any) => {
|
||||
// 初始化数字滚动
|
||||
const initData = async () => {
|
||||
resourceOpLogApi.getAccountResourceOpLogs
|
||||
.request({ resourceType: TagResourceTypeEnum.MachineAuthCert.value, pageSize: state.defaultLogSize })
|
||||
.request({ resourceType: TagResourceTypeEnum.Machine.value, pageSize: state.defaultLogSize })
|
||||
.then(async (res: any) => {
|
||||
const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
|
||||
state.machine.tagInfos = tagInfos;
|
||||
@@ -396,7 +401,7 @@ const initData = async () => {
|
||||
});
|
||||
|
||||
resourceOpLogApi.getAccountResourceOpLogs
|
||||
.request({ resourceType: TagResourceTypeEnum.DbName.value, pageSize: state.defaultLogSize })
|
||||
.request({ resourceType: TagResourceTypeEnum.DbInstance.value, pageSize: state.defaultLogSize })
|
||||
.then(async (res: any) => {
|
||||
const tagInfos = await getAllTagInfoByCodePaths(res.list?.map((item: any) => item.codePath));
|
||||
state.db.tagInfos = tagInfos;
|
||||
|
||||
@@ -38,7 +38,11 @@
|
||||
:value="TagResourceTypeEnum.Machine.value"
|
||||
/>
|
||||
|
||||
<el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
|
||||
<el-option
|
||||
:key="TagResourceTypeEnum.DbInstance.value"
|
||||
:label="TagResourceTypeEnum.DbInstance.label"
|
||||
:value="TagResourceTypeEnum.DbInstance.value"
|
||||
/>
|
||||
<el-option
|
||||
:key="TagResourceTypeEnum.Redis.value"
|
||||
:label="$t(TagResourceTypeEnum.Redis.label)"
|
||||
@@ -154,7 +158,7 @@ const DefaultForm = {
|
||||
username: '',
|
||||
ciphertextType: AuthCertCiphertextTypeEnum.Password.value,
|
||||
type: AuthCertTypeEnum.Private.value,
|
||||
resourceType: TagResourceTypeEnum.AuthCert.value,
|
||||
resourceType: TagResourceTypeEnum.PublicAuthCert.value,
|
||||
resourceCode: '',
|
||||
ciphertext: '',
|
||||
extra: {} as any,
|
||||
|
||||
@@ -57,7 +57,7 @@ import { isPrefixSubsequence } from '@/common/utils/string';
|
||||
|
||||
const props = defineProps({
|
||||
resourceType: {
|
||||
type: [Number],
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
defaultExpandedKeys: {
|
||||
|
||||
@@ -58,7 +58,7 @@ const props = defineProps({
|
||||
default: 'calc(100vh - 330px)',
|
||||
},
|
||||
tagType: {
|
||||
type: [Number, Array<Number>],
|
||||
type: [Number, Array<Number>, String, Array<String>],
|
||||
default: TagResourceTypeEnum.Tag.value,
|
||||
},
|
||||
nodeKey: {
|
||||
|
||||
@@ -43,7 +43,7 @@ import { tagApi } from '../tag/api';
|
||||
|
||||
const props = defineProps({
|
||||
resourceType: {
|
||||
type: [Number],
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
tagPathNodeType: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {OptionsApi, SearchItem} from '@/components/SearchForm';
|
||||
import {ContextmenuItem} from '@/components/contextmenu';
|
||||
import {TagResourceTypeEnum} from '@/common/commonEnum';
|
||||
import {tagApi} from '../tag/api';
|
||||
import { OptionsApi, SearchItem } from '@/components/SearchForm';
|
||||
import { ContextmenuItem } from '@/components/contextmenu';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { tagApi } from '../tag/api';
|
||||
|
||||
export class TagTreeNode {
|
||||
/**
|
||||
@@ -160,9 +160,9 @@ export class NodeType {
|
||||
* @param resourceType 资源类型
|
||||
* @returns
|
||||
*/
|
||||
export function getTagPathSearchItem(resourceType: number) {
|
||||
export function getTagPathSearchItem(resourceType: any) {
|
||||
return SearchItem.select('tagPath', 'common.tag').withOptionsApi(
|
||||
OptionsApi.new(tagApi.getResourceTagPaths, {resourceType}).withConvertFn((res: any) => {
|
||||
OptionsApi.new(tagApi.getResourceTagPaths, { resourceType }).withConvertFn((res: any) => {
|
||||
return res.map((x: any) => {
|
||||
return {
|
||||
label: x,
|
||||
@@ -180,7 +180,7 @@ export function getTagPathSearchItem(resourceType: number) {
|
||||
*/
|
||||
export function getTagTypeCodeByPath(codePath: string) {
|
||||
const result: any = {};
|
||||
if (!codePath) return result
|
||||
if (!codePath) return result;
|
||||
const parts = codePath.split('/'); // 切分字符串并保留数字和对应的值部分
|
||||
|
||||
for (let part of parts) {
|
||||
@@ -208,7 +208,7 @@ export function getTagTypeCodeByPath(codePath: string) {
|
||||
* @returns
|
||||
*/
|
||||
export async function getAllTagInfoByCodePaths(codePaths: string[]) {
|
||||
if (!codePaths) return
|
||||
if (!codePaths) return;
|
||||
const allTypeAndCode: any = {};
|
||||
|
||||
for (let codePath of codePaths) {
|
||||
@@ -222,7 +222,7 @@ export async function getAllTagInfoByCodePaths(codePaths: string[]) {
|
||||
if (type == TagResourceTypeEnum.Tag.value) {
|
||||
continue;
|
||||
}
|
||||
const tagInfo = await tagApi.listByQuery.request({type: type, codes: allTypeAndCode[type]});
|
||||
const tagInfo = await tagApi.listByQuery.request({ type: type, codes: allTypeAndCode[type] });
|
||||
allTypeAndCode[type] = tagInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ const getAuthCerts = async () => {
|
||||
const inst: any = props.instance;
|
||||
const res = await resourceAuthCertApi.listByQuery.request({
|
||||
resourceCode: inst.code,
|
||||
resourceType: TagResourceTypeEnum.Db.value,
|
||||
resourceType: TagResourceTypeEnum.DbInstance.value,
|
||||
pageSize: 100,
|
||||
});
|
||||
state.authCerts = res.list || [];
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<ResourceAuthCertTableEdit
|
||||
v-model="form.authCerts"
|
||||
:resource-code="form.code"
|
||||
:resource-type="TagResourceTypeEnum.Db.value"
|
||||
:resource-type="TagResourceTypeEnum.DbInstance.value"
|
||||
:test-conn-btn-loading="testConnBtnLoading"
|
||||
@test-conn="testConn"
|
||||
:disable-ciphertext-type="[AuthCertCiphertextTypeEnum.PrivateKey.value]"
|
||||
@@ -128,6 +128,7 @@ import { AuthCertCiphertextTypeEnum } from '../tag/enums';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import { useI18nFormValidate, useI18nPleaseInput, useI18nPleaseSelect, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notBlankI18n } from '@/common/assert';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -258,6 +259,7 @@ const testConn = async (authCert: any) => {
|
||||
const btnOk = async () => {
|
||||
await useI18nFormValidate(dbForm);
|
||||
state.submitForm = await getReqForm();
|
||||
notBlankI18n(state.submitForm.authCerts, 'db.acName');
|
||||
await saveInstanceExec();
|
||||
useI18nSaveSuccessMsg();
|
||||
state.form.id = saveInstanceRes as any;
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button v-auth="perms.saveInstance" type="primary" icon="plus" @click="editInstance(false)">{{ $t('common.create') }}</el-button>
|
||||
<el-button v-auth="perms.delInstance" :disabled="selectionData.length < 1" @click="deleteInstance()" type="danger" icon="delete">{{
|
||||
$t('common.delete')
|
||||
}}</el-button>
|
||||
<el-button v-auth="perms.delInstance" :disabled="selectionData.length < 1" @click="deleteInstance()" type="danger" icon="delete">
|
||||
{{ $t('common.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
@@ -76,7 +76,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { dbApi } from './api';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
@@ -88,7 +87,7 @@ import { SearchItem } from '@/components/SearchForm';
|
||||
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle } from '@/hooks/useI18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -110,10 +109,7 @@ const perms = {
|
||||
saveDb: 'db:save',
|
||||
};
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.DbAuthCert.value),
|
||||
];
|
||||
const searchItems = [SearchItem.input('keyword', 'common.keyword').withPlaceholder('db.keywordPlaceholder'), getTagPathSearchItem(TagResourceTypePath.Db)];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('tags[0].tagPath', 'tag.relateTag').isSlot('tagPath').setAddWidth(20),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Pane size="20" max-size="30">
|
||||
<tag-tree
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
:resource-type="TagResourceTypeEnum.DbName.value"
|
||||
:resource-type="TagResourceTypePath.Db"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
ref="tagTreeRef"
|
||||
>
|
||||
@@ -246,7 +246,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
|
||||
import { getDbDialect, schemaDbTypes } from './dialect/index';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
import { useEventListener, useStorage } from '@vueuse/core';
|
||||
import SqlExecBox from '@/views/ops/db/component/sqleditor/SqlExecBox';
|
||||
@@ -597,7 +597,7 @@ const autoOpenDb = (codePath: string) => {
|
||||
const typeAndCodes: any = getTagTypeCodeByPath(codePath);
|
||||
const tagPath = typeAndCodes[TagResourceTypeEnum.Tag.value].join('/') + '/';
|
||||
|
||||
const dbCode = typeAndCodes[TagResourceTypeEnum.DbName.value][0];
|
||||
const dbCode = typeAndCodes[TagResourceTypeEnum.Db.value][0];
|
||||
state.defaultExpendKey = [tagPath, dbCode];
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-bind="$attrs"
|
||||
v-model="selectNode"
|
||||
@change="changeNode"
|
||||
:resource-type="TagResourceTypeEnum.Db.value"
|
||||
:resource-type="TagResourceTypePath.Db"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
>
|
||||
<template #iconPrefix>
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypeEnum, TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
|
||||
import { dbApi } from '@/views/ops/db/api';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
|
||||
@@ -309,7 +309,7 @@ const onRunSql = async (newTab = false) => {
|
||||
sqlPrefix.startsWith('update') ||
|
||||
sqlPrefix.startsWith('insert') ||
|
||||
sqlPrefix.startsWith('delete') ||
|
||||
sqlPrefix.startsWith('alert') ||
|
||||
sqlPrefix.startsWith('alter') ||
|
||||
sqlPrefix.startsWith('drop') ||
|
||||
sqlPrefix.startsWith('create');
|
||||
|
||||
|
||||
@@ -483,7 +483,7 @@ const setTableColumns = (columns: any) => {
|
||||
x.dataType = dbDialect.getDataType(x.columnType);
|
||||
x.dataTypeSubscript = ColumnTypeSubscript[x.dataType];
|
||||
x.remark = `${x.columnType} ${x.columnComment ? ' | ' + x.columnComment : ''}`;
|
||||
|
||||
console.log(x);
|
||||
return {
|
||||
...x,
|
||||
key: columnName,
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
<el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark" />
|
||||
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" :title="$t('common.delete')" @confirm="deleteRow(scope.$index)">
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" :title="$t('common.deleteConfirm')" @confirm="deleteRow(scope.$index)">
|
||||
<template #reference>
|
||||
<el-link type="danger" plain size="small" :underline="false">{{ $t('common.delete') }}</el-link>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import {dbApi} from './api';
|
||||
import {getTextWidth} from '@/common/utils/string';
|
||||
import { dbApi } from './api';
|
||||
import { getTextWidth } from '@/common/utils/string';
|
||||
import SqlExecBox from './component/sqleditor/SqlExecBox';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import {editor, languages, Position} from 'monaco-editor';
|
||||
import { editor, languages, Position } from 'monaco-editor';
|
||||
|
||||
import {registerCompletionItemProvider} from '@/components/monaco/completionItemProvider';
|
||||
import {DbDialect, EditorCompletionItem, getDbDialect} from './dialect';
|
||||
import {type RemovableRef, useLocalStorage} from '@vueuse/core';
|
||||
import {DbGetDbNamesMode} from './enums';
|
||||
import {ElMessage} from 'element-plus';
|
||||
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
|
||||
import { type RemovableRef, useLocalStorage } from '@vueuse/core';
|
||||
import { DbGetDbNamesMode } from './enums';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
|
||||
const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
|
||||
@@ -101,13 +101,12 @@ export class DbInst {
|
||||
// 重置列信息缓存与表提示信息
|
||||
db.columnsMap?.clear();
|
||||
console.log(`load tables -> dbName: ${dbName}`);
|
||||
tables = await dbApi.tableInfos.request({id: this.id, db: dbName});
|
||||
tables = await dbApi.tableInfos.request({ id: this.id, db: dbName });
|
||||
tableStorage.value.set(key, tables);
|
||||
db.tables = tables;
|
||||
|
||||
// 异步加载表提示信息
|
||||
this.loadDbHints(dbName, true).then(() => {
|
||||
});
|
||||
this.loadDbHints(dbName, true).then(() => {});
|
||||
return tables;
|
||||
}
|
||||
|
||||
@@ -116,7 +115,7 @@ export class DbInst {
|
||||
// 表名联想
|
||||
let suggestions: languages.CompletionItem[] = [];
|
||||
tables?.forEach((tableMeta: any, index: any) => {
|
||||
const {tableName, tableComment} = tableMeta;
|
||||
const { tableName, tableComment } = tableMeta;
|
||||
suggestions.push({
|
||||
label: {
|
||||
label: tableName + ' - ' + tableComment,
|
||||
@@ -129,7 +128,7 @@ export class DbInst {
|
||||
sortText: 300 + index + '',
|
||||
});
|
||||
});
|
||||
return {suggestions};
|
||||
return { suggestions };
|
||||
}
|
||||
|
||||
/** 加载列信息提示 */
|
||||
@@ -154,7 +153,7 @@ export class DbInst {
|
||||
});
|
||||
});
|
||||
|
||||
return {suggestions};
|
||||
return { suggestions };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,7 +211,7 @@ export class DbInst {
|
||||
return hints;
|
||||
}
|
||||
console.log(`load db-hits -> dbName: ${dbName}`);
|
||||
hints = await dbApi.hintTables.request({id: this.id, db: db.name});
|
||||
hints = await dbApi.hintTables.request({ id: this.id, db: db.name });
|
||||
db.tableHints = hints;
|
||||
hintsStorage.value.set(key, hints);
|
||||
return hints;
|
||||
@@ -410,7 +409,7 @@ export class DbInst {
|
||||
dbInst.databases = inst.databases;
|
||||
|
||||
if (dbInst.databases?.[0]) {
|
||||
dbInst.version = await dbApi.getCompatibleDbVersion.request({id: inst.id, db: dbInst.databases?.[0]});
|
||||
dbInst.version = await dbApi.getCompatibleDbVersion.request({ id: inst.id, db: dbInst.databases?.[0] });
|
||||
}
|
||||
|
||||
dbInstCache.set(dbInst.id, dbInst);
|
||||
@@ -447,7 +446,7 @@ export class DbInst {
|
||||
return Promise.resolve(dbInst);
|
||||
}
|
||||
|
||||
const dbInfoRes = await dbApi.dbs.request({id: dbId});
|
||||
const dbInfoRes = await dbApi.dbs.request({ id: dbId });
|
||||
const db = dbInfoRes.list[0];
|
||||
return Promise.resolve(DbInst.getOrNewInst(db));
|
||||
}
|
||||
@@ -465,7 +464,7 @@ export class DbInst {
|
||||
* @returns
|
||||
*/
|
||||
static isNumber(columnType: string) {
|
||||
return columnType && columnType.match(/^(int|uint|double|float|number|decimal|byte|bit)/i);
|
||||
return columnType && columnType.match(/(int|uint|double|float|number|decimal|byte|bit)/gi);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -545,7 +544,7 @@ export class DbInst {
|
||||
return db.database.split(' ');
|
||||
}
|
||||
|
||||
return await dbApi.getDbNamesByAc.request({authCertName: db.authCertName});
|
||||
return await dbApi.getDbNamesByAc.request({ authCertName: db.authCertName });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,9 +652,9 @@ function registerCompletions(
|
||||
) {
|
||||
// mysql关键字
|
||||
completions.forEach((item: EditorCompletionItem) => {
|
||||
let {label, insertText, description} = item;
|
||||
let { label, insertText, description } = item;
|
||||
suggestions.push({
|
||||
label: {label, description},
|
||||
label: { label, description },
|
||||
kind,
|
||||
insertText: insertText || label,
|
||||
range,
|
||||
@@ -674,20 +673,20 @@ function registerCompletions(
|
||||
export function registerDbCompletionItemProvider(dbId: number, db: string, dbs: any[] = [], dbType: string) {
|
||||
let dbDialect = getDbDialect(dbType);
|
||||
let dbDialectInfo = dbDialect.getInfo();
|
||||
let {keywords, operators, functions, variables} = dbDialectInfo.editorCompletions;
|
||||
let { keywords, operators, functions, variables } = dbDialectInfo.editorCompletions;
|
||||
registerCompletionItemProvider('sql', {
|
||||
triggerCharacters: ['.', ' '],
|
||||
provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
|
||||
let word = model.getWordUntilPosition(position);
|
||||
const dbInst = await DbInst.getInstA(dbId);
|
||||
const {lineNumber, column} = position;
|
||||
const {startColumn, endColumn} = word;
|
||||
const { lineNumber, column } = position;
|
||||
const { startColumn, endColumn } = word;
|
||||
|
||||
// 当前行文本
|
||||
let lineContent = model.getLineContent(lineNumber);
|
||||
// 注释行不需要代码提示
|
||||
if (lineContent.startsWith('--')) {
|
||||
return {suggestions: []};
|
||||
return { suggestions: [] };
|
||||
}
|
||||
|
||||
let range = {
|
||||
@@ -812,7 +811,7 @@ export function registerDbCompletionItemProvider(dbId: number, db: string, dbs:
|
||||
// 当前库的表名联想
|
||||
const tables = await dbInst.loadTables(db);
|
||||
tables.forEach((tableMeta: any, index: any) => {
|
||||
const {tableName, tableComment} = tableMeta;
|
||||
const { tableName, tableComment } = tableMeta;
|
||||
suggestions.push({
|
||||
label: {
|
||||
label: tableName + ' - ' + tableComment,
|
||||
@@ -839,11 +838,17 @@ export function registerDbCompletionItemProvider(dbId: number, db: string, dbs:
|
||||
});
|
||||
}
|
||||
|
||||
function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string): {
|
||||
function getTableName4SqlCtx(
|
||||
sql: string,
|
||||
alias: string = '',
|
||||
defaultDb: string
|
||||
):
|
||||
| {
|
||||
tableName: string;
|
||||
tableAlias: string;
|
||||
db: string
|
||||
} | undefined {
|
||||
db: string;
|
||||
}
|
||||
| undefined {
|
||||
// 去除多余的换行、空格和制表符
|
||||
sql = sql.replace(/[\r\n\s\t]+/g, ' ');
|
||||
|
||||
@@ -865,7 +870,7 @@ function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string)
|
||||
tableName = info[1];
|
||||
}
|
||||
const tableAlias = matches[2] ? matches[2].replace(/[`"]/g, '') : tableName;
|
||||
tables.push({tableName, tableAlias, db});
|
||||
tables.push({ tableName, tableAlias, db });
|
||||
}
|
||||
|
||||
if (alias) {
|
||||
|
||||
@@ -269,7 +269,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { formatByteSize, formatDate } from '@/common/utils/format';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { TagResourceTypePath } from '@/common/commonEnum';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import { getTagPathSearchItem } from '../component/tag';
|
||||
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
|
||||
@@ -311,7 +311,7 @@ const perms = {
|
||||
|
||||
const searchItems = [
|
||||
SearchItem.input('keyword', 'common.keyword').withPlaceholder('machine.keywordPlaceholder'),
|
||||
getTagPathSearchItem(TagResourceTypeEnum.MachineAuthCert.value),
|
||||
getTagPathSearchItem(TagResourceTypePath.MachineAuthCert),
|
||||
];
|
||||
|
||||
const columns = [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<tag-tree
|
||||
class="machine-terminal-tree"
|
||||
ref="tagTreeRef"
|
||||
:resource-type="TagResourceTypeEnum.MachineAuthCert.value"
|
||||
:resource-type="TagResourceTypeEnum.Machine.value"
|
||||
:tag-path-node-type="NodeTypeTagPath"
|
||||
:default-expanded-keys="state.defaultExpendKey"
|
||||
>
|
||||
@@ -378,7 +378,7 @@ const autoOpenTerminal = (codePath: string) => {
|
||||
const machineCode = typeAndCodes[TagResourceTypeEnum.Machine.value][0];
|
||||
state.defaultExpendKey = [tagPath, machineCode];
|
||||
|
||||
const authCertName = typeAndCodes[TagResourceTypeEnum.MachineAuthCert.value][0];
|
||||
const authCertName = typeAndCodes[TagResourceTypeEnum.PublicAuthCert.value][0];
|
||||
setTimeout(() => {
|
||||
// 置空
|
||||
autoOpenResourceStore.setMachineCodePath('');
|
||||
|
||||
@@ -39,7 +39,11 @@
|
||||
/></el-form-item>
|
||||
|
||||
<el-form-item ref="tagSelectRef" prop="codePaths" :label="$t('machine.relateMachine')">
|
||||
<tag-tree-check height="200px" :tag-type="TagResourceTypeEnum.Machine.value" v-model="form.codePaths" />
|
||||
<tag-tree-check
|
||||
height="200px"
|
||||
:tag-type="`${TagResourceTypeEnum.Machine.value}/${TagResourceTypeEnum.AuthCert.value}`"
|
||||
v-model="form.codePaths"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
:title="$t('machine.cmdConfig')"
|
||||
v-model="dialogVisible"
|
||||
:show-close="false"
|
||||
width="600px"
|
||||
size="40%"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
@@ -78,7 +78,11 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item ref="tagSelectRef" prop="codePaths" :label="$t('machine.relateMachine')">
|
||||
<tag-tree-check height="calc(100vh - 430px)" :tag-type="TagResourceTypeEnum.MachineAuthCert.value" v-model="form.codePaths" />
|
||||
<tag-tree-check
|
||||
height="calc(100vh - 430px)"
|
||||
:tag-type="`${TagResourceTypeEnum.Machine.value}/${TagResourceTypeEnum.AuthCert.value}`"
|
||||
v-model="form.codePaths"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="cancel" />
|
||||
</template>
|
||||
|
||||
@@ -344,7 +344,7 @@ const allowDrag = (node: any) => {
|
||||
const tagType = node.data.type;
|
||||
return (
|
||||
tagType == TagResourceTypeEnum.Tag.value ||
|
||||
tagType == TagResourceTypeEnum.Db.value ||
|
||||
tagType == TagResourceTypeEnum.DbInstance.value ||
|
||||
tagType == TagResourceTypeEnum.Redis.value ||
|
||||
tagType == TagResourceTypeEnum.Machine.value ||
|
||||
tagType == TagResourceTypeEnum.Mongo.value
|
||||
|
||||
@@ -7,7 +7,7 @@ export const tagApi = {
|
||||
delTagTree: Api.newDelete('/tag-trees/{id}'),
|
||||
movingTag: Api.newPost('/tag-trees/moving'),
|
||||
|
||||
getResourceTagPaths: Api.newGet('/tag-trees/resources/{resourceType}/tag-paths'),
|
||||
getResourceTagPaths: Api.newGet('/tag-trees/resources/tag-paths'),
|
||||
countTagResource: Api.newGet('/tag-trees/resources/count'),
|
||||
getRelateTagIds: Api.newGet('/tag-trees/relate/{relateType}/{relateId}'),
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ func (d *Db) Dbs(rc *req.Ctx) {
|
||||
|
||||
// 不存在可访问标签id,即没有可操作数据
|
||||
tags := d.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
Types: collx.AsArray(tagentity.TagTypeDb),
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeDbInstance, tagentity.TagTypeAuthCert, tagentity.TagTypeDb)),
|
||||
CodePathLikes: collx.AsArray(queryCond.TagPath),
|
||||
})
|
||||
if len(tags) == 0 {
|
||||
|
||||
@@ -32,7 +32,7 @@ func (d *Instance) Instances(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage[*entity.InstanceQuery](rc, new(entity.InstanceQuery))
|
||||
|
||||
tags := d.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
Types: collx.AsArray(tagentity.TagTypeDbAuthCert),
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeDbInstance, tagentity.TagTypeAuthCert)),
|
||||
CodePathLikes: collx.AsArray(queryCond.TagPath),
|
||||
})
|
||||
// 不存在可操作的数据库,即没有可操作数据
|
||||
@@ -50,7 +50,7 @@ func (d *Instance) Instances(rc *req.Ctx) {
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
// 填充授权凭证信息
|
||||
d.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeDbAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
|
||||
d.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeAuthCert, tagCodePaths...), collx.ArrayMap(instvos, func(vos *vo.InstanceListVO) tagentity.IAuthCert {
|
||||
return vos
|
||||
})...)
|
||||
|
||||
|
||||
@@ -158,10 +158,21 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
defer ticker.Stop()
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
errInfo := anyx.ToString(err)
|
||||
if len(errInfo) > 500 {
|
||||
errInfo = errInfo[:500] + "..."
|
||||
}
|
||||
d.MsgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), errInfo).WithClientId(fm.ClientId))
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
biz.ErrIsNilAppendErr(err, "failed to connect to the target database: %s")
|
||||
}
|
||||
|
||||
errSql := ""
|
||||
err = sqlparser.SQLSplit(reader, func(sql string) error {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@@ -175,11 +186,14 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
|
||||
}
|
||||
executedStatements++
|
||||
_, err = targetDbConn.Exec(sql)
|
||||
if err != nil {
|
||||
errSql = sql
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
biz.ErrIsNilAppendErr(err, "sql execution failed: %s")
|
||||
biz.ErrIsNil(err, "[%s] execution failed: %s", errSql, err)
|
||||
}
|
||||
|
||||
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), fmt.Sprintf("sql execution successfully: %s", filename)).WithClientId(fm.ClientId))
|
||||
|
||||
@@ -98,7 +98,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
|
||||
Name: dbEntity.Name,
|
||||
}},
|
||||
ParentTagCode: authCert.Name,
|
||||
ParentTagType: tagentity.TagTypeDbAuthCert,
|
||||
ParentTagType: tagentity.TagTypeAuthCert,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
|
||||
}
|
||||
}
|
||||
if authCert.Name != old.AuthCertName {
|
||||
return d.tagApp.ChangeParentTag(ctx, tagentity.TagTypeDb, old.Code, tagentity.TagTypeDbAuthCert, authCert.Name)
|
||||
return d.tagApp.ChangeParentTag(ctx, tagentity.TagTypeDb, old.Code, tagentity.TagTypeAuthCert, authCert.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -260,7 +260,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
|
||||
return &tagdto.ResourceTag{
|
||||
Code: val.Name,
|
||||
Name: val.Username,
|
||||
Type: tagentity.TagTypeDbAuthCert,
|
||||
Type: tagentity.TagTypeAuthCert,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3,16 +3,10 @@ package dbi
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"gitee.com/chunanyong/dm"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 游标遍历查询结果集处理函数
|
||||
@@ -30,6 +24,16 @@ type DbConn struct {
|
||||
type QueryColumn struct {
|
||||
Name string `json:"name"` // 列名
|
||||
Type string `json:"type"` // 类型
|
||||
|
||||
SqlColType *sql.ColumnType `json:"-"`
|
||||
}
|
||||
|
||||
func NewQueryColumn(colName string, col *sql.ColumnType) *QueryColumn {
|
||||
return &QueryColumn{
|
||||
Name: col.Name(),
|
||||
Type: col.DatabaseTypeName(),
|
||||
SqlColType: col,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DbConn) GetDb() *sql.DB {
|
||||
@@ -76,7 +80,7 @@ func (d *DbConn) Query2Struct(execSql string, dest any) error {
|
||||
|
||||
// WalkQueryRows 游标方式遍历查询结果集, walkFn返回error不为nil, 则跳出遍历并取消查询
|
||||
func (d *DbConn) WalkQueryRows(ctx context.Context, querySql string, walkFn WalkQueryRowsFunc, args ...any) ([]*QueryColumn, error) {
|
||||
return walkQueryRows(ctx, d.db, querySql, walkFn, args...)
|
||||
return walkQueryRows(ctx, d.GetDialect(), d.db, querySql, walkFn, args...)
|
||||
}
|
||||
|
||||
// WalkTableRows 游标方式遍历指定表的结果集, walkFn返回error不为nil, 则跳出遍历并取消查询
|
||||
@@ -154,7 +158,7 @@ func (d *DbConn) Close() {
|
||||
}
|
||||
|
||||
// 游标方式遍历查询rows, walkFn error不为nil, 则跳出遍历
|
||||
func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn WalkQueryRowsFunc, args ...any) ([]*QueryColumn, error) {
|
||||
func walkQueryRows(ctx context.Context, dialect Dialect, db *sql.DB, selectSql string, walkFn WalkQueryRowsFunc, args ...any) ([]*QueryColumn, error) {
|
||||
cancelCtx, cancelFunc := context.WithCancel(ctx)
|
||||
defer cancelFunc()
|
||||
|
||||
@@ -166,6 +170,8 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
||||
// 后面的链接过来直接报错或拒绝,实际上也没有起效果
|
||||
defer rows.Close()
|
||||
|
||||
columnHelper := dialect.GetColumnHelper()
|
||||
|
||||
colTypes, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -182,15 +188,9 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
||||
if colName == "" {
|
||||
colName = fmt.Sprintf("<anonymous%d>", k+1)
|
||||
}
|
||||
cols[k] = &QueryColumn{Name: colName, Type: colType.DatabaseTypeName()}
|
||||
// 这里scans引用values,把数据填充到[]byte里
|
||||
if cols[k].Type == "st_point" { // 达梦的空间坐标数据
|
||||
var point dm.DmStruct
|
||||
scans[k] = &point
|
||||
} else {
|
||||
var s = make([]byte, 0)
|
||||
scans[k] = &s
|
||||
}
|
||||
qc := NewQueryColumn(colName, colType)
|
||||
cols[k] = qc
|
||||
scans[k] = columnHelper.GetScanDestPtr(qc)
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
@@ -202,10 +202,10 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
||||
rowData := make(map[string]any, lenCols)
|
||||
// 把values中的数据复制到row中
|
||||
for i, v := range scans {
|
||||
rowData[cols[i].Name] = valueConvert(v, colTypes[i])
|
||||
rowData[cols[i].Name] = columnHelper.ConvertScanDestValue(v, cols[i])
|
||||
}
|
||||
if err = walkFn(rowData, cols); err != nil {
|
||||
logx.ErrorfContext(ctx, "[%s]游标遍历查询结果集出错, 退出遍历: %s", selectSql, err.Error())
|
||||
logx.ErrorfContext(ctx, "[%s] cursor traversal query result set error, exit traversal: %s", selectSql, err.Error())
|
||||
cancelFunc()
|
||||
return cols, err
|
||||
}
|
||||
@@ -214,108 +214,13 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
||||
return cols, nil
|
||||
}
|
||||
|
||||
func ParseDmStruct(dmStruct *dm.DmStruct) string {
|
||||
if !dmStruct.Valid {
|
||||
return ""
|
||||
}
|
||||
|
||||
name, _ := dmStruct.GetSQLTypeName()
|
||||
attributes, _ := dmStruct.GetAttributes()
|
||||
arr := make([]string, len(attributes))
|
||||
arr = append(arr, name, "(")
|
||||
|
||||
for i, v := range attributes {
|
||||
if blb, ok1 := v.(*dm.DmBlob); ok1 {
|
||||
if blb.Valid {
|
||||
length, _ := blb.GetLength()
|
||||
var dest = make([]byte, length)
|
||||
_, _ = blb.Read(dest)
|
||||
// 2进制转16进制字符串
|
||||
hexStr := hex.EncodeToString(dest)
|
||||
arr = append(arr, "0x", strings.ToUpper(hexStr))
|
||||
}
|
||||
} else {
|
||||
arr = append(arr, anyx.ToString(v))
|
||||
}
|
||||
if i < len(attributes)-1 {
|
||||
arr = append(arr, ",")
|
||||
}
|
||||
}
|
||||
|
||||
arr = append(arr, ")")
|
||||
return strings.Join(arr, "")
|
||||
|
||||
}
|
||||
|
||||
// 将查询的值转为对应列类型的实际值,不全部转为字符串
|
||||
func valueConvert(data interface{}, colType *sql.ColumnType) any {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 达梦特殊数据类型
|
||||
if dmStruct, ok := data.(*dm.DmStruct); ok {
|
||||
return ParseDmStruct(dmStruct)
|
||||
}
|
||||
|
||||
// 列的数据库类型名
|
||||
colDatabaseTypeName := strings.ToLower(colType.DatabaseTypeName())
|
||||
|
||||
// 这里把[]byte数据转成string
|
||||
stringV := ""
|
||||
if slicePtr, ok := data.(*[]uint8); ok {
|
||||
stringV = string(*slicePtr)
|
||||
// 如果类型是bit,则直接返回第一个字节即可
|
||||
if strings.Contains(colDatabaseTypeName, "bit") {
|
||||
return (*slicePtr)[0]
|
||||
}
|
||||
|
||||
if colDatabaseTypeName == "blob" {
|
||||
return hex.EncodeToString(*slicePtr)
|
||||
}
|
||||
}
|
||||
|
||||
if colType == nil || colType.ScanType() == nil {
|
||||
return stringV
|
||||
}
|
||||
colScanType := strings.ToLower(colType.ScanType().Name())
|
||||
|
||||
if strings.Contains(colScanType, "int") {
|
||||
// 如果长度超过16位,则返回字符串,因为前端js长度大于16会丢失精度
|
||||
if len(stringV) > 16 {
|
||||
return stringV
|
||||
}
|
||||
intV, _ := strconv.Atoi(stringV)
|
||||
switch colType.ScanType().Kind() {
|
||||
case reflect.Int8:
|
||||
return int8(intV)
|
||||
case reflect.Uint8:
|
||||
return uint8(intV)
|
||||
case reflect.Int64:
|
||||
return int64(intV)
|
||||
case reflect.Uint64:
|
||||
return uint64(intV)
|
||||
case reflect.Uint:
|
||||
return uint(intV)
|
||||
default:
|
||||
return intV
|
||||
}
|
||||
}
|
||||
if strings.Contains(colScanType, "float") || strings.Contains(colDatabaseTypeName, "decimal") {
|
||||
floatV, _ := strconv.ParseFloat(stringV, 64)
|
||||
return floatV
|
||||
}
|
||||
|
||||
return stringV
|
||||
}
|
||||
|
||||
// 包装sql执行相关错误
|
||||
func wrapSqlError(err error) error {
|
||||
if err == context.Canceled {
|
||||
return errorx.NewBiz("取消执行")
|
||||
return errorx.NewBiz("execution cancel")
|
||||
}
|
||||
if err == context.DeadlineExceeded {
|
||||
return errorx.NewBiz("执行超时")
|
||||
return errorx.NewBiz("execution timeout")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@ package dbi
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"mayfly-go/internal/db/dbm/sqlparser"
|
||||
"mayfly-go/internal/db/dbm/sqlparser/pgsql"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
pq "gitee.com/liuzongyang/libpq"
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
const DefaultQuoter = `"`
|
||||
@@ -157,6 +161,12 @@ type ColumnHelper interface {
|
||||
|
||||
// FixColumn 根据数据库类型修复字段长度、精度等
|
||||
FixColumn(column *Column)
|
||||
|
||||
// GetScanDestPtr 获取scan列目标值指针,用于在*sql.Rows.Scan()填充该值
|
||||
GetScanDestPtr(*QueryColumn) any
|
||||
|
||||
// ConvertScanDestValue 将scan的填充的原始值data转为可阅读的值,如[]byte -> string or number...
|
||||
ConvertScanDestValue(data any, qc *QueryColumn) any
|
||||
}
|
||||
|
||||
type DefaultColumnHelper struct {
|
||||
@@ -168,6 +178,74 @@ func (dd *DefaultColumnHelper) ToColumn(commonColumn *Column) {}
|
||||
|
||||
func (dd *DefaultColumnHelper) FixColumn(column *Column) {}
|
||||
|
||||
func (dd *DefaultColumnHelper) GetScanDestPtr(qc *QueryColumn) any {
|
||||
return &[]byte{}
|
||||
}
|
||||
|
||||
func (dd *DefaultColumnHelper) ConvertScanDestValue(data any, qc *QueryColumn) any {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
colType := qc.SqlColType
|
||||
// 列的数据库类型名
|
||||
colDatabaseTypeName := strings.ToLower(colType.DatabaseTypeName())
|
||||
|
||||
stringV := ""
|
||||
if slicePtr, ok := data.(*[]uint8); ok {
|
||||
bytes := *slicePtr
|
||||
if bytes == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 如果类型是bit,则直接返回第一个字节即可
|
||||
if strings.Contains(colDatabaseTypeName, "bit") {
|
||||
return (bytes)[0]
|
||||
}
|
||||
|
||||
if colDatabaseTypeName == "blob" {
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
// 把[]byte数据转成string
|
||||
stringV = string(bytes)
|
||||
} else {
|
||||
stringV = cast.ToString(data)
|
||||
}
|
||||
|
||||
if colType == nil || colType.ScanType() == nil {
|
||||
return stringV
|
||||
}
|
||||
colScanType := strings.ToLower(colType.ScanType().Name())
|
||||
|
||||
if strings.Contains(colScanType, "int") {
|
||||
// 如果长度超过16位,则返回字符串,因为前端js长度大于16会丢失精度
|
||||
if len(stringV) > 16 {
|
||||
return stringV
|
||||
}
|
||||
intV, _ := strconv.Atoi(stringV)
|
||||
switch colType.ScanType().Kind() {
|
||||
case reflect.Int8:
|
||||
return int8(intV)
|
||||
case reflect.Uint8:
|
||||
return uint8(intV)
|
||||
case reflect.Int64:
|
||||
return int64(intV)
|
||||
case reflect.Uint64:
|
||||
return uint64(intV)
|
||||
case reflect.Uint:
|
||||
return uint(intV)
|
||||
default:
|
||||
return intV
|
||||
}
|
||||
}
|
||||
if strings.Contains(colScanType, "float") || strings.Contains(colDatabaseTypeName, "decimal") {
|
||||
floatV, _ := strconv.ParseFloat(stringV, 64)
|
||||
return floatV
|
||||
}
|
||||
|
||||
return stringV
|
||||
}
|
||||
|
||||
// DumpHelper 导出辅助方法
|
||||
type DumpHelper interface {
|
||||
BeforeInsert(writer io.Writer, tableName string)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitee.com/chunanyong/dm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -174,6 +177,7 @@ func (dc *DataHelper) WrapValue(dbColumnValue any, dataType dbi.DataType) string
|
||||
}
|
||||
|
||||
type ColumnHelper struct {
|
||||
dbi.DefaultColumnHelper
|
||||
}
|
||||
|
||||
func (ch *ColumnHelper) ToCommonColumn(dialectColumn *dbi.Column) {
|
||||
@@ -212,6 +216,58 @@ func (ch *ColumnHelper) FixColumn(column *dbi.Column) {
|
||||
}
|
||||
}
|
||||
|
||||
func (dd *ColumnHelper) GetScanDestPtr(qc *dbi.QueryColumn) any {
|
||||
if qc.Type == "st_point" {
|
||||
return &dm.DmStruct{}
|
||||
}
|
||||
return dd.DefaultColumnHelper.GetScanDestPtr(qc)
|
||||
}
|
||||
|
||||
func (dd *ColumnHelper) ConvertScanDestValue(data any, qc *dbi.QueryColumn) any {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 达梦特殊数据类型
|
||||
if dmStruct, ok := data.(*dm.DmStruct); ok {
|
||||
return ParseDmStruct(dmStruct)
|
||||
}
|
||||
|
||||
return dd.DefaultColumnHelper.ConvertScanDestValue(data, qc)
|
||||
}
|
||||
|
||||
func ParseDmStruct(dmStruct *dm.DmStruct) string {
|
||||
if !dmStruct.Valid {
|
||||
return ""
|
||||
}
|
||||
|
||||
name, _ := dmStruct.GetSQLTypeName()
|
||||
attributes, _ := dmStruct.GetAttributes()
|
||||
arr := make([]string, len(attributes))
|
||||
arr = append(arr, name, "(")
|
||||
|
||||
for i, v := range attributes {
|
||||
if blb, ok1 := v.(*dm.DmBlob); ok1 {
|
||||
if blb.Valid {
|
||||
length, _ := blb.GetLength()
|
||||
var dest = make([]byte, length)
|
||||
_, _ = blb.Read(dest)
|
||||
// 2进制转16进制字符串
|
||||
hexStr := hex.EncodeToString(dest)
|
||||
arr = append(arr, "0x", strings.ToUpper(hexStr))
|
||||
}
|
||||
} else {
|
||||
arr = append(arr, anyx.ToString(v))
|
||||
}
|
||||
if i < len(attributes)-1 {
|
||||
arr = append(arr, ",")
|
||||
}
|
||||
}
|
||||
|
||||
arr = append(arr, ")")
|
||||
return strings.Join(arr, "")
|
||||
}
|
||||
|
||||
type DumpHelper struct {
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ func (dc *DataHelper) WrapValue(dbColumnValue any, dataType dbi.DataType) string
|
||||
}
|
||||
|
||||
type ColumnHelper struct {
|
||||
dbi.DefaultColumnHelper
|
||||
}
|
||||
|
||||
func (ch *ColumnHelper) ToCommonColumn(dialectColumn *dbi.Column) {
|
||||
|
||||
@@ -177,6 +177,7 @@ func (dc *DataHelper) WrapValue(dbColumnValue any, dataType dbi.DataType) string
|
||||
}
|
||||
|
||||
type ColumnHelper struct {
|
||||
dbi.DefaultColumnHelper
|
||||
}
|
||||
|
||||
func (ch *ColumnHelper) ToCommonColumn(dialectColumn *dbi.Column) {
|
||||
|
||||
@@ -141,6 +141,7 @@ func (dc *DataHelper) WrapValue(dbColumnValue any, dataType dbi.DataType) string
|
||||
}
|
||||
|
||||
type ColumnHelper struct {
|
||||
dbi.DefaultColumnHelper
|
||||
}
|
||||
|
||||
func (ch *ColumnHelper) ToCommonColumn(dialectColumn *dbi.Column) {
|
||||
|
||||
@@ -173,6 +173,7 @@ func (dc *DataHelper) WrapValue(dbColumnValue any, dataType dbi.DataType) string
|
||||
}
|
||||
|
||||
type ColumnHelper struct {
|
||||
dbi.DefaultColumnHelper
|
||||
}
|
||||
|
||||
func (ch *ColumnHelper) ToCommonColumn(column *dbi.Column) {
|
||||
|
||||
@@ -18,8 +18,6 @@ var (
|
||||
|
||||
dataTypeRegexp = regexp.MustCompile(`(\w+)\((\d*),?(\d*)\)`)
|
||||
|
||||
dateHelper = new(DataHelper)
|
||||
|
||||
// sqlite数据类型 映射 公共数据类型
|
||||
commonColumnTypeMap = map[string]dbi.ColumnDataType{
|
||||
"int": dbi.CommonTypeInt,
|
||||
@@ -100,27 +98,27 @@ func (dc *DataHelper) FormatData(dbColumnValue any, dataType dbi.DataType) strin
|
||||
switch dataType {
|
||||
case dbi.DataTypeDateTime: // "2024-01-02T22:08:22.275697+08:00"
|
||||
// 尝试用时间格式解析
|
||||
res, err := time.Parse(time.DateTime, str)
|
||||
_, err := time.Parse(time.DateTime, str)
|
||||
if err == nil {
|
||||
return str
|
||||
}
|
||||
res, _ = time.Parse(time.RFC3339, str)
|
||||
res, _ := time.Parse(time.RFC3339, str)
|
||||
return res.Format(time.DateTime)
|
||||
case dbi.DataTypeDate: // "2024-01-02T00:00:00+08:00"
|
||||
// 尝试用时间格式解析
|
||||
res, err := time.Parse(time.DateOnly, str)
|
||||
_, err := time.Parse(time.DateOnly, str)
|
||||
if err == nil {
|
||||
return str
|
||||
}
|
||||
res, _ = time.Parse(time.RFC3339, str)
|
||||
res, _ := time.Parse(time.RFC3339, str)
|
||||
return res.Format(time.DateOnly)
|
||||
case dbi.DataTypeTime: // "0000-01-01T22:08:22.275688+08:00"
|
||||
// 尝试用时间格式解析
|
||||
res, err := time.Parse(time.TimeOnly, str)
|
||||
_, err := time.Parse(time.TimeOnly, str)
|
||||
if err == nil {
|
||||
return str
|
||||
}
|
||||
res, _ = time.Parse(time.RFC3339, str)
|
||||
res, _ := time.Parse(time.RFC3339, str)
|
||||
return res.Format(time.TimeOnly)
|
||||
}
|
||||
return str
|
||||
@@ -152,6 +150,7 @@ func (dc *DataHelper) WrapValue(dbColumnValue any, dataType dbi.DataType) string
|
||||
}
|
||||
|
||||
type ColumnHelper struct {
|
||||
dbi.DefaultColumnHelper
|
||||
}
|
||||
|
||||
func (ch *ColumnHelper) ToCommonColumn(dialectColumn *dbi.Column) {
|
||||
@@ -176,10 +175,6 @@ func (ch *ColumnHelper) ToColumn(commonColumn *dbi.Column) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *ColumnHelper) FixColumn(column *dbi.Column) {
|
||||
|
||||
}
|
||||
|
||||
type DumpHelper struct {
|
||||
dbi.DefaultDumpHelper
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"mayfly-go/internal/machine/application"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
@@ -16,7 +17,7 @@ type Dashbord struct {
|
||||
func (m *Dashbord) Dashbord(rc *req.Ctx) {
|
||||
accountId := rc.GetLoginAccount().Id
|
||||
|
||||
tagCodePaths := m.TagTreeApp.GetAccountTags(accountId, &tagentity.TagTreeQuery{Types: collx.AsArray(tagentity.TagTypeMachineAuthCert)}).GetCodePaths()
|
||||
tagCodePaths := m.TagTreeApp.GetAccountTags(accountId, &tagentity.TagTreeQuery{TypePaths: collx.AsArray(entity.NewTypePaths(tagentity.TagTypeMachine, tagentity.TagTypeAuthCert))}).GetCodePaths()
|
||||
machineCodes := tagentity.GetCodesByCodePaths(tagentity.TagTypeMachine, tagCodePaths...)
|
||||
|
||||
rc.ResData = collx.M{
|
||||
|
||||
@@ -44,7 +44,7 @@ func (m *Machine) Machines(rc *req.Ctx) {
|
||||
condition, pageParam := req.BindQueryAndPage(rc, new(entity.MachineQuery))
|
||||
|
||||
tags := m.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
Types: collx.AsArray(tagentity.TagTypeMachineAuthCert),
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeMachine, tagentity.TagTypeAuthCert)),
|
||||
CodePathLikes: collx.AsArray(condition.TagPath),
|
||||
})
|
||||
// 不存在可操作的机器-授权凭证标签,即没有可操作数据
|
||||
@@ -71,7 +71,7 @@ func (m *Machine) Machines(rc *req.Ctx) {
|
||||
})...)
|
||||
|
||||
// 填充授权凭证信息
|
||||
m.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeMachineAuthCert, tagCodePaths...), collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
|
||||
m.ResourceAuthCertApp.FillAuthCertByAcNames(tagentity.GetCodesByCodePaths(tagentity.TagTypeAuthCert, tagCodePaths...), collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.IAuthCert {
|
||||
return mvo
|
||||
})...)
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ func (m *machineAppImpl) toMi(me *entity.Machine, authCert *tagentity.ResourceAu
|
||||
mi.Name = me.Name
|
||||
mi.Ip = me.Ip
|
||||
mi.Port = me.Port
|
||||
mi.CodePath = m.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeMachineAuthCert), authCert.Name)
|
||||
mi.CodePath = m.tagApp.ListTagPathByTypeAndCode(int8(tagentity.TagTypeAuthCert), authCert.Name)
|
||||
mi.EnableRecorder = me.EnableRecorder
|
||||
mi.Protocol = me.Protocol
|
||||
|
||||
@@ -363,7 +363,7 @@ func (m *machineAppImpl) genMachineResourceTag(me *entity.Machine, authCerts []*
|
||||
return &tagdto.ResourceTag{
|
||||
Code: val.Name,
|
||||
Name: val.Username,
|
||||
Type: tagentity.TagTypeMachineAuthCert,
|
||||
Type: tagentity.TagTypeAuthCert,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
|
||||
|
||||
// 不存在可访问标签id,即没有可操作数据
|
||||
tags := m.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
Types: []tagentity.TagType{tagentity.TagTypeMongo},
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeMongo)),
|
||||
CodePathLikes: []string{queryCond.TagPath},
|
||||
})
|
||||
if len(tags) == 0 {
|
||||
|
||||
@@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/common/utils"
|
||||
"mayfly-go/internal/redis/api/form"
|
||||
@@ -34,7 +33,7 @@ func (r *Redis) RedisList(rc *req.Ctx) {
|
||||
|
||||
// 不存在可访问标签id,即没有可操作数据
|
||||
tags := r.TagApp.GetAccountTags(rc.GetLoginAccount().Id, &tagentity.TagTreeQuery{
|
||||
Types: collx.AsArray(tagentity.TagTypeRedis),
|
||||
TypePaths: collx.AsArray(tagentity.NewTypePaths(tagentity.TagTypeRedis)),
|
||||
CodePathLikes: collx.AsArray(queryCond.TagPath),
|
||||
})
|
||||
if len(tags) == 0 {
|
||||
@@ -59,15 +58,22 @@ func (r *Redis) TestConn(rc *req.Ctx) {
|
||||
form := &form.Redis{}
|
||||
redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis))
|
||||
|
||||
biz.ErrIsNil(r.RedisApp.TestConn(&dto.SaveRedis{
|
||||
Redis: redis,
|
||||
AuthCert: &tagentity.ResourceAuthCert{
|
||||
Name: fmt.Sprintf("redis_%s_ac", redis.Code),
|
||||
authCert := &tagentity.ResourceAuthCert{
|
||||
Username: form.Username,
|
||||
Ciphertext: form.Password,
|
||||
CiphertextType: tagentity.AuthCertCiphertextTypePassword,
|
||||
Type: tagentity.AuthCertTypePrivate,
|
||||
},
|
||||
}
|
||||
|
||||
if form.Mode == string(rdm.SentinelMode) {
|
||||
encPwd, err := utils.PwdAesEncrypt(form.RedisNodePassword)
|
||||
biz.ErrIsNil(err)
|
||||
authCert.SetExtraValue("redisNodePassword", encPwd)
|
||||
}
|
||||
|
||||
biz.ErrIsNil(r.RedisApp.TestConn(&dto.SaveRedis{
|
||||
Redis: redis,
|
||||
AuthCert: authCert,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ import (
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type TagTree struct {
|
||||
@@ -23,14 +21,14 @@ type TagTree struct {
|
||||
|
||||
func (p *TagTree) GetTagTree(rc *req.Ctx) {
|
||||
tagTypesStr := rc.Query("type")
|
||||
var tagTypes []entity.TagType
|
||||
var typePaths []entity.TypePath
|
||||
if tagTypesStr != "" {
|
||||
tagTypes = collx.ArrayMap[string, entity.TagType](strings.Split(tagTypesStr, ","), func(val string) entity.TagType {
|
||||
return entity.TagType(cast.ToInt8(val))
|
||||
typePaths = collx.ArrayMap[string, entity.TypePath](strings.Split(tagTypesStr, ","), func(val string) entity.TypePath {
|
||||
return entity.TypePath(val)
|
||||
})
|
||||
}
|
||||
|
||||
accountTags := p.TagTreeApp.GetAccountTags(rc.GetLoginAccount().Id, &entity.TagTreeQuery{Types: tagTypes})
|
||||
accountTags := p.TagTreeApp.GetAccountTags(rc.GetLoginAccount().Id, &entity.TagTreeQuery{TypePaths: typePaths})
|
||||
if len(accountTags) == 0 {
|
||||
rc.ResData = []any{}
|
||||
return
|
||||
@@ -114,9 +112,9 @@ func (p *TagTree) MovingTag(rc *req.Ctx) {
|
||||
|
||||
// 获取用户可操作的标签路径
|
||||
func (p *TagTree) TagResources(rc *req.Ctx) {
|
||||
resourceType := int8(rc.PathParamInt("rtype"))
|
||||
accountId := rc.GetLoginAccount().Id
|
||||
tagResources := p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{Types: collx.AsArray(entity.TagType(resourceType))})
|
||||
resourceType := rc.Query("resourceType")
|
||||
biz.NotEmpty(resourceType, "resourceType cannot be empty")
|
||||
tagResources := p.TagTreeApp.GetAccountTags(rc.GetLoginAccount().Id, &entity.TagTreeQuery{TypePaths: collx.AsArray(entity.TypePath(resourceType))})
|
||||
|
||||
tagPath2Resource := collx.ArrayToMap[*dto.SimpleTagTree, string](tagResources, func(tagResource *dto.SimpleTagTree) string {
|
||||
return string(entity.CodePath(tagResource.CodePath).GetTag())
|
||||
@@ -133,11 +131,11 @@ func (p *TagTree) CountTagResource(rc *req.Ctx) {
|
||||
accountId := rc.GetLoginAccount().Id
|
||||
|
||||
machineCodes := entity.GetCodesByCodePaths(entity.TagTypeMachine, p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
|
||||
Types: collx.AsArray(entity.TagTypeMachineAuthCert),
|
||||
TypePaths: collx.AsArray(entity.NewTypePaths(entity.TagTypeMachine, entity.TagTypeAuthCert)),
|
||||
CodePathLikes: collx.AsArray(tagPath),
|
||||
}).GetCodePaths()...)
|
||||
|
||||
dbCodes := entity.GetCodesByCodePaths(entity.TagTypeDbInstance, p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
|
||||
dbCodes := entity.GetCodesByCodePaths(entity.TagTypeDb, p.TagTreeApp.GetAccountTags(accountId, &entity.TagTreeQuery{
|
||||
Types: collx.AsArray(entity.TagTypeDb),
|
||||
CodePathLikes: collx.AsArray(tagPath),
|
||||
}).GetCodePaths()...)
|
||||
|
||||
@@ -152,7 +152,6 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *dt
|
||||
oldName2AuthCert := collx.ArrayToMap(oldAuthCert, func(ac *entity.ResourceAuthCert) string {
|
||||
return ac.Name
|
||||
})
|
||||
acTagType := GetResourceAuthCertTagType(params.ResourceType)
|
||||
for _, unmodifyAcName := range unmodifyAcNames {
|
||||
unmodifyAc := name2AuthCert[unmodifyAcName]
|
||||
|
||||
@@ -163,8 +162,8 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *dt
|
||||
}
|
||||
|
||||
// 如果修改了用户名,且该凭证关联至标签,则需要更新对应的标签名(资源授权凭证类型的标签名为username)
|
||||
if oldAuthCert.Username != unmodifyAc.Username && acTagType != 0 {
|
||||
r.tagTreeApp.UpdateTagName(ctx, acTagType, unmodifyAcName, unmodifyAc.Username)
|
||||
if oldAuthCert.Username != unmodifyAc.Username {
|
||||
r.tagTreeApp.UpdateTagName(ctx, entity.TagTypeAuthCert, unmodifyAcName, unmodifyAc.Username)
|
||||
}
|
||||
logx.DebugfContext(ctx, "RelateAuthCert[%d-%s] - Update Authorization credential - [%v]", resourceType, resourceCode, unmodifyAcName)
|
||||
if err := r.UpdateByCond(ctx, unmodifyAc, &entity.ResourceAuthCert{Name: unmodifyAcName, ResourceCode: resourceCode, ResourceType: resourceType}); err != nil {
|
||||
@@ -328,12 +327,8 @@ func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.R
|
||||
|
||||
resourceCode := rac.ResourceCode
|
||||
resourceType := rac.ResourceType
|
||||
// 资源对应的授权凭证标签类型,若为0则说明该资源不需要关联至资源tagTree
|
||||
authCertTagType := GetResourceAuthCertTagType(entity.TagType(resourceType))
|
||||
|
||||
var resourceTagCodePaths []string
|
||||
// 如果该资源存在对应的授权凭证标签类型,则说明需要关联至tagTree,否则直接从授权凭证库中验证资源编号是否正确即可(一个资源最少有一个授权凭证)
|
||||
if authCertTagType != 0 {
|
||||
// 获取资源编号对应的资源标签信息
|
||||
resourceTags, _ := r.tagTreeApp.ListByCond(&entity.TagTree{Type: entity.TagType(resourceType), Code: resourceCode})
|
||||
// 资源标签tagPath(相当于父tag)
|
||||
@@ -343,11 +338,6 @@ func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.R
|
||||
if len(resourceTagCodePaths) == 0 {
|
||||
return errorx.NewBizI(ctx, imsg.ErrResourceTagNotExist, "resourceCode", resourceCode)
|
||||
}
|
||||
} else {
|
||||
if r.CountByCond(&entity.ResourceAuthCert{ResourceCode: resourceCode, ResourceType: resourceType}) == 0 {
|
||||
return errorx.NewBizI(ctx, imsg.ErrResourceNotExist)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果密文类型不为公共凭证,则进行加密。公共凭证密文内容存的是明文的公共凭证名
|
||||
if rac.CiphertextType != entity.AuthCertCiphertextTypePublic {
|
||||
@@ -357,11 +347,11 @@ func (r *resourceAuthCertAppImpl) addAuthCert(ctx context.Context, rac *entity.R
|
||||
return r.Tx(ctx, func(ctx context.Context) error {
|
||||
// 若存在需要关联到的资源标签,则关联到对应的资源标签下
|
||||
if len(resourceTagCodePaths) > 0 {
|
||||
logx.DebugfContext(ctx, "[%d-%s] - AC tag [%d-%s] associated to the resource tag [%v] ", resourceType, resourceCode, authCertTagType, rac.Name, resourceTagCodePaths)
|
||||
logx.DebugfContext(ctx, "[%d-%s] - AC tag [%d-%s] associated to the resource tag [%v] ", resourceType, resourceCode, entity.TagTypeAuthCert, rac.Name, resourceTagCodePaths)
|
||||
return r.tagTreeApp.SaveResourceTag(ctx, &dto.SaveResourceTag{
|
||||
ResourceTag: &dto.ResourceTag{
|
||||
Code: rac.Name,
|
||||
Type: GetResourceAuthCertTagType(entity.TagType(resourceType)),
|
||||
Type: entity.TagTypeAuthCert,
|
||||
Name: rac.Username,
|
||||
},
|
||||
ParentTagCodePaths: resourceTagCodePaths,
|
||||
@@ -402,14 +392,11 @@ func (r *resourceAuthCertAppImpl) updateAuthCert(ctx context.Context, rac *entit
|
||||
|
||||
// 修改了用户名,则需要同步更新对应授权凭证标签里的名称
|
||||
if rac.Username != oldRac.Username && rac.ResourceType == int8(entity.TagTypeMachine) {
|
||||
authCertTagType := GetResourceAuthCertTagType(entity.TagType(oldRac.ResourceType))
|
||||
if authCertTagType != 0 {
|
||||
if err := r.tagTreeApp.UpdateTagName(ctx, authCertTagType, oldRac.Name, rac.Username); err != nil {
|
||||
if err := r.tagTreeApp.UpdateTagName(ctx, entity.TagTypeAuthCert, oldRac.Name, rac.Username); err != nil {
|
||||
return errorx.NewBiz("Synchronously updating the authorization credential tag name failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 密文存的不是公共授权凭证名,则进行密文加密处理
|
||||
if rac.CiphertextType != entity.AuthCertCiphertextTypePublic {
|
||||
@@ -444,17 +431,3 @@ func (r *resourceAuthCertAppImpl) decryptAuthCert(authCert *entity.ResourceAuthC
|
||||
}
|
||||
return authCert, nil
|
||||
}
|
||||
|
||||
// GetResourceAuthCertTagType 根据资源类型,获取对应的授权凭证标签类型,return 0 说明该资源授权凭证不关联至tagTree
|
||||
func GetResourceAuthCertTagType(resourceType entity.TagType) entity.TagType {
|
||||
if resourceType == entity.TagTypeMachine {
|
||||
return entity.TagTypeMachineAuthCert
|
||||
}
|
||||
|
||||
if resourceType == entity.TagTypeDbInstance {
|
||||
return entity.TagTypeDbAuthCert
|
||||
}
|
||||
|
||||
// 该资源不存在对应的授权凭证标签,即tag_tree不关联该资源的授权凭证
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -49,12 +49,23 @@ func (rol *resourceOpLogAppImpl) AddResourceOpLog(ctx context.Context, codePath
|
||||
}
|
||||
tagTree := &entity.TagTree{CodePath: codePath}
|
||||
if err := rol.tagTreeApp.GetByCond(tagTree); err != nil {
|
||||
return errorx.NewBiz("resource not found")
|
||||
return errorx.NewBiz("tag resource not found")
|
||||
}
|
||||
|
||||
resourceType := tagTree.Type
|
||||
// 获取第一段资源类型即可
|
||||
pathSections := entity.CodePath(tagTree.CodePath).GetPathSections()
|
||||
for _, ps := range pathSections {
|
||||
if ps.Type == entity.TagTypeTag {
|
||||
continue
|
||||
}
|
||||
resourceType = ps.Type
|
||||
break
|
||||
}
|
||||
|
||||
return rol.Save(ctx, &entity.ResourceOpLog{
|
||||
ResourceCode: tagTree.Code,
|
||||
ResourceType: int8(tagTree.Type),
|
||||
ResourceType: int8(resourceType),
|
||||
CodePath: tagTree.CodePath,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ import (
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type TagTree interface {
|
||||
@@ -342,29 +345,110 @@ func (p *tagTreeAppImpl) GetAccountTags(accountId uint64, query *entity.TagTreeQ
|
||||
var tagResources []*dto.SimpleTagTree
|
||||
var accountTagPaths []string
|
||||
|
||||
if accountId != consts.AdminId {
|
||||
if accountId == consts.AdminId {
|
||||
// admin账号,获取所有root tag进行查找过滤
|
||||
tagTypeTags, _ := p.ListByCond(&entity.TagTree{Type: entity.TagTypeTag}, "code_path")
|
||||
accountTagPaths = collx.ArrayFilter(collx.ArrayMap(tagTypeTags, func(item *entity.TagTree) string {
|
||||
return item.CodePath
|
||||
}), func(path string) bool {
|
||||
return len(entity.CodePath(path).GetPathSections()) == 1
|
||||
})
|
||||
} else {
|
||||
// 获取账号有权限操作的标签路径列表
|
||||
accountTagPaths = p.ListTagByAccountId(accountId)
|
||||
}
|
||||
|
||||
if len(accountTagPaths) == 0 {
|
||||
return tagResources
|
||||
}
|
||||
}
|
||||
|
||||
// 去除空字符串标签
|
||||
tagPaths := collx.ArrayRemoveBlank(query.CodePathLikes)
|
||||
// 如果需要查询指定标签下的资源标签,则需要与用户拥有的权限进行过滤,避免越权
|
||||
if len(tagPaths) > 0 {
|
||||
// 为空则说明为admin 则直接赋值需要获取的标签
|
||||
if len(accountTagPaths) == 0 {
|
||||
accountTagPaths = tagPaths
|
||||
} else {
|
||||
accountTagPaths = filterCodePaths(accountTagPaths, tagPaths)
|
||||
}
|
||||
|
||||
codePathLikes := accountTagPaths
|
||||
needFilterAccountTagPaths := accountTagPaths
|
||||
|
||||
needFilter := false
|
||||
typePaths := query.TypePaths
|
||||
if len(typePaths) > 0 {
|
||||
needFilterAccountTagPaths = make([]string, 0)
|
||||
codePathLikes = []string{}
|
||||
|
||||
for _, typePath := range typePaths {
|
||||
childOrderTypes := typePath.ToTagTypes()
|
||||
// 如果不是获取所有子节点,则需要追加Type进行过滤
|
||||
if !query.GetAllChildren {
|
||||
tagResourceQuery.Types = append(tagResourceQuery.Types, childOrderTypes[len(childOrderTypes)-1])
|
||||
}
|
||||
|
||||
// 资源类型模糊匹配,若childTypes = [machineType, authcertType] => machineType|%/authcertType|%/
|
||||
// 标签加上路径即可过滤出需要的标签,-> tag1/tag2/machineType|%/authcertType|%/
|
||||
childOrderTypesMatch := strings.Join(collx.ArrayMap(childOrderTypes, func(tt entity.TagType) string {
|
||||
return cast.ToString(int8(tt)) + entity.CodePathResourceSeparator + "%"
|
||||
}), entity.CodePathSeparator) + entity.CodePathSeparator
|
||||
|
||||
// 根据用户拥有的标签路径,赋值要过滤匹配的标签路径条件
|
||||
for _, accountTag := range accountTagPaths {
|
||||
accountTagCodePath := entity.CodePath(accountTag)
|
||||
// 标签路径,不包含资源段,如tag1/tag2/1|xxx => tag1/tag2/
|
||||
tagPath := accountTagCodePath.GetTag()
|
||||
// 纯纯的标签类型(不包含资源段),则直接在该标签路径上补上对应的子资源类型匹配表达式
|
||||
if tagPath == accountTagCodePath {
|
||||
needFilterAccountTagPaths = append(needFilterAccountTagPaths, accountTag)
|
||||
// 查询标签类型为标签时,特殊处理
|
||||
if len(childOrderTypes) == 1 && childOrderTypes[0] == entity.TagTypeTag {
|
||||
codePathLikes = append(codePathLikes, accountTag)
|
||||
continue
|
||||
}
|
||||
|
||||
// 纯标签可能还有其他子标签的纯标签,故需要多加一个匹配,如tagPath = tag1/,而系统还有 tag1/tag2/ tag1/tag2/tag3等,故需要多一个tag模糊匹配,即tag1/%/xxx
|
||||
codePathLikes = append(codePathLikes, accountTag+childOrderTypesMatch, accountTag+"%"+entity.CodePathSeparator+childOrderTypesMatch)
|
||||
continue
|
||||
}
|
||||
|
||||
// 将用户有权限操作的标签如 tag1/tag2/1|xxx 替换为tag1/tag2/1|%,并与需要查询的资源类型进行匹配
|
||||
accountTagCodePathSections := accountTagCodePath.GetPathSections()
|
||||
for _, section := range accountTagCodePathSections {
|
||||
if section.Type == entity.TagTypeTag {
|
||||
continue
|
||||
}
|
||||
section.Code = "%"
|
||||
}
|
||||
|
||||
codePathLike := string(tagPath) + childOrderTypesMatch
|
||||
if entity.CodePath(accountTagCodePathSections.ToCodePath()).CanAccess(codePathLike) {
|
||||
codePathLikes = append(codePathLikes, codePathLike)
|
||||
needFilterAccountTagPaths = append(needFilterAccountTagPaths, accountTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去重处理
|
||||
codePathLikes = collx.ArrayDeduplicate(codePathLikes)
|
||||
needFilter = true
|
||||
}
|
||||
|
||||
// 账号权限经过处理为空,则说明没有用户可以操作的标签,直接返回即可
|
||||
if needFilter && len(needFilterAccountTagPaths) == 0 {
|
||||
return tagResources
|
||||
}
|
||||
|
||||
tagResourceQuery.Codes = query.Codes
|
||||
tagResourceQuery.CodePathLikes = accountTagPaths
|
||||
tagResourceQuery.CodePathLikes = codePathLikes
|
||||
p.ListByQuery(tagResourceQuery, &tagResources)
|
||||
|
||||
if needFilter {
|
||||
tagResources = collx.ArrayFilter(tagResources, func(tr *dto.SimpleTagTree) bool {
|
||||
return slices.ContainsFunc(needFilterAccountTagPaths, func(accountTagPath string) bool {
|
||||
return entity.CodePath(accountTagPath).CanAccess(tr.CodePath)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return tagResources
|
||||
}
|
||||
|
||||
@@ -391,8 +475,9 @@ func (p *tagTreeAppImpl) CanAccess(accountId uint64, tagPath ...string) error {
|
||||
tagPaths := p.ListTagByAccountId(accountId)
|
||||
// 判断该资源标签是否为该账号拥有的标签或其子标签
|
||||
for _, v := range tagPaths {
|
||||
accountTagCodePath := entity.CodePath(v)
|
||||
for _, tp := range tagPath {
|
||||
if strings.HasPrefix(tp, v) {
|
||||
if accountTagCodePath.CanAccess(tp) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
38
server/internal/tag/application/tag_tree_test.go
Normal file
38
server/internal/tag/application/tag_tree_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
func TestTagPath(t *testing.T) {
|
||||
childOrderTypes := []int{1, 5}
|
||||
childOrderTypesMatch := strings.Join(collx.ArrayMap(childOrderTypes, func(tt int) string {
|
||||
return cast.ToString(tt) + entity.CodePathResourceSeparator + "%"
|
||||
}), entity.CodePathSeparator) + entity.CodePathSeparator
|
||||
fmt.Println(childOrderTypesMatch)
|
||||
}
|
||||
|
||||
func TestTagPathMatch(t *testing.T) {
|
||||
// accountCodePath := "tag1/tag2/2|xxdd/"
|
||||
// resourceCodePath := "tag1/tag2/1|%/11|%/%"
|
||||
|
||||
codePathLike := "default/2|%/22|%/"
|
||||
accountCodePath := "default/2|db_local/5|db_local_root/"
|
||||
|
||||
sections := entity.CodePath(accountCodePath).GetPathSections()
|
||||
for _, section := range sections {
|
||||
if section.Type == entity.TagTypeTag {
|
||||
continue
|
||||
}
|
||||
section.Code = "%"
|
||||
}
|
||||
accountTagPathPattern := sections.ToCodePath()
|
||||
match := strings.HasPrefix(codePathLike, accountTagPathPattern)
|
||||
fmt.Println(match)
|
||||
}
|
||||
@@ -1,15 +1,36 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type TypePath string
|
||||
|
||||
// ToTagTypes 转为路径对应的TagType数组
|
||||
func (t TypePath) ToTagTypes() []TagType {
|
||||
return collx.ArrayMap(strings.Split(string(t), CodePathSeparator), func(val string) TagType { return TagType(cast.ToInt8(val)) })
|
||||
}
|
||||
|
||||
// NewTypePaths
|
||||
func NewTypePaths(types ...TagType) TypePath {
|
||||
return TypePath(strings.Join(collx.ArrayMap[TagType, string](types, func(val TagType) string { return cast.ToString(int8(val)) }), CodePathSeparator))
|
||||
}
|
||||
|
||||
type TagTreeQuery struct {
|
||||
model.Model
|
||||
|
||||
Types []TagType
|
||||
TypePaths []TypePath // 类型路径。如 machineType/authcertType,即获取机器下的授权凭证
|
||||
Codes []string
|
||||
CodePaths []string // 标识路径
|
||||
Name string `json:"name"` // 名称
|
||||
CodePathLikes []string
|
||||
|
||||
GetAllChildren bool // 是否获取所有子节点
|
||||
}
|
||||
|
||||
type TeamQuery struct {
|
||||
|
||||
@@ -34,12 +34,8 @@ const (
|
||||
TagTypeDbInstance TagType = TagType(consts.ResourceTypeDbInstance) // 数据库实例
|
||||
TagTypeRedis TagType = TagType(consts.ResourceTypeRedis)
|
||||
TagTypeMongo TagType = TagType(consts.ResourceTypeMongo)
|
||||
TagTypeAuthCert TagType = 5 // 授权凭证类型
|
||||
|
||||
// ----- (单独声明各个资源的授权凭证类型而不统一使用一个授权凭证类型是为了获取登录账号的授权凭证标签(ResourceAuthCertApp.GetAccountAuthCert)时,避免查出所有资源的授权凭证)
|
||||
|
||||
TagTypeMachineAuthCert TagType = 11 // 机器-授权凭证
|
||||
|
||||
TagTypeDbAuthCert TagType = 21 // 数据库-授权凭证
|
||||
TagTypeDb TagType = 22 // 数据库名
|
||||
)
|
||||
|
||||
@@ -175,6 +171,14 @@ func (cp CodePath) GetAllPath() []string {
|
||||
})
|
||||
}
|
||||
|
||||
// CanAccess 判断该标签路径是否允许访问操作指定标签路径,即是否为指定标签路径的父级路径。cp通常为用户拥有的标签路径
|
||||
//
|
||||
// // cp = tag1/tag2/ codePath = tag1/tag2/test/ -> true
|
||||
// // cp = tag1/tag2/ codePath = tag1/ -> false
|
||||
func (cp CodePath) CanAccess(codePath string) bool {
|
||||
return strings.HasPrefix(codePath, string(cp))
|
||||
}
|
||||
|
||||
// PathSection 标签路径段
|
||||
type PathSection struct {
|
||||
Type TagType `json:"type"` // 类型: -1.普通标签; 其他值则为对应的资源类型
|
||||
|
||||
@@ -29,7 +29,7 @@ func InitTagTreeRouter(router *gin.RouterGroup) {
|
||||
|
||||
req.NewPost("/moving", m.MovingTag).Log(req.NewLogSaveI(imsg.LogTagMove)).RequiredPermissionCode("tag:save"),
|
||||
|
||||
req.NewGet("/resources/:rtype/tag-paths", m.TagResources),
|
||||
req.NewGet("/resources/tag-paths", m.TagResources),
|
||||
|
||||
req.NewGet("/resources/count", m.CountTagResource),
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import "fmt"
|
||||
|
||||
const (
|
||||
AppName = "mayfly-go"
|
||||
Version = "v1.9.1"
|
||||
Version = "v1.9.2"
|
||||
)
|
||||
|
||||
func GetAppInfo() string {
|
||||
|
||||
3
server/resources/script/sql/v1.9/v1.9.2.sql
Normal file
3
server/resources/script/sql/v1.9/v1.9.2.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
UPDATE `t_tag_tree` SET type = 5 WHERE type = 21 or type = 11;
|
||||
UPDATE `t_tag_tree` SET code_path = REPLACE(code_path, '/11|', '/5|') WHERE type = 5;
|
||||
UPDATE `t_tag_tree` SET code_path = REPLACE(code_path, '/21|', '/5|') WHERE type = 5 or type = 22;
|
||||
Reference in New Issue
Block a user