mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 00:10:25 +08:00 
			
		
		
		
	feat: redis支持zset、redis数据操作界面优化
This commit is contained in:
		@@ -23,13 +23,13 @@
 | 
			
		||||
    "monaco-sql-languages": "^0.11.0",
 | 
			
		||||
    "monaco-themes": "^0.4.2",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.0.33",
 | 
			
		||||
    "screenfull": "^6.0.2",
 | 
			
		||||
    "sortablejs": "^1.13.0",
 | 
			
		||||
    "sql-formatter": "^12.1.2",
 | 
			
		||||
    "vue": "^3.2.47",
 | 
			
		||||
    "vue-clipboard3": "^1.0.1",
 | 
			
		||||
    "vue-router": "^4.1.6",
 | 
			
		||||
    "pinia": "^2.0.33",
 | 
			
		||||
    "xterm": "^5.1.0",
 | 
			
		||||
    "xterm-addon-fit": "^0.7.0"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ const config = {
 | 
			
		||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
			
		||||
 | 
			
		||||
    // 系统版本
 | 
			
		||||
    version: 'v1.4.1'
 | 
			
		||||
    version: 'v1.4.2'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default config
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="monaco-editor" style="border: 1px solid #ccc;">
 | 
			
		||||
        <div ref="monacoTextarea" :style="{ height: height }"></div>
 | 
			
		||||
        <div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
 | 
			
		||||
        <el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
 | 
			
		||||
            <el-option v-for="mode in languages" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
 | 
			
		||||
        </el-select>
 | 
			
		||||
 
 | 
			
		||||
@@ -244,6 +244,7 @@ export async function initRouter() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let SysWs: any;
 | 
			
		||||
let loadRouter = false;
 | 
			
		||||
 | 
			
		||||
// 路由加载前
 | 
			
		||||
router.beforeEach(async (to, from, next) => {
 | 
			
		||||
@@ -283,8 +284,10 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
    if (!SysWs && to.path != '/machine/terminal') {
 | 
			
		||||
        SysWs = sockets.sysMsgSocket();
 | 
			
		||||
    }
 | 
			
		||||
    if (useRoutesList().routesList.length == 0) {
 | 
			
		||||
    // 不存在路由(避免刷新页面找不到路由)并且未加载过(避免token过期,导致获取权限接口报权限不足,无限获取),则重新初始化路由
 | 
			
		||||
    if (useRoutesList().routesList.length == 0 && !loadRouter) {
 | 
			
		||||
        await initRouter();
 | 
			
		||||
        loadRouter = true;
 | 
			
		||||
        next({ path: to.path, query: to.query });
 | 
			
		||||
    } else {
 | 
			
		||||
        next();
 | 
			
		||||
 
 | 
			
		||||
@@ -328,6 +328,10 @@ body,
 | 
			
		||||
    float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fr {
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-form {
 | 
			
		||||
    .el-form-item {
 | 
			
		||||
        margin-bottom: 3px;
 | 
			
		||||
 
 | 
			
		||||
@@ -46,26 +46,16 @@
 | 
			
		||||
                            <el-form-item>
 | 
			
		||||
                                <el-button @click="searchKey()" type="success" icon="search" plain></el-button>
 | 
			
		||||
                                <el-button @click="scan()" icon="bottom" plain>scan</el-button>
 | 
			
		||||
                                <el-popover placement="right" :width="200" trigger="click">
 | 
			
		||||
                                    <template #reference>
 | 
			
		||||
                                        <el-button type="primary" icon="plus" plain v-auth="'redis:data:save'"></el-button>
 | 
			
		||||
                                    </template>
 | 
			
		||||
                                    <el-tag @click="onAddData('string')" :color="getTypeColor('string')"
 | 
			
		||||
                                        style="cursor: pointer">string</el-tag>
 | 
			
		||||
                                    <el-tag @click="onAddData('hash')" :color="getTypeColor('hash')" class="ml5"
 | 
			
		||||
                                        style="cursor: pointer">hash</el-tag>
 | 
			
		||||
                                    <el-tag @click="onAddData('set')" :color="getTypeColor('set')" class="ml5"
 | 
			
		||||
                                        style="cursor: pointer">set</el-tag>
 | 
			
		||||
                                    <!-- <el-tag @click="onAddData('list')" :color="getTypeColor('list')" class="ml5" style="cursor: pointer">list</el-tag> -->
 | 
			
		||||
                                </el-popover>
 | 
			
		||||
                                <el-button @click="showNewKeyDialog" type="primary" icon="plus" plain
 | 
			
		||||
                                    v-auth="'redis:data:save'"></el-button>
 | 
			
		||||
                            </el-form-item>
 | 
			
		||||
                            <div style="float: right">
 | 
			
		||||
                                <span>keys: {{ state.dbsize }}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </el-form>
 | 
			
		||||
                    </el-col>
 | 
			
		||||
                    <el-table v-loading="state.loading" :data="state.keys" :height="tableHeight" stripe :highlight-current-row="true"
 | 
			
		||||
                        style="cursor: pointer">
 | 
			
		||||
                    <el-table v-loading="state.loading" :data="state.keys" :height="tableHeight" stripe
 | 
			
		||||
                        :highlight-current-row="true" style="cursor: pointer">
 | 
			
		||||
                        <el-table-column show-overflow-tooltip prop="key" label="key"></el-table-column>
 | 
			
		||||
                        <el-table-column prop="type" label="type" width="80">
 | 
			
		||||
                            <template #default="scope">
 | 
			
		||||
@@ -79,7 +69,8 @@
 | 
			
		||||
                        </el-table-column>
 | 
			
		||||
                        <el-table-column label="操作">
 | 
			
		||||
                            <template #default="scope">
 | 
			
		||||
                                <el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看
 | 
			
		||||
                                <el-button @click="showKeyDetail(scope.row)" type="success" icon="search" plain
 | 
			
		||||
                                    size="small">查看
 | 
			
		||||
                                </el-button>
 | 
			
		||||
                                <el-button v-auth="'redis:data:del'" @click="del(scope.row.key)" type="danger" icon="delete"
 | 
			
		||||
                                    plain size="small">删除
 | 
			
		||||
@@ -93,21 +84,37 @@
 | 
			
		||||
 | 
			
		||||
        <div style="text-align: center; margin-top: 10px"></div>
 | 
			
		||||
 | 
			
		||||
        <hash-value v-model:visible="hashValueDialog.visible" :operationType="dataEdit.operationType"
 | 
			
		||||
            :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" :db="scanParam.db"
 | 
			
		||||
            @cancel="onCancelDataEdit" @valChange="searchKey" />
 | 
			
		||||
        <el-dialog title="Key详情" v-model="keyDetailDialog.visible" width="800px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <key-detail :redisId="scanParam.id" :db="scanParam.db" :key-info="keyDetailDialog.keyInfo"
 | 
			
		||||
                @change-key="searchKey()" />
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
        <string-value v-model:visible="stringValueDialog.visible" :operationType="dataEdit.operationType"
 | 
			
		||||
            :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" :db="scanParam.db"
 | 
			
		||||
            @cancel="onCancelDataEdit" @valChange="searchKey" />
 | 
			
		||||
        <el-dialog title="新增Key" v-model="newKeyDialog.visible" width="500px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <el-form ref="keyForm" label-width="50px">
 | 
			
		||||
                <el-form-item prop="key" label="键名">
 | 
			
		||||
                    <el-input v-model.trim="keyDetailDialog.keyInfo.key" placeholder="请输入键名"></el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item prop="type" label="类型">
 | 
			
		||||
                    <el-select v-model="keyDetailDialog.keyInfo.type" default-first-option style="width: 100%"
 | 
			
		||||
                        placeholder="请选择类型">
 | 
			
		||||
                        <el-option key="string" label="string" value="string"></el-option>
 | 
			
		||||
                        <el-option key="hash" label="hash" value="hash"></el-option>
 | 
			
		||||
                        <el-option key="set" label="set" value="set"></el-option>
 | 
			
		||||
                        <el-option key="zset" label="zset" value="zset"></el-option>
 | 
			
		||||
                        <el-option key="list" label="list" value="list"></el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
        <set-value v-model:visible="setValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo"
 | 
			
		||||
            :redisId="scanParam.id" :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey"
 | 
			
		||||
            @cancel="onCancelDataEdit" />
 | 
			
		||||
 | 
			
		||||
        <list-value v-model:visible="listValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo"
 | 
			
		||||
            :redisId="scanParam.id" :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey"
 | 
			
		||||
            @cancel="onCancelDataEdit" />
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="cancelNewKey()">取 消</el-button>
 | 
			
		||||
                    <el-button v-auth="'machine:script:save'" type="primary" @click="newKey">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -119,10 +126,7 @@ import { isTrue, notBlank, notNull } from '@/common/assert';
 | 
			
		||||
import { TagTreeNode } from '../component/tag';
 | 
			
		||||
import TagTree from '../component/TagTree.vue';
 | 
			
		||||
 | 
			
		||||
const HashValue = defineAsyncComponent(() => import('./HashValue.vue'));
 | 
			
		||||
const StringValue = defineAsyncComponent(() => import('./StringValue.vue'));
 | 
			
		||||
const SetValue = defineAsyncComponent(() => import('./SetValue.vue'));
 | 
			
		||||
const ListValue = defineAsyncComponent(() => import('./ListValue.vue'));
 | 
			
		||||
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 树节点类型
 | 
			
		||||
@@ -144,31 +148,20 @@ const state = reactive({
 | 
			
		||||
    scanParam: {
 | 
			
		||||
        id: null as any,
 | 
			
		||||
        mode: '',
 | 
			
		||||
        db: '',
 | 
			
		||||
        db: 0,
 | 
			
		||||
        match: null,
 | 
			
		||||
        count: 10,
 | 
			
		||||
        cursor: {},
 | 
			
		||||
    },
 | 
			
		||||
    dataEdit: {
 | 
			
		||||
    keyDetailDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        title: '新增数据',
 | 
			
		||||
        operationType: 1,
 | 
			
		||||
        keyInfo: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            timed: -1,
 | 
			
		||||
            key: '',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    hashValueDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
    },
 | 
			
		||||
    stringValueDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
    },
 | 
			
		||||
    setValueDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
    },
 | 
			
		||||
    listValueDialog: {
 | 
			
		||||
    newKeyDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
    },
 | 
			
		||||
    keys: [],
 | 
			
		||||
@@ -178,11 +171,8 @@ const state = reactive({
 | 
			
		||||
const {
 | 
			
		||||
    tableHeight,
 | 
			
		||||
    scanParam,
 | 
			
		||||
    dataEdit,
 | 
			
		||||
    hashValueDialog,
 | 
			
		||||
    stringValueDialog,
 | 
			
		||||
    setValueDialog,
 | 
			
		||||
    listValueDialog,
 | 
			
		||||
    keyDetailDialog,
 | 
			
		||||
    newKeyDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -339,50 +329,56 @@ const resetScanParam = () => {
 | 
			
		||||
    state.scanParam.cursor = {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getValue = async (row: any) => {
 | 
			
		||||
const showKeyDetail = async (row: any) => {
 | 
			
		||||
    const type = row.type;
 | 
			
		||||
 | 
			
		||||
    state.dataEdit.keyInfo.type = type;
 | 
			
		||||
    state.dataEdit.keyInfo.timed = row.ttl;
 | 
			
		||||
    state.dataEdit.keyInfo.key = row.key;
 | 
			
		||||
    state.dataEdit.operationType = 2;
 | 
			
		||||
    state.dataEdit.title = '查看数据';
 | 
			
		||||
 | 
			
		||||
    if (type == 'hash') {
 | 
			
		||||
        state.hashValueDialog.visible = true;
 | 
			
		||||
    } else if (type == 'string') {
 | 
			
		||||
        state.stringValueDialog.visible = true;
 | 
			
		||||
    } else if (type == 'set') {
 | 
			
		||||
        state.setValueDialog.visible = true;
 | 
			
		||||
    } else if (type == 'list') {
 | 
			
		||||
        state.listValueDialog.visible = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        ElMessage.warning('暂不支持该类型');
 | 
			
		||||
    }
 | 
			
		||||
    state.keyDetailDialog.keyInfo.type = type;
 | 
			
		||||
    state.keyDetailDialog.keyInfo.timed = row.ttl;
 | 
			
		||||
    state.keyDetailDialog.keyInfo.key = row.key;
 | 
			
		||||
    state.keyDetailDialog.visible = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onAddData = (type: string) => {
 | 
			
		||||
const closeKeyDetail = () => {
 | 
			
		||||
 | 
			
		||||
    // state.keyDetailDialog.visible = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showNewKeyDialog = () => {
 | 
			
		||||
    notNull(state.scanParam.id, '请先选择redis');
 | 
			
		||||
    state.dataEdit.operationType = 1;
 | 
			
		||||
    state.dataEdit.title = '新增数据';
 | 
			
		||||
    state.dataEdit.keyInfo.type = type;
 | 
			
		||||
    state.dataEdit.keyInfo.timed = -1;
 | 
			
		||||
    if (type == 'hash') {
 | 
			
		||||
        state.hashValueDialog.visible = true;
 | 
			
		||||
    } else if (type == 'string') {
 | 
			
		||||
        state.stringValueDialog.visible = true;
 | 
			
		||||
    } else if (type == 'set') {
 | 
			
		||||
        state.setValueDialog.visible = true;
 | 
			
		||||
    } else if (type == 'list') {
 | 
			
		||||
        state.listValueDialog.visible = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        ElMessage.warning('暂不支持该类型');
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
    notNull(state.scanParam.db, "请选择要操作的库")
 | 
			
		||||
    resetKeyDetailInfo();
 | 
			
		||||
    state.newKeyDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onCancelDataEdit = () => {
 | 
			
		||||
    state.dataEdit.keyInfo = {} as any;
 | 
			
		||||
};
 | 
			
		||||
const cancelNewKey = () => {
 | 
			
		||||
    resetKeyDetailInfo();
 | 
			
		||||
    state.newKeyDialog.visible = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const newKey = async () => {
 | 
			
		||||
    const keyInfo = state.keyDetailDialog.keyInfo
 | 
			
		||||
    const keyType = keyInfo.type
 | 
			
		||||
    const key = keyInfo.key;
 | 
			
		||||
    notBlank(key, "键名不能为空");
 | 
			
		||||
 | 
			
		||||
    if (keyType == 'string') {
 | 
			
		||||
        await redisApi.setString.request({
 | 
			
		||||
            id: state.scanParam.id,
 | 
			
		||||
            db: state.scanParam.db,
 | 
			
		||||
            key: key,
 | 
			
		||||
            value: '',
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    state.newKeyDialog.visible = false;
 | 
			
		||||
    state.keyDetailDialog.visible = true;
 | 
			
		||||
    searchKey();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const resetKeyDetailInfo = () => {
 | 
			
		||||
    state.keyDetailDialog.keyInfo.key = '';
 | 
			
		||||
    state.keyDetailDialog.keyInfo.type = 'string';
 | 
			
		||||
    state.keyDetailDialog.keyInfo.timed = -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const del = (key: string) => {
 | 
			
		||||
    ElMessageBox.confirm(`确定删除[ ${key} ] 该key?`, '提示', {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,108 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div style="width: 100%;">
 | 
			
		||||
        <el-input @input="onInput" type="textarea" v-model="modelValue" :autosize="autosize" :rows="rows" />
 | 
			
		||||
        <div style="padding: 3px; float: right" class="mr5 format-btns">
 | 
			
		||||
            <div>
 | 
			
		||||
                <el-button @click="showFormatDialog()" :underline="false" type="success" icon="MagicStick" size="small">
 | 
			
		||||
                </el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-dialog @opened="opened" width="60%" :title="title" v-model="formatDialog.visible"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <monaco-editor ref="monacoEditorRef" :canChangeMode="true" v-model="formatDialog.value" language="json" />
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <el-button @click="formatDialog.visible = false">取 消</el-button>
 | 
			
		||||
                    <el-button @click="onConfirmValue" type="primary">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, watch, toRefs, onMounted } from 'vue';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    modelValue: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    rows: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
    },
 | 
			
		||||
    autosize: {
 | 
			
		||||
        type: Object
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:modelValue'])
 | 
			
		||||
 | 
			
		||||
const monacoEditorRef: any = ref(null)
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    rows: 2,
 | 
			
		||||
    autosize: {},
 | 
			
		||||
    modelValue: '',
 | 
			
		||||
    formatDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        value: '',
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    rows,
 | 
			
		||||
    autosize,
 | 
			
		||||
    modelValue,
 | 
			
		||||
    formatDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.modelValue,
 | 
			
		||||
    (val: any) => {
 | 
			
		||||
        state.modelValue = val;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.modelValue = props.modelValue as any;
 | 
			
		||||
    state.autosize = props.autosize as any;
 | 
			
		||||
    state.rows = props.rows as any;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const showFormatDialog = () => {
 | 
			
		||||
    state.formatDialog.visible = true;
 | 
			
		||||
    state.formatDialog.value = state.modelValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const opened = () => {
 | 
			
		||||
    monacoEditorRef.value.format();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onConfirmValue = () => {
 | 
			
		||||
    // 尝试压缩json
 | 
			
		||||
    try {
 | 
			
		||||
        state.modelValue = JSON.stringify(JSON.parse(state.formatDialog.value));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        state.modelValue = state.formatDialog.value;
 | 
			
		||||
    }
 | 
			
		||||
    emit('update:modelValue', state.modelValue);
 | 
			
		||||
    state.formatDialog.visible = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onInput = (value: any) => {
 | 
			
		||||
    emit('update:modelValue', value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.format-btns {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
    right: 5px;
 | 
			
		||||
    top: 4px;
 | 
			
		||||
    max-width: 120px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										104
									
								
								mayfly_go_web/src/views/ops/redis/FormatViewer.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								mayfly_go_web/src/views/ops/redis/FormatViewer.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="format-viewer-container">
 | 
			
		||||
        <div class="mb5 fr">
 | 
			
		||||
            <el-select v-model="selectedView" class='format-selector' size='mini' placeholder='Text'>
 | 
			
		||||
                <template #prefix>
 | 
			
		||||
                    <SvgIcon name="view" />
 | 
			
		||||
                </template>
 | 
			
		||||
                <el-option v-for="item of Object.keys(viewers)" :key="item" :label="item" :value="item">
 | 
			
		||||
                </el-option>
 | 
			
		||||
            </el-select>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <component ref='viewerRef' :is='components[viewerComponent]' :content='state.content' :name="selectedView">
 | 
			
		||||
        </component>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, computed, shallowReactive, watch, toRefs, onMounted } from 'vue';
 | 
			
		||||
import ViewerText from './ViewerText.vue';
 | 
			
		||||
import ViewerJson from './ViewerJson.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    content: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const components = shallowReactive({
 | 
			
		||||
    ViewerText, ViewerJson
 | 
			
		||||
})
 | 
			
		||||
const viewerRef: any = ref(null)
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    content: '',
 | 
			
		||||
    selectedView: 'Text',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const viewers = {
 | 
			
		||||
    "Text": {
 | 
			
		||||
        value: 'ViewerText',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    "Json": {
 | 
			
		||||
        value: 'ViewerJson',
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    selectedView,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const viewerComponent = computed(() => {
 | 
			
		||||
    return viewers[state.selectedView].value;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.content,
 | 
			
		||||
    (val: any) => {
 | 
			
		||||
        state.content = val;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.content = props.content as any;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const getContent = () => {
 | 
			
		||||
    return viewerRef.value.getContent();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ getContent })
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.format-selector {
 | 
			
		||||
    width: 130px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.format-selector .el-input__inner {
 | 
			
		||||
    height: 22px !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*outline same with text viewer's .el-textarea__inner*/
 | 
			
		||||
.format-viewer-container .text-formated-container {
 | 
			
		||||
    border: 1px solid #dcdfe6;
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    clear: both
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.format-viewer-container .formater-binary-tag {
 | 
			
		||||
    font-size: 80%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 默认文本框样式
 | 
			
		||||
.format-viewer-container .el-textarea textarea {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    height: calc(100vh - 536px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.format-viewer-container .monaco-editor-content {
 | 
			
		||||
    height: calc(100vh - 550px) !important;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,266 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-dialog class="el-table-z-index-inherit" :title="title" v-model="dialogVisible" :before-close="cancel" width="800px" :destroy-on-close="true">
 | 
			
		||||
        <el-form label-width="85px">
 | 
			
		||||
            <el-form-item prop="key" label="key:">
 | 
			
		||||
                <el-input :disabled="operationType == 2" v-model="key.key"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="timed" label="过期时间:">
 | 
			
		||||
                <el-input v-model.number="key.timed" type="number"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="dataType" label="数据类型:">
 | 
			
		||||
                <el-input v-model="key.type" disabled></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-row class="mt10">
 | 
			
		||||
                <el-form label-position="right" :inline="true">
 | 
			
		||||
                    <el-form-item label="field" label-width="40px" v-if="operationType == 2">
 | 
			
		||||
                        <el-input placeholder="支持*模糊field" style="width: 140px" v-model="scanParam.match" clearable
 | 
			
		||||
                            size="small"></el-input>
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
                    <el-form-item label="count" v-if="operationType == 2">
 | 
			
		||||
                        <el-input placeholder="count" style="width: 62px" v-model.number="scanParam.count" size="small">
 | 
			
		||||
                        </el-input>
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
                    <el-form-item>
 | 
			
		||||
                        <el-button v-if="operationType == 2" @click="reHscan()" type="success" icon="search" plain
 | 
			
		||||
                            size="small"></el-button>
 | 
			
		||||
                        <el-button v-if="operationType == 2" @click="hscan()" icon="bottom" plain size="small">scan
 | 
			
		||||
                        </el-button>
 | 
			
		||||
                        <el-button @click="onAddHashValue" icon="plus" size="small" plain>添加</el-button>
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
                    <div v-if="operationType == 2" class="mt10" style="float: right">
 | 
			
		||||
                        <span>fieldSize: {{ keySize }}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </el-form>
 | 
			
		||||
            </el-row>
 | 
			
		||||
            <el-table :data="hashValues" stripe style="width: 100%;">
 | 
			
		||||
                <el-table-column prop="field" label="field" width>
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-input v-model="scope.row.field" clearable size="small"></el-input>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column prop="value" label="value" min-width="200">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <format-input :title="`type:【${key.type}】key:【${key.key}】field:【${scope.row.field}】`" v-model="scope.row.value"
 | 
			
		||||
                            :autosize="{ minRows: 2, maxRows: 10 }" size="small"></format-input>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column label="操作" width="120">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-button v-if="operationType == 2" type="success" @click="hset(scope.row)" icon="check"
 | 
			
		||||
                            size="small" plain></el-button>
 | 
			
		||||
                        <el-button v-auth="'redis:data:del'" type="danger" @click="hdel(scope.row.field, scope.$index)" icon="delete" size="small"
 | 
			
		||||
                            plain></el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <template #footer v-if="operationType == 1">
 | 
			
		||||
            <div class="dialog-footer">
 | 
			
		||||
                <el-button @click="cancel()">取 消</el-button>
 | 
			
		||||
                <el-button @click="saveValue" type="primary" v-auth="'redis:data:save'">确 定</el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { reactive, watch, toRefs } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { isTrue, notEmpty } from '@/common/assert';
 | 
			
		||||
import FormatInput from './FormatInput.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    // 操作类型,1:新增,2:修改
 | 
			
		||||
    operationType: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [String],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
    hashValue: {
 | 
			
		||||
        type: [Array, Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    operationType: 1,
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
    db: '0',
 | 
			
		||||
    key: {
 | 
			
		||||
        key: '',
 | 
			
		||||
        type: 'hash',
 | 
			
		||||
        timed: -1,
 | 
			
		||||
    },
 | 
			
		||||
    scanParam: {
 | 
			
		||||
        key: '',
 | 
			
		||||
        id: 0,
 | 
			
		||||
        db: '0',
 | 
			
		||||
        cursor: 0,
 | 
			
		||||
        match: '',
 | 
			
		||||
        count: 10,
 | 
			
		||||
    },
 | 
			
		||||
    keySize: 0,
 | 
			
		||||
    hashValues: [
 | 
			
		||||
        {
 | 
			
		||||
            field: '',
 | 
			
		||||
            value: '',
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    operationType,
 | 
			
		||||
    key,
 | 
			
		||||
    scanParam,
 | 
			
		||||
    keySize,
 | 
			
		||||
    hashValues,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        state.hashValues = [];
 | 
			
		||||
        state.key = {} as any;
 | 
			
		||||
    }, 500);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    const visible = newValue.visible;
 | 
			
		||||
    state.redisId = newValue.redisId;
 | 
			
		||||
    state.db = newValue.db;
 | 
			
		||||
    state.key = newValue.keyInfo;
 | 
			
		||||
    state.operationType = newValue.operationType;
 | 
			
		||||
 | 
			
		||||
    if (visible && state.operationType == 2) {
 | 
			
		||||
        state.scanParam.id = props.redisId as any;
 | 
			
		||||
        state.scanParam.key = state.key.key;
 | 
			
		||||
        await reHscan();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.dialogVisible = visible;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const reHscan = async () => {
 | 
			
		||||
    state.scanParam.id = state.redisId;
 | 
			
		||||
    state.scanParam.db = state.db;
 | 
			
		||||
    state.scanParam.cursor = 0;
 | 
			
		||||
    hscan();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const hscan = async () => {
 | 
			
		||||
    const match = state.scanParam.match;
 | 
			
		||||
    if (!match || match == '' || match == '*') {
 | 
			
		||||
        if (state.scanParam.count > 100) {
 | 
			
		||||
            ElMessage.error('match为空或者*时, count不能超过100');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        if (state.scanParam.count > 1000) {
 | 
			
		||||
            ElMessage.error('count不能超过1000');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const scanRes = await redisApi.hscan.request(state.scanParam);
 | 
			
		||||
    state.scanParam.cursor = scanRes.cursor;
 | 
			
		||||
    state.keySize = scanRes.keySize;
 | 
			
		||||
 | 
			
		||||
    const keys = scanRes.keys;
 | 
			
		||||
    const hashValue = [];
 | 
			
		||||
    const fieldCount = keys.length / 2;
 | 
			
		||||
    let nextFieldIndex = 0;
 | 
			
		||||
    for (let i = 0; i < fieldCount; i++) {
 | 
			
		||||
        hashValue.push({ field: keys[nextFieldIndex++], value: keys[nextFieldIndex++] });
 | 
			
		||||
    }
 | 
			
		||||
    state.hashValues = hashValue;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const hdel = async (field: any, index: any) => {
 | 
			
		||||
    // 如果是新增操作,则直接数组移除即可
 | 
			
		||||
    if (state.operationType == 1) {
 | 
			
		||||
        state.hashValues.splice(index, 1);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    await ElMessageBox.confirm(`确定删除[${field}]?`, '提示', {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
    });
 | 
			
		||||
    await redisApi.hdel.request({
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key.key,
 | 
			
		||||
        field,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success('删除成功');
 | 
			
		||||
    reHscan();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const hset = async (row: any) => {
 | 
			
		||||
    await redisApi.saveHashValue.request({
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key.key,
 | 
			
		||||
        timed: state.key.timed,
 | 
			
		||||
        value: [
 | 
			
		||||
            {
 | 
			
		||||
                field: row.field,
 | 
			
		||||
                value: row.value,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onAddHashValue = () => {
 | 
			
		||||
    state.hashValues.unshift({ field: '', value: '' });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveValue = async () => {
 | 
			
		||||
    notEmpty(state.key.key, 'key不能为空');
 | 
			
		||||
    isTrue(state.hashValues.length > 0, 'hash内容不能为空');
 | 
			
		||||
    const sv = { value: state.hashValues, id: state.redisId, db: state.db };
 | 
			
		||||
    Object.assign(sv, state.key);
 | 
			
		||||
    await redisApi.saveHashValue.request(sv);
 | 
			
		||||
    ElMessage.success('保存成功');
 | 
			
		||||
 | 
			
		||||
    cancel();
 | 
			
		||||
    emit('valChange');
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#string-value-text {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .text-type-select {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: 10px;
 | 
			
		||||
        max-width: 70px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										136
									
								
								mayfly_go_web/src/views/ops/redis/KeyDetail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								mayfly_go_web/src/views/ops/redis/KeyDetail.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-container direction="vertical" class="key-tab-container">
 | 
			
		||||
            <!-- key info -->
 | 
			
		||||
            <key-header ref="keyHeader" :redis-id="redisId" :db="db" :key-info="keyInfo" @refresh-content="refreshContent"
 | 
			
		||||
                @change-key="changeKey" class="key-header-info">
 | 
			
		||||
            </key-header>
 | 
			
		||||
 | 
			
		||||
            <!-- key content -->
 | 
			
		||||
            <component ref="keyValueRef" :is="components[componentName]" :redis-id="redisId" :db="db" :key-info="keyInfo">
 | 
			
		||||
            </component>
 | 
			
		||||
        </el-container>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { defineAsyncComponent, ref, shallowReactive, reactive, computed, toRefs } from 'vue';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import KeyHeader from './KeyHeader.vue'
 | 
			
		||||
 | 
			
		||||
const KeyValueString = defineAsyncComponent(() => import('./KeyValueString.vue'));
 | 
			
		||||
const KeyValueHash = defineAsyncComponent(() => import('./KeyValueHash.vue'));
 | 
			
		||||
const KeyValueSet = defineAsyncComponent(() => import('./KeyValueSet.vue'));
 | 
			
		||||
const KeyValueList = defineAsyncComponent(() => import('./KeyValueList.vue'));
 | 
			
		||||
const KeyValueZset = defineAsyncComponent(() => import('./KeyValueZset.vue'));
 | 
			
		||||
 | 
			
		||||
const components = shallowReactive({
 | 
			
		||||
    KeyValueString, KeyValueHash, KeyValueSet, KeyValueList, KeyValueZset
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const keyValueRef = ref(null) as any
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: Number
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: Number
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'changeKey', 'valChange'])
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const componentMap = {
 | 
			
		||||
    string: 'KeyValueString',
 | 
			
		||||
    hash: 'KeyValueHash',
 | 
			
		||||
    zset: 'KeyValueZset',
 | 
			
		||||
    set: 'KeyValueSet',
 | 
			
		||||
    list: 'KeyValueList',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const componentName = computed(() => {
 | 
			
		||||
    const component = componentMap[props.keyInfo?.type]
 | 
			
		||||
    if (!component) {
 | 
			
		||||
        ElMessage.error("暂不支持该类型")
 | 
			
		||||
        return ''
 | 
			
		||||
    }
 | 
			
		||||
    return component;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const refreshContent = () => {
 | 
			
		||||
    keyValueRef.value?.initData();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const changeKey = () => {
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
// watch(
 | 
			
		||||
//     () => props.keyInfo,
 | 
			
		||||
//     (val) => {
 | 
			
		||||
//         state.keyInfo = val;
 | 
			
		||||
//     }
 | 
			
		||||
// );
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.key-tab-container {
 | 
			
		||||
    /*padding-left: 5px;*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key-header-info {
 | 
			
		||||
    // margin-top: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key-content-container {
 | 
			
		||||
    margin-top: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// .key-detail-filter-value {
 | 
			
		||||
//     width: 90%;
 | 
			
		||||
//     height: 24px;
 | 
			
		||||
//     padding: 0 5px;
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/*tooltip in table width limit*/
 | 
			
		||||
.el-tooltip__popper {
 | 
			
		||||
    max-width: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content-more-container {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content-more-container .content-more-btn {
 | 
			
		||||
    width: 95%;
 | 
			
		||||
    padding-top: 5px;
 | 
			
		||||
    padding-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*data table list styles*/
 | 
			
		||||
.key-content-container .el-table {
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*table list height*/
 | 
			
		||||
.key-content-container .el-table .el-table__body td {
 | 
			
		||||
    padding: 0px 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*table list border*/
 | 
			
		||||
.key-content-container .el-table--border td,
 | 
			
		||||
.key-content-container .el-table--border th {
 | 
			
		||||
    border-right-width: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										200
									
								
								mayfly_go_web/src/views/ops/redis/KeyHeader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								mayfly_go_web/src/views/ops/redis/KeyHeader.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,200 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <!-- key name -->
 | 
			
		||||
        <div class="key-header-item key-name-input">
 | 
			
		||||
            <el-input ref="keyNameInput" v-model="keyInfo.key" title="点击重命名" placeholder="KeyName">
 | 
			
		||||
                <template #prepend>
 | 
			
		||||
                    <span class="key-detail-type">{{ keyInfo.type }}</span>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #suffix>
 | 
			
		||||
                    <SvgIcon v-auth="'redis:data:save'" @click="renameKey" title="点击重命名" name="check"
 | 
			
		||||
                        class="cursor-pointer" />
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
            </el-input>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- key ttl -->
 | 
			
		||||
        <div class="key-header-item key-ttl-input">
 | 
			
		||||
            <el-input type="number" v-model.number="keyInfo.timed" placeholder="单位(秒),负数永久" title="点击修改过期时间">
 | 
			
		||||
                <template #prepend>
 | 
			
		||||
                    <span slot="prepend">TTL</span>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #suffix>
 | 
			
		||||
                    <!-- save ttl -->
 | 
			
		||||
                    <SvgIcon v-auth="'redis:data:save'" @click="ttlKey" title="点击修改过期时间" name="check" />
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-input>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- del & refresh btn -->
 | 
			
		||||
        <div class='key-header-item key-header-btn-con'>
 | 
			
		||||
            <el-button slot="reference" ref='refreshBtn' type="success" @click="refreshKey" icon="refresh"
 | 
			
		||||
                title="刷新"></el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { reactive, watch, toRefs, onMounted } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: Number
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: Number
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['refreshContent', 'changeKey', 'valChange'])
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        key: '',
 | 
			
		||||
        type: '',
 | 
			
		||||
        timed: -1,
 | 
			
		||||
    } as any,
 | 
			
		||||
    oldKey: '',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.keyInfo = props.keyInfo
 | 
			
		||||
    state.oldKey = props.keyInfo?.key
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const refreshKey = async () => {
 | 
			
		||||
    const ttl = await redisApi.keyTtl.request({
 | 
			
		||||
        id: props.redisId,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        key: state.oldKey,
 | 
			
		||||
    })
 | 
			
		||||
    state.keyInfo.timed = ttl;
 | 
			
		||||
    emit('refreshContent');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const renameKey = async () => {
 | 
			
		||||
    if (!state.oldKey || state.keyInfo.key == state.oldKey) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    await redisApi.renameKey.request({
 | 
			
		||||
        id: props.redisId,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        newKey: state.keyInfo.key,
 | 
			
		||||
        key: state.oldKey
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success("设置成功")
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ttlKey = async () => {
 | 
			
		||||
    if (!state.oldKey) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    // ttl <= 0,则持久化该key
 | 
			
		||||
    if (state.keyInfo.timed <= 0) {
 | 
			
		||||
        try {
 | 
			
		||||
            await ElMessageBox.confirm(
 | 
			
		||||
                '确定持久化该key?',
 | 
			
		||||
                'Warning',
 | 
			
		||||
                {
 | 
			
		||||
                    confirmButtonText: '确认',
 | 
			
		||||
                    cancelButtonText: '取消',
 | 
			
		||||
                    type: 'warning',
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        await persistKey();
 | 
			
		||||
        state.keyInfo.timed = -1;
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await redisApi.expireKey.request({
 | 
			
		||||
        id: props.redisId,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        key: state.keyInfo.key,
 | 
			
		||||
        seconds: state.keyInfo.timed
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success("设置成功")
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const persistKey = async () => {
 | 
			
		||||
    await redisApi.persistKey.request({
 | 
			
		||||
        id: props.redisId,
 | 
			
		||||
        db: props.db,
 | 
			
		||||
        key: state.keyInfo.key,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success("设置成功")
 | 
			
		||||
    emit('changeKey');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    keyInfo,
 | 
			
		||||
    oldKey,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
// watch(
 | 
			
		||||
//     () => props.keyInfo,
 | 
			
		||||
//     (val) => {
 | 
			
		||||
//         state.keyInfo = val;
 | 
			
		||||
//         state.keyName = state.keyInfo.key;
 | 
			
		||||
//     }
 | 
			
		||||
// );
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.key-detail-type {
 | 
			
		||||
    text-transform: capitalize;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    min-width: 34px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cursor-pointer {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key-header-item {
 | 
			
		||||
    /*padding-right: 15px;*/
 | 
			
		||||
    /*margin-bottom: 10px;*/
 | 
			
		||||
    float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key-header-item.key-name-input {
 | 
			
		||||
    width: calc(100% - 332px);
 | 
			
		||||
    min-width: 220px;
 | 
			
		||||
    max-width: 800px;
 | 
			
		||||
    margin-right: 15px;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key-header-item.key-ttl-input {
 | 
			
		||||
    width: 220px;
 | 
			
		||||
    margin-right: 15px;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*hide number input button*/
 | 
			
		||||
.key-header-item.key-ttl-input input::-webkit-inner-spin-button,
 | 
			
		||||
.key-header-item.key-ttl-input input::-webkit-outer-spin-button {
 | 
			
		||||
    appearance: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key-header-item.key-header-btn-con .el-button+.el-button {
 | 
			
		||||
    margin-left: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*refresh btn rotating*/
 | 
			
		||||
.key-header-info .key-header-btn-con .rotating .el-icon-refresh {
 | 
			
		||||
    animation: rotate 1.5s linear infinite;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										241
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueHash.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueHash.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,241 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
 | 
			
		||||
        <el-table size="small" border :data="hashValues" min-height=300 stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="field" label="field" show-overflow-tooltip min-width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column label="操作">
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <el-input class="key-detail-filter-value" v-model="state.filterValue" @keyup.enter='hscan(true, true)'
 | 
			
		||||
                        placeholder="输入关键词回车搜索" clearable size="small" />
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit"
 | 
			
		||||
                        plain></el-link>
 | 
			
		||||
                    <el-popconfirm title="确定删除?" @confirm="hdel(scope.row.field, scope.$index)">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small"
 | 
			
		||||
                                plain class="ml5"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popconfirm>
 | 
			
		||||
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        <!-- load more content -->
 | 
			
		||||
        <div class='content-more-container'>
 | 
			
		||||
            <el-button size='small' @click='hscan()' :disabled='loadMoreDisable' class='content-more-btn'>
 | 
			
		||||
                加载更多
 | 
			
		||||
            </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <el-form>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <el-input v-model="editDialog.field" placeholder="field" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <format-viewer class="w100" ref="formatViewerRef" :content="editDialog.value"></format-viewer>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="editDialog.visible = false">取 消</el-button>
 | 
			
		||||
                    <el-button v-auth="'redis:data:save'" type="primary" @click="confirmEditData">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, onMounted, reactive, watch, toRefs } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { notBlank } from '@/common/assert';
 | 
			
		||||
import FormatViewer from './FormatViewer.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
    db: 0,
 | 
			
		||||
    key: '',
 | 
			
		||||
    scanParam: {
 | 
			
		||||
        cursor: 0,
 | 
			
		||||
        count: 50,
 | 
			
		||||
    },
 | 
			
		||||
    filterValue: '',
 | 
			
		||||
    hashValues: [] as any,
 | 
			
		||||
    total: 0,
 | 
			
		||||
    loadMoreDisable: false,
 | 
			
		||||
    editDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        field: '',
 | 
			
		||||
        value: '',
 | 
			
		||||
        dataRow: null as any,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    hashValues,
 | 
			
		||||
    total,
 | 
			
		||||
    loadMoreDisable,
 | 
			
		||||
    editDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    state.filterValue = '';
 | 
			
		||||
    hscan(true, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getScanMatch = () => {
 | 
			
		||||
    return state.filterValue ? `*${state.filterValue}*` : '*';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const hscan = async (resetTableData = false, resetCursor = false) => {
 | 
			
		||||
    if (resetCursor) {
 | 
			
		||||
        state.scanParam.cursor = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const scanRes = await redisApi.hscan.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        match: getScanMatch(),
 | 
			
		||||
        ...state.scanParam
 | 
			
		||||
    });
 | 
			
		||||
    state.scanParam.cursor = scanRes.cursor;
 | 
			
		||||
    state.loadMoreDisable = scanRes.cursor == 0
 | 
			
		||||
    state.total = scanRes.keySize;
 | 
			
		||||
 | 
			
		||||
    const keys = scanRes.keys;
 | 
			
		||||
    const hashValue = [];
 | 
			
		||||
    const fieldCount = keys.length / 2;
 | 
			
		||||
    let nextFieldIndex = 0;
 | 
			
		||||
    for (let i = 0; i < fieldCount; i++) {
 | 
			
		||||
        hashValue.push({ field: keys[nextFieldIndex++], value: keys[nextFieldIndex++] });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (resetTableData) {
 | 
			
		||||
        state.hashValues = hashValue;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.hashValues.push(...hashValue)
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const hdel = async (field: any, index: any) => {
 | 
			
		||||
    await redisApi.hdel.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        field,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success('删除成功');
 | 
			
		||||
    state.hashValues.splice(index, 1)
 | 
			
		||||
    state.total--;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showEditDialog = (row: any) => {
 | 
			
		||||
    state.editDialog.dataRow = row;
 | 
			
		||||
    state.editDialog.field = row ? row.field : '';
 | 
			
		||||
    state.editDialog.value = row ? row.value : '';
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const confirmEditData = async () => {
 | 
			
		||||
    const param = getBaseReqParam();
 | 
			
		||||
 | 
			
		||||
    const field = state.editDialog.field;
 | 
			
		||||
    notBlank(field, "field不能为空");
 | 
			
		||||
 | 
			
		||||
    // 存在数据行,则说明为修改,则要先删除旧数据后新增
 | 
			
		||||
    const dataRow = state.editDialog.dataRow
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        await redisApi.hdel.request({
 | 
			
		||||
            ...param,
 | 
			
		||||
            field: dataRow.field,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取hash value内容并新增
 | 
			
		||||
    const value = formatViewerRef.value.getContent()
 | 
			
		||||
    const res = await redisApi.hset.request({
 | 
			
		||||
        ...param,
 | 
			
		||||
        value: [
 | 
			
		||||
            {
 | 
			
		||||
                field,
 | 
			
		||||
                value: value,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success("保存成功");
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        state.editDialog.dataRow.value = value;
 | 
			
		||||
        state.editDialog.dataRow.field = field;
 | 
			
		||||
    } else {
 | 
			
		||||
        // 响应0则为被覆盖,则重新scan
 | 
			
		||||
        if (res == 0) {
 | 
			
		||||
            hscan(true, true);
 | 
			
		||||
        } else {
 | 
			
		||||
            state.hashValues.unshift({ value, field });
 | 
			
		||||
            state.total++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = false;
 | 
			
		||||
    state.editDialog.dataRow = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#string-value-text {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .text-type-select {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: 10px;
 | 
			
		||||
        max-width: 70px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										199
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,199 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
 | 
			
		||||
        <el-table size="small" border :data="values" min-height=300 stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column label="操作">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit"
 | 
			
		||||
                        plain></el-link>
 | 
			
		||||
                    <el-popconfirm title="确定删除?" @confirm="lrem(scope.row, scope.$index)">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small"
 | 
			
		||||
                                plain class="ml5"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popconfirm>
 | 
			
		||||
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        <!-- load more content -->
 | 
			
		||||
        <div class='content-more-container'>
 | 
			
		||||
            <el-button size='small' @click='getListValue(false)' :disabled='loadMoreDisable' class='content-more-btn'>
 | 
			
		||||
                加载更多
 | 
			
		||||
            </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <el-form>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <format-viewer class="w100" ref="formatViewerRef" :content="editDialog.content"></format-viewer>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="editDialog.visible = false">取 消</el-button>
 | 
			
		||||
                    <el-button v-auth="'redis:data:save'" type="primary" @click="confirmEditData">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, toRefs, onMounted } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import FormatViewer from './FormatViewer.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
    db: 0,
 | 
			
		||||
    key: '',
 | 
			
		||||
    pageNum: 1,
 | 
			
		||||
    pageSize: 50,
 | 
			
		||||
    total: 0,
 | 
			
		||||
    values: [] as any,
 | 
			
		||||
    loadMoreDisable: false,
 | 
			
		||||
    editDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        content: '',
 | 
			
		||||
        dataRow: null as any,
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    total,
 | 
			
		||||
    values,
 | 
			
		||||
    loadMoreDisable,
 | 
			
		||||
    editDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    state.pageNum = 1;
 | 
			
		||||
    getListValue(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getListValue = async (resetTableData = false) => {
 | 
			
		||||
    const pageNum = state.pageNum;
 | 
			
		||||
    const pageSize = state.pageSize;
 | 
			
		||||
    const res = await redisApi.getListValue.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        start: (pageNum - 1) * pageSize,
 | 
			
		||||
        stop: pageNum * pageSize - 1,
 | 
			
		||||
    });
 | 
			
		||||
    state.total = res.len;
 | 
			
		||||
 | 
			
		||||
    const datas = res.list.map((x: any) => {
 | 
			
		||||
        return {
 | 
			
		||||
            value: x,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
    if (resetTableData) {
 | 
			
		||||
        state.values = datas;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.values.push(...datas)
 | 
			
		||||
    }
 | 
			
		||||
    state.pageNum++;
 | 
			
		||||
    state.loadMoreDisable = state.values.length === state.total
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const lset = async (row: any, rowIndex: number) => {
 | 
			
		||||
    await redisApi.setListValue.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        index: (state.pageNum - 1) * state.pageSize + rowIndex,
 | 
			
		||||
        value: row.value,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success('数据保存成功');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showEditDialog = (row: any) => {
 | 
			
		||||
    state.editDialog.dataRow = row;
 | 
			
		||||
    state.editDialog.content = row ? row.value : '';
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const confirmEditData = async () => {
 | 
			
		||||
    const param = getBaseReqParam();
 | 
			
		||||
 | 
			
		||||
    // 存在数据行,则说明为修改,则要先删除旧数据后新增
 | 
			
		||||
    const dataRow = state.editDialog.dataRow
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        await redisApi.lrem.request({
 | 
			
		||||
            member: state.editDialog.dataRow.value,
 | 
			
		||||
            count: 1,
 | 
			
		||||
            ...param
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取list member内容并新增
 | 
			
		||||
    const member = formatViewerRef.value.getContent()
 | 
			
		||||
    await redisApi.saveListValue.request({
 | 
			
		||||
        value: [member],
 | 
			
		||||
        ...param
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success("保存成功");
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        state.editDialog.dataRow.value = member;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.values.push({ value: member });
 | 
			
		||||
        state.total++;
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = false;
 | 
			
		||||
    state.editDialog.dataRow = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const lrem = async (row: any, index: any) => {
 | 
			
		||||
    await redisApi.lrem.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        member: row.value,
 | 
			
		||||
        count: 1,
 | 
			
		||||
    })
 | 
			
		||||
    ElMessage.success("删除成功");
 | 
			
		||||
    state.values.splice(index, 1)
 | 
			
		||||
    state.total--;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
							
								
								
									
										205
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueSet.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueSet.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,205 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
 | 
			
		||||
        <el-table size="small" border :data="setDatas" min-height=300 stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column label="操作">
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <el-input class="key-detail-filter-value" v-model="state.filterValue"
 | 
			
		||||
                        @keyup.enter='sscanData(true, true)' placeholder="输入关键词回车搜索" clearable size="small" />
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit"
 | 
			
		||||
                        plain></el-link>
 | 
			
		||||
                    <el-popconfirm title="确定删除?" @confirm="srem(scope.row, scope.$index)">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small"
 | 
			
		||||
                                plain class="ml5"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popconfirm>
 | 
			
		||||
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        <!-- load more content -->
 | 
			
		||||
        <div class='content-more-container'>
 | 
			
		||||
            <el-button size='small' @click='sscanData(false)' :disabled='loadMoreDisable' class='content-more-btn'>
 | 
			
		||||
                加载更多
 | 
			
		||||
            </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <el-form>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <format-viewer class="w100" ref="formatViewerRef" :content="editDialog.content"></format-viewer>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="editDialog.visible = false">取 消</el-button>
 | 
			
		||||
                    <el-button v-auth="'redis:data:save'" type="primary" @click="confirmEditData">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, toRefs, onMounted } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import FormatViewer from './FormatViewer.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
    db: 0,
 | 
			
		||||
    key: '',
 | 
			
		||||
 | 
			
		||||
    filterValue: '',
 | 
			
		||||
    scanParam: {
 | 
			
		||||
        count: 50,
 | 
			
		||||
        cursor: 0,
 | 
			
		||||
    },
 | 
			
		||||
    total: 0,
 | 
			
		||||
    setDatas: [] as any,
 | 
			
		||||
    loadMoreDisable: false,
 | 
			
		||||
    value: [{ value: '' }],
 | 
			
		||||
    editDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        content: '',
 | 
			
		||||
        dataRow: null as any,
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    total,
 | 
			
		||||
    setDatas,
 | 
			
		||||
    loadMoreDisable,
 | 
			
		||||
    editDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    state.filterValue = '';
 | 
			
		||||
    sscanData(true, true);
 | 
			
		||||
    getTotal();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getScanMatch = () => {
 | 
			
		||||
    return state.filterValue ? `*${state.filterValue}*` : '*';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const sscanData = async (resetDatas = true, resetCursor = false) => {
 | 
			
		||||
    if (resetCursor) {
 | 
			
		||||
        state.scanParam.cursor = 0;
 | 
			
		||||
    }
 | 
			
		||||
    const res = await redisApi.sscan.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        match: getScanMatch(),
 | 
			
		||||
        ...state.scanParam
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (resetDatas) {
 | 
			
		||||
        state.setDatas = [];
 | 
			
		||||
    }
 | 
			
		||||
    res.keys.forEach((x: any) => {
 | 
			
		||||
        state.setDatas.push({
 | 
			
		||||
            value: x,
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
    state.scanParam.cursor = res.cursor;
 | 
			
		||||
    state.loadMoreDisable = res.cursor == 0
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTotal = () => {
 | 
			
		||||
    redisApi.scard.request(getBaseReqParam()).then((res) => {
 | 
			
		||||
        state.total = res;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showEditDialog = (row: any) => {
 | 
			
		||||
    state.editDialog.dataRow = row;
 | 
			
		||||
    state.editDialog.content = row ? row.value : '';
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const confirmEditData = async () => {
 | 
			
		||||
    const param = getBaseReqParam();
 | 
			
		||||
 | 
			
		||||
    // 存在数据行,则说明为修改,则要先删除旧数据后新增
 | 
			
		||||
    const dataRow = state.editDialog.dataRow
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        await redisApi.srem.request({
 | 
			
		||||
            member: state.editDialog.dataRow.value,
 | 
			
		||||
            ...param
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 获取set member内容并新增
 | 
			
		||||
    const member = formatViewerRef.value.getContent()
 | 
			
		||||
    await redisApi.sadd.request({
 | 
			
		||||
        member,
 | 
			
		||||
        ...param
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success("保存成功");
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        state.editDialog.dataRow.value = member;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.setDatas.unshift({ value: member });
 | 
			
		||||
        state.total++;
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = false;
 | 
			
		||||
    state.editDialog.dataRow = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const srem = async (row: any, index: any) => {
 | 
			
		||||
    await redisApi.srem.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        member: row.value,
 | 
			
		||||
    })
 | 
			
		||||
    ElMessage.success("删除成功");
 | 
			
		||||
    state.setDatas.splice(index, 1)
 | 
			
		||||
    state.total--;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
							
								
								
									
										111
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueString.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueString.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-form class='key-content-string' label-width="85px">
 | 
			
		||||
            <div>
 | 
			
		||||
                <format-viewer ref="formatViewerRef" :content="string.value"></format-viewer>
 | 
			
		||||
            </div>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <div class="mt10 fr">
 | 
			
		||||
            <el-button @click="saveValue" type="primary" v-auth="'redis:data:save'">保 存</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, watch, toRefs, onMounted } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { notEmpty } from '@/common/assert';
 | 
			
		||||
import FormatViewer from './FormatViewer.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
    db: 0,
 | 
			
		||||
    key: '',
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        key: '',
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        timed: -1,
 | 
			
		||||
    },
 | 
			
		||||
    string: {
 | 
			
		||||
        type: 'text',
 | 
			
		||||
        value: '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    string,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId
 | 
			
		||||
    state.db = props.db
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
    getStringValue();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getStringValue = async () => {
 | 
			
		||||
    if (state.key) {
 | 
			
		||||
        state.string.value = await redisApi.getString.request(getBaseReqParam());
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveValue = async () => {
 | 
			
		||||
    state.string.value = formatViewerRef.value.getContent();
 | 
			
		||||
    notEmpty(state.string.value, 'value不能为空');
 | 
			
		||||
 | 
			
		||||
    await redisApi.setString.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        value: state.string.value,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success('数据保存成功');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.key-content-string .format-viewer-container {
 | 
			
		||||
    min-height: calc(100vh - 453px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*text viewer box*/
 | 
			
		||||
.key-content-string .el-textarea textarea {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    height: calc(100vh - 436px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*json in monaco editor*/
 | 
			
		||||
.key-content-string .monaco-editor-content {
 | 
			
		||||
    height: calc(100vh - 450px) !important;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										257
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueZset.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								mayfly_go_web/src/views/ops/redis/KeyValueZset.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,257 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
 | 
			
		||||
        <el-table size="small" border :data="values" min-height=300 stripe>
 | 
			
		||||
            <el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="score" label="score" show-overflow-tooltip min-width="100">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200">
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column label="操作">
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <el-input class="key-detail-filter-value" v-model="state.filterValue" @keyup.enter='zscanData(true)'
 | 
			
		||||
                        placeholder="输入关键词回车搜索" clearable size="small" />
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                    <el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit"
 | 
			
		||||
                        plain></el-link>
 | 
			
		||||
                    <el-popconfirm title="确定删除?" @confirm="zrem(scope.row, scope.$index)">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                            <el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small"
 | 
			
		||||
                                plain class="ml5"></el-link>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </el-popconfirm>
 | 
			
		||||
 | 
			
		||||
                </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
        </el-table>
 | 
			
		||||
        <!-- load more content -->
 | 
			
		||||
        <div class='content-more-container'>
 | 
			
		||||
            <el-button size='small' @click='loadDatas()' :disabled='loadMoreDisable' class='content-more-btn'>
 | 
			
		||||
                加载更多
 | 
			
		||||
            </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-dialog title="添加新行" v-model="editDialog.visible" width="600px" :destroy-on-close="true"
 | 
			
		||||
            :close-on-click-modal="false">
 | 
			
		||||
            <el-form>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <el-input type="number" v-model.number="editDialog.score" placeholder="score" />
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
                <el-form-item>
 | 
			
		||||
                    <format-viewer class="w100" ref="formatViewerRef" :content="editDialog.content"></format-viewer>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
            </el-form>
 | 
			
		||||
 | 
			
		||||
            <template #footer>
 | 
			
		||||
                <div class="dialog-footer">
 | 
			
		||||
                    <el-button @click="editDialog.visible = false">取 消</el-button>
 | 
			
		||||
                    <el-button v-auth="'redis:data:save'" type="primary" @click="confirmEditData">确 定</el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </el-dialog>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, toRefs, onMounted } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import FormatViewer from './FormatViewer.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
        default: 0
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formatViewerRef = ref(null) as any;
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    redisId: 0,
 | 
			
		||||
    db: 0,
 | 
			
		||||
    key: '',
 | 
			
		||||
    filterValue: '',
 | 
			
		||||
    scanCursor: 0,
 | 
			
		||||
    pageNum: 1,
 | 
			
		||||
    pageSize: 50,
 | 
			
		||||
    total: 0,
 | 
			
		||||
    values: [] as any,
 | 
			
		||||
    loadMoreDisable: false,
 | 
			
		||||
    editDialog: {
 | 
			
		||||
        visible: false,
 | 
			
		||||
        score: 0,
 | 
			
		||||
        content: '',
 | 
			
		||||
        dataRow: null as any,
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    total,
 | 
			
		||||
    values,
 | 
			
		||||
    loadMoreDisable,
 | 
			
		||||
    editDialog,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.redisId = props.redisId;
 | 
			
		||||
    state.db = props.db;
 | 
			
		||||
    state.key = props.keyInfo?.key;
 | 
			
		||||
    initData();
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const initData = async () => {
 | 
			
		||||
    state.pageNum = 1;
 | 
			
		||||
    state.filterValue = '';
 | 
			
		||||
    await getTotal();
 | 
			
		||||
    await zrevrange(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const loadDatas = (resetTableData = false) => {
 | 
			
		||||
    if (state.filterValue) {
 | 
			
		||||
        zscanData(resetTableData);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    zrevrange(resetTableData);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const zrevrange = async (resetTableData = false) => {
 | 
			
		||||
    const pageNum = state.pageNum;
 | 
			
		||||
    const pageSize = state.pageSize;
 | 
			
		||||
    const res = await redisApi.zrevrange.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        start: (pageNum - 1) * pageSize,
 | 
			
		||||
        stop: pageNum * pageSize - 1,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const vs = [];
 | 
			
		||||
    for (let member of res) {
 | 
			
		||||
        vs.push({
 | 
			
		||||
            score: member.Score,
 | 
			
		||||
            value: member.Member
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    if (resetTableData) {
 | 
			
		||||
        state.values = vs;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.values.push(...vs);
 | 
			
		||||
    }
 | 
			
		||||
    state.pageNum++;
 | 
			
		||||
    state.loadMoreDisable = state.total <= state.values.length
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getScanMatch = () => {
 | 
			
		||||
    return state.filterValue ? `*${state.filterValue}*` : '*';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const zscanData = async (resetTableData = true, resetCursor = false) => {
 | 
			
		||||
    if (resetCursor) {
 | 
			
		||||
        state.scanCursor = 0;
 | 
			
		||||
    }
 | 
			
		||||
    const res = await redisApi.zscan.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        match: getScanMatch(),
 | 
			
		||||
        cursor: state.scanCursor,
 | 
			
		||||
        count: state.pageSize
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const keys = res.keys;
 | 
			
		||||
    const vs = [];
 | 
			
		||||
    const memCount = keys.length / 2;
 | 
			
		||||
    let nextMemndex = 0;
 | 
			
		||||
    for (let i = 0; i < memCount; i++) {
 | 
			
		||||
        vs.push({ value: keys[nextMemndex++], score: keys[nextMemndex++] });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (resetTableData) {
 | 
			
		||||
        state.values = vs;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.values.push(...vs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    state.scanCursor = res.cursor;
 | 
			
		||||
    state.loadMoreDisable = res.cursor == 0
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTotal = () => {
 | 
			
		||||
    redisApi.zcard.request({
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }).then((res) => {
 | 
			
		||||
        state.total = res;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showEditDialog = (row: any) => {
 | 
			
		||||
    state.editDialog.dataRow = row;
 | 
			
		||||
    state.editDialog.content = row ? row.value : '';
 | 
			
		||||
    state.editDialog.score = row ? row.score : null;
 | 
			
		||||
    state.editDialog.visible = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const confirmEditData = async () => {
 | 
			
		||||
    const param = getBaseReqParam();
 | 
			
		||||
 | 
			
		||||
    // 存在数据行,则说明为修改,则要先删除旧数据后新增
 | 
			
		||||
    const dataRow = state.editDialog.dataRow
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        await redisApi.zrem.request({
 | 
			
		||||
            member: state.editDialog.dataRow.value,
 | 
			
		||||
            ...param
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const score = state.editDialog.score
 | 
			
		||||
    // 获取zset member内容并新增
 | 
			
		||||
    const member = formatViewerRef.value.getContent()
 | 
			
		||||
    await redisApi.zadd.request({
 | 
			
		||||
        score,
 | 
			
		||||
        member,
 | 
			
		||||
        ...param
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    ElMessage.success("保存成功");
 | 
			
		||||
    if (dataRow) {
 | 
			
		||||
        state.editDialog.dataRow.value = member;
 | 
			
		||||
        state.editDialog.dataRow.score = score;
 | 
			
		||||
    } else {
 | 
			
		||||
        state.values.unshift({ value: member, score });
 | 
			
		||||
        state.total++;
 | 
			
		||||
    }
 | 
			
		||||
    state.editDialog.visible = false;
 | 
			
		||||
    state.editDialog.dataRow = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const zrem = async (row: any, index: any) => {
 | 
			
		||||
    await redisApi.zrem.request({
 | 
			
		||||
        ...getBaseReqParam(),
 | 
			
		||||
        member: row.value,
 | 
			
		||||
    })
 | 
			
		||||
    ElMessage.success("删除成功");
 | 
			
		||||
    state.values.splice(index, 1)
 | 
			
		||||
    state.total--;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getBaseReqParam = () => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ initData })
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -1,199 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-dialog class="el-table-z-index-inherit" :title="title" v-model="dialogVisible" :before-close="cancel" width="800px" :destroy-on-close="true">
 | 
			
		||||
        <el-form label-width="85px">
 | 
			
		||||
            <el-form-item prop="key" label="key:">
 | 
			
		||||
                <el-input :disabled="operationType == 2" v-model="key.key"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="timed" label="过期时间:">
 | 
			
		||||
                <el-input v-model.number="key.timed" type="number"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="dataType" label="数据类型:">
 | 
			
		||||
                <el-input v-model="key.type" disabled></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <!-- <el-button @click="onAddListValue" icon="plus" size="small" plain class="mt10">添加</el-button> -->
 | 
			
		||||
            <div v-if="operationType == 2" class="mt10" style="float: left">
 | 
			
		||||
                <span>len: {{ len }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <el-table :data="value" stripe style="width: 100%">
 | 
			
		||||
                <el-table-column prop="value" label="value" min-width="200">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <format-input :title="`type:【${key.type}】key:【${key.key}】`" v-model="scope.row.value"
 | 
			
		||||
                            :autosize="{ minRows: 2, maxRows: 10 }" size="small"></format-input>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column label="操作" width="140">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-button v-if="operationType == 2" type="success" @click="lset(scope.row, scope.$index)"
 | 
			
		||||
                            icon="check" size="small" plain></el-button>
 | 
			
		||||
                        <!-- <el-button type="danger" @click="set.value.splice(scope.$index, 1)" icon="delete" size="small" plain></el-button> -->
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
            <el-row style="margin-top: 20px" type="flex" justify="end">
 | 
			
		||||
                <el-pagination style="text-align: right" :total="len" layout="prev, pager, next, total"
 | 
			
		||||
                    @current-change="handlePageChange" v-model:current-page="pageNum" :page-size="pageSize">
 | 
			
		||||
                </el-pagination>
 | 
			
		||||
            </el-row>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <!-- <template #footer>
 | 
			
		||||
            <div class="dialog-footer">
 | 
			
		||||
                <el-button @click="cancel()">取 消</el-button>
 | 
			
		||||
                <el-button @click="saveValue" type="primary" v-auth="'redis:data:save'">确 定</el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template> -->
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { reactive, watch, toRefs } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import FormatInput from './FormatInput.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [String],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
    // 操作类型,1:新增,2:修改
 | 
			
		||||
    operationType: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
    },
 | 
			
		||||
    listValue: {
 | 
			
		||||
        type: [Array, Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    operationType: 1,
 | 
			
		||||
    redisId: '',
 | 
			
		||||
    db: '0',
 | 
			
		||||
    key: {
 | 
			
		||||
        key: '',
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        timed: -1,
 | 
			
		||||
    },
 | 
			
		||||
    value: [{ value: '' }],
 | 
			
		||||
    len: 0,
 | 
			
		||||
    start: 0,
 | 
			
		||||
    stop: 0,
 | 
			
		||||
    pageNum: 1,
 | 
			
		||||
    pageSize: 10,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    operationType,
 | 
			
		||||
    key,
 | 
			
		||||
    value,
 | 
			
		||||
    len,
 | 
			
		||||
    pageNum,
 | 
			
		||||
    pageSize,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        state.key = {
 | 
			
		||||
            key: '',
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            timed: -1,
 | 
			
		||||
        };
 | 
			
		||||
        state.value = [];
 | 
			
		||||
    }, 500);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
    state.redisId = newValue.redisId;
 | 
			
		||||
    state.db = newValue.db;
 | 
			
		||||
    state.key = newValue.keyInfo;
 | 
			
		||||
    state.operationType = newValue.operationType;
 | 
			
		||||
    // 如果是查看编辑操作,则获取值
 | 
			
		||||
    if (state.dialogVisible && state.operationType == 2) {
 | 
			
		||||
        getListValue();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getListValue = async () => {
 | 
			
		||||
    const pageNum = state.pageNum;
 | 
			
		||||
    const pageSize = state.pageSize;
 | 
			
		||||
    const res = await redisApi.getListValue.request({
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key.key,
 | 
			
		||||
        start: (pageNum - 1) * pageSize,
 | 
			
		||||
        stop: pageNum * pageSize - 1,
 | 
			
		||||
    });
 | 
			
		||||
    state.len = res.len;
 | 
			
		||||
    state.value = res.list.map((x: any) => {
 | 
			
		||||
        return {
 | 
			
		||||
            value: x,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const lset = async (row: any, rowIndex: number) => {
 | 
			
		||||
    await redisApi.setListValue.request({
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key.key,
 | 
			
		||||
        index: (state.pageNum - 1) * state.pageSize + rowIndex,
 | 
			
		||||
        value: row.value,
 | 
			
		||||
    });
 | 
			
		||||
    ElMessage.success('数据保存成功');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// const saveValue = async () => {
 | 
			
		||||
//     notEmpty(state.key.key, 'key不能为空');
 | 
			
		||||
//     isTrue(state.value.length > 0, 'list内容不能为空');
 | 
			
		||||
//     // const sv = { value: state.value.map((x) => x.value), id: state.redisId };
 | 
			
		||||
//     // Object.assign(sv, state.key);
 | 
			
		||||
//     // await redisApi.saveSetValue.request(sv);
 | 
			
		||||
 | 
			
		||||
//     ElMessage.success('数据保存成功');
 | 
			
		||||
//     cancel();
 | 
			
		||||
//     emit('valChange');
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
// const onAddListValue = () => {
 | 
			
		||||
//     state.value.unshift({ value: '' });
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
const handlePageChange = (curPage: number) => {
 | 
			
		||||
    state.pageNum = curPage;
 | 
			
		||||
    getListValue();
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#string-value-text {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .text-type-select {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: 10px;
 | 
			
		||||
        max-width: 70px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,162 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-dialog class="el-table-z-index-inherit" :title="title" v-model="dialogVisible" :before-close="cancel" width="800px" :destroy-on-close="true">
 | 
			
		||||
        <el-form label-width="85px">
 | 
			
		||||
            <el-form-item prop="key" label="key:">
 | 
			
		||||
                <el-input :disabled="operationType == 2" v-model="key.key"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="timed" label="过期时间:">
 | 
			
		||||
                <el-input v-model.number="key.timed" type="number"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="dataType" label="数据类型:">
 | 
			
		||||
                <el-input v-model="key.type" disabled></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <el-button @click="onAddSetValue" icon="plus" size="small" plain class="mt10">添加</el-button>
 | 
			
		||||
            <el-table :data="value" stripe style="width: 100%">
 | 
			
		||||
                <el-table-column prop="value" label="value" min-width="200">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <format-input :title="`type:【${key.type}】key:【${key.key}】`" v-model="scope.row.value" clearable type="textarea"
 | 
			
		||||
                            :autosize="{ minRows: 2, maxRows: 10 }" size="small"></format-input>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column label="操作" width="90">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-button v-auth="'redis:data:del'" type="danger" @click="value.splice(scope.$index, 1)" icon="delete" size="small"
 | 
			
		||||
                            plain>删除</el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <template #footer>
 | 
			
		||||
            <div class="dialog-footer">
 | 
			
		||||
                <el-button @click="cancel()">取 消</el-button>
 | 
			
		||||
                <el-button @click="saveValue" type="primary" v-auth="'redis:data:save'">确 定</el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { reactive, watch, toRefs } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { isTrue, notEmpty } from '@/common/assert';
 | 
			
		||||
import FormatInput from './FormatInput.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [String],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
    // 操作类型,1:新增,2:修改
 | 
			
		||||
    operationType: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
    },
 | 
			
		||||
    setValue: {
 | 
			
		||||
        type: [Array, Object],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    operationType: 1,
 | 
			
		||||
    redisId: '',
 | 
			
		||||
    db: '0',
 | 
			
		||||
    key: {
 | 
			
		||||
        key: '',
 | 
			
		||||
        type: 'set',
 | 
			
		||||
        timed: -1,
 | 
			
		||||
    },
 | 
			
		||||
    value: [{ value: '' }],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    operationType,
 | 
			
		||||
    key,
 | 
			
		||||
    value,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        state.key = {
 | 
			
		||||
            key: '',
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            timed: -1,
 | 
			
		||||
        };
 | 
			
		||||
        state.value = [];
 | 
			
		||||
    }, 500);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
    state.redisId = newValue.redisId;
 | 
			
		||||
    state.db = newValue.db;
 | 
			
		||||
    state.key = newValue.keyInfo;
 | 
			
		||||
    state.operationType = newValue.operationType;
 | 
			
		||||
    // 如果是查看编辑操作,则获取值
 | 
			
		||||
    if (state.dialogVisible && state.operationType == 2) {
 | 
			
		||||
        getSetValue();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getSetValue = async () => {
 | 
			
		||||
    const res = await redisApi.getSetValue.request({
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key.key,
 | 
			
		||||
    });
 | 
			
		||||
    state.value = res.map((x: any) => {
 | 
			
		||||
        return {
 | 
			
		||||
            value: x,
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveValue = async () => {
 | 
			
		||||
    notEmpty(state.key.key, 'key不能为空');
 | 
			
		||||
    isTrue(state.value.length > 0, 'set内容不能为空');
 | 
			
		||||
    const sv = { value: state.value.map((x) => x.value), id: state.redisId, db: state.db };
 | 
			
		||||
    Object.assign(sv, state.key);
 | 
			
		||||
    await redisApi.saveSetValue.request(sv);
 | 
			
		||||
 | 
			
		||||
    ElMessage.success('数据保存成功');
 | 
			
		||||
    cancel();
 | 
			
		||||
    emit('valChange');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onAddSetValue = () => {
 | 
			
		||||
    state.value.unshift({ value: '' });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#string-value-text {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .text-type-select {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: 10px;
 | 
			
		||||
        max-width: 70px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,167 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" width="800px" :destroy-on-close="true">
 | 
			
		||||
        <el-form label-width="85px">
 | 
			
		||||
            <el-form-item prop="key" label="key:">
 | 
			
		||||
                <el-input :disabled="operationType == 2" v-model="key.key"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="timed" label="过期时间:">
 | 
			
		||||
                <el-input v-model.number="key.timed" type="number"></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
            <el-form-item prop="dataType" label="数据类型:">
 | 
			
		||||
                <el-input v-model="key.type" disabled></el-input>
 | 
			
		||||
            </el-form-item>
 | 
			
		||||
 | 
			
		||||
            <div id="string-value-text" style="width: 100%">
 | 
			
		||||
                <format-input :title="`type:【${key.type}】key:【${key.key}】`" v-model="string.value" :autosize="{ minRows: 10, maxRows: 20 }"></format-input>
 | 
			
		||||
                <!-- <el-select class="text-type-select" @change="onChangeTextType" v-model="string.type">
 | 
			
		||||
                    <el-option key="text" label="text" value="text"> </el-option>
 | 
			
		||||
                    <el-option key="json" label="json" value="json"> </el-option>
 | 
			
		||||
                </el-select> -->
 | 
			
		||||
            </div>
 | 
			
		||||
        </el-form>
 | 
			
		||||
        <template #footer>
 | 
			
		||||
            <div class="dialog-footer">
 | 
			
		||||
                <el-button @click="cancel()">取 消</el-button>
 | 
			
		||||
                <el-button @click="saveValue" type="primary" v-auth="'redis:data:save'">确 定</el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { reactive, watch, toRefs } from 'vue';
 | 
			
		||||
import { redisApi } from './api';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import { notEmpty } from '@/common/assert';
 | 
			
		||||
import FormatInput from './FormatInput.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    visible: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    title: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
    redisId: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    db: {
 | 
			
		||||
        type: [String],
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
    keyInfo: {
 | 
			
		||||
        type: [Object],
 | 
			
		||||
    },
 | 
			
		||||
    // 操作类型,1:新增,2:修改
 | 
			
		||||
    operationType: {
 | 
			
		||||
        type: [Number],
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    dialogVisible: false,
 | 
			
		||||
    operationType: 1,
 | 
			
		||||
    redisId: '',
 | 
			
		||||
    db: '0',
 | 
			
		||||
    key: {
 | 
			
		||||
        key: '',
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        timed: -1,
 | 
			
		||||
    },
 | 
			
		||||
    string: {
 | 
			
		||||
        type: 'text',
 | 
			
		||||
        value: '',
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    dialogVisible,
 | 
			
		||||
    operationType,
 | 
			
		||||
    key,
 | 
			
		||||
    string,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
    emit('cancel');
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        state.key = {
 | 
			
		||||
            key: '',
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            timed: -1,
 | 
			
		||||
        };
 | 
			
		||||
        state.string.value = '';
 | 
			
		||||
        state.string.type = 'text';
 | 
			
		||||
    }, 500);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.visible,
 | 
			
		||||
    (val) => {
 | 
			
		||||
        state.dialogVisible = val;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.redisId,
 | 
			
		||||
    (val) => {
 | 
			
		||||
        state.redisId = val as any;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.db,
 | 
			
		||||
    (val) => {
 | 
			
		||||
        state.db = val as any;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(props, async (newValue: any) => {
 | 
			
		||||
    state.dialogVisible = newValue.visible;
 | 
			
		||||
    state.redisId = newValue.redisId;
 | 
			
		||||
    state.db = newValue.db;
 | 
			
		||||
    state.key = newValue.keyInfo;
 | 
			
		||||
    state.operationType = newValue.operationType;
 | 
			
		||||
    // 如果是查看编辑操作,则获取值
 | 
			
		||||
    if (state.dialogVisible && state.operationType == 2) {
 | 
			
		||||
        getStringValue();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getStringValue = async () => {
 | 
			
		||||
    state.string.value = await redisApi.getStringValue.request({
 | 
			
		||||
        id: state.redisId,
 | 
			
		||||
        db: state.db,
 | 
			
		||||
        key: state.key.key,
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveValue = async () => {
 | 
			
		||||
    notEmpty(state.key.key, 'key不能为空');
 | 
			
		||||
 | 
			
		||||
    notEmpty(state.string.value, 'value不能为空');
 | 
			
		||||
    const sv = { value: state.string.value, id: state.redisId, db: state.db };
 | 
			
		||||
    Object.assign(sv, state.key);
 | 
			
		||||
    await redisApi.saveStringValue.request(sv);
 | 
			
		||||
    ElMessage.success('数据保存成功');
 | 
			
		||||
    cancel();
 | 
			
		||||
    emit('valChange');
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
#string-value-text {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .text-type-select {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
        right: 10px;
 | 
			
		||||
        top: 10px;
 | 
			
		||||
        max-width: 70px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										56
									
								
								mayfly_go_web/src/views/ops/redis/ViewerJson.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								mayfly_go_web/src/views/ops/redis/ViewerJson.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="text-formated-container">
 | 
			
		||||
        <monaco-editor ref="monacoEditorRef" :canChangeMode="false" v-model="state.modelValue" language="json" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, onMounted } from 'vue';
 | 
			
		||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    content: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        default: '',
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const monacoEditorRef = ref(null) as any
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    modelValue: '',
 | 
			
		||||
    content: null as any,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 因为默认从Text viewer开始,暂时不watch(保存时会触发重新格式化)。
 | 
			
		||||
// watch(
 | 
			
		||||
//     () => props.content,
 | 
			
		||||
//     (val: any) => {
 | 
			
		||||
//         setContent(val);
 | 
			
		||||
//     }
 | 
			
		||||
// );
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    setContent(props.content)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const setContent = (val: any) => {
 | 
			
		||||
    state.modelValue = val;
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        monacoEditorRef.value.format();
 | 
			
		||||
    }, 200)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getContent = () => {
 | 
			
		||||
    // 尝试压缩json
 | 
			
		||||
    try {
 | 
			
		||||
        state.content = JSON.stringify(JSON.parse(state.modelValue));
 | 
			
		||||
        return state.content;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return state.modelValue
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ getContent })
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
							
								
								
									
										41
									
								
								mayfly_go_web/src/views/ops/redis/ViewerText.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								mayfly_go_web/src/views/ops/redis/ViewerText.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <el-input type="textarea" v-model="modelValue" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { reactive, watch, toRefs, onMounted } from 'vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    content: {
 | 
			
		||||
        type: String,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const state = reactive({
 | 
			
		||||
    modelValue: '',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
    modelValue,
 | 
			
		||||
} = toRefs(state)
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.content,
 | 
			
		||||
    (val: any) => {
 | 
			
		||||
        state.modelValue = val;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    state.modelValue = props.content as any;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const getContent = () => {
 | 
			
		||||
    return state.modelValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({ getContent })
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss"></style>
 | 
			
		||||
@@ -7,20 +7,41 @@ export const redisApi = {
 | 
			
		||||
    clusterInfo: Api.newGet("/redis/{id}/cluster-info"),
 | 
			
		||||
    saveRedis: Api.newPost("/redis"),
 | 
			
		||||
    delRedis: Api.newDelete("/redis/{id}"),
 | 
			
		||||
 | 
			
		||||
    keyTtl: Api.newGet("/redis/{id}/{db}/key-ttl"),
 | 
			
		||||
    renameKey: Api.newPost("/redis/{id}/{db}/rename-key"),
 | 
			
		||||
    expireKey: Api.newPost("/redis/{id}/{db}/expire-key"),
 | 
			
		||||
    persistKey: Api.newDelete("/redis/{id}/{db}/persist-key"),
 | 
			
		||||
 | 
			
		||||
    // 获取权限列表
 | 
			
		||||
    scan: Api.newPost("/redis/{id}/{db}/scan"),
 | 
			
		||||
    getStringValue: Api.newGet("/redis/{id}/{db}/string-value"),
 | 
			
		||||
    saveStringValue: Api.newPost("/redis/{id}/{db}/string-value"),
 | 
			
		||||
    getString: Api.newGet("/redis/{id}/{db}/string-value"),
 | 
			
		||||
    setString: Api.newPost("/redis/{id}/{db}/string-value"),
 | 
			
		||||
    getHashValue: Api.newGet("/redis/{id}/{db}/hash-value"),
 | 
			
		||||
    hscan: Api.newGet("/redis/{id}/{db}/hscan"),
 | 
			
		||||
    hget: Api.newGet("/redis/{id}/{db}/hget"),
 | 
			
		||||
    hset: Api.newPost("/redis/{id}/{db}/hset"),
 | 
			
		||||
    hdel: Api.newDelete("/redis/{id}/{db}/hdel"),
 | 
			
		||||
    saveHashValue: Api.newPost("/redis/{id}/{db}/hash-value"),
 | 
			
		||||
 | 
			
		||||
    getSetValue: Api.newGet("/redis/{id}/{db}/set-value"),
 | 
			
		||||
    scard: Api.newGet("/redis/{id}/{db}/scard"),
 | 
			
		||||
    sscan: Api.newPost("/redis/{id}/{db}/sscan"),
 | 
			
		||||
    sadd: Api.newPost("/redis/{id}/{db}/sadd"),
 | 
			
		||||
    srem: Api.newPost("/redis/{id}/{db}/srem"),
 | 
			
		||||
    saveSetValue: Api.newPost("/redis/{id}/{db}/set-value"),
 | 
			
		||||
 | 
			
		||||
    del: Api.newDelete("/redis/{id}/{db}/scan/{cursor}/{count}"),
 | 
			
		||||
    delKey: Api.newDelete("/redis/{id}/{db}/key"),
 | 
			
		||||
 | 
			
		||||
    lrem: Api.newPost("/redis/{id}/{db}/lrem"),
 | 
			
		||||
    getListValue: Api.newGet("/redis/{id}/{db}/list-value"),
 | 
			
		||||
    saveListValue: Api.newPost("/redis/{id}/{db}/list-value"),
 | 
			
		||||
    setListValue: Api.newPost("/redis/{id}/{db}/list-value/lset"),
 | 
			
		||||
 | 
			
		||||
    zcard: Api.newGet("/redis/{id}/{db}/zcard"),
 | 
			
		||||
    zscan: Api.newGet("/redis/{id}/{db}/zscan"),
 | 
			
		||||
    zrevrange: Api.newGet("/redis/{id}/{db}/zrevrange"),
 | 
			
		||||
    zadd: Api.newPost("/redis/{id}/{db}/zadd"),
 | 
			
		||||
    zrem: Api.newPost("/redis/{id}/{db}/zrem"),
 | 
			
		||||
}
 | 
			
		||||
@@ -14,7 +14,7 @@ func InitCommonRouter(router *gin.RouterGroup) {
 | 
			
		||||
		// 获取公钥
 | 
			
		||||
		common.GET("public-key", func(g *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(g).
 | 
			
		||||
				WithNeedToken(false).
 | 
			
		||||
				DontNeedToken().
 | 
			
		||||
				Handle(c.RasPublicKey)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,16 @@ type Redis struct {
 | 
			
		||||
	Remark             string `json:"remark"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Rename struct {
 | 
			
		||||
	Key    string `binding:"required" json:"key"`
 | 
			
		||||
	NewKey string `binding:"required" json:"newKey"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Expire struct {
 | 
			
		||||
	Key     string `binding:"required" json:"key"`
 | 
			
		||||
	Seconds int64  `binding:"required" json:"seconds"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type KeyInfo struct {
 | 
			
		||||
	Key   string `binding:"required" json:"key"`
 | 
			
		||||
	Timed int64
 | 
			
		||||
@@ -50,3 +60,27 @@ type RedisScanForm struct {
 | 
			
		||||
	Match  string            `json:"match"`
 | 
			
		||||
	Count  int64             `json:"count"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ScanForm struct {
 | 
			
		||||
	Key    string `json:"key"`
 | 
			
		||||
	Cursor uint64 `json:"cursor"`
 | 
			
		||||
	Match  string `json:"match"`
 | 
			
		||||
	Count  int64  `json:"count"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SmemberOption struct {
 | 
			
		||||
	Key    string `json:"key"`
 | 
			
		||||
	Member any    `json:"member"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LRemOption struct {
 | 
			
		||||
	Key    string `json:"key"`
 | 
			
		||||
	Count  int64  `json:"count"`
 | 
			
		||||
	Member any    `json:"member"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ZAddOption struct {
 | 
			
		||||
	Key    string  `json:"key"`
 | 
			
		||||
	Score  float64 `json:"score"`
 | 
			
		||||
	Member any     `json:"member"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								server/internal/redis/api/hash.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								server/internal/redis/api/hash.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"mayfly-go/internal/redis/api/form"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Hscan(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	count := ginx.QueryInt(g, "count", 10)
 | 
			
		||||
	match := g.Query("match")
 | 
			
		||||
	cursor := ginx.QueryInt(g, "cursor", 0)
 | 
			
		||||
	contextTodo := context.TODO()
 | 
			
		||||
 | 
			
		||||
	cmdable := ri.GetCmdable()
 | 
			
		||||
	keys, nextCursor, err := cmdable.HScan(contextTodo, key, uint64(cursor), match, int64(count)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hcan err: %s")
 | 
			
		||||
	keySize, err := cmdable.HLen(contextTodo, key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hlen err: %s")
 | 
			
		||||
 | 
			
		||||
	rc.ResData = map[string]interface{}{
 | 
			
		||||
		"keys":    keys,
 | 
			
		||||
		"cursor":  nextCursor,
 | 
			
		||||
		"keySize": keySize,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Hdel(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	field := rc.GinCtx.Query("field")
 | 
			
		||||
 | 
			
		||||
	delRes, err := ri.GetCmdable().HDel(context.TODO(), key, field).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hdel err: %s")
 | 
			
		||||
	rc.ResData = delRes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Hget(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	field := rc.GinCtx.Query("field")
 | 
			
		||||
 | 
			
		||||
	res, err := ri.GetCmdable().HGet(context.TODO(), key, field).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hget err: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Hset(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	hashValue := new(form.HashValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, hashValue)
 | 
			
		||||
 | 
			
		||||
	hv := hashValue.Value[0]
 | 
			
		||||
	res, err := r.getRedisIns(rc).GetCmdable().HSet(context.TODO(), hashValue.Key, hv["field"].(string), hv["value"]).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hset失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetHashValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	hashValue := new(form.HashValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, hashValue)
 | 
			
		||||
 | 
			
		||||
	ri := r.getRedisIns(rc)
 | 
			
		||||
	cmd := ri.GetCmdable()
 | 
			
		||||
 | 
			
		||||
	key := hashValue.Key
 | 
			
		||||
	contextTodo := context.TODO()
 | 
			
		||||
	for _, v := range hashValue.Value {
 | 
			
		||||
		res := cmd.HSet(contextTodo, key, v["field"].(string), v["value"])
 | 
			
		||||
		biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
 | 
			
		||||
	}
 | 
			
		||||
	if hashValue.Timed != 0 && hashValue.Timed != -1 {
 | 
			
		||||
		cmd.Expire(context.TODO(), key, time.Second*time.Duration(hashValue.Timed))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										189
									
								
								server/internal/redis/api/key.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								server/internal/redis/api/key.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/internal/redis/api/form"
 | 
			
		||||
	"mayfly-go/internal/redis/api/vo"
 | 
			
		||||
	"mayfly-go/internal/redis/domain/entity"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/redis/go-redis/v9"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// scan获取redis的key列表信息
 | 
			
		||||
func (r *Redis) Scan(rc *req.Ctx) {
 | 
			
		||||
	ri := r.getRedisIns(rc)
 | 
			
		||||
 | 
			
		||||
	form := &form.RedisScanForm{}
 | 
			
		||||
	ginx.BindJsonAndValid(rc.GinCtx, form)
 | 
			
		||||
 | 
			
		||||
	cmd := ri.GetCmdable()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	kis := make([]*vo.KeyInfo, 0)
 | 
			
		||||
	var cursorRes map[string]uint64 = make(map[string]uint64)
 | 
			
		||||
 | 
			
		||||
	mode := ri.Info.Mode
 | 
			
		||||
	if mode == "" || mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel {
 | 
			
		||||
		redisAddr := ri.Cli.Options().Addr
 | 
			
		||||
		// 汇总所有的查询出来的键值
 | 
			
		||||
		var keys []string
 | 
			
		||||
		// 有通配符或空时使用scan,非模糊匹配直接匹配key
 | 
			
		||||
		if form.Match == "" || strings.ContainsAny(form.Match, "*") {
 | 
			
		||||
			cursorRes[redisAddr] = form.Cursor[redisAddr]
 | 
			
		||||
			for {
 | 
			
		||||
				ks, cursor := ri.Scan(cursorRes[redisAddr], form.Match, form.Count)
 | 
			
		||||
				cursorRes[redisAddr] = cursor
 | 
			
		||||
				if len(ks) > 0 {
 | 
			
		||||
					// 返回了数据则追加总集合中
 | 
			
		||||
					keys = append(keys, ks...)
 | 
			
		||||
				}
 | 
			
		||||
				// 匹配的数量满足用户需求退出
 | 
			
		||||
				if int32(len(keys)) >= int32(form.Count) {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				// 匹配到最后退出
 | 
			
		||||
				if cursor == 0 {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// 精确匹配
 | 
			
		||||
			keys = append(keys, form.Match)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var keyInfoSplit []string
 | 
			
		||||
		if len(keys) > 0 {
 | 
			
		||||
			keyInfosLua := `local result = {}
 | 
			
		||||
							-- KEYS[1]为第1个参数,lua数组下标从1开始
 | 
			
		||||
							for i = 1, #KEYS do
 | 
			
		||||
								local ttl = redis.call('ttl', KEYS[i]);
 | 
			
		||||
								local keyType = redis.call('type', KEYS[i]);
 | 
			
		||||
								table.insert(result, string.format("%d,%s", ttl, keyType['ok']));
 | 
			
		||||
							end;
 | 
			
		||||
							return table.concat(result, ".");`
 | 
			
		||||
			// 通过lua获取 ttl,type.ttl2,type2格式,以便下面切割获取ttl和type。避免多次调用ttl和type函数
 | 
			
		||||
			keyInfos, err := cmd.Eval(ctx, keyInfosLua, keys).Result()
 | 
			
		||||
			biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
 | 
			
		||||
			keyInfoSplit = strings.Split(keyInfos.(string), ".")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for i, k := range keys {
 | 
			
		||||
			ttlType := strings.Split(keyInfoSplit[i], ",")
 | 
			
		||||
			ttl, _ := strconv.Atoi(ttlType[0])
 | 
			
		||||
			// 没有存在该key,则跳过
 | 
			
		||||
			if ttl == -2 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
 | 
			
		||||
			kis = append(kis, ki)
 | 
			
		||||
		}
 | 
			
		||||
	} else if mode == entity.RedisModeCluster {
 | 
			
		||||
		var keys []string
 | 
			
		||||
		// 有通配符或空时使用scan,非模糊匹配直接匹配key
 | 
			
		||||
		if form.Match == "" || strings.ContainsAny(form.Match, "*") {
 | 
			
		||||
			mu := &sync.Mutex{}
 | 
			
		||||
			// 遍历所有master节点,并执行scan命令,合并keys
 | 
			
		||||
			ri.ClusterCli.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
 | 
			
		||||
				redisAddr := client.Options().Addr
 | 
			
		||||
				nowCursor := form.Cursor[redisAddr]
 | 
			
		||||
				for {
 | 
			
		||||
					ks, cursor, _ := client.Scan(ctx, nowCursor, form.Match, form.Count).Result()
 | 
			
		||||
					// 遍历节点的内部回调函数使用异步调用,如不加锁会导致集合并发错误
 | 
			
		||||
					mu.Lock()
 | 
			
		||||
					cursorRes[redisAddr] = cursor
 | 
			
		||||
					nowCursor = cursor
 | 
			
		||||
					if len(ks) > 0 {
 | 
			
		||||
						// 返回了数据则追加总集合中
 | 
			
		||||
						keys = append(keys, ks...)
 | 
			
		||||
					}
 | 
			
		||||
					mu.Unlock()
 | 
			
		||||
					// 匹配的数量满足用户需求退出
 | 
			
		||||
					if int32(len(keys)) >= int32(form.Count) {
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					// 匹配到最后退出
 | 
			
		||||
					if cursor == 0 {
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return nil
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			// 精确匹配
 | 
			
		||||
			keys = append(keys, form.Match)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 因为redis集群模式执行lua脚本key必须位于同一slot中,故单机获取的方式不适合
 | 
			
		||||
		// 使用lua获取key的ttl以及类型,减少网络调用
 | 
			
		||||
		keyInfoLua := `local ttl = redis.call('ttl', KEYS[1]);
 | 
			
		||||
					   local keyType = redis.call('type', KEYS[1]);
 | 
			
		||||
					   return string.format("%d,%s", ttl, keyType['ok'])`
 | 
			
		||||
		for _, k := range keys {
 | 
			
		||||
			keyInfo, err := cmd.Eval(ctx, keyInfoLua, []string{k}).Result()
 | 
			
		||||
			biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
 | 
			
		||||
			ttlType := strings.Split(keyInfo.(string), ",")
 | 
			
		||||
			ttl, _ := strconv.Atoi(ttlType[0])
 | 
			
		||||
			// 没有存在该key,则跳过
 | 
			
		||||
			if ttl == -2 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
 | 
			
		||||
			kis = append(kis, ki)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size, _ := cmd.DBSize(context.TODO()).Result()
 | 
			
		||||
	rc.ResData = &vo.Keys{Cursor: cursorRes, Keys: kis, DbSize: size}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) TtlKey(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	ttl, err := ri.GetCmdable().TTL(context.Background(), key).Result()
 | 
			
		||||
	biz.ErrIsNil(err, "ttl失败: %s")
 | 
			
		||||
 | 
			
		||||
	if ttl == -1 {
 | 
			
		||||
		rc.ResData = -1
 | 
			
		||||
	} else {
 | 
			
		||||
		rc.ResData = ttl.Seconds()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) DeleteKey(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> 删除key: %s", ri.Info.GetLogDesc(), key)
 | 
			
		||||
	ri.GetCmdable().Del(context.Background(), key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) RenameKey(rc *req.Ctx) {
 | 
			
		||||
	form := &form.Rename{}
 | 
			
		||||
	ginx.BindJsonAndValid(rc.GinCtx, form)
 | 
			
		||||
 | 
			
		||||
	ri := r.getRedisIns(rc)
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> 重命名key[%s] -> [%s]", ri.Info.GetLogDesc(), form.Key, form.NewKey)
 | 
			
		||||
	ri.GetCmdable().Rename(context.Background(), form.Key, form.NewKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) ExpireKey(rc *req.Ctx) {
 | 
			
		||||
	form := &form.Expire{}
 | 
			
		||||
	ginx.BindJsonAndValid(rc.GinCtx, form)
 | 
			
		||||
 | 
			
		||||
	ri := r.getRedisIns(rc)
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> 重置key[%s]过期时间为%d", ri.Info.GetLogDesc(), form.Key, form.Seconds)
 | 
			
		||||
	ri.GetCmdable().Expire(context.Background(), form.Key, time.Duration(form.Seconds)*time.Second)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除过期时间
 | 
			
		||||
func (r *Redis) PersistKey(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> 移除key[%s]的过期时间", ri.Info.GetLogDesc(), key)
 | 
			
		||||
	ri.GetCmdable().Persist(context.Background(), key)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								server/internal/redis/api/list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								server/internal/redis/api/list.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"mayfly-go/internal/redis/api/form"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Redis) GetListValue(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	cmdable := ri.GetCmdable()
 | 
			
		||||
 | 
			
		||||
	len, err := cmdable.LLen(ctx, key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取list长度失败: %s")
 | 
			
		||||
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	start := ginx.QueryInt(g, "start", 0)
 | 
			
		||||
	stop := ginx.QueryInt(g, "stop", 10)
 | 
			
		||||
	res, err := cmdable.LRange(ctx, key, int64(start), int64(stop)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取list值失败: %s")
 | 
			
		||||
 | 
			
		||||
	rc.ResData = map[string]interface{}{
 | 
			
		||||
		"len":  len,
 | 
			
		||||
		"list": res,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Lrem(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	option := new(form.LRemOption)
 | 
			
		||||
	ginx.BindJsonAndValid(g, option)
 | 
			
		||||
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
	res, err := cmd.LRem(context.TODO(), option.Key, int64(option.Count), option.Member).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "lrem失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SaveListValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	listValue := new(form.ListValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, listValue)
 | 
			
		||||
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
 | 
			
		||||
	key := listValue.Key
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	for _, v := range listValue.Value {
 | 
			
		||||
		cmd.RPush(ctx, key, v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetListValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	listSetValue := new(form.ListSetValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, listSetValue)
 | 
			
		||||
 | 
			
		||||
	ri := r.getRedisIns(rc)
 | 
			
		||||
 | 
			
		||||
	_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "list set失败: %s")
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,6 @@ package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/internal/redis/api/form"
 | 
			
		||||
	"mayfly-go/internal/redis/api/vo"
 | 
			
		||||
	"mayfly-go/internal/redis/application"
 | 
			
		||||
@@ -13,11 +12,9 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/model"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/redis/go-redis/v9"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -194,314 +191,20 @@ func (r *Redis) ClusterInfo(rc *req.Ctx) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scan获取redis的key列表信息
 | 
			
		||||
func (r *Redis) Scan(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
 | 
			
		||||
	form := &form.RedisScanForm{}
 | 
			
		||||
	ginx.BindJsonAndValid(rc.GinCtx, form)
 | 
			
		||||
 | 
			
		||||
	cmd := ri.GetCmdable()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	kis := make([]*vo.KeyInfo, 0)
 | 
			
		||||
	var cursorRes map[string]uint64 = make(map[string]uint64)
 | 
			
		||||
 | 
			
		||||
	mode := ri.Info.Mode
 | 
			
		||||
	if mode == "" || mode == entity.RedisModeStandalone || mode == entity.RedisModeSentinel {
 | 
			
		||||
		redisAddr := ri.Cli.Options().Addr
 | 
			
		||||
		// 汇总所有的查询出来的键值
 | 
			
		||||
		var keys []string
 | 
			
		||||
		// 有通配符或空时使用scan,非模糊匹配直接匹配key
 | 
			
		||||
		if form.Match == "" || strings.ContainsAny(form.Match, "*") {
 | 
			
		||||
			cursorRes[redisAddr] = form.Cursor[redisAddr]
 | 
			
		||||
			for {
 | 
			
		||||
				ks, cursor := ri.Scan(cursorRes[redisAddr], form.Match, form.Count)
 | 
			
		||||
				cursorRes[redisAddr] = cursor
 | 
			
		||||
				if len(ks) > 0 {
 | 
			
		||||
					// 返回了数据则追加总集合中
 | 
			
		||||
					keys = append(keys, ks...)
 | 
			
		||||
				}
 | 
			
		||||
				// 匹配的数量满足用户需求退出
 | 
			
		||||
				if int32(len(keys)) >= int32(form.Count) {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				// 匹配到最后退出
 | 
			
		||||
				if cursor == 0 {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// 精确匹配
 | 
			
		||||
			keys = append(keys, form.Match)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var keyInfoSplit []string
 | 
			
		||||
		if len(keys) > 0 {
 | 
			
		||||
			keyInfosLua := `local result = {}
 | 
			
		||||
							-- KEYS[1]为第1个参数,lua数组下标从1开始
 | 
			
		||||
							for i = 1, #KEYS do
 | 
			
		||||
								local ttl = redis.call('ttl', KEYS[i]);
 | 
			
		||||
								local keyType = redis.call('type', KEYS[i]);
 | 
			
		||||
								table.insert(result, string.format("%d,%s", ttl, keyType['ok']));
 | 
			
		||||
							end;
 | 
			
		||||
							return table.concat(result, ".");`
 | 
			
		||||
			// 通过lua获取 ttl,type.ttl2,type2格式,以便下面切割获取ttl和type。避免多次调用ttl和type函数
 | 
			
		||||
			keyInfos, err := cmd.Eval(ctx, keyInfosLua, keys).Result()
 | 
			
		||||
			biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
 | 
			
		||||
			keyInfoSplit = strings.Split(keyInfos.(string), ".")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for i, k := range keys {
 | 
			
		||||
			ttlType := strings.Split(keyInfoSplit[i], ",")
 | 
			
		||||
			ttl, _ := strconv.Atoi(ttlType[0])
 | 
			
		||||
			// 没有存在该key,则跳过
 | 
			
		||||
			if ttl == -2 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
 | 
			
		||||
			kis = append(kis, ki)
 | 
			
		||||
		}
 | 
			
		||||
	} else if mode == entity.RedisModeCluster {
 | 
			
		||||
		var keys []string
 | 
			
		||||
		// 有通配符或空时使用scan,非模糊匹配直接匹配key
 | 
			
		||||
		if form.Match == "" || strings.ContainsAny(form.Match, "*") {
 | 
			
		||||
			mu := &sync.Mutex{}
 | 
			
		||||
			// 遍历所有master节点,并执行scan命令,合并keys
 | 
			
		||||
			ri.ClusterCli.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
 | 
			
		||||
				redisAddr := client.Options().Addr
 | 
			
		||||
				nowCursor := form.Cursor[redisAddr]
 | 
			
		||||
				for {
 | 
			
		||||
					ks, cursor, _ := client.Scan(ctx, nowCursor, form.Match, form.Count).Result()
 | 
			
		||||
					// 遍历节点的内部回调函数使用异步调用,如不加锁会导致集合并发错误
 | 
			
		||||
					mu.Lock()
 | 
			
		||||
					cursorRes[redisAddr] = cursor
 | 
			
		||||
					nowCursor = cursor
 | 
			
		||||
					if len(ks) > 0 {
 | 
			
		||||
						// 返回了数据则追加总集合中
 | 
			
		||||
						keys = append(keys, ks...)
 | 
			
		||||
					}
 | 
			
		||||
					mu.Unlock()
 | 
			
		||||
					// 匹配的数量满足用户需求退出
 | 
			
		||||
					if int32(len(keys)) >= int32(form.Count) {
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					// 匹配到最后退出
 | 
			
		||||
					if cursor == 0 {
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return nil
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
		} else {
 | 
			
		||||
			// 精确匹配
 | 
			
		||||
			keys = append(keys, form.Match)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 因为redis集群模式执行lua脚本key必须位于同一slot中,故单机获取的方式不适合
 | 
			
		||||
		// 使用lua获取key的ttl以及类型,减少网络调用
 | 
			
		||||
		keyInfoLua := `local ttl = redis.call('ttl', KEYS[1]);
 | 
			
		||||
					   local keyType = redis.call('type', KEYS[1]);
 | 
			
		||||
					   return string.format("%d,%s", ttl, keyType['ok'])`
 | 
			
		||||
		for _, k := range keys {
 | 
			
		||||
			keyInfo, err := cmd.Eval(ctx, keyInfoLua, []string{k}).Result()
 | 
			
		||||
			biz.ErrIsNilAppendErr(err, "执行lua脚本获取key信息失败: %s")
 | 
			
		||||
			ttlType := strings.Split(keyInfo.(string), ",")
 | 
			
		||||
			ttl, _ := strconv.Atoi(ttlType[0])
 | 
			
		||||
			// 没有存在该key,则跳过
 | 
			
		||||
			if ttl == -2 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
 | 
			
		||||
			kis = append(kis, ki)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	size, _ := cmd.DBSize(context.TODO()).Result()
 | 
			
		||||
	rc.ResData = &vo.Keys{Cursor: cursorRes, Keys: kis, DbSize: size}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) DeleteKey(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	key := g.Query("key")
 | 
			
		||||
// 校验查询参数中的key为必填项,并返回redis实例
 | 
			
		||||
func (r *Redis) checkKeyAndGetRedisIns(rc *req.Ctx) (*application.RedisInstance, string) {
 | 
			
		||||
	key := rc.GinCtx.Query("key")
 | 
			
		||||
	biz.NotEmpty(key, "key不能为空")
 | 
			
		||||
	return r.getRedisIns(rc), key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
func (r *Redis) getRedisIns(rc *req.Ctx) *application.RedisInstance {
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(getIdAndDbNum(rc.GinCtx))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> 删除key: %s", ri.Info.GetLogDesc(), key)
 | 
			
		||||
	ri.GetCmdable().Del(context.Background(), key)
 | 
			
		||||
	return ri
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) checkKey(rc *req.Ctx) (*application.RedisInstance, string) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	key := g.Query("key")
 | 
			
		||||
	biz.NotEmpty(key, "key不能为空")
 | 
			
		||||
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
 | 
			
		||||
	return ri, key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) GetStringValue(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKey(rc)
 | 
			
		||||
	str, err := ri.GetCmdable().Get(context.TODO(), key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
 | 
			
		||||
	rc.ResData = str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetStringValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	keyValue := new(form.StringValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, keyValue)
 | 
			
		||||
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> %s", ri.Info.GetLogDesc(), utils.ToString(keyValue))
 | 
			
		||||
 | 
			
		||||
	str, err := ri.GetCmdable().Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
 | 
			
		||||
	rc.ResData = str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Hscan(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKey(rc)
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	count := ginx.QueryInt(g, "count", 10)
 | 
			
		||||
	match := g.Query("match")
 | 
			
		||||
	cursor := ginx.QueryInt(g, "cursor", 0)
 | 
			
		||||
	contextTodo := context.TODO()
 | 
			
		||||
 | 
			
		||||
	cmdable := ri.GetCmdable()
 | 
			
		||||
	keys, nextCursor, err := cmdable.HScan(contextTodo, key, uint64(cursor), match, int64(count)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hcan err: %s")
 | 
			
		||||
	keySize, err := cmdable.HLen(contextTodo, key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hlen err: %s")
 | 
			
		||||
 | 
			
		||||
	rc.ResData = map[string]interface{}{
 | 
			
		||||
		"keys":    keys,
 | 
			
		||||
		"cursor":  nextCursor,
 | 
			
		||||
		"keySize": keySize,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Hdel(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKey(rc)
 | 
			
		||||
	field := rc.GinCtx.Query("field")
 | 
			
		||||
 | 
			
		||||
	delRes, err := ri.GetCmdable().HDel(context.TODO(), key, field).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hdel err: %s")
 | 
			
		||||
	rc.ResData = delRes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Hget(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKey(rc)
 | 
			
		||||
	field := rc.GinCtx.Query("field")
 | 
			
		||||
 | 
			
		||||
	res, err := ri.GetCmdable().HGet(context.TODO(), key, field).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "hget err: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetHashValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	hashValue := new(form.HashValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, hashValue)
 | 
			
		||||
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
 | 
			
		||||
	cmd := ri.GetCmdable()
 | 
			
		||||
	key := hashValue.Key
 | 
			
		||||
	contextTodo := context.TODO()
 | 
			
		||||
	for _, v := range hashValue.Value {
 | 
			
		||||
		res := cmd.HSet(contextTodo, key, v["field"].(string), v["value"])
 | 
			
		||||
		biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
 | 
			
		||||
	}
 | 
			
		||||
	if hashValue.Timed != 0 && hashValue.Timed != -1 {
 | 
			
		||||
		cmd.Expire(context.TODO(), key, time.Second*time.Duration(hashValue.Timed))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) GetSetValue(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKey(rc)
 | 
			
		||||
	res, err := ri.GetCmdable().SMembers(context.TODO(), key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetSetValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	keyvalue := new(form.SetValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, keyvalue)
 | 
			
		||||
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
	cmd := ri.GetCmdable()
 | 
			
		||||
 | 
			
		||||
	key := keyvalue.Key
 | 
			
		||||
	// 简单处理->先删除,后新增
 | 
			
		||||
	cmd.Del(context.TODO(), key)
 | 
			
		||||
	cmd.SAdd(context.TODO(), key, keyvalue.Value...)
 | 
			
		||||
 | 
			
		||||
	if keyvalue.Timed != -1 {
 | 
			
		||||
		cmd.Expire(context.TODO(), key, time.Second*time.Duration(keyvalue.Timed))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) GetListValue(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKey(rc)
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	cmdable := ri.GetCmdable()
 | 
			
		||||
 | 
			
		||||
	len, err := cmdable.LLen(ctx, key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取list长度失败: %s")
 | 
			
		||||
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	start := ginx.QueryInt(g, "start", 0)
 | 
			
		||||
	stop := ginx.QueryInt(g, "stop", 10)
 | 
			
		||||
	res, err := cmdable.LRange(ctx, key, int64(start), int64(stop)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取list值失败: %s")
 | 
			
		||||
 | 
			
		||||
	rc.ResData = map[string]interface{}{
 | 
			
		||||
		"len":  len,
 | 
			
		||||
		"list": res,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SaveListValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	listValue := new(form.ListValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, listValue)
 | 
			
		||||
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
	cmd := ri.GetCmdable()
 | 
			
		||||
 | 
			
		||||
	key := listValue.Key
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	for _, v := range listValue.Value {
 | 
			
		||||
		cmd.RPush(ctx, key, v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if listValue.Timed != -1 {
 | 
			
		||||
		cmd.Expire(context.TODO(), key, time.Second*time.Duration(listValue.Timed))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetListValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	listSetValue := new(form.ListSetValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, listSetValue)
 | 
			
		||||
 | 
			
		||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db"))
 | 
			
		||||
	biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.LoginAccount.Id, ri.Info.TagPath), "%s")
 | 
			
		||||
 | 
			
		||||
	_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "list set失败: %s")
 | 
			
		||||
// 获取redis id与要操作的库号(统一路径)
 | 
			
		||||
func getIdAndDbNum(g *gin.Context) (uint64, int) {
 | 
			
		||||
	return uint64(ginx.PathParamInt(g, "id")), ginx.PathParamInt(g, "db")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								server/internal/redis/api/set.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								server/internal/redis/api/set.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"mayfly-go/internal/redis/api/form"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Redis) GetSetValue(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	res, err := ri.GetCmdable().SMembers(context.TODO(), key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetSetValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	keyvalue := new(form.SetValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, keyvalue)
 | 
			
		||||
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
 | 
			
		||||
	key := keyvalue.Key
 | 
			
		||||
	// 简单处理->先删除,后新增
 | 
			
		||||
	cmd.Del(context.TODO(), key)
 | 
			
		||||
	cmd.SAdd(context.TODO(), key, keyvalue.Value...)
 | 
			
		||||
 | 
			
		||||
	if keyvalue.Timed != -1 {
 | 
			
		||||
		cmd.Expire(context.TODO(), key, time.Second*time.Duration(keyvalue.Timed))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Scard(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
 | 
			
		||||
	total, err := ri.GetCmdable().SCard(context.TODO(), key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "scard失败: %s")
 | 
			
		||||
	rc.ResData = total
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Sscan(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	scan := new(form.ScanForm)
 | 
			
		||||
	ginx.BindJsonAndValid(g, scan)
 | 
			
		||||
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
	keys, cursor, err := cmd.SScan(context.TODO(), scan.Key, scan.Cursor, scan.Match, scan.Count).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "sscan失败: %s")
 | 
			
		||||
	rc.ResData = map[string]interface{}{
 | 
			
		||||
		"keys":   keys,
 | 
			
		||||
		"cursor": cursor,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Sadd(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	option := new(form.SmemberOption)
 | 
			
		||||
	ginx.BindJsonAndValid(g, option)
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
 | 
			
		||||
	res, err := cmd.SAdd(context.TODO(), option.Key, option.Member).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "sadd失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) Srem(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	option := new(form.SmemberOption)
 | 
			
		||||
	ginx.BindJsonAndValid(g, option)
 | 
			
		||||
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
	res, err := cmd.SRem(context.TODO(), option.Key, option.Member).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "srem失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								server/internal/redis/api/string.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								server/internal/redis/api/string.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/internal/redis/api/form"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
	"mayfly-go/pkg/utils"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Redis) GetStringValue(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	str, err := ri.GetCmdable().Get(context.TODO(), key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
 | 
			
		||||
	rc.ResData = str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) SetStringValue(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	keyValue := new(form.StringValue)
 | 
			
		||||
	ginx.BindJsonAndValid(g, keyValue)
 | 
			
		||||
 | 
			
		||||
	ri := r.getRedisIns(rc)
 | 
			
		||||
	cmd := ri.GetCmdable()
 | 
			
		||||
	rc.ReqParam = fmt.Sprintf("%s -> %s", ri.Info.GetLogDesc(), utils.ToString(keyValue))
 | 
			
		||||
 | 
			
		||||
	str, err := cmd.Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
 | 
			
		||||
	rc.ResData = str
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								server/internal/redis/api/zset.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								server/internal/redis/api/zset.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"mayfly-go/internal/redis/api/form"
 | 
			
		||||
	"mayfly-go/pkg/biz"
 | 
			
		||||
	"mayfly-go/pkg/ginx"
 | 
			
		||||
	"mayfly-go/pkg/req"
 | 
			
		||||
 | 
			
		||||
	"github.com/redis/go-redis/v9"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (r *Redis) ZCard(rc *req.Ctx) {
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
 | 
			
		||||
	total, err := ri.GetCmdable().ZCard(context.TODO(), key).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "zcard失败: %s")
 | 
			
		||||
	rc.ResData = total
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) ZScan(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
 | 
			
		||||
	cursor := uint64(ginx.QueryInt(g, "cursor", 0))
 | 
			
		||||
	match := ginx.Query(g, "match", "*")
 | 
			
		||||
	count := ginx.QueryInt(g, "count", 50)
 | 
			
		||||
 | 
			
		||||
	keys, cursor, err := ri.GetCmdable().ZScan(context.TODO(), key, cursor, match, int64(count)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "sscan失败: %s")
 | 
			
		||||
	rc.ResData = map[string]interface{}{
 | 
			
		||||
		"keys":   keys,
 | 
			
		||||
		"cursor": cursor,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) ZRevRange(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	ri, key := r.checkKeyAndGetRedisIns(rc)
 | 
			
		||||
	start := ginx.QueryInt(g, "start", 0)
 | 
			
		||||
	stop := ginx.QueryInt(g, "stop", 50)
 | 
			
		||||
 | 
			
		||||
	res, err := ri.GetCmdable().ZRevRangeWithScores(context.TODO(), key, int64(start), int64(stop)).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "ZRevRange失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) ZRem(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	option := new(form.SmemberOption)
 | 
			
		||||
	ginx.BindJsonAndValid(g, option)
 | 
			
		||||
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
	res, err := cmd.ZRem(context.TODO(), option.Key, option.Member).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "zrem失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Redis) ZAdd(rc *req.Ctx) {
 | 
			
		||||
	g := rc.GinCtx
 | 
			
		||||
	option := new(form.ZAddOption)
 | 
			
		||||
	ginx.BindJsonAndValid(g, option)
 | 
			
		||||
 | 
			
		||||
	cmd := r.getRedisIns(rc).GetCmdable()
 | 
			
		||||
	zm := redis.Z{
 | 
			
		||||
		Score:  option.Score,
 | 
			
		||||
		Member: option.Member,
 | 
			
		||||
	}
 | 
			
		||||
	res, err := cmd.ZAdd(context.TODO(), option.Key, zm).Result()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "zadd失败: %s")
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
@@ -49,15 +49,47 @@ func InitRedisRouter(router *gin.RouterGroup) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.Scan)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// 删除key
 | 
			
		||||
		deleteKeyL := req.NewLogInfo("redis-删除key").WithSave(true)
 | 
			
		||||
		deleteKeyP := req.NewPermission("redis:data:del")
 | 
			
		||||
		redis.DELETE(":id/:db/key", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).WithLog(deleteKeyL).WithRequiredPermission(deleteKeyP).Handle(rs.DeleteKey)
 | 
			
		||||
		redis.GET(":id/:db/key-ttl", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.TtlKey)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// 保存数据权限
 | 
			
		||||
		saveDataP := req.NewPermission("redis:data:save")
 | 
			
		||||
		// 删除数据权限
 | 
			
		||||
		deleteDataP := req.NewPermission("redis:data:del")
 | 
			
		||||
 | 
			
		||||
		// 删除key
 | 
			
		||||
		deleteKeyL := req.NewLogInfo("redis-删除key").WithSave(true)
 | 
			
		||||
		redis.DELETE(":id/:db/key", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(deleteKeyL).
 | 
			
		||||
				WithRequiredPermission(deleteDataP).
 | 
			
		||||
				Handle(rs.DeleteKey)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		renameKeyL := req.NewLogInfo("redis-重命名key").WithSave(true)
 | 
			
		||||
		redis.POST(":id/:db/rename-key", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(renameKeyL).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.RenameKey)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		expireKeyL := req.NewLogInfo("redis-设置key过期时间").WithSave(true)
 | 
			
		||||
		redis.POST(":id/:db/expire-key", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(expireKeyL).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.ExpireKey)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		persistKeyL := req.NewLogInfo("redis-移除key过期时间").WithSave(true)
 | 
			
		||||
		redis.DELETE(":id/:db/persist-key", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(persistKeyL).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.PersistKey)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// 获取string类型值
 | 
			
		||||
		redis.GET(":id/:db/string-value", func(c *gin.Context) {
 | 
			
		||||
@@ -67,7 +99,10 @@ func InitRedisRouter(router *gin.RouterGroup) {
 | 
			
		||||
		// 设置string类型值
 | 
			
		||||
		setStringL := req.NewLogInfo("redis-setString").WithSave(true)
 | 
			
		||||
		redis.POST(":id/:db/string-value", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).WithLog(setStringL).WithRequiredPermission(saveDataP).Handle(rs.SetStringValue)
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(setStringL).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.SetStringValue)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// hscan
 | 
			
		||||
@@ -79,25 +114,60 @@ func InitRedisRouter(router *gin.RouterGroup) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.Hget)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		hsetL := req.NewLogInfo("redis-hset").WithSave(true)
 | 
			
		||||
		redis.POST(":id/:db/hset", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(hsetL).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.Hset)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		hdelL := req.NewLogInfo("redis-hdel").WithSave(true)
 | 
			
		||||
		redis.DELETE(":id/:db/hdel", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).WithLog(hdelL).WithRequiredPermission(deleteKeyP).Handle(rs.Hdel)
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(hdelL).
 | 
			
		||||
				WithRequiredPermission(deleteDataP).
 | 
			
		||||
				Handle(rs.Hdel)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// 设置hash类型值
 | 
			
		||||
		setHashValueL := req.NewLogInfo("redis-setHashValue").WithSave(true)
 | 
			
		||||
		redis.POST(":id/:db/hash-value", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).WithLog(setHashValueL).WithRequiredPermission(saveDataP).Handle(rs.SetHashValue)
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithLog(setHashValueL).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.SetHashValue)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// 获取set类型值
 | 
			
		||||
		// set操作
 | 
			
		||||
		redis.GET(":id/:db/set-value", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.GetSetValue)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// 设置set类型值
 | 
			
		||||
		redis.POST(":id/:db/set-value", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).WithRequiredPermission(saveDataP).Handle(rs.SetSetValue)
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.SetSetValue)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.GET(":id/:db/scard", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.Scard)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.POST(":id/:db/sscan", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.Sscan)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.POST(":id/:db/sadd", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.Sadd)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.POST(":id/:db/srem", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithRequiredPermission(deleteDataP).
 | 
			
		||||
				Handle(rs.Srem)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// 获取list类型值
 | 
			
		||||
@@ -112,5 +182,36 @@ func InitRedisRouter(router *gin.RouterGroup) {
 | 
			
		||||
		redis.POST(":id/:db/list-value/lset", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.SetListValue)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.POST(":id/:db/lrem", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithRequiredPermission(deleteDataP).
 | 
			
		||||
				Handle(rs.Lrem)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// zset操作
 | 
			
		||||
		redis.GET(":id/:db/zcard", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.ZCard)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.GET(":id/:db/zscan", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.ZScan)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.GET(":id/:db/zrevrange", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).Handle(rs.ZRevRange)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.POST(":id/:db/zrem", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithRequiredPermission(deleteDataP).
 | 
			
		||||
				Handle(rs.ZRem)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		redis.POST(":id/:db/zadd", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).
 | 
			
		||||
				WithRequiredPermission(saveDataP).
 | 
			
		||||
				Handle(rs.ZAdd)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ func InitAccountRouter(router *gin.RouterGroup) {
 | 
			
		||||
		loginLog := req.NewLogInfo("用户登录").WithSave(true)
 | 
			
		||||
		account.POST("login", func(g *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(g).
 | 
			
		||||
				WithNeedToken(false).
 | 
			
		||||
				DontNeedToken().
 | 
			
		||||
				WithLog(loginLog).
 | 
			
		||||
				Handle(a.Login)
 | 
			
		||||
		})
 | 
			
		||||
@@ -35,7 +35,7 @@ func InitAccountRouter(router *gin.RouterGroup) {
 | 
			
		||||
		changePwdLog := req.NewLogInfo("用户修改密码").WithSave(true)
 | 
			
		||||
		account.POST("change-pwd", func(g *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(g).
 | 
			
		||||
				WithNeedToken(false).
 | 
			
		||||
				DontNeedToken().
 | 
			
		||||
				WithLog(changePwdLog).
 | 
			
		||||
				Handle(a.ChangePassword)
 | 
			
		||||
		})
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ func InitCaptchaRouter(router *gin.RouterGroup) {
 | 
			
		||||
	captcha := router.Group("sys/captcha")
 | 
			
		||||
	{
 | 
			
		||||
		captcha.GET("", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).WithNeedToken(false).Handle(api.GenerateCaptcha)
 | 
			
		||||
			req.NewCtxWithGin(c).DontNeedToken().Handle(api.GenerateCaptcha)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ func InitSysConfigRouter(router *gin.RouterGroup) {
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		db.GET("/value", func(c *gin.Context) {
 | 
			
		||||
			req.NewCtxWithGin(c).WithNeedToken(false).Handle(r.GetConfigValueByKey)
 | 
			
		||||
			req.NewCtxWithGin(c).DontNeedToken().Handle(r.GetConfigValueByKey)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		saveConfig := req.NewLogInfo("保存系统配置信息").WithSave(true)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import "fmt"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	AppName = "mayfly-go"
 | 
			
		||||
	Version = "v1.4.1"
 | 
			
		||||
	Version = "v1.4.2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetAppInfo() string {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,15 @@ func GetPageParam(g *gin.Context) *model.PageParam {
 | 
			
		||||
	return &model.PageParam{PageNum: QueryInt(g, "pageNum", 1), PageSize: QueryInt(g, "pageSize", 10)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取查询参数,不存在则返回默认值
 | 
			
		||||
func Query(g *gin.Context, qm string, defaultStr string) string {
 | 
			
		||||
	qv := g.Query(qm)
 | 
			
		||||
	if qv == "" {
 | 
			
		||||
		return defaultStr
 | 
			
		||||
	}
 | 
			
		||||
	return qv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取查询参数中指定参数值,并转为int
 | 
			
		||||
func QueryInt(g *gin.Context, qm string, defaultInt int) int {
 | 
			
		||||
	qv := g.Query(qm)
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,7 @@ func GetByIdIn(model interface{}, list interface{}, ids []uint64, orderBy ...str
 | 
			
		||||
	global.Db.Model(model).Where("id in (?)", ids).Order(orderByStr).Find(list)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id列表查询
 | 
			
		||||
// 根据map指定条件查询列表
 | 
			
		||||
func SelectByMap(model interface{}, list interface{}, where map[string]interface{}, orderBy ...string) {
 | 
			
		||||
	var orderByStr string
 | 
			
		||||
	if orderBy == nil {
 | 
			
		||||
@@ -94,7 +94,7 @@ func SelectByMap(model interface{}, list interface{}, where map[string]interface
 | 
			
		||||
	global.Db.Model(model).Where(where).Order(orderByStr).Find(list)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id列表查询
 | 
			
		||||
// 根据model指定条件统计数量
 | 
			
		||||
func CountBy(model interface{}) int64 {
 | 
			
		||||
	var count int64
 | 
			
		||||
	global.Db.Model(model).Where(model).Count(&count)
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,6 @@ func NewPermission(code string) *Permission {
 | 
			
		||||
	return &Permission{NeedToken: true, Code: code}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Permission) WithNeedToken(needToken bool) *Permission {
 | 
			
		||||
	p.NeedToken = needToken
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	permissionCodeRegistry PermissionCodeRegistry
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -80,8 +80,8 @@ func (r *Ctx) WithRequiredPermission(permission *Permission) *Ctx {
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 是否需要token
 | 
			
		||||
func (r *Ctx) WithNeedToken(needToken bool) *Ctx {
 | 
			
		||||
// 不需要token校验
 | 
			
		||||
func (r *Ctx) DontNeedToken() *Ctx {
 | 
			
		||||
	r.RequiredPermission = &Permission{NeedToken: false}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user