ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

uniapp推送服务端对接指南:ThinkPHP集成个推REST API V2

uniapp推送服务端对接指南:ThinkPHP集成个推REST API V2 简介这是一套面向 uniapp 开发者的移动端推送功能完整后端实现资源核心解决 uniapp 项目中集成 unipush 与个推 SDK 的服务端接口设计问题。内容基于 Thinkphp RestAPI V2 构建覆盖客户端设备注册、Token 上报、服务端调用个推 API 发送通知、结果反馈与异常重试完整链路适合具备基础 PHP 和 uniapp 知识、希望快速落地推送模块的中高级开发者参考。压缩包共 40 个文件以 37 个 PHP 源码为主包含 GeTui.php、GTPushApi.php、GTClient.php 等 SDK 封装与接口实现另有 README、license 及 composer.json 辅助说明包体仅 42KB体积小巧、结构清晰可按模块直接引入项目二次开发。资源已获得 3754 人学习兼具实战代码与集成思路能帮助读者省去排查官方文档和联调的时间快速理解 unipush 与个推服务端的鉴权、推送及回执处理流程。1. uniapp 用 uniPush 做推送卡住你的往往是服务端个推SDK和ThinkPHP怎么接“前端明明拿到了cid后端却始终推不出去”是 uniPush 接入里最典型的隐形门槛。几乎所有人都会先在前端完成 init却把真正的推送逻辑——个推 SDK、REST API V2 鉴权、通知和透传的报文结构——留到最后才碰。某开发者接推送时前后端一起写前端半小时跑通了后端光签名就调了一天。后来把 ThinkPHPRestAPI V2 整合成一套完整版服务端才发现问题不是“接入”而是“协议”。这套方案适合正在用 uniapp 做 App、后端用 PHP、想把通知栏消息和透传消息都走通的开发者。下面按链路拆解到代码落地再把排查清单放最后照着落地即可。2. 推送链路拆解uniPush、个推通道与 ThinkPHP 服务端的边界2.1 一次推送从触发到展示经过了哪几层先明确链路。App 收推送消息时不会由 uniapp 自己从业务服务器拉取而是依赖一个推送服务商的通道。uniPush 2.0 的前端能力本质是把某个推送服务商的 SDK 封装成了一套 uniapp 插件前端只需要拿 clientid、监听消息回调具体的下发、离线消息存储、厂商通道接入全部在服务端和该推送平台侧发生。这个链路大致是业务后端 → 调个推的 REST API V2 → 个推服务端按设备在线状态决定走哪条通道 → 在线设备走个推长连接离线设备走厂商离线通道比如 APNs、华为推送、小米推送等 → 客户端收到后交给系统通知栏或进入监听回调。明白这个层级就能理解为什么“推送不显示”不一定是你代码写错而是厂商通道没配置好。在线消息走个推长连接只要手机网络通畅后台发送几乎即时到达熄屏后应用进程被系统杀掉消息就要靠厂商通道接力这一步依赖你在个推后台申请的厂商推送服务以及 App 打包时的证书、包名、签名设置。这些虽然是前端工作之外的事但服务端对接哪个通道、选择什么报文直接影响送达率。2.2 为什么不建议直接用官方 SDK 裸写很多 PHP 后端第一次做推送会以为引入个推官方 SDK拿到 appId 和 appKey 就可以直接 new 一个客户端推一把。实际上 SDK 封装得再好接入时仍然要面对三个问题token 有效期管理、返回码处理、业务侧推送记录落库。直接在业务方法里 new 一个推送对象token 请求会重复发生往往一天下来会把当天配额耗在半数以上。另外一个推送服务不止“发一条消息”。实际项目里通常有单推、cid 批量推、按条件推、透传消息还要求通知点击后有回调落地。如果所有推送逻辑都散落在 controller 里修改签名算法或加个模板字段就要到处找。常见做法是抽出一个独立的 PushService 类负责 token 缓存、报文组装、网络请求、落库回执controller 只管接收参数并调用。选择 ThinkPHPRestAPI V2 整合不是为了复用 TP 的 ORM 那么简单。TP 自带的命令行模式可以让你写一个php think push:test去手动验证推送队列组件可以处理大批量 cid 推送的异步分发日志组件能记录每次推送的请求体与返回值。这些对于一个上线后“看得到回执”的服务来说是刚需。2.3 鉴权参数与 REST API V2 的签名规则开始写代码前需要先从推送平台后台拿到 appId、appKey、masterSecret 三项。appId 标识应用appKey 是开放平台应用标识masterSecret 是服务端专用密钥。注意 masterSecret 不能写在客户端代码里也不能随前端打包下发否则等于把服务器钥匙放进用户手机里。REST API V2 的 token 获取过程是用 appKey、appId 和 timestamp 算出一个签名然后 POST 到鉴权接口。签名这里最容易出错。我一开始用 sha1、用的是秒级时间戳、拼接顺序搞错白白调试一个多小时。正确的规则是按固定顺序做字符串拼接appKey appId timestamp做 sha256timestamp 取毫秒级时间戳。这里有个玄学点有些接口文档写的是 timestamp 毫秒有些示例却是秒如果服务端返回时间戳相关错误第一反应就是查单位。我们后面代码里统一用 13 位毫秒。token 拿到后有效期默认是一段时间可以缓存在 think\Cache 里避免每次推送前都重新鉴权。提示masterSecret 泄露后记得去后台重置否则任何人都能调你的接口给你的用户发垃圾消息。3. ThinkPHPRestAPI V2 服务端实现从数据库到推送报文3.1 数据库表设计与接口约定推送服务端先要把数据模型定下来。单推要记录 cid、标题、内容、taskId、推送结果批量推还要记录任务批次、总条数、成功条数。实际项目通常建一张 push_log 表关键字段如下。CREATE TABLE push_log ( id int(11) NOT NULL AUTO_INCREMENT, cid varchar(64) NOT NULL DEFAULT COMMENT 客户端唯一标识, title varchar(128) NOT NULL DEFAULT COMMENT 通知标题, content text COMMENT 通知内容, payload text COMMENT 透传数据JSON字符串, task_id varchar(64) NOT NULL DEFAULT COMMENT 个推返回的任务ID, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0处理中 1成功 2失败, error_msg varchar(255) NOT NULL DEFAULT COMMENT 失败原因, create_time int(11) NOT NULL, update_time int(11) NOT NULL, PRIMARY KEY (id), KEY idx_cid (cid), KEY idx_task_id (task_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT推送记录表;cid 字段对应 uniapp 端 uni.getPushClientId 拿到的值一个 App 安装一次会生成一个或多个 cid具体数量取决于不同端的通道注册情况。task_id 是个推服务端返回的任务标识用于后续查询送达回执。status 字段不能只存成功和失败要加一个处理中因为批量推送是异步任务接口返回 code 为 0 不代表每条都送达。接口约定上我会设计三个 RestAPI 风格端点单推、批量推、透传。单推和透传解耦开来前端业务上“点击通知打开页面”走通知栏消息payload“静默更新”走透传消息。这样前端 onPushMessage 回调可以区分消息类型。3.2 封装 PushServicetoken 获取与缓存下面的 PushService 是我习惯用的结构核心是 getAccessToken 方法和 sendSingle 方法。先看鉴权部分。?php namespace app\common\service; use think\facade\Cache; use think\facade\Log; class PushService { private $appId; private $appKey; private $masterSecret; private $baseUrl https://restapi.example.com/v2; public function __construct() { $this-appId config(push.app_id); $this-appKey config(push.app_key); $this-masterSecret config(push.master_secret); } public function getAccessToken() { $cacheKey push_token_ . $this-appId; $token Cache::get($cacheKey); if ($token) { return $token; } $timestamp (string)(time() * 1000); $sign hash(sha256, $this-appKey . $this-appId . $timestamp); $url $this-baseUrl . / . $this-appId . /auth; $body [ sign $sign, timestamp $timestamp, appkey $this-appKey ]; $result $this-httpPost($url, $body); if (isset($result[code]) $result[code] 0) { $data $result[data]; Cache::set($cacheKey, $data[token], 60 * 60); return $data[token]; } Log::error(push auth fail: . json_encode($result, JSON_UNESCAPED_UNICODE)); return null; } private function httpPost($url, $params) { $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); curl_setopt($ch, CURLOPT_HTTPHEADER, [Content-Type: application/json]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response curl_exec($ch); curl_close($ch); return json_decode($response, true); } }这段代码里 token 缓存是重点。Cache::set 往 think 的缓存通道里写一小时有效一小时到期后重新获取避免每个请求都触发一次 auth。timestamp 拼接成毫秒时间戳后转成字符串避免 PHP 整数溢出。httpPost 方法里设了 10 秒超时推送接口在弱网下偶发慢响应超过 10 秒就返回。这个超时可以按业务接受度调整但不要设置成 0否则 curl 会一直等ThinkPHP 的请求队列会积压。auth 接口的返回结构是 code 为 0 时data 里带 token。如果 code 非 0通常错误信息里会明确提示 sign 问题还是 timestamp 问题。我第一次调通后发现错误提示签名校验失败检查后发现是把 appId 和 appKey 拼反了。这个接口对拼接顺序高度敏感不是 sha256 算法选错是原材料顺序错了。3.3 单推、cid 批量推与透传消息完整请求报文token 拿到后推送才是最要命的。个推 REST API V2 的单推端点根据 cid 走批量推送可以传多个 cid这里给出我验证过的报文组装方式。public function sendSingle($cid, $title, $content, $payload []) { $token $this-getAccessToken(); if (!$token) { return [code -1, msg token获取失败]; } $requestId uniqid(, true); $url $this-baseUrl . / . $this-appId . /push/single/cid; $notification [ title $title, body $content, click_type payload, payload json_encode($payload, JSON_UNESCAPED_UNICODE) ]; $body [ request_id $requestId, audience [cid [$cid]], push_message [ notification $notification ], push_channel [ android [ ups [ notification $notification ] ], ios [ apns [ aps [ alert [ title $title, body $content ] ], payload json_encode($payload, JSON_UNESCAPED_UNICODE) ] ] ] ]; $result $this-httpPostWithToken($url, $body, $token); $this-logPush($cid, $title, $content, $payload, $result); return $result; }这里最关键的字段是 click_type。当设置为 payload 后用户点击通知栏消息时App 会收到透传 payload可以由此跳转到具体页面。如果你不设置它点击通知只会打开 App不能拿到跳转参数。Android 通道里我把 notification 重复放在 push_channel.android.ups 下面这是厂商通道的消息体和顶层 push_message.notification 并存。iOS 走 apns 的 alert 字段payload 作为一个自定义键传给客户端。注意 request_id 要保证每次推送唯一个推后台会用它做消息去重和任务追踪。遇到同一内容重复推送时如果你沿用同一个 request_id个推可能直接丢弃。我见过一次线上重复推送查了半天才发现是代码里把 request_id 写死了。uniqid(, true) 足够日常使用并发高时可以在后缀拼接随机数。批量推送端点类似只是把 url 换成批量 cid 端点。批量推不要循环调用 sendSingle否则每个 cid 都会触发一次 token 校验和 HTTP 请求批量 1000 个 cid 就会产生 1000 个请求。正确做法是让接口一次提交多 cid 到批量端点。public function sendBatch($cidList, $title, $content) { $token $this-getAccessToken(); $requestId uniqid(batch_, true); $body [ request_id $requestId, audience [cid $cidList], push_message [ notification [ title $title, body $content ] ], push_channel [ android [ups [notification [title $title, body $content]]], ios [apns [aps [alert [title $title, body $content]]]] ] ]; return $this-httpPostWithToken($this-baseUrl . / . $this-appId . /push/list/cid, $body, $token); }批量推送的返回结构与单推一致都会返回 taskId但离线推送状态还要等厂商回执。批量接口一次提交的 cid 数量有上限控制我习惯每 500 个 cid 拆成一批避免单次请求体过大。拆批逻辑放在控制器层批量任务入口负责切分循环调用 sendBatch。透传消息和通知消息的最大区别是没有 notification 字段客户端收到 message 后不展示系统通知而是直接把 payload 传给 uniPush 回调。场景是 App 静默更新缓存、刷新数据、状态同步。发送方式和单推相同端点只是 push_message 里只放 transmission 字段。$body [ request_id uniqid(trans_, true), audience [cid [$cid]], push_message [ transmission json_encode($payload, JSON_UNESCAPED_UNICODE) ], push_channel [ android [ups [transmission json_encode($payload, JSON_UNESCAPED_UNICODE)]], ios [apns [aps [content-available 1]]] ] ];透传在 iOS 端要注意content-available 设为 1 表示后台静默推送这种推送不一定每次都会唤醒 App苹果对静默推送有频率限制。不要把关键业务数据完全押在静默推送的送达上有价值的消息仍要发通知栏消息并附加 payload。3.4 推送落库与结果判断前面 sendSingle 方法里调用 logPush 做了落库这个步骤不能省。推送请求返回后先用 code 判断接口级成功再记录 taskId状态置为处理中。等个推的回执回调打过来后再更新 status 为最终状态。private function logPush($cid, $title, $content, $payload, $result) { $data [ cid $cid, title $title, content is_array($content) ? json_encode($content) : $content, payload json_encode($payload, JSON_UNESCAPED_UNICODE), task_id isset($result[data][taskId]) ? $result[data][taskId] : , status isset($result[code]) $result[code] 0 ? 0 : 2, error_msg isset($result[msg]) ? $result[msg] : , create_time time(), update_time time() ]; \think\facade\Db::name(push_log)-insert($data); }为什么接口返回成功仍然置为“处理中”这是个推的重要特点。单推时接口返回成功表示消息已接受但送达回执是异步的尤其离线消息要等厂商通道确认快则秒级慢则几分钟。如果直接把 status 置为成功你看到的是“已发送”而不是“已收到”后面排查送达率时数据全假。回执接口可以单独开发通过 push_log 里的 task_id 去匹配对应记录。如果 code 不为 0error_msg 里会直接给失败原因。比较常见的是 cid 不存在、应用被卸载、token 过期。此时状态置为 2后续脚本可以筛出这些失败 cid 做清理。我的习惯是每天跑一次定时任务把近七天的失败记录按 cid 分组确认无效 cid 就从用户表里解绑避免每轮推送都对着无效 cid 空打。4. 前端联调与避坑排查cid拿不到、熄屏收不到、厂商通道限制4.1 uniapp 端 cid 获取与上报后端准备好之后前端要确保 cid 正确传到服务端。在 uniapp 的 App 生命周期里调用 uni.getPushClientId 获取 clientid然后 POST 到我们自己的接口。代码不复杂但要注意调用时机。要在 manifest.json 里勾选 uniPush 并配置好相关参数后再调用获取方法否则拿到的可能是空。async function bindPush() { const res await uni.getPushClientId(); const cid res.cid; if (cid) { uni.request({ url: https://api.yourdomain.com/api/push/bindCid, method: POST, data: { cid: cid, userId: uni.getStorageSync(userId) }, success: (resp) { console.log(cid绑定成功, resp.data); } }); } } uni.onPushMessage((res) { if (res.type click) { // 用户点击通知栏消息payload在这里 console.log(click payload, res.payload); } else if (res.type message) { // 在线消息或透传消息 console.log(message, res.payload); } });bindPush 方法应在 App 启动时调用且 cid 可能随登录用户变化所以用户切换登录后要重新绑定一次。这里的 bindCid 接口后端要处理“一个 userId 多个 cid”的情况因为同一个用户可能在不同设备登录每台设备会生成不同 cid。简单做法是绑定表里插入一条记录设备下线时调用 unbind。onPushMessage 回调里的 type 很关键。消息退了通知栏后用户点击回调 type 是 clickApp 在前台收到的消息走 message。有些开发者把业务跳转逻辑全写在 message 分支结果点击通知时没有任何反应就是因为 click 分支没处理。4.2 熄屏收不到消息厂商通道参数与签名这是接入过程中血泪经验最多的地方。App 在后台或熄屏时系统会杀死应用进程长连接断掉个推自己的通道无法直接通信必须靠华为、小米、OPPO、vivo、魅族或苹果 APNs 的离线通道送达。个推的 REST API V2 能不能走通厂商通道取决于你在推送平台后台填写的厂商推送配置以及服务端报文里 push_channel 下的 android 或 ios 内容是否完整。从现象上讲同一套后端代码App 在前台时每次都能收到锁屏过几分钟再解锁就发现消息不见了。这不是代码逻辑问题是厂商通道没申请下来或者申请下来但签名对不上。Android 各厂商要求 App 签名证书 sha256 值必须在厂商推送平台登记否则厂商服务器直接拒绝下发。你的 App 打包若使用云打包需要确认云打包用的证书与申请厂商通道时填写的签名一致。很多人开发阶段用调试证书申请了通道后来换成正式证书打包忘记更新厂商平台的指纹信息测试时就会出现这个翻车现场。iOS 端呢推送走 APNs 时对证书或 token 要求更严格。如果收不到推送先看官方的 device token 有没有成功传给个推后台再看 APNs 的 p12 证书过期没有。证书过期不会报错只是静默丢弃消息后端的 taskId 显示已发送但实际没有送达。4.3 常见问题排查清单现象→原因→解决以下几条是从调通到上线过程里最常见的坑把现象和根因与处理办法按固定格式整理出来方便直接对照。现象一前端拿到的 cid 为空。 原因manifest.json 中 uniPush 配置没打开或 App 端还没触发初始化。 解决检查 manifest 配置确认 App 端使用 apk 包调试而不是 HBuilderX 模拟器预览重新打包后再调用 getPushClientId。现象二推送接口返回 code 为 0但手机收不到任何消息。 原因应用杀进程后离线厂商通道配置缺失或签名指纹不匹配。 解决先让 App 处于前台测试一次能收到说明在线通道正常再锁屏测试仍能收到则回到厂商通道配置排查。现象三Android 8.0 以上设备收不到通知但低版本手机正常。 原因Android 8.0 开始通知必须有 channelId个推在部分厂商通道需要指定 notification channel否则系统直接丢弃。 解决在推送报文的 android.ups.notification 里增加 channel_id 字段并在 App 端提前创建同名的通知渠道。现象四同一台设备登录两个账号后推送绑到旧账号。 原因cid 是设备维度账号切换时没有重新走 bindCid 逻辑。 解决切换登录后前端主动调用 bindCid把新的 userId 重新绑定到同一 cid后端绑定表采用 userIdcid 联合更新。现象五点击通知没有触发回调App 只是被打开。 原因payload 没有放在 click_type 对应的字段里或 Android 厂商通道用了你自己的广告跳转。 解决确认通知里 click_type 为 payloadpayload 为 JSON 字符串并在 onPushMessage 的 click 分支解析不要写在 message 分支里。注意真正排查“送达率”问题后端 taskId 只是入口回执回调才是出口。只盯发送成功会漏掉一半问题。5. 把推送测试变成自动化CLI命令与回执核对习惯推送接口在开发期调试时最忌讳在 uniapp 页面里写一个按钮去触发送推送。前端刚跑起来就要启动模拟器、登录、绑定 cid麻烦且容易误导。正确做法是在 ThinkPHP 里写一个命令行指令直接传 cid、标题、内容触发推送服务这样每次联调只需一行命令。?php namespace app\command; use app\common\service\PushService; use think\console\Command; use think\console\Input; use think\console\Output; use think\console\input\Option; class PushTest extends Command { protected function configure() { $this-setName(push:test) -addOption(cid, null, Option::VALUE_REQUIRED, 设备cid) -addOption(title, null, Option::VALUE_REQUIRED, 通知标题) -addOption(body, null, Option::VALUE_OPTIONAL, 通知内容, test); } protected function execute(Input $input, Output $output) { $cid $input-getOption(cid); $title $input-getOption(title); $body $input-getOption(body); $pushService new PushService(); $result $pushService-sendSingle($cid, $title, $body, [page /pages/index/index]); $output-writeln(推送返回 . json_encode($result, JSON_UNESCAPED_UNICODE)); } }这个命令的方便之处在于不依赖任何登录态拿到一个真实设备 cid 就能即刻验证后端改动。推送结果直接显示在终端里返回的 taskId 可以复制到推送平台后台的任务列表里查询送达状态。我平时调厂商通道参数就是靠它改一次报文跑一次命令省掉前端打点的时间。验证“是否真的送达”还有一个习惯不要在收到 code 为 0 后就开始改下一个需求。等推送返回后我会等十秒再扫一遍 push_log主动查一下回执状态。如果回执状态迟迟不更新就去后台看 taskId 的详情确认是卡在厂商通道还是已经确认送达。这种看起来很麻烦的流程其实能救你于上线第二天的“推送失灵”事故。从那以后我每次接入新版本 SDK 或换打包证书都会强制走一遍这个命令测试再重查一次回执确认回执状态里展示的是厂商通道的成功回执才敢把改动提交。希望这套链路和排查思路帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表