mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-02 23:15:59 +00:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
@@ -36,10 +36,41 @@ import { bizService } from './services/bizService'
|
||||
autoUpdater.autoDownload = false
|
||||
autoUpdater.autoInstallOnAppQuit = true
|
||||
autoUpdater.disableDifferentialDownload = true // 禁用差分更新,强制全量下载
|
||||
// Windows x64 与 arm64 使用不同更新通道,避免 latest.yml 互相覆盖导致下错架构安装包。
|
||||
if (process.platform === 'win32' && process.arch === 'arm64') {
|
||||
autoUpdater.channel = 'latest-arm64'
|
||||
// 更新通道策略:
|
||||
// - 稳定版(如 4.3.0)默认走 latest
|
||||
// - 预览版(如 4.3.0-preview.26.1)默认走 preview
|
||||
// - 开发版(如 4.3.0-dev.26.3.4)默认走 dev
|
||||
// - 用户可在设置页切换稳定/预览/开发,切换后即时生效
|
||||
// 同时区分 Windows x64 / arm64,避免更新清单互相覆盖。
|
||||
const appVersion = app.getVersion()
|
||||
const defaultUpdateTrack: 'stable' | 'preview' | 'dev' = (() => {
|
||||
if (/-preview\.\d+\.\d+$/i.test(appVersion)) return 'preview'
|
||||
if (/-dev\.\d+\.\d+\.\d+$/i.test(appVersion)) return 'dev'
|
||||
if (/(alpha|beta|rc)/i.test(appVersion)) return 'dev'
|
||||
return 'stable'
|
||||
})()
|
||||
const isPrereleaseBuild = defaultUpdateTrack !== 'stable'
|
||||
let configService: ConfigService | null = null
|
||||
|
||||
const normalizeUpdateTrack = (raw: unknown): 'stable' | 'preview' | 'dev' | null => {
|
||||
if (raw === 'stable' || raw === 'preview' || raw === 'dev') return raw
|
||||
return null
|
||||
}
|
||||
|
||||
const applyAutoUpdateChannel = (reason: 'startup' | 'settings' = 'startup') => {
|
||||
const configuredTrack = normalizeUpdateTrack(configService?.get('updateChannel'))
|
||||
const track: 'stable' | 'preview' | 'dev' = configuredTrack || defaultUpdateTrack
|
||||
const baseUpdateChannel = track === 'stable' ? 'latest' : track
|
||||
autoUpdater.allowPrerelease = track !== 'stable'
|
||||
autoUpdater.allowDowngrade = isPrereleaseBuild && track === 'stable'
|
||||
autoUpdater.channel =
|
||||
process.platform === 'win32' && process.arch === 'arm64'
|
||||
? `${baseUpdateChannel}-arm64`
|
||||
: baseUpdateChannel
|
||||
console.log(`[Update](${reason}) 当前版本 ${appVersion},渠道偏好: ${track},更新通道: ${autoUpdater.channel}`)
|
||||
}
|
||||
|
||||
applyAutoUpdateChannel('startup')
|
||||
const AUTO_UPDATE_ENABLED =
|
||||
process.env.AUTO_UPDATE_ENABLED === 'true' ||
|
||||
process.env.AUTO_UPDATE_ENABLED === '1' ||
|
||||
@@ -87,7 +118,6 @@ function sanitizePathEnv() {
|
||||
sanitizePathEnv()
|
||||
|
||||
// 单例服务
|
||||
let configService: ConfigService | null = null
|
||||
|
||||
// 协议窗口实例
|
||||
let agreementWindow: BrowserWindow | null = null
|
||||
@@ -1118,6 +1148,9 @@ function registerIpcHandlers() {
|
||||
|
||||
ipcMain.handle('config:set', async (_, key: string, value: any) => {
|
||||
const result = configService?.set(key as any, value)
|
||||
if (key === 'updateChannel') {
|
||||
applyAutoUpdateChannel('settings')
|
||||
}
|
||||
void messagePushService.handleConfigChanged(key)
|
||||
return result
|
||||
})
|
||||
@@ -2742,6 +2775,7 @@ app.whenReady().then(async () => {
|
||||
// 初始化配置服务
|
||||
updateSplashProgress(5, '正在加载配置...')
|
||||
configService = new ConfigService()
|
||||
applyAutoUpdateChannel('startup')
|
||||
|
||||
// 将用户主题配置推送给 Splash 窗口
|
||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||
|
||||
@@ -15,15 +15,31 @@ class CloudControlService {
|
||||
private timer: NodeJS.Timeout | null = null
|
||||
private pages: Set<string> = new Set()
|
||||
private platformVersionCache: string | null = null
|
||||
private pendingReports: UsageStats[] = []
|
||||
private flushInProgress = false
|
||||
private retryDelayMs = 5_000
|
||||
private consecutiveFailures = 0
|
||||
private circuitOpenedAt = 0
|
||||
private nextDelayOverrideMs: number | null = null
|
||||
private initialized = false
|
||||
|
||||
private static readonly BASE_FLUSH_MS = 300_000
|
||||
private static readonly JITTER_MS = 30_000
|
||||
private static readonly MAX_BUFFER_REPORTS = 200
|
||||
private static readonly MAX_BATCH_REPORTS = 20
|
||||
private static readonly MAX_RETRY_MS = 120_000
|
||||
private static readonly CIRCUIT_FAIL_THRESHOLD = 5
|
||||
private static readonly CIRCUIT_COOLDOWN_MS = 120_000
|
||||
|
||||
async init() {
|
||||
if (this.initialized) return
|
||||
this.initialized = true
|
||||
this.deviceId = this.getDeviceId()
|
||||
await wcdbService.cloudInit(300)
|
||||
await this.reportOnline()
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
this.reportOnline()
|
||||
}, 300000)
|
||||
this.enqueueCurrentReport()
|
||||
await this.flushQueue(true)
|
||||
this.scheduleNextFlush(this.nextDelayOverrideMs ?? undefined)
|
||||
this.nextDelayOverrideMs = null
|
||||
}
|
||||
|
||||
private getDeviceId(): string {
|
||||
@@ -33,8 +49,8 @@ class CloudControlService {
|
||||
return crypto.createHash('md5').update(machineId).digest('hex')
|
||||
}
|
||||
|
||||
private async reportOnline() {
|
||||
const data: UsageStats = {
|
||||
private buildCurrentReport(): UsageStats {
|
||||
return {
|
||||
appVersion: app.getVersion(),
|
||||
platform: this.getPlatformVersion(),
|
||||
deviceId: this.deviceId,
|
||||
@@ -42,11 +58,69 @@ class CloudControlService {
|
||||
online: true,
|
||||
pages: Array.from(this.pages)
|
||||
}
|
||||
}
|
||||
|
||||
await wcdbService.cloudReport(JSON.stringify(data))
|
||||
private enqueueCurrentReport() {
|
||||
const report = this.buildCurrentReport()
|
||||
this.pendingReports.push(report)
|
||||
if (this.pendingReports.length > CloudControlService.MAX_BUFFER_REPORTS) {
|
||||
this.pendingReports.splice(0, this.pendingReports.length - CloudControlService.MAX_BUFFER_REPORTS)
|
||||
}
|
||||
this.pages.clear()
|
||||
}
|
||||
|
||||
private isCircuitOpen(nowMs: number): boolean {
|
||||
if (this.circuitOpenedAt <= 0) return false
|
||||
return nowMs-this.circuitOpenedAt < CloudControlService.CIRCUIT_COOLDOWN_MS
|
||||
}
|
||||
|
||||
private scheduleNextFlush(delayMs?: number) {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
const jitter = Math.floor(Math.random() * CloudControlService.JITTER_MS)
|
||||
const nextDelay = Math.max(1_000, Number(delayMs) > 0 ? Number(delayMs) : CloudControlService.BASE_FLUSH_MS + jitter)
|
||||
this.timer = setTimeout(() => {
|
||||
this.enqueueCurrentReport()
|
||||
this.flushQueue(false).finally(() => {
|
||||
this.scheduleNextFlush(this.nextDelayOverrideMs ?? undefined)
|
||||
this.nextDelayOverrideMs = null
|
||||
})
|
||||
}, nextDelay)
|
||||
}
|
||||
|
||||
private async flushQueue(force: boolean) {
|
||||
if (this.flushInProgress) return
|
||||
if (this.pendingReports.length === 0) return
|
||||
const now = Date.now()
|
||||
if (!force && this.isCircuitOpen(now)) {
|
||||
return
|
||||
}
|
||||
this.flushInProgress = true
|
||||
try {
|
||||
while (this.pendingReports.length > 0) {
|
||||
const batch = this.pendingReports.slice(0, CloudControlService.MAX_BATCH_REPORTS)
|
||||
const result = await wcdbService.cloudReport(JSON.stringify(batch))
|
||||
if (!result || result.success !== true) {
|
||||
this.consecutiveFailures += 1
|
||||
this.retryDelayMs = Math.min(CloudControlService.MAX_RETRY_MS, this.retryDelayMs * 2)
|
||||
if (this.consecutiveFailures >= CloudControlService.CIRCUIT_FAIL_THRESHOLD) {
|
||||
this.circuitOpenedAt = Date.now()
|
||||
}
|
||||
this.nextDelayOverrideMs = this.retryDelayMs
|
||||
return
|
||||
}
|
||||
this.pendingReports.splice(0, batch.length)
|
||||
this.consecutiveFailures = 0
|
||||
this.retryDelayMs = 5_000
|
||||
this.circuitOpenedAt = 0
|
||||
}
|
||||
} finally {
|
||||
this.flushInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
private getPlatformVersion(): string {
|
||||
if (this.platformVersionCache) {
|
||||
return this.platformVersionCache
|
||||
@@ -146,9 +220,16 @@ class CloudControlService {
|
||||
|
||||
stop() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.pendingReports = []
|
||||
this.flushInProgress = false
|
||||
this.retryDelayMs = 5_000
|
||||
this.consecutiveFailures = 0
|
||||
this.circuitOpenedAt = 0
|
||||
this.nextDelayOverrideMs = null
|
||||
this.initialized = false
|
||||
wcdbService.cloudStop()
|
||||
}
|
||||
|
||||
@@ -158,4 +239,3 @@ class CloudControlService {
|
||||
}
|
||||
|
||||
export const cloudControlService = new CloudControlService()
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ interface ConfigSchema {
|
||||
|
||||
// 更新相关
|
||||
ignoredUpdateVersion: string
|
||||
updateChannel: 'auto' | 'stable' | 'preview' | 'dev'
|
||||
|
||||
// 通知
|
||||
notificationEnabled: boolean
|
||||
@@ -119,6 +120,7 @@ export class ConfigService {
|
||||
authUseHello: false,
|
||||
authHelloSecret: '',
|
||||
ignoredUpdateVersion: '',
|
||||
updateChannel: 'auto',
|
||||
notificationEnabled: true,
|
||||
notificationPosition: 'top-right',
|
||||
notificationFilterMode: 'all',
|
||||
|
||||
@@ -2681,7 +2681,7 @@ export class WcdbCore {
|
||||
}
|
||||
try {
|
||||
const outCursor = [0]
|
||||
const result = this.wcdbOpenMessageCursorLite(
|
||||
let result = this.wcdbOpenMessageCursorLite(
|
||||
this.handle,
|
||||
sessionId,
|
||||
batchSize,
|
||||
@@ -2690,6 +2690,29 @@ export class WcdbCore {
|
||||
endTimestamp,
|
||||
outCursor
|
||||
)
|
||||
|
||||
// result=-3 表示 WCDB_STATUS_NO_MESSAGE_DB:消息数据库缓存为空
|
||||
// 自动强制重连并重试一次
|
||||
if (result === -3 && outCursor[0] <= 0) {
|
||||
this.writeLog('openMessageCursorLite: result=-3 (no message db), attempting forceReopen...', true)
|
||||
const reopened = await this.forceReopen()
|
||||
if (reopened && this.handle !== null) {
|
||||
outCursor[0] = 0
|
||||
result = this.wcdbOpenMessageCursorLite(
|
||||
this.handle,
|
||||
sessionId,
|
||||
batchSize,
|
||||
ascending ? 1 : 0,
|
||||
beginTimestamp,
|
||||
endTimestamp,
|
||||
outCursor
|
||||
)
|
||||
this.writeLog(`openMessageCursorLite retry after forceReopen: result=${result} cursor=${outCursor[0]}`, true)
|
||||
} else {
|
||||
this.writeLog('openMessageCursorLite forceReopen failed, giving up', true)
|
||||
}
|
||||
}
|
||||
|
||||
if (result !== 0 || outCursor[0] <= 0) {
|
||||
await this.printLogs(true)
|
||||
this.writeLog(
|
||||
|
||||
Reference in New Issue
Block a user