Browse Source

💪🏼 Improve API calls

BrettonYe 2 years ago
parent
commit
92a2982618
6 changed files with 361 additions and 207 deletions
  1. 1 1
      app/Models/Node.php
  2. 45 32
      app/Utils/CurrencyExchange.php
  3. 209 106
      app/Utils/IP.php
  4. 96 49
      app/Utils/NetworkDetection.php
  5. 9 19
      app/helpers.php
  6. 1 0
      config/services.php

+ 1 - 1
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)]);
                 });

+ 45 - 32
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);
+                }
+            } catch (Exception $e) {
+                Log::error("[$api] ±ÒÖÖ»ãÂÊÐÅÏ¢»ñÈ¡±¨´í: ".$e->getMessage());
 
-            return round($amount * $rate, 2);
+                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&currencies=$target");
+            $response = self::$basicRequest->withHeaders(['apikey' => $key])->get("https://api.apilayer.com/currency_data/live?source=$base&currencies=$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();
 

+ 209 - 106
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
+    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 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
     {
-        $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++;
-        }
-
-        return Arr::only($ret, ['latitude', 'longitude']);
+        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()) {

+ 96 - 49
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;
-
-        while ($ret === null && $source <= 2) { // 依次尝试接口
-            $ret = match ($source) {
-                0 => $this->oiowebPing($ip),
-                1 => $this->xiaoapiPing($ip),
-                2 => $this->yum6Ping($ip),
-            };
+        $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');
+
+        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;
-
-        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),
-            };
-
-            $source++;
+        $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');
+
+        foreach ($checkers as $checker) {
+            try {
+                $result = $this->callChecker($checker, $ip, $port);
+                if ($result !== null) {
+                    return $result;
+                }
+            } catch (Exception $e) {
+                Log::error("[$checker] 网络阻断测试报错: ".$e->getMessage());
+
+                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;
+    }
 }

+ 9 - 19
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 = [
-            '~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '[', '{', ']', '}', '\\', '|', ';', ':', '"', "'", '&#8216;', '&#8217;', '&#8220;',
-            '&#8221;', '&#8211;', '&#8212;', '—', '–', ',', '<', '.', '>', '/', '?',
-        ];
-        $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;

+ 1 - 0
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' => [