// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\controller; use app\api\model\Code as CodeModel; use app\api\model\Notify as NotifyModel; use app\api\model\Shop as ShopModel; use app\api\model\Order as OrderModel; use think\cache\driver\Redis; use cores\exception\BaseException; use think\facade\Cache; use think\response\Json; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Cookie\CookieJar; /** * 产码控制器 * Class Code * @package app\api\controller */ class Code extends Controller { /** * 查询支付状态 * @throws \app\common\exception\BaseException * @throws \think\exception\DbException */ public function payStatus() { $data = $this->request->get(); $zone = isset($data['zone'])?$data['zone']:1; //订单分区 $this->saveLog("进入支付查询"); //开始 $model = new CodeModel(); $orderModel = new OrderModel(); $NotifyModel = new NotifyModel(); $array = $model->getList($zone); $this->saveLog("进入支付查询,分区:".$zone.",订单数量:".count($array)); //获取IP组 $iPGroup = $this->getIPGroup(); foreach ($array as $k=>$val) { $payTotal = $orderModel->getZoneOrderTotal([['code_id', '=', $val['code_id']],['order_status', '=', 20]]); $waitPayTotal = $orderModel->getZoneOrderTotal([['code_id', '=', $val['code_id']],['order_status', '=', 10]]); $modelOr = OrderModel::detail([['code_id', '=', $val['code_id']],['order_status', '=', 10]]); $text = $val['passageway'] == 'ZFBH5'?'订单已支付':'该订单已完成支付'; if (isset($modelOr['order_id']) && !empty($modelOr['order_id']) && $payTotal <= 0 && $waitPayTotal == 1) { if(isset($iPGroup[0]['ip'])) { $res = $this->checkStatusProxy(htmlspecialchars_decode($val['search_url']), $val['passageway'], $iPGroup[0]['ip'], $iPGroup[0]['port'], $text); }else { $res = $this->checkStatus(htmlspecialchars_decode($val['search_url']), $val['passageway'], $text); } if ($res !== false) { //支付成功 $pass = $modelOr['callback'] == 1 ? $NotifyModel->add($modelOr) : true; if ($pass) { $model->update([ 'order_id' => $modelOr['order_id'], 'pay_status' => 1, ],['code_id'=>$val['code_id']]); $modelOr->save([ 'pay_status' => 20, 'pay_time' => time(), 'order_status' => 20, ]); return $this->renderSuccess('操作成功'); } else { return $this->renderError($NotifyModel->getError() ?: '操作失败'); } } } } $this->saveLog("支付查询结束,分区:".$zone.",订单数量:".count($array)); } /** * 查询失效状态 * @throws \app\common\exception\BaseException * @throws \think\exception\DbException */ public function lapseStatus() { //连接本地的 Redis 服务 // 获取分布式锁 $lock_key = 'lapseStatus_lock'; $max_wait_time = 10; // 最大等待时间(秒) $retry_interval = 100; // 重试间隔时间(毫秒) $lock_ttl = 600; // 锁定时间(秒) $lock_count = 0; // 锁的重试次数 // 获取分布式锁 $redis = Cache::store('redis'); $lock = false; $wait_time = 0; $lock_value = ''; while ($wait_time <= $max_wait_time) { $lock_value = uniqid(); // 生成唯一的锁值 if ($redis->set($lock_key, $lock_value, 'EX', $lock_ttl, 'NX')) { $lock = true; break; } else { // 获取锁失败,等待一段时间后重试 $lock_count++; usleep($retry_interval * 1000); $wait_time += ($retry_interval / 1000); if ($lock_count > 40) { // 重试次数超过限制,抛出异常 $this->saveLog("失效查询重试次数超过限制"); } } } if($lock) { try { // TODO: 需要加锁的业务逻辑 // 字符串操作 $conn = new \think\cache\driver\Redis(config('cache.stores.redis')); $codesIp = $conn->get('codesIp'); $codesPort = $conn->get('codesPort'); $codeNum = $conn->get('codeNum'); if (empty($codesIp) || empty($codeNum) || $codeNum <= 0) { //获取IP组 $iPGroup = $this->getIPGroup(); $codesIp = isset($iPGroup[0]['ip']) ? $iPGroup[0]['ip'] : ''; $codesPort = isset($iPGroup[0]['port']) ? $iPGroup[0]['port'] : ''; $codeNum = 20; $conn->set('codesIp', $codesIp); $conn->set('codesPort', $codesPort); } $modelOr = new OrderModel(); $model = new CodeModel(); $this->saveLog("失效查询开始"); //非失效码的状态更新 $nonFailure = $model->getNonFailure(); foreach ($nonFailure as $val) { $codeM = CodeModel::detail($val['code_id']); $create_time = is_numeric($val['create_time']) ? $val['create_time'] : strtotime($val['create_time']); //支付码失效 if ($create_time + $val['expiration_time'] < time()) { $codeM->save([ 'status' => 3, ]); //分配时间过长未支付,订单超时,支付码释放 } elseif ($val['allot_time'] + 300 < time()) { $text = $val['passageway'] == 'ZFBH5' ? '订单已支付' : '该订单已完成支付'; if (!empty($codesIp)) { $conn->set('codeNum', $codeNum - 1); $res = $this->checkStatusProxy(htmlspecialchars_decode($val['search_url']), $val['passageway'], $codesIp, $codesPort, $text); } else { $res = $this->checkStatus(htmlspecialchars_decode($val['search_url']), $val['passageway'], $text); } $status = 1; $pay_status = 0; if ($res !== false) { //已支付 更改状态 $status = 3; $pay_status = 1; } else { //重新写入队列左边(为的是后进先出,失败的一般代表比较前面的码,所以放在左边) $code_queue = ['code_id' => (int)$val['code_id'], 'expire_time' => $create_time + (float)$val['expiration_time']]; // 过期时间 //先清楚队列再加入,避免重复 Cache::store('redis')->lRem($val['passageway'] . '_' . ($val['money'] * 1), json_encode($code_queue, 320), 1); Cache::store('redis')->lPush($val['passageway'] . '_' . ($val['money'] * 1), json_encode($code_queue, 320)); $this->saveLog("回码:".json_encode($code_queue, 320)); } $codeM->save([ 'allot_time' => 0, 'back_time' => time(), 'status' => $status, 'pay_status' => $pay_status, ]); } if ($val['code_id']) { $modelOr->update([ 'code_id' => 0, 'order_status' => 30, 'old_code_id' => $val['code_id'] ], ['code_id' => $val['code_id'], 'order_status' => 10]); } } //处理未匹配上码的订单为超时 $modelOr->update([ 'order_status' => 30, ], [['code_id', '=', 0], ['order_status', '=', 10], ['create_time', '<', time() - 120]]); $this->saveLog("失效查询结束"); } finally { // 释放锁 $lock_value_in_redis = $redis->get($lock_key); if ($lock_value_in_redis === $lock_value) { // 如果当前锁的值和刚才设置的值相等,则说明当前线程持有该锁,可以进行释放 $redis->del($lock_key); // 删除锁 } } } //未使用超过调起次数的支付码 /*$modelNew = new CodeModel(); $modelNew->setStatusLose([],['status' => array('=', 1), 'num' => array('>=', 100)]);*/ } public function getIPGroup(): array { $html = @file_get_contents("http://http.tiqu.letecs.com/getip3?num=1&type=2&pro=&city=0&yys=0&port=1&time=1&ts=0&ys=0&cs=0&lb=1&sb=0&pb=4&mr=1®ions="); $result = json_decode($html,true); $ips = is_array($result['data'])?$result['data']:[]; return $ips; } public function test(){ $data = $this->request->get(); $queue_name = $data['passageway'].'_'.$data['money']; $queue_length = Cache::store('redis')->lLen($queue_name); var_dump($queue_length); // 获取队列中的所有值 $queue_values = Cache::store('redis')->lRange($queue_name, 0, $queue_length - 1); var_dump($queue_values); die(); /*return $this->renderSuccess([], '提交成功'); $html = @file_get_contents("http://http.tiqu.letecs.com/getip3?num=1&type=2&pro=&city=0&yys=0&port=1&pack=301270&ts=0&ys=0&cs=0&lb=1&sb=0&pb=4&mr=1®ions="); $result = json_decode($html,true); $ips = $result['data']; var_dump(date("Y-m-d H:i:s"));*/ $data = $this->request->get(); $order_id = $data['order_id']; var_dump($this->checkQueue($order_id));die(); $val = OrderModel::detail($order_id); $codesIp = ''; $codesPort = ''; //获取IP组 $iPGroup = $this->getIPGroup(); var_dump($iPGroup); $codesIp = isset($iPGroup[0]['ip'])?$iPGroup[0]['ip']:''; $codesPort = isset($iPGroup[0]['port'])?$iPGroup[0]['port']:''; /*$aa = str_replace("&", "%26", $val['search_url']); $bb = str_replace("amp;", "", $aa); $stream_opts = [ "ssl" => [ "verify_peer" => false, "verify_peer_name" => false, ] ]; $html = @file_get_contents(urldecode($bb), false, stream_context_create($stream_opts)); $res = strpos($html, '该订单已完成支付');*/ $text = $val['passageway'] == 'ZFBH5'?'订单已支付':'该订单已完成支付'; /*if(!empty($codesIp)) { $res = $this->checkStatusProxy(htmlspecialchars_decode($val['search_url']), $val['passageway'], $codesIp, $codesPort, $text); }else { $res = $this->checkStatus(htmlspecialchars_decode($val['search_url']), $val['passageway'], $text); }*/ $res = $this->checkStatusProxy(htmlspecialchars_decode($val['search_url']), $val['passageway'], $codesIp, $codesPort, $text); //$res = $this->checkStatus(htmlspecialchars_decode($val['search_url'])); var_dump($res); var_dump(date("Y-m-d H:i:s")); } /** * 插入队列判断当前是否可执行 * @param $code_id * @return */ public function checkQueue($code_id) { $message = Cache::store('redis')->lPop("queue:$code_id"); // 如果队列为空,则写入队列 if ($message === null || $message === false) { // 消息内容 $message = 1; // 监控客户的消息 Hash Cache::store('redis')->watch("code:$code_id"); // 开始事务 Cache::store('redis')->multi(); // 将消息添加到队列末尾 Cache::store('redis')->rPush("queue:$code_id", $message); // 提交事务 $result = Cache::store('redis')->exec(); if ($result !== null && $result !== false) { return true; } } return false; } public function checkStatus($url,$passageway,$text='该订单已完成支付'){ if(in_array($passageway,['WX','SNZFBH5'])){ $ch = curl_init(); curl_setopt ($ch, CURLOPT_URL, $url); curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT,10); curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt ($ch, CURLOPT_MAXREDIRS, 3); $content = curl_exec($ch); }else { $publicPath = app()->getRootPath() . 'public/'; $escaped_url = escapeshellarg($url); $command = "node {$publicPath}myscript.js {$escaped_url} 2>&1"; $content = shell_exec($command); } //$res = strpos((string)$content,$text); return (string)$content; } public function checkStatusProxy($targetUrl,$passageway,$ip,$port,$text='该订单已完成支付'){ if(in_array($passageway,['WX','SNZFBH5'])){ // 代理服务器 $proxyServer = "http://" . $ip . ":" . $port; // 隧道身份信息 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $targetUrl); curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 设置代理服务器 //curl_setopt($ch, CURLOPT_PROXYTYPE, 0); //http curl_setopt($ch, CURLOPT_PROXYTYPE, 5); //sock5 curl_setopt($ch, CURLOPT_PROXY, $proxyServer); // 设置隧道验证信息 curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727;)"); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); curl_setopt($ch, CURLOPT_TIMEOUT, 5); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); }else{ $publicPath = app()->getRootPath() . 'public/'; $escaped_url = escapeshellarg($targetUrl); //$command = "node {$publicPath}myscript_proxy.js {$escaped_url} {$ip} {$port} 2>&1"; $command = "node {$publicPath}myscript.js {$escaped_url} 2>&1"; $result = shell_exec($command); } $res = strpos((string)$result,$text); return $res; } public function jysx(){ $res = $this->checkStatus('https://t-h5.einrkv.com/qAvYzu2Uraqq/ak7X8TC8Vq6TCkRvtlHzJZE2%252FeIBI0HjIVvQc0IIwXhcng%252FC2I3D0bicsYRpOpUqmtASUp6v7MiNCtPpPPV%2BKgeNvxF%2BQ%252FEXaPT886oB785uqOkFAlj6KDk8hEzwLQ4pjMuZ1xnPeU5fljvX84bwZPx9J%2Bb7nMmsvF2yTahh6FU%3D', 'zfb'); var_dump($res); return; $this->saveLog("码校验查询开始"); //开始 $model = new CodeModel(); //连接本地的 Redis 服务 $conn = new \Redis(); // 实例化 $conn->connect('127.0.0.1','6379'); $codepage = $conn->get('codepagejy'); $codepage = empty($codepage)?1:$codepage; $array = $model->getSx($codepage); $this->saveLog("进入码校验查询,页码:".$codepage.",码数:".count($array)); //获取IP组 $iPGroup = $this->getIPGroup(); foreach ($array as $k=>$val) { $text = $val['passageway'] == 'ZFBH5'?'订单已支付':'该订单已完成支付'; if(isset($iPGroup[0]['ip'])) { $res = $this->checkStatusProxy(htmlspecialchars_decode($val['search_url']), $iPGroup[0]['ip'], $iPGroup[0]['port'], $text); }else { $res = $this->checkStatus(htmlspecialchars_decode($val['search_url']), $text); } if ($res !== false) { //支付成功 $model->update([ 'status' => 3, ],['code_id'=>$val['code_id']]); } } if(count($array) < 20){ $conn->set('codepagejy',1); }else{ $conn->set('codepagejy',$codepage+1); } $this->saveLog("码校验查询结束"); } }