新增 /api/subscribe 接口:授权上报与风险查询
- grantQuota/getQuotaStatus 从 index.js 导出供测试直接调用 - app.listen 加 require.main 守卫,避免测试 require 本文件时 真的起服务占用端口、cron 定时器让进程无法退出
This commit is contained in:
@@ -4,6 +4,8 @@ const express = require('express')
|
|||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const wx = require('./wx')
|
const wx = require('./wx')
|
||||||
const reminder = require('./reminder')
|
const reminder = require('./reminder')
|
||||||
|
const quota = require('./quota')
|
||||||
|
const atRisk = require('./atRisk')
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(express.json({ limit: '1mb' }))
|
app.use(express.json({ limit: '1mb' }))
|
||||||
@@ -221,6 +223,39 @@ function getPersons(openid) {
|
|||||||
return { success: true, data: rows }
|
return { success: true, data: rows }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 订阅额度 ----
|
||||||
|
|
||||||
|
// 前端授权成功后上报。纯累加、不去重:微信侧确实每次授权都 +1,如实记录即可。
|
||||||
|
function grantQuota(openid, data) {
|
||||||
|
const count = (data && data.count) || 1
|
||||||
|
return { success: true, balance: quota.grant(openid, count) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首页查询:返回余额和「哪几个人的提醒有风险」
|
||||||
|
function getQuotaStatus(openid) {
|
||||||
|
const balance = quota.getBalance(openid)
|
||||||
|
const rows = db.prepare('SELECT * FROM anniversaries WHERE openid = ? AND remindEnabled = 1').all(openid)
|
||||||
|
const { atRiskCount, atRiskNames } = atRisk.computeAtRisk(rows.map(normalize), balance)
|
||||||
|
return { success: true, balance, atRiskCount, atRiskNames }
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post('/api/subscribe', (req, res) => {
|
||||||
|
try {
|
||||||
|
const openid = req.headers['x-openid'] || req.body.openid
|
||||||
|
if (!openid) return res.json({ success: false, error: '缺少 openid' })
|
||||||
|
|
||||||
|
const { action, data } = req.body
|
||||||
|
switch (action) {
|
||||||
|
case 'grant': return res.json(grantQuota(openid, data))
|
||||||
|
case 'get': return res.json(getQuotaStatus(openid))
|
||||||
|
default: return res.json({ success: false, error: '未知操作' })
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('订阅额度操作失败:', err.message)
|
||||||
|
res.json({ success: false, error: err.message })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 手动触发提醒任务(便于调试,无需等到定点)
|
// 手动触发提醒任务(便于调试,无需等到定点)
|
||||||
app.post('/api/reminder/run', async (req, res) => {
|
app.post('/api/reminder/run', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -231,10 +266,17 @@ app.post('/api/reminder/run', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
module.exports = { grantQuota, getQuotaStatus }
|
||||||
|
|
||||||
// ---- 启动 ----
|
// ---- 启动 ----
|
||||||
|
|
||||||
|
// 只在直接执行本文件(node src/index.js / npm start)时才真正监听端口、注册定时任务。
|
||||||
|
// 测试用 require('../src/index') 只是拿函数引用,不需要(也不能)真的起服务——
|
||||||
|
// 否则会占住端口且 cron 定时器让进程一直不退出,测试永远卡住。
|
||||||
|
if (require.main === module) {
|
||||||
const PORT = process.env.PORT || 3000
|
const PORT = process.env.PORT || 3000
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`生日提醒后端已启动,监听 ${PORT}`)
|
console.log(`生日提醒后端已启动,监听 ${PORT}`)
|
||||||
reminder.start()
|
reminder.start()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
const test = require('node:test')
|
||||||
|
const assert = require('node:assert')
|
||||||
|
const { useTempDb } = require('./helper')
|
||||||
|
|
||||||
|
useTempDb()
|
||||||
|
const db = require('../src/db')
|
||||||
|
const { grantQuota, getQuotaStatus } = require('../src/index')
|
||||||
|
|
||||||
|
const OPENID = 'o_test_1'
|
||||||
|
|
||||||
|
// 往指定用户名下插一条纪念日
|
||||||
|
function seed(openid, id, personName, offsetDays, remindDays) {
|
||||||
|
const d = new Date(Date.now() + offsetDays * 86400000)
|
||||||
|
db.prepare(`
|
||||||
|
INSERT OR REPLACE INTO anniversaries
|
||||||
|
(id, openid, personId, personName, type, isLunar, solarYear, solarMonth, solarDay,
|
||||||
|
importance, remindEnabled, remindDays, createTime, updateTime)
|
||||||
|
VALUES (?, ?, 'p1', ?, 'birthday', 0, ?, ?, ?, 'high', 1, ?, 0, 0)
|
||||||
|
`).run(id, openid, personName, d.getFullYear(), d.getMonth() + 1, d.getDate(), remindDays)
|
||||||
|
}
|
||||||
|
|
||||||
|
seed(OPENID, 'seed1', '张三', 10, 3)
|
||||||
|
|
||||||
|
test('grant 累加并返回新余额', () => {
|
||||||
|
assert.deepStrictEqual(grantQuota(OPENID, { count: 1 }), { success: true, balance: 1 })
|
||||||
|
assert.deepStrictEqual(grantQuota(OPENID, { count: 1 }), { success: true, balance: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('grant 的 data 缺省时按 1 计', () => {
|
||||||
|
const before = getQuotaStatus(OPENID).balance
|
||||||
|
grantQuota(OPENID, undefined)
|
||||||
|
assert.strictEqual(getQuotaStatus(OPENID).balance, before + 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('get 返回余额与风险信息', () => {
|
||||||
|
const res = getQuotaStatus(OPENID)
|
||||||
|
assert.strictEqual(res.success, true)
|
||||||
|
assert.strictEqual(typeof res.balance, 'number')
|
||||||
|
assert.ok(Array.isArray(res.atRiskNames))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('无纪念日时没有风险', () => {
|
||||||
|
const res = getQuotaStatus('o_empty')
|
||||||
|
assert.strictEqual(res.atRiskCount, 0)
|
||||||
|
assert.deepStrictEqual(res.atRiskNames, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('纪念日多于余额时报出风险人名', () => {
|
||||||
|
const OP = 'o_test_2'
|
||||||
|
// 造两条近期纪念日,共 4 个提醒事件,但只给 1 点额度
|
||||||
|
seed(OP, 'x1', '甲', 10, 3)
|
||||||
|
seed(OP, 'x2', '乙', 20, 3)
|
||||||
|
require('../src/quota').grant(OP, 1)
|
||||||
|
|
||||||
|
const res = getQuotaStatus(OP)
|
||||||
|
assert.ok(res.atRiskCount >= 1, '应当报出风险')
|
||||||
|
assert.ok(res.atRiskNames.includes('乙'), '时间靠后的乙必然有风险')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user