将Multer验证错误传递给React组件

时间:2019-04-04 05:04:50

标签: node.js reactjs express multer

我正在与MulterRedux一起学习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

当我尝试上传不带.jpeg和.png扩展名的文件时,如何在React组件中出现Try to upload .jpeg or .png file.错误?

3 个答案:

答案 0 :(得分:4)

您不必发送500状态代码,而应该发送400

 res.status(400).json({
            message: error // I would like to send error from Here.
        });

答案 1 :(得分:4)

Error在通过res.json()传递时无法解析为有效的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;
});

答案 2 :(得分:3)

这是我为与主应用程序一起创建的头像微服务完成此工作的方式。

警告:此说明贯穿于整个流程,因此,如果您已经理解的话,它可能是冗长且多余的。


创建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创建者:

uploadAvatar重击动作创建者(这是等待我们服务器中的responseerror的诺言)

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传递,该配置确定如何处理我们服务器返回的responseerror。然后它将responseerror传递给.then()创建者的.catch()actionaction创建者将其移交给reducer,后者会更新redux状态,然后会更新connect ed组件。


服务器(微服务)设置。

无论您要定义express中间件(例如bodyParsercors还是passport等),都需要创建一个{{1} }中间件功能(每次上传文件时,文件会先通过此功能 first ):

middlewares / index.js

multer

services / saveImage.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 中间件函数)

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控制器,在我的情况下,该控制器存储文件的路径( /uploads/name-of-file.ext),以及将图像(http://localhost:4000/uploads/name-of-file.ext)检索到我的数据库的字符串。在我的情况下,该字符串然后被发送回客户端以存储为redux状态,然后作为用户的化身进行更新(当将字符串传递到create时,它向<img src={avatarurl} alt="avatarurl.png" />发出一个请求微服务)。


验证失败。

可以说用户尝试上传GET图片。它通过我们的.tiff multer中间件函数传递,该函数触发express错误,此错误通过"That file extension is not accepted!"返回到req.err,后者返回saveImage为: req.err

在我们的客户端,return res.status(400).json({ err: req.err });流经我们的axios 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 ); 拿起服务器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并显示它(不必担心这里的逻辑,只是它是一个将state.server.error显示为{{1 }}):

state.server.error

最终结果是向用户显示serverError错误:

enter image description here