- ipc-handlers: withLog extracts actual connection ID from args[0] (P2#20) - updater: add initialized guard to prevent duplicate IPC registration (P2#25) - win-state: log writeFile errors instead of empty callback (P2#26) - format: detectFormat samples up to 4096 chars instead of 100 (P2#28) - commandLogger: add 30+ missing write commands to WRITE_COMMANDS (P2#32) - ReJsonEditor: bind Monaco theme to monacoTheme computed (P2#19) - keys: normalize empty SCAN pattern to '*' (P2#21) - SlowLogView: remove duplicate <style scoped> block (P2#29) - CliView: deduplicate XGROUP in writeCommands Set (P2#33) - key store: use connectionStore instead of IPC for separator (P2#22) - NewConnectionDialog: add visible guard after async watch (P2#30) - KeyList: remove dangerouslyUseHTMLString, use plain text + i18n (P2#31) - KeyList: batch DUMP export with Promise.allSettled (batch=20) (P2#23) - HashEditor: lazy load field TTL on click instead of bulk HTTL (P2#24) - P2#27 already fixed in P0#2; P2#34 is feature (N4); P2#35 not a bug - docs: update PROJECT_REVIEW.md with P2 fix status
16 KiB
JRedisDesktop 项目审查报告
审查日期:2026-07-12 审查方式:并行扫描主进程、渲染进程、IPC 契约一致性 审查范围:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道 发现问题:54 个(P0×6, P1×12, P2×17, P3×19) 已修复:P0×6 + P1×11 + P2×14 全部修复 (2026-07-12) 可开发需求:26 项(高优先级 6,中 11,低 9)
目录
一、缺陷清单(按优先级)
P0 - 必须立即修复(安全/数据丢失) — 全部已修复
| # | 位置 | 问题 | 状态 |
|---|---|---|---|
| 1 | src/main/ipc-handlers.ts:55-62 |
凭据明文存储:storage:saveConnection 加密 auth/sshPassword/sshPassphrase (enc: 前缀);storage:getConnections 读取时解密 |
已修复 |
| 2 | src/main/redis/format.ts:51-79 |
命令注入:新增 ALLOWED_FORMATTERS 白名单(xxd/jq/python3/python/php/column),不在白名单则拒绝执行 |
已修复 |
| 3 | src/main/ipc-handlers.ts:105-121 |
无命令白名单:新增 BLOCKED_COMMANDS (FLUSHALL/FLUSHDB/SHUTDOWN/DEBUG);redis:execute 拦截危险命令;新增 redis:executeUnsafe 供 CLI 绕过 |
已修复 |
| 4 | src/main/index.ts:67-70 |
退出无清理:新增 app.on('will-quit') 调用 disconnectAll() + stopAllPubSubClients() |
已修复 |
| 5 | src/main/redis/connection.ts (3处) |
超时竞态:Sentinel/Cluster/普通模式的 setTimeout 均保存 timer ID,在 ready/error 路径调用 clearTimeout | 已修复 |
| 6 | src/main/credential.ts:4-5 |
safeStorage 不可用时回退明文:encrypt/decrypt 在 safeStorage 不可用时抛出错误,decrypt catch 保留 base64 兼容旧数据并加 console.warn | 已修复 |
P1 - 严重 Bug(功能错误) - 全部已处理
| # | 位置 | 问题 | 状态 |
|---|---|---|---|
| 7 | CliView.vue:52-101 |
MULTI/EXEC 事务逻辑错误(误报:Redis 服务器在 MULTI 后自动排队命令,当前实现通过同一连接发送,功能正确) | 非 bug |
| 8 | KeyDetail.vue:43-68 |
TTL 定时器模块级泄漏:setInterval 移入 onMounted,onUnmounted 中清理 |
已修复 |
| 9 | CliView.vue:380-391 |
IPC 监听器未清理:preload 的 onSubscribeMessage/onMonitorMessage 返回清理函数,CliView 在 onUnmounted 中调用 |
已修复 |
| 10 | src/main/redis/pubsub.ts |
PubSub sender 失效:添加 sender.isDestroyed() 检查 + sender.once('destroyed', ...) 自动清理 |
已修复 |
| 11 | src/main/redis/pubsub.ts:57 |
Monitor 未处理 rejection:添加 .catch() 处理 Promise 拒绝 + isDestroyed 检查 |
已修复 |
| 12 | src/main/redis/connection.ts:96-104 |
SSH error 监听器注册过晚:server.on('error') 移到 server.listen() 之前 |
已修复 |
| 13 | key.ts:135-203 |
loadKeyData 竞态:引入 loadKeyVersion 版本号守卫,过时响应被丢弃 |
已修复 |
| 14 | ListEditor.vue:84-93 |
LREM 按值删除删错项:改为 LSET 哨兵值 + LREM 按索引删除 | 已修复 |
| 15 | connection.ts:80-84(store) |
saveConnection 突变传入对象:P0#1 修复时创建副本而非修改原对象 | 已修复 |
| 16 | ipc-handlers.ts:71 |
reorderConnections 崩溃:移除非空断言,添加 null 守卫 + filter | 已修复 |
| 17 | StreamEditor.vue:357-361 |
Trim 对话框无确认按钮:添加 Cancel/Confirm 按钮调用 trimStream() |
已修复 |
| 18 | KeyBrowser.vue + DbSelector.vue |
双重扫描:移除 KeyBrowser watch 的 { immediate: true } |
已修复 |
P2 - 应修复(质量/性能) - 全部已处理
| # | 位置 | 问题 | 状态 |
|---|---|---|---|
| 19 | ReJsonEditor.vue:88 |
Monaco 主题改为 :theme="monacoTheme" 动态绑定 |
已修复 |
| 20 | ipc-handlers.ts:13,16 |
withLog 从 args[0] 提取实际连接 ID |
已修复 |
| 21 | keys.ts:96 |
空模式规范化为 '*' |
已修复 |
| 22 | key.ts:115-119 |
改从 useConnectionStore() 获取 separator,消除 IPC 往返 |
已修复 |
| 23 | KeyList.vue:143-180 |
批量导出改为 Promise.allSettled 分批并行(每批 20) |
已修复 |
| 24 | HashEditor.vue:80-89 |
HTTL 改为点击字段时按需加载(延迟加载) | 已修复 |
| 25 | updater.ts |
initUpdater 添加 initialized 守卫防重复注册 |
已修复 |
| 26 | win-state.ts:47 |
写错误回调改为 console.error |
已修复 |
| 27 | format.ts:56-58 |
临时目录清理(随 P0#2 修复) | 已修复 |
| 28 | format.ts:46 |
detectFormat 检查范围扩大到 4096 字符 |
已修复 |
| 29 | SlowLogView.vue:241-283 |
删除重复 <style scoped> 块 |
已修复 |
| 30 | NewConnectionDialog.vue:46-95 |
async watch 中 await 后加 if (!props.visible) return 守卫 |
已修复 |
| 31 | KeyList.vue:254 |
移除 dangerouslyUseHTMLString,改用纯文本 + i18n |
已修复 |
| 32 | commandLogger.ts:16-25 |
WRITE_COMMANDS 补充 30+ 缺失写命令 |
已修复 |
| 33 | CliView.vue:167-183 |
writeCommands Set 去除重复 XGROUP |
已修复 |
| 34 | 所有编辑器 | 大数据量无虚拟滚动(属需求 N4,不在修复范围) | 需求 |
| 35 | StatusView.vue:144 |
refreshTimer 已在 <script setup> 内,onUnmounted 正确清理 |
非 bug |
P3 - 代码质量改进
| # | 问题 |
|---|---|
| 36 | any 滥用:ipc-handlers.ts、connection.ts(sshConfig/tlsOpts/clusterOptions)、string.ts、stream.ts、preload/index.d.ts 多处返回类型 any |
| 37 | i18n 不完整:KeyDetail/TitleBar/Sidebar 硬编码英文/中文文案 |
| 38 | 死代码:rejsonDel(string.ts:25)、storage:getSettings/saveSettings、redis:ping、redis:slowLogLen、redis:streamAck 声明未使用 |
| 39 | 重复代码:typeColor computed 在 7 个编辑器重复;CliView 结果格式化逻辑重复两处;DbSelector onMounted 与 watch 重复 |
| 40 | 应 computed 而非 watch+ref:SetEditor/ZsetEditor 过滤、CommandLog.filteredEntries |
| 41 | AGENTS.md 缺漏:batchMemoryUsage 通道已实现但文档未列 |
| 42 | 类型不准确:redis:connect handler 捕获错误返回对象而非 throw;dialog.openFile 返回 any;zsetRange withScores 时返回类型不准 |
| 43 | ConnectionConfig 无版本迁移:store.ts 直接 JSON 存取,字段变更无迁移路径 |
| 44 | 静默 catch:TLS/SSH 文件读取失败静默回退,用户不知密钥未生效 |
二、系统性架构问题
问题 A:资源生命周期管理完全缺失
表现:连接泄漏(P0#4)、定时器泄漏(P1#8,P2#35)、IPC 监听器泄漏(P1#9)、主题监听器无清理、healthTimers Map 无 dispose、PubSub sender 失效(P1#10)。
根因:无统一的资源注册与清理机制,各组件/store 自行管理,遗忘清理。
架构级方案:引入 Disposable 模式 -- 统一的资源注册表(连接、定时器、监听器、订阅),在窗口/应用生命周期事件统一释放。主进程加 app.on('will-quit') 调 disconnectAll+stopAllPubSub;渲染进程用 tryOnScopeDispose 或统一 composable 管理 setInterval/IPC 监听。
问题 B:安全模型不完整
表现:凭据明文(P0#1)、safeStorage 回退明文(P0#6)、命令注入(P0#2)、无命令白名单(P0#3)、XSS 风险(P2#31)。
根因:安全作为事后补丁而非设计原则,无统一的安全边界。
架构级方案:
- 凭据:所有敏感字段(auth/sshPassword/sshPassphrase)在 store 层自动加解密,safeStorage 不可用时拒绝存储而非明文回退
- 命令:
redis:execute加中间件层,非 CLI 视图禁用破坏性命令;customFormat加白名单 - 输入:移除
dangerouslyUseHTMLString或用严格 sanitizer
问题 C:错误处理策略不统一
表现:redis:connect 吞错误返回对象,其他 handler throw;disconnect 无 try/catch;win-state 静默;PubSub Promise 未 await;Monitor 无 catch;TLS/SSH 文件读取静默。
根因:无统一 IPC 错误处理中间件。
架构级方案:所有 ipcMain.handle 包统一 wrapper,自动 try/catch 并以标准错误对象 throw;渲染进程封装 safeInvoke 统一 ElMessage 错误提示。
问题 D:类型安全形同虚设
表现:大量 any、as any、preload d.ts 返回类型 any、ConnectionConfig 无 schema 版本。
架构级方案:定义 Redis 操作的完整类型(替换 ioredis as any);preload d.ts 用泛型精确化返回类型;ConnectionConfig 加 version 字段与迁移函数。
问题 E:性能策略缺失
表现:无虚拟滚动、串行 IPC、重复 IPC 调用、SCAN 全量加载。
架构级方案:编辑器引入 el-table-v2/虚拟列表;批量操作用 pipeline;key.ts 从 store 取连接信息而非重复 IPC。
三、可开发需求清单(按优先级)
高优先级
| # | 需求 | 用户价值 | 复杂度 | 实现思路 |
|---|---|---|---|---|
| N1 | 凭据加密加固 | 修复 P0 安全漏洞,密码不再明文落盘 | 低 | storage:saveConnection 前 encrypt 敏感字段,getConnections 时 decrypt;safeStorage 不可用拒绝存储 |
| N2 | 资源生命周期管理器 | 修复所有泄漏,退出干净释放 | 中 | 主进程 app.on('will-quit') 调清理;渲染进程 useDisposable composable |
| N3 | CLI 事务正确实现 | MULTI/EXEC 真正生效 | 中 | 事务模式本地排队,EXEC 时 pipeline 一次性发送 |
| N4 | 虚拟滚动 | 数万字段/成员不卡顿 | 中 | Hash/List/Set/ZsetEditor 用 el-table-v2 或自定义虚拟列表 |
| N5 | Stream Consumer Group 完整化 | ACK 已实现但未用,消费组可视化不全 | 中 | StreamEditor 接入 redis:streamAck;加消费组详情面板、pending entries 列表、claim 功能 |
| N6 | 命令白名单中间件 | 防误操作 FLUSHALL/SHUTDOWN | 低 | redis:execute 加危险命令二次确认 |
中优先级
| # | 需求 | 用户价值 | 复杂度 | 实现思路 |
|---|---|---|---|---|
| N7 | Cluster/Sentinel 支持 | 生产环境主流部署方式,当前仅单机+SSH | 高 | connection.ts 已有 cluster 分支需完善;加节点拓扑可视化、failover 操作 |
| N8 | ACL 用户管理 | Redis 6+ ACL 已普及 | 中 | 新建 ACL 管理视图:用户 CRUD、权限矩阵、ACL WHOAMI/ACL LIST |
| N9 | 数据导入导出 | 迁移/备份场景 | 中 | 导出:RDB/DUMP/JSON 格式;导入:批量 SET/管道;KeyList 已有导出雏形需完善 |
| N10 | Function 管理(Redis 7+) | 替代 EVAL 的服务端函数 | 中 | 新建 Function 视图:LIST/LOAD/DELETE/调用 |
| N11 | 大 Key 扫描 | 生产排障刚需 | 中 | MEMORY USAGE 扫描已有,扩展为后台任务 + 排序 + 导出报告 |
| N12 | Latency Monitor | 补充慢日志的延迟诊断 | 低 | LATENCY HISTORY/LATENCY DOCTOR 视图 |
| N13 | PubSub 可视化改进 | 当前仅 CLI 内嵌,无独立视图 | 中 | 新建 PubSub 视图:频道树、消息流、JSON 高亮、订阅状态 |
| N14 | 连接分组/标签 | 连接多了难管理 | 低 | ConnectionList 加分组折叠、颜色标签、搜索过滤已有可扩展 |
| N15 | TTL 实时倒计时优化 | 修复泄漏 + 加批量设 TTL | 低 | 移到 onMounted;KeyList 批量设 TTL/过期时间 |
| N16 | 主题完整适配 | ReJsonEditor 主题修复 + Monaco 跟随 | 低 | Monaco 编辑器 theme 绑定到 monacoTheme 响应式变量 |
| N17 | i18n 补全 | 中/日/韩/德文案完整 | 低 | 扫描硬编码文案替换为 t() |
低优先级
| # | 需求 | 用户价值 | 复杂度 | 实现思路 |
|---|---|---|---|---|
| N18 | Key 详情对比 | 编辑前后 diff | 中 | StringEditor/HashEditor 加 diff 视图 |
| N19 | 命令收藏夹 | 常用命令快速调用 | 低 | CliView 加收藏侧栏,localStorage 持久化 |
| N20 | 连接 SSH 跳板机 | 多跳 SSH 场景 | 中 | ssh2 隧道级联 |
| N21 | 慢日志分析图表 | 趋势可视化 | 中 | SlowLogView 加 ECharts 时间分布图 |
| N22 | 快捷键体系完善 | 当前仅 5 个 | 低 | 加 Ctrl+F 搜索、Ctrl+1~9 切 tab、Ctrl+L 聚焦 CLI |
| N23 | 测试体系 | 当前无测试 | 高 | Vitest 渲染进程单元测试;主进程 Redis 模块用 ioredis-mock |
| N24 | CI/CD | 自动化构建发布 | 中 | GitHub Actions:lint+typecheck+build 三平台 |
| N25 | TypeScript 严格化 | 类型安全 | 中 | 逐步消除 any,preload d.ts 精确化 |
| N26 | 错误上报 | 收集崩溃信息 | 低 | 主进程 crashReporter + 渲染进程 errorHandler |
四、修复路线图建议
立即(P0): [已完成] N1 凭据加密 -> N2 资源管理 -> N6 命令白名单 -> P0#2/5/6
本周(P1): N3 事务修复 -> P1 定时器/监听器泄漏 -> N5 Stream ACK -> N16 主题
本月(P2): N4 虚拟滚动 -> N7 Cluster -> N9 导入导出 -> N11 大Key扫描 -> N13 PubSub视图
季度(P3): N8 ACL -> N10 Function -> N23 测试 -> N25 严格化 -> N24 CI
五、关键数据
- 审查范围:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道
- 发现问题:54 个(P0×6, P1×12, P2×17, P3×19)
- 已修复:P0×6 + P1×11 + P2×14 = 31 个 (2026-07-12)
- 安全问题:6 个(P0 全部已修复)
- 死代码:6 处
- 可开发需求:26 项(高优先级 6,中 11,低 9)
修复记录 (2026-07-12)
| 缺陷 | 修复内容 | 涉及文件 |
|---|---|---|
| P0#1 凭据明文 | storage:saveConnection 加密敏感字段;getConnections 读取时解密 | ipc-handlers.ts |
| P0#2 命令注入 | customFormat 白名单(xxd/jq/python3/python/php/column) | format.ts |
| P0#3 无命令白名单 | BLOCKED_COMMANDS 拦截 FLUSHALL/SHUTDOWN/DEBUG;新增 executeUnsafe | ipc-handlers.ts, preload/index.ts, preload/index.d.ts, CliView.vue, StatusView.vue |
| P0#4 退出无清理 | app.on('will-quit') 调 disconnectAll + stopAllPubSubClients | index.ts, pubsub.ts |
| P0#5 超时竞态 | 3处 setTimeout 保存 timer ID,ready/error 路径 clearTimeout | connection.ts |
| P0#6 safeStorage 回退 | encrypt/decrypt 在 safeStorage 不可用时抛出错误 | credential.ts |
| P1#15 突变副作用 | saveConnection 创建副本而非修改原对象(随 P0#1 修复) | ipc-handlers.ts |
| P1#8 TTL 定时器泄漏 | setInterval 移入 onMounted,onUnmounted 中清理 | KeyDetail.vue |
| P1#9 IPC 监听器泄漏 | preload onSubscribeMessage/onMonitorMessage 返回清理函数,CliView onUnmounted 调用 | preload/index.ts, preload/index.d.ts, CliView.vue |
| P1#10 PubSub sender 失效 | 添加 sender.isDestroyed() 检查 + destroyed 事件自动清理 | pubsub.ts |
| P1#11 Monitor 未处理 rejection | 添加 .catch() + isDestroyed 检查 | pubsub.ts |
| P1#12 SSH error 监听器过晚 | server.on('error') 移到 server.listen() 之前 | connection.ts |
| P1#13 loadKeyData 竞态 | 引入 loadKeyVersion 版本号守卫,丢弃过时响应 | stores/key.ts |
| P1#14 LREM 删错项 | 改为 LSET 哨兵值 + LREM 按索引删除 | ListEditor.vue |
| P1#16 reorderConnections 崩溃 | 移除非空断言,添加 null 守卫 + filter | ipc-handlers.ts |
| P1#17 Trim 对话框无确认 | 添加 Cancel/Confirm 按钮调用 trimStream() | StreamEditor.vue |
| P1#18 双重扫描 | 移除 KeyBrowser watch 的 { immediate: true } | KeyBrowser.vue |
| P1#7 MULTI/EXEC(误报) | 调查确认:Redis 服务器在 MULTI 后自动排队,当前实现正确 | 无需修改 |