含有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 }

Wechat

  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 }

 



版权声明:本文为everz原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/everz/archive/2018/03/15/8572650.html