| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- <?php
- namespace App\Http\Controllers\Api\Wechat;
- use App\Http\Controllers\Controller;
- use App\Models\Api\Personnel\EmployeeOpenid as EmployeeOpenidModel;
- use App\Facades\Servers\Logs\Log;
- use App\Servers\Wechat\Official;
- use App\Jobs\Manager\Process\SubscriptionJobs;
- class OfficialNotify extends Controller
- {
- /**
- * 公众号关注回调 - 自动绑定用户公众号OpenID
- * @author 唐远望
- * @version 1.0
- * @date 2026-03-10
- */
- public function callback(EmployeeOpenidModel $EmployeeOpenidModel)
- {
- // 1. 处理微信服务器验证(GET请求)
- if ($_SERVER['REQUEST_METHOD'] === 'GET') {
- return $this->checkSignature();
- }
- // 2. 处理微信事件推送(POST请求)
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- return $this->handleEvent($EmployeeOpenidModel);
- }
- return 'success';
- }
- /**
- * 处理微信事件
- */
- private function handleEvent($EmployeeOpenidModel)
- {
- // 获取微信推送的原始数据
- $xmlData = file_get_contents('php://input');
- $xml = simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA);
- if (!$xml) {
- return 'success';
- }
- $xmlData = $this->xmlToArray($xmlData);
- $response_data_xml = $this->decryptMsg($xmlData['Encrypt']);
- $response_data = $this->xmlToArray($response_data_xml);
- Log::info('wechat_subscribe_info', '微信公众号事件,解密内容', ['xmlData' => $xmlData, 'response_data' => $response_data]);
- // 提取关键信息
- $fromUsername = (string)$response_data['FromUserName']; // 用户的公众号OpenID
- $toUsername = (string)$response_data['ToUserName']; // 公众号原始ID
- $event = (string)$response_data['Event']; // 事件类型
- // 处理关注事件
- if ($event == 'subscribe') {
- try {
- // 尝试获取用户UnionID
- $Official = new Official();
- $official_user_info = $Official->getApp()->user->get($fromUsername);
- Log::info('wechat_subscribe_info', '微信公众号事件,获取用户信息', ['FromUserName' => $fromUsername, 'response_data' => $official_user_info]);
- $unionid = isset($official_user_info['unionid']) ? $official_user_info['unionid'] : '';
- if ($unionid) {
- // 1. 有UnionID,直接绑定公众号OpenID
- $user_open_data = $EmployeeOpenidModel->where(['unionid' => $unionid])->first();
- if ($user_open_data) {
- $user_open_data->official_openid = $fromUsername;
- $user_open_data->save();
- } else {
- //新增记录
- $insert_data = [
- 'unionid' => $unionid,
- 'official_openid' => $fromUsername,
- 'insert_time' => time(),
- 'type' => 1
- ];
- $EmployeeOpenidModel->insertGetId($insert_data);
- }
- } else {
- Log::info('wechat_subscribe_error', '获取UnionID失败', [
- 'data' => $fromUsername,
- 'request_data' => $official_user_info
- ]);
- }
- } catch (\Exception $e) {
- Log::error('wechat_subscribe_exception', '处理异常', [
- 'error' => $e->getMessage()
- ]);
- }
- }
- // 重要:必须返回success
- return 'success';
- }
- /**
- * 解密消息
- */
- private function decryptMsg($encrypt)
- {
- try {
- // Base64解码
- $encrypted = base64_decode($encrypt);
- $encodingAesKey = config('wechat.openplat.aes_key', '');
- $appId = config('wechat.openplat.app_id', '');
- // 解密
- $key = base64_decode($encodingAesKey . '=');
- $iv = substr($key, 0, 16);
- $decrypted = openssl_decrypt(
- $encrypted,
- 'AES-256-CBC',
- $key,
- OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
- $iv
- );
- // 去除填充的字符
- $pad = ord(substr($decrypted, -1));
- $decrypted = substr($decrypted, 0, -$pad);
- // 解析内容
- $content = substr($decrypted, 16); // 去掉16个随机字节
- $xmlLen = unpack('N', substr($content, 0, 4))[1];
- // 验证AppId
- $appId = substr($content, 4 + $xmlLen);
- if ($appId != $appId) {
- Log::error('wechat_appid_error', 'AppId不匹配', ['got' => $appId, 'expect' => $appId]);
- return false;
- }
- // 返回XML内容
- return substr($content, 4, $xmlLen);
- } catch (\Exception $e) {
- Log::error('wechat_decrypt_error', '解密异常', ['error' => $e->getMessage()]);
- return false;
- }
- }
- /**
- * 验证服务器地址有效性
- */
- private function checkSignature()
- {
- $signature = $_GET["signature"] ?? '';
- $timestamp = $_GET["timestamp"] ?? '';
- $nonce = $_GET["nonce"] ?? '';
- $echostr = $_GET["echostr"] ?? '';
- if (!$signature || !$timestamp || !$nonce) {
- return 'Invalid request';
- }
- $token = config('wechat.openplat.token', 'your_token_here');
- $tmpArr = [$token, $timestamp, $nonce];
- sort($tmpArr, SORT_STRING);
- $tmpStr = sha1(implode($tmpArr));
- if ($tmpStr == $signature) {
- return $echostr; // 验证成功,返回echostr
- } else {
- return 'Invalid signature';
- }
- }
- /**
- * XML转数组
- * @param string $xml XML字符串
- * @return array
- */
- private function xmlToArray($xml)
- {
- // 禁止引用外部xml实体
- libxml_disable_entity_loader(true);
- // 加载XML字符串
- $xmlObject = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
- if (!$xmlObject) {
- return [];
- }
- // 将SimpleXMLElement对象转换为JSON,再转换为数组
- $json = json_encode($xmlObject);
- $array = json_decode($json, true);
- return $array ?: [];
- }
- /**
- * 公众号测试消息订阅推送
- * @author 唐远望
- * @version 1.0
- * @date 2026-03-10
- */
- public function test()
- {
- $message_data = ['notice_type'=>'low_price_goods'];
- $response_data = SubscriptionJobs::dispatchSync($message_data);
- return json_send(['code' => 'success', 'msg' => '执行成功', 'data' => $response_data]);
- }
- }
|