用 nodejs 写一个 cocos2dx-js 游戏自动更新版本的脚本

   接触 nodejs 有一段时间了,感叹于 nodejs 的强大和方便。最近看到别人写的自动化脚本很方便,于是自己也想用 nodejs 写一个 cocos2dx 游戏更新版本的自动化脚本。确实能节约不少时间,还能管理 N 个服务器,也不会出错,人工确实太麻烦了,又容易出错。
    废话不多说,进入正题,我们的游戏客户端放在本地的 svn 上面,于是更新一个游戏版本包含以下步骤:
    1. svn 更新 src 和 res 到最新版
    2. 比较最新版本和第一次打包的版本进行对比,看看有哪些文件是增加的或修改过的,把这些文件全部 check 出来
    3. 把 check 出来的 src 和 res 打个包 src.zip 和 res.zip    
    4. 修改 project.manifest 和 version.manifest 中的版本号
    5. 把 src.zip、res.zip、project.manifest、version.manifest 上传到指定的 ftp 服务器上。
    
    依次来说说关键步骤相关的 nodejs 怎么弄。本人用的是 mac os 系统,因为 mac os 本身集成了 svn 命令,用起来很爽。

    一、nodejs 执行 shell 脚本
    为什么要执行 shell 脚本,因为 svn 的命令 mac os 本身有集成,所以只要用 nodejs 调用 shell 脚本就阔以了。
     执行 shell 脚本的代码也是从网上找来的,增加稍微封装了下:
var spawn = require('child_process').spawn;

/**
 * 调用 shell 脚本
 * @param cmd 命令名字 比如 svnadmin
 * @param params 参数数组
 * @param exitBack 执行完毕回调
 * @param exitThisObj
 * @param stdoutBack 输出回调
 * @param stdoutThisObj
 * @constructor
 */
var ShellCommand = function (cmd, params, exitBack, exitThisObj, stdoutBack, stdoutThisObj,stderrBack,stderrThisObj) {
    free = spawn(cmd, params);

    // 捕获标准输出并将其打印到控制台
    free.stdout.on('data', function (data) {
        if (stdoutBack) {
            stdoutBack.apply(stdoutThisObj, [data.toString()]);
        }
        //console.log('shell: ' + data);
    });

    // 捕获标准错误输出并将其打印到控制台
    free.stderr.on('data', function (data) {
        if (stderrBack) {
            stderrBack.apply(stderrThisObj, [data.toString()]);
        }
    });

    // 注册子进程关闭事件
    free.on('exit', function (code, signal) {
        if(exitBack) {
            exitBack.apply(exitThisObj, [code]);
        }
        //console.log('shell eixt ,exit: ' + code);
    });
}

global.ShellCommand = ShellCommand;

二、svn 的命令  
       mac 上面 svn 的命令是参考的 http://blog.csdn.net/itianyi/article/details/8981989       所以写个 SVNShell 的脚本封装下就阔以,比如:
var SVNShell = function (svnurl, localdir, user, password) {
    if (localdir.charAt(localdir.length - 1) != "/") {
        localdir = localdir + "/";
    }
    this.svnurl = svnurl;
    this.localdir = localdir;
    var svnpath = "svndir/";
    this.localsvndir = this.localdir + svnpath;
    this.user = user;
    this.password = password;
    this.serverStart = false;
    this.projectdir = this.localdir + svnpath;
    this.lastVersion = 0;
}

SVNShell.prototype.getReady = function (complete, thisObj) {
    this.readyComplete = complete;
    this.readyThis = thisObj;
    if (!this.isExist()) {
        this.createLocalSVN(this.getReadyCreateSVNComplete, this);
    } else {
        this.getReadyCreateSVNComplete();
    }
}

SVNShell.prototype.getReadyCreateSVNComplete = function () {
    this.startSVNServer();
    if (!this.hasCheckOut()) {
        this.checkOut(this.getReadyCheckOut, this);
    } else {
        this.getReadyCheckOut();
    }
}

SVNShell.prototype.getReadyCheckOut = function () {
    if (this.readyComplete) {
        this.readyComplete.apply(this.readyThis);
        this.readyComplete = null;
        this.readyThis = null;
    }
}

/**
 * 本地的 svn 目录是否存在
 */
SVNShell.prototype.isExist = function () {
    var file = new File(this.localdir + "conf/svnserve.conf");
    return file.isExist();
}

/**
 * 不存在则创建
 */
SVNShell.prototype.createLocalSVN = function (complete, thisObj) {
    var _this = this;
    new ShellCommand("svnadmin", ["create", this.localdir], function () {
        _this.createLocalSVNComplete();
        if (complete) {
            complete.apply(thisObj);
        }
    });
}

/**
 * 创建本地 svn 完成后
 */
SVNShell.prototype.createLocalSVNComplete = function () {
    //console.log("创建本地 svn 目录完毕," + this.localdir + " -> " + this.svnurl);
    //console.log("初始化 svn 配置");

    //console.log("修改 conf/svnserve.conf");
    var file = new File(this.localdir + "conf/svnserve.conf");
    var content = file.readContent();
    var index, str;
    var strs = ["# anon-access = read", "# auth-access = write", "# password-db = passwd", "# authz-db = authz"];
    for (var i = 0; i < strs.length; i++) {
        str = strs[i];
        var index = StringDo.findString(content, str, 0);
        content = content.slice(0, index) + str.slice(2, str.length) + content.slice(index + str.length, content.length);
    }
    file.save(content);

    //console.log("写入用户名信息 conf/passwd");
    file = new File(this.localdir + "conf/passwd");
    content = file.readContent();
    content += "\n" + this.user + "=" + this.password;
    file.save(content);

    //console.log("修改用户权限 conf/authz");
    file = new File(this.localdir + "conf/authz");
    content = file.readContent();
    content += "\n[/]\n" + this.user + "=rw";
    file.save(content);
}

/**
 * 启动 svn 服务器
 */
SVNShell.prototype.startSVNServer = function () {
    if (this.serverStart == false) {
        new ShellCommand("svnserve", ["-d", "-r", this.localdir], function () {
            this.serverStart = true;
        }, null);
    } else {
    }
}

/**
 * 是否 checkOut 过
 */
SVNShell.prototype.hasCheckOut = function () {
    var file = new File(this.projectdir);
    return file.isExist();
}

/**
 * checkOut 目录
 */
SVNShell.prototype.checkOut = function (complete, thisObj) {
    new ShellCommand("svn", ["checkout", this.svnurl,
        "--username=" + this.user, "--password=" + this.password, this.projectdir], function () {
        //console.log("check out complete !");
        if (complete) {
            complete.apply(thisObj);
        }
    }, null);
}

/**
 * 更新版本
 * @param complete
 * @param thisObj
 */
SVNShell.prototype.update = function (complete, thisObj) {
    var content = "";
    var _this = this;
    new ShellCommand("svn", ["update", this.projectdir], function () {
        var start = StringDo.findString(content, "revision ", 0) + "revision ".length;
        var end = StringDo.findString(content, ".", start);
        _this.lastVersion = parseInt(content.slice(start, end));
        if (complete) {
            complete.apply(thisObj);
        }
    }, null, function (data) {
        content += data;
    }, null);
}

/**
 * 检查版本差异
 * @param v1
 * @param v2
 * @param complete
 * @param thisObj
 * @return Array<SVNDifference>
 */
SVNShell.prototype.checkVersionDifference = function (v1, v2, complete, thisObj) {
    var diffs = [];
    var content = "";
    var _this = this;
    new ShellCommand("svn", ["diff", "-r", v1 + ":" + v2, this.projectdir], function () {
        diffs = SVNDifference.changeStringToDifferences(content, _this);
        if (complete) {
            complete.apply(thisObj, [diffs]);
        }
    }, null, function (data) {
        content += data;
    });
}

global.SVNShell = SVNShell;

其中用于比较 svn 版本差异的分析是自己找的规律,可能不准确:
var SVNDifference = function (url, lastVersion, currentVersion, svn) {
    this.url = url;
    this.relativeurl = url.slice(svn.localsvndir.length, url.length);
    this.lastVersion = lastVersion;
    this.currentVersion = currentVersion;
    this.type = null;
}

SVNDifference.prototype.delete = function () {
    this.type = SVNDifferenceType.DEL;
}

SVNDifference.prototype.add = function () {
    this.type = SVNDifferenceType.ADD;
}

SVNDifference.prototype.modify = function () {
    this.type = SVNDifferenceType.MODIFY;
}

/**
 *
 Index: svnsrc/svndir/CCC.txt
 ===================================================================
 --- svnsrc/svndir/CCC.txt       (revision 1331)
 +++ svnsrc/svndir/CCC.txt       (revision 1332)
 @@ -1 +0,0 @@
 -cccc
 \ No newline at end of file


 Index: svnsrc/svndir/BBB.txt
 ===================================================================
 --- svnsrc/svndir/BBB.txt       (revision 1331)
 +++ svnsrc/svndir/BBB.txt       (revision 1332)
 @@ -1 +1 @@
 -111
 \ No newline at end of file
 +bbbb
 \ No newline at end of file


 Index: svnsrc/svndir/DDD.txt
 ===================================================================
 --- svnsrc/svndir/DDD.txt       (revision 0)
 +++ svnsrc/svndir/DDD.txt       (revision 1332)
 @@ -0,0 +1 @@
 +ddddd
 \ No newline at end of file

 Property changes on: svnsrc/svndir/DDD.txt
 ___________________________________________________________________
 Added: svn:eol-style
 ## -0,0 +1 ##
 +native
 \ No newline at end of property
 * @param content
 */
SVNDifference.changeStringToDifferences = function (content, svn) {
    var list = [];
    while (StringDo.findString(content, "Index: ", 0) != -1) {
        //处理 Index: svnsrc/svndir/DDD.txt
        var index = 0;
        var start = StringDo.findString(content, "Index: ", index) + "Index: ".length;
        var end = StringDo.findString(content, "\n", start);
        var url = content.slice(start, end);
        content = content.slice(end + 1, content.length);


        //跳过===================================================================
        end = StringDo.findString(content, "\n", index);
        content = content.slice(end + 1, content.length);


        //分析 --- svnsrc/svndir/DDD.txt       (revision 0)
        //    +++ svnsrc/svndir/DDD.txt       (revision 1332)
        start = StringDo.findString(content, "(revision ", index);
        end = StringDo.findString(content, ")", index);
        var lastVersion = parseInt(content.slice(start + "(revision ".length, end));
        //跳过 --- svnsrc/svndir/DDD.txt       (revision 0)
        end = StringDo.findString(content, "\n", index);
        content = content.slice(end + 1, content.length);

        start = StringDo.findString(content, "(revision ", index);
        end = StringDo.findString(content, ")", index);
        var currentVersion = parseInt(content.slice(start + "(revision ".length, end));
        //跳过 +++ svnsrc/svndir/DDD.txt       (revision 1332)
        end = StringDo.findString(content, "\n", index);
        content = content.slice(end + 1, content.length);

        var diff = new SVNDifference(url, lastVersion, currentVersion,svn);
        list.push(diff);
        //分析 @@ -1 +0,0 @@ 删除
        //分析 @@ -1 +1 @@ 修改
        //分析 @@ -0,0 +1 @@ 增加
        start = StringDo.findString(content, "@@ ", index) + "@@ ".length;
        end = StringDo.findString(content, " @@", start);
        var linestr = content.slice(start, end);
        var lastChar = linestr.split(",")[linestr.split(",").length - 1];
        if (lastChar == "0") { //删除
            diff.delete();
            //console.log("删除");
        } else if (lastVersion == 0) { //添加
            diff.add();
            //console.log("添加");
        } else { //修改
            diff.modify();
            //console.log("修改");
        }
        //跳过 @@ ... @@
        end = StringDo.findString(content, "\n", index);
        content = content.slice(end + 1, content.length);
    }
    return list;
}

var SVNDifferenceType = {
    "ADD": "add", //增加
    "DEL": "del", //删除
    "MODIFY": "modify" //修改
};

global.SVNDifference = SVNDifference;
global.SVNDifferenceType = SVNDifferenceType;

三、上传 ftp  
     这部分参考的哪个文章忘记,前段时间测试过,稍微封装了下:
var path = require('path');
var fs = require('fs');
var Client = require('ftp');
var Promise = require('bluebird');

/**
 * 初始化 ftp 客户端
 * @param host ip 地址
 * @param user 用户名
 * @param password 密码
 * @constructor
 */
global.FTP = function (host, user, password) {
    this.client = null;
    this.host = host;
    this.user = user;
    this.password = password;
    this.isconnect = false;
}

global.__define(global.FTP.prototype, "hasConnect"
    , function () {
        return this.isconnect;
    }
    , function (val) {
    }
);

global.FTP.prototype.connect = function (connectBack, thisObj, args) {
    if (!this.client) {
        this.client = new Client();
        var _this = this;
        this.client.on("ready", function () {
            _this.isconnect = true;
            if (connectBack) {
                connectBack.apply(thisObj, args);
            }
        });
    }
    this.client.connect({
        host: this.host,
        user: this.user,
        password: this.password
    });
}

/**
 * 上传文件
 * @param file 本地文件
 * @param ftpurl ftp 上的目录
 * @param complete 完成回调
 * @param thisObj 完成回调 this 指针
 */
global.FTP.prototype.upload = function (file, ftpurl, complete, thisObj) {
    if (!this.isconnect) {
        this.connect(this.upload, this, arguments);
        return;
    }
    var client = this.client;
    var _this = this;
    client.put(file, ftpurl, function (err) {
        _this.close();
        if (err) {
            console.log(err);
            throw err;
        } else {
            if (complete) {
                complete.apply(thisObj);
            }
        }
    });
}

global.FTP.prototype.close = function () {
    this.client.end();
    this.client = null;
    this.isconnect = false;
}

四 自己的版本更新逻辑封装 有了上面这些东西,再写自己的逻辑就阔以了,比如写个配置文件管理所有的游戏服务器配置,这样以后只要配置一下每个服务器,然后一个命令就更新好所有的服务器了:
{
  "desc": "版本更新配置",
  "list": [
    { //一个 Object 相当于一个服务器,比如外网单独配一个Object,内网单独配一个Object
      "name": "out-net",//名称
      "svn": {
        "url": "https://192.168.1.253/svn/paike/paike_client/ParkerEmpire/", //svn地址
        "user": "limengjie", //svn 用户名
        "password": "lmj111111" //svn 密码
      },
      "ftp": {
        "ip": "121.40.18.57", //上传的 ftp IP
        "user": "nihaowalk", //ftp 用户名
        "password": "walkwest-98", //ftp 密码
        "direction":"ParkerEmpire/" //上传到哪个目录
      },
      "src": {
        "init": 1033, //src 目录的初始化 svn 版本号
        "last": 1055 //服务器上的最新的版本
      },
      "res": {
        "init": 1033,
        "last": 1033
      },
      "version": { //版本信息
        "last": "1.0.0" //最新版本号
      }
    }
  ]
}

五 zip  
     zip 也是用的系统命令,没有下 nodejs zip 相关的模块,用系统的就很方便了,其实我试着下了 zip 的类库,但是没用起来,git 上的代码不全,也是醉了,后面灵机一动,系统应该有命令,也是很快就搞好了。           
     
     六 总结      
     nodejs 确实强大,不过有时候用别人的类库可能很麻烦,如果系统命令有集成这些功能何不直接执行系统命令呢,很容易就搞定了,总之灵活运用可用资源,没必要一味死磕:)
 
     所有代码已上传到个人的 github 仓库: https://github.com/mengjieli/nodejstools
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值