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
相关产品推荐
相关产品推荐

