·更新于 2026年10月25日· 17 次浏览 微信小程序Node.jsRedisBUG解决工程实践

微信小程序订阅消息总发不出去:我把授权、模板和 Node.js 重试队列补齐

一次支付成功后的订阅消息没送达,我把问题拆成授权、模板、错误码和重试队列四段,补上可观测与延迟重试。

微信小程序订阅消息总发不出去:我把授权、模板和 Node.js 重试队列补齐 封面
ZHOUYI

作者

ZHOUYI

周五晚上收到反馈:支付成功了,但小程序订阅消息没提醒。第一反应是接口挂了,看日志才发现发送任务有时根本没进发送函数,有时进了却被微信拒掉。订阅消息不是单个接口问题,而是一条链路:前端授权、模板配置、后端发送、失败重试,哪一段断了都会表现为“发不出去”。

先别急着重试:把失败分成三类

  • 授权失败:用户没点允许、一次性授权次数不足,或者授权记录没落库。
  • 模板失败:模板 ID 换了、字段映射错了、模板状态不可用。
  • 发送失败:网络超时、接口限流、参数不合法、用户拒收。

我把前两类当成配置和状态问题,不做盲目重试。授权不足就引导用户重新订阅;模板变更就在配置表里统一切换,避免代码里散落旧 ID。第三类才进入重试,但也要看错误类型:网络、超时、限流可以延迟重试;用户拒收、模板不可用、参数错误就记录原因,重到天涯海角也没用。具体错误码含义以官方文档为准。

用 Redis 延迟队列补上偶发失败

后端收到支付事件后,先生成发送任务,写入 MongoDB,再投递到 Redis ZSET。score 是下次执行时间,消费者只取到期的任务。发送前检查任务状态和幂等键,避免同一笔订单提醒发两遍。失败后按退避时间重新入队,超过最大次数就转人工排查。

javascript
async function sendWithRetry(task) {
  try {
    await sendSubscribeMessage(task);
    await markDone(task.id);
  } catch (err) {
    if (err.retryable && task.attempts < 5) {
      const delay = Math.min(60_000 * 2 ** task.attempts, 10 * 60_000);
      await redis.zadd('subscribe:retry', Date.now() + delay, JSON.stringify({ ...task, attempts: task.attempts + 1 }));
    } else {
      await markFailed(task.id, err.message);
    }
  }
}

前端授权也要有回执

小程序端不要只在支付按钮里顺手调 wx.requestSubscribeMessage。我会把授权结果、模板 ID、场景值一起回传给后端,后端按用户和模板记录可用次数。用户拒绝时给出明确提示,而不是默默失败。这样排查时能回答“是没授权、没次数,还是发送时参数错了”。

“这次最大的感受是:订阅消息的稳定性不靠运气,靠链路。授权有记录、模板有配置、错误有分类、失败有重试,问题才不会变成玄学。”

Guestbook

留言

若这篇文章也曾停在你心上,留一行字吧,我会看的。

先确认一下邮箱

多这一步,是为了少些机器骚扰,也让我能更踏实地读每一条真人写下的字。验证码 10 分钟内有效;同一邮箱 60 秒内只能发一封。

本文留言

共 0 条已加载 0 条

这里还静悄悄的——愿意做第一个留下脚印的人吗?