npm安装东西时候的困惑

不知道npm是什么的同学,自己google,提示一下,跟node相关。

npm安装分为两种,local和global,有什么区别这里说得已经很详细了:http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/,我也就不再废话了。

只是有些东西还要解释一下。

刚装好npm的时候,我惊异地发现它所有的安装目录全是777的权限,表示不理解(而且terminal里面看过去,非常不好看),手工改成755。这样带来的问题是如果做global安装的话,普通用户则无法安装;ok没问题,我可以sudo来搞,但是sudo之后新的问题又来了,~/.npm是做cache用的,sudo安装的话这里面东西可都属于root了。这个设计比较土,理想的方案是local安装,~/.npm作为cache,global安装,cache在npm自己的安装路径下。

还有local这种安装方式看起来隔离性挺好,但是对版本管理工具可不怎么友好,不知道有没有办法。

其实根本没有object,所有的东西都是function

我是意思是js里压根儿就没有object,全是浮云;其实根本就是function。

越看越糊涂,这语言设计得真是够可以的。都说是OO,其实就是个屁,所有东西都是用function模拟的。没有任何东西能跟传统意义上的OO对应起来,然后就说这门语言比较深奥,受过传统程序设计教育的人不容易理解。操蛋也不能操成这样吧?如果你说我就是不支持OO,OO算个屁,那也好很多。

只是发发牢骚,不能这么让人忽悠了,但是该学还是要好好学,既然它这么设计了,就咬牙跟进吧。

果然js给我上的第一课就是this

this在js里设计得还真是够复杂,或者确切地说根本就是设计错了(这不是我说的,是写那本the good parts的老大说的)。

一个例子:

1
2
3
4
5
6
7
myObject.double = function () {
    var helper = function () {
        this.value = add(this.value, this.value);
    };
 
    helper();
};

这个方法要做的事情很简单,就是把自己的value属性double一下。一切看起来都很完美,直到真正跑起来,你才会发现报错说value没定义。

原因在于那个内部匿名函数里的this并不是myObject,而是这个函数本身!诡异得很。

解决起来倒是简单:

1
2
3
4
5
6
7
8
9
myObject.double = function () {
    var that = this;
 
    var helper = function () {
        that.value = add(that.value, that.value);
    };
 
    helper();
};

习惯上大家都用that。怪不得之前总是看到that这that那的,还以为是js的保留字。

给自己建一个Nexus

我不是做CM的,但是家里有两台工作机器、一台二奶机,所以弄个Nexus还是有点意义的。

过程相当容易。

1. 这里下:http://nexus.sonatype.org/

2. 解包

3. bin/jsw/<arch>/nexus start(Windows底下不是这个,我不想写,自己研究吧)

4. http://<server>:8081/nexus

5. 在Repositories里面加自己需要的repository,注意三种类型:hosted、proxy、virtual;鼠标移动到问号上有提示,这里就不多说了

6. 修改$HOME/.m2/settings.xml

1
2
3
4
5
6
    <mirror>
      <id>id</id>
      <mirrorOf>*</mirrorOf>
      <name>xxx</name>
      <url>http://<server>:8081/nexus/content/groups/public/</url>
    </mirror>

这里把所有的repository都mirror到Nexus去。

7. 还有复杂的权限控制,我暂时用不到,以后需要再说吧。

关于node.js的第一贴

node.js是一个server端js的运行时。

想研究一下,就拿cheater来练手。(注:cheater是在公司里对付cc zone的实现自动登录的小工具)

首先想到的是用来做管理、监控的socket端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var net = require('net');
var server = net.createServer(function (client) { // 有client连上来的时候调用此回调函数
  client.setEncoding('ascii'); // 设置编码,否则下面的data默认使用Buffer
  client.write('> ');
  client.on('data', function (data) { // client发数据过来,触发'data'事件,然后调用此回调函数
    switch (data.trim()) {
      case 'status':
        client.write('heartbeating...\r\n');
        client.write('> ');
        break;
      case 'quit':
        client.end(); // 关掉client
        break;
      case 'shutdown':
        client.end();
        server.close(); // 关掉server
        break;
      default:
        client.write('> ');
        break;
    }
  });
});
server.listen(44050, 'localhost');

值得注意的是,这里只有一个线程,所有异步的事情都靠事件驱动。这也就是node.js一个最核心的思想:用单个线程充分压榨CPU资源,能异步的全都异步了。

刚开始玩儿,哪里不对的,请多指正。顺便帮cnodejs做个广告。

如何给当前页面的菜单加上URL

默认情况下,Lift的菜单系统对于当前页面是没有<a/>这个tag的,也就是说假如你处在页面A上,菜单里的A是没有URL的。但是有些时候确实又需要,enable的方法很简单:

1
<pre lang="html" colla="+"><span class="lift:Menu.builder?linkToSelf=true"></span>

Lift的REST支持

跟所有Lift的feature一样,支持REST也是非常非常得简单。

Mixin这个trait:

1
object AdminAPI extends RestHelper

Boot.scala里面加上:

1
LiftRules.dispatch.append(AdminAPI)

如果不需要创建session(不创建著名的S对象),还可以:

1
LiftRules.statelessDispatchTable.append(AdminAPI)

然后就是URL匹配:

1
2
3
4
5
6
serve {
  case "api" :: "add" :: Nil JsonGet _ => add
  case "api" :: "delete" :: Nil JsonGet _ => delete
  case "api" :: "edit" :: Nil JsonGet _ => edit
  case "api" :: "get" :: Nil JsonGet _ => get
}

以上都是GET的JSON接口。JsonGet会检查HTTP头里面的Accept以确认client是否支持JSON,这里需要注意的是“*/*”表示所有都接受(这个应该已经在2.3里面被修复掉了);当然如果Accept里面不支持JSON,JsonGet还会去查询URL是否是.json结尾。

具体解释一下上面的例子。

对于URL为 http://server/api/add.json 的请求,第一条匹配规则命中,最后那个“_”是Req,然后调用add方法,add的返回值需要是LiftResponse类型;其实LiftReponse是个啥都不干的trait,有很多实现,JsonResponse、XmlResponse等等,总之几乎不用特别在意,Lift里面能作为response返回给client的都能用。

其他的匹配类似处理。

给个add方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
private def add = {
  val linkId = param(ShortenedUrl.originUrl.name).map(x =&gt; {
    shortenedUrl.find(ShortenedUrl.originUrl.name -> x).map(_.linkId.value) or {
      val tmp = (DependencyFactory.inject[NextIdGenerator].open_! !? 'id).toString
      shortenedUrl.createRecord.linkId(tmp).originUrl(x).shortUrl(Props.get(Site).open_! + "/" + tmp).date(new Date).
              ip(containerRequest.map(_.remoteAddress).toString).clickCount(0).save
      Full(tmp)
    }
  })
  (StatusField -> linkId.map(_.map(x => SuccessStatus)).openOr(Full(FailedStatus)).openOr(FailedStatus)) ~
          (ShortenedUrl.linkId.name -> linkId.openOr(Full("")).openOr(""))
}

返回一个JSON对象{status: “successful”, linkId: “1″}。

Lift利用Scala对DSL的支持把JSON整得很舒服,几乎跟js原生的用起来没区别:

1
2
3
("name" -> "honnix") ~
  ("address" -> "somewhere") ~
    ("phone" -> List("111", "222", "333"))

等价于:

1
2
3
{name: "honnix",
 address: "somewhere",
 phone: ["111", "222", "333"]}

具爽吧?

稍显诡异的MongoDB Collection

今天给同事演示的时候露怯了,直接飞出个异常。

对一个不存在的collection,find,count之类的操作都没问题,但是直接mapReduce就完蛋了,告诉我“ns doesn’t exist”。

解决办法有两个:

  1. 查询db的system.namespaces,这样最可靠
  2. 直接count,如果是0,可以当作没有来处理;不过这个要看业务逻辑,因为collection里面没有数据的时候也会返回0

有人提了新加几个接口的需求,不知道什么时候会做进去:http://jira.mongodb.org/browse/SERVER-1938

Lift的i18n

基本上是转帖:http://www.assembla.com/wiki/show/liftweb/Internationalization

但是还上要说一下,设计得太爽了。没怎么用过其他的web框架,不过Lift提供的真是好用。尤其是最后一点,template也可以直接国际化:index_en_US.html,index_zh_CN.html。不是所有都可以直接通过resource bundle来进行翻译,所以直接对template也就是页面本身来搞,真是很爽。

Lift里进行MongoDB的MapReduce

其实说Lift里不够准确,Lift对MapReduce没做什么封装,基本上就是直接调用mongo-java-driver的API。

举例说明,计算所有URL的点击次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    def countClicks = {
      MongoDB.useCollection(shortenedUrl.collectionName) {
        x =&gt; {
          val map = """function() { emit("totalClickCount", this.%s); }""" format ShortenedUrl.clickCount.name
          val reduce = """function(key, values) { return Array.sum(values); }"""
          val results = x.mapReduce(map, reduce, null, null).results
 
          /**
           * all numbers returned by mongodb is Double since this is how number defined by javascript
           */
          if (results.hasNext) results.next.get("value").asInstanceOf[Number].intValue.toString else "0"
        }
      }
    }

map的时候把this.clickCount的值塞给”totalClickCount”这个key,reduce把所有这些值加起来,MongoDB会生成一个临时的collection,从里面选择“value”这个字段就可以了。

需要注意的是所有返回的数值类型都是Double,虽然BSON里对各种类型都有定义,但是目前为止MongoDB只支持返回Double。对js了解的人可能会比较清楚,但我就惨了。调了很长时间,一个bit一个bit的看,甚至还去MongoDB的JIRA上问:http://jira.mongodb.org/browse/SERVER-2688。土死了……