初始化项目:添加后端代码、ThinkPHP框架、前端资源
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace app\callback\controller\app;
|
||||
|
||||
use think\Log;
|
||||
use TuzhiEasyWeChat\Kernel\Support\XML;
|
||||
|
||||
/**
|
||||
* 微信小程序消息推送回调
|
||||
* 处理虚拟支付相关的消息推送(道具发货、代币支付、退款、投诉)
|
||||
*/
|
||||
class Wxapp
|
||||
{
|
||||
/**
|
||||
* 消息推送入口
|
||||
* GET请求:验证URL有效性(echostr验证)
|
||||
* POST请求:接收微信推送的事件消息
|
||||
* @return string
|
||||
*/
|
||||
public function notify()
|
||||
{
|
||||
$uniacid = input('uniacid', 0);
|
||||
if ($uniacid) {
|
||||
define('UNIACID', intval($uniacid));
|
||||
}
|
||||
|
||||
$wxConfig = \app\common\model\config\System::getConfig('wxMiniProgram');
|
||||
$msgPushConfig = \app\common\model\config\System::getConfig('wxMiniProgramMsgPush');
|
||||
|
||||
if (!$msgPushConfig || empty($msgPushConfig['msg_push_token'])) {
|
||||
Log::error('[WxappNotify] 消息推送配置不完整');
|
||||
return 'config error';
|
||||
}
|
||||
|
||||
$token = $msgPushConfig['msg_push_token'];
|
||||
$appId = $wxConfig['app_id'];
|
||||
|
||||
try {
|
||||
if (request()->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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user