需求
图片压缩服务很常见,比如 tinypng.com,压缩图等,但是如何打造自己的图片压缩服务?
思路
首先想到使用tinypng.com
提供的API,但是也是有限制或者购买服务,不符合我们自建服务的需要,我们需要完全开源的自有服务,于是了解到了imagemin,有node版本
解决方案
采用koa后台框架搭建node服务
- 先安装依赖
npm i imagemin imagemin-giflossy imagemin-mozjpeg imagemin-pngquant imagemin-svgo imagemin-webp
- 编写koa基础服务,参考https://www.liaoxuefeng.com/wiki/1022910821149312/1099849448318080
-
编写imagemin服务
// 安装依赖:npm i imagemin imagemin-giflossy imagemin-mozjpeg imagemin-pngquant imagemin-svgo imagemin-webp
const fs = require('fs');
const path = require('path');
const imagemin = require('imagemin');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminPngquant = require('imagemin-pngquant');
const imageminGiflossy = require('imagemin-giflossy');
const imageminWebp = require('imagemin-webp');
const imageminSvgo = require('imagemin-svgo');
//stream pipeline
const util = require('util');
const stream = require('stream');
const imgMIMEList = {
svg: 'image/svg+xml',
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
bmp: 'image/bmp',
webp: 'image/webp',
ico: 'image/x-icon',
tif: 'image/tiff',
psd: 'application/octet-stream',
}
//TODO:提示压缩率
const fn_imagemin = async (ctx, next) => {
const file = ctx.request.files.file; // 获取上传文件
const reader = fs.createReadStream(file.path); // 创建可读流
const ext = file.name.split('.').pop(); // 获取上传文件扩展名
const dataMIME = "data:" + imgMIMEList[ext] + ";base64,"; //根据扩展名设定媒体类型
const imgName = getRandomId() + '.' + ext; //压缩图片文件名
const imgSourcePath = `storage/upload/img`; //压缩源图片文件夹
const imgResultPath = `storage/result/img`; //压缩目标文件夹
const imgSourcePathFile = `${imgSourcePath}/${imgName}`; //压缩源图片路径
const imgSourcePathFileCWD = path.resolve(process.cwd(), imgSourcePathFile); //压缩源图片绝对路径
const imgSourcePathCWD = path.resolve(process.cwd(), imgSourcePath); //源文件夹绝对路径
const imgResultPathCWD = path.resolve(process.cwd(), imgResultPath); //目标文件夹绝对路径
const upStream = fs.createWriteStream(imgSourcePathFileCWD); // 创建可写流 存储源图片
// reader.pipe(upStream);// 可读流通过管道写入可写流
const pipeline = util.promisify(stream.pipeline);
let resultData = null;
await pipeline(reader, upStream);
await imageCompress(imgSourcePathFile, imgResultPath, (imgBuffer) => {
resultData = dataMIME + imgBuffer.toString('base64');
}).catch((err) => {
ctx.response.status = 201
})
//删除图片
setTimeout(()=>{
deleteFile(imgSourcePathCWD,imgName);
deleteFile(imgResultPathCWD,imgName);
},0)
if (file) {
ctx.response.body = resultData;
}
else {
ctx.response.status = 204;
}
};
/**
* 压缩图片
* @param {String} sourcePath
* @param {String} resultPath
* @param {Function} callback
*/
async function imageCompress(sourcePath, resultPath, callback) {
const files = await imagemin([sourcePath], {
destination: resultPath,
plugins: [
imageminMozjpeg({
quality: 70
}),
imageminPngquant(),
imageminGiflossy({
lossy: 80
}),
imageminWebp(),
imageminSvgo()
]
});
const imgBuffer = files[0].data;
callback(imgBuffer)
}
/**
* 获取随机id
*/
function getRandomId() {
return Math.random().toString(36).substr(2) + Date.now().toString(36);
}
/**
* 删除某一个包下面的需要符合格式的文件。
* @param {String} url 文件路径,绝对路径
* @param {String} name 需要删除的文件名称
* @return {Null}
* @author huangh 20170123
*/
function deleteFile(url,name){
let files = [];
if( fs.existsSync(url) ) { //判断给定的路径是否存在
files = fs.readdirSync(url); //返回文件和子目录的数组
files.forEach(function(file,index){
let curPath = path.join(url,file);
if(fs.statSync(curPath).isDirectory()) { //同步读取文件夹文件,如果是文件夹,则函数回调
deleteFile(curPath,name);
} else {
if(file.indexOf(name)>-1){ //是指定文件,则删除
fs.unlinkSync(curPath);
console.log("删除文件:"+curPath);
}
}
});
}else{
console.log("给定的路径不存在!");
}
}
module.exports = {
'POST /upload/img': fn_imagemin
};
注意
本教程在于提供可行思路及核心代码,完整项目程序暂未整理.因为笔者在编写过程中发现,本地windows环境没有问题,但是docker环境下无法正常运行,所以整体的node服务仍然处于研究阶段,希望有兴趣的朋友实验成功后分享出来.
参考
https://github.com/imagemin/imagemin
https://web.dev/use-imagemin-to-compress-images/
https://www.liaoxuefeng.com/wiki/1022910821149312/1099849448318080