微信公众平台 自动回复消息
含有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 // 最后错误信息 230 protected $lastErrorMsg = \'\'; 231 232 public $url; 233 // 收到的 array数据 234 public $receiveArray = []; 235 // 微信发送消息的时间 236 public $receiveTime = 0; 237 // 收到的xml数据 238 public $receiveMsg = null; 239 // 收到的解密xml数据 240 public $receiveEncryptMsg = null; 241 // 发送的未加密xml数据 242 public $sendEncryptMsg = null; 243 // 发送的xml数据 244 public $sendMsg = null; 245 246 /** 247 * 初始化类 248 * @param array $user 公众号数据 249 * id 原始ID, 250 * token 令牌(Token), 251 * appId 开发者ID(AppID), 252 * appSecret 开发者密码(AppSecret), 253 * encodingAESKey 消息加解密密钥(EncodingAESKey) 254 * authMsg 验证消息 255 */ 256 public function __construct ($user = []) 257 { 258 // 参数赋值 259 isset($user[\'id\']) && $this->id = $user[\'id\']; 260 isset($user[\'token\']) && $this->token = $user[\'token\']; 261 isset($user[\'appId\']) && $this->appId = $user[\'appId\']; 262 isset($user[\'appSecret\']) && $this->appSecret = $user[\'appSecret\']; 263 isset($user[\'encodingAESKey\']) && strlen($user[\'encodingAESKey\']) == 43 && $this->encodingAESKey = $user[\'encodingAESKey\']; 264 isset($user[\'authMsg\']) && $this->authMsg = $user[\'authMsg\']; 265 $this->timeStamp = time(); 266 267 // 处理类赋值 268 // 加解密消息类 269 $this->encrypt = new Encrypt($this->encodingAESKey); 270 $this->encodeXml = new Encodexml(); 271 } 272 273 /** 274 * 获取信息 275 * @param array $getData $_GET 数据 276 * @param string $postData file_get_contents(\'php://input\') 数据 277 * @param \Closure $response 回调函数 生成返回消息 xml ,参数 (string $msgType 消息类型, array $msg 消息数据) 返回 Encodexml 278 * @return mixed 错误代码string 和 消息array 279 */ 280 public function getBackMsg ($getData, $postData, $response=null) 281 { 282 // 验证消息赋值 283 isset($getData[\'signature\']) && $this->signature = $getData[\'signature\']; 284 isset($getData[\'msg_signature\']) && $this->msgSignature = $getData[\'msg_signature\']; 285 isset($getData[\'echostr\']) && $this->echoStr = $getData[\'echostr\']; 286 isset($getData[\'timestamp\']) && $this->timeStamp = $getData[\'timestamp\']; 287 isset($getData[\'nonce\']) && $this->nonce = $getData[\'nonce\']; 288 isset($getData[\'encrypt_type\']) && $this->encryptType = $getData[\'encrypt_type\']; 289 // 保存接收到的xml数据 290 $this->receiveMsg = $postData; 291 // 验证 get 消息 292 $result = $this->checkSignature($this->signature); 293 // 验证 get 消息失败 294 if ($result[0] != 0) { 295 return $result[0]; 296 } 297 // 验证 post 消息 无post消息返回验证字符串 298 if (!$postData) { 299 return $result[1]; 300 } 301 // 获取信息数据 302 $result = $this->responseMsg($postData); 303 // 返回错误信息 304 if ($result[0]!==0) { 305 return $result[0]; 306 } 307 // 获取信息类型 308 $msgtype = $this->getMsgType(); 309 // 生成返回 xml 310 if ($response instanceof \Closure) { 311 // 使用回调函数返回数据 312 $result = $response($msgtype,$result[1]); 313 } else { 314 // 默认返回 success 315 $result = $this->encodeXml->text(\'success\'); 316 } 317 // 返回错误信息 318 if (!($result instanceof Encodexml)) { 319 return $result[0]; 320 } 321 // 加密 xml 322 $result = $this->replyMsg($result); 323 // 返回错误信息 324 if ($result[0] !== 0) { 325 return $result[0]; 326 } 327 return $result[1]; 328 } 329 330 /** 331 * 获取 令牌 332 * @return array 错误代码 和 令牌 333 */ 334 public function getAccessToken () 335 { 336 // 获取令牌缓存 可以使用 文件 或 数据库 来存取 337 $result = cache(\'Weixin.access_token.\'.$this->id); 338 if ($result) { 339 return [0, $result]; 340 } 341 342 // 获取请求 url 343 $this->url = self::$server . self::$serverpath[\'getAccessToken\']; 344 $this->url .= "?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}"; 345 // 发送 Get 请求 346 $result = $this->send_data($this->url); 347 // 排除连接错误 348 if ($result[0] !== 0) { 349 return [$result[0], null]; 350 } 351 try { 352 // json 数据转数组 353 $result = json_decode($result[1], true); 354 } catch (\Exception $e) { 355 // 权限令牌json解析失败 356 return [-50004, null]; 357 } 358 359 if (isset($result[\'access_token\'])) { 360 // 缓存令牌数据 361 cache(\'Weixin.access_token.\'.$this->id, $result[\'access_token\'], self::$expiresIn); 362 return [0, $result[\'access_token\']]; 363 } else { 364 // 接口请求出错 返回无令牌数据 365 return [-50002, null]; 366 } 367 } 368 369 /** 370 * 通过错误代码 获取错误信息 371 * @param mixed $code 错误代码 372 * @return string 返回错误代码 和 错误信息 373 */ 374 public function getError ($code) 375 { 376 if (is_numeric($code)) { 377 if (isset(self::$errorCode[$code])) { 378 return $code . \' [\' . self::$errorCode[$code] . \']\'; 379 } else { 380 return $code . \' [\' . $this->lastErrorMsg . \']\'; 381 } 382 } else { 383 return "0 [成功]"; 384 } 385 } 386 387 /** 388 * 回复微信消息 389 * @param Encodexml $class 处理消息类 390 * @return array 错误代码 和 返回信息 391 */ 392 protected function replyMsg (Encodexml $class) 393 { 394 try { 395 $xml = $class->buildXml($this->receiveArray[\'FromUserName\'], $this->receiveArray[\'ToUserName\']); 396 } catch (\Exception $e) { 397 return [-50008, null]; 398 } 399 if (!$this->encryptType) { 400 // 待发送的数据 401 $this->sendMsg = $xml; 402 return [0, $xml]; 403 } 404 // 待发送的未加密的 xml 405 $this->sendEncryptMsg = $xml; 406 // 加密xml 407 $result = $this->encrypt->encrypt($xml, $this->appId); 408 409 // 加密失败 410 if ($result[0]!== 0) { 411 return $result; 412 } 413 414 // 生成签名信息 415 $nonce = $this->encrypt->getRandomStr(); 416 $timeStamp = time(); 417 $msgSignature = $this->getSha($timeStamp, $nonce, $result[1]); 418 419 // 签名失败 420 if ($msgSignature[0]!== 0) { 421 return $msgSignature; 422 } 423 424 // 生成加密信息 425 $xml = "<xml><Encrypt><![CDATA["; 426 $xml .= $result[1]; 427 $xml .= "]]></Encrypt><MsgSignature>{$msgSignature[1]}</MsgSignature><TimeStamp>{$timeStamp}</TimeStamp><Nonce>{$nonce}</Nonce></xml>"; 428 // 待发送的 加密的 xml 429 $this->sendMsg = $xml; 430 return [0, $xml]; 431 } 432 433 /** 434 * 回复微信消息 435 * @return mixed 获取消息类型 436 */ 437 protected function getMsgType () 438 { 439 if (!isset($this->receiveArray[\'MsgType\'])) { 440 return false; 441 } 442 443 $MsgType = $this->receiveArray[\'MsgType\']; 444 445 if ($MsgType != \'event\') { 446 return $MsgType; 447 } 448 449 if (!isset($this->receiveArray[\'Event\'])) { 450 return false; 451 } 452 453 return "{$MsgType}_{$this->receiveArray[\'Event\']}"; 454 } 455 456 /** 457 * 验证消息安全性 458 * @param string $encryptMsg 加密数据 459 * @return array 错误代码 和 返回消息 460 */ 461 protected function checkSignature ($signature, $encryptMsg = \'\') 462 { 463 // 不验证消息 464 if (!$this->encryptType && $this->authMsg === false) { 465 return [0, $this->echoStr]; 466 } 467 // 验证消息完整信 468 if (!($this->signature && $this->timeStamp && $this->nonce)) { 469 // 消息数据不完整 470 return [-50001, null]; 471 } 472 // 获取签名结果 473 $result = $this->getSha($this->timeStamp, $this->nonce, $encryptMsg); 474 if ($result[0] != 0) { 475 return $result; 476 } 477 // 对比签名结果 478 if ($result[1] == $signature) { 479 return [0, $this->echoStr]; 480 } else { 481 if ($encryptMsg) { 482 // 数据签名验证错误 483 return [-50006, null]; 484 } else { 485 // 消息签名验证错误 486 return [-40001, null]; 487 } 488 } 489 } 490 491 /** 492 * 获取服务器发送的消息 493 * @param string $postData 加密数据 494 * @return array 错误代码 和 返回消息数组 495 */ 496 protected function responseMsg ($postData) 497 { 498 // libxml_disable_entity_loader(true); // xml 安全认证 499 // xml解析消息 500 try { 501 $xml = simplexml_load_string($postData, \'SimpleXMLElement\', LIBXML_NOCDATA); 502 } catch (\Exception $e) { 503 // xml 解析失败 504 return [-40002, null]; 505 } 506 // json 数据 转数组 507 $msgArray = json_decode(json_encode($xml), true); 508 // 不是加密消息 直接返回数据 509 if (!$this->encryptType) { 510 $this->receiveArray = $msgArray; 511 return [0, $msgArray]; 512 } 513 // 验证 待解密 数据 514 try { 515 $userName = $msgArray[\'ToUserName\']; 516 $encrypt = $msgArray[\'Encrypt\']; 517 } catch (\Exception $e) { 518 // 待解密消息不完整 519 return [-50005, null]; 520 } 521 // 安全签名验证 522 $sha1 = $this->checkSignature($this->msgSignature,$encrypt); 523 if ($sha1[0]!==0) { 524 return $sha1; 525 } 526 // 解密数据 527 $decryptMsg = $this->encrypt->decrypt($encrypt, $this->appId); 528 // 解密消息失败 529 if ($decryptMsg[0] !== 0) { 530 return [$decryptMsg[0], null]; 531 } 532 // 保存解密后的xml数据 533 $this->receiveEncryptMsg = $decryptMsg[1]; 534 // xml解析 加密消息 535 try { 536 $xml = simplexml_load_string($decryptMsg[1], \'SimpleXMLElement\', LIBXML_NOCDATA); 537 } catch (\Exception $e) { 538 // xml 解析加密数据失败 539 return [-50007, null]; 540 } 541 // json 数据 转数组 542 $msgArray = json_decode(json_encode($xml), true); 543 $msgArray[\'ToUserName\'] = $userName; 544 $this->receiveTime = isset($msgArray[\'TimeStamp\']) ? $msgArray[\'TimeStamp\'] : time(); 545 $this->receiveArray = $msgArray; 546 return [0, $msgArray]; 547 } 548 549 /** 550 * 发送数据请求 551 * @param string $url 微信服务器地址 552 * @param string $type 发送方式 get post 553 * @param string $data 发送数据 554 * @return array 得到返回数据 错误代码和数据 555 */ 556 public function send_data ($url, $type = "get", $data = null) 557 { 558 //$header[] = "Content-type: text/xml"; //定义content-type为xml 559 $header[] = "Accept-Charset: utf-8"; 560 // 初始化 Curl 561 $ch = curl_init(); 562 // 判断服务器地址 是否 ssl 563 $ssl = substr($url, 0, 8) == "https://" ? true : false; 564 curl_setopt($ch, CURLOPT_URL, $url); 565 // 修改 header 566 curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 567 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); 568 // 是否返回数据到变量 569 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 570 If ($type == "post") { // post 模式 571 curl_setopt($ch, CURLOPT_POST, true); 572 curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 573 } 574 if ($ssl) { // ssl 模式 575 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 576 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 577 } 578 // 执行 579 $returndata = curl_exec($ch); 580 if (curl_errno($ch)) { 581 curl_close($ch); 582 // 连接到远程服务器错误 583 return [-50003, null]; 584 } else { 585 curl_close($ch); 586 try { 587 $json = json_decode($returndata,true); 588 if (isset($json[\'errcode\']) && $json[\'errcode\']!=0 ) { 589 isset($json[\'errmsg\']) && $this->lastErrorMsg = $json[\'errmsg\']; 590 return [$json[\'errcode\'], $returndata]; 591 } 592 } catch (\Exception $e) { 593 } 594 return [0, $returndata]; 595 } 596 } 597 598 /** 599 * 用SHA1算法生成安全签名 600 * @param string $token 票据 601 * @param string $timestamp 时间戳 602 * @param string $nonce 随机字符串 603 * @param string $encrypt 密文消息 604 * @return array 返回 安全签名结果 和 数据 605 */ 606 protected function getSha ($timestamp, $nonce, $msg) 607 { 608 try { 609 $array = [$this->token, $timestamp, $nonce, $msg]; 610 // 排序 611 sort($array, SORT_STRING); 612 $str = implode($array); 613 // 返回签名结果 614 return [0, sha1($str)]; 615 } catch (\Exception $e) { 616 // sha加密生成签名失败 617 return [-40003, null]; 618 } 619 } 620 621 622 }
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位随机字符串,网络字节序和AppId 119 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 // 消息ID 110 $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 // 消息ID 121 $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 // 二维码的ticket 135 $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 // 二维码的ticket 151 $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 }