阅读 267

react+express+axios上传图片踩坑指南

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()函数中就可以了。

文章分类
前端
文章标签