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 // 用于排序
lastTimestamp: number // 用于显示时间
lastMsgType: number
messageCountHint?: number
displayName?: string
avatarUrl?: string
lastMsgSender?: string
@@ -421,6 +422,21 @@ class ChatService {
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 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
@@ -439,6 +455,7 @@ class ChatService {
sortTimestamp: sortTs,
lastTimestamp: lastTs,
lastMsgType,
messageCountHint,
displayName,
avatarUrl,
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 {
const result = await wcdbService.getMessageCount(sessionId)
const nextCount = result.success && typeof result.count === 'number' ? result.count : 0
const result = await wcdbService.getMessageCounts(batch)
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
this.sessionMessageCountCache.set(sessionId, {
count: nextCount,
updatedAt: Date.now()
})
} catch {
counts[sessionId] = 0
this.sessionMessageCountCache.set(sessionId, {
count: 0,
updatedAt: Date.now()
updatedAt: nowTs
})
}
})
}
return { success: true, counts }
} 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 }> {
if (!this.ensureReady()) {
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 })
}
async getMessageCounts(sessionIds: string[]): Promise<{ success: boolean; counts?: Record<string, number>; error?: string }> {
return this.callWorker('getMessageCounts', { sessionIds })
}
/**
* 获取联系人昵称
*/