|
|
@@ -21,15 +21,15 @@ class OfficialNotify extends Controller
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
|
return $this->checkSignature();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 2. 处理微信事件推送(POST请求)
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
|
return $this->handleEvent($EmployeeOpenidModel);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return 'success';
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 处理微信事件
|
|
|
*/
|
|
|
@@ -38,44 +38,48 @@ class OfficialNotify extends Controller
|
|
|
// 获取微信推送的原始数据
|
|
|
$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)$xml->FromUserName; // 用户的公众号OpenID
|
|
|
- $toUsername = (string)$xml->ToUserName; // 公众号原始ID
|
|
|
- $event = (string)$xml->Event; // 事件类型
|
|
|
- $eventKey = (string)$xml->EventKey; // 事件KEY值(扫码关注时会有)
|
|
|
-
|
|
|
- // 记录日志,方便调试
|
|
|
- Log::info('wechat_subscribe_info', '微信关注事件', [
|
|
|
- 'FromUserName' => $fromUsername,
|
|
|
- 'ToUserName' => $toUsername,
|
|
|
- 'event' => $event,
|
|
|
- 'eventKey' => $eventKey
|
|
|
- ]);
|
|
|
-
|
|
|
+ $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();
|
|
|
- Log::info('wechat_subscribe_success', '绑定成功', ['unionid' => $unionid, 'openid' => $fromUsername]);
|
|
|
+ } 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,
|
|
|
+ 'data' => $fromUsername,
|
|
|
'request_data' => $official_user_info
|
|
|
]);
|
|
|
}
|
|
|
@@ -85,11 +89,56 @@ class OfficialNotify extends Controller
|
|
|
]);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 重要:必须返回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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 验证服务器地址有效性
|
|
|
*/
|
|
|
@@ -99,20 +148,44 @@ class OfficialNotify extends Controller
|
|
|
$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 ?: [];
|
|
|
+ }
|
|
|
+}
|