- credential: encrypt/decrypt now throw when safeStorage unavailable instead of base64 fallback
- ipc-handlers: encrypt auth/sshPassword/sshPassphrase before storing, decrypt on read (avoid mutation)
- ipc-handlers: block FLUSHALL/FLUSHDB/SHUTDOWN/DEBUG in redis:execute, add redis:executeUnsafe for CLI
- format: whitelist allowed external formatters (xxd/jq/python3/python/php/column), clean up tmpDir
- connection: clearTimeout on ready/error paths to fix timeout race in Sentinel/Cluster/normal modes
- index: add app.on('will-quit') to disconnectAll + stopAllPubSubClients on exit
- pubsub: add stopAllPubSubClients() to clean up all sub/monitor connections
- docs: add PROJECT_REVIEW.md with full audit report and fix status
14 KiB
JRedisDesktop 项目审查报告
审查日期:2026-07-12 审查方式:并行扫描主进程、渲染进程、IPC 契约一致性 审查范围:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道 发现问题:54 个(P0×6, P1×12, P2×17, P3×19) 已修复:P0×6 全部修复 (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 事务逻辑错误:事务模式下每条命令仍通过 IPC 单独发送到 Redis,服务器立即执行而非排队。完全破坏事务语义。应本地排队,EXEC 时 pipeline 一次性发送。 |
| 8 | KeyDetail.vue:43-68 |
TTL 定时器模块级泄漏:setInterval 在 <script setup> 顶层立即创建,非 onMounted。组件销毁后定时器仍每秒运行访问已卸载响应式数据。 |
| 9 | CliView.vue:380-391 |
IPC 监听器未清理:onSubscribeMessage/onMonitorMessage 在 onUnmounted 中未移除。每次切换标签页累积监听器。 |
| 10 | src/main/redis/pubsub.ts:13,24-31 |
PubSub sender 失效:存储 WebContents 引用,窗口重载(HMR/导航)后失效,消息静默丢弃。无检测/重建机制。 |
| 11 | src/main/redis/pubsub.ts:57 |
Monitor 未处理 rejection:monitor.monitor().then(...) 无 .catch(),失败时未处理 Promise 拒绝。 |
| 12 | src/main/redis/connection.ts:96-104 |
SSH error 监听器注册过晚:server.listen() 在 server.on('error') 之前,期间错误未捕获。 |
| 13 | key.ts:135-203 |
loadKeyData 竞态:快速点击不同 key 时后 resolve 的响应覆盖最新选中 key 的数据。 |
| 14 | ListEditor.vue:84-93 |
LREM 按值删除删错项:重复值时删除第一个匹配,非当前选中索引。 |
| 15 | connection.ts:80-84(store) |
saveConnection 突变传入对象:直接修改 conn.auth 为加密串,若调用方传响应式对象则 UI 显示加密密码。 |
| 16 | ipc-handlers.ts:71 |
reorderConnections 崩溃:非空断言 !,ID 不匹配时 TypeError。 |
| 17 | StreamEditor.vue:357-361 |
Trim 对话框无确认按钮:打开后只能关闭,无法执行 trim。 |
| 18 | KeyBrowser.vue + DbSelector.vue |
双重扫描:KeyBrowser 的 watch immediate 与 DbSelector 的 onMounted 都调 scanKeys,首次连接扫两次。 |
P2 - 应修复(质量/性能)
| # | 位置 | 问题 |
|---|---|---|
| 19 | ReJsonEditor.vue:88 |
Monaco 主题硬编码 vs-dark,亮色主题下对比度极差。 |
| 20 | ipc-handlers.ts:13,16 |
withLog 记录 'current' 而非实际连接 ID,命令日志无用。 |
| 21 | keys.ts:96 |
空模式 '' 导致 SCAN 匹配空字符串键,应规范化为 '*'。 |
| 22 | key.ts:115-119 |
每次扫描都调 getConnections() IPC,连接信息已在 store 中。 |
| 23 | KeyList.vue:143-180 |
批量导出串行 DUMP,1000 key = 1000 次 IPC 往返。 |
| 24 | HashEditor.vue:80-89 |
逐个字段 HTTL,大 hash 数百次 IPC。 |
| 25 | updater.ts:15,25,35 |
重复调用 initUpdater 时 ipcMain.handle 抛重复注册错误。 |
| 26 | win-state.ts:47 |
写错误空回调静默吞噬。 |
| 27 | format.ts:56-58 |
临时目录 mkdtemp 创建后成功路径不清理,/tmp/jrdm-fmt-*/ 累积。 |
| 28 | format.ts:46 |
detectFormat 仅检查前 100 字符,前 100 为 ASCII 的二进制值误判为 text。 |
| 29 | SlowLogView.vue:241-283 |
重复 <style scoped> 块。 |
| 30 | NewConnectionDialog.vue:46-95 |
异步 watch 返回后可能对话框已关闭仍在设状态。 |
| 31 | KeyList.vue:254 |
dangerouslyUseHTMLString: true 潜在 XSS(依赖 escapeHtml 完整性)。 |
| 32 | commandLogger.ts:16-25 |
WRITE_COMMANDS 缺 JSON.SET/JSON.DEL/COPY/RESTORE 等。 |
| 33 | CliView.vue:167-183 |
writeCommands Set 含重复 XGROUP 三次。 |
| 34 | 所有编辑器 | 大数据量无虚拟滚动,数万条记录一次性渲染。 |
| 35 | StatusView.vue:144 |
refreshTimer 模块级变量,重新挂载丢引用。 |
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×1 = 7 个 (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 |