doge 13 часов назад
Родитель
Сommit
0d9edbc1bc
2 измененных файлов с 154 добавлено и 0 удалено
  1. 121 0
      app/Helpers/IpCountry.php
  2. 33 0
      app/Http/Controllers/admin/User.php

+ 121 - 0
app/Helpers/IpCountry.php

@@ -0,0 +1,121 @@
+<?php
+
+namespace App\Helpers;
+
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 国家/地区:按 IP 解析(带缓存),用于登录日志回填展示
+ */
+class IpCountry
+{
+    private const CACHE_TTL = 86400;
+    private const CACHE_PREFIX = 'ip_country_v2_';
+
+    private const CODE_NAME_MAP = [
+        'CN' => '中国',
+        'HK' => '香港',
+        'MO' => '澳门',
+        'TW' => '台湾',
+        'US' => '美国',
+        'JP' => '日本',
+        'KR' => '韩国',
+        'SG' => '新加坡',
+        'MY' => '马来西亚',
+        'TH' => '泰国',
+        'VN' => '越南',
+        'ID' => '印尼',
+        'PH' => '菲律宾',
+        'KH' => '柬埔寨',
+        'MM' => '缅甸',
+        'IN' => '印度',
+        'GB' => '英国',
+        'AU' => '澳大利亚',
+        'CA' => '加拿大',
+        'DE' => '德国',
+        'FR' => '法国',
+        'RU' => '俄罗斯',
+        'BR' => '巴西',
+        'AE' => '阿联酋',
+    ];
+
+    public static function resolve(string $ip): string
+    {
+        $ip = trim($ip);
+        if ($ip === '') {
+            return '';
+        }
+        if (in_array($ip, ['127.0.0.1', '::1'], true)) {
+            return '本地';
+        }
+        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
+            return '';
+        }
+
+        $cacheKey = self::CACHE_PREFIX . md5($ip);
+        $cached = Cache::get($cacheKey);
+        if (is_string($cached)) {
+            return $cached;
+        }
+
+        $country = self::lookup($ip);
+        Cache::put($cacheKey, $country, self::CACHE_TTL);
+        return $country;
+    }
+
+    /**
+     * 批量解析,减少列表页重复请求
+     *
+     * @param array<int, string> $ips
+     * @return array<string, string> ip => country
+     */
+    public static function resolveMany(array $ips): array
+    {
+        $result = [];
+        foreach (array_unique(array_filter(array_map('trim', $ips))) as $ip) {
+            $result[$ip] = self::resolve($ip);
+        }
+        return $result;
+    }
+
+    protected static function lookup(string $ip): string
+    {
+        $url = 'http://ip-api.com/json/' . rawurlencode($ip)
+            . '?fields=status,country,countryCode,regionName,city&lang=zh-CN';
+
+        try {
+            $ctx = stream_context_create([
+                'http' => [
+                    'timeout' => 2,
+                    'method' => 'GET',
+                ],
+            ]);
+            $raw = @file_get_contents($url, false, $ctx);
+            if ($raw === false || $raw === '') {
+                return '';
+            }
+            $data = json_decode($raw, true);
+            if (!is_array($data) || ($data['status'] ?? '') !== 'success') {
+                return '';
+            }
+
+            $code = strtoupper((string)($data['countryCode'] ?? ''));
+            $country = (string)($data['country'] ?? '');
+            if ($country === '' && $code !== '' && isset(self::CODE_NAME_MAP[$code])) {
+                $country = self::CODE_NAME_MAP[$code];
+            }
+
+            $parts = array_filter([
+                $country,
+                (string)($data['regionName'] ?? ''),
+                (string)($data['city'] ?? ''),
+            ], static fn ($v) => $v !== '');
+
+            return implode(' ', array_unique($parts));
+        } catch (\Throwable $e) {
+            Log::warning('ip_country_lookup_failed', ['ip' => $ip, 'error' => $e->getMessage()]);
+            return '';
+        }
+    }
+}

+ 33 - 0
app/Http/Controllers/admin/User.php

@@ -17,6 +17,7 @@ use Illuminate\Http\JsonResponse;
 use App\Models\User as UserModel;
 use App\Models\UserSession;
 use App\Models\UserLogin;
+use App\Helpers\IpCountry;
 use App\Models\ThirdGameRecycle;
 use App\Models\Wallet;
 use App\Services\BalanceLogService;
@@ -558,6 +559,38 @@ class User extends Controller
                 ->forPage($page, $limit)
                 ->orderByDesc('user_login.id')
                 ->get();
+
+            // 历史数据 country 为空时按 login_ip 回填展示,并尽量写回库
+            $emptyIps = $list
+                ->filter(fn ($row) => empty($row->country) && !empty($row->login_ip))
+                ->pluck('login_ip')
+                ->unique()
+                ->values()
+                ->all();
+            if ($emptyIps) {
+                $countryMap = IpCountry::resolveMany($emptyIps);
+                foreach ($list as $row) {
+                    if (!empty($row->country) || empty($row->login_ip)) {
+                        continue;
+                    }
+                    $country = $countryMap[$row->login_ip] ?? '';
+                    if ($country === '') {
+                        continue;
+                    }
+                    $row->country = $country;
+                }
+                foreach ($countryMap as $ip => $country) {
+                    if ($country === '') {
+                        continue;
+                    }
+                    UserLogin::where('login_ip', $ip)
+                        ->where(function ($q) {
+                            $q->whereNull('country')->orWhere('country', '');
+                        })
+                        ->limit(200)
+                        ->update(['country' => $country]);
+                }
+            }
         } catch (Exception $e) {
             return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
         }