Files
JRedisDesktop/docs/PROJECT_REVIEW.md
T
Jokul bf0bccb19f refactor: patch all P3 code quality issues, complete full audit
- string.ts: remove dead rejsonDel function (P3#38)
- AGENTS.md: add batchMemoryUsage and executeUnsafe to IPC channels table (P3#41)
- connection.ts: add console.warn to silent TLS/SSH catch blocks (P3#44)
- i18n: replace hardcoded strings in KeyDetail/TitleBar/Sidebar with t() (P3#37)
- composables/useTypeColor.ts: extract shared typeColor, replace in 7 editors (P3#39)
- CliView: extract formatRedisResult function to deduplicate (P3#39)
- SetEditor/ZsetEditor/CommandLog: watch+ref -> computed (P3#40)
- preload/index.d.ts: replace 15+ any types with concrete types (P3#36, P3#42)
- store.ts: add ConnectionConfig version field + migrateConnections() (P3#43)
- docs: update PROJECT_REVIEW.md - all 54 issues processed
2026-07-12 14:42:17 +08:00

221 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JRedisDesktop 项目审查报告
> 审查日期:2026-07-12
> 审查方式:并行扫描主进程、渲染进程、IPC 契约一致性
> 审查范围:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道
> 发现问题:54 个(P0×6, P1×12, P2×17, P3×19)
> 已修复:**P0×6 + P1×11 + P2×14 + P3×9 = 全部 54 个问题已处理** (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` 滥用**:preload d.ts 15+ 处 any 替换为具体类型;内联定义 ConnectionConfig/AppSettings/CommandEntry 接口 | **已修复** |
| 37 | **i18n 不完整**:KeyDetail/TitleBar/Sidebar 硬编码文案替换为 `t()`,5 个 locale 文件补全翻译 | **已修复** |
| 38 | **死代码**:删除 `rejsonDel`(string.ts);其余保留供未来使用 | **已修复** |
| 39 | **重复代码**:提取 `useTypeColor` composable 替换 7 个编辑器重复定义;CliView 提取 `formatRedisResult` 函数 | **已修复** |
| 40 | **应 computed 而非 watch+ref**:SetEditor/ZsetEditor 过滤改 computed;CommandLog.filteredEntries 改 computed | **已修复** |
| 41 | **AGENTS.md 缺漏**:补充 `batchMemoryUsage``executeUnsafe` 通道 | **已修复** |
| 42 | **类型不准确**:connect/scanKeys/hashScan/dialog.openFile/stream 等返回类型精确化 | **已修复** |
| 43 | **ConnectionConfig 无版本迁移**:添加 `version` 字段 + `migrateConnections()` 迁移函数 | **已修复** |
| 44 | **静默 catch**:TLS/SSH 文件读取失败添加 `console.warn` 日志 | **已修复** |
---
## 二、系统性架构问题
### 问题 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 + P3×9 = **全部 54 个问题已处理** (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 后自动排队,当前实现正确 | 无需修改 |