修复(chat): 新增真实 IP 获取中间件及重构用户 IP 轨迹追踪逻辑
- 新增 CloudflareProxies 前置中间件,强制解析 CDN 透传的 CF-Connecting-IP 与 X-Real-IP 并在底层接管,修复 Nginx 代理造成的全局 IP 同化 (127.0.0.1) 问题 - 修改 User 模型,新增 migration 以补全真正的 previous_ip 储存通道 - 修改 AuthController 登录逻辑,在覆写 last_ip 前实现向 previous_ip 的自动历史快照备份 - 修改 UserController API 返回逻辑,实现 first_ip、last_ip(上次)以及 login_ip(本次)的三轨分离 - 更新 user-actions.blade.php 管理员视野面板,同步增加并校验“首次IP”、“上次IP”、“本次IP”三级字段映射的准确性
This commit is contained in:
@@ -130,8 +130,9 @@ class AuthController extends Controller
|
||||
// 递增访问次数
|
||||
$user->increment('visit_num');
|
||||
|
||||
// 更新最后登录IP和时间
|
||||
// 更新最后登录IP和时间(同时将旧IP转移到 previous_ip 作上次登录记录)
|
||||
$user->update([
|
||||
'previous_ip' => $user->last_ip,
|
||||
'last_ip' => $ip,
|
||||
'log_time' => now(),
|
||||
'in_time' => now(),
|
||||
|
||||
@@ -83,8 +83,10 @@ class UserController extends Controller
|
||||
// 拥有封禁IP(level_banip)或踢人以上权限的管理,可以查看IP和归属地
|
||||
$levelBanIp = (int) Sysparam::getValue('level_banip', '15');
|
||||
if ($operator && $operator->user_level >= $levelBanIp) {
|
||||
$data['last_ip'] = $targetUser->last_ip;
|
||||
// last_ip 在每次登录时更新,即为用户最近一次登录的 IP(本次IP)
|
||||
$data['first_ip'] = $targetUser->first_ip;
|
||||
// last_ip 目前定义为『上次登录IP』(取数据库 previous_ip)
|
||||
$data['last_ip'] = $targetUser->previous_ip;
|
||||
// login_ip 目前定义为『本次登录IP』(取数据库 last_ip)
|
||||
$data['login_ip'] = $targetUser->last_ip;
|
||||
|
||||
// 解析归属地:使用 ip2region 离线库,直接返回原生中文(省|市|ISP)
|
||||
|
||||
32
app/Http/Middleware/CloudflareProxies.php
Normal file
32
app/Http/Middleware/CloudflareProxies.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CloudflareProxies
|
||||
{
|
||||
/**
|
||||
* 文件功能:强制信任并解析 CDN 传导的真实客户端 IP。
|
||||
* 解决 Herd 环境 / Nginx 本地反代时,丢失 X-Forwarded-For 导致全员 IP 变成 127.0.0.1 的问题。
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// 优先采纳 Cloudflare 的 CF-Connecting-IP
|
||||
if ($request->hasHeader('CF-Connecting-IP')) {
|
||||
$realIp = $request->header('CF-Connecting-IP');
|
||||
$request->server->set('REMOTE_ADDR', $realIp);
|
||||
$request->headers->set('X-Forwarded-For', $realIp);
|
||||
}
|
||||
// 其次兜底常见的国内 CDN 厂商(如腾讯云 EdgeOne / 阿里云 DCDN)
|
||||
elseif ($request->hasHeader('X-Real-IP')) {
|
||||
$realIp = $request->header('X-Real-IP');
|
||||
$request->server->set('REMOTE_ADDR', $realIp);
|
||||
$request->headers->set('X-Forwarded-For', $realIp);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ class User extends Authenticatable
|
||||
'user_level',
|
||||
'room_id',
|
||||
'first_ip',
|
||||
'previous_ip',
|
||||
'last_ip',
|
||||
'usersf',
|
||||
'vip_level_id',
|
||||
|
||||
@@ -12,14 +12,17 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
// 强制解析并信任 CDN (如 Cloudflare) 透传的真实 IP (最高优先级)
|
||||
$middleware->prepend(\App\Http\Middleware\CloudflareProxies::class);
|
||||
|
||||
// 信任所有代理转发头(腾讯 EdgeCDN HTTPS 回源 HTTP 场景)
|
||||
// CDN 携带 X-Forwarded-Proto: https,Laravel 据此将请求识别为 HTTPS,url()/route() 生成正确的 https:// 链接
|
||||
$middleware->trustProxies(at: '*');
|
||||
|
||||
$middleware->alias([
|
||||
'chat.auth' => \App\Http\Middleware\ChatAuthenticate::class,
|
||||
'chat.level' => \App\Http\Middleware\LevelRequired::class,
|
||||
'chat.site_owner' => \App\Http\Middleware\SiteOwnerRequired::class,
|
||||
'chat.auth' => \App\Http\Middleware\ChatAuthenticate::class,
|
||||
'chat.level' => \App\Http\Middleware\LevelRequired::class,
|
||||
'chat.site_owner' => \App\Http\Middleware\SiteOwnerRequired::class,
|
||||
'chat.has_position' => \App\Http\Middleware\HasActivePosition::class,
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('previous_ip', 45)->nullable()->after('first_ip')->comment('真正的上一次登录IP');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('previous_ip');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -664,7 +664,9 @@
|
||||
style="display: none; padding: 8px 10px; background: #fff5f5; border: 1px dashed #fca5a5;
|
||||
border-top: none; border-radius: 0 0 8px 8px; font-size: 11px; color: #991b1b;">
|
||||
<div style="display: flex; flex-direction: column; gap: 3px;">
|
||||
<div><span style="opacity: 0.8;">主要IP:</span><span x-text="userInfo.last_ip || '无'"></span>
|
||||
<div><span style="opacity: 0.8;">首次IP:</span><span x-text="userInfo.first_ip || '无'"></span>
|
||||
</div>
|
||||
<div><span style="opacity: 0.8;">上次IP:</span><span x-text="userInfo.last_ip || '无'"></span>
|
||||
</div>
|
||||
<div><span style="opacity: 0.8;">本次IP:</span><span x-text="userInfo.login_ip || '无'"></span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user