Search by

flash-express / php-push-sdk

githubfisher

FlashExpress Push Message SDK - 支持多场景 push 消息发送、站内信、设备 token 获取,通过 JSON-RPC 调用 ard-api / by_rpc

Package info

gitee.com/hifisher/php-push-sdk.git

pkg:composer/flash-express/php-push-sdk

Statistics

Installs: 62

Dependents: 0

Suggesters: 0

v1.4.1 2026-10-16 08:08 UTC

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.4.1

各版本新增 / 调整的功能见 CHANGELOG.md

快速开始

初始化

<?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',

    // 可选: 签名开关, 默认 false (关闭)
    // 关闭时不校验 app_id / app_key, 也不生成签名
    // 开启时 app_id 与 app_key 必填, 缺任一则初始化直接报错
    'signature_enabled' => true,

    // 条件必填: push.* 接口签名鉴权凭据 (签名开关开启时必填)
    'app_id'  => 'your_app_id',
    'app_key' => 'your_app_key',

    // 可选: 语言环境, 默认 zh-CN
    'locale' => 'zh-CN',

    // 可选: HTTP 超时 (秒), 默认 5
    'timeout' => 5,

    // 可选: RPC 失败重试次数, 默认 0 (不重试)
    'retry_times' => 0,

    // 可选: 重试间隔 (毫秒), 默认 200
    'retry_delay' => 200,

    // 可选: 性能追踪开关, 默认 false (关闭时不采集任何指标)
    'trace_enabled' => false,
]);

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_GOOGLE1
Client::DEVICE_TYPE_HUAWEI2

响应格式

所有方法统一返回 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\RpcExceptionJSON-RPC 调用异常
FlashExpress\Push\Exceptions\PushExceptionSDK 通用异常 (基类)
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)不受影响,无需签名。

签名开关

由配置项 signature_enabled 控制,默认关闭:

signature_enabledapp_id / app_key行为
false(默认)不校验不生成签名,请求不带任何签名 Header
true两个都不为空生成签名并附加到请求
true任一为空初始化即抛出 ValidationException,不会继续发请求

开启签名但凭据缺失时会在 new Client([...]) 阶段直接报错,错误信息与 getErrors() 会指明缺失的字段,避免带着无效配置发起注定失败的请求。

也可以在运行时切换(用法同 setDebug(),支持链式调用):

$client->setSignatureEnabled(true);   // 开启; 凭据缺失会抛 ValidationException 且不改变当前状态
$client->setSignatureEnabled(false);  // 关闭; 后续请求不再带签名头

申请凭据

在 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',
    'signature_enabled' => true,              // 开启签名
    'app_id'            => 'your_app_id',     // 开启签名时必填
    'app_key'           => 'your_app_key',    // 开启签名时必填
    'locale'            => 'zh-CN',
    'timeout'           => 5,
]);

$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:

Header说明
X-App-Id应用 ID
X-Timestamp秒级时间戳,服务端 ±5 分钟 有效期
X-Nonce32 位随机十六进制字符串
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签名开关未开启,或开启了但未传 app_id / app_key开启 signature_enabled 并确认凭据非空(开启时凭据缺失会在 SDK 初始化阶段直接报错)
无效的 App IDapp_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

性能追踪

debug 解决「请求发生了什么」,性能追踪解决「时间花在哪里」。通过配置项 trace_enabled(默认关闭)或运行时 setTraceEnabled() 开启,开启后每次业务调用会记录一条链路:root span(业务方法)+ 每次 RPC 尝试 + 本地构建阶段。

$client = new Client([
    'ard_api_endpoint' => 'http://ard-api.svc/svc/call',
    'trace_enabled'    => true,   // 默认 false
]);

// 或运行时开启 (用法同 setDebug, 支持链式调用)
$client->setTraceEnabled(true)->pushToStaffs([...]);

$trace = $client->getTraceInfo();

getTraceInfo() 返回结构:

[
    'total_ms' => 12.34,                    // 本次调用总耗时
    'spans'    => [                         // 按开始顺序, depth 表示层级
        ['name' => 'pushToStaffs',  'depth' => 0, 'duration_ms' => 12.34, 'meta' => []],
        ['name' => 'rpc:push.batchGetDeviceToken', 'depth' => 1, 'duration_ms' => 9.80,
         'meta' => ['method' => 'push.batchGetDeviceToken', 'attempt' => 1, 'status' => 'ok']],
        ['name' => 'build_messages', 'depth' => 1, 'duration_ms' => 0.07, 'meta' => []],
        ['name' => 'rpc:push.sendToMQ', 'depth' => 1, 'duration_ms' => 2.40,
         'meta' => ['method' => 'push.sendToMQ', 'attempt' => 1, 'status' => 'ok']],
    ],
    'summary'  => [                         // 按 span 名称聚合, 便于看重复 RPC 的累积耗时
        'rpc:push.getPendingCount' => ['count' => 3, 'total_ms' => 90.0, 'max_ms' => 40.0],
    ],
]

要点:

  • 未开启时不创建 Tracer,所有打点直接跳过,不产生采集开销
  • 每次 RPC 尝试单独一个 span:meta.attempt 区分第几次尝试,meta.status 为 ok / error,meta.retryable 标记是否可重试 —— 能分辨「一次慢请求」还是「多次重试累积慢」
  • root span 开在业务方法层,所以能看到「卡在哪一环」,而不是只有单条 RPC
  • 若已通过 setLogger() 注入 logger,trace 开启时每个 span 还会写一条 [PushSDK][trace] 的结构化 JSON 日志(级别 info),便于接入日志聚合分析;未注入 logger 时只保留内存结果
  • getTraceInfo() 只保留最近一次调用,未开启追踪时返回 null

请求超时设置

默认超时时间在初始化时通过 timeout 配置项设定(秒,默认 5)。如需在运行时临时调整,可调用 setTimeout(),对后续所有 RPC 调用立即生效。

// 初始化时设定默认超时
$client = new Client([
    'ard_api_endpoint'  => 'http://ard-api.svc/svc/call',
    'signature_enabled' => true,
    'app_id'            => 'your_app_id',
    'app_key'           => 'your_app_key',
    'timeout'           => 5,  // 默认 5 秒
]);

// 批量推送可能耗时较长, 临时调大超时
$client->setTimeout(30);
$response = $client->pushToStaffs([...]);

// 复位为默认值
$client->setTimeout(5);

// 支持链式调用
$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