diff --git a/docs/PROJECT_REVIEW.md b/docs/PROJECT_REVIEW.md index fb6b71c..e42d4eb 100644 --- a/docs/PROJECT_REVIEW.md +++ b/docs/PROJECT_REVIEW.md @@ -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` 在 ` diff --git a/src/renderer/src/components/key/KeyDetail.vue b/src/renderer/src/components/key/KeyDetail.vue index 6ccbffc..53c1408 100644 --- a/src/renderer/src/components/key/KeyDetail.vue +++ b/src/renderer/src/components/key/KeyDetail.vue @@ -1,5 +1,5 @@ diff --git a/src/renderer/src/stores/key.ts b/src/renderer/src/stores/key.ts index c97dcfc..77eef2f 100644 --- a/src/renderer/src/stores/key.ts +++ b/src/renderer/src/stores/key.ts @@ -132,10 +132,14 @@ export const useKeyStore = defineStore('key', () => { selectedTTL.value = ttl } + let loadKeyVersion = 0 + async function loadKeyData(connId: string, key: string) { + const currentVersion = ++loadKeyVersion loading.value = true try { await loadKeyTypeAndTTL(connId, key) + if (currentVersion !== loadKeyVersion) return selectedKey.value = key // Key no longer exists — show expired state and refresh list @@ -160,29 +164,35 @@ export const useKeyStore = defineStore('key', () => { break case 'hash': { const result = await window.electronAPI.redis.hashScan(connId, key, '0', 100) + if (currentVersion !== loadKeyVersion) return keyData.value = result.fields break } case 'list': { const len = await window.electronAPI.redis.listLength(connId, key) + if (currentVersion !== loadKeyVersion) return keyData.value = { length: len } break } case 'set': { const size = await window.electronAPI.redis.setSize(connId, key) + if (currentVersion !== loadKeyVersion) return keyData.value = { size } break } case 'zset': { const size = await window.electronAPI.redis.zsetSize(connId, key) + if (currentVersion !== loadKeyVersion) return keyData.value = { size } break } case 'stream': { try { const info = await window.electronAPI.redis.streamInfo(connId, key) + if (currentVersion !== loadKeyVersion) return keyData.value = { length: info.length } } catch { + if (currentVersion !== loadKeyVersion) return keyData.value = { length: 0 } } break @@ -195,6 +205,7 @@ export const useKeyStore = defineStore('key', () => { // command (e.g. key was overwritten). Re-fetch the type so the // correct editor is shown. await loadKeyTypeAndTTL(connId, key) + if (currentVersion !== loadKeyVersion) return keyData.value = null } } finally {