diff --git a/app/Listeners/LogSuccessfulLogin.php b/app/Listeners/LogSuccessfulLogin.php index c10fc79..f63a186 100644 --- a/app/Listeners/LogSuccessfulLogin.php +++ b/app/Listeners/LogSuccessfulLogin.php @@ -34,10 +34,36 @@ class LogSuccessfulLogin */ public function handle(Login $event) { + $ip = $this->request->ip(); + $userAgent = $this->request->userAgent(); + + // 防重覆機制 (Debouncing): 10 秒內同使用者、同 IP 的記錄視為重複 + $recentLog = UserLoginLog::where('user_id', $event->user->id) + ->where('ip_address', $ip) + ->where('login_at', '>=', now()->subSeconds(10)) + ->first(); + + if ($recentLog) { + return; + } + + $agent = new \Jenssegers\Agent\Agent(); + $agent->setUserAgent($userAgent); + + $deviceType = 'desktop'; + if ($agent->isTablet()) { + $deviceType = 'tablet'; + } elseif ($agent->isMobile()) { + $deviceType = 'mobile'; + } + UserLoginLog::create([ 'user_id' => $event->user->id, - 'ip_address' => $this->request->ip(), - 'user_agent' => $this->request->userAgent(), + 'ip_address' => $ip, + 'user_agent' => $userAgent, + 'device_type' => $deviceType, + 'browser' => $agent->browser(), + 'platform' => $agent->platform(), 'login_at' => now(), ]); } diff --git a/app/Models/UserLoginLog.php b/app/Models/UserLoginLog.php index c86211f..dc70a54 100644 --- a/app/Models/UserLoginLog.php +++ b/app/Models/UserLoginLog.php @@ -13,6 +13,9 @@ class UserLoginLog extends Model 'user_id', 'ip_address', 'user_agent', + 'device_type', + 'browser', + 'platform', 'login_at', ]; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 7cf7f53..0336d56 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,9 +25,6 @@ class AppServiceProvider extends ServiceProvider if (!$this->app->isLocal()) { \Illuminate\Support\Facades\URL::forceScheme('https'); } - - // 記錄使用者成功登入的歷史 - Event::listen(Login::class, LogSuccessfulLogin::class); } } diff --git a/composer.json b/composer.json index 76680ea..fc678cd 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "require": { "php": "^8.2", "guzzlehttp/guzzle": "^7.8", + "jenssegers/agent": "^2.6", "laravel/framework": "^12.0", "laravel/sanctum": "^4.3", "laravel/tinker": "^2.10.1" @@ -67,4 +68,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index bf4158e..a5051d5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc689fd91200cf19e401759d009094a3", + "content-hash": "0c5398ab8233c21548345608b86027cc", "packages": [ { "name": "brick/math", @@ -1052,6 +1052,141 @@ ], "time": "2025-08-22T14:27:06+00:00" }, + { + "name": "jaybizzle/crawler-detect", + "version": "v1.3.7", + "source": { + "type": "git", + "url": "https://github.com/JayBizzle/Crawler-Detect.git", + "reference": "7f7a45b5d5df9c95ba6b2008544e6cf8e66de6f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/7f7a45b5d5df9c95ba6b2008544e6cf8e66de6f5", + "reference": "7f7a45b5d5df9c95ba6b2008544e6cf8e66de6f5", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.5|^6.5|^7.5|^8.5|^9.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jaybizzle\\CrawlerDetect\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Beech", + "email": "m@rkbee.ch", + "role": "Developer" + } + ], + "description": "CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent", + "homepage": "https://github.com/JayBizzle/Crawler-Detect/", + "keywords": [ + "crawler", + "crawler detect", + "crawler detector", + "crawlerdetect", + "php crawler detect" + ], + "support": { + "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.3.7" + }, + "time": "2026-02-02T19:15:54+00:00" + }, + { + "name": "jenssegers/agent", + "version": "v2.6.4", + "source": { + "type": "git", + "url": "https://github.com/jenssegers/agent.git", + "reference": "daa11c43729510b3700bc34d414664966b03bffe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jenssegers/agent/zipball/daa11c43729510b3700bc34d414664966b03bffe", + "reference": "daa11c43729510b3700bc34d414664966b03bffe", + "shasum": "" + }, + "require": { + "jaybizzle/crawler-detect": "^1.2", + "mobiledetect/mobiledetectlib": "^2.7.6", + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5.0|^6.0|^7.0" + }, + "suggest": { + "illuminate/support": "Required for laravel service providers" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Agent": "Jenssegers\\Agent\\Facades\\Agent" + }, + "providers": [ + "Jenssegers\\Agent\\AgentServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Jenssegers\\Agent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jens Segers", + "homepage": "https://jenssegers.com" + } + ], + "description": "Desktop/mobile user agent parser with support for Laravel, based on Mobiledetect", + "homepage": "https://github.com/jenssegers/agent", + "keywords": [ + "Agent", + "browser", + "desktop", + "laravel", + "mobile", + "platform", + "user agent", + "useragent" + ], + "support": { + "issues": "https://github.com/jenssegers/agent/issues", + "source": "https://github.com/jenssegers/agent/tree/v2.6.4" + }, + "funding": [ + { + "url": "https://github.com/jenssegers", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/jenssegers/agent", + "type": "tidelift" + } + ], + "time": "2020-06-13T08:05:20+00:00" + }, { "name": "laravel/framework", "version": "v12.53.0", @@ -2082,6 +2217,68 @@ ], "time": "2026-01-15T06:54:53+00:00" }, + { + "name": "mobiledetect/mobiledetectlib", + "version": "2.8.45", + "source": { + "type": "git", + "url": "https://github.com/serbanghita/Mobile-Detect.git", + "reference": "96aaebcf4f50d3d2692ab81d2c5132e425bca266" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/96aaebcf4f50d3d2692ab81d2c5132e425bca266", + "reference": "96aaebcf4f50d3d2692ab81d2c5132e425bca266", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.36" + }, + "type": "library", + "autoload": { + "psr-0": { + "Detection": "namespaced/" + }, + "classmap": [ + "Mobile_Detect.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Serban Ghita", + "email": "serbanghita@gmail.com", + "homepage": "http://mobiledetect.net", + "role": "Developer" + } + ], + "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.", + "homepage": "https://github.com/serbanghita/Mobile-Detect", + "keywords": [ + "detect mobile devices", + "mobile", + "mobile detect", + "mobile detector", + "php mobile detect" + ], + "support": { + "issues": "https://github.com/serbanghita/Mobile-Detect/issues", + "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.45" + }, + "funding": [ + { + "url": "https://github.com/serbanghita", + "type": "github" + } + ], + "time": "2023-11-07T21:57:25+00:00" + }, { "name": "monolog/monolog", "version": "3.10.0", diff --git a/database/migrations/2026_03_13_022045_add_device_info_to_user_login_logs_table.php b/database/migrations/2026_03_13_022045_add_device_info_to_user_login_logs_table.php new file mode 100644 index 0000000..939a815 --- /dev/null +++ b/database/migrations/2026_03_13_022045_add_device_info_to_user_login_logs_table.php @@ -0,0 +1,30 @@ +string('device_type')->nullable()->after('user_agent'); // desktop, mobile, tablet + $table->string('browser')->nullable()->after('device_type'); + $table->string('platform')->nullable()->after('browser'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('user_login_logs', function (Blueprint $table) { + $table->dropColumn(['device_type', 'browser', 'platform']); + }); + } +}; diff --git a/resources/views/profile/partials/login-history.blade.php b/resources/views/profile/partials/login-history.blade.php index 6817664..85a164a 100644 --- a/resources/views/profile/partials/login-history.blade.php +++ b/resources/views/profile/partials/login-history.blade.php @@ -33,10 +33,22 @@ {{ __('Success') }} -

- {{ __('Device:') }} - {{ $log->user_agent }} -

+
+
+ @if($log->device_type === 'mobile') + + @elseif($log->device_type === 'tablet') + + @else + + @endif + {{ $log->platform ?: 'Unknown OS' }} +
+ + + {{ $log->browser ?: 'Unknown Browser' }} + +