From b7e9c80022413d47907029c9707828dfb3acefdc Mon Sep 17 00:00:00 2001 From: Jokul Date: Sat, 11 Jul 2026 22:31:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=9E=E6=8E=A5=E6=96=AD=E5=BC=80?= =?UTF-8?q?=E5=90=8E=20IPC=20handler=20=E4=B8=8D=E5=86=8D=E6=8A=A5=20Clien?= =?UTF-8?q?t=20null=20/=20Connection=20closed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - connection.ts: 新增 requireClient(id),状态非 ready 时抛明确错误 - connection.ts: Redis client 监听 close 事件自动从 Map 清理 - 8 个 redis 模块: getClient + null check 统一替换为 requireClient - sentinel/cluster/普通连接三种模式均添加 close 自动清理 --- src/main/redis/connection.ts | 24 ++++++++++++++++++++++++ src/main/redis/hash.ts | 11 ++++------- src/main/redis/keys.ts | 29 ++++++++++------------------- src/main/redis/list.ts | 17 ++++++----------- src/main/redis/pubsub.ts | 6 +++--- src/main/redis/server.ts | 23 ++++++++--------------- src/main/redis/set.ts | 14 +++++--------- src/main/redis/stream.ts | 32 +++++++++++--------------------- src/main/redis/string.ts | 17 ++++++----------- src/main/redis/zset.ts | 14 +++++--------- 10 files changed, 82 insertions(+), 105 deletions(-) diff --git a/src/main/redis/connection.ts b/src/main/redis/connection.ts index a909cd4..780aff6 100644 --- a/src/main/redis/connection.ts +++ b/src/main/redis/connection.ts @@ -153,6 +153,14 @@ export function getClient(id: string): RedisClient | undefined { return clients.get(id)?.client } +export function requireClient(id: string): RedisClient { + const entry = clients.get(id) + if (!entry || entry.client.status !== 'ready') { + throw new Error(`Connection ${id} is not connected`) + } + return entry.client +} + export function isClientConnected(id: string): boolean { const entry = clients.get(id) return entry?.client?.status === 'ready' @@ -195,6 +203,11 @@ export async function connect(id: string, opts: ConnectionOptions): Promise { clients.set(id, { client: redis }) + redis.once('close', () => { + if (clients.get(id)?.client === redis) { + clients.delete(id) + } + }) resolve() }) redis.once('error', (err: Error) => { @@ -231,6 +244,11 @@ export async function connect(id: string, opts: ConnectionOptions): Promise { clients.set(id, { client: redis as any }) + redis.once('close', () => { + if (clients.get(id)?.client === (redis as any)) { + clients.delete(id) + } + }) resolve() }) redis.once('error', (err: Error) => { @@ -259,6 +277,12 @@ export async function connect(id: string, opts: ConnectionOptions): Promise { clients.set(id, { client: redis, sshClient, tunnelServer }) + // Auto-cleanup when connection is lost + redis.once('close', () => { + if (clients.get(id)?.client === redis) { + clients.delete(id) + } + }) resolve() }) diff --git a/src/main/redis/hash.ts b/src/main/redis/hash.ts index a8421a5..3351f7d 100644 --- a/src/main/redis/hash.ts +++ b/src/main/redis/hash.ts @@ -1,11 +1,10 @@ // Hash 操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function getHashFields( id: string, key: string, cursor: string, count: number ): Promise<{ cursor: string; fields: [string, string][] }> { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) const [nextCursor, raw] = await client.hscan(key, cursor, 'COUNT', count) const fields: [string, string][] = [] for (let i = 0; i < raw.length; i += 2) { @@ -17,13 +16,11 @@ export async function getHashFields( export async function setHashField( id: string, key: string, field: string, value: string ): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await client.hset(key, field, value) } export async function deleteHashField(id: string, key: string, field: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await client.hdel(key, field) } diff --git a/src/main/redis/keys.ts b/src/main/redis/keys.ts index d8defe2..0bc8a95 100644 --- a/src/main/redis/keys.ts +++ b/src/main/redis/keys.ts @@ -1,27 +1,23 @@ // Key 操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function getKeyType(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.type(key) } export async function getKeyTTL(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.ttl(key) } export async function deleteKey(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await client.del(key) } export async function setKeyTTL(id: string, key: string, ttl: number): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) if (ttl < 0) { await client.persist(key) } else { @@ -30,20 +26,17 @@ export async function setKeyTTL(id: string, key: string, ttl: number): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await client.rename(oldKey, newKey) } export async function existsKey(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return (await client.exists(key)) === 1 } export async function batchDelete(id: string, keys: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) if (keys.length === 0) return 0 const pipeline = client.pipeline() for (const key of keys) { @@ -54,8 +47,7 @@ export async function batchDelete(id: string, keys: string[]): Promise { } export async function memoryUsage(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.memory('USAGE', key) as Promise } @@ -65,8 +57,7 @@ export async function scanKeys( pattern: string, count: number ): Promise<{ cursor: string; keys: string[] }> { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) const [nextCursor, keys] = await client.scan(cursor, 'MATCH', pattern, 'COUNT', count) return { cursor: nextCursor, keys } } diff --git a/src/main/redis/list.ts b/src/main/redis/list.ts index 01fef69..3859a20 100644 --- a/src/main/redis/list.ts +++ b/src/main/redis/list.ts @@ -1,32 +1,27 @@ // List 操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function listRange(id: string, key: string, start: number, stop: number): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.lrange(key, start, stop) } export async function listLength(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.llen(key) } export async function listPush(id: string, key: string, ...values: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.rpush(key, ...values) } export async function listSet(id: string, key: string, index: number, value: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await client.lset(key, index, value) } export async function listRemove(id: string, key: string, count: number, value: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.lrem(key, count, value) } diff --git a/src/main/redis/pubsub.ts b/src/main/redis/pubsub.ts index 12690d7..42261e5 100644 --- a/src/main/redis/pubsub.ts +++ b/src/main/redis/pubsub.ts @@ -1,6 +1,6 @@ // 发布订阅 + MONITOR 流式接口 import Redis from 'ioredis' -import { getClient } from './connection' +import { requireClient } from './connection' import type { WebContents } from 'electron' const subClients = new Map() @@ -13,7 +13,7 @@ export function startSubscribe( sender: WebContents ): void { stopSubscribe(id) - const existing = getClient(id) + const existing = requireClient(id) if (!existing) throw new Error('No active connection') const sub = new Redis(existing.options) @@ -48,7 +48,7 @@ export function stopSubscribe(id: string): void { export function startMonitor(id: string, sender: WebContents): void { stopMonitor(id) - const existing = getClient(id) + const existing = requireClient(id) if (!existing) throw new Error('No active connection') const monitor = new Redis(existing.options) diff --git a/src/main/redis/server.ts b/src/main/redis/server.ts index 72076b8..d5c2d75 100644 --- a/src/main/redis/server.ts +++ b/src/main/redis/server.ts @@ -1,44 +1,37 @@ // 服务器操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function execute(id: string, command: string, args: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.call(command, ...args) } export async function getServerInfo(id: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.info() as Promise } export async function selectDb(id: string, db: number): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await client.select(db) } export async function dbSize(id: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.dbsize() as Promise } export async function ping(id: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.ping() } export async function slowLogGet(id: string, count: number): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.slowlog('GET', count) as Promise } export async function slowLogLen(id: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.slowlog('LEN') as Promise } diff --git a/src/main/redis/set.ts b/src/main/redis/set.ts index 96a931d..fe4d061 100644 --- a/src/main/redis/set.ts +++ b/src/main/redis/set.ts @@ -1,26 +1,22 @@ // Set 操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function setMembers(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.smembers(key) } export async function setSize(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.scard(key) } export async function setAdd(id: string, key: string, ...members: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.sadd(key, ...members) } export async function setRemove(id: string, key: string, ...members: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.srem(key, ...members) } diff --git a/src/main/redis/stream.ts b/src/main/redis/stream.ts index ee4cc6d..a785436 100644 --- a/src/main/redis/stream.ts +++ b/src/main/redis/stream.ts @@ -1,71 +1,61 @@ // Stream 操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function streamInfo(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.xinfo('STREAM', key) } export async function streamRange( id: string, key: string, start: string, end: string, count: number ): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.xrange(key, start, end, 'COUNT', count) } export async function streamAdd( id: string, key: string, streamId: string, fieldValues: string[] ): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.xadd(key, streamId, ...fieldValues) } export async function streamTrim(id: string, key: string, maxLen: number): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.xtrim(key, 'MAXLEN', maxLen) } export async function streamDel(id: string, key: string, ...ids: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.xdel(key, ...ids) } // Consumer group operations export async function streamGroups(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.xinfo('GROUPS', key) as Promise } export async function streamConsumers(id: string, key: string, group: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.xinfo('CONSUMERS', key, group) as Promise } export async function streamGroupCreate( id: string, key: string, group: string, startId: string, mkstream: boolean ): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) const args: any[] = [key, group, startId] if (mkstream) args.push('MKSTREAM') return (client as any).xgroup('CREATE', ...args) } export async function streamGroupDestroy(id: string, key: string, group: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return (client as any).xgroup('DESTROY', key, group) } export async function streamAck(id: string, key: string, group: string, ...ids: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return (client as any).xack(key, group, ...ids) } diff --git a/src/main/redis/string.ts b/src/main/redis/string.ts index 512e979..5f297ab 100644 --- a/src/main/redis/string.ts +++ b/src/main/redis/string.ts @@ -1,33 +1,28 @@ // String + ReJson 操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function getStringValue(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.get(key) } export async function setStringValue(id: string, key: string, value: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await client.set(key, value) } // ReJson operations export async function rejsonGet(id: string, key: string, path: string = '.'): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return (client as any).call('JSON.GET', key, path) } export async function rejsonSet(id: string, key: string, path: string, value: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) await (client as any).call('JSON.SET', key, path, value) } export async function rejsonDel(id: string, key: string, path: string = '.'): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return (client as any).call('JSON.DEL', key, path) } diff --git a/src/main/redis/zset.ts b/src/main/redis/zset.ts index ba2bd2a..271848d 100644 --- a/src/main/redis/zset.ts +++ b/src/main/redis/zset.ts @@ -1,27 +1,23 @@ // Sorted Set 操作 -import { getClient } from './connection' +import { requireClient } from './connection' export async function zsetRange(id: string, key: string, start: number, stop: number, withScores: boolean): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) if (withScores) return client.zrange(key, start, stop, 'WITHSCORES') return client.zrange(key, start, stop) } export async function zsetSize(id: string, key: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.zcard(key) } export async function zsetAdd(id: string, key: string, score: number, member: string): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.zadd(key, score, member) } export async function zsetRemove(id: string, key: string, ...members: string[]): Promise { - const client = getClient(id) - if (!client) throw new Error(`Client ${id} not found`) + const client = requireClient(id) return client.zrem(key, ...members) }