JavaScript我爱编程

【Node.js】基于EventProxy模块的顺序异步调用

2017-07-26  本文已影响100人  孙朗斌

【Node.js】基于EventProxy模块的顺序异步调用

[TOC]

Tag:Node.jsWebStormBmobEventProxy

背景

最近2个多月,由于笔者的老手机iPhone 6 plus 刷了iOS11的测试版,在公司的网络下,不挂VPN基本无法正常打开网址、图片、超链等。笔者还穷酸,不舍得花钱买收费的VPN,偶然的机会遇到一个好心人架设的免费VPN。每日签到送流量。对于我这种低流量用户来说,够用了。为了保证账户的激活状态,加上偏执症发作,每日签到。自己写了一个简单的iOS应用,一键签到领流量。无奈舍不得花钱购买开发者账号,所以在测试机模式下,居然要每7天安装一次。又是懒癌发作,动手自己写一个循环定时签到的服务吧。

本来昨天和今天打算用Python写一个每日签到领流量的小定时器服务。结果折腾了一天,发现笔者电脑上的Python可能是由于版本的问题,无法通过网站的SSL验证。无奈之下,再次祭起Node.js和Bmob。又是折腾了一天,期间犯了很多低级错误。

流程

云逻辑及云数据表

https://bmob.cn/

笔者很喜欢的一个服务平台Bmob,收费后,免费版功能大减,但是偶尔写写小东西还是不错的,也够用。

这里也不多说了,挺简单的。创建一个应用,在新应用中左面的工具栏选择云逻辑(基于Node.js,但是封装了自己的一部分模块,并且不太支持第三方其他模块)。添加一个方法icafe

由于密码是以明文存在的,所以笔者不想直接在代码中暴露用户名和密码,因此先把用户名和密码存在Bmob上云数据库中。

创建一张云数据库表app_icafe_info,新添加两列emailpasswd,分别用于存放登录邮箱和密码。请读者将自己的icafe用户名和密码存在这张表里。

数据库读取

此部分的云逻辑代码如下:

//往http://bmob.cn/save发起POST请求
db.findOne({
   "table":"app_icafe_info",             //表名
   "objectId":"这里替换掉主键"         //记录的objectId
   },function(err,dbdata){//回调函数
        if (!err) {
            var dataObject= JSON.parse(dbdata);
        }           
});

因为这条数据记录是人工录入进去的,并且数据表中也只有这一条,所以使用的是findOne方法。如果有其他的请自行查看Bmob的开发文档。

爱咖啡这个网站,签到需要两步。先登录,再签到。

登录API

https://icafe.tech/login

就是这个地方,由于笔者的Python问题,无法验证https,因此更换的Bmob的nodejs。

需要传参用户名和密码

{
  "email": "此处填写登录邮箱", 
  "passwd": "此处填写登录密码"
}

由于密码传输未做任何加密或者编码,是明文。所以建议用POST一个form

此部分的云逻辑代码如下:

http.post('https://icafe.tech/login', {form:{'email': dataObject.email, 'passwd': dataObject.passwd}},      function (error, loginres, loginbody) {
            if (!error && loginres.statusCode == 200) {
              
            }
     });

可补充一下Node.js的代码实现:

// request Request 
(function(callback) {
    'use strict';
        
    const httpTransport = require('https');
    const responseEncoding = 'utf8';
    const httpOptions = {
        hostname: 'icafe.tech',
        port: '443',
        path: '/login',
        method: 'POST',
        headers: {"Content-Type":"application/x-www-form-urlencoded; charset=utf-8"}
    };
    httpOptions.headers['User-Agent'] = 'node ' + process.version;
 
    // Paw Store Cookies option is not supported

    const request = httpTransport.request(httpOptions, (res) => {
        let responseBufs = [];
        let responseStr = '';
        
        res.on('data', (chunk) => {
            if (Buffer.isBuffer(chunk)) {
                responseBufs.push(chunk);
            }
            else {
                responseStr = responseStr + chunk;            
            }
        }).on('end', () => {
            responseStr = responseBufs.length > 0 ? 
                Buffer.concat(responseBufs).toString(responseEncoding) : responseStr;
            
            callback(null, res.statusCode, res.headers, responseStr);
        });
        
    })
    .setTimeout(0)
    .on('error', (error) => {
        callback(error);
    });
    request.write("email=这里替换登录用户名&passwd=这里替换密码")
    request.end();
    

})((error, statusCode, headers, body) => {
    console.log('ERROR:', error); 
    console.log('STATUS:', statusCode);
    console.log('HEADERS:', JSON.stringify(headers));
    console.log('BODY:', body);
});

在登录成功之后,即response为一下之后,才可以进行签到操作。

{
  "code": 0,
  "msg": "请求成功",
  "data": "登录成功!"
}

如果用户名密码错误的话,则会收到一下response

{
  "code": 1,
  "msg": "用户密码错误!",
  "data": []
}

签到API

https://icafe.tech/checkIn

此部分的云逻辑代码如下:

http.post('https://icafe.tech/checkIn', function (error, checkInres, checkInbody) {
       if (!error && checkInres.statusCode == 200) {
            var str = JSON.parse(checkInbody);
            var regs = str.msg.match('\\d+\\.?\\d*');   
            var flow_capacity = regs instanceof Array?regs[0]:"0"              
       }
});

可补充一下Node.js的代码实现:

// request Request 
(function(callback) {
    'use strict';
        
    const httpTransport = require('https');
    const responseEncoding = 'utf8';
    const httpOptions = {
        hostname: 'icafe.tech',
        port: '443',
        path: '/checkIn',
        method: 'POST'
    };
    httpOptions.headers['User-Agent'] = 'node ' + process.version;
 
    // Paw Store Cookies option is not supported

    const request = httpTransport.request(httpOptions, (res) => {
        let responseBufs = [];
        let responseStr = '';
        
        res.on('data', (chunk) => {
            if (Buffer.isBuffer(chunk)) {
                responseBufs.push(chunk);
            }
            else {
                responseStr = responseStr + chunk;            
            }
        }).on('end', () => {
            responseStr = responseBufs.length > 0 ? 
                Buffer.concat(responseBufs).toString(responseEncoding) : responseStr;
            
            callback(null, res.statusCode, res.headers, responseStr);
        });
        
    })
    .setTimeout(0)
    .on('error', (error) => {
        callback(error);
    });
    request.write("")
    request.end();
    

})((error, statusCode, headers, body) => {
    console.log('ERROR:', error); 
    console.log('STATUS:', statusCode);
    console.log('HEADERS:', JSON.stringify(headers));
    console.log('BODY:', body);
});

成功的话,会收到如下response

{
  "code": 0,
  "msg": "恭喜您获得 702MB 流量",
  "data": []
}

如果每天重复签到的话,会收到如下response

{
  "code": 1,
  "msg": "您今天已经签过到了,明天再来吧~",
  "data": []
}

数据库存储

笔者希望将每日签到的结果存入数据表中备查。同时,也将签到得到的流量分离出来,将来可以用于统计分析。Bmob用代码进行数据表存储,可以不先创建数据表。代码是执行时会自动进行查找判断,如果存在表,则进行记录操作;如果表不存在,则按照代码中的数据记录对象(json)自动生成对应的表结构,再进行相应的操作。

此部分的云逻辑代码如下:

db.insert({
   "table":"app_icafe_records",             //表名
   "data":{"msg":str.msg,"flow_capacity":str.flow_capacity}            //需要更新的数据,格式为JSON
   },function(err,data){
         response.send(data  + err);//显示结果
});

因此自动生成的表结构如下图所示:

那么问题来了,这里需要四次操作才可以完成一次签到操作。

  1. 从数据库中读取用户名和密码;
  2. 使用用户名和密码进行登录验证;
  3. 调用签到API;
  4. 签到成功后,将结果存入数据库中。

在nodejs中,四者都是异步操作,并且是一环套一环的有先后顺序。所以需要进行深度callback嵌套……笔者开始纠结了。

因此,

解决回调嵌套

笔者在网络中偶然查找到下面的EventProxy,据说正是用于解决回调嵌套的。

https://github.com/JacksonTian/eventproxy

EventProxy 仅仅是一个很轻量的工具,但是能够带来一种事件式编程的思维变化。有几个特点:

  1. 利用事件机制解耦复杂业务逻辑
  2. 移除被广为诟病的深度callback嵌套问题
  3. 将串行等待变成并行等待,提升多异步协作场景下的执行效率
  4. 友好的Error handling
  5. 无平台依赖,适合前后端,能用于浏览器和Node.js
  6. 兼容CMD,AMD以及CommonJS模块环境

此处主要使用了allemit两个方法。

使用方法和其他API请自行查阅官方的API和开发文档。

全部代码

下面是全部云逻辑代码:

function onRequest(request, response, modules) {

    /**
    *发起Post请求
    */
    //获取Http模块
    var http = modules.oHttp.defaults({jar: true});
    //eventproxy模块,解决异步回调的问题
    var ep = modules.oEvent;  
      //获取数据库对象
    var db = modules.oData;
    
    ep.all('step3 checkin',function(str){  //任务3完成时执行
        db.insert({
            "table":"app_icafe_records",             //表名
            "data":{"msg":str.msg,"flow_capacity":str.flow_capacity}            //需要更新的数据,格式为JSON
            },function(err,data){
                response.send(data  + err);//显示结果
        });
    })
    
    
    ep.all('step2 login',function(){  //任务2完成时执行
        http.post('https://icafe.tech/checkIn', function (error, checkInres, checkInbody) {
            if (!error && checkInres.statusCode == 200) {
                var str = JSON.parse(checkInbody);
                var regs = str.msg.match('\\d+\\.?\\d*');   //使用正则表达式来获取中文字符串中的数字
                str.flow_capacity= regs instanceof Array?regs[0]:"0" // 重复签到时会在数据表中的流量列写入0
                ep.emit('step3 checkin',str);
            }
        });
    });
    
            
    ep.all('step1 db',function(dataObject){   //任务1完成时执行
        http.post('https://icafe.tech/login', {form:{'email': dataObject.email, 'passwd': dataObject.passwd}}, function (error, loginres, loginbody) {
            if (!error && loginres.statusCode == 200) {
                ep.emit('step2 login');  //异步任务2完成
            }
        });
        
    });
    
    //往http://bmob.cn/save发起POST请求
    db.findOne({
        "table":"app_icafe_info",             //表名
        "objectId":"8tXE2225"         //记录的objectId
        },function(err,dbdata){//回调函数
        if (!err) {
            var dataObject= JSON.parse(dbdata);
            ep.emit('step1 db',dataObject);  //异步任务1完成
        }
    });
 }                                                                                    

设置定时任务

具体设置方式如下图所示:

0 0 9 * * *表示每天的9时0分0秒,触发定时器一次。

基本上就是这些了。文中未尽事宜,读者如有疑问,可以留言或者发邮件。笔者尽能力一一解答。

完结撒花。

以上。

上一篇 下一篇

猜你喜欢

热点阅读