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

Laravel集成Dropzone提交表单时Masseur模型重复入库问题

问题描述

我有两个模型:Masseur和MasseurImage,二者通过belongsTo()关联。我搭建了一个包含基础输入项和Dropzone图片上传组件的表单,预期提交「创建新按摩师」表单时,在数据库中创建一条Masseur记录,并根据Dropzone中添加的图片数量创建多条MasseurImage记录。

目前所有功能基本正常,但表单提交后,Masseur模型会被重复存入数据库两次。


视图代码

<form method="post" id="etCreateMasseurForm" action="{{ route('masseur.add') }}" name="etCreateMasseurForm" enctype="multipart/form-data">
    @csrf
    <input type="hidden" name="masseur_id" id="masseur_id">
    <div class="form-group mb-3">
        <label class="form-label" for="nickname">Becenév</label>
        <input class="form-control" type="text" name="nickname" id="nickname" required>
    </div>
    <div class="form-group mb-3">
        <label class="form-label" for="name">Név</label>
        <input class="form-control" type="text" name="name" id="name" required>
    </div>
    <div id="dropzoneDragArea" class="dropzone dz-default dz-message dropzoneDragArea form-control"></div>
</form>

控制器代码

public function store(Request $request)
{
    $masseur = Masseur::create($request->all());
    if ($request->hasFile('file')) {
        foreach ($request->file('file') as $file) {
            $filename = $file->store('public/files/'.$masseur->id);
            MasseurImage::create([
                'masseur_id' => $masseur->id,
                'filename' => $filename
            ]);
        }
    }
    
    return response()->json(['status' => "success", 'masseur_id'=> $masseur->id]);
}

JavaScript代码

Dropzone.autoDiscover = false;
let token = $('meta[name="csrf-token"]').attr('content');
$(function () {
    var myDropzone = new Dropzone("div#dropzoneDragArea", {
        paramName: "file",
        url: "{{ route('masseur.add') }}",
        addRemoveLinks: true,
        autoProcessQueue: false,
        uploadMultiple: true,
        parallelUploads: 100,
        maxFiles: 100,
        params: {
            _token: token
        },
        init: function () {
            var myDropzone = this;
            $("form[name='etCreateMasseurForm']").submit(function (event) {
                event.preventDefault();
                URL = $("#etCreateMasseurForm").attr('action');
                formData = $('#etCreateMasseurForm').serialize();
                $.ajax({
                    type: 'POST',
                    url: URL,
                    data: formData,
                    success: function (result) {
                        if (result.status == "success") {
                            var masseur_id = result.masseur_id;
                            $("#masseur_id").val(masseur_id);
                            myDropzone.processQueue();
                        } else {
                            console.log("error");
                        }
                    }
                });
            });
            this.on('sending', function (file, xhr, formData) {
                let masseur_id = document.getElementById('masseur_id').value;
                formData.append('masseur_id', masseur_id);
            });
            this.on("success", function (file, response) {
                window.location.href = "/";
            });
            this.on("queuecomplete", function () {
            });
        }
    });
});

路由代码

Route::post('store-masseur', [MasseurController::class, 'store'])->name('masseur.add');

问题原因

重复创建Masseur的核心原因是两次调用了同一个store接口:

  1. 表单提交时的AJAX请求会触发一次store方法,创建第一条Masseur记录
  2. Dropzone执行processQueue时,会再次向同一个store路由发送请求,而控制器代码不管请求是否包含图片,都会先执行Masseur::create($request->all()),因此会创建第二条重复的Masseur记录
解决方案

方案一:在原控制器中增加判断逻辑

修改store方法,通过请求中的masseur_id判断是否为图片上传请求,仅在无masseur_id时创建新的Masseur:

public function store(Request $request)
{
    // 若请求携带masseur_id,说明是上传图片的请求,直接获取已有记录
    if ($request->masseur_id) {
        $masseur = Masseur::findOrFail($request->masseur_id);
    } else {
        // 无masseur_id则创建新的Masseur
        $masseur = Masseur::create($request->all());
    }

    if ($request->hasFile('file')) {
        foreach ($request->file('file') as $file) {
            $filename = $file->store('public/files/'.$masseur->id);
            MasseurImage::create([
                'masseur_id' => $masseur->id,
                'filename' => $filename
            ]);
        }
    }

    return response()->json(['status' => "success", 'masseur_id'=> $masseur->id]);
}

方案二:拆分路由和控制器方法(推荐,逻辑更清晰)

将创建Masseur和上传图片的逻辑拆分到不同的接口,避免冲突:

1. 修改路由

// 创建按摩师接口
Route::post('masseur', [MasseurController::class, 'createMasseur'])->name('masseur.create');
// 按摩师图片上传接口
Route::post('masseur/{masseur}/images', [MasseurController::class, 'uploadImages'])->name('masseur.images.upload');

2. 新增控制器方法

// 创建按摩师
public function createMasseur(Request $request)
{
    $masseur = Masseur::create($request->all());
    return response()->json(['status' => "success", 'masseur_id'=> $masseur->id]);
}

// 上传按摩师图片
public function uploadImages(Request $request, Masseur $masseur)
{
    if ($request->hasFile('file')) {
        foreach ($request->file('file') as $file) {
            $filename = $file->store('public/files/'.$masseur->id);
            MasseurImage::create([
                'masseur_id' => $masseur->id,
                'filename' => $filename
            ]);
        }
    }
    return response()->json(['status' => "success"]);
}

3. 修改前端JavaScript代码

调整Dropzone的上传URL,并在创建Masseur成功后替换URL中的按摩师ID:

Dropzone.autoDiscover = false;
let token = $('meta[name="csrf-token"]').attr('content');
$(function () {
    var myDropzone = new Dropzone("div#dropzoneDragArea", {
        paramName: "file",
        // 先设置占位符URL
        url: "{{ route('masseur.images.upload', ['masseur' => '']) }}",
        addRemoveLinks: true,
        autoProcessQueue: false,
        uploadMultiple: true,
        parallelUploads: 100,
        maxFiles: 100,
        params: {
            _token: token
        },
        init: function () {
            var myDropzone = this;
            $("form[name='etCreateMasseurForm']").submit(function (event) {
                event.preventDefault();
                // 修改为创建按摩师的接口
                URL = "{{ route('masseur.create') }}";
                formData = $('#etCreateMasseurForm').serialize();
                $.ajax({
                    type: 'POST',
                    url: URL,
                    data: formData,
                    success: function (result) {
                        if (result.status == "success") {
                            var masseur_id = result.masseur_id;
                            $("#masseur_id").val(masseur_id);
                            // 替换Dropzone的上传URL为实际的图片上传接口
                            myDropzone.options.url = "{{ route('masseur.images.upload', ['masseur' => '']) }}" + masseur_id;
                            myDropzone.processQueue();
                        } else {
                            console.log("error");
                        }
                    }
                });
            });
            this.on("success", function (file, response) {
                window.location.href = "/";
            });
            this.on("queuecomplete", function () {
            });
        }
    });
});

同时修改表单的action属性:

<form method="post" id="etCreateMasseurForm" action="{{ route('masseur.create') }}" name="etCreateMasseurForm" enctype="multipart/form-data">

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:50:29