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

微信公眾號開發:消息回覆類完整實現與最佳實踐

     編輯:PHP 綜合知識

前言:微信開發的核心——訊息處理

微信公眾平台開發的核心在於**接收使用者訊息、回覆響應訊息**。官方文檔定義了 XML 格式的訊息結構,涵蓋文字、圖片、語音、視頻、圖文、音樂等多種類型。本文提供一個功能完整、結構清晰的 BBCweixin 回覆類,支援所有主流訊息類型,並封裝了素材管理、選單操作、客服訊息、OAuth 授權等高階功能。

核心回覆方法:XML 模板 + sprintf

所有回覆均遵循微信 XML 協定,採用 sprintf 填充模板,確保格式正確、CDATA 區塊完整。

1. 文字訊息回覆

<?php
public function resText($object, $content, $flag = 0) {
    $xml = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[%s]]></Content>
        <FuncFlag>%d</FuncFlag>
    </xml>";
    $result = sprintf($xml,
        $object->FromUserName,
        $object->ToUserName,
        time(),
        $content,
        $flag
    );
    echo $result; exit();
}
?>

2. 圖片訊息回覆

<?php
public function resImage($object, $media_id) {
    $xml = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[image]]></MsgType>
        <Image><MediaId><![CDATA[%s]]></MediaId></Image>
    </xml>";
    $result = sprintf($xml, $object->FromUserName, $object->ToUserName, time(), $media_id);
    echo $result; exit();
}
?>

3. 圖文訊息回覆 (支援多條,最多 10 條)

<?php
public function resNews($object, $newsData = []) {
    $createTime = time();
    $funcFlag = 0;
    $itemCount = min(count($newsData), 10); // 微信限制最多 10 條
    
    $header = "<xml>
        <ToUserName><![CDATA[{$object->FromUserName}]]></ToUserName>
        <FromUserName><![CDATA[{$object->ToUserName}]]></FromUserName>
        <CreateTime>$createTime</CreateTime>
        <MsgType><![CDATA[news]]></MsgType>
        <ArticleCount>$itemCount</ArticleCount>
        <Articles>";
    
    $itemTpl = "<item>
        <Title><![CDATA[%s]]></Title>
        <Description><![CDATA[%s]]></Description>
        <PicUrl><![CDATA[%s]]></PicUrl>
        <Url><![CDATA[%s]]></Url>
    </item>";
    
    $content = '';
    foreach (array_slice($newsData, 0, 10) as $item) {
        $content .= sprintf($itemTpl,
            $item['Title'],
            $item['Description'],
            $item['PicUrl'],
            $item['Url']
        );
    }
    
    $footer = "</Articles>
        <FuncFlag>$funcFlag</FuncFlag>
    </xml>";
    
    echo $header . $content . $footer; exit();
}
?>

4. 音樂訊息回覆

<?php
public function resMusic($object, $musicContent = []) {
    $xml = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[music]]></MsgType>
        <Music>
            <Title><![CDATA[%s]]></Title>
            <Description><![CDATA[%s]]></Description>
            <MusicUrl><![CDATA[%s]]></MusicUrl>
            <HQMusicUrl><![CDATA[%s]]></HQMusicUrl>
        </Music>
    </xml>";
    
    // 若無高品質連結,預設用普通連結
    if (empty($musicContent[0]['HQMusicUrl'])) {
        $musicContent[0]['HQMusicUrl'] = $musicContent[0]['MusicUrl'];
    }
    
    $result = sprintf($xml,
        $object->FromUserName, $object->ToUserName, time(),
        $musicContent[0]['Title'],
        $musicContent[0]['Description'],
        $musicContent[0]['MusicUrl'],
        $musicContent[0]['HQMusicUrl']
    );
    echo $result; exit();
}
?>

進階功能模組

素材上傳 (永久/臨時)

<?php
public function uploadMedia($accessToken, $type = 'image', $mediaArr) {
    // $mediaArr = ['media' => new CURLFile('/path/to/file.jpg')]
    $url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=$accessToken&type=$type";
    return $this->curlPost($mediaArr, $url);
}
?>

GPS/Google 座標轉百度座標

<?php
public function mapApi($lng, $lat, $type) {
    $map = [];
    if ($type === 'gps') {
        // 使用第三方轉換 API
        $url = "https://map.yanue.net/gpsApi.php?lat=$lat&lng=$lng";
        $res = json_decode(file_get_contents($url));
        $map['lng'] = $res->baidu->lng;
        $map['lat'] = $res->baidu->lat;
    } elseif ($type === 'google') {
        $url = "https://api.map.baidu.com/ag/coord/convert?from=2&to=4&mode=1&x=$lng&y=$lat";
        $res = json_decode(file_get_contents($url));
        $map['lng'] = base64_decode($res[0]->x);
        $map['lat'] = base64_decode($res[0]->y);
    }
    return $map;
}
?>

JSON 中文不轉義編碼

<?php
public function arrayRecursive(&$array, $function, $apply_to_keys_also = false) {
    static $recursive_counter = 0;
    if (++$recursive_counter > 1000) die('possible deep recursion attack');
    foreach ($array as $key => $value) {
        if (is_array($value)) {
            $this->arrayRecursive($array[$key], $function, $apply_to_keys_also);
        } else {
            $array[$key] = $function($value);
        }
        if ($apply_to_keys_also && is_string($key)) {
            $new_key = $function($key);
            if ($new_key != $key) {
                $array[$new_key] = $array[$key];
                unset($array[$key]);
            }
        }
    }
    $recursive_counter--;
}

public function JSON($array) {
    $this->arrayRecursive($array, 'urlencode', true);
    $json = json_encode($array);
    return urldecode($json);
}
?>

自定義選單建立

<?php
public function creatMenu($shop_id, $data) {
    $jsonArray = $this->JSON($data);
    $accessToken = $this->accessToken($weiXin[0]['key'], $weiXin[0]['secret']); // 需調整獲取邏輯
    $url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=$accessToken";
    return $this->curlPost($jsonArray, $url);
}
?>

客服訊息發送

<?php
public function customService($jsonArray, $hash) {
    if (empty($jsonArray)) return false;
    // 從資料庫獲取該公眾號憑證
    $db = M(); // ThinkPHP 模型,需根據實際框架調整
    $sql = "SELECT * FROM bbc_wechats WHERE hash='$hash'";
    $weChats = $db->query($sql);
    $accessToken = $this->accessToken($weChats[0]['key'], $weChats[0]['secret']);
    $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=$accessToken";
    return $this->curlPost($jsonArray, $url);
}
?>

Access Token 管理 (快取 1 小時)

<?php
public function accessToken($appid, $secret) {
    $cacheKey = 'accesstoken' . $appid;
    $access_token = BBCcache::getCache($cacheKey); // 需自行實作快取類
    if ($access_token) return $access_token;
    
    $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$secret";
    $res = @file_get_contents($url);
    $obj = json_decode($res);
    $accessToken = $obj->access_token;
    BBCcache::setCache($cacheKey, $accessToken, 3600); // 快取 1 小時
    return $accessToken;
}
?>

通用 cURL POST 封裝

<?php
public function curlPost($data, $url) {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_CUSTOMREQUEST  => 'POST',
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)',
        CURLOPT_FOLLOWLOCATION => 1,
        CURLOPT_AUTOREFERER    => 1,
        CURLOPT_POSTFIELDS     => $data,
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $info = curl_exec($ch);
    if (curl_errno($ch)) {
        error_log('cURL Error: ' . curl_error($ch));
    }
    curl_close($ch);
    return json_decode($info);
}
?>

經緯度距離計算 (Haversine 公式)

<?php
function getRadian($d) { return $d * M_PI / 180; }

function getDistance($lat1, $lng1, $lat2, $lng2) {
    $R = 6378.137; // 地球半徑 km
    $lat1 = $this->getRadian($lat1);
    $lat2 = $this->getRadian($lat2);
    $a = $lat1 - $lat2;
    $b = $this->getRadian($lng1) - $this->getRadian($lng2);
    $v = 2 * asin(sqrt(pow(sin($a/2), 2) + cos($lat1) * cos($lat2) * pow(sin($b/2), 2)));
    return round($R * $v * 10000) / 10000; // 回傳 km,精度 0.1m
}
?>

完整使用流程範例

<?php
// 入口檔案 (如 index.php)
require 'BBCweixin.class.php';

$weixin = new BBCweixin();

// 1. 驗證簽名 (首次接入驗證)
// $weixin->valid();

// 2. 獲取微信推送的 XML 訊息
$postStr = file_get_contents('php://input');
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);

// 3. 根據訊息類型分發處理
switch ($postObj->MsgType) {
    case 'text':
        $keyword = trim($postObj->Content);
        if ($keyword === '圖文') {
            $news = [
                ['Title' => '標題1', 'Description' => '描述1', 'PicUrl' => 'https://xxx.com/1.jpg', 'Url' => 'https://xxx.com/1'],
                ['Title' => '標題2', 'Description' => '描述2', 'PicUrl' => 'https://xxx.com/2.jpg', 'Url' => 'https://xxx.com/2'],
            ];
            $weixin->resNews($postObj, $news);
        } elseif ($keyword === '音樂') {
            $music = [['Title'=>'歌名','Description'=>'歌手','MusicUrl'=>'https://xxx.com/m.mp3','HQMusicUrl'=>'']];
            $weixin->resMusic($postObj, $music);
        } else {
            $weixin->resText($postObj, "收到文字:$keyword");
        }
        break;
    case 'event':
        if ($postObj->Event === 'subscribe') {
            $weixin->resText($postObj, "歡迎關注!回復『圖文』或『音樂』體驗。");
        }
        break;
    case 'image':
        // 回覆收到的圖片
        $weixin->resImage($postObj, $postObj->MediaId);
        break;
}
?>

安全與最佳實踐提醒

  • Token 驗證:入口必須實作 valid() 方法驗證 signature、timestamp、nonce,防止偽造請求。
  • HTTPS 強制:所有 API 請求必須走 HTTPS,生產環境切勿關閉 SSL 驗證 (開發測試除外)。
  • Access Token 快取:全域唯一、有效期 7200 秒,**必須集中快取**,避免重複請求導致封禁。
  • CDATA 區塊:所有 XML 內容字段**必須**包裹在 `` 中,防止特殊字元破壞 XML 結構。
  • 被動回覆時效:微信要求 5 秒內回覆,耗時操作 (如 AI 處理) 應改用**客服訊息**主動發送。
  • 錯誤日誌:所有 API 請求結果、異常情況完整記錄日誌,便於排查。

總結

這個 BBCweixin 類封裝了微信公眾號開發的**核心回覆協定、素材管理、選單操作、客服訊息、OAuth 授權、地理位置轉換、距離計算**等高頻功能。採用 **XML 模板 + sprintf** 確保協定合規,**cURL 封裝**統一網路請求,**Access Token 快取**避免頻率限制。實際專案中建議:

  1. 將快取層 (BBCcache) 替換為 Redis/Memcached 實現。
  2. 資料庫操作抽離為 Repository 模式,解耦框架依賴。
  3. 引入 PSR-4 自動載入、Composer 依賴管理。
  4. 擴充事件處理器模式,支援插件式業務邏輯擴充。

掌握此類核心封裝,即能快速構建穩定、可維護的微信公眾號應用系統。

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