fix: patch all P1 bugs from project review

- pubsub: add sender.isDestroyed() checks + destroyed event auto-cleanup (P1#10)
- pubsub: add .catch() to monitor() promise + isDestroyed check (P1#11)
- connection: move server.on('error') before server.listen() (P1#12)
- preload: onSubscribeMessage/onMonitorMessage return cleanup functions (P1#9)
- CliView: save and call IPC listener cleanup in onUnmounted (P1#9)
- KeyDetail: move setInterval into onMounted, cleanup in onUnmounted (P1#8)
- key store: add loadKeyVersion guard to prevent race condition (P1#13)
- ListEditor: LSET sentinel + LREM for index-based deletion (P1#14)
- ipc-handlers: remove non-null assertion, add null guard in reorderConnections (P1#16)
- StreamEditor: add Confirm/Cancel buttons to Trim dialog (P1#17)
- KeyBrowser: remove { immediate: true } to prevent double scan (P1#18)
- P1#7 MULTI/EXEC: investigated, not a bug (Redis server handles queuing)
- docs: update PROJECT_REVIEW.md with P1 fix status
This commit is contained in:
2026-07-12 14:18:48 +08:00
parent 9d73ff019c
commit 70db25b337
12 changed files with 112 additions and 61 deletions
+28 -17
View File
@@ -4,7 +4,7 @@
> 审查方式:并行扫描主进程、渲染进程、IPC 契约一致性
> 审查范围:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道
> 发现问题:54 个(P0×6, P1×12, P2×17, P3×19)
> 已修复:**P0×6 全部修复** (2026-07-12)
> 已修复:**P0×6 + P1×11 全部修复** (2026-07-12)
> 可开发需求:26 项(高优先级 6,中 11,低 9)
---
@@ -32,22 +32,22 @@
| 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(功能错误)
### 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 显示加密密码。 | **已修复**(P0#1 修复时创建副本而非修改原对象) |
| 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`,首次连接扫两次。 |
| # | 位置 | 问题 | 状态 |
|---|------|------|------|
| 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 - 应修复(质量/性能)
@@ -191,7 +191,7 @@
- **审查范围**:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道
- **发现问题**:54 个(P0×6, P1×12, P2×17, P3×19)
- **已修复**:P0×6 + P1×1 = **7 个** (2026-07-12)
- **已修复**:P0×6 + P1×11 = **17 个** (2026-07-12)
- **安全问题**:6 个(P0 全部已修复)
- **死代码**:6 处
- **可开发需求**:26 项(高优先级 6,中 11,低 9)
@@ -207,3 +207,14 @@
| 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 后自动排队,当前实现正确 | 无需修改 |