UNPKG

f2e-server3

Version:

f2e-server 3.0

189 lines (175 loc) 5.65 kB
import * as fs from 'node:fs'; import * as path from 'node:path'; import logger from '../logger'; export interface DBFileOptions<T> { /** * 文件存储路径 */ filePath: string; /** * 初始化数据 */ initData: T; /** * 内存数据持久化频率,即:节流时间 单位 ms * @default 3000 */ flushInterval?: number; /** * 是否启用文件监听 * @default false */ watchFile?: boolean; /** * 是否自动存储,开启后,不需要主动执行update */ autoSave?: boolean; /** * 加载已存储文件信息为数据 * @param filePath * @returns */ fileToData?: (filePath: string) => T /** * 将数据转换为文件内容的函数 * * @param data 要转换的数据对象 * @returns 返回可以直接写入文件的字符串或二进制数据 */ dataToFile?: (data: T) => string | NodeJS.ArrayBufferView /** * 文件变化时的回调函数 * @param newData 新的数据 * @param oldData 旧的数据 */ onFileChange?: (newData?: T, oldData?: T) => void; } const defaults: Omit<Required<DBFileOptions<any>>, 'filePath' | 'initData'> = { flushInterval: 3000, watchFile: false, autoSave: false, fileToData: (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8')), dataToFile: (data) => JSON.stringify(data.toString(), null, 2), onFileChange: () => {}, } export class DBFile<T = any> { options: Required<DBFileOptions<T>> data: T save_timer?: Timer private watcher?: fs.FSWatcher private isUpdating: boolean = false; private lastUpdateTime: number = 0; private readonly DEBOUNCE_TIME = 100; // 防抖时间,单位毫秒 constructor(options: DBFileOptions<T>) { this.options = { ...defaults, ...options, }; const { filePath, initData, autoSave, dataToFile, fileToData, watchFile, } = this.options this.data = options.initData if (fs.existsSync(filePath)) { this.data = fileToData?.(filePath) } else if (initData && dataToFile) { const dirname = path.dirname(filePath) if (!fs.existsSync(dirname)) { fs.mkdirSync(dirname, { recursive: true }) } fs.writeFileSync(filePath, dataToFile(initData)) } if (watchFile) { this.startWatching() } if (autoSave) { // 根据持久化频率保存文件 this.save_timer = setInterval(() => { if (this.isUpdating) return; try { this.isUpdating = true; fs.writeFileSync(filePath, dataToFile(this.data)); } catch (error) { logger.error('自动保存失败:', error); } finally { this.isUpdating = false; } }, this.options.flushInterval); } } private startWatching = () => { const { filePath, fileToData, onFileChange } = this.options this.watcher = fs.watch(filePath, (eventType) => { if (eventType !== 'change' || this.isUpdating) return; const now = Date.now(); if (now - this.lastUpdateTime < this.DEBOUNCE_TIME) return; this.lastUpdateTime = now; try { this.isUpdating = true; const oldData = this.data const newData = fileToData(filePath); this.data = newData; onFileChange(newData, oldData); } catch (error) { logger.error('文件监听更新失败:', error); } finally { this.isUpdating = false; } }); // 处理监听器错误 this.watcher.on('error', (error) => { logger.error('文件监听错误:', error); }); } /** * 修改数据并持久化,autoSave = true 时,仅修改数据,持久化使用定时器实现 * @param data 需要修改的数据,不提供不修改 * @returns */ update = (data?: T) => { if (data) { this.data = data; } const { autoSave, filePath, dataToFile, flushInterval, } = this.options if (autoSave) { // 自动保存模式下,不处理 return; } if (this.save_timer) { clearTimeout(this.save_timer) } if (dataToFile) { this.save_timer = setTimeout(() => { this.isUpdating = true try { fs.writeFileSync(filePath, dataToFile(this.data)) } catch (error) { logger.error('数据持久化失败:', error) } finally { this.isUpdating = false } }, flushInterval) } } destroy = () => { if (this.save_timer) { if (this.options.autoSave) { clearInterval(this.save_timer); } else { clearTimeout(this.save_timer); } } if (this.watcher) { this.watcher.close() } } }