微信公眾平台開發的核心在於**接收使用者訊息、回覆響應訊息**。官方文檔定義了 XML 格式的訊息結構,涵蓋文字、圖片、語音、視頻、圖文、音樂等多種類型。本文提供一個功能完整、結構清晰的 BBCweixin 回覆類,支援所有主流訊息類型,並封裝了素材管理、選單操作、客服訊息、OAuth 授權等高階功能。
所有回覆均遵循微信 XML 協定,採用 sprintf 填充模板,確保格式正確、CDATA 區塊完整。
<?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();
}
?>
<?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();
}
?>
<?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();
}
?>
<?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);
}
?>
<?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;
}
?>
<?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);
}
?>
<?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;
}
?>
<?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);
}
?>
<?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;
}
?>
valid() 方法驗證 signature、timestamp、nonce,防止偽造請求。這個 BBCweixin 類封裝了微信公眾號開發的**核心回覆協定、素材管理、選單操作、客服訊息、OAuth 授權、地理位置轉換、距離計算**等高頻功能。採用 **XML 模板 + sprintf** 確保協定合規,**cURL 封裝**統一網路請求,**Access Token 快取**避免頻率限制。實際專案中建議:
BBCcache) 替換為 Redis/Memcached 實現。掌握此類核心封裝,即能快速構建穩定、可維護的微信公眾號應用系統。