Передать ошибку проверки Multer в React Component - PullRequest
3 голосов
/ 04 апреля 2019

Я учу Multer вместе с Redux и React.

Мой express роутер похож на

router.post('/upload', addressController.uploadImage);

Мой Multer код похож на ниже

const uploadImage = (req, res, next) => {

    const storage = multer.diskStorage({
        destination: function(req, file, cb) {
            cb(null, './uploads/');
        },
        filename: function(req, file, cb) {
            cb(null, Date.now() + '-' + file.originalname);
        }
    });

    const fileFilter = (req, file, cb) => {
        if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
            cb(null, true);
        } else {
            cb(new Error('Try to upload .jpeg or .png file.'), false);
        }
    };

    const upload = multer({
        storage: storage,
        limits: {
            fileSize: 1024 * 1024 * 5
        },
        fileFilter: fileFilter
    }).single('addressImage');

    upload(req, res, function(error) {
        if (error) {
            // An error occurred when uploading
            res.status(500).json({
                message: error // I would like to send error from Here.
            });
            console.log(error);
        } else {
            if (req.file.filename === res.req.res.req.file.filename) {
                res.status(200).json({
                    message: 'File uploaded',
                    file: req.file.filename
                });
            }
            return;
        }
    });
}

Мое действие похоже ниже

export const uploadImage = (formData, id, config) => dispatch => {
  return Axios.post('/api/address/upload', formData, config)
    .then(response => {
      dispatch({
        type: 'uploadImage',
        payload: response.data
      });
    })
    .catch(error => {
      dispatch({
        type: 'uploadImage',
        payload: error // I would like to pass error through here.
      });
      return false;
    });
};

Мой редуктор похож на ниже

const addressReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'getAddresses': {
            return {
                ...state,
                controlModal: action.payload.valueModal,
                address: action.payload.addressData
            };
        }
        case 'uploadImage': {
            return {
                ...state,
                uploadImage: action.payload 
            };
        }
        default:
            return state;
    }
};

Я хотел бы получить ошибку в моем компоненте, как ниже

render() {
        console.log(this.props.uploadImage);
}


const mapStateToProps = state => ( {
    uploadImage: state.addressReducer.uploadImage
} );


export default connect(mapStateToProps)(ModalElement);

Мой вывод на консоль похож на ниже

enter image description here

Как я могу получить ошибку Try to upload .jpeg or .png file. в своем компоненте React, когда я пытаюсь загрузить файл без расширения .jpeg и .png?

Ответы [ 3 ]

4 голосов
/ 24 апреля 2019

Error не преобразуется в действительный json, когда он проходит через res.json() и, таким образом, он удаляется.

Таким образом, чтобы получить доступ к сообщению "Try to upload .jpeg or .png file.", необходимо обновитькод Multer, подобный следующему:

if (error) {
    // An error occurred when uploading
    res.status(500).json({
        /** error.message => "Try to upload .jpeg or .png file." */
        message: error.message // I would like to send error from Here.
    });
    console.log(error);
}

Если вы попытаетесь загрузить файл с помощью Postman, у вас будет следующий ответ API:

{
    "message": "Try to upload .jpeg or .png file."
}

Как только вы это сделаете, выможно изменить dispatch() как:

.catch(error => {
    dispatch({
        type: "uploadImage",
        /** error.data is the response. We want the `message` property from it */
        payload: error.data.message // I would like to pass error through here.
    });
    return false;
});
4 голосов
/ 04 апреля 2019

вам не нужно отправлять 500 кодов статуса, вместо этого вы должны отправить 400

 res.status(400).json({
            message: error // I would like to send error from Here.
        });
3 голосов
/ 26 апреля 2019

Вот как я смог сделать это для микросервиса аватара, который я создал для работы с моим основным приложением.

ПРЕДУПРЕЖДЕНИЕ : Это объяснение распространяется на весь поток, поэтому оно может быть длинным и избыточным, если вы его уже понимаете.


Создать конфигурацию axios.

Сначала необходимо создать конфигурацию axios.По умолчанию axios не будет отображать err, возвращаемый сервером, вместо этого он будет просто отображать общий Error объект.Вам нужно настроить interceptor.

utils / axiosConfig.js

import get from 'lodash/get';
import axios from 'axios';

export const avatarAPI = axios.create({
  baseURL: 'http://localhost:4000/api/', // this makes it easier so that any request will be prepended with this baseURL
});

avatarAPI.interceptors.response.use(
  response => response, // returns the server response
  error => {
    const err = get(error, ['response', 'data', 'err']); // this checks if "error.response.data.err" is present (this is the error returned from the server); VERY IMPORTANT: this "err" property is specified in our express middlewares/controllers, so please pay attention to the naming convention.

    return err ? Promise.reject(err) : Promise.reject(error.message); // if the above is present, return the server error, else return a generic Error object
  },
);

Поток изклиент к серверу обратно к клиенту.

Клиент

Пользователь отправляет форму с formData, и это вызывает action создатель:

thunk action uploadAvatarсоздатель (обещание, ожидающее response или error с нашего сервера) :

import { avatarAPI } from '../utils/axiosConfig'; // import the custom axios configuration that was created above
import * as types from 'types';

const uploadAvatar = formData => dispatch =>
  avatarAPI
    .post(`avatar/create`, formData) // this makes a POST request to our server -- this also uses the baseURL from the custom axios configuration, which is the same as "http://localhost:4000/api/avatar/create"
    .then(({ data }) => {
      dispatch({ type: types.SET_CURRENT_AVATAR, payload: data.avatarurl });
    })
    .catch(err => // this will return our server "err" string if present, otherwise it'll return a generic Error object. IMPORTANT: Just in case we get a generic Error object, we'll want to convert it to a string (otherwise, if it passes the generic Error object to our reducer, stores it to redux state, passes it to our connected component, which then tries to display it... it'll cause our app to crash, as React can't display objects)
      dispatch({ type: types.SERVER_ERROR, payload: err.toString() }),
    );

Сервер

Запрос POST выбранвверх по нашему express маршруту :

app.post('/api/avatar/create', saveImage, create);

Запрос попадает на этот маршрут: '/api/avatar/create', проходит через функцию промежуточного программного обеспечения (см. ниже) до прохождения через другую функцию saveImage промежуточного программного обеспечения, донаконец, проходя к контроллеру create.

Клиент

Сервер отправляет ответ клиенту.Ответ от нашего сервера проходит через axios конфигурацию interceptor, которая определяет, как обрабатывать response или error, которые были возвращены с нашего сервера.Затем он передает response или error .then() или .catch() создателя action.Создатель action передает его в reducer, который обновляет состояние redux, которое затем обновляет connect ed компонент.


Настройка сервера (микросервис).

Где бы вы ни определяли ваше промежуточное программное обеспечение express (например: bodyParser, cors или passport и т. Д.), Вам нужно будет создать функцию промежуточного программного обеспечения multer (каждый раз, когда файл загружается,она проходит через эту функцию first ):

middlewares / index.js

app.use(cors({ origin: "http://localhost:3000" }));
app.use(bodyParser.json());
app.use(
    multer({
      limits: {
        fileSize: 10240000,
        files: 1,
        fields: 1
      },
      fileFilter: (req, file, next) => {
        if (!/\.(jpe?g|png|gif|bmp)$/i.test(file.originalname)) {
          req.err = "That file extension is not accepted!"; // this part is important, I'm attaching the err to req (which gets passed to the next middleware function => saveImage)
          next(null, false);
        }
        next(null, true);
      }
    }).single("file")
  );
...etc

services / saveImage.js (после прохождения вышеупомянутой функции промежуточного программного обеспечения результат передается этой функции промежуточного программного обеспечения saveImage)

const fs = require("fs");
const sharp = require("sharp");
const { createRandomString } = require('../../utils/helpers');

module.exports = (req, res, next) => {   
  // if the file failed to pass the middleware function above, we'll return the "req.err" as "err" or return a string if "req.file" is undefined. In short, this returns an "error.response.data.err" to the client.
  if (req.err || !req.file) {
    return res.status(400).json({ err: req.err || "Unable to process file." });
  }

  const randomString = createRandomString();

  const filename = `${Date.now()}-${randomString}-${req.file.originalname}`;
  const filepath = `uploads/${filename}`;

  const setFile = () => {
    req.file.path = filepath;
    return next();
  };

  /\.(gif|bmp)$/i.test(req.file.originalname)
    ? fs.writeFile(filepath, req.file.buffer, (err) => {
      if (err) return res.status(400).json({ "Unable to process file." });
      setFile();
    })
    : sharp(req.file.buffer)
      .resize(256, 256)
      .max()
      .withoutEnlargement()
      .toFile(filepath)
      .then(() => setFile());
};

Если вышеуказанное проходит, то передается req (который содержит req.file и всеего свойства) на контроллер create, который в моем случае хранит путь к файлу (/uploads/name-of-file.ext) и строку для извлечения изображения (http://localhost:4000/uploads/name-of-file.ext) на мойВ моем случае эта строка затем отправляется обратно клиенту для сохранения в состояние избыточности и затем обновляется как аватара пользователя.ar (при передаче строки в <img src={avatarurl} alt="avatarurl.png" /> он отправляет запрос GET обратно в микросервис).


Проверка не пройдена.

Допустим, пользователь пыталсязагрузить .tiff изображение.Он проходит через нашу многопользовательскую функцию express, которая вызывает ошибку "That file extension is not accepted!", эта ошибка возвращается через req.err в saveImage, которая возвращает req.err как: return res.status(400).json({ err: req.err });

Вкл.на нашей клиентской стороне, что err протекает через наши оси interceptor:

avatarAPI.interceptors.response.use(
  response => response,
  error => {
    const err = get(error, ['response', 'data', 'err']); // this checks if "error.response.data.err" is present; which it is, and is now "That file extension is not accepted!"

    return err ? Promise.reject(err) : Promise.reject(error.message); // that err string gets returned to our uploadAvatar action creator's "catch" block
   },
);

Блок uploadAvatar создателя действий catch запускается:

.catch(err => // our server "err" is passed to here from the interceptor
   dispatch({ type: types.SERVER_ERROR, payload: err.toString() }), // then that "err" is passed to a reducer
); 

The reducer берет сервер err и сохраняет его в состоянии:

import * as types from 'types';

const serverInitialState = {
  error: '',
  message: '',
};

const ServerReducer = (state = serverInitialState, { payload, type }) => {
  switch (type) {
    case types.RESET_SERVER_MESSAGES:
      return { ...state, error: '' }; 
    case types.SERVER_ERROR:
      return { ...state, error: payload }; // the server err is stored to redux state as "state.server.error"
    case types.SERVER_MESSAGE:
      return { ...state, message: payload };
    default:
      return state;
  }
};

export default ServerReducer;

Компонент connect ed извлекает этот state.server.error и отображает его (не слишком беспокоитесь о логике здесь,просто это подключенный компонент, отображающий state.server.error как serverError):

class RenderMessages extends Component {
  shouldComponentUpdate = nextProps =>
    this.props.serverError !== '' ||
    nextProps.serverError !== '' ||
    this.props.serverMessage !== '' ||
    nextProps.serverMessage !== '';

  componentDidUpdate = () => {
    const { serverError, serverMessage } = this.props;
    if (serverError || serverMessage) {
      const notification = serverError
        ? serverErrorMessage(serverError)
        : serverSuccessMessage(serverMessage);
      this.renderNotification(...notification);
    }
  };

  renderNotification = ({ noteType, description }) => {
    notification[noteType]({
      message: noteType === 'error' ? 'Error' : 'Update',
      description,
      icon: descriptionLayout(noteType),
    });
    setTimeout(() => this.props.resetServerMessages(), 3000);
  };

  render = () => null;
}

export default connect(
  state => ({
    serverError: state.server.error, // retrieving the error from redux state
    serverMessage: state.server.message,
  }),
  { resetServerMessages },
)(RenderMessages);

Окончательный результат - That file extension is not accepted! Ошибка отображается для пользователя:

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...