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

Postman正常的Post接口在React+Axios调用时出现ERR_HTTP_HEADERS_SENT错误

解决ERR_HTTP_HEADERS_SENT错误:Node.js/Express商品上传接口问题

错误原因

ERR_HTTP_HEADERS_SENT错误本质是同一请求多次发送HTTP响应,代码里的问题点:

  1. db.query是异步操作,调用它之后立刻执行了res.status(200).send(...),此时数据库插入逻辑还未完成。
  2. 如果后续数据库查询出错(比如参数格式错误、表不存在等),回调里的return res.json(err)会尝试再次发送响应,违反HTTP“一请求一响应”的规则,触发错误。
  3. Postman测试时未触发数据库错误,所以问题没暴露;React Axios调用时可能因参数或环境差异触发了数据库错误,导致重复响应。

修复方案

方案1:改用Promise封装数据库操作(推荐)

将回调式的db.query转为Promise,用async/await控制异步流程,确保所有操作完成后再发送响应:

const addProduct = async (req, res) => {
    const files = req.files;
    const urls = [];
    const precio_final = parseInt(req.body.precio);
    const product = req.body.product;
    let q = '';
    let values = [];

    try {
        // 上传文件到S3
        for (const file of files) {
            const fileStream = fs.createReadStream(file.path);
            const folderName = product + "/";
            const key = folderName + file.originalname;
            const params = {
                Bucket: 'pisos-parador-colombia',
                Key: key,
                Body: fileStream
            };
            const command = new PutObjectCommand(params);
            await s3Client.send(command);
            const fileUrl = `https://pisos-parador-colombia.s3.us-east-2.amazonaws.com/${key}`;
            urls.push(fileUrl);
        }

        // 构建商品插入语句
        if (product === "pisos_laminados" || product === "pisos_estructurados") {
            q = `INSERT INTO ${product} (titulo,precio,descripcion, trafico, grosor, junta , medidas) VALUES (?)`;
            values = [
                req.body.titulo,
                precio_final,
                req.body.descripcion,
                req.body.trafico,
                req.body.grosor,
                req.body.junta,
                req.body.medidas
            ];
        } else {
            q = `INSERT INTO ${product} (titulo,precio,descripcion) VALUES (?)`;
            values = [
                req.body.titulo,
                precio_final,
                req.body.descripcion,
            ];
        }

        // 封装db.query为Promise
        const queryPromise = (sql, params) => {
            return new Promise((resolve, reject) => {
                db.query(sql, params, (err, data) => {
                    if (err) reject(err);
                    else resolve(data);
                });
            });
        };

        // 插入商品
        const productData = await queryPromise(q, [values]);
        console.log("product has been created succesfully!");
        const laminateFloorId = productData.insertId;

        // 批量插入图片URL
        const imageQueries = urls.map(url => {
            const imageValues = [url, laminateFloorId];
            const imageQuery = `INSERT INTO imagenes_${product} (image_url, product_id) VALUES (?, ?)`;
            return queryPromise(imageQuery, imageValues);
        });
        await Promise.all(imageQueries);
        console.log('All Image URLs have been stored successfully!');

        // 所有操作完成后发送响应
        res.status(200).send("everything added succesfully");
    } catch (err) {
        console.error("este es el error" + err.message);
        // 只发送一次错误响应
        res.status(500).send('Failed to upload files');
    }
};

module.exports = addProduct;

方案2:调整响应位置到回调内部

如果不想改Promise,也可以把res.send放到数据库操作的回调末尾,确保所有异步操作完成后再响应:

const addProduct = async (req, res) => {
    const files = req.files;
    const urls = [];
    const precio_final = parseInt(req.body.precio);
    const product = req.body.product;
    let q = '';
    let values = [];

    try {
        for (const file of files) {
            const fileStream = fs.createReadStream(file.path);
            const folderName = product + "/";
            const key = folderName + file.originalname;
            const params = {
                Bucket: 'pisos-parador-colombia',
                Key: key,
                Body: fileStream
            };
            const command = new PutObjectCommand(params);
            await s3Client.send(command);
            const fileUrl = `https://pisos-parador-colombia.s3.us-east-2.amazonaws.com/${key}`;
            urls.push(fileUrl);
        }

        if (product === "pisos_laminados" || product === "pisos_estructurados") {
            q = `INSERT INTO ${product} (titulo,precio,descripcion, trafico, grosor, junta , medidas) VALUES (?)`;
            values = [
                req.body.titulo,
                precio_final,
                req.body.descripcion,
                req.body.trafico,
                req.body.grosor,
                req.body.junta,
                req.body.medidas
            ];
        } else {
            q = `INSERT INTO ${product} (titulo,precio,descripcion) VALUES (?)`;
            values = [
                req.body.titulo,
                precio_final,
                req.body.descripcion,
            ];
        }

        db.query(q, [values], (err, data) => {
            if (err) {
                console.error(err);
                return res.status(500).json(err); // 统一错误响应
            }

            console.log("product has been created succesfully!");
            const laminateFloorId = data.insertId;

            // 统计图片插入完成数量
            let completed = 0;
            const total = urls.length;

            if (total === 0) {
                return res.status(200).send("everything added succesfully");
            }

            for (const url of urls) {
                const imageValues = [url, laminateFloorId];
                const imageQuery = `INSERT INTO imagenes_${product} (image_url, product_id) VALUES (?, ?)`;
                db.query(imageQuery, imageValues, (err, imageData) => {
                    if (err) console.error(err);
                    else console.log('Image URL has been stored successfully!');
                    
                    completed++;
                    // 所有图片插入完成后发送响应
                    if (completed === total) {
                        res.status(200).send("everything added succesfully");
                    }
                });
            }
        });

        // 移除提前发送的响应
        // res.status(200).send("everything added succesfully");
    } catch (err) {
        console.error("este es el error" + err.message);
        res.status(500).send('Failed to upload files');
    }
};

module.exports = addProduct;

额外注意事项

  • 要确保product参数的安全性,避免SQL注入(当前直接用${product}拼接SQL存在注入风险,建议用白名单过滤允许的表名)。
  • 处理文件上传后的临时文件清理(比如Multer生成的临时文件,上传到S3后要删除,避免占用磁盘空间)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:38:08