嗨,希望有人能帮忙。使用MERN构建项目,需要有文件上传功能。已尝试实施各种解决方案,但似乎一无所获。任何帮助将不胜感激。
这是我的快速路线
const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth');
const { check, validationResult } = require('express-validator');
// bring in normalize to give us a proper url, regardless of what user entered
const normalize = require('normalize-url');
const checkObjectId = require('../../middleware/checkObjectId');
const path = require('path');
const multer = require('multer');
const Profile = require('../../models/Profile');
const User = require('../../models/User');
const Post = require('../../models/Post');
const upload = multer({
storage: multer.diskStorage({
destination(req, res, cb) {
cb(null, './files');
},
filename(req, file, cb) {
cb(null, `${new Date().getTime()}_${file.originalname}`);
}
}),
limits: {
fileSize: 1000000 // max file size 1MB = 1000000 bytes
},
fileFilter(req, file, cb) {
if (!file.originalname.match(/\.(jpeg|jpg|png)$/)) {
return cb(
new Error(
'only upload files with jpg, jpeg, format.'
)
);
}
cb(undefined, true); // continue with upload
}
});
// @route POST api/profile/upload
// @desc Upload profile image
// @access Private
router.post(
'/upload',
upload.single('file'),
async (req, res) => {
try {
console.log('Hello')
const { path, mimetype, originalname } = req.file;
const profileFields = {};
profileFields.user = req.user.id;
if (path) profileFields.image.path = path;
if (mimetype) profileFields.image.mimetype = mimetype;
if (originalname) profileFields.image.originalname = originalname;
let profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFields },
{ new: true, upsert: true, setDefaultsOnInsert: true }
);
return res.json(profile);
} catch (err) {
console.log('help')
res.sendStatus(400).send('Error while uploading file. Please try again later.')
}
},
(error, req, res, next) => {
if (error) {
res.status(500).send(error.message);
}
}
);
这是我的模型
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
location: {
type: String,
required: true
},
bio: {
type: String
},
image: {
origianlname: {
type: String,
required: true
},
path: {
type: String,
required: true
},
mineType: {
type: String,
required: true
},
},
topics: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'topic',
}
],
social: {
youtube: {
type: String
},
twitter: {
type: String
},
facebook: {
type: String
},
linkedin: {
type: String
},
instagram: {
type: String
}
},
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('profile', ProfileSchema);
我在后端设置了一个名为 files 的文件夹
前端
动作文件
//Upload profile picture
export const uploadImage = (file) => async (dispatch) => {
try {
const formData = new FormData();
formData.append('file', file);
await axios.post('api/profile/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
dispatch(setAlert('Profile picture uploaded successfully', 'success'))
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status}
});
}
}
Upload.js 组件文件
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Form, Button } from 'react-bootstrap';
import { uploadImage } from '../../actions/profile';
const Upload = ({ errors, dispatch }) => {
const [file, setFile] = useState(null);
const [isSubmitted, setIsSubmitted] = useState(false);
const [errorMsg, setErrorMsg] = useState(null);
useEffect(() => {
setErrorMsg(errors);
}, [errors]);
useEffect(() => {
setErrorMsg('');
}, [])
const handleOnChange = (event) => {
const file = event.target.files[0];
setFile(file);
};
const handleFormSubmit = (event) => {
event.preventDefault();
if (file) {
setErrorMsg('');
dispatch(uploadImage(file));
setIsSubmitted(true);
}
};
return (
<React.Fragment>
{errorMsg && errorMsg.upload_error ? (
<p className="errorMsg centered-message">{errorMsg.upload_error}</p>
) : (
isSubmitted && (
<p className="successMsg centered-message">
Photo uploaded successfully
</p>
)
)}
<Form
onSubmit={handleFormSubmit}
method="post"
encType="multipart/form-data"
className="upload-form"
>
<Form.Group>
<Form.Label>Choose photo to upload</Form.Label>
<Form.Control type ="file" name="file" onChange={handleOnChange} />
</Form.Group>
<Button
variant="primary"
type="submit"
className={`${!file ? 'disabled submit-btn' : 'submit-btn'}`}
>
Upload
</Button>
</Form>
</React.Fragment>
);
};
const mapStateToProps = (state) => ({
file: state.file || [],
errors: state.errors || {}
});
export default connect(mapStateToProps)(Upload);
减速器文件
import {
GET_PROFILE,
PROFILE_ERROR,
CLEAR_PROFILE,
GET_PROFILES,
} from '../actions/types';
const initialState = {
profile: null,
profiles: [],
repos: [],
loading: true,
error: {}
};
function profileReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROFILE:
return {
...state,
profile: payload,
loading: false
};
case GET_PROFILES:
return {
...state,
profiles: payload,
loading: false
};
case PROFILE_ERROR:
return {
...state,
error: payload,
loading: false,
profile: null
};
case CLEAR_PROFILE:
return {
...state,
profile: null,
repos: []
};
default:
return state;
}
}
export default profileReducer;
希望有人能看到我哪里出错了。我在 redux devtools 上的操作负载说 PROFILE_ERROR 正在运行,我收到状态 400 错误请求。
答案 0 :(得分:0)
所以我已经玩过了,现在有了一个有效的解决方案。将其张贴在此处,供其他需要有关如何执行此操作的线索的人使用。
这里是快速路线
const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth');
const { check, validationResult } = require('express-validator');
// bring in normalize to give us a proper url, regardless of what user entered
const normalize = require('normalize-url');
const checkObjectId = require('../../middleware/checkObjectId');
const path = require('path');
const multer = require('multer');
const Profile = require('../../models/Profile');
const User = require('../../models/User');
const Post = require('../../models/Post');
const upload = multer({
storage: multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads');
},
filename(req, file, cb) {
cb(null, `${new Date().getTime()}_${file.originalname}`);
}
}),
limits: {
fileSize: 1024 * 1024 * 5
},
fileFilter(req, file, cb) {
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png' || file.mimetype === 'image/gif' || file.mimetype === 'image/jpg' || file.mimetype ==='image/jfif') {
cb(null, true)
} else {
cb(new Error('Please upload a file type of jpeg, png or gif'), false)
}
}
});
// @route POST api/profile
// @desc Create or update user profile
// @access Private
router.post(
'/',
upload.single('file'),
auth,
check('location', 'Location is required').not().isEmpty(),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
console.log(req.file);
console.log(req.body);
// destructure the request
const {
location,
bio,
youtube,
twitter,
instagram,
linkedin,
facebook,
} = req.body;
// build a profile
const profileFields = {};
profileFields.user = req.user.id;
if(req.file !== undefined) {
let fileUrl = "/" + req.file.path.replace(/\\/g, "/");
if (path) profileFields.path = fileUrl;
if (mimetype) profileFields.mimeType = req.file.mimetype;
if (originalname) profileFields.orginalName = req.file.originalname;
if (location) profileFields.location = req.body.location;
if (bio) profileFields.bio = req.body.bio;
} else {
if (location) profileFields.location = req.body.location;
if (bio) profileFields.bio = req.body.bio;
}
// Build social object
const socialFields = {youtube, twitter, instagram, linkedin, facebook}
for (const [key, value] of Object.entries(socialFields)) {
if (value && value.length > 0)
socialFields[key] = normalize(value, { forceHttps: true });
}
try {
// Using upsert option (creates new doc if no match is found):
let profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFields },
{ new: true, upsert: true, setDefaultsOnInsert: true }
);
return res.json(profile);
} catch (err) {
console.error(err.message);
return res.status(500).send('Server Error');
}
}
);
这是模型
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
location: {
type: String,
required: true
},
bio: {
type: String
},
orginalName: {
type: String,
required: true
},
path: {
type: String,
required: true
},
mimeType: {
type: String,
required: true
},
topics: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'topic',
}
],
social: {
youtube: {
type: String
},
twitter: {
type: String
},
facebook: {
type: String
},
linkedin: {
type: String
},
instagram: {
type: String
}
},
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('profile', ProfileSchema);
这里是动作文件
// Create or update profile
export const createProfile = (payload, history, edit = false) => async (
dispatch
) => {
try {
const res = await api.post('/profile', payload, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
dispatch({
type: GET_PROFILE,
payload: res.data
});
dispatch(setAlert(edit ? 'Profile Updated' : 'Profile Created', 'success'));
if (!edit) {
history.push('/dashboard');
}
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
这里是减速机
import {
GET_PROFILE,
PROFILE_ERROR,
CLEAR_PROFILE,
GET_PROFILES,
} from '../actions/types';
const initialState = {
profile: null,
profiles: [],
loading: true,
error: {}
};
function profileReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROFILE:
return {
...state,
profile: payload,
loading: false
};
case GET_PROFILES:
return {
...state,
profiles: payload,
loading: false
};
case PROFILE_ERROR:
return {
...state,
error: payload,
loading: false,
profile: null
};
case CLEAR_PROFILE:
return {
...state,
profile: null,
};
default:
return state;
}
}
export default profileReducer;
这里是上传组件
import React, { useEffect, useState, Fragment } from 'react';
import { Link, withRouter, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createProfile, getCurrentProfile } from '../../actions/profile';
const CreateProfile = ({
createProfile,
getCurrentProfile,
profile: { profile, loading },
history
}) => {
const [formData, setFormData] = useState({
location: '',
bio: '',
twitter: '',
facebook: '',
linkedin: '',
youtube: '',
instagram: ''
});
const [file, setFile] = useState("");
const [imageName, setImageName] = useState("Choose file");
const [displaySocialInputs, toggleSocialInputs] = useState(false);
const {
location,
bio,
twitter,
facebook,
linkedin,
youtube,
instagram
} = formData;
const onFileChange = (e) => {
setFile(e.target.files[0]);
setImageName(e.target.files[0].name);
}
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = (e) => {
e.preventDefault();
const payload = new FormData();
payload.append("file", file);
payload.append("location", formData.location);
payload.append("bio", formData.bio);
payload.append("twitter", formData.twitter);
payload.append("facebook", formData.facebook);
payload.append("linkedin", formData.linkedin);
payload.append("youtube", formData.youtube);
payload.append("instagram", formData.instagram);
createProfile(payload, history);
};
useEffect(() => {
getCurrentProfile();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getCurrentProfile]);
return loading && profile === null ? (
<Redirect to='/dashboard' />
) : (
<Fragment>
<h1 className='large text-primary'>Create Your Profile</h1>
<p className='lead'>
<i className='fas fa-user' /> Let's get some information to make your
profile stand out
</p>
<small>* = required field</small>
<form className='form' onSubmit={e => onSubmit(e)} encType="multipart/form-data">
<div className='form-group'>
<input
type='text'
placeholder='Location'
name='location'
value={location}
onChange={(e) => onChange(e)}
/>
<small className='form-text'>
City (eg. Glasgow)
</small>
</div>
<div className='form-group'>
<textarea
placeholder='A short bio of yourself'
name='bio'
value={bio}
onChange={(e) => onChange(e)}
/>
<small className='form-text'>Tell us a little about yourself</small>
</div>
<div className="form-group">
<label htmlFor="image">Upload Profile Image</label><br></br>
<input type="file" onChange={(e) => onFileChange(e)} accept="image/*" />
</div>
<div className='my-2'>
<button
onClick={() => toggleSocialInputs(!displaySocialInputs)}
type='button'
className='btn btn-light'
>
Add Social Network Links
</button>
<span>Optional</span>
</div>
{displaySocialInputs && (
<Fragment>
<div className='form-group social-input'>
<i className='fab fa-twitter fa-2x' />
<input
type='text'
placeholder='Twitter URL'
name='twitter'
value={twitter}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group social-input'>
<i className='fab fa-facebook fa-2x' />
<input
type='text'
placeholder='Facebook URL'
name='facebook'
value={facebook}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group social-input'>
<i className='fab fa-youtube fa-2x' />
<input
type='text'
placeholder='YouTube URL'
name='youtube'
value={youtube}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group social-input'>
<i className='fab fa-linkedin fa-2x' />
<input
type='text'
placeholder='Linkedin URL'
name='linkedin'
value={linkedin}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group social-input'>
<i className='fab fa-instagram fa-2x' />
<input
type='text'
placeholder='Instagram URL'
name='instagram'
value={instagram}
onChange={e => onChange(e)}
/>
</div>
</Fragment>
)}
<input type='submit' className='btn btn-primary my-1' />
<Link className='btn btn-light my-1' to='/dashboard'>
Go Back
</Link>
</form>
</Fragment>
);
};
CreateProfile.propTypes = {
createProfile: PropTypes.func.isRequired,
getCurrentProfile: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
profile: state.profile
});
export default connect(mapStateToProps, { createProfile, getCurrentProfile })(withRouter(CreateProfile));
希望对遇到类似问题的人有所帮助