【问题标题】:File Upload Error - Works in Postman but not on frontend文件上传错误 - 在 Postman 中有效,但在前端无效
【发布时间】: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>
  );
};

【问题讨论】:

标签: javascript node.js reactjs express redux


【解决方案1】:

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

例子:

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

然后将 formData 作为对象发送

【讨论】:

  • 非常感谢,伙计 :)
【解决方案2】:

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

【讨论】:

  • 感谢您的反馈。这正是我所做的。我将图像发送到 cloudinary。
猜你喜欢
  • 1970-01-01
  • 2018-07-14
  • 2020-04-28
  • 2020-04-06
  • 2021-12-23
  • 2019-11-28
  • 2020-10-15
  • 2017-05-03
  • 2017-06-26
相关资源
最近更新 更多