我们都知道图片的存储是二进制的。当上传图片的时候,如果不使用第三方库,那么解析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