koa文件上传的那点事

5,779 阅读4分钟

前言

  • 【音乐博客】在做注册的时候,头像上传到服务器上,使用koa做后台


四个方面

1. 使用formidable库上传文件

  • var formidable = require('formidable')
        var form = new formidable.IncomingForm()
        form.uploadDir = uploadPath
       
        form.parse(ctx.req, function (err, fields, files) {
            console.log('files', files)
            //报错的时候直接抛出错误
            if (err) {
                throw err;
            }
    
           
            //每当触发事件的时候就产生一个随机数
            // var ran=parseInt(Math.random()*89999+10000);
    
            //    var extname=path.extname(files.appealFile.name); // m4a  改名用的随机数
            var extname = files.appealFile.name;
    
            // 要改路径,就改这两个变量的路径就好
            const oldPath = __dirname + "/../" + files.appealFile.path;
            // console.log(oldPath);
            const newPath = __dirname + "/../audio/" + extname;
            // console.log(newPath);
    
    
            fs.rename(oldPath, newPath, (err) => {
                if (err) {
                    console.log(err);
                    throw Error('改名失败');
                }
                res.send(newPath).end("success");
            })
        })
  • 结果发现form.parse方法都进不去,更别说后面的保存操作了
  • 尝试过多种方法,最后以失败告终;以前使用node是没问题的,但一到koa这就进不去,也不会报错,不明觉厉

2. 使用koa-body上传文件

  • 最后换种文件上传的方法,使用流的形式上传到本地
  • 安装koa-body
  • npm install koa-body --save

  • 在koa项目中引用koa-body中间件 
  • const koaBody = require('koa-body');
    app.use(koaBody({
        multipart: true,
        formidable: {
            maxFileSize: 200*1024*1024    // 设置上传文件大小最大限制,默认2M
        }
    }));

  • 使用koa-body中间件后,即可通过ctx.request.files获取上传的文件

    提醒:
    新版本的koa-body通过ctx.request.files获取上传的文件
    旧版本的koa-body通过ctx.request.body.files获取上传的文件

  • 获取到文件之后,通过fs将文件保存到服务器的指定目录

  • // 支持多文件上传到本地
    const uploadFile = (fileArr) => {
    
        var resultArr = []
        fileArr.forEach(element => {
      
          // 防止文件命名一致,文件名后面加上时间的时分
          // const randomNum =  Math.floor((Math.random() * 9 + 1)*1000);
          const randomNum = new Date().getHours()+""+new Date().getMinutes();
      
          // 创建可读流
          const reader = fs.createReadStream(element[1].path);
          let filePath = path.join(__dirname, '../public/node/upload/image/user/') + `${randomNum}_${element[1].name}`;
          // let filePath = path.join(__dirname, '../public/upload/image/user/') + `/${element[1].name}`; 斜杆问题注意一下
          // 创建可写流
          const upStream = fs.createWriteStream(filePath);
          // 可读流通过管道写入可写流
          reader.pipe(upStream);
      
          resultArr.push({
            fileName: element[1].name,
            filePath:  filePath.replace(new RegExp(/(\\)/,"g"),'\\/'),  // 上传到服务器上的真实路径
            fileUrl: `/node/upload/image/user/${randomNum}_${element[1].name}`,  //给前端显示头像
            randomNum: randomNum
          })
        });
      
        return resultArr;
      }

3. 前端上传文件(原生)

  • 写法可以做下相对应的修改,这里用的是ts编写
  • html
  • <form  id="sendAppealForm">
    	<a href="javascript:void(0);" class="upload">选择文件 > <span class="unfile">未选择任何文件</span>
    		 <input class="replyFileid"  name="appealFile" id="appealFile" type="file"  multiple="multiple"  />
    	</a>
    </form>
    
    <el-button
    	type="danger"
    	style="width:100%; margin-bottom:30px;"
    	@click.native.prevent="handleRegiste"
      >{{ $t('registe.registeIn') }}</el-button>
    </el-form>
  •  private handleRegiste() {
    	var aaa: any = document.getElementById('sendAppealForm')
    	var formData = new FormData(aaa);
    
    	var myDate = new Date();  
    	formData.append("uploadTime", myDate.toLocaleDateString());       // 上传日期
    
    
    	var _this = this;
    
    	this.uploadFile(formData).then(res => {
    		console.log(res);
    	});
    }
    
    private uploadFile = (param: any) => {
         
        return new Promise((resolve, reject) => {
          axios
            .post("http://localhost:3000/users/register", param, {
              headers: { "Content-Type": "multipart/form-data" }
            })
            .then(res => {
              resolve(res.data);
            })
            .catch(err => {
              reject(err.data);
            });
        });
      };

4. 前端上传文件(饿了么组件upload)

  • 手动上传文件,且只能上传单个文件,并能覆盖上传
  • 先上代码,下面做分析:
  • <el-upload
      class="avatar-uploader"
      action="/certificateAuthentication/upload"
      :http-request="imgUpload"
      accept=".png, .jpg, .gif, .jpeg"
      :show-file-list="false"
      ref="upload"
      :auto-upload="false"
      :on-change="handleChange"  :before-upload="beforeAvatarUpload"
    >
    	<img v-if="imageUrl" :src="imageUrl" class="avatar" />
    	<i v-else class="el-icon-plus avatar-uploader-icon"></i>
    </el-upload>
    
    <el-button
    	type="danger"
    	@click.native.prevent="handleRegiste"
      >注册</el-button>
    </el-form>
    

  • fileList = [];
    
    handleChange(file, fileList) {
    	 if (fileList.length > 0) {
    		 this.fileList = [fileList[fileList.length - 1]]  // 这一步,是 展示最后一次选择的csv文件
    	 }
     }
    
    private handleRegiste() {
      (this.$refs.upload as any).submit();}
    
    private async imgUpload(e: any) {
        const param = new FormData();
        param.append("info", JSON.stringify(this.loginForm));
        param.append("file", this.fileList);   // e.file
        const data: any = await register(param);
        // console.log(data);
    
        if (data.result.length !== 0) {
          this.$router.push({
            path: "/login",
            query: {}
          });
    
          this.$message({
            message: "注册成功",
            type: "success"
          });
        }
      }
    
    
    

  • 因为是注册功能,我不想点击上传文件就上传到服务端去,我是要点击注册按钮才会将信息和图片一起上传上去,所以就需要手动上传
  • 饿了么upload组件可以设置手动上传(自动上传关闭)
  • :auto-upload="false"

  • 然后在注册按钮方法调用
  • this.$refs.upload.submit(); 

  • 他就会去找到组件的:http-request="imgUpload"属性,然后去调用imgUpload方法
  • 手动上传已解决,接下来讲一下只能上传单个文件
  • file-list属性,保存的是用户选择的文件数组。 

  •  想通过on-change方法,改变file-list里选择的文件列表,只保留最后一项。

  • 基本功能上实现了目标场景,但是有一个样式问题,因为是认为改变file-list,取最后一项,因此,用户选择第二个文件后,从第一个文件到第二个文件,有动态切换的效果,这不是我想要的,我想要的是 用户点击“上传文件”,本地电脑 选择文件,点击“确定”,页面上直接展示所选文件,不要动态切换。 

  • css去除这部分动画

  • <style lang="scss" scoped>
        .upload-demo {
          display: flex;
        }
        /deep/ .el-list-enter-active,
        /deep/ .el-list-leave-active {
          transition: none;
        }
     
        /deep/ .el-list-enter,
        /deep/ .el-list-leave-active {
          opacity: 0;
        }
        /deep/ .el-upload-list {
          height: 40px;
        }
    </style>

  •  至于css中的 /deep/ 是干嘛的,其实是修改elementui等第三方组件内部样式,做的渗透。如果不用scss, 可以使用 >>> 符号来修改第三方组件内部样式。
  • 完美解决

最后总结

路过的猿友,要是知道koa使用formidable的用法、和为什么我进不去form.parse方法的原因,方便的话麻烦在评论区告知,谢谢哦!


参考

NodeJs koa2实现文件上传 :www.jianshu.com/p/34d0e1a5a…

vue上传文件的坑:blog.csdn.net/fengtingYan…

element ui实现手动上传文件,且只能上传单个文件,并能覆盖上传:www.cnblogs.com/lovemomo/p/…