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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 05:17:34