a3e8911deb
- 新增作息报时器独立模块,包含完整的日程管理、语音播报和待机功能 - 短链接正式前缀由/l/改为/to/,解决手机小屏下与I、i、1字形混淆问题 - 保留旧前缀/l/、/L/、/I/、/i/兼容已分发的旧短链 - 新增短码大小写兜底匹配,仅唯一匹配时生效避免歧义 - 新增防爆紧急替换功能,可轮换首页鉴权并替换短链接 - 新增多个调试和集成测试脚本,保障数据安全和功能回归
89 lines
4.0 KiB
JavaScript
89 lines
4.0 KiB
JavaScript
/**
|
||
* 数据体检工具:确认关键私有数据是否完好、有无测试残留
|
||
*
|
||
* 由来:2026-09-30 调试脚本曾误删用户真实日程;此后每次涉及数据的操作,
|
||
* 都跑一遍本工具确认「用户数据没少、测试垃圾没留」。只读,不修改任何数据。
|
||
*
|
||
* 运行:node dev_test_scripts/tools/tool_check_data_integrity.js
|
||
*/
|
||
const path = require('path')
|
||
const fs = require('fs')
|
||
const Database = require('better-sqlite3')
|
||
|
||
const dataDir = path.join(process.cwd(), 'data')
|
||
let problems = 0
|
||
const report = (label, value, ok = true) => {
|
||
if (!ok) problems += 1
|
||
console.log(` ${ok ? '✔' : '✘'} ${label}: ${value}`)
|
||
}
|
||
|
||
const openReadonly = name => {
|
||
const p = path.join(dataDir, name)
|
||
if (!fs.existsSync(p)) return null
|
||
return new Database(p, { readonly: true })
|
||
}
|
||
|
||
console.log('=== 数据体检(只读) ===\n')
|
||
|
||
// ---------- 1. 短链接 ----------
|
||
console.log('[1] 短链接 data/short_link.db')
|
||
const sdb = openReadonly('short_link.db')
|
||
if (!sdb) {
|
||
console.log(' (数据库不存在,跳过)')
|
||
} else {
|
||
const links = sdb.prepare('SELECT code, title, click_count, is_active FROM short_links ORDER BY id').all()
|
||
const clicks = sdb.prepare('SELECT COUNT(1) AS c FROM short_link_clicks').get().c
|
||
console.log(` 共 ${links.length} 条短链接:`)
|
||
links.forEach(r => console.log(` [${r.code}] ${r.title || '(无标题)'} clicks=${r.click_count} active=${r.is_active}`))
|
||
console.log(` 点击记录总数:${clicks}`)
|
||
|
||
// 测试残留短码:本工具测过的临时短码统一以 zz 开头
|
||
const stray = links.filter(r => String(r.code).startsWith('zz'))
|
||
report('测试残留短码(应为 0)', stray.length, stray.length === 0)
|
||
if (stray.length) stray.forEach(r => console.log(` → 残留 ${r.code}`))
|
||
|
||
// 点击记录里的孤立短码(短链已删但点击记录还在)
|
||
const codes = links.map(r => r.code)
|
||
const clickCodes = sdb.prepare('SELECT DISTINCT code FROM short_link_clicks').all().map(r => r.code)
|
||
const orphans = clickCodes.filter(c => codes.indexOf(c) < 0)
|
||
report('点击记录中的孤立短码(应为 0)', orphans.length, orphans.length === 0)
|
||
if (orphans.length) orphans.forEach(c => console.log(` → 孤立 ${c}`))
|
||
sdb.close()
|
||
}
|
||
|
||
// ---------- 2. 作息报时器 ----------
|
||
console.log('\n[2] 作息报时器 data/app_order_box_schedule_reminder.db')
|
||
const odb = openReadonly('app_order_box_schedule_reminder.db')
|
||
if (!odb) {
|
||
console.log(' (数据库不存在,跳过)')
|
||
} else {
|
||
const alive = odb.prepare('SELECT title, items_json FROM schedules WHERE deleted = 0').all()
|
||
const softDeleted = odb.prepare('SELECT COUNT(1) AS c FROM schedules WHERE deleted = 1').get().c
|
||
console.log(` 未删除的日程 ${alive.length} 套:`)
|
||
alive.forEach(r => {
|
||
let n = 0
|
||
try { n = JSON.parse(r.items_json || '[]').length } catch (_) {}
|
||
console.log(` ${r.title}(${n} 个时刻)`)
|
||
})
|
||
console.log(` 已软删除的历史行:${softDeleted}(这些是测试残留或用户主动删除,不影响使用)`)
|
||
|
||
const logs = odb.prepare('SELECT COUNT(1) AS c FROM logs').get().c
|
||
const testLogs = odb.prepare('SELECT COUNT(1) AS c FROM logs WHERE schedule_title != ?').get('周三在公司').c
|
||
console.log(` 播报记录:${logs} 条(非用户日程的:${testLogs})`)
|
||
report('用户日程仍存在(至少 1 套)', alive.length, alive.length >= 1)
|
||
odb.close()
|
||
}
|
||
|
||
// ---------- 3. 运动提醒宝(框子另一个 kit) ----------
|
||
console.log('\n[3] 运动提醒宝 data/app_order_box_motion_reminder.db')
|
||
const mdb = openReadonly('app_order_box_motion_reminder.db')
|
||
if (!mdb) {
|
||
console.log(' (数据库不存在,跳过)')
|
||
} else {
|
||
const cards = mdb.prepare('SELECT title FROM cards WHERE deleted = 0').all()
|
||
console.log(` 未删除的流程卡片 ${cards.length} 张:${cards.map(c => c.title).join(' / ') || '(无)'}`)
|
||
mdb.close()
|
||
}
|
||
|
||
console.log(`\n=== 体检完成:${problems === 0 ? '未发现异常' : '发现 ' + problems + ' 处异常'} ===`)
|
||
process.exit(problems === 0 ? 0 : 1) |