flash-express / php-push-sdk
FlashExpress Push Message SDK - 支持多场景 push 消息发送、站内信、设备 token 获取,通过 JSON-RPC 调用 ard-api / by_rpc
Requires
- php: >=7.2
- ext-json: *
- guzzlehttp/guzzle: ^6.5 || ^7.0
Requires (Dev)
- phpunit/phpunit: ^8.0 || ^9.0 || ^10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
FlashExpress 推送消息 PHP SDK,支持单条/批量推送、站内信发送、设备 Token 获取,通过 JSON-RPC 调用 ard-api / by_rpc 接口。
环境要求
- PHP >= 7.2
- ext-curl
- ext-json
- ext-openssl
安装
composer require flash-express/php-push-sdk:^1.0
快速开始
初始化
<?php
use FlashExpress\Push\Client;
$client = new Client([
// 必填: ard-api SVC JSON-RPC 入口地址 (例如 /svc/call)
'ard_api_endpoint' => 'http://ard-api.svc/svc/call',
// 可选: by_rpc 地址 (用于获取待审批数量等)
'by_rpc_endpoint' => 'http://by-rpc.svc',
// 必填: push.* 接口签名鉴权凭据 (ard-api SignatureAuthMiddleware 强制校验)
'app_id' => 'your_app_id',
'app_key' => 'your_app_key',
// 可选: 语言环境, 默认 zh-CN
'locale' => 'zh-CN',
// 可选: HTTP 超时 (秒), 默认 10
'timeout' => 10,
// 可选: RPC 失败重试次数, 默认 0 (不重试)
'retry_times' => 0,
// 可选: 重试间隔 (毫秒), 默认 200
'retry_delay' => 200,
]);
API 接口
1. 单条推送 (按工号/客户 ID)
向单个指定用户发送 push 消息,SDK 自动获取设备 Token。
$response = $client->pushToStaff([
'src' => Client::SRC_BACKYARD, // 来源系统: c | ka | kit | backyard | osm
'staff_info_id' => '100001', // 工号或客户 ID
'message_title' => '您有新的审批待处理',
'message_content' => '请及时前往审批中心处理',
'message_scheme' => 'app://approval/detail', // 可选: 点击跳转 Scheme
'message_priority' => 0, // 可选: 0 普通 | 1 优先
'silence' => 0, // 可选: 0 有声音 | 1 静默推送
'thumbnail' => 'https://xxx.com/icon.png', // 可选: 图片推送 (iOS 富推送 / Android 图文)
]);
if ($response->isSuccess()) {
echo $response->getMessage();
} else {
echo $response->getMessage();
}
来源系统常量:
| 常量 | 值 | 说明 |
|---|---|---|
Client::SRC_C | 'c' | C端用户 |
Client::SRC_KA | 'ka' | KA客户 |
Client::SRC_KIT | 'kit' | KIT (快递员手持客户端) |
Client::SRC_BACKYARD | 'backyard' | OA (员工端) |
Client::SRC_OSM | 'osm' | OSM (外协员工管理客户端) |
Client::SRC_PAY | 'flashpay' | FlashPay (支付客户端) |
2. 批量推送 (按工号/客户 ID 列表)
一次性向最多 100 个用户推送消息。外层 message_* 字段作为公共参数,list 内每个 item 可单独覆盖(内层优先,缺失字段自动回落到外层)。
SDK 内部自动检测"统一内容"模式:当 list 中每个 item 只含 staff_info_id 和 src 两个字段时,说明消息内容一致,会按 os + src + device_type 分组合并 device_token 为数组,一条队列消息最多携带 100 个 token,大幅减少 MQ 写入量。
情形一: 统一内容 (推荐, 消息内容一致时使用)
$response = $client->pushToStaffs([
// 外层公共参数: 所有用户共享同一消息内容
'message_title' => '团队通知',
'message_content' => '今日例会时间调整为 15:00',
'message_scheme' => 'app://meeting',
'message_priority' => 0,
'src' => Client::SRC_BACKYARD
// 每个 item 只需 staff_info_id + src, 消息内容全部取自外层
// SDK 会自动合并同 os+src+device_type 的设备 token, 一条消息最多 100 个
'list' => [
['staff_info_id' => '100001'],
['staff_info_id' => '100002'],
['staff_info_id' => '100003'],
// ... 最多 100 个
],
]);
if ($response->isSuccess()) {
$data = $response->getData();
echo "总计: {$data['total']}, 成功: {$data['success']}, 失败: {$data['fail']}";
}
情形二: 非统一内容 (每个用户消息不同)
$response = $client->pushToStaffs([
// 外层公共参数 (被 list 内层 item 覆盖, 内层缺失的字段从此处取)
'message_title' => '团队通知',
'message_content' => '今日例会时间调整为 15:00',
'message_scheme' => 'app://meeting',
'message_priority' => 0,
// 每个 item 可携带额外字段覆盖外层, 消息内容各不相同
'list' => [
['staff_info_id' => '100001', 'src' => Client::SRC_BACKYARD],
['staff_info_id' => '100002', 'src' => Client::SRC_BACKYARD],
[
'staff_info_id' => '100003',
'src' => Client::SRC_KA,
'message_title' => '单独标题', // 仅覆盖此条的 title, 其他继续取外层
'message_content' => '单独内容',
],
],
]);
if ($response->isSuccess()) {
$data = $response->getData();
echo "总计: {$data['total']}, 成功: {$data['success']}, 失败: {$data['fail']}";
}
3. 直接设备 Token 推送
跳过设备 Token 查询,直接推送。适用于调用方已有设备 Token 的场景。
SDK 内部自动检测"统一内容"模式:当 list 中每个 item 只含 os、device_token、device_type、src(或其子集)时,说明消息内容一致,会按 os + src + device_type 分组合并 device_token 为数组,一条队列消息最多携带 100 个 token。
情形一: 统一内容 (推荐, 消息内容一致时使用)
$response = $client->pushToDevices([
// 外层公共参数: 所有设备共享同一消息内容
'message_title' => '新订单来了',
'message_content' => '点击查看详情',
'message_scheme' => 'app://order',
'message_priority' => 0,
'src' => Client::SRC_OSM
// 每个 item 只需 os/device_token/device_type/src, 消息内容全部取自外层
// SDK 会自动合并同 os+src+device_type 的设备 token, 一条消息最多 100 个
'list' => [
['os' => 'android', 'device_token' => 'token1', 'device_type' => Client::DEVICE_TYPE_GOOGLE],
['os' => 'android', 'device_token' => 'token2', 'device_type' => Client::DEVICE_TYPE_HUAWEI],
['os' => 'ios', 'device_token' => 'token3', 'device_type' => Client::DEVICE_TYPE_APPLE],
// ... 最多 100 个
],
]);
if ($response->isSuccess()) {
$data = $response->getData();
echo "总计: {$data['total']}, 成功: {$data['success']}, 失败: {$data['fail']}";
}
情形二: 非统一内容 (每个设备消息不同)
$response = $client->pushToDevices([
// 外层公共参数 (被 list 内层 item 覆盖, 内层缺失的字段从此处取)
'message_priority' => 0,
// 每个 item 可携带额外字段覆盖外层, 消息内容各不相同
'list' => [
[
'os' => 'android',
'device_token' => 'APA91b...',
'device_type' => Client::DEVICE_TYPE_GOOGLE,
'staff_info_id' => '100001',
'src' => Client::SRC_OSM,
'message_title' => '新订单来了',
'message_content' => '点击查看详情',
'message_scheme' => 'app://order',
'badge' => 0,
],
[
'os' => 'ios',
'device_token' => 'abc123...',
'device_type' => Client::DEVICE_TYPE_APPLE,
'staff_info_id' => '100002',
'src' => Client::SRC_KIT,
'message_title' => '配送提醒',
'message_content' => '您有新的配送任务',
'message_scheme' => 'app://delivery',
],
],
]);
if ($response->isSuccess()) {
$data = $response->getData();
echo "总计: {$data['total']}, 成功: {$data['success']}, 失败: {$data['fail']}";
}
设备类型常量:
| 常量 | 值 |
|---|---|
Client::DEVICE_TYPE_GOOGLE | 1 |
Client::DEVICE_TYPE_HUAWEI | 2 |
响应格式
所有方法统一返回 FlashExpress\Push\Contracts\ResponseInterface 对象:
$response->isSuccess(); // code === 1
$response->isFail(); // code === 0
$response->isError(); // code === -1
$response->getCode(); // 1=成功 | 0=业务失败 | -1=系统/参数错误
$response->getMessage(); // 消息文本
$response->getData(); // 业务数据 (mixed)
$response->getRaw(); // 原始 JSON-RPC 响应 (string)
$response->toArray(); // 转数组 ['code'=>1, 'msg'=>'ok', 'data'=>...]
异常处理
SDK 不会主动抛出异常,所有错误都通过 Response::error() / Response::fail() 返回。
如需捕获底层异常,可 catch 以下类型:
| 异常类 | 触发场景 |
|---|---|
FlashExpress\Push\Exceptions\ValidationException | 配置项缺失 / 参数校验失败 |
FlashExpress\Push\Exceptions\RpcException | JSON-RPC 调用异常 |
FlashExpress\Push\Exceptions\PushException | SDK 通用异常 (基类) |
use FlashExpress\Push\Exceptions\RpcException;
use FlashExpress\Push\Exceptions\ValidationException;
try {
$response = $client->pushToStaff([...]);
} catch (ValidationException $e) {
echo '参数错误: ' . $e->getMessage();
echo '详细: ' . json_encode($e->getErrors());
} catch (RpcException $e) {
echo 'RPC 调用失败: ' . $e->getMessage();
}
鉴权配置
ard-api 的 push.* 开头的 JSON-RPC 接口已强制开启签名鉴权,由 SignatureAuthMiddleware 在 SVC Server 层统一拦截校验。SDK 自动完成签名计算并附加到每个请求,调用方只需传入凭据。
范围说明:仅对 JSON-RPC 方法名以
push.开头的接口生效(如push.sendToMQ、push.getDeviceToken、push.sendInternalMessage),其他 SVC 方法(如 by_rpc 的get_panding_count)不受影响,无需签名。
申请凭据
在 ard-api 侧为调用方分配一对 app_id / app_key,服务端通过以下方式之一验证:
- 推荐(生产):环境变量
PUSH_SDK_APPKEY_{APP_ID}(app_id 转大写) - 开发调试:
SignatureAuthMiddleware::APP_SECRETS常量数组
完整调用示例
use FlashExpress\Push\Client;
$client = new Client([
'ard_api_endpoint' => 'http://ard-api.svc/svc/call',
'app_id' => 'your_app_id', // 必填
'app_key' => 'your_app_key', // 必填
'locale' => 'zh-CN',
'timeout' => 10,
]);
$response = $client->pushToStaff([
'src' => Client::SRC_BACKYARD,
'staff_info_id' => '100001',
'message_title' => '您有新的审批待处理',
'message_content' => '请及时前往审批中心处理',
]);
if ($response->isSuccess()) {
echo "推送成功\n";
} else {
echo "推送失败: " . $response->getMessage() . "\n";
}
SDK 自动附加的 HTTP Header
所有对 push.* 方法的调用会自动附带以下 Header,调用方无需手动设置:
| Header | 说明 |
|---|---|
X-App-Id | 应用 ID |
X-Timestamp | 秒级时间戳,服务端 ±5 分钟 有效期 |
X-Nonce | 32 位随机十六进制字符串 |
X-Signature | 签名值 |
签名算法(与服务端 SignatureAuthMiddleware 一致)
stringToSign = X-Timestamp + X-Nonce + json_encode(params, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)
X-Signature = hash_hmac('sha256', stringToSign, app_key)
注意:签名仅对 JSON-RPC 请求体的
params字段计算,不包含jsonrpc/id/method等其他顶层字段。
常见鉴权失败
| 服务端返回 | 原因 | 排查方向 |
|---|---|---|
缺少必要 Header | SDK 未传 app_id / app_key 或值为空 | 确认初始化配置已传凭据 |
无效的 App ID | app_id 在服务端未注册 | 检查 PUSH_SDK_APPKEY_{APP_ID} 环境变量或常量 |
时间戳已过期 | 服务器时间与客户端差超过 5 分钟 | 检查服务器/客户端 NTP 同步 |
签名不匹配 | 签名计算逻辑不一致 | 确认 SDK 版本与服务端 SignatureAuthMiddleware 一致 |
调试技巧
开启 debug 模式可在日志中看到每次请求的完整 Header、Payload 和响应:
$client->setDebug(true);
// 或直接在 config 里传 'debug' => true
$response = $client->pushToStaff([...]);
// 最近一次请求的完整调试上下文
$debug = $client->getDebugInfo();
echo json_encode($debug, JSON_PRETTY_PRINT);
// 包含: endpoint / method / payload / headers / http_code / curl_error / response / duration_ms
请求超时设置
默认超时时间在初始化时通过 timeout 配置项设定(秒,默认 10)。如需在运行时临时调整,可调用 setTimeout(),对后续所有 RPC 调用立即生效。
// 初始化时设定默认超时
$client = new Client([
'ard_api_endpoint' => 'http://ard-api.svc/svc/call',
'app_id' => 'your_app_id',
'app_key' => 'your_app_key',
'timeout' => 10, // 默认 10 秒
]);
// 批量推送可能耗时较长, 临时调大超时
$client->setTimeout(30);
$response = $client->pushToStaffs([...]);
// 复位为默认值
$client->setTimeout(10);
// 支持链式调用
$response = $client->setTimeout(30)->pushToStaffs([...]);
架构说明
┌──────────────┐ JSON-RPC ┌──────────────┐
│ Push SDK │ ───────────▶ │ ard-api │
│ (Client) │ sendToMQ │ PushController
└──────┬───────┘ getDeviceToken └──────────────┘
│
│ JSON-RPC
▼
┌──────────────┐
│ by_rpc │
│ (待审批数) │
└──────────────┘
SDK 通过 push.* 方法名调用 ard-api SVC 接口,通过 get_panding_count 调用 by_rpc。
License
MIT