dootask/app/Module/AppPush/DooPushClient.php
kuaifan a49ee608a5 feat(push): 新增 DooPush 推送通道,与友盟并存
- 新增 app_push_devices 表与 api/push/device 接口(新版 App 上报/删除 DooPush 设备)
- app/Module/AppPush 统一分发友盟与 DooPush,业务字段经 payload.data 下发
- 系统设置「App 推送」新增 DooPush 配置;点击通知打开对应会话
- 更新 Expo 壳子模块(接入 DooPush)、语言包与 ai-kb
2026-10-19 10:33:19 +00:00

108 lines
5.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Module\AppPush;
use App\Models\AppPushDevice;
use App\Models\WebSocketDialogMsgRead;
use App\Module\Base;
use App\Module\Ihttp;
use Carbon\Carbon;
class DooPushClient
{
public function pushToUsers(array|int $userids, array $msg): void
{
try {
$setting = Base::setting('appPushSetting');
$appId = trim((string)($setting['doopush_app_id'] ?? ''));
$secret = trim((string)($setting['doopush_app_secret'] ?? ''));
if (($setting['doopush'] ?? 'close') !== 'open' || $appId === '' || $secret === '') {
return;
}
$content = str_replace(["\r\n", "\r", "\n"], '', $msg['body'] ?? '');
$title = trim(str_replace(["\r\n", "\r", "\n"], '', $msg['title'] ?? ''));
$title = $title === '' ? trim($content) : $title;
if ($title === '' || trim($content) === '') {
return;
}
$extra = [];
foreach (is_array($msg['extra'] ?? null) ? $msg['extra'] : [] as $key => $value) {
$extra[$key] = is_scalar($value) || $value === null ? (string)$value : Base::array2json($value);
}
// DooPush 只接受 action/url/data 与厂商参数,自定义字段须以 JSON 字符串放在 data(客户端解析)。
$payload = array_merge($extra ? ['data' => Base::array2json($extra)] : [], [
'huawei' => ['importance' => 'NORMAL', 'category' => 'IM'],
'honor' => ['importance' => 'NORMAL'],
'oppo' => ['category' => 'IM'],
// DooPush 的 vivo 通道使用 classification,而非 category。
'vivo' => ['classification' => 1],
]);
$baseUrl = rtrim(trim((string)($setting['doopush_base_url'] ?? '')), '/') ?: 'https://doopush.com/api/v1';
$url = $baseUrl . '/apps/' . rawurlencode($appId) . '/push';
$headers = ['Authorization' => 'Bearer ' . $secret, 'Content-Type' => 'application/json'];
$userids = array_unique(array_map('intval', (array)$userids));
foreach ($userids as $userid) {
if ($userid <= 0) {
continue;
}
try {
// 按用户单独取最新5台,避免跨查询分块时重复发送或突破设备数限制。
$deviceIds = AppPushDevice::whereChannel('doopush')->whereUserid($userid)
->where('updated_at', '>', Carbon::now()->subMonth())
->orderByDesc('updated_at')->orderByDesc('id')->limit(5)
->pluck('device_id')
// API 要求整数 ID,避免超长数字强转后意外指向另一台设备。
->map(fn ($id) => filter_var(ltrim($id, '0'), FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]))
->filter(fn ($id) => $id !== false)->values()->all();
if (!$deviceIds) {
continue;
}
$badge = WebSocketDialogMsgRead::whereUserid($userid)->whereSilence(0)->whereReadAt(null)->count();
$this->send($url, $headers, [
'title' => mb_substr($title, 0, 200),
'content' => $content,
'badge' => $badge,
'payload' => $payload,
'target' => ['type' => 'devices', 'device_ids' => $deviceIds],
], $userid);
} catch (\Throwable $e) {
info('[DooPush] user push failed: ' . $e->getMessage(), ['userid' => $userid]);
}
}
} catch (\Throwable $e) {
info('[DooPush] push failed: ' . $e->getMessage());
}
}
private function send(string $url, array $headers, array $body, int $userid): void
{
// 首次发送 + 最多2次重试;不在长生命周期进程中保存待发送状态。
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
$response = $this->request($url, $headers, $body);
$result = Base::json2array($response['data'] ?? '');
$status = (int)($response['msg'] ?? 0);
if (Base::isSuccess($response) && $status >= 200 && $status < 300 && ($result['code'] ?? 0) === 200) {
return;
}
info('[DooPush] send failed', [
'userid' => $userid,
'attempt' => $attempt,
'status' => $status,
'message' => mb_substr((string)($result['message'] ?? $response['msg'] ?? 'Invalid response'), 0, 500),
]);
} catch (\Throwable $e) {
info('[DooPush] send failed: ' . $e->getMessage(), ['userid' => $userid, 'attempt' => $attempt]);
}
}
}
protected function request(string $url, array $headers, array $body): array
{
return Ihttp::ihttp_request($url, json_encode($body, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), array_merge($headers, [
'CURLOPT_SSL_VERIFYPEER' => true,
'CURLOPT_SSL_VERIFYHOST' => 2,
]), 10);
}
}