尝试使用 MERN 上传图像时出现 400 错误请求错误

Posted

技术标签:

【中文标题】尝试使用 MERN 上传图像时出现 400 错误请求错误【英文标题】:Trying to upload an image using MERN getting 400 bad request error 【发布时间】:2021-05-13 10:00:46 【问题描述】:

您好,希望有人可以提供帮助。使用MERN构建项目,需要有文件上传功能。已尝试实施各种解决方案,但似乎无济于事。任何帮助将不胜感激。

这是我的特快路线

const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth');
const  check, validationResult  = require('express-validator');
// bring in normalize to give us a proper url, regardless of what user entered
const normalize = require('normalize-url');
const checkObjectId = require('../../middleware/checkObjectId');

const path = require('path');
const multer = require('multer');


const Profile = require('../../models/Profile');
const User = require('../../models/User');
const Post = require('../../models/Post');



const upload = multer(
  storage: multer.diskStorage(
    destination(req, res, cb) 
      cb(null, './files');
    ,
    filename(req, file, cb) 
      cb(null, `$new Date().getTime()_$file.originalname`);
    
  ),
  limits: 
    fileSize: 1000000 // max file size 1MB = 1000000 bytes
  ,
  fileFilter(req, file, cb) 
    if (!file.originalname.match(/\.(jpeg|jpg|png)$/)) 
      return cb(
        new Error(
          'only upload files with jpg, jpeg, format.'
        )
      );
    
    cb(undefined, true); // continue with upload
  
);

// @route    POST api/profile/upload
// @desc     Upload profile image
// @access   Private

router.post(
  '/upload',
  upload.single('file'),
  async (req, res) => 
    try 
      console.log('Hello')
      const  path, mimetype, originalname  = req.file;

      const profileFields = ;
      profileFields.user = req.user.id;
      if (path) profileFields.image.path = path;
      if (mimetype) profileFields.image.mimetype = mimetype;
      if (originalname) profileFields.image.originalname = originalname;

      let profile = await Profile.findOneAndUpdate(
         user: req.user.id ,
         $set: profileFields ,
         new: true, upsert: true, setDefaultsOnInsert: true 
      );

      return res.json(profile);

     catch (err) 
      console.log('help')
      res.sendStatus(400).send('Error while uploading file. Please try again later.')
    
  ,
  (error, req, res, next) => 
    if (error) 
      res.status(500).send(error.message);
    
  
);

这是我的模型

const mongoose = require('mongoose');

const ProfileSchema = new mongoose.Schema(
  user: 
    type: mongoose.Schema.Types.ObjectId,
    ref: 'user'
  ,
  location: 
    type: String,
    required: true
  ,
  bio: 
    type: String
  ,
  image: 
    origianlname: 
        type: String,
        required: true
    ,
    path: 
        type: String,
        required: true
    ,
    mineType: 
        type: String,
        required: true
    ,
,
  topics: [
    
      type: mongoose.Schema.Types.ObjectId,
      ref: 'topic',
    
  ],
  social: 
    youtube: 
      type: String
    ,
    twitter: 
      type: String
    ,
    facebook: 
      type: String
    ,
    linkedin: 
      type: String
    ,
    instagram: 
      type: String
    
  ,
  date: 
    type: Date,
    default: Date.now
  
);

module.exports = mongoose.model('profile', ProfileSchema);

我在后端设置了一个名为 files 的文件夹

前端

动作文件

//Upload profile picture

export const uploadImage = (file) => async (dispatch) => 
    try 
    const formData = new FormData();
    formData.append('file', file);
    await axios.post('api/profile/upload', formData, 
      headers: 
        'Content-Type': 'multipart/form-data'
      
    );
    dispatch(setAlert('Profile picture uploaded successfully', 'success'))
   catch (err) 
    dispatch(
      type: PROFILE_ERROR,
      payload:  msg: err.response.statusText, status: err.response.status
    );
  

上传.js 组件文件

import React,  useState, useEffect  from 'react';
import  connect  from 'react-redux';
import  Form, Button  from 'react-bootstrap';
import  uploadImage  from '../../actions/profile';


const Upload = ( errors, dispatch ) => 
    const [file, setFile] = useState(null);
    const [isSubmitted, setIsSubmitted] = useState(false);
    const [errorMsg, setErrorMsg] = useState(null);

    useEffect(() => 
        setErrorMsg(errors);
    , [errors]);

    useEffect(() => 
        setErrorMsg('');
    , [])

    const handleOnChange = (event) => 
        const file = event.target.files[0];
        setFile(file);
    ;

    const handleFormSubmit = (event) => 
        event.preventDefault();
        if (file) 
            setErrorMsg('');
            dispatch(uploadImage(file));
            setIsSubmitted(true);
        
    ;

    return (
        <React.Fragment>
            errorMsg && errorMsg.upload_error ? (
                <p className="errorMsg centered-message">errorMsg.upload_error</p>
            ) : (
                isSubmitted && (
                    <p className="successMsg centered-message">
                        Photo uploaded successfully
                    </p>
            )
            )
            <Form
            onSubmit=handleFormSubmit
            method="post"
            encType="multipart/form-data"
            className="upload-form"
            >
                <Form.Group>
                    <Form.Label>Choose photo to upload</Form.Label>
                    <Form.Control type ="file" name="file" onChange=handleOnChange />
                </Form.Group>
                <Button
                variant="primary"
                type="submit"
                className=`$!file ? 'disabled submit-btn' : 'submit-btn'`
                >
                Upload
                </Button>
            </Form>
        </React.Fragment>
    );
;

const mapStateToProps = (state) => (
    file: state.file || [],
    errors: state.errors || 
);

export default connect(mapStateToProps)(Upload);

减速器文件

import 
  GET_PROFILE,
  PROFILE_ERROR,
  CLEAR_PROFILE,
  GET_PROFILES,
 from '../actions/types';

const initialState = 
  profile: null,
  profiles: [],
  repos: [],
  loading: true,
  error: 
;

function profileReducer(state = initialState, action) 
  const  type, payload  = action;

  switch (type) 
    case GET_PROFILE:
      return 
        ...state,
        profile: payload,
        loading: false
      ;
    case GET_PROFILES:
      return 
        ...state,
        profiles: payload,
        loading: false
      ;
    case PROFILE_ERROR:
      return 
        ...state,
        error: payload,
        loading: false,
        profile: null
      ;
    case CLEAR_PROFILE:
      return 
        ...state,
        profile: null,
        repos: []
      ;
    default:
      return state;
  


export default profileReducer;

希望有人能看到我哪里出错了。我在 redux devtools 上的操作负载说 PROFILE_ERROR 正在运行,我收到状态 400 错误请求。

【问题讨论】:

您的模型中有一个错字 originalname 拼写错误,这肯定会引发错误 谢谢你已经修复它,但还没有解决问题 【参考方案1】:

所以我玩过,现在有一个可行的解决方案。将其发布在此处,以供需要了解如何执行此操作的其他人使用。

这里是特快路线

const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth');
const  check, validationResult  = require('express-validator');
// bring in normalize to give us a proper url, regardless of what user entered
const normalize = require('normalize-url');
const checkObjectId = require('../../middleware/checkObjectId');


const path = require('path');
const multer = require('multer');


const Profile = require('../../models/Profile');
const User = require('../../models/User');
const Post = require('../../models/Post');





const upload = multer(
  storage: multer.diskStorage(
    destination(req, file, cb) 
      cb(null, 'uploads');
    ,
    filename(req, file, cb) 
      cb(null, `$new Date().getTime()_$file.originalname`);
    
  ),
  limits: 
    fileSize: 1024 * 1024 * 5
  ,
  fileFilter(req, file, cb) 
    if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png' || file.mimetype === 'image/gif' ||  file.mimetype === 'image/jpg' || file.mimetype ==='image/jfif') 
      cb(null, true)
   else 
    cb(new Error('Please upload a file type of jpeg, png or gif'), false)
  
  
);


// @route    POST api/profile
// @desc     Create or update user profile
// @access   Private
router.post(
  '/',
  upload.single('file'),
  auth,
  check('location', 'Location is required').not().isEmpty(),
  async (req, res) => 
    const errors = validationResult(req);
    if (!errors.isEmpty()) 
      return res.status(400).json( errors: errors.array() );
    
    console.log(req.file);
    console.log(req.body);

    // destructure the request
    const 
      location,
      bio,
      youtube,
      twitter,
      instagram,
      linkedin,
      facebook,
     = req.body;



    // build a profile
    const profileFields = ;
    profileFields.user = req.user.id;
    if(req.file !== undefined) 
      let fileUrl = "/" + req.file.path.replace(/\\/g, "/");
      if (path) profileFields.path = fileUrl;
      if (mimetype) profileFields.mimeType = req.file.mimetype;
      if (originalname) profileFields.orginalName = req.file.originalname;
      if (location) profileFields.location = req.body.location;
      if (bio) profileFields.bio = req.body.bio;
     else 
      if (location) profileFields.location = req.body.location;
      if (bio) profileFields.bio = req.body.bio;
    

    
    
   // Build social object
   const socialFields = youtube, twitter, instagram, linkedin, facebook


    for (const [key, value] of Object.entries(socialFields)) 
      if (value && value.length > 0) 
      socialFields[key] = normalize(value,  forceHttps: true );
    

    try 
      // Using upsert option (creates new doc if no match is found):
      let profile = await Profile.findOneAndUpdate(
         user: req.user.id ,
         $set: profileFields ,
         new: true, upsert: true, setDefaultsOnInsert: true 
      );
      return res.json(profile);
     catch (err) 
      console.error(err.message);
      return res.status(500).send('Server Error');
    
  
);

这是模型

const mongoose = require('mongoose');

const ProfileSchema = new mongoose.Schema(
  user: 
    type: mongoose.Schema.Types.ObjectId,
    ref: 'user'
  ,
  location: 
    type: String,
    required: true
  ,
  bio: 
    type: String
  ,
  orginalName: 
    type: String,
    required: true
  ,
  path: 
    type: String,
    required: true
  ,
  mimeType: 
    type: String,
    required: true
  ,
  topics: [
    
      type: mongoose.Schema.Types.ObjectId,
      ref: 'topic',
    
  ],
  social: 
    youtube: 
      type: String
    ,
    twitter: 
      type: String
    ,
    facebook: 
      type: String
    ,
    linkedin: 
      type: String
    ,
    instagram: 
      type: String
    
  ,
  date: 
    type: Date,
    default: Date.now
  
);

module.exports = mongoose.model('profile', ProfileSchema);

这是动作文件

// Create or update profile
export const createProfile = (payload, history, edit = false) => async (
  dispatch
) => 
  try 
    const res = await api.post('/profile', payload, 
      headers: 
        'Content-Type': 'multipart/form-data'
      
    );

    dispatch(
      type: GET_PROFILE,
      payload: res.data
    );

    dispatch(setAlert(edit ? 'Profile Updated' : 'Profile Created', 'success'));

    if (!edit) 
      history.push('/dashboard');
    
   catch (err) 
    const errors = err.response.data.errors;

    if (errors) 
      errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
    

    dispatch(
      type: PROFILE_ERROR,
      payload:  msg: err.response.statusText, status: err.response.status 
    );
  
;

这里是减速器

import 
  GET_PROFILE,
  PROFILE_ERROR,
  CLEAR_PROFILE,
  GET_PROFILES,
 from '../actions/types';

const initialState = 
  profile: null,
  profiles: [],
  loading: true,
  error: 
;

function profileReducer(state = initialState, action) 
  const  type, payload  = action;

  switch (type) 
    case GET_PROFILE:
      return 
        ...state,
        profile: payload,
        loading: false
      ;
    case GET_PROFILES:
      return 
        ...state,
        profiles: payload,
        loading: false
      ;
    case PROFILE_ERROR:
      return 
        ...state,
        error: payload,
        loading: false,
        profile: null
      ;
    case CLEAR_PROFILE:
      return 
        ...state,
        profile: null,
      ;
    default:
      return state;
  


export default profileReducer;

这里是上传组件

import React,  useEffect, useState, Fragment  from 'react';
import  Link, withRouter, Redirect  from 'react-router-dom';
import PropTypes from 'prop-types';
import  connect  from 'react-redux';
import  createProfile, getCurrentProfile  from '../../actions/profile';

const CreateProfile = (
    createProfile,
    getCurrentProfile,
    profile:  profile, loading ,
    history
) => 
    const [formData, setFormData] = useState(
        location: '',
        bio: '',
        twitter: '',
        facebook: '',
        linkedin: '',
        youtube: '',
        instagram: ''
    );

    const [file, setFile] = useState("");
    const [imageName, setImageName] = useState("Choose file");

    const [displaySocialInputs, toggleSocialInputs] = useState(false);

    const 
        location,
        bio,
        twitter,
        facebook,
        linkedin,
        youtube,
        instagram
     = formData;

    const onFileChange = (e) => 
        setFile(e.target.files[0]);
        setImageName(e.target.files[0].name);
    

    const onChange = (e) => 
        setFormData( ...formData, [e.target.name]: e.target.value );
    ;

    const onSubmit = (e) => 
        e.preventDefault();

        const payload = new FormData();
        payload.append("file", file);
        payload.append("location", formData.location);
        payload.append("bio", formData.bio);
        payload.append("twitter", formData.twitter);
        payload.append("facebook", formData.facebook);
        payload.append("linkedin", formData.linkedin);
        payload.append("youtube", formData.youtube);
        payload.append("instagram", formData.instagram);

        createProfile(payload, history);
    ;

    useEffect(() => 
        getCurrentProfile();
        // eslint-disable-next-line react-hooks/exhaustive-deps
    , [getCurrentProfile]);



    return loading && profile === null ? (
        <Redirect to='/dashboard' />
    ) : (
        <Fragment>
            <h1 className='large text-primary'>Create Your Profile</h1>
            <p className='lead'>
                <i className='fas fa-user' /> Let's get some information to make your
                profile stand out
            </p>
            <small>* = required field</small>
            <form className='form' onSubmit=e => onSubmit(e) encType="multipart/form-data">
                <div className='form-group'>
                    <input
                        type='text'
                        placeholder='Location'
                        name='location'
                        value=location
                        onChange=(e) => onChange(e)
                    />
                    <small className='form-text'>
                        City (eg. Glasgow)
                    </small>
                </div>
                <div className='form-group'>
                    <textarea
                        placeholder='A short bio of yourself'
                        name='bio'
                        value=bio
                        onChange=(e) => onChange(e)
                    />
                    <small className='form-text'>Tell us a little about yourself</small>
                </div>
                <div className="form-group">
                    <label htmlFor="image">Upload Profile Image</label><br></br>
                    <input type="file" onChange=(e) => onFileChange(e) accept="image/*" />
                </div>

                <div className='my-2'>
                    <button
                        onClick=() => toggleSocialInputs(!displaySocialInputs)
                        type='button'
                        className='btn btn-light'
                    >
                        Add Social Network Links
                    </button>
                    <span>Optional</span>
                </div>
                displaySocialInputs && (
                    <Fragment>
                        <div className='form-group social-input'>
                            <i className='fab fa-twitter fa-2x' />
                            <input
                                type='text'
                                placeholder='Twitter URL'
                                name='twitter'
                                value=twitter
                                onChange=e => onChange(e)
                            />
                        </div>

                        <div className='form-group social-input'>
                            <i className='fab fa-facebook fa-2x' />
                            <input
                                type='text'
                                placeholder='Facebook URL'
                                name='facebook'
                                value=facebook
                                onChange=e => onChange(e)
                            />
                        </div>

                        <div className='form-group social-input'>
                            <i className='fab fa-youtube fa-2x' />
                            <input
                                type='text'
                                placeholder='YouTube URL'
                                name='youtube'
                                value=youtube
                                onChange=e => onChange(e)
                            />
                        </div>

                        <div className='form-group social-input'>
                            <i className='fab fa-linkedin fa-2x' />
                            <input
                                type='text'
                                placeholder='Linkedin URL'
                                name='linkedin'
                                value=linkedin
                                onChange=e => onChange(e)
                            />
                        </div>

                        <div className='form-group social-input'>
                            <i className='fab fa-instagram fa-2x' />
                            <input
                                type='text'
                                placeholder='Instagram URL'
                                name='instagram'
                                value=instagram
                                onChange=e => onChange(e)
                            />
                        </div>
                    </Fragment>
                )

                <input type='submit' className='btn btn-primary my-1' />
                <Link className='btn btn-light my-1' to='/dashboard'>
                    Go Back
                </Link>
            </form>
        </Fragment>
    );
;




CreateProfile.propTypes = 
    createProfile: PropTypes.func.isRequired,
    getCurrentProfile: PropTypes.func.isRequired,
    profile: PropTypes.object.isRequired
;


const mapStateToProps = state => (
    profile: state.profile
);






export default connect(mapStateToProps,  createProfile, getCurrentProfile )(withRouter(CreateProfile));

希望这对遇到类似问题的人有所帮助

【讨论】:

以上是关于尝试使用 MERN 上传图像时出现 400 错误请求错误的主要内容,如果未能解决你的问题,请参考以下文章

从 React 前端将图像上传到烧瓶后端时出现 KeyError 'image'

尝试将文件(图像)上传到服务器时出现错误 403

尝试通过 XCode 上传二进制文件时出现“无效的图像路径”错误

验证表单“发送的请求在语法上不正确”时出现错误 400

尝试在已部署的反应客户端和快速服务器上上传图像文件时出现 503 错误,然后是 CORS 错误

尝试使用 XHR 联系 WCF 服务时出现 400 错误请求