<?php /** * Created by PhpStorm. * User: zhl * Date: 2023/09/02 * Time: 10:36 */ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\SyncSubmitTask\SyncSubmitTask; use App\Models\WebSetting\WebSetting; use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Validator; use ZipArchive; /** * Class NoticeController * @package App\Http\Controllers\Api */ class NoticeController extends Controller { const SUCCESS = 200; const ERROR = 400; const SERVERERROR = 500; const FORBBIDEN = 401; const TYPEVISIT = "visit"; const TYPEINQUIRY = "inquiry"; const TRAFFICZERO = 0; const TRAFFICONE = 1; const DEVICE_PORT_ONE = 1; const DEVICE_PORT_TWO = 2; protected $header = [];//设置请求头参数 /** * @param array $data * @param string $message * @param int $status * @return string */ protected function success($data = [], $message = 'success', $status = 200) { $array = compact('status', 'message', 'data'); return json_encode($array, JSON_UNESCAPED_UNICODE); } /** * @param int $status * @param string $message * @param array $data * @return string */ protected function error($message = 'error', $status = 400, $data = []) { $array = compact('status', 'message', 'data'); return json_encode($array, JSON_UNESCAPED_UNICODE); } /** * 响应 * @param string $msg * @param int $code * @param array $data * @param int $result_code * @param string $type * @return void */ public function response($msg = 'success', $code = self::SUCCESS, $data = [], $result_code = 200, $type = 'application/json') { $result = [ 'msg' => $msg, 'code' => $code, 'data' => $data, ]; $this->header['Content-Type'] = $type; $response = response($result, $result_code, $this->header); throw new HttpResponseException($response); } /** * GET请求 * @param $url * @return mixed */ public function curlGet($url) { $ch1 = curl_init(); $timeout = 0; curl_setopt($ch1, CURLOPT_URL, $url); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch1, CURLOPT_ENCODING, ''); curl_setopt($ch1, CURLOPT_MAXREDIRS, 10); curl_setopt($ch1, CURLOPT_HTTPHEADER, array()); curl_setopt($ch1, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch1, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch1, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch1, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch1, CURLOPT_CUSTOMREQUEST, "GET"); curl_setopt($ch1, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); $access_txt = curl_exec($ch1); curl_close($ch1); return json_decode($access_txt, true); } /** * 发送http post请求 * @param $url * @param $data * @param array $header * @param bool $is_json * @return mixed|string */ function httpPost($url, $data, $header = [], $is_json = true) { if (empty($header)) { $header = array( "Accept: application/json", "Content-Type:application/json;charset=utf-8", "token:" . env("SECRET_TOKEN"), "pid:" . env("MERCHANT_NUMBER") ); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)'); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); if (curl_errno($ch)) { $error_message = curl_error($ch); @file_put_contents(storage_path('logs/error.log'), var_export($error_message, true) . PHP_EOL, FILE_APPEND); } curl_close($ch); if ($is_json) { return json_decode($res, true); } return trim($res); } /** * A端上传验证代码 * @param Request $request * @return string */ public function uploadVerifyFile(Request $request) { $domain = $request->getHost(); $files = $request->allFiles(); $file_name = ''; foreach ($files as $file) { $destinationPath = public_path($domain); $file->move($destinationPath, $file->getClientOriginalName()); $filePath = $destinationPath . '/' . $file->getClientOriginalName(); @file_put_contents(storage_path('logs/upload_file.log'), date('Y-m-d H:i:s') . " " . $filePath . PHP_EOL, FILE_APPEND); $file_name = $file->getClientOriginalName(); } if ($file_name) { $transmitUrl = env("TRANSMIT_URL"); $this->httpPost($transmitUrl . "api/selfSiteVerify/", json_encode(['file_name' => $file_name, 'type' => 1])); } return $this->success(); } /** * A端上传amp站验证代码 * @param Request $request * @return string */ public function uploadAmpVerifyFile(Request $request) { $domain = $request->getHost(); $domain_array = parse_url($domain); $host = $domain_array['host'] ? $domain_array['path'] : ""; $host_array = explode('.', $host); if (count($host_array) <= 2) { array_unshift($host_array, 'm'); } else { $host_array[0] = 'm'; } $amp_domain = implode('.', $host_array); $files = $request->allFiles(); $file_name = ''; foreach ($files as $file) { $destinationPath = public_path($amp_domain); $file->move($destinationPath, $file->getClientOriginalName()); $filePath = $destinationPath . '/' . $file->getClientOriginalName(); @file_put_contents(storage_path('logs/upload_file.log'), date('Y-m-d H:i:s') . " " . $filePath . PHP_EOL, FILE_APPEND); $file_name = $file->getClientOriginalName(); } if ($file_name) { $transmitUrl = env("TRANSMIT_URL"); $this->httpPost($transmitUrl . "api/selfSiteVerify/", json_encode(['file_name' => $file_name, 'type' => 2])); } return $this->success(); } /** * 获取需要下载的文件url * @param Request $request * @return string */ public function websiteHtml(Request $request) { $domain = $request->getHost(); $site_token = $request->input('site_token'); $zip_count = $request->input('zip_count'); $token = env("SECRET_TOKEN"); $pid = env("MERCHANT_NUMBER"); $apiUrl = env("API_URL"); if (empty($site_token)) { return $this->error('请求无效'); } if ($site_token != $token) { return $this->error('请求无效'); } $requestUrl = $apiUrl . "api/get_url_verify_token/?pid=" . $pid . "&token=" . $token . "&domain=" . $domain; @file_put_contents(storage_path('logs/notify_get_url.log'), date('Y-m-d H:i:s') . "接收到通知:" . $requestUrl . PHP_EOL, FILE_APPEND); try { $res = $this->curlGet($requestUrl); if ($res["status"] != self::SUCCESS) { $msg = isset($res["message"]) && !empty($res["message"]) ? $res["message"] : "请求失败!"; return $this->error($msg); } } catch (\Exception $e) { return $this->error($e->getMessage()); } $info = [ "domain" => $domain, "zip_count" => $zip_count ]; $info = json_encode($info); Redis::lpush('handle_html', $info); return $this->success(); } /** * 文案 * @param Request $request */ public function getRandInquiryText(Request $request) { //IP限流+验证参数 $this->limitIp($request); try { $requestUrl = "https://fob.ai.cc/api/get_inquiry_text"; $resStr = $this->httpGet($requestUrl); } catch (\Exception $e) { $resStr = $e->getMessage(); } $this->response("ok", self::SUCCESS, $resStr); return; } /** * 拉代码 * @param Request $request * @return string */ public function pullCode(Request $request) { $command = "cd /www/wwwroot/self_site && ./pull_custom_code.sh"; shell_exec($command); return $this->success(); } /** * @param $url * @return bool|string */ public function httpGet($url) { $ch1 = curl_init(); $timeout = 0; curl_setopt($ch1, CURLOPT_URL, $url); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch1, CURLOPT_ENCODING, ''); curl_setopt($ch1, CURLOPT_MAXREDIRS, 10); curl_setopt($ch1, CURLOPT_HTTPHEADER, array()); curl_setopt($ch1, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch1, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch1, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch1, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch1, CURLOPT_CUSTOMREQUEST, 'GET'); curl_setopt($ch1, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); $access_txt = curl_exec($ch1); curl_close($ch1); return $access_txt; } /** * IP限流 * @param $request */ public function limitIp($request) { //同一ip 2秒内超过6次 限制1分钟 $ip = $request->ip(); $ip_limit_key = 'ip_limit_' . $ip; $num_limit_key = 'num_limit_' . $ip; if (Cache::has($ip_limit_key)) { $this->response("error", self::FORBBIDEN, "请求频繁,请稍后再试"); } if (Cache::has($num_limit_key)) { $count = Cache::get($num_limit_key); if ($count >= 6) { Cache::put($ip_limit_key, 1, 60); $this->response("error", self::FORBBIDEN, "请求频繁,请稍后再试"); } Cache::increment($num_limit_key); } else { Cache::put($num_limit_key, 1, 2); } } /** * 网站html解压 * @param $zip_count * @param $domain */ public function websiteHtmlHandle($zip_count, $domain) { $api_url = env('API_URL'); for ($i = 0; $i <= $zip_count; $i++) { $targetFile = $this->downLoadFile($api_url . $domain . '_part' . $i . '.zip'); if ($targetFile == "") { $this->output('文件 ' . $targetFile . ' 不存在'); continue; } $zip = new ZipArchive(); if ($zip->open($targetFile) === TRUE) { $outputFolder = public_path($domain); if (!is_dir($outputFolder)) { mkdir($outputFolder, 0777, true); } // 解压缩文件,保留原文件结构 $zip->extractTo($outputFolder); $zip->close(); $this->deleteDirectory($targetFile); } else { // 处理打开压缩文件失败的情况 $this->output('解压文件 ' . $targetFile . ' 失败'); continue; } } $transmitUrl = env("TRANSMIT_URL"); $this->httpPost($transmitUrl . "api/selfSiteNotify/", json_encode(['domain' => $domain])); } /** * 下载文件(下载到public目录下) * @param $url * @return string */ public function downLoadFile($url) { // 创建一个流上下文,设置 SSL 选项 $context = stream_context_create([ "ssl" => [ "verify_peer" => false, // 不验证对等证书 "verify_peer_name" => false, // 不验证对等证书名称 ], ]); $savePath = public_path(); if (!file_exists($savePath)) { mkdir($savePath, 0777, true); } $targetFile = $savePath . '/' . basename($url); if (!file_exists($targetFile)) { $file = fopen($targetFile, 'w', false, $context); fclose($file); chmod($targetFile, 0755); } $remoteFile = fopen($url, 'rb', false, $context); $localFile = fopen($targetFile, 'wb', false, $context); if ($remoteFile && $localFile) { while (!feof($remoteFile)) { fwrite($localFile, fread($remoteFile, 1024 * 8), 1024 * 8); } fclose($remoteFile); fclose($localFile); return $targetFile; } else { return ""; } // $context = stream_context_create([ // 'http' => [ // 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3", // ], // ]); // $fileContent = @file_get_contents($url, false, $context); // if ($fileContent !== false) { // file_put_contents($targetFile, $fileContent); // return $targetFile; // } else { // return ""; // } } /** * 删除文件 * @param $path * @return bool */ public function deleteDirectory($path) { if (!is_dir($path)) { try { unlink($path); } catch (\Exception $e) { return true; } return true; } $files = array_diff(scandir($path), array('.', '..')); foreach ($files as $file) { $filePath = $path . '/' . $file; if (is_dir($filePath)) { $this->deleteDirectory($filePath); } else { try { unlink($filePath); } catch (\Exception $e) { return true; } } } rmdir($path); return true; } /** * 提交 * @param $request * @param string $type * @param int $traffic * @return string|void */ public function transmit($request, $type = self::TYPEVISIT, $traffic = self::TRAFFICZERO) { if ($request->getClientIp() == "127.0.0.1") { $this->response(); } //判断是否是爬虫 $isReptile = $this->isReptile($request); if ($isReptile) { $this->response(); } $data = $request->all(); if (empty($data)) { $this->response(); } $data["device_port"] = $this->userAgentHandle($request->userAgent(), $data["device_port"] ?? self::DEVICE_PORT_ONE); $req["data"] = $data; $referrer_url = $data["referrer_url"] ?? $request->header('Referer'); if ($type == self::TYPEVISIT) { $referrer_url = $this->visitInfoHandle($referrer_url); } $req["referer"] = $referrer_url; $req["domain"] = $request->getHost(); $req["ip"] = $request->getClientIp(); $req["user_agent"] = $request->userAgent(); $req["files"] = isset($data["files"]) ? $data["files"] : null; $req["type"] = $type; $req["traffic"] = $traffic; $req["timestamp"] = time(); //转发接口 $transmitUrl = env("TRANSMIT_URL"); $resp = $this->httpPost($transmitUrl . "api/selfSiteApi/", json_encode($req)); if (($resp["status"] ?? 0) != self::SUCCESS) { $dataJson = json_encode($req); if (!file_exists(storage_path('logs/fail_req'))) { mkdir(storage_path('logs/fail_req', 0777, true)); } @file_put_contents(storage_path('logs/fail_req/' . date('Y-m-d') . '.log'), var_export($dataJson, true) . PHP_EOL, FILE_APPEND); } $this->response("success", self::SUCCESS, $resp["data"] ?? []); } /** * 处理user_agent * @param $user_agent * @param $device_port * @return int */ public function userAgentHandle($user_agent, $device_port) { if (!in_array($device_port, [self::DEVICE_PORT_ONE, self::DEVICE_PORT_TWO])) { $device_port = self::DEVICE_PORT_ONE; } // 头信息中带有这些信息, 代表是手机端, 重置设备类型 if (preg_match('/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|wap|windowsce|ucweb/', $user_agent)) { $device_port = self::DEVICE_PORT_TWO; } return $device_port; } /** * 客户访问埋点接口 * @param Request $request * @return string */ public function customerVisit(Request $request) { return $this->transmit($request); } /** * 埋点信息处理 * @param $referrerUrl * @return string */ public function visitInfoHandle($referrerUrl) { if (preg_match('/google|facebook|bing|yahoo|youtobe|linkedin|messefrankfurt|yandex|tiktok|twitter|instagram|reddit|telegram|pinterest|tumblr/', $referrerUrl)) { } else if ($referrerUrl == null) { //直访用户 $referrerUrl = ""; } else { $referrerUrl = "https://www.google.com/"; } return $referrerUrl; } /** * 是否是爬虫访问 * @param $request * @return bool */ public function isReptile($request) { $agent = $request->header('User-Agent'); if (!empty($agent)) { $spiderSite = array( "TencentTraveler", "Baiduspider+", "BaiduGame", "Googlebot", "msnbot", "Sosospider+", "Sogou web spider", "ia_archiver", "Yahoo! Slurp", "YoudaoBot", "Yahoo Slurp", "MSNBot", "Java (Often spam bot)", "BaiDuSpider", "Voila", "Yandex bot", "BSpider", "twiceler", "Sogou Spider", "Speedy Spider", "Google AdSense", "Heritrix", "Python-urllib", "Alexa (IA Archiver)", "Ask", "Exabot", "Custo", "OutfoxBot/YodaoBot", "yacy", "SurveyBot", "legs", "lwp-trivial", "Nutch", "StackRambler", "The web archive (IA Archiver)", "Perl tool", "MJ12bot", "Netcraft", "MSIECrawler", "WGet tools", "larbin", "Fish search", "yandex.com/bots", "google.com/bot", "bingbot", "YandexMobileBot", "BingPreview", "AhrefsBot", "bot" ); $flag = 0; foreach ($spiderSite as $val) { $str = strtolower($val); if (strpos($agent, $str) !== false) { $flag = 1; } } if ($flag == 1) { return true; } else { return false; } } else { return false; } } /** * 接口 * @param Request $request * @return string */ public function trafficVisit(Request $request) { return $this->transmit($request, self::TYPEVISIT, self::TRAFFICONE); } /** * C端表单提交 询盘信息 * @param Request $request * @return string */ public function inquiry(Request $request) { $data = $request->all(); $black_ips = ['203.86.233.27', '37.19.221.202']; if (in_array(request()->getClientIp(), $black_ips)) { $this->success(); } //同一ip 5秒内超过5次 限制1小时 $ip = $request->ip(); $lock_key = 'lock_ip_' . $ip; $rate_key = 'rate_limit:' . $ip; if (Cache::has($lock_key)) { $this->success(); } if (Cache::has($rate_key)) { $count = Cache::get($rate_key); if ($count >= 5) { Cache::put($lock_key, 1, 3600); $this->success(); } Cache::increment($rate_key); } else { Cache::put($rate_key, 1, 5); } $data["files"] = $request->allFiles(); return $this->transmit($request, self::TYPEINQUIRY, self::TRAFFICZERO); } /** * 收集邮箱或者手机号等其他信息 * @param Request $request * @return string */ public function inquiryOtherInfo(Request $request) { return $this->transmit($request, self::TYPEINQUIRY); } /** * 起点表单询盘 * @param Request $request * @return string */ public function inquiryQd(Request $request) { return $this->transmit($request, self::TYPEINQUIRY); } /** * 搜索 * @param Request $request * @return mixed|string */ public function search(Request $request) { $data = $request->all(); //获取搜索参数 $data["search_content"] = ''; if (isset($data['s'])) { $req["search_content"] = $data['s']; } if (isset($data['search'])) { $req["search_content"] = $data['search']; } //分页 $req["page"] = isset($data['page']) && (int)$data['page'] > 1 ? (int)$data['page'] : 1; $req["domain"] = $request->getHost(); //转发data $transmitUrl = env("API_URL"); $resp = $this->httpPost($transmitUrl . "api/get_search_content/", json_encode($req)); return $resp["data"]["html"]; } /** * 生成robots.txt * @param Request $request */ public function updateRobots(Request $request){ $req["domain"] = $request->getHost(); //转发data $transmitUrl = env("API_URL"); $resp = $this->httpPost($transmitUrl . "api/get_robots_content/", json_encode($req)); $robotsContent = $resp["data"]["content"]; $robotTxtPath = public_path($req["domain"]."/robots.txt"); $this->putSitemapFile($robotTxtPath,$robotsContent); $this->response("success", self::SUCCESS, $resp["data"] ?? []); } /** * 放内容 * @param $sitemapPath * @param $sitemapXmlDom * @return false|int */ public function putSitemapFile($sitemapPath, $sitemapXmlDom) { if (file_exists($sitemapPath)) { $this->deleteDirectory($sitemapPath); } $res = file_put_contents($sitemapPath, $sitemapXmlDom); try { chmod($sitemapPath, 0777); } catch (\Exception $e) { return 1; } return $res; } /** * 输出处理日志 * @param $message * @return bool */ public function output($message) { echo date('Y-m-d H:i:s') . ' | ' . $message . PHP_EOL; return true; } }