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

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>

问题原因

核心问题出在后端发送请求的方式:

  1. 使用pickle.dumps(data)序列化数据后通过requests.post的data参数发送,这种方式不符合HTTP多文件上传的规范,API端解析序列化数据时,列表形式的images字段可能丢失除最后一个元素外的所有内容。
  2. 将文件内容直接读取后存入列表再序列化,破坏了文件上传的原始多部分表单结构,导致数据传递异常。

解决方案

修改后端代码,改用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:19:52