微信公众平台 自动回复消息

2018-06-22 05:37:03来源:未知 阅读 ()

新老客户大回馈,云服务器低至5折

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

 



标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:[日常] 正则表达式模式修正符简单测试

下一篇:Yii2基本概念之——事件(Event)