Files
BYW-VoiceHub/server/api/admin/wechat/test-subscribe.post.ts
T
Henry_Du a2ba15c3ca feat(notify): 微信订阅消息推送(驳回通知已对接)
- 新增 server/services/wechatSubscribeService.ts:access_token 缓存 + 模板配置 + 字段裁剪 + subscribe/send
  · 内置默认模板 SONG_REJECTED(模板 ID erAK**…,审核内容=thing6←歌名,驳回原因=thing4←驳回原因)
  · 环境变量 WECHAT_MINI_SUBSCRIBE_TEMPLATES 可按键覆盖默认模板
  · thing 字段按微信限制裁到 20 字(超长截 19 字 + 省略号),避免 47003
  · 发送永不抛异常;43101(用户没授权/没额度)只记 info
- 新增 server/api/user/wechat-subscribe-templates.get.ts:给小程序返回模板 ID(客户端要拿它去申请授权)
- 新增 server/api/admin/wechat/test-subscribe.post.ts:超管自测接口(token/openid/字段/微信原文一次看清)
- notificationService.ts:歌曲被选中/已播放/被驳回/系统通知 四处顺带发微信订阅消息
- .env.example + 部署流水线:WECHAT_MINI_SUBSCRIBE_TEMPLATES 可用仓库 Secret 下发
2026-09-27 13:55:55 +08:00

93 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
buildTemplateData,
getMiniAccessToken,
getSubscribeTemplates,
getWechatOpenid,
sendRawSubscribe
} from '~~/server/services/wechatSubscribeService'
/**
* 微信订阅消息的**自测接口**(仅超级管理员)—— 站长部署完用它「试一下」。
*
* 一次调用能看出四件事:
* 1. access_token 能不能拿到(appid / secret 是否有效)
* 2. 目标用户有没有绑微信(有没有 openid;纯密码用户必然是 null)
* 3. 模板字段名 / 长度对不对(微信会回 47003 / 43101 之类的错误码)
* 4. 微信返回的原文
*
* body(都可省):
* { userId?: number, kind?: string, openid?: string, templateId?: string, data?: object }
* · 默认给自己发、用配置里的第一个模板
* · 想直接指定 openid / 模板 ID / data 也可以(用于排查)
*/
export default defineEventHandler(async (event) => {
const user = event.context.user
if (!user || user.role !== 'SUPER_ADMIN') {
throw createError({ statusCode: 403, message: '只有超级管理员可以测试微信订阅消息' })
}
const body = await readBody(event)
const templates = getSubscribeTemplates()
const kinds = Object.keys(templates)
const kind = body?.kind || kinds[0] || ''
const conf = kind ? templates[kind] : null
const templateId = body?.templateId || conf?.id || ''
const targetUserId = Number(body?.userId) || Number(user.id)
const token = await getMiniAccessToken()
const openid = body?.openid || (await getWechatOpenid(targetUserId))
if (!templateId) {
return {
ok: false,
reason: 'no-template',
hint: '服务器环境变量 WECHAT_MINI_SUBSCRIBE_TEMPLATES 没配置模板 ID(见 wechatSubscribeService.ts 头部注释)',
configuredKinds: kinds,
tokenOk: !!token,
openid: openid ? openid.slice(0, 6) + '****' : null
}
}
if (!openid) {
return {
ok: false,
reason: 'no-wechat-openid',
hint: '该用户没有绑定微信(userIdentities 里没有 provider=wechat-mp 的记录),微信发不到他那里',
kind: kind,
templateId: templateId,
tokenOk: !!token,
targetUserId: targetUserId
}
}
const data =
body?.data && typeof body.data === 'object'
? body.data
: buildTemplateData(conf || { id: templateId }, {
songTitle: '测试歌曲',
message: '这是一条来自 VoiceHub 的测试通知'
})
const result = await sendRawSubscribe(openid, templateId, data)
return {
ok: result.ok,
errcode: result.errcode,
errmsg: result.errmsg,
hint:
result.errcode === 0
? '发送成功,去微信里看「服务通知」'
: result.errcode === 43101
? '用户没有可用授权(一次性订阅:需要用户在小程序里点过「开启微信通知」才行)'
: '看 errmsg:47003 = 模板字段名/长度不对;40003 = openid 不对;40001 = token 失效',
configuredKinds: kinds,
kind: kind,
templateId: templateId,
targetUserId: targetUserId,
openid: openid.slice(0, 6) + '****',
tokenOk: !!token,
data: data,
raw: result.raw
}
})