architecture: 数据唯一权威迁至服务器——本地降级为实时缓存,写后即时推送+快照重建;账号登录转为云端原生(新增 /api/auth/password、/api/auth/delete、每日自动备份)
This commit is contained in:
+59
-1
@@ -46,7 +46,7 @@ const SECRET = readJson('secret.key', { secret: 'fallback-secret' }).secret
|
||||
|
||||
const registryFile = 'registry.json'
|
||||
let registry = readJson(registryFile, { users: [] })
|
||||
function saveRegistry() { writeJson(registryFile, registry) }
|
||||
function saveRegistry() { writeJson(registryFile, registry); try { doBackup() } catch { /* 忽略 */ } }
|
||||
|
||||
function userFile(userId) { return path.join(DATA_DIR, 'users', userId + '.json') }
|
||||
function loadUserData(userId) {
|
||||
@@ -196,6 +196,36 @@ async function handle(req, res) {
|
||||
return
|
||||
}
|
||||
|
||||
// 修改登录密码(验证旧密码后重置盐与哈希)
|
||||
if (req.method === 'POST' && route === '/api/auth/password') {
|
||||
const auth = verifyToken(String(req.headers['authorization'] || '').replace(/^Bearer\s+/i, '').trim())
|
||||
if (!auth) { json(res, 401, { error: '未认证' }); return }
|
||||
const body = await readBody(req)
|
||||
const rec = registry.users.find(u => u.id === auth.userId)
|
||||
if (!rec) { json(res, 404, { error: '用户不存在' }); return }
|
||||
if (pbkdf2Hash(String(body.oldPassword || ''), rec.pwd_salt) !== rec.pwd_hash) { json(res, 400, { error: '旧密码不正确' }); return }
|
||||
if (String(body.newPassword || '').length < 6) { json(res, 400, { error: '新密码至少 6 位' }); return }
|
||||
rec.pwd_salt = crypto.randomBytes(16).toString('hex')
|
||||
rec.pwd_hash = pbkdf2Hash(String(body.newPassword), rec.pwd_salt)
|
||||
rec.pwd_changed_at = new Date().toISOString()
|
||||
saveRegistry()
|
||||
json(res, 200, { ok: true })
|
||||
return
|
||||
}
|
||||
|
||||
// 删除云端账号:注册表与全部业务数据一并清除(不可恢复,客户端已做二次确认)
|
||||
if (req.method === 'POST' && route === '/api/auth/delete') {
|
||||
const auth = verifyToken(String(req.headers['authorization'] || '').replace(/^Bearer\s+/i, '').trim())
|
||||
if (!auth) { json(res, 401, { error: '未认证' }); return }
|
||||
const idx = registry.users.findIndex(u => u.id === auth.userId)
|
||||
if (idx >= 0) { registry.users.splice(idx, 1); saveRegistry() }
|
||||
try { fs.rmSync(userFile(auth.userId), { force: true }) } catch { /* 忽略 */ }
|
||||
for (const sseRes of (sseClients.get(auth.userId) || [])) { try { sseRes.end() } catch { /* 忽略 */ } }
|
||||
sseClients.delete(auth.userId)
|
||||
json(res, 200, { ok: true })
|
||||
return
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && route === '/api/auth/register') {
|
||||
const body = await readBody(req)
|
||||
const username = String(body.username || '').trim()
|
||||
@@ -319,6 +349,34 @@ async function handle(req, res) {
|
||||
json(res, 404, { error: 'not found' })
|
||||
}
|
||||
|
||||
// ---------- 自动备份:服务器是唯一数据源,必须自带兜底 ----------
|
||||
function doBackup() {
|
||||
try {
|
||||
const day = new Date().toISOString().slice(0, 10)
|
||||
const dir = path.join(DATA_DIR, 'backups', day)
|
||||
fs.mkdirSync(path.join(dir, 'users'), { recursive: true })
|
||||
try { fs.copyFileSync(path.join(DATA_DIR, registryFile), path.join(dir, 'registry.json')) } catch { /* 忽略 */ }
|
||||
try { fs.copyFileSync(path.join(DATA_DIR, 'secret.key'), path.join(dir, 'secret.key')) } catch { /* 忽略 */ }
|
||||
try {
|
||||
const usersDir = path.join(DATA_DIR, 'users')
|
||||
if (fs.existsSync(usersDir)) {
|
||||
for (const f of fs.readdirSync(usersDir)) {
|
||||
if (f.endsWith('.json')) fs.copyFileSync(path.join(usersDir, f), path.join(dir, 'users', f))
|
||||
}
|
||||
}
|
||||
} catch { /* 忽略 */ }
|
||||
// 保留最近 14 天
|
||||
try {
|
||||
const backupsRoot = path.join(DATA_DIR, 'backups')
|
||||
const days = fs.readdirSync(backupsRoot).filter(d => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort()
|
||||
while (days.length > 14) { const old = days.shift(); try { fs.rmSync(path.join(backupsRoot, old), { recursive: true, force: true }) } catch { /* 忽略 */ } }
|
||||
} catch { /* 忽略 */ }
|
||||
} catch (e) { console.error('backup failed:', e && e.message) }
|
||||
}
|
||||
doBackup()
|
||||
setInterval(doBackup, 24 * 60 * 60 * 1000)
|
||||
|
||||
// ---------- HTTP 服务 ----------
|
||||
const server = http.createServer((req, res) => {
|
||||
handle(req, res).catch(err => {
|
||||
json(res, 500, { error: err.message || 'server error' })
|
||||
|
||||
Reference in New Issue
Block a user