初始化项目:添加后端代码、ThinkPHP框架、前端资源

This commit is contained in:
amb
2026-09-03 12:42:39 +08:00
commit 482bece22e
3726 changed files with 416708 additions and 0 deletions
@@ -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);
}
}