独书先生 Menu

All items for 6月, 2020

自建node图片压缩服务

需求

图片压缩服务很常见,比如 tinypng.com,压缩图等,但是如何打造自己的图片压缩服务?

思路

首先想到使用tinypng.com提供的API,但是也是有限制或者购买服务,不符合我们自建服务的需要,我们需要完全开源的自有服务,于是了解到了imagemin,有node版本

解决方案

采用koa后台框架搭建node服务

  1. 先安装依赖
npm i imagemin imagemin-giflossy imagemin-mozjpeg imagemin-pngquant imagemin-svgo imagemin-webp
  1. 编写koa基础服务,参考https://www.liaoxuefeng.com/wiki/1022910821149312/1099849448318080

  2. 编写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

如何清除微信网页缓存nginx配置 | 终极解决方案亲测有效

问题

微信为了提高自身的性能,对微信网页进行了缓存,且普通方法无法及时清除,项目更新后,微信无法及时更新网页内容

解决方案:

更改服务器配置,强制不缓存入口文件,通常为网站首页(index.html),其他静态正常缓存,再针对首页中引入的资源文件(js/css)采用修改文件名及文件引入链接强制获取新的文件避免缓存。

操作步骤:

  1. 在nginx中修改配置文件(nginx.conf),在所有的网站首页匹配的location /下增加#### kill cache的部分,如下:
location / {
    root   /mnt/dat1/test/tes-app;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
    #### kill cache
    add_header Last-Modified $date_gmt;
    add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
    if_modified_since off;
    expires off;
    etag off;
}
  1. 修改index.html中的资源引入链接,分两种情况:
    • 一是前端框架已经做好了打包后的文件名自动携带hash值,且引入链接也自动修改了,这种情况无需手动再修改文件名
    • 二是部分系统采用的原始引入方式开发,没有自动打包系统的,需要自己手动更改文件名和引入链接,如下:
<script type="text/javascript" src="js/login.js" ></script>

更改为

<script type="text/javascript" src="js/login_01.js" ></script>

且login.js重命名为login_01.js即可