首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

Typecho 插件開發教程(六):API 呼叫與 HTTP 客戶端整合

     編輯:PHP 綜合知識

插件架構回顧

前幾章已完成:插件啟用/停用、配置儲存、掛載發布文章鉤子、取得文章永久連結。本章實作核心邏輯:呼叫百度站長平台 API 推送 URL。

方案一:原生 cURL 實作

<?php
class BaiduSubmit_Plugin implements Typecho_Plugin_Interface {
    // ... activate, deactivate, config 等方法 ...
    
    /** 發送推送請求 */
    public static function sendUrls(array $urls, array $options) {
        $api = $options->plugin('BaiduSubmit')->api;
        if (empty($api)) return false;
        
        $ch = curl_init($api);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POSTFIELDS => implode("\n", $urls),
            CURLOPT_HTTPHEADER => ['Content-Type: text/plain'],
            CURLOPT_TIMEOUT => 10,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);
        
        $response = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($errno) {
            self::log("cURL 錯誤 [$errno]: $error");
            return false;
        }
        
        self::log("API 回應: $response");
        return json_decode($response, true);
    }
    
    private static function log($message) {
        $logFile = __DIR__ . '/../logs/baidu_submit.log';
        @file_put_contents($logFile, date('Y-m-d H:i:s') . " $message\n", FILE_APPEND);
    }
}
?>

方案二:Typecho 內建 HTTP 客戶端(推薦)

<?php
use Typecho\Http\Client;

class BaiduSubmit_Plugin implements Typecho_Plugin_Interface {
    // ...
    
    public static function sendUrls(array $urls, $options) {
        $api = $options->plugin('BaiduSubmit')->api;
        if (empty($api)) return false;
        
        // 檢查 HTTP 客戶端可用性
        if (false === Client::get()) {
            throw new Typecho_Plugin_Exception(
                '主機不支援 php-curl 且未開啟 allow_url_fopen,無法使用此功能'
            );
        }
        
        $http = Client::get();
        $http->setData(implode("\n", $urls));
        $http->setHeader('Content-Type', 'text/plain');
        
        try {
            $response = $http->send($api);
            self::log("API 回應: $response");
            return json_decode($response, true);
        } catch (Exception $e) {
            self::log("請求異常: " . $e->getMessage());
            return false;
        }
    }
    
    private static function log($message) {
        // 建議使用 Typecho 內建日誌或資料庫記錄
        $logFile = Typecho_Common::config()->rootDir . '/usr/logs/baidu_submit.log';
        @file_put_contents($logFile, date('c') . " $message\n", FILE_APPEND);
    }
}
?>

完整發布鉤子處理流程

<?php
// 在 Plugin.php 中的 finishPublish 鉤子回調
public static function onPublish($contents, $widget) {
    // 1. 檢查文章狀態:僅發布狀態且非預約發文
    if ($contents['visibility'] !== 'publish' || $contents['created'] > time()) {
        return;
    }
    
    // 2. 取得系統配置
    $options = Helper::options();
    $pluginConfig = $options->plugin('BaiduSubmit');
    if (empty($pluginConfig->api)) {
        return; // 未配置 API
    }
    
    // 3. 產生文章永久連結
    $type = $contents['type']; // 'post' 或 'page'
    $routeExists = (NULL !== Typecho_Router::get($type));
    $pathInfo = $routeExists ? Typecho_Router::url($type, $contents) : '#';
    $permalink = Typecho_Common::url($pathInfo, $options->index);
    
    // 4. 非同步發送(避免阻塞發布流程)
    self::asyncSend([$permalink], $pluginConfig);
}

private static function asyncSend(array $urls, $config) {
    // 方案 A: fastcgi_finish_request() 後執行 (PHP-FPM)
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request(); // 先回應使用者
        self::sendUrls($urls, $config); // 背景執行
        return;
    }
    
    // 方案 B: 背景進程 (需 shell_exec 權限)
    $script = __DIR__ . '/sender.php';
    $data = escapeshellarg(json_encode(['urls' => $urls, 'api' => $config->api]));
    shell_exec("php $script $data > /dev/null 2>&1 &");
    
    // 方案 C: 佇列系統 (Redis/RabbitMQ/Database) - 生產環境推薦
    // Queue::push(new BaiduSubmitJob($urls, $config->api));
}
?>

sender.php 背景執行腳本

<?php
// sender.php - 獨立執行,不載入 Typecho 核心
$data = json_decode($argv[1], true);
$urls = $data['urls'];
$api = $data['api'];

$ch = curl_init($api);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => implode("\n", $urls),
    CURLOPT_HTTPHEADER => ['Content-Type: text/plain'],
    CURLOPT_TIMEOUT => 30,
]);

$response = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);

// 記錄結果到資料庫或檔案
$log = [
    'time' => date('c'),
    'urls' => $urls,
    'status' => $errno ? 'failed' : 'success',
    'response' => $errno ? curl_error($ch) : $response,
];
// 寫入 SQLite/MySQL/檔案...
?>

錯誤處理與重試策略

private static function sendWithRetry(array $urls, $api, $maxRetries = 3) {
    $delay = 1; // 秒
    for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
        $result = self::sendUrls($urls, $api);
        if ($result && isset($result['success']) && $result['success'] > 0) {
            return $result; // 成功
        }
        
        self::log("第 $attempt 次嘗試失敗,{$delay}秒後重試...");
        sleep($delay);
        $delay *= 2; // 指數退避
    }
    
    self::log("重試 $maxRetries 次均失敗,記錄待人工處理");
    // 寫入失敗佇列供後台重試
    return false;
}

API 回應格式說明

{
  "remain": 4999,    // 當日剩餘配額
  "success": 1,      // 成功推送數量
  "error": 0,        // 失敗數量
  "message": "success" // 狀態訊息
}

建議記錄 remain 監控配額用量,接近限制時發出警示。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved