小程序消息推送怎么做?模板消息开发全解析
小程序消息推送怎么做?模板消息开发全解析
在移动互联网的下半场,用户留存比拉新更重要。对于微信小程序而言,如何在不打扰用户的前提下,将关键信息(如订单状态、审核结果、物流进度)精准触达用户,是提升用户体验和复购率的关键。
虽然微信官方已逐步用“订阅消息”取代了传统的“模板消息”,但其核心逻辑——基于模板ID、通过后端服务向指定用户发送结构化数据——依然未变。本文将基于 ThinkPHP 5 (TP5) 框架,带你从零构建一套稳定、高效的小程序消息推送服务。
一、 核心原理与前置准备
在写代码之前,我们需要理清消息推送的三个核心要素:
- Access Token:接口调用的凭证,由 AppID 和 AppSecret 换取,有效期2小时,需缓存。
- Template ID:在微信公众平台后台配置的模板唯一标识。
- OpenID:用户的唯一标识,必须通过用户授权登录获取。
注意:自2020年起,微信逐步下线模板消息,转为订阅消息。本文以 TP5 实现通用推送逻辑为主,代码结构兼容两者,仅需调整 API 地址和参数结构即可。
1.1 公众平台配置
- 登录 微信公众平台。
- 进入“功能” -> “订阅消息”或“模板消息”。
- 选用或创建模板,获取
template_id。 - 记录 AppID 和 AppSecret(位于“开发” -> “开发设置”)。
二、 ThinkPHP 5 项目架构设计
为了保持代码的整洁与可维护性,我们不建议将微信逻辑散落在 Controller 中。建议采用以下结构:
application/
├── common/
│ └── service/
│ └── WechatService.php # 微信核心服务类
├── index/
│ ├── controller/
│ │ └── Message.php # 消息推送控制器
│ └── model/
│ └── User.php # 用户模型
└── config/
└── wechat.php # 微信配置文件2.1 配置文件 (config/wechat.php)
将敏感配置独立出来,便于多环境管理。
<?php
return [
'app_id' => 'wx1234567890abcdef',
'app_secret' => 'your_app_secret_here',
// 模板ID映射,方便业务调用
'templates' => [
'order_pay' => 'TEMPLATE_ID_FOR_ORDER_PAY',
'ship_notice' => 'TEMPLATE_ID_FOR_SHIP_NOTICE',
'audit_result' => 'TEMPLATE_ID_FOR_AUDIT_RESULT',
],
// Redis缓存前缀
'cache_prefix' => 'wechat_',
];三、 核心服务层开发
这是本文的重点。我们将所有微信交互逻辑封装在 WechatService 中,利用 TP5 的缓存机制解决 Access Token 过期问题。
3.1 安装 HTTP 客户端
TP5 本身没有内置强大的 HTTP 客户端,建议使用 guzzlehttp/guzzle 或 TP5 自带的 curl 封装。这里为了演示清晰,使用原生 curl 封装,不依赖额外扩展。
3.2 编写 WechatService.php
<?php
namespace app\common\service;
use think\Cache;
use think\Config;
class WechatService
{
private $appId;
private $appSecret;
public function __construct()
{
$this->appId = Config::get('wechat.app_id');
$this->appSecret = Config::get('wechat.app_secret');
}
/**
* 获取 Access Token (带缓存)
* @return string
*/
public function getAccessToken()
{
$cacheKey = Config::get('wechat.cache_prefix') . 'access_token';
$token = Cache::get($cacheKey);
if ($token) {
return $token;
}
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}";
$res = $this->httpGet($url);
$result = json_decode($res, true);
if (isset($result['access_token'])) {
// 微信默认有效期7200秒,我们缓存7000秒以确保安全
Cache::set($cacheKey, $result['access_token'], 7000);
return $result['access_token'];
}
throw new \Exception("获取AccessToken失败: " . ($result['errmsg'] ?? '未知错误'));
}
/**
* 发送订阅消息/模板消息
* @param string $openid 用户openid
* @param string $templateId 模板ID
* @param array $data 模板数据
* @param string $page 点击消息跳转页面
* @return array
*/
public function sendSubscribeMessage($openid, $templateId, $data, $page = '')
{
$accessToken = $this->getAccessToken();
// 订阅消息接口地址 (如果是旧版模板消息,地址不同,参数结构也略有差异)
$url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={$accessToken}";
$postData = [
'touser' => $openid,
'template_id' => $templateId,
'page' => $page,
'data' => $data,
// 'miniprogram_state' => 'formal', // 可选: developer/trial/formal
];
$res = $this->httpPost($url, json_encode($postData, JSON_UNESCAPED_UNICODE));
$result = json_decode($res, true);
if ($result['errcode'] != 0) {
// 记录日志,方便排查
\think\Log::error("微信消息推送失败: openid={$openid}, errcode={$result['errcode']}, errmsg={$result['errmsg']}");
return ['status' => 0, 'msg' => $result['errmsg']];
}
return ['status' => 1, 'msg' => '发送成功'];
}
/**
* GET 请求
*/
private function httpGet($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
/**
* POST 请求
*/
private function httpPost($url, $data)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 设置Header
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
}四、 业务场景实战:订单支付成功通知
假设用户支付成功后,我们需要推送一条“支付成功通知”。
4.1 控制器调用 (index/controller/Message.php)
<?php
namespace app\index\controller;
use think\Controller;
use app\common\service\WechatService;
use think\Config;
class Message extends Controller
{
/**
* 模拟订单支付成功回调触发推送
*/
public function notifyOrderPay()
{
// 1. 获取参数 (实际场景中应从订单表或支付回调中获取)
$openid = input('post.openid');
$orderId = input('post.order_id');
if (!$openid || !$orderId) {
return json(['code' => 400, 'msg' => '参数缺失']);
}
// 2. 准备模板数据
// 注意:订阅消息的数据格式为 { "thing1": { "value": "xxx" } }
// 具体字段名(thing1, date2等)取决于你在公众平台创建的模板详情
$templateData = [
'character_string1' => ['value' => $orderId], // 订单编号
'amount2' => ['value' => '99.00元'], // 支付金额
'time3' => ['value' => date('Y-m-d H:i:s')], // 支付时间
'thing4' => ['value' => '微信支付'], // 支付方式
];
// 3. 获取对应的 Template ID
$templateId = Config::get('wechat.templates.order_pay');
// 4. 调用服务发送
try {
$wechatService = new WechatService();
$result = $wechatService->sendSubscribeMessage(
$openid,
$templateId,
$templateData,
'/pages/order/detail?id=' . $orderId // 点击跳转路径
);
if ($result['status'] == 1) {
return json(['code' => 200, 'msg' => '推送成功']);
} else {
return json(['code' => 500, 'msg' => '推送失败: ' . $result['msg']]);
}
} catch (\Exception $e) {
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
}五、 关键难点与避坑指南
在实际开发中,很多开发者会遇到“推送失败”或“收不到消息”的问题,以下是常见坑点:
5.1 订阅消息 vs 模板消息
- 模板消息(旧):无需用户每次授权,但有行业限制,且已逐步停用。
订阅消息(新):
- 一次性订阅:用户每点击一次授权按钮,只能发送一条消息。适合支付成功、审核结果等低频场景。
- 长期订阅:仅对政务、医疗、交通等特定行业开放。
- 关键点:前端必须先调用
wx.requestSubscribeMessage接口,用户点击“允许”后,后端才能发送。否则接口会返回43101(用户拒绝接受消息)。
5.2 数据格式严格匹配
微信对模板数据的长度和内容有严格限制:
thing(事物): 不超过20个字符。character_string(字符串): 不超过32个字符。number(数字): 不超过32位。time(时间): 格式需符合 ISO 8601 或特定要求。- 建议:在后端发送前,对数据进行
mb_substr截断处理,避免因超长导致发送失败。
5.3 Access Token 并发问题
在高并发场景下,多个请求同时发现 Token 过期,会同时去微信服务器请求新 Token,可能导致冲突。
- 解决方案:使用 Redis 的
setnx或文件锁机制,确保同一时间只有一个进程去刷新 Token。上述代码中的Cache::set在 TP5 默认文件缓存下基本够用,高并发建议切换为 Redis 驱动。
六、 总结
基于 ThinkPHP 5 实现小程序消息推送,核心在于分层架构与缓存策略。
- 封装服务层:将
getAccessToken和sendMessage封装在Service层,避免代码重复。 - 合理缓存:利用 TP5 的 Cache 机制管理 Token,减少 API 调用次数。
- 遵循规范:严格遵守微信订阅消息的数据格式和用户授权流程。
通过这套方案,你可以轻松集成订单通知、物流提醒、审核结果等多种业务场景,有效提升小程序的用户活跃度和转化率。
温馨提示:代码仅为核心逻辑演示,生产环境请补充完善的异常处理、日志记录以及前端授权逻辑配合。
还没有人发表评论