Merge pull request #136 from JiQingzhe2004/dev

feat: 一些适配
This commit is contained in:
Forrest
2026-01-29 22:03:02 +08:00
committed by GitHub
14 changed files with 1580 additions and 242 deletions

View File

@@ -369,6 +369,66 @@ function createVideoPlayerWindow(videoPath: string, videoWidth?: number, videoHe
return win
}
/**
* 创建独立的聊天记录窗口
*/
function createChatHistoryWindow(sessionId: string, messageId: number) {
const isDev = !!process.env.VITE_DEV_SERVER_URL
const iconPath = isDev
? join(__dirname, '../public/icon.ico')
: join(process.resourcesPath, 'icon.ico')
// 根据系统主题设置窗口背景色
const isDark = nativeTheme.shouldUseDarkColors
const win = new BrowserWindow({
width: 600,
height: 800,
minWidth: 400,
minHeight: 500,
icon: iconPath,
webPreferences: {
preload: join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
},
titleBarStyle: 'hidden',
titleBarOverlay: {
color: '#00000000',
symbolColor: isDark ? '#ffffff' : '#1a1a1a',
height: 32
},
show: false,
backgroundColor: isDark ? '#1A1A1A' : '#F0F0F0',
autoHideMenuBar: true
})
win.once('ready-to-show', () => {
win.show()
})
if (process.env.VITE_DEV_SERVER_URL) {
win.loadURL(`${process.env.VITE_DEV_SERVER_URL}#/chat-history/${sessionId}/${messageId}`)
win.webContents.on('before-input-event', (event, input) => {
if (input.key === 'F12' || (input.control && input.shift && input.key === 'I')) {
if (win.webContents.isDevToolsOpened()) {
win.webContents.closeDevTools()
} else {
win.webContents.openDevTools()
}
event.preventDefault()
}
})
} else {
win.loadFile(join(__dirname, '../dist/index.html'), {
hash: `/chat-history/${sessionId}/${messageId}`
})
}
return win
}
function showMainWindow() {
shouldShowMain = true
if (mainWindowReady) {
@@ -530,6 +590,12 @@ function registerIpcHandlers() {
createVideoPlayerWindow(videoPath, videoWidth, videoHeight)
})
// 打开聊天记录窗口
ipcMain.handle('window:openChatHistoryWindow', (_, sessionId: string, messageId: number) => {
createChatHistoryWindow(sessionId, messageId)
return true
})
// 根据视频尺寸调整窗口大小
ipcMain.handle('window:resizeToFitVideo', (event, videoWidth: number, videoHeight: number) => {
const win = BrowserWindow.fromWebContents(event.sender)
@@ -698,7 +764,7 @@ function registerIpcHandlers() {
})
})
ipcMain.handle('chat:getMessageById', async (_, sessionId: string, localId: number) => {
ipcMain.handle('chat:getMessage', async (_, sessionId: string, localId: number) => {
return chatService.getMessageById(sessionId, localId)
})

View File

@@ -57,7 +57,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
openVideoPlayerWindow: (videoPath: string, videoWidth?: number, videoHeight?: number) =>
ipcRenderer.invoke('window:openVideoPlayerWindow', videoPath, videoWidth, videoHeight),
resizeToFitVideo: (videoWidth: number, videoHeight: number) =>
ipcRenderer.invoke('window:resizeToFitVideo', videoWidth, videoHeight)
ipcRenderer.invoke('window:resizeToFitVideo', videoWidth, videoHeight),
openChatHistoryWindow: (sessionId: string, messageId: number) =>
ipcRenderer.invoke('window:openChatHistoryWindow', sessionId, messageId)
},
// 数据库路径
@@ -121,7 +123,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
},
execQuery: (kind: string, path: string | null, sql: string) =>
ipcRenderer.invoke('chat:execQuery', kind, path, sql),
getContacts: () => ipcRenderer.invoke('chat:getContacts')
getContacts: () => ipcRenderer.invoke('chat:getContacts'),
getMessage: (sessionId: string, localId: number) =>
ipcRenderer.invoke('chat:getMessage', sessionId, localId)
},

View File

@@ -58,6 +58,26 @@ export interface Message {
encrypVer?: number
cdnThumbUrl?: string
voiceDurationSeconds?: number
// Type 49 细分字段
linkTitle?: string // 链接/文件标题
linkUrl?: string // 链接 URL
linkThumb?: string // 链接缩略图
fileName?: string // 文件名
fileSize?: number // 文件大小
fileExt?: string // 文件扩展名
xmlType?: string // XML 中的 type 字段
// 名片消息
cardUsername?: string // 名片的微信ID
cardNickname?: string // 名片的昵称
// 聊天记录
chatRecordTitle?: string // 聊天记录标题
chatRecordList?: Array<{
datatype: number
sourcename: string
sourcetime: string
datadesc: string
datatitle?: string
}>
}
export interface Contact {
@@ -106,6 +126,9 @@ class ChatService {
timeColumn?: string
name2IdTable?: string
}>()
// 缓存会话表信息,避免每次查询
private sessionTablesCache = new Map<string, Array<{ tableName: string; dbPath: string }>>()
private readonly sessionTablesCacheTtl = 300000 // 5分钟
constructor() {
this.configService = new ConfigService()
@@ -1023,6 +1046,26 @@ class ChatService {
let encrypVer: number | undefined
let cdnThumbUrl: string | undefined
let voiceDurationSeconds: number | undefined
// Type 49 细分字段
let linkTitle: string | undefined
let linkUrl: string | undefined
let linkThumb: string | undefined
let fileName: string | undefined
let fileSize: number | undefined
let fileExt: string | undefined
let xmlType: string | undefined
// 名片消息
let cardUsername: string | undefined
let cardNickname: string | undefined
// 聊天记录
let chatRecordTitle: string | undefined
let chatRecordList: Array<{
datatype: number
sourcename: string
sourcetime: string
datadesc: string
datatitle?: string
}> | undefined
if (localType === 47 && content) {
const emojiInfo = this.parseEmojiInfo(content)
@@ -1040,6 +1083,23 @@ class ChatService {
videoMd5 = this.parseVideoMd5(content)
} else if (localType === 34 && content) {
voiceDurationSeconds = this.parseVoiceDurationSeconds(content)
} else if (localType === 42 && content) {
// 名片消息
const cardInfo = this.parseCardInfo(content)
cardUsername = cardInfo.username
cardNickname = cardInfo.nickname
} else if (localType === 49 && content) {
// Type 49 消息(链接、文件、小程序、转账等)
const type49Info = this.parseType49Message(content)
xmlType = type49Info.xmlType
linkTitle = type49Info.linkTitle
linkUrl = type49Info.linkUrl
linkThumb = type49Info.linkThumb
fileName = type49Info.fileName
fileSize = type49Info.fileSize
fileExt = type49Info.fileExt
chatRecordTitle = type49Info.chatRecordTitle
chatRecordList = type49Info.chatRecordList
} else if (localType === 244813135921 || (content && content.includes('<type>57</type>'))) {
const quoteInfo = this.parseQuoteMessage(content)
quotedContent = quoteInfo.content
@@ -1066,7 +1126,18 @@ class ChatService {
voiceDurationSeconds,
aesKey,
encrypVer,
cdnThumbUrl
cdnThumbUrl,
linkTitle,
linkUrl,
linkThumb,
fileName,
fileSize,
fileExt,
xmlType,
cardUsername,
cardNickname,
chatRecordTitle,
chatRecordList
})
const last = messages[messages.length - 1]
if ((last.localType === 3 || last.localType === 34) && (last.localId === 0 || last.createTime === 0)) {
@@ -1164,17 +1235,35 @@ class ChatService {
return `[链接] ${title}`
case '6':
return `[文件] ${title}`
case '19':
return `[聊天记录] ${title}`
case '33':
case '36':
return `[小程序] ${title}`
case '57':
// 引用消息title 就是回复的内容
return title
case '2000':
return `[转账] ${title}`
default:
return title
}
}
return '[消息]'
// 如果没有 title根据 type 返回默认标签
switch (type) {
case '6':
return '[文件]'
case '19':
return '[聊天记录]'
case '33':
case '36':
return '[小程序]'
case '2000':
return '[转账]'
default:
return '[消息]'
}
}
/**
@@ -1458,6 +1547,185 @@ class ChatService {
}
}
/**
* 解析名片消息
* 格式: <msg username="wxid_xxx" nickname="昵称" ... />
*/
private parseCardInfo(content: string): { username?: string; nickname?: string } {
try {
if (!content) return {}
// 提取 username
const username = this.extractXmlAttribute(content, 'msg', 'username') || undefined
// 提取 nickname
const nickname = this.extractXmlAttribute(content, 'msg', 'nickname') || undefined
return { username, nickname }
} catch (e) {
console.error('[ChatService] 名片解析失败:', e)
return {}
}
}
/**
* 解析 Type 49 消息(链接、文件、小程序、转账等)
* 根据 <appmsg><type>X</type> 区分不同类型
*/
private parseType49Message(content: string): {
xmlType?: string
linkTitle?: string
linkUrl?: string
linkThumb?: string
fileName?: string
fileSize?: number
fileExt?: string
chatRecordTitle?: string
chatRecordList?: Array<{
datatype: number
sourcename: string
sourcetime: string
datadesc: string
datatitle?: string
}>
} {
try {
if (!content) return {}
// 提取 appmsg 中的 type
const xmlType = this.extractXmlValue(content, 'type')
if (!xmlType) return {}
const result: any = { xmlType }
// 提取通用字段
const title = this.extractXmlValue(content, 'title')
const url = this.extractXmlValue(content, 'url')
switch (xmlType) {
case '6': {
// 文件消息
result.fileName = title || this.extractXmlValue(content, 'filename')
result.linkTitle = result.fileName
// 提取文件大小
const fileSizeStr = this.extractXmlValue(content, 'totallen') ||
this.extractXmlValue(content, 'filesize')
if (fileSizeStr) {
const size = parseInt(fileSizeStr, 10)
if (!isNaN(size)) {
result.fileSize = size
}
}
// 提取文件扩展名
const fileExt = this.extractXmlValue(content, 'fileext')
if (fileExt) {
result.fileExt = fileExt
} else if (result.fileName) {
// 从文件名提取扩展名
const match = /\.([^.]+)$/.exec(result.fileName)
if (match) {
result.fileExt = match[1]
}
}
break
}
case '19': {
// 聊天记录
result.chatRecordTitle = title || '聊天记录'
// 解析聊天记录列表
const recordList: Array<{
datatype: number
sourcename: string
sourcetime: string
datadesc: string
datatitle?: string
}> = []
// 查找所有 <recorditem> 标签
const recordItemRegex = /<recorditem>([\s\S]*?)<\/recorditem>/gi
let match: RegExpExecArray | null
while ((match = recordItemRegex.exec(content)) !== null) {
const itemXml = match[1]
const datatypeStr = this.extractXmlValue(itemXml, 'datatype')
const sourcename = this.extractXmlValue(itemXml, 'sourcename')
const sourcetime = this.extractXmlValue(itemXml, 'sourcetime')
const datadesc = this.extractXmlValue(itemXml, 'datadesc')
const datatitle = this.extractXmlValue(itemXml, 'datatitle')
if (sourcename && datadesc) {
recordList.push({
datatype: datatypeStr ? parseInt(datatypeStr, 10) : 0,
sourcename,
sourcetime: sourcetime || '',
datadesc,
datatitle: datatitle || undefined
})
}
}
if (recordList.length > 0) {
result.chatRecordList = recordList
}
break
}
case '33':
case '36': {
// 小程序
result.linkTitle = title
result.linkUrl = url
// 提取缩略图
const thumbUrl = this.extractXmlValue(content, 'thumburl') ||
this.extractXmlValue(content, 'cdnthumburl')
if (thumbUrl) {
result.linkThumb = thumbUrl
}
break
}
case '2000': {
// 转账
result.linkTitle = title || '[转账]'
// 可以提取转账金额等信息
const payMemo = this.extractXmlValue(content, 'pay_memo')
const feedesc = this.extractXmlValue(content, 'feedesc')
if (payMemo) {
result.linkTitle = payMemo
} else if (feedesc) {
result.linkTitle = feedesc
}
break
}
default: {
// 其他类型,提取通用字段
result.linkTitle = title
result.linkUrl = url
const thumbUrl = this.extractXmlValue(content, 'thumburl') ||
this.extractXmlValue(content, 'cdnthumburl')
if (thumbUrl) {
result.linkThumb = thumbUrl
}
}
}
return result
} catch (e) {
console.error('[ChatService] Type 49 消息解析失败:', e)
return {}
}
}
//手动查找 media_*.db 文件(当 WCDB DLL 不支持 listMediaDbs 时的 fallback
private async findMediaDbsManually(): Promise<string[]> {
try {
@@ -3198,19 +3466,35 @@ class ChatService {
async getMessageById(sessionId: string, localId: number): Promise<{ success: boolean; message?: Message; error?: string }> {
try {
// 1. 获取会话所在的消息
// 注意:这里使用 getMessageTableStats 而不是 getMessageTables因为前者包含 db_path
const tableStats = await wcdbService.getMessageTableStats(sessionId)
if (!tableStats.success || !tableStats.tables || tableStats.tables.length === 0) {
return { success: false, error: '未找到会话消息表' }
// 1. 尝试从缓存获取会话表信息
let tables = this.sessionTablesCache.get(sessionId)
if (!tables) {
// 缓存未命中,查询数据库
const tableStats = await wcdbService.getMessageTableStats(sessionId)
if (!tableStats.success || !tableStats.tables || tableStats.tables.length === 0) {
return { success: false, error: '未找到会话消息表' }
}
// 提取表信息并缓存
tables = tableStats.tables
.map(t => ({
tableName: t.table_name || t.name,
dbPath: t.db_path
}))
.filter(t => t.tableName && t.dbPath) as Array<{ tableName: string; dbPath: string }>
if (tables.length > 0) {
this.sessionTablesCache.set(sessionId, tables)
// 设置过期清理
setTimeout(() => {
this.sessionTablesCache.delete(sessionId)
}, this.sessionTablesCacheTtl)
}
}
// 2. 遍历表查找消息 (通常只有一个主表,但可能有归档)
for (const tableInfo of tableStats.tables) {
const tableName = tableInfo.table_name || tableInfo.name
const dbPath = tableInfo.db_path
if (!tableName || !dbPath) continue
for (const { tableName, dbPath } of tables) {
// 构造查询
const sql = `SELECT * FROM ${tableName} WHERE local_id = ${localId} LIMIT 1`
const result = await wcdbService.execQuery('message', dbPath, sql)

View File

@@ -415,14 +415,8 @@ export class ImageDecryptService {
if (imageDatName) this.cacheDatPath(accountDir, imageDatName, hardlinkPath)
return hardlinkPath
}
// hardlink 找到的是缩略图,但要求高清图:尝试同目录查找高清变体
// hardlink 找到的是缩略图,但要求高清图,直接返回 null不再搜索
if (!allowThumbnail && isThumb) {
const hdPath = this.findHdVariantInSameDir(hardlinkPath)
if (hdPath) {
this.cacheDatPath(accountDir, imageMd5, hdPath)
if (imageDatName) this.cacheDatPath(accountDir, imageDatName, hdPath)
return hdPath
}
return null
}
}
@@ -438,11 +432,6 @@ export class ImageDecryptService {
return fallbackPath
}
if (!allowThumbnail && isThumb) {
const hdPath = this.findHdVariantInSameDir(fallbackPath)
if (hdPath) {
this.cacheDatPath(accountDir, imageDatName, hdPath)
return hdPath
}
return null
}
}
@@ -460,20 +449,15 @@ export class ImageDecryptService {
this.cacheDatPath(accountDir, imageDatName, hardlinkPath)
return hardlinkPath
}
// hardlink 找到的是缩略图,但要求高清图:尝试同目录查找高清变体
// hardlink 找到的是缩略图,但要求高清图,直接返回 null
if (!allowThumbnail && isThumb) {
const hdPath = this.findHdVariantInSameDir(hardlinkPath)
if (hdPath) {
this.cacheDatPath(accountDir, imageDatName, hdPath)
return hdPath
}
return null
}
}
this.logInfo('[ImageDecrypt] hardlink miss (datName)', { imageDatName })
}
// 如果要求高清图但 hardlink 没找到,也不要搜索全盘了(搜索太慢)
// 如果要求高清图但 hardlink 没找到,也不要搜索了(搜索太慢)
if (!allowThumbnail) {
return null
}
@@ -483,9 +467,6 @@ export class ImageDecryptService {
const cached = this.resolvedCache.get(imageDatName)
if (cached && existsSync(cached)) {
if (allowThumbnail || !this.isThumbnailPath(cached)) return cached
// 缓存的是缩略图,尝试同目录找高清变体
const hdPath = this.findHdVariantInSameDir(cached)
if (hdPath) return hdPath
}
}
@@ -530,38 +511,6 @@ export class ImageDecryptService {
return this.searchDatFile(accountDir, imageDatName, true, true)
}
/**
* 在同目录中尝试查找高清图变体
* 缩略图: xxx_t.dat / xxx.t.dat -> 高清图: xxx_h.dat / xxx.h.dat / xxx.dat
*/
private findHdVariantInSameDir(thumbPath: string): string | null {
try {
const dir = dirname(thumbPath)
const fileName = basename(thumbPath).toLowerCase()
let baseName = fileName
if (baseName.endsWith('_t.dat')) {
baseName = baseName.slice(0, -6)
} else if (baseName.endsWith('.t.dat')) {
baseName = baseName.slice(0, -6)
} else {
return null
}
const variants = [
`${baseName}_h.dat`,
`${baseName}.h.dat`,
`${baseName}.dat`
]
for (const variant of variants) {
const candidate = join(dir, variant)
if (existsSync(candidate)) return candidate
}
} catch { }
return null
}
private async checkHasUpdate(
payload: { sessionId?: string; imageMd5?: string; imageDatName?: string },
cacheKey: string,
@@ -950,42 +899,71 @@ export class ImageDecryptService {
}
private findCachedOutput(cacheKey: string, preferHd: boolean = false, sessionId?: string): string | null {
const root = this.getCacheRoot()
const allRoots = this.getAllCacheRoots()
const normalizedKey = this.normalizeDatBase(cacheKey.toLowerCase())
const extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp']
if (sessionId) {
const sessionDir = join(root, this.sanitizeDirName(sessionId))
if (existsSync(sessionDir)) {
try {
const sessionEntries = readdirSync(sessionDir)
for (const entry of sessionEntries) {
const timeDir = join(sessionDir, entry)
if (!this.isDirectory(timeDir)) continue
const hit = this.findCachedOutputInDir(timeDir, normalizedKey, extensions, preferHd)
if (hit) return hit
}
} catch {
// ignore
// 遍历所有可能的缓存根路径
for (const root of allRoots) {
// 策略1: 新目录结构 Images/{sessionId}/{YYYY-MM}/{file}_hd.jpg
if (sessionId) {
const sessionDir = join(root, this.sanitizeDirName(sessionId))
if (existsSync(sessionDir)) {
try {
const dateDirs = readdirSync(sessionDir, { withFileTypes: true })
.filter(d => d.isDirectory() && /^\d{4}-\d{2}$/.test(d.name))
.map(d => d.name)
.sort()
.reverse() // 最新的日期优先
for (const dateDir of dateDirs) {
const imageDir = join(sessionDir, dateDir)
const hit = this.findCachedOutputInDir(imageDir, normalizedKey, extensions, preferHd)
if (hit) return hit
}
} catch { }
}
}
}
// 新目录结构: Images/{normalizedKey}/{normalizedKey}_thumb.jpg 或 _hd.jpg
const imageDir = join(root, normalizedKey)
if (existsSync(imageDir)) {
const hit = this.findCachedOutputInDir(imageDir, normalizedKey, extensions, preferHd)
if (hit) return hit
}
// 策略2: 遍历所有 sessionId 目录查找(如果没有指定 sessionId
try {
const sessionDirs = readdirSync(root, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name)
// 兼容旧的平铺结构
for (const ext of extensions) {
const candidate = join(root, `${cacheKey}${ext}`)
if (existsSync(candidate)) return candidate
}
for (const ext of extensions) {
const candidate = join(root, `${cacheKey}_t${ext}`)
if (existsSync(candidate)) return candidate
for (const session of sessionDirs) {
const sessionDir = join(root, session)
// 检查是否是日期目录结构
try {
const subDirs = readdirSync(sessionDir, { withFileTypes: true })
.filter(d => d.isDirectory() && /^\d{4}-\d{2}$/.test(d.name))
.map(d => d.name)
for (const dateDir of subDirs) {
const imageDir = join(sessionDir, dateDir)
const hit = this.findCachedOutputInDir(imageDir, normalizedKey, extensions, preferHd)
if (hit) return hit
}
} catch { }
}
} catch { }
// 策略3: 旧目录结构 Images/{normalizedKey}/{normalizedKey}_thumb.jpg
const oldImageDir = join(root, normalizedKey)
if (existsSync(oldImageDir)) {
const hit = this.findCachedOutputInDir(oldImageDir, normalizedKey, extensions, preferHd)
if (hit) return hit
}
// 策略4: 最旧的平铺结构 Images/{file}.jpg
for (const ext of extensions) {
const candidate = join(root, `${cacheKey}${ext}`)
if (existsSync(candidate)) return candidate
}
for (const ext of extensions) {
const candidate = join(root, `${cacheKey}_t${ext}`)
if (existsSync(candidate)) return candidate
}
}
return null
@@ -1155,15 +1133,19 @@ export class ImageDecryptService {
if (this.cacheIndexed) return
if (this.cacheIndexing) return this.cacheIndexing
this.cacheIndexing = new Promise((resolve) => {
const root = this.getCacheRoot()
try {
this.indexCacheDir(root, 2, 0)
} catch {
this.cacheIndexed = true
this.cacheIndexing = null
resolve()
return
// 扫描所有可能的缓存根目录
const allRoots = this.getAllCacheRoots()
this.logInfo('开始索引缓存', { roots: allRoots.length })
for (const root of allRoots) {
try {
this.indexCacheDir(root, 3, 0) // 增加深度到3支持 sessionId/YYYY-MM 结构
} catch (e) {
this.logError('索引目录失败', e, { root })
}
}
this.logInfo('缓存索引完成', { entries: this.resolvedCache.size })
this.cacheIndexed = true
this.cacheIndexing = null
resolve()
@@ -1171,6 +1153,39 @@ export class ImageDecryptService {
return this.cacheIndexing
}
/**
* 获取所有可能的缓存根路径(用于查找已缓存的图片)
* 包含当前路径、配置路径、旧版本路径
*/
private getAllCacheRoots(): string[] {
const roots: string[] = []
const configured = this.configService.get('cachePath')
const documentsPath = app.getPath('documents')
// 主要路径(当前使用的)
const mainRoot = this.getCacheRoot()
roots.push(mainRoot)
// 如果配置了自定义路径,也检查其下的 Images
if (configured) {
roots.push(join(configured, 'Images'))
roots.push(join(configured, 'images'))
}
// 默认路径
roots.push(join(documentsPath, 'WeFlow', 'Images'))
roots.push(join(documentsPath, 'WeFlow', 'images'))
// 兼容旧路径(如果有的话)
roots.push(join(documentsPath, 'WeFlowData', 'Images'))
// 去重并过滤存在的路径
const uniqueRoots = Array.from(new Set(roots))
const existingRoots = uniqueRoots.filter(r => existsSync(r))
return existingRoots
}
private indexCacheDir(root: string, maxDepth: number, depth: number): void {
let entries: string[]
try {

View File

@@ -246,15 +246,37 @@ export class WcdbCore {
// InitProtection (Added for security)
try {
this.wcdbInitProtection = this.lib.func('int32 InitProtection(const char* resourcePath)')
const protectionCode = this.wcdbInitProtection(dllDir)
if (protectionCode !== 0) {
console.error('Core security check failed:', protectionCode)
lastDllInitError = `初始化失败,错误码: ${protectionCode}`
return false
this.wcdbInitProtection = this.lib.func('bool InitProtection(const char* resourcePath)')
// 尝试多个可能的资源路径
const resourcePaths = [
dllDir, // DLL 所在目录
dirname(dllDir), // 上级目录
this.resourcesPath, // 配置的资源路径
join(process.cwd(), 'resources') // 开发环境
].filter(Boolean)
let protectionOk = false
for (const resPath of resourcePaths) {
try {
console.log(`[WCDB] 尝试 InitProtection: ${resPath}`)
protectionOk = this.wcdbInitProtection(resPath)
if (protectionOk) {
console.log(`[WCDB] InitProtection 成功: ${resPath}`)
break
}
} catch (e) {
console.warn(`[WCDB] InitProtection 失败 (${resPath}):`, e)
}
}
if (!protectionOk) {
console.warn('[WCDB] Core security check failed - 继续运行但可能不稳定')
this.writeLog('InitProtection 失败,继续运行')
// 不返回 false允许继续运行
}
} catch (e) {
console.warn('InitProtection symbol not found or failed:', e)
console.warn('InitProtection symbol not found:', e)
}
// 定义类型
@@ -1439,4 +1461,4 @@ export class WcdbCore {
return { success: false, error: String(e) }
}
}
}
}