Browse Source

feat: maxmind geoip2 for ip location

M1Screw 2 years ago
parent
commit
ca62dc6524
8 changed files with 80 additions and 181 deletions
  1. 6 4
      composer.json
  2. 3 0
      config/.config.example.php
  3. 2 4
      resources/views/tabler/staff.tpl
  4. 14 0
      src/Command/Update.php
  5. 43 0
      src/Utils/GeoIP2.php
  6. 0 167
      src/Utils/QQWry.php
  7. 12 4
      src/Utils/Tools.php
  8. 0 2
      update.sh

+ 6 - 4
composer.json

@@ -1,18 +1,18 @@
 {
     "require": {
         "php": "^8.0",
+        "ext-bcmath": "*",
         "ext-curl": "*",
         "ext-json": "*",
         "ext-mysqli": "*",
-        "ext-xml": "*",
-        "ext-zip": "*",
         "ext-openssl": "*",
-        "ext-bcmath": "*",
         "ext-pdo": "*",
+        "ext-xml": "*",
+        "ext-zip": "*",
         "anankke/omnipay-alipay": "^3.1.3",
         "aws/aws-sdk-php": "^3",
         "cloudflare/sdk": "^1",
-        "ozdemir/datatables": "^2",
+        "geoip2/geoip2": "~2.0",
         "guzzlehttp/guzzle": "^7.4",
         "guzzlehttp/psr7": "^2.4",
         "illuminate/database": "^9.0",
@@ -22,6 +22,7 @@
         "league/html-to-markdown": "^5.1",
         "league/omnipay": "^3.2.1",
         "mailgun/mailgun-php": "^3",
+        "ozdemir/datatables": "^2",
         "phpmailer/phpmailer": "^6",
         "postal/postal": "^1.0",
         "ramsey/uuid": "^4",
@@ -33,6 +34,7 @@
         "stripe/stripe-php": "^7",
         "symfony/console": "*",
         "symfony/yaml": "^6",
+        "tronovav/geoip2-update": "^2.1",
         "vectorface/googleauthenticator": "^3.0",
         "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
 $_ENV['sentry_dsn'] = '';
 
+// MaxMind License Key
+$_ENV['maxmind_license_key'] = '';
+
 // ClientDownload 命令解决 API 访问频率高而被限制使用的 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
         SOFTWARE.</p>
         <br>
-        <br>
         <h1><a href="https://wiki.sspanel.org/#/contributors">贡献者清单</a></h1>
         <br>
-        <br>
         <h1><a href="https://github.com/Anankke/SSPanel-Uim">GitHub</h1>
         <br>
-        <br>
         <h1>SSPanel-UIM 中所使用的开源项目</h1>
         <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/orvice/ss-panel">ss-panel</a></p>
         <p><a href="https://github.com/tabler/tabler">tabler</a></p>
         <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>
         <h1>鸣谢</h1>
         <p>所有被引用过代码的开发者,以及所有提交过 PR 的贡献者。当然,还有在使用这份程序的你我Ta。</p>

+ 14 - 0
src/Command/Update.php

@@ -4,6 +4,9 @@ declare(strict_types=1);
 
 namespace App\Command;
 
+use tronovav\GeoIP2Update\Client;
+use const BASE_PATH;
+
 final class Update extends Command
 {
     public string $description = <<< END
@@ -12,6 +15,7 @@ END;
 
     public function boot(): void
     {
+        // 迁移配置
         global $_ENV;
         $copy_result = copy(BASE_PATH . '/config/.config.php', BASE_PATH . '/config/.config.php.bak');
         if ($copy_result === true) {
@@ -74,5 +78,15 @@ END;
 
         file_put_contents(BASE_PATH . '/config/.config.php', $config_new);
         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\User;
 use App\Services\Config;
+use GeoIp2\Exception\AddressNotFoundException;
+use MaxMind\Db\Reader\InvalidDatabaseException;
 use function floatval;
 use function in_array;
 use function intval;
@@ -21,11 +23,17 @@ final class Tools
     /**
      * 查询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 Tool importAllSettings
     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(){
@@ -35,7 +34,6 @@ do_update_sspanel_release(){
     php xcat Update
     php xcat Tool importAllSettings
     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