MERN/Redux 应用程序:图片上传功能正在开发中,而非生产中
MERN/Redux App: Image upload feature works in development, not production
React/Node/Express 带有 Redux 的应用,连接到 Mongo Atlas DB Cloud
大家好。我为想要上传和展示他的艺术作品的艺术家创建了一个带有完整 CRUD 的基本作品集应用程序(在我的代码库中称为“佣金”)。
我按照这篇文章了解如何将图片上传到 mongoose:
https://codeburst.io/image-uploading-using-react-and-node-to-get-the-images-up-c46ec11a7129
基本上,本文使用的策略允许您在上传图片时做两件事:
1 - saves/sends mongo 将文档发送到 mongoDB(在我的例子中,Mongo Atlas Cloud)
2 - 将图像保存在应用程序的本地文件目录中
它在本地有效。我可以上传任意数量的 files/images,它全部显示在组件上,发送到 mongo 并保存在 app/uploads/
然而,一旦我将应用程序部署到 Heroku,它就不允许我上传任何图像。并且本地上传的图片不显示,只显示其他内容,如commission.title
、commission.description
等
代码
佣金模式
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CommissionSchema = new Schema({
imageName: {
type: String,
default: "none",
required: true
},
imageData: {
type: String,
required: true
},
title: {
type: String,
maxlength: 22,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: false
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = Commission = mongoose.model('commission', CommissionSchema);
通过 Multer 上传中间件
const multer = require('multer');
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/jpg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(null, false);
};
};
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5
},
fileFilter: fileFilter
});
module.exports = upload;
Post 新佣金路线
const express = require('express');
const router = express.Router();
const upload = require('../../middleware/upload');
const Commission = require('../../models/Commission');
router.route('/').post(upload.single('imageData'), (req, res) => {
const newCommission = new Commission({
imageName: req.body.imageName,
imageData: req.file.path,
title: req.body.title,
description: req.body.description,
price: req.body.price
});
newCommission.save()
.then(commission => res.json(commission))
.catch(err => res.status(400).json(`Create new commission failed: ${err}`));
});
axios 的 Redux 操作 post
export const addCommission = commission => (dispatch, getState) => {
axios
.post('/commissions', commission, tokenConfig(getState))
.then(res => dispatch({
type: ADD_COMMISSION,
payload: res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({ type: ADD_COMMISSION_FAIL });
});
axios
.get('/commissions')
.then(res => dispatch({
type: GET_COMMISSIONS,
payload: res.data
}));
};
表单组件(相关代码用<~~箭头表示)
import React, { Component } from 'react';
import {
Col,
Form,
FormGroup,
Label,
Input,
Button
} from 'reactstrap';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addCommission } from '../../actions/commissionActions'; <~~<~~<~~ <~~<~~<~~
import { Redirect } from 'react-router-dom';
import ImagePreview from '../../images/ImagePreview.png';
class NewCommissionForm extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleImageChange = this.handleImageChange.bind(this); <~~<~~<~~ <~~<~~<~~
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
title: "",
description: "",
price: "",
image: ImagePreview,
redirectToCommissions: false
};
};
componentDidMount() {
if(this.state.redirectToCommissions) {
this.setState({
redirectToCommissions: false
});
};
};
static propTypes = {
addCommission: PropTypes.func.isRequired,
user: PropTypes.object.isRequired
};
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
};
handleImageChange(e) {
this.setState({
image: URL.createObjectURL(e.target.files[0]) <~~<~~<~~ <~~<~~<~~
});
};
handleSubmit(e) {
e.preventDefault();
let imageFormObj = {}; <~~<~~<~~ <~~<~~<~~
imageFormObj = new FormData(); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("imageName", "multer-image-" + Date.now()); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("imageData", e.target.elements.image.files[0]); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("title", this.state.title); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("description", this.state.description); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("price", this.state.price); <~~<~~<~~ <~~<~~<~~
this.props.addCommission(imageFormObj); <~~<~~<~~ <~~<~~<~~
this.setState({
title: "",
description: "",
price: "",
image: ImagePreview,
redirectToCommissions: true
})
};
render() {
const redirectToCommissions = this.state.redirectToCommissions;
const { isAuthenticated } = this.props.user;
if(!isAuthenticated) {
return (
<h1 style={styles.accessDenied}>
You don't have access to this page
</h1>
);
} else {
return (
<div>
<Form style={styles.container} autoFocus={false} onSubmit={this.handleSubmit}>
<h1 style={styles.title}>Upload a new Commission</h1>
<FormGroup row>
<Label for="title" style={styles.labelText} sm={2}>Title</Label>
<Col sm={10}>
<Input
type="text"
name="title"
id="title"
maxLength="22"
autoFocus
required
onChange={this.handleChange}
value={this.state.title}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="description" style={styles.labelText} sm={2}>Description</Label>
<Col sm={10}>
<Input
type="textarea"
name="description"
id="description"
required
onChange={this.handleChange}
value={this.state.description}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="price" style={styles.labelText} sm={2}>Price</Label>
<Col sm={10}>
<Input
type="number"
name="price"
id="price"
min={0}
onChange={this.handleChange}
value={this.state.price}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="image" style={styles.labelText} sm={2}>Image</Label>
<Col sm={10}>
<Input
type="file"
name="image"
id="image"
required
onChange={e => this.handleImageChange(e)} <~~<~~<~~ <~~<~~<~~
/>
<img
src={this.state.image}
alt="Commission Preview"
style={styles.imagePreview}
/>
</Col>
</FormGroup>
<FormGroup row>
<Col sm={2}>
</Col>
<Col sm={10}>
<Button outline block color="info" style={styles.submitButton}>Submit</Button>
</Col>
</FormGroup>
</Form>
{ redirectToCommissions ? <Redirect to="/" /> : null }
</div>
);
}
};
};
const styles = {
container: {
paddingLeft: '5%',
paddingRight: '5%'
},
title: {
paddingBottom: 50,
textAlign: 'center'
},
labelText: {
fontWeight: 'bold',
textShadow: '2px 2px 4px black'
},
submitContainer: {
paddingTop: 50,
paddingLeft: 'auto',
paddingRight: 'auto',
display: 'flex',
justifyContent: 'center'
},
submitButton: {
fontSize: '1.2em',
backgroundColor: 'black',
color: 'white'
},
accessDenied: {
textAlign:'center',
paddingTop:50,
paddingBottom:50
},
imagePreview: {
marginTop: 15,
width: 300,
height: 300
}
};
const mapStateToProps = state => ({
commissions: state.commission.commissions,
loading: state.commission.loading,
user: state.user
});
export default connect(mapStateToProps, {addCommission})(NewCommissionForm);
我通过跟踪狂找到文章作者并联系他找到了解决方案。
在本地上传临时图像(使用图像文件更新 root/uploads/ 目录)
保存并commit/push到Git
部署到 Heroku
仅此而已。当我部署应用程序时,我清理了 /uploads/ 文件夹(因为我不希望我在本地玩弄的测试图像出现在实际站点上)。
由于某种原因,这导致该功能无法在生产中使用。也许 Heroku 会在目录为空时忽略或清除该目录。
但是,使用目录中的文件部署它可以使该功能按预期工作。
React/Node/Express 带有 Redux 的应用,连接到 Mongo Atlas DB Cloud
大家好。我为想要上传和展示他的艺术作品的艺术家创建了一个带有完整 CRUD 的基本作品集应用程序(在我的代码库中称为“佣金”)。
我按照这篇文章了解如何将图片上传到 mongoose:
https://codeburst.io/image-uploading-using-react-and-node-to-get-the-images-up-c46ec11a7129
基本上,本文使用的策略允许您在上传图片时做两件事:
1 - saves/sends mongo 将文档发送到 mongoDB(在我的例子中,Mongo Atlas Cloud)
2 - 将图像保存在应用程序的本地文件目录中
它在本地有效。我可以上传任意数量的 files/images,它全部显示在组件上,发送到 mongo 并保存在 app/uploads/
然而,一旦我将应用程序部署到 Heroku,它就不允许我上传任何图像。并且本地上传的图片不显示,只显示其他内容,如commission.title
、commission.description
等
代码
佣金模式
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CommissionSchema = new Schema({
imageName: {
type: String,
default: "none",
required: true
},
imageData: {
type: String,
required: true
},
title: {
type: String,
maxlength: 22,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: false
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = Commission = mongoose.model('commission', CommissionSchema);
通过 Multer 上传中间件
const multer = require('multer');
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/jpg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(null, false);
};
};
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5
},
fileFilter: fileFilter
});
module.exports = upload;
Post 新佣金路线
const express = require('express');
const router = express.Router();
const upload = require('../../middleware/upload');
const Commission = require('../../models/Commission');
router.route('/').post(upload.single('imageData'), (req, res) => {
const newCommission = new Commission({
imageName: req.body.imageName,
imageData: req.file.path,
title: req.body.title,
description: req.body.description,
price: req.body.price
});
newCommission.save()
.then(commission => res.json(commission))
.catch(err => res.status(400).json(`Create new commission failed: ${err}`));
});
axios 的 Redux 操作 post
export const addCommission = commission => (dispatch, getState) => {
axios
.post('/commissions', commission, tokenConfig(getState))
.then(res => dispatch({
type: ADD_COMMISSION,
payload: res.data
}))
.catch(err => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({ type: ADD_COMMISSION_FAIL });
});
axios
.get('/commissions')
.then(res => dispatch({
type: GET_COMMISSIONS,
payload: res.data
}));
};
表单组件(相关代码用<~~箭头表示)
import React, { Component } from 'react';
import {
Col,
Form,
FormGroup,
Label,
Input,
Button
} from 'reactstrap';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addCommission } from '../../actions/commissionActions'; <~~<~~<~~ <~~<~~<~~
import { Redirect } from 'react-router-dom';
import ImagePreview from '../../images/ImagePreview.png';
class NewCommissionForm extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleImageChange = this.handleImageChange.bind(this); <~~<~~<~~ <~~<~~<~~
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
title: "",
description: "",
price: "",
image: ImagePreview,
redirectToCommissions: false
};
};
componentDidMount() {
if(this.state.redirectToCommissions) {
this.setState({
redirectToCommissions: false
});
};
};
static propTypes = {
addCommission: PropTypes.func.isRequired,
user: PropTypes.object.isRequired
};
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
};
handleImageChange(e) {
this.setState({
image: URL.createObjectURL(e.target.files[0]) <~~<~~<~~ <~~<~~<~~
});
};
handleSubmit(e) {
e.preventDefault();
let imageFormObj = {}; <~~<~~<~~ <~~<~~<~~
imageFormObj = new FormData(); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("imageName", "multer-image-" + Date.now()); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("imageData", e.target.elements.image.files[0]); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("title", this.state.title); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("description", this.state.description); <~~<~~<~~ <~~<~~<~~
imageFormObj.append("price", this.state.price); <~~<~~<~~ <~~<~~<~~
this.props.addCommission(imageFormObj); <~~<~~<~~ <~~<~~<~~
this.setState({
title: "",
description: "",
price: "",
image: ImagePreview,
redirectToCommissions: true
})
};
render() {
const redirectToCommissions = this.state.redirectToCommissions;
const { isAuthenticated } = this.props.user;
if(!isAuthenticated) {
return (
<h1 style={styles.accessDenied}>
You don't have access to this page
</h1>
);
} else {
return (
<div>
<Form style={styles.container} autoFocus={false} onSubmit={this.handleSubmit}>
<h1 style={styles.title}>Upload a new Commission</h1>
<FormGroup row>
<Label for="title" style={styles.labelText} sm={2}>Title</Label>
<Col sm={10}>
<Input
type="text"
name="title"
id="title"
maxLength="22"
autoFocus
required
onChange={this.handleChange}
value={this.state.title}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="description" style={styles.labelText} sm={2}>Description</Label>
<Col sm={10}>
<Input
type="textarea"
name="description"
id="description"
required
onChange={this.handleChange}
value={this.state.description}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="price" style={styles.labelText} sm={2}>Price</Label>
<Col sm={10}>
<Input
type="number"
name="price"
id="price"
min={0}
onChange={this.handleChange}
value={this.state.price}
/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="image" style={styles.labelText} sm={2}>Image</Label>
<Col sm={10}>
<Input
type="file"
name="image"
id="image"
required
onChange={e => this.handleImageChange(e)} <~~<~~<~~ <~~<~~<~~
/>
<img
src={this.state.image}
alt="Commission Preview"
style={styles.imagePreview}
/>
</Col>
</FormGroup>
<FormGroup row>
<Col sm={2}>
</Col>
<Col sm={10}>
<Button outline block color="info" style={styles.submitButton}>Submit</Button>
</Col>
</FormGroup>
</Form>
{ redirectToCommissions ? <Redirect to="/" /> : null }
</div>
);
}
};
};
const styles = {
container: {
paddingLeft: '5%',
paddingRight: '5%'
},
title: {
paddingBottom: 50,
textAlign: 'center'
},
labelText: {
fontWeight: 'bold',
textShadow: '2px 2px 4px black'
},
submitContainer: {
paddingTop: 50,
paddingLeft: 'auto',
paddingRight: 'auto',
display: 'flex',
justifyContent: 'center'
},
submitButton: {
fontSize: '1.2em',
backgroundColor: 'black',
color: 'white'
},
accessDenied: {
textAlign:'center',
paddingTop:50,
paddingBottom:50
},
imagePreview: {
marginTop: 15,
width: 300,
height: 300
}
};
const mapStateToProps = state => ({
commissions: state.commission.commissions,
loading: state.commission.loading,
user: state.user
});
export default connect(mapStateToProps, {addCommission})(NewCommissionForm);
我通过跟踪狂找到文章作者并联系他找到了解决方案。
在本地上传临时图像(使用图像文件更新 root/uploads/ 目录)
保存并commit/push到Git
部署到 Heroku
仅此而已。当我部署应用程序时,我清理了 /uploads/ 文件夹(因为我不希望我在本地玩弄的测试图像出现在实际站点上)。
由于某种原因,这导致该功能无法在生产中使用。也许 Heroku 会在目录为空时忽略或清除该目录。
但是,使用目录中的文件部署它可以使该功能按预期工作。