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
















