【问题标题】:Pass Multer validation error to React Component将 Multer 验证错误传递给 React 组件
【发布时间】:2019-08-25 18:04:44
【问题描述】:

我正在学习 Multer 以及 ReduxReact

我的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;
    });
};

我的 Reducer 如下所示

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

我的控制台输出如下所示

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

【问题讨论】:

  • 它的 500 内部服务器错误所以,你能发布你在服务器端得到的错误日志吗?
  • 感谢@VikashSingh。如果我使用此代码upload(req, res, function(error) { if (error) { console.log(error); // I need to pass this error to React } else { if (req.file.filename === res.req.res.req.file.filename) { res.status(200).json({ message: 'File uploaded', file: req.file.filename }); } return; } });。我得到这个输出i.stack.imgur.com/mJitG.png
  • 有一个很好的教程可以解决你对 multer 的疑惑,请看一遍:scotch.io/tutorials/express-file-uploads-with-multer我希望这对你有帮助。进一步如果您面临更多挑战,请告诉我。
  • 根据屏幕截图,文件验证失败。您是在上传 .jpeg 还是 .png 图像文件?
  • 感谢@VikashSingh。我想在 React 组件中获取该文件验证消息。

标签: node.js reactjs express multer


【解决方案1】:

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

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

【讨论】:

  • 感谢@Vikash Singh。我使用此代码res.status(200).json({ error }); 并收到此错误i.stack.imgur.com/s9Ax8.png
  • 它告诉存储错误,这可能是由于提供目录路径或目录权限的方式不正确。尝试给出完整的绝对路径并分配合适的权限。
  • 感谢@Vikash Singh。目录路径正确。如果我使用 .png 或 .jpeg 一切正常。我想将验证消息传递给 React。如果我使用if (error) { console.log(error); },则消息将打印在终端中。谢谢。
  • res.status(400).json({ error }); 这应该可以解决
  • 您好,您能否展开您之前屏幕截图中的request 对象并显示其中的内容?
【解决方案2】:

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

【讨论】:

    【解决方案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 thunk 动作创建者(这是一个等待来自我们服务器的 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 传递给action 创建者的.then().catch()action 创建者将其交给reducer,后者更新redux 状态,然后更新connected 组件。


    服务器(微服务)设置。

    无论您在哪里定义 express 中间件(例如:bodyParsercorspassport 等),您都需要创建一个 multer 中间件函数(任何时候上传文件,它通过这个函数首先):

    中间件/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) 检索到我的数据库的字符串。在我的例子中,该字符串然后被发送回客户端以存储到 redux 状态,然后更新为用户的头像(当将字符串传递给<img src={avatarurl} alt="avatarurl.png" /> 时,它会向微服务发出GET 请求)。


    验证失败。

    假设用户尝试上传.tiff 图片。它通过我们的expressmulter中间件函数,触发"That file extension is not accepted!"错误,这个错误通过req.err返回到saveImage,它返回req.err为:return res.status(400).json({ err: req.err });

    在我们的客户端,err 流经我们的 axios 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;
    

    一个connected 组件检索这个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! err 正在显示给用户:

    【讨论】:

      猜你喜欢
      • 2021-09-22
      • 2016-02-29
      • 2015-02-27
      • 2019-01-18
      • 1970-01-01
      • 2017-12-16
      • 2016-08-27
      • 2021-08-16
      • 1970-01-01
      相关资源
      最近更新 更多