乐趣区

关于node.js:AutoTinyPng从程序员的角度来压缩图片

Dear,大家好,我是“前端小鑫同学”,😇长期从事前端开发,安卓开发,热衷技术,在编程路上越走越远~


前言:

说来很奇怪,当初的不少技术交换群外面存在这一些“伪程序员”,就比如说下图的这段对话,用在线的图片压缩网站要对本人的大量图片进行压缩,竟然嫌麻烦都跑群外面问要怎么办?

从程序员的角度来解决这个问题:
  1. 下班摸鱼法:一张一张来,干一张算一张。
  2. 土豪氪金法:通过网站凋谢的 API 进行简略编程进行批量解决,当然你解决的越多就须要领取一些费用。
  3. 展现技术法:适宜在正当的数量内,难得的机会中温习一下你的编程常识,还能把活干好。
  4. 其余:。。。

打码前的筹备:

  1. 咱们抉择 展现技术法 来做明天的 Demo,我也感觉这是一个程序员的抉择(丢给美工的事我。。。);
  2. 一款产品的品质也是须要逐步进行打磨优化,tinypng在程序员两头还是流传的较为好用的一款产品,咱们仍然抉择tinypng,用他人业余的工具做本人的事,丑陋!。

    思路介绍:

  3. 递归获取本地文件夹里的文件
  4. 过滤文件,格局必须是.jpg .png, 大小小于 5MB.(文件夹递归)
  5. 每次只解决一个文件(能够绕过 20 个的数量限度)
  6. 解决返回数据拿到近程优化图片地址
  7. 取回图片更新本地图片
  8. 纯 node 实现不依赖任何其余代码片段

    打码实现:

    仅实用 Node 提供的模块:
    const fs = require("fs");
    const {Console} = require("console");
    const path = require("path");
    const https = require("https");
    const URL = require("url").URL;
    通用浏览器标识, 避免同一标识被服务器拦挡:
    const USER_AGENT = ["Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
      "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv,2.0.1) Gecko/20100101 Firefox/4.0.1",
      "Mozilla/5.0 (Windows NT 6.1; rv,2.0.1) Gecko/20100101 Firefox/4.0.1",
      "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
      "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
      "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
      "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; maxthon 2.0)",
      "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)",
    ];
    定义 log, 反对输入日志到文件:
    // 定义 log, 反对输入日志到文件
    class Log {
      options = {
     flags: "a", // append 模式
     encoding: "utf8", // utf8 编码
      };
    
      logger = {};
    
      /**
    * 初始化打印配置
    */
      constructor() {
     this.logger = new Console({stdout: fs.createWriteStream("./log.tinypng.stdout.log", this.options),
       stderr: fs.createWriteStream("./log.tinypng.stderr.log", this.options),
     });
      }
    
      /**
    * log 级别
    * @param {*} message 输入信息
    */
      log(message) {if (message) {this.logger.log(message);
       console.log(message);
     }
      }
    
      /**
    * error 级别
    * @param {*} message 输入 err 信息
    */
      error(message) {if (message) {this.logger.error(message);
       console.error(message);
     }
      }
    }
    
    // 实例化 Log 对象
    const Tlog = new Log();
    定义 TinyPng 对象:
    class TinyPng {
      // 配置信息: 后缀格局和最大文件大小受接管限度不容许调整
      config = {files: [],
     entryFolder: "./",
     deepLoop: false,
     extension: [".jpg", ".png"],
     max: 5200000, // 5MB == 5242848.754299136
     min: 100000, // 100KB
      };
    
      // 胜利解决计数
      successCount = 0;
      // 失败解决计数
      failCount = 0;
    
      /**
    * TinyPng 结构器
    * @param {*} entry 入口文件
    * @param {*} deep 是否递归
    */
      constructor(entry, deep) {console.log(USER_AGENT[Math.floor(Math.random() * 10)]);
     if (entry != undefined) {this.config.entryFolder = entry;}
     if (deep != undefined) {this.config.deepLoop = deep;}
     // 过滤传入入口目录中合乎调整的待处理文件
     this.fileFilter(this.config.entryFolder);
     Tlog.log(` 本次执行脚本的配置:`);
     Object.keys(this.config).forEach((key) => {if (key !== "files") {Tlog.log(` 配置 ${key}:${this.config[key]}`);
       }
     });
     Tlog.log(` 期待解决文件的数量:${this.config.files.length}`);
      }
    
      /**
    * 执行压缩
    */
      compress() {Tlog.log("启动图像压缩, 请稍等...");
     let asyncAll = [];
     if (this.config.files.length > 0) {this.config.files.forEach((img) => {asyncAll.push(this.fileUpload(img));
       });
       Promise.all(asyncAll)
         .then(() => {
           Tlog.log(` 处理完毕: 胜利: ${this.successCount}张, 成功率 ${this.successCount / this.config.files.length}`
           );
         })
         .catch((error) => {Tlog.error(error);
         });
     }
      }
    
      /**
    * 过滤待处理文件夹,失去待处理文件列表
    * @param {*} folder 待处理文件夹
    * @param {*} files 待处理文件列表
    */
      fileFilter(folder) {
     // 读取文件夹
     fs.readdirSync(folder).forEach((file) => {let fullFilePath = path.join(folder, file);
       // 读取文件信息
       let fileStat = fs.statSync(fullFilePath);
       // 过滤文件安全性 / 大小限度 / 后缀名
       if (
         fileStat.size <= this.config.max &&
         fileStat.size >= this.config.min &&
         fileStat.isFile() &&
         this.config.extension.includes(path.extname(file))
       ) {this.config.files.push(fullFilePath);
       }
       // 是都要深度递归解决文件夹
       else if (this.config.deepLoop && fileStat.isDirectory()) {this.fileFilter(fullFilePath);
       }
     });
      }
    
      /**
    * TinyPng 近程压缩 HTTPS 申请的配置生成办法
    */
      getAjaxOptions() {
     return {
       method: "POST",
       hostname: "tinypng.com",
       path: "/web/shrink",
       headers: {
         rejectUnauthorized: false,
         "X-Forwarded-For": Array(4)
           .fill(1)
           .map(() => parseInt(Math.random() * 254 + 1))
           .join("."),
         "Postman-Token": Date.now(),
         "Cache-Control": "no-cache",
         "Content-Type": "application/x-www-form-urlencoded",
         "User-Agent": USER_AGENT[Math.floor(Math.random() * 10)],
       },
     };
      }
    
      /**
    * TinyPng 近程压缩 HTTPS 申请
    * @param {string} img 待处理的文件
    * @success {*              "input": { "size": 887, "type": "image/png"},
    *              "output": {"size": 785, "type": "image/png", "width": 81, "height": 81, "ratio": 0.885, "url": "https://tinypng.com/web/output/7aztz90nq5p9545zch8gjzqg5ubdatd6"}
    *           }
    * @error  {"error": "Bad request", "message" : "Request is invalid"}
    */
      fileUpload(imgPath) {return new Promise((resolve) => {let req = https.request(this.getAjaxOptions(), (res) => {res.on("data", async (buf) => {let obj = JSON.parse(buf.toString());
           if (obj.error) {Tlog.log(` 压缩失败!\n 以后文件:${imgPath} \n ${obj.message}`);
           } else {resolve(await this.fileUpdate(imgPath, obj));
           }
         });
       });
       req.write(fs.readFileSync(imgPath), "binary");
       req.on("error", (e) => {Tlog.log(` 申请谬误! \n 以后文件:${imgPath} \n, ${e}`);
       });
       req.end();}).catch((error) => {Tlog.log(error);
     });
      }
    
      // 该办法被循环调用, 申请图片数据
      fileUpdate(entryImgPath, obj) {return new Promise((resolve) => {let options = new URL(obj.output.url);
       let req = https.request(options, (res) => {
         let body = "";
         res.setEncoding("binary");
         res.on("data", (data) => (body += data));
         res.on("end", () => {fs.writeFile(entryImgPath, body, "binary", (err) => {if (err) {Tlog.log(err);
             } else {
               this.successCount++;
               let message = ` 压缩胜利 : 优化比例: ${((1 - obj.output.ratio) *
                 100
               ).toFixed(2)}%,原始大小: ${(obj.input.size / 1024).toFixed(2)}KB,压缩大小: ${(obj.output.size / 1024).toFixed(2)}KB,文件:${entryImgPath}`;
               Tlog.log(message);
               resolve(message);
             }
           });
         });
       });
       req.on("error", (e) => {Tlog.log(e);
       });
       req.end();}).catch((error) => {Tlog.log(error);
     });
      }
    }
    
    module.exports = TinyPng;
    
    入口脚本:
    /**
     * 因网络起因和第三方接口防刷等技术限度导致局部图像处理失败
     */
    const TinyPng = require("./tinypng.compress.img");
    
    function getEntryPath() {let i = process.argv.findIndex((i) => i === "-p");
      if (process.argv[i + 1]) {return process.argv[i + 1];
      }
    }
    new TinyPng(getEntryPath(), true).compress();
    执行演示:

    日志记录:

    结语:

    程序员还是要将反复的工作简单化,几年前就靠这份脚本将 150 多 M 的前端我的项目压到了 20~30 兆,你会想着说怎么还能有这样的我的项目,你们我的项目很大么?说实话就是不标准导致的,多年积攒的文件你要一张张去解决你感觉靠谱么,你刚压缩完其余共事又提交了一堆大图片怎么办,那么最好将脚本改一下再退出到编译时的插件中,完满!


欢送关注我的公众号“前端小鑫同学”,原创技术文章第一工夫推送。

退出移动版