Files
WeFlow/electron/services/wcdbService.ts
cc 2e41a03c96 feat: 所有数据解析完全后台进行以解决页面未响应的问题;优化了头像渲染逻辑以提升渲染速度
fix: 修复了虚拟机上无法索引到wxkey的问题;修复图片密钥扫描的问题;修复年度报告错误;修复了年度报告和数据分析中的发送者错误问题;修复了部分页面偶发的未渲染名称问题;修复了头像偶发渲染失败的问题;修复了部分图片无法解密的问题
2026-01-14 22:43:42 +08:00

347 lines
10 KiB
TypeScript

import { Worker } from 'worker_threads'
import { join } from 'path'
import { existsSync } from 'fs'
/**
* Worker 消息接口
*/
interface WorkerMessage {
id: number
result?: any
error?: string
}
/**
* WCDB 服务 (客户端代理)
* 负责与后台 Worker 线程通信,执行数据库操作
* 避免主进程阻塞
*/
export class WcdbService {
private worker: Worker | null = null
private messageId = 0
private pending = new Map<number, { resolve: (val: any) => void; reject: (err: any) => void }>()
private resourcesPath: string | null = null
private userDataPath: string | null = null
private logEnabled = false
constructor() {
this.initWorker()
}
/**
* 初始化 Worker 线程
*/
private initWorker() {
if (this.worker) return
const isDev = process.env.NODE_ENV === 'development'
const workerPath = isDev
? join(__dirname, '../dist-electron/wcdbWorker.js')
: join(__dirname, 'wcdbWorker.js')
let finalPath = workerPath
if (isDev && !existsSync(finalPath)) {
finalPath = join(__dirname, 'wcdbWorker.js')
}
try {
this.worker = new Worker(finalPath)
this.worker.on('message', (msg: WorkerMessage) => {
const { id, result, error } = msg
const p = this.pending.get(id)
if (p) {
this.pending.delete(id)
if (error) p.reject(new Error(error))
else p.resolve(result)
}
})
this.worker.on('error', (err) => {
console.error('WCDB Worker 错误:', err)
})
this.worker.on('exit', (code) => {
if (code !== 0) console.error(`WCDB Worker 异常退出,退出码: ${code}`)
this.worker = null
})
// 如果已有路径配置,重新发送给新的 worker
if (this.resourcesPath && this.userDataPath) {
this.setPaths(this.resourcesPath, this.userDataPath)
}
this.setLogEnabled(this.logEnabled)
} catch (e) {
console.error('创建 WCDB Worker 失败:', e)
}
}
/**
* 发送消息到 Worker 并等待响应
*/
private callWorker<T>(type: string, payload: any = {}): Promise<T> {
if (!this.worker) this.initWorker()
if (!this.worker) return Promise.reject(new Error('WCDB Worker 不可用'))
return new Promise((resolve, reject) => {
const id = ++this.messageId
this.pending.set(id, { resolve, reject })
this.worker!.postMessage({ id, type, payload })
})
}
/**
* 设置资源路径
*/
setPaths(resourcesPath: string, userDataPath: string): void {
this.resourcesPath = resourcesPath
this.userDataPath = userDataPath
this.callWorker('setPaths', { resourcesPath, userDataPath }).catch(console.error)
}
/**
* 启用/禁用日志
*/
setLogEnabled(enabled: boolean): void {
this.logEnabled = enabled
this.callWorker('setLogEnabled', { enabled }).catch(console.error)
}
/**
* 检查服务是否就绪
*/
isReady(): boolean {
return !!this.worker
}
// ==========================================
// 代理方法 (Proxy Methods)
// ==========================================
/**
* 测试数据库连接
*/
async testConnection(dbPath: string, hexKey: string, wxid: string): Promise<{ success: boolean; error?: string; sessionCount?: number }> {
return this.callWorker('testConnection', { dbPath, hexKey, wxid })
}
/**
* 打开数据库
*/
async open(dbPath: string, hexKey: string, wxid: string): Promise<boolean> {
return this.callWorker('open', { dbPath, hexKey, wxid })
}
/**
* 关闭数据库连接
*/
async close(): Promise<void> {
return this.callWorker('close')
}
/**
* 关闭服务
*/
shutdown(): void {
this.close()
if (this.worker) {
this.worker.terminate()
this.worker = null
}
}
/**
* 获取数据库连接状态
* 注意:此方法现在是异步的
*/
async isConnected(): Promise<boolean> {
return this.callWorker('isConnected')
}
/**
* 获取会话列表
*/
async getSessions(): Promise<{ success: boolean; sessions?: any[]; error?: string }> {
return this.callWorker('getSessions')
}
/**
* 获取消息列表
*/
async getMessages(sessionId: string, limit: number, offset: number): Promise<{ success: boolean; messages?: any[]; error?: string }> {
return this.callWorker('getMessages', { sessionId, limit, offset })
}
/**
* 获取消息总数
*/
async getMessageCount(sessionId: string): Promise<{ success: boolean; count?: number; error?: string }> {
return this.callWorker('getMessageCount', { sessionId })
}
/**
* 获取联系人昵称
*/
async getDisplayNames(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> {
return this.callWorker('getDisplayNames', { usernames })
}
/**
* 获取头像 URL
*/
async getAvatarUrls(usernames: string[]): Promise<{ success: boolean; map?: Record<string, string>; error?: string }> {
return this.callWorker('getAvatarUrls', { usernames })
}
/**
* 获取群成员数量
*/
async getGroupMemberCount(chatroomId: string): Promise<{ success: boolean; count?: number; error?: string }> {
return this.callWorker('getGroupMemberCount', { chatroomId })
}
/**
* 批量获取群成员数量
*/
async getGroupMemberCounts(chatroomIds: string[]): Promise<{ success: boolean; map?: Record<string, number>; error?: string }> {
return this.callWorker('getGroupMemberCounts', { chatroomIds })
}
/**
* 获取群成员列表
*/
async getGroupMembers(chatroomId: string): Promise<{ success: boolean; members?: any[]; error?: string }> {
return this.callWorker('getGroupMembers', { chatroomId })
}
/**
* 获取消息表列表
*/
async getMessageTables(sessionId: string): Promise<{ success: boolean; tables?: any[]; error?: string }> {
return this.callWorker('getMessageTables', { sessionId })
}
/**
* 获取消息表统计
*/
async getMessageTableStats(sessionId: string): Promise<{ success: boolean; tables?: any[]; error?: string }> {
return this.callWorker('getMessageTableStats', { sessionId })
}
/**
* 获取消息元数据
*/
async getMessageMeta(dbPath: string, tableName: string, limit: number, offset: number): Promise<{ success: boolean; rows?: any[]; error?: string }> {
return this.callWorker('getMessageMeta', { dbPath, tableName, limit, offset })
}
/**
* 获取联系人详情
*/
async getContact(username: string): Promise<{ success: boolean; contact?: any; error?: string }> {
return this.callWorker('getContact', { username })
}
/**
* 获取聚合统计数据
*/
async getAggregateStats(sessionIds: string[], beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
return this.callWorker('getAggregateStats', { sessionIds, beginTimestamp, endTimestamp })
}
/**
* 获取可用年份
*/
async getAvailableYears(sessionIds: string[]): Promise<{ success: boolean; data?: number[]; error?: string }> {
return this.callWorker('getAvailableYears', { sessionIds })
}
/**
* 获取年度报告统计
*/
async getAnnualReportStats(sessionIds: string[], beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
return this.callWorker('getAnnualReportStats', { sessionIds, beginTimestamp, endTimestamp })
}
/**
* 获取年度报告扩展数据
*/
async getAnnualReportExtras(sessionIds: string[], beginTimestamp: number, endTimestamp: number, peakDayBegin: number, peakDayEnd: number): Promise<{ success: boolean; data?: any; error?: string }> {
return this.callWorker('getAnnualReportExtras', { sessionIds, beginTimestamp, endTimestamp, peakDayBegin, peakDayEnd })
}
/**
* 获取群聊统计
*/
async getGroupStats(chatroomId: string, beginTimestamp: number = 0, endTimestamp: number = 0): Promise<{ success: boolean; data?: any; error?: string }> {
return this.callWorker('getGroupStats', { chatroomId, beginTimestamp, endTimestamp })
}
/**
* 打开消息游标
*/
async openMessageCursor(sessionId: string, batchSize: number, ascending: boolean, beginTimestamp: number, endTimestamp: number): Promise<{ success: boolean; cursor?: number; error?: string }> {
return this.callWorker('openMessageCursor', { sessionId, batchSize, ascending, beginTimestamp, endTimestamp })
}
/**
* 打开轻量级消息游标
*/
async openMessageCursorLite(sessionId: string, batchSize: number, ascending: boolean, beginTimestamp: number, endTimestamp: number): Promise<{ success: boolean; cursor?: number; error?: string }> {
return this.callWorker('openMessageCursorLite', { sessionId, batchSize, ascending, beginTimestamp, endTimestamp })
}
/**
* 获取下一批消息
*/
async fetchMessageBatch(cursor: number): Promise<{ success: boolean; rows?: any[]; hasMore?: boolean; error?: string }> {
return this.callWorker('fetchMessageBatch', { cursor })
}
/**
* 关闭消息游标
*/
async closeMessageCursor(cursor: number): Promise<{ success: boolean; error?: string }> {
return this.callWorker('closeMessageCursor', { cursor })
}
/**
* 执行 SQL 查询
*/
async execQuery(kind: string, path: string | null, sql: string): Promise<{ success: boolean; rows?: any[]; error?: string }> {
return this.callWorker('execQuery', { kind, path, sql })
}
/**
* 获取表情包 CDN URL
*/
async getEmoticonCdnUrl(dbPath: string, md5: string): Promise<{ success: boolean; url?: string; error?: string }> {
return this.callWorker('getEmoticonCdnUrl', { dbPath, md5 })
}
/**
* 列出消息数据库
*/
async listMessageDbs(): Promise<{ success: boolean; data?: string[]; error?: string }> {
return this.callWorker('listMessageDbs')
}
/**
* 列出媒体数据库
*/
async listMediaDbs(): Promise<{ success: boolean; data?: string[]; error?: string }> {
return this.callWorker('listMediaDbs')
}
/**
* 根据 ID 获取消息
*/
async getMessageById(sessionId: string, localId: number): Promise<{ success: boolean; message?: any; error?: string }> {
return this.callWorker('getMessageById', { sessionId, localId })
}
}
export const wcdbService = new WcdbService()