PHP项目PHP项目如何对接推送服务

wen PHP项目 28

本文目录导读:

PHP项目PHP项目如何对接推送服务

  1. 常见推送服务类型
  2. PHP对接实现方案
  3. 推送服务管理类
  4. 最佳实践建议

我来详细介绍PHP项目对接推送服务的方法,涵盖常见推送服务类型和具体实现方案。

常见推送服务类型

第三方推送服务

  • 极光推送 - 国内主流
  • 个推 - 覆盖广
  • 友盟+ - 阿里旗下
  • 信鸽/腾讯移动推送 - 腾讯系

平台原生推送

  • APNs - Apple Push Notification service
  • FCM - Firebase Cloud Messaging

实时通信推送

  • WebSocket
  • SSE (Server-Sent Events)
  • 长轮询

PHP对接实现方案

使用第三方推送服务SDK

极光推送示例

// 1. 安装SDK
composer require jpush/jpush
// 2. 基础推送代码
<?php
require_once 'vendor/autoload.php';
use JPush\Client;
class PushService {
    private $client;
    public function __construct() {
        $appKey = 'your_app_key';
        $masterSecret = 'your_master_secret';
        $this->client = new Client($appKey, $masterSecret);
    }
    // 推送通知
    public function pushNotification($title, $content, $audience = 'all') {
        try {
            $push = $this->client->push();
            $push->setPlatform('all')
                 ->addAllAudience()
                 ->iosNotification($content, [
                     'title' => $title,
                     'sound' => 'default',
                     'badge' => '+1',
                     'extras' => ['key' => 'value']
                 ])
                 ->androidNotification($title, [
                     'title' => $title,
                     'builder_id' => 1,
                     'extras' => ['key' => 'value']
                 ])
                 ->message($content, [
                     'title' => $title,
                     'content_type' => 'text',
                     'extras' => ['key' => 'value']
                 ]);
            $response = $push->send();
            return $response;
        } catch (\JPush\Exceptions\APIConnectionException $e) {
            // 网络连接错误
            error_log('Push connection error: ' . $e->getMessage());
            return false;
        } catch (\JPush\Exceptions\APIRequestException $e) {
            // API请求错误
            error_log('Push API error: ' . $e->getMessage());
            return false;
        }
    }
    // 按标签推送
    public function pushByTags($tags, $title, $content) {
        $push = $this->client->push();
        $push->setPlatform('all')
             ->addTag($tags)
             ->iosNotification($content, ['title' => $title])
             ->androidNotification($title, ['title' => $title]);
        return $push->send();
    }
    // 推送统计
    public function getPushReport($pushId) {
        try {
            $report = $this->client->report();
            return $report->getReceived($pushId);
        } catch (\Exception $e) {
            return false;
        }
    }
}
// 使用示例
$push = new PushService();
$result = $push->pushNotification('新消息', '您有一条新通知');

个推推送示例

<?php
require_once 'vendor/autoload.php';
use Getui\IGtPush;
use Getui\IGtNotification;
use Getui\IGtSingleMessage;
class GetuiPushService {
    private $push;
    private $appId;
    private $appKey;
    private $masterSecret;
    public function __construct() {
        $this->appId = 'your_app_id';
        $this->appKey = 'your_app_key';
        $this->masterSecret = 'your_master_secret';
        $host = 'https://api.getui.com';
        $this->push = new IGtPush($host, $this->appKey, $this->masterSecret);
    }
    // 单推
    public function pushToSingle($cid, $title, $content) {
        $template = $this->createNotificationTemplate($title, $content);
        $message = new IGtSingleMessage();
        $message->setData($template);
        $message->setOffline(true);
        $message->setOfflineExpireTime(24 * 3600 * 1000); // 离线有效期
        return $this->push->pushMessageToSingle($message, $cid);
    }
    // 群推
    public function pushToList($cids, $title, $content) {
        $template = $this->createNotificationTemplate($title, $content);
        $message = new IGtListMessage();
        $message->setData($template);
        $message->setOffline(true);
        $contentId = $this->push->getContentId($message, 'toList_task');
        return $this->push->pushMessageToList($contentId, $cids);
    }
    private function createNotificationTemplate($title, $content) {
        $template = new IGtNotification();
        $template->setAppId($this->appId);
        $template->setAppKey($this->appKey);
        $template->setTitle($title);
        $template->setText($content);
        $template->setLogo('push.png');
        $template->setLogoUrl('');
        $template->setIsRing(true);
        $template->setIsVibrate(true);
        $template->setIsClearable(true);
        $template->setTransmissionType(2);
        $template->setTransmissionContent(json_encode([
            'type' => 'message',
            'data' => $content
        ]));
        return $template;
    }
}

使用cURL直接调用API

<?php
class PushApiClient {
    private $apiUrl;
    private $appKey;
    private $appSecret;
    public function __construct($config) {
        $this->apiUrl = $config['api_url'];
        $this->appKey = $config['app_key'];
        $this->appSecret = $config['app_secret'];
    }
    // 通用API请求
    private function request($endpoint, $data = []) {
        $url = $this->apiUrl . $endpoint;
        $headers = [
            'Content-Type: application/json',
            'Authorization: ' . $this->getAuthToken()
        ];
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($httpCode == 200) {
            return json_decode($response, true);
        }
        return false;
    }
    private function getAuthToken() {
        // 实现token获取逻辑
        return base64_encode($this->appKey . ':' . $this->appSecret);
    }
    // 发送推送
    public function sendPush($title, $content, $deviceTokens = []) {
        $data = [
            'title' => $title,
            'content' => $content,
            'device_tokens' => $deviceTokens,
            'platform' => 'all'
        ];
        return $this->request('/push/send', $data);
    }
}

WebSocket推送

<?php
// 使用 Ratchet 库实现 WebSocket 服务器
// 安装: composer require cboden/ratchet
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class PushWebSocket implements MessageComponentInterface {
    protected $clients;
    protected $userConnections; // 用户ID -> 连接映射
    public function __construct() {
        $this->clients = new \SplObjectStorage;
        $this->userConnections = [];
    }
    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        echo "New connection: {$conn->resourceId}\n";
    }
    public function onMessage(ConnectionInterface $from, $msg) {
        $data = json_decode($msg, true);
        switch ($data['type']) {
            case 'auth':
                // 用户认证,绑定用户ID
                $userId = $data['user_id'];
                $this->userConnections[$userId] = $from;
                $from->userId = $userId;
                break;
            case 'subscribe':
                // 订阅特定频道
                $channel = $data['channel'];
                $from->channels[] = $channel;
                break;
        }
    }
    // 向特定用户推送
    public function pushToUser($userId, $data) {
        if (isset($this->userConnections[$userId])) {
            $conn = $this->userConnections[$userId];
            $conn->send(json_encode($data));
        }
    }
    // 广播消息
    public function broadcast($data) {
        foreach ($this->clients as $client) {
            $client->send(json_encode($data));
        }
    }
    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
        // 清理用户连接
        if (isset($conn->userId)) {
            unset($this->userConnections[$conn->userId]);
        }
        echo "Connection {$conn->resourceId} disconnected\n";
    }
    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "Error: {$e->getMessage()}\n";
        $conn->close();
    }
}
// WebSocket 服务启动脚本
// server.php
require 'vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\EventLoop\Factory;
$loop = Factory::create();
$pusher = new PushWebSocket();
// WebSocket服务器
$webSock = new React\Socket\Server('0.0.0.0:8080', $loop);
$webServer = new IoServer(
    new HttpServer(
        new WsServer(
            $pusher
        )
    ),
    $webSock
);
// HTTP API服务器(用于内部推送调用)
$httpServer = new React\Http\Server($loop, function ($request, $response) use ($pusher) {
    $body = $request->getBody();
    $data = json_decode($body, true);
    if ($data && isset($data['user_id'])) {
        $pusher->pushToUser($data['user_id'], $data['message']);
        $response->writeHead(200, ['Content-Type' => 'application/json']);
        $response->end(json_encode(['status' => 'success']));
    } else {
        $response->writeHead(400);
        $response->end('Bad Request');
    }
});
$httpServer->listen('127.0.0.1:8081');
echo "WebSocket server started on port 8080\n";
echo "Internal API server started on port 8081\n";
$loop->run();
// PHP客户端调用推送
// push_client.php
function sendInternalPush($userId, $message) {
    $ch = curl_init('http://127.0.0.1:8081');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'user_id' => $userId,
        'message' => $message
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_exec($ch);
    curl_close($ch);
}

SSE (Server-Sent Events) 推送

<?php
// server_sse.php - SSE推送服务端
class SSEServer {
    public function handle() {
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('Connection: keep-alive');
        header('Access-Control-Allow-Origin: *');
        // 获取用户ID(通过token验证)
        $userId = $this->authenticate();
        // 创建数据库连接检查新消息
        $db = new PDO('mysql:host=localhost;dbname=push', 'user', 'pass');
        $lastCheck = time();
        while (true) {
            // 检查是否有新消息
            $stmt = $db->prepare("SELECT * FROM messages WHERE user_id = ? AND created_at > ?");
            $stmt->execute([$userId, date('Y-m-d H:i:s', $lastCheck)]);
            $messages = $stmt->fetchAll();
            foreach ($messages as $msg) {
                // 发送SSE事件
                echo "event: message\n";
                echo "id: {$msg['id']}\n";
                echo "data: " . json_encode($msg) . "\n\n";
                ob_flush();
                flush();
            }
            $lastCheck = time();
            // 发送心跳
            echo ": heartbeat\n\n";
            ob_flush();
            flush();
            // 检查客户端是否断开
            if (connection_aborted()) {
                break;
            }
            sleep(1); // 间隔1秒检查
        }
    }
    private function authenticate() {
        // 实现身份验证逻辑
        return 1; // 返回用户ID
    }
}
// index.php - 入口
$sse = new SSEServer();
$sse->handle();
// 客户端JavaScript
/*
const eventSource = new EventSource('/sse/server_sse.php');
eventSource.addEventListener('message', function(e) {
    const data = JSON.parse(e.data);
    console.log('New message:', data);
});
eventSource.onerror = function(e) {
    console.error('SSE error:', e);
    eventSource.close();
};
*/

推送服务管理类

<?php
class PushManager {
    private $config;
    private $providers = [];
    public function __construct($config) {
        $this->config = $config;
        $this->initProviders();
    }
    private function initProviders() {
        // 初始化不同推送服务商
        if (isset($this->config['jiguang'])) {
            $this->providers['jiguang'] = new JiguangPush($this->config['jiguang']);
        }
        if (isset($this->config['getui'])) {
            $this->providers['getui'] = new GetuiPush($this->config['getui']);
        }
        if (isset($this->config['websocket'])) {
            $this->providers['websocket'] = new WebSocketPush($this->config['websocket']);
        }
    }
    // 智能推送:根据用户设备选择最优通道
    public function smartPush($userId, $title, $content, $options = []) {
        $results = [];
        // 获取用户设备信息
        $devices = $this->getUserDevices($userId);
        foreach ($devices as $device) {
            switch ($device['platform']) {
                case 'ios':
                    // iOS设备使用APNs
                    $results[] = $this->pushApns($device['token'], $title, $content);
                    break;
                case 'android':
                    // Android设备使用FCM或第三方
                    $results[] = $this->pushFcm($device['token'], $title, $content);
                    break;
                case 'web':
                    // Web端使用WebSocket或SSE
                    $results[] = $this->pushWebSocket($userId, $title, $content);
                    break;
            }
        }
        // 记录推送日志
        $this->logPush($userId, $title, $content, $results);
        return $results;
    }
    // 消息队列处理(异步推送)
    public function pushToQueue($userId, $title, $content) {
        // 使用Redis队列
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $pushData = [
            'user_id' => $userId,
            'title' => $title,
            'content' => $content,
            'created_at' => time()
        ];
        $redis->lPush('push_queue', json_encode($pushData));
        return true;
    }
    // 处理队列中的推送
    public function processQueue() {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        while ($data = $redis->rPop('push_queue')) {
            $pushData = json_decode($data, true);
            // 处理推送
            $this->smartPush(
                $pushData['user_id'],
                $pushData['title'],
                $pushData['content']
            );
            // 避免CPU占用过高
            usleep(100000); // 0.1秒
        }
    }
    // 推送失败重试机制
    public function retryFailedPush($pushId) {
        $failedPushes = $this->getFailedPushes();
        foreach ($failedPushes as $push) {
            if ($push['retry_count'] < 3) {
                $result = $this->smartPush(
                    $push['user_id'],
                    $push['title'],
                    $push['content']
                );
                if ($result) {
                    $this->markPushSuccess($push['id']);
                } else {
                    $this->incrementRetryCount($push['id']);
                }
            }
        }
    }
    private function getUserDevices($userId) {
        // 从数据库获取用户设备信息
        $db = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
        $stmt = $db->prepare("SELECT * FROM user_devices WHERE user_id = ?");
        $stmt->execute([$userId]);
        return $stmt->fetchAll();
    }
    private function logPush($userId, $title, $content, $results) {
        // 记录推送日志到数据库
        $db = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
        $stmt = $db->prepare("
            INSERT INTO push_logs (user_id, title, content, results, created_at) 
            VALUES (?, ?, ?, ?, NOW())
        ");
        $stmt->execute([$userId, $title, $content, json_encode($results)]);
    }
}

最佳实践建议

错误处理

try {
    $result = $pushService->pushNotification($title, $content);
} catch (Exception $e) {
    // 记录错误日志
    Log::error('Push failed: ' . $e->getMessage());
    // 放入重试队列
    Queue::push('RetryPush', [
        'title' => $title,
        'content' => $content,
        'error' => $e->getMessage()
    ]);
}

速度控制

// 使用令牌桶算法控制推送速率
class RateLimiter {
    private $redis;
    private $maxTokens;
    private $refillRate;
    public function __construct($maxTokens = 100, $refillRate = 10) {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
        $this->maxTokens = $maxTokens;
        $this->refillRate = $refillRate;
    }
    public function acquire($key) {
        $tokens = $this->redis->get("rate_limit:$key");
        if ($tokens === false) {
            $this->redis->set("rate_limit:$key", $this->maxTokens - 1);
            $this->redis->expire("rate_limit:$key", 1);
            return true;
        }
        if ($tokens > 0) {
            $this->redis->decr("rate_limit:$key");
            return true;
        }
        return false;
    }
}

推送模板管理

class PushTemplate {
    private $templates = [];
    public function __construct() {
        $this->loadTemplates();
    }
    public function render($templateName, $data = []) {
        if (!isset($this->templates[$templateName])) {
            throw new Exception("Template not found: $templateName");
        }
        $template = $this->templates[$templateName];
        // 替换变量
        foreach ($data as $key => $value) {
            $template['title'] = str_replace("{{$key}}", $value, $template['title']);
            $template['content'] = str_replace("{{$key}}", $value, $template['content']);
        }
        return $template;
    }
    private function loadTemplates() {
        // 从数据库或配置文件加载模板
        $this->templates = [
            'order_notify' => [
                'title' => '订单通知',
                'content' => '您的订单{{order_no}}状态已更新为{{status}}'
            ],
            'new_message' => [
                'title' => '新消息',
                'content' => '您收到来自{{sender}}的新消息'
            ]
        ];
    }
}

配置文件示例

// config/push.php
return [
    'default' => 'jiguang',
    'providers' => [
        'jiguang' => [
            'app_key' => env('JPUSH_APP_KEY'),
            'master_secret' => env('JPUSH_MASTER_SECRET'),
            'apns_production' => env('APP_ENV') === 'production'
        ],
        'getui' => [
            'app_id' => env('GETUI_APP_ID'),
            'app_key' => env('GETUI_APP_KEY'),
            'master_secret' => env('GETUI_MASTER_SECRET')
        ],
        'apns' => [
            'certificate' => storage_path('apns-cert.pem'),
            'passphrase' => env('APNS_PASSPHRASE'),
            'sandbox' => env('APP_ENV') !== 'production'
        ],
        'fcm' => [
            'server_key' => env('FCM_SERVER_KEY'),
            'sender_id' => env('FCM_SENDER_ID')
        ]
    ],
    'rate_limit' => [
        'max_per_second' => 100,
        'max_per_minute' => 5000
    ],
    'retry' => [
        'max_attempts' => 3,
        'delay' => 300 // 5分钟
    ]
];

选择推送方案时,建议根据项目规模、用户量、实时性要求和预算来综合考虑,对于大多数PHP项目,使用第三方推送服务是最快速可靠的方案。

抱歉,评论功能暂时关闭!