文件上传错误 - 在 Postman 中有效,但在前端无效

Posted

技术标签:

【中文标题】文件上传错误 - 在 Postman 中有效,但在前端无效【英文标题】:File Upload Error - Works in Postman but not on frontend 【发布时间】:2022-01-09 21:08:19 【问题描述】:

我正在通过 devchallenges.io 上的全栈认证,我正在做 authentication app challenge。到目前为止,我已经能够创建登录和注册功能,并且能够设置功能以获取登录用户并显示他们的信息,但是,当尝试在前端上传文件时,图像上传不会t 似乎工作。如video 所示,它在 Postman 中运行良好。在前端,其他字段似乎得到了更新,例如姓名、简历。错误here的示例。

Github 源码:https://github.com/gbopola/Auth-App

server.js

const express = require('express');
const connectDB = require('./config/db');
const app = express();
const  check, validationResult  = require('express-validator');
const User = require('./models/User');
const gravatar = require('gravatar');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('config');
const auth = require('./middleware/auth');
const cloudinary = require('./utils/cloudinary');
const upload = require('./utils/multer');

// Connect database
connectDB();

// Init Middleware
app.use(express.json( limit: '50mb' ));
app.use(express.urlencoded( limit: '50mb', extended: true ));

// @route    POST /register
// @desc     Register user
// @access   Public
app.post(
  '/register',
  [
    check('email', 'Please include a valid email').isEmail(),
    check('password', 'Please enter a password').notEmpty(),
  ],
  async (req, res) => 
    const errors = validationResult(req);
    if (!errors.isEmpty()) 
      return res.status(400).json( errors: errors.array() );
    
    const  email, password  = req.body;

    try 
      // See if user exists
      let user = await AuthUser.findOne( email );

      if (user) 
        return res
          .status(400)
          .json( errors: [ msg: 'User already exists' ] );
      
      // Get users gravatar
      const avatar = gravatar.url(email, 
        s: '200',
        r: 'pg',
        d: 'mm',
      );

      user = new AuthUser(
        email,
        avatar,
        password,
      );

      // Encrypt password
      const salt = await bcrypt.genSalt(10);

      user.password = await bcrypt.hash(password, salt);

      await user.save();

      // Return jsonwebtoken
      const payload = 
        user: 
          id: user.id,
        ,
      ;

      jwt.sign(
        payload,
        config.get('jwtSecret'),
         expiresIn: '5 days' ,
        (err, token) => 
          if (err) throw err;
          res.json( token );
        
      );
     catch (error) 
      console.error(error.message);
      res.status(500).send('Server error');
    
  
);

// @route    POST /login
// @desc     Authenticate user & get token
// @access   Public

app.post(
  '/login',
  check('email', 'Please include a valid email').isEmail(),
  check('password', 'Password is required').exists(),
  async (req, res) => 
    const errors = validationResult(req);
    if (!errors.isEmpty()) 
      return res.status(400).json(
        errors: errors.array(),
      );
    

    const  email, password  = req.body;

    try 
      // See if user exists
      let user = await User.findOne( email );

      if (!user) 
        return res
          .status(400)
          .json( errors: [ msg: 'Invalid credentials' ] );
      

      const isMatch = await bcrypt.compare(password, user.password);

      if (!isMatch) 
        return res
          .status(400)
          .json( errors: [ msg: 'Invalid credentials' ] );
      

      // Return jsonwebtoken
      const payload = 
        user: 
          id: user.id,
        ,
      ;

      jwt.sign(
        payload,
        config.get('jwtSecret'),
         expiresIn: '5 days' ,
        (err, token) => 
          if (err) throw err;
          res.json( token );
        
      );
     catch (err) 
      console.error(err.message);
      res.status(500).send('Server error');
    
  
);

// @route    GET /profile
// @desc     Get full user profile
// @access   Private

app.get('/profile', auth, async (req, res) => 
  try 
    let user = await User.findById(req.user.id).select('-password');

    res.json(user);
   catch (err) 
    console.error(err.message);
    res.status(500).send('Server error');
  
);

// @route    POST /profile/edit/:id
// @desc     edit profile
// @access   Private

app.put('/profile/edit/:id', upload.single('image'), auth, async (req, res) => 
  const  name, bio, email, phone, password  = req.body;

  try 
    let user = await AuthUser.findById(req.params.id);

    // Delete image from cloudinary
    if (user.cloudinary_id !== '')
      await cloudinary.uploader.destroy(user.cloudinary_id);

    // Upload image to cloudinary
    let result;
    if (req.file) 
      result = await cloudinary.uploader.upload(req.file.path);
    

    const data = 
      name: name || user.name,
      avatar: (result && result.secure_url) || user.avatar,
      bio: bio || user.bio,
      email: email || user.email,
      phone: phone || user.phone,
      password: password || user.password,
      cloudinary_id: (result && result.public_id) || user.cloudinary_id,
    ;

    if (password !== '') 
      // Encrypt password
      const salt = await bcrypt.genSalt(10);

      data.password = await bcrypt.hash(password, salt);
    

    //   Update
    user = await User.findByIdAndUpdate(req.params.id, data,  new: true );

    return res.json(data);
   catch (err) 
    console.error(err.message);
    res.status(500).send('Server error');
  
);

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => console.log(`Server started on port $PORT`));

验证 Action.js

// Update use profile
export const updateProfile = (
  name,
  bio,
  phone,
  email,
  password,
  id,
  profileImg,
  navigate,
) => 
  return async (dispatch) => 
    const config = 
      headers: 
        'Content-Type': 'application/json',
      ,
    ;
    const body = JSON.stringify(
      name,
      bio,
      phone,
      email,
      password,
      id,
      profileImg,
    );

    try 
      const res = await axios.put(`/profile/edit/$id`, body, config);

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

      navigate('/profile');
     catch (error) 
      console.log(error);
    
  ;
;

import React,  useEffect, useState, useRef  from 'react';
import  Navbar  from './Navbar';
import  useSelector  from 'react-redux';
import  useDispatch  from 'react-redux';
import  loadUser  from '../redux/actions/auth';
import  Link, useParams, useNavigate  from 'react-router-dom';
import  store  from '../store';
import  updateProfile  from '../redux/actions/auth';
export const EditProfile = () => 
  const state = useSelector((state) => state.auth);
  const  id  = useParams();
  const dispatch = useDispatch();
  const navigate = useNavigate();
  // States
  const [isEditing, setEdit] = useState(false);
  const [profileImg, setImg] = useState(state.user.avatar);
  const [formData, setFormData] = useState(
    name: '',
    bio: '',
    phone: '',
    email: '',
    password: '',
  );

  const  email, password, bio, phone, name  = formData;

  const inputFile = useRef(null);

  let styles = 
    width: '72px',
    height: '72px',
    borderRadius: '8px',
    backgroundImage: `url($!isEditing ? state.user.avatar : profileImg)`,
    backgroundPosition: 'center',
    backgroundSize: 'cover',
    position: 'relative',
  ;

  // handle image change
  const imageHandler = (e) => 
    const reader = new FileReader();
    reader.onload = () => 
      if (reader.readyState === 2) 
        setImg(reader.result);
        setEdit(true);
      
    ;
    if (e.target.files[0]) 
      reader.readAsDataURL(e.target.files[0]);
    
  ;

  const changePhoto = () => 
    inputFile.current.click();
  ;

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

  const changeInfo = () => 
    dispatch(
      updateProfile(
        name,
        bio,
        phone,
        email,
        password,
        id,
        profileImg,
        navigate,
      )
    );
  ;

  return (
    <div className="EditProfile">
      <Navbar />
      <div className="edit-profile-container">
        <div className="back-to-profile">
          <Link className="link-to-profile" to="/profile">
            <span>
              <i className="fas fa-chevron-left"></i>
            </span>
            Back
          </Link>
        </div>
        <div className="profile-wrapper">
          <div className="profile-heading">
            <div>
              <h2>Change Info</h2>
              <p className="personal-info-grey">
                Changes will be reflected to every services
              </p>
            </div>
          </div>
          <div className="profile-photo">
            <input
              type="file"
              accept="image/*"
              name="image-upload"
              id="upload"
              onChange=imageHandler
              ref=inputFile
            />
            <div className="example" onClick=changePhoto>
              <i className="fas fa-camera"></i>
              <div id="overlay"></div>
              <div id="profile-img-edit" style=styles></div>
            </div>
            <p className="personal-info-grey change-photo">CHANGE PHOTO</p>
          </div>
          <div className="name">
            <label>Name</label>
            <input
              type="text"
              className="edit-profile-input"
              placeholder="Enter your name"
              name="name"
              value=name
              onChange=(e) => onChange(e)
            />
          </div>
          <div className="bio">
            <label>Bio</label>
            <textarea
              className="edit-profile-input"
              id="bio"
              placeholder="Enter your bio"
              name="bio"
              value=bio
              onChange=(e) => onChange(e)
            />
          </div>
          <div className="phone">
            <label>Phone</label>
            <input
              type="text"
              className="edit-profile-input"
              placeholder="Enter your phone"
              name="phone"
              value=phone
              onChange=(e) => onChange(e)
            />
          </div>
          <div className="email">
            <label>Email</label>
            <input
              type="text"
              className="edit-profile-input"
              placeholder="Enter your email"
              name="email"
              value=email
              onChange=(e) => onChange(e)
            />
          </div>
          <div className="password">
            <label>Password</label>
            <input
              type="password"
              className="edit-profile-input"
              placeholder="Enter your password"
              name="password"
              value=password
              onChange=(e) => onChange(e)
            />
            <button className="edit-save" onClick=changeInfo>
              Save
            </button>
          </div>
        </div>
      </div>
    </div>
  );
;

【问题讨论】:

您没有将内容类型指定为multipart/form-data。这会很有帮助***.com/questions/39663961/… 【参考方案1】:

除非您将图像附加到新的表单数据中,否则通常无法将图像传递到后端

例子:

const formData = new FormData();
formData.append('image', image);

然后将 formData 作为对象发送

【讨论】:

非常感谢,伙计 :)【参考方案2】:

通常您不会将图像发送给用户,您只需发送指向图像的 url。

【讨论】:

感谢您的反馈。这正是我所做的。我将图像发送到 cloudinary。

以上是关于文件上传错误 - 在 Postman 中有效,但在前端无效的主要内容,如果未能解决你的问题,请参考以下文章

我的请求在 Postman 中有效,但在浏览器中无效(React、Node、Cloudinary)

Workday 请求在 SoapUI 中有效,但在 Postman 中无效

HTTP 请求在 Postman 中有效,但在 C# 代码中无效

HTTP POST 请求在 Postman 中有效,但在代码中无效

简单的 POST 请求在 Postman 中有效,但在浏览器中无效

Http POST 在 Postman 中有效,但在 Flutter 中无效