首页 > 解决方案 > 尝试使用 MERN 上传图像时出现 400 错误请求错误

问题描述

您好希望有人可以提供帮助。使用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}
    });
  }
}

Upload.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 错误请求。

标签: javascriptreactjsexpressmongoosereact-redux

解决方案


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

这里是特快路线

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));

希望这可以帮助遇到类似问题的任何人


推荐阅读