【问题标题】:Files sent through form with multipart/form-data are sent as empty object通过带有 multipart/form-data 的表单发送的文件作为空对象发送
【发布时间】:2018-10-19 01:08:32
【问题描述】:

我正在创建一个 VueJS 组件,它是一个询问用户信息并允许上传多个图像的表单。

表单有 enctype="multipart/form-data" 并且看起来像这样:

<template>
  <div>        
    <form enctype="multipart/form-data" novalidate>
        <label>Name</label>
        <input type="text" v-model="name">
        <label>Surname</label>
        <input type="text" v-model="surname">
        <label> Description </label>
        <input type="text" v-model="description">        
        <input 
         type="file"
         id="resources"
         ref="images"
         multiple 
         @change="handleFileUpload()"
         accept="image/*">   

    <button @click="clear">Clear Form</button>
     <button @click="submit"> Submit  </button>
        </div>
      </form>
 </template>

我的函数 handleFileUpload() 是这样做的:

this.images = this.$refs.images.files

我的提交函数如下所示:

submit ()  {
// Saves image to formData 
let formData = new FormData()

for( var i = 0; i < this.images.length; i++ ){
   let img = this.images[i];
   formData.append('image[' + i + ']', img);
}

let newUser = {
  name : this.name,
  surname:this.surname,
  description : this.description,
  images : formData
}

axios.post('http://localhost:3000/api/users', newUser)
 .then(()=>{console.log('ok')})
 .catch((err)=>{console.log(err)})
}

我的问题是来自 newUser 的 images 字段作为空对象发送。我不确定我是否应该发送这样的 formData 或者我为此构建的 express API 存在一些问题:

var express = require('express');
var cors = require('cors');
var bodyParser = require('body-parser');
var app = express();

app.use(bodyParser.json());

app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(cors());


let users = [],

app.post('/api/users',(req,res) => {
  const newUser = req.body
  users.push(newUser)
  res.json(users)
}

API 中 newUser 的 console.log 会返回:

{ name: 'test',      
  surname: 'test',
  description: 'test',
  images: {} }

【问题讨论】:

    标签: javascript file express vue.js multipartform-data


    【解决方案1】:

    我也在为我的 api 服务器项目中的 empty request body 苦苦挣扎。 我想将由字符串/文本数据和图像组成的多部分形式数据发布到后端 RESTful api 服务器。我认为仅通过 Axios 使用 formData 对象传递数据就足够了。然而,我最终几乎感到沮丧,因为在后端的 restful api 项目中,我一直得到空的请求正文。谢天谢地,通过搜索一些文章、stackoverflow 和 youtube。我设法组装了一个完整的 node.js 程序。不再有空的请求正文数据!!! . 在这里我想分享一下我的工作代码。

    为了将 multipart/form-data 从客户端传递到服务器,我发现我们使用了几个中间件和 node/java 对象:

    • 在服务器端 api 项目中:我使用 {multer, body-parser} 中间件
    • 在客户端应用项目中:我使用 formData 和 axios

    这是我的工作代码:(在 node.js 中编码,使用 Visual Studio 代码)

    // in my (server backend api) node project :  /src/routes/products/index.js
    
    const express = require("express")
    const router = express.Router()
    const multer = require('multer')
    const ProductsController = require('../controllers/products')
    
    const storage = multer.diskStorage({
      destination: function(req, file, cb) {
        cb(null, './uploads/')
      },
      filename: function(req, file, cb) {
        // use this on Windows dir
        cb(null, new Date().toISOString().replace(/:/g, '-') + file.originalname)
        // otherwise (in Linux or IOS use this
        // cb(null, new Date().toISOString() + file.originalname)
      }
    })
    
    const fileFilter = (req, file, cb) => {
    // reject a file if not allowed
    var allowedMimes = ['image/jpeg','image/png' , 'image/gif']
    if (allowedMimes.indexOf(file.mimetype) > -1) {
      cb(null, true)
      } else {
      cb(null, false)
      }   
    }
    
    const upload = multer({
      storage: storage,
      limits: {fileSize: 1024 * 1024 * 10},
      fileFilter: fileFilter
    })
    
    router.post("/", upload.single('productImage'),ProductsController.products_create_product)
    
    module.exports = router
    
    // end of  /src/routes/products/index.js
    

    现在在(服务器后端 api)/src/app.js 中:

    const express = require("express")
    const morgan = require("morgan")
    const bodyParser = require("body-parser")
    const mongoose = require("mongoose")
    const productRoutes = require("./routes/products")
    
    const app = express()
    
    mongoose.connect('mongodb://localhost/shopcart', {
      useMongoClient: true
    })
    
    var db = mongoose.connection
    db.on('error', console.error.bind(console, 'connection error:'))
    db.once('open', function() {
      // we're connected!
      console.log("Connected to mongoDB..."+ db.db.databaseName+ ', url: ' +'http://localhost:5000')
    })
    
    mongoose.Promise = global.Promise
    
    app.use(morgan("dev"))
    app.use('/uploads', express.static('uploads'))
    app.use(bodyParser.urlencoded({ extended: false }))
    app.use(bodyParser.json())
    
    app.use((req, res, next) => {
      res.header("Access-Control-Allow-Origin", "*")
      res.header(
        "Access-Control-Allow-Headers",
        "Origin, X-Requested-With, Content-Type, Accept, Authorization"
      )
      if (req.method === "OPTIONS") {
        res.header("Access-Control-Allow-Methods", "PUT, POST, PATCH, DELETE, GET")
        return res.status(200).json({})
      }
      next()
    })
    
    // Routes which should handle requests
    app.use("/products", productRoutes)
    
    app.use((req, res, next) => {
      const error = new Error("api Not found...")
      error.status = 404
      next(error)
    })
    
    app.use((error, req, res, next) => {
      res.status(error.status || 500)
      res.json({
        error: {
          message: error.message
        }
      })
    })
    
    module.exports = app
    //end of : /src/app.js
    

    现在在(服务器后端 api)/src/index.js 中:

    const http = require('http')
    const app = require('./app.js')
    
    const ip_addr = 'localhost'
    const port = process.env.PORT || 3000
    const server = http.createServer(app)
    
    server.listen(port , ip_addr)
    console.log('Server started on %s port: %s',ip_addr , port )
    
    // end of : /src/index.js
    

    (后端 api)中 ProductController 的代码:/src/controller/Products.js: (请注意,req.file 是由 'multer' 中间件自动添加的)

    exports.products_create_product = (req, res, next) => {
      // note : productImage: req.file.path
      //        (file) is added by 'multer' middleware 
      const product = new Product({
        _id: new mongoose.Types.ObjectId(),
        name: req.body.name,
        price: req.body.price,
        specification: req.body.specification,
        productImage: req.file.path
      })
    
      product
        .save()
        .then(result => {
          res.status(201).json({
            message: "Product created successfully",
            createdProduct: {
              name: result.name,
              price: result.price,
              specification: result.specification,
              _id: result._id,
              productImage: result.productImage,
              request: {
                type: "POST",
                url: "http://localhost:5000/products/"  
              }
            }
          })
        })
        .catch(err => {
          res.status(500).json({
            error: err
          })
        })
    }
    

    最后在我的客户端应用程序项目中,我使用 AxiosformData 将 post 请求发送到后端 api 服务器。

    这是前端客户端应用程序的代码摘录:

      function createProduct (payload, apiUrl, myAuthToken) {
    
      const headers = {
          'Content-Type': 'multipart/form-data',
          // 'Authorization': 'Bearer ' + myAuthToken // if using JWT 
      }
      var formData = new FormData()
    
      formData.append( 'productImage', payload.productImage)
      formData.append( 'name', payload.name)
      formData.append( 'price', payload.price)
      formData.append( 'specification', payload.specification)
    
      Axios.post(apiUrl + '/products',  
       formData,
        {'headers' : headers} 
      )
        .then((response) => {
    
          var imgUrl = response.data.createdProduct.productImage
          payload.productImage = apiUrl+'/'+ imgUrl.replace(/\\/g, "/")
          payload.id= response.data.createdProduct._id
          payload.specification = response.data.createdProduct.specification
          payload.price =response.data.createdProduct.price
          commit('createProduct', payload)
        })
        .catch((error) => {
          alert(error)
        })
    }
    

    以下函数可用于从输入的 html 事件中提取图像数据:

        function onFilePicked (event) {
    
        const files = event.target.files
        if (!files) {
          return
        }
        let filename = files[0].name
        if (filename.lastIndexOf('.') <= 0) {
          return alert('Please add a valid file!')
        }
        const fileReader = new FileReader()
        fileReader.addEventListener('load', () => {
          this.productImage = fileReader.result
        })
        fileReader.readAsDataURL(files[0])
    
        return files[0]
      }
    

    我希望这些代码能帮助任何遇到空请求正文问题的人。

    【讨论】:

      【解决方案2】:

      body-parser 模块不支持 multipart/form-data,正如其文档中所说的那样(如果您是 nodejs 新手,这可能很难弄清楚)。

      您需要找到一些可以为您处理此问题的中间件。 The body-parser documentation recommends a few:

      这不处理多部分实体,因为它们复杂且 通常大的性质。对于多部分实体,您可能对 以下模块:

      每个库的工作方式略有不同,因此您应该使用它们的文档在服务器端构建您的 images 对象。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-04-30
        • 2016-06-13
        • 2011-08-21
        • 2019-05-27
        • 2018-04-08
        • 1970-01-01
        相关资源
        最近更新 更多