前端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 就能自动压缩图片了,并且不会有数量限制!

谈谈如何绕过 TinyPNG 对上传图片数量的限制的更多相关文章

  1. 微信小程序 - 上传图片(组件)

    更新日期: 2019/3/14:首次发布,更新了2018/12/30的UI以及反馈信息获取方式,具体请下载:demo. 2019/3/20:感谢544429676@qq.com指出的现存bug,已修复 ...

  2. 技术分享:杂谈如何绕过WAF(Web应用防火墙)(转)

    0×01开场白 这个议题呢,主要是教大家一个思路,而不是把现成准备好的代码放给大家. 可能在大家眼中WAF(Web应用防火墙)就是"不要脸"的代名词.如果没有他,我们的" ...

  3. angular批量上传图片并进行校验

    最近做一个项目,有一个批量上传图片,并校验是否是图片和上传最大数量的需求.下面分享一下我的想法,欢迎大神探讨. 首先是在html中支持批量上传和接收图片(mac下设置了之后不存在类型校验),下面上ht ...

  4. shopnc2014年11版数据库字典

    shopnc_activity 表注释: 活动表 字段 类型 空 默认 注释 activity_id mediumint(9) 否   id activity_title varchar(255) 否 ...

  5. 【项目相关】MVC中将WebUploader进行封装

    昨天初步将webuploader用在了项目中,但紧接着就发现了一个问题,每一个页面都需要写那么多初始化webUploader的代码,这样肯定是我不能接受的,于是花了半天时间对该控件进行了封装. 最后, ...

  6. angularjs中使用 <input type="file">标签实现一次最多上传5张图片

    前期准备: 1.angular.js 2.bootstrap.css 具体如何实现呢?请看下面代码哈哈哈. 在angular项目中,如果要给<input type="file" ...

  7. 上传漏洞总结-upload-labs

    介绍: Upload-labs是一个所有类型的上传漏洞的靶场 项目地址:https://github.com/c0ny1/upload-labs 思维导图: 小试牛刀: Pass-01 客户端js检查 ...

  8. asp.net mvc 多文件上传

    @{ ViewBag.Title = "多文件上传测试"; } <h2>多文件上传测试</h2> <form action="/Demo/I ...

  9. TP图片上传

    //控制器文件 public function index(){ if(!empty($_POST)){ $file = $_FILES["file"]; if(!isset($f ...

随机推荐

  1. 使用Python代码制作GIF动态图

    使用Python  PIL.Image 制作GIF图片: import  PIL.Image                      相关模块 img = Image.open(img_name)  ...

  2. Java实现 LeetCode 290 单词规律

    290. 单词规律 给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律. 这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非 ...

  3. Java实现 LeetCode 119 杨辉三角 II

    119. 杨辉三角 II 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行. 在杨辉三角中,每个数是它左上方和右上方的数的和. 示例: 输入: 3 输出: [1,3,3,1] 进阶: ...

  4. Java实现 LeetCode 65 有效数字

    65. 有效数字 验证给定的字符串是否可以解释为十进制数字. 例如: "0" => true " 0.1 " => true "abc&q ...

  5. 第七届蓝桥杯JavaA组国(决)赛部分真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.阶乘位数 阶乘位数 9的阶乘等于:362880 它的二进制表示为:1011000100110000000 这个数字共有19位. 请你计 ...

  6. java实现逻辑推断

    A.B.C.D.E.F.G.H.I.J 共10名学生有可能参加本次计算机竞赛,也可能不参加.因为某种原因,他们是否参赛受到下列条件的约束: 如果A参加,B也参加: 如果C不参加,D也不参加: A和C中 ...

  7. Spring Data Jpa Specification 调用Oracle 函数/方法

    开发框架用的Jpa,数据库是 Oracle. 在开发中难免会遇到需要数据库字段是字符串格式,但是又需要对其进行范围查询(数据库设计问题,后续应避免).那么问题来了, Jpa Specification ...

  8. pi-star 升级固件命令

    单天线热点: sudo pistar-mmdvmhshatflash hs_hat 双天线热点: sudo pistar-mmdvmhshatflash hs_dual_hat 命令: wget ht ...

  9. ASP.NET Core Blazor WebAssembly实现一个简单的TODO List

    基于blazor实现的一个简单的TODO List 最近看到一些大佬都开始关注blazor,我也想学习一下.做了一个小的demo,todolist,仅是一个小示例,参考此vue项目的实现http:// ...

  10. 大型Electron应用本地数据库技术选型

    开发一个大型Electron的应用,或许需要在客户端存储大量的数据,比如聊天应用或邮件客户端 可选的客户端数据库方案看似很多,但一一对比下来,最优解只有一个 接下来我们就一起来经历一下这个技术选型的过 ...