Bläddra i källkod

feat: maxmind geoip2 for ip location

M1Screw 2 år sedan
förälder
incheckning
ca62dc6524

+ 6 - 4
composer.json

@@ -1,18 +1,18 @@
 {
 {
     "require": {
     "require": {
         "php": "^8.0",
         "php": "^8.0",
+        "ext-bcmath": "*",
         "ext-curl": "*",
         "ext-curl": "*",
         "ext-json": "*",
         "ext-json": "*",
         "ext-mysqli": "*",
         "ext-mysqli": "*",
-        "ext-xml": "*",
-        "ext-zip": "*",
         "ext-openssl": "*",
         "ext-openssl": "*",
-        "ext-bcmath": "*",
         "ext-pdo": "*",
         "ext-pdo": "*",
+        "ext-xml": "*",
+        "ext-zip": "*",
         "anankke/omnipay-alipay": "^3.1.3",
         "anankke/omnipay-alipay": "^3.1.3",
         "aws/aws-sdk-php": "^3",
         "aws/aws-sdk-php": "^3",
         "cloudflare/sdk": "^1",
         "cloudflare/sdk": "^1",
-        "ozdemir/datatables": "^2",
+        "geoip2/geoip2": "~2.0",
         "guzzlehttp/guzzle": "^7.4",
         "guzzlehttp/guzzle": "^7.4",
         "guzzlehttp/psr7": "^2.4",
         "guzzlehttp/psr7": "^2.4",
         "illuminate/database": "^9.0",
         "illuminate/database": "^9.0",
@@ -22,6 +22,7 @@
         "league/html-to-markdown": "^5.1",
         "league/html-to-markdown": "^5.1",
         "league/omnipay": "^3.2.1",
         "league/omnipay": "^3.2.1",
         "mailgun/mailgun-php": "^3",
         "mailgun/mailgun-php": "^3",
+        "ozdemir/datatables": "^2",
         "phpmailer/phpmailer": "^6",
         "phpmailer/phpmailer": "^6",
         "postal/postal": "^1.0",
         "postal/postal": "^1.0",
         "ramsey/uuid": "^4",
         "ramsey/uuid": "^4",
@@ -33,6 +34,7 @@
         "stripe/stripe-php": "^7",
         "stripe/stripe-php": "^7",
         "symfony/console": "*",
         "symfony/console": "*",
         "symfony/yaml": "^6",
         "symfony/yaml": "^6",
+        "tronovav/geoip2-update": "^2.1",
         "vectorface/googleauthenticator": "^3.0",
         "vectorface/googleauthenticator": "^3.0",
         "voku/anti-xss": "^4"
         "voku/anti-xss": "^4"
     },
     },

+ 3 - 0
config/.config.example.php

@@ -174,6 +174,9 @@ foreach ($_ENV['cdn_forwarded_ip'] as $cdn_forwarded_ip) {
 // https://sentry.io for production debugging
 // https://sentry.io for production debugging
 $_ENV['sentry_dsn'] = '';
 $_ENV['sentry_dsn'] = '';
 
 
+// MaxMind License Key
+$_ENV['maxmind_license_key'] = '';
+
 // ClientDownload 命令解决 API 访问频率高而被限制使用的 Github access token
 // ClientDownload 命令解决 API 访问频率高而被限制使用的 Github access token
 $_ENV['github_access_token'] = '';
 $_ENV['github_access_token'] = '';
 
 

+ 2 - 4
resources/views/tabler/staff.tpl

@@ -31,19 +31,17 @@
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.</p>
         SOFTWARE.</p>
         <br>
         <br>
-        <br>
         <h1><a href="https://wiki.sspanel.org/#/contributors">贡献者清单</a></h1>
         <h1><a href="https://wiki.sspanel.org/#/contributors">贡献者清单</a></h1>
         <br>
         <br>
-        <br>
         <h1><a href="https://github.com/Anankke/SSPanel-Uim">GitHub</h1>
         <h1><a href="https://github.com/Anankke/SSPanel-Uim">GitHub</h1>
         <br>
         <br>
-        <br>
         <h1>SSPanel-UIM 中所使用的开源项目</h1>
         <h1>SSPanel-UIM 中所使用的开源项目</h1>
         <p><a href="https://github.com/smarty-php/smarty">Smarty template engine</a></p>
         <p><a href="https://github.com/smarty-php/smarty">Smarty template engine</a></p>
         <p><a href="https://github.com/slimphp/Slim">Slim Framework</a></p>
         <p><a href="https://github.com/slimphp/Slim">Slim Framework</a></p>
-        <p><a href="https://github.com/orvice/ss-panel">ss-panel</a></p>
         <p><a href="https://github.com/tabler/tabler">tabler</a></p>
         <p><a href="https://github.com/tabler/tabler">tabler</a></p>
         <br>
         <br>
+        <p>This product includes GeoLite2 data created by MaxMind, available from
+        <a href="https://www.maxmind.com">https://www.maxmind.com</a>.</p>
         <br>
         <br>
         <h1>鸣谢</h1>
         <h1>鸣谢</h1>
         <p>所有被引用过代码的开发者,以及所有提交过 PR 的贡献者。当然,还有在使用这份程序的你我Ta。</p>
         <p>所有被引用过代码的开发者,以及所有提交过 PR 的贡献者。当然,还有在使用这份程序的你我Ta。</p>

+ 14 - 0
src/Command/Update.php

@@ -4,6 +4,9 @@ declare(strict_types=1);
 
 
 namespace App\Command;
 namespace App\Command;
 
 
+use tronovav\GeoIP2Update\Client;
+use const BASE_PATH;
+
 final class Update extends Command
 final class Update extends Command
 {
 {
     public string $description = <<< END
     public string $description = <<< END
@@ -12,6 +15,7 @@ END;
 
 
     public function boot(): void
     public function boot(): void
     {
     {
+        // 迁移配置
         global $_ENV;
         global $_ENV;
         $copy_result = copy(BASE_PATH . '/config/.config.php', BASE_PATH . '/config/.config.php.bak');
         $copy_result = copy(BASE_PATH . '/config/.config.php', BASE_PATH . '/config/.config.php.bak');
         if ($copy_result === true) {
         if ($copy_result === true) {
@@ -74,5 +78,15 @@ END;
 
 
         file_put_contents(BASE_PATH . '/config/.config.php', $config_new);
         file_put_contents(BASE_PATH . '/config/.config.php', $config_new);
         echo "迁移完成。\n";
         echo "迁移完成。\n";
+
+        if ($_ENV['maxmind_license_key'] !== '') {
+            echo "正在更新 GeoLite2 数据库...\n";
+            $client = new Client(array(
+                'license_key' => $_ENV['maxmind_license_key'],
+                'dir' => BASE_PATH . '/storage/',
+                'editions' => array('GeoLite2-City', "GeoLite2-Country"),
+            ));
+            $client->run();
+        }
     }
     }
 }
 }

+ 43 - 0
src/Utils/GeoIP2.php

@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Utils;
+
+use GeoIp2\Database\Reader;
+use GeoIp2\Exception\AddressNotFoundException;
+use MaxMind\Db\Reader\InvalidDatabaseException;
+use const BASE_PATH;
+
+final class GeoIP2
+{
+    private Reader $reader;
+    /**
+     * @throws InvalidDatabaseException
+     */
+    public function __construct()
+    {
+        $this->city_reader = new Reader(BASE_PATH . '/storage/GeoLite2-City/GeoLite2-City.mmdb');
+        $this->country_reader = new Reader(BASE_PATH . '/storage/GeoLite2-Country/GeoLite2-Country.mmdb');
+    }
+
+    /**
+     * @throws AddressNotFoundException
+     * @throws InvalidDatabaseException
+     */
+    public function getCity(string $ip): ?string
+    {
+        $record = $this->city_reader->city($ip);
+        return $record->city->name;
+    }
+
+    /**
+     * @throws AddressNotFoundException
+     * @throws InvalidDatabaseException
+     */
+    public function getCountry(string $ip): ?string
+    {
+        $record = $this->country_reader->country($ip);
+        return $record->country->name;
+    }
+}

+ 0 - 167
src/Utils/QQWry.php

@@ -1,167 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Utils;
-
-use function chr;
-use function ord;
-
-final class QQWry
-{
-    private $fp;
-
-    private mixed $firstip;
-
-    private mixed $lastip;
-
-    private int|float $totalip;
-
-    public function __construct()
-    {
-        $filename = BASE_PATH . '/storage/qqwry.dat';
-        $this->fp = fopen($filename, 'rb');
-
-        if ($this->fp !== false) {
-            $this->firstip = $this->getlong();
-            $this->lastip = $this->getlong();
-            $this->totalip = ($this->lastip - $this->firstip) / 7;
-            register_shutdown_function([&$this, '__destruct']);
-        }
-    }
-
-    public function __destruct()
-    {
-        if ($this->fp) {
-            fclose($this->fp);
-        }
-        $this->fp = 0;
-    }
-
-    public function getlocation($ip): ?array
-    {
-        if (! $this->fp) {
-            return null;
-        }
-
-        $location = [];
-        $location['ip'] = gethostbyname($ip);
-        $ip = $this->packip($location['ip']);
-        $l = 0;
-        $u = $this->totalip;
-        $findip = $this->lastip;
-
-        while ($l <= $u) {
-            $i = floor(($l + $u) / 2);
-            fseek($this->fp, (int) ($this->firstip + $i * 7));
-            $beginip = strrev(fread($this->fp, 4));
-
-            if ($ip < $beginip) {
-                $u = $i - 1;
-            } else {
-                fseek($this->fp, $this->getlong3());
-                $endip = strrev(fread($this->fp, 4));
-
-                if ($ip > $endip) {
-                    $l = $i + 1;
-                } else {
-                    $findip = (int) ($this->firstip + $i * 7);
-                    break;
-                }
-            }
-        }
-
-        fseek($this->fp, $findip);
-        $location['beginip'] = long2ip($this->getlong());
-        $offset = $this->getlong3();
-        fseek($this->fp, $offset);
-        $location['endip'] = long2ip($this->getlong());
-        $byte = fread($this->fp, 1);
-
-        switch (ord($byte)) {
-            case 1:
-                $countryOffset = $this->getlong3();
-                fseek($this->fp, $countryOffset);
-                $byte = fread($this->fp, 1);
-
-                if (ord($byte) === 2) {
-                    fseek($this->fp, $this->getlong3());
-                    $location['country'] = $this->getstring();
-                    fseek($this->fp, $countryOffset + 4);
-                } else {
-                    $location['country'] = $this->getstring($byte);
-                }
-                break;
-            case 2:
-                fseek($this->fp, $this->getlong3());
-                $location['country'] = $this->getstring();
-                fseek($this->fp, $offset + 8);
-                break;
-            default:
-                $location['country'] = $this->getstring($byte);
-                break;
-        }
-
-        $location['area'] = $this->getarea();
-
-        if ($location['country'] === ' CZ88.NET') {
-            $location['country'] = '未知';
-        }
-
-        if ($location['area'] === ' CZ88.NET') {
-            $location['area'] = '';
-        }
-
-        return $location;
-    }
-
-    private function getlong()
-    {
-        $result = unpack('Vlong', fread($this->fp, 4));
-        return $result['long'];
-    }
-
-    private function getlong3()
-    {
-        $result = unpack('Vlong', fread($this->fp, 3) . chr(0));
-        return $result['long'];
-    }
-
-    private function packip($ip): string
-    {
-        return pack('N', (int) ip2long($ip));
-    }
-
-    private function getstring($data = '')
-    {
-        $char = fread($this->fp, 1);
-
-        while (ord($char) > 0) {
-            $data .= $char;
-            $char = fread($this->fp, 1);
-        }
-
-        return $data;
-    }
-
-    private function getarea()
-    {
-        $byte = fread($this->fp, 1);
-
-        switch (ord($byte)) {
-            case 0:
-                $area = '';
-                break;
-            case 1:
-            case 2:
-                fseek($this->fp, $this->getlong3());
-                $area = $this->getstring();
-                break;
-            default:
-                $area = $this->getstring($byte);
-                break;
-        }
-
-        return $area;
-    }
-}

+ 12 - 4
src/Utils/Tools.php

@@ -10,6 +10,8 @@ use App\Models\Paylist;
 use App\Models\Setting;
 use App\Models\Setting;
 use App\Models\User;
 use App\Models\User;
 use App\Services\Config;
 use App\Services\Config;
+use GeoIp2\Exception\AddressNotFoundException;
+use MaxMind\Db\Reader\InvalidDatabaseException;
 use function floatval;
 use function floatval;
 use function in_array;
 use function in_array;
 use function intval;
 use function intval;
@@ -21,11 +23,17 @@ final class Tools
     /**
     /**
      * 查询IP归属
      * 查询IP归属
      */
      */
-    public static function getIpLocation($ip): false|string
+    public static function getIpLocation($ip): string
     {
     {
-        $iplocation = new QQWry();
-        $location = $iplocation->getlocation($ip);
-        return iconv('gbk', 'utf-8//IGNORE', $location['country'] . $location['area']);
+        $geoip = new GeoIP2();
+        try {
+            $city = $geoip->getCity($ip);
+            $country = $geoip->getCountry($ip);
+        } catch (AddressNotFoundException|InvalidDatabaseException $e) {
+            return '未知';
+        }
+
+        return $country . ' ' . $city;
     }
     }
 
 
     /**
     /**

+ 0 - 2
update.sh

@@ -21,7 +21,6 @@ do_update_sspanel_dev(){
     php xcat Update
     php xcat Update
     php xcat Tool importAllSettings
     php xcat Tool importAllSettings
     php xcat Migration latest
     php xcat Migration latest
-    wget https://cdn.jsdelivr.net/gh/sspanel-uim/qqwry.dat@latest/qqwry.dat -O storage/qqwry.dat 
 }
 }
 
 
 do_update_sspanel_release(){
 do_update_sspanel_release(){
@@ -35,7 +34,6 @@ do_update_sspanel_release(){
     php xcat Update
     php xcat Update
     php xcat Tool importAllSettings
     php xcat Tool importAllSettings
     php xcat Migration $db_version
     php xcat Migration $db_version
-    wget https://cdn.jsdelivr.net/gh/sspanel-uim/qqwry.dat@latest/qqwry.dat -O storage/qqwry.dat 
 }
 }
 
 
 if [[ $1 == "dev" ]]; then
 if [[ $1 == "dev" ]]; then