Vue2+Koa2 图片上传功能实战

前言

vue2+koa2 开发已经不是什么新鲜,而基于 kos2 开为作后台也越来越流行,但是在用 koa2 进行开发时我们肯定会遇到一些问题,比如这篇文章要分享的图片上传功能。这个功能不复杂,但也不容易,说它不复杂是因为使用到的技术及知识点不多,都是一些基础,说不容易是因为,不管做什么只要不知道或者不太清楚套路,那么它会让你很难下手,或者手足无措。

看了本文,很有可能会解决你一些疑惑,比如:怎么上传多张图,为什么传多张图片,在 controller 中却拿不到图片对象,而设置单个文件上传时却可以。

你能学到什么?

  • 1、input 文件多选
  • 2、FormDate 相关知识
  • 3、koa-multer 实战用法
  • 4、用 Node.js 的 fs  文件模块创建目录(判断及创建)
  • 5、单文件上传、多文件上传
  • 6、文件上传的全家桶(完整套路)

图片上传实战

前期准备

在分享这个之前,我们需要知道一些基本的知识点。比如:

  • Vue-Router 路由管理器的基本用法
  • 简单的 html 常识
  • FormDate 对象的用法
  • koa-multer 模块的用法
  • Node.js 的 fs 文件模块

上面第一点和第二点应该没什么好说的,尤其是第一点,因为在这篇文章中,它不是主角并且,我们只是通过它来向服务器发请求而已。

而 FormDate 对象相对于 Vue-Router 来说更重要一些,因为它负责把我们前端上传的图片进行处理。

第二点 html 常识就是上传图片时要用到的 input 标签的基本用法,说白了就是 input 的 type="file" 和 input multi 属性。

对于 FormDate 有几点在这里我觉得有必要说下(关键步骤):

1、通过 new 关键字来创建一个 FormDate 实例,用于保存前端要上传的文件(input 选择的图片)。

2、通过 append() 方法把 input 中的图片放到 FormDate 实例中。

3、最后通过 post 请求把这个 FormDate 实例传给后端。

到这里前端已经完成任务,剩下的任务就得交给后端处理了。

至于后端,我们使用 koa-multer 模块来完成图片信息的接收工作。从 koa-multer 介绍文档中我们就可以很容易地知道它的基本用法。 我们还需要把图片按照年份日期来进行存储。

为什么还会用到 Node.js 中的 fs 文件模块呢?其一是原因是上面 koa-multer 模块保存上传图片时我想把图片存放在不同的目录,不同的上传时间上传到不同的目录下,为此需要用到 fs 模块系统,如果目录不存在则创建,另一个很重要的原因就是通过这个 fs 文件系统来读取这些目录及图片并返回图片路径给前端。

上面就是要完成图片的完整套路,下面我们就要上战场了。

前端实现

首先给你呈上的是前端代码:

html 代码

<input type="file" @change="uploadFild">

可以看到这一行代码,一个 input 标签带一个 type 属性,并绑定了 @change 事件,在 input 发生变化时调用 uploadFild 方法。

JavaScript 代码

async uploadFild(event) {
  const files = event.target.files;
  const formDate = new FormData();
  for (let file of Object.values(files)) {
    formDate.append("avatar", file);
  }
  const response = await uploadFilds(formDate);
  if (response.code === 1) {
    this.$toast.show("文件上传成功!");
  }
},

可以看到,我们往 uploadFild 方法中传入了 event 对象,通过它我们就可以拿到本地要上传的图片的一个集合(一个 FileList 数组)。

接着通过 FormDate 对象的 append() 方法把所有的文件一个一个添加到 FormDate 实例中。

这里有一个地方需要注意的,那就是 append() 方法的第一个参数,这个虽然可以随意起名,但得跟后端接收时对应上(要一致),不然会报错。

至于上面的 this.$toast.show() 方法你也不用管,它只是一个用于提示的插件而已。

uploadFilds() 方法就是我们请求服务器的方法,定义如下:

export const uploadFilds = (formDate) => {
  return BaseService.post("api/uploadFilds", formDate);
}

BaseService 你不用管,与本例无关,我只是把 axios 简单的封装了下,这都不要紧。

以上就是前端所有要做的事(代码)。

后端实现

后端实现就相对来说比较复杂一点。其实 koa-multer 的使用比较简单,我把路由配置单独出来了,不直接放在 index.js 中,而是通过引入 routes/index.js 来简化 index.js 中的代码量,下面是 routers/index.js 的关键代码。

const Router = require('koa-router');
const multer = require('../config/multer-config');
const Routers = new Router({
  prefix: '/api'
});

Routers.post('/uploadFilds', multer.single('avatar'), PostController.uploadFilds);
module.exports = Routers;

请注意代码中的 avatar 字符串,这个就是需要对前端  formDate 的 append() 方法的第一个参数要一致。

除了路由外,我还把 koa-multer 的相关配置代码也单独到了一个文件中 conifg/multer-config.js,也就是上面引入的 multer-config 文件,它的代码如下:

const multer = require('koa-multer'); // 引入 koa-multer 模块
const fs = require("fs"); // 引入 fs 文件管理器
const storage = multer.diskStorage({
  //文件保存路径  
  destination: function (req, file, cb) {
    // 这里是我默认的图片目录,后面还得根据不同的年份月份来创建相应的保存目录
    const imagesPath = "public/uploads/images/";
    const date = new Date();
    const year = date.getFullYear();
    let month = date.getMonth() + 1;
    if (month < 10) {
      month = "0" + month;
    }
    // 创建年份目录
    const existsYearDir = fs.existsSync(imagesPath + year + "/");
    if (!existsYearDir) { // 如果目录不存在
      fs.mkdirSync(imagesPath + year + "/", 0777);
    }
    // 创建月份目录
    const existsMonthDir = fs.existsSync(imagesPath + year + "/" + month + "/");
    if (!existsMonthDir) { // 如果目录不存在
      fs.mkdirSync(imagesPath + year + "/" + month + "/", 0777);
    }
    cb(null, 'public/uploads/images/' + year + '/' + month + '/');
  },
})

const upload = multer({
  storage: storage
});

module.exports = upload

这就是 koa-multer 的配置项,不懂的可以查看说明文档。

imagesPath 为图片默认的存在根目录。

上面对小于 10 的月份进行补零。

fs.existsSync() 方法:判断给定路径是否存在(返回 Boolean)。

fs.mkdirSync() 就是同步方式创建目录的方法。第一个参数是目录路由,第二个参数为目录的访问权限。

路由还对应着一个 controller ,相关代码如下:

class PostController {
  static async uploadFilds(ctx) {
    // 返回上传文件对象返回给前端
    ctx.body = {
      code: 1, // 状态码
      data: ctx.req.file
    }
  }
}
module.exports = PostController

至此,单文件上传已经完成。

提升篇

有时候我们需要同时选中多张图片并上传。那么要怎么做呢,是不是上面的单文件套路就不适用了呢?答案是否定的,因为我们只需要对上面的代码稍作修改就可以实现多文件上传了。

第一步,给 ipnut 标签添加 multiple 属性,让你可以在资源管理器中选择多张图片:

<input type="file" @change="uploadFild" multiple="multiple">

第二步,修改后面 koa-multer 接收方式

Routers.post('/uploadFilds', multer.array('avatar'), PostController.uploadFilds);

第三步:修改 controller 中从上下文获取文件的属性

static async uploadFilds(ctx) {
  // 获取上传文件对象,返回给前端
  // ctx.req.file 单文件上传时,文件数据存入变量
  // ctx.req.files 多文件上传时,文件数据存入变量
  ctx.body = {
    code: 1,
    data: ctx.req.files
  }
}

注意代码中的注解使用 koa-multer 单文件上传跟多文件上传时保存到上下文中的属性略有不同。这也是一个不小的坑,可能就这么一点就会让你耗上很长一段时间。

multer.array() 方法还可以接收第二个参数,用来限制上传的图片数量,比如:最多只能上传三张图片 multer.array('avatar',3)。

以上就是本文的全部内容,希望能解决你的一两点疑惑。