Laravel 9x:调用PNG接口时模型重复创建记录问题
单次PNG路由调用导致数据库插入两条记录的解决方法
我开发了一个邮件追踪小应用,当访问PNG路由时,会返回PNG图片并在数据库插入一条访问记录。但出现异常:单次URL调用会在email_tracker_histories表中插入两条记录;若注释掉响应中的->header('Content-Type', 'image/png'),则仅插入一条记录(符合预期),但图片会以二进制形式返回,无法正常显示。
预期行为:单次URL调用仅插入一条记录,同时正常返回PNG图片。
控制器代码
<?php namespace App\Http\Controllers; use App\Models\EmailTrackerHistory; use Illuminate\Http\Request; use App\Models\EmailTracker; class EmailTrackerController extends Controller { public function getPixel(Request $request) { $trackerId = $request->get('tracker'); $filename = 'ma-logo.png'; $model = EmailTracker::find($trackerId); if ($model->empty_pixel == true) { $filename = 'pixel.png'; } $img = public_path($filename); $history = new EmailTrackerHistory(); $history->email_tracker_id = $trackerId; $history->open_time = now(); $history->opener_ip = $request->ip(); $history->opener_user_agent = $request->userAgent(); if ($history->save()) { $content = file_get_contents($img); return response($content) ->header('Content-Type', 'image/png') ->header('Content-Disposition', 'inline; filename="' . $filename . '"') ->header('Content-Transfer-Encoding', 'binary') ->header('Expires', '0') ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->header('Pragma', 'public') ->header('Content-Length', strlen($content)); } abort(500); } }
模型代码
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class EmailTrackerHistory extends Model { use HasFactory; }
路由代码
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\EmailTrackerController; Route::get('/logo.png', [EmailTrackerController::class, 'getPixel'])->name('getPixel');
问题根源
这种情况通常是客户端(浏览器/邮件客户端)对图片资源发起了重复请求:当返回正确的image/png头时,客户端可能因缓存机制、预加载或资源验证(比如检查图片是否可访问)发起第二次请求;而返回二进制时,客户端识别不到这是图片,不会发起二次请求。
解决方案
方案1:使用Laravel内置文件响应方法(推荐)
Laravel的response()->file()方法会自动处理所有正确的HTTP头,避免手动设置头可能带来的问题,同时降低客户端重复请求的概率:
if ($history->save()) { // 用file方法返回图片,自动处理Content-Type等头信息 return response()->file($img, [ 'Content-Disposition' => 'inline; filename="' . $filename . '"', 'Cache-Control' => 'no-store, no-cache, must-revalidate', 'Expires' => '0', 'Pragma' => 'no-cache' ]); }
方案2:添加请求去重逻辑
如果客户端仍发起重复请求,可在保存记录前检查最近几秒内是否有相同访问记录,避免重复插入:
// 创建记录前先做去重检查 $recentRecord = EmailTrackerHistory::where('email_tracker_id', $trackerId) ->where('opener_ip', $request->ip()) ->where('opener_user_agent', $request->userAgent()) ->where('open_time', '>=', now()->subSeconds(5)) // 检查最近5秒内的重复记录 ->first(); if (!$recentRecord) { $history = new EmailTrackerHistory(); $history->email_tracker_id = $trackerId; $history->open_time = now(); $history->opener_ip = $request->ip(); $history->opener_user_agent = $request->userAgent(); $history->save(); } // 无论是否新增记录,都返回图片 return response()->file($img, [ 'Content-Disposition' => 'inline; filename="' . $filename . '"' ]);
方案3:优化缓存头
调整缓存控制头,明确告知客户端不要缓存该图片,避免重复验证请求:
return response($content) ->header('Content-Type', 'image/png') ->header('Content-Disposition', 'inline; filename="' . $filename . '"') ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') ->header('Expires', 'Thu, 01 Jan 1970 00:00:00 GMT') ->header('Pragma', 'no-cache');
内容的提问来源于stack exchange,提问作者Mahmoud Abdelsattar
相关产品推荐
相关产品推荐

