cookie = $this->_cookie(); $this->os = $os; } /** * @throws ModelNotFoundException * @throws DataNotFoundException * @throws DbException * @throws Exception */ public function get($page, $start = null, $end = null, $orderId = '') { if (empty($start) || empty($end)) { $start = date('Y-m-d 00:00:00'); $end = date('Y-m-d 23:59:59'); } if ($orderId) { $start = $end = null; } $_list = []; $list = $this->_certificateList($start, $end, $page, $orderId); if (empty($list->data->list) || $list->code != 1000) { $this->totalPage = 1; return $_list; } Log::info($start . '--' . $end . " 同程 page:{$page} total_count:" . $list->data->totalCount . " page_count:" . $list->data->totalPage); foreach ($list->data->list as $order) { Log::info("同程 订单号:{$order->orderId} \n\n"); // $orderDetail = $this->_orderDetail($order->order_id, $order->status); // if (empty($orderDetail)) { // Log::info("同程 订单详情拉取失败:{$order->order_id}"); // throw new Exception("同程拉单{$order->order_id}详情获取失败"); // } $item = new Orders(); $item->os = $this->os; $item->sn = $order->orderId; $item->product_id = $order->lineId; $item->product_name = $order->title; $item->category_id = $order->agentFinderId; $item->category_desc = $order->agentFinderNickname; $item->create_at = strtotime($order->createTime) * 1000; $item->mobile = $order->mobile; $item->travel_date = null; $item->total_price = $order->orderAmount; $item->quantity = $order->productCount; $item->unit_price = $order->orderAmount/$order->productCount; $item->actual_price = $order->orderAmount; $item->order_status = $order->orderStatus; // 订单状态(100 - 待付款,150-用户取消,200-待使用,205-预约中(抖音),210-已预约(抖音),310-已履约(抖音),300-已完成,400-已关闭) $item->appointment_status = in_array($order->orderStatus, [210, 310, 300]) ? 1 : 0; // 是否已预约 $item->asset_status = $order->status ?? 0; // 核销状态 $item->asset_price = $order->orderStatus == 300 ? $order->orderAmount : 0; // 核销金额 //抖音的核销金额需要查核销订单 $_list[] = $item; } return $_list; //返回半成品 } /** * @throws Exception */ public function _certificateList($start, $end, $page, $orderId) { $params = [ 'begin' => $start,// 开始时间,格式:2024-09-01 00:00:00 'end' => $end,// 结束时间 格式:2024-09-01 00:00:00 'orderStatus' => "",// 订单状态 'current' => $page, // 当前数量 'size' => 50, // 显示数量 'orderId' => $orderId, // 订单号 ]; $this->_getRetriesLock(); $list = $this->_curl("/merchant/api/market-live/wxVideoV2/supplierOrderByPage", $params, 'POST'); if (empty($list) || $list->code != 1000) { $this->_getRetriesLock(); $list = $this->_curl("/merchant/api/market-live/wxVideoV2/supplierOrderByPage", $params, 'POST'); if (empty($list) || $list->status_code != 0) { Log::error('===查询时间:' . $start . '--' . $end . '====certificate_list: ' . json_encode($list)); throw new Exception("同程拉单失败,Err:" . json_encode($list)); } } return $list; } public function _getRetriesLock(): bool { $pattern = 'php webman spider:tc'; $maxProcesses = 8; $this->_killMiddleProcesses($pattern, $maxProcesses); $maxRetries = 5; $retries = 0; while ($retries < $maxRetries) { $back = Redis::set('SpiderTc:CertificateList:lock', time(), 'EX', 1, 'NX'); if ($back) { break; } $retries++; sleep(1); } return true; } public function _killMiddleProcesses($pattern, $maxProcesses) { // 检查操作系统是否为 Linux if (PHP_OS !== 'Linux') { echo "当前系统不是 Linux,跳过执行。\n"; return false; } // 获取当前进程的 PID $currentPid = getmypid(); // 获取进程列表 $command = "ps aux | grep '$pattern' | grep -v grep | grep -v '$currentPid' | awk '{print $2,$10}'"; $output = []; exec($command, $output); $processList = []; foreach ($output as $line) { list($pid, $runtime) = explode(' ', $line); $runtimeParts = explode(':', $runtime); $hours = intval($runtimeParts[0]); $minutes = intval($runtimeParts[1]); $totalMinutes = $hours * 60 + $minutes; $processList[] = ['pid' => $pid, 'runtime' => $totalMinutes]; } // 按运行时间排序 usort($processList, function ($a, $b) { return $a['runtime'] <=> $b['runtime']; }); // 过滤掉当前 PID 和运行时间最短的 PID if (!empty($processList)) { array_shift($processList); // 移除运行时间最短的 } $processList = array_filter($processList, function ($process) use ($currentPid) { return $process['pid'] != $currentPid; }); $processCount = count($processList); // 如果进程数量超过最大值,则终止中间部分的进程 if ($processCount > $maxProcesses) { $processesToKill = $processCount - $maxProcesses; $processesKilled = 0; // 杀死运行时间最短的进程 foreach ($processList as $process) { if ($processesKilled >= $processesToKill) { break; } exec("kill -9 {$process['pid']}"); $processesKilled++; echo "Killed process with PID: {$process['pid']}\n"; } if ($processesKilled === 0) { echo "No processes to kill.\n"; } } else { echo "Process count is not over the limit.\n"; } return true; } public function _orderDetail($orderId = null, $status = null) { $orderDetail = $this->_curl("/life/trip/v2/travel_agency/book/detail", [ 'order_id' => $orderId, 'book_order_id' => '', 'status' => $status, ]); return $orderDetail->data ?? null; } public function _cookie($data = '') { $key = 'Tongcheng:cookie-' . $this->os; if ($data) Redis::set($key, $data); return Redis::get($key); } public function _curl($url, $params, $method = 'GET') { // 请求次数统计 $key = sprintf("Tongcheng:ReqiestCount:%s", date('Y-m-d')); $requestCount = Redis::incrBy($key, 1); if ($requestCount == 1) { Redis::expire($key, 604800); // 7天 } $cookiePath = sprintf(runtime_path() . '%s_tc_cookie.txt', $this->os); $http = $this->gate . $url; if ($method == 'GET') { $http = $http . '?' . http_build_query($params); } $ch = curl_init($http); $header = [ 'Content-type: application/json' ]; if ($method == 'GET' || $method == 'POST') { curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); } else { curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_COOKIEJAR, $cookiePath); } curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_COOKIE, $this->_cookie()); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_COOKIEFILE, $cookiePath); if ($method == 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); } $body = curl_exec($ch); curl_close($ch); if (!in_array($method, ['GET', 'POST'])) { $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header = substr($body, 0, $header_size); $in = explode("\n", $body); foreach ($in as $i) { if (stripos($i, 'x-ware-csrf-token') !== false) { $inn = explode(',', $i); return $inn[1]; } } return null; } return json_decode($body); } }