mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 00:10:25 +08:00 
			
		
		
		
	refactor: 标签资源重构
This commit is contained in:
		@@ -22,6 +22,8 @@ export class EnumValue {
 | 
			
		||||
     */
 | 
			
		||||
    tag: EnumValueTag;
 | 
			
		||||
 | 
			
		||||
    extra: any;
 | 
			
		||||
 | 
			
		||||
    constructor(value: any, label: string) {
 | 
			
		||||
        this.value = value;
 | 
			
		||||
        this.label = label;
 | 
			
		||||
@@ -53,6 +55,11 @@ export class EnumValue {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setExtra(extra: any): EnumValue {
 | 
			
		||||
        this.extra = extra;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static of(value: any, label: string): EnumValue {
 | 
			
		||||
        return new EnumValue(value, label);
 | 
			
		||||
    }
 | 
			
		||||
@@ -60,11 +67,12 @@ export class EnumValue {
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据枚举值获取指定枚举值对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param enumValues 所有枚举值
 | 
			
		||||
     * @param enums 枚举对象
 | 
			
		||||
     * @param value 需要匹配的枚举值
 | 
			
		||||
     * @returns 枚举值对象
 | 
			
		||||
     */
 | 
			
		||||
    static getEnumByValue(enumValues: EnumValue[], value: any): EnumValue | null {
 | 
			
		||||
    static getEnumByValue(enums: any, value: any): EnumValue | null {
 | 
			
		||||
        const enumValues = Object.values(enums) as any;
 | 
			
		||||
        for (let enumValue of enumValues) {
 | 
			
		||||
            if (enumValue.value == value) {
 | 
			
		||||
                return enumValue;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,9 @@ import EnumValue from './Enum';
 | 
			
		||||
 | 
			
		||||
// 标签关联的资源类型
 | 
			
		||||
export const TagResourceTypeEnum = {
 | 
			
		||||
    Machine: EnumValue.of(1, '机器'),
 | 
			
		||||
    Db: EnumValue.of(2, '数据库'),
 | 
			
		||||
    Redis: EnumValue.of(3, 'redis'),
 | 
			
		||||
    Mongo: EnumValue.of(4, 'mongo'),
 | 
			
		||||
    Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
 | 
			
		||||
    Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor' }),
 | 
			
		||||
    Db: EnumValue.of(2, '数据库').setExtra({ icon: 'Coin' }),
 | 
			
		||||
    Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis' }),
 | 
			
		||||
    Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo' }),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -2,3 +2,8 @@ export const AccountUsernamePattern = {
 | 
			
		||||
    pattern: /^[a-zA-Z0-9_]{5,20}$/g,
 | 
			
		||||
    message: '只允许输入5-20位大小写字母、数字、下划线',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ResourceCodePattern = {
 | 
			
		||||
    pattern: /^[a-zA-Z0-9_]{1,32}$/g,
 | 
			
		||||
    message: '只允许输入1-32位大小写字母、数字、下划线',
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ onMounted(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const convert = (value: any) => {
 | 
			
		||||
    const enumValue = EnumValue.getEnumByValue(Object.values(props.enums as any) as any, value) as any;
 | 
			
		||||
    const enumValue = EnumValue.getEnumByValue(props.enums, value) as any;
 | 
			
		||||
    if (!enumValue) {
 | 
			
		||||
        state.enumLabel = '-';
 | 
			
		||||
        state.type = 'danger';
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ onMounted(async () => {
 | 
			
		||||
        state.selectTags = props.selectTags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.tags = await tagApi.getTagTrees.request(null);
 | 
			
		||||
    state.tags = await tagApi.getTagTrees.request({ type: -1 });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const changeTag = () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,14 @@
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item prop="code" label="编号" required>
 | 
			
		||||
                    <el-input
 | 
			
		||||
                        :disabled="form.id"
 | 
			
		||||
                        v-model.trim="form.code"
 | 
			
		||||
                        placeholder="请输入机器编号 (数字字母下划线), 不可修改"
 | 
			
		||||
                        auto-complete="off"
 | 
			
		||||
                    ></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="name" label="别名" required>
 | 
			
		||||
                    <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
@@ -96,6 +104,7 @@ import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
			
		||||
import type { CheckboxValueType } from 'element-plus';
 | 
			
		||||
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
 | 
			
		||||
import { DbType } from '@/views/ops/db/dialect';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -128,7 +137,18 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -239,10 +239,15 @@ const perms = {
 | 
			
		||||
    restoreDb: 'db:restore',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Db.value), SearchItem.slot('instanceId', '实例', 'instanceSelect')];
 | 
			
		||||
const searchItems = [
 | 
			
		||||
    getTagPathSearchItem(TagResourceTypeEnum.Db.value),
 | 
			
		||||
    SearchItem.slot('instanceId', '实例', 'instanceSelect'),
 | 
			
		||||
    SearchItem.input('code', '编号'),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
    TableColumn.new('code', '编号'),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
 | 
			
		||||
    TableColumn.new('instanceName', '实例名'),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" :before-close="cancel" width="650px">
 | 
			
		||||
        <el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <el-form :model="form" ref="machineForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
@@ -18,19 +22,25 @@
 | 
			
		||||
                                style="width: 100%"
 | 
			
		||||
                            />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="protocol" label="机器类型" required>
 | 
			
		||||
                            <el-radio-group v-model="form.protocol" @change="handleChangeProtocol">
 | 
			
		||||
                                <el-radio :value="1">SSH</el-radio>
 | 
			
		||||
                                <el-radio :value="2">RDP</el-radio>
 | 
			
		||||
                                <!-- <el-radio :value="3">VNC</el-radio> -->
 | 
			
		||||
                            </el-radio-group>
 | 
			
		||||
                        <el-form-item prop="code" label="编号" required>
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                :disabled="form.id"
 | 
			
		||||
                                v-model.trim="form.code"
 | 
			
		||||
                                placeholder="请输入机器编号 (数字字母下划线), 不可修改"
 | 
			
		||||
                                auto-complete="off"
 | 
			
		||||
                            ></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入机器别名" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="ip" label="ip" req uired>
 | 
			
		||||
                        <el-form-item prop="protocol" label="协议" required>
 | 
			
		||||
                            <el-radio-group v-model="form.protocol" @change="handleChangeProtocol">
 | 
			
		||||
                                <el-radio v-for="item in MachineProtocolEnum" :key="item.value" :label="item.label" :value="item.value"></el-radio>
 | 
			
		||||
                            </el-radio-group>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="ip" label="ip" required>
 | 
			
		||||
                            <el-col :span="18">
 | 
			
		||||
                                <el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"> </el-input>
 | 
			
		||||
                                <el-input v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"> </el-input>
 | 
			
		||||
                            </el-col>
 | 
			
		||||
                            <el-col style="text-align: center" :span="1">:</el-col>
 | 
			
		||||
                            <el-col :span="5">
 | 
			
		||||
@@ -81,7 +91,7 @@
 | 
			
		||||
                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -92,6 +102,9 @@ import { ElMessage } from 'element-plus';
 | 
			
		||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
			
		||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import AuthCertSelect from './authcert/AuthCertSelect.vue';
 | 
			
		||||
import { MachineProtocolEnum } from './enums';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -116,6 +129,18 @@ const rules = {
 | 
			
		||||
            trigger: ['change'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
@@ -134,7 +159,7 @@ const rules = {
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入主机ip和端口',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    authCertId: [
 | 
			
		||||
@@ -263,11 +288,11 @@ const getReqForm = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleChangeProtocol = (val: any) => {
 | 
			
		||||
    if (val == 1) {
 | 
			
		||||
    if (val == MachineProtocolEnum.Ssh.value) {
 | 
			
		||||
        state.form.port = 22;
 | 
			
		||||
    } else if (val == 2) {
 | 
			
		||||
    } else if (val == MachineProtocolEnum.Rdp.value) {
 | 
			
		||||
        state.form.port = 3389;
 | 
			
		||||
    } else if (val == 3) {
 | 
			
		||||
    } else {
 | 
			
		||||
        state.form.port = 5901;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -251,10 +251,16 @@ const perms = {
 | 
			
		||||
    terminal: 'machine:terminal',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Machine.value), SearchItem.input('ip', 'IP'), SearchItem.input('name', '名称')];
 | 
			
		||||
const searchItems = [
 | 
			
		||||
    getTagPathSearchItem(TagResourceTypeEnum.Machine.value),
 | 
			
		||||
    SearchItem.input('code', '编号'),
 | 
			
		||||
    SearchItem.input('ip', 'IP'),
 | 
			
		||||
    SearchItem.input('name', '名称'),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const columns = [
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
    TableColumn.new('code', '编号'),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('ipPort', 'ip:port').isSlot().setAddWidth(50),
 | 
			
		||||
    TableColumn.new('stat', '运行状态').isSlot().setAddWidth(55),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,10 @@
 | 
			
		||||
import { EnumValue } from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
export const MachineProtocolEnum = {
 | 
			
		||||
    Ssh: EnumValue.of(1, 'SSH'),
 | 
			
		||||
    Rdp: EnumValue.of(2, 'RDP'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 脚本执行结果类型
 | 
			
		||||
export const ScriptResultEnum = {
 | 
			
		||||
    Result: EnumValue.of(1, '有结果'),
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,14 @@
 | 
			
		||||
                                style="width: 100%"
 | 
			
		||||
                            />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
 | 
			
		||||
                        <el-form-item prop="code" label="编号" required>
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                :disabled="form.id"
 | 
			
		||||
                                v-model.trim="form.code"
 | 
			
		||||
                                placeholder="请输入机器编号 (数字字母下划线), 不可修改"
 | 
			
		||||
                                auto-complete="off"
 | 
			
		||||
                            ></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入名称" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
@@ -57,6 +64,7 @@ import { mongoApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
			
		||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -81,6 +89,18 @@ const rules = {
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@ import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import { getTagPathSearchItem } from '../component/tag';
 | 
			
		||||
import { SearchItem } from '@/components/SearchForm';
 | 
			
		||||
 | 
			
		||||
const MongoEdit = defineAsyncComponent(() => import('./MongoEdit.vue'));
 | 
			
		||||
const MongoDbs = defineAsyncComponent(() => import('./MongoDbs.vue'));
 | 
			
		||||
@@ -67,10 +68,11 @@ const props = defineProps({
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Mongo.value)];
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Mongo.value), SearchItem.input('code', '编号')];
 | 
			
		||||
 | 
			
		||||
const columns = [
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
    TableColumn.new('code', '编号'),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('uri', '连接uri'),
 | 
			
		||||
    TableColumn.new('createTime', '创建时间').isTime(),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
 | 
			
		||||
        <el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
 | 
			
		||||
            <template #header>
 | 
			
		||||
                <DrawerHeader :header="title" :back="cancel" />
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <el-form :model="form" ref="redisForm" :rules="rules" label-width="auto">
 | 
			
		||||
                <el-tabs v-model="tabActiveName">
 | 
			
		||||
                    <el-tab-pane label="基础信息" name="basic">
 | 
			
		||||
@@ -17,6 +21,14 @@
 | 
			
		||||
                                style="width: 100%"
 | 
			
		||||
                            />
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="code" label="编号" required>
 | 
			
		||||
                            <el-input
 | 
			
		||||
                                :disabled="form.id"
 | 
			
		||||
                                v-model.trim="form.code"
 | 
			
		||||
                                placeholder="请输入机器编号 (数字字母下划线), 不可修改"
 | 
			
		||||
                                auto-complete="off"
 | 
			
		||||
                            ></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
                        <el-form-item prop="name" label="名称" required>
 | 
			
		||||
                            <el-input v-model.trim="form.name" placeholder="请输入redis名称" auto-complete="off"></el-input>
 | 
			
		||||
                        </el-form-item>
 | 
			
		||||
@@ -90,7 +102,7 @@
 | 
			
		||||
                    <el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
        </el-drawer>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -102,6 +114,8 @@ import { RsaEncrypt } from '@/common/rsa';
 | 
			
		||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
 | 
			
		||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
 | 
			
		||||
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
 | 
			
		||||
import { ResourceCodePattern } from '@/common/pattern';
 | 
			
		||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
@@ -125,6 +139,18 @@ const rules = {
 | 
			
		||||
            trigger: ['blur', 'change'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    code: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
            message: '请输入编码',
 | 
			
		||||
            trigger: ['change', 'blur'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            pattern: ResourceCodePattern.pattern,
 | 
			
		||||
            message: ResourceCodePattern.message,
 | 
			
		||||
            trigger: ['blur'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    name: [
 | 
			
		||||
        {
 | 
			
		||||
            required: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -161,6 +161,7 @@ import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import { getTagPathSearchItem } from '../component/tag';
 | 
			
		||||
import { SearchItem } from '@/components/SearchForm';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    lazy: {
 | 
			
		||||
@@ -172,10 +173,11 @@ const props = defineProps({
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const pageTableRef: Ref<any> = ref(null);
 | 
			
		||||
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Redis.value)];
 | 
			
		||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Redis.value), SearchItem.input('code', '编号')];
 | 
			
		||||
 | 
			
		||||
const columns = ref([
 | 
			
		||||
    TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
 | 
			
		||||
    TableColumn.new('code', '编号'),
 | 
			
		||||
    TableColumn.new('name', '名称'),
 | 
			
		||||
    TableColumn.new('host', 'host:port'),
 | 
			
		||||
    TableColumn.new('mode', 'mode'),
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,9 @@
 | 
			
		||||
                    >
 | 
			
		||||
                        <template #default="{ data }">
 | 
			
		||||
                            <span class="custom-tree-node">
 | 
			
		||||
                                <span style="font-size: 13px">
 | 
			
		||||
                                <SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" />
 | 
			
		||||
 | 
			
		||||
                                <span class="ml5">
 | 
			
		||||
                                    {{ data.code }}
 | 
			
		||||
                                    <span style="color: #3c8dbc">【</span>
 | 
			
		||||
                                    {{ data.name }}
 | 
			
		||||
@@ -61,6 +63,10 @@
 | 
			
		||||
                    <el-tabs @tab-change="tabChange" v-model="state.activeTabName" v-if="currentTag">
 | 
			
		||||
                        <el-tab-pane label="标签详情" :name="TagDetail">
 | 
			
		||||
                            <el-descriptions :column="2" border>
 | 
			
		||||
                                <el-descriptions-item label="类型">
 | 
			
		||||
                                    <EnumTag :enums="TagResourceTypeEnum" :value="currentTag.type" />
 | 
			
		||||
                                </el-descriptions-item>
 | 
			
		||||
 | 
			
		||||
                                <el-descriptions-item label="code">{{ currentTag.code }}</el-descriptions-item>
 | 
			
		||||
                                <el-descriptions-item label="code路径">{{ currentTag.codePath }}</el-descriptions-item>
 | 
			
		||||
                                <el-descriptions-item label="名称">{{ currentTag.name }}</el-descriptions-item>
 | 
			
		||||
@@ -73,19 +79,19 @@
 | 
			
		||||
                            </el-descriptions>
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                        <el-tab-pane :label="`机器 (${resourceCount.machine})`" :name="MachineTag">
 | 
			
		||||
                        <el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`机器 (${resourceCount.machine})`" :name="MachineTag">
 | 
			
		||||
                            <MachineList lazy ref="machineListRef" />
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                        <el-tab-pane :label="`数据库 (${resourceCount.db})`" :name="DbTag">
 | 
			
		||||
                        <el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`数据库 (${resourceCount.db})`" :name="DbTag">
 | 
			
		||||
                            <DbList lazy ref="dbListRef" />
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                        <el-tab-pane :label="`Redis (${resourceCount.redis})`" :name="RedisTag">
 | 
			
		||||
                        <el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`Redis (${resourceCount.redis})`" :name="RedisTag">
 | 
			
		||||
                            <RedisList lazy ref="redisListRef" />
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
 | 
			
		||||
                        <el-tab-pane :label="`Mongo (${resourceCount.mongo})`" :name="MongoTag">
 | 
			
		||||
                        <el-tab-pane v-if="currentTag.type == TagResourceTypeEnum.Tag.value" :label="`Mongo (${resourceCount.mongo})`" :name="MongoTag">
 | 
			
		||||
                            <MongoList lazy ref="mongoListRef" />
 | 
			
		||||
                        </el-tab-pane>
 | 
			
		||||
                    </el-tabs>
 | 
			
		||||
@@ -129,6 +135,9 @@ import MachineList from '../machine/MachineList.vue';
 | 
			
		||||
import RedisList from '../redis/RedisList.vue';
 | 
			
		||||
import MongoList from '../mongo/MongoList.vue';
 | 
			
		||||
import DbList from '../db/DbList.vue';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
interface Tree {
 | 
			
		||||
    id: number;
 | 
			
		||||
@@ -155,6 +164,10 @@ const MongoTag = 'mongoTag';
 | 
			
		||||
const contextmenuAdd = new ContextmenuItem('addTag', '添加子标签')
 | 
			
		||||
    .withIcon('circle-plus')
 | 
			
		||||
    .withPermission('tag:save')
 | 
			
		||||
    .withHideFunc((data: any) => {
 | 
			
		||||
        // 非标签类型不可添加子标签
 | 
			
		||||
        return data.type != -1;
 | 
			
		||||
    })
 | 
			
		||||
    .withOnClick((data: any) => showSaveTagDialog(data));
 | 
			
		||||
 | 
			
		||||
const contextmenuEdit = new ContextmenuItem('edit', '编辑')
 | 
			
		||||
@@ -167,7 +180,7 @@ const contextmenuDel = new ContextmenuItem('delete', '删除')
 | 
			
		||||
    .withPermission('tag:del')
 | 
			
		||||
    .withHideFunc((data: any) => {
 | 
			
		||||
        // 存在子标签,则不允许删除
 | 
			
		||||
        return data.children;
 | 
			
		||||
        return data.children || data.type != -1;
 | 
			
		||||
    })
 | 
			
		||||
    .withOnClick((data: any) => deleteTag(data));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,9 @@
 | 
			
		||||
                    >
 | 
			
		||||
                        <template #default="{ data }">
 | 
			
		||||
                            <span class="custom-tree-node">
 | 
			
		||||
                                <span style="font-size: 13px">
 | 
			
		||||
                                <SvgIcon :name="EnumValue.getEnumByValue(TagResourceTypeEnum, data.type)?.extra.icon" />
 | 
			
		||||
 | 
			
		||||
                                <span class="font13 ml5">
 | 
			
		||||
                                    {{ data.code }}
 | 
			
		||||
                                    <span style="color: #3c8dbc">【</span>
 | 
			
		||||
                                    {{ data.name }}
 | 
			
		||||
@@ -128,6 +130,8 @@ import PageTable from '@/components/pagetable/PageTable.vue';
 | 
			
		||||
import { TableColumn } from '@/components/pagetable';
 | 
			
		||||
import { SearchItem } from '@/components/SearchForm';
 | 
			
		||||
import AccountSelectFormItem from '@/views/system/account/components/AccountSelectFormItem.vue';
 | 
			
		||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
 | 
			
		||||
import EnumValue from '@/common/Enum';
 | 
			
		||||
 | 
			
		||||
const teamForm: any = ref(null);
 | 
			
		||||
const tagTreeRef: any = ref(null);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ export const tagApi = {
 | 
			
		||||
    delTagTree: Api.newDelete('/tag-trees/{id}'),
 | 
			
		||||
 | 
			
		||||
    getResourceTagPaths: Api.newGet('/tag-trees/resources/{resourceType}/tag-paths'),
 | 
			
		||||
    getTagResources: Api.newGet('/tag-trees/resources'),
 | 
			
		||||
    countTagResource: Api.newGet('/tag-trees/resources/count'),
 | 
			
		||||
 | 
			
		||||
    getTeams: Api.newGet('/teams'),
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,6 @@ server:
 | 
			
		||||
    enable: false
 | 
			
		||||
    key-file: ./default.key
 | 
			
		||||
    cert-file: ./default.pem
 | 
			
		||||
machine:
 | 
			
		||||
  # 如果需要添加rdp服务器,需要安装guacd服务,docker跑一个就行  docker run --name guacd -d -p 4822:4822 guacamole/guacd
 | 
			
		||||
  # 如果连接频繁中断,重启一下guacd
 | 
			
		||||
  guacd-host: 127.0.0.1
 | 
			
		||||
  guacd-port: 4822
 | 
			
		||||
jwt:
 | 
			
		||||
  # jwt key,不设置默认使用随机字符串
 | 
			
		||||
  key: 
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ require (
 | 
			
		||||
	github.com/sijms/go-ora/v2 v2.8.10
 | 
			
		||||
	github.com/stretchr/testify v1.8.4
 | 
			
		||||
	go.mongodb.org/mongo-driver v1.14.0 // mongo
 | 
			
		||||
	golang.org/x/crypto v0.21.0 // ssh
 | 
			
		||||
	golang.org/x/crypto v0.22.0 // ssh
 | 
			
		||||
	golang.org/x/oauth2 v0.18.0
 | 
			
		||||
	golang.org/x/sync v0.6.0
 | 
			
		||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
			
		||||
@@ -89,7 +89,7 @@ require (
 | 
			
		||||
	golang.org/x/exp v0.0.0-20230519143937-03e91628a987 // indirect
 | 
			
		||||
	golang.org/x/image v0.13.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.18.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.19.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.14.0 // indirect
 | 
			
		||||
	google.golang.org/appengine v1.6.7 // indirect
 | 
			
		||||
	google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package form
 | 
			
		||||
 | 
			
		||||
type DbForm struct {
 | 
			
		||||
	Id             uint64   `json:"id"`
 | 
			
		||||
	Code           string   `binding:"required" json:"code"`
 | 
			
		||||
	Name           string   `binding:"required" json:"name"`
 | 
			
		||||
	Database       string   `json:"database"`
 | 
			
		||||
	Remark         string   `json:"remark"`
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
	"mayfly-go/pkg/utils/structx"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -79,13 +78,18 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
 | 
			
		||||
			return errorx.NewBiz("该实例下数据库名已存在")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		resouceCode := stringx.Rand(16)
 | 
			
		||||
		dbEntity.Code = resouceCode
 | 
			
		||||
		if d.CountByCond(&entity.Db{Code: dbEntity.Code}) > 0 {
 | 
			
		||||
			return errorx.NewBiz("该编码已存在")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return d.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
			return d.Insert(ctx, dbEntity)
 | 
			
		||||
		}, func(ctx context.Context) error {
 | 
			
		||||
			return d.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeDb, tagIds)
 | 
			
		||||
			return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
				ResourceCode: dbEntity.Code,
 | 
			
		||||
				ResourceType: consts.TagResourceTypeDb,
 | 
			
		||||
				TagIds:       tagIds,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -116,10 +120,16 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db, tagIds ...u
 | 
			
		||||
		d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: dbId, Db: v})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 防止误传修改
 | 
			
		||||
	dbEntity.Code = ""
 | 
			
		||||
	return d.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
		return d.UpdateById(ctx, dbEntity)
 | 
			
		||||
	}, func(ctx context.Context) error {
 | 
			
		||||
		return d.tagApp.RelateResource(ctx, old.Code, consts.TagResourceTypeDb, tagIds)
 | 
			
		||||
		return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
			ResourceCode: old.Code,
 | 
			
		||||
			ResourceType: consts.TagResourceTypeDb,
 | 
			
		||||
			TagIds:       tagIds,
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -142,8 +152,10 @@ func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
 | 
			
		||||
			// 删除该库下用户保存的所有sql信息
 | 
			
		||||
			return d.dbSqlRepo.DeleteByCond(ctx, &entity.DbSql{DbId: id})
 | 
			
		||||
		}, func(ctx context.Context) error {
 | 
			
		||||
			var tagIds []uint64
 | 
			
		||||
			return d.tagApp.RelateResource(ctx, db.Code, consts.TagResourceTypeDb, tagIds)
 | 
			
		||||
			return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
				ResourceCode: db.Code,
 | 
			
		||||
				ResourceType: consts.TagResourceTypeDb,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ type DbTransferLogQuery struct {
 | 
			
		||||
 | 
			
		||||
// 数据库查询实体,不与数据库表字段一一对应
 | 
			
		||||
type DbQuery struct {
 | 
			
		||||
	Id uint64 `form:"id"`
 | 
			
		||||
 | 
			
		||||
	Id       uint64 `form:"id"`
 | 
			
		||||
	Code     string `json:"code" form:"code"`
 | 
			
		||||
	Name     string `orm:"column(name)" json:"name"`
 | 
			
		||||
	Database string `orm:"column(database)" json:"database"`
 | 
			
		||||
	Remark   string `json:"remark"`
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageP
 | 
			
		||||
		Eq("db.instance_id", condition.InstanceId).
 | 
			
		||||
		Eq("db.id", condition.Id).
 | 
			
		||||
		Like("db.database", condition.Database).
 | 
			
		||||
		Eq("db.code", condition.Code).
 | 
			
		||||
		In("db.code", condition.Codes).
 | 
			
		||||
		Eq0("db."+model.DeletedColumn, model.ModelUndeleted).
 | 
			
		||||
		Eq0("inst."+model.DeletedColumn, model.ModelUndeleted)
 | 
			
		||||
@@ -39,5 +40,8 @@ func (d *dbRepoImpl) Count(condition *entity.DbQuery) int64 {
 | 
			
		||||
	if condition.InstanceId > 0 {
 | 
			
		||||
		where["instance_id"] = condition.InstanceId
 | 
			
		||||
	}
 | 
			
		||||
	if condition.Code != "" {
 | 
			
		||||
		where["code"] = condition.Code
 | 
			
		||||
	}
 | 
			
		||||
	return d.CountByCond(where)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package form
 | 
			
		||||
type MachineForm struct {
 | 
			
		||||
	Id       uint64 `json:"id"`
 | 
			
		||||
	Protocol int    `json:"protocol" binding:"required"`
 | 
			
		||||
	Code     string `json:"code" binding:"required"`
 | 
			
		||||
	Name     string `json:"name" binding:"required"`
 | 
			
		||||
	Ip       string `json:"ip" binding:"required"`   // IP地址
 | 
			
		||||
	Port     int    `json:"port" binding:"required"` // 端口号
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/logx"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"mayfly-go/pkg/scheduler"
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -88,15 +87,21 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, ta
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return errorx.NewBiz("该机器信息已存在")
 | 
			
		||||
		}
 | 
			
		||||
		resouceCode := stringx.Rand(16)
 | 
			
		||||
		me.Code = resouceCode
 | 
			
		||||
		if m.CountByCond(&entity.Machine{Code: me.Code}) > 0 {
 | 
			
		||||
			return errorx.NewBiz("该编码已存在")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 新增机器,默认启用状态
 | 
			
		||||
		me.Status = entity.MachineStatusEnable
 | 
			
		||||
 | 
			
		||||
		return m.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
			return m.Insert(ctx, me)
 | 
			
		||||
		}, func(ctx context.Context) error {
 | 
			
		||||
			return m.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeMachine, tagIds)
 | 
			
		||||
			return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
				ResourceCode: me.Code,
 | 
			
		||||
				ResourceType: consts.TagResourceTypeMachine,
 | 
			
		||||
				TagIds:       tagIds,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -111,10 +116,16 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, me *entity.Machine, ta
 | 
			
		||||
 | 
			
		||||
	// 关闭连接
 | 
			
		||||
	mcm.DeleteCli(me.Id)
 | 
			
		||||
	// 防止误传修改
 | 
			
		||||
	me.Code = ""
 | 
			
		||||
	return m.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
		return m.UpdateById(ctx, me)
 | 
			
		||||
	}, func(ctx context.Context) error {
 | 
			
		||||
		return m.tagApp.RelateResource(ctx, oldMachine.Code, consts.TagResourceTypeMachine, tagIds)
 | 
			
		||||
		return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
			ResourceCode: oldMachine.Code,
 | 
			
		||||
			ResourceType: consts.TagResourceTypeMachine,
 | 
			
		||||
			TagIds:       tagIds,
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -158,8 +169,10 @@ func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
 | 
			
		||||
		func(ctx context.Context) error {
 | 
			
		||||
			return m.DeleteById(ctx, id)
 | 
			
		||||
		}, func(ctx context.Context) error {
 | 
			
		||||
			var tagIds []uint64
 | 
			
		||||
			return m.tagApp.RelateResource(ctx, machine.Code, consts.TagResourceTypeMachine, tagIds)
 | 
			
		||||
			return m.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
				ResourceCode: machine.Code,
 | 
			
		||||
				ResourceType: consts.TagResourceTypeMachine,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ func GetMachine() *Machine {
 | 
			
		||||
	mc.UploadMaxFileSize = uploadMaxFileSize
 | 
			
		||||
	mc.TermOpSaveDays = cast.ToIntD(jm["termOpSaveDays"], 30)
 | 
			
		||||
	// guacd
 | 
			
		||||
	mc.GuacdHost = cast.ToStringD(jm["guacdHost"], "127.0.0.1")
 | 
			
		||||
	mc.GuacdHost = cast.ToString(jm["guacdHost"])
 | 
			
		||||
	mc.GuacdPort = cast.ToIntD(jm["guacdPort"], 4822)
 | 
			
		||||
	mc.GuacdFilePath = cast.ToStringD(jm["guacdFilePath"], "")
 | 
			
		||||
	mc.GuacdRecPath = cast.ToStringD(jm["guacdRecPath"], "")
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import "time"
 | 
			
		||||
 | 
			
		||||
type MachineQuery struct {
 | 
			
		||||
	Ids     string `json:"ids" form:"ids"`
 | 
			
		||||
	Code    string `json:"code" form:"code"`
 | 
			
		||||
	Name    string `json:"name" form:"name"`
 | 
			
		||||
	Status  int8   `json:"status" form:"status"`
 | 
			
		||||
	Ip      string `json:"ip" form:"ip"` // IP地址
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,15 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mayfly-go/internal/machine/config"
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/logx"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// creates the tunnel to the remote machine (via guacd)
 | 
			
		||||
@@ -62,7 +64,12 @@ func DoConnect(query url.Values, parameters map[string]string, machineId uint64)
 | 
			
		||||
	conf.ImageMimetypes = []string{"image/jpeg", "image/png", "image/webp"}
 | 
			
		||||
 | 
			
		||||
	logx.Debug("Connecting to guacd")
 | 
			
		||||
	guacdAddr := fmt.Sprintf("%v:%v", config.GetMachine().GuacdHost, config.GetMachine().GuacdPort)
 | 
			
		||||
 | 
			
		||||
	machineConfig := config.GetMachine()
 | 
			
		||||
	if machineConfig.GuacdHost == "" {
 | 
			
		||||
		return nil, errorx.NewBiz("请前往'系统配置-机器配置'中配置guacd相关信息")
 | 
			
		||||
	}
 | 
			
		||||
	guacdAddr := fmt.Sprintf("%v:%v", machineConfig.GuacdHost, machineConfig.GuacdPort)
 | 
			
		||||
	addr, err := net.ResolveTCPAddr("tcp", guacdAddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logx.Error("error resolving guacd address", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,8 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
 | 
			
		||||
		Eq("status", condition.Status).
 | 
			
		||||
		Like("ip", condition.Ip).
 | 
			
		||||
		Like("name", condition.Name).
 | 
			
		||||
		In("code", condition.Codes)
 | 
			
		||||
		In("code", condition.Codes).
 | 
			
		||||
		Eq("code", condition.Code)
 | 
			
		||||
 | 
			
		||||
	// 只查询ssh服务器
 | 
			
		||||
	if condition.Ssh == entity.MachineProtocolSsh {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package form
 | 
			
		||||
 | 
			
		||||
type Mongo struct {
 | 
			
		||||
	Id                 uint64   `json:"id"`
 | 
			
		||||
	Code               uint64   `json:"code" binding:"required"`
 | 
			
		||||
	Uri                string   `binding:"required" json:"uri"`
 | 
			
		||||
	SshTunnelMachineId int      `json:"sshTunnelMachineId"` // ssh隧道机器id
 | 
			
		||||
	Name               string   `binding:"required" json:"name"`
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/base"
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Mongo interface {
 | 
			
		||||
@@ -59,8 +58,10 @@ func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
 | 
			
		||||
			return d.DeleteById(ctx, id)
 | 
			
		||||
		},
 | 
			
		||||
		func(ctx context.Context) error {
 | 
			
		||||
			var tagIds []uint64
 | 
			
		||||
			return d.tagApp.RelateResource(ctx, mongoEntity.Code, consts.TagResourceTypeMongo, tagIds)
 | 
			
		||||
			return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
				ResourceType: consts.TagResourceTypeMongo,
 | 
			
		||||
				ResourceCode: mongoEntity.Code,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -81,14 +82,18 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return errorx.NewBiz("该名称已存在")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		resouceCode := stringx.Rand(16)
 | 
			
		||||
		m.Code = resouceCode
 | 
			
		||||
		if d.CountByCond(&entity.Mongo{Code: m.Code}) > 0 {
 | 
			
		||||
			return errorx.NewBiz("该编码已存在")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return d.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
			return d.Insert(ctx, m)
 | 
			
		||||
		}, func(ctx context.Context) error {
 | 
			
		||||
			return d.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeMongo, tagIds)
 | 
			
		||||
			return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
				ResourceType: consts.TagResourceTypeMongo,
 | 
			
		||||
				ResourceCode: m.Code,
 | 
			
		||||
				TagIds:       tagIds,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -103,10 +108,15 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagIds ..
 | 
			
		||||
 | 
			
		||||
	// 先关闭连接
 | 
			
		||||
	mgm.CloseConn(m.Id)
 | 
			
		||||
	m.Code = ""
 | 
			
		||||
	return d.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
		return d.UpdateById(ctx, m)
 | 
			
		||||
	}, func(ctx context.Context) error {
 | 
			
		||||
		return d.tagApp.RelateResource(ctx, oldMongo.Code, consts.TagResourceTypeMongo, tagIds)
 | 
			
		||||
		return d.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
			ResourceType: consts.TagResourceTypeMongo,
 | 
			
		||||
			ResourceCode: oldMongo.Code,
 | 
			
		||||
			TagIds:       tagIds,
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import "mayfly-go/pkg/model"
 | 
			
		||||
type MongoQuery struct {
 | 
			
		||||
	model.Model
 | 
			
		||||
 | 
			
		||||
	Code               string `json:"code" form:"code"`
 | 
			
		||||
	Name               string
 | 
			
		||||
	Uri                string
 | 
			
		||||
	SshTunnelMachineId uint64 // ssh隧道机器id
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ func newMongoRepo() repository.Mongo {
 | 
			
		||||
func (d *mongoRepoImpl) GetList(condition *entity.MongoQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	qd := gormx.NewQuery(new(entity.Mongo)).
 | 
			
		||||
		Like("name", condition.Name).
 | 
			
		||||
		Eq("code", condition.Code).
 | 
			
		||||
		In("code", condition.Codes)
 | 
			
		||||
	return gormx.PageQuery(qd, pageParam, toEntity)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package form
 | 
			
		||||
 | 
			
		||||
type Redis struct {
 | 
			
		||||
	Id                 uint64   `json:"id"`
 | 
			
		||||
	Code               string   `json:"code" binding:"required"`
 | 
			
		||||
	Name               string   `json:"name"`
 | 
			
		||||
	Host               string   `json:"host" binding:"required"`
 | 
			
		||||
	Username           string   `json:"username"`
 | 
			
		||||
 
 | 
			
		||||
@@ -95,17 +95,22 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return errorx.NewBiz("该实例已存在")
 | 
			
		||||
		}
 | 
			
		||||
		if r.CountByCond(&entity.Redis{Code: re.Code}) > 0 {
 | 
			
		||||
			return errorx.NewBiz("该编码已存在")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if errEnc := re.PwdEncrypt(); errEnc != nil {
 | 
			
		||||
			return errorx.NewBiz(errEnc.Error())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		resouceCode := stringx.Rand(16)
 | 
			
		||||
		re.Code = resouceCode
 | 
			
		||||
 | 
			
		||||
		return r.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
			return r.Insert(ctx, re)
 | 
			
		||||
		}, func(ctx context.Context) error {
 | 
			
		||||
			return r.tagApp.RelateResource(ctx, resouceCode, consts.TagResourceTypeRedis, tagIds)
 | 
			
		||||
			return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
				ResourceType: consts.TagResourceTypeRedis,
 | 
			
		||||
				ResourceCode: re.Code,
 | 
			
		||||
				TagIds:       tagIds,
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -128,10 +133,15 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
 | 
			
		||||
	if errEnc := re.PwdEncrypt(); errEnc != nil {
 | 
			
		||||
		return errorx.NewBiz(errEnc.Error())
 | 
			
		||||
	}
 | 
			
		||||
	re.Code = ""
 | 
			
		||||
	return r.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
		return r.UpdateById(ctx, re)
 | 
			
		||||
	}, func(ctx context.Context) error {
 | 
			
		||||
		return r.tagApp.RelateResource(ctx, oldRedis.Code, consts.TagResourceTypeRedis, tagIds)
 | 
			
		||||
		return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
			ResourceType: consts.TagResourceTypeRedis,
 | 
			
		||||
			ResourceCode: oldRedis.Code,
 | 
			
		||||
			TagIds:       tagIds,
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -150,8 +160,10 @@ func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
 | 
			
		||||
	return r.Tx(ctx, func(ctx context.Context) error {
 | 
			
		||||
		return r.DeleteById(ctx, id)
 | 
			
		||||
	}, func(ctx context.Context) error {
 | 
			
		||||
		var tagIds []uint64
 | 
			
		||||
		return r.tagApp.RelateResource(ctx, re.Code, consts.TagResourceTypeRedis, tagIds)
 | 
			
		||||
		return r.tagApp.SaveResource(ctx, &tagapp.SaveResourceTagParam{
 | 
			
		||||
			ResourceType: consts.TagResourceTypeRedis,
 | 
			
		||||
			ResourceCode: re.Code,
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package entity
 | 
			
		||||
 | 
			
		||||
type RedisQuery struct {
 | 
			
		||||
	Id   uint64 `form:"id"`
 | 
			
		||||
	Code string `json:"code" form:"code"`
 | 
			
		||||
	Name string `orm:"column(name)" json:"name" form:"name"`
 | 
			
		||||
	Host string `orm:"column(host)" json:"host" form:"host"`
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,11 +16,12 @@ func newRedisRepo() repository.Redis {
 | 
			
		||||
	return &redisRepoImpl{base.RepoImpl[*entity.Redis]{M: new(entity.Redis)}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 分页获取机器信息列表
 | 
			
		||||
// 分页获取redis信息列表
 | 
			
		||||
func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
 | 
			
		||||
	qd := gormx.NewQuery(new(entity.Redis)).
 | 
			
		||||
		Eq("id", condition.Id).
 | 
			
		||||
		Like("host", condition.Host).
 | 
			
		||||
		Eq("code", condition.Code).
 | 
			
		||||
		In("code", condition.Codes)
 | 
			
		||||
	return gormx.PageQuery(qd, pageParam, toEntity)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,15 +14,15 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TagTree struct {
 | 
			
		||||
	TagTreeApp     application.TagTree     `inject:""`
 | 
			
		||||
	TagResourceApp application.TagResource `inject:""`
 | 
			
		||||
	TagTreeApp application.TagTree `inject:""`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *TagTree) GetTagTree(rc *req.Ctx) {
 | 
			
		||||
	tagType := rc.QueryInt("type")
 | 
			
		||||
	// 超管返回所有标签树
 | 
			
		||||
	if rc.GetLoginAccount().Id == consts.AdminId {
 | 
			
		||||
		var tagTrees vo.TagTreeVOS
 | 
			
		||||
		p.TagTreeApp.ListByQuery(new(entity.TagTreeQuery), &tagTrees)
 | 
			
		||||
		p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{Type: int8(tagType)}, &tagTrees)
 | 
			
		||||
		rc.ResData = tagTrees.ToTrees(0)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -36,11 +36,12 @@ func (p *TagTree) GetTagTree(rc *req.Ctx) {
 | 
			
		||||
		tags := rootTag[root]
 | 
			
		||||
		tags = append(tags, accountTagPath)
 | 
			
		||||
		rootTag[root] = tags
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 获取所有以root标签开头的子标签
 | 
			
		||||
	tags := p.TagTreeApp.ListTagByPath(collx.MapKeys(rootTag)...)
 | 
			
		||||
	var tags []*entity.TagTree
 | 
			
		||||
	p.TagTreeApp.ListByQuery(&entity.TagTreeQuery{CodePathLikes: collx.MapKeys(rootTag), Type: int8(tagType)}, &tags)
 | 
			
		||||
 | 
			
		||||
	tagTrees := make(vo.TagTreeVOS, 0)
 | 
			
		||||
	for _, tag := range tags {
 | 
			
		||||
		tagPath := tag.CodePath
 | 
			
		||||
@@ -84,8 +85,8 @@ func (p *TagTree) DelTagTree(rc *req.Ctx) {
 | 
			
		||||
func (p *TagTree) TagResources(rc *req.Ctx) {
 | 
			
		||||
	resourceType := int8(rc.PathParamInt("rtype"))
 | 
			
		||||
	tagResources := p.TagTreeApp.GetAccountTagResources(rc.GetLoginAccount().Id, resourceType, "")
 | 
			
		||||
	tagPath2Resource := collx.ArrayToMap[entity.TagResource, string](tagResources, func(tagResource entity.TagResource) string {
 | 
			
		||||
		return tagResource.TagPath
 | 
			
		||||
	tagPath2Resource := collx.ArrayToMap[entity.TagTree, string](tagResources, func(tagResource entity.TagTree) string {
 | 
			
		||||
		return tagResource.GetParentPath()
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	tagPaths := collx.MapKeys(tagPath2Resource)
 | 
			
		||||
@@ -104,10 +105,3 @@ func (p *TagTree) CountTagResource(rc *req.Ctx) {
 | 
			
		||||
		"mongo":   len(p.TagTreeApp.GetAccountResourceCodes(accountId, consts.TagResourceTypeMongo, tagPath)),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 资源标签关联信息查询
 | 
			
		||||
func (p *TagTree) QueryTagResources(rc *req.Ctx) {
 | 
			
		||||
	var trs []*entity.TagResource
 | 
			
		||||
	p.TagResourceApp.ListByQuery(req.BindQuery(rc, new(entity.TagResourceQuery)), &trs)
 | 
			
		||||
	rc.ResData = trs
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,5 +7,4 @@ import (
 | 
			
		||||
func InitIoc() {
 | 
			
		||||
	ioc.Register(new(tagTreeAppImpl), ioc.WithComponentName("TagTreeApp"))
 | 
			
		||||
	ioc.Register(new(teamAppImpl), ioc.WithComponentName("TeamApp"))
 | 
			
		||||
	ioc.Register(new(tagResourceAppImpl), ioc.WithComponentName("TagResourceApp"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
package application
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/internal/tag/domain/entity"
 | 
			
		||||
	"mayfly-go/internal/tag/domain/repository"
 | 
			
		||||
	"mayfly-go/pkg/base"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TagResource interface {
 | 
			
		||||
	base.App[*entity.TagResource]
 | 
			
		||||
 | 
			
		||||
	ListByQuery(condition *entity.TagResourceQuery, toEntity any)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tagResourceAppImpl struct {
 | 
			
		||||
	base.AppImpl[*entity.TagResource, repository.TagResource]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 注入TagResourceRepo
 | 
			
		||||
func (tr *tagResourceAppImpl) InjectTagResourceRepo(repo repository.TagResource) {
 | 
			
		||||
	tr.Repo = repo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tr *tagResourceAppImpl) ListByQuery(condition *entity.TagResourceQuery, toEntity any) {
 | 
			
		||||
	tr.Repo.SelectByCondition(condition, toEntity)
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,15 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 保存资源标签参数
 | 
			
		||||
type SaveResourceTagParam struct {
 | 
			
		||||
	ResourceCode string
 | 
			
		||||
	ResourceName string
 | 
			
		||||
	ResourceType int8
 | 
			
		||||
 | 
			
		||||
	TagIds []uint64 // 关联标签,相当于父标签 pid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TagTree interface {
 | 
			
		||||
	base.App[*entity.TagTree]
 | 
			
		||||
 | 
			
		||||
@@ -25,23 +34,17 @@ type TagTree interface {
 | 
			
		||||
	// @param accountId 账号id
 | 
			
		||||
	// @param resourceType 资源类型
 | 
			
		||||
	// @param tagPath 访问指定的标签路径下关联的资源
 | 
			
		||||
	GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagResource
 | 
			
		||||
	GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagTree
 | 
			
		||||
 | 
			
		||||
	// 获取指定账号有权限操作的资源codes
 | 
			
		||||
	GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string
 | 
			
		||||
 | 
			
		||||
	// 关联资源
 | 
			
		||||
	// @resourceCode 资源唯一编号
 | 
			
		||||
	// @resourceType 资源类型
 | 
			
		||||
	// @tagIds 资源关联的标签
 | 
			
		||||
	RelateResource(ctx context.Context, resourceCode string, resourceType int8, tagIds []uint64) error
 | 
			
		||||
	// SaveResource 保存资源标签
 | 
			
		||||
	SaveResource(ctx context.Context, req *SaveResourceTagParam) error
 | 
			
		||||
 | 
			
		||||
	// 根据资源信息获取对应的标签路径列表
 | 
			
		||||
	ListTagPathByResource(resourceType int8, resourceCode string) []string
 | 
			
		||||
 | 
			
		||||
	// 根据tagPath获取自身及其所有子标签信息
 | 
			
		||||
	ListTagByPath(tagPath ...string) []*entity.TagTree
 | 
			
		||||
 | 
			
		||||
	// 根据账号id获取其可访问标签信息
 | 
			
		||||
	ListTagByAccountId(accountId uint64) []string
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +59,6 @@ type tagTreeAppImpl struct {
 | 
			
		||||
	base.AppImpl[*entity.TagTree, repository.TagTree]
 | 
			
		||||
 | 
			
		||||
	tagTreeTeamRepo repository.TagTreeTeam `inject:"TagTreeTeamRepo"`
 | 
			
		||||
	tagResourceApp  TagResource            `inject:"TagResourceApp"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 注入TagTreeRepo
 | 
			
		||||
@@ -76,9 +78,9 @@ func (p *tagTreeAppImpl) Save(ctx context.Context, tag *entity.TagTree) error {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errorx.NewBiz("父节点不存在")
 | 
			
		||||
			}
 | 
			
		||||
			if p.tagResourceApp.CountByCond(&entity.TagResource{TagId: tag.Pid}) > 0 {
 | 
			
		||||
				return errorx.NewBiz("该父标签已关联资源, 无法添加子标签")
 | 
			
		||||
			}
 | 
			
		||||
			// if p.tagResourceApp.CountByCond(&entity.TagResource{TagId: tag.Pid}) > 0 {
 | 
			
		||||
			// 	return errorx.NewBiz("该父标签已关联资源, 无法添加子标签")
 | 
			
		||||
			// }
 | 
			
		||||
 | 
			
		||||
			tag.CodePath = parentTag.CodePath + tag.Code + entity.CodePathSeparator
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -98,6 +100,8 @@ func (p *tagTreeAppImpl) Save(ctx context.Context, tag *entity.TagTree) error {
 | 
			
		||||
			return errorx.NewBiz("已存在该标签路径开头的标签, 请修改该标识code")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 普通标签类型
 | 
			
		||||
		tag.Type = -1
 | 
			
		||||
		return p.Insert(ctx, tag)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -111,12 +115,12 @@ func (p *tagTreeAppImpl) ListByQuery(condition *entity.TagTreeQuery, toEntity an
 | 
			
		||||
	p.GetRepo().SelectByCondition(condition, toEntity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagResource {
 | 
			
		||||
	tagResourceQuery := &entity.TagResourceQuery{
 | 
			
		||||
		ResourceType: resourceType,
 | 
			
		||||
func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType int8, tagPath string) []entity.TagTree {
 | 
			
		||||
	tagResourceQuery := &entity.TagTreeQuery{
 | 
			
		||||
		Type: resourceType,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tagResources []entity.TagResource
 | 
			
		||||
	var tagResources []entity.TagTree
 | 
			
		||||
	var accountTagPaths []string
 | 
			
		||||
 | 
			
		||||
	if accountId != consts.AdminId {
 | 
			
		||||
@@ -127,70 +131,82 @@ func (p *tagTreeAppImpl) GetAccountTagResources(accountId uint64, resourceType i
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tagResourceQuery.TagPathLike = tagPath
 | 
			
		||||
	tagResourceQuery.TagPathLikes = accountTagPaths
 | 
			
		||||
	p.tagResourceApp.ListByQuery(tagResourceQuery, &tagResources)
 | 
			
		||||
	tagResourceQuery.CodePathLike = tagPath
 | 
			
		||||
	tagResourceQuery.CodePathLikes = accountTagPaths
 | 
			
		||||
	p.ListByQuery(tagResourceQuery, &tagResources)
 | 
			
		||||
	return tagResources
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagTreeAppImpl) GetAccountResourceCodes(accountId uint64, resourceType int8, tagPath string) []string {
 | 
			
		||||
	tagResources := p.GetAccountTagResources(accountId, resourceType, tagPath)
 | 
			
		||||
	// resouce code去重
 | 
			
		||||
	code2Resource := collx.ArrayToMap[entity.TagResource, string](tagResources, func(val entity.TagResource) string {
 | 
			
		||||
		return val.ResourceCode
 | 
			
		||||
	code2Resource := collx.ArrayToMap[entity.TagTree, string](tagResources, func(val entity.TagTree) string {
 | 
			
		||||
		return val.Code
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return collx.MapKeys(code2Resource)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagTreeAppImpl) RelateResource(ctx context.Context, resourceCode string, resourceType int8, tagIds []uint64) error {
 | 
			
		||||
func (p *tagTreeAppImpl) SaveResource(ctx context.Context, req *SaveResourceTagParam) error {
 | 
			
		||||
	resourceCode := req.ResourceCode
 | 
			
		||||
	resourceType := req.ResourceType
 | 
			
		||||
	resourceName := req.ResourceName
 | 
			
		||||
	tagIds := req.TagIds
 | 
			
		||||
 | 
			
		||||
	if resourceCode == "" {
 | 
			
		||||
		return errorx.NewBiz("资源编号不能为空")
 | 
			
		||||
	}
 | 
			
		||||
	// 如果tagIds为空数组,则为解绑该标签资源关联关系
 | 
			
		||||
	if len(tagIds) == 0 {
 | 
			
		||||
		return p.tagResourceApp.DeleteByCond(ctx, &entity.TagResource{
 | 
			
		||||
			ResourceCode: resourceCode,
 | 
			
		||||
			ResourceType: resourceType,
 | 
			
		||||
		})
 | 
			
		||||
	if resourceType == 0 {
 | 
			
		||||
		return errorx.NewBiz("资源类型不能为空")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var oldTagResources []*entity.TagResource
 | 
			
		||||
	p.tagResourceApp.ListByQuery(&entity.TagResourceQuery{ResourceType: resourceType, ResourceCode: resourceCode}, &oldTagResources)
 | 
			
		||||
	// 如果tagIds为空数组,则为删除该资源标签
 | 
			
		||||
	if len(tagIds) == 0 {
 | 
			
		||||
		return p.DeleteByCond(ctx, &entity.TagTree{Code: resourceCode, Type: resourceType})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if resourceName == "" {
 | 
			
		||||
		resourceName = resourceCode
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 该资源对应的旧资源标签信息
 | 
			
		||||
	var oldTagTree []*entity.TagTree
 | 
			
		||||
	p.ListByCond(&entity.TagTree{Type: resourceType, Code: resourceCode}, &oldTagTree)
 | 
			
		||||
 | 
			
		||||
	var addTagIds, delTagIds []uint64
 | 
			
		||||
	if len(oldTagResources) == 0 {
 | 
			
		||||
	if len(oldTagTree) == 0 {
 | 
			
		||||
		addTagIds = tagIds
 | 
			
		||||
	} else {
 | 
			
		||||
		oldTagIds := collx.ArrayMap[*entity.TagResource, uint64](oldTagResources, func(tr *entity.TagResource) uint64 {
 | 
			
		||||
			return tr.TagId
 | 
			
		||||
		oldTagIds := collx.ArrayMap(oldTagTree, func(tag *entity.TagTree) uint64 {
 | 
			
		||||
			return tag.Pid
 | 
			
		||||
		})
 | 
			
		||||
		addTagIds, delTagIds, _ = collx.ArrayCompare[uint64](tagIds, oldTagIds)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(addTagIds) > 0 {
 | 
			
		||||
		addTagResource := make([]*entity.TagResource, 0)
 | 
			
		||||
		addTagResource := make([]*entity.TagTree, 0)
 | 
			
		||||
		for _, tagId := range addTagIds {
 | 
			
		||||
			tag, err := p.GetById(new(entity.TagTree), tagId)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errorx.NewBiz("存在错误标签id")
 | 
			
		||||
			}
 | 
			
		||||
			addTagResource = append(addTagResource, &entity.TagResource{
 | 
			
		||||
				ResourceCode: resourceCode,
 | 
			
		||||
				ResourceType: resourceType,
 | 
			
		||||
				TagId:        tagId,
 | 
			
		||||
				TagPath:      tag.CodePath,
 | 
			
		||||
			addTagResource = append(addTagResource, &entity.TagTree{
 | 
			
		||||
				Pid:      tagId,
 | 
			
		||||
				Code:     resourceCode,
 | 
			
		||||
				Type:     resourceType,
 | 
			
		||||
				Name:     resourceName,
 | 
			
		||||
				CodePath: tag.CodePath + resourceCode + entity.CodePathSeparator,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		if err := p.tagResourceApp.BatchInsert(ctx, addTagResource); err != nil {
 | 
			
		||||
		if err := p.BatchInsert(ctx, addTagResource); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(delTagIds) > 0 {
 | 
			
		||||
		for _, tagId := range delTagIds {
 | 
			
		||||
			cond := &entity.TagResource{ResourceCode: resourceCode, ResourceType: resourceType, TagId: tagId}
 | 
			
		||||
			if err := p.tagResourceApp.DeleteByCond(ctx, cond); err != nil {
 | 
			
		||||
			cond := &entity.TagTree{Code: resourceCode, Type: resourceType, Pid: tagId}
 | 
			
		||||
			if err := p.DeleteByCond(ctx, cond); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -200,19 +216,13 @@ func (p *tagTreeAppImpl) RelateResource(ctx context.Context, resourceCode string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string {
 | 
			
		||||
	var trs []*entity.TagResource
 | 
			
		||||
	p.tagResourceApp.ListByQuery(&entity.TagResourceQuery{ResourceType: resourceType, ResourceCode: resourceCode}, &trs)
 | 
			
		||||
	return collx.ArrayMap(trs, func(tr *entity.TagResource) string {
 | 
			
		||||
		return tr.TagPath
 | 
			
		||||
	var trs []*entity.TagTree
 | 
			
		||||
	p.ListByCond(&entity.TagTree{Type: resourceType, Code: resourceCode}, &trs)
 | 
			
		||||
	return collx.ArrayMap(trs, func(tr *entity.TagTree) string {
 | 
			
		||||
		return tr.CodePath
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagTreeAppImpl) ListTagByPath(tagPaths ...string) []*entity.TagTree {
 | 
			
		||||
	var tags []*entity.TagTree
 | 
			
		||||
	p.GetRepo().SelectByCondition(&entity.TagTreeQuery{CodePathLikes: tagPaths}, &tags)
 | 
			
		||||
	return tags
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagTreeAppImpl) ListTagByAccountId(accountId uint64) []string {
 | 
			
		||||
	return p.tagTreeTeamRepo.SelectTagPathsByAccountId(accountId)
 | 
			
		||||
}
 | 
			
		||||
@@ -245,12 +255,12 @@ func (p *tagTreeAppImpl) FillTagInfo(resources ...entity.ITagResource) {
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// 获取所有资源code关联的标签列表信息
 | 
			
		||||
	var tagResources []*entity.TagResource
 | 
			
		||||
	p.tagResourceApp.ListByQuery(&entity.TagResourceQuery{ResourceCodes: collx.MapKeys(resourceCode2Resouce)}, &tagResources)
 | 
			
		||||
	var tagResources []*entity.TagTree
 | 
			
		||||
	p.ListByQuery(&entity.TagTreeQuery{Codes: collx.MapKeys(resourceCode2Resouce)}, &tagResources)
 | 
			
		||||
 | 
			
		||||
	for _, tr := range tagResources {
 | 
			
		||||
		// 赋值标签信息
 | 
			
		||||
		resourceCode2Resouce[tr.ResourceCode].SetTagInfo(entity.ResourceTag{TagId: tr.TagId, TagPath: tr.TagPath})
 | 
			
		||||
		resourceCode2Resouce[tr.Code].SetTagInfo(entity.ResourceTag{TagId: tr.Pid, TagPath: tr.GetParentPath()})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -264,7 +274,7 @@ func (p *tagTreeAppImpl) Delete(ctx context.Context, id uint64) error {
 | 
			
		||||
		return errorx.NewBiz("您无权删除该标签")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.tagResourceApp.CountByCond(&entity.TagResource{TagId: id}) > 0 {
 | 
			
		||||
	if p.CountByCond(&entity.TagTree{Pid: id}) > 0 {
 | 
			
		||||
		return errorx.NewBiz("请先移除该标签关联的资源")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@ type TagTreeQuery struct {
 | 
			
		||||
	model.Model
 | 
			
		||||
 | 
			
		||||
	Pid           uint64
 | 
			
		||||
	Code          string `json:"code"`     // 标识
 | 
			
		||||
	Type          int8   `json:"type"`
 | 
			
		||||
	Code          string `json:"code"` // 标识
 | 
			
		||||
	Codes         []string
 | 
			
		||||
	CodePath      string `json:"codePath"` // 标识路径
 | 
			
		||||
	CodePaths     []string
 | 
			
		||||
	Name          string `json:"name"` // 名称
 | 
			
		||||
@@ -14,19 +16,6 @@ type TagTreeQuery struct {
 | 
			
		||||
	CodePathLikes []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TagResourceQuery struct {
 | 
			
		||||
	model.Model
 | 
			
		||||
 | 
			
		||||
	TagPath       string   `json:"string"` // 标签路径
 | 
			
		||||
	TagId         uint64   `json:"tagId" form:"tagId"`
 | 
			
		||||
	ResourceType  int8     `json:"resourceType" form:"resourceType"` // 资源编码
 | 
			
		||||
	ResourceCode  string   `json:"resourceCode" form:"resourceCode"` // 资源编码
 | 
			
		||||
	ResourceCodes []string // 资源编码列表
 | 
			
		||||
 | 
			
		||||
	TagPathLike  string // 标签路径模糊查询
 | 
			
		||||
	TagPathLikes []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TeamQuery struct {
 | 
			
		||||
	model.Model
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +0,0 @@
 | 
			
		||||
package entity
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 标签资源关联
 | 
			
		||||
type TagResource struct {
 | 
			
		||||
	model.Model
 | 
			
		||||
 | 
			
		||||
	TagId        uint64 `json:"tagId"`
 | 
			
		||||
	TagPath      string `json:"tagPath"`      // 标签路径
 | 
			
		||||
	ResourceCode string `json:"resourceCode"` // 资源标识
 | 
			
		||||
	ResourceType int8   `json:"resourceType"` // 资源类型
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
 | 
			
		||||
type ITagResource interface {
 | 
			
		||||
	// 获取资源code
 | 
			
		||||
	GetCode() string
 | 
			
		||||
 | 
			
		||||
	// 赋值标签基本信息
 | 
			
		||||
	SetTagInfo(rt ResourceTag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 资源关联的标签信息
 | 
			
		||||
type ResourceTag struct {
 | 
			
		||||
	TagId   uint64 `json:"tagId" gorm:"-"`
 | 
			
		||||
	TagPath string `json:"tagPath" gorm:"-"` // 标签路径
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceTag) SetTagInfo(rt ResourceTag) {
 | 
			
		||||
	r.TagId = rt.TagId
 | 
			
		||||
	r.TagPath = rt.TagPath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 资源标签列表
 | 
			
		||||
type ResourceTags struct {
 | 
			
		||||
	Tags []ResourceTag `json:"tags" gorm:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceTags) SetTagInfo(rt ResourceTag) {
 | 
			
		||||
	if r.Tags == nil {
 | 
			
		||||
		r.Tags = make([]ResourceTag, 0)
 | 
			
		||||
	}
 | 
			
		||||
	r.Tags = append(r.Tags, rt)
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,8 @@ type TagTree struct {
 | 
			
		||||
	model.Model
 | 
			
		||||
 | 
			
		||||
	Pid      uint64 `json:"pid"`
 | 
			
		||||
	Code     string `json:"code"`     // 标识
 | 
			
		||||
	Type     int8   `json:"type"`     // 类型: -1.普通标签; 其他值则为对应的资源类型
 | 
			
		||||
	Code     string `json:"code"`     // 标识编码, 若类型不为-1,则为对应资源编码
 | 
			
		||||
	CodePath string `json:"codePath"` // 标识路径
 | 
			
		||||
	Name     string `json:"name"`     // 名称
 | 
			
		||||
	Remark   string `json:"remark"`   // 备注说明
 | 
			
		||||
@@ -21,7 +22,54 @@ const (
 | 
			
		||||
	CodePathSeparator = "/"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 获取根路径信息
 | 
			
		||||
// GetRootCode 获取根路径信息
 | 
			
		||||
func (pt *TagTree) GetRootCode() string {
 | 
			
		||||
	return strings.Split(pt.CodePath, CodePathSeparator)[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetParentPath 获取父标签路径, 如CodePath = test/test1/test2/  -> test/test1/
 | 
			
		||||
func (pt *TagTree) GetParentPath() string {
 | 
			
		||||
	// 去掉末尾的分隔符
 | 
			
		||||
	input := strings.TrimRight(pt.CodePath, CodePathSeparator)
 | 
			
		||||
 | 
			
		||||
	// 查找倒数第二个连字符位置
 | 
			
		||||
	lastHyphenIndex := strings.LastIndex(input, CodePathSeparator)
 | 
			
		||||
	if lastHyphenIndex == -1 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 截取字符串
 | 
			
		||||
	return input[:lastHyphenIndex+1]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 标签接口资源,如果要实现资源结构体填充标签信息,则资源结构体需要实现该接口
 | 
			
		||||
type ITagResource interface {
 | 
			
		||||
	// 获取资源code
 | 
			
		||||
	GetCode() string
 | 
			
		||||
 | 
			
		||||
	// 赋值标签基本信息
 | 
			
		||||
	SetTagInfo(rt ResourceTag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 资源关联的标签信息
 | 
			
		||||
type ResourceTag struct {
 | 
			
		||||
	TagId   uint64 `json:"tagId" gorm:"-"`
 | 
			
		||||
	TagPath string `json:"tagPath" gorm:"-"` // 标签路径
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceTag) SetTagInfo(rt ResourceTag) {
 | 
			
		||||
	r.TagId = rt.TagId
 | 
			
		||||
	r.TagPath = rt.TagPath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 资源标签列表
 | 
			
		||||
type ResourceTags struct {
 | 
			
		||||
	Tags []ResourceTag `json:"tags" gorm:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ResourceTags) SetTagInfo(rt ResourceTag) {
 | 
			
		||||
	if r.Tags == nil {
 | 
			
		||||
		r.Tags = make([]ResourceTag, 0)
 | 
			
		||||
	}
 | 
			
		||||
	r.Tags = append(r.Tags, rt)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
package repository
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/internal/tag/domain/entity"
 | 
			
		||||
	"mayfly-go/pkg/base"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TagResource interface {
 | 
			
		||||
	base.Repo[*entity.TagResource]
 | 
			
		||||
 | 
			
		||||
	SelectByCondition(condition *entity.TagResourceQuery, toEntity any, orderBy ...string)
 | 
			
		||||
}
 | 
			
		||||
@@ -7,7 +7,6 @@ import (
 | 
			
		||||
func InitIoc() {
 | 
			
		||||
	ioc.Register(newTagTreeRepo(), ioc.WithComponentName("TagTreeRepo"))
 | 
			
		||||
	ioc.Register(newTagTreeTeamRepo(), ioc.WithComponentName("TagTreeTeamRepo"))
 | 
			
		||||
	ioc.Register(newTagResourceRepo(), ioc.WithComponentName("TagResourceRepo"))
 | 
			
		||||
	ioc.Register(newTeamRepo(), ioc.WithComponentName("TeamRepo"))
 | 
			
		||||
	ioc.Register(newTeamMemberRepo(), ioc.WithComponentName("TeamMemberRepo"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
package persistence
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/internal/tag/domain/entity"
 | 
			
		||||
	"mayfly-go/internal/tag/domain/repository"
 | 
			
		||||
	"mayfly-go/pkg/base"
 | 
			
		||||
	"mayfly-go/pkg/gormx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type tagResourceRepoImpl struct {
 | 
			
		||||
	base.RepoImpl[*entity.TagResource]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTagResourceRepo() repository.TagResource {
 | 
			
		||||
	return &tagResourceRepoImpl{base.RepoImpl[*entity.TagResource]{M: new(entity.TagResource)}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagResourceRepoImpl) SelectByCondition(condition *entity.TagResourceQuery, toEntity any, orderBy ...string) {
 | 
			
		||||
	sql := "SELECT tr.resource_type, tr.resource_code, tr.tag_id, tr.tag_path FROM t_tag_resource tr WHERE tr.is_deleted = 0 "
 | 
			
		||||
 | 
			
		||||
	params := make([]any, 0)
 | 
			
		||||
 | 
			
		||||
	if condition.ResourceType != 0 {
 | 
			
		||||
		sql = sql + " AND tr.resource_type = ?"
 | 
			
		||||
		params = append(params, condition.ResourceType)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if condition.ResourceCode != "" {
 | 
			
		||||
		sql = sql + " AND tr.resource_code = ?"
 | 
			
		||||
		params = append(params, condition.ResourceCode)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(condition.ResourceCodes) > 0 {
 | 
			
		||||
		sql = sql + " AND tr.resource_code IN (?)"
 | 
			
		||||
		params = append(params, condition.ResourceCodes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if condition.TagId != 0 {
 | 
			
		||||
		sql = sql + " AND tr.tag_id = ?"
 | 
			
		||||
		params = append(params, condition.TagId)
 | 
			
		||||
	}
 | 
			
		||||
	if condition.TagPath != "" {
 | 
			
		||||
		sql = sql + " AND tr.tag_path = ?"
 | 
			
		||||
		params = append(params, condition.TagPath)
 | 
			
		||||
	}
 | 
			
		||||
	if condition.TagPathLike != "" {
 | 
			
		||||
		sql = sql + " AND tr.tag_path LIKE ?"
 | 
			
		||||
		params = append(params, condition.TagPathLike+"%")
 | 
			
		||||
	}
 | 
			
		||||
	if len(condition.TagPathLikes) > 0 {
 | 
			
		||||
		sql = sql + " AND ("
 | 
			
		||||
		for i, v := range condition.TagPathLikes {
 | 
			
		||||
			if i == 0 {
 | 
			
		||||
				sql = sql + "tr.tag_path LIKE ?"
 | 
			
		||||
			} else {
 | 
			
		||||
				sql = sql + " OR tr.tag_path LIKE ?"
 | 
			
		||||
			}
 | 
			
		||||
			params = append(params, v+"%")
 | 
			
		||||
		}
 | 
			
		||||
		sql = sql + ")"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sql = sql + " ORDER BY tr.tag_path"
 | 
			
		||||
	gormx.GetListBySql2Model(sql, toEntity, params...)
 | 
			
		||||
}
 | 
			
		||||
@@ -16,7 +16,7 @@ func newTagTreeRepo() repository.TagTree {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEntity any, orderBy ...string) {
 | 
			
		||||
	sql := "SELECT DISTINCT(p.id), p.pid, p.code, p.code_path, p.name, p.remark, p.create_time, p.creator, p.update_time, p.modifier FROM t_tag_tree p WHERE p.is_deleted = 0 "
 | 
			
		||||
	sql := "SELECT DISTINCT(p.id), p.pid, p.type, p.code, p.code_path, p.name, p.remark, p.create_time, p.creator, p.update_time, p.modifier FROM t_tag_tree p WHERE p.is_deleted = 0 "
 | 
			
		||||
 | 
			
		||||
	params := make([]any, 0)
 | 
			
		||||
	if condition.Name != "" {
 | 
			
		||||
@@ -27,6 +27,10 @@ func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEn
 | 
			
		||||
		sql = sql + " AND p.code_path = ?"
 | 
			
		||||
		params = append(params, condition.CodePath)
 | 
			
		||||
	}
 | 
			
		||||
	if len(condition.Codes) > 0 {
 | 
			
		||||
		sql = sql + " AND p.code IN (?)"
 | 
			
		||||
		params = append(params, condition.Codes)
 | 
			
		||||
	}
 | 
			
		||||
	if len(condition.CodePaths) > 0 {
 | 
			
		||||
		sql = sql + " AND p.code_path IN (?)"
 | 
			
		||||
		params = append(params, condition.CodePaths)
 | 
			
		||||
@@ -39,6 +43,10 @@ func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEn
 | 
			
		||||
		sql = sql + " AND p.pid = ?"
 | 
			
		||||
		params = append(params, condition.Pid)
 | 
			
		||||
	}
 | 
			
		||||
	if condition.Type != 0 {
 | 
			
		||||
		sql = sql + " AND p.type = ?"
 | 
			
		||||
		params = append(params, condition.Type)
 | 
			
		||||
	}
 | 
			
		||||
	if len(condition.CodePathLikes) > 0 {
 | 
			
		||||
		sql = sql + " AND ("
 | 
			
		||||
		for i, v := range condition.CodePathLikes {
 | 
			
		||||
@@ -51,6 +59,6 @@ func (p *tagTreeRepoImpl) SelectByCondition(condition *entity.TagTreeQuery, toEn
 | 
			
		||||
		}
 | 
			
		||||
		sql = sql + ")"
 | 
			
		||||
	}
 | 
			
		||||
	sql = sql + " ORDER BY p.code_path"
 | 
			
		||||
	sql = sql + " ORDER BY p.type, p.code_path"
 | 
			
		||||
	gormx.GetListBySql2Model(sql, toEntity, params...)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,6 @@ func InitTagTreeRouter(router *gin.RouterGroup) {
 | 
			
		||||
			req.NewGet("/resources/:rtype/tag-paths", m.TagResources),
 | 
			
		||||
 | 
			
		||||
			req.NewGet("/resources/count", m.CountTagResource),
 | 
			
		||||
 | 
			
		||||
			req.NewGet("/resources", m.QueryTagResources),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		req.BatchSetGroup(tagTree, reqs[:])
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql/driver"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -125,3 +127,14 @@ func GetIdByGenType(genType IdGenType) uint64 {
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Map[K comparable, V any] map[K]V
 | 
			
		||||
 | 
			
		||||
func (m *Map[K, V]) Scan(value any) error {
 | 
			
		||||
	return json.Unmarshal(value.([]byte), m)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m Map[K, V]) Value() (driver.Value, error) {
 | 
			
		||||
	return json.Marshal(m)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -334,6 +334,7 @@ CREATE TABLE `t_machine` (
 | 
			
		||||
  `name` varchar(32) DEFAULT NULL,
 | 
			
		||||
  `ip` varchar(50) NOT NULL,
 | 
			
		||||
  `port` int(12) NOT NULL,
 | 
			
		||||
  `protocol` tinyint(2) NULL COMMENT '协议  1、SSH  2、RDP',
 | 
			
		||||
  `username` varchar(12) NOT NULL,
 | 
			
		||||
  `auth_method` tinyint(2) DEFAULT NULL COMMENT '1.密码登录2.publickey登录',
 | 
			
		||||
  `password` varchar(100) DEFAULT NULL,
 | 
			
		||||
@@ -634,7 +635,7 @@ INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, cre
 | 
			
		||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('oauth2登录配置', 'Oauth2Login', '[{"name":"是否启用","model":"enable","placeholder":"是否启用oauth2登录","options":"true,false"},{"name":"名称","model":"name","placeholder":"oauth2名称"},{"name":"Client ID","model":"clientId","placeholder":"Client ID"},{"name":"Client Secret","model":"clientSecret","placeholder":"Client Secret"},{"name":"Authorization URL","model":"authorizationURL","placeholder":"Authorization URL"},{"name":"AccessToken URL","model":"accessTokenURL","placeholder":"AccessToken URL"},{"name":"Redirect URL","model":"redirectURL","placeholder":"本系统地址"},{"name":"Scopes","model":"scopes","placeholder":"Scopes"},{"name":"Resource URL","model":"resourceURL","placeholder":"获取用户信息资源地址"},{"name":"UserIdentifier","model":"userIdentifier","placeholder":"用户唯一标识字段;格式为type:fieldPath(string:username)"},{"name":"是否自动注册","model":"autoRegister","placeholder":"","options":"true,false"}]', '', 'oauth2登录相关配置信息', 'admin,', '2023-07-22 13:58:51', 1, 'admin', '2023-07-22 19:34:37', 1, 'admin', 0, NULL);
 | 
			
		||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('ldap登录配置', 'LdapLogin', '[{"name":"是否启用","model":"enable","placeholder":"是否启用","options":"true,false"},{"name":"host","model":"host","placeholder":"host"},{"name":"port","model":"port","placeholder":"port"},{"name":"bindDN","model":"bindDN","placeholder":"LDAP 服务的管理员账号,如: \\"cn=admin,dc=example,dc=com\\""},{"name":"bindPwd","model":"bindPwd","placeholder":"LDAP 服务的管理员密码"},{"name":"baseDN","model":"baseDN","placeholder":"用户所在的 base DN, 如: \\"ou=users,dc=example,dc=com\\""},{"name":"userFilter","model":"userFilter","placeholder":"过滤用户的方式, 如: \\"(uid=%s)、(&(objectClass=organizationalPerson)(uid=%s))\\""},{"name":"uidMap","model":"uidMap","placeholder":"用户id和 LDAP 字段名之间的映射关系,如: cn"},{"name":"udnMap","model":"udnMap","placeholder":"用户姓名(dispalyName)和 LDAP 字段名之间的映射关系,如: displayName"},{"name":"emailMap","model":"emailMap","placeholder":"用户email和 LDAP 字段名之间的映射关系"},{"name":"skipTLSVerify","model":"skipTLSVerify","placeholder":"客户端是否跳过 TLS 证书验证","options":"true,false"},{"name":"安全协议","model":"securityProtocol","placeholder":"安全协议(为Null不使用安全协议),如: StartTLS, LDAPS","options":"Null,StartTLS,LDAPS"}]', '', 'ldap登录相关配置', 'admin,', '2023-08-25 21:47:20', 1, 'admin', '2023-08-25 22:56:07', 1, 'admin', 0, NULL);
 | 
			
		||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('系统全局样式设置', 'SysStyleConfig', '[{"model":"logoIcon","name":"logo图标","placeholder":"系统logo图标(base64编码, 建议svg格式,不超过10k)","required":false},{"model":"title","name":"菜单栏标题","placeholder":"系统菜单栏标题展示","required":false},{"model":"viceTitle","name":"登录页标题","placeholder":"登录页标题展示","required":false},{"model":"useWatermark","name":"是否启用水印","placeholder":"是否启用系统水印","options":"true,false","required":false},{"model":"watermarkContent","name":"水印补充信息","placeholder":"额外水印信息","required":false}]', '{"title":"mayfly-go","viceTitle":"mayfly-go","logoIcon":"","useWatermark":"true","watermarkContent":""}', '系统icon、标题、水印信息等配置', 'all', '2024-01-04 15:17:18', 1, 'admin', '2024-01-05 09:40:44', 1, 'admin', 0, NULL);
 | 
			
		||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB\\2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"100MB"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-01-15 16:30:22', 1, 'admin', 0, NULL);
 | 
			
		||||
INSERT INTO t_sys_config ( name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"},{"name":"guacd服务记录存储位置","model":"guacdRecPath","placeholder":"guacd服务记录存储位置,用于记录rdp操作记录"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file","guacdRecPath":"./guacd/rdp-rec"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-04-06 12:25:03', 1, 'admin', 0, NULL);
 | 
			
		||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('数据库备份恢复', 'DbBackupRestore', '[{"model":"backupPath","name":"备份路径","placeholder":"备份文件存储路径"}]', '{"backupPath":"./db/backup"}', '', 'admin,', '2023-12-29 09:55:26', 1, 'admin', '2023-12-29 15:45:24', 1, 'admin', 0, NULL);
 | 
			
		||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('Mysql可执行文件', 'MysqlBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mysql/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
 | 
			
		||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('MariaDB可执行文件', 'MariadbBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mariadb/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
 | 
			
		||||
@@ -936,26 +937,6 @@ CREATE TABLE `t_tag_tree_team` (
 | 
			
		||||
  KEY `idx_tag_id` (`tag_id`) USING BTREE
 | 
			
		||||
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签树团队关联信息';
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `t_tag_resource`;
 | 
			
		||||
CREATE TABLE `t_tag_resource` (
 | 
			
		||||
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
 | 
			
		||||
  `tag_id` bigint NOT NULL,
 | 
			
		||||
  `tag_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '标签路径',
 | 
			
		||||
  `resource_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '资源编码',
 | 
			
		||||
  `resource_type` tinyint NOT NULL COMMENT '资源类型',
 | 
			
		||||
  `create_time` datetime NOT NULL,
 | 
			
		||||
  `creator_id` bigint NOT NULL,
 | 
			
		||||
  `creator` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
 | 
			
		||||
  `update_time` datetime NOT NULL,
 | 
			
		||||
  `modifier_id` bigint NOT NULL,
 | 
			
		||||
  `modifier` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
 | 
			
		||||
  `is_deleted` tinyint DEFAULT '0',
 | 
			
		||||
  `delete_time` datetime DEFAULT NULL,
 | 
			
		||||
  PRIMARY KEY (`id`),
 | 
			
		||||
  KEY `idx_tag_path` (`tag_path`(100)) USING BTREE,
 | 
			
		||||
  KEY `idx_resource_code` (`resource_code`) USING BTREE
 | 
			
		||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='标签资源关联表';
 | 
			
		||||
 | 
			
		||||
-- ----------------------------
 | 
			
		||||
-- Records of t_tag_tree_team
 | 
			
		||||
-- ----------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,43 @@ ALTER TABLE t_sys_log ADD extra varchar(5000) NULL;
 | 
			
		||||
ALTER TABLE t_sys_log MODIFY COLUMN resp text NULL;
 | 
			
		||||
 | 
			
		||||
-- rdp相关
 | 
			
		||||
ALTER TABLE `t_machine` ADD COLUMN `protocol` tinyint(2) NULL COMMENT '机器类型  1、SSH  2、RDP' AFTER `name`;
 | 
			
		||||
ALTER TABLE `t_machine` ADD COLUMN `protocol` tinyint(2) NULL COMMENT '协议  1、SSH  2、RDP' AFTER `name`;
 | 
			
		||||
update `t_machine` set `protocol` = 1 where `protocol`  is NULL;
 | 
			
		||||
delete from `t_sys_config` where `key` = 'MachineConfig';
 | 
			
		||||
INSERT INTO `t_sys_config` (`id`, `name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES (12, '机器相关配置', 'MachineConfig', '[{\"name\":\"终端回放存储路径\",\"model\":\"terminalRecPath\",\"placeholder\":\"终端回放存储路径\"},{\"name\":\"uploadMaxFileSize\",\"model\":\"uploadMaxFileSize\",\"placeholder\":\"允许上传的最大文件大小(1MB、2GB等)\"},{\"model\":\"termOpSaveDays\",\"name\":\"终端记录保存时间\",\"placeholder\":\"终端记录保存时间(单位天)\"},{\"model\":\"guacdHost\",\"name\":\"guacd服务ip\",\"placeholder\":\"guacd服务ip,默认 127.0.0.1\"},{\"name\":\"guacd服务端口\",\"model\":\"guacdPort\",\"placeholder\":\"guacd服务端口,默认 4822\"},{\"model\":\"guacdFilePath\",\"name\":\"guacd服务文件存储位置\",\"placeholder\":\"guacd服务文件存储位置,用于挂载RDP文件夹\"},{\"name\":\"guacd服务记录存储位置\",\"model\":\"guacdRecPath\",\"placeholder\":\"guacd服务记录存储位置,用于记录rdp操作记录\"}]', '{\"terminalRecPath\":\"./rec\",\"uploadMaxFileSize\":\"1000MB\",\"termOpSaveDays\":\"30\",\"guacdHost\":\"127.0.0.1\",\"guacdPort\":\"4822\",\"guacdFilePath\":\"/Users/leozy/Desktop/developer/service/guacd/rdp-file\",\"guacdRecPath\":\"/Users/leozy/Desktop/developer/service/guacd/rdp-rec\"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-04-04 13:11:52', 12, 'liuzongyang', 0, NULL);
 | 
			
		||||
INSERT INTO t_sys_config ( name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"},{"name":"guacd服务记录存储位置","model":"guacdRecPath","placeholder":"guacd服务记录存储位置,用于记录rdp操作记录"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file","guacdRecPath":"./guacd/rdp-rec"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-04-06 12:25:03', 1, 'admin', 0, NULL);
 | 
			
		||||
 | 
			
		||||
BEGIN;
 | 
			
		||||
INSERT
 | 
			
		||||
	INTO
 | 
			
		||||
	t_tag_tree (pid,
 | 
			
		||||
	code,
 | 
			
		||||
	code_path,
 | 
			
		||||
	type,
 | 
			
		||||
    name,
 | 
			
		||||
	create_time,
 | 
			
		||||
	creator_id,
 | 
			
		||||
	creator,
 | 
			
		||||
	update_time,
 | 
			
		||||
	modifier_id,
 | 
			
		||||
	modifier,
 | 
			
		||||
	is_deleted)
 | 
			
		||||
select
 | 
			
		||||
	tag_id,
 | 
			
		||||
	resource_code,
 | 
			
		||||
	CONCAT(tag_path , resource_code, '/'),
 | 
			
		||||
	resource_type,
 | 
			
		||||
    resource_code,
 | 
			
		||||
	DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),
 | 
			
		||||
	1,
 | 
			
		||||
	'admin',
 | 
			
		||||
	DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),
 | 
			
		||||
	1,
 | 
			
		||||
	'admin',
 | 
			
		||||
    0
 | 
			
		||||
from
 | 
			
		||||
	t_tag_resource
 | 
			
		||||
WHERE
 | 
			
		||||
	is_deleted = 0;
 | 
			
		||||
 | 
			
		||||
DROP TABLE t_tag_tree;
 | 
			
		||||
COMMIT;
 | 
			
		||||
		Reference in New Issue
	
	Block a user