前端er, 又称为切图仔,平时经常需要用 PSD 导出 PNG 或 JPG,但是导出来的的图片一般比较大,往往需要用一些其他工具压缩后再发布到生产环境。

以前常用的做法是,使用 image-webpack-loader ,在 webpack 打包项目时自动压缩图片。但是这 loader 是基于 imagemin 的,他的压缩失真比较严重,而且压缩率也不是很高。经常出现一种情况就是,在本地开发的时候,图片明明很清晰的,但是一旦发布到生产环境 ,因为经过了 img-webapck-loader 压缩,导致图片严重失真。也因为这个原因,美术小姐姐在验收项目的时候,常常会把我叫过去,说:哎呀,我的 PSD 明明是很清晰的呀,为什么你放到网站上去就那么模糊了呢。我说因为压缩过了呀。然后她说这个压缩工具不给力呀,就推荐了一个在线压缩工具给我: TinyPNG在线地址

初体验 tinypng 真是把我惊呆了, 一般的 PNG 压缩率竟然可以高达 50% - 70% ,并且肉眼看不出来任何的失真。

TinyPNG 的原理是将 PNG24 位真彩色图片压缩成 PNG8 位索引图片,从而做到基本不损失画质和观感。至于具体算法怎么实现的也没有深入研究守,有兴趣的可以自行查阅相关资料。

tinypg 除了可以通过网页端上传图片,也提供了 api 的方式 ,所以可以使用 node 或其他脚本语言进行上传的,可以参考这里:

https://tinify.cn/developers/reference

然而,这么好用的压缩工具有一个很明显缺陷的,就是每天只能正常压缩 20 张图片。超过 20 张之后,就会经常出来 Too many files uploaded at once。如下红色的部分:

当然,这是针对免费版用户而言的。如果想要避免这个限制,你需要花 25 美金买一年的会员。当然,我们程序员大多数都是很吝啬的,就连到外面吃饭要不要加个卤蛋都要纠结半个小时的,更别说给 25 美金他了。

但是,程序员的特点是,都爱折腾!所以我决定要尝试着绕过这 20 张图片的上传限制。一般来说,这种免登录就可试用的系统,都是通过用户IP来限制用户的操作次数的,所以就决定从修改 IP 的方式来绕开这个限制。

最简单的作法是使用动态的代理 IP, 比如这里 https://ip.ihuan.me/ 就有一些免费代理IP,但是,这些 IP 都是国外的,很慢的,我们的目的是要上传并压缩图片,使用这么慢的代理显然是不可行的。

所以就只能寻找其他的办法。

就在陷入迷茫的时候,突然想到了一个事实 :目前的 web 架构大多数都是通过 nginx 作为反向代理的,如下:

从上图可以看出,客户端并不是直接请求应用服务的,而是通过统一接入层(往往是 nginx) 来转发请求的。那么问题来,应用服务是如何获取到客户端的 IP 的呢?

玩过 nginx 的同学应该都知道 , 这个通用的解决方案就是 X-Forwarded-For 请求头。

简单的来说,就是所有的反向代理都实现一个统一的约定,在转发请求给下游服务之前,把请求代理的 IP 地址写入到 X-Forwarded-For 头中,形成了一个 IP 地址列:

X-Forwarded-For: client, proxy1, proxy2

这个方案虽然不是正式的 HTTP 协议,但已经成为了一个事实标准,基本上所有的反向代理服务都实现了这个功能,以确保下游的服务可以感知到经过的反向代理,并从中获取到用户的 IP 地址。

好了,既然应用服务是通过 X-Forwarded-For 头部来获取客户端 IP 的,那么我们能不能在客户端伪造这个头部,每次上传图片的时候都设置一个随机的 IP 呢?所以就决定尝试一下。结果发现,该方法确实可行!竟然可以欺骗 tinypng 的服务器,从而绕开了上传次数的限制。

完整代码如下:

const fs = require('fs');
const path = require('path');
const https = require('https');
const { URL } = require('url'); const cwd = process.cwd(); const root = cwd;
exts = ['.jpg', '.png'],
max = 5200000; // 5MB == 5242848.754299136 const options = {
method: 'POST',
hostname: 'tinypng.com',
path: '/web/shrink',
headers: {
rejectUnauthorized: false,
'Postman-Token': Date.now(),
'Cache-Control': 'no-cache',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent':
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
}
}; fileList(root); // 生成随机IP, 赋值给 X-Forwarded-For
function getRandomIP() {
return Array.from(Array(4)).map(() => parseInt(Math.random() * 255)).join('.')
} // 获取文件列表
function fileList(folder) {
fs.readdir(folder, (err, files) => {
if (err) console.error(err);
files.forEach(file => {
fileFilter(path.join(folder, file));
});
});
} // 过滤文件格式,返回所有jpg,png图片
function fileFilter(file) {
fs.stat(file, (err, stats) => {
if (err) return console.error(err);
if (
// 必须是文件,小于5MB,后缀 jpg||png
stats.size <= max &&
stats.isFile() &&
exts.includes(path.extname(file))
) { // 通过 X-Forwarded-For 头部伪造客户端IP
options.headers['X-Forwarded-For'] = getRandomIP(); fileUpload(file); // console.log('可以压缩:' + file);
}
// if (stats.isDirectory()) fileList(file + '/');
});
} // 异步API,压缩图片
// {"error":"Bad request","message":"Request is invalid"}
// {"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" }}
function fileUpload(img) {
var req = https.request(options, function(res) {
res.on('data', buf => {
let obj = JSON.parse(buf.toString());
if (obj.error) {
console.log(`[${img}]:压缩失败!报错:${obj.message}`);
} else {
fileUpdate(img, obj);
}
});
}); req.write(fs.readFileSync(img), 'binary');
req.on('error', e => {
console.error(e);
});
req.end();
}
// 该方法被循环调用,请求图片数据
function fileUpdate(imgpath, obj) {
const outputDir = path.join(cwd , 'output');
imgpath = path.join(cwd , 'output', imgpath.replace(cwd, '')); if(!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
} let options = new URL(obj.output.url);
let req = https.request(options, res => {
let body = '';
res.setEncoding('binary');
res.on('data', function(data) {
body += data;
}); res.on('end', function() {
fs.writeFile(imgpath, body, 'binary', err => {
if (err) return console.error(err);
console.log(
`[${imgpath}] \n 压缩成功,原始大小-${obj.input.size},压缩大小-${
obj.output.size
},优化比例-${obj.output.ratio}`
);
});
});
});
req.on('error', e => {
console.error(e);
});
req.end();
}

这段代码是参考了 nodejs 全自动使用 Tinypng (免费版,无需任何配置)压缩图片 ,只是简单的加上了动态的 IP 头,从而实现了不受上传次数限制。

核心的代码在这里:

// 生成随机IP, 赋值给 X-Forwarded-For
function getRandomIP() {
return Array.from(Array(4)).map(() => parseInt(Math.random() * 255)).join('.')
} // ....
options.headers['X-Forwarded-For'] = getRandomIP();
//....

代码上传到了 github ,有兴趣的可以点击下面连接查看并 star 关注 喔:

super-tinypng

同时,也发布到了 npm 上面。只需要全局安装:


npm i super-tinypng -g

然后在你想要压缩图片的目录里面运行 super-tinypng 就能自动压缩图片了,并且不会有数量限制!

最新文章

  1. 使用Oracle官方巡检工具ORAchk巡检数据库
  2. 久违的phpstorm
  3. VR外包 虚拟现实外包 北京软件公司
  4. CentOS6.5源码安装python3.5.2
  5. windows 配置 apache + php
  6. 介绍开源的.net通信框架NetworkComms框架 源码分析(七)ProtobufSerializer
  7. HttpClient_httpclient 4.3.1 post get的工具类
  8. org.apache.log4j与org.apache.commons.logging这两个包有什么区别
  9. Ubuntu开机自动挂载Windows分区
  10. SQL-LINQ-Lambda 语法对照
  11. Android 自定义shape圆形按钮
  12. git切换远程
  13. Initialising Memories
  14. 用websocket实现后台推送消息
  15. 201521123007《Java程序设计》第1周学习总结
  16. presto
  17. 部署上次的Hapi到Windows+Docker,WindowsDocker
  18. bootstrap-treeview 树形菜单带复选框以及级联选择
  19. 白话TCP为什么需要进行三次握手
  20. vue-cli使用sockjs即时通信

热门文章

  1. Java实现 蓝桥杯 基础练习 查找整数
  2. Java实现 LeetCode 318 最大单词长度乘积
  3. Java实现 蓝桥杯VIP 算法提高 最长公共子序列
  4. Java实现 LeetCode 77 组合
  5. Java实现蓝桥杯历届试题填字母游戏
  6. 浅谈js运行机制
  7. 全面概述Gitee和GitHub生成/添加SSH公钥
  8. OSI七层模型及各层作用
  9. NetAnalyzer笔记 之 十四 NetAnalyzer 6.0 的使用方法 -- 3.协议分析与统计
  10. 1.react的基础