|
|
@@ -1,152 +0,0 @@
|
|
|
-<?php
|
|
|
-
|
|
|
-namespace App\Helpers;
|
|
|
-
|
|
|
-/**
|
|
|
- * 登录客户端信息:国家/地区、浏览器、操作系统
|
|
|
- */
|
|
|
-class LoginClientInfo
|
|
|
-{
|
|
|
- /**
|
|
|
- * 解析请求中的客户端信息
|
|
|
- */
|
|
|
- public static function fromRequest(?string $ip = null, ?string $userAgent = null): array
|
|
|
- {
|
|
|
- $ip = $ip ?: (string)request()->ip();
|
|
|
- $userAgent = $userAgent ?? (string)request()->userAgent();
|
|
|
-
|
|
|
- return [
|
|
|
- 'login_ip' => $ip,
|
|
|
- 'country' => self::resolveCountry($ip),
|
|
|
- 'browser' => self::parseBrowser($userAgent),
|
|
|
- 'os' => self::parseOs($userAgent),
|
|
|
- 'user_agent' => mb_substr($userAgent, 0, 500),
|
|
|
- ];
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 国家/地区:优先 Cloudflare 头,其次 ip-api
|
|
|
- */
|
|
|
- public static function resolveCountry(string $ip): string
|
|
|
- {
|
|
|
- if ($ip === '' || $ip === '127.0.0.1' || $ip === '::1') {
|
|
|
- return '本地';
|
|
|
- }
|
|
|
-
|
|
|
- $cfCountry = (string)request()->header('CF-IPCountry', '');
|
|
|
- if ($cfCountry !== '' && strtoupper($cfCountry) !== 'XX') {
|
|
|
- return strtoupper($cfCountry);
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- $url = 'http://ip-api.com/json/' . urlencode($ip) . '?fields=status,country,regionName,city&lang=zh-CN';
|
|
|
- $ctx = stream_context_create([
|
|
|
- 'http' => [
|
|
|
- 'timeout' => 1.5,
|
|
|
- 'method' => 'GET',
|
|
|
- ],
|
|
|
- ]);
|
|
|
- $raw = @file_get_contents($url, false, $ctx);
|
|
|
- if ($raw === false) {
|
|
|
- return '';
|
|
|
- }
|
|
|
- $data = json_decode($raw, true);
|
|
|
- if (!is_array($data) || ($data['status'] ?? '') !== 'success') {
|
|
|
- return '';
|
|
|
- }
|
|
|
- $parts = array_filter([
|
|
|
- $data['country'] ?? '',
|
|
|
- $data['regionName'] ?? '',
|
|
|
- $data['city'] ?? '',
|
|
|
- ]);
|
|
|
- return implode(' ', $parts);
|
|
|
- } catch (\Throwable $e) {
|
|
|
- return '';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public static function parseBrowser(string $ua): string
|
|
|
- {
|
|
|
- if ($ua === '') {
|
|
|
- return '未知';
|
|
|
- }
|
|
|
-
|
|
|
- $map = [
|
|
|
- 'Edg' => 'Edge',
|
|
|
- 'Edge' => 'Edge',
|
|
|
- 'OPR' => 'Opera',
|
|
|
- 'Opera' => 'Opera',
|
|
|
- 'Chrome' => 'Chrome',
|
|
|
- 'Firefox' => 'Firefox',
|
|
|
- 'Safari' => 'Safari',
|
|
|
- 'MSIE' => 'IE',
|
|
|
- 'Trident' => 'IE',
|
|
|
- 'MicroMessenger' => '微信',
|
|
|
- 'QQBrowser' => 'QQ浏览器',
|
|
|
- 'UCBrowser' => 'UC浏览器',
|
|
|
- 'Telegram' => 'Telegram',
|
|
|
- ];
|
|
|
-
|
|
|
- foreach ($map as $needle => $name) {
|
|
|
- if (stripos($ua, $needle) === false) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if ($needle === 'Safari' && stripos($ua, 'Chrome') !== false) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if ($needle === 'Chrome' && (stripos($ua, 'Edg') !== false || stripos($ua, 'OPR') !== false)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- $version = '';
|
|
|
- if (preg_match('/' . preg_quote($needle, '/') . '[\/\s]?([\d.]+)/i', $ua, $m)) {
|
|
|
- $version = $m[1];
|
|
|
- } elseif ($needle === 'Trident' && preg_match('/rv:([\d.]+)/i', $ua, $m)) {
|
|
|
- $version = $m[1];
|
|
|
- }
|
|
|
- return $version !== '' ? "{$name}({$version})" : $name;
|
|
|
- }
|
|
|
-
|
|
|
- return '未知浏览器';
|
|
|
- }
|
|
|
-
|
|
|
- public static function parseOs(string $ua): string
|
|
|
- {
|
|
|
- if ($ua === '') {
|
|
|
- return '未知';
|
|
|
- }
|
|
|
-
|
|
|
- $rules = [
|
|
|
- '/windows nt 10/i' => 'Windows 10/11',
|
|
|
- '/windows nt 6\.3/i' => 'Windows 8.1',
|
|
|
- '/windows nt 6\.2/i' => 'Windows 8',
|
|
|
- '/windows nt 6\.1/i' => 'Windows 7',
|
|
|
- '/windows nt 6\.0/i' => 'Windows Vista',
|
|
|
- '/windows nt 5\.1|windows xp/i' => 'Windows XP',
|
|
|
- '/windows phone/i' => 'Windows Phone',
|
|
|
- '/android/i' => 'Android',
|
|
|
- '/iphone|ipad|ipod/i' => 'iOS',
|
|
|
- '/mac os x/i' => 'Mac OS',
|
|
|
- '/macintosh/i' => 'Mac OS',
|
|
|
- '/linux/i' => 'Linux',
|
|
|
- '/ubuntu/i' => 'Ubuntu',
|
|
|
- '/cros/i' => 'Chrome OS',
|
|
|
- ];
|
|
|
-
|
|
|
- foreach ($rules as $pattern => $name) {
|
|
|
- if (preg_match($pattern, $ua)) {
|
|
|
- if ($name === 'Android' && preg_match('/Android\s+([\d.]+)/i', $ua, $m)) {
|
|
|
- return 'Android ' . $m[1];
|
|
|
- }
|
|
|
- if ($name === 'iOS' && preg_match('/OS\s+([\d_]+)/i', $ua, $m)) {
|
|
|
- return 'iOS ' . str_replace('_', '.', $m[1]);
|
|
|
- }
|
|
|
- if ($name === 'Mac OS' && preg_match('/Mac OS X\s+([\d_]+)/i', $ua, $m)) {
|
|
|
- return 'Mac OS ' . str_replace('_', '.', $m[1]);
|
|
|
- }
|
|
|
- return $name;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return '未知操作系统';
|
|
|
- }
|
|
|
-}
|