Skip to content

Commit 2e1ed9e

Browse files
committed
feat(wcdb): 优化
1 parent 0f7dc66 commit 2e1ed9e

6 files changed

Lines changed: 215 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,4 @@ skills-lock.json
7878
# heroui-agents-md
7979
.heroui-docs/
8080
release-verify/
81+
tools/

CipherTalk-CLI/src/services/db/wcdbCore.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { basename, delimiter, dirname, join } from 'path'
22
import { existsSync, readdirSync, statSync } from 'fs'
3+
import * as https from 'https'
34
import { createRequire } from 'module'
45

56
const require = createRequire(import.meta.url)
@@ -55,6 +56,10 @@ export class WcdbCore {
5556
private wcdbStopMonitorPipe: any = null
5657
private wcdbGetMonitorPipeName: any = null
5758
private wcdbSetMyWxid: any = null
59+
private wcdbSetTrustedTime: any = null
60+
61+
// 可信时间同步定时器(防本地改时钟绕过到期)
62+
private trustedTimeTimer: any = null
5863

5964
// 管道监控状态
6065
private monitorPipeClient: any = null
@@ -132,19 +137,105 @@ export class WcdbCore {
132137
this.wcdbStopMonitorPipe = tryBind('int32 wcdb_stop_monitor_pipe()')
133138
this.wcdbGetMonitorPipeName = tryBind('int32 wcdb_get_monitor_pipe_name(_Out_ void** outName)')
134139
this.wcdbSetMyWxid = tryBind('int32 wcdb_set_my_wxid(int64 handle, const char* wxid)')
140+
this.wcdbSetTrustedTime = tryBind('int32 wcdb_set_trusted_time(int64 epochSeconds)')
135141

136-
const initResult = this.wcdbInit()
142+
let initResult = this.wcdbInit()
143+
if (initResult === -8 && this.wcdbSetTrustedTime) {
144+
await this.syncTrustedTime()
145+
initResult = this.wcdbInit()
146+
}
137147
if (initResult !== 0) {
138148
return { success: false, error: `wcdb_init() 返回错误码: ${initResult}` }
139149
}
140150

141151
this.initialized = true
152+
this.startTrustedTimeSync(false)
142153
return { success: true }
143154
} catch (e: any) {
144155
return { success: false, error: `WCDB 初始化异常: ${e.message || String(e)}` }
145156
}
146157
}
147158

159+
// ============== 可信时间同步(防本地改时钟绕过到期)==============
160+
// native 维护"高水位 + 联网投影"的有效时间;这里负责从公共时间 API 取可信时间喂进 DLL。
161+
// 取不到(离线)就静默,DLL 退化为本地时钟 + 历史高水位,离线照常可用。
162+
private startTrustedTimeSync(syncNow = true): void {
163+
if (!this.wcdbSetTrustedTime) return
164+
if (syncNow) void this.syncTrustedTime()
165+
if (this.trustedTimeTimer) clearInterval(this.trustedTimeTimer)
166+
this.trustedTimeTimer = setInterval(() => { void this.syncTrustedTime() }, 6 * 60 * 60 * 1000)
167+
this.trustedTimeTimer?.unref?.()
168+
}
169+
170+
private stopTrustedTimeSync(): void {
171+
if (this.trustedTimeTimer) {
172+
clearInterval(this.trustedTimeTimer)
173+
this.trustedTimeTimer = null
174+
}
175+
}
176+
177+
private async syncTrustedTime(): Promise<void> {
178+
try {
179+
if (!this.wcdbSetTrustedTime) return
180+
const epoch = await this.fetchNetworkEpochSeconds()
181+
if (epoch && this.isPlausibleEpoch(epoch)) {
182+
this.wcdbSetTrustedTime(epoch)
183+
}
184+
} catch {
185+
// 离线/失败静默,靠 native 本地高水位兜底
186+
}
187+
}
188+
189+
private isPlausibleEpoch(sec: number): boolean {
190+
return Number.isFinite(sec) && sec > 1700000000 && sec < 4102444800
191+
}
192+
193+
private async fetchNetworkEpochSeconds(): Promise<number | null> {
194+
const sources: Array<{ url: string; parse: (body: string) => number | null }> = [
195+
{
196+
url: 'https://worldtimeapi.org/api/timezone/Etc/UTC',
197+
parse: (b) => { try { const j = JSON.parse(b); return typeof j.unixtime === 'number' ? j.unixtime : null } catch { return null } },
198+
},
199+
{
200+
url: 'https://timeapi.io/api/time/current/zone?timeZone=Etc%2FUTC',
201+
parse: (b) => { try { const j = JSON.parse(b); const t = Date.parse(String(j.dateTime).replace(/Z?$/, 'Z')); return Number.isFinite(t) ? Math.floor(t / 1000) : null } catch { return null } },
202+
},
203+
{
204+
url: 'https://www.cloudflare.com/cdn-cgi/trace',
205+
parse: (b) => { const m = /(?:^|\n)ts=([0-9.]+)/.exec(b); return m ? Math.floor(parseFloat(m[1])) : null },
206+
},
207+
]
208+
for (const s of sources) {
209+
try {
210+
const { body, dateHeader } = await this.httpGetText(s.url, 4000)
211+
let epoch = s.parse(body)
212+
if ((!epoch || !this.isPlausibleEpoch(epoch)) && dateHeader) {
213+
const d = Date.parse(dateHeader)
214+
if (Number.isFinite(d)) epoch = Math.floor(d / 1000)
215+
}
216+
if (epoch && this.isPlausibleEpoch(epoch)) return epoch
217+
} catch {
218+
// 试下一个源
219+
}
220+
}
221+
return null
222+
}
223+
224+
private httpGetText(url: string, timeoutMs: number): Promise<{ body: string; dateHeader?: string }> {
225+
return new Promise((resolve, reject) => {
226+
const req = https.get(url, { timeout: timeoutMs, headers: { 'User-Agent': 'CipherTalk' } }, (res) => {
227+
const rawDateHeader = res.headers?.date
228+
const dateHeader = Array.isArray(rawDateHeader) ? rawDateHeader[0] : rawDateHeader
229+
let body = ''
230+
res.setEncoding('utf8')
231+
res.on('data', (c: string) => { if (body.length < 8192) body += c })
232+
res.on('end', () => resolve({ body, dateHeader }))
233+
})
234+
req.on('timeout', () => req.destroy(new Error('timeout')))
235+
req.on('error', reject)
236+
})
237+
}
238+
148239
// ============== 路径解析 ==============
149240
private findSessionDbs(dir: string, depth = 0, results: string[] = []): string[] {
150241
if (depth > 5) return results
@@ -291,6 +382,7 @@ export class WcdbCore {
291382
}
292383

293384
close(): void {
385+
this.stopTrustedTimeSync()
294386
if (this.handle !== null && this.wcdbCloseAccount) {
295387
try { this.wcdbCloseAccount(this.handle) } catch (e) { console.error('关闭 WCDB 句柄失败:', e) }
296388
}
@@ -784,12 +876,19 @@ export class WcdbCore {
784876
case -4: return '数据库打开失败'
785877
case -5: return '查询执行失败'
786878
case -6: return 'WCDB 尚未初始化'
879+
case -7: return 'WCDB 表结构不匹配'
880+
case -8: return '软件偷来的吧!'
881+
case -9: return '快提醒作者更新软件了!'
882+
case -10: return '靠,你从哪搞得软件?'
787883
default: return `WCDB 错误码: ${code}`
788884
}
789885
}
790886

791887
private mapCursorStatusCode(code: number, prefix: string): string {
792888
if (code === -7) return 'message schema mismatch:当前账号消息表结构与程序要求不一致'
889+
if (code === -8) return `${prefix}: ${code}(软件偷来的吧!)`
890+
if (code === -9) return `${prefix}: ${code}(快提醒作者更新软件了!)`
891+
if (code === -10) return `${prefix}: ${code}(靠,你从哪搞得软件?)`
793892
if (code === -3) return `${prefix}: ${code}(消息数据库未找到)`
794893
return `${prefix}: ${code}`
795894
}

build_log.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'��引擎(链接其导入库)' is not recognized as an internal or external command,
2+
operable program or batch file.
3+
The system cannot find the path specified.
4+
'Visual' is not recognized as an internal or external command,
5+
operable program or batch file.
6+
's.x86.x64' is not recognized as an internal or external command,
7+
operable program or batch file.
8+
The system cannot find the path specified.
9+
'导入库存在' is not recognized as an internal or external command,
10+
operable program or batch file.
11+
The system cannot find the path specified.
12+
'B_DIRWCDB_LIB_DIR"' is not recognized as an internal or external command,
13+
operable program or batch file.
14+
'B.dll' is not recognized as an internal or external command,
15+
operable program or batch file.
16+
'rshell' is not recognized as an internal or external command,
17+
operable program or batch file.
18+
-match was unexpected at this time.

electron/services/wcdbCore.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { basename, delimiter, dirname, join } from 'path'
22
import { existsSync, readdirSync, statSync } from 'fs'
3+
import * as https from 'https'
34

45
// 稳定性开关:native 消息游标保留原生速度。若 koffi/native 触发 fatal,
56
// 现在只会终止 Electron utilityProcess,主进程会重启子进程并让本次请求回退 SQL。
@@ -44,6 +45,10 @@ export class WcdbCore {
4445
private wcdbStopMonitorPipe: any = null
4546
private wcdbGetMonitorPipeName: any = null
4647
private wcdbSetMyWxid: any = null
48+
private wcdbSetTrustedTime: any = null
49+
50+
// 可信时间同步定时器(防本地改时钟绕过到期)
51+
private trustedTimeTimer: any = null
4752

4853
// 管道监控状态
4954
private monitorPipeClient: any = null
@@ -135,19 +140,105 @@ export class WcdbCore {
135140
this.wcdbStopMonitorPipe = tryBind('int32 wcdb_stop_monitor_pipe()')
136141
this.wcdbGetMonitorPipeName = tryBind('int32 wcdb_get_monitor_pipe_name(_Out_ void** outName)')
137142
this.wcdbSetMyWxid = tryBind('int32 wcdb_set_my_wxid(int64 handle, const char* wxid)')
143+
this.wcdbSetTrustedTime = tryBind('int32 wcdb_set_trusted_time(int64 epochSeconds)')
138144

139-
const initResult = this.wcdbInit()
145+
let initResult = this.wcdbInit()
146+
if (initResult === -8 && this.wcdbSetTrustedTime) {
147+
await this.syncTrustedTime()
148+
initResult = this.wcdbInit()
149+
}
140150
if (initResult !== 0) {
141151
return { success: false, error: `wcdb_init() 返回错误码: ${initResult}` }
142152
}
143153

144154
this.initialized = true
155+
this.startTrustedTimeSync(false)
145156
return { success: true }
146157
} catch (e: any) {
147158
return { success: false, error: `WCDB 初始化异常: ${e.message || String(e)}` }
148159
}
149160
}
150161

162+
// ============== 可信时间同步(防本地改时钟绕过到期)==============
163+
// native 维护"高水位 + 联网投影"的有效时间;这里负责从公共时间 API 取可信时间喂进 DLL。
164+
// 取不到(离线)就静默,DLL 退化为本地时钟 + 历史高水位,离线照常可用。
165+
private startTrustedTimeSync(syncNow = true): void {
166+
if (!this.wcdbSetTrustedTime) return // 老 dll 未导出该符号则跳过
167+
if (syncNow) void this.syncTrustedTime() // 立即同步一次,不阻塞 init
168+
if (this.trustedTimeTimer) clearInterval(this.trustedTimeTimer)
169+
this.trustedTimeTimer = setInterval(() => { void this.syncTrustedTime() }, 6 * 60 * 60 * 1000)
170+
this.trustedTimeTimer?.unref?.()
171+
}
172+
173+
private stopTrustedTimeSync(): void {
174+
if (this.trustedTimeTimer) {
175+
clearInterval(this.trustedTimeTimer)
176+
this.trustedTimeTimer = null
177+
}
178+
}
179+
180+
private async syncTrustedTime(): Promise<void> {
181+
try {
182+
if (!this.wcdbSetTrustedTime) return
183+
const epoch = await this.fetchNetworkEpochSeconds()
184+
if (epoch && this.isPlausibleEpoch(epoch)) {
185+
this.wcdbSetTrustedTime(epoch) // koffi int64:秒级在安全整数范围内,直接传 number
186+
}
187+
} catch {
188+
// 离线/失败静默,靠 native 本地高水位兜底
189+
}
190+
}
191+
192+
private isPlausibleEpoch(sec: number): boolean {
193+
return Number.isFinite(sec) && sec > 1700000000 && sec < 4102444800 // ~2023-11 .. 2100
194+
}
195+
196+
private async fetchNetworkEpochSeconds(): Promise<number | null> {
197+
const sources: Array<{ url: string; parse: (body: string) => number | null }> = [
198+
{
199+
url: 'https://worldtimeapi.org/api/timezone/Etc/UTC',
200+
parse: (b) => { try { const j = JSON.parse(b); return typeof j.unixtime === 'number' ? j.unixtime : null } catch { return null } },
201+
},
202+
{
203+
url: 'https://timeapi.io/api/time/current/zone?timeZone=Etc%2FUTC',
204+
parse: (b) => { try { const j = JSON.parse(b); const t = Date.parse(String(j.dateTime).replace(/Z?$/, 'Z')); return Number.isFinite(t) ? Math.floor(t / 1000) : null } catch { return null } },
205+
},
206+
{
207+
url: 'https://www.cloudflare.com/cdn-cgi/trace',
208+
parse: (b) => { const m = /(?:^|\n)ts=([0-9.]+)/.exec(b); return m ? Math.floor(parseFloat(m[1])) : null },
209+
},
210+
]
211+
for (const s of sources) {
212+
try {
213+
const { body, dateHeader } = await this.httpGetText(s.url, 4000)
214+
let epoch = s.parse(body)
215+
if ((!epoch || !this.isPlausibleEpoch(epoch)) && dateHeader) {
216+
const d = Date.parse(dateHeader) // HTTP Date 头为 GMT,时区安全
217+
if (Number.isFinite(d)) epoch = Math.floor(d / 1000)
218+
}
219+
if (epoch && this.isPlausibleEpoch(epoch)) return epoch
220+
} catch {
221+
// 试下一个源
222+
}
223+
}
224+
return null
225+
}
226+
227+
private httpGetText(url: string, timeoutMs: number): Promise<{ body: string; dateHeader?: string }> {
228+
return new Promise((resolve, reject) => {
229+
const req = https.get(url, { timeout: timeoutMs, headers: { 'User-Agent': 'CipherTalk' } }, (res) => {
230+
const rawDateHeader = res.headers?.date
231+
const dateHeader = Array.isArray(rawDateHeader) ? rawDateHeader[0] : rawDateHeader
232+
let body = ''
233+
res.setEncoding('utf8')
234+
res.on('data', (c: string) => { if (body.length < 8192) body += c })
235+
res.on('end', () => resolve({ body, dateHeader }))
236+
})
237+
req.on('timeout', () => req.destroy(new Error('timeout')))
238+
req.on('error', reject)
239+
})
240+
}
241+
151242
// ============== 路径解析 ==============
152243
private findSessionDbs(dir: string, depth = 0, results: string[] = []): string[] {
153244
if (depth > 5) return results
@@ -294,6 +385,7 @@ export class WcdbCore {
294385
}
295386

296387
close(): void {
388+
this.stopTrustedTimeSync()
297389
if (this.handle !== null && this.wcdbCloseAccount) {
298390
try { this.wcdbCloseAccount(this.handle) } catch (e) { console.error('关闭 WCDB 句柄失败:', e) }
299391
}

resources/wcdb_api.dll

23 KB
Binary file not shown.

syntax_check.bat

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@echo off
2+
call "E:\VS\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 >nul
3+
cl /Zs /std:c++17 /EHsc /utf-8 /DWCDB_API_BUILDING_LIBRARY /D_WIN32_WINNT=0x0601 /DNOMINMAX /DWIN32_LEAN_AND_MEAN /I"C:\ctbuild\wcdb_api\include" "C:\ctbuild\wcdb_api\src\wcdb_api.cpp"

0 commit comments

Comments
 (0)