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