koa处理上传图片

2,559 阅读4分钟

我们都知道图片的存储是二进制的。当上传图片的时候,如果不使用第三方库,那么解析body是非常麻烦的。所以下面我们就来看看如何通过koa以及相关库,来对上传的图片处理吧。

以下代码及步骤只是给出参考的思路,具体代码请访问github

建立一个数据库,创建一个file表

      async createAvatar (filename, mimetype, size, userId) {
        const state = `INSERT INTO avatar (filename, mimetype, size, user_id) VALUES (?, ?, ?, ?)`;
        const result = await connection.execute(state, [filename, mimetype, size, userId])
        return result[0]
      }

image.png 我们知道,一般做一些业务,最常见的就是上传用户图像或者一些大的action图片。就好比掘金来说,就是上传用户头像和文章背景图像。

处理头像的上传

对于koa来说,有一个库koa-multer可以方便我们处理上传的图片。具体的使用就不介绍了,具体请看以前的文章。koa的学习。并且,如果我们想要对图片做处理,我们可以通过jimp库,来操作。体积比较小。

首先我们需要封装一个图片上传的中间件。

    const path = require("path")
    const multer = require("koa-multer");
    const Jimp = require("jimp");
    const { AVATAR_PATH, PICTURE_PATH } = require("../app/filePath");
    
    // 处理头像上传
    const avatarUpload = multer({
      dest: path.resolve(__dirname, AVATAR_PATH)
    })

    // 处理图片上传
    const pictureUpload = multer({
      dest: path.resolve(__dirname, PICTURE_PATH)
    })

    // 处理用户头像
    const avatarHandler = avatarUpload.single("avatar")


    // 处理action动态头像
    const pictureHandler = pictureUpload.array("picture")

    // 处理图片大小
    // 根据前端传入的w*h来确定图片的大小,这里就没有复杂处理,我们只做了大中小处理。
    const pictureResizeHandler = async (ctx, next) => {
      const files = ctx.req.files;
      for (let file of files) {
        Jimp.read(file.path).then(image => {
          // 根据宽度,高度自适应
          image.resize(1280, Jimp.AUTO).write(`${file.path}-large`);
          image.resize(640, Jimp.AUTO).write(`${file.path}-middle`);
          image.resize(320, Jimp.AUTO).write(`${file.path}-small`);
        })
      }
      await next()
    }
    
    module.exports = {
      avatarHandler,
      pictureHandler,
      pictureResizeHandler
    }

一般情况下,我们点击用户头像,都是通过http://localhost/users/avatar/:userId这样的方式访问。并且会将图片保存在user表中。

     // 创建用户头像
      async createAvatar (ctx, next) {
        // 获取上传的头像信息。
        const { filename, mimetype, size } = ctx.req.file;
        const userId = ctx.user.id;
        const result = await createAvatar(filename, mimetype, size, userId)
        // 将头像的url保存到users表中
        const avatarUrl = `${APP_HOST}:${APP_PORT}/users/avatar/${userId}`
        await saveAvatar(avatarUrl, userId)
        ctx.body = result
      }

通过上面的操作,图片将被保存到本地指定的文件夹下,接下来,我们就需要通过读取数据库中头像的信息,来返回本地头像给用户了。

先来读取头像信息

  // 获取头像图片详情
  async detailAvatar (userId) {
    const state = `SELECT * FROM avatar WHERE user_id = ?;`;
    const [result] = await connection.execute(state, [userId])
    return result.pop()
  }

下面我们就可以读取本地文件,返回头像。根据用户id

     // 返回用户头像
      async getUserAvatar (ctx, next) {
        const { userId } = ctx.params;
        // 从数据库中获取当前图片的详情信息。然后用于本地获取图片。
        const avatarInfo = await detailAvatar(userId);

        // 2.提供图像信息,以便浏览器解析
        ctx.response.set('content-type', avatarInfo.mimetype);
        ctx.body = fs.createReadStream(path.resolve(__dirname, `${AVATAR_PATH}/${avatarInfo.filename}`));
      }

下面来看看效果吧 touxiang.gif

处理图片的上传

其实上传的过程都是一样的。只不过,我们还需要处理图片的大小,就是上面的中间件。 创建图片表,并且根据图片名称获取图片的详细信息。

     // 创建动态图片
      async createPicture (filename, mimetype, size, userId, actionId) {
        const state = `INSERT INTO file (filename, mimetype, size, user_id, action_id) VALUES (?, ?, ?, ?, ?)`;
        const result = await connection.execute(state, [filename, mimetype, size, userId, actionId])
        return result[0]
      }

一般情况下,我们点击用户头像,都是通过http://localhost/actions/images/:filename?type=small这样的方式访问。由于图片和文章是多对一或者多对多的,所以我们需要将action_id保存在file表中。

      // 创建action图像
      async createPicture (ctx, next) {
        const files = ctx.req.files;
        const userId = ctx.user.id;
        const actionId = ctx.request.query.actionId;
        for (let file of files) {
          const { filename, mimetype, size } = file
          await createPicture(filename, mimetype, size, userId, actionId)
        }
        ctx.body = "上传成功"
      }

通过上面的操作,图片将被保存到本地指定的文件夹下(并且保存了大中小三种大小的图片),接下来,我们就需要通过读取数据库中对应图片的信息,来返回本地图片。

先来读取头像信息, 根据图片名称(自动生成的名称)

      // 获取图片详情
      async getActionPictureInfoByFileName (filename) {
        const state = `SELECT * FROM file WHERE filename = ?;`;
        const [result] = await connection.execute(state, [filename])
        return result
      }

下面我们就可以读取本地文件,返回图片信息了。根据图片名称,然后我们可以指定query的type字段来决定返回那种大小的图片。

 // 返回action动态图片
  async getActionPictureByFileName (ctx, next) {
    // 通过params传入的filename
    const filename = ctx.request.params.filename;
    // 通过query传入的type
    const imageType = ctx.request.query.type;
    let imageUrl = ""
    const types = ["small", "middle", "large"];
    
    if (types.includes(imageType)) {
      imageUrl = path.resolve(__dirname, `${PICTURE_PATH}/${filename}-${imageType}`)
    } else {
      imageUrl = path.resolve(__dirname, `${PICTURE_PATH}/${filename}`)
    }
    const result = await getActionPictureInfoByFileName(filename)
    ctx.response.set("content-type", result[0].mimetype)
    ctx.body = fs.createReadStream(imageUrl)
  }

下面来看看效果吧

touxiang1.gif

以上代码及步骤只是给出参考的思路,具体代码请访问github