Файлы, отправленные через форму с multipart / form-data, отправляются как пустой объект - PullRequest
0 голосов
/ 09 мая 2018

Я создаю компонент 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 отправляется как пустой объект. Я не уверен, должен ли я отправлять formData таким образом или есть какая-то проблема с экспресс-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)
}

console.log newUser в API возвращает это:

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

Спасибо заранее за любую помощь!

Ответы [ 2 ]

0 голосов
/ 01 августа 2018

Я также боролся с пустым телом запроса в моем проекте сервера api. Я хотел опубликовать данные из нескольких частей, которые состоят из строковых / текстовых данных и изображения, на бэкэнд-сервер успокоительного API. Я подумал, что достаточно просто передать данные с помощью объекта formData через Axios. Однако в итоге я был почти разочарован, потому что в backend restful api project я получал пустое тело запроса. К счастью, через поиск по некоторым статьям, stackoverflow и youtube. Мне удалось собрать полностью работающую программу node.js. Нет больше пустых данных тела запроса !!! , Здесь я хотел бы поделиться своими рабочими кодами.

Чтобы передать multipart / form-data от клиента к серверу, я обнаружил, что мы должны использовать несколько промежуточных программ и объектов node / java:

  • В проекте api на стороне сервера: я использую {multer, body-parser} middlewares
  • В проекте клиентского приложения: я использую 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

Код для ProductController в (бэкэнд API): /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
      })
    })
}

И, наконец, в проекте моего клиентского приложения , я использую Axios и formData , чтобы отправить запрос на отправку серверному серверу 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]
  }

Я надеюсь, что эти коды помогут любому, кто столкнется с проблемой пустого тела запроса.

0 голосов
/ 09 мая 2018

Модуль body-parser не поддерживает multipart / form-data, как сказано в их документации (это может быть трудно понять, если вы новичок в nodejs).

Вам нужно будет найти какое-нибудь промежуточное программное обеспечение, которое справится с вами. Документация body-parser рекомендует несколько :

Это не относится к составным телам из-за их сложных и типично большая природа. Для многочастных органов вас может заинтересовать следующие модули:

Каждая библиотека будет работать немного по-своему, поэтому вы должны использовать их документацию для создания вашего images объекта на стороне сервера.

...