867 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			867 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| /**
 | ||
|  * 商业使用授权协议
 | ||
|  * 
 | ||
|  * Copyright (c) 2025 [云泽网]. 保留所有权利.
 | ||
|  * 
 | ||
|  * 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
 | ||
|  * 未经授权商业使用本软件属于侵权行为,将承担法律责任。
 | ||
|  * 
 | ||
|  * 授权购买请联系: 357099073@qq.com
 | ||
|  * 官方网站: https://www.yunzer.cn
 | ||
|  * 
 | ||
|  * 评估用户须知:
 | ||
|  * 1. 禁止移除版权声明
 | ||
|  * 2. 禁止用于生产环境
 | ||
|  * 3. 禁止转售或分发
 | ||
|  */
 | ||
| 
 | ||
| namespace app\index\controller;
 | ||
| 
 | ||
| use think\facade\Request;
 | ||
| use think\facade\Log;
 | ||
| use think\facade\Cache;
 | ||
| use GuzzleHttp\Client;
 | ||
| use app\index\model\Users;
 | ||
| use app\index\model\AdminConfig;
 | ||
| 
 | ||
| class WechatController extends BaseController
 | ||
| {
 | ||
|     /**
 | ||
|      * 测试接口是否正常工作
 | ||
|      */
 | ||
|     public function test()
 | ||
|     {
 | ||
|         try {
 | ||
|             // 设置响应头
 | ||
|             header('Content-Type: application/json; charset=utf-8');
 | ||
|             
 | ||
|             $data = [
 | ||
|                 'time' => date('Y-m-d H:i:s'),
 | ||
|                 'status' => 'ok',
 | ||
|                 'server' => [
 | ||
|                     'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? '',
 | ||
|                     'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? '',
 | ||
|                     'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? '',
 | ||
|                     'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'] ?? '',
 | ||
|                 ]
 | ||
|             ];
 | ||
|             
 | ||
|             // 记录详细日志
 | ||
|             Log::info('接口测试 - 请求信息:' . json_encode($data, JSON_UNESCAPED_UNICODE));
 | ||
|             
 | ||
|             return json($data, JSON_UNESCAPED_UNICODE);
 | ||
|         } catch (\Exception $e) {
 | ||
|             $error = [
 | ||
|                 'error' => $e->getMessage(),
 | ||
|                 'time' => date('Y-m-d H:i:s')
 | ||
|             ];
 | ||
|             Log::error('接口测试错误:' . json_encode($error, JSON_UNESCAPED_UNICODE));
 | ||
|             return json($error, JSON_UNESCAPED_UNICODE);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     public function index()
 | ||
|     {
 | ||
|         try {
 | ||
|             // 设置响应头
 | ||
|             header('Content-Type: text/html; charset=utf-8');
 | ||
|             
 | ||
|             // 记录原始请求信息
 | ||
|             $requestInfo = [
 | ||
|                 'time' => date('Y-m-d H:i:s'),
 | ||
|                 'method' => Request::method(),
 | ||
|                 'url' => Request::url(true),
 | ||
|                 'ip' => Request::ip(),
 | ||
|                 'real_ip' => Request::server('HTTP_X_REAL_IP'),
 | ||
|                 'forwarded_ip' => Request::server('HTTP_X_FORWARDED_FOR'),
 | ||
|                 'params' => Request::param(),
 | ||
|                 'headers' => getallheaders(),
 | ||
|                 'server' => [
 | ||
|                     'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? '',
 | ||
|                     'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? '',
 | ||
|                     'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? '',
 | ||
|                     'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'] ?? '',
 | ||
|                     'QUERY_STRING' => $_SERVER['QUERY_STRING'] ?? '',
 | ||
|                     'HTTP_USER_AGENT' => $_SERVER['HTTP_USER_AGENT'] ?? '',
 | ||
|                     'HTTP_REFERER' => $_SERVER['HTTP_REFERER'] ?? '',
 | ||
|                     'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'] ?? '',
 | ||
|                     'CONTENT_LENGTH' => $_SERVER['CONTENT_LENGTH'] ?? '',
 | ||
|                     'SERVER_NAME' => $_SERVER['SERVER_NAME'] ?? '',
 | ||
|                     'SERVER_ADDR' => $_SERVER['SERVER_ADDR'] ?? '',
 | ||
|                     'SERVER_PORT' => $_SERVER['SERVER_PORT'] ?? '',
 | ||
|                     'REQUEST_SCHEME' => $_SERVER['REQUEST_SCHEME'] ?? '',
 | ||
|                 ],
 | ||
|                 'env' => [
 | ||
|                     'app_debug' => config('app.debug'),
 | ||
|                     'app_env' => config('app.env'),
 | ||
|                     'domain' => config('app.domain'),
 | ||
|                 ]
 | ||
|             ];
 | ||
|             
 | ||
|             Log::info('微信接口访问请求信息:' . json_encode($requestInfo, JSON_UNESCAPED_UNICODE));
 | ||
|             
 | ||
|             // 获取原始数据
 | ||
|             $rawData = file_get_contents("php://input");
 | ||
|             if (!empty($rawData)) {
 | ||
|                 $rawData = mb_convert_encoding($rawData, 'UTF-8', 'UTF-8,GBK,GB2312');
 | ||
|                 Log::info('微信接口原始数据:' . $rawData);
 | ||
|             }
 | ||
| 
 | ||
|             // 检查请求方法  
 | ||
|             if (Request::method() == 'GET') {
 | ||
|                 Log::info('微信接口:GET请求,进行签名验证');
 | ||
|                 // 首次验证服务器地址的有效性  
 | ||
|                 $this->checkSignature();
 | ||
|             } elseif (Request::method() == 'POST') {
 | ||
|                 Log::info('微信接口:POST请求,处理消息');
 | ||
|                 // 接收消息并回复  
 | ||
|                 $response = $this->receiveMessage();
 | ||
|                 // 直接输出回复的XML字符串  
 | ||
|                 echo $response;
 | ||
|                 exit;
 | ||
|             } else {
 | ||
|                 Log::error('微信接口:不支持的请求方法 - ' . Request::method());
 | ||
|                 echo '';
 | ||
|                 exit;
 | ||
|             }
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('微信接口错误:' . $e->getMessage());
 | ||
|             Log::error('错误堆栈:' . $e->getTraceAsString());
 | ||
|             // 返回空字符串,避免微信服务器重试
 | ||
|             echo '';
 | ||
|             exit;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 验证签名  
 | ||
|     protected function checkSignature()
 | ||
|     {
 | ||
|         try {
 | ||
|             $signature = Request::get('signature');
 | ||
|             $timestamp = Request::get('timestamp');
 | ||
|             $nonce = Request::get('nonce');
 | ||
|             $echostr = Request::get('echostr');
 | ||
|             
 | ||
|             $debugInfo = [
 | ||
|                 'signature' => $signature,
 | ||
|                 'timestamp' => $timestamp,
 | ||
|                 'nonce' => $nonce,
 | ||
|                 'echostr' => $echostr,
 | ||
|                 'url' => Request::url(true),
 | ||
|                 'time' => date('Y-m-d H:i:s'),
 | ||
|                 'ip' => Request::ip(),
 | ||
|                 'headers' => getallheaders()
 | ||
|             ];
 | ||
|             
 | ||
|             Log::info('微信验证参数:' . json_encode($debugInfo, JSON_UNESCAPED_UNICODE));
 | ||
|             
 | ||
|             if (empty($signature) || empty($timestamp) || empty($nonce)) {
 | ||
|                 Log::error('微信验证参数缺失', $debugInfo);
 | ||
|                 exit;
 | ||
|             }
 | ||
| 
 | ||
|             $token = AdminConfig::where('config_name', 'wechat_token')->value('config_value');
 | ||
|             if (empty($token)) {
 | ||
|                 Log::error('微信token未配置');
 | ||
|                 exit;
 | ||
|             }
 | ||
| 
 | ||
|             Log::info('微信token:' . $token);
 | ||
| 
 | ||
|             $tmpArr = array($token, $timestamp, $nonce);
 | ||
|             sort($tmpArr, SORT_STRING);
 | ||
|             $tmpStr = implode($tmpArr);
 | ||
|             $tmpStr = sha1($tmpStr);
 | ||
| 
 | ||
|             $verifyInfo = [
 | ||
|                 'tmpStr' => $tmpStr,
 | ||
|                 'signature' => $signature,
 | ||
|                 'token' => $token,
 | ||
|                 'timestamp' => $timestamp,
 | ||
|                 'nonce' => $nonce
 | ||
|             ];
 | ||
|             
 | ||
|             Log::info('签名验证:' . json_encode($verifyInfo, JSON_UNESCAPED_UNICODE));
 | ||
| 
 | ||
|             if ($tmpStr == $signature) {
 | ||
|                 Log::info('微信签名验证成功');
 | ||
|                 echo $echostr;
 | ||
|                 exit;
 | ||
|             } else {
 | ||
|                 Log::error('微信签名验证失败', $verifyInfo);
 | ||
|                 exit;
 | ||
|             }
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('微信验证签名错误:' . $e->getMessage());
 | ||
|             Log::error('错误堆栈:' . $e->getTraceAsString());
 | ||
|             exit;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 接收消息  
 | ||
|     protected function receiveMessage()
 | ||
|     {
 | ||
|         try {
 | ||
|             $postStr = file_get_contents("php://input");
 | ||
|             if (empty($postStr)) {
 | ||
|                 Log::error('微信消息:接收数据为空');
 | ||
|                 return '';
 | ||
|             }
 | ||
| 
 | ||
|             // 转换编码
 | ||
|             $postStr = mb_convert_encoding($postStr, 'UTF-8', 'UTF-8,GBK,GB2312');
 | ||
|             Log::info('微信消息原始数据:' . $postStr);
 | ||
| 
 | ||
|             // 使用DOMDocument替代simplexml_load_string
 | ||
|             $dom = new \DOMDocument();
 | ||
|             $dom->loadXML($postStr, LIBXML_NOCDATA | LIBXML_NOBLANKS);
 | ||
|             $postObj = simplexml_import_dom($dom);
 | ||
|             
 | ||
|             if ($postObj === false) {
 | ||
|                 Log::error('微信消息:XML解析失败');
 | ||
|                 return '';
 | ||
|             }
 | ||
|             
 | ||
|             // 检查消息类型  
 | ||
|             $msgType = strtolower((string) $postObj->MsgType);
 | ||
|             Log::info('微信消息类型:' . $msgType);
 | ||
| 
 | ||
|             // 处理事件消息
 | ||
|             if ($msgType == 'event') {
 | ||
|                 $event = strtolower((string) $postObj->Event);
 | ||
|                 Log::info('微信事件类型:' . $event);
 | ||
| 
 | ||
|                 // 处理扫码事件和关注事件
 | ||
|                 if ($event == 'scan' || $event == 'subscribe') {
 | ||
|                     try {
 | ||
|                         $scene_str = trim((string) $postObj->EventKey);
 | ||
|                         // 如果是关注事件,需要去掉前缀 'qrscene_'
 | ||
|                         if ($event == 'subscribe' && strpos($scene_str, 'qrscene_') === 0) {
 | ||
|                             $scene_str = substr($scene_str, 8);
 | ||
|                         }
 | ||
|                         
 | ||
|                         $fromUsername = trim((string) $postObj->FromUserName);
 | ||
|                         $toUsername = trim((string) $postObj->ToUserName);
 | ||
|                         $ticket = (string) $postObj->Ticket;
 | ||
| 
 | ||
|                         Log::info('微信扫码/关注事件详情', [
 | ||
|                             'event' => $event,
 | ||
|                             'scene_str' => $scene_str,
 | ||
|                             'fromUsername' => $fromUsername,
 | ||
|                             'toUsername' => $toUsername,
 | ||
|                             'ticket' => $ticket,
 | ||
|                             'time' => date('Y-m-d H:i:s'),
 | ||
|                             'raw_data' => $postStr
 | ||
|                         ]);
 | ||
| 
 | ||
|                         // 创建票据保存目录
 | ||
|                         $upload_dir = 'public/storage/uploads/ticket/';
 | ||
|                         if (!is_dir($upload_dir)) {
 | ||
|                             if (!mkdir($upload_dir, 0755, true)) {
 | ||
|                                 Log::error('创建票据保存目录失败:' . $upload_dir);
 | ||
|                                 return 'success';
 | ||
|                             }
 | ||
|                             Log::info('创建票据保存目录成功:' . $upload_dir);
 | ||
|                         }
 | ||
| 
 | ||
|                         // 保存扫码数据
 | ||
|                         $data = [
 | ||
|                             'openid' => $fromUsername,
 | ||
|                             'scene_str' => $scene_str,
 | ||
|                             'ticket' => $ticket,
 | ||
|                             'scan_time' => time(),
 | ||
|                             'event' => $event,
 | ||
|                             'raw_data' => $postStr
 | ||
|                         ];
 | ||
|                         
 | ||
|                         $content = json_encode($data, JSON_UNESCAPED_UNICODE);
 | ||
|                         $file = $upload_dir . $scene_str . "_" . $ticket . ".json";
 | ||
|                         
 | ||
|                         if (file_put_contents($file, $content) === false) {
 | ||
|                             Log::error('保存扫码数据失败:' . $file);
 | ||
|                             return 'success';
 | ||
|                         }
 | ||
|                         Log::info('保存扫码数据成功:' . $file);
 | ||
| 
 | ||
|                         // 获取用户信息
 | ||
|                         try {
 | ||
|                             $accessToken = $this->getGZHAccessToken();
 | ||
|                             Log::info('获取access_token成功:' . $accessToken);
 | ||
|                             
 | ||
|                             $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token={$accessToken}&openid={$fromUsername}&lang=zh_CN";
 | ||
|                             $client = new Client(['verify' => false]);
 | ||
|                             $response = $client->get($url);
 | ||
|                             $userInfo = json_decode($response->getBody(), true);
 | ||
|                             
 | ||
|                             Log::info('获取用户信息结果:' . json_encode($userInfo, JSON_UNESCAPED_UNICODE));
 | ||
|                             
 | ||
|                             if (isset($userInfo['openid'])) {
 | ||
|                                 // 先检查是否存在该openid的用户
 | ||
|                                 $user = Users::where('openid', $fromUsername)->find();
 | ||
|                                 
 | ||
|                                 if ($user) {
 | ||
|                                     // 已存在用户,直接登录
 | ||
|                                     $user->login_count = $user->login_count + 1;  // 增加登录次数
 | ||
|                                     $user->update_time = time();  // 更新登录时间
 | ||
|                                     
 | ||
|                                     // 只在头像为空或为默认头像时更新
 | ||
|                                     if (empty($user->avatar) || $user->avatar === '/static/images/avatar.png') {
 | ||
|                                         if (!empty($userInfo['headimgurl'])) {
 | ||
|                                             $user->avatar = $userInfo['headimgurl'];
 | ||
|                                         } elseif (empty($user->avatar)) {
 | ||
|                                             $user->avatar = '/static/images/avatar.png';
 | ||
|                                         }
 | ||
|                                     }
 | ||
|                                     
 | ||
|                                     if (!$user->save()) {
 | ||
|                                         Log::error('更新用户登录信息失败:' . json_encode($user->getError(), JSON_UNESCAPED_UNICODE));
 | ||
|                                         return 'success';
 | ||
|                                     }
 | ||
|                                     Log::info('用户登录成功:' . json_encode($user->toArray(), JSON_UNESCAPED_UNICODE));
 | ||
|                                 } else {
 | ||
|                                     // 不存在用户,创建新用户
 | ||
|                                     $user = new Users;
 | ||
|                                     $user->openid = $fromUsername;
 | ||
|                                     // 生成默认账号
 | ||
|                                     $defaultAccount = 'wx_' . substr(md5($fromUsername), 0, 8);
 | ||
|                                     $user->account = $defaultAccount;
 | ||
|                                     $user->name = $defaultAccount;  // 将默认账号同时设置为用户名
 | ||
|                                     // 设置头像,确保有值
 | ||
|                                     $user->avatar = !empty($userInfo['headimgurl']) ? $userInfo['headimgurl'] : '/static/images/avatar.png';
 | ||
|                                     // 生成随机密码
 | ||
|                                     $user->password = md5(uniqid() . rand(1000, 9999));
 | ||
|                                     $user->create_time = time();  // 设置创建时间
 | ||
|                                     $user->login_count = 1;  // 首次登录,设置登录次数为1
 | ||
|                                     
 | ||
|                                     if (!$user->save()) {
 | ||
|                                         Log::error('创建用户失败:' . json_encode($user->getError(), JSON_UNESCAPED_UNICODE));
 | ||
|                                         return 'success';
 | ||
|                                     }
 | ||
|                                     Log::info('创建新用户成功:' . json_encode($user->toArray(), JSON_UNESCAPED_UNICODE));
 | ||
|                                 }
 | ||
| 
 | ||
|                                 // 更新票据文件
 | ||
|                                 $data = [
 | ||
|                                     'uid' => (int)$user->uid,  // 确保uid是整数
 | ||
|                                     'name' => $user->name,
 | ||
|                                     'avatar' => $user->avatar ?: '/static/images/avatar.png',  // 确保avatar不为空
 | ||
|                                     'openid' => $user->openid,
 | ||
|                                     'user_account' => $user->account,
 | ||
|                                     'user_password' => $user->password,
 | ||
|                                     'expire_time' => time() + (7 * 24 * 3600),  // 7天过期
 | ||
|                                     'is_auto_login' => true,
 | ||
|                                     'login_status' => 'success'
 | ||
|                                 ];
 | ||
|                                 
 | ||
|                                 if (file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE)) === false) {
 | ||
|                                     Log::error('更新票据文件失败:' . $file);
 | ||
|                                 } else {
 | ||
|                                     Log::info('更新票据文件成功:' . json_encode($data, JSON_UNESCAPED_UNICODE));
 | ||
|                                 }
 | ||
| 
 | ||
|                                 // 设置cookie
 | ||
|                                 $expire = 7 * 24 * 3600;  // 7天过期
 | ||
|                                 cookie('user_account', $user->account, ['expire' => $expire]);
 | ||
|                                 cookie('user_avatar', $user->avatar ?: '/static/images/avatar.png', ['expire' => $expire]);
 | ||
|                                 cookie('user_name', $user->name, ['expire' => $expire]);
 | ||
|                                 cookie('open_id', $user->openid, ['expire' => $expire]);
 | ||
|                                 
 | ||
|                                 // 发送登录成功消息
 | ||
|                                 $messageContent = [
 | ||
|                                     'content' => "您好!\n您已成功登录系统。\n登录时间:" . date('Y-m-d H:i:s')
 | ||
|                                 ];
 | ||
|                                 $this->sendCustomMessage($fromUsername, 'text', $messageContent);
 | ||
|                                 
 | ||
|                                 Log::info('用户登录成功:' . json_encode([
 | ||
|                                     'uid' => (int)$user->uid,
 | ||
|                                     'name' => $user->name,
 | ||
|                                     'openid' => $user->openid,
 | ||
|                                     'account' => $user->account,
 | ||
|                                     'cookies' => [
 | ||
|                                         'user_account' => $user->account,
 | ||
|                                         'user_avatar' => $user->avatar,
 | ||
|                                         'user_name' => $user->name,
 | ||
|                                         'open_id' => $user->openid
 | ||
|                                     ]
 | ||
|                                 ], JSON_UNESCAPED_UNICODE));
 | ||
|                             } else {
 | ||
|                                 Log::error('获取用户信息失败:' . json_encode($userInfo, JSON_UNESCAPED_UNICODE));
 | ||
|                             }
 | ||
|                         } catch (\Exception $e) {
 | ||
|                             Log::error('获取用户信息失败:' . $e->getMessage());
 | ||
|                             Log::error('错误堆栈:' . $e->getTraceAsString());
 | ||
|                         }
 | ||
|                     } catch (\Exception $e) {
 | ||
|                         Log::error('处理扫码事件失败:' . $e->getMessage());
 | ||
|                         Log::error('错误堆栈:' . $e->getTraceAsString());
 | ||
|                     }
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             return 'success';
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('处理微信消息错误:' . $e->getMessage());
 | ||
|             Log::error('错误堆栈:' . $e->getTraceAsString());
 | ||
|             return 'success';
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 发送小程序卡片消息的方法  
 | ||
|     private function sendMiniProgramCard($openid)
 | ||
|     {
 | ||
| 
 | ||
|         $accessToken = $this->getGZHAccessToken();
 | ||
|         print_r($accessToken);
 | ||
|         if (!$accessToken) {
 | ||
|             // 处理获取access_token失败的情况  
 | ||
|             return;
 | ||
|         }
 | ||
|         $postData = json_encode([
 | ||
|             'touser' => $openid, // 接收者(用户)的openid
 | ||
|             'msgtype' => 'miniprogrampage',
 | ||
|             'miniprogrampage' => [
 | ||
|                 'title' => '小程序标题',
 | ||
|                 'appid' => '小程序appid',
 | ||
|                 'pagepath' => '小程序路径',
 | ||
|                 'thumb_media_id' => '你的thumb_media_id'
 | ||
|             ]
 | ||
|         ], JSON_UNESCAPED_UNICODE);
 | ||
|         $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={$accessToken}";
 | ||
|         $result = json_decode(file_get_contents($url, false, stream_context_create([
 | ||
|             'http' => [
 | ||
|                 'method' => 'POST',
 | ||
|                 'header' => "Content-type: application/json\r\n",
 | ||
|                 'content' => $postData,
 | ||
|                 'timeout' => 60 // 超时时间(单位:s)
 | ||
|             ]
 | ||
|         ])), true);
 | ||
|         // 处理返回结果
 | ||
|         if ($result['errcode'] == 0) {
 | ||
|             // 发送成功
 | ||
|         } else {
 | ||
|             // 发送失败
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 获取公众号Access token
 | ||
|     public function getGZHAccessToken()
 | ||
|     {
 | ||
|         // 从数据库获取配置
 | ||
|         $appid = AdminConfig::where('config_name', 'wechat_appid')->value('config_value');
 | ||
|         $secret = AdminConfig::where('config_name', 'wechat_appsecret')->value('config_value');
 | ||
| 
 | ||
|         if (empty($appid) || empty($secret)) {
 | ||
|             throw new \Exception('微信配置信息未设置');
 | ||
|         }
 | ||
| 
 | ||
|         // 构建请求URL
 | ||
|         $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
 | ||
| 
 | ||
|         try {
 | ||
|             // 使用 GuzzleHttp 发送请求,禁用SSL验证
 | ||
|             $client = new Client([
 | ||
|                 'verify' => false
 | ||
|             ]);
 | ||
|             $response = $client->get($url);
 | ||
|             $data = json_decode($response->getBody(), true);
 | ||
| 
 | ||
|             if (!isset($data['access_token'])) {
 | ||
|                 throw new \Exception("获取access_token失败: {$data['errmsg']}", $data['errcode'] ?? -1);
 | ||
|             }
 | ||
| 
 | ||
|             return $data['access_token'];
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('获取access_token失败:' . $e->getMessage());
 | ||
|             throw $e;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 获取微信登录二维码
 | ||
|      * @return \think\response\Json
 | ||
|      */
 | ||
|     public function getLoginTicket()
 | ||
|     {
 | ||
|         try {
 | ||
|             Log::info('开始获取微信登录二维码');
 | ||
|             
 | ||
|             // 获取access_token
 | ||
|             $access_token = $this->getGZHAccessToken();
 | ||
|             Log::info('获取access_token成功:' . $access_token);
 | ||
|             
 | ||
|             // 构建请求URL - 使用正确的接口生成临时二维码
 | ||
|             $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={$access_token}";
 | ||
|             
 | ||
|             // 生成唯一场景值
 | ||
|             $scene_str = md5(uniqid() . time());
 | ||
|             Log::info('生成场景值:' . $scene_str);
 | ||
|             
 | ||
|             // 准备请求数据 - 生成临时二维码,有效期5分钟
 | ||
|             $postData = json_encode([
 | ||
|                 'expire_seconds' => 300, // 5分钟有效期
 | ||
|                 'action_name' => 'QR_STR_SCENE',
 | ||
|                 'action_info' => [
 | ||
|                     'scene' => [
 | ||
|                         'scene_str' => $scene_str
 | ||
|                     ]
 | ||
|                 ]
 | ||
|             ]);
 | ||
| 
 | ||
|             // 发送请求获取ticket
 | ||
|             $client = new Client(['verify' => false]);
 | ||
|             $response = $client->post($url, [
 | ||
|                 'body' => $postData,
 | ||
|                 'headers' => [
 | ||
|                     'Content-Type' => 'application/json'
 | ||
|                 ]
 | ||
|             ]);
 | ||
| 
 | ||
|             $result = json_decode($response->getBody(), true);
 | ||
|             Log::info('微信返回结果:' . json_encode($result, JSON_UNESCAPED_UNICODE));
 | ||
| 
 | ||
|             if (isset($result['errcode']) && $result['errcode'] != 0) {
 | ||
|                 Log::error('获取二维码失败:' . $result['errmsg']);
 | ||
|                 return json(['code' => 1, 'msg' => '获取二维码失败:' . $result['errmsg']]);
 | ||
|             }
 | ||
| 
 | ||
|             // 使用ticket获取二维码图片URL
 | ||
|             $ticket = urlencode($result['ticket']);
 | ||
|             $qrcodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={$ticket}";
 | ||
| 
 | ||
|             // 将场景值保存到缓存中,用于后续验证
 | ||
|             Cache::set('wx_login_scene_' . $result['ticket'], [
 | ||
|                 'scene_str' => $scene_str,
 | ||
|                 'create_time' => time(),
 | ||
|                 'expire_time' => time() + 300
 | ||
|             ], 300);
 | ||
| 
 | ||
|             Log::info('二维码生成成功', [
 | ||
|                 'ticket' => $result['ticket'],
 | ||
|                 'scene_str' => $scene_str,
 | ||
|                 'expire_time' => time() + 300
 | ||
|             ]);
 | ||
| 
 | ||
|             return json([
 | ||
|                 'code' => 0,
 | ||
|                 'msg' => '获取二维码成功',
 | ||
|                 'data' => [
 | ||
|                     'ticket' => $result['ticket'],
 | ||
|                     'expire_seconds' => $result['expire_seconds'],
 | ||
|                     'url' => $qrcodeUrl,
 | ||
|                     'scene_str' => $scene_str
 | ||
|                 ]
 | ||
|             ]);
 | ||
| 
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('获取微信登录二维码失败:' . $e->getMessage());
 | ||
|             Log::error('错误堆栈:' . $e->getTraceAsString());
 | ||
|             return json(['code' => 1, 'msg' => '获取二维码失败:' . $e->getMessage()]);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 重新生成二维码
 | ||
|      * @return \think\response\Json
 | ||
|      */
 | ||
|     public function reGenerateQrcode()
 | ||
|     {
 | ||
|         try {
 | ||
|             $scene_str = Request::post('scene_str');
 | ||
|             if (empty($scene_str)) {
 | ||
|                 return json(['code' => 1, 'msg' => '参数错误']);
 | ||
|             }
 | ||
| 
 | ||
|             // 清除旧的缓存
 | ||
|             Cache::delete('wx_login_scene_' . $scene_str);
 | ||
| 
 | ||
|             // 获取access_token
 | ||
|             $access_token = $this->getGZHAccessToken();
 | ||
|             
 | ||
|             // 构建请求URL
 | ||
|             $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={$access_token}";
 | ||
|             
 | ||
|             // 生成新的场景值
 | ||
|             $new_scene_str = md5(uniqid() . time());
 | ||
|             
 | ||
|             // 准备请求数据
 | ||
|             $postData = json_encode([
 | ||
|                 'expire_seconds' => 300,
 | ||
|                 'action_name' => 'QR_STR_SCENE',
 | ||
|                 'action_info' => [
 | ||
|                     'scene' => [
 | ||
|                         'scene_str' => $new_scene_str
 | ||
|                     ]
 | ||
|                 ]
 | ||
|             ]);
 | ||
| 
 | ||
|             // 发送请求获取ticket
 | ||
|             $client = new Client(['verify' => false]);
 | ||
|             $response = $client->post($url, [
 | ||
|                 'body' => $postData,
 | ||
|                 'headers' => [
 | ||
|                     'Content-Type' => 'application/json'
 | ||
|                 ]
 | ||
|             ]);
 | ||
| 
 | ||
|             $result = json_decode($response->getBody(), true);
 | ||
| 
 | ||
|             if (isset($result['errcode']) && $result['errcode'] != 0) {
 | ||
|                 return json(['code' => 1, 'msg' => '获取二维码失败:' . $result['errmsg']]);
 | ||
|             }
 | ||
| 
 | ||
|             // 使用ticket获取二维码图片URL
 | ||
|             $ticket = urlencode($result['ticket']);
 | ||
|             $qrcodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={$ticket}";
 | ||
| 
 | ||
|             // 将新的场景值保存到缓存中
 | ||
|             Cache::set('wx_login_scene_' . $new_scene_str, [
 | ||
|                 'scene_str' => $new_scene_str,
 | ||
|                 'create_time' => time(),
 | ||
|                 'expire_time' => time() + 300
 | ||
|             ], 300);
 | ||
| 
 | ||
|             return json([
 | ||
|                 'code' => 0,
 | ||
|                 'msg' => '获取二维码成功',
 | ||
|                 'data' => [
 | ||
|                     'ticket' => $result['ticket'],
 | ||
|                     'expire_seconds' => $result['expire_seconds'],
 | ||
|                     'url' => $qrcodeUrl,
 | ||
|                     'scene_str' => $new_scene_str
 | ||
|                 ]
 | ||
|             ]);
 | ||
| 
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('重新生成二维码失败:' . $e->getMessage());
 | ||
|             return json(['code' => 1, 'msg' => '重新生成二维码失败:' . $e->getMessage()]);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 检查微信扫码登录状态
 | ||
|      * @return \think\response\Json
 | ||
|      */
 | ||
|     public function checkLoginStatus()
 | ||
|     {
 | ||
|         try {
 | ||
|             $scene_str = Request::post('scene_str');
 | ||
|             $ticket = Request::post('ticket');
 | ||
|             
 | ||
|             if (empty($scene_str) || empty($ticket)) {
 | ||
|                 return json(['code' => 0, 'msg' => '参数错误']);
 | ||
|             }
 | ||
| 
 | ||
|             // 检查票据文件
 | ||
|             $file = 'public/storage/uploads/ticket/' . $scene_str . "_" . $ticket . ".json";
 | ||
|             
 | ||
|             if (!file_exists($file)) {
 | ||
|                 return json(['code' => 0, 'msg' => '等待扫码']);
 | ||
|             }
 | ||
| 
 | ||
|             $loginData = json_decode(file_get_contents($file), true);
 | ||
|             if (!$loginData) {
 | ||
|                 return json(['code' => 0, 'msg' => '等待扫码']);
 | ||
|             }
 | ||
| 
 | ||
|             // 检查是否已经扫码
 | ||
|             if (!isset($loginData['openid'])) {
 | ||
|                 return json(['code' => 0, 'msg' => '等待扫码']);
 | ||
|             }
 | ||
| 
 | ||
|             // 检查登录状态
 | ||
|             if (!isset($loginData['login_status']) || $loginData['login_status'] !== 'success') {
 | ||
|                 return json(['code' => 0, 'msg' => '正在处理登录,请稍候...']);
 | ||
|             }
 | ||
| 
 | ||
|             // 登录成功,设置session
 | ||
|             session('user_id', $loginData['uid']);
 | ||
|             session('user_name', $loginData['name']);
 | ||
|             session('user_avatar', $loginData['avatar']);
 | ||
|             session('user_account', $loginData['user_account']);
 | ||
|             session('openid', $loginData['openid']);
 | ||
| 
 | ||
|             // 删除临时文件
 | ||
|             @unlink($file);
 | ||
| 
 | ||
|             // 返回用户信息
 | ||
|             return json([
 | ||
|                 'code' => 1,
 | ||
|                 'msg' => '登录成功',
 | ||
|                 'data' => [
 | ||
|                     'uid' => $loginData['uid'],
 | ||
|                     'name' => $loginData['name'],
 | ||
|                     'avatar' => $loginData['avatar'],
 | ||
|                     'openid' => $loginData['openid'],
 | ||
|                     'user_account' => $loginData['user_account'],
 | ||
|                     'user_password' => $loginData['user_password'],
 | ||
|                     'expire_time' => $loginData['expire_time'],
 | ||
|                     'is_auto_login' => $loginData['is_auto_login']
 | ||
|                 ]
 | ||
|             ]);
 | ||
| 
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('检查登录状态失败:' . $e->getMessage());
 | ||
|             return json(['code' => 0, 'msg' => '系统错误']);
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 根据openid获取用户信息
 | ||
|      * @param string $openid
 | ||
|      * @return array|null
 | ||
|      */
 | ||
|     private function getUserInfoByOpenid($openid)
 | ||
|     {
 | ||
|         try {
 | ||
|             // 这里需要根据你的用户表结构来实现
 | ||
|             // 示例:从用户表中查询openid对应的用户信息
 | ||
|             $user = Users::where('openid', $openid)->find();
 | ||
|             if ($user) {
 | ||
|                 return $user->toArray();
 | ||
|             }
 | ||
|             return null;
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('获取用户信息失败:' . $e->getMessage());
 | ||
|             return null;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 测试微信服务器访问
 | ||
|      */
 | ||
|     public function testWechat()
 | ||
|     {
 | ||
|         try {
 | ||
|             // 记录所有请求信息
 | ||
|             $requestInfo = [
 | ||
|                 'time' => date('Y-m-d H:i:s'),
 | ||
|                 'method' => Request::method(),
 | ||
|                 'url' => Request::url(true),
 | ||
|                 'ip' => Request::ip(),
 | ||
|                 'real_ip' => Request::server('HTTP_X_REAL_IP'),
 | ||
|                 'forwarded_ip' => Request::server('HTTP_X_FORWARDED_FOR'),
 | ||
|                 'params' => Request::param(),
 | ||
|                 'headers' => getallheaders(),
 | ||
|                 'server' => [
 | ||
|                     'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? '',
 | ||
|                     'REQUEST_URI' => $_SERVER['REQUEST_URI'] ?? '',
 | ||
|                     'HTTP_HOST' => $_SERVER['HTTP_HOST'] ?? '',
 | ||
|                     'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'] ?? '',
 | ||
|                     'QUERY_STRING' => $_SERVER['QUERY_STRING'] ?? '',
 | ||
|                     'HTTP_USER_AGENT' => $_SERVER['HTTP_USER_AGENT'] ?? '',
 | ||
|                     'HTTP_REFERER' => $_SERVER['HTTP_REFERER'] ?? '',
 | ||
|                     'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'] ?? '',
 | ||
|                     'CONTENT_LENGTH' => $_SERVER['CONTENT_LENGTH'] ?? '',
 | ||
|                     'SERVER_NAME' => $_SERVER['SERVER_NAME'] ?? '',
 | ||
|                     'SERVER_ADDR' => $_SERVER['SERVER_ADDR'] ?? '',
 | ||
|                     'SERVER_PORT' => $_SERVER['SERVER_PORT'] ?? '',
 | ||
|                     'REQUEST_SCHEME' => $_SERVER['REQUEST_SCHEME'] ?? '',
 | ||
|                 ]
 | ||
|             ];
 | ||
|             
 | ||
|             Log::info('微信测试接口访问:' . json_encode($requestInfo, JSON_UNESCAPED_UNICODE));
 | ||
|             
 | ||
|             // 获取原始数据
 | ||
|             $rawData = file_get_contents("php://input");
 | ||
|             if (!empty($rawData)) {
 | ||
|                 $rawData = mb_convert_encoding($rawData, 'UTF-8', 'UTF-8,GBK,GB2312');
 | ||
|                 Log::info('微信测试接口原始数据:' . $rawData);
 | ||
|             }
 | ||
| 
 | ||
|             // 如果是GET请求,进行签名验证
 | ||
|             if (Request::method() == 'GET') {
 | ||
|                 $signature = Request::get('signature');
 | ||
|                 $timestamp = Request::get('timestamp');
 | ||
|                 $nonce = Request::get('nonce');
 | ||
|                 $echostr = Request::get('echostr');
 | ||
|                 
 | ||
|                 $verifyInfo = [
 | ||
|                     'signature' => $signature,
 | ||
|                     'timestamp' => $timestamp,
 | ||
|                     'nonce' => $nonce,
 | ||
|                     'echostr' => $echostr,
 | ||
|                     'time' => date('Y-m-d H:i:s'),
 | ||
|                     'ip' => Request::ip(),
 | ||
|                     'headers' => getallheaders()
 | ||
|                 ];
 | ||
|                 
 | ||
|                 Log::info('微信测试接口验证参数:' . json_encode($verifyInfo, JSON_UNESCAPED_UNICODE));
 | ||
|                 
 | ||
|                 if (!empty($signature) && !empty($timestamp) && !empty($nonce)) {
 | ||
|                     $token = AdminConfig::where('config_name', 'wechat_token')->value('config_value');
 | ||
|                     if (empty($token)) {
 | ||
|                         Log::error('微信token未配置');
 | ||
|                         return 'token not configured';
 | ||
|                     }
 | ||
| 
 | ||
|                     $tmpArr = array($token, $timestamp, $nonce);
 | ||
|                     sort($tmpArr, SORT_STRING);
 | ||
|                     $tmpStr = implode($tmpArr);
 | ||
|                     $tmpStr = sha1($tmpStr);
 | ||
| 
 | ||
|                     if ($tmpStr == $signature) {
 | ||
|                         Log::info('微信测试接口验证成功');
 | ||
|                         return $echostr;
 | ||
|                     } else {
 | ||
|                         Log::error('微信测试接口验证失败');
 | ||
|                         return 'signature verification failed';
 | ||
|                     }
 | ||
|                 }
 | ||
|             }
 | ||
| 
 | ||
|             return 'success';
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('微信测试接口错误:' . $e->getMessage());
 | ||
|             Log::error('错误堆栈:' . $e->getTraceAsString());
 | ||
|             return 'error';
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 发送客服消息
 | ||
|      * @param string $openid 接收者openid
 | ||
|      * @param string $type 消息类型
 | ||
|      * @param array $content 消息内容
 | ||
|      * @return bool
 | ||
|      */
 | ||
|     private function sendCustomMessage($openid, $type = 'text', $content = [])
 | ||
|     {
 | ||
|         try {
 | ||
|             $accessToken = $this->getGZHAccessToken();
 | ||
|             $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={$accessToken}";
 | ||
|             
 | ||
|             $data = [
 | ||
|                 'touser' => $openid,
 | ||
|                 'msgtype' => $type
 | ||
|             ];
 | ||
|             
 | ||
|             if ($type === 'text') {
 | ||
|                 $data['text'] = ['content' => $content['content'] ?? '登录成功'];
 | ||
|             }
 | ||
|             
 | ||
|             $jsonData = json_encode($data, JSON_UNESCAPED_UNICODE);
 | ||
|             
 | ||
|             $client = new Client(['verify' => false]);
 | ||
|             $response = $client->post($url, [
 | ||
|                 'body' => $jsonData,
 | ||
|                 'headers' => [
 | ||
|                     'Content-Type' => 'application/json; charset=utf-8'
 | ||
|                 ]
 | ||
|             ]);
 | ||
|             
 | ||
|             $result = json_decode($response->getBody(), true);
 | ||
|             
 | ||
|             if (isset($result['errcode']) && $result['errcode'] == 0) {
 | ||
|                 Log::info('发送客服消息成功:' . json_encode($result, JSON_UNESCAPED_UNICODE));
 | ||
|                 return true;
 | ||
|             } else {
 | ||
|                 Log::error('发送客服消息失败:' . json_encode($result, JSON_UNESCAPED_UNICODE));
 | ||
|                 return false;
 | ||
|             }
 | ||
|         } catch (\Exception $e) {
 | ||
|             Log::error('发送客服消息异常:' . $e->getMessage());
 | ||
|             return false;
 | ||
|         }
 | ||
|     }
 | ||
| } |