给一群人做的课表小程序上线第一周就出事了。提醒功能前两天正常, 第三天开始有人收不到提醒,第四天几乎全挂。用户在群里问:「是不是坏了?」
我一开始以为是定时任务挂了,查日志发现任务跑得好好的,是发送接口返回了错误码。 追下去才发现:我对订阅消息的授权机制理解错了。
我以为的 vs 实际的
我以为:用户点一次「允许」,就代表以后都能给他发通知。 这是以前模板消息的逻辑,我脑子里存的是旧知识。
实际是:现在用的是订阅消息,分两种。
- 一次性订阅:用户每授权一次,你只能发一条。发完就消耗掉,下次还得重新授权。
- 长期订阅:可以多次下发,但只对特定行业开放(政务、医疗、交通等),个人主体和小企业主体基本申请不下来。
也就是说,我那个「每天推一条」的需求,在一次性订阅的规则下, 用户每天都要重新点一次授权。这在体验上很难接受, 但没有别的路——除非申请到长期订阅。
错误码对照
那几天我攒了几个高频的错误码,记下来省得再查文档:
43101:用户拒绝接受消息,或没有订阅次数。最常见,就是上面说的原因。40003:touser 无效,一般是 openid 拿错了或者用了另一个小程序的 openid。47003:模板参数不合法。注意参数值不能超长,也不能是空字符串。41030:page 路径不存在。跳转页要写相对路径,且不能在路径里带参数。
其中 43101 最坑,因为它不区分「用户拒绝了」和「次数用完了」,
排查时只能靠自己记录每个用户的剩余次数。
现在我的做法
1. 在「有明确意图」的时刻请求授权
别一进小程序就弹授权框——用户还不知道你要干什么,大概率点拒绝, 而且拒绝一次之后体验很差。我的做法是:用户在课表页点「开启提醒」这个按钮时, 才调起授权。
这时候用户已经知道自己要什么了,同意率明显高。而且逻辑上也说得通: 他点了这个按钮,就是同意了「接收提醒」这件事。
// 点击「开启提醒」时调用
wx.requestSubscribeMessage({
tmplIds: ['模板ID_1', '模板ID_2', '模板ID_3'], // 一次最多 3 个
success(res) {
// res['模板ID_1'] === 'accept' 表示同意
// 'reject' 拒绝,'ban' 被后台封禁
const accepted = Object.keys(res).filter(
key => res[key] === 'accept'
);
if (accepted.length > 0) {
// 告诉后端:这个用户还有几次可发
wx.request({
url: 'https://your.api/subscribe',
method: 'POST',
data: { tmplIds: accepted }
});
}
}
});
2. 一次请求多个模板,攒次数
用户的每一次点击授权,都会给对应模板加一次可发次数。
tmplIds 一次最多传 3 个,所以一次弹窗最多能攒 3 次。
如果一个用户反复点「开启提醒」,次数会累加——但别滥用,频繁弹窗会被用户烦到。
3. 后端要记剩余次数
这是关键。我建了一张表,记 openid、tmplId、剩余次数。
每次发送成功就减一,减到 0 就不再发,而是引导用户重新授权。
这样就不会出现「一直发失败、日志里全是 43101」的情况。
4. 失败的兜底
发送失败不能静默丢弃。我的处理是:
- 次数用尽 → 在小程序内的「消息中心」留一条站内提醒,用户打开时能看到。
- 用户拒收(
43101且剩余次数大于 0)→ 标记该用户拒收,不再重复尝试。 - 其他错误 → 记录完整响应,人工看。
体验上的取舍
说实话,每天要求用户授权一次,体验不可能好。 我试过在小程序里放一个「本周提醒已开启」的状态条,让用户知道还差几次; 也在群里说明过「点一下那个按钮就能收到明天的提醒」。
效果一般。最后我接受了一个现实: 在平台规则的限制下,有些体验就是做不到。 能做的是把摩擦做到最小——按钮位置显眼、文案说清楚、点了之后给明确反馈, 而不是靠技术手段绕过去(绕过平台规则的事我也不敢做,会封号)。
这也是我做这个小程序的收获:写代码之前先读一遍平台规则, 比写完再改要省事得多。规则不会为你让路。