欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-23 19:22:52
含有4个类 1 namespace app\common\logic\Wechat; 2 3 /** 4 * Class Weixin 获取服务器发送的消息并返回消息 5 * 6 * 包含 Encrypt AES加解密类 7 * 包含 Encodexml xml格式化类 8 */ 9 10 clas ......

含有4个类

Wechat 消息处理
Encrypt 消息加解密
Encodexml xml处理
WeixinEvent 返回数据处理

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