MCPcopy Create free account
hub / github.com/Rfym21/Qwen2API / DataPersistence

Class DataPersistence

src/utils/data-persistence.js:14–354  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

12const STATS_PERSIST_DEBOUNCE_MS = 5000
13// 备份(data.json.bak)最小刷新间隔——不必每次落盘都多写一份
14const BACKUP_MIN_INTERVAL_MS = 5 * 60 * 1000
15// rename 重试间隔:Windows / 网络盘上目标文件被占用时可能短暂 EPERM / EBUSY
16const RENAME_RETRY_DELAYS_MS = [20, 50, 100]
17
18// 进程内串行化:所有对 data.json 的 read-modify-write 排队执行。
19// 队列必须是模块级的——进程里存在多个 DataPersistence 实例
20// (utils/account.js 一个、routes/settings.js 一个),实例级的锁挡不住并发。
21// 注:PM2 cluster(PM2_INSTANCES > 1)下跨进程仍可能「后写覆盖先写」丢一次统计,
22// 但原子 rename 保证任何时刻文件都是完整 JSON,不会再出现半截内容。
23let fileWriteQueue = Promise.resolve()
24let tmpFileCounter = 0
25let lastBackupAt = 0
26
27/**
28 * 把一次文件读写排入串行队列
29 * @param {Function} task - 返回 Promise 的任务
30 * @returns {Promise<*>} 任务结果
31 */
32const enqueueFileTask = (task) => {
33 const result = fileWriteQueue.then(task, task)
34 // 队列本身不能因为单次失败而中断
35 fileWriteQueue = result.then(() => { }, () => { })
36 return result
37}
38
39const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
40
41class DataPersistence {
42 constructor() {
43 this.dataFilePath = path.join(__dirname, '../../data/data.json')
44 // 最近一次「解析成功」的快照,用于文件损坏时自愈
45 this.backupFilePath = `${this.dataFilePath}.bak`
46 // 每个 email 的待持久化 stats 与定时器(debounce)
47 this._statsPersistTimers = new Map()
48 this._statsPendingPayload = new Map()
49 }
50
51 /**
52 * 加载所有账户数据
53 * @returns {Promise<Array>} 账户列表
54 */
55 async loadAccounts() {
56 try {
57 switch (config.dataSaveMode) {
58 case 'redis':
59 return await this._loadFromRedis()
60 case 'file':
61 return await this._loadFromFile()
62 case 'none':
63 return await this._loadFromEnv()
64 default:
65 logger.error(`不支持的数据保存模式: ${config.dataSaveMode}`, 'DATA')
66 throw new Error(`不支持的数据保存模式: ${config.dataSaveMode}`)
67 }
68 } catch (error) {
69 logger.error('加载账户数据失败', 'DATA', '', error)
70 throw error
71 }

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected