两处提醒查询补 ORDER BY id,保证平局取舍可复现

reminder.js 扫全表和 index.js 的 getQuotaStatus 都没有 ORDER BY,
两边的 sort 又都是稳定排序,同一天、同 kind、同 importance 的条目
先后完全由 SQLite 返回顺序决定。一个走全表扫、一个可能走索引,
顺序一致纯属巧合——一旦不一致,首页预告的人名就和实际被跳过的人对不上。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
yuming
2026-08-16 10:19:47 +08:00
parent 981e802532
commit 12b146c2c9
2 changed files with 8 additions and 2 deletions
+3 -1
View File
@@ -234,7 +234,9 @@ function grantQuota(openid, data) {
// 首页查询:返回余额和「哪几个人的提醒有风险」 // 首页查询:返回余额和「哪几个人的提醒有风险」
function getQuotaStatus(openid) { function getQuotaStatus(openid) {
const balance = quota.getBalance(openid) const balance = quota.getBalance(openid)
const rows = db.prepare('SELECT * FROM anniversaries WHERE openid = ? AND remindEnabled = 1').all(openid) // ORDER BY id 必须与 reminder.js 的取数保持一致:平局(同日、同 kind、同 importance)时
// 先后顺序完全由这里决定,两边不一致就会出现「预告的人名不是真正被跳过的人」
const rows = db.prepare('SELECT * FROM anniversaries WHERE openid = ? AND remindEnabled = 1 ORDER BY id').all(openid)
const { atRiskCount, atRiskNames } = atRisk.computeAtRisk(rows.map(normalize), balance) const { atRiskCount, atRiskNames } = atRisk.computeAtRisk(rows.map(normalize), balance)
return { success: true, balance, atRiskCount, atRiskNames } return { success: true, balance, atRiskCount, atRiskNames }
} }
+5 -1
View File
@@ -144,7 +144,11 @@ async function runForUser(openid, items, today = new Date()) {
async function runOnce() { async function runOnce() {
console.log('[reminder] 开始扫描纪念日...') console.log('[reminder] 开始扫描纪念日...')
const list = db.prepare('SELECT * FROM anniversaries WHERE remindEnabled = 1').all() // ORDER BY id 不是为了排序好看,而是为了「平局可复现」:
// 同一天、同 kind、同 importance 的条目在 sort 里比不出先后(JS sort 是稳定的),
// 最终取舍就取决于 SQLite 的返回顺序。这里和 index.js 的 getQuotaStatus 必须用同一个
// 兜底顺序,否则首页预告「谁有风险」的人名会和实际被跳过的人对不上。
const list = db.prepare('SELECT * FROM anniversaries WHERE remindEnabled = 1 ORDER BY id').all()
console.log(`[reminder] 启用提醒的纪念日 ${list.length}`) console.log(`[reminder] 启用提醒的纪念日 ${list.length}`)
// 额度是按用户算的,所以必须分组处理 // 额度是按用户算的,所以必须分组处理