perf(export): batch message count retrieval for large session lists

This commit is contained in:
tisonhuang
2026-03-01 18:02:51 +08:00
parent a8eb0057e3
commit 5147b3f0e4
6 changed files with 81 additions and 12 deletions

View File

@@ -29,6 +29,7 @@ export interface ChatSession {
sortTimestamp: number // 用于排序 sortTimestamp: number // 用于排序
lastTimestamp: number // 用于显示时间 lastTimestamp: number // 用于显示时间
lastMsgType: number lastMsgType: number
messageCountHint?: number
displayName?: string displayName?: string
avatarUrl?: string avatarUrl?: string
lastMsgSender?: string lastMsgSender?: string
@@ -421,6 +422,21 @@ class ChatService {
const summary = this.cleanString(row.summary || row.digest || row.last_msg || row.lastMsg || '') const summary = this.cleanString(row.summary || row.digest || row.last_msg || row.lastMsg || '')
const lastMsgType = parseInt(row.last_msg_type || row.lastMsgType || '0', 10) const lastMsgType = parseInt(row.last_msg_type || row.lastMsgType || '0', 10)
const messageCountHintRaw =
row.message_count ??
row.messageCount ??
row.msg_count ??
row.msgCount ??
row.total_count ??
row.totalCount ??
row.n_msg ??
row.nMsg ??
row.message_num ??
row.messageNum
const parsedMessageCountHint = Number(messageCountHintRaw)
const messageCountHint = Number.isFinite(parsedMessageCountHint) && parsedMessageCountHint >= 0
? Math.floor(parsedMessageCountHint)
: undefined
// 先尝试从缓存获取联系人信息(快速路径) // 先尝试从缓存获取联系人信息(快速路径)
let displayName = username let displayName = username
@@ -439,6 +455,7 @@ class ChatService {
sortTimestamp: sortTs, sortTimestamp: sortTs,
lastTimestamp: lastTs, lastTimestamp: lastTs,
lastMsgType, lastMsgType,
messageCountHint,
displayName, displayName,
avatarUrl, avatarUrl,
lastMsgSender: row.last_msg_sender, lastMsgSender: row.last_msg_sender,
@@ -812,23 +829,30 @@ class ChatService {
} }
} }
await this.forEachWithConcurrency(pendingSessionIds, 16, async (sessionId) => { const batchSize = 320
for (let i = 0; i < pendingSessionIds.length; i += batchSize) {
const batch = pendingSessionIds.slice(i, i + batchSize)
let batchCounts: Record<string, number> = {}
try { try {
const result = await wcdbService.getMessageCount(sessionId) const result = await wcdbService.getMessageCounts(batch)
const nextCount = result.success && typeof result.count === 'number' ? result.count : 0 if (result.success && result.counts) {
batchCounts = result.counts
}
} catch {
// noop
}
const nowTs = Date.now()
for (const sessionId of batch) {
const nextCountRaw = batchCounts[sessionId]
const nextCount = Number.isFinite(nextCountRaw) ? Math.max(0, Math.floor(nextCountRaw)) : 0
counts[sessionId] = nextCount counts[sessionId] = nextCount
this.sessionMessageCountCache.set(sessionId, { this.sessionMessageCountCache.set(sessionId, {
count: nextCount, count: nextCount,
updatedAt: Date.now() updatedAt: nowTs
})
} catch {
counts[sessionId] = 0
this.sessionMessageCountCache.set(sessionId, {
count: 0,
updatedAt: Date.now()
}) })
} }
}) }
return { success: true, counts } return { success: true, counts }
} catch (e) { } catch (e) {

View File

@@ -1144,6 +1144,40 @@ export class WcdbCore {
} }
} }
async getMessageCounts(sessionIds: string[]): Promise<{ success: boolean; counts?: Record<string, number>; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
const normalizedSessionIds = Array.from(
new Set(
(sessionIds || [])
.map((id) => String(id || '').trim())
.filter(Boolean)
)
)
if (normalizedSessionIds.length === 0) {
return { success: true, counts: {} }
}
try {
const counts: Record<string, number> = {}
for (let i = 0; i < normalizedSessionIds.length; i += 1) {
const sessionId = normalizedSessionIds[i]
const outCount = [0]
const result = this.wcdbGetMessageCount(this.handle, sessionId, outCount)
counts[sessionId] = result === 0 && Number.isFinite(outCount[0]) ? Math.max(0, Math.floor(outCount[0])) : 0
if (i > 0 && i % 160 === 0) {
await new Promise(resolve => setImmediate(resolve))
}
}
return { success: true, counts }
} catch (e) {
return { success: false, error: String(e) }
}
}
async getDisplayNames(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> { async getDisplayNames(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> {
if (!this.ensureReady()) { if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' } return { success: false, error: 'WCDB 未连接' }
@@ -2093,4 +2127,3 @@ export class WcdbCore {
}) })
} }
} }

View File

@@ -218,6 +218,10 @@ export class WcdbService {
return this.callWorker('getMessageCount', { sessionId }) return this.callWorker('getMessageCount', { sessionId })
} }
async getMessageCounts(sessionIds: string[]): Promise<{ success: boolean; counts?: Record<string, number>; error?: string }> {
return this.callWorker('getMessageCounts', { sessionIds })
}
/** /**
* 获取联系人昵称 * 获取联系人昵称
*/ */

View File

@@ -54,6 +54,9 @@ if (parentPort) {
case 'getMessageCount': case 'getMessageCount':
result = await core.getMessageCount(payload.sessionId) result = await core.getMessageCount(payload.sessionId)
break break
case 'getMessageCounts':
result = await core.getMessageCounts(payload.sessionIds)
break
case 'getDisplayNames': case 'getDisplayNames':
result = await core.getDisplayNames(payload.usernames) result = await core.getDisplayNames(payload.usernames)
break break

View File

@@ -624,6 +624,10 @@ function ExportPage() {
const count = prev[session.username] const count = prev[session.username]
if (typeof count === 'number') { if (typeof count === 'number') {
next[session.username] = count next[session.username] = count
continue
}
if (typeof session.messageCountHint === 'number' && Number.isFinite(session.messageCountHint) && session.messageCountHint >= 0) {
next[session.username] = Math.floor(session.messageCountHint)
} }
} }
return next return next

View File

@@ -7,6 +7,7 @@ export interface ChatSession {
sortTimestamp: number // 用于排序 sortTimestamp: number // 用于排序
lastTimestamp: number // 用于显示时间 lastTimestamp: number // 用于显示时间
lastMsgType: number lastMsgType: number
messageCountHint?: number // 会话总消息数提示(若底层直接可取)
displayName?: string displayName?: string
avatarUrl?: string avatarUrl?: string
lastMsgSender?: string lastMsgSender?: string