Skip to content

Commit b6b75f6

Browse files
committed
修复:数据文件损坏时不再静默覆盖销毁凭证 (v1.15.1)
- store.load() 原先对「文件不存在」和「文件损坏」一视同仁:损坏时退回空默认值, 再走到末尾无条件 save(),把含站点令牌/密码的 stations.json 覆盖成空,永久丢失且无备份 - 现区分三种情况:文件不存在→首次启动创建默认值(不变);JSON 解析失败→原始内容 备份为 stations.json.corrupt-<时间戳>(0600) 并中止启动,原文件一字节不动;其它读取 错误(权限/IO,可能是暂时性)→中止启动,不覆盖 - server.js 启动处捕获后打印 [致命] 信息并 exit(1),不再抛未处理的 rejection 堆栈
1 parent ae81c94 commit b6b75f6

3 files changed

Lines changed: 36 additions & 6 deletions

File tree

lib/store.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,34 @@ export class Store {
6767
}
6868

6969
async load() {
70+
let raw = null;
7071
try {
71-
const raw = await readFile(this.file, "utf8");
72-
const parsed = JSON.parse(raw);
72+
raw = await readFile(this.file, "utf8");
73+
} catch (err) {
74+
// 文件不存在(ENOENT)→ 首次启动,走默认值并在末尾创建文件。
75+
// 其它读取错误(权限/IO,可能是暂时性的)→ 文件可能仍在,绝不用空默认值覆盖,
76+
// 直接中止启动,交由运维排查后重启,避免销毁现有站点令牌/密码。
77+
if (err.code !== "ENOENT") {
78+
throw new Error(
79+
`读取数据文件失败(${err.code}):${err.message}。已中止启动以避免覆盖现有数据,请排查后重启。`
80+
);
81+
}
82+
}
83+
if (raw != null) {
84+
let parsed;
85+
try {
86+
parsed = JSON.parse(raw);
87+
} catch (err) {
88+
// 文件已损坏:绝不静默退默认值再在末尾 save() 覆盖——那会永久销毁含令牌/密码的凭证。
89+
// 先把原始内容备份为带时间戳的副本,再中止启动;原文件保持不动,
90+
// 运维修复或删除 stations.json 后重启即可(重启前不会有任何写覆盖)。
91+
const backup = `${this.file}.corrupt-${Date.now()}`;
92+
await writeFile(backup, raw, { mode: 0o600 }).catch(() => {});
93+
throw new Error(
94+
`数据文件损坏,无法解析(${err.message})。原始内容已备份为 ${backup}。` +
95+
`已中止启动以避免覆盖凭证,请修复或删除 ${this.file} 后重启。`
96+
);
97+
}
7398
this.data = {
7499
stations: Array.isArray(parsed.stations) ? parsed.stations : [],
75100
settings: { ...DEFAULT_SETTINGS, ...(parsed.settings || {}) },
@@ -79,8 +104,6 @@ export class Store {
79104
rules: { ...DEFAULT_RULES, ...(parsed.notifications?.rules || {}) },
80105
},
81106
};
82-
} catch {
83-
// 文件不存在或损坏:使用默认值并写入
84107
}
85108
// 迁移:历代固定成本字段(fixedMonthlyCny / fixedCostCny+fixedDays+fixedStartDate)
86109
// 统一为付费记录数组 fixedPurchases

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "relay-monitor",
3-
"version": "1.15.0",
3+
"version": "1.15.1",
44
"private": true,
55
"description": "监控 sub2api / new-api 中转站余额的网页面板",
66
"type": "module",

server.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ const APP_INFO = {
2828
};
2929

3030
const store = new Store(join(__dirname, "data", "stations.json"));
31-
await store.load();
31+
try {
32+
await store.load();
33+
} catch (err) {
34+
// 数据文件损坏/不可读:store.load() 已备份并中止,此处打印致命信息并退出,
35+
// 绝不带着空状态继续运行(那会覆盖掉现有站点凭证)。
36+
console.error(`\n[致命] ${err.message}\n`);
37+
process.exit(1);
38+
}
3239
const history = await new History(join(__dirname, "data", "history.json")).load();
3340
const sessions = await new SessionManager(join(__dirname, "data", "secret.key")).init();
3441

0 commit comments

Comments
 (0)