diff --git a/app/Models/Node.php b/app/Models/Node.php index 98dd9105..d95c24c1 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -115,7 +115,7 @@ class Node extends Model if ($ip !== []) { $data = IP::getIPGeo($ip[0]); // 复数IP都以第一个为准 - if ($data !== null) { + if ($data) { self::withoutEvents(function () use ($data) { $this->update(['geo' => ($data['latitude'] ?? null).','.($data['longitude'] ?? null)]); }); diff --git a/app/Utils/CurrencyExchange.php b/app/Utils/CurrencyExchange.php index ffb5d0c1..09fd66ac 100644 --- a/app/Utils/CurrencyExchange.php +++ b/app/Utils/CurrencyExchange.php @@ -3,54 +3,67 @@ namespace App\Utils; use Cache; +use Exception; use Http; +use Illuminate\Http\Client\PendingRequest; use Log; class CurrencyExchange { + private static PendingRequest $basicRequest; + /** * @param string $target target Currency * @param float|int $amount exchange amount - * @param string $base Base Currency + * @param string|null $base Base Currency * @return float|null amount in target currency */ - public static function convert(string $target, float|int $amount, string $base = 'default'): ?float + public static function convert(string $target, float|int $amount, string $base = null): ?float { - if ($base === 'default') { - $base = sysConfig('standard_currency'); + if ($base === null) { + $base = (string) sysConfig('standard_currency'); } $cacheKey = "Currency_{$base}_{$target}_ExRate"; - $isStored = Cache::has($cacheKey); - if ($isStored) { + if (Cache::has($cacheKey)) { return round($amount * Cache::get($cacheKey), 2); } - $source = 0; - $rate = null; - while ($source <= 7 && $rate === null) { - $rate = match ($source) { - 0 => self::exchangerateApi($base, $target), - 1 => self::k780($base, $target), - 2 => self::it120($base, $target), - 3 => self::exchangerate($base, $target), - 4 => self::fixer($base, $target), - 5 => self::currencyData($base, $target), - 6 => self::exchangeRatesData($base, $target), - 7 => self::jsdelivrFile($base, $target), - }; - $source++; - } + $apis = ['exchangerateApi', 'k780', 'it120', 'exchangerate', 'fixer', 'currencyData', 'exchangeRatesData', 'jsdelivrFile']; + self::$basicRequest = Http::timeout(15)->withOptions(['http_errors' => false])->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); - if ($rate !== null) { - Cache::put($cacheKey, $rate, Day); + foreach ($apis as $api) { + try { + $rate = self::callApis($api, $base, $target); + if ($rate !== null) { + Cache::put($cacheKey, $rate, Day); - return round($amount * $rate, 2); + return round($amount * $rate, 2); + } + } catch (Exception $e) { + Log::error("[$api] ֻϢȡ: ".$e->getMessage()); + + continue; + } } return null; } + private static function callApis(string $api, string $base, string $target): ?float + { + return match ($api) { + 'exchangerateApi' => self::exchangerateApi($base, $target), + 'k780' => self::k780($base, $target), + 'it120' => self::it120($base, $target), + 'exchangerate' => self::exchangerate($base, $target), + 'fixer' => self::fixer($base, $target), + 'currencyData' => self::currencyData($base, $target), + 'exchangeRatesData' => self::exchangeRatesData($base, $target), + 'jsdelivrFile' => self::jsdelivrFile($base, $target), + }; + } + private static function exchangerateApi(string $base, string $target): ?float { // Reference: https://www.exchangerate-api.com/docs/php-currency-api $key = config('services.currency.exchangerate-api_key'); @@ -59,7 +72,7 @@ class CurrencyExchange } else { $url = "https://open.er-api.com/v6/latest/$base"; } - $response = Http::get($url); + $response = self::$basicRequest->get($url); if ($response->ok()) { $data = $response->json(); @@ -76,7 +89,7 @@ class CurrencyExchange private static function k780(string $base, string $target): ?float { // Reference: https://www.nowapi.com/api/finance.rate - $response = Http::get("https://sapi.k780.com/?app=finance.rate&scur=$base&tcur=$target&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"); + $response = self::$basicRequest->get("https://sapi.k780.com/?app=finance.rate&scur=$base&tcur=$target&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"); if ($response->ok()) { $data = $response->json(); @@ -93,7 +106,7 @@ class CurrencyExchange private static function it120(string $base, string $target): ?float { // Reference: https://www.it120.cc/help/fnun8g.html - $response = Http::get("https://api.it120.cc/gooking/forex/rate?fromCode=$target&toCode=$base"); + $response = self::$basicRequest->get("https://api.it120.cc/gooking/forex/rate?fromCode=$target&toCode=$base"); if ($response->ok()) { $data = $response->json(); @@ -110,7 +123,7 @@ class CurrencyExchange private static function exchangerate(string $base, string $target): ?float { // Reference: https://exchangerate.host/#/ - $response = Http::get("https://api.exchangerate.host/latest?base=$base&symbols=$target"); + $response = self::$basicRequest->get("https://api.exchangerate.host/latest?base=$base&symbols=$target"); if ($response->ok()) { $data = $response->json(); @@ -128,7 +141,7 @@ class CurrencyExchange { // Reference: https://apilayer.com/marketplace/fixer-api RATE LIMIT: 100 Requests / Monthly!!!! $key = config('services.currency.apiLayer_key'); if ($key) { - $response = Http::withHeaders(['apikey' => $key])->get("https://api.apilayer.com/fixer/latest?symbols=$target&base=$base"); + $response = self::$basicRequest->withHeaders(['apikey' => $key])->get("https://api.apilayer.com/fixer/latest?symbols=$target&base=$base"); if ($response->ok()) { $data = $response->json(); @@ -149,7 +162,7 @@ class CurrencyExchange { // Reference: https://apilayer.com/marketplace/currency_data-api RATE LIMIT: 100 Requests / Monthly $key = config('services.currency.apiLayer_key'); if ($key) { - $response = Http::withHeaders(['apikey' => $key])->get("https://api.apilayer.com/currency_data/live?source=$base¤cies=$target"); + $response = self::$basicRequest->withHeaders(['apikey' => $key])->get("https://api.apilayer.com/currency_data/live?source=$base¤cies=$target"); if ($response->ok()) { $data = $response->json(); @@ -170,7 +183,7 @@ class CurrencyExchange { // Reference: https://apilayer.com/marketplace/exchangerates_data-api RATE LIMIT: 250 Requests / Monthly $key = config('services.currency.apiLayer_key'); if ($key) { - $response = Http::withHeaders(['apikey' => $key])->get("https://api.apilayer.com/exchangerates_data/latest?symbols=$target&base=$base"); + $response = self::$basicRequest->withHeaders(['apikey' => $key])->get("https://api.apilayer.com/exchangerates_data/latest?symbols=$target&base=$base"); if ($response->ok()) { $data = $response->json(); @@ -188,7 +201,7 @@ class CurrencyExchange private static function jsdelivrFile(string $base, string $target): ?float { // Reference: https://github.com/fawazahmed0/currency-api - $response = Http::get('https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/'.strtolower($base).'/'.strtolower($target).'.min.json'); + $response = self::$basicRequest->get('https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/'.strtolower($base).'/'.strtolower($target).'.min.json'); if ($response->ok()) { $data = $response->json(); diff --git a/app/Utils/IP.php b/app/Utils/IP.php index b95a4aff..fdeaf58a 100644 --- a/app/Utils/IP.php +++ b/app/Utils/IP.php @@ -21,6 +21,10 @@ class IP { private static bool $is_ipv4; + private static string $ip; + + private static PendingRequest $basicRequest; + public static function getClientIP(): ?string { // 获取访客真实IP return request()?->ip(); @@ -38,8 +42,6 @@ class IP return $info; } - $ret = null; - $source = 0; if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { self::$is_ipv4 = true; } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { @@ -47,58 +49,17 @@ class IP } else { return false; } + self::$ip = $ip; + self::$basicRequest = Http::timeout(10)->withOptions(['http_errors' => false])->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); if (app()->getLocale() === 'zh_CN') { if (self::$is_ipv4) { - while ($source <= 11 && ($ret === null || (is_array($ret) && empty(array_filter($ret))))) { // 中文ipv6 - $ret = match ($source) { - 0 => self::ipApi($ip), - 1 => self::Baidu($ip), - 2 => self::baiduBce($ip), - 3 => self::ipGeoLocation($ip), - 4 => self::TaoBao($ip), - 5 => self::speedtest($ip), - 6 => self::TenAPI($ip), - 7 => self::fkcoder($ip), - 8 => self::juHe($ip), - 9 => self::ipjiance($ip), - 10 => self::ip2Region($ip), - 11 => self::IPIP($ip), - //10 => self::userAgentInfo($ip), // 无法查外网的ip - }; - $source++; - } + $ret = self::IPLookup(['ipApi', 'Baidu', 'baiduBce', 'ipw', 'ipGeoLocation', 'TaoBao', 'speedtest', 'bjjii', 'TenAPI', 'fkcoder', 'vore', 'juHe', 'vvhan', 'ipjiance', 'ip2Region', 'IPIP']); } else { - while ($source <= 5 && ($ret === null || (is_array($ret) && empty(array_filter($ret))))) { - $ret = match ($source) { - 0 => self::ipApi($ip), - 1 => self::baiduBce($ip), - 2 => self::Baidu($ip), - 3 => self::ipGeoLocation($ip), - 4 => self::TenAPI($ip), - 5 => self::ip2Region($ip), - }; - $source++; - } + $ret = self::IPLookup(['ipApi', 'Baidu', 'baiduBce', 'ipw', 'ipGeoLocation', 'TenAPI', 'vore', 'ip2Region']); } } else { - while ($source <= 11 && ($ret === null || (is_array($ret) && empty(array_filter($ret))))) { // 英文 - $ret = match ($source) { - 0 => self::ipApi($ip), - 1 => self::IPSB($ip), - 2 => self::ipinfo($ip), - 3 => self::ip234($ip), - 4 => self::ipGeoLocation($ip), - 5 => self::dbIP($ip), - 6 => self::IP2Online($ip), - 7 => self::ipdata($ip), - 8 => self::ipApiCo($ip), - 9 => self::ip2Location($ip), - 10 => self::GeoIP2($ip), - 11 => self::ipApiCom($ip), - }; - $source++; - } + $ret = self::IPLookup(['ipApi', 'IPSB', 'ipinfo', 'ip234', 'ipGeoLocation', 'dbIP', 'IP2Online', 'ipdata', 'ipApiCo', 'ip2Location', 'GeoIP2', 'ipApiCom']); } if ($ret !== null) { @@ -109,16 +70,68 @@ class IP return $ret; } + private static function IPLookup(array $checkers): ?array + { + foreach ($checkers as $checker) { + try { + $result = self::callApi($checker); + if (is_array($result) && ! empty(array_filter($result))) { + return $result; + } + } catch (Exception $e) { + Log::error("[$checker] IP信息获取报错: ".$e->getMessage()); + + continue; + } + } + + return null; + } + + private static function callApi(string $checker): ?array + { + $ip = self::$ip; + + return match ($checker) { + 'ipApi' => self::ipApi($ip), + 'Baidu' => self::Baidu($ip), + 'baiduBce' => self::baiduBce($ip), + 'ipGeoLocation' => self::ipGeoLocation($ip), + 'TaoBao' => self::TaoBao($ip), + 'speedtest' => self::speedtest($ip), + 'TenAPI' => self::TenAPI($ip), + 'fkcoder' => self::fkcoder($ip), + 'juHe' => self::juHe($ip), + 'ip2Region' => self::ip2Region($ip), + 'IPIP' => self::IPIP($ip), + 'ipjiance' => self::ipjiance($ip), + 'IPSB' => self::IPSB($ip), + 'ipinfo' => self::ipinfo($ip), + 'ip234' => self::ip234($ip), + 'dbIP' => self::dbIP($ip), + 'IP2Online' => self::IP2Online($ip), + 'ipdata' => self::ipdata($ip), + 'ipApiCo' => self::ipApiCo($ip), + 'ip2Location' => self::ip2Location($ip), + 'GeoIP2' => self::GeoIP2($ip), + 'ipApiCom' => self::ipApiCom($ip), + 'vore' => self::vore($ip), + 'vvan' => self::vvhan($ip), + 'ipw' => self::ipw($ip), + 'bjjii' => self::bjjii($ip), + }; + } + private static function ipApi(string $ip): ?array { // 开发依据: https://ip-api.com/docs/api:json $key = config('services.ip.ip-api_key'); if ($key) { - $response = self::setBasicHttp()->withHeaders(['Origin' => 'https://members.ip-api.com'])->acceptJson()->get("https://pro.ip-api.com/json/$ip?fields=49881&key=$key&lang=".str_replace('_', '-', app()->getLocale())); + $response = self::$basicRequest->withHeaders(['Origin' => 'https://members.ip-api.com'])->acceptJson()->get("https://pro.ip-api.com/json/$ip?fields=49881&key=$key&lang=".str_replace('_', '-', app()->getLocale())); if (! $response->ok()) { - $response = self::setBasicHttp()->acceptJson()->get("http://ip-api.com/json/$ip?fields=49881&lang=".str_replace('_', '-', app()->getLocale())); + $response = self::$basicRequest->acceptJson()->get("http://ip-api.com/json/$ip?fields=49881&lang=".str_replace('_', '-', app()->getLocale())); } } else { - $response = self::setBasicHttp()->acceptJson()->get("http://ip-api.com/json/$ip?fields=49881&lang=".str_replace('_', '-', app()->getLocale())); + $response = self::$basicRequest->acceptJson()->get("http://ip-api.com/json/$ip?fields=49881&lang=".str_replace('_', '-', app()->getLocale())); } if ($response->ok()) { @@ -143,18 +156,12 @@ class IP return null; } - private static function setBasicHttp(): PendingRequest - { - return Http::timeout(10)->withOptions(['http_errors' => false])->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); - } - private static function Baidu(string $ip): ?array {// 通过api.map.baidu.com查询IP地址的详细信息 $key = config('services.ip.baidu_ak'); if ($key) { // 依据 http://lbsyun.baidu.com/index.php?title=webapi/ip-api 开发 - $response = self::setBasicHttp()->get("https://api.map.baidu.com/location/ip?ak=$key&ip=$ip&coor=gcj02"); - + $response = self::$basicRequest->get("https://api.map.baidu.com/location/ip?ak=$key&ip=$ip&coor=gcj02"); if ($response->ok()) { $message = $response->json(); if ($message['status'] === 0) { @@ -187,7 +194,7 @@ class IP } else { $url = "https://qifu-api.baidubce.com/ip/geo/v1/ipv6/district?ip=$ip"; } - $response = self::setBasicHttp()->get($url); + $response = self::$basicRequest->get($url); $data = $response->json(); if ($response->ok()) { if ($data['code'] === 'Success' && $ip === $data['ip']) { @@ -212,7 +219,7 @@ class IP private static function ipGeoLocation(string $ip): ?array { // 开发依据: https://ipgeolocation.io/documentation.html - $response = self::setBasicHttp()->withHeaders(['Origin' => 'https://ipgeolocation.io']) + $response = self::$basicRequest->withHeaders(['Origin' => 'https://ipgeolocation.io']) ->get("https://api.ipgeolocation.io/ipgeo?ip=$ip&fields=country_name,state_prov,district,city,isp,latitude,longitude&lang=".config('common.language.'.app()->getLocale().'.1')); if ($response->ok()) { $data = $response->json(); @@ -233,7 +240,7 @@ class IP private static function TaoBao(string $ip): ?array { // 通过ip.taobao.com查询IP地址的详细信息 依据 https://ip.taobao.com/instructions 开发 - $response = self::setBasicHttp()->retry(2)->post("https://ip.taobao.com/outGetIpInfo?ip=$ip&accessKey=alibaba-inc"); + $response = self::$basicRequest->retry(2)->post("https://ip.taobao.com/outGetIpInfo?ip=$ip&accessKey=alibaba-inc"); $message = $response->json(); if ($response->ok()) { @@ -258,7 +265,7 @@ class IP private static function speedtest(string $ip): ?array { - $response = self::setBasicHttp()->get("https://forge.speedtest.cn/api/location/info?ip=$ip"); + $response = self::$basicRequest->get("https://forge.speedtest.cn/api/location/info?ip=$ip"); $data = $response->json(); if ($response->ok()) { if ($data['ip'] === $ip) { @@ -283,7 +290,7 @@ class IP private static function TenAPI(string $ip): ?array { // 开发依据: https://docs.tenapi.cn/utility/getip.html - $response = self::setBasicHttp()->asForm()->post('https://tenapi.cn/v2/getip', ['ip' => $ip]); + $response = self::$basicRequest->asForm()->post('https://tenapi.cn/v2/getip', ['ip' => $ip]); if ($response->ok()) { $data = $response->json(); @@ -293,7 +300,7 @@ class IP 'region' => $data['data']['province'], 'city' => $data['data']['city'], 'isp' => $data['data']['isp'], - 'area' => '', + 'area' => null, ]; } } @@ -303,7 +310,7 @@ class IP private static function fkcoder(string $ip): ?array { // 开发依据: https://www.fkcoder.com/ - $response = self::setBasicHttp()->acceptJson()->get("https://www.fkcoder.com/ip?ip=$ip"); + $response = self::$basicRequest->acceptJson()->get("https://www.fkcoder.com/ip?ip=$ip"); if ($response->ok()) { $data = $response->json(); @@ -321,7 +328,7 @@ class IP private static function juHe(string $ip): ?array { // 开发依据: https://www.juhe.cn/docs/api/id/1 - $response = self::setBasicHttp()->asForm()->post('https://apis.juhe.cn/ip/Example/query.php', ['IP' => $ip]); + $response = self::$basicRequest->asForm()->post('https://apis.juhe.cn/ip/Example/query.php', ['IP' => $ip]); if ($response->ok()) { $data = $response->json(); if ($data['resultcode'] === '200' && $data['error_code'] === 0) { @@ -378,16 +385,16 @@ class IP private static function ipjiance(string $ip): ?array { - $response = self::setBasicHttp()->get("https://www.ipjiance.com/api/geoip/report?ip=$ip"); + $response = self::$basicRequest->get("https://www.ipjiance.com/api/geoip/report?ip=$ip"); $data = $response->json(); if ($response->ok()) { if ($data['code'] === 1) { return [ 'country' => $data['data']['country'], - 'region' => '', + 'region' => null, 'city' => $data['data']['city'], 'isp' => $data['data']['isp'], - 'area' => '', + 'area' => null, 'latitude' => $data['data']['latitude'], 'longitude' => $data['data']['longitude'], ]; @@ -404,7 +411,7 @@ class IP private static function IPSB(string $ip): ?array { // 通过api.ip.sb查询IP地址的详细信息 try { - $response = self::setBasicHttp()->post("https://api.ip.sb/geoip/$ip"); + $response = self::$basicRequest->post("https://api.ip.sb/geoip/$ip"); if ($response->ok()) { $data = $response->json(); @@ -428,9 +435,9 @@ class IP { // 开发依据: https://ipinfo.io/account/home $key = config('services.ip.ipinfo_token'); if ($key) { - $response = self::setBasicHttp()->acceptJson()->get("https://ipinfo.io/$ip?token=$key"); + $response = self::$basicRequest->acceptJson()->get("https://ipinfo.io/$ip?token=$key"); } else { - $response = self::setBasicHttp()->acceptJson()->withHeaders(['Referer' => 'https://ipinfo.io/'])->get("https://ipinfo.io/widget/demo/$ip"); + $response = self::$basicRequest->acceptJson()->withHeaders(['Referer' => 'https://ipinfo.io/'])->get("https://ipinfo.io/widget/demo/$ip"); } if ($response->ok()) { @@ -454,7 +461,7 @@ class IP private static function ip234(string $ip): ?array { - $response = self::setBasicHttp()->get("https://ip234.in/search_ip?ip=$ip"); + $response = self::$basicRequest->get("https://ip234.in/search_ip?ip=$ip"); $data = $response->json(); if ($response->ok()) { if ($data['code'] === 0) { @@ -463,7 +470,7 @@ class IP 'region' => $data['data']['region'], 'city' => $data['data']['city'], 'isp' => $data['data']['organization'], - 'area' => '', + 'area' => null, 'latitude' => $data['data']['latitude'], 'longitude' => $data['data']['longitude'], ]; @@ -479,7 +486,7 @@ class IP private static function dbIP(string $ip): ?array { // 开发依据: https://db-ip.com/api/doc.php - $response = self::setBasicHttp()->acceptJson()->get("https://api.db-ip.com/v2/free/$ip"); + $response = self::$basicRequest->acceptJson()->get("https://api.db-ip.com/v2/free/$ip"); if ($response->ok()) { $data = $response->json(); @@ -499,7 +506,7 @@ class IP { // 开发依据: https://www.ip2location.io/ip2location-documentation $key = config('services.ip.IP2Location_key'); if ($key) { - $response = self::setBasicHttp()->acceptJson()->get("https://api.ip2location.io/?key=$key&ip=$ip"); + $response = self::$basicRequest->acceptJson()->get("https://api.ip2location.io/?key=$key&ip=$ip"); if ($response->ok()) { $data = $response->json(); @@ -522,7 +529,7 @@ class IP { // 开发依据: https://docs.ipdata.co/docs $key = config('services.ip.ipdata_key'); if ($key) { - $response = self::setBasicHttp()->get("https://api.ipdata.co/$ip?api-key=$key&fields=ip,city,region,country_name,latitude,longitude,asn"); + $response = self::$basicRequest->get("https://api.ipdata.co/$ip?api-key=$key&fields=ip,city,region,country_name,latitude,longitude,asn"); if ($response->ok()) { $data = $response->json(); @@ -543,7 +550,7 @@ class IP private static function ipApiCo(string $ip): ?array { // 开发依据: https://ipapi.co/api/ - $response = self::setBasicHttp()->get("https://ipapi.co/$ip/json/"); + $response = self::$basicRequest->get("https://ipapi.co/$ip/json/"); if ($response->ok()) { $data = $response->json(); @@ -608,7 +615,7 @@ class IP private static function ipApiCom(string $ip): ?array { // 开发依据: https://docs.ipdata.co/docs - $response = self::setBasicHttp()->get("https://ipapi.com/ip_api.php?ip=$ip"); + $response = self::$basicRequest->get("https://ipapi.com/ip_api.php?ip=$ip"); if ($response->ok()) { $data = $response->json(); @@ -626,39 +633,135 @@ class IP return null; } - public static function getIPGeo(string $ip): ?array - { - $ret = null; - $source = 0; - while ($source <= 13 && ($ret === null || (is_array($ret) && empty(array_filter($ret))))) { - $ret = match ($source) { - 0 => self::IPSB($ip), - 1 => self::ipApi($ip), - 2 => self::baiduBce($ip), - 3 => self::ipinfo($ip), - 4 => self::IP2Online($ip), - 5 => self::speedtest($ip), - 6 => self::Baidu($ip), - 7 => self::ip234($ip), - 8 => self::ipdata($ip), - 9 => self::ipGeoLocation($ip), - 10 => self::ipjiance($ip), - 11 => self::ipApiCo($ip), - 12 => self::ipApiCom($ip), - 13 => self::ip2Location($ip), - }; - $source++; + private static function vore(string $ip): ?array + { // 开发依据: https://api.vore.top/ + $response = self::$basicRequest->get("https://api.vore.top/api/IPdata?ip=$ip"); + if ($response->ok()) { + $data = $response->json(); + + if ($data['code'] === 200) { + return [ + 'country' => $data['ipdata']['info1'], + 'region' => $data['ipdata']['info2'], + 'city' => $data['ipdata']['info3'], + 'isp' => $data['ipdata']['isp'], + 'area' => null, + ]; + } } - return Arr::only($ret, ['latitude', 'longitude']); + return null; + } + + private static function vvhan(string $ip): ?array + { + $response = self::$basicRequest->get("https://api.vvhan.com/api/getIpInfo?ip=$ip"); + if ($response->ok()) { + $data = $response->json(); + + if ($data['success'] && $data['ip'] === $ip) { + return [ + 'country' => $data['info']['country'], + 'region' => $data['info']['prov'], + 'city' => $data['info']['city'], + 'isp' => $data['info']['isp'], + 'area' => null, + ]; + } + } + + return null; + } + + private static function ipw(string $ip): ?array + { // 开发依据: https://api.vore.top/ + if (self::$is_ipv4) { + $response = self::$basicRequest->asForm()->withHeaders(['Referer' => 'https://ipw.cn/'])->post('https://rest.ipw.cn/api/ip/queryThird', + ['ip' => $ip, 'param1' => '33546680dcec944422ee9fea64ced0fb6', 'param2' => '5ac8d31b5b3434350048af37a497a9']); + } else { + $response = self::$basicRequest->asForm()->withHeaders(['Referer' => 'https://ipw.cn/'])->get("https://rest.ipw.cn/api/aw/v1/ipv6?ip=$ip&warning=1"); + } + + if ($response->ok()) { + $data = $response->json(); + if (self::$is_ipv4) { + if ($data['result'] && $data['Result']['code'] === 'Success' && $data['Result']['ip'] === $ip) { + $data = $data['Result']['data']; + + return [ + 'country' => $data['country'], + 'region' => $data['prov'], + 'city' => $data['city'], + 'isp' => $data['isp'], + 'area' => $data['district'], + 'latitude' => $data['lat'], + 'longitude' => $data['lng'], + ]; + } + } elseif ($data['code'] === 'Success' && $data['ip'] === $ip) { + $data = $data['data']; + + return [ + 'country' => $data['country'], + 'region' => $data['prov'], + 'city' => $data['city'], + 'isp' => $data['isp'], + 'area' => $data['district'], + 'latitude' => $data['lat'], + 'longitude' => $data['lng'], + ]; + } + } + + return null; + } + + private static function bjjii(string $ip): ?array + { // 开发依据: https://api.bjjii.com/doc/77 + $key = config('services.ip.bjjii_key'); + if ($key) { + $response = self::$basicRequest->get("https://api.bjjii.com/api/ip/query?key=$key&ip=$ip"); + if ($response->ok()) { + $data = $response->json(); + + if ($data['code'] === 200 && $data['data']['ip'] === $ip) { + $data = $data['data']['info']; + + return [ + 'country' => $data['nation'], + 'region' => $data['province'], + 'city' => $data['city'], + 'isp' => $data['isp'], + 'area' => $data['district'], + 'latitude' => $data['lat'], + 'longitude' => $data['lng'], + ]; + } + } + } + + return null; + } + + public static function getIPGeo(string $ip): array|false + { + self::$ip = $ip; + self::$basicRequest = Http::timeout(10)->withOptions(['http_errors' => false])->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); + + $ret = self::IPLookup(['IPSB', 'ipApi', 'baiduBce', 'ipw', 'ipinfo', 'IP2Online', 'speedtest', 'bjjii', 'Baidu', 'ip234', 'ipdata', 'ipGeoLocation', 'ipjiance', 'ipApiCo', 'ipApiCom', 'ip2Location']); + if (is_array($ret)) { + return Arr::only($ret, ['latitude', 'longitude']); + } + + return false; } private static function userAgentInfo(string $ip): ?array - { // 开发依据: https://ip.useragentinfo.com/api + { // 开发依据: https://ip.useragentinfo.com/api 无法查外网的ip if (self::$is_ipv4) { - $response = self::setBasicHttp()->withBody("ip:$ip")->get('https://ip.useragentinfo.com/json'); + $response = self::$basicRequest->withBody("ip:$ip")->get('https://ip.useragentinfo.com/json'); } else { - $response = self::setBasicHttp()->get("https://ip.useragentinfo.com/ipv6/$ip"); + $response = self::$basicRequest->get("https://ip.useragentinfo.com/ipv6/$ip"); } if ($response->ok()) { diff --git a/app/Utils/NetworkDetection.php b/app/Utils/NetworkDetection.php index fb34bff8..78d78217 100644 --- a/app/Utils/NetworkDetection.php +++ b/app/Utils/NetworkDetection.php @@ -2,35 +2,50 @@ namespace App\Utils; +use Exception; use Http; +use Illuminate\Http\Client\PendingRequest; use Log; class NetworkDetection { + private static PendingRequest $basicRequest; + public function ping(string $ip): ?string { // 用外部API进行Ping检测. TODO: 无权威外部API,功能缺失 - $ret = null; - $source = 0; + $testers = ['oiowebPing', 'xiaoapiPing', 'yum6Ping']; + self::$basicRequest = Http::timeout(15)->withOptions(['http_errors' => false])->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); - while ($ret === null && $source <= 2) { // 依次尝试接口 - $ret = match ($source) { - 0 => $this->oiowebPing($ip), - 1 => $this->xiaoapiPing($ip), - 2 => $this->yum6Ping($ip), - }; + foreach ($testers as $tester) { + try { + $result = $this->callLatencyTester($tester, $ip); + if ($result !== null) { + return $result; + } + } catch (Exception $e) { + Log::error("[$tester] 网络延迟测试报错: ".$e->getMessage()); - $source++; + continue; + } } - return $ret; + return null; + } + + private function callLatencyTester(string $tester, string $ip): ?array + { + return match ($tester) { + 'oiowebPing' => $this->oiowebPing($ip), + 'xiaoapiPing' => $this->xiaoapiPing($ip), + 'yum6Ping' => $this->yum6Ping($ip), + }; } private function oiowebPing(string $ip) { $msg = null; foreach ([1, 6, 14] as $line) { - $url = "https://api.oioweb.cn/api/hostping.php?host=$ip&node=$line"; // https://api.iiwl.cc/api/ping.php?host= - $response = Http::timeout(20)->get($url); + $response = self::$basicRequest->get("https://api.oioweb.cn/api/hostping.php?host=$ip&node=$line"); // https://api.iiwl.cc/api/ping.php?host= // 发送成功 if ($response->ok()) { @@ -54,9 +69,7 @@ class NetworkDetection private function xiaoapiPing(string $ip) { // 开发依据 https://xiaoapi.cn/?action=doc&id=3 - $msg = null; - - $response = Http::timeout(15)->get("https://xiaoapi.cn/API/sping.php?url=$ip"); + $response = self::$basicRequest->get("https://xiaoapi.cn/API/sping.php?url=$ip"); // 发送成功 if ($response->ok()) { @@ -70,8 +83,7 @@ class NetworkDetection private function yum6Ping(string $ip) { // 来源 https://api.yum6.cn/ping.php?host=api.yum6.cn - $url = "https://api.yum6.cn/ping.php?host=$ip"; - $response = Http::timeout(20)->get($url); + $response = self::$basicRequest->get("https://api.yum6.cn/ping.php?host=$ip"); // 发送成功 if ($response->ok()) { @@ -112,33 +124,46 @@ class NetworkDetection return null; } - public function networkCheck(string $ip, int $port): ?array + private function networkCheck(string $ip, int $port): ?array { // 通过众多API进行节点阻断检测. - $ret = null; - $source = 1; + $checkers = ['toolsdaquan', 'flyzy2005', 'idcoffer', 'ip112', 'upx8', 'vps234', 'rss', 'gd', 'vps1352']; + self::$basicRequest = Http::timeout(10)->withOptions(['http_errors' => false])->withoutVerifying()->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); - while ($ret === null && $source <= 8) { // 依次尝试接口 - $ret = match ($source) { - 1 => $this->toolsdaquan($ip, $port), - 2 => $this->gd($ip, $port), - 3 => $this->vps234($ip), - 4 => $this->flyzy2005($ip, $port), - 5 => $this->idcoffer($ip, $port), - 6 => $this->ip112($ip, $port), - 7 => $this->upx8($ip, $port), - 8 => $this->vps1352($ip, $port), - }; + foreach ($checkers as $checker) { + try { + $result = $this->callChecker($checker, $ip, $port); + if ($result !== null) { + return $result; + } + } catch (Exception $e) { + Log::error("[$checker] 网络阻断测试报错: ".$e->getMessage()); - $source++; + continue; + } } - return $ret; + return null; + } + + private function callChecker(string $checker, string $ip, int $port): ?array + { + return match ($checker) { + 'toolsdaquan' => $this->toolsdaquan($ip, $port), + 'gd' => $this->gd($ip, $port), + 'vps234' => $this->vps234($ip), + 'flyzy2005' => $this->flyzy2005($ip, $port), + 'idcoffer' => $this->idcoffer($ip, $port), + 'ip112' => $this->ip112($ip, $port), + 'upx8' => $this->upx8($ip, $port), + 'vps1352' => $this->vps1352($ip, $port), + 'rss' => $this->rss($ip, $port), + }; } private function toolsdaquan(string $ip, int $port): ?array { // 开发依据: https://www.toolsdaquan.com/ipcheck/ - $response_inner = Http::timeout(15)->withHeaders(['Referer' => 'https://www.toolsdaquan.com/ipcheck/'])->get("https://www.toolsdaquan.com/toolapi/public/ipchecking/$ip/$port"); - $response_outer = Http::timeout(15)->withHeaders(['Referer' => 'https://www.toolsdaquan.com/ipcheck/'])->get("https://www.toolsdaquan.com/toolapi/public/ipchecking2/$ip/$port"); + $response_inner = self::$basicRequest->withHeaders(['Referer' => 'https://www.toolsdaquan.com/ipcheck/'])->get("https://www.toolsdaquan.com/toolapi/public/ipchecking/$ip/$port"); + $response_outer = self::$basicRequest->withHeaders(['Referer' => 'https://www.toolsdaquan.com/ipcheck/'])->get("https://www.toolsdaquan.com/toolapi/public/ipchecking2/$ip/$port"); if ($response_inner->ok() && $response_outer->ok()) { return $this->common_detection($response_inner->json(), $response_outer->json(), $ip); @@ -169,7 +194,7 @@ class NetworkDetection private function gd(string $ip, int $port): ?array { // 开发依据: https://ping.gd/ - $response = Http::timeout(20)->get("https://ping.gd/api/ip-test/$ip:$port"); + $response = self::$basicRequest->get("https://ping.gd/api/ip-test/$ip:$port"); if ($response->ok()) { $data = $response->json(); @@ -193,7 +218,7 @@ class NetworkDetection private function vps234(string $ip): ?array { // 开发依据: https://www.vps234.com/ipchecker/ - $response = Http::withoutVerifying()->timeout(15)->asForm()->post('https://www.vps234.com/ipcheck/getdata/', ['ip' => $ip]); + $response = self::$basicRequest->asForm()->post('https://www.vps234.com/ipcheck/getdata/', ['ip' => $ip]); if ($response->ok()) { $data = $response->json(); if ($data) { @@ -221,8 +246,8 @@ class NetworkDetection private function flyzy2005(string $ip, int $port): ?array { // 开发依据: https://www.flyzy2005.cn/tech/ip-check/ - $response_inner = Http::timeout(15)->get("https://mini.flyzy2005.cn/ip_check.php?ip=$ip&port=$port"); - $response_outer = Http::timeout(15)->get("https://mini.flyzy2005.cn/ip_check_outside.php?ip=$ip&port=$port"); + $response_inner = self::$basicRequest->get("https://mini.flyzy2005.cn/ip_check.php?ip=$ip&port=$port"); + $response_outer = self::$basicRequest->get("https://mini.flyzy2005.cn/ip_check_outside.php?ip=$ip&port=$port"); if ($response_inner->ok() && $response_outer->ok()) { return $this->common_detection($response_inner->json(), $response_outer->json(), $ip); @@ -233,8 +258,8 @@ class NetworkDetection private function idcoffer(string $ip, int $port): ?array { // 开发依据: https://www.idcoffer.com/ipcheck - $response_inner = Http::timeout(15)->get("https://api.24kplus.com/ipcheck?host=$ip&port=$port"); - $response_outer = Http::timeout(15)->get("https://api.idcoffer.com/ipcheck?host=$ip&port=$port"); + $response_inner = self::$basicRequest->get("https://api.24kplus.com/ipcheck?host=$ip&port=$port"); + $response_outer = self::$basicRequest->get("https://api.idcoffer.com/ipcheck?host=$ip&port=$port"); if ($response_inner->ok() && $response_outer->ok()) { $inner = $response_inner->json(); @@ -262,8 +287,8 @@ class NetworkDetection private function ip112(string $ip, int $port = 443): ?array { // 开发依据: https://ip112.cn/ - $response_inner = Http::asForm()->post('https://api.ycwxgzs.com/ipcheck/index.php', ['ip' => $ip, 'port' => $port]); - $response_outer = Http::asForm()->post('https://api.52bwg.com/ipcheck/ipcheck.php', ['ip' => $ip, 'port' => $port]); + $response_inner = self::$basicRequest->asForm()->post('https://api.ycwxgzs.com/ipcheck/index.php', ['ip' => $ip, 'port' => $port]); + $response_outer = self::$basicRequest->asForm()->post('https://api.52bwg.com/ipcheck/ipcheck.php', ['ip' => $ip, 'port' => $port]); if ($response_inner->ok() && $response_outer->ok()) { $inner = $response_inner->json(); @@ -288,8 +313,8 @@ class NetworkDetection private function upx8(string $ip, int $port = 443): ?array { // 开发依据: https://blog.upx8.com/ipcha.html - $response_inner = Http::asForm()->post('https://ip.upx8.com/check.php', ['ip' => $ip, 'port' => $port]); - $response_outer = Http::asForm()->post('https://ip.7761.cf/check.php', ['ip' => $ip, 'port' => $port]); + $response_inner = self::$basicRequest->asForm()->post('https://ip.upx8.com/check.php', ['ip' => $ip, 'port' => $port]); + $response_outer = self::$basicRequest->asForm()->post('https://ip.7761.cf/check.php', ['ip' => $ip, 'port' => $port]); if ($response_inner->ok() && $response_outer->ok()) { $inner = $response_inner->json(); @@ -314,7 +339,7 @@ class NetworkDetection private function vps1352(string $ip, int $port): ?array { // 开发依据: https://www.51vps.info/ipcheck.html https://www.vps1352.com/ipcheck.html 有缺陷api,查不了海外做判断 备用 - $response = Http::asForm()->withHeaders(['Referer' => 'https://www.51vps.info'])->post('https://www.vps1352.com/check.php', ['ip' => $ip, 'port' => $port]); + $response = self::$basicRequest->asForm()->withHeaders(['Referer' => 'https://www.51vps.info'])->post('https://www.vps1352.com/check.php', ['ip' => $ip, 'port' => $port]); if ($response->ok()) { $data = $response->json(); @@ -335,4 +360,26 @@ class NetworkDetection return null; } + + private function rss(string $ip, int $port): ?array + { // https://ip.rss.ink/index/check + $client = self::$basicRequest->withHeaders(['X-Token' => '5AXfB1xVfuq5hxv4']); + + foreach (['in', 'out'] as $type) { + foreach (['icmp', 'tcp'] as $protocol) { + $response = $client->get('https://ip.rss.ink/netcheck/'.($type === 'in' ? 'cn' : 'global')."/api/check/$protocol?ip=$ip".($protocol === 'tcp' ? "&port=$port" : '')); + + if ($response->ok()) { + $data = $response->json(); + $ret[$type][$protocol] = $data['msg'] === 'success'; + } + } + } + + if (! isset($ret)) { + Log::warning("【阻断检测】检测{$ip}时, [rss]接口返回异常"); + } + + return $ret ?? null; + } } diff --git a/app/helpers.php b/app/helpers.php index 5fcf1b9b..cf91a328 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -36,8 +36,7 @@ if (! function_exists('formatBytes')) { $bytes /= 1024 ** $power; if ($base) { - $basePower = array_search($base, $units); - $power += max($basePower, 0); + $power += max(array_search($base, $units), 0); } return round($bytes, $precision).' '.$units[$power]; @@ -48,7 +47,7 @@ if (! function_exists('formatBytes')) { if (! function_exists('formatTime')) { function formatTime(int $seconds): string { - $output = ''; + $timeString = ''; $units = [ trans('validation.attributes.day') => 86400, trans('validation.attributes.hour') => 3600, @@ -56,15 +55,14 @@ if (! function_exists('formatTime')) { trans('validation.attributes.second') => 1, ]; - foreach ($units as $unit => $value) { - if ($seconds >= $value) { - $count = floor($seconds / $value); - $output .= $count.$unit; - $seconds %= $value; + foreach ($units as $unitName => $secondsInUnit) { + if ($seconds >= $secondsInUnit) { + $timeString .= floor($seconds / $secondsInUnit).' '.$unitName.' '; + $seconds %= $secondsInUnit; } } - return $output; + return trim($timeString); } } @@ -97,20 +95,12 @@ if (! function_exists('array_clean')) { if (! function_exists('string_urlsafe')) { function string_urlsafe($string, $force_lowercase = true, $anal = false): string { - $strip = [ - '~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '[', '{', ']', '}', '\\', '|', ';', ':', '"', "'", '‘', '’', '“', - '”', '–', '—', '—', '–', ',', '<', '.', '>', '/', '?', - ]; - $clean = trim(str_replace($strip, '_', strip_tags($string))); + $clean = preg_replace('/[~`!@#$%^&*()_=+\[\]{}\\|;:"\'<>,.?\/]/', '_', strip_tags($string)); $clean = preg_replace('/\s+/', '-', $clean); $clean = ($anal) ? preg_replace('/[^a-zA-Z0-9]/', '', $clean) : $clean; if ($force_lowercase) { - if (function_exists('mb_strtolower')) { - $clean = mb_strtolower($clean, 'UTF-8'); - } else { - $clean = strtolower($clean); - } + $clean = function_exists('mb_strtolower') ? mb_strtolower($clean, 'UTF-8') : strtolower($clean); } return $clean; diff --git a/config/services.php b/config/services.php index 2307f471..c3a99faa 100644 --- a/config/services.php +++ b/config/services.php @@ -86,6 +86,7 @@ return [ 'IP2Location_key' => env('IP2LOCATION_API_KEY'), 'ip-api_key' => env('IP_API_KEY'), 'ipdata_key' => env('IPDATA_API_KEY'), + 'bjjii_key' => env('BJJII_KEY'), ], 'currency' => [