You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel中实现访客IP与时间戳至少1分钟间隔存储的方案问询

Laravel 页面访问日志去重优化方案

问题背景

Laravel应用中,未登录用户访问特定页面时会记录其IP地址与访问时间,但存在短时间内刷新页面导致同一IP针对同一提案在1分钟内重复存储日志的问题,需要实现同一IP针对同一提案仅间隔至少1分钟才记录一次的逻辑。

原始实现代码

核心业务代码

if (! auth()->check()) {
    $attributes = [
        'type' => 'Visited',
        'description' => 'The proposal was recently visited by someone with IP address '.\request()->getClientIp(),
        'ip' => \request()->getClientIp()
    ];

    (new ProposalLogController())->store($proposal, $attributes);
}

存储逻辑代码

public function store($proposal, $attributes)
{
    $proposalLog = new ProposalLog();
    $proposalLog->proposal_id       = $proposal->id;
    $proposalLog->event_type        = $attributes['type'];
    $proposalLog->event_description = $attributes['description'];
    $proposalLog->ip_address        = $attributes['ip'];
    $proposalLog->user_name         = $attributes['user']??'';
    $proposalLog->save();
}

现有尝试代码

if (! auth()->check()) {
    $log = ProposalLog::where('ip_address', \request()->getClientIp())->where('proposal_id', $proposal->id) ->latest()->first();
    if ($log) {
        $logTime = date('Y-m-d g:i', strtotime($log->created_at));
        $now     = Carbon::now()->format('Y-m-d g:i');

        if ($logTime !== $now) {
            $attributes = [
                'type' => 'Visited',
                'description' => 'The proposal was recently visited by someone with IP address '.\request()->getClientIp(),
                'ip' => \request()->getClientIp()
            ];
            (new ProposalLogController())->store($proposal, $attributes);
        }
    }
}

优化实现方案

方案1:利用Carbon精确时间比较

直接通过Laravel内置的Carbon扩展进行时间判断,避免字符串格式化带来的时区、12/24小时制误差,代码更简洁精准:

if (!auth()->check()) {
    $ip = request()->getClientIp();
    $lastLog = ProposalLog::where('ip_address', $ip)
        ->where('proposal_id', $proposal->id)
        ->latest()
        ->first();

    // 无历史日志,或最后一次日志已超过1分钟,才记录
    if (!$lastLog || $lastLog->created_at->addMinutes(1)->isPast()) {
        $attributes = [
            'type' => 'Visited',
            'description' => "The proposal was recently visited by someone with IP address {$ip}",
            'ip' => $ip
        ];
        (new ProposalLogController())->store($proposal, $attributes);
    }
}

方案2:数据库层面直接过滤(性能更优)

通过数据库查询直接判断是否存在1分钟内的日志,无需加载完整模型实例,减少内存占用:

if (!auth()->check()) {
    $ip = request()->getClientIp();
    $hasRecentLog = ProposalLog::where('ip_address', $ip)
        ->where('proposal_id', $proposal->id)
        ->where('created_at', '>=', now()->subMinutes(1))
        ->exists();

    if (!$hasRecentLog) {
        $attributes = [
            'type' => 'Visited',
            'description' => "The proposal was recently visited by someone with IP address {$ip}",
            'ip' => $ip
        ];
        (new ProposalLogController())->store($proposal, $attributes);
    }
}

方案3:封装模型方法(提高复用性)

将判断逻辑封装到ProposalLog模型中,让业务代码更简洁,便于后续维护:

// 在ProposalLog模型中添加静态方法
public static function shouldLogVisit($proposalId, $ip)
{
    return !self::where('ip_address', $ip)
        ->where('proposal_id', $proposalId)
        ->where('created_at', '>=', now()->subMinutes(1))
        ->exists();
}

业务代码中直接调用:

if (!auth()->check() && ProposalLog::shouldLogVisit($proposal->id, request()->getClientIp())) {
    $attributes = [
        'type' => 'Visited',
        'description' => "The proposal was recently visited by someone with IP address " . request()->getClientIp(),
        'ip' => request()->getClientIp()
    ];
    (new ProposalLogController())->store($proposal, $attributes);
}

方案4:缓存标记(高并发场景优化)

针对高访问量场景,利用缓存临时标记已记录的访问,减少数据库查询次数:

if (!auth()->check()) {
    $ip = request()->getClientIp();
    $cacheKey = "proposal_visit_{$proposal->id}_{$ip}";
    
    // 缓存1分钟,期间不再触发数据库写入
    if (!Cache::has($cacheKey)) {
        $attributes = [
            'type' => 'Visited',
            'description' => "The proposal was recently visited by someone with IP address {$ip}",
            'ip' => $ip
        ];
        (new ProposalLogController())->store($proposal, $attributes);
        Cache::put($cacheKey, true, 60); // 缓存有效期60秒
    }
}

内容的提问来源于stack exchange,提问作者Vic Andam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 00:25:20