独书先生 Menu

Node ReadStream和WriteStream finish/close/end顺序

const reader = fs.createReadStream(file.path);  // 创建可读流


const upStream = fs.createWriteStream(resultPath);      // 创建可写流
reader.pipe(upStream);


upStream.on('finish',()=>{
    console.info(' upStream finish!!')

});

upStream.on('close',()=>{
    console.info(' upStream close!!')

});
reader.on('close',()=>{
    console.info(' reader close!!')

});
reader.on('end',()=>{
    console.info(' reader end!!')

});

输出:// reader end!!
// upStream finish!!
// reader close!!
// upStream close!!

验证码服务 2Captcha 测评 2022 | 原理 用途 入门

原文:https://lwebapp.com/zh/post/2captcha-review

背景

大家对网页验证码肯定不陌生,几乎所有带有登陆功能的网页或者 APP,都有验证码功能。

验证码不一定每一次登陆都会出现,但是当你登陆多次失败的时候就会让你输入验证码问题答案了,主要是为了防止你恶意登陆其他人的账户。或者有的网站更严格,每一次都会弹出验证码窗口,这种验证码通常为了防止机器人登陆,就像以前的 12306 火车票网站,每次买票的时候都会弹出验证码,而且很难校验成功,确实把脚本拦住了,但是把我们正常用户也拦住了。

Continue reading…

Nodejs Playwright 2Captcha 验证码识别实现自动登陆

原文:https://lwebapp.com/zh/post/bypass-captcha

需求

日常工作当中,为了提高工作效率,我们可能会写脚本来自动执行任务。有些网站因为需要用户登陆,所以脚本的自动登陆功能必不可少。

不过我们在登陆网站的时候经常会出现验证码,验证码的目的就是为了防止机器登陆、自动化脚本操作,那么有没有办法让脚本能自动识别验证码实现登陆呢?

接下来我以 B 站为例给大家讲解下,如何解决自动登陆脚本中最关键的验证码问题。

Continue reading…

自建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