我正在学习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);
我的控制台输出如下
当我尝试上传没有.jpeg和.png扩展名的文件时,如何在我的React组件中获取Try to upload .jpeg or .png file.
错误?
您不必发送500状态代码,而应发送400
res.status(400).json({
message: error // I would like to send error from Here.
});
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;
});
这就是我能够为我创建的与我的主应用程序一起工作的头像微服务完成它的方法。
警告:此解释贯穿整个流程,因此如果您已经了解它,它可能会冗长且冗余。
首先,您必须创建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 action creator(这是一个等待来自我们服务器的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
中间件函数(任何时候上传文件,它首先通过这个函数):
中间件/ 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
图像。它通过我们的express
multer中间件函数,触发"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;
一个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!
err正在向用户显示: