含有4个类
Wechat 消息处理
Encrypt 消息加解密
Encodexml xml处理
WeixinEvent 返回数据处理


1 namespace app\common\logic\Wechat; 2 3 /** 4 * Class Weixin 获取服务器发送的消息并返回消息 5 * 6 * 包含 Encrypt AES加解密类 7 * 包含 Encodexml xml格式化类 8 */ 9 10 class Wechat 11 { 12 // 消息加解密类 13 protected $encrypt; 14 15 // 公众号 原始ID 16 protected $id = ‘‘; 17 // 令牌(Token) 18 protected $token = ‘‘; 19 // 开发者ID(AppID) 20 protected $appId = ‘‘; 21 // 开发者密码(AppSecret) 22 protected $appSecret = ‘‘; 23 // 消息加解密密钥(EncodingAESKey) 24 protected $encodingAESKey = ‘‘; 25 26 // 强制验证消息 27 protected $authMsg = true; 28 29 // 消息数据 30 protected $signature = ‘‘; 31 protected $msgSignature = ‘‘; 32 protected $echoStr = ‘‘; 33 protected $timeStamp = 0; 34 protected $nonce = ‘‘; 35 protected $encryptType = null; 36 37 // 请求服务器 38 protected static $server = ‘https://api.weixin.qq.com‘; 39 // 请求路径 40 protected static $serverpath = [ 41 ‘getAccessToken‘ => ‘/cgi-bin/token‘ 42 ]; 43 // 令牌缓存时间 44 protected static $expiresIn = 7200; 45 46 // 错误代码 47 protected static $errorCode = [ 48 -99999 => ‘未知错误‘, 49 50 0 => ‘成功‘, 51 -40001 => ‘签名验证错误‘, 52 -40002 => ‘xml解析失败‘, 53 -40003 => ‘sha加密生成签名失败‘, 54 -40004 => ‘encodingAesKey 非法‘, 55 -40005 => ‘appid 校验错误‘, 56 -40006 => ‘aes 加密失败‘, 57 -40007 => ‘aes 解密失败‘, 58 -40008 => ‘解密后得到的buffer非法‘, 59 -40009 => ‘base64加密失败‘, 60 -40010 => ‘base64解密失败‘, 61 -40011 => ‘生成xml失败‘, 62 63 -50001 => ‘消息验证数据不完整‘, 64 -50002 => ‘接口请求出错 返回无令牌数据‘, 65 -50003 => ‘连接到远程服务器错误‘, 66 -50004 => ‘获取 access_token(权限令牌) json解析错误‘, 67 -50005 => ‘待解密消息不完整‘, 68 -50006 => ‘数据验证签名错误‘, 69 -50007 => ‘xml 解析加密数据失败‘, 70 -50008 => ‘从 xml 获取信息出错‘, 71 72 -51001 => ‘微信返回消息 事件 错误‘, 73 -51002 => ‘微信返回消息 数据 错误‘, 74 75 -1 => ‘系统繁忙,此时请开发者稍候再试‘, 76 40001 => ‘获取 access_token 时 AppSecret 错误,或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的公众号调用接口‘, 77 40002 => ‘不合法的凭证类型‘, 78 40003 => ‘不合法的 OpenID ,请开发者确认 OpenID (该用户)是否已关注公众号,或是否是其他公众号的 OpenID‘, 79 40004 => ‘不合法的媒体文件类型‘, 80 40005 => ‘不合法的文件类型‘, 81 40006 => ‘不合法的文件大小‘, 82 40007 => ‘不合法的媒体文件 id‘, 83 40008 => ‘不合法的消息类型‘, 84 40009 => ‘不合法的图片文件大小‘, 85 40010 => ‘不合法的语音文件大小‘, 86 40011 => ‘不合法的视频文件大小‘, 87 40012 => ‘不合法的缩略图文件大小‘, 88 40013 => ‘不合法的 AppID ,请开发者检查 AppID 的正确性,避免异常字符,注意大小写‘, 89 40014 => ‘不合法的 access_token ,请开发者认真比对 access_token 的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口‘, 90 40015 => ‘不合法的菜单类型‘, 91 40016 => ‘不合法的按钮个数‘, 92 40017 => ‘不合法的按钮个数‘, 93 40018 => ‘不合法的按钮名字长度‘, 94 40019 => ‘不合法的按钮 KEY 长度‘, 95 40020 => ‘不合法的按钮 URL 长度‘, 96 40021 => ‘不合法的菜单版本号‘, 97 40022 => ‘不合法的子菜单级数‘, 98 40023 => ‘不合法的子菜单按钮个数‘, 99 40024 => ‘不合法的子菜单按钮类型‘,100 40025 => ‘不合法的子菜单按钮名字长度‘,101 40026 => ‘不合法的子菜单按钮 KEY 长度‘,102 40027 => ‘不合法的子菜单按钮 URL 长度‘,103 40028 => ‘不合法的自定义菜单使用用户‘,104 40029 => ‘不合法的 oauth_code‘,105 40030 => ‘不合法的 refresh_token‘,106 40031 => ‘不合法的 openid 列表‘,107 40032 => ‘不合法的 openid 列表长度‘,108 40033 => ‘不合法的请求字符,不能包含 \uxxxx 格式的字符‘,109 40035 => ‘不合法的参数‘,110 40038 => ‘不合法的请求格式‘,111 40039 => ‘不合法的 URL 长度‘,112 40050 => ‘不合法的分组 id‘,113 40051 => ‘分组名字不合法‘,114 40060 => ‘删除单篇图文时,指定的 article_idx 不合法‘,115 40117 => ‘分组名字不合法‘,116 40118 => ‘media_id 大小不合法‘,117 40119 => ‘button 类型错误‘,118 40120 => ‘button 类型错误‘,119 40121 => ‘不合法的 media_id 类型‘,120 40132 => ‘微信号不合法‘,121 40137 => ‘不支持的图片格式‘,122 40155 => ‘请勿添加其他公众号的主页链接‘,123 41001 => ‘缺少 access_token 参数‘,124 41002 => ‘缺少 appid 参数‘,125 41003 => ‘缺少 refresh_token 参数‘,126 41004 => ‘缺少 secret 参数‘,127 41005 => ‘缺少多媒体文件数据‘,128 41006 => ‘缺少 media_id 参数‘,129 41007 => ‘缺少子菜单数据‘,130 41008 => ‘缺少 oauth code‘,131 41009 => ‘缺少 openid‘,132 42001 => ‘access_token 超时,请检查 access_token 的有效期,请参考基础支持 - 获取 access_token 中,对 access_token 的详细机制说明‘,133 42002 => ‘refresh_token 超时‘,134 42003 => ‘oauth_code 超时‘,135 42007 => ‘用户修改微信密码, accesstoken 和 refreshtoken 失效,需要重新授权‘,136 43001 => ‘需要 GET 请求‘,137 43002 => ‘需要 POST 请求‘,138 43003 => ‘需要 HTTPS 请求‘,139 43004 => ‘需要接收者关注‘,140 43005 => ‘需要好友关系‘,141 43019 => ‘需要将接收者从黑名单中移除‘,142 44001 => ‘多媒体文件为空‘,143 44002 => ‘POST 的数据包为空‘,144 44003 => ‘图文消息内容为空‘,145 44004 => ‘文本消息内容为空‘,146 45001 => ‘多媒体文件大小超过限制‘,147 45002 => ‘消息内容超过限制‘,148 45003 => ‘标题字段超过限制‘,149 45004 => ‘描述字段超过限制‘,150 45005 => ‘链接字段超过限制‘,151 45006 => ‘图片链接字段超过限制‘,152 45007 => ‘语音播放时间超过限制‘,153 45008 => ‘图文消息超过限制‘,154 45009 => ‘接口调用超过限制‘,155 45010 => ‘创建菜单个数超过限制‘,156 45011 => ‘API 调用太频繁,请稍候再试‘,157 45015 => ‘回复时间超过限制‘,158 45016 => ‘系统分组,不允许修改‘,159 45017 => ‘分组名字过长‘,160 45018 => ‘分组数量超过上限‘,161 45047 => ‘客服接口下行条数超过上限‘,162 46001 => ‘不存在媒体数据‘,163 46002 => ‘不存在的菜单版本‘,164 46003 => ‘不存在的菜单数据‘,165 46004 => ‘不存在的用户‘,166 47001 => ‘解析 JSON/XML 内容错误‘,167 48001 => ‘api 功能未授权,请确认公众号已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限‘,168 48002 => ‘粉丝拒收消息(粉丝在公众号选项中,关闭了 “ 接收消息 ” )‘,169 48004 => ‘api 接口被封禁,请登录 mp.weixin.qq.com 查看详情‘,170 48005 => ‘api 禁止删除被自动回复和自定义菜单引用的素材‘,171 48006 => ‘api 禁止清零调用次数,因为清零次数达到上限‘,172 48008 => ‘没有该类型消息的发送权限‘,173 50001 => ‘用户未授权该 api‘,174 50002 => ‘用户受限,可能是违规后接口被封禁‘,175 61451 => ‘参数错误 (invalid parameter)‘,176 61452 => ‘无效客服账号 (invalid kf_account)‘,177 61453 => ‘客服帐号已存在 (kf_account exsited)‘,178 61454 => ‘客服帐号名长度超过限制 ( 仅允许 10 个英文字符,不包括 @ 及 @ 后的公众号的微信号 )(invalid kf_acount length)‘,179 61455 => ‘客服帐号名包含非法字符 ( 仅允许英文 + 数字 )(illegal character in kf_account)‘,180 61456 => ‘客服帐号个数超过限制 (10 个客服账号 )(kf_account count exceeded)‘,181 61457 => ‘无效头像文件类型 (invalid file type)‘,182 61450 => ‘系统错误 (system error)‘,183 61500 => ‘日期格式错误‘,184 65301 => ‘不存在此 menuid 对应的个性化菜单‘,185 65302 => ‘没有相应的用户‘,186 65303 => ‘没有默认菜单,不能创建个性化菜单‘,187 65304 => ‘MatchRule 信息为空‘,188 65305 => ‘个性化菜单数量受限‘,189 65306 => ‘不支持个性化菜单的帐号‘,190 65307 => ‘个性化菜单信息为空‘,191 65308 => ‘包含没有响应类型的 button‘,192 65309 => ‘个性化菜单开关处于关闭状态‘,193 65310 => ‘填写了省份或城市信息,国家信息不能为空‘,194 65311 => ‘填写了城市信息,省份信息不能为空‘,195 65312 => ‘不合法的国家信息‘,196 65313 => ‘不合法的省份信息‘,197 65314 => ‘不合法的城市信息‘,198 65316 => ‘该公众号的菜单设置了过多的域名外跳(最多跳转到 3 个域名的链接)‘,199 65317 => ‘不合法的 URL‘,200 9001001 => ‘POST 数据参数不合法‘,201 9001002 => ‘远端服务不可用‘,202 9001003 => ‘Ticket 不合法‘,203 9001004 => ‘获取摇周边用户信息失败‘,204 9001005 => ‘获取商户信息失败‘,205 9001006 => ‘获取 OpenID 失败‘,206 9001007 => ‘上传文件缺失‘,207 9001008 => ‘上传素材的文件类型不合法‘,208 9001009 => ‘上传素材的文件尺寸不合法‘,209 9001010 => ‘上传失败‘,210 9001020 => ‘帐号不合法‘,211 9001021 => ‘已有设备激活率低于 50% ,不能新增设备‘,212 9001022 => ‘设备申请数不合法,必须为大于 0 的数字‘,213 9001023 => ‘已存在审核中的设备 ID 申请‘,214 9001024 => ‘一次查询设备 ID 数量不能超过 50‘,215 9001025 => ‘设备 ID 不合法‘,216 9001026 => ‘页面 ID 不合法‘,217 9001027 => ‘页面参数不合法‘,218 9001028 => ‘一次删除页面 ID 数量不能超过 10‘,219 9001029 => ‘页面已应用在设备中,请先解除应用关系再删除‘,220 9001030 => ‘一次查询页面 ID 数量不能超过 50‘,221 9001031 => ‘时间区间不合法‘,222 9001032 => ‘保存设备与页面的绑定关系参数错误‘,223 9001033 => ‘门店 ID 不合法‘,224 9001034 => ‘设备备注信息过长‘,225 9001035 => ‘设备申请参数不合法‘,226 9001036 => ‘查询起始值 begin 不合法‘227 ];228 229 public $url;230 // 收到的 array数据231 public $receiveArray = [];232 // 微信发送消息的时间233 public $receiveTime = 0;234 // 收到的xml数据235 public $receiveMsg = null;236 // 收到的解密xml数据237 public $receiveEncryptMsg = null;238 // 发送的未加密xml数据239 public $sendEncryptMsg = null;240 // 发送的xml数据241 public $sendMsg = null;242 243 /**244 * 初始化类245 * @param array $user 公众号数据246 * id 原始ID,247 * token 令牌(Token),248 * appId 开发者ID(AppID),249 * appSecret 开发者密码(AppSecret),250 * encodingAESKey 消息加解密密钥(EncodingAESKey)251 * authMsg 验证消息252 */253 public function __construct ($user = [])254 {255 // 参数赋值256 isset($user[‘id‘]) && $this->id = $user[‘id‘];257 isset($user[‘token‘]) && $this->token = $user[‘token‘];258 isset($user[‘appId‘]) && $this->appId = $user[‘appId‘];259 isset($user[‘appSecret‘]) && $this->appSecret = $user[‘appSecret‘];260 isset($user[‘encodingAESKey‘]) && strlen($user[‘encodingAESKey‘]) == 43 && $this->encodingAESKey = $user[‘encodingAESKey‘];261 isset($user[‘authMsg‘]) && $this->authMsg = $user[‘authMsg‘];262 $this->timeStamp = time();263 264 // 处理类赋值265 // 加解密消息类266 $this->encrypt = new Encrypt($this->encodingAESKey);267 $this->encodeXml = new Encodexml();268 }269 270 /**271 * 获取信息272 * @param array $getData $_GET 数据273 * @param string $postData file_get_contents(‘php://input‘) 数据274 * @param \Closure $response 回调函数 生成返回消息 xml ,参数 (string $msgType 消息类型, array $msg 消息数据) 返回 Encodexml275 * @return mixed 错误代码string 和 消息array276 */277 public function getBackMsg ($getData, $postData, $response=null)278 {279 // 验证消息赋值280 isset($getData[‘signature‘]) && $this->signature = $getData[‘signature‘];281 isset($getData[‘msg_signature‘]) && $this->msgSignature = $getData[‘msg_signature‘];282 isset($getData[‘echostr‘]) && $this->echoStr = $getData[‘echostr‘];283 isset($getData[‘timestamp‘]) && $this->timeStamp = $getData[‘timestamp‘];284 isset($getData[‘nonce‘]) && $this->nonce = $getData[‘nonce‘];285 isset($getData[‘encrypt_type‘]) && $this->encryptType = $getData[‘encrypt_type‘];286 // 保存接收到的xml数据287 $this->receiveMsg = $postData;288 // 验证 get 消息289 $result = $this->checkSignature($this->signature);290 // 验证 get 消息失败291 if ($result[0] != 0) {292 return $result[0];293 }294 // 验证 post 消息 无post消息返回验证字符串295 if (!$postData) {296 return $result[1];297 }298 // 获取信息数据299 $result = $this->responseMsg($postData);300 // 返回错误信息301 if ($result[0]!==0) {302 return $result[0];303 }304 // 获取信息类型305 $msgtype = $this->getMsgType();306 // 生成返回 xml307 if ($response instanceof \Closure) {308 // 使用回调函数返回数据309 $result = $response($msgtype,$result[1]);310 } else {311 // 默认返回 success312 $result = $this->encodeXml->text(‘success‘);313 }314 // 返回错误信息315 if (!($result instanceof Encodexml)) {316 return $result[0];317 }318 // 加密 xml319 $result = $this->replyMsg($result);320 // 返回错误信息321 if ($result[0] !== 0) {322 return $result[0];323 }324 return $result[1];325 }326 327 /**328 * 获取 令牌329 * @return array 错误代码 和 令牌330 */331 public function getAccessToken ()332 {333 // 获取令牌缓存 可以使用 文件 或 数据库 来存取334 $result = cache(‘Weixin.access_token‘);335 if ($result) {336 return [0, $result];337 }338 339 // 获取请求 url340 $this->url = self::$server . self::$serverpath[‘getAccessToken‘];341 $this->url .= "?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}";342 343 // 发送 Get 请求344 $result = $this->send_data($this->url);345 // 排除连接错误346 if ($result[0] !== 0) {347 return [$result[0], null];348 }349 350 try {351 // json 数据转数组352 $result = json_decode($result, true);353 } catch (\Exception $e) {354 // 权限令牌json解析失败355 return [-50004, null];356 }357 358 if (isset($result[‘access_token‘])) {359 // 缓存令牌数据360 cache(‘Weixin.access_token‘, $result[‘access_token‘], self::$expiresIn);361 return [0, $result[‘access_token‘]];362 } else {363 // 接口请求出错 返回无令牌数据364 return [-50002, null];365 }366 }367 368 /**369 * 通过错误代码 获取错误信息370 * @param mixed $code 错误代码371 * @return string 返回错误代码 和 错误信息372 */373 public function getError ($code)374 {375 if (is_numeric($code) && isset(self::$errorCode[$code])) {376 return $code . ‘ [‘ . self::$errorCode[$code] . ‘]‘;377 } else {378 return "0 [成功]";379 }380 }381 382 /**383 * 回复微信消息384 * @param Encodexml $class 处理消息类385 * @return array 错误代码 和 返回信息386 */387 protected function replyMsg (Encodexml $class)388 {389 try {390 $xml = $class->buildXml($this->receiveArray[‘FromUserName‘], $this->receiveArray[‘ToUserName‘]);391 } catch (\Exception $e) {392 return [-50008, null];393 }394 if (!$this->encryptType) {395 // 待发送的数据396 $this->sendMsg = $xml;397 return [0, $xml];398 }399 // 待发送的未加密的 xml400 $this->sendEncryptMsg = $xml;401 // 加密xml402 $result = $this->encrypt->encrypt($xml, $this->appId);403 404 // 加密失败405 if ($result[0]!== 0) {406 return $result;407 }408 409 // 生成签名信息410 $nonce = $this->encrypt->getRandomStr();411 $timeStamp = time();412 $msgSignature = $this->getSha($timeStamp, $nonce, $result[1]);413 414 // 签名失败415 if ($msgSignature[0]!== 0) {416 return $msgSignature;417 }418 419 // 生成加密信息420 $xml = "<xml><Encrypt><![CDATA[";421 $xml .= $result[1];422 $xml .= "]]></Encrypt><MsgSignature>{$msgSignature[1]}</MsgSignature><TimeStamp>{$timeStamp}</TimeStamp><Nonce>{$nonce}</Nonce></xml>";423 // 待发送的 加密的 xml424 $this->sendMsg = $xml;425 return [0, $xml];426 }427 428 /**429 * 回复微信消息430 * @return mixed 获取消息类型431 */432 protected function getMsgType ()433 {434 if (!isset($this->receiveArray[‘MsgType‘])) {435 return false;436 }437 438 $MsgType = $this->receiveArray[‘MsgType‘];439 440 if ($MsgType != ‘event‘) {441 return $MsgType;442 }443 444 if (!isset($this->receiveArray[‘Event‘])) {445 return false;446 }447 448 return "{$MsgType}_{$this->receiveArray[‘Event‘]}";449 }450 451 /**452 * 验证消息安全性453 * @param string $encryptMsg 加密数据454 * @return array 错误代码 和 返回消息455 */456 protected function checkSignature ($signature, $encryptMsg = ‘‘)457 {458 // 不验证消息459 if (!$this->encryptType && $this->authMsg === false) {460 return [0, $this->echoStr];461 }462 // 验证消息完整信463 if (!($this->signature && $this->timeStamp && $this->nonce)) {464 // 消息数据不完整465 return [-50001, null];466 }467 // 获取签名结果468 $result = $this->getSha($this->timeStamp, $this->nonce, $encryptMsg);469 if ($result[0] != 0) {470 return $result;471 }472 // 对比签名结果473 if ($result[1] == $signature) {474 return [0, $this->echoStr];475 } else {476 if ($encryptMsg) {477 // 数据签名验证错误478 return [-50006, null];479 } else {480 // 消息签名验证错误481 return [-40001, null];482 }483 }484 }485 486 /**487 * 获取服务器发送的消息488 * @param string $postData 加密数据489 * @return array 错误代码 和 返回消息数组490 */491 protected function responseMsg ($postData)492 {493 // libxml_disable_entity_loader(true); // xml 安全认证494 // xml解析消息495 try {496 $xml = simplexml_load_string($postData, ‘SimpleXMLElement‘, LIBXML_NOCDATA);497 } catch (\Exception $e) {498 // xml 解析失败499 return [-40002, null];500 }501 // json 数据 转数组502 $msgArray = json_decode(json_encode($xml), true);503 // 不是加密消息 直接返回数据504 if (!$this->encryptType) {505 $this->receiveArray = $msgArray;506 return [0, $msgArray];507 }508 // 验证 待解密 数据509 try {510 $userName = $msgArray[‘ToUserName‘];511 $encrypt = $msgArray[‘Encrypt‘];512 } catch (\Exception $e) {513 // 待解密消息不完整514 return [-50005, null];515 }516 // 安全签名验证517 $sha1 = $this->checkSignature($this->msgSignature,$encrypt);518 if ($sha1[0]!==0) {519 return $sha1;520 }521 // 解密数据522 $decryptMsg = $this->encrypt->decrypt($encrypt, $this->appId);523 // 解密消息失败524 if ($decryptMsg[0] !== 0) {525 return [$decryptMsg[0], null];526 }527 // 保存解密后的xml数据528 $this->receiveEncryptMsg = $decryptMsg[1];529 // xml解析 加密消息530 try {531 $xml = simplexml_load_string($decryptMsg[1], ‘SimpleXMLElement‘, LIBXML_NOCDATA);532 } catch (\Exception $e) {533 // xml 解析加密数据失败534 return [-50007, null];535 }536 // json 数据 转数组537 $msgArray = json_decode(json_encode($xml), true);538 $msgArray[‘ToUserName‘] = $userName;539 $this->receiveTime = isset($msgArray[‘TimeStamp‘]) ? $msgArray[‘TimeStamp‘] : time();540 $this->receiveArray = $msgArray;541 return [0, $msgArray];542 }543 544 /**545 * 发送数据请求546 * @param string $url 微信服务器地址547 * @param string $type 发送方式 get post548 * @param string $data 发送数据549 * @return array 得到返回数据 错误代码和数据550 */551 protected function send_data ($url, $type = "get", $data = null)552 {553 //$header[] = "Content-type: text/xml"; //定义content-type为xml554 $header[] = "Accept-Charset: utf-8";555 // 初始化 Curl556 $ch = curl_init();557 // 判断服务器地址 是否 ssl558 $ssl = substr($url, 0, 8) == "https://" ? true : false;559 curl_setopt($ch, CURLOPT_URL, $url);560 // 修改 header561 curl_setopt($ch, CURLOPT_HTTPHEADER, $header);562 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);563 // 是否返回数据到变量564 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);565 If ($type == "post") { // post 模式566 curl_setopt($ch, CURLOPT_POST, true);567 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);568 }569 if ($ssl) { // ssl 模式570 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);571 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);572 }573 // 执行574 $returndata = curl_exec($ch);575 if (curl_errno($ch)) {576 curl_close($ch);577 // 连接到远程服务器错误578 return [-50003, null];579 } else {580 curl_close($ch);581 return [0, $returndata];582 }583 }584 585 /**586 * 用SHA1算法生成安全签名587 * @param string $token 票据588 * @param string $timestamp 时间戳589 * @param string $nonce 随机字符串590 * @param string $encrypt 密文消息591 * @return array 返回 安全签名结果 和 数据592 */593 protected function getSha ($timestamp, $nonce, $msg)594 {595 try {596 $array = [$this->token, $timestamp, $nonce, $msg];597 // 排序598 sort($array, SORT_STRING);599 $str = implode($array);600 // 返回签名结果601 return [0, sha1($str)];602 } catch (\Exception $e) {603 // sha加密生成签名失败604 return [-40003, null];605 }606 }607 608 609 }


1 namespace app\common\logic\Wechat; 2 3 /** 4 * Prpcrypt class 5 * 6 * 提供接收和推送给公众平台消息的加解密接口. 7 */ 8 class Encrypt 9 { 10 public $key; 11 public static $block_size = 32; 12 13 public function __construct($k) 14 { 15 $this->key = base64_decode($k . "="); 16 } 17 18 /** 19 * 对需要加密的明文进行填充补位 20 * @param string $text 需要进行填充补位操作的明文 21 * @return string 补齐明文字符串 22 */ 23 protected function encode($text) 24 { 25 $block_size = self::$block_size; 26 $text_length = strlen($text); 27 //计算需要填充的位数 28 $amount_to_pad = self::$block_size - ($text_length % self::$block_size); 29 if ($amount_to_pad == 0) { 30 $amount_to_pad = self::$block_size; 31 } 32 //获得补位所用的字符 33 $pad_chr = chr($amount_to_pad); 34 $tmp = ""; 35 for ($index = 0; $index < $amount_to_pad; $index++) { 36 $tmp .= $pad_chr; 37 } 38 return $text . $tmp; 39 } 40 41 /** 42 * 对解密后的明文进行补位删除 43 * @param string $text 解密后的明文 44 * @return string 删除填充补位后的明文 45 */ 46 protected function decode($text) 47 { 48 49 $pad = ord(substr($text, -1)); 50 if ($pad < 1 || $pad > 32) { 51 $pad = 0; 52 } 53 return substr($text, 0, (strlen($text) - $pad)); 54 } 55 56 /** 57 * 对明文进行加密 58 * @param string $text 需要加密的明文 59 * @return array 加密后的密文 60 */ 61 public function encrypt($text, $appid) 62 { 63 64 try { 65 //获得16位随机字符串,填充到明文之前 66 $random = $this->getRandomStr(); 67 $text = $random . pack("N", strlen($text)) . $text . $appid; 68 // 网络字节序 69 $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); 70 $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, ‘‘, MCRYPT_MODE_CBC, ‘‘); 71 $iv = substr($this->key, 0, 16); 72 //使用自定义的填充方式对明文进行补位填充 73 $text = $this->encode($text); 74 mcrypt_generic_init($module, $this->key, $iv); 75 //加密 76 $encrypted = mcrypt_generic($module, $text); 77 mcrypt_generic_deinit($module); 78 mcrypt_module_close($module); 79 80 //print(base64_encode($encrypted)); 81 //使用BASE64对加密后的字符串进行编码 82 return [0, base64_encode($encrypted)]; 83 } catch (\Exception $e) { 84 // aes 加密失败 85 return [-40006, null]; 86 } 87 } 88 89 /** 90 * 对密文进行解密 91 * @param string $encrypted 需要解密的密文 92 * @param string $appid appID 93 * @return array 解密得到的明文 94 */ 95 public function decrypt($encrypted, $appid) 96 { 97 98 try { 99 //使用BASE64对需要解密的字符串进行解码100 $ciphertext_dec = base64_decode($encrypted);101 $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, ‘‘, MCRYPT_MODE_CBC, ‘‘);102 $iv = substr($this->key, 0, 16);103 mcrypt_generic_init($module, $this->key, $iv);104 105 //解密106 $decrypted = mdecrypt_generic($module, $ciphertext_dec);107 mcrypt_generic_deinit($module);108 mcrypt_module_close($module);109 } catch (\Exception $e) {110 // aes 解密失败111 return [-40007, null];112 }113 114 115 try {116 //去除补位字符117 $result = $this->decode($decrypted);118 //去除16位随机字符串,网络字节序和AppId119 if (strlen($result) < 16) {120 // 解密后得到的buffer非法121 return [-40008, null];122 }123 $content = substr($result, 16, strlen($result));124 $len_list = unpack("N", substr($content, 0, 4));125 $xml_len = $len_list[1];126 $xml_content = substr($content, 4, $xml_len);127 $from_appid = substr($content, $xml_len + 4);128 } catch (\Exception $e) {129 // 解密后得到的buffer非法130 return [-40008, null];131 }132 if ($from_appid != $appid) {133 // appid 校验错误134 return [-40005, null];135 }136 return [0, $xml_content];137 138 }139 140 141 /**142 * 随机生成16位字符串143 * @return string 生成的字符串144 */145 public function getRandomStr()146 {147 148 $str = "";149 $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";150 $max = strlen($str_pol) - 1;151 for ($i = 0; $i < 16; $i++) {152 $str .= $str_pol[mt_rand(0, $max)];153 }154 return $str;155 }156 157 }
Encrypt


1 namespace app\common\logic\Wechat; 2 3 class Encodexml { 4 5 protected $xml=‘‘; 6 protected $articleCount = 0; 7 protected $articles; 8 9 protected static $shareFormat = "<ToUserName><![CDATA[%1\$s]]></ToUserName><FromUserName><![CDATA[%2\$s]]></FromUserName><CreateTime>%3\$d</CreateTime>";10 protected static $format = [11 ‘text‘ => "<MsgType><![CDATA[%1\$s]]></MsgType><Content><![CDATA[%2\$s]]></Content>",12 ‘image‘ => "<MsgType><![CDATA[%1\$s]]></MsgType><Image><MediaId><![CDATA[%2\$s]]></MediaId></Image>",13 ‘voice‘ => "<MsgType><![CDATA[%1\$s]]></MsgType><Voice><MediaId><![CDATA[%2\$s]]></MediaId></Voice>",14 ‘video‘ => "<MsgType><![CDATA[%1\$s]]></MsgType><Video><MediaId><![CDATA[%2\$s]]></MediaId><Title><![CDATA[%3\$s]]></Title><Description><![CDATA[%4\$s]]></Description></Video>",15 ‘music‘ => "<MsgType><![CDATA[%1\$s]]></MsgType><Music><Title><![CDATA[%2\$s]]></Title><Description><![CDATA[%3\$s]]></Description><MusicUrl><![CDATA[%4\$s]]></MusicUrl><HQMusicUrl><![CDATA[%5\$s]]></HQMusicUrl><ThumbMediaId><![CDATA[%6\$s]]></ThumbMediaId></Music>",16 ‘news‘ => "<MsgType><![CDATA[%1\$s]]></MsgType><ArticleCount>%2\$d</ArticleCount><Articles>%3\$s</Articles>",17 ‘article‘ => "<item><Title><![CDATA[%1\$s]]></Title> <Description><![CDATA[%2\$s]]></Description><PicUrl><![CDATA[%3\$s]]></PicUrl><Url><![CDATA[%4\$s]]></Url></item>"18 ];19 20 public function text ($content) {21 $this->xml = sprintf(self::$format[‘text‘], ‘text‘, $content);22 return $this;23 }24 25 public function image ($MediaId) {26 $this->xml = sprintf(self::$format[‘image‘], ‘image‘, $MediaId);27 return $this;28 }29 30 public function voice ($MediaId) {31 $this->xml = sprintf(self::$format[‘voice‘], ‘voice‘, $MediaId);32 return $this;33 }34 35 public function video ($MediaId, $Title, $Description) {36 $this->xml = sprintf(self::$format[‘video‘], ‘video‘, $MediaId, $Title, $Description);37 return $this;38 }39 40 public function music ($ThumbMediaId, $Title=‘‘, $Description=‘‘, $MusicUrl=‘‘, $HQMusicUrl=‘‘) {41 $this->xml = sprintf(self::$format[‘music‘], ‘music‘, $Title, $Description, $MusicUrl, $HQMusicUrl, $ThumbMediaId);42 return $this;43 }44 45 public function articleAdd ($Title, $Description, $PicUrl, $Url)46 {47 $this->articleCount +=1;48 $this->articles .= sprintf(self::$format[‘article‘], $Title, $Description, $PicUrl, $Url);49 50 51 $this->xml = sprintf(self::$format[‘news‘], ‘news‘, $this->articleCount, $this->articles);52 return $this;53 }54 55 public function buildXml($ToUserName, $FromUserName) {56 if (!$this->xml) {57 return "success";58 }59 $xml = $this->xml;60 $this->xml = ‘‘;61 $this->articleCount = 0;62 $this->articles = ‘‘;63 $share = sprintf(self::$shareFormat, $ToUserName, $FromUserName, time());64 return "<xml>{$share}{$xml}</xml>";65 }66 }
Encodexml


1 namespace app\common\logic\Wechat; 2 3 class WeixinEvent { 4 5 protected $ToUserName; 6 protected $FromUserName; 7 protected $CreateTime; 8 9 protected $encodexml; 10 11 public function __construct () 12 { 13 $this->encodexml = new Encodexml(); 14 } 15 16 public function __call ($name, $arguments) 17 { 18 if ( isset(array_flip(get_class_methods($this))[$name] )) { 19 // 处理微信消息事件 后返回 20 try { 21 $this->ToUserName = $arguments[0][‘ToUserName‘]; 22 $this->FromUserName = $arguments[0][‘FromUserName‘]; 23 $this->CreateTime = $arguments[0][‘CreateTime‘]; 24 } catch (\Exception $e) { 25 return [-51002, null]; 26 } 27 return call_user_func_array([$this, $name], $arguments); 28 } else { 29 return [-51001, null]; 30 } 31 } 32 33 // 处理文字消息 34 protected function text($param) { 35 // 文本消息 36 $Content = $param[‘Content‘]; 37 // 消息ID 38 $MsgId = $param[‘MsgId‘]; 39 40 41 $result = $this->encodexml->text(‘欢迎!‘); 42 return $result; 43 } 44 45 // 处理图片消息 46 protected function image($param) { 47 // 图片链接 48 $PicUrl = $param[‘PicUrl‘]; 49 // 图片消息媒体id 50 $MediaId = $param[‘MediaId‘]; 51 // 消息ID 52 $MsgId = $param[‘MsgId‘]; 53 54 $result = $this->encodexml->text(‘success‘); 55 return $result; 56 } 57 58 // 处理语音消息 59 protected function voice($param) { 60 // 语音消息媒体id 61 $MediaId = $param[‘MediaId‘]; 62 // 语音格式 63 $Format = $param[‘Format‘]; 64 // 消息ID 65 $MsgId = $param[‘MsgId‘]; 66 // 语音识别结果 67 $Recognition = isset($param[‘Recognition‘]) ? $param[‘Recognition‘] : null; 68 69 $result = $this->encodexml->text(‘success‘); 70 return $result; 71 } 72 73 // 处理视频消息 74 protected function video($param) { 75 // 视频消息媒体id 76 $MediaId = $param[‘MediaId‘]; 77 // 视频缩略图id 78 $ThumbMediaId = $param[‘ThumbMediaId‘]; 79 // 消息ID 80 $MsgId = $param[‘MsgId‘]; 81 82 $result = $this->encodexml->text(‘success‘); 83 return $result; 84 } 85 86 // 处理小视频消息 87 protected function shortvideo($param) { 88 // 小视频消息媒体id 89 $MediaId = $param[‘MediaId‘]; 90 // 视频缩略图id 91 $ThumbMediaId = $param[‘ThumbMediaId‘]; 92 // 消息ID 93 $MsgId = $param[‘MsgId‘]; 94 95 $result = $this->encodexml->text(‘success‘); 96 return $result; 97 } 98 99 // 处理地理位置消息100 protected function location($param) {101 // 地理位置维度102 $Location_X = $param[‘Location_X‘];103 // 地理位置经度104 $Location_Y = $param[‘Location_Y‘];105 // 地图缩放大小106 $Scale = $param[‘Scale‘];107 // 地理位置信息108 $Label = $param[‘Label‘];109 // 消息ID110 $MsgId = $param[‘MsgId‘];111 112 $result = $this->encodexml->text(‘success‘);113 return $result;114 }115 116 // 处理链接消息117 protected function link($param) {118 // 消息标题119 $Title = $param[‘Title‘];120 // 消息ID121 $MsgId = $param[‘MsgId‘];122 123 $result = $this->encodexml->text(‘success‘);124 return $result;125 }126 127 // 处理事件 关注公众号128 protected function event_subscribe($param) {129 130 if (isset($param[‘EventKey‘])) {131 // 扫描带参数二维码事件132 // 事件KEY值133 $EventKey = $param[‘EventKey‘];134 // 二维码的ticket135 $Ticket = $param[‘Ticket‘];136 137 } else {138 // 关注事件139 140 }141 142 $result = $this->encodexml->text(‘success‘);143 return $result;144 }145 146 // 处理事件 用户已关注时的事件推送147 protected function event_SCAN($param) {148 // 事件KEY值149 $EventKey = $param[‘EventKey‘];150 // 二维码的ticket151 $Ticket = $param[‘Ticket‘];152 153 $result = $this->encodexml->text(‘success‘);154 return $result;155 }156 157 // 处理事件 上报地理位置事件158 protected function event_LOCATION($param) {159 // 地理位置纬度160 $Latitude = $param[‘Latitude‘];161 // 地理位置经度162 $Longitude = $param[‘Longitude‘];163 // 地理位置精度164 $Precision = $param[‘Precision‘];165 166 $result = $this->encodexml->text(‘success‘);167 return $result;168 }169 170 // 处理事件 自定义菜单事件 点击菜单拉取消息时的事件推送171 protected function event_CLICK($param) {172 // 事件KEY值173 $EventKey = $param[‘EventKey‘];174 175 $result = $this->encodexml->text(‘success‘);176 return $result;177 }178 179 // 处理事件 自定义菜单事件 点击菜单跳转链接时的事件推送180 protected function event_VIEW($param) {181 // 事件KEY值182 $EventKey = $param[‘EventKey‘];183 184 $result = $this->encodexml->text(‘success‘);185 return $result;186 }187 188 }
WeixinEvent
使用方法
1 namespace app\wechat\Controller; 2 3 use think\Controller; 4 use app\common\logic\Wechat; 5 6 class Index extends Controller 7 { 8 9 public function api ()10 {11 config(‘app_trace‘, false);12 13 $userid = input(‘get.id/s,0‘);14 // 查询公众号15 $data = db(‘wechat_accounts‘)->where([‘account_id‘=>$userid])->find();16 if (!$data) {exit;}17 $user[‘id‘] = $data[‘account_id‘];18 $user[‘token‘] = $data[‘token‘];19 $user[‘appId‘] = $data[‘app_id‘];20 $user[‘appSecret‘] = $data[‘app_secret‘];21 $user[‘encodingAESKey‘] = $data[‘aeskey‘];22 // 不验证消息23 //$user[‘authMsg‘] = false;24 25 // 回调函数26 $response = function ($msgType,$data) {27 $event = new Wechat\WeixinEvent();28 return $event->$msgType($data);29 };30 31 $wiki = new Wechat\Wechat($user);32 // 获取信息体33 $response = $wiki->getBackMsg($_GET, file_get_contents(‘php://input‘),$response);34 35 // 记录日志36 RecordLog(37 ‘WikiMsg‘,38 [39 ‘time‘ => $wiki->receiveTime,40 ‘state‘ => $wiki->getError($response),41 ‘receiveMsg‘ => $wiki->receiveMsg,42 ‘receiveEncryptMsg‘ => $wiki->receiveEncryptMsg,43 ‘sendMsg‘ => $wiki->sendMsg,44 ‘sendEncryptMsg‘ => $wiki->sendEncryptMsg,45 ]);46 47 print $response;48 49 }50 51 }