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

使用Retrofit 2+Node.js+Multer上传文件时req.body为空求助

Retrofit2 + Node.js/Multer 文件上传 req.body 为空问题解决

问题描述

使用Retrofit 2、Node.js、Express和Multer实现文件上传功能,文件可成功上传,但服务器端req.body始终为空,导致文件被上传到名为undefined的文件夹,文件名显示为「当前时间戳-undefined」。尝试将Android端createPartFromString方法中的RequestBody类型改为text/plain,问题依旧。


相关代码

Android端Retrofit代码

上传方法

private suspend fun uploadNewFiles(
    context: Context, files: List<FileData>,
    productModel: ProductModel
): Response<ProductModel> {
    val part = MultiPartUtils.prepareFilePart(context, "file", files[0].uri)

    val fileName = "${productModel.p_id.toString()}-${productModel.p_nm}"
    val fileNameRequestBody: RequestBody = MultiPartUtils.createPartFromString(fileName)

    val folderNameRequestBody: RequestBody = MultiPartUtils.createPartFromString("products")

    return productAPI.uploadSingleFile(part, fileNameRequestBody, folderNameRequestBody)
}

工具类

object MultiPartUtils {

    fun createPartFromString(descriptionString: String): RequestBody {
        return descriptionString.toRequestBody(MultipartBody.FORM)
    }

    fun prepareFilePart(context: Context, partName: String, fileUri: Uri): MultipartBody.Part {
        // 通过Uri获取实际文件
        val file: File = FileUtils.getFileNew(fileUri, context)

        // 从文件创建RequestBody
        val requestFile: RequestBody = file
            .asRequestBody(context.contentResolver.getType(fileUri)?.toMediaTypeOrNull())

        // 创建包含文件名的MultipartBody.Part
        return MultipartBody.Part.createFormData(partName, file.name, requestFile)
    }
}

Retrofit接口

@Multipart
@POST("/products/upload-products")
suspend fun uploadSingleFile(
    @Part ImageToUpload: MultipartBody.Part,
    @Part("fileName") fileName: RequestBody,
    @Part("folderName") folderName: RequestBody
): Response<ProductModel>

Node.js端代码

上传控制器

exports.uploadSingle = async (req, res) => {
    console.log("upload-1")
    const uploadFile = util.promisify(upload.single('file'));
    try {
        await uploadFile(req, res);
        res.status(200).send({message:"File Uploaded"})
    } catch (error) {
        res.status(401).json({message:"Error while uploading!"})
    }
}

Multer配置

const multer = require('multer');
const path = require('path');
const fs = require('fs-extra');

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        console.log(req.body) // 此处输出为空
        const { folderName } = req.body
        const path = `public/images/${folderName}/`;
        fs.mkdirSync(path, { recursive: true })
        cb(null, path)
    },
    filename: (req, file, cb) => {
        const { fileName } = req.body
        cb(null, Date.now()+"-"+fileName + path.extname(file.originalname))
    },
    limits: { fileSize: 10000000 },
    fileFilter: function (req, file, cb) {
        if (!file.originalname.match(/\.(jpg|JPG|webp|jpeg|JPEG|png|PNG|gif|GIF|jfif|JFIF)$/)) {
            req.fileValidationError = 'Only image files are allowed!';
            return cb(null, false);
        }
        cb(null, true);
    }
});

const upload = multer({ storage: storage }); 

module.exports = upload;

解决方案

核心原因

Multer在处理diskStorage的destination和filename回调时,表单字段的发送顺序直接影响req.body的解析时机:如果文件Part先于文本字段发送,Multer会优先处理文件,此时文本字段尚未被解析,req.body自然为空。你的Android代码中先构造并发送了文件Part,导致Node端无法获取到fileName和folderName。

修复步骤

  1. 调整Android端Part的发送顺序
    将文本字段(fileName、folderName)放在文件Part之前发送,确保Multer先解析文本字段:

    • 修改上传方法的调用顺序:
      return productAPI.uploadSingleFile(
          fileNameRequestBody,
          folderNameRequestBody,
          part // 文件Part移到最后
      )
      
    • 同步更新Retrofit接口的参数顺序:
      @Multipart
      @POST("/products/upload-products")
      suspend fun uploadSingleFile(
          @Part("fileName") fileName: RequestBody,
          @Part("folderName") folderName: RequestBody,
          @Part ImageToUpload: MultipartBody.Part
      ): Response<ProductModel>
      
  2. 验证调试
    在Node端的destination回调中添加日志,确认req.body是否正常获取到字段:

    destination: (req, file, cb) => {
        console.log("req.body content:", req.body); // 现在应能看到fileName和folderName
        const { folderName } = req.body;
        const filePath = `public/images/${folderName}/`;
        fs.mkdirSync(filePath, { recursive: true });
        cb(null, filePath);
    }
    

注意事项

  • 无需修改createPartFromString方法的RequestBody类型,保持MultipartBody.FORM即可,这是Multipart表单的标准类型。
  • 确保Node端Express中间件配置正确,不需要额外添加body-parser处理Multipart表单,Multer会自动解析。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 18:25:01