完成前端缓存监控适配
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# 前端移除 Redis 实施记录
|
||||
|
||||
## 实施时间
|
||||
- 2026-03-28
|
||||
|
||||
## 修改内容
|
||||
- 将缓存概览页从 Redis 专属字段改为本地缓存统计字段展示
|
||||
- 将第二张图表改为本地缓存统计柱状图,并保留原有页面布局
|
||||
- 为缓存概览页补充空数据保护、命中率计算和监控采样时间展示
|
||||
- 为缓存列表页补充空列表兜底、清理后的详情重置和全部清理后的界面清空
|
||||
- 复核缓存监控 API 路径不变,仅调整接口注释为缓存语义
|
||||
|
||||
## 文档路径
|
||||
- `doc/2026-03-28-remove-redis-frontend-plan.md`
|
||||
- `doc/implementation-report-2026-03-28-remove-redis-frontend.md`
|
||||
|
||||
## 验证结果
|
||||
- 已两次验证 `npm --prefix ruoyi-ui run build:prod` 通过,输出为 `Build complete.`
|
||||
- 已验证 `npm --prefix ruoyi-ui run dev` 可成功启动,前端开发服务地址为 `http://localhost:1025`
|
||||
- 已在浏览器中完成登录并打开 `/monitor/cache`、`/monitor/cacheList` 路由,确认页面可正常进入
|
||||
- 当前环境下真实接口 `/dev-api/monitor/cache`、`/dev-api/monitor/cache/getNames`、`/dev-api/monitor/cache/getKeys/*` 请求均出现 10 秒超时,未能完成真实后端联调
|
||||
- 已使用与后端实现一致的返回结构进行浏览器侧 mock 验证,确认缓存概览字段映射、图表渲染、缓存名称到键名联动、缓存内容展示以及清理后表单清空行为正常
|
||||
|
||||
## 说明
|
||||
- 按仓库要求,本次前端开发直接在当前分支进行,未使用 `git worktree`
|
||||
- 计划明确不引入新的前端测试框架,因此本次以前端生产构建与本地联调作为主要验证手段
|
||||
- 联调与验证结束后,已主动停止本次任务启动的 Node 前端进程
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询缓存详细
|
||||
// 查询缓存概览
|
||||
export function getCache() {
|
||||
return request({
|
||||
url: '/monitor/cache',
|
||||
@@ -32,7 +32,7 @@ export function getCacheValue(cacheName, cacheKey) {
|
||||
})
|
||||
}
|
||||
|
||||
// 清理指定名称缓存
|
||||
// 清理指定名称下的缓存
|
||||
export function clearCacheName(cacheName) {
|
||||
return request({
|
||||
url: '/monitor/cache/clearCacheName/' + cacheName,
|
||||
|
||||
271
ruoyi-ui/src/views/monitor/cache/index.vue
vendored
271
ruoyi-ui/src/views/monitor/cache/index.vue
vendored
@@ -8,34 +8,34 @@
|
||||
<table cellspacing="0" style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Redis版本</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">缓存类型</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ cacheTypeText }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行模式</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">端口</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">客户端数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ cacheModeText }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">总键数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ displayDbSize }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">写入次数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ cache.info.write_count }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行时间(天)</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用CPU</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">内存配置</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">命中次数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ cache.info.hit_count }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">未命中次数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ cache.info.miss_count }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">过期清理次数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ cache.info.expired_count }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">命中率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ hitRateText }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">AOF是否开启</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled == "0" ? "否" : "是" }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">RDB是否成功</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Key数量</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">网络入口/出口</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">统计说明</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">进程内累计统计</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">监控范围</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">当前应用实例</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">图表数据项</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ commandStats.length }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">监控采样时间</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sampleTime }}</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<div slot="header"><span><i class="el-icon-odometer"></i> 内存信息</span></div>
|
||||
<div slot="header"><span><i class="el-icon-odometer"></i> 缓存命中概览</span></div>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<div ref="usedmemory" style="height: 420px" />
|
||||
</div>
|
||||
@@ -74,74 +74,197 @@ export default {
|
||||
return {
|
||||
// 统计命令信息
|
||||
commandstats: null,
|
||||
// 使用内存
|
||||
// 缓存统计
|
||||
usedmemory: null,
|
||||
// cache信息
|
||||
cache: []
|
||||
cache: {
|
||||
info: {
|
||||
cache_type: "IN_MEMORY",
|
||||
cache_mode: "single-instance",
|
||||
key_size: 0,
|
||||
hit_count: 0,
|
||||
miss_count: 0,
|
||||
expired_count: 0,
|
||||
write_count: 0
|
||||
},
|
||||
dbSize: 0,
|
||||
commandStats: []
|
||||
},
|
||||
sampleTime: "-",
|
||||
resizeHandler: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.openLoading()
|
||||
this.getList()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener("resize", this.resizeHandler)
|
||||
}
|
||||
if (this.commandstats) {
|
||||
this.commandstats.dispose()
|
||||
}
|
||||
if (this.usedmemory) {
|
||||
this.usedmemory.dispose()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cacheTypeText() {
|
||||
return this.formatCacheType(this.cache.info.cache_type)
|
||||
},
|
||||
cacheModeText() {
|
||||
return this.formatCacheMode(this.cache.info.cache_mode)
|
||||
},
|
||||
displayDbSize() {
|
||||
return this.toNumber(this.cache.dbSize || this.cache.info.key_size)
|
||||
},
|
||||
hitRateText() {
|
||||
const hitCount = this.toNumber(this.cache.info.hit_count)
|
||||
const missCount = this.toNumber(this.cache.info.miss_count)
|
||||
const total = hitCount + missCount
|
||||
if (!total) {
|
||||
return "0.00%"
|
||||
}
|
||||
return ((hitCount / total) * 100).toFixed(2) + "%"
|
||||
},
|
||||
commandStats() {
|
||||
return this.normalizeCommandStats(this.cache.commandStats)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 查缓存询信息 */
|
||||
getList() {
|
||||
getCache().then((response) => {
|
||||
this.cache = response.data
|
||||
this.cache = this.normalizeCacheData(response.data)
|
||||
this.sampleTime = this.formatSampleTime(new Date())
|
||||
this.$modal.closeLoading()
|
||||
|
||||
this.commandstats = echarts.init(this.$refs.commandstats, "macarons")
|
||||
this.commandstats.setOption({
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)",
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "命令",
|
||||
type: "pie",
|
||||
roseType: "radius",
|
||||
radius: [15, 95],
|
||||
center: ["50%", "38%"],
|
||||
data: response.data.commandStats,
|
||||
animationEasing: "cubicInOut",
|
||||
animationDuration: 1000,
|
||||
}
|
||||
]
|
||||
})
|
||||
this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons")
|
||||
this.usedmemory.setOption({
|
||||
tooltip: {
|
||||
formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "峰值",
|
||||
type: "gauge",
|
||||
min: 0,
|
||||
max: 1000,
|
||||
detail: {
|
||||
formatter: this.cache.info.used_memory_human,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: parseFloat(this.cache.info.used_memory_human),
|
||||
name: "内存消耗",
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
window.addEventListener("resize", () => {
|
||||
this.commandstats.resize()
|
||||
this.usedmemory.resize()
|
||||
this.$nextTick(() => {
|
||||
this.renderCharts()
|
||||
})
|
||||
}).catch(() => {
|
||||
this.cache = this.normalizeCacheData()
|
||||
this.sampleTime = "-"
|
||||
this.$modal.closeLoading()
|
||||
})
|
||||
},
|
||||
// 打开加载层
|
||||
openLoading() {
|
||||
this.$modal.loading("正在加载缓存监控数据,请稍候!")
|
||||
},
|
||||
normalizeCacheData(data) {
|
||||
const source = data || {}
|
||||
const info = source.info || {}
|
||||
return {
|
||||
info: {
|
||||
cache_type: info.cache_type || "IN_MEMORY",
|
||||
cache_mode: info.cache_mode || "single-instance",
|
||||
key_size: this.toNumber(info.key_size),
|
||||
hit_count: this.toNumber(info.hit_count),
|
||||
miss_count: this.toNumber(info.miss_count),
|
||||
expired_count: this.toNumber(info.expired_count),
|
||||
write_count: this.toNumber(info.write_count)
|
||||
},
|
||||
dbSize: this.toNumber(source.dbSize || info.key_size),
|
||||
commandStats: source.commandStats || []
|
||||
}
|
||||
},
|
||||
normalizeCommandStats(commandStats) {
|
||||
const stats = Array.isArray(commandStats) ? commandStats : []
|
||||
return stats.map((item) => ({
|
||||
name: this.statLabel(item.name),
|
||||
value: this.toNumber(item.value)
|
||||
}))
|
||||
},
|
||||
statLabel(name) {
|
||||
const labelMap = {
|
||||
hit_count: "命中次数",
|
||||
miss_count: "未命中次数",
|
||||
expired_count: "过期清理次数",
|
||||
write_count: "写入次数"
|
||||
}
|
||||
return labelMap[name] || name || "未命名统计"
|
||||
},
|
||||
formatCacheType(type) {
|
||||
const typeMap = {
|
||||
IN_MEMORY: "进程内缓存"
|
||||
}
|
||||
return typeMap[type] || type || "-"
|
||||
},
|
||||
formatCacheMode(mode) {
|
||||
const modeMap = {
|
||||
"single-instance": "单实例"
|
||||
}
|
||||
return modeMap[mode] || mode || "-"
|
||||
},
|
||||
formatSampleTime(date) {
|
||||
const current = date || new Date()
|
||||
const pad = (value) => String(value).padStart(2, "0")
|
||||
return `${current.getFullYear()}-${pad(current.getMonth() + 1)}-${pad(current.getDate())} ${pad(current.getHours())}:${pad(current.getMinutes())}:${pad(current.getSeconds())}`
|
||||
},
|
||||
toNumber(value) {
|
||||
const parsedValue = Number(value)
|
||||
return Number.isFinite(parsedValue) ? parsedValue : 0
|
||||
},
|
||||
renderCharts() {
|
||||
if (!this.commandstats) {
|
||||
this.commandstats = echarts.init(this.$refs.commandstats, "macarons")
|
||||
}
|
||||
if (!this.usedmemory) {
|
||||
this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons")
|
||||
}
|
||||
this.commandstats.setOption({
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "缓存统计",
|
||||
type: "pie",
|
||||
roseType: "radius",
|
||||
radius: [15, 95],
|
||||
center: ["50%", "38%"],
|
||||
data: this.commandStats,
|
||||
animationEasing: "cubicInOut",
|
||||
animationDuration: 1000
|
||||
}
|
||||
]
|
||||
})
|
||||
this.usedmemory.setOption({
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "shadow"
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: this.commandStats.map((item) => item.name),
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
minInterval: 1
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "统计次数",
|
||||
type: "bar",
|
||||
barMaxWidth: 48,
|
||||
data: this.commandStats.map((item) => item.value)
|
||||
}
|
||||
]
|
||||
})
|
||||
if (!this.resizeHandler) {
|
||||
this.resizeHandler = () => {
|
||||
this.commandstats && this.commandstats.resize()
|
||||
this.usedmemory && this.usedmemory.resize()
|
||||
}
|
||||
window.addEventListener("resize", this.resizeHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
ruoyi-ui/src/views/monitor/cache/list.vue
vendored
20
ruoyi-ui/src/views/monitor/cache/list.vue
vendored
@@ -175,7 +175,10 @@ export default {
|
||||
getCacheNames() {
|
||||
this.loading = true
|
||||
listCacheName().then(response => {
|
||||
this.cacheNames = response.data
|
||||
this.cacheNames = response.data || []
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.cacheNames = []
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
@@ -188,7 +191,7 @@ export default {
|
||||
handleClearCacheName(row) {
|
||||
clearCacheName(row.cacheName).then(response => {
|
||||
this.$modal.msgSuccess("清理缓存名称[" + row.cacheName + "]成功")
|
||||
this.getCacheKeys()
|
||||
this.getCacheKeys(row)
|
||||
})
|
||||
},
|
||||
/** 查询缓存键名列表 */
|
||||
@@ -199,7 +202,13 @@ export default {
|
||||
}
|
||||
this.subLoading = true
|
||||
listCacheKey(cacheName).then(response => {
|
||||
this.cacheKeys = response.data
|
||||
this.cacheKeys = response.data || []
|
||||
this.cacheForm = {}
|
||||
this.subLoading = false
|
||||
this.nowCacheName = cacheName
|
||||
}).catch(() => {
|
||||
this.cacheKeys = []
|
||||
this.cacheForm = {}
|
||||
this.subLoading = false
|
||||
this.nowCacheName = cacheName
|
||||
})
|
||||
@@ -227,13 +236,16 @@ export default {
|
||||
/** 查询缓存内容详细 */
|
||||
handleCacheValue(cacheKey) {
|
||||
getCacheValue(this.nowCacheName, cacheKey).then(response => {
|
||||
this.cacheForm = response.data
|
||||
this.cacheForm = response.data || {}
|
||||
})
|
||||
},
|
||||
/** 清理全部缓存 */
|
||||
handleClearCacheAll() {
|
||||
clearCacheAll().then(response => {
|
||||
this.$modal.msgSuccess("清理全部缓存成功")
|
||||
this.cacheKeys = []
|
||||
this.cacheForm = {}
|
||||
this.nowCacheName = ""
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user