当前位置:首页 > 问答 > 正文

Redis开发 后台管理 从零开始轻松编写Redis管理页面,redis管理页面编写

Redis开发 | 后台管理:从零开始轻松编写Redis管理页面

场景引入:一个开发者的烦恼

"小王最近接手了一个电商项目,数据库里存着大量用户购物车数据、秒杀活动缓存和会话信息,全都用Redis处理,每次排查问题都要登录服务器敲命令行,查看键值对简直像大海捞针,团队里非技术同事更是一头雾水,总来问他'这个缓存能不能清一下?''那个数据能不能导出来?'..."

如果你也遇到过类似情况,是时候自己动手搭建一个可视化的Redis管理页面了!今天我们就从零开始,用最接地气的方式实现这个需求。


准备工作:选对工具再开工

1 技术栈选择

  • 前端:Vue.js + Element UI(简单好上手)
  • 后端:Node.js + Express(轻量级够用)
  • 数据库连接:ioredis(比node-redis更好用的客户端)

2 环境准备

# 前端项目初始化
npm install -g @vue/cli
vue create redis-admin
cd redis-admin
npm install element-ui axios --save
# 后端服务初始化
mkdir server
cd server
npm init -y
npm install express ioredis cors body-parser --save

后端篇:搭建Redis连接桥梁

1 基础服务搭建

server/index.js中写入:

const express = require('express');
const Redis = require('ioredis');
const cors = require('cors');
const bodyParser = require('body-parser');
const app = express();
app.use(cors());
app.use(bodyParser.json());
// 创建Redis连接(实际项目记得用环境变量配置)
const redis = new Redis({
  host: '127.0.0.1',
  port: 6379,
  password: '你的密码' // 没有密码就去掉这行
});
// 健康检查接口
app.get('/ping', async (req, res) => {
  try {
    const pong = await redis.ping();
    res.send(pong);
  } catch (e) {
    res.status(500).send('Redis连接失败');
  }
});
app.listen(3001, () => {
  console.log('Redis管理后端已启动: http://localhost:3001');
});

启动服务试试:

node index.js

2 核心API实现

继续在index.js中添加这些实用接口:

Redis开发 后台管理 从零开始轻松编写Redis管理页面,redis管理页面编写

// 获取键列表(支持模式匹配)
app.post('/keys', async (req, res) => {
  const { pattern = '*' } = req.body;
  try {
    const keys = await redis.keys(pattern);
    res.json({ success: true, data: keys });
  } catch (e) {
    res.status(500).json({ success: false, message: e.message });
  }
});
// 获取键值详情
app.post('/key-info', async (req, res) => {
  const { key } = req.body;
  try {
    const type = await redis.type(key);
    let value, ttl;
    switch (type) {
      case 'string':
        value = await redis.get(key);
        break;
      case 'hash':
        value = await redis.hgetall(key);
        break;
      case 'list':
        value = await redis.lrange(key, 0, -1);
        break;
      case 'set':
        value = await redis.smembers(key);
        break;
      case 'zset':
        value = await redis.zrange(key, 0, -1, 'WITHSCORES');
        break;
    }
    ttl = await redis.ttl(key);
    res.json({ success: true, data: { type, value, ttl } });
  } catch (e) {
    res.status(500).json({ success: false, message: e.message });
  }
});
// 删除键
app.post('/delete-key', async (req, res) => {
  const { key } = req.body;
  try {
    await redis.del(key);
    res.json({ success: true });
  } catch (e) {
    res.status(500).json({ success: false, message: e.message });
  }
});

前端篇:打造清爽管理界面

1 基础布局搭建

修改src/App.vue

<template>
  <div id="app">
    <el-container>
      <!-- 侧边栏 -->
      <el-aside width="250px" style="background:#545c64;min-height:100vh">
        <div style="color:#fff;padding:15px;font-size:18px">
          <i class="el-icon-menu"></i> Redis管理
        </div>
        <el-menu
          background-color="#545c64"
          text-color="#fff"
          active-text-color="#ffd04b">
          <el-menu-item index="1">
            <i class="el-icon-document"></i>
            <span>键值管理</span>
          </el-menu-item>
          <el-menu-item index="2">
            <i class="el-icon-setting"></i>
            <span>服务器状态</span>
          </el-menu-item>
        </el-menu>
      </el-aside>
      <!-- 主内容区 -->
      <el-main>
        <router-view/>
      </el-main>
    </el-container>
  </div>
</template>
<script>
export default {
  name: 'App'
}
</script>

2 键值管理页面

创建src/views/KeyManager.vue

<template>
  <div class="key-manager">
    <el-card shadow="hover">
      <div slot="header" class="clearfix">
        <span>Redis键值管理</span>
        <el-button-group style="float:right">
          <el-button size="mini" @click="refreshKeys">刷新</el-button>
          <el-button size="mini" type="primary" @click="showAddDialog">
            新增键
          </el-button>
        </el-button-group>
      </div>
      <!-- 搜索框 -->
      <el-input
        v-model="searchPattern"
        placeholder="输入匹配模式(如:user:*)"
        clearable
        style="margin-bottom:20px">
        <el-button slot="append" icon="el-icon-search" @click="fetchKeys"/>
      </el-input>
      <!-- 键列表表格 -->
      <el-table
        :data="keys"
        border
        stripe
        height="500"
        v-loading="loading"
        @row-click="showKeyDetail">
        <el-table-column prop="key" label="键名" width="300"/>
        <el-table-column label="操作" width="120">
          <template slot-scope="scope">
            <el-button
              size="mini"
              type="danger"
              icon="el-icon-delete"
              @click.stop="deleteKey(scope.row.key)"/>
          </template>
        </el-table-column>
      </el-table>
      <!-- 键详情对话框 -->
      <el-dialog :title="currentKey" :visible.sync="detailVisible" width="70%">
        <el-tabs v-model="activeTab">
          <el-tab-pane label="基本信息" name="info">
            <el-descriptions border column={2}>
              <el-descriptions-item label="键类型">
                {{ keyInfo.type }}
              </el-descriptions-item>
              <el-descriptions-item label="剩余TTL">
                {{ keyInfo.ttl === -1 ? '永不过期' : `${keyInfo.ttl}秒` }}
              </el-descriptions-item>
            </el-descriptions>
          </el-tab-pane>
          <el-tab-pane :label="`值内容(${keyInfo.type})`" name="value">
            <!-- 字符串类型展示 -->
            <el-input
              v-if="keyInfo.type === 'string'"
              type="textarea"
              :rows="5"
              v-model="keyInfo.value"
              readonly/>
            <!-- 哈希类型展示 -->
            <el-table
              v-else-if="keyInfo.type === 'hash'"
              :data="hashToArray(keyInfo.value)"
              border>
              <el-table-column prop="field" label="字段名"/>
              <el-table-column prop="value" label="字段值"/>
            </el-table>
            <!-- 其他类型展示... -->
          </el-tab-pane>
        </el-tabs>
      </el-dialog>
    </el-card>
  </div>
</template>
<script>
export default {
  data() {
    return {
      searchPattern: '*',
      keys: [],
      loading: false,
      currentKey: '',
      detailVisible: false,
      activeTab: 'info',
      keyInfo: {
        type: '',
        value: null,
        ttl: -1
      }
    }
  },
  methods: {
    async fetchKeys() {
      this.loading = true;
      try {
        const res = await this.$http.post('/keys', { 
          pattern: this.searchPattern 
        });
        this.keys = res.data.data.map(key => ({ key }));
      } catch (e) {
        this.$message.error('获取键列表失败: ' + e.message);
      } finally {
        this.loading = false;
      }
    },
    async showKeyDetail(row) {
      this.currentKey = row.key;
      try {
        const res = await this.$http.post('/key-info', { key: row.key });
        this.keyInfo = res.data.data;
        this.detailVisible = true;
      } catch (e) {
        this.$message.error('获取键详情失败: ' + e.message);
      }
    },
    async deleteKey(key) {
      try {
        await this.$confirm(`确定删除键 "${key}" 吗?`, '警告', {
          type: 'warning'
        });
        await this.$http.post('/delete-key', { key });
        this.$message.success('删除成功');
        this.fetchKeys();
      } catch (e) {
        if (e !== 'cancel') {
          this.$message.error('删除失败: ' + e.message);
        }
      }
    },
    hashToArray(hash) {
      return Object.keys(hash).map(key => ({
        field: key,
        value: hash[key]
      }));
    },
    refreshKeys() {
      this.searchPattern = '*';
      this.fetchKeys();
    }
  },
  mounted() {
    this.fetchKeys();
  }
}
</script>

3 配置路由和全局样式

src/main.js中:

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import axios from 'axios'
import router from './router'
Vue.use(ElementUI)
// 配置axios
Vue.prototype.$http = axios.create({
  baseURL: 'http://localhost:3001'
})
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

创建src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import KeyManager from '@/views/KeyManager.vue'
Vue.use(Router)
export default new Router({
  routes: [
    { path: '/', redirect: '/keys' },
    { path: '/keys', component: KeyManager }
  ]
})

进阶功能:让管理更顺手

1 添加Redis服务器状态监控

在后端index.js中添加:

Redis开发 后台管理 从零开始轻松编写Redis管理页面,redis管理页面编写

// 获取服务器信息
app.get('/server-info', async (req, res) => {
  try {
    const info = await redis.info();
    // 将Redis的INFO命令返回的多行文本转为对象
    const result = {};
    info.split('\r\n').forEach(line => {
      if (line && !line.startsWith('#')) {
        const [key, value] = line.split(':');
        if (key && value) result[key] = value;
      }
    });
    res.json({ success: true, data: result });
  } catch (e) {
    res.status(500).json({ success: false, message: e.message });
  }
});

前端添加ServerStatus.vue组件(代码类似键值管理,主要展示内存使用、连接数等关键指标)

2 实现键值修改功能

在后端添加:

// 设置键值
app.post('/set-key', async (req, res) => {
  const { key, value, ttl } = req.body;
  try {
    if (ttl > 0) {
      await redis.set(key, value, 'EX', ttl);
    } else {
      await redis.set(key, value);
    }
    res.json({ success: true });
  } catch (e) {
    res.status(500).json({ success: false, message: e.message });
  }
});

部署与安全建议

  1. 不要直接暴露给公网:内网使用或添加登录认证
  2. 接口添加权限控制:简单的可以加API密钥
  3. 生产环境配置
    // 使用连接池
    const redis = new Redis.Cluster([
      { host: 'redis-node1', port: 6379 },
      { host: 'redis-node2', port: 6379 }
    ]);

现在你的团队再也不用对着命令行发愁了!这个管理页面虽然简单,但已经覆盖了80%的日常需求,根据实际业务,你还可以继续添加:

  • 批量操作功能
  • 键值导入导出
  • 操作日志记录
  • 性能监控图表

希望这篇指南能帮你快速搭建起实用的Redis管理工具,如果有任何问题,欢迎在评论区交流讨论!

发表评论