perf(export): cache counts and speed sns/session stats

This commit is contained in:
tisonhuang
2026-03-01 17:51:28 +08:00
parent bf9b5ba593
commit 7604ff2ae4
7 changed files with 365 additions and 62 deletions

View File

@@ -196,6 +196,9 @@ class ChatService {
// 缓存会话表信息,避免每次查询
private sessionTablesCache = new Map<string, Array<{ tableName: string; dbPath: string }>>()
private readonly sessionTablesCacheTtl = 300000 // 5分钟
private sessionMessageCountCache = new Map<string, { count: number; updatedAt: number }>()
private sessionMessageCountCacheScope = ''
private readonly sessionMessageCountCacheTtlMs = 10 * 60 * 1000
constructor() {
this.configService = new ConfigService()
@@ -795,13 +798,35 @@ class ChatService {
return { success: true, counts: {} }
}
this.refreshSessionMessageCountCacheScope()
const counts: Record<string, number> = {}
await this.forEachWithConcurrency(normalizedSessionIds, 8, async (sessionId) => {
const now = Date.now()
const pendingSessionIds: string[] = []
for (const sessionId of normalizedSessionIds) {
const cached = this.sessionMessageCountCache.get(sessionId)
if (cached && now - cached.updatedAt <= this.sessionMessageCountCacheTtlMs) {
counts[sessionId] = cached.count
} else {
pendingSessionIds.push(sessionId)
}
}
await this.forEachWithConcurrency(pendingSessionIds, 16, async (sessionId) => {
try {
const result = await wcdbService.getMessageCount(sessionId)
counts[sessionId] = result.success && typeof result.count === 'number' ? result.count : 0
const nextCount = result.success && typeof result.count === 'number' ? result.count : 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()
})
}
})
@@ -1455,6 +1480,15 @@ class ChatService {
await Promise.all(runners)
}
private refreshSessionMessageCountCacheScope(): void {
const dbPath = String(this.configService.get('dbPath') || '')
const myWxid = String(this.configService.get('myWxid') || '')
const scope = `${dbPath}::${myWxid}`
if (scope === this.sessionMessageCountCacheScope) return
this.sessionMessageCountCacheScope = scope
this.sessionMessageCountCache.clear()
}
private async collectSessionExportStats(
sessionId: string,
selfIdentitySet: Set<string>

View File

@@ -229,6 +229,10 @@ class SnsService {
private configService: ConfigService
private contactCache: ContactCacheService
private imageCache = new Map<string, string>()
private exportStatsCache: { totalPosts: number; totalFriends: number; updatedAt: number } | null = null
private readonly exportStatsCacheTtlMs = 5 * 60 * 1000
private lastTimelineFallbackAt = 0
private readonly timelineFallbackCooldownMs = 3 * 60 * 1000
constructor() {
this.configService = new ConfigService()
@@ -403,38 +407,66 @@ class SnsService {
return { success: true, usernames: result.rows.map((r: any) => r.user_name).filter(Boolean) }
}
async getExportStats(): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number }; error?: string }> {
try {
let totalPosts = 0
let totalFriends = 0
private async getExportStatsFromTableCount(): Promise<{ totalPosts: number; totalFriends: number }> {
let totalPosts = 0
let totalFriends = 0
const postCountResult = await wcdbService.execQuery('sns', null, 'SELECT COUNT(1) AS total FROM SnsTimeLine')
if (postCountResult.success && postCountResult.rows && postCountResult.rows.length > 0) {
totalPosts = this.parseCountValue(postCountResult.rows[0])
}
const postCountResult = await wcdbService.execQuery('sns', null, 'SELECT COUNT(1) AS total FROM SnsTimeLine')
if (postCountResult.success && postCountResult.rows && postCountResult.rows.length > 0) {
totalPosts = this.parseCountValue(postCountResult.rows[0])
}
if (totalPosts > 0) {
const friendCountPrimary = await wcdbService.execQuery(
if (totalPosts > 0) {
const friendCountPrimary = await wcdbService.execQuery(
'sns',
null,
"SELECT COUNT(DISTINCT user_name) AS total FROM SnsTimeLine WHERE user_name IS NOT NULL AND user_name <> ''"
)
if (friendCountPrimary.success && friendCountPrimary.rows && friendCountPrimary.rows.length > 0) {
totalFriends = this.parseCountValue(friendCountPrimary.rows[0])
} else {
const friendCountFallback = await wcdbService.execQuery(
'sns',
null,
"SELECT COUNT(DISTINCT user_name) AS total FROM SnsTimeLine WHERE user_name IS NOT NULL AND user_name <> ''"
"SELECT COUNT(DISTINCT userName) AS total FROM SnsTimeLine WHERE userName IS NOT NULL AND userName <> ''"
)
if (friendCountPrimary.success && friendCountPrimary.rows && friendCountPrimary.rows.length > 0) {
totalFriends = this.parseCountValue(friendCountPrimary.rows[0])
} else {
const friendCountFallback = await wcdbService.execQuery(
'sns',
null,
"SELECT COUNT(DISTINCT userName) AS total FROM SnsTimeLine WHERE userName IS NOT NULL AND userName <> ''"
)
if (friendCountFallback.success && friendCountFallback.rows && friendCountFallback.rows.length > 0) {
totalFriends = this.parseCountValue(friendCountFallback.rows[0])
if (friendCountFallback.success && friendCountFallback.rows && friendCountFallback.rows.length > 0) {
totalFriends = this.parseCountValue(friendCountFallback.rows[0])
}
}
}
return { totalPosts, totalFriends }
}
async getExportStats(options?: {
allowTimelineFallback?: boolean
preferCache?: boolean
}): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number }; error?: string }> {
const allowTimelineFallback = options?.allowTimelineFallback ?? true
const preferCache = options?.preferCache ?? false
const now = Date.now()
try {
if (preferCache && this.exportStatsCache && now - this.exportStatsCache.updatedAt <= this.exportStatsCacheTtlMs) {
return {
success: true,
data: {
totalPosts: this.exportStatsCache.totalPosts,
totalFriends: this.exportStatsCache.totalFriends
}
}
}
// 某些环境下 SnsTimeLine 统计查询会返回 0这里回退到与导出同源的 timeline 接口统计。
if (totalPosts <= 0 || totalFriends <= 0) {
let { totalPosts, totalFriends } = await this.getExportStatsFromTableCount()
// 某些环境下 SnsTimeLine 统计查询会返回 0这里在允许时回退到与导出同源的 timeline 接口统计。
if (
allowTimelineFallback &&
(totalPosts <= 0 || totalFriends <= 0) &&
now - this.lastTimelineFallbackAt >= this.timelineFallbackCooldownMs
) {
this.lastTimelineFallbackAt = now
const timelineStats = await this.getExportStatsFromTimeline()
if (timelineStats.totalPosts > 0) {
totalPosts = timelineStats.totalPosts
@@ -444,12 +476,34 @@ class SnsService {
}
}
this.exportStatsCache = {
totalPosts,
totalFriends,
updatedAt: Date.now()
}
return { success: true, data: { totalPosts, totalFriends } }
} catch (e) {
if (this.exportStatsCache) {
return {
success: true,
data: {
totalPosts: this.exportStatsCache.totalPosts,
totalFriends: this.exportStatsCache.totalFriends
}
}
}
return { success: false, error: String(e) }
}
}
async getExportStatsFast(): Promise<{ success: boolean; data?: { totalPosts: number; totalFriends: number }; error?: string }> {
return this.getExportStats({
allowTimelineFallback: false,
preferCache: true
})
}
// 安装朋友圈删除拦截
async installSnsBlockDeleteTrigger(): Promise<{ success: boolean; alreadyInstalled?: boolean; error?: string }> {
return wcdbService.installSnsBlockDeleteTrigger()