0. 前言
最近在做上传图片到服务器并显示的功能时,踩了很多坑。于是,写了这篇文章,来记录一下这几天踩的坑。上传图片,采用的技术栈是:
- 前端:React;axios用来发送ajax请求
- 后端:express搭建服务器;formidable用来解析From表单并且转存文件
1. 踩坑历程
1.1. 遇到问题
为了解决上传图片到服务器,我在网上查找了很多资料,最终决定在前端使用axios库发送ajax请求,将图片打包到FormData对象中,后端利用formidable解析FormData并且将文件保存到express的静态资源文件夹下。实现代码如下:
- 前端渲染:
<div>
<input type="file" name="add-img" ref={this.fileInput}/>
<input type="button" value="上传" onClick={this.upload}/>
</div>
复制代码
- 前端获取图片:
// 在react组件的构造器中注册 ref
constructor(props){
super(props)
this.fileInput = React.createRef()
}
复制代码
- 前端上传图片:
upload = () => {
// console.log(this.fileInput.current.files[0])
const data = new FormData()
data.append('imgFile', this.fileInput.current.files[0])
console.log(data.get('imgFile'))
const uploadPicture=(formData)=>{
//url暂时使用了前端代理跨域
return axios.post("/api/upload",formData);
}
uploadPicture(data)
.then(response => {
console.log(response)
})
.catch(err => {
throw err
})
}
复制代码
- 后端服务器注册静态文件夹:
app.use('/public', express.static('public'))
复制代码
- 后端服务器解析数据并保存文件:
app.post('/upload', (req,res) => {
var form = new formidable.IncomingForm()
var path = require('path')
form.uploadDir = path.resolve(__dirname,'../../public')
console.log(form.uploadDir)
form.keepExtensions = true
form.parse(req, (err, fields, files) => {
if(err) {
console.log(err)
console.log(fields)
console.log(files)
res.json({
'code': 1,
'err': err,
})
} else {
res.json({
'code': 0,
'file': files
})
}
})
})
复制代码
然而,将前后端服务器运行之后,发现在单击上传图片按钮的时候会报错。于是开始了我漫长的debug旅途...
1.2. 分析问题
在查找了很多资料之后,最终把问题定位到发送ajax请求时的请求头上面,在我的代码中,请求头是直接更改的:
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
复制代码
然后,第一次运行之后,报错:
Error in posting multipart/form-data. Content-Type header is missing boundary
复制代码
于是我就在其他地方找了一个boundary,添加在了multipart/form-data的后面。
之后,持续报错:
Error: MultipartParser.end(): stream ended unexpectedly: state = START_BOUNDARY
复制代码
然后我试图打印file信息和fields信息,得到的结果都是{}
空对象。
在查找了N多资料之后,最终把问题定位到请求头添加的Content-Type
属性上面。
上传文件的时候,需要将请求头的Content-Type
设置为multipart/form-data
,同时还必须添加Boundary
作为解析文件的边界。否则,后端无法解析表单数据。这个时候,问题就变成了如何获取正确的Boundary。
1.3. 解决方案
最终,我在一篇博客中找到解决方案。废话不多说,贴代码:
axios.interceptors.request.use(config => {
let csrfToken = localStorage.getItem("token");
config.headers["token"] = csrfToken;
if(config.url=="api/upload"){
//以formData上传时请求头Content-Type类型要改为multipart/form-data,所以加了判断
config.headers['Content-Type']="multipart/form-data";
}else{
config.headers['Content-Type'] = 'application/json';
}
return config;
})
复制代码
将这段代码添加到upload()
函数中就可以了。