我们都知道图片的存储是二进制的。当上传图片的时候,如果不使用第三方库,那么解析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] }
我们知道,一般做一些业务,最常见的就是上传用户图像或者一些大的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}`)); }
下面来看看效果吧
处理图片的上传
其实上传的过程都是一样的。只不过,我们还需要处理图片的大小,就是上面的中间件。 创建图片表,并且根据图片名称获取图片的详细信息。
// 创建动态图片 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) }
下面来看看效果吧
以上代码及步骤只是给出参考的思路,具体代码请访问github