isGet()) { return $this->verifyUrl($token); } return $this->handleMessage(); } catch (\Exception $e) { Log::error('[WxappNotify] 处理异常: ' . $e->getMessage()); return $this->buildResponse(0, 'error'); } } /** * URL验证(GET请求) * 微信服务器发送验证请求时,校验signature并返回echostr * @param string $token 验证Token * @return string */ private function verifyUrl($token) { $signature = input('signature'); $timestamp = input('timestamp'); $nonce = input('nonce'); $echostr = input('echostr', ''); $tmpArr = [$token, $timestamp, $nonce]; sort($tmpArr, SORT_STRING); $tmpStr = implode($tmpArr); $tmpStr = sha1($tmpStr); if ($tmpStr === $signature) { echo $echostr; exit; } echo 'Invalid signature'; exit; } /** * 处理推送消息(POST请求) * 明文模式:先校验消息签名,再解析消息内容,根据事件类型分发到对应处理器 * @return string */ private function handleMessage() { $content = file_get_contents('php://input'); if (empty($content)) { Log::error('[WxappNotify] 未收到消息内容'); return $this->buildResponse(0, 'no content'); } $message = $this->parseMessage($content); if (empty($message)) { Log::error('[WxappNotify] 解析消息失败'); return $this->buildResponse(0, 'parse error'); } if (!is_array($message)) { Log::error('[WxappNotify] 消息格式错误: 期望数组类型, 实际类型:' . gettype($message) . ', content=' . $content); return $this->buildResponse(0, 'invalid message type'); } if (!isset($message['Event'])) { Log::error('[WxappNotify] 消息格式错误或缺少Event字段: ' . json_encode($message, JSON_UNESCAPED_UNICODE)); return $this->buildResponse(0, 'invalid message'); } if (!$this->verifyPushSignature($message)) { Log::error('[WxappNotify] 消息签名校验失败'); return $this->buildResponse(0, 'invalid signature'); } $event = $message['Event']; Log::info('[WxappNotify] 收到事件: ' . $event . ', message=' . json_encode($message, JSON_UNESCAPED_UNICODE)); try { $handler = new \app\callback\library\app\wxapp\Notify(); $result = $handler->handle($event, $message); if ($result !== null && is_array($result)) { return json_encode($result, JSON_UNESCAPED_UNICODE); } return $this->buildSuccessResponse(); } catch (\Exception $e) { Log::error('[WxappNotify] 事件处理失败: ' . $e->getMessage()); return $this->buildResponse(0, $e->getMessage()); } } /** * 校验微信推送消息签名 * 使用 msg_push_token 对消息内容进行签名校验,防止伪造推送 * @param array $message 解析后的消息数组 * @return bool */ private function verifyPushSignature($message) { $msgPushConfig = \app\common\model\config\System::getConfig('wxMiniProgramMsgPush'); $token = $msgPushConfig['msg_push_token'] ?? ''; if (empty($token)) { Log::error('[WxappNotify] 消息推送Token未配置,无法校验签名'); return false; } $signature = input('signature'); $timestamp = input('timestamp'); $nonce = input('nonce'); if (empty($signature) || empty($timestamp) || empty($nonce)) { Log::error('[WxappNotify] 缺少签名参数'); return false; } $tmpArr = [$token, $timestamp, $nonce]; sort($tmpArr, SORT_STRING); $tmpStr = implode($tmpArr); $expectedSignature = sha1($tmpStr); if (!hash_equals($expectedSignature, $signature)) { Log::error('[WxappNotify] 签名不匹配: expected=' . $expectedSignature . ', received=' . $signature); return false; } $timestampInt = intval($timestamp); $now = time(); if (abs($now - $timestampInt) > 300) { Log::error('[WxappNotify] 消息时间戳已过期: timestamp=' . $timestampInt . ', now=' . $now . ', diff=' . abs($now - $timestampInt)); return false; } return true; } /** * 解析消息内容(支持XML和JSON格式) * @param string $content 原始消息内容 * @return array|null */ private function parseMessage($content) { if (empty($content)) { return null; } if (0 === stripos($content, '<')) { $result = XML::parse($content); if (is_array($result)) { return $result; } Log::warning('[WxappNotify] XML解析结果非数组类型: ' . gettype($result)); return null; } $data = json_decode($content, true); if ($data && is_array($data) && json_last_error() === JSON_ERROR_NONE) { return $data; } return null; } /** * 构建成功响应(明文模式) * @return string */ private function buildSuccessResponse() { return json_encode(['ErrCode' => 0, 'ErrMsg' => 'success'], JSON_UNESCAPED_UNICODE); } /** * 构建错误响应 * @param int $errCode 错误码 * @param string $errMsg 错误信息 * @return string */ private function buildResponse($errCode, $errMsg = '') { return json_encode(['ErrCode' => $errCode, 'ErrMsg' => $errMsg], JSON_UNESCAPED_UNICODE); } }