杂食性程序猿:专注于 Android,web frontend,flutter,node.js 等大前端领域,也会玩玩 python 和 java 等后端技术 ...

node.js对session的简单封装

Session是神马?
百度百科(百度最有价值的产品)上的解释是
“在计算机专业术语中,session是指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间以及如果需要的话,可能还有一定的操作空间。”
通俗的讲,就是服务器端一段记录当前访问用户的空间。客户端的则是cookie,因为http协议无状态的特性,所以session的存在则必须依托于cookie。
前段时间打算搞个NodeJS的简单框架来支持WALNUT的后台的。突然发现NodeJS神马的,连个session也没有,提供给一次请求的参数就是http. ServerResponse和http.ClientRequest实例化出来的对象。充满幻想的我顿时内牛满面 … …
只好自己动手丰衣足食了。
服务器端必须在客户端使用cookie,可以在返回的头文件中设置,使用http.ServerResponse类中的:

response.setHeader('Set-Cookie', ['SID=' + sID]);

再通过每次获得客户端传来的cookie的,取出存放在服务器端对应session的方式,则可以很简单的模拟出session的效果。
如果不存在则新建一个session,代码如下所示:

var parse = require('querystring').parse;

/**
 * @description 设置session过期时间
 */
var EXPIRE_TIME = 3 * 60 * 1000;

/**
 * @description 存放服务器端所有session
 */
var _sessions = {};

function genSID(pre) {
  pre = (pre)?pre : 'SESSION';
  var time = new Date().getTime() + '';
  var id = pre + '_' + (time).substring(time.length - 6) + '_' + (Math.round(Math.random() * 1000));
  return id;
}

/**
 * @description 定时清理过期的session
 */
setInterval(function(){
  for (var id in _sessions) {
    if (!_sessions.hasOwnProperty(id)) 
      continue;
    if (new Date() - _sessions[id].timestamp > EXPIRE_TIME)
      delete _sessions[id];
    }
}, 1000);

var createSession = function(sID) {
  var session = {
    SID: sID,
    timestamp: new Date()
  }
  return session;
}

/**
 * @description 维护了对session的引用,可进行增删查改操作
 * @param {string} sID 当前用户的session ID
 * @param {object} _sessions
 */
var context = function(_sessions, sID) {
  this.poke = function() {
    _sessions[sID].timestamp = new Date();
  };
  this.destory = function() {
    delete _sessions[sID];
  };
  this.del = function(key) {
    this.poke();
    delete _sessions[sID][key];
  }
  this.set = function(key, value) {
    this.poke();
    _sessions[sID][key] = value;
  };
  this.get = function(key) {
    this.poke();
    return _sessions[sID][key];
  };
}

/**
 * @description 开始session
 * @param {object} request 
 * @param {object} response
 * @param {function} process 回调函数
 */
exports.startSession = function(request, response, process) {
  var cookies = parse(request.headers.cookie, '; ');
  var sID;
  for (var i in cookies) {
    if (i == 'SID') {
      sID = cookies[i];
      break;
    }
  }
  if (!sID || typeof _sessions[sID] == 'undefined') {
    var sID = genSID();
    _sessions[sID] = createSession(sID);
  }
  response.setHeader('Set-Cookie', ['SID=' + sID]);
  process.call(new context(_sessions, sID), request, response);
}

测试代码,主程序:

var http = require('http');
var sessionFactory = require('./session');

var server = http.createServer(function(request, response) {
  sessionFactory.startSession(request, response, handler);
});

var handler = function(request, response) {
  var session = this;
  session.set('banana', '你个巴啦~');
  response.end(session.get('banana'));
}

server.listen('80');

OK.
写在最后:
NodeJS,语言既是服务器,服务器既是语言的,这种方式很特别。想当初,nginx,apache的设计,就是为了把应用服务器和应用给分开来。这样的设计究竟是进步了还是倒退了,嗯~ 不好说。
总而言之,其实也无所谓啦!存在即合理,适合的就是最好的。
转载请注明出处
botobe.net
本文Github链接

2011.12.6,一切安好。
Merci !

JS框架系列笔记 —— prototype.js的面向对象

本文假定你有了一定的javascript面向对象的基础了,就不过多的关于javascript面向对象基础的介绍。
首先,让我们来举个例子,做个传统意义上的javascript的继承。

/**
 * @description 新建个“人类”的类,拥有三个初始的属性,姓名,年龄,性别。
 * 在其构造方法中,我们可以对这些属性做一个初始化的工作。
 *
 * @param {string} name 
 * @param {number} age
 * @param {string} gender
 */
function Humen(name, age, gender) {
  this.age = 0 || age;
  this.name = '' || name;
  this.gender = '' || gender;
}
Humen.prototype.eat = function() {}
Humen.prototype.sleep = function() {}

/**
 * @description 新建个“超人”的类,拥有三个初始的属性,姓名,年龄,性别。
 * 在完成了和人类相同的属性的同时,他比人类还多了一个属性“power”,能量值。
 *
 * @param {string} name 
 * @param {number} age
 * @param {string} gender
 * @param {number} power
 */
function SuperMen(name, age, gender, power) {
  this.age = 0 || age;
  this.name = '' || name;
  this.gender = '' || gender;
  this.power = '' || power;
}
SuperMen.prototype = new Humen;
SuperMen.prototype.constructor = SuperMen;
SuperMen.prototype.eat = function() {}
SuperMen.prototype.fly = function() {}

OK, 初步完成了一个“人类”和一个“超人”的基本结构了,下面让我们看下这段对“人类”的描述代码,在结构上究竟有什么不足呢?
1. SuperMen在继承的过程中出现了代码的冗余,this.xxx重复的调用了。
2. 在“超人”中,同样拥有了“人类”吃的属性,但是超人可能“吃”的具体行为和“人类”不一样。“超人”需要在拥有了“人类”吃的全部属性的同时,增加一些新的特点,比如喝喝汽油,吃吃燃料啥的。在代码中的具体表现,就是突发调动父类的方法。导致了代码会冗余。
3. 结构不够优雅(好吧,这个就见仁见智了,要是你说这样的代码很漂亮,那… … 他就很漂亮吧~)

下面让我们看下prototype.js对这段代码的重新定义

var Humen = Class.create({
  initialize: function(name, age, gender) {
    this.age = age;
    this.name = name;
    this.gender = gender;
  },
  eat: function() {
    console.log('I eat normal food !');
  },
  sleep: function() {}
});

var SuperMen = Class.create(Humen, {
  initialize: function($super, name, age, gender, power) {
    $super(name, age, gender, power);
  },
  eat: function($super) {
    $super();
    console.log('I am ' + this.name + ', I still can eat gasoil, can you ?');
  }
})

var sm = new SuperMen('supermen');
sm.eat();

结果如下:
I eat normal food ! general.js:9
I am supermen, I still can eat gasoil, can you ?

嗯,让我们现在再来做个对比,看看prototype.js中OOP的继承部分的大概结构。
prototype.js定义类的方法很简单,比起原始的js用function来定义而言,引入了Class类别。具体可以看这里。
initialize方法定义了这个类的构造函数,初始化类的基本数据。这里,值得关注的是构造方法中的第一个参数$super,如果父类中有同样的方法,$super则表示父类中同名的函数。这样一来,是不是就可以调用父类的方法了呢。而不至于造成代码的冗余。

在prototype.js的类中,有两个比较特别的参数superclasses,subclasses。分别表示了当前类的所有的子类,或者是所有的父类。这样一来,在当前的类中,我们就不不止可以操作父类中的方法,而且祖宗八倍的方法都可以调用了
是不是很棒!?

prototype.js异于jQuery的最明显的区别,就在于设计的理念不一样了,jQuery崇尚的是select and do,而在Prototype.js里,体现的更多的是一切皆是对象的思想。
prototype.js胜于jQuery的一步就在于它在定义了一些工具类方法,屏蔽了浏览器差异的同时,也为灵活的js规定了一套编程的规范,代码的规整,类的封装等等。使用这套框架的开发人员都按照这样的规则去写,代码就不至于显得太凌乱。
转载请注明出处
botobe.net
本文Github链接

2012.08.13
一切安好。
Merci !

Chrome自定义Userscript脚本

随着现代浏览器的不断升级,浏览器给我们带来的已经不仅是浏览网页的体验。而是提供了各种的接口,来满足用户多样化的需求。从这种角度看,浏览器已经渐渐的在脱离它本来的初衷,而是渐渐的朝着Web平台的方向在发展。

本文我们来介绍的是Chrome自己提供的Userscript脚本的机制,用户可以在浏览器中加入自己写的脚本达到各种的需求。删除页面上牛皮癣似的广告啦,添加一些自定义的数据啦,当然还有个貌似最重要的在某神奇的车票网站上刷票啦!
这一切都可以通过添加Userscript脚本的方式来实现!!
还在等什么!?是不是突然发现无比的神奇呢?是不是觉得自己已经摆脱了屌丝的命运,俨然化身成互联网的神了呢?
哈哈,自己DIY,一切就是那么简单!

首先要创建一个以.user.js为后缀名的js脚本文件,例如创建了一个名为home.user.js的文件。
安装脚本的方式也是非常的简单,在浏览器的地址栏中输入“chrome://extensions/”,你就可以看到当前你的Chrome中所包含的扩展包。将我们的home.user.js文件拖拽入其中。

会出现

的提示,说明插件已经安装完成。
这是最新的Chrome为开发者内建了这种简单的扩展机制,非常的方便。

这只是添加了这么一个Userscript空格的脚本,基本上没什么作用。我们也无法看到任何的效果。
下边我们来往home.user.js中添加代码。

从用途上来讲,Userscript代码的结构可以分为两大部分,第一部分是Userscript的注释。用来告诉浏览器,你的这段脚本的作者,名称,日期,作用范围,版本等等一些基本的信息。第二部分则是开发者自己编写的功能代码。
让我们用一个最简单的删除Gmail顶部的牛皮癣小广告的作为例子。

头部的注释的一个简单范例如下所示:

// ==UserScript==
// @name           Just for testing Chrome APP
// @description    Yes
// @version        1.0
// @author         Kai.XU
// @namespace      http://botobe.net/
// @include        https://mail.google.com/*
// ==/UserScript==

让我们一条条的做个解释。

首先外层的

// ==UserScript==
//
// ==/UserScript==

固定格式,浏览器会读取这段注释中的基本信息。
@name@description@version@author用来记录名称,描述和当前软件的版本号以及开发者的名称。
@namespace是浏览器在识别查件的时候,当插件名一样时用以区分不同插件的命名空间。
在头部注释中,比较重要的,是@include@exclude,前者用来表示当前的脚本所能作用的域,后者表示在前者包括的内容中再剔除后者表示的域。
例如:

// @include    *
// @exclude    http://127.0.0.1:3000/*

则表示,该脚本能作用的地址为除了127.0.0.1:3000域名下的所有地址。

脚本内容中能够使用包括当前页面提供的,以及Chrome原生支持的所有API,文档可以参考这里
好了,现在我们可以编写删除Gmail中广告部分的代码了,如下所示

var removeAdd = function() {
  var mqEl = document.getElementsByClassName('mq');
  if (mqEl && mqEl.length != 0) {
    mqEl = mqEl[0];
    mqEl.remove();
  } 
  setTimeout(removeAdd, 500);
};

removeAdd();

OK,到此为止,插件差不多完成了。用上述的步骤,重新安装Userscript,是不是发现打开Gmail的时候,广告不见了呢?
如果需要调试我们的插件,可以打开控制台 ——> source ——> content script来进行调试。

当然,这里还需要注意一个问题,就是这段代码究竟是在页面加载到何时的情况下运行的呢?
可以写个简单的测试脚本:

document.addEventListener('DOMContentLoaded', function() {
  console.log('Document load event fired!');
}, false);

window.addEventListener('load', function() {
  console.log('Window onload event fired!');
}, false);

再将home.user.js脚本中打印一串“User script loaded!”。在不断地刷新页面,可以得出结论,userscript应该是放在window.onload中执行的,位于domready时间之后,因此可以放心的编写安全的代码了。

Enjoy yourself !
转载请注明出处
botobe.net
本文Github链接

2013.06.28
一切安好。
Merci !

node.js在生产(线上)环境中的部署的一点思考总结

什么是生产环境,给人造成的字面上的解释是满是隆隆机器的工厂?或者是妇产医院的婴儿房?在Web开发中,开发环境是指已经运行在正式环境中的代码,与其相对的则是在开发环境中的代码,balabalabala...(扯O中...)

希望上边的一段话不会让你认为本文是懒婆娘的裹脚布的开端。
相比于开发环境,生产环境有几个特点:

a). 对性能的需求

开发环境中,对网站的uv(user view)为1,没什么好说的了,怎么方便,怎么能提升开发效率。抄起你的十八般武艺,怎么使得顺手怎么招呼。
总之一句话,开发效率为先。
对比与线上环境,uv是个不确定的数值,说不定今天放了几张小黄图会引来无数狼友的围观。说不定某天也会门庭冷清。
对于性能的支持上,首先从硬件拼起,本文以本站音韵码工(下文中以"本站"来代替)为例。作者本是北漂的屌丝小码农一枚,没有太多预算,所以本站用的是一台512M,双核的BurstNet小破VPS。用脚趾丫都能理解的到,自然是服务器的数量越多,配置越高,网站的性能会越好。
在硬件恒定的情况下,线上环境,当然要把它调整到最佳状态。可以使用node.js的clurster模块,充分的利用多核CPU的特性。
我进行过简单的测试,cluster能使本站的qps能提升了50%,还是非常给力的,下边的继续优化的瓶颈应该就是I/O的操作了。
详见官本站的启动脚本app.js第55行
正常情况下,你很happy的加入了cluster后,你可能会发现session经常会偶发性的失效了。
因为使用了cluster,查看名为node的进程后你会发现已经不止一个进程了,而express.js提供的默认session是无法在多进程之间共享的。
其实在默认的情况下,当你使用了production的方式启动了express.js,命令行里都会有警告的日志,提示默认的session存在内存泄露的风险,官方不建议使用。
我们需要找个中间的媒介存放session数据,这个时候就connect-redis模块在天空一阵巨响后闪亮登场了。
首先要安装了redis,再安装了connect-redis依赖后,用app.js第32的方式,就可以顺利调用Redis存放session数据了。

对于生产环境和开发环境,express.js也就是本站使用的node.js框架,提供了一个很便捷的配置方式:

app.configure('development', function() {
  app.use(express.logger('dev'));
  app.use(express.errorHandler());
});

app.configure('production', function() {
  app.use(express.compress());
  app.use(function(req, resp, next) {
    resp.removeHeader('X-Powered-By');
    next();
  });
});

configure方法的第一个参数是环境名,只要你愿意,除了production,其他的环境你可以叫他小黄,小白,旺财,小强...
线上环境相比于开发环境,多了压缩,去除log,取消打印错误堆栈的特征,用这个方法很容易实现。
对于性能的配置上,最重要的一点是网站静态资源的管理,也可以非常大幅度的提升网站的整体请求速度。
一个糟糕的做法,是把静态资源直接挂载在网站的主域下,尤其像BurstNet这种主机在国外的VPS,访问速度本来就不怎么给力。
比如有个叫general.js的文件,我直接放在了botobe.net/general.js下。
这种做法同时也是占用了node.js的处理资源,把更多的任务交给node.js进程(也就是网站所在的VPS)去处理。
通常的做法,是把静态资源给分离出来。比如本站的静态时挂载在百度的App Engine服务器上的。静态的位置是可配置的。
可参考本站的配置文件config.js第7~13行

还有些基本的前端的优化,比如css放在头部,js放在尾部,以及spirit,减少文件请求等等。这里就不做累赘的重复。

b). 对稳定性的需求

如果线上网站的网站三天两头的挂掉,这也不是我们愿意看到的。
上线前也不会有QA进行严格的测试验证,遇到问题宕掉也是正常情况。但是作为一个个人网站,问题的关键在于,服务宕掉后,能迅速的定位出问题出在了哪里。
起初我是直接来翻看nginx的error和access几十兆的文本日志。
这个方法很管用,不过却很繁琐,因为访问你网站的在绝大部分情况下,不会是某个“人”。而是一些搜索引擎的爬虫,以及一些机器的自动请求,他们会测试网站的各种对外暴露的请求是否OK,“404的请求”, “文章不存在在的请求”,“以及错误文章url的请求”等。
在这种情况下,于是我最终使用了(forever.js)[https://github.com/nodejitsu/forever]来运行node。
至此,妈妈再也没有担心过我的网站会宕机了。同时能根据指定的日志文件,更加有效和快捷的定位问题。

c). 对于安全性的需求

这是个很大的话题,最常见的需要考虑的一个是XSS,一个是数据库安全。
这个也不是一句两句能说的清楚了,从本站点的角度来讲,对mongodb的操作,一定要在启动参数中加上bind_ip选项。
对于XSS的操作,如果是一个团队开发的话,则一定要协定 好,XSS的问题要在前端(包括前端模板的拼装)处理或者是后台处理。
这是每个网站开发都会遇到的问题,这里就不作详细介绍。

d). 整体流程的优化

这点对于一个项目的开发来说还是比较重要的。
项目从开发到上线,必然会经历一个从测试环境到正式环境的过程。是不可能直接把开发环境中的代码投放到生产环境中的,尤其是前端的代码,否则会的话,严重的情况下回导致你...................................................被鄙视!
在这个环境转换的过程里,如何做到平滑的过渡,是个很值得思考的问题。
本站则是使用了一个node.js的脚本deploy.js来解决这个问题。
几年的从业经验看来,大部分网站解决线上环境到测试环境前端代码的优化的过程,都是采用的一个脚本自己来完成或是调用ant以及grunt之类的自动化处理的工具来完成包括变量的替换,代码的错误检测,压缩以及优化,自动化测试等等工作。
当然也有其他的方式,这个就取决于整个团队的技术实力是否足够强大,或者是否拥有足够多的时间。
比如人人网就是引入了一个名为OPM的工具(PS. 当然是在人人网鼎盛时期,诸神俱在的时候),支付宝据说使用的一个叫SPM自动工具来完成。
具体的工作原理,我就不在这里班门弄斧了,这些工具也都已经开元并且托管在了Github上,大家可以自行翻阅文档。

e). 后台的管理

相信大部分混迹于互联网的geek们都应该见过workdpress的后台,那可真叫一个赞!
相比于本站,那就是金光灿灿的水晶鞋和底部有一个洞的小塑料拖似得。

我是不会告诉你,这个就是笔者混迹于各个豪华CBD之间的常配装备之一的。
水晶的光芒,刺穿的小破鞋如同海盗船上满是漏洞的破帆似得。

本站自己写了一个专门用于发布文章的发布器进行发布文章。
并且写了个只有寥寥几行html的简易后台操作相应的评论等等。

记在最后:
从谷歌里搜索了一圈,发现介绍node.js部署的文章不是很多,于是尝试着自己撰文一篇。
欢迎交流和拍砖。适合初级选手,大神可略过。
转载请注明出处
http://botobe.net/
本文Github链接

2013.10.28
几个月前来到了鹿妖山,一切安好。
Merci !

观察者模式的在前端开发中的应用

观察者模式,是Gof4在经典书籍《设计模式--可复用面向对象软件的基础》提出的一种设计模式。
何为观察者模式呢?

让我们举个

就一目了然了。

大家应该都有去过银行取款的经历,刚进银行,会有热情的客服MM迎上来让你取号。
取号的过程,就可以理解为加入了某个观察团,对被观察者(柜台)进行观察。
试想,如果没有这个过程,我们会怎么做呢?没错,就是不断的去柜台进行询问和催促:轮到我了没?柜台的GGMM每天要接待上百个业务,被你不断的骚扰说不定会有拿出砍刀的冲动。
被观察者的状态会不断发生变化,变化的时候会通过广播的方式来告知大家。
这样就构成了一个简单的观察者模式。

也许你已经有了大概的了解,在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。
而不是需要观察者不断的去询问被观察者是否要有相应的动作。

再来举个新浪微博的例子:
写个简单的观察者类模拟观察者:

var Observer = (function(global) {
  var Observer = function(data) {
    this.data = data;
    this.evtHandler = {};
  };

  var op = Observer.prototype;

  op.trigger = function(evt, params) {
    if (!this.evtHandler[evt]) {
      return;
    }

    var callbacks = this.evtHandler[evt];
    for (var i = 0; i < callbacks.length; i++) {
      callbacks[i](params);
    }
  };

  op.registObserver = function(evt, callback) {
    if (!this.evtHandler[evt]) {
      this.evtHandler[evt] = [];
    }
    this.evtHandler[evt].push(callback);
  };

  return Observer;
})(this);

我们可以把输入框作为个被观察者,当我们发布了一条普通的状态,整个页面上会产生哪些变化呢?
1. 右侧的微博条数应该会加一(在微博改版后好像就不会加一了,老版是会加一的)

2. 会产生一条新鲜事

3. 发布框的状态会发生改变,右上角的数字会变为140

通过代码的方式,可以如下表示出来

var handler = new Observer();

handler.registObserver('publishUGC', function() {
   // 请求一次新鲜事,并且更新界面
   // ...
})
handler.registObserver('publishUGC', function() {
   // 改变发布框的状态(更新可发布文字)
   // ...
})

handler.registObserver('publishUGC', function() {
   // 
})

这时候你肯定会想,这么做有什么好处呢。代码的复杂度上升了,若是直接在调用了发送新鲜事的接口的回调中几十来行的代码把上诉的1,2,3立即给解决了岂不是更爽?
各路看官应该都会有自己的看法,我的看法如下两点:

  1. 模块之间最大程度的得到了解耦 随着前端的代码结构的越来越复杂,很多团队在开发时候都采用了模块化的方式。使用观察者模式,使代码达到解耦的优势也自然体现出来。
  2. 便于开发人员后续的维护 面向对象编程有个很经典的观点,增加原有系统的功能最好的方式是扩展他,而不是去修改他。 观察者模式做到了能把系统用扩展的方式来增加功能,而不需要在代码中一遍遍的修改,改到最后变得一团糟,最后变得难以维护坑爹骂娘的吐槽着极品的前任(有木有淡淡躺枪的感觉?)。
  3. (欢迎补充或者拍砖)

现在流行的web框架backbone.js,是使用观察者模式的一个很经典的例子,将模型(Module)和视图(View)的修改进行绑定。
改变了模型,绑定在这个模型上的一个或者多个相应的视图也会跟着进行改变。

写的有点乱。
最后的吐槽:
原来的Wordpress网站botobe.com被停掉了,可4我一直在用他的代码排版格式化插件啊!知晓第一时间内为毛有种菊花一紧的赶脚?!
看来我连格式化代码的功能都要投向Github的的怀抱了。

本文Github链接

2013.09.04
一切安好。
Merci !

好歌分享

贴上一首非常好听的陈升的《不再让你孤单》和大家分享。
近期的目标就是将这首歌熟练的弹唱,虽然已经沉默很久了,可是依旧文艺依然。(狂笑状...)

我的2012

静静的就这么走过了2012年。也是我的第二个本命年。
本来以为今年会定格在12.22日,结果在23号之后被证明了,原来这只是个国际玩笑而已。

想想在2011年初的时候,我有想过2012年要过成一个不平凡的一年。
2012年末的时候,做个小小的总结。

2012年年初,奋斗了好长时间的hibox项目被终止了,于是我离开了有货。
非常的感谢qq和danvan同学,算是我职业道路上一个导师了。教会了我很多东西。这些东西,对我来说,用固定的价值是无法衡量的,随着时间的退役,这些价值会凸显的越来越来大。

于是来到了人人网,来到了大北京这个城市。

北京的生活步骤明显的要比南京这座城市高出一个级别。
从每天上下班的公交,或是地铁上就可见一斑。
熙熙攘攘的人群,在地铁里想密密麻麻的蚂蚁一样,拥堵在地下通道里。在一些地铁交汇的站点,实在挤不上去的话,工作人员也会在你后面推上一把把你挤上去,等好不容易挤上去了,才发现又是动弹不得,十分的不舒服。
真不知道这样的情况下,我该回头向工作人员说声谢谢呢?还是一句“艹”。
不过不可否认的,这就是北京,这座载着很多人梦想,悲伤,无奈,和希望的地方,这里不是个属于我的地方,我对他而言可能只是个过客而已。

不过是否去挤地铁或者是公交这样的情况还是取决于你自己的,我所说的也是一个正常规律的早九晚五的上班族的正常情况下遇到的情况。
为什么要加了好几个“正常”呢?
因为我是个做“挨踢”的,就打破了这一连串的“正常”。整体的时间表比“正常”要往后推延了2个小时,好避开北京这种全军万马过独木桥般的局面。
好在人人网的福利还算不错,每天都可以报销打车回家的费用。

再来说说人人网,给我2012年的生活添上了浓墨重彩的一笔。
来到人人,从参与了广告edm的项目开始,到参与了现行的宿舍圈项目,到参与,设计,并且架构了人人网新版的公共主页的前端。
基本上投入我所有的时间和精力。
每次在线上看到我做东西,心里也是有点小小的骄傲和自豪,但是当时也是为了赶工期,留下了不少的隐患,需要后续来慢慢的解决。

来到人人也有一段时间了,实话说,不大喜欢这里的工作氛围。
作为个奋战在一线的码农,我是个对代码有着一定程度洁癖的人,从我手中出来的每个字符,我都自信的说一定会去考虑他的兼容,扩展,健壮,性能等等特性。我觉得这是一个技术人员应该的有的操守和态度。
节操啊什么的,碎了一地也好,被风吹的散的连沫沫都见不着也好,但是有些东西还是要有底线的。
否则就像在埋地雷一样,迟早会被踩到。
关于工作就就不想说太多了。

说说2012年最让我值得骄傲的事,一个是完成公共主页的2.0新版。第二个便是我这个纯手工100%敲代码完成的博客啦(画外音:废话)。并且放到github上去了。
而且这么个小小的博客,可以说得上是麻雀虽小,五脏俱全。从开发,调试,版本控制,部署,前端扩展,按需加载等等包括了一系列应该有的流程。

好吧,其他的就不多说了,2012年要是给自己打个分的话,应该有90分吧,哈哈。
最后,恭祝大家新年快乐,拥抱2013!
2012.02.13
一切安好。
Merci!

UglifyJS使用指南

前几天在围脖上看到了个关于DailyJS的javascript开发者的调查
发现了UglifyJS貌似非常火爆的样子。实话说,虽然作为一名前端开发人员,却显为耳闻UglifyJS,看来真是工作的时间越久,好奇心也跟着降低了。没有那股子初生牛犊的劲头去折腾这些新东西了。

谷歌之,发现没有什么关于UglifyJS的具体的中文帮助文档。要不就是几篇被到处转载的基本上没有什么实质内容的文章。

研读了下UglifyJS的文档(尼玛,文档什么的真的很重要啊!?有木有?我有告诉大家,yinyin网什么的某些网站,连个文档都没有,写个几千行代码的框架就可以充胖子了吗?有木有??!搞得开发人员都在翻源码啊!博主真是忍不住想吐槽!!),做些笔记,给大家分享。

UglifyJS是遵循了CommonJS规范写成的,可以在支持CommonJS模块系统的javascript环境中运行。当然,这是官方的说法,通俗的说,就是可以在浏览器里和node.js的环境中兼可运行了。

UglifyJS的作者在今年9月份的时候,开始了UglifyJS2的项目,作者本人也是比较推崇UglifyJS2,因此,本文就只是关于UglifyJS2的一些介绍。下文中所有的UglifyJS都是指代UglifyJS2。
UglifyJS其实不仅仅是个js的压缩工具,同时可以对代码进行最小化,和美化的工具包。

担心篇幅太长,在这里只做关于node.js环境下使用UglifyJS的介绍。

1. 安装:
—— 从npm安装

npm install uglify-js

—— 从github安装

git clone git://github.com/mishoo/UglifyJS2.git cd UglifyJS2 npm link .

2. 概述:
在控制台/命令行中输入uglifyjs –help,如果出来帮助信息,表明uglifyjs可以正确使用了。

uglifyjs [input files] [options]

举个栗子:

// 有个叫main.js的待压缩/美化的文件 uglifyjs main.js -o main-min.js -c

uglifyjs的作者建议的就是先把文件放在前面,再把一些压缩/美化的参数跟在后边。

一些常用的参数列表

-o,--output 指定输出文件,默认情况下为命令行 -b,--beautify 美化代码格式的参数 -m,--mangle 改变变量名称(ex:在一些例如YUI Compressor压缩完的代码后你可以看到 a,b,c,d,e,f之类的变量,加了-m参数,uglifyjs也可以做到,默认情况下,是不会改变变量名称的) -r,--reserved 保留的变量名称,不需要被-m参数改变变量名的 -c,--compress OK,主角登场了,这是让uglifyjs进行代码压缩的参数。可以在-c后边添加 一些具体的参数来控制压缩的特性,下文中会具体介绍。 --comments 用来控制注释的代码的

3. 变量名压缩
你需要传入-m来进行变量名的压缩(将一系列很长的变量名压缩为a,b,c,d,e,f云云)。当使用了-m参数进行改变变量名时,如果还想保留一些变量名让他们不被改变,就需要用–reserved (-r) 。例如

uglifyjs ... -m -r '$,require,exports'

4. 进行压缩的选项
当使用了–compress (-c) 参数时,就启用了uglifyjs对代码的压缩的功能。你可以在-c后边传入一系列逗号分开的选项来指定压缩的具体特性。
对于这些选项使用的方法,可以参考下文中的“代码美化选项”部分。
(巴拉巴拉巴拉,此处省去一千字就不做具体的介绍了。总而言之,你可以让uglifyjs可选的除去你在代码中留下的debugger的等等关键字,或是各种变态的注释等等,一般用户这里忽略了,如真有特殊需求,可以自己翻阅文档。)

4.1 预编译选项
你可以使用–define (-d)来定义一些给uglifyjs压缩/美化代码的时候使用的变量,此话怎讲呢?说着玄乎,其实举个例子就能明白了。为了调试代码,你可以在你的代码中加入如下的示例

if (DEBUG) { console.log("debug stuff"); }

当然,在最后发布的时候。普通的码农呢,会勤勤恳恳的一个个把这样的调试代码给删除掉,但是万一工程过于庞大,溜掉了一两个也是无可厚非的。聪明的码农呢,就会用到了uglifyjs中的-m这个选项了。
首先要保证的就是DEBUG不是一个本地的变量,再使用如下命令

uglifyjs -o main-min.js main.js -c --define DEBUG=true

得到的main-min.js文件中,上边的调试代码就不见了。是不是很cool?

还有种使用预编译选项的方式,就是把预编译的所有选项放到一个js文件中。例如,有个define文件,有如下内容

const DEBUG = false; const PRODUCTION = true; // etc.

注:必须要使用const来声明需要预编译的变量。
用如下的方式编译你的代码

uglifyjs build/defines.js js/foo.js js/bar.js... -c

最终起到的效果和上面是一样的。

5. 代码美化选项
从博主的个人角度来讲,觉着这个有必要学习下,代码规范在什么时候都是一个码农离不开的话题。为了保证在一个大型项目中,你的代码具有良好的可读性,可以由jsdoc之类的工具生成一份具体的文档供别人阅读是很有重要的。
uglifyjs提供的代码美化的选项比较有限,就算是代码压缩功能的一个附属小功能吧。
这里需要强调下的是,美化中的所有选项,是对-b参数而言的,就是说在使用的时候,这些选项需要放在-b后边,例如:

beautify 进行代码的美化,默认的情况下等于-b beautify=true,你也可以吧beautfy设置为false跳过 美化步骤。 indent-level 设置代码缩进,例如github默认的情况下缩进是2个tab,默认的数值为4,例如 (uglifyjs -o main-min.js main.js -b "indent-level=2") indent-start 对代码进行整体缩进默认值为0,可以这么使用 (uglifyjs -o main-min.js main.js -b "indent-start=4,indent-level=8") quote-keys 默认为false。如果设置为true,则将所有对象中的键用引号引起来 space-colon 会在一个单引号后边插入一个空格 ascii-only 转义Unicode字符 inline-script 转义js代码字符串变量中的script标签 width 设置代码的宽度,默认情况下是80

5.1 保留copyright字样或者是其他的一些注释
默认的情况下uglifyjs只会保留符合jsdoc规范的注释。
你可以在–comments后面加上一串正则表达式,来匹配出你需要保留的注释,例如–comments ‘/foo|bar/’。
如果在–comments后加了个all,则保留了所有的注释。

2013.01.04
又是新的一年啦,一切安好。
Merci !

开篇第一文章

历时三个月的开发,Melody coder 0.1版本上线了!Github地址:https://github.com/EchoFUN/melodycoder,欢迎fork(not fuck)!谢谢。

node.js大文件的下载以及断点续传

断点续传其实不是什么新鲜的功能了。
简单的做个扫盲,其基本原理就是,在文件的下载断开以后。客户端继续向服务器端请求的时候,http请求的头文件中会多了一个参数“Range”,来标示当前下载的文件所断开的位置。
用如下命令可以做个测试
wget -c -d –limit-rate=2048k -O target “http://127.0.0.1:8000″(ps. c表示让wget进行断点续传,如果不加c发送请求的头文件中是不会,d标示打印调试信息)

接下来的下载,就是从这个断开的位置开始,服务器继续向客户端传输文件剩余的内容了。

主要的是关注两个地方,一个就是返回头文件的设置,和文件内容的读取。
新建一个叫Transfer的类,把请求的两个参数当成构造函数传进去。

function Transfer(req, resp) {
    this.req = req;
    this.resp = resp;
}

计算文件开始的位置和设置头文件的信息如下

/**
 * @description 计算上次的断点信息
 * @param {string} Range 请求http头文件中的断点信息,如果没有则为undefined,格式(range: bytes=232323-)
 * @return {integer} startPos 开始的下载点
 */
Transfer.prototype._calStartPosition = function(Range) {
    var startPos = 0;
    if( typeof Range != 'undefined') {
        var startPosMatch = /^bytes=([0-9]+)-$/.exec(Range);
        startPos = Number(startPosMatch[1]);
    }
    return startPos;
}
/**
 * @description 配置头文件
 * @param {object} Config 头文件配置信息(包含了下载的起始位置和文件的大小)
 */
Transfer.prototype._configHeader = function(Config) {
    var startPos = Config.startPos, 
        fileSize = Config.fileSize,
        resp = this.resp;
    // 如果startPos为0,表示文件从0开始下载的,否则则表示是断点下载的。
    if(startPos == 0) {
        resp.setHeader('Accept-Range', 'bytes');
    } else {
        resp.setHeader('Content-Range', 'bytes ' + startPos + '-' + (fileSize - 1) + '/' + fileSize);
    }
    resp.writeHead(206, 'Partial Content', {
        'Content-Type' : 'application/octet-stream',
    });
}

在nodejs中每一次http请求的头文件,已经被他封装在了http.ServerRequest的headers对象中,用node inspector的方式进行调试,就可以很清楚的看到了http.ServerRequest和http.ServerResponse对象的结构:

剩下来要做的,就是从断开的位置继续读取文件,并将其返回给客户端,可以用nodejs提供的ReadStream来实现:

/**
 * @description 初始化配置信息
 * @param {string} filePath
 * @param {function} down 下载开始的回调函数
 */
Transfer.prototype._init = function(filePath, down) {
    var config = {};
    var self = this;
    fs.stat(filePath, function(error, state) {
        if(error)
            throw error;

        config.fileSize = state.size;
        var range = self.req.headers.range;
        config.startPos = self._calStartPosition(range);
        self.config = config;
        self._configHeader(config);
        down();
    });
}
/**
 * @description 生成大文件文档流,并发送
 * @param {string} filePath 文件地址
 */
Transfer.prototype.Download = function(filePath) {
    var self = this;
    path.exists(filePath, function(exist) {
        if(exist) {
            self._init(filePath, function() {
                var config = self.config
                    resp = self.resp;
                fReadStream = fs.createReadStream(filePath, {
                    encoding : 'binary',
                    bufferSize : 1024 * 1024,
                    start : config.startPos,
                    end : config.fileSize
                });
                fReadStream.on('data', function(chunk) {
                    resp.write(chunk, 'binary');
                });
                fReadStream.on('end', function() {
                    resp.end();
                });
            });
        } else {
            console.log('文件不存在!');
            return;
        }
    });
}

最终的可以用如下代码进行测试

var fs = require('fs')
http = require('http')
path = require('path');
var server = http.createServer(function(req, resp) {
    var transfer = new Transfer(req, resp);
    var filePath = '/Users/xukai/Downloads/love.rmvb';
    transfer.Download(filePath);
});
server.listen('8000');

转载请注明出处
botobe.net
本文Github地址

按照惯例
2012.3.10,情况一般。
Merci !