refactor(微信订阅): 重写订阅消息链路,每一步失败都可见
把「微信订阅消息」这套代码按验收标准推翻重写:用户能自己看懂为什么收不到,
任何一步失败都必须看得见。对外函数名不变,notificationService / test-subscribe
等调用点无需改动。
新增 server/utils/wechatSubscribeTemplates.ts(纯函数,零依赖):
- 模板登记表唯一来源(内置 SONG_SELECTED / SONG_REJECTED),服务层不再自己写死
- parseSubscribeTemplates 如实报告环境变量覆盖情况:坏 JSON 只报告不抛、
只给 id 时沿用内置字段映射、缺 id 的项记进 unknownKeys 并忽略
- findEmptyFields / findEmptyDataFields 发出前自查空字段(微信空值整条 47003)
- explainWechatErrcode 把 43101/47003/40003/40037/40001/45009 翻成人话
重写 server/services/wechatSubscribeService.ts:
- sendWechatSubscribe 返回 SendOutcome,用 stage 明确「卡在哪一步」:
no-template / disabled-by-user / no-wechat-openid / no-access-token /
empty-field / no-grant / rejected-no-quota / rejected-bad-field / rejected-other
- 每一次返回都写一行日志,不再有静默跳过(这是「发不出去全靠猜」的根因)
- inspectWechatSubscribe 只读体检:不发消息、不消耗额度,返回逐项 verdict + actions
- grantWechatSubscribe 依靠新增的 WechatSubscribeGrant 表记账:微信没有查询
剩余授权的接口,只能自己数(用户点允许 +1、发送成功 -1)
- 记账表不可用时失败开放(fail-open):额度预检关闭、照常发送,避免通知整体哑掉
- access_token 显式 responseType: json 并做字符串兜底解析
新增接口:
- GET /api/user/wechat-subscribe-status 小程序显示「还能收到几条 / 为什么收不到」
- POST /api/user/wechat-subscribe-grant 小程序授权成功后上报 accept 的模板
- GET /api/admin/wechat/diagnose 重写:人话结论 + actions,?send=1 真发一次
其他:新增 WechatSubscribeGrant 表(schema + 手写迁移)、.env.example 说明 JSON 覆盖
写法、.gitattributes 补 *.sql eol=lf。
测试:新增 test/wechatSubscribeTemplates.test.mjs(13 个用例,可直接 import .ts,
不依赖 Nuxt 别名),npm test 里订阅相关 25/25 通过。
This commit is contained in:
@@ -84,7 +84,7 @@ test('空白与换行会被压成一行(微信不接受换行)', () => {
|
||||
})
|
||||
|
||||
test('SONG_SELECTED 的内置字段映射:每个字段名后缀都要跟发出去的值类型匹配', () => {
|
||||
// 与 server/services/wechatSubscribeService.ts 的 DEFAULT_TEMPLATES.SONG_SELECTED 保持一致
|
||||
// 字段映射的唯一来源是 server/utils/wechatSubscribeTemplates.ts(那里有更完整的测试)
|
||||
const fields = {
|
||||
thing2: 'songTitle',
|
||||
date4: 'playDate',
|
||||
@@ -136,14 +136,15 @@ test('SONG_REJECTED 的内置字段映射:长驳回原因必须裁进 thing
|
||||
|
||||
test('内置模板与线上代码一致(直接读源码,防止映射被改坏没人发现)', async () => {
|
||||
/*
|
||||
* 为什么不 import:wechatSubscribeService.ts 顶部 import 了 `~~/server/...` 这类 Nuxt 别名,
|
||||
* 纯 `node --test` 解析不了。这里只校验「模板 ID 和字段映射」这类静态事实,读文本足够,
|
||||
* 而且不需要跑起来就能在 CI 里拦住「手滑改了模板 ID / 字段名」。
|
||||
* 模板定义现在的唯一来源是 server/utils/wechatSubscribeTemplates.ts(纯 .ts,无 Nuxt 别名),
|
||||
* 但这里仍然用「读源码 + 正则」而不是 import:这个测试文件只依赖 wechatSubscribeFields.ts,
|
||||
* 不想因为模板文件偶尔引入别名而整个挂掉。详细的解析行为在
|
||||
* test/wechatSubscribeTemplates.test.mjs 里测。
|
||||
*/
|
||||
const { readFileSync } = await import('node:fs')
|
||||
const { fileURLToPath } = await import('node:url')
|
||||
const src = readFileSync(
|
||||
fileURLToPath(new URL('../server/services/wechatSubscribeService.ts', import.meta.url)),
|
||||
fileURLToPath(new URL('../server/utils/wechatSubscribeTemplates.ts', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const block = (kind) => {
|
||||
|
||||
Reference in New Issue
Block a user