f2e-server3
Version:
f2e-server 3.0
189 lines (175 loc) • 5.65 kB
text/typescript
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()
}
}
}