mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 00:10:25 +08:00 
			
		
		
		
	feat: 新增pgsql数据操作&redis集群操作
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
# 🌈mayfly-go
 | 
					# 🌈mayfly-go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 介绍
 | 
					### 介绍
 | 
				
			||||||
简单基于DDD(领域驱动设计)分层架构实现的web版 **linux、数据库(mysql)、redis、mongo统一管理操作平台**
 | 
					简单基于DDD(领域驱动设计)分层架构实现的web版 **linux、数据库(mysql postgres)、redis(单机 集群)、mongo统一管理操作平台**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 开发语言与主要框架
 | 
					### 开发语言与主要框架
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@
 | 
				
			|||||||
    "countup.js": "^2.0.7",
 | 
					    "countup.js": "^2.0.7",
 | 
				
			||||||
    "cropperjs": "^1.5.11",
 | 
					    "cropperjs": "^1.5.11",
 | 
				
			||||||
    "echarts": "^5.3.3",
 | 
					    "echarts": "^5.3.3",
 | 
				
			||||||
    "element-plus": "^2.2.8",
 | 
					    "element-plus": "^2.2.9",
 | 
				
			||||||
    "jsoneditor": "^9.9.0",
 | 
					    "jsoneditor": "^9.9.0",
 | 
				
			||||||
    "lodash": "^4.17.21",
 | 
					    "lodash": "^4.17.21",
 | 
				
			||||||
    "mitt": "^3.0.0",
 | 
					    "mitt": "^3.0.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="35%">
 | 
					        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
 | 
				
			||||||
            <el-form :model="form" ref="dbForm" :rules="rules" label-width="85px">
 | 
					            <el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
 | 
				
			||||||
                <el-form-item prop="projectId" label="项目:" required>
 | 
					                <el-form-item prop="projectId" label="项目:" required>
 | 
				
			||||||
                    <el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
 | 
					                    <el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>
 | 
				
			||||||
                        <el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
 | 
					                        <el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
 | 
				
			||||||
@@ -19,6 +19,7 @@
 | 
				
			|||||||
                <el-form-item prop="type" label="类型:" required>
 | 
					                <el-form-item prop="type" label="类型:" required>
 | 
				
			||||||
                    <el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
 | 
					                    <el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
 | 
				
			||||||
                        <el-option key="item.id" label="mysql" value="mysql"> </el-option>
 | 
					                        <el-option key="item.id" label="mysql" value="mysql"> </el-option>
 | 
				
			||||||
 | 
					                        <el-option key="item.id" label="postgres" value="postgres"> </el-option>
 | 
				
			||||||
                    </el-select>
 | 
					                    </el-select>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="host" label="host:" required>
 | 
					                <el-form-item prop="host" label="host:" required>
 | 
				
			||||||
@@ -39,6 +40,9 @@
 | 
				
			|||||||
                        autocomplete="new-password"
 | 
					                        autocomplete="new-password"
 | 
				
			||||||
                    ></el-input>
 | 
					                    ></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="params" label="连接参数:">
 | 
				
			||||||
 | 
					                    <el-input v-model="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="database" label="数据库名:" required>
 | 
					                <el-form-item prop="database" label="数据库名:" required>
 | 
				
			||||||
                    <el-tag
 | 
					                    <el-tag
 | 
				
			||||||
                        v-for="db in databaseList"
 | 
					                        v-for="db in databaseList"
 | 
				
			||||||
@@ -116,6 +120,7 @@ export default defineComponent({
 | 
				
			|||||||
                port: 3306,
 | 
					                port: 3306,
 | 
				
			||||||
                username: null,
 | 
					                username: null,
 | 
				
			||||||
                password: null,
 | 
					                password: null,
 | 
				
			||||||
 | 
					                params: null,
 | 
				
			||||||
                database: '',
 | 
					                database: '',
 | 
				
			||||||
                project: null,
 | 
					                project: null,
 | 
				
			||||||
                projectId: null,
 | 
					                projectId: null,
 | 
				
			||||||
@@ -173,13 +178,6 @@ export default defineComponent({
 | 
				
			|||||||
                        trigger: ['change', 'blur'],
 | 
					                        trigger: ['change', 'blur'],
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
                // password: [
 | 
					 | 
				
			||||||
                //     {
 | 
					 | 
				
			||||||
                //         required: true,
 | 
					 | 
				
			||||||
                //         message: '请输入密码',
 | 
					 | 
				
			||||||
                //         trigger: ['change', 'blur'],
 | 
					 | 
				
			||||||
                //     },
 | 
					 | 
				
			||||||
                // ],
 | 
					 | 
				
			||||||
                database: [
 | 
					                database: [
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        required: true,
 | 
					                        required: true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,15 +20,15 @@
 | 
				
			|||||||
                        </el-radio>
 | 
					                        </el-radio>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
                <el-table-column prop="project" label="项目" min-width="100"></el-table-column>
 | 
					                <el-table-column prop="project" label="项目" min-width="100" show-overflow-tooltip></el-table-column>
 | 
				
			||||||
                <el-table-column prop="env" label="环境" min-width="100"></el-table-column>
 | 
					                <el-table-column prop="env" label="环境" min-width="100"></el-table-column>
 | 
				
			||||||
                <el-table-column prop="name" label="名称" min-width="200"></el-table-column>
 | 
					                <el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
 | 
				
			||||||
                <el-table-column min-width="160" label="host:port">
 | 
					                <el-table-column min-width="170" label="host:port" show-overflow-tooltip>
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        {{ `${scope.row.host}:${scope.row.port}` }}
 | 
					                        {{ `${scope.row.host}:${scope.row.port}` }}
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
                <el-table-column prop="type" label="类型" min-width="80"></el-table-column>
 | 
					                <el-table-column prop="type" label="类型" min-width="90"></el-table-column>
 | 
				
			||||||
                <el-table-column prop="database" label="数据库" min-width="160">
 | 
					                <el-table-column prop="database" label="数据库" min-width="160">
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        <el-tag
 | 
					                        <el-tag
 | 
				
			||||||
@@ -224,6 +224,7 @@
 | 
				
			|||||||
                <el-table-column prop="columnName" label="列名" show-overflow-tooltip> </el-table-column>
 | 
					                <el-table-column prop="columnName" label="列名" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                <el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
 | 
					                <el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                <el-table-column prop="indexType" label="类型"> </el-table-column>
 | 
					                <el-table-column prop="indexType" label="类型"> </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="indexComment" label="备注" min-width="230" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
        </el-dialog>
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -276,6 +276,7 @@ export default defineComponent({
 | 
				
			|||||||
            dbs: [], // 数据库实例列表
 | 
					            dbs: [], // 数据库实例列表
 | 
				
			||||||
            databaseList: [], // 数据库实例拥有的数据库列表,1数据库实例  -> 多数据库
 | 
					            databaseList: [], // 数据库实例拥有的数据库列表,1数据库实例  -> 多数据库
 | 
				
			||||||
            db: '', // 当前操作的数据库
 | 
					            db: '', // 当前操作的数据库
 | 
				
			||||||
 | 
					            dbType: '',
 | 
				
			||||||
            tables: [],
 | 
					            tables: [],
 | 
				
			||||||
            dbId: null, // 当前选中操作的数据库实例
 | 
					            dbId: null, // 当前选中操作的数据库实例
 | 
				
			||||||
            tableName: '',
 | 
					            tableName: '',
 | 
				
			||||||
@@ -622,7 +623,9 @@ export default defineComponent({
 | 
				
			|||||||
         */
 | 
					         */
 | 
				
			||||||
        const changeDbInstance = (dbId: any) => {
 | 
					        const changeDbInstance = (dbId: any) => {
 | 
				
			||||||
            state.db = '';
 | 
					            state.db = '';
 | 
				
			||||||
            state.databaseList = (state.dbs.find((e: any) => e.id == dbId) as any).database.split(' ');
 | 
					            const dbInfo = state.dbs.find((e: any) => e.id == dbId) as any;
 | 
				
			||||||
 | 
					            state.dbType = dbInfo.type;
 | 
				
			||||||
 | 
					            state.databaseList = dbInfo.database.split(' ');
 | 
				
			||||||
            clearDb();
 | 
					            clearDb();
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -788,16 +791,21 @@ export default defineComponent({
 | 
				
			|||||||
         * 获取默认查询语句
 | 
					         * 获取默认查询语句
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        const getDefaultSelectSql = (tableName: string, where: string = '', orderBy: string = '', pageNum: number = 1) => {
 | 
					        const getDefaultSelectSql = (tableName: string, where: string = '', orderBy: string = '', pageNum: number = 1) => {
 | 
				
			||||||
            return `SELECT * FROM \`${tableName}\` ${where ? 'WHERE ' + where : ''} ${orderBy ? orderBy : ''} LIMIT ${
 | 
					            const baseSql = `SELECT * FROM ${tableName} ${where ? 'WHERE ' + where : ''} ${orderBy ? orderBy : ''}`;
 | 
				
			||||||
                (pageNum - 1) * state.defalutLimit
 | 
					            if (state.dbType == 'mysql') {
 | 
				
			||||||
            }, ${state.defalutLimit}`;
 | 
					                 return `${baseSql} LIMIT ${(pageNum - 1) * state.defalutLimit}, ${state.defalutLimit};`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (state.dbType == 'postgres') {
 | 
				
			||||||
 | 
					                return `${baseSql} OFFSET ${(pageNum - 1) * state.defalutLimit} LIMIT ${state.defalutLimit};`
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return baseSql;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 获取默认查询统计语句
 | 
					         * 获取默认查询统计语句
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        const getDefaultCountSql = (tableName: string, where: string = '') => {
 | 
					        const getDefaultCountSql = (tableName: string, where: string = '') => {
 | 
				
			||||||
            return `SELECT COUNT(*) count FROM \`${tableName}\` ${where ? 'WHERE ' + where : ''}`;
 | 
					            return `SELECT COUNT(*) count FROM ${tableName} ${where ? 'WHERE ' + where : ''}`;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
@@ -819,7 +827,7 @@ export default defineComponent({
 | 
				
			|||||||
            const tableName = state.activeName;
 | 
					            const tableName = state.activeName;
 | 
				
			||||||
            const sortType = sort.order == 'descending' ? 'DESC' : 'ASC';
 | 
					            const sortType = sort.order == 'descending' ? 'DESC' : 'ASC';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const orderBy = `ORDER BY \`${sort.prop}\` ${sortType}`;
 | 
					            const orderBy = `ORDER BY ${sort.prop} ${sortType}`;
 | 
				
			||||||
            state.dataTabs[state.activeName].orderBy = orderBy;
 | 
					            state.dataTabs[state.activeName].orderBy = orderBy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            onRefresh(tableName);
 | 
					            onRefresh(tableName);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@
 | 
				
			|||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="params" label="参数">
 | 
					                <el-form-item prop="params" label="参数">
 | 
				
			||||||
                    <el-input v-model.trim="form.params" placeholder="参数数组json,若无可不填"></el-input>
 | 
					                    <el-input v-model="form.params" placeholder="参数数组json,若无可不填"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <el-form-item prop="script" label="内容" id="content">
 | 
					                <el-form-item prop="script" label="内容" id="content">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@
 | 
				
			|||||||
                                ></el-input>
 | 
					                                ></el-input>
 | 
				
			||||||
                            </el-form-item>
 | 
					                            </el-form-item>
 | 
				
			||||||
                            <el-form-item label="count" label-width="60px">
 | 
					                            <el-form-item label="count" label-width="60px">
 | 
				
			||||||
                                <el-input placeholder="count" style="width: 62px" v-model="scanParam.count"></el-input>
 | 
					                                <el-input placeholder="count" style="width: 62px" v-model.number="scanParam.count"></el-input>
 | 
				
			||||||
                            </el-form-item>
 | 
					                            </el-form-item>
 | 
				
			||||||
                            <el-form-item>
 | 
					                            <el-form-item>
 | 
				
			||||||
                                <el-button @click="searchKey()" type="success" icon="search" plain></el-button>
 | 
					                                <el-button @click="searchKey()" type="success" icon="search" plain></el-button>
 | 
				
			||||||
@@ -92,7 +92,7 @@ import { toRefs, reactive, defineComponent } from 'vue';
 | 
				
			|||||||
import { ElMessage, ElMessageBox } from 'element-plus';
 | 
					import { ElMessage, ElMessageBox } from 'element-plus';
 | 
				
			||||||
import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
 | 
					import ProjectEnvSelect from '../component/ProjectEnvSelect.vue';
 | 
				
			||||||
import DataEdit from './DataEdit.vue';
 | 
					import DataEdit from './DataEdit.vue';
 | 
				
			||||||
import { isTrue, notNull } from '@/common/assert';
 | 
					import { isTrue, notBlank, notNull } from '@/common/assert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
    name: 'DataOperation',
 | 
					    name: 'DataOperation',
 | 
				
			||||||
@@ -103,18 +103,15 @@ export default defineComponent({
 | 
				
			|||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
        const state = reactive({
 | 
					        const state = reactive({
 | 
				
			||||||
            loading: false,
 | 
					            loading: false,
 | 
				
			||||||
            cluster: 0,
 | 
					 | 
				
			||||||
            redisList: [],
 | 
					            redisList: [],
 | 
				
			||||||
            query: {
 | 
					            query: {
 | 
				
			||||||
                envId: 0,
 | 
					                envId: 0,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            scanParam: {
 | 
					            scanParam: {
 | 
				
			||||||
                id: null,
 | 
					                id: null,
 | 
				
			||||||
                cluster: 0,
 | 
					 | 
				
			||||||
                match: null,
 | 
					                match: null,
 | 
				
			||||||
                count: 10,
 | 
					                count: 10,
 | 
				
			||||||
                cursor: 0,
 | 
					                cursor: {},
 | 
				
			||||||
                prevCursor: null,
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            valueDialog: {
 | 
					            valueDialog: {
 | 
				
			||||||
                visible: false,
 | 
					                visible: false,
 | 
				
			||||||
@@ -151,31 +148,33 @@ export default defineComponent({
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const changeRedis = () => {
 | 
					        const changeRedis = (id: number) => {
 | 
				
			||||||
            resetScanParam();
 | 
					            resetScanParam(id);
 | 
				
			||||||
            state.keys = [];
 | 
					            state.keys = [];
 | 
				
			||||||
            state.dbsize = 0;
 | 
					            state.dbsize = 0;
 | 
				
			||||||
            searchKey();
 | 
					            searchKey();
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const scan = () => {
 | 
					        const scan = async () => {
 | 
				
			||||||
            isTrue(state.scanParam.id != null, '请先选择redis');
 | 
					            isTrue(state.scanParam.id != null, '请先选择redis');
 | 
				
			||||||
 | 
					            notBlank(state.scanParam.count, 'count不能为空');
 | 
				
			||||||
            isTrue(state.scanParam.count < 20001, 'count不能超过20000');
 | 
					            isTrue(state.scanParam.count < 20001, 'count不能超过20000');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            state.loading = true;
 | 
					            state.loading = true;
 | 
				
			||||||
            state.scanParam.cluster = state.cluster == 0 ? 0 : 1;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            redisApi.scan.request(state.scanParam).then((res) => {
 | 
					            try {
 | 
				
			||||||
 | 
					                const res = await redisApi.scan.request(state.scanParam);
 | 
				
			||||||
                state.keys = res.keys;
 | 
					                state.keys = res.keys;
 | 
				
			||||||
                state.dbsize = res.dbSize;
 | 
					                state.dbsize = res.dbSize;
 | 
				
			||||||
                state.scanParam.cursor = res.cursor;
 | 
					                state.scanParam.cursor = res.cursor;
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
                state.loading = false;
 | 
					                state.loading = false;
 | 
				
			||||||
            });
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const searchKey = () => {
 | 
					        const searchKey = async () => {
 | 
				
			||||||
            state.scanParam.cursor = 0;
 | 
					            state.scanParam.cursor = {};
 | 
				
			||||||
            scan();
 | 
					            await scan();
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const clearRedis = () => {
 | 
					        const clearRedis = () => {
 | 
				
			||||||
@@ -193,10 +192,17 @@ export default defineComponent({
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const resetScanParam = () => {
 | 
					        const resetScanParam = (id: number = 0) => {
 | 
				
			||||||
            state.scanParam.match = null;
 | 
					 | 
				
			||||||
            state.scanParam.cursor = 0;
 | 
					 | 
				
			||||||
            state.scanParam.count = 10;
 | 
					            state.scanParam.count = 10;
 | 
				
			||||||
 | 
					            if (id != 0) {
 | 
				
			||||||
 | 
					                const redis: any = state.redisList.find((x: any) => x.id == id);
 | 
				
			||||||
 | 
					                // 集群模式count设小点,因为后端会从所有master节点scan一遍然后合并结果
 | 
				
			||||||
 | 
					                if (redis && redis.mode == 'cluster') {
 | 
				
			||||||
 | 
					                    state.scanParam.count = 5;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            state.scanParam.match = null;
 | 
				
			||||||
 | 
					            state.scanParam.cursor = {};
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const getValue = async (row: any) => {
 | 
					        const getValue = async (row: any) => {
 | 
				
			||||||
@@ -204,11 +210,9 @@ export default defineComponent({
 | 
				
			|||||||
            const key = row.key;
 | 
					            const key = row.key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let res: any;
 | 
					            let res: any;
 | 
				
			||||||
            const id = state.cluster == 0 ? state.scanParam.id : state.cluster;
 | 
					 | 
				
			||||||
            const reqParam = {
 | 
					            const reqParam = {
 | 
				
			||||||
                cluster: state.cluster,
 | 
					 | 
				
			||||||
                key: row.key,
 | 
					                key: row.key,
 | 
				
			||||||
                id,
 | 
					                id: state.scanParam.id,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            switch (type) {
 | 
					            switch (type) {
 | 
				
			||||||
                case 'string':
 | 
					                case 'string':
 | 
				
			||||||
@@ -260,29 +264,27 @@ export default defineComponent({
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const del = (key: string) => {
 | 
					        const del = (key: string) => {
 | 
				
			||||||
            ElMessageBox.confirm(`此操作将删除对应的key , 是否继续?`, '提示', {
 | 
					            ElMessageBox.confirm(`确定删除[ ${key} ] 该key?`, '提示', {
 | 
				
			||||||
                confirmButtonText: '确定',
 | 
					                confirmButtonText: '确定',
 | 
				
			||||||
                cancelButtonText: '取消',
 | 
					                cancelButtonText: '取消',
 | 
				
			||||||
                type: 'warning',
 | 
					                type: 'warning',
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
                .then(() => {
 | 
					                .then(() => {
 | 
				
			||||||
                    let id = state.cluster == 0 ? state.scanParam.id : state.cluster;
 | 
					 | 
				
			||||||
                    redisApi.delKey
 | 
					                    redisApi.delKey
 | 
				
			||||||
                        .request({
 | 
					                        .request({
 | 
				
			||||||
                            cluster: state.cluster,
 | 
					 | 
				
			||||||
                            key,
 | 
					                            key,
 | 
				
			||||||
                            id,
 | 
					                            id: state.scanParam.id,
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                        .then(() => {
 | 
					                        .then(() => {
 | 
				
			||||||
                            ElMessage.success('删除成功!');
 | 
					                            ElMessage.success('删除成功!');
 | 
				
			||||||
                            scan();
 | 
					                            searchKey();
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                .catch(() => {});
 | 
					                .catch(() => {});
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const ttlConveter = (ttl: any) => {
 | 
					        const ttlConveter = (ttl: any) => {
 | 
				
			||||||
            if (ttl == -1) {
 | 
					            if (ttl == -1 || ttl == 0) {
 | 
				
			||||||
                return '永久';
 | 
					                return '永久';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!ttl) {
 | 
					            if (!ttl) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,8 +13,14 @@
 | 
				
			|||||||
                        <el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
 | 
					                        <el-option v-for="item in envs" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
 | 
				
			||||||
                    </el-select>
 | 
					                    </el-select>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="mode" label="mode:" required>
 | 
				
			||||||
 | 
					                    <el-select style="width: 100%" v-model="form.mode" placeholder="请选择模式">
 | 
				
			||||||
 | 
					                        <el-option label="standalone" value="standalone"> </el-option>
 | 
				
			||||||
 | 
					                        <el-option label="cluster" value="cluster"> </el-option>
 | 
				
			||||||
 | 
					                    </el-select>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="host" label="host:" required>
 | 
					                <el-form-item prop="host" label="host:" required>
 | 
				
			||||||
                    <el-input v-model.trim="form.host" placeholder="请输入host:port" auto-complete="off"></el-input>
 | 
					                    <el-input v-model.trim="form.host" placeholder="请输入host:port,集群模式用','分割" auto-complete="off" type="textarea"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <el-form-item prop="password" label="密码:">
 | 
					                <el-form-item prop="password" label="密码:">
 | 
				
			||||||
                    <el-input
 | 
					                    <el-input
 | 
				
			||||||
@@ -28,6 +34,9 @@
 | 
				
			|||||||
                <el-form-item prop="db" label="库号:" required>
 | 
					                <el-form-item prop="db" label="库号:" required>
 | 
				
			||||||
                    <el-input v-model.number="form.db" placeholder="请输入库号"></el-input>
 | 
					                    <el-input v-model.number="form.db" placeholder="请输入库号"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
 | 
					                <el-form-item prop="remark" label="备注:">
 | 
				
			||||||
 | 
					                    <el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
 | 
				
			||||||
 | 
					                </el-form-item>
 | 
				
			||||||
            </el-form>
 | 
					            </el-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <template #footer>
 | 
					            <template #footer>
 | 
				
			||||||
@@ -71,12 +80,14 @@ export default defineComponent({
 | 
				
			|||||||
            form: {
 | 
					            form: {
 | 
				
			||||||
                id: null,
 | 
					                id: null,
 | 
				
			||||||
                name: null,
 | 
					                name: null,
 | 
				
			||||||
 | 
					                mode: "standalone",
 | 
				
			||||||
                host: null,
 | 
					                host: null,
 | 
				
			||||||
                password: null,
 | 
					                password: null,
 | 
				
			||||||
                project: null,
 | 
					                project: null,
 | 
				
			||||||
                projectId: null,
 | 
					                projectId: null,
 | 
				
			||||||
                envId: null,
 | 
					                envId: null,
 | 
				
			||||||
                env: null,
 | 
					                env: null,
 | 
				
			||||||
 | 
					                remark: "",
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            btnLoading: false,
 | 
					            btnLoading: false,
 | 
				
			||||||
            rules: {
 | 
					            rules: {
 | 
				
			||||||
@@ -108,6 +119,13 @@ export default defineComponent({
 | 
				
			|||||||
                        trigger: ['change', 'blur'],
 | 
					                        trigger: ['change', 'blur'],
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
 | 
					                mode: [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        required: true,
 | 
				
			||||||
 | 
					                        message: '请输入模式',
 | 
				
			||||||
 | 
					                        trigger: ['change', 'blur'],
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,16 +5,12 @@
 | 
				
			|||||||
            <el-button type="primary" icon="edit" :disabled="currentId == null" @click="editRedis(false)" plain>编辑</el-button>
 | 
					            <el-button type="primary" icon="edit" :disabled="currentId == null" @click="editRedis(false)" plain>编辑</el-button>
 | 
				
			||||||
            <el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除</el-button>
 | 
					            <el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除</el-button>
 | 
				
			||||||
            <div style="float: right">
 | 
					            <div style="float: right">
 | 
				
			||||||
                <!-- <el-input placeholder="host"  style="width: 140px" v-model="query.host" @clear="search" plain clearable></el-input>
 | 
					 | 
				
			||||||
                <el-select v-model="params.clusterId"  clearable placeholder="集群选择">
 | 
					 | 
				
			||||||
                    <el-option v-for="item in clusters" :key="item.id" :value="item.id" :label="item.name"></el-option>
 | 
					 | 
				
			||||||
                </el-select> -->
 | 
					 | 
				
			||||||
                <el-select v-model="query.projectId" placeholder="请选择项目" filterable clearable>
 | 
					                <el-select v-model="query.projectId" placeholder="请选择项目" filterable clearable>
 | 
				
			||||||
                    <el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
 | 
					                    <el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
 | 
				
			||||||
                </el-select>
 | 
					                </el-select>
 | 
				
			||||||
                <el-button class="ml5" @click="search" type="success" icon="search"></el-button>
 | 
					                <el-button class="ml5" @click="search" type="success" icon="search"></el-button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <el-table :data="redisTable" style="width: 100%" @current-change="choose" stripe>
 | 
					            <el-table :data="redisTable" @current-change="choose" stripe>
 | 
				
			||||||
                <el-table-column label="选择" width="60px">
 | 
					                <el-table-column label="选择" width="60px">
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        <el-radio v-model="currentId" :label="scope.row.id">
 | 
					                        <el-radio v-model="currentId" :label="scope.row.id">
 | 
				
			||||||
@@ -22,19 +18,23 @@
 | 
				
			|||||||
                        </el-radio>
 | 
					                        </el-radio>
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
                <el-table-column prop="project" label="项目" width></el-table-column>
 | 
					                <el-table-column prop="project" label="项目" min-width="100"></el-table-column>
 | 
				
			||||||
                <el-table-column prop="env" label="环境" width></el-table-column>
 | 
					                <el-table-column prop="env" label="环境" min-width="100"></el-table-column>
 | 
				
			||||||
                <el-table-column prop="host" label="host:port" width></el-table-column>
 | 
					                <el-table-column prop="host" label="host:port" min-width="150" show-overflow-tooltip> </el-table-column>
 | 
				
			||||||
                <el-table-column prop="createTime" label="创建时间">
 | 
					                <el-table-column prop="mode" label="mode" min-width="100"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="remark" label="备注" min-width="100"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="createTime" label="创建时间" min-width="160">
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        {{ $filters.dateFormat(scope.row.createTime) }}
 | 
					                        {{ $filters.dateFormat(scope.row.createTime) }}
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
                <el-table-column prop="creator" label="创建人"></el-table-column>
 | 
					                <el-table-column prop="creator" label="创建人" min-width="100"></el-table-column>
 | 
				
			||||||
                <el-table-column label="操作" width>
 | 
					                <el-table-column label="更多" min-width="130" fixed="right">
 | 
				
			||||||
                    <template #default="scope">
 | 
					                    <template #default="scope">
 | 
				
			||||||
                        <el-button type="primary" @click="info(scope.row)" icon="tickets" plain size="small">info</el-button>
 | 
					                        <el-link v-if="scope.row.mode == 'standalone'" type="primary" @click="info(scope.row)" :underline="false">单机信息</el-link>
 | 
				
			||||||
                        <!-- <el-button type="success" @click="manage(scope.row)" :ref="scope.row"  plain>数据管理</el-button> -->
 | 
					                        <el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode == 'cluster'" type="success" :underline="false"
 | 
				
			||||||
 | 
					                            >集群信息</el-link
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </el-table-column>
 | 
					                </el-table-column>
 | 
				
			||||||
            </el-table>
 | 
					            </el-table>
 | 
				
			||||||
@@ -52,6 +52,84 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        <info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info>
 | 
					        <info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <el-dialog width="1000px" title="集群信息" v-model="clusterInfoDialog.visible">
 | 
				
			||||||
 | 
					            <el-input type="textarea" :autosize="{ minRows: 12, maxRows: 12 }" v-model="clusterInfoDialog.info"> </el-input>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <el-divider content-position="left">节点信息</el-divider>
 | 
				
			||||||
 | 
					            <el-table :data="clusterInfoDialog.nodes" stripe size="small" border>
 | 
				
			||||||
 | 
					                <el-table-column prop="nodeId" label="nodeId" min-width="300">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        nodeId
 | 
				
			||||||
 | 
					                        <el-tooltip class="box-item" effect="dark" content="节点id" placement="top">
 | 
				
			||||||
 | 
					                            <el-icon><question-filled /></el-icon>
 | 
				
			||||||
 | 
					                        </el-tooltip>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="ip" label="ip" min-width="180">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        ip
 | 
				
			||||||
 | 
					                        <el-tooltip
 | 
				
			||||||
 | 
					                            class="box-item"
 | 
				
			||||||
 | 
					                            effect="dark"
 | 
				
			||||||
 | 
					                            content="ip:port1@port2:port1指redis服务器与客户端通信的端口,port2则是集群内部节点间通信的端口"
 | 
				
			||||||
 | 
					                            placement="top"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <el-icon><question-filled /></el-icon>
 | 
				
			||||||
 | 
					                        </el-tooltip>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        <el-tag
 | 
				
			||||||
 | 
					                            @click="info({ id: clusterInfoDialog.redisId, ip: scope.row.ip })"
 | 
				
			||||||
 | 
					                            effect="plain"
 | 
				
			||||||
 | 
					                            type="success"
 | 
				
			||||||
 | 
					                            size="small"
 | 
				
			||||||
 | 
					                            style="cursor: pointer"
 | 
				
			||||||
 | 
					                            >{{ scope.row.ip }}</el-tag
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="flags" label="flags" min-width="110"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="masterSlaveRelation" label="masterSlaveRelation" min-width="300">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        masterSlaveRelation
 | 
				
			||||||
 | 
					                        <el-tooltip
 | 
				
			||||||
 | 
					                            class="box-item"
 | 
				
			||||||
 | 
					                            effect="dark"
 | 
				
			||||||
 | 
					                            content="如果节点是slave,并且已知master节点,则为master节点ID;否则为符号'-'"
 | 
				
			||||||
 | 
					                            placement="top"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <el-icon><question-filled /></el-icon>
 | 
				
			||||||
 | 
					                        </el-tooltip>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="pingSent" label="pingSent" min-width="130" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        {{ scope.row.pingSent == 0 ? 0 : new Date(parseInt(scope.row.pingSent)).toLocaleString() }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="pongRecv" label="pongRecv" min-width="130" show-overflow-tooltip>
 | 
				
			||||||
 | 
					                    <template #default="scope">
 | 
				
			||||||
 | 
					                        {{ scope.row.pongRecv == 0 ? 0 : new Date(parseInt(scope.row.pongRecv)).toLocaleString() }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="configEpoch" label="configEpoch" min-width="130">
 | 
				
			||||||
 | 
					                    <template #header>
 | 
				
			||||||
 | 
					                        configEpoch
 | 
				
			||||||
 | 
					                        <el-tooltip
 | 
				
			||||||
 | 
					                            class="box-item"
 | 
				
			||||||
 | 
					                            effect="dark"
 | 
				
			||||||
 | 
					                            content="节点的epoch值(如果该节点是从节点,则为其主节点的epoch值)。每当节点发生失败切换时,都会创建一个新的,独特的,递增的epoch。"
 | 
				
			||||||
 | 
					                            placement="top"
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                            <el-icon><question-filled /></el-icon>
 | 
				
			||||||
 | 
					                        </el-tooltip>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="linkState" label="linkState" min-width="100"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="slot" label="slot" min-width="100"></el-table-column>
 | 
				
			||||||
 | 
					            </el-table>
 | 
				
			||||||
 | 
					        </el-dialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <redis-edit
 | 
					        <redis-edit
 | 
				
			||||||
            @val-change="valChange"
 | 
					            @val-change="valChange"
 | 
				
			||||||
            :projects="projects"
 | 
					            :projects="projects"
 | 
				
			||||||
@@ -92,6 +170,12 @@ export default defineComponent({
 | 
				
			|||||||
            redisInfo: {
 | 
					            redisInfo: {
 | 
				
			||||||
                url: '',
 | 
					                url: '',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            clusterInfoDialog: {
 | 
				
			||||||
 | 
					                visible: false,
 | 
				
			||||||
 | 
					                redisId: 0,
 | 
				
			||||||
 | 
					                info: '',
 | 
				
			||||||
 | 
					                nodes: [],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            clusters: [
 | 
					            clusters: [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    id: 0,
 | 
					                    id: 0,
 | 
				
			||||||
@@ -134,12 +218,6 @@ export default defineComponent({
 | 
				
			|||||||
            state.currentData = item;
 | 
					            state.currentData = item;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // connect() {
 | 
					 | 
				
			||||||
        //   Req.post('/open/redis/connect', this.form, res => {
 | 
					 | 
				
			||||||
        //     this.redisInfo = res
 | 
					 | 
				
			||||||
        //   })
 | 
					 | 
				
			||||||
        // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const deleteRedis = async () => {
 | 
					        const deleteRedis = async () => {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                await ElMessageBox.confirm(`确定删除该redis?`, '提示', {
 | 
					                await ElMessageBox.confirm(`确定删除该redis?`, '提示', {
 | 
				
			||||||
@@ -155,12 +233,23 @@ export default defineComponent({
 | 
				
			|||||||
            } catch (err) {}
 | 
					            } catch (err) {}
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const info = (redis: any) => {
 | 
					        const info = async (redis: any) => {
 | 
				
			||||||
            redisApi.redisInfo.request({ id: redis.id }).then((res: any) => {
 | 
					            var host = redis.host;
 | 
				
			||||||
                state.infoDialog.info = res;
 | 
					            if (redis.ip) {
 | 
				
			||||||
                state.infoDialog.title = `'${redis.host}' info`;
 | 
					                host = redis.ip.split('@')[0];
 | 
				
			||||||
                state.infoDialog.visible = true;
 | 
					            }
 | 
				
			||||||
            });
 | 
					            const res = await redisApi.redisInfo.request({ id: redis.id, host });
 | 
				
			||||||
 | 
					            state.infoDialog.info = res;
 | 
				
			||||||
 | 
					            state.infoDialog.title = `'${host}' info`;
 | 
				
			||||||
 | 
					            state.infoDialog.visible = true;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const onShowClusterInfo = async (redis: any) => {
 | 
				
			||||||
 | 
					            const ci = await redisApi.clusterInfo.request({ id: redis.id });
 | 
				
			||||||
 | 
					            state.clusterInfoDialog.info = ci.clusterInfo;
 | 
				
			||||||
 | 
					            state.clusterInfoDialog.nodes = ci.clusterNodes;
 | 
				
			||||||
 | 
					            state.clusterInfoDialog.redisId = redis.id;
 | 
				
			||||||
 | 
					            state.clusterInfoDialog.visible = true;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const search = async () => {
 | 
					        const search = async () => {
 | 
				
			||||||
@@ -192,6 +281,7 @@ export default defineComponent({
 | 
				
			|||||||
            handlePageChange,
 | 
					            handlePageChange,
 | 
				
			||||||
            choose,
 | 
					            choose,
 | 
				
			||||||
            info,
 | 
					            info,
 | 
				
			||||||
 | 
					            onShowClusterInfo,
 | 
				
			||||||
            deleteRedis,
 | 
					            deleteRedis,
 | 
				
			||||||
            editRedis,
 | 
					            editRedis,
 | 
				
			||||||
            valChange,
 | 
					            valChange,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,11 @@ import Api from '@/common/Api';
 | 
				
			|||||||
export const redisApi = {
 | 
					export const redisApi = {
 | 
				
			||||||
    redisList : Api.create("/redis", 'get'),
 | 
					    redisList : Api.create("/redis", 'get'),
 | 
				
			||||||
    redisInfo: Api.create("/redis/{id}/info", 'get'),
 | 
					    redisInfo: Api.create("/redis/{id}/info", 'get'),
 | 
				
			||||||
 | 
					    clusterInfo: Api.create("/redis/{id}/cluster-info", 'get'),
 | 
				
			||||||
    saveRedis: Api.create("/redis", 'post'),
 | 
					    saveRedis: Api.create("/redis", 'post'),
 | 
				
			||||||
    delRedis: Api.create("/redis/{id}", 'delete'),
 | 
					    delRedis: Api.create("/redis/{id}", 'delete'),
 | 
				
			||||||
    // 获取权限列表
 | 
					    // 获取权限列表
 | 
				
			||||||
    scan: Api.create("/redis/{id}/scan/{cursor}/{count}", 'get'),
 | 
					    scan: Api.create("/redis/{id}/scan", 'post'),
 | 
				
			||||||
    getStringValue: Api.create("/redis/{id}/string-value", 'get'),
 | 
					    getStringValue: Api.create("/redis/{id}/string-value", 'get'),
 | 
				
			||||||
    saveStringValue: Api.create("/redis/{id}/string-value", 'post'),
 | 
					    saveStringValue: Api.create("/redis/{id}/string-value", 'post'),
 | 
				
			||||||
    getHashValue: Api.create("/redis/{id}/hash-value", 'get'),
 | 
					    getHashValue: Api.create("/redis/{id}/hash-value", 'get'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%" :destroy-on-close="true">
 | 
					        <el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%" :destroy-on-close="true">
 | 
				
			||||||
            <el-form :model="form" ref="accountForm" :rules="rules" label-width="85px">
 | 
					            <el-form :model="form" ref="accountForm" :rules="rules" label-width="85px">
 | 
				
			||||||
                <el-form-item prop="username" label="用户名:" required>
 | 
					                <el-form-item prop="username" label="用户名:" required>
 | 
				
			||||||
                    <el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名" auto-complete="off"></el-input>
 | 
					                    <el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名,密码默认与账号名一致" auto-complete="off"></el-input>
 | 
				
			||||||
                </el-form-item>
 | 
					                </el-form-item>
 | 
				
			||||||
                <!-- <el-form-item prop="password" label="密码:" required>
 | 
					                <!-- <el-form-item prop="password" label="密码:" required>
 | 
				
			||||||
                    <el-input type="password" v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password"></el-input>
 | 
					                    <el-input type="password" v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password"></el-input>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
    <div class="role-list">
 | 
					    <div class="role-list">
 | 
				
			||||||
        <el-card>
 | 
					        <el-card>
 | 
				
			||||||
            <el-button v-auth="'account:add'" type="primary" icon="plus" @click="editAccount(true)">添加</el-button>
 | 
					            <el-button v-auth="'account:add'" type="primary" icon="plus" @click="editAccount(true)">添加</el-button>
 | 
				
			||||||
            <el-button v-auth="'account:update'" :disabled="chooseId == null" @click="editAccount(false)" type="primary" icon="edit">编辑</el-button>
 | 
					            <!-- <el-button v-auth="'account:update'" :disabled="chooseId == null" @click="editAccount(false)" type="primary" icon="edit">编辑</el-button> -->
 | 
				
			||||||
            <el-button v-auth="'account:saveRoles'" :disabled="chooseId == null" @click="roleEdit()" type="success" icon="setting"
 | 
					            <el-button v-auth="'account:saveRoles'" :disabled="chooseId == null" @click="roleEdit()" type="success" icon="setting"
 | 
				
			||||||
                >角色分配</el-button
 | 
					                >角色分配</el-button
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -633,10 +633,10 @@ echarts@^5.3.3:
 | 
				
			|||||||
    tslib "2.3.0"
 | 
					    tslib "2.3.0"
 | 
				
			||||||
    zrender "5.3.2"
 | 
					    zrender "5.3.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
element-plus@^2.2.8:
 | 
					element-plus@^2.2.9:
 | 
				
			||||||
  version "2.2.8"
 | 
					  version "2.2.9"
 | 
				
			||||||
  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.8.tgz#6bba6285c20d8bea42247977d8f605611fc2da93"
 | 
					  resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.9.tgz#f0366dfb2048d614813926274cb443f17e5fdef2"
 | 
				
			||||||
  integrity sha512-+cubFh1rgeGcc2LeBm7hv/1BKFJre/LIIdRntm9OLaIhysCxigjEwcxk9gbVT4KsbcjmoqZUr4/mwhIhQV6mvw==
 | 
					  integrity sha512-jYbL0JkCdv95rkT6trZJjCAizLPySa0qcd2cgq+57SKQnCZAcNDDq4GbTuFRnNavdoeCJnuM3HIficTIUpsMOQ==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@ctrl/tinycolor" "^3.4.1"
 | 
					    "@ctrl/tinycolor" "^3.4.1"
 | 
				
			||||||
    "@element-plus/icons-vue" "^2.0.6"
 | 
					    "@element-plus/icons-vue" "^2.0.6"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
app:
 | 
					app:
 | 
				
			||||||
  name: mayfly-go
 | 
					  name: mayfly-go
 | 
				
			||||||
  version: 1.0.0
 | 
					  version: 1.2.0
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
server:
 | 
					server:
 | 
				
			||||||
  # debug release test
 | 
					  # debug release test
 | 
				
			||||||
@@ -29,10 +29,6 @@ jwt:
 | 
				
			|||||||
  # 过期时间单位分钟
 | 
					  # 过期时间单位分钟
 | 
				
			||||||
  expire-time: 1440
 | 
					  expire-time: 1440
 | 
				
			||||||
 | 
					
 | 
				
			||||||
redis:
 | 
					 | 
				
			||||||
  host: 127.0.0.1
 | 
					 | 
				
			||||||
  port: 6379
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mysql:
 | 
					mysql:
 | 
				
			||||||
  host: localhost:3306
 | 
					  host: localhost:3306
 | 
				
			||||||
  username: root
 | 
					  username: root
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,9 @@ go 1.18
 | 
				
			|||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/dgrijalva/jwt-go v3.2.0+incompatible // jwt
 | 
						github.com/dgrijalva/jwt-go v3.2.0+incompatible // jwt
 | 
				
			||||||
	github.com/gin-gonic/gin v1.8.1
 | 
						github.com/gin-gonic/gin v1.8.1
 | 
				
			||||||
	github.com/go-redis/redis v6.15.9+incompatible
 | 
						github.com/go-redis/redis/v8 v8.11.5
 | 
				
			||||||
	github.com/gorilla/websocket v1.5.0
 | 
						github.com/gorilla/websocket v1.5.0
 | 
				
			||||||
 | 
						github.com/lib/pq v1.10.6
 | 
				
			||||||
	github.com/mojocn/base64Captcha v1.3.5 // 验证码
 | 
						github.com/mojocn/base64Captcha v1.3.5 // 验证码
 | 
				
			||||||
	github.com/pkg/sftp v1.13.4
 | 
						github.com/pkg/sftp v1.13.4
 | 
				
			||||||
	github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
						github.com/robfig/cron/v3 v3.0.1 // 定时任务
 | 
				
			||||||
@@ -21,6 +22,8 @@ require (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
 | 
						github.com/cespare/xxhash/v2 v2.1.2 // indirect
 | 
				
			||||||
 | 
						github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 | 
				
			||||||
	github.com/gin-contrib/sse v0.1.0 // indirect
 | 
						github.com/gin-contrib/sse v0.1.0 // indirect
 | 
				
			||||||
	github.com/go-playground/locales v0.14.0 // indirect
 | 
						github.com/go-playground/locales v0.14.0 // indirect
 | 
				
			||||||
	github.com/go-playground/universal-translator v0.18.0 // indirect
 | 
						github.com/go-playground/universal-translator v0.18.0 // indirect
 | 
				
			||||||
@@ -39,8 +42,6 @@ require (
 | 
				
			|||||||
	github.com/mattn/go-isatty v0.0.14 // indirect
 | 
						github.com/mattn/go-isatty v0.0.14 // indirect
 | 
				
			||||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
						github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
				
			||||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
						github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
				
			||||||
	github.com/onsi/ginkgo v1.16.5 // indirect
 | 
					 | 
				
			||||||
	github.com/onsi/gomega v1.18.1 // indirect
 | 
					 | 
				
			||||||
	github.com/pelletier/go-toml/v2 v2.0.1 // indirect
 | 
						github.com/pelletier/go-toml/v2 v2.0.1 // indirect
 | 
				
			||||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
						github.com/pkg/errors v0.9.1 // indirect
 | 
				
			||||||
	github.com/ugorji/go/codec v1.2.7 // indirect
 | 
						github.com/ugorji/go/codec v1.2.7 // indirect
 | 
				
			||||||
@@ -49,10 +50,11 @@ require (
 | 
				
			|||||||
	github.com/xdg-go/stringprep v1.0.2 // indirect
 | 
						github.com/xdg-go/stringprep v1.0.2 // indirect
 | 
				
			||||||
	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 | 
						github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 | 
				
			||||||
	golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
 | 
						golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
 | 
				
			||||||
	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
 | 
						golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
 | 
				
			||||||
	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
 | 
						golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
 | 
						golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
 | 
				
			||||||
	golang.org/x/text v0.3.7 // indirect
 | 
						golang.org/x/text v0.3.7 // indirect
 | 
				
			||||||
 | 
						golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 | 
				
			||||||
	google.golang.org/protobuf v1.28.0 // indirect
 | 
						google.golang.org/protobuf v1.28.0 // indirect
 | 
				
			||||||
	gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
						gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -184,13 +184,15 @@ func (d *Db) DumpSql(rc *ctx.ReqCtx) {
 | 
				
			|||||||
	// 是否需要导出数据
 | 
						// 是否需要导出数据
 | 
				
			||||||
	needData := dumpType == "2" || dumpType == "3"
 | 
						needData := dumpType == "2" || dumpType == "3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbInstance := d.DbApp.GetDbInstance(dbId, db)
 | 
				
			||||||
 | 
						biz.ErrIsNilAppendErr(d.ProjectApp.CanAccess(rc.LoginAccount.Id, dbInstance.ProjectId), "%s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	now := time.Now()
 | 
						now := time.Now()
 | 
				
			||||||
	filename := fmt.Sprintf("%s.%s.sql", db, now.Format("200601021504"))
 | 
						filename := fmt.Sprintf("%s.%s.sql", db, now.Format("200601021504"))
 | 
				
			||||||
	g.Header("Content-Type", "application/octet-stream")
 | 
						g.Header("Content-Type", "application/octet-stream")
 | 
				
			||||||
	g.Header("Content-Disposition", "attachment; filename="+filename)
 | 
						g.Header("Content-Disposition", "attachment; filename="+filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc.ReqParam = fmt.Sprintf("数据库id: %d -- %s", dbId, db)
 | 
						rc.ReqParam = fmt.Sprintf("数据库id: %d -- %s", dbId, db)
 | 
				
			||||||
	dbInstance := d.DbApp.GetDbInstance(dbId, db)
 | 
					 | 
				
			||||||
	writer := g.Writer
 | 
						writer := g.Writer
 | 
				
			||||||
	writer.WriteString("-- ----------------------------")
 | 
						writer.WriteString("-- ----------------------------")
 | 
				
			||||||
	writer.WriteString("\n-- 导出平台: mayfly-go")
 | 
						writer.WriteString("\n-- 导出平台: mayfly-go")
 | 
				
			||||||
@@ -222,7 +224,13 @@ func (d *Db) DumpSql(rc *ctx.ReqCtx) {
 | 
				
			|||||||
			pageNum++
 | 
								pageNum++
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		sqlTmp := "SELECT * FROM %s LIMIT %d, %d"
 | 
							var sqlTmp string
 | 
				
			||||||
 | 
							switch dbInstance.Type {
 | 
				
			||||||
 | 
							case "mysql":
 | 
				
			||||||
 | 
								sqlTmp = "SELECT * FROM %s LIMIT %d, %d"
 | 
				
			||||||
 | 
							case "postgres":
 | 
				
			||||||
 | 
								sqlTmp = "SELECT * FROM %s OFFSET %d LIMIT %d"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		for index := 0; index < pageNum; index++ {
 | 
							for index := 0; index < pageNum; index++ {
 | 
				
			||||||
			sql := fmt.Sprintf(sqlTmp, table, index*DEFAULT_COLUMN_SIZE, DEFAULT_COLUMN_SIZE)
 | 
								sql := fmt.Sprintf(sqlTmp, table, index*DEFAULT_COLUMN_SIZE, DEFAULT_COLUMN_SIZE)
 | 
				
			||||||
			columns, result, _ := dbInstance.SelectData(sql)
 | 
								columns, result, _ := dbInstance.SelectData(sql)
 | 
				
			||||||
@@ -301,7 +309,7 @@ func (d *Db) HintTables(rc *ctx.ReqCtx) {
 | 
				
			|||||||
		columnName := fmt.Sprintf("%s  [%s]", v["columnName"], v["columnType"])
 | 
							columnName := fmt.Sprintf("%s  [%s]", v["columnName"], v["columnType"])
 | 
				
			||||||
		comment := v["columnComment"]
 | 
							comment := v["columnComment"]
 | 
				
			||||||
		// 如果字段备注不为空,则加上备注信息
 | 
							// 如果字段备注不为空,则加上备注信息
 | 
				
			||||||
		if comment != "" {
 | 
							if comment != nil && comment != "" {
 | 
				
			||||||
			columnName = fmt.Sprintf("%s[%s]", columnName, comment)
 | 
								columnName = fmt.Sprintf("%s[%s]", columnName, comment)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ type DbForm struct {
 | 
				
			|||||||
	Port      int    `binding:"required" json:"port"`
 | 
						Port      int    `binding:"required" json:"port"`
 | 
				
			||||||
	Username  string `binding:"required" json:"username"`
 | 
						Username  string `binding:"required" json:"username"`
 | 
				
			||||||
	Password  string `json:"password"`
 | 
						Password  string `json:"password"`
 | 
				
			||||||
 | 
						Params    string `json:"params"`
 | 
				
			||||||
	Database  string `binding:"required" json:"database"`
 | 
						Database  string `binding:"required" json:"database"`
 | 
				
			||||||
	ProjectId uint64 `binding:"required" json:"projectId"`
 | 
						ProjectId uint64 `binding:"required" json:"projectId"`
 | 
				
			||||||
	Project   string `json:"project"`
 | 
						Project   string `json:"project"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ type Redis struct {
 | 
				
			|||||||
	Id        uint64
 | 
						Id        uint64
 | 
				
			||||||
	Host      string `binding:"required" json:"host"`
 | 
						Host      string `binding:"required" json:"host"`
 | 
				
			||||||
	Password  string `json:"password"`
 | 
						Password  string `json:"password"`
 | 
				
			||||||
 | 
						Mode      string `json:"mode"`
 | 
				
			||||||
	Db        int    `json:"db"`
 | 
						Db        int    `json:"db"`
 | 
				
			||||||
	ProjectId uint64 `binding:"required" json:"projectId"`
 | 
						ProjectId uint64 `binding:"required" json:"projectId"`
 | 
				
			||||||
	Project   string `json:"project"`
 | 
						Project   string `json:"project"`
 | 
				
			||||||
@@ -30,3 +31,9 @@ type SetValue struct {
 | 
				
			|||||||
	KeyInfo
 | 
						KeyInfo
 | 
				
			||||||
	Value []interface{} `binding:"required" json:"value"`
 | 
						Value []interface{} `binding:"required" json:"value"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RedisScanForm struct {
 | 
				
			||||||
 | 
						Cursor map[string]uint64 `json:"cursor"`
 | 
				
			||||||
 | 
						Match  string            `json:"match"`
 | 
				
			||||||
 | 
						Count  int64             `json:"count"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package api
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"mayfly-go/internal/devops/api/form"
 | 
						"mayfly-go/internal/devops/api/form"
 | 
				
			||||||
	"mayfly-go/internal/devops/api/vo"
 | 
						"mayfly-go/internal/devops/api/vo"
 | 
				
			||||||
	"mayfly-go/internal/devops/application"
 | 
						"mayfly-go/internal/devops/application"
 | 
				
			||||||
@@ -11,7 +12,10 @@ import (
 | 
				
			|||||||
	"mayfly-go/pkg/utils"
 | 
						"mayfly-go/pkg/utils"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Redis struct {
 | 
					type Redis struct {
 | 
				
			||||||
@@ -45,7 +49,40 @@ func (r *Redis) DeleteRedis(rc *ctx.ReqCtx) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
 | 
					func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
 | 
				
			||||||
	res, _ := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(rc.GinCtx, "id"))).Cli.Info().Result()
 | 
						ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(rc.GinCtx, "id")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var res string
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						if ri.Mode == "" || ri.Mode == entity.RedisModeStandalone {
 | 
				
			||||||
 | 
							res, err = ri.Cli.Info(ctx).Result()
 | 
				
			||||||
 | 
						} else if ri.Mode == entity.RedisModeCluster {
 | 
				
			||||||
 | 
							host := rc.GinCtx.Query("host")
 | 
				
			||||||
 | 
							biz.NotEmpty(host, "集群模式host信息不能为空")
 | 
				
			||||||
 | 
							clusterClient := ri.ClusterCli
 | 
				
			||||||
 | 
							var redisClient *redis.Client
 | 
				
			||||||
 | 
							// 遍历集群的master节点找到该redis client
 | 
				
			||||||
 | 
							clusterClient.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
 | 
				
			||||||
 | 
								if host == client.Options().Addr {
 | 
				
			||||||
 | 
									redisClient = client
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							if redisClient == nil {
 | 
				
			||||||
 | 
								// 遍历集群的slave节点找到该redis client
 | 
				
			||||||
 | 
								clusterClient.ForEachSlave(ctx, func(ctx context.Context, client *redis.Client) error {
 | 
				
			||||||
 | 
									if host == client.Options().Addr {
 | 
				
			||||||
 | 
										redisClient = client
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							biz.NotNil(redisClient, "该实例不在该集群中")
 | 
				
			||||||
 | 
							res, err = redisClient.Info(ctx).Result()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						biz.ErrIsNilAppendErr(err, "获取redis info失败: %s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	datas := strings.Split(res, "\r\n")
 | 
						datas := strings.Split(res, "\r\n")
 | 
				
			||||||
	i := 0
 | 
						i := 0
 | 
				
			||||||
@@ -81,43 +118,125 @@ func (r *Redis) RedisInfo(rc *ctx.ReqCtx) {
 | 
				
			|||||||
	rc.ResData = parseMap
 | 
						rc.ResData = parseMap
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Redis) ClusterInfo(rc *ctx.ReqCtx) {
 | 
				
			||||||
 | 
						g := rc.GinCtx
 | 
				
			||||||
 | 
						ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
				
			||||||
 | 
						biz.IsEquals(ri.Mode, entity.RedisModeCluster, "非集群模式")
 | 
				
			||||||
 | 
						info, _ := ri.ClusterCli.ClusterInfo(context.Background()).Result()
 | 
				
			||||||
 | 
						nodesStr, _ := ri.ClusterCli.ClusterNodes(context.Background()).Result()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodesRes := make([]map[string]string, 0)
 | 
				
			||||||
 | 
						nodes := strings.Split(nodesStr, "\n")
 | 
				
			||||||
 | 
						for _, node := range nodes {
 | 
				
			||||||
 | 
							if node == "" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							nodeInfos := strings.Split(node, " ")
 | 
				
			||||||
 | 
							node := make(map[string]string)
 | 
				
			||||||
 | 
							node["nodeId"] = nodeInfos[0]
 | 
				
			||||||
 | 
							// ip:port1@port2:port1指redis服务器与客户端通信的端口,port2则是集群内部节点间通信的端口
 | 
				
			||||||
 | 
							node["ip"] = nodeInfos[1]
 | 
				
			||||||
 | 
							node["flags"] = nodeInfos[2]
 | 
				
			||||||
 | 
							// 如果节点是slave,并且已知master节点,则为master节点ID;否则为符号"-"
 | 
				
			||||||
 | 
							node["masterSlaveRelation"] = nodeInfos[3]
 | 
				
			||||||
 | 
							// 最近一次发送ping的时间,这个时间是一个unix毫秒时间戳,0代表没有发送过
 | 
				
			||||||
 | 
							node["pingSent"] = nodeInfos[4]
 | 
				
			||||||
 | 
							// 最近一次收到pong的时间,使用unix时间戳表示
 | 
				
			||||||
 | 
							node["pongRecv"] = nodeInfos[5]
 | 
				
			||||||
 | 
							// 节点的epoch值(如果该节点是从节点,则为其主节点的epoch值)。每当节点发生失败切换时,都会创建一个新的,独特的,递增的epoch。
 | 
				
			||||||
 | 
							// 如果多个节点竞争同一个哈希槽时,epoch值更高的节点会抢夺到
 | 
				
			||||||
 | 
							node["configEpoch"] = nodeInfos[6]
 | 
				
			||||||
 | 
							// node-to-node集群总线使用的链接的状态,我们使用这个链接与集群中其他节点进行通信.值可以是 connected 和 disconnected
 | 
				
			||||||
 | 
							node["linkState"] = nodeInfos[7]
 | 
				
			||||||
 | 
							// slave节点没有插槽信息
 | 
				
			||||||
 | 
							if len(nodeInfos) > 8 {
 | 
				
			||||||
 | 
								// slot:master节点第9位为哈希槽值或者一个哈希槽范围,代表当前节点可以提供服务的所有哈希槽值。如果只是一个值,那就是只有一个槽会被使用。
 | 
				
			||||||
 | 
								// 如果是一个范围,这个值表示为起始槽-结束槽,节点将处理包括起始槽和结束槽在内的所有哈希槽。
 | 
				
			||||||
 | 
								node["slot"] = nodeInfos[8]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							nodesRes = append(nodesRes, node)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rc.ResData = map[string]interface{}{
 | 
				
			||||||
 | 
							"clusterInfo":  info,
 | 
				
			||||||
 | 
							"clusterNodes": nodesRes,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// scan获取redis的key列表信息
 | 
					// scan获取redis的key列表信息
 | 
				
			||||||
func (r *Redis) Scan(rc *ctx.ReqCtx) {
 | 
					func (r *Redis) Scan(rc *ctx.ReqCtx) {
 | 
				
			||||||
	g := rc.GinCtx
 | 
						g := rc.GinCtx
 | 
				
			||||||
 | 
					 | 
				
			||||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
						ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
				
			||||||
	biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
						biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	keys, cursor := ri.Scan(uint64(ginx.PathParamInt(g, "cursor")), g.Query("match"), int64(ginx.PathParamInt(g, "count")))
 | 
						form := &form.RedisScanForm{}
 | 
				
			||||||
 | 
						ginx.BindJsonAndValid(rc.GinCtx, form)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var keyInfoSplit []string
 | 
						cmd := ri.GetCmdable()
 | 
				
			||||||
	if len(keys) > 0 {
 | 
						ctx := context.Background()
 | 
				
			||||||
		keyInfoLua := `
 | 
					 | 
				
			||||||
			local result = {}
 | 
					 | 
				
			||||||
			-- KEYS[1]为第1个参数,lua数组下标从1开始
 | 
					 | 
				
			||||||
			local ttl = redis.call('ttl', KEYS[1]);
 | 
					 | 
				
			||||||
			local keyType = redis.call('type', KEYS[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, _ := ri.Cli.Eval(keyInfoLua, keys).Result()
 | 
					 | 
				
			||||||
		keyInfoSplit = strings.Split(keyInfos.(string), ".")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	kis := make([]*vo.KeyInfo, 0)
 | 
						kis := make([]*vo.KeyInfo, 0)
 | 
				
			||||||
	for i, k := range keys {
 | 
						var cursorRes map[string]uint64 = make(map[string]uint64)
 | 
				
			||||||
		ttlType := strings.Split(keyInfoSplit[i], ",")
 | 
					
 | 
				
			||||||
		ttl, _ := strconv.Atoi(ttlType[0])
 | 
						if ri.Mode == "" || ri.Mode == entity.RedisModeStandalone {
 | 
				
			||||||
		ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
 | 
							redisAddr := ri.Cli.Options().Addr
 | 
				
			||||||
		kis = append(kis, ki)
 | 
							keys, cursor := ri.Scan(form.Cursor[redisAddr], form.Match, form.Count)
 | 
				
			||||||
 | 
							cursorRes[redisAddr] = cursor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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])
 | 
				
			||||||
 | 
								ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
 | 
				
			||||||
 | 
								kis = append(kis, ki)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if ri.Mode == entity.RedisModeCluster {
 | 
				
			||||||
 | 
							var keys []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mu := &sync.Mutex{}
 | 
				
			||||||
 | 
							// 遍历所有master节点,并执行scan命令,合并keys
 | 
				
			||||||
 | 
							ri.ClusterCli.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
 | 
				
			||||||
 | 
								redisAddr := client.Options().Addr
 | 
				
			||||||
 | 
								ks, cursor, _ := client.Scan(ctx, form.Cursor[redisAddr], form.Match, form.Count).Result()
 | 
				
			||||||
 | 
								// 遍历节点的内部回调函数使用异步调用,如不加锁会导致集合并发错误
 | 
				
			||||||
 | 
								mu.Lock()
 | 
				
			||||||
 | 
								cursorRes[redisAddr] = cursor
 | 
				
			||||||
 | 
								keys = append(keys, ks...)
 | 
				
			||||||
 | 
								mu.Unlock()
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 因为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])
 | 
				
			||||||
 | 
								ki := &vo.KeyInfo{Key: k, Type: ttlType[1], Ttl: int64(ttl)}
 | 
				
			||||||
 | 
								kis = append(kis, ki)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	size, _ := ri.Cli.DBSize().Result()
 | 
						size, _ := cmd.DBSize(context.TODO()).Result()
 | 
				
			||||||
	rc.ResData = &vo.Keys{Cursor: cursor, Keys: kis, DbSize: size}
 | 
						rc.ResData = &vo.Keys{Cursor: cursorRes, Keys: kis, DbSize: size}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
 | 
					func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
 | 
				
			||||||
@@ -129,7 +248,7 @@ func (r *Redis) DeleteKey(rc *ctx.ReqCtx) {
 | 
				
			|||||||
	biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
						biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rc.ReqParam = key
 | 
						rc.ReqParam = key
 | 
				
			||||||
	ri.Cli.Del(key)
 | 
						ri.GetCmdable().Del(context.Background(), key)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
 | 
					func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
 | 
				
			||||||
@@ -145,14 +264,14 @@ func (r *Redis) checkKey(rc *ctx.ReqCtx) (*application.RedisInstance, string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (r *Redis) GetStringValue(rc *ctx.ReqCtx) {
 | 
					func (r *Redis) GetStringValue(rc *ctx.ReqCtx) {
 | 
				
			||||||
	ri, key := r.checkKey(rc)
 | 
						ri, key := r.checkKey(rc)
 | 
				
			||||||
	str, err := ri.Cli.Get(key).Result()
 | 
						str, err := ri.GetCmdable().Get(context.TODO(), key).Result()
 | 
				
			||||||
	biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
 | 
						biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
 | 
				
			||||||
	rc.ResData = str
 | 
						rc.ResData = str
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Redis) GetHashValue(rc *ctx.ReqCtx) {
 | 
					func (r *Redis) GetHashValue(rc *ctx.ReqCtx) {
 | 
				
			||||||
	ri, key := r.checkKey(rc)
 | 
						ri, key := r.checkKey(rc)
 | 
				
			||||||
	res, err := ri.Cli.HGetAll(key).Result()
 | 
						res, err := ri.GetCmdable().HGetAll(context.TODO(), key).Result()
 | 
				
			||||||
	biz.ErrIsNilAppendErr(err, "获取hash值失败: %s")
 | 
						biz.ErrIsNilAppendErr(err, "获取hash值失败: %s")
 | 
				
			||||||
	rc.ResData = res
 | 
						rc.ResData = res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -165,7 +284,7 @@ func (r *Redis) SetStringValue(rc *ctx.ReqCtx) {
 | 
				
			|||||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
						ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
				
			||||||
	biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
						biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	str, err := ri.Cli.Set(keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
 | 
						str, err := ri.GetCmdable().Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
 | 
				
			||||||
	biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
 | 
						biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
 | 
				
			||||||
	rc.ResData = str
 | 
						rc.ResData = str
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -178,21 +297,22 @@ func (r *Redis) SetHashValue(rc *ctx.ReqCtx) {
 | 
				
			|||||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
						ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
				
			||||||
	biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
						biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cmd := ri.GetCmdable()
 | 
				
			||||||
	key := hashValue.Key
 | 
						key := hashValue.Key
 | 
				
			||||||
	// 简单处理->先删除,后新增
 | 
						// 简单处理->先删除,后新增
 | 
				
			||||||
	ri.Cli.Del(key)
 | 
						cmd.Del(context.TODO(), key)
 | 
				
			||||||
	for _, v := range hashValue.Value {
 | 
						for _, v := range hashValue.Value {
 | 
				
			||||||
		res := ri.Cli.HSet(key, v["key"].(string), v["value"])
 | 
							res := cmd.HSet(context.TODO(), key, v["key"].(string), v["value"])
 | 
				
			||||||
		biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
 | 
							biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if hashValue.Timed != -1 {
 | 
						if hashValue.Timed != -1 {
 | 
				
			||||||
		ri.Cli.Expire(key, time.Second*time.Duration(hashValue.Timed))
 | 
							cmd.Expire(context.TODO(), key, time.Second*time.Duration(hashValue.Timed))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *Redis) GetSetValue(rc *ctx.ReqCtx) {
 | 
					func (r *Redis) GetSetValue(rc *ctx.ReqCtx) {
 | 
				
			||||||
	ri, key := r.checkKey(rc)
 | 
						ri, key := r.checkKey(rc)
 | 
				
			||||||
	res, err := ri.Cli.SMembers(key).Result()
 | 
						res, err := ri.GetCmdable().SMembers(context.TODO(), key).Result()
 | 
				
			||||||
	biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
 | 
						biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
 | 
				
			||||||
	rc.ResData = res
 | 
						rc.ResData = res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -204,13 +324,14 @@ func (r *Redis) SetSetValue(rc *ctx.ReqCtx) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
						ri := r.RedisApp.GetRedisInstance(uint64(ginx.PathParamInt(g, "id")))
 | 
				
			||||||
	biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
						biz.ErrIsNilAppendErr(r.ProjectApp.CanAccess(rc.LoginAccount.Id, ri.ProjectId), "%s")
 | 
				
			||||||
 | 
						cmd := ri.GetCmdable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	key := keyvalue.Key
 | 
						key := keyvalue.Key
 | 
				
			||||||
	// 简单处理->先删除,后新增
 | 
						// 简单处理->先删除,后新增
 | 
				
			||||||
	ri.Cli.Del(key)
 | 
						cmd.Del(context.TODO(), key)
 | 
				
			||||||
	ri.Cli.SAdd(key, keyvalue.Value...)
 | 
						cmd.SAdd(context.TODO(), key, keyvalue.Value...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if keyvalue.Timed != -1 {
 | 
						if keyvalue.Timed != -1 {
 | 
				
			||||||
		ri.Cli.Expire(key, time.Second*time.Duration(keyvalue.Timed))
 | 
							cmd.Expire(context.TODO(), key, time.Second*time.Duration(keyvalue.Timed))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ type SelectDataDbVO struct {
 | 
				
			|||||||
	Host       *string    `json:"host"`
 | 
						Host       *string    `json:"host"`
 | 
				
			||||||
	Port       *int       `json:"port"`
 | 
						Port       *int       `json:"port"`
 | 
				
			||||||
	Type       *string    `json:"type"`
 | 
						Type       *string    `json:"type"`
 | 
				
			||||||
 | 
						Params     *string    `json:"params"`
 | 
				
			||||||
	Database   *string    `json:"database"`
 | 
						Database   *string    `json:"database"`
 | 
				
			||||||
	Username   *string    `json:"username"`
 | 
						Username   *string    `json:"username"`
 | 
				
			||||||
	ProjectId  *int64     `json:"projectId"`
 | 
						ProjectId  *int64     `json:"projectId"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@ type Redis struct {
 | 
				
			|||||||
	Db         int        `json:"db"`
 | 
						Db         int        `json:"db"`
 | 
				
			||||||
	ProjectId  *int64     `json:"projectId"`
 | 
						ProjectId  *int64     `json:"projectId"`
 | 
				
			||||||
	Project    *string    `json:"project"`
 | 
						Project    *string    `json:"project"`
 | 
				
			||||||
 | 
						Mode       *string    `json:"mode"`
 | 
				
			||||||
 | 
						Remark     *string    `json:"remark"`
 | 
				
			||||||
	Env        *string    `json:"env"`
 | 
						Env        *string    `json:"env"`
 | 
				
			||||||
	EnvId      *int64     `json:"envId"`
 | 
						EnvId      *int64     `json:"envId"`
 | 
				
			||||||
	CreateTime *time.Time `json:"createTime"`
 | 
						CreateTime *time.Time `json:"createTime"`
 | 
				
			||||||
@@ -17,9 +19,9 @@ type Redis struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Keys struct {
 | 
					type Keys struct {
 | 
				
			||||||
	Cursor uint64     `json:"cursor"`
 | 
						Cursor map[string]uint64 `json:"cursor"`
 | 
				
			||||||
	Keys   []*KeyInfo `json:"keys"`
 | 
						Keys   []*KeyInfo        `json:"keys"`
 | 
				
			||||||
	DbSize int64      `json:"dbSize"`
 | 
						DbSize int64             `json:"dbSize"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type KeyInfo struct {
 | 
					type KeyInfo struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,8 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ "github.com/lib/pq"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Db interface {
 | 
					type Db interface {
 | 
				
			||||||
@@ -228,10 +230,6 @@ func (d *DbInstance) SelectData(execSql string) ([]string, []map[string]interfac
 | 
				
			|||||||
	if !isSelect && !isShow && !isExplain {
 | 
						if !isSelect && !isShow && !isExplain {
 | 
				
			||||||
		return nil, nil, errors.New("该sql非查询语句")
 | 
							return nil, nil, errors.New("该sql非查询语句")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// 没加limit,则默认限制50条
 | 
					 | 
				
			||||||
	if isSelect && !strings.Contains(execSql, "limit") && !strings.Contains(execSql, "LIMIT") {
 | 
					 | 
				
			||||||
		execSql = execSql + " LIMIT 50"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rows, err := d.db.Query(execSql)
 | 
						rows, err := d.db.Query(execSql)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -335,8 +333,21 @@ func (d *DbInstance) Close() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 获取dataSourceName
 | 
					// 获取dataSourceName
 | 
				
			||||||
func getDsn(d *entity.Db) string {
 | 
					func getDsn(d *entity.Db) string {
 | 
				
			||||||
 | 
						var dsn string
 | 
				
			||||||
	if d.Type == "mysql" {
 | 
						if d.Type == "mysql" {
 | 
				
			||||||
		return fmt.Sprintf("%s:%s@%s(%s:%d)/%s?timeout=8s", d.Username, d.Password, d.Network, d.Host, d.Port, d.Database)
 | 
							dsn = fmt.Sprintf("%s:%s@%s(%s:%d)/%s?timeout=8s", d.Username, d.Password, d.Network, d.Host, d.Port, d.Database)
 | 
				
			||||||
 | 
							if d.Params != "" {
 | 
				
			||||||
 | 
								dsn = fmt.Sprintf("%s&%s", dsn, d.Params)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return dsn
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if d.Type == "postgres" {
 | 
				
			||||||
 | 
							dsn = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", d.Host, d.Port, d.Username, d.Password, d.Database)
 | 
				
			||||||
 | 
							if d.Params != "" {
 | 
				
			||||||
 | 
								dsn = fmt.Sprintf("%s %s", dsn, strings.Join(strings.Split(d.Params, "&"), " "))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return dsn
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ""
 | 
						return ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -370,7 +381,7 @@ const (
 | 
				
			|||||||
	DEFAULT_COLUMN_SIZE = 2000
 | 
						DEFAULT_COLUMN_SIZE = 2000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// mysql 列信息元数据
 | 
						// mysql 列信息元数据
 | 
				
			||||||
	MYSQL_COLOUMN_MA = `SELECT table_name tableName, column_name columnName, column_type columnType,
 | 
						MYSQL_COLUMN_MA = `SELECT table_name tableName, column_name columnName, column_type columnType,
 | 
				
			||||||
	column_comment columnComment, column_key columnKey, extra, is_nullable nullable from information_schema.columns
 | 
						column_comment columnComment, column_key columnKey, extra, is_nullable nullable from information_schema.columns
 | 
				
			||||||
	WHERE table_name in (%s) AND table_schema = (SELECT database()) ORDER BY tableName, ordinal_position LIMIT %d, %d`
 | 
						WHERE table_name in (%s) AND table_schema = (SELECT database()) ORDER BY tableName, ordinal_position LIMIT %d, %d`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -379,10 +390,58 @@ const (
 | 
				
			|||||||
	WHERE table_name in (%s) AND table_schema = (SELECT database())`
 | 
						WHERE table_name in (%s) AND table_schema = (SELECT database())`
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// postgres 表信息元数据
 | 
				
			||||||
 | 
						PGSQL_TABLE_MA = `SELECT obj_description(c.oid) AS "tableComment", c.relname AS "tableName" FROM pg_class c 
 | 
				
			||||||
 | 
						JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = (select current_schema()) AND c.reltype > 0`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PGSQL_TABLE_INFO = `SELECT obj_description(c.oid) AS "tableComment", c.relname AS "tableName" FROM pg_class c 
 | 
				
			||||||
 | 
						JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = (select current_schema()) AND c.reltype > 0`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PGSQL_INDEX_INFO = `SELECT indexname AS "indexName", indexdef AS "indexComment"
 | 
				
			||||||
 | 
						FROM pg_indexes WHERE schemaname =  (select current_schema()) AND tablename = '%s'`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PGSQL_COLUMN_MA = `SELECT
 | 
				
			||||||
 | 
							C.relname AS "tableName",
 | 
				
			||||||
 | 
							A.attname AS "columnName",
 | 
				
			||||||
 | 
							concat_ws ( '', t.typname, SUBSTRING ( format_type ( a.atttypid, a.atttypmod ) FROM '\(.*\)' ) ) AS "columnType",
 | 
				
			||||||
 | 
							d.description AS "columnComment" 
 | 
				
			||||||
 | 
						FROM
 | 
				
			||||||
 | 
							pg_attribute a LEFT JOIN pg_description d ON d.objoid = a.attrelid 
 | 
				
			||||||
 | 
							AND d.objsubid = A.attnum
 | 
				
			||||||
 | 
							LEFT JOIN pg_class c ON A.attrelid = c.oid
 | 
				
			||||||
 | 
							LEFT JOIN pg_namespace pn ON c.relnamespace = pn.oid
 | 
				
			||||||
 | 
							LEFT JOIN pg_type t ON a.atttypid = t.oid 
 | 
				
			||||||
 | 
						WHERE
 | 
				
			||||||
 | 
							A.attnum >= 0 
 | 
				
			||||||
 | 
							AND pn.nspname = (select current_schema())
 | 
				
			||||||
 | 
							AND C.relname in (%s)
 | 
				
			||||||
 | 
						ORDER BY
 | 
				
			||||||
 | 
							C.relname DESC,
 | 
				
			||||||
 | 
							A.attnum ASC
 | 
				
			||||||
 | 
						OFFSET %d LIMIT %d	
 | 
				
			||||||
 | 
						`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						PGSQL_COLUMN_MA_COUNT = `SELECT COUNT(*) "maNum"
 | 
				
			||||||
 | 
						FROM
 | 
				
			||||||
 | 
							pg_attribute a LEFT JOIN pg_description d ON d.objoid = a.attrelid 
 | 
				
			||||||
 | 
							AND d.objsubid = A.attnum
 | 
				
			||||||
 | 
							LEFT JOIN pg_class c ON A.attrelid = c.oid
 | 
				
			||||||
 | 
							LEFT JOIN pg_namespace pn ON c.relnamespace = pn.oid
 | 
				
			||||||
 | 
							LEFT JOIN pg_type t ON a.atttypid = t.oid 
 | 
				
			||||||
 | 
						WHERE
 | 
				
			||||||
 | 
							A.attnum >= 0 
 | 
				
			||||||
 | 
							AND pn.nspname = (select current_schema())
 | 
				
			||||||
 | 
							AND C.relname in (%s)
 | 
				
			||||||
 | 
						`
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *DbInstance) GetTableMetedatas() []map[string]interface{} {
 | 
					func (d *DbInstance) GetTableMetedatas() []map[string]interface{} {
 | 
				
			||||||
	var sql string
 | 
						var sql string
 | 
				
			||||||
	if d.Type == "mysql" {
 | 
						if d.Type == "mysql" {
 | 
				
			||||||
		sql = MYSQL_TABLE_MA
 | 
							sql = MYSQL_TABLE_MA
 | 
				
			||||||
 | 
						} else if d.Type == "postgres" {
 | 
				
			||||||
 | 
							sql = PGSQL_TABLE_MA
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, res, _ := d.SelectData(sql)
 | 
						_, res, _ := d.SelectData(sql)
 | 
				
			||||||
	return res
 | 
						return res
 | 
				
			||||||
@@ -401,7 +460,10 @@ func (d *DbInstance) GetColumnMetadatas(tableNames ...string) []map[string]inter
 | 
				
			|||||||
	var sqlTmp string
 | 
						var sqlTmp string
 | 
				
			||||||
	if d.Type == "mysql" {
 | 
						if d.Type == "mysql" {
 | 
				
			||||||
		countSqlTmp = MYSQL_COLOUMN_MA_COUNT
 | 
							countSqlTmp = MYSQL_COLOUMN_MA_COUNT
 | 
				
			||||||
		sqlTmp = MYSQL_COLOUMN_MA
 | 
							sqlTmp = MYSQL_COLUMN_MA
 | 
				
			||||||
 | 
						} else if d.Type == "postgres" {
 | 
				
			||||||
 | 
							countSqlTmp = PGSQL_COLUMN_MA_COUNT
 | 
				
			||||||
 | 
							sqlTmp = PGSQL_COLUMN_MA
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	countSql := fmt.Sprintf(countSqlTmp, tableName)
 | 
						countSql := fmt.Sprintf(countSqlTmp, tableName)
 | 
				
			||||||
@@ -433,6 +495,8 @@ func (d *DbInstance) GetTableInfos() []map[string]interface{} {
 | 
				
			|||||||
	var sql string
 | 
						var sql string
 | 
				
			||||||
	if d.Type == "mysql" {
 | 
						if d.Type == "mysql" {
 | 
				
			||||||
		sql = MYSQL_TABLE_INFO
 | 
							sql = MYSQL_TABLE_INFO
 | 
				
			||||||
 | 
						} else if d.Type == "postgres" {
 | 
				
			||||||
 | 
							sql = PGSQL_TABLE_INFO
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, res, _ := d.SelectData(sql)
 | 
						_, res, _ := d.SelectData(sql)
 | 
				
			||||||
	return res
 | 
						return res
 | 
				
			||||||
@@ -442,6 +506,8 @@ func (d *DbInstance) GetTableIndex(tableName string) []map[string]interface{} {
 | 
				
			|||||||
	var sql string
 | 
						var sql string
 | 
				
			||||||
	if d.Type == "mysql" {
 | 
						if d.Type == "mysql" {
 | 
				
			||||||
		sql = fmt.Sprintf(MYSQL_INDEX_INFO, tableName)
 | 
							sql = fmt.Sprintf(MYSQL_INDEX_INFO, tableName)
 | 
				
			||||||
 | 
						} else if d.Type == "postgres" {
 | 
				
			||||||
 | 
							sql = fmt.Sprintf(PGSQL_INDEX_INFO, tableName)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, res, _ := d.SelectData(sql)
 | 
						_, res, _ := d.SelectData(sql)
 | 
				
			||||||
	return res
 | 
						return res
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package application
 | 
					package application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/internal/devops/domain/entity"
 | 
						"mayfly-go/internal/devops/domain/entity"
 | 
				
			||||||
	"mayfly-go/internal/devops/domain/repository"
 | 
						"mayfly-go/internal/devops/domain/repository"
 | 
				
			||||||
@@ -9,9 +10,10 @@ import (
 | 
				
			|||||||
	"mayfly-go/pkg/cache"
 | 
						"mayfly-go/pkg/cache"
 | 
				
			||||||
	"mayfly-go/pkg/global"
 | 
						"mayfly-go/pkg/global"
 | 
				
			||||||
	"mayfly-go/pkg/model"
 | 
						"mayfly-go/pkg/model"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-redis/redis"
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Redis interface {
 | 
					type Redis interface {
 | 
				
			||||||
@@ -102,24 +104,53 @@ func (r *redisAppImpl) GetRedisInstance(id uint64) *RedisInstance {
 | 
				
			|||||||
	// 缓存不存在,则回调获取redis信息
 | 
						// 缓存不存在,则回调获取redis信息
 | 
				
			||||||
	re := r.GetById(id)
 | 
						re := r.GetById(id)
 | 
				
			||||||
	biz.NotNil(re, "redis信息不存在")
 | 
						biz.NotNil(re, "redis信息不存在")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						redisMode := re.Mode
 | 
				
			||||||
 | 
						ri := &RedisInstance{Id: id, ProjectId: re.ProjectId, Mode: redisMode}
 | 
				
			||||||
 | 
						if redisMode == "" || redisMode == entity.RedisModeStandalone {
 | 
				
			||||||
 | 
							rcli := getRedisCient(re)
 | 
				
			||||||
 | 
							// 测试连接
 | 
				
			||||||
 | 
							_, e := rcli.Ping(context.Background()).Result()
 | 
				
			||||||
 | 
							if e != nil {
 | 
				
			||||||
 | 
								rcli.Close()
 | 
				
			||||||
 | 
								panic(biz.NewBizErr(fmt.Sprintf("redis连接失败: %s", e.Error())))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ri.Cli = rcli
 | 
				
			||||||
 | 
						} else if redisMode == entity.RedisModeCluster {
 | 
				
			||||||
 | 
							ccli := getRedisClusterClient(re)
 | 
				
			||||||
 | 
							// 测试连接
 | 
				
			||||||
 | 
							_, e := ccli.Ping(context.Background()).Result()
 | 
				
			||||||
 | 
							if e != nil {
 | 
				
			||||||
 | 
								ccli.Close()
 | 
				
			||||||
 | 
								panic(biz.NewBizErr(fmt.Sprintf("redis集群连接失败: %s", e.Error())))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ri.ClusterCli = ccli
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	global.Log.Infof("连接redis: %s", re.Host)
 | 
						global.Log.Infof("连接redis: %s", re.Host)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	rcli := redis.NewClient(&redis.Options{
 | 
					 | 
				
			||||||
		Addr:     re.Host,
 | 
					 | 
				
			||||||
		Password: re.Password, // no password set
 | 
					 | 
				
			||||||
		DB:       re.Db,       // use default DB
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	// 测试连接
 | 
					 | 
				
			||||||
	_, e := rcli.Ping().Result()
 | 
					 | 
				
			||||||
	biz.ErrIsNilAppendErr(e, "redis连接失败: %s")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ri := &RedisInstance{Id: id, ProjectId: re.ProjectId, Cli: rcli}
 | 
					 | 
				
			||||||
	if needCache {
 | 
						if needCache {
 | 
				
			||||||
		redisCache.Put(re.Id, ri)
 | 
							redisCache.Put(re.Id, ri)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ri
 | 
						return ri
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getRedisCient(re *entity.Redis) *redis.Client {
 | 
				
			||||||
 | 
						return redis.NewClient(&redis.Options{
 | 
				
			||||||
 | 
							Addr:        re.Host,
 | 
				
			||||||
 | 
							Password:    re.Password, // no password set
 | 
				
			||||||
 | 
							DB:          re.Db,       // use default DB
 | 
				
			||||||
 | 
							DialTimeout: 8 * time.Second,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getRedisClusterClient(re *entity.Redis) *redis.ClusterClient {
 | 
				
			||||||
 | 
						return redis.NewClusterClient(&redis.ClusterOptions{
 | 
				
			||||||
 | 
							Addrs:       strings.Split(re.Host, ","),
 | 
				
			||||||
 | 
							Password:    re.Password,
 | 
				
			||||||
 | 
							DialTimeout: 8 * time.Second,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//------------------------------------------------------------------------------
 | 
					//------------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// redis客户端连接缓存,30分钟内没有访问则会被关闭
 | 
					// redis客户端连接缓存,30分钟内没有访问则会被关闭
 | 
				
			||||||
@@ -127,35 +158,64 @@ var redisCache = cache.NewTimedCache(30*time.Minute, 5*time.Second).
 | 
				
			|||||||
	WithUpdateAccessTime(true).
 | 
						WithUpdateAccessTime(true).
 | 
				
			||||||
	OnEvicted(func(key interface{}, value interface{}) {
 | 
						OnEvicted(func(key interface{}, value interface{}) {
 | 
				
			||||||
		global.Log.Info(fmt.Sprintf("删除redis连接缓存 id = %d", key))
 | 
							global.Log.Info(fmt.Sprintf("删除redis连接缓存 id = %d", key))
 | 
				
			||||||
		value.(*RedisInstance).Cli.Close()
 | 
							value.(*RedisInstance).Close()
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// redis实例
 | 
					 | 
				
			||||||
type RedisInstance struct {
 | 
					 | 
				
			||||||
	Id        uint64
 | 
					 | 
				
			||||||
	ProjectId uint64
 | 
					 | 
				
			||||||
	Cli       *redis.Client
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 移除redis连接缓存并关闭redis连接
 | 
					// 移除redis连接缓存并关闭redis连接
 | 
				
			||||||
func CloseRedis(id uint64) {
 | 
					func CloseRedis(id uint64) {
 | 
				
			||||||
	redisCache.Delete(id)
 | 
						redisCache.Delete(id)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRedisConnection(re *entity.Redis) {
 | 
					func TestRedisConnection(re *entity.Redis) {
 | 
				
			||||||
	rcli := redis.NewClient(&redis.Options{
 | 
						var cmd redis.Cmdable
 | 
				
			||||||
		Addr:     re.Host,
 | 
						if re.Mode == "" || re.Mode == entity.RedisModeStandalone {
 | 
				
			||||||
		Password: re.Password, // no password set
 | 
							rcli := getRedisCient(re)
 | 
				
			||||||
		DB:       re.Db,       // use default DB
 | 
							defer rcli.Close()
 | 
				
			||||||
	})
 | 
							cmd = rcli
 | 
				
			||||||
	defer rcli.Close()
 | 
						} else if re.Mode == entity.RedisModeCluster {
 | 
				
			||||||
 | 
							ccli := getRedisClusterClient(re)
 | 
				
			||||||
 | 
							defer ccli.Close()
 | 
				
			||||||
 | 
							cmd = ccli
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 测试连接
 | 
						// 测试连接
 | 
				
			||||||
	_, e := rcli.Ping().Result()
 | 
						_, e := cmd.Ping(context.Background()).Result()
 | 
				
			||||||
	biz.ErrIsNilAppendErr(e, "Redis连接失败: %s")
 | 
						biz.ErrIsNilAppendErr(e, "Redis连接失败: %s")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// redis实例
 | 
				
			||||||
 | 
					type RedisInstance struct {
 | 
				
			||||||
 | 
						Id         uint64
 | 
				
			||||||
 | 
						ProjectId  uint64
 | 
				
			||||||
 | 
						Mode       string
 | 
				
			||||||
 | 
						Cli        *redis.Client
 | 
				
			||||||
 | 
						ClusterCli *redis.ClusterClient
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取命令执行接口的具体实现
 | 
				
			||||||
 | 
					func (r *RedisInstance) GetCmdable() redis.Cmdable {
 | 
				
			||||||
 | 
						redisMode := r.Mode
 | 
				
			||||||
 | 
						if redisMode == "" || redisMode == entity.RedisModeStandalone {
 | 
				
			||||||
 | 
							return r.Cli
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if r.Mode == entity.RedisModeCluster {
 | 
				
			||||||
 | 
							return r.ClusterCli
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r *RedisInstance) Scan(cursor uint64, match string, count int64) ([]string, uint64) {
 | 
					func (r *RedisInstance) Scan(cursor uint64, match string, count int64) ([]string, uint64) {
 | 
				
			||||||
	keys, newcursor, err := r.Cli.Scan(cursor, match, count).Result()
 | 
						keys, newcursor, err := r.GetCmdable().Scan(context.Background(), cursor, match, count).Result()
 | 
				
			||||||
	biz.ErrIsNilAppendErr(err, "scan失败: %s")
 | 
						biz.ErrIsNilAppendErr(err, "scan失败: %s")
 | 
				
			||||||
	return keys, newcursor
 | 
						return keys, newcursor
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *RedisInstance) Close() {
 | 
				
			||||||
 | 
						if r.Mode == entity.RedisModeStandalone {
 | 
				
			||||||
 | 
							r.Cli.Close()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if r.Mode == entity.RedisModeCluster {
 | 
				
			||||||
 | 
							r.ClusterCli.Close()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ type Db struct {
 | 
				
			|||||||
	Username  string `orm:"column(username)" json:"username"`
 | 
						Username  string `orm:"column(username)" json:"username"`
 | 
				
			||||||
	Password  string `orm:"column(password)" json:"-"`
 | 
						Password  string `orm:"column(password)" json:"-"`
 | 
				
			||||||
	Database  string `orm:"column(database)" json:"database"`
 | 
						Database  string `orm:"column(database)" json:"database"`
 | 
				
			||||||
 | 
						Params    string `json:"params"`
 | 
				
			||||||
	ProjectId uint64
 | 
						ProjectId uint64
 | 
				
			||||||
	Project   string
 | 
						Project   string
 | 
				
			||||||
	EnvId     uint64
 | 
						EnvId     uint64
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,10 +8,17 @@ type Redis struct {
 | 
				
			|||||||
	model.Model
 | 
						model.Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Host      string `orm:"column(host)" json:"host"`
 | 
						Host      string `orm:"column(host)" json:"host"`
 | 
				
			||||||
 | 
						Mode      string `json:"mode"`
 | 
				
			||||||
	Password  string `orm:"column(password)" json:"-"`
 | 
						Password  string `orm:"column(password)" json:"-"`
 | 
				
			||||||
	Db        int    `orm:"column(database)" json:"db"`
 | 
						Db        int    `orm:"column(database)" json:"db"`
 | 
				
			||||||
 | 
						Remark    string
 | 
				
			||||||
	ProjectId uint64
 | 
						ProjectId uint64
 | 
				
			||||||
	Project   string
 | 
						Project   string
 | 
				
			||||||
	EnvId     uint64
 | 
						EnvId     uint64
 | 
				
			||||||
	Env       string
 | 
						Env       string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						RedisModeStandalone = "standalone"
 | 
				
			||||||
 | 
						RedisModeCluster    = "cluster"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,8 +35,12 @@ func InitRedisRouter(router *gin.RouterGroup) {
 | 
				
			|||||||
			ctx.NewReqCtxWithGin(c).Handle(rs.RedisInfo)
 | 
								ctx.NewReqCtxWithGin(c).Handle(rs.RedisInfo)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							redis.GET(":id/cluster-info", func(c *gin.Context) {
 | 
				
			||||||
 | 
								ctx.NewReqCtxWithGin(c).Handle(rs.ClusterInfo)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 获取指定redis keys
 | 
							// 获取指定redis keys
 | 
				
			||||||
		redis.GET(":id/scan/:cursor/:count", func(c *gin.Context) {
 | 
							redis.POST(":id/scan", func(c *gin.Context) {
 | 
				
			||||||
			ctx.NewReqCtxWithGin(c).Handle(rs.Scan)
 | 
								ctx.NewReqCtxWithGin(c).Handle(rs.Scan)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,12 +22,13 @@ DROP TABLE IF EXISTS `t_db`;
 | 
				
			|||||||
CREATE TABLE `t_db` (
 | 
					CREATE TABLE `t_db` (
 | 
				
			||||||
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 | 
					  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 | 
				
			||||||
  `name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
 | 
					  `name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
 | 
				
			||||||
  `host` varchar(20) COLLATE utf8mb4_bin NOT NULL,
 | 
					  `host` varchar(50) COLLATE utf8mb4_bin NOT NULL,
 | 
				
			||||||
  `port` int(8) NOT NULL,
 | 
					  `port` int(8) NOT NULL,
 | 
				
			||||||
  `username` varchar(255) COLLATE utf8mb4_bin NOT NULL,
 | 
					  `username` varchar(255) COLLATE utf8mb4_bin NOT NULL,
 | 
				
			||||||
  `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
  `type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
 | 
					  `type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
 | 
				
			||||||
  `database` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
 | 
					  `database` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
 | 
				
			||||||
 | 
					  `params` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '其他连接参数',
 | 
				
			||||||
  `network` varchar(8) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `network` varchar(8) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
  `project_id` bigint(20) DEFAULT NULL,
 | 
					  `project_id` bigint(20) DEFAULT NULL,
 | 
				
			||||||
  `project` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `project` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
@@ -103,7 +104,7 @@ CREATE TABLE `t_machine` (
 | 
				
			|||||||
  `project_id` bigint(20) DEFAULT NULL,
 | 
					  `project_id` bigint(20) DEFAULT NULL,
 | 
				
			||||||
  `project_name` varchar(36) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `project_name` varchar(36) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
  `name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
  `ip` varchar(18) COLLATE utf8mb4_bin NOT NULL,
 | 
					  `ip` varchar(36) COLLATE utf8mb4_bin NOT NULL,
 | 
				
			||||||
  `port` int(12) NOT NULL,
 | 
					  `port` int(12) NOT NULL,
 | 
				
			||||||
  `username` varchar(12) COLLATE utf8mb4_bin NOT NULL,
 | 
					  `username` varchar(12) COLLATE utf8mb4_bin NOT NULL,
 | 
				
			||||||
  `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
@@ -253,9 +254,11 @@ CREATE TABLE `t_project_member` (
 | 
				
			|||||||
DROP TABLE IF EXISTS `t_redis`;
 | 
					DROP TABLE IF EXISTS `t_redis`;
 | 
				
			||||||
CREATE TABLE `t_redis` (
 | 
					CREATE TABLE `t_redis` (
 | 
				
			||||||
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 | 
					  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 | 
				
			||||||
  `host` varchar(32) COLLATE utf8mb4_bin NOT NULL,
 | 
					  `host` varchar(255) COLLATE utf8mb4_bin NOT NULL,
 | 
				
			||||||
  `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
  `db` int(32) DEFAULT NULL,
 | 
					  `db` int(32) DEFAULT NULL,
 | 
				
			||||||
 | 
					  `mode` varchar(32) DEFAULT NULL,
 | 
				
			||||||
 | 
					  `remark` varchar(125) DEFAULT NULL,
 | 
				
			||||||
  `project_id` bigint(20) DEFAULT NULL,
 | 
					  `project_id` bigint(20) DEFAULT NULL,
 | 
				
			||||||
  `project` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
					  `project` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
 | 
				
			||||||
  `env_id` bigint(20) DEFAULT NULL,
 | 
					  `env_id` bigint(20) DEFAULT NULL,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
package global
 | 
					package global
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"github.com/go-redis/redis"
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,11 @@
 | 
				
			|||||||
package rediscli
 | 
					package rediscli
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-redis/redis"
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var cli *redis.Client
 | 
					var cli *redis.Client
 | 
				
			||||||
@@ -19,7 +20,7 @@ func GetCli() *redis.Client {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// get key value
 | 
					// get key value
 | 
				
			||||||
func Get(key string) string {
 | 
					func Get(key string) string {
 | 
				
			||||||
	val, err := cli.Get(key).Result()
 | 
						val, err := cli.Get(context.TODO(), key).Result()
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case err == redis.Nil:
 | 
						case err == redis.Nil:
 | 
				
			||||||
		fmt.Println("key does not exist")
 | 
							fmt.Println("key does not exist")
 | 
				
			||||||
@@ -33,32 +34,32 @@ func Get(key string) string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// set key value
 | 
					// set key value
 | 
				
			||||||
func Set(key string, val string, expiration time.Duration) {
 | 
					func Set(key string, val string, expiration time.Duration) {
 | 
				
			||||||
	cli.Set(key, val, expiration)
 | 
						cli.Set(context.TODO(), key, val, expiration)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func HSet(key string, field string, val interface{}) {
 | 
					func HSet(key string, field string, val interface{}) {
 | 
				
			||||||
	cli.HSet(key, field, val)
 | 
						cli.HSet(context.TODO(), key, field, val)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// hget
 | 
					// hget
 | 
				
			||||||
func HGet(key string, field string) string {
 | 
					func HGet(key string, field string) string {
 | 
				
			||||||
	val, _ := cli.HGet(key, field).Result()
 | 
						val, _ := cli.HGet(context.TODO(), key, field).Result()
 | 
				
			||||||
	return val
 | 
						return val
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// hget
 | 
					// hget
 | 
				
			||||||
func HExist(key string, field string) bool {
 | 
					func HExist(key string, field string) bool {
 | 
				
			||||||
	val, _ := cli.HExists(key, field).Result()
 | 
						val, _ := cli.HExists(context.TODO(), key, field).Result()
 | 
				
			||||||
	return val
 | 
						return val
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// hgetall
 | 
					// hgetall
 | 
				
			||||||
func HGetAll(key string) map[string]string {
 | 
					func HGetAll(key string) map[string]string {
 | 
				
			||||||
	vals, _ := cli.HGetAll(key).Result()
 | 
						vals, _ := cli.HGetAll(context.TODO(), key).Result()
 | 
				
			||||||
	return vals
 | 
						return vals
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// hdel
 | 
					// hdel
 | 
				
			||||||
func HDel(key string, fields ...string) int {
 | 
					func HDel(key string, fields ...string) int {
 | 
				
			||||||
	return int(cli.HDel(key, fields...).Val())
 | 
						return int(cli.HDel(context.TODO(), key, fields...).Val())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
package starter
 | 
					package starter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"mayfly-go/pkg/config"
 | 
						"mayfly-go/pkg/config"
 | 
				
			||||||
	"mayfly-go/pkg/global"
 | 
						"mayfly-go/pkg/global"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-redis/redis"
 | 
						"github.com/go-redis/redis/v8"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func InitRedis() {
 | 
					func InitRedis() {
 | 
				
			||||||
@@ -26,7 +27,7 @@ func ConnRedis() *redis.Client {
 | 
				
			|||||||
		DB:       redisConf.Db,       // use default DB
 | 
							DB:       redisConf.Db,       // use default DB
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	// 测试连接
 | 
						// 测试连接
 | 
				
			||||||
	_, e := rdb.Ping().Result()
 | 
						_, e := rdb.Ping(context.TODO()).Result()
 | 
				
			||||||
	if e != nil {
 | 
						if e != nil {
 | 
				
			||||||
		global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d]", redisConf.Host, redisConf.Port))
 | 
							global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d]", redisConf.Host, redisConf.Port))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user