HTML表单多文件上传异常:仅最后一张图片提交成功
多张附加图片上传仅最后一张生效的问题排查与解决
我正在开发一个支持游戏及多张附加图片上传的网站,图片会提交至API,打开游戏页面时从API获取图片展示。但目前选择多张附加图片上传时,仅最后一张被成功提交。我已在file输入框添加multiple属性,但问题仍存在。以下是相关代码:
HTML表单代码
{% extends "base.html" %} {% block body %} <form id="chloroform" action="/senddata" method="post" enctype=multipart/form-data > <h2>Загрузить игру</h2> <fieldset> <legend>Основные данные</legend> <label for="title">Название игры:</label> <input type="text" name="title" id="title" maxlength="24" required> </fieldset> <div class="container col-md-6" style="float:right; position: relative; top: -100px; left: 20px"> <p style="outline:2px solid #555; border-radius:5px; width: 300px; height: 300px;"> <img id="frame" src="" class="img-fluid" style="max-width: 300px; max-height: 300px; " /> </p> <div class="mb-5"> <input class="form-control" name="prev" type="file" id="formFile" onchange="preview()" accept="image/png" required> </div> </div> <script> function checkImage(file) { return new Promise((resolve, reject) => { if (file.type === 'image/png') { const img = new Image(); img.onload = () => { if (img.width === img.height) { resolve(); } else { reject(new Error('Изображение должно быть квадратным!')); } }; img.src = URL.createObjectURL(file); } else { reject(new Error('Изображение должно быть в формате PNG!')); } }); } function preview() { const file = event.target.files[0]; checkImage(file) .then(() => { const frame = document.getElementById('frame'); frame.src = URL.createObjectURL(file); frame.style.display = 'block'; }) .catch(error => { alert(error.message); document.getElementById('formFile').value = null; const frame = document.getElementById('frame'); frame.src = ''; frame.style.display = 'none'; }); } </script> <!-- THIS IS IMAGES WHERE I GET THE PROBLEM--> <fieldset> <legend>Additional data</legend> <div> <textarea name="desc" placeholder="Brief description" id="desc" style="width: 312px; height: 150px;" required></textarea> </div> <div> </div> </fieldset> <fieldset> <div> <legend for="file" class="form-label">Zip file </legend> <input class="form-control form-control-lg" name="file" id="file" type="file" accept=".zip" required> <script> document.getElementById('file').onchange = function(e) { if(!e.target.files[0].name.match(/\.(zip)$/)) { alert('Only .zip!'); e.target.value = ''; } }; </script> </div> <div> <legend for='files'>Select additional images: </legend> <label>Only png!</label> <input id='files' type='file' class="btn btn-outline-info" name="files" accept="image/png" multiple> </div> </fieldset> <div> <button type="submit">Submit</button> </div> </form> {% endblock %}
Flask后端数据处理代码
@app.route('/senddata', methods=['POST', 'GET']) def senddata(): url = f"{api_url}/games" if request.method == 'POST': title = request.form.get('title') desc = request.form.get('desc', '') author = int(current_user.get_id()) prev_file = request.files['prev'] zip_file = request.files['file'] files = request.files.getlist('files') if not prev_file.filename.endswith('.png'): prev_file = open('url_for(static, filename=images/default.png)', 'rb') images = [] for file in files: if file.filename.lower().endswith('.png'): images.append(("png", file.read())) print('Check') data = { "title": title, "desc": desc, "author": author, "prev": ("png", prev_file.read()), "file": ("zip", zip_file.read()), "images": images } requests.post(url, data=pickle.dumps(data)).json() return redirect('/index')
前端展示代码
<div id="info" style="width: 48vw"> <p id="infoframe" style="outline:2px solid #555; border-radius:5px; width: 100%; min-height: 15vw;"> {{game['desc']}} </p> <div id="imgframe" style="width: 100%"> <div id="images" class="carousel slide carousel-fade" data-bs-ride="carousel" style="width: max-content; margin: auto"> <div class="carousel-inner"> {% for image in game['images'] %} <div class="carousel-item {% if loop.first %}active{% endif %}"> <img src="{{api_url}}/file?p={{image['img']}}" class="d-block w-100" style="max-width: 20vw;max-height: 20vw;"> </div> {% endfor %} </div> <button class="carousel-control-prev" type="button" data-bs-target="#images" data-bs-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="visually-hidden">Предыдущая</span> </button> <button class="carousel-control-next" type="button" data-bs-target="#images" data-bs-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="visually-hidden">Следующая</span> </button> </div> </div> </div>
问题原因
核心问题出在后端发送请求的方式:
- 使用
pickle.dumps(data)序列化数据后通过requests.post的data参数发送,这种方式不符合HTTP多文件上传的规范,API端解析序列化数据时,列表形式的images字段可能丢失除最后一个元素外的所有内容。 - 将文件内容直接读取后存入列表再序列化,破坏了文件上传的原始多部分表单结构,导致数据传递异常。
解决方案
修改后端代码,改用requests的files参数构建符合规范的多部分表单请求,同时调整数据传递方式:
@app.route('/senddata', methods=['POST', 'GET']) def senddata(): url = f"{api_url}/games" if request.method == 'POST': title = request.form.get('title') desc = request.form.get('desc', '') author = int(current_user.get_id()) prev_file = request.files['prev'] zip_file = request.files['file'] files = request.files.getlist('files') # 处理默认预览图 if not prev_file.filename.lower().endswith('.png'): default_path = url_for('static', filename='images/default.png') prev_file = open(default_path[1:], 'rb') # 去掉开头的/,使用本地路径 # 构建请求的files字典 request_files = { "prev": ("prev.png", prev_file, "image/png"), "file": ("game.zip", zip_file, "application/zip") } # 添加附加图片,注意字段名要和API预期一致,比如用images[]表示数组 for idx, file in enumerate(files): if file.filename.lower().endswith('.png'): request_files[f"images[{idx}]"] = (file.filename, file, "image/png") # 构建表单数据 form_data = { "title": title, "desc": desc, "author": str(author) } # 发送请求,用data传表单字段,files传文件 response = requests.post(url, data=form_data, files=request_files) response.json() return redirect('/index')
另外需要确认API端是否支持接收images[]这样的数组形式文件字段,或者调整字段名以匹配API的预期格式。
内容的提问来源于stack exchange,提问作者MrLeviReaper
相关产品推荐
相关产品推荐

