/** * 订阅消息额度记账 * * ⚠️ balance 是估算值,不是权威数据。真值只存在于微信服务器,且没有接口可查。 * 我们靠「前端授权成功上报 +1、发送成功 -1」维护,并在发送返回 43101 时归零校正。 * 任何地方都不要把 balance 当作可信的强校验依据。 * * 额度是「每用户 × 每模板」维度的,所以以 openid 为主键;本项目只有一个模板,故不再分列。 */ const db = require('./db') const MAX_GRANT_PER_CALL = 50 // 单次上报上限,防御异常入参 function getBalance(openid) { const row = db.prepare('SELECT balance FROM subscribe_quota WHERE openid = ?').get(openid) return row ? row.balance : 0 } // 把任意入参收敛成 1..MAX_GRANT_PER_CALL 的整数 function _normalize(count) { const n = parseInt(count, 10) if (!Number.isFinite(n) || n < 1) return 1 return Math.min(n, MAX_GRANT_PER_CALL) } // 授权上报:纯累加、不去重——微信侧确实每次授权都 +1,如实记录即可 function grant(openid, count = 1) { const n = _normalize(count) db.prepare(` INSERT INTO subscribe_quota (openid, balance, grantedTotal, sentTotal, updateTime) VALUES (@openid, @n, @n, 0, @now) ON CONFLICT(openid) DO UPDATE SET balance = balance + @n, grantedTotal = grantedTotal + @n, updateTime = @now `).run({ openid, n, now: Date.now() }) return getBalance(openid) } function consume(openid, count = 1) { const n = _normalize(count) db.prepare(` UPDATE subscribe_quota SET balance = MAX(0, balance - @n), sentTotal = sentTotal + @n, updateTime = @now WHERE openid = @openid `).run({ openid, n, now: Date.now() }) return getBalance(openid) } // 余额归零:发送返回 43101 时调用,说明微信侧实际已无额度(或用户关了通知总开关) function reset(openid) { db.prepare(` INSERT INTO subscribe_quota (openid, balance, grantedTotal, sentTotal, updateTime) VALUES (@openid, 0, 0, 0, @now) ON CONFLICT(openid) DO UPDATE SET balance = 0, updateTime = @now `).run({ openid, now: Date.now() }) } module.exports = { getBalance, grant, consume, reset }