2015年五月7日晚上 7:14:38 大家对于富应用的前端,一个页面调用很多第三方平台的RESTful api的方式,有什么好建议吗?

如果移到后端,逻辑会清晰点,但是后台服务器负荷会增加。 如果在前端,结果会直观,但是验证逻辑的代码量增加,而且service不通过后台就直接去第三方平台做get,put…的操作也觉得不是很安全。

2015年五月7日晚上 6:01:50 JS.ORG:为 GitHub pages 上的 JavaScript project 提供免费的二级域名

来自 JS.ORG 的福利,为 JavaScript 项目或者个人 github pages 提供免费的二级域名,有需要的可以去申请个。

PS: CNodejs.org 或许也可以提供一个类似的功能。

2015年五月7日下午 5:52:11 提一个小建议。

刚才回答问题的时候发现一个小问题。js 代码块里面的汉字和字母宽度比不等于2,一般终端里面都是2倍的,导致下面的文字有些错位。 如果能改成2倍的话,就完美了。

PHP->Node.js─┬──读─┬──读经典的书
             │     └──读优质的代码
             ├──写─┬──边学边写点小代码
             │     └──写一些小玩意
             └──思─┬──刨根问底
                   ├──举一反三
                   └──用脑图
2015年五月7日下午 5:47:27 求上谷歌方法

本来不想买vpn 最近平常上的搜索都给封了 所以,便宜的vpn也认了 当然,还是免费的最好啦 对这种闭关锁国的寡妇网表示鄙视 各位大大,给点推荐吧

2015年五月7日下午 4:58:47 tcp(net模块) 文件分包传输解决方案

想知道各位有没有tcp 文件分包传输的解决方案。 附上找到的一个方案。http://yoyo.play175.com/p/nodejs-exbuffer.html

2015年五月7日下午 4:47:17 坛子之前有没有回复邮件提醒功能来着

segmentfault里貌似不在线的用户回收到评论的邮件提醒。 隐约记得坛子好像有这功能,不知道是不是幻觉? 咋个判断要不要发邮件?

2015年五月7日下午 4:35:57 lsyncd实时同步搭建指南——取代rsync+inotify

1. 几大实时同步工具比较

1.1 inotify + rsync

最近一直在寻求生产服务服务器上的同步替代方案,原先使用的是inotify + rsync,但随着文件数量的增大到100W+,目录下的文件列表就达20M,在网络状况不佳或者限速的情况下,变更的文件可能10来个才几M,却因此要发送的文件列表就达20M,严重减低的带宽的使用效率以及同步效率;更为要紧的是,加入inotifywait在5s内监控到10个小文件发生变化,便会触发10个rsync同步操作,结果就是真正需要传输的才2-3M的文件,比对的文件列表就达200M。使用这两个组合的好处在于,它们都是最基本的软件,可以通过不同选项做到很精确的控制,比如排除同步的目录,同步多个模块或同步到多个主机。

搭建过程参考 Linux下同步工具inotify+rsync使用详解这里

1.2 sersync

后来听同事说 sersync 这么个工具可以提高同步的性能,也解决了同步大文件时出现异常的问题,所以就尝试了一下。sersync是国内的一个开发者开源出来的,使用c++编写,采用多线程的方式进行同步,失败后还有重传机制,对临时文件过滤,自带crontab定时同步功能。网上看到有人说性能还不错,说一下我的观点:

虽然不懂c++,但大致看了下源码 FileSynchronize,拼接rsync命令大概在273行左右,最后一个函数就是排除选项,简单一点可以将--exclude=改成--eclude-from来灵活控制。有机会再改吧。

另外,在作者的文章 Sersync服务器同步程序 项目简介与设计框架 评论中,说能解决上面 rsync + inotify中所描述的问题。阅读了下源码,这个应该是没有解决,因为在拼接rsync命令时,后面的目的地址始终是针对module的,只要执行rsync命令,就会对整个目录进行遍历,发送要比对的文件列表,然后再发送变化的文件。sersync只是减少了监听的事件,减少了rsync的次数——这已经是很大的改进,但每次rsync没办法改变。(如有其它看法可与我讨论)

其实我们也不能要求每一个软件功能都十分健全,关键是看能否满足我们当下的特定的需求。所谓好的架构不是设计出来的,而是进化来的。目前使用sersync2没什么问题,而且看了它的设计思路应该是比较科学的,特别是过滤队列的设计。双向同步看起来也是可以实现。

1.3 lsyncd

废话说这么多,本文就是介绍它了。有些博客说lsyncd是谷歌开源的,实际不是了,只是托管在了googlecode上而已,幸运的是已经迁移到github了:https://github.com/axkibe/lsyncd

Lysncd 实际上是lua语言封装了 inotify 和 rsync 工具,采用了 Linux 内核(2.6.13 及以后)里的 inotify 触发机制,然后通过rsync去差异同步,达到实时的效果。我认为它最令人称道的特性是,完美解决了 inotify + rsync海量文件同步带来的文件频繁发送文件列表的问题 —— 通过时间延迟或累计触发事件次数实现。另外,它的配置方式很简单,lua本身就是一种配置语言,可读性非常强。lsyncd也有多种工作模式可以选择,本地目录cp,本地目录rsync,远程目录rsyncssh。

实现简单高效的本地目录同步备份(网络存储挂载也当作本地目录),一个命令搞定。

2. 使用 lsyncd 本地目录实时备份

这一节实现的功能是,本地目录source实时同步到另一个目录target,而在source下有大量的文件,并且有部分目录和临时文件不需要同步。

2.1 安装lsyncd

安装lsyncd极为简单,已经收录在ubuntu的官方镜像源里,直接通过apt-get install lsyncd就可以。
在Redhat系(我的环境是CentOS 6.2 x86_64 ),可以手动去下载 lsyncd-2.1.5-6.fc21.x86_64.rpm,但首先你得安装两个依赖yum install lua lua-devel。也可以通过在线安装,需要epel-release扩展包:

# rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
# yum install lsyncd

源码编译安装
从源码编译安装可以使用最新版的lsyncd程序,但必须要相应的依赖库文件和编译工具:yum install lua lua-devel asciidoc cmake

googlecode lsyncd 上下载的lsyncd-2.1.5.tar.gz,直接./configuremake && make install就可以了。

从github上下载lsyncd-master.zip 的2.1.5版本使用的是 cmake 编译工具,无法./configure

# uzip lsyncd-master.zip
# cd lsyncd-master
# cmake -DCMAKE_INSTALL_PREFIX=/usr/local/lsyncd-2.1.5
# make && make install

我这个版本编译时有个小bug,如果按照INSTALLbuild目录中make,会提示:

[100%] Generating doc/lsyncd.1
Updating the manpage
a2x: failed: source file not found: doc/lsyncd.1.txt
make[2]: *** [doc/lsyncd.1] Error 1
make[1]: *** [CMakeFiles/manpage.dir/all] Error 2
make: *** [all] Error 2

解决办法是要么直接在解压目录下cmake,不要mkdir build,要么在CMakeList.txt中搜索doc字符串,在前面加上${PROJECT_SOURCE_DIR}

2.2 lsyncd.conf

下面都是在编译安装的情况下操作。

2.2.1 lsyncd同步配置

# cd /usr/local/lsyncd-2.1.5
# mkdir etc var
# vi etc/lsyncd.conf
settings {
    logfile      ="/usr/local/lsyncd-2.1.5/var/lsyncd.log",
    statusFile   ="/usr/local/lsyncd-2.1.5/var/lsyncd.status",
    inotifyMode  = "CloseWrite",
    maxProcesses = 7,
    -- nodaemon =true,
    }

sync {
    default.rsync,
    source    = "/tmp/src",
    target    = "/tmp/dest",
    -- excludeFrom = "/etc/rsyncd.d/rsync_exclude.lst",
    rsync     = {
        binary    = "/usr/bin/rsync",
        archive   = true,
        compress  = true,
        verbose   = true
        }
    }

到这启动 lsycnd 就可以完成实时同步了,默认的许多参数可以满足绝大部分需求,非常简单。

2.2.2 lsyncd.conf配置选项说明

settings
里面是全局设置,--开头表示注释,下面是几个常用选项说明:

sync
里面是定义同步参数,可以继续使用maxDelays来重写settings的全局变量。一般第一个参数指定lsyncd以什么模式运行:rsyncrsyncsshdirect三种模式:

rsync
(提示一下,deleteexclude本来都是rsync的选项,上面是配置在sync中的,我想这样做的原因是为了减少rsync的开销)

其它还有rsyncssh模式独有的配置项,如hosttargetdirrsync_pathpassword_file,见后文示例。rsyncOps={"-avz","--delete"}这样的写法在2.1.*版本已经不支持。

lsyncd.conf可以有多个sync,各自的source,各自的target,各自的模式,互不影响。

2.3 启动lsyncd

使用命令加载配置文件,启动守护进程,自动同步目录操作。

lsyncd -log Exec /usr/local/lsyncd-2.1.5/etc/lsyncd.conf

2.4 lsyncd.conf其它模式示例

以下配置本人都已经过验证可行,必须根据实际需要裁剪配置:

settings {
    logfile ="/usr/local/lsyncd-2.1.5/var/lsyncd.log",
    statusFile ="/usr/local/lsyncd-2.1.5/var/lsyncd.status",
    inotifyMode = "CloseWrite",
    maxProcesses = 8,
    }


-- I. 本地目录同步,direct:cp/rm/mv。 适用:500+万文件,变动不大
sync {
    default.direct,
    source    = "/tmp/src",
    target    = "/tmp/dest",
    delay = 1
    maxProcesses = 1
    }

-- II. 本地目录同步,rsync模式:rsync
sync {
    default.rsync,
    source    = "/tmp/src",
    target    = "/tmp/dest1",
    excludeFrom = "/etc/rsyncd.d/rsync_exclude.lst",
    rsync     = {
        binary = "/usr/bin/rsync",
        archive = true,
        compress = true,
        bwlimit   = 2000
        } 
    }

-- III. 远程目录同步,rsync模式 + rsyncd daemon
sync {
    default.rsync,
    source    = "/tmp/src",
    target    = "syncuser@172.29.88.223::module1",
    delete="running",
    exclude = { ".*", ".tmp" },
    delay = 30,
    init = false,
    rsync     = {
        binary = "/usr/bin/rsync",
        archive = true,
        compress = true,
        verbose   = true,
        password_file = "/etc/rsyncd.d/rsync.pwd",
        _extra    = {"--bwlimit=200"}
        }
    }

-- IV. 远程目录同步,rsync模式 + ssh shell
sync {
    default.rsync,
    source    = "/tmp/src",
    target    = "172.29.88.223:/tmp/dest",
    -- target    = "root@172.29.88.223:/remote/dest",
    -- 上面target,注意如果是普通用户,必须拥有写权限
    maxDelays = 5,
    delay = 30,
    -- init = true,
    rsync     = {
        binary = "/usr/bin/rsync",
        archive = true,
        compress = true,
        bwlimit   = 2000
        -- rsh = "/usr/bin/ssh -p 22 -o StrictHostKeyChecking=no"
        -- 如果要指定其它端口,请用上面的rsh
        }
    }

-- V. 远程目录同步,rsync模式 + rsyncssh,效果与上面相同
sync {
    default.rsyncssh,
    source    = "/tmp/src2",
    host      = "172.29.88.223",
    targetdir = "/remote/dir",
    excludeFrom = "/etc/rsyncd.d/rsync_exclude.lst",
    -- maxDelays = 5,
    delay = 0,
    -- init = false,
    rsync    = {
        binary = "/usr/bin/rsync",
        archive = true,
        compress = true,
        verbose   = true,
        _extra = {"--bwlimit=2000"},
        },
    ssh      = {
        port  =  1234
        }
    }

上面的内容几乎涵盖了所有同步的模式,其中第III个要求像rsync一样配置rsyncd服务端,见本文开头。第IVV配置ssh方式同步,达到的效果相同,但实际同步时你会发现每次同步都会提示输入ssh的密码,可以通过以下方法解决:

在远端被同步的服务器上开启ssh无密码登录,请注意用户身份:

user$ ssh-keygen -t rsa
一路回车...
user$ cd ~/.ssh
user$ cat id_rsa.pub >> authorized_keys

id_rsa私钥拷贝到执行lsyncd的机器上

user$ chmod 600 ~/.ssh/id_rsa
测试能否无密码登录
user$ ssh user@172.29.88.223

3. lsyncd的其它功能

lsyncd的功能不仅仅是同步,官方手册 Lsyncd 2.1.x ‖ Layer 2 Config ‖ Advanced onAction 高级功能提到,还可以监控某个目录下的文件,根据触发的事件自己定义要执行的命令,example是监控某个某个目录,只要是有jpg、gif、png格式的文件参数,就把它们转成pdf,然后同步到另一个目录。正好在我运维的一个项目中有这个需求,现在都是在java代码里转换,还容易出现异常,通过lsyncd可以代替这样的功能。但,门槛在于要会一点点lua语言(根据官方example还是可以写出来)。

另外偶然想到个问题,同时设置了maxDelaysdelay,当监控目录一直没有文件变化了,也会发生同步操作,虽然没有可rsync的文件。

TO-DO:

参考


原文链接地址:http://seanlook.com/2015/05/06/lsyncd-synchronize-realtime/


2015年五月7日下午 4:30:48 请问如何用 node.js来获取到安卓手机指定程序的内存占用??

要求仅仅返回这个内存占用的数值,其他的都不要。。可以但是通过ADB命令实现,但是我始终无法单独获得到那个数值。。求大神给给思路。

2015年五月7日下午 1:57:47 北京皇玩科技有限公司招node.js服务器主程序一枚

公司名称:皇玩科技有限公司 工作地点:北京(上地地铁附近) 工作环境:良好 招一名有多年游戏开发经验的node.js服务器主程序一名 待遇 15-25K 联系方式:QQ 34753134 本人技术出身,不太擅长写招聘信息,多多见谅! 详情请直接Q我,有兴趣的技术大神们多多联系我~ 谢谢!

2015年五月7日下午 1:47:14 你们是怎么验证表单的

寻求验证表单的优雅方法。 这是我目前的方法,以express为例子

var required = [/**表单需要接受的字段**/];
var optional = [/**可选字段**/];
var body = req.body;
var tbody = {};
//检测必选字段
required.forEach(function (item,index) {
  if(body[item]){
    tbody.item = xss(body[item]);//附带xss处理
  }else{
    //返回错误信息
  }
});
//检测可选字段
optional.forEach(function (item,index) {
  if(body[item]){
    tbody.item = xss(body[item]);
  }
});
//字段去重,当然,这里用的是express就不需要

//然后各种validator验证类型 巴拉巴拉****
2015年五月7日下午 1:18:42 alsotang commented on issue cnodejs/nodeclub#523
alsotang commented on issue cnodejs/nodeclub#523
@alsotang

html 片段缓存中的【片段】是指渲染出来之后的 html。

2015年五月7日中午 12:04:18 [北京] 招前端伙伴--不讲情怀

出来混的,总是要还的。很抱歉,一直没给cnode社区贡献过什么内容,未来一定改进。

看到各家招人各种福利和卖萌,都不知道该如何介绍我们了。在这里我无法给大家一个描述的非常理想的工作环境,只能说我们在努力,努力让团队里的每一人都觉得加入这个团队是一件非常值得怀念的一段日子。

技术人员选择一家公司一起成长,我个人认为会考虑几件事:

  1. 公司靠谱不?要完全靠谱可以选择BAT,不过也保不齐公司战略一推动,前端面临转方向和转岗问题。作为一家创业公司我无法保证公司一定靠谱,因为中国互联网创业失败比例是很高的,基本达到95%以上。把公司的事业做成一件靠谱的事是我们努力的方向。
  2. 环境如何?个人技术上的成长是每一个技术人对一个技术团队考量的重要指标。现在公司技术人员15人,其中有5年以上工作经验地6人,10年以上工作经验地2人。分别在这几个领域做出过一点成绩:流媒体,CDN技术,大数据处理。一个在技术管理岗位有过10多年管理经验的老技术人员和一个在写过10多年javascript,并在前端和后端都有点技术能力的技术人。团队深知前端的价值和方向,也懂得如何平衡技术,产品,市场。
  3. 薪资福利呢?就不提各种现在标配的招聘福利了,我个人觉得有点虚。15k-40k/月,期望这份薪水还算对得起你的付出。 公司做视频,目前产品线上有tv端,电视盒子,移动端和社区。我们市场目标在海外,努力让世界了解中国。这些都不重要,重要的是你选择了一个有活力和快速增长的团队,还有能给你一个不错的待遇。

现觅几位前端工程师一起加入。只有一点要求:熟悉HTML/CSS/Javascript(或精通其中一项),热爱自己,热爱生活。

我们的项目中小范围使用ES6、CSS3、HTML5、AngularJS、Grunt、Bower。Web Component、Shadow DOM、React Native、Node.js都是我们将要玩的东西。希望你能够善于学习,有梦想,对于技术有一份热爱和执着。个人认为有自己的梦想,并且热爱自己,热爱生活的人在其它方面都不会差。

加分:

  1. 有美感,有自己的思想。
  2. Github有自己的项目或给大型项目贡献过代码
  3. 有过分享
  4. 写过自己的所思所想
  5. 豆瓣书单
  6. 懂交互、设计和细节
  7. 能站在不同的角度看待问题

联系: [mashihua@goline.com](mailto:mashihua@gochinatv.com?subject=前端入伙 来自cnodejs)

报告管理员,markdown编辑器有点问题。

2015年五月7日中午 11:39:49 Lua 学习笔记(六) —— 字符串操作

1 模式

1.1 字符类

字符类代表一组字符。可以用下列组合来表示一个字符类。

组合 代表字母 代表字符类型
x (变量 x) ^$()%.[]*+-?以外的任一字符
. (dot) 任意字符
%a (alphabet) 字母
%b (bracket) 对称字符以及字符间的内容
%c (control) 控制字符(即各类转义符)
%d (digits) 数字
%l (lowercase) 小写字母
%p (punctuation) 标点符号
%s (space) 空格
%u (uppercase) 大写字母
%w (word) 字母和数字
%x (hexadecimal) 十六进制字符
%z (zero) 值为 0 的字符,即 '\0'
%x (变量 x) 字母和数字以外的任一字符

如果组合中的字符写成大写形式(例如将 '%a' 写成 '%A'),相当于对原来所代表的字符类型取补集

例子:

前两行的数字标出每个字符的下标。find函数返回找出第一个符合查找条件的字符的下标。

-----------------00000000001111111112 222222222333333333344444444445555 5
-----------------12345678901234567890 123456789012345678901234567890123 4
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","i")
    --> 6
x = string.find("Tutu is a young man.\n His student number is 20230001.\0",".")
    --> 1
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%a")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%c")    --> 21 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%d")    --> 45 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%l")    --> 2   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%p")    --> 20 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%s")    --> 5   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%u")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%w")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%x")    --> 9   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%z")    --> 54 

() 表示捕捉,find的第三个参数返回被捕捉到的字符串,在这里即返回找到的那个字符。

x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%%)")   --> 1   1   %
x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%#)")   --> 7   7   #
x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%\")")  --> 16  16  "

下句中的 + 表示取一个或多个满足条件的连续字符。

                 --1 2 3 4 5 6 7 8
x,y = string.find("\a\b\f\n\r\t\v\0","%c+")     --> 1   7

上句基本列出了所有控制字符,并不是所有转义符都是控制字符,例如 \\\xff 不属于控制字符。

match 函数返回符合匹配条件的字符子串。

x = string.match("0123456789ABCDEFabcdefg","%x+")   --> 0123456789ABCDEFabcdef

输出的符号即为 %x 所支持的所有字符。

%b 的使用方法与前面的组合形式略有不同,其形式为 %bxy,使用示例如下:

---------------------00000000001111111112 22222222233333333334444444444555555 5
---------------------12345678901234567890 12345678901234567890123456789012345 6
x,y,z = string.find("Tutu is a young man.\n His student number is [20230001].\0","(%b[])")  --> 45  54  [20230001]
x,y,z = string.find("Tutu is a young man.\n His student number is _20230001_.\0","(%b__)")  --> 45  54  _20230001_
x,y,z = string.find("Tutu is a young man.\n His student number is _20230001_.\0","(%b21)")  --> 48  53  230001

1.2 [] 字符集

字符集操作是对字符类组合的一个扩展。可以通过 [] 制定出用户所需的一套字符选取范围。

---------------------0000000001111111111222222222
---------------------1234567890123456789012345678
x,y,z = string.find("[Email]: tangyikejun@163.com","([123])")           --> 22  22  1
x,y,z = string.find("[Email]: tangyikejun@163.com","([l]])")            --> 6   7   l]
x,y,z = string.find("[Email]: tangyikejun@163.com","([1-3])")           --> 22  22  1
x,y,z = string.find("[Email]: tangyikejun@163.com","([^1-3])")          --> 1   1   [
x,y,z = string.find("[Email]: tangyikejun@163.com","([^%d])")           --> 1   1   [
x,y,z = string.find("[Email]: tangyikejun@163.com","([0-9][%d][%d])")   --> 22  24  163
x,y,z = string.find("[Email]: tangyikejun@163.com","([0-9]+)")          --> 22  24  163

使用特点:

  1. 每个字符集仅限定一个字符的范围。
  2. 连字符 - 用于限定字符的范围,值域根据字符在ASCII码中对应的值得出,例如 [0-7] 代表字符范围为 0-7。
    x,y,z = string.find("!\"#$%&0123","([$-1]+)") --> 4 8 $%&01
  3. 添加 ^ 表示对指定的字符范围取补集。[^%d] 等价于 [%D]

1.3 模式项

模式项 作用
+ 匹配1个或多个字符,尽可能多地匹配
- 匹配0个或多个字符,尽可能少地匹配
* 匹配0个或多个字符,尽可能多地匹配
匹配0个或1个字符,尽可能多地匹配

使用特点:

  1. 模式项都是针对前一个字符而言的。例如 abc- 作用于字符 c
---------------------0000000001
---------------------1234567890
x,y,z = string.find("aaaabbbccc","(%a+)")       --> 1   10  aaaabbbccc
x,y,z = string.find("bbbccc","(a+)")            --> nil nil nil
x,y,z = string.find("aaaabbbccc","(ab-c)")      --> 4   8   abbbc
-- x,y,z = string.find("aaaaccc","(ab-c)")      --> 4   5   ac
-- x,y,z = string.find("aaaaccc","(ab*c)")      --> 4   5   ac
-- x,y,z = string.find("aaaabccc","(ab?c)")     --> 4   6   abc
-- x,y,z = string.find("aaaabccc","(ba?c)")     --> 5   6   bc
---------------------000000000111 111111122
---------------------123456789012 345678901
x,y,z = string.find("tangyikejun\0 163.com","(%z%s%w+)")    --> 12  16  
x,y,z = string.find("tangyikejun\0163.com","(%z%d%w+)")     --> nil nil     nil 

注意: \0 后面不能跟数字。而且用 find 返回的匹配字符串无法输出 \0 之后的部分。

1.4 模式

多个模式项组合形成模式

---------------------0000000001111111111222222222
---------------------1234567890123456789012345678
x,y,z = string.find("[Email]: tangyikejun@163.com","^(.%a+)")   -->1    6   [Email
x,y,z = string.find("[Email]: tangyikejun@163.com","(%a+)$")    -->26   28  com

1.5 ()捕捉

捕捉是指将括号内的组合匹配结果保存起来,每个括号保存一个结果。
保存的数据的顺序按照左括号的顺序排列。

x,y,z,h,l = string.find("Tutu is a young man.\n His student number is _20230001_.\0","((%a+%s)(%a+%s)%b__)")    --> 35  54  number is _20230001_    number  is 

字符串模式匹配可参考Lua模式匹配

2 库函数

string.find(s,pattern[,init[,plain]])

查找字符串的子串,如果找到,返回子串的起始位置、结束位置;找不到返回 nil。
如果使用捕获(即对模式串用括号包裹起来),则一并返回匹配得到的字符串。

定义

string.find([字符串],[待查找字符串],[查找起始位置]=1,[禁用模式匹配]=false)

只有显式指定了 init 参数才能控制 plain 参数。

例子

x,y,z = string.find("1001 is a Robot", "Robot")
print(x,y,z)                                --> 11 15   nil
x,y,z = string.find("1001 is a Robot","1%d",1,true)
print(x,y,z)                                --> nil nil nil
x,y,z = string.find("1001 is a Robot","(%d+)",1,false)
print(x,y,z)                                --> 1   2   1001

string.match(s,pattern[,init])

string.find 类似,返回值不一样。string.match 查找字符串的子串,如果找到,返回子串;找不到返回 nil。

支持模式匹配。

定义

例子

x = string.match("1001 is a Robot","001")
print(x)                --> 001                             
x = string.match("1001 is a Robot","%d%d")
print(x)                --> 10      

string.gmatch(s,pattern)

返回一个迭代函数,该函数每执行一次,就返回下一个捕捉到的匹配(如果没有使用捕捉,就返回整个匹配结果)。

例子

for s in string.gmatch("I have a Dream.","%a+") do
    print(s)
end
--> I
--> have
--> a
--> Dream
t = {}
s = "name=tangyikejun, number=20250001"

-- 将捕获的两个子串分别作为键和值放到表t中
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
    t[k] = v
end

-- 输出表t
for k,v in pairs(t) do
    print(k,v)
end

--> name    tangyikejun
--> number  20250001

string.format(formatstring,...)

返回格式化之后的字符串。

定义

例子

string.format("My name is %s", "tangyikejun")   --> My name is tangyikejun

string.len(s)

返回字符串长度

string.lower(s)

返回小写字母的字符串

string.upper(s)

返回大写字母的字符串

string.rep(s,n)

对字符串进行重复

定义

string.rep([字符串],[重复次数])

例子

string.rep("Hello",4)   -- HelloHelloHelloHello

string.reverse(s)

返回反转后的字符串。

string.sub(s,i[,j])

返回子字符串。

定义

string.sub([字符串],[开始字符下标],[结束字符下标]=-1)

例子

x = string.sub("tangyikejun",7)
print(x)                --> kejun
x = string.sub("tangyikejun",1,-6)
print(x)                --> tangyi

string.gsub(s,pattern,repl[,n])

根据模式匹配对字符串中每一个匹配部分都做替换处理,返回替换后的字符串。

定义

string.gsub([字符串],[模式匹配],[替换字符],[最大替换次数] = 无限制)

repl 参数([替换字符])支持 字符串、表、函数。

如果 repl 是字符串,那么该字符串就是用于替换的字符串。同时支持 %n 转义符操作,n 的范围是 0-9。n 范围为 [1,9] 时表示第 n 个捕获的匹配字符串,%0 表示整个匹配的字符串,%% 表示替换为一个 %

如果 repl 是表,那么将捕获的第一个字符串作为键值(Key)进行查询(没有定义捕捉则以整个匹配的字符串为键值),查到的值作为替换的字符串。

如果 repl 是函数,那么每次匹配成功都会调用该函数,并以按序以所有捕捉作为参数传入函数。没有捕捉则以整个匹配的字符作为参数。

如果从表或函数得到是字符串或者是数字,就将其用于替换;如果得到的是 false 或 nil,那么匹配部分将不会发生变化。

例子

repl 为字符串

s = "Never say die."
x = string.gsub(s,"die","never")            --> Never say never.
x = string.gsub(s,"die","'%0'")             --> Never say 'die'.
x = string.gsub(s,"(%a+)%s%a+%s(%a+)","%2") --> die.

限制最大替换次数

s = "never say never."
x = string.gsub(s,"never","Never",1)    --> Never say never.

repl 是表

t = {name="Lua",version="5.1"}
x = string.gsub("$name-$version.tar.gz","$(%a+)",t) --> Lua-5.1.tar.gz

repl是函数

x = string.gsub("4+5 = $return 4+5$","%$(.-)%$",function(s)return loadstring(s)() end)  --> 4+5 = 9
x = string.gsub("23+45=$result", "((%d+)%+(%d+)=)%$%a+", function (s,a,b)
    sum = a+b
    return s..sum
end)    --> 23+45=68

~~注意:似乎只支持匿名函数。~~

从表或函数返回的是 false 或 nil

x = string.gsub("4+5 = $return 4+5$","%$(.-)%$",function(s)return nil end)  --> 4+5 = $return 4+5$
t = {name="Lua",version=false}
x = string.gsub("$name-$version.tar.gz","$(%a+)",t) --> Lua-$version.tar.gz

string.byte(s[,i[,j]])

返回字符的 ASCII 码值。

定义

string.byte([字符串],[起始下标]=1,[结束下标]=[起始下标])

例子

x,y,z = string.byte("abc",2)    --> 98  nil nil
x,y,z = string.byte("abc",1,3)  --> 97  98  99

string.char(...)

根据传入的 ASCII 编码值([0-255])得到对应的字符,传入多少编码值就返回多长的字符串。

例子

x = string.char(98,99,100)  --> bcd

如果输入字符超限会编译报错。

string.dump(function)

返回函数的二进制表示(字符串形式),把这个返回值传给 loadingstring 可以获得函数的一份拷贝(传入的函数必须是没有上值的 Lua 函数)。

例子

function sum(a,b)
    return a + b
end

s = string.dump(sum)
x = loadstring(s)(4,4) -- 8

参考链接

Lua模式匹配(参考了此文中对 %b 的使用)

2015年五月7日上午 11:07:33 Stream 中间件框架

对 Transform 流的数据进行处理,目前用于抓取数据后的过滤。 利用 ES6 的 Generator Function,类似 koa 的中间件机制。 https://github.com/tvrcgo/hq

2015年五月7日上午 10:28:52 【上海五角场】减肥健身App-练练 招募Node.js小伙伴!~团队欢乐,待遇棒棒。被看好的创业公司,欢迎你加入

岗位名称:Node.js高级开发工程师

工作职责:

  1. 负责服务器后端开发
  2. 负责MongoDB、MySql配置

任职要求:

  1. 2年及以上工作经验,全日制本科学历,计算机及通讯技术相关专业。
  2. 精通Node开发
  3. 熟悉MongoDB Replication和Sharding
  4. 有丰富服务器开发经验
  5. 适应性强,能迅速融入团队,抗压性高。

优先考虑条件:

  1. 熟悉设计模式
  2. 熟悉敏捷开发
  3. 熟悉Restful API设计
  4. 熟悉开源项目,并有参与经历

关于我们: 我们是一家初创移动互联网公司,目前已有一款名为”练练-定制你的专属减肥健身方案”的APP,Android和iOS版本均已上线,超过60万的用户在跟我们一起,没事练练。 我们希望在健身行业拥有自己的一片天地,改变运动习惯,让全民一起练练。团队虽不大,但配置俱全,帅哥萌妹,程序猿,程序媛,应有尽有。我们有激情,有想法,有节操,能吐槽。 我们离成功其实只差4步,一步A轮,二步B轮,三步C轮,四步IPO……然后就可以分钱了(打我可以,不要打脸)。 小白人是我们的吉祥物,用萌系丰富运动,用萌系改变生活,期待你的加入!

我们可以提供: 一份对得起你水平的薪酬 正规的五险一金 轻松、舒适的工作环境 每人都有肾5、肾6做测试机 有独立的办公区域,坐落于魔都五角场,美食外卖停不下来。 同事精挑细选,没有猪一样的队友,不怕神一样的对手。

感兴趣的小伙伴可以发送简历至270558403@qq.com 成就1.4.png 启动页.jpg 计时器.jpg

2015年五月7日上午 10:20:12 alsotang commented on issue cnodejs/nodeclub#523
alsotang commented on issue cnodejs/nodeclub#523
@alsotang

暂时还是不要了。现在这方面的性能问题也不大。 按照这篇文章来说,对于这类场景我还是倾向于 html 片段缓存:https://cnodejs.org/topic/55210d88c4f5240812f55408

2015年五月7日上午 9:57:22 请问怎么利用v8.log来分析node写的服务的性能呢?

目前我知道关于v8.log的一切,就是node --prof app.js,然后会生成一个v8.log,但是这文件里一堆东西,看不懂。。。哪里有什么资料可以找,或者工具可以分析吗?谢谢~

2015年五月7日凌晨 12:38:31 关于 node 爬虫问题

最近在看<包教不包会>教程,遇到上一些问题不太明白

  1. 为什么会有时能爬到数据,有时却爬不到数据呢?
  2. async.mapLimit 这个接口有点疑问?这个是每爬完一条 url 就返回的结果数据吗?如果是,那么我怎样才能知道所有数据已爬完毕呢?

代码如下:

var express = require('express');
var superagent = require('superagent');
var cheerio = require('cheerio');
var url = require('url');
var async = require('async');

var app = express();

app.get('/', function(req, res){
  var targetUrl = 'https://cnodejs.org';
  superagent.get(targetUrl).end(function(err, html){
    if(err){
      console.error('urls: ', err);
    }

    var $ = cheerio.load(html.text);
    var urls = [];
    // 获取所有链接
    $('#topic_list .topic_title').each(function(key, value){
      var $obj = $(value);
      var href = $obj.attr('href');
      var link = url.resolve(targetUrl, href)

      urls.push(link);
    });

    // 定义当前并发数
    var concurrencyCount = 0;
    function fetchUrls(url, callback){
      // 抓取单条主题的内容
      var topic = {};
      superagent.get(url).end(function(err, thtml){
        var $ = cheerio.load(thtml);
        var title = $('#content .topic_header .topic_full_title').text().trim();
        var content = $('#content .topic_content .markdown-text').text().trim();
        var link = url;

        topic = {
          "title" : title,
          "content" : content,
          "link" : link
        };
      });

      // 随机间隔时间
      var delay = parseInt((Math.random() * 10000000) % 3000, 10);
      concurrencyCount++;
      console.log('当前并发数是'+ concurrencyCount +', 正在抓取的是'+ url +', 耗时'+ delay + '毫秒.');

      setTimeout(function(){
        concurrencyCount--;
        callback(null, topic);
      }, delay);
    }

    // 控制并发数
    async.mapLimit(urls, 3, function(url, callback){
      fetchUrls(url, callback);
    }, function(err, result){
      if(err){
        console.error('final: ', err);
      }

      console.log('final: ', result);
      res.send(result);
    });
  });

});

app.listen(4000, function(){
  console.log('hello cnodejs.');
});

谢谢你看到这里.

2015年五月7日凌晨 12:28:07 构造url参数上的gbk编码

最近有一个需求,需要组装url进行接口调用,但是url上其中一个参数需要通过gbk编码,知道nodejs在utf8的编码比较麻烦,找了iconv-lite模块可以转化参数,但转化出来变成 QQ截图20150507003059.png 想问问各位有什么方案可以解决。

2015年五月6日晚上 8:21:08 alsotang commented on issue cnodejs/nodeclub#522
alsotang commented on issue cnodejs/nodeclub#522
@alsotang

在 render 的时候应该是可以指定 layout 的吧?特定页面指定一下就好了。 不过还是不太懂你问的是什么。 2015-05-06 16:25 GMT+08:00 stiyes notifications@github.com: 我的已经是,模板文件layout.html,再加几个模板…

2015年五月6日晚上 7:56:10 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日晚上 7:55:36 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年五月6日晚上 7:54:17 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日下午 5:45:19 koa有什么优势么?昨天压测了下,结果略失望

node版本为0.11.14,以http和koa分别写简单server服务(输出hello world),高并发高请求下,http的qps要比koa高100左右(例如c300n10000时,http的qps为1623,koa为1527;c500n15000时,http的qps为1288,koa为1152)。且单个请求的平均响应时间,http的要比koa的快。 之前把一个服务重新用koa写了下,压测的时候发现qps反而降低了。。。打击啊:(

2015年五月6日下午 5:04:23 8 个你可能不知道的 Docker 知识

Docker 这个工具已经出现很长一段时间了,但是可能还有很多人对 Docker 的概念不太清楚,因此这次翻译 8 个你可能不知道的 Docker 知识 这篇文章,和大家介绍一下生产环境中的 Docker 用例。

自从上世纪 90 年代硬件虚拟化被主流的技术广泛普及之后,对数据中心而言,发生的最大的变革莫过于容器和容器管理工具,例如:Docker。在过去的一年内,Docker 技术已经逐渐走向成熟,并且推动了大型初创公司例如 Twitter 和 Airbnb 的发展,甚至在银行、连锁超市、甚至 NASA 的数据中心都赢得了一席之地。当我几年前第一次直到 Docker 的时候,我还对 Docker 的未来持怀疑的态度,我认为他们是把以前的 Linux 容器的概念拿出来包装了一番推向市场。但是使用 Docker 成功进行了几个项目 例如 Spantree 之后,我改变了我的看法:Docker 帮助我们节省了大量的时间和经历,并且已经成为我们技术团队中不可或缺的工具。

Github 上面每天都会催生出各式各样的工具、形态各异的语言和千奇百怪的概念。如果你和我一样,没有时间去把他们全部都测试一遍,甚至没有时间去亲自测试 Docker,那么你可以看一下我的这篇文章:我将会用我们在 Docker 中总结的经验来告诉你什么是 Docker、为什么 Docker 会这么火。

Docker 是容器管理工具

Docker 是一个轻量级、便携式、与外界隔离的容器,也是一个可以在容器中很方便地构建、传输、运行应用的引擎。和传统的虚拟化技术不同的是,Docker 引擎并不虚拟出一台虚拟机,而是直接使用宿主机的内核和硬件,直接在宿主机上运行容器内应用。也正是得益于此,Docker 容器内运行的应用和宿主机上运行的应用性能差距几乎可以忽略不计。
但是 Docker 本身并不是一个容器系统,而是一个基于原有的容器化工具 LXC 用来创建虚拟环境的工具。类似 LXC 的工具已经在生产环境中使用多年,Docker 则基于此提供了更加友好的镜像管理工具和部署工具。

Docker 不是虚拟化引擎

Docker 第一次发布的时候,很多人都拿 Docker 和虚拟机 VMware、KVM 和 VirtualBox 比较。尽管从功能上看,Docker 和虚拟化技术致力于解决的问题都差不多,但是 Docker 却是采取了另一种非常不同的方式。虚拟机是虚拟出一套硬件,虚拟机的系统进行的磁盘操作,其实都是在对虚拟出来的磁盘进行操作。当运行 CPU 密集型的任务时,是虚拟机把虚拟系统里的 CPU 指令“翻译”成宿主机的CPU指令并进行执行。两个磁盘层,两个处理器调度器,两个操作系统消耗的内存,所有虚拟出的这些都会带来相当多的性能损失,一台虚拟机所消耗的硬件资源和对应的硬件相当,一台主机上跑太多的虚拟机之后就会过载。而 Docker 就没有这种顾虑。Docker 运行应用采取的是“容器”的解决方案:使用 namespace 和 CGroup 进行资源限制,和宿主机共享内核,不虚拟磁盘,所有的容器磁盘操作其实都是对 /var/lib/docker/ 的操作。简言之,Docker 其实只是在宿主机中运行了一个受到限制的应用程序。

从上面不难看出,容器和虚拟机的概念并不相同,容器也并不能取代虚拟机。在容器力所不能及的地方,虚拟机可以大显身手。例如:宿主机是 Linux,只能通过虚拟机运行 Windows,Docker 便无法做到。再例如,宿主机是 Windows,Windows 并不能直接运行 Docker,Windows上的 Docker 其实是运行在 VirtualBox 虚拟机里的。

Docker 使用层级的文件系统

前面提到过,Docker 和现有容器技术 LXC 等相比,优势之一就是 Docker 提供了镜像管理。对于 Docker 而言,镜像是一个静态的、只读的容器文件系统的快照。然而不仅如此,Docker 中所有的磁盘操作都是对特定的Copy-On-Write文件系统进行的。下面通过一个例子解释一下这个问题。
例如我们要建立一个容器运行 JAVA Web 应用,那么我们应该使用一个已经安装了 JAVA 的镜像。在 Dockerfile(一个用于生成镜像的指令文件)中,应该指明“基于 JAVA 镜像”,这样 Docker 就会去 Docker Hub Registry 上下载提前构建好的 JAVA 镜像。然后再 Dockerfile 中指明下载并解压 Apache Tomcat 软件到 /opt/tomcat 文件夹中。这条命令并不会对原有的 JAVA 镜像产生任何影响,而仅仅是在原有镜像上面添加了一个改动层。当一个容器启动时,容器内的所有改动层都会启动,容器会从第一层中运行 /usr/bin/java 命令,并且调用另外一层中的 /opt/tomcat/bin 命令。实际上,Dockerfile 中每一条指令都会产生一个新的改动层,即便只有一个文件被改动。如果用过 Git 就能更清楚地认识这一点,每条指令就像是每次 commit,都会留下记录。但是对于 Docker 来说,这种文件系统提供了更大的灵活性,也可以更方便地管理应用程序。
我们Spantree的团队有一个自己维护的含有 Tomcat 的镜像。发布新版本也非常简单:使用 Dockerfile 将新版本拷贝进镜像从而创建一个新镜像,然后给新镜像贴上版本的标签。不同版本的镜像的不同之处仅仅是一个 90 MB 大小的 WAR 文件,他们所基于的主镜像都是相同的。如果使用虚拟机去维护这些不同的版本的话,还要消耗掉很多不同的磁盘去存储相同的系统,而使用 Docker 就只需要很小的磁盘空间。即便我们同时运行这个镜像的很多实例,我们也只需要一个基础的 JAVA / TOMCAT 镜像。

Docker 可以节约时间

很多年前我在为一个连锁餐厅开发软件时,仅仅是为了描述如何搭建环境都需要写一个 12 页的 Word 文档。例如本地 Oracle 数据库,特定版本的 JAVA,以及其他七七八八的系统工具和共享库、软件包。整个搭建过程浪费掉了我们团队每个人几乎一天的时间,如果用金钱衡量的话,花掉了我们上万美金的时间成本。虽然客户已经对这种事情习以为常,甚至认为这是引入新成员、让成员适应环境、让自己的员工适应我们的软件所必须的成本,但是相比较起来,我们宁愿把更多的时间花在为客户构建可以增进业务的功能上面。

如果当时有 Docker,那么构建环境就会像使用自动化搭建工具 Puppet / Chef / Salt / Ansible 一样简单,我们也可以把整个搭建时间周期从一天缩短为几分钟。但是和这些工具不同的地方在于,Docker 可以不仅仅可以搭建整个环境,还可以将整个环境保存成磁盘文件,然后复制到别的地方。需要从源码编译 Node.js 吗?Docker 做得到。Docker 不仅仅可以构建一个 Node.js 环境,还可以将整个环境做成镜像,然后保存到任何地方。当然,由于 Docker 是一个容器,所以不用担心容器内执行的东西会对宿主机产生任何的影响。

现在新加入我们团队的人只需要运行 docker-compose up 命令,便可以喝杯咖啡,然后开始工作了。

Docker 可以节省开销

当然,时间就是金钱。除了时间外,Docker 还可以节省在基础设施硬件上的开销。高德纳和麦肯锡的研究表明,数据中心的利用率在 6% - 12% 左右。不仅如此,如果采用虚拟机的话,你还需要被动地监控和设置每台虚拟机的 CPU 硬盘和内存的使用率,因为采用了静态分区(static partitioning)所以资源并不能完全被利用。。而容器可以解决这个问题:容器可以在实例之间进行内存和磁盘共享。你可以在同一台主机上运行多个服务、可以不用去限制容器所消耗的资源、可以去限制资源、可以在不需要的时候停止容器,也不用担心启动已经停止的程序时会带来过多的资源消耗。凌晨三点的时候只有很少的人会去访问你的网站,同时你需要比较多的资源执行夜间的批处理任务,那么可以很简单的便实现资源的交换。

虚拟机所消耗的内存、硬盘、CPU 都是固定的,一般动态调整都需要重启虚拟机。而用 Docker 的话,你可以进行资源限制,得益于 CGroup,可以很方便动态调整资源限制,让然也可以不进行资源限制。Docker 容器内的应用对宿主机而言只是两个隔离的应用程序,并不是两个虚拟机,所以宿主机也可以自行去分配资源。

Docker 有一个健壮的镜像托管系统

前面提到过,这个托管系统就叫做 Docker Hub Registry。截止到 2015年4月29日,互联网上大约有 14000 个公共的 Docker,而大部分都被托管在 Docker Hub 上面。和 Github 已经很大程度上成为开源项目的代表一样,Docker 官方的 Docker Hub 则已经是公共 Docker 镜像的代表。这些镜像可以作为你应用和数据服务的基础。
也正是得益于此,你可以随意尝试最新的技术:说不定有些人就把图形化数据库的实例打包成了 Docker 镜像托管在上面。再例如 Gitlab,手工搭建 Gitlab 非常困难,译者不建议普通用户去手工搭建,而如果使用 Docker Gitlab,这个镜像则会五秒内便搭建完成。再例如特定 Ruby 版本的 Rails 应用,再例如 Linux 上的 .NET 应用,这些都可以使用简单的一条 Docker 命令搭建完成。

Docker 官方镜像都有 official 标签,安全性可以保证。但是第三方镜像的安全性无法保证,所以请谨慎下载第三方镜像。生产环境下可以只使用第三方提供的 Dockerfile 构建镜像。

Docker Github 介绍:5秒内搞定一个 Gitlab

关于 Linux 上的 .NET 应用和 Rails 应用,Segmenfault 社区将会在以后的文章中做详细介绍。

Docker 可以避免产生BUG

Spantree 一直是“固定基础设置”(immutable infrastructure)的狂热爱好者。换句话说,除非有心脏出血这种漏洞,我们尽量不对系统做升级,也尽量不去改变系统的设置。当添加新服务器的时候,我们也会从头构建服务器的系统,然后直接将镜像导入,将服务器放入负载均衡的集群里,然后对要退休的服务器进行健康检查,检查完毕后移除集群。得益于 Docker 镜像可以很轻松的导入导出,我们可以最大程度地减少因为环境和版本问题导致的不兼容,即便有不兼容了也可以很轻松地回滚。当然,有了 Docker,我们在生产、测试和开发中的运行环境得到统一。以前在协同开发时,会因为每个人开发的电脑配置不同而导致“在我的电脑上是能运行的,你的怎么不行”的情况,而如今 Docker 已经帮我们解决了这个问题。

Docker目前只能运行在 Linux 上

前面也提到过,Docker 使用的是经过长时间生产环境检验的技术,虽然这些技术已经都出现很长时间了,但是大部分技术都还是 Linux 独有的,例如 LXC 和 Cgroup。也就是说,截止到现在,Docker 容器内只能在 Linux 上运行 Linux 上的服务和应用。Microsoft 正在和 Docker 紧密合作,并且已经宣布了下一个版本的 Windows Server 将会支持 Docker 容器,并且命名为 Windows Docker,估计采用的技术应该是Hyper-V Container,我们有望在未来的几年内看到这个版本。
除此之外,类似 boot2docker 和 Docker Machine 这种工具已经可以让我们在 Mac 和 Windows 下通过虚拟机运行 Docker 了。

后记

悄悄的说一句,前文中提到的 Docker 安装Docker 操作DockerfileDocker Hub、搭建 Rails 环境、甚至搭建 .NET 环境,SegmentFault 正在组织编写相关的文档,欢迎关注我们,及时获取更多最新的教程。

2015年五月6日下午 4:27:46 改写了memwatch的代码,支持v0.11之后的nodejs

具体位置是 https://github.com/crystaldust/node-memwatch/tree/higher-than-v10

npm 安装: npm install memwatch@git://github.com/crystaldust/node-memwatch.git#higher-than-v10

小弟没怎么弄过v8下的编程,所以也是一边找资料一边学习一边修改的。欢迎大家测试拍砖。附上截图: memwatch.jpg

memwatch2.jpg

2015年五月6日下午 4:00:21 websocket和HTTP使用不同端口可以吗

比如:HTTP监听端口80,websocket想分离到专门的服务器上监听端口6180 浏览器端会存在安全限制吗?

2015年五月6日下午 3:53:49 要记录用户点击不同链接的次数,是每次用户点击都去插入数据库还是有什么策略?

要记录用户点击不同链接的次数,是每次用户点击都去插入数据库还是有什么策略?

2015年五月6日下午 3:48:33 惊爆!Node.js 和 io.js 准备合并

因为对Node.js管理方Joyent公司不满,多位核心开发者自创门户建立了分支io.js,其开发非常活跃,甚至刚刚发布了 io.js 2.0.0。 而如今,不到半年时间,两个项目突然就化敌为友了。两个互相竞争的项目如今正在Node.js基金会的名义下准备合并,合并完成之后github.org/iojs项目的所有权将转移到Node.js基金会,iojs.org 和nodejs.org域名的所有权以及相关社交媒体账号也都将转移给Node.js基金会。

原文:http://www.solidot.org/story?sid=43951

2015年五月6日下午 3:06:36 用async并发执行几个函数,同时写入一个全局的变量,是否保证有序?

事情是这样的,产品的同事希望在一个用户列表中,按照不同的标准选取几类用户(假设有A, Bl两种用户),然后同时呈现。但是有些用户可能同时满足2个标准,比如某个用户既符合A标准,也符合B标准,这样返回的结果如果不加限定,就会出现同一个用户出现多次的情况。

现在我是这么考虑的,先去找A类的用户,查找结束后,把A类用户的_id传递给第二个函数,第二个函数在查找B类用户时候,把第所有A类用户的_id都剔除掉。程序结构大概是这样:



var exclude = [];

async.series( [
    function( callback ) {
        db.collection('users').find( { /*category A的条件*/} ).toArray( function( err, users ) {
            users.forEach( function( user ) {
                exclude.push( user._id );
            } );
            callback( null, users );
        } )
    },

    function( callback ) {
        db.collection('users').find( { /*category B的条件*/ _id : { $not : { $in : exclude } }/*限定不包含category A的用户*/ } ).toArray( function( err, users ) {
            callback( null, users );
        } )
    }

], function( err, result ) {
    var users_A = result[0];
    var users_B = result[1];
    // Handle category A and B...   
} )

为了保证不重复,需要A执行完后在执行B。顺序执行,响应时间太长。如果改成并发执行,那么每个函数去查找用户时,都要包含{$not : { $in : exclude } }条件。关键是是否会出现这样一种情况: 第一个函数先执行回调,然后向exclude写入id,还没有写完时,第二个函数执行回调了,但是exclude还是空的,那么exclude的限定在第二个函数里其实就没有作用了。

小弟表达能力较差,说的不清楚的地方,还请大家指出,期待各路高手给点儿意见啊,跪谢先 m( _ _ ) m

2015年五月6日下午 3:04:45 哪位大神 帮忙指点 uglifyjs 怎么批量压缩

已安装 1、node.js 2、uglifyjs

2015年五月6日下午 2:37:25 求bower转spm的回答

项目最近因为变动,计划从bower转spm。毕竟方便很多。但是项目本身bower 的很多文件在spm上没有。大神们怎么解决的? 直接复制过去,然后依次放到spmmodules的组件对应的版本目录,直接引用还是?对spm机制不太了解。特来求教。 引用的包有点多=,= 求解答。

"angular": "~1.3.11",
"angular-animate": "~1.3.11",
"angular-cookies": "~1.3.11",
"angular-resource": "~1.3.11",
"angular-sanitize": "~1.3.11",
"angular-touch": "~1.3.11",
"angular-translate": "~2.5.2",
"angular-translate-loader-static-files": "~2.5.2",
"angular-translate-storage-cookie": "~2.5.2",
"angular-translate-storage-local": "~2.5.2",
"angular-bootstrap": "~0.12.0",
"angular-bootstrap-nav-tree": "*",
"angular-ui-router": "~0.2.11",
"angular-ui-utils": "~0.2.1",
"angular-file-upload": "~1.1.1",
"angular-ui-select": "~0.8.3",
"angular-ui-calendar": "latest",
"angular-ui-grid": "~3.0.0-rc.16",
"angular-xeditable": "~0.1.8",
"angular-smart-table": "~1.4.9",
"angularjs-toaster": "~0.4.8",
"ng-grid": "~2.0.13",
"ngImgCrop": "~0.2.0",
"ngstorage": "~0.3.0",
"oclazyload": "~0.5.1",
"textAngular": "~1.2.2",
"venturocket-angular-slider": "~0.3.2",
"videogular": "~0.7.0",
"videogular-controls": "~0.7.0",
"videogular-buffering": "~0.7.0",
"videogular-overlay-play": "~0.7.0",
"videogular-poster": "~0.7.0",
"videogular-ima-ads": "~0.7.0",
"jquery": "~2.1.3",
"animate.css": "~3.2.0",
"bootstrap": "~3.3.0",
"bootstrap-filestyle": "~1.1.2",
"bootstrap-slider": "*",
"bootstrap-touchspin": "~3.0.1",
"bootstrap-wysiwyg": "*",
"bower-jvectormap": "~1.2.2",
"bootstrap-chosen": "~1.0.0",
"chosen": "https://github.com/harvesthq/chosen/releases/download/v1.3.0/chosen_v1.3.0.zip",
"datatables": "~1.10.4",
"plugins": "datatables/plugins#~1.0.1",
"footable": "~2.0.3",
"font-awesome": "~4.2.0",
"fullcalendar": "~2.2.6",
"html5sortable": "*",
"moment": "~2.8.3",
"nestable": "*",
"screenfull": "~1.2.1",
"slimscroll": "~1.3.3",
"simple-line-icons": "~0.1.1",
"jquery_appear": "~0.3.3",
"jquery.easy-pie-chart": "~2.1.6",
"jquery.sparkline": "~2.1.2",
"flot": "~0.8.3",
"flot.tooltip": "~0.8.4",
"flot.orderbars": "*",
"bootstrap-daterangepicker": "~1.3.17",
"bootstrap-tagsinput": "~0.4.2"
2015年五月6日下午 1:56:44 在本地页面通过一个按钮调用系统linux命令

在本地页面通过一个按钮调用系统linux命令,初学node不知道怎么用 一下是一段调用系统命令的node代码,单独用 node test.js 命令测试正常,现在我想加进一个本地的HTML代码中,请问怎么添加? var exec = require(‘child_process’).exec;

exec("python test.py", function(error, stdout, stderr){ if ( !error ) { console.log(stdout); } else { console.log(error); } });

2015年五月6日下午 12:44:09 正则表达式笔记(三)

String.replace

细心的读者可能会发现,上篇文章我们遗漏了 String.replace 这个方法。String.replace 在 JS 中有着更加强大的用法和灵活性,所以这里剥离出来单独介绍。

API

String.replace(searchValue, replacement)

String.replace 同时支持进行正则表达式或者字符串替换,并返回一个新的字符串。因为我们的主题是正则表达式,所以接下来的内容,会以正则替换的情况为主。

默认情况下,String.replace只进行一次替换。若设置了 g 模式,则所有匹配到的字符串都会替换

参数说明

用法

字符串替换

'I am back end developer'.replace('back','front');
//"I am front end developer"

直接把字符串中的 back 替换成 front。当字符串中有两个 back,情况回事怎样呢?

'I am back end developer, you are back end developer'.replace('back','front');
//"I am front end developer, you are back end developer"

可以看到,第2个 back,并没有被替换。如果需要把其他 back 也一起替换,这个时候就需要用到正则表达式。

正则表达式替换

设置了 g 模式,全局替换。

'I am back end developer, you are back end developer'.replace(/back/g,'front');
//"I am front end developer, you are front end developer"

replacement 字符串中,还有一些特殊的变量可以使用。

特殊变量 说明
$1,$2,$3...$n 表示对应捕获分组匹配的文本
$& 与正则相匹配的字符串
$$ '$' 字符
$` 匹配字符串左边的字符
$' 匹配字符串右边的字符

有趣的字符串替换

使用 $& 操作匹配的字符串。

var str = '有趣的字符串替换';
str.replace(/有趣的字符串/,'($&)');

//"(有趣的字符串)替换"

使用 $$ 声明 $ 字符。

var str = '这个商品的价格是12.99';
str.replace(/\d+\.\d{2}/,'$$$&');

//"这个商品的价格是$12.99"

使用 $` 和 $' 字符替换内容

'abc'.replace(/b/,"$`");//aac
'abc'.replace(/b/,"$'");//acc

使用分组匹配组合新的字符串

'2015-05-06'.replace(/(\d{4})-(\d{2})-(\d{2})/,"$3/$2/$1")
//"06/05/2015"

函数参数

replacement是一个函数参数的时候,对字符串操作的灵活性将有一个质的提高。

说明

'Stirng.replace'.replace(/(\w+)(\.)(\w+)/,function(){
    console.log(arguments) // ["Stirng.replace", "Stirng", ".", "replace", 0, "Stirng.replace"]
    return '返回值会替换掉匹配到的字符'
})
参数 说明
match 匹配到的字符串(此例为 String.replace)
p1, p2, ... 正则使用了分组匹配时分组文本,否则无此参数(此例为 "Stirng", ".", "replace")
offset 匹配字符串的对应索引位置 (此例为 0)
source 原始字符串(此例为 String.replace)

案例 -- 样式属性的转换

把驼峰字符转换为 - 连接符形式

'borderTop'.replace(/[A-Z]/g,function(m){
    return '-'+ m.toLowerCase()
})

//"border-top"

- 连接符形式转换为驼峰形式

'border-top'.replace(/-\w/g,function(m){
    return m.slice(1).toUpperCase()
})

//"borderTop"

最后的牛刀小试

交给阅读者发挥的内容:

需要将Hello-World使用正则替换成 HWeolrllod

2015年五月6日下午 12:34:48 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日下午 12:33:48 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日中午 12:08:31 推荐一款可以在iPhone和iPad上读nodejs官方文档的app
专门为nodejs doc开发的,比dash的阅读体验更好,比如代码的高亮,链接的处理等。目前只支持iOS8
下载地址

https://itunes.apple.com/cn/app/nodejs-api/id983630798?mt=8

下载.png

页面截图

首页

api 详情

分类

2015年五月6日中午 11:58:58 [产品更新] SegmentFault 全新文章专栏上线

经过半个多月的努力,全新的 SegmentFault 文章终于和大家见面了。这次的全新改版,我们提出了文章专栏的理念:

  1. 所有用户都可以拥有自己的专栏
  2. 将更加注重和鼓励原创且具有启发性的用户内容
  3. 对相关的声望值做出调整

关于原创和启发性

原创的文章是自己学习和探索的结果,独立的思考会给他人更大的启发,会引导他人去发现、实现可能更加有趣的事。

故此,我们将更加注重和鼓励原创:

新版文章将在专栏设置页增加版权信息注明,原创文章将不再受版权问题的困扰。同时,专栏的首篇文章也将会被官方审阅,这也是对于原创优秀内容的鼓励。

版权信息设置.jpg

私人的,也是所有人的专栏

新版文章所有用户都将可以开始撰写自己的专栏:已经开通过博客的老用户,博客将自动升级为的专栏;还没有开通博客的用户,现在可以直接开通专栏了。

我们后续会推出官方认证专栏,如果你的文章写得足够好,会出现在网站所有用户的时间线上,这便是私人的、也是所有人的专栏。

相关声望值调整

新版文章的声望值权重也会相应作出上调:文章被投推荐票 +10,文章被收藏 +5。另外,如果你的文章被推作官方推荐文章或者精选文章,都会有相应的声望值增加,当然,推荐与加精更多是对你内容价值的认可。



其他更多的细节与优化,由你们来发现。

这是一个简易的技术专栏,是学习与分享知识的工具。这将是你的私人文章,请认真地撰写;这也将会是所有人的文章,热心地与大家切磋共享。


相关的产品建议和反馈提交请到我们的社区建设:0x.sf.gg
相关的技术 bug 提交请到我们的 GitHub Report:SegmentFault Report

2015年五月6日中午 11:51:44 Cnode社区回复操作是否为bug?

我在一个话题下添加回复完毕后,想跳回上一页页面,第一次点击浏览器的回退的按钮,却回不到上一页面,第二次点击就可以,是故意这样设计的还是?我是小白,请大神指点

2015年五月6日中午 11:44:28 寻求一种方式重启node的方式

虽然说生产环境老是重启不是很安全,但每次更新版本时都需要找PE来重启node,感觉好麻烦,有没有类似LAMP那种方式,比如我修改完php代码上线后能立即运行,nodemon启动方式不算。求大神们指点迷津。

2015年五月6日中午 11:41:58 alsotang commented on commit cnodejs/nodeclub@0f6cc14f6b
alsotang commented on commit cnodejs/nodeclub@0f6cc14f6b
@alsotang

是的 2015-05-06 11:37 GMT+08:00 Jackson Tian <notifications@github.com>:

2015年五月6日上午 11:01:32 [北京] NodeJS 前端后端开发攻城狮1名 8K-16K

【关于我们】 公司成立于14年底,核心团队成员来自IBM,联想,百度,并具有10年以上的丰富经验。现从事校园生活相关的移动互联网项目,因团队高速发展需要,急招有志于用技术改变生活的nodejs 前后端攻城狮1名

【任职要求】 Requirements

* Strong Javascript skills
* Knowledge of Node.js packages (Express, Async, Mongoose, Socket.io, Request, etc.)
* Experience with message and job queuing services (RabbitMQ, Redis, etc.)
* Very strong ability to design and develop scalable systems on top of Node.js
* Experience working with MongoDB, Mysql and Redis.
* Disciplined approach to testing and quality assurance, knowledge of Javascript testing tools.
* High understanding of computer science concepts such as: common data structures and algorithms, profiling/optimization

Responsibilities

* Build and deploy robust, manageable and scalable back ends
* Integrate 3rd party services via RESTful and streaming APIs
* Design and implement RESTful interfaces that exposes our data
* Rapidly fix bugs and solve problems
* Work closely with front-end teams to create optimally integrated solutions
* Plus Point:  have experience ionic framework and wechat JS SDK

【关于职位】 NodeJS 前端&后端开发工程师 工作地点:北京.海淀.上地 工作年限:3年以上 最低学历:本科 招聘人数:1 职位月薪:¥8 ,000 - 16,000 英语:读写流利,英语六级

【联系我们】 有意者请将您的简历直接直接发送至hr@guagua2shou.com,我们会尽快回复您,谢谢!

2015年五月6日上午 9:52:37 nodejs对http协议的支持

http协议的method 安全方法:HEAD, GET, OPTIONS, and TRACE 不安全的方法:POST, PUT and DELETE 测试了一下发现其中的Trace方法返回error; 想问下各位大神。这个trace方法会返回什么。怎么开启看看。

2015年五月6日上午 9:36:35 在阿里云使用npm安装pm2一直加载

untitled1.png

2015年五月6日早上 8:37:59 io.js前天发布了2.0.0版本

io.js v2.0.0 changelog 大版本变化。 更稳定了么?

2015年五月6日早上 7:10:15 [成都]GamEver招2名手游服务器端Nodejs工程师7~15k

关于我们(热血,专注,快乐,成长,勇敢,创造力)

我们是一群对游戏非常热爱,专注于制作精品的单纯家伙 多年的打磨学会了在商业和艺术之间寻找平衡 平均从业经验5年以上, 聚焦海外市场 行业顶尖美术和金牌制作人,资深欧美制作经验 产品专注三个核心:Creative, Passionate, Imapctive

公司成立于2014年1月,规模25人,现在已有一款上市产品Age of warriors,(中国区暂时还没上线)获得了美国区苹果APPSTORE三次全球首页推荐。

我们期待的伙伴: 3d客户端程序:

至少熟悉一种3d游戏引擎。 熟悉openGL图形管线,能独立学习制作游戏需要的shader效果 有扎实的c++编码功底 爱好技术,踏实好学,对游戏有爱

服务器程序:

踏实靠谱,热爱游戏 不需要您一定要是技术大牛,只要你基础扎实愿意接受新技术,快速学习 1~2年编码经验,熟悉nodejs,mongodb,或者python,ruby,mysql,erlang 团队协作精神,自我管理良好,有工程师文化基因。 善于沟通,有同理心

系统/数值/UI策划:

踏实靠谱,心态开放成熟,渴望做出富有灵魂的作品 有激情,有动力,平时热衷于各种平台的游戏 游戏经验丰富,了解各类游戏的特色与设计思路 熟悉玩家的消费行为以及游戏行为,喜欢和玩家一起交流 良好的语言沟通和书面表达能力(熟悉策划案撰写,意图表达明确,条理清晰) 严密谨慎的逻辑能力,对RPG以及SLG类游戏数值平衡有独特兴趣和偏好 或者擅长UI设计,熟悉至少一种动画关卡编辑器 有较好的英文阅读和书面日常交流能力

游戏文案策划:

沟通能力好,主动性责任心强。 有激情,有动力,平时热衷于各种平台的游戏。 拥有游戏经验丰富,了解各类游戏的特色与设计思路 。 熟悉玩家的群体,喜欢和玩家一起交流。 良好的语言沟通和书面表达能力(能熟练撰写有想象力的文案剧情和幽默的人物对白)。 喜欢看美剧,看小说,熟悉玩家语言。

具有较好的英文阅读写作能力。

游戏UI设计美术:

有丰富的UI设计经验, 热爱游戏,对各种题材的UI又自己独到见解 3年以上工作经验为佳。

FlashUI美术:

熟悉flash,懂基本as2.0 熟悉各种动画制作和特效制作。 有1到2年flash制作经验 如果有个人业余小游戏作品,那是极好的。

我们将提供给您: 7~15k左右的基本薪资待遇(具体和能力挂钩) 每年最高4次项目奖金,年底双薪 节假日过节奖金,项目完成后的集体旅行 灵活可变的弹性上班时间,宽松自由的团队氛围 一群靠谱的伙伴,快乐生活,做好游戏 低层级公司,没有不做事混日子的伙伴。 每一个产品都会是精品设计,绝对不会开发浪费大家光阴的项目。 好的技术学习讨论氛围 macpro笔记本开发

如果您也对制作游戏充满热忱,渴望能有一群靠谱的伙伴一起快乐工作,一起旅行,一起开创未来!请联系xiao.wen@gamever.org screen696x696.jpeg

2015年五月6日早上 6:51:43 关于Express中使用req.pipe(busboy)的用途是什么?是多文件上传吗?

大家好:

我在看源码时,发现req.pipe方法,但在google上没有找到解释其具体是什么含义?(我猜是不是,多文件上传的意思?) express API上也没有找到。

坛子里的先闻道者,麻烦帮忙下,谢谢。

code: exports.upload = function (req, res, next) { req.busboy.on('file’, function (fieldname, file, filename, encoding, mimetype) { store.upload(file, {filename: filename}, function (err, result) { if (err) { return next(err); } res.json({ success: true, url: result.url, }); }); });

req.pipe(req.busboy); };

2015年五月6日凌晨 12:50:57 异步流程控制:7 行代码学会 co 模块

首先请原谅我的标题党(●—●),tj 大神的 co 模块源码200多行,显然不是我等屌丝能随便几行代码就能重写的。只是当今大家都喜欢《7天学会xx语言》之类的速效仙丹,于是我也弄个类似的名字《7行代码学会co模块》来博眼球。

为了避免被拖出去弹小JJ,还是先放出所谓的 7 行代码给大家压压惊:

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
} 

万恶的回调

对前端工程师来说,异步回调是再熟悉不过了,浏览器中的各种交互逻辑都是通过事件回调实现的,前端逻辑越来越复杂,导致回调函数越来越多,同时 nodejs 的流行也让 javascript 在后端的复杂场景中得到应用,在 nodejs 代码中更是经常看到层层嵌套。

以下是一个典型的异步场景:先通过异步请求获取页面数据,然后根据页面数据请求用户信息,最后根据用户信息请求用户的产品列表。过多的回调函数嵌套,使得程序难以维护,发展成万恶的回调

javascript$.get('/api/data', function(data) {
    console.log(data);
    $.get('/api/user', function(user) {
        console.log(user);
        $.get('/api/products', function(products) {
            console.log(products)
        });
    });
});

异步流程控制

$.get('/api/data')
.then(function(data) {
    console.log(data);
    return $.get('/api/user');
})
.then(function(user) {
    console.log(user);
    return $.get('/api/products');
})
.then(function(products) {
    console.log(products);
});
co(function *() {
    var data = yield $.get('/api/data');
    console.log(data);
    var user = yield $.get('/api/user');
    console.log(user);
    var products = yield $.get('/api/products');
    console.log(products);
});

co 模块

上文已经简单介绍了co 模块是能让我们以同步的形式编写异步代码的 nodejs 模块,主要得益于 ES6 的 generator。nodejs >= 0.11 版本可以加 --harmony 参数来体验 ES6 的 generator 特性,iojs 则已经默认开启了 generator 的支持。

要了解 co ,就不得不先简单了解下 ES6 的 generator 和 iterator。

Iterator

Iterator 迭代器是一个对象,知道如何从一个集合一次取出一项,而跟踪它的当前序列所在的位置,它提供了一个next()方法返回序列中的下一个项目。

javascriptvar lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
var pair = it.next(); 
console.log(pair); // ["name", "JavaScript"]
pair = it.next(); 
console.log(pair); // ["birthYear", 1995]
pair = it.next(); // A StopIteration exception is thrown

乍一看好像没什么奇特的,不就是一步步的取对象中的 key 和 value 吗,for ... in也能做到,但是把它跟 generator 结合起来就大有用途了。

Generator

Generator 生成器允许你通过写一个可以保存自己状态的的简单函数来定义一个迭代算法。
Generator 是一种可以停止并在之后重新进入的函数。生成器的环境(绑定的变量)会在每次执行后被保存,下次进入时可继续使用。generator 字面上是“生成器”的意思,在 ES6 里是迭代器生成器,用于生成一个迭代器对象。

function *gen() {
    yield 'hello';
    yield 'world';
    return true;
}

以上代码定义了一个简单的 generator,看起来就像一个普通的函数,区别是function关键字后面有个*号,函数体内可以使用yield语句进行流程控制。

javascriptvar iter = gen();
var a = iter.next();
console.log(a); // {value:'hello', done:false}
var b = iter.next();
console.log(b); // {value:'world', done:false}
var c = iter.next();
console.log(c); // {value:true, done:true}

当执行gen()的时候,并不执行 generator 函数体,而是返回一个迭代器。迭代器具有next()方法,每次调用 next() 方法,函数就执行到yield语句的地方。next() 方法返回一个对象,其中value属性表示 yield 关键词后面表达式的值,done 属性表示是否遍历结束。generator 生成器通过nextyield的配合实现流程控制,上面的代码执行了三次 next() ,generator 函数体才执行完毕。

co 模块思路

从上面的例子可以看出,generator 函数体可以停在 yield 语句处,直到下一次执行 next()。co 模块的思路就是利用 generator 的这个特性,将异步操作跟在 yield 后面,当异步操作完成并返回结果后,再触发下一次 next() 。当然,跟在 yield 后面的异步操作需要遵循一定的规范 thunks 和 promises。

yieldables

The yieldable objects currently supported are:
- promises
- thunks (functions)
- array (parallel execution)
- objects (parallel execution)
- generators (delegation)
- generator functions (delegation)

7行代码

再看看文章开头的7行代码:

javascriptfunction co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}

首先生成一个迭代器,然后执行一遍 next(),得到的 value 是一个 Promise 对象,Promise.then() 里面再执行 next()。当然这只是一个原理性的演示,很多错误处理和循环调用 next() 的逻辑都没有写出来。

下面做个简单对比:
传统方式,sayhello是一个异步函数,执行helloworld会先输出"world"再输出"hello"

function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
function helloworld() {
    sayhello();
    console.log('world');
}
helloworld();

输出

> "world"
> "hello"

co 的方式,会先输出"hello"再输出"world"

javascriptfunction co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}
function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
co(function *helloworld() {
    yield sayhello();
    console.log('world');
});

输出

javascript> "hello"
> "world"

消除回调金字塔

假设sayhello/sayworld/saybye是三个异步函数,用真正的 co 模块就可以这么写:

var co = require('co');
co(function *() {
    yield sayhello();
    yield sayworld();
    yield saybye();
});

输出

javascript> "hello"
> "world"
> "bye"

参考

《es7-async》 https://github.com/jaydson/es7-async
《Generator 函数的含义与用法》 http://www.ruanyifeng.com/blog/2015/04/generator.html
《Iterator》 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glob...

2015年五月6日凌晨 12:30:43 关于express中app.post的一个问题

var express = require(‘express’); var app = express(); app.listen(3000); var bodyParser = require(‘body-parser’) var hbs = require(‘hbs’); var spawn = require(‘child_process’).spawn;

app.set('view engine’, ‘html’); app.engine('html’, hbs.__express); app.use(express.static(‘public’)); app.use(bodyParser.urlencoded({ extended: false }));

ls = spawn(‘某程序’);

app.post('/’, function(req, res) { var info = req.body;//请求前端得到表单的值 ls.stdin.write(“calculate(“+info.xxx+”)"+ “\n”);//子线程调用程序完成一些计算 ls.stdout.on('data’, function(data) { //做一些数据的处理 res.render('…’,{data:…});//然后反馈呈现到前端页面上 }); }); 这样做的话提交一次表单运行正常,第二次就会报错说Can’t set headers after they are sent, 查了之后说是回调有问题,后来把res.render去掉,console一些需要的结果,发现第一次提交表单运算后输出一次,当第二次提交表单的时候输出两次,第三次提交输出三次。。。

下面是把ls.stdout.on拿到post外面,这样做的话提交一次表单运算后输出一次,不会出现上面重复的问题,但是这里就不能用res.render()去把结果呈现在前端上了,因为在post外面,做不了res的反馈。。。 app.post('/’, function(req, res) { var info = req.body; ls.stdin.write("calculate(info.xxx+ “\n”);//子线程调用程序完成一些计算 }); ls.stdout.on('data’, function(data) { //处理计算得到后的数据。console.log一些需要的结果,方便在终端查看结果 });

请教懂的小伙伴们,怎么做能一举两得呐?我是小白一枚。。。

2015年五月6日凌晨 12:05:52 小白请教一个简单的session问题

前端提交数据表单给nodejs,调用一个计算软件计算得到结果后反馈到前端页面上,在localhost:3000上能实现这个过程了,这是单用户情况。 现在要加多用户进去,用session模块,怎么设置能将调试的这台电脑作为服务器,然后让其他电脑访问完成提交数据运行计算软件得到结果的这个功能。

2015年五月5日晚上 11:46:21 [MySQL]查询学生选课的情况(一)

这是我工作遇到的问题,现在自己设计一个简化的类似场景,现实中这样的数据表设计可能有很多不合理的地方。
首先看表结构:

+--------+--------------+------+-----+---------+-------+
| Field  | Type         | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| id     | varchar(38)  | NO   | PRI | NULL    |       |
| name   | varchar(255) | YES  |     | NULL    |       |
| course | varchar(300) | YES  |     | NULL    |       |
+--------+--------------+------+-----+---------+-------+

这里只是记录学生的ID,名字,还有选课的科目,科目有很多,在没有关联表的情况下,这么多科目只保存在一个字段中,用逗号隔开。
再看一些数据:

+--------------------------------------+--------+--------------------------------+
| id                                   | name   | course                         |
+--------------------------------------+--------+--------------------------------+
| 32268995-f33d-11e4-a31d-089e0140e076 | 张三   | Math,English,Chinese           |
| 3d670ef2-f33d-11e4-a31d-089e0140e076 | 李四   | Math,English,Chinese,Algorithm |
| 475d51a6-f33d-11e4-a31d-089e0140e076 | 李五   | Math,English,Algorithm         |
| 547fdea0-f33d-11e4-a31d-089e0140e076 | 王小明 | Math,English,Japanese          |
| 656a247a-f33d-11e4-a31d-089e0140e076 | 曹达华 | Chesses                        |
+--------------------------------------+--------+--------------------------------+

那么如何查找到选择了Math课程的学生?

想想使用关联表的时候,张三, 李四, 李五, 王小明这四个人都一条选择了Math这门课的记录,还有其他不是Math的记录。此时要查找选择了Math课程的学生,一般使用IN语句就可以了:

select * from student_course where course IN ('Math');

如果要查找选择了MathAlgorithm课程的学生呢:

select * from student_course where course IN ('Math', 'Algorithm');

如此,回到原来的问题,如果我设计一个类似IN一样的函数,那么就可以解决这个问题了。
这个流程我们可以想象出来,是这样子的:
我们取张三的课程信息Math,English,Chinese,首先切割成Math, English,Chinese三个字段,然后分别与与查找条件做比较,类似'Math'.indexOf('Math');,'Math'.indexOf('English');...
只要找到一个就认为符合查找条件。
同样的,如果要查找选择了MathAlgorithm课程的学生,比较过程就变成了:
'Math,Algorithm'.indexOf('Math');,'Math,Algorithm'.indexOf('English');...

切割函数 getSplitTotalLength, getSplitString

CREATE DEFINER = `root`@`%` FUNCTION `getSplitTotalLength`(`f_string` varchar(500),`f_delimiter` varchar(5))
 RETURNS int(11)
BEGIN
    # 计算传入字符串能切分成多少段 
   return 1+(length(f_string) - length(replace(f_string,f_delimiter,''))); 

    RETURN 0;
END;
CREATE DEFINER = `root`@`%` FUNCTION `getSplitString`(`f_string` varchar(500),`f_delimiter` varchar(5),`f_order` int)
 RETURNS varchar(500)
BEGIN
    #拆分传入的字符串,分隔符,顺序,返回拆分所得的新字符串
    declare result varchar(500) default '';
    set result = reverse(substring_index(reverse(substring_index(f_string,f_delimiter,f_order)),f_delimiter,1));
    RETURN result;
END;

类似IN的那个函数 isInSearch

CREATE DEFINER=`root`@`%` FUNCTION `isInSearch`(f_course VARCHAR(300), f_string VARCHAR(300)) RETURNS INT
BEGIN

    DECLARE len INT DEFAULT 0;
    DECLARE idx INT DEFAULT 0;
    DECLARE item_code VARCHAR(300) DEFAULT '';
    DECLARE item_index INT DEFAULT 0;
    IF f_course IS NULL THEN
        RETURN 0;
    END IF;
    SELECT getSplitTotalLength(f_course, ',') INTO len;
    label: LOOP
        SET idx = idx + 1;
        IF idx > len THEN
            LEAVE label;
        END IF;
        SELECT getSplitString(f_course , ',', idx) INTO item_code;
        # f_string.indexOf(item_code) > -1 ?
        SELECT LOCATE(item_code, f_string) INTO item_index;
        IF item_index > 0 THEN
            RETURN 1; # got one
        END IF;
    END LOOP label;
    RETURN 0;
    END;

这里说下locate函数,locate(item_code, f_string),如果item_codef_string的子串,返回的结果大于0,是item_codef_string的起始下标(从1开始算起),这个一般的indexOf函数有些不同。

mysql> select locate('Math','Math,Algorithm');
+---------------------------------+
| locate('Math','Math,Algorithm') |
+---------------------------------+
|                               1 |
+---------------------------------+
mysql> select locate('Math','Chinese,Math,Algorithm');
+-----------------------------------------+
| locate('Math','Chinese,Math,Algorithm') |
+-----------------------------------------+
|                                       9 |
+-----------------------------------------+
mysql> select locate('Math','Chinese,Algorithm');
+------------------------------------+
| locate('Math','Chinese,Algorithm') |
+------------------------------------+
|                                  0 |
+------------------------------------+

可以看到isInSearch函数返回的是INT类似,因为MySQLIN也是这样的机制。

mysql> select 'Math' in ('Math','Algorightm');
+---------------------------------+
| 'Math' in ('Math','Algorightm') |
+---------------------------------+
|                               1 |
+---------------------------------+
mysql> select 'Math' in ('Chinese','Algorightm');
+------------------------------------+
| 'Math' in ('Chinese','Algorightm') |
+------------------------------------+
|                                  0 |
+------------------------------------+

如果存在返回1,不存在返回0。

在SELECT语句中使用自定义的函数

mysql> select * from student_course where isInSearch(course, 'Math');
+--------------------------------------+--------+--------------------------------+
| id                                   | name   | course                         |
+--------------------------------------+--------+--------------------------------+
| 32268995-f33d-11e4-a31d-089e0140e076 | 张三   | Math,English,Chinese           |
| 3d670ef2-f33d-11e4-a31d-089e0140e076 | 李四   | Math,English,Chinese,Algorithm |
| 475d51a6-f33d-11e4-a31d-089e0140e076 | 李五   | Math,English,Algorithm         |
| 547fdea0-f33d-11e4-a31d-089e0140e076 | 王小明 | Math,English,Japanese          |
+--------------------------------------+--------+--------------------------------+

mysql> select * from student_course where isInSearch(course, 'Chinese,Japanese');
+--------------------------------------+--------+--------------------------------+
| id                                   | name   | course                         |
+--------------------------------------+--------+--------------------------------+
| 32268995-f33d-11e4-a31d-089e0140e076 | 张三   | Math,English,Chinese           |
| 3d670ef2-f33d-11e4-a31d-089e0140e076 | 李四   | Math,English,Chinese,Algorithm |
| 547fdea0-f33d-11e4-a31d-089e0140e076 | 王小明 | Math,English,Japanese          |
+--------------------------------------+--------+--------------------------------+
2015年五月5日晚上 11:05:02 PHP 到 Node.js的路该如何走?

无js基础,php出身,该通过什么方式能够快速掌握Node.js?

2015年五月5日晚上 10:00:43 求大神给建立个符合要求mongodb集合

nodejs新手,想做个社交聊天的玩具(就是图个新鲜)现在遇见难题了,跪求大神帮助 我数据库用的是mongodb,建立了个用户表 var users=new Schema( { email:String,//邮箱 password:String,//密码 username:String,//用户昵称 avatar:String,//头像 sex:String,//性别 auth:String,//验证码 condition:Boolean,//账号是否激活 address:String,//地址 marry:String,//婚姻状况 proclammation:String,//个性宣言 friends:[{fid:Object,//朋友id remarks:String,//备注 group:String,//分组 allow:Boolean}] }); 我现在遇见的问题是如何通过fid获取朋友的信息, 我刚开始是这样做的 if(docs.friends.length>0){ docs.friends.forEach(function(b){ users.findById(b.fid,function(err,frids){ if(err){ console.log(“加载朋友时候失败了”); } var arr={ "userId":frids._id, "username":frids.username, "avatar":frids.avatar, "remarks": b.remarks, "group": b.group } frs.push(arr);

                    });
                    });
                     res.render('coze',{frs:frs});

但是我对回调函数,异步又不熟悉,设计上有问题,所以最后得不到frs 里的数据, 然后我尝试这用Population,发现friends 里关联的就是users ,而friends 就在users 中,怎么办 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 我的目的其实很简单,就是实现类似QQ那样加载好友信息,同时加载备注的功能,大神给个解决方案吧 参考资料也可以的

2015年五月5日晚上 8:33:45 alsotang commented on pull request cnodejs/nodeclub#521
alsotang commented on pull request cnodejs/nodeclub#521
@alsotang

域名还不在我手上,想去乌云认领都难。

2015年五月5日晚上 8:29:50 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 8:27:08 alsotang commented on pull request cnodejs/nodeclub#521
alsotang commented on pull request cnodejs/nodeclub#521
@alsotang

呃。不好意思刚看到。。。已经修复了。 0f6cc14

2015年五月5日晚上 8:27:08 alsotang closed pull request cnodejs/nodeclub#521
alsotang closed pull request cnodejs/nodeclub#521
@alsotang
security fix:wooyun-2010-0112230
2015年五月5日晚上 8:22:08 How to structure models in NodeJS

我想请问下,在nodeJS 中Model 的结构是咋样的? 我之前是做Java/C# 程序的, 在以往我的model 全部都是单独作为一个文件来存放(POJO),然后会有一个专门的数据访问类,或则一个Repository,例如:

– 纯model 文件 publi class User{ poublic int Id {get; set} public string FirstName { get; set; } public string LastName { get; set; } … } – 数据访问类 public class UserRep{ public IEnumerable<User> All(); public User Get(); … }

但是,我看网上有好些教程,大都是把 Model 的字段和方法放在同一个文件里头: var User = function(id, firstname, lastname) { this.Id=id; … } // 先是一些静态方法 User.findById = function() { } // 然后还有些 实例方法 User.prototype.save = function() {}

这样做我感觉怪怪的, 数据, 和数据访问层不应该分开吗?还有几个疑问(我不用Mongodb, 我用的是传统的RDBS):

  1. 如何做 data validation;
  2. 单元测试如何集成;
  3. 如果使用ORM 该怎么样用;
2015年五月5日晚上 7:46:08 iojs v2.0 开启 use strong 之后。。。

就可以和var说再见了~~一遍es6、7写下来,如果再使用flow,那感觉基本就不是js了⊙﹏⊙b。 iojs-new-features

2015年五月5日晚上 7:44:21 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 7:43:50 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 7:32:41 正则表达式笔记(二)

JS 中的正则表达式

概述

在 JS 中,正则表达式是个 RegExp 对象,具有 exectest 方法。而在 String 对象中,也存在 matchreplacesearchsplit 操作正则表达式的方法。

声明

JS 中的正则表达式有两种声明方式(对象字面量 / 构造函数),都会生产一个 RegExp 对象的实例。

/pattern/flags
new RegExp(pattern[, flags])

RegExp 对象

实例

var pattern = /quick\s(brown).+?(jumps)/ig;
var pattern = new RegExp("quick\\s(brown).+?(jumps)","ig");

实例之后的 pattern 对象就具有以下属性:

注意使用构造函数声明正则表达式的时候,需合理使用转义符。

方法

RegExp.exec 检索字符串中指定的值。返回一个结果数组。该方法总是返回单词匹配的结果。

在正则表达式设置了 g 模式的情况下,会同时更新 RegExp 对象的 lastIndex 属性。

var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
// console.log(result)
// ["Quick Brown Fox Jumps", "Brown", "Jumps"]

var pattern = /ab*/g;
var str = 'abbcdefabh';
var matchArray;
while ((matchArray = pattern.exec(str)) !== null) {
  var msg = 'Found ' + matchArray[0] + '. ';
  msg += 'Next match starts at ' + pattern.lastIndex;
  //console.log(msg);
  // Found abb. Next match starts at 3
  // Found ab. Next match starts at 9
}

使用循环的时候,不要把正则表达式的操作放置在循环体中(这样每次都会重新生成 RegExp 对象),同时必须设置全局模式,可能会造成死循环。

RegExp.test 执行检索字符串中指定值的操作。返回 truefalse

var pattern = /^\d{4}-\d{2}-\d{2}$/;
var result = re.test('2010-12-20');

console.log(result)
// true

在正则表达式设置了 g 模式的情况下,跟 RegExp.exec 一样,会同时更新 RegExp 对象的 lastIndex 属性。

var pattern = /^\d{4}-\d{2}-\d{2}$/g;
pattern.test('2010-12-20'); // true
pattern.test('2010-12-20'); // false

RegExp.test 在匹配成功之后,各个捕获分组的文本会保存下来,用 RegExp.$1RegExp.$2··· 就可以获得,不过,保存整个表达式匹配文本的 RegExp.$0 并不存在。

String 对象

方法

String.match 返回一个结果数组或null

在正则表达式设置了 g 模式的情况下,match 默认返回所有可能的匹配结果。

var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var re = /[A-E]/gi;
var matches = str.match(re);

console.log(matches);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']

如果正则表达式没有设置 g 模式,那么 match 方法返回的结果与 RegExp.exec() 一致。

var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var re = /[A-E]/i;
var matches_1 = str.match(re);
var matches_2 = re.exec(str)

console.log(matches_1, matches_2)

//[ 'A',
  index: 0,
  input: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' ]

该返回结果包含匹配的字符串,还有一个额外的 index 属性,表示匹配字符串的位置,input 属性即是所传入的字符串。

String.search 返回第一次匹配的位置或 -1

该方法用来寻找某个正则表达式在字符串中第一次匹配成功的位置,失败的时候返回 -1

这个方法只能找到“第一次”,即使设置了 g 模式,结果也不会有任何变化,所以如果需要使用一个正则表达式多次验证字符串,调用该方法判断结果是否为 0,是更好的办法。

"1234".search(/^\d+$/g) == 0 // true
"5678".search(/^\d+$/g) == 0 // true

var pattern = /^\d+$/g;
pattern.test("1234"); // true
pattern.test("5678"); // false

String.split 使用一个正则表达式来切割字符串。返回数组

正则表达式是否设置了g模式对结果没有影响。

var matchArray = "a b c".split(/\s+/);

console.log(matchArray);
// ["a", "b", "c"]

也可以设置第二个参数 limit,指定返回数组的数目。在 JS 中,该参数取值的结果如下

取值 结果
limit < 0 || limit > n 等同于未指定
0<=limit<=n 返回一个包含 n 元素的数组
var matchArray = "a b c".split(/\s+/,2);

console.log(matchArray);
// ["a", "b"]
2015年五月5日晚上 7:24:48 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 6:39:24 node js callback 的一些问题

小弟最近在练习node js 但总是遇到一个问题不知道该怎么解决 是关于callback的问题 举个例子来说 mysql 模塊中對mysql下query

var rs; connection.query('SELECT 1 + 1 AS solution’, function(err, rows, fields) { if (err) throw err; rs = rows; console.log('The solution is: ', rows[0].solution); });

根据官方文件 返回的结果 会传给rows 但我在外面宣告一个变数去里面把rows传给他 但之后console看却是undefine 不确定是不是callback函数的范围问题

如果我想要对结果作处理一定要在callback里面做吗? 得到的结果可以传出来吗?这个地方我一直有问题观念不是很懂 所以来这边请教一下各位

2015年五月5日晚上 6:16:05 关于MongoDB 和 缓存

MongoDB 号称查询已经非常快,而且热数据是在内存上的,那还有必要专门优化吗?

2015年五月5日晚上 6:07:28 求解答socket.io client代码处理方案

socket.io在客户端需要写相应的代码去请求server端的事件。那这样就相当于把client 的js代码暴露在浏览器,这样不就很危险,相当于用户可以通过编写js去请求我的server服务,想问问各位有什么好的处理方案。例如js客户端代码加密(初步方案而已)。

2015年五月5日下午 4:49:16 在用npm start 启动项目的时候,会出现如图情况。

cnodejs.png

启动就是用express生成的项目,在线等。

对了,补充一下。是window7系统

2015年五月5日下午 2:57:14 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月5日下午 2:13:42 来帮我做个小调研《Node使用情况小调查》

问卷地址在这里:http://survey.taobao.com/survey/AgT436qK

不用登陆。只有5个小题。么么哒。

调研已经结束,谢谢各位。

2015年五月5日下午 2:06:55 糗百的数据迁移实践

糗事百科(以下简称“糗百”)被誉为移动互联网时代的新娱乐手段,其上海量真实用户的糗事深受喜爱,每天有1亿次动态请求,峰值请求数为每秒30000次。面对如此高的并发访问量,糗百原来自建的平台越来越难以支撑,开始出现服务器过载、跨机房同步延时大、图片中心磁盘I/O成为瓶颈等问题。

为了解决这些刚性的服务压力,优化用户的服务体验,并考虑到七牛对静态资源存储的强大技术实力和优秀的解决方案,糗百决定将图片存储迁移到七牛平台上,并开始使用七牛提供的CDN服务。本文将结合糗百的数据迁移实践,来详细讲述如何在不中断服务的情况下,将海量数据平滑迁移到七牛平台的全过程。

qrsync+镜像存储打造平滑迁移方案

传统的数据迁移方案是:关掉网站原来的数据上传通道,所有数据变成只读,然后将所有数据上传到新的存储节点,再将上传入口改为新的存储节点,之后开放网站的上传功能。这样带来的问题是,数据迁移过程中,用户长时间不能进行上传操作,用户体验非常差。如何解决这个问题呢?

针对糗百这么大体量的应用,七牛提供的数据迁移方案——上传工具qrsync+镜像存储,很好地绕开了传统迁移方案所带来的问题。糗百先通过七牛的数据上传工具qrsync将大量冷数据传到七牛平台上,并将数据访问地址切换成七牛的域名。由于用户生成的大量热数据还在糗百自己的平台上,为了不出现数据丢失的情况,保证用户访问的流畅性,糗百选用了七牛的镜像存储服务。

七牛的镜像存储为整个数据迁移过程提供良好的过渡支持作用。当用户访问的数据不在七牛平台上时,镜像存储服务将回糗百源站抓取数据,并保存在七牛平台上。故此,镜像存储对每个资源只需回源一次,后续访问的时候就不再回源了。
图片描述

随后,为了进一步缓解糗百源站的I/O压力,糗百对旧有系统做了一次版本升级,将新系统的图片存储直接放在七牛平台上。新版本的用户可以顺畅地将数据上传到七牛平台上,并实现访问,而旧系统的App版本还会有一部分用户在使用。这时,就要在一段时间内保证两套系统可用。但旧系统的App用户所产生的数据还是会被上传到糗百的自建平台中,在用户第一次访问这些数据时,镜像存储服务对糗百源站做回源,很好地确保了这部分数据的可用性。由于目前App客户端的版本更新速度比较快,因此在所有用户都更新成新版系统,源站的回源流量逐渐趋于0时,就可以将镜像功能删除了。

就这样,在用户毫无感知的情况下,糗百轻松实现了对图片存储的迁移,平稳地解决了图片中心磁盘I/O的瓶颈问题。

镜像存储的使用方法

假设源站所有的图片,放在一个叫img.example.com的子域里。那么平滑迁移的方式是:

1、在七牛上建立一个镜像bucket,设定源站为img.example.com。假设镜像bucket是example-img,到空间设置的域名设置中即可找到形式为7xiuqc.com1.z0.glb.clouddn.com的七牛域名;
2、将所有对外使用的图片的域名改为7xiuqc.com1.z0.glb. clouddn.com;
3、如果网站数据是UGC(用户产生内容)的,调整上传流程,传到七牛的镜像 bucket,这样源站就变成只读;
4、使用qrsync同步工具将历史数据全部同步到七牛的镜像bucket。

如此就完成了整个迁移过程。此时img.example.com这个源站就可以废弃不用了。

结语

相信数据资源高速增长这样的“甜蜜负担”,是很多企业都会遇到的。而如何借助云服务来合理扩容,如何在不中断服务的前提下,平滑地实现数据迁移,将成为决定企业未来命运的关键一环。七牛云存储不仅能为企业用户稳定高效的底层存储平台,镜像存储等优质的服务更能在数据迁移过程中提供强大的助力。此外,完成数据迁移之后,七牛提供的丰富的图片、音视频处理功能也为包括糗百在内的诸多企业带来了很大的惊喜。后续我们将专门撰文分享这部分内容。

2015年五月5日中午 11:55:15 CentOS 6.x 升级 Git

准备

说明

公司服务器为centos,安装git后的默认版本是1.7.1,在执行git clone命令时报错如下:

    fatal: HTTP request failed

经过一番搜索终于找到可行的办法,即为升级git版本,升级时间比较长,需要比较好的网络支持.

git版本检测

CentOS下使用git --version 检测git的版本

# git --version
git version 1.7.1

系统检测

# cat /etc/redhat-release
CentOS release 6.5 (Final)
# uname -a
Linux rmhost 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

通过以上信息可知系统版本为 CentOS 6.5 64位

升级

1.导入证书

# rpm --import http://apt.sw.be/RPM-GPG-KEY.dag.txt

2.安装RPMForge源

这里查找对应的版本,比如我这里根据系统版本选择了rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm,右键拷贝地址, 粘贴到命令rpm -i命令后面执行

# rpm -i http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm

package rpmforge-release-0.5.3-1.el6.rf.x86_64 is already installed

符合系统版本的文件可能有多个,选一个较新的即可.

3.更新rpmforge-extra源

# yum --enablerepo=rpmforge-extras update

途中会有选项Is this ok [y/N]:询问是否下载, 如果选了y会更新所有的软件到匹配的最新版本,包括git,如果选N也可以手动安装git到最新版

这里建议选择N,选y需要较长时间(我当前网速下测试为一小时左右),一些软件升级后可能需要重新配置才能起作用, 比如MySQL从5.1升级到了5.5, 由于未配置直接导致我在线的两个项目无法运行, 只能手忙脚乱的赶紧修改配置,如果你不幸出了同样的问题, 可以到文章末尾找到解决办法

4.查找符合系统的git版本

通过下面的命令查找(推荐)
    # yum --enablerepo=rpmforge-extras provides git
    git-1.7.12.4-1.el6.rfx.x86_64 : Git core and tools
    Repo        : installed
    匹配来自于:
    Other       : 提供依赖满足:git
或者在软件库中查找

这里找到系统能支持的git最新版本

5.安装git

# yum --enablerepo=rpmforge-extras install perl-Git-1.7.12.4-1.el6.rfx.x86_64.rpm 
# git --version
git version 1.7.12.4

MySQL升级后不能启动的解决办法

系统使用rpm源升级了所有软件, MySQL从5.1升级到了5.5, 启动的时候抛出异常:

MySQL Daemon failed to start.
正在启动 mysqld:                                   [失败]

原因:

MySQL升级之后,由于配置文件/etc/my.cnf还是原来5.1的,对5.5已经不适用了,所以出错

解决办法:

用MySQL-5.5的配置文件替换原来的/etc/my.cnf,具体操作

cp /usr/share/mysql/my-medium.cnf /etc/my.cnf

注意:MySQL配置模板文件共有5个:my-huge.cnf、my-innodb-heavy-4G.cnf、my-large.cnf、mymedium.cnf、my-small.cnf,根据自己的服务器硬件配置选择相应的模板文件即可

参考

CentOS升级git:

MySQL无法启动:

2015年五月5日中午 11:39:23 有人用过apache的ab test测试post请求么?post请求的文件正确格式是啥?

求问,网上搜了很多都只介绍了ab test的基本用法,测post请求的还真难搜到,我想知道如果我post一个复杂的json数据(比如对象包含对象,包含数组这种结构的),post请求的file文件正确格式是啥?有什么工具或方法可以帮我把json数据转换成正确的post格式么?谢谢!

2015年五月5日中午 11:37:38 【北京】Melotic招聘 Nodejs/ops工程师(15k- 30k)

【关于我们】 Melotic成立于2014年年初,以香港为基地的数字资产交换和流通平台,在大陆、台湾、美国分别设有分公司,平台应用了革命性的金融技术,将迅速在全球多个国家开展业务。 团队已是获美国LIGHT SPEED VENTURE风投支持,CEO是斯坦福大学毕业并在美国硅谷创业多年的JACK WANG, 中英文双语工作环境,工作地点为团结湖地铁站附近的三里屯创业空间科技寺。 【关于职位】 Nodejs/ops工程师,后端工程师,国际平台,只要你有能力,一切都不是问题 工作地点:北京市 工作年限:3年以上 最低学历:本科 招聘人数:1 职位月薪:¥15,000 - 30,000 【职责&要求】 岗位职责 Responsibilities

任职要求 Requirements

Desired

【联系我们】 如果您对工作职位感兴趣,请将您的简历或情况发送到邮箱sindy@melotic.com联系我们,我们会尽快回复~

2015年五月5日中午 11:36:05 CentOS 7 安装 Gitlab

安装基本系统与依赖包

安装 Gitlab 依赖的工具

bashyum -y update
yum -y groupinstall 'Development Tools'
yum -y install readline readline-devel ncurses-devel gdbm-devel glibc-devel tcl-devel openssl-devel curl-devel expat-devel db4-devel byacc sqlite-devel libyaml libyaml-devel libffi libffi-devel libxml2 libxml2-devel libxslt libxslt-devel libicu libicu-devel system-config-firewall-tui git redis ruby sudo wget crontabs logwatch logrotate perl-Time-HiRes

安装 Redis

访问 http://www.redis.io/download,下载 Redis 源代码。

bashwget http://download.redis.io/releases/redis-3.0.0.tar.gz
tar zxvf redis-3.0.0.tar.gz
cd redis-3.0.0
make

若在编译过程中出错,则可以执行下面的命令:

bashsudo make test

安装:

bashsudo make install
sudo ./utils/install_server.sh

配置

创建 /etc/init.d/redis 并使用下面的代码作为启动脚本。

添加如下内容:

bash###########################
PATH=/usr/local/bin:/sbin:/usr/bin:/bin

REDISPORT=6379
EXEC=/usr/local/bin/redis-server
REDIS_CLI=/usr/local/bin/redis-cli

PIDFILE=/var/run/redis.pid
CONF="/etc/redis/6379.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        if [ "$?"="0" ]
        then
              echo "Redis is running..."
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $REDIS_CLI -p $REDISPORT SHUTDOWN
                while [ -x ${PIDFILE} ]
               do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
   restart|force-reload)
        ${0} stop
        ${0} start
        ;;
  *)
    echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
        exit 1
esac
##############################

保存后,添加可执行权限:

sudo chmod +x /etc/init.d/redis

确保 redis 能随系统启动:

vi /etc/rc.d/rc.local

在文件末尾添加下面这行:

service redis start

然后使用上面同样的命令启动 redis 服务:

service redis start

安装邮件服务器

yum -y install postfix

安装Git

先删除系统中原有的老版本 git

bashyum -y remove git
yum install zlib-devel perl-CPAN gettext curl-devel expat-devel gettext-devel openssl-devel

从官方网站下载源代码进行:

bashcurl --progress https://www.kernel.org/pub/software/scm/git/git-2.4.0.tar.gz | tar xz
cd git-2.4.0/
./configure
make
make prefix=/usr/local install

然后使用下面这个命令检测安装是否有效:

which git

安装 ruby

如果 ruby 的版本低于 2.0 的话,则需要重新安装 ruby

bashcd ~
curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.2/ruby-2.2.2.tar.gz | tar xz
cd ruby-2.2.2
./configure --disable-install-rdoc
make
make prefix=/usr/local install

为 Gitlab 添加系统用户

adduser --system --shell /bin/bash --comment 'GitLab' --create-home --home-dir /home/git/ git

为了包含/usr/local/bin到git用户的$PATH,一个方法是编辑超级用户文件。以管理员身份运行:

visudo

然后搜索:

Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin

将其改成:

Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

安装数据库

MySQL 已经不再包含在 CentOS 7 的源中,而改用了 MariaDB,先搜索 MariaDB 现有的包:

rpm -qa | grep mariadb

然后全部删除:

rpm -e --nodeps mariadb-*

然后创建 /etc/yum.repos.d/MariaDB.repo

vi /etc/yum.repos.d/MariaDB.repo

将以下内容添加至该文件中:

# MariaDB 10.0 CentOS repository list - created 2015-05-04 19:16 UTC
# http://mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.0/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

然后运行下面命令安装 MariaDB 10.0

sudo yum install MariaDB-server MariaDB-client

然后启动 MariaDB 服务:

service mysql start

接着运行 mysql_secure_installation

mysql_secure_installation

登录 MariaDB 并创建相应的数据库用户与数据库:

mysql -uroot -p
CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
SET storage_engine=INNODB;
CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'git'@'localhost';
\q

尝试使用新用户连接数据库:

sudo -u git -H mysql -u git -p -D gitlabhq_production
\q

安装 Gitlab

克隆源

sudo -u -git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-10-stable gitlab

配置

cd /home/git/gitlab

# Copy the example GitLab config
# 复制GitLab的示例配置文件
sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml

# Make sure to change "localhost" to the fully-qualified domain name of your host serving GitLab where necessary
# 确保修改“localhost”为你的GitLab主机的FQDN
#
# If you want to use https make sure that you set `https` to `true`. See #using-https for all necessary details.
# 如果你想要使用https确保你设置了`https`为`true`。具体必要的细节参见#using-https
#
# If you installed Git from source, change the git bin_path to /usr/local/bin/git
# 如果你从源代码安装了Git,修改git的bin_path为/usr/local/bin/git
sudo -u git -H editor config/gitlab.yml

# Make sure GitLab can write to the log/ and tmp/ directories
# 确保GitLab可以写入log/和temp/目录
chown -R git {log,tmp}
chmod -R u+rwX  {log,tmp}

# Create directory for satellites
# 为卫星(?)创建目录
sudo -u git -H mkdir /home/git/gitlab-satellites
chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites

# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
# 确保GitLab可以写入tmp/pids/和temp/sockets/目录
chmod -R u+rwX  tmp/{pids,sockets}

# Make sure GitLab can write to the public/uploads/ directory
# 确保GitLab可以写入public/uploads/目录
chmod -R u+rwX  public/uploads

# Copy the example Unicorn config
# 复制Unicorn的示例配置文件
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb

# Enable cluster mode if you expect to have a high load instance
# Ex. change amount of workers to 3 for 2GB RAM server
# 启用集群模式如果你期望拥有一个高负载实例
# 附:修改worker的数量到3用于2GB内存的服务器
sudo -u git -H editor config/unicorn.rb

# Copy the example Rack attack config
# 复制Rack attack的示例配置文件
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb

# Configure Git global settings for git user, useful when editing via web
# Edit user.email according to what is set in config/gitlab.yml
# 为git用户配置Git全局设定,当通过web修改时有用
# 修改user.email根据config/gitlab.yml中的设定
sudo -u git -H git config --global user.name "GitLab"
sudo -u git -H git config --global user.email "gitlab@localhost"
sudo -u git -H git config --global core.autocrlf input

数据库配置

# MySQL only:
# 仅限MySQL:
sudo -u git cp config/database.yml.mysql config/database.yml

# MySQL and remote PostgreSQL only:
# Update username/password in config/database.yml.
# You only need to adapt the production settings (first part).
# If you followed the database guide then please do as follows:
# Change 'secure password' with the value you have given to $password
# You can keep the double quotes around the password
# 仅限MySQL和远程PostgreSQL:
# 在config/database.yml中更新用户名/密码;
# 你只需要适配生产设定(第一部分);
# 如果你跟从数据库向导,请按以下操作:
# 修改'secure password'使用你刚才设定的$password;
# 你可以保留密码两端的双引号。
sudo -u git -H editor config/database.yml

# PostgreSQL and MySQL:
# Make config/database.yml readable to git only
# PostgreSQL和MySQL:
# 设置config/database.yml仅对git可读。
sudo -u git -H chmod o-rwx config/database.yml

安装 Gems

cd /home/git/gitlab

# For users from China mainland only
# 仅限中国大陆用户
nano /home/git/gitlab/Gemfile
source "http://ruby.taobao.org" // 原始 source "https://rubygems.org/"

# For MySQL (note, the option says "without ... postgres")
sudo -u git -H bundle install --deployment --without development test postgres aws

Install GitLab shell

安装GitLab Shell

GitLab Shell是一个专门为GitLab开发的SSH访问和源管理软件。

# Go to the Gitlab installation folder:
# 转到GitLab安装目录:
cd /home/git/gitlab

# For users from China mainland only
# 仅限中国大陆用户
nano /home/git/gitlab/Gemfile
source "http://ruby.taobao.org" // 原始 source "https://rubygems.org/"

# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
# 运行gitlab-shell的安装任务(替换`REDIS_URL`如果有需要的话):
sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.6] REDIS_URL=redis://localhost:6379 RAILS_ENV=production

# By default, the gitlab-shell config is generated from your main gitlab config.
# 默认的,gitlab-shell的配置文件是由你的gitlab主配置文件生成的。
#
# Note: When using GitLab with HTTPS please change the following:
# - Provide paths to the certificates under `ca_file` and `ca_path options.
# - The `gitlab_url` option must point to the https endpoint of GitLab.
# - In case you are using self signed certificate set `self_signed_cert` to `true`.
# See #using-https for all necessary details.
# 提示:当通过HTTPS使用GitLab时,请做出如下更改:
# - 提供证书的路径在`ca_file`和`ca_path`选项;
# - `gitlab_url`选项必须指向GitLab的https端点;
# - 如果你使用自签名的证书,设置`self-signed_cert`为`true`。
# 所有必需的具体细节参见#using-https
#
# You can review (and modify) it as follows:
# 你可以检查(并修改该)通过以下方法:
sudo -u git -H editor /home/git/gitlab-shell/config.yml

# Ensure the correct SELinux contexts are set
# Read http://wiki.centos.org/HowTos/Network/SecuringSSH
# 确保正确的SELinux上下文被设置
# 阅读http://wiki.centos.org/HowTos/Network/SecuringSSH
restorecon -Rv /home/git/.ssh

初始化数据库和激活高级功能

sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
# Type 'yes' to create the database tables.
# When done you see 'Administrator account created:'

提示:你可以设置管理员密码通过在环境变量GITLAB_ROOT_PASSWORD中提供,例如:

sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=newpassword

安装初始化脚本

下载初始化脚本(将放在/etc/init.d/gitlab):

sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
chmod +x /etc/init.d/gitlab
chkconfig --add gitlab

设置GitLab开机启动:

chkconfig gitlab on

设置日志翻转

cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab

检查应用状态

sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production

编译静态文件

sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production

启动实例

/etc/init.d/gitlab start
2015年五月5日上午 11:16:04 布隆过滤器 -- 空间效率很高的数据结构

哈希 hash

原理

Hash (哈希,或者散列)函数在计算机领域,尤其是数据快速查找领域,加密领域用的极广。

其作用是将一个大的数据集映射到一个小的数据集上面(这些小的数据集叫做哈希值,或者散列值)

一个应用是Hash table(散列表,也叫哈希表),是根据哈希值 (Key value) 而直接进行访问的数据结构。也就是说,它通过把哈希值映射到表中一个位置来访问记录,以加快查找的速度。下面是一个典型的 hash 函数 / 表示意图:

图片描述

哈希函数有以下两个特点:

缺点: 引用吴军博士的《数学之美》中所言,哈希表的空间效率还是不够高。如果用哈希表存储一亿个垃圾邮件地址,每个email地址 对应 8bytes, 而哈希表的存储效率一般只有50%,因此一个email地址需要占用16bytes. 因此一亿个email地址占用1.6GB,如果存储几十亿个email address则需要上百GB的内存。除非是超级计算机,一般的服务器是无法存储的。

所以要引入下面的 Bloom Filter。

布隆过滤器 Bloom Filter

原理

如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢。

Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对 bit-map 的扩展, 它的原理是:

当一个元素被加入集合时,通过 KHash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:

优点

It tells us that the element either definitely is not in the set or may be in the set.

它的优点是空间效率查询时间都远远超过一般的算法,布隆过滤器存储空间和插入 / 查询时间都是常数O(k)。另外, 散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

缺点

但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

(误判补救方法是:再建立一个小的白名单,存储那些可能被误判的信息。)

另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

Example

可以快速且空间效率高的判断一个元素是否属于一个集合;用来实现数据字典,或者集合求交集。

如: Google chrome 浏览器使用bloom filter识别恶意链接(能够用较少的存储空间表示较大的数据集合,简单的想就是把每一个URL都可以映射成为一个bit)
得多,并且误判率在万分之一以下。
又如: 检测垃圾邮件

假定我们存储一亿个电子邮件地址,我们先建立一个十六亿二进制(比特),即两亿字节的向量,然后将这十六亿个二进制全部设置为零。对于每一个电子邮件地址 X,我们用八个不同的随机数产生器(F1,F2, ...,F8) 产生八个信息指纹(f1, f2, ..., f8)。再用一个随机数产生器 G 把这八个信息指纹映射到 1 到十六亿中的八个自然数 g1, g2, ...,g8。现在我们把这八个位置的二进制全部设置为一。当我们对这一亿个 email 地址都进行这样的处理后。一个针对这些 email 地址的布隆过滤器就建成了。

再如此题:

A,B 两个文件,各存放 50 亿条 URL,每条 URL 占用 64 字节,内存限制是 4G,让你找出 A,B 文件共同的 URL。如果是三个乃至 n 个文件呢?

分析 :如果允许有一定的错误率,可以使用 Bloom filter,4G 内存大概可以表示 340 亿 bit。将其中一个文件中的 url 使用 Bloom filter 映射为这 340 亿 bit,然后挨个读取另外一个文件的 url,检查是否与 Bloom filter,如果是,那么该 url 应该是共同的 url(注意会有一定的错误率)。”

Network applications of Bloom Filter: a survey

2015年五月5日上午 11:05:30 node.js通过get访问该网址,返回302?

网址:http://search.jd.com/Search?keyword=%E7%AC%94%E8%AE%B0%E6%9C%AC&enc=utf-8 该网址是京东搜索笔记本的url;

2015年五月5日上午 10:52:28 alsotang starred coocood/freecache
alsotang starred coocood/freecache
2015年五月5日早上 7:55:36 命令行模式下的思维导图:mindmap

安装:

npm install mindmap

截图:

screenshot

用法:

  load [mindmap name] or l [mindmap name]
    Load or create a mindmap.
  add [id] [text] or a [id] [text]
    Add a child.
  insert [id] [text] or i [id] [text]
    Insert a node.
        insert -1 Animal
        insert 10 White house
  edit [id] [text] or e [id] [text]
    Edit a node.
  delete [id] or del [id] or d [id]
    Delete a node.
  help or h
    Help information.
  exit
    Exit program.
2015年五月4日晚上 11:57:41 请教这段代码cluster worker和net模块一起使用为何会出现阻塞?

很神奇的是,在master里直接调用socketHandler的话ab测试下不会阻塞

在worker里调用,ab测试并发数为1的时候不会阻塞

但像下面这样写,ab测试

ab -n 10 -c 2 http://localhost:10086

请求直接就阻塞了。。。

var net = require('net');
var http = require('http');
var cluster = require('cluster');

var recevice_socket_count = 0;
var recevice_http_count = 0;

var httpServer = http.createServer(function(req,res){
    console.log('recevice_http_count',++recevice_http_count);
    res.write('wow');
    res.end();
});

function socketHandler(socket){
  console.log('recevice_socket_count',++recevice_socket_count);
  socket.readable = socket.writeable = true;
  httpServer.emit('connection',socket);
  socket.emit('connect');
}


if(cluster.isMaster){
  var worker = cluster.fork();
  net.createServer(function(socket){
    worker.send("socket", socket);
    //socketHandler(socket);
  }).listen(10086, function() {
    console.log('netServer bound');
  });
}else{

  cluster.worker.on("message", function(data, socket) {
        socketHandler(socket);
  });
}

2015年五月4日晚上 10:43:37 做了个模板引擎 nging

安装:

npm install nging

用法:

var app = require("express")();
var nging = require("nging");
 
function Comment() {
    this.jml = function() {
        return ["div",{className:"comment"},
                    ["h2",{className:"commentAuthor", style:this.props.style},
                        this.props.author
                    ]
               ].concat(this.props.nodes);
    };
}
 
function CommentList() {
    this.jml = function() {
        var commentNodes = this.props.data.map(function (comment) {
          return [Comment, {author:comment.author}, comment.text];
        });
 
        return ["div", {className:"commentList"}].concat(commentNodes);
    };
}
 
var comments = [
  {"author": "Pete Hunt", "text": "This is one comment"},
  {"author": "Jordan Walke", "text": "This is *another* comment"}
];
 
var jml1 = ["div",{class:"yes"},["p",{style:"color:red"},"Hello!!!",["img",{src:"http://i.imgur.com/gEZsVCW.png",width:500}]]];
var jml2 = ["html",["body",["div","dfe"],["span","ddd"]]];
 
var jml3 = [Comment, {author:"John", style:"color:red"}, "Hello!"];
 
var jml4 = ["html",jml3,jml3,jml3,jml3,jml3,jml3,jml3];
 
var jml5 =["form",{className:"commentForm", onSubmit:"this.handleSubmit"},
            ["input", {type:"text", placeholder:"Your name", ref:"author"}],
            ["input", {type:"text", placeholder:"Say something...", ref:"text"}],
            ["input", {type:"submit", value:"Post"}]
        ];
 
var jml6 =[CommentList, {data:comments}];
 
var jml7 = ["html", jml1,jml2,jml3,jml4,jml5];
 
app.get("/", function(req, res) {
    res.send(nging.render(jml7));
});
 
app.listen(8080);

2015年五月4日晚上 10:27:23 请问有什么分析日志的好的方法或者工具么?

比如在我的node服务里,我把每一个请求完成的响应时间都写在了本地的log文件里,我想统计所有请求的平均响应时间,是不是只能靠编写复杂的shell脚本来实现?另外关于日志文件,有什么好的管理方法么?比如定期清理过时的日志文件应该怎么做呢?

2015年五月4日晚上 9:53:45 [译] Python 学习 —— __init__() 方法 2

通过工厂函数对 __init__() 加以利用

我们可以通过工厂函数来构建一副完整的扑克牌。这会比枚举所有52张扑克牌要好得多,在Python中,我们有如下两种常见的工厂方法:

在Python中,类并不是必须的。只是当有相关的工厂非常复杂的时候才会显现出优势。Python的优势就是当一个简单的函数可以做的更好的时候我们决不强迫使用类层次结构。

虽然这是一本关于面向对象编程的书,但函数真是一个好东西。这在Python中是常见的也是最地道的。

如果需要的话,我们总是可以将一个函数重写为适当的可调用对象。我们可以将一个可调用对象重构到我们的工厂类层次结构中。我们将在第五章《使用可调用对象和上下文》中学习可调用对象。

一般,类定义的优点是通过继承实现代码重用。工厂类的函数就是包装一些目标类层次结构和复杂对象的构造。如果我们有一个工厂类,当扩展目标类层次结构的时候,我们可以添加子类到工厂类中。这给我们提供了多态性工厂类;不同的工厂类定义具有相同的方法签名,可以交替使用。

这类水平的多态性对于静态编译语言如Java或C++非常有用。编译器可以解决类和方法生成代码的细节。

如果选择的工厂定义不能重用任何代码,则在Python中类层次结构不会有任何帮助。我们可以简单的使用具有相同签名的函数。

以下是我们各种Card子类的工厂函数:

pythondef card(rank, suit):
    if rank == 1:
        return AceCard('A', suit)
    elif 2 <= rank < 11: 
        return NumberCard(str(rank), suit)

    elif 11 <= rank < 14:
        name = {11: 'J', 12: 'Q', 13: 'K' }[rank]
        return FaceCard(name, suit)
    else:
        raise Exception("Rank out of range")

这个函数通过数值类型的ranksuit对象构建Card类。我们现在可以非常简单的构建牌了。我们已经封装构造问题到一个单一的工厂函数中,允许应用程序在不知道精确的类层次结构和多态设计是如何工作的情况下进行构建。

下面是一个如何通过这个工厂函数构建一副牌的示例:

pythondeck = [card(rank, suit) for rank in range(1,14) for suit in (Club, Diamond, Heart, Spade)]

它枚举了所有的牌值和花色来创建完整的52张牌。

1. 错误的工厂设计和模糊的else子句

注意card()函数里面的if语句结构。我们没有使用“包罗万象”的else子句来做任何处理;我们只是抛出异常。使用“包罗万象”的else子句会引出一个小小的辩论。

一方面,从属于else子句的条件不能不言而喻,因为它可能隐藏着微妙的设计错误。另一方面,一些else子句确实是显而易见的。

重要的是要避免含糊的else子句。

考虑下面工厂函数定义的变体:

pythondef card2(rank, suit):
    if rank == 1: 
        return AceCard('A', suit)
    elif 2 <= rank < 11: 
        return NumberCard(str(rank), suit)
    else:
        name = {11: 'J', 12: 'Q', 13: 'K'}[rank]
        return FaceCard(name, suit)

以下是当我们尝试创建整副牌将会发生的事情:

pythondeck2 = [card2(rank, suit) for rank in range(13) for suit in (Club, Diamond, Heart, Spade)]

它起作用了吗?如果if条件更复杂了呢?

一些程序员扫视的时候可以理解这个if语句。其他人将难以确定是否所有情况都正确执行了。

对于高级Python编程,我们不应该把它留给读者去演绎条件是否适用于else子句。对于菜鸟条件应该是显而易见的,至少也应该是显示的。

何时使用“包罗万象”的else

尽量的少使用。使用它只有当条件是显而易见的时候。当有疑问时,显式的并抛出异常。

避免含糊的else子句。

2. 简单一致的使用elif序列

我们的工厂函数card()是两种常见工厂设计模式的混合物:

为了简单起见,最好是专注于这些技术的一个而不是两个。

我们总是可以用映射来代替elif条件。(是的,总是。但相反是不正确的;改变elif条件为映射将是具有挑战性的。)

以下是没有映射的Card工厂:

pythondef card3(rank, suit):
    if rank == 1: 
        return AceCard('A', suit)
    elif 2 <= rank < 11: 
        return NumberCard(str(rank), suit)
    elif rank == 11:
        return FaceCard('J', suit)
    elif rank == 12:
        return FaceCard('Q', suit)
    elif rank == 13:
        return FaceCard('K', suit)
    else:
        raise Exception("Rank out of range")

我们重写了card()工厂函数。映射已经转化为额外的elif子句。这个函数有个优点就是它比之前的版本更加一致。

3. 简单的使用映射和类对象

在一些示例中,我们可以使用映射来代替一连串的elif条件。很可能发现条件太复杂,这个时候或许只有使用一连串的elif条件来表达才是明智的选择。对于简单示例,无论如何,映射可以做的更好且可读性更强。

因为class是最好的对象,我们可以很容易的映射rank参数到已经构造好的类中。

以下是仅使用映射的Card工厂:

python def card4(rank, suit):
    class_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard)
    return class_(rank, suit)

我们已经映射rank对象到类中。然后,我们传递rank值和suit值到类来创建最终的Card实例。

最好我们使用defaultdict类。无论如何,对于微不足道的静态映射不会比这更简单了。看起来像下面代码片段那样:

pythondefaultdict(lambda: NumberCard, {1: AceCard, 11: FaceCard, 12: FaceCard, 12: FaceCard})

注意:defaultdict默认必须是零参数的函数。我们已经使用了lambda创建必要的函数来封装常量。这个函数,无论如何,都有一些缺陷。对于我们之前版本中缺少1A13K的转换。当我们试图增加这些特性时,一定会出现问题的。

我们需要修改映射来提供可以和字符串版本的rank对象一样的Card子类。对于这两部分的映射我们还可以做什么?有四种常见解决方案:

我们来看看每一个具体的例子。

3.1. 两个并行映射

以下是两个并行映射解决方案的关键代码:

pythonclass_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard)
rank_str= {1:'A', 11:'J', 12:'Q', 13:'K'}.get(rank, str(rank))
return class_(rank_str, suit)

这并不可取的。它涉及到重复映射键1111213序列。重复是糟糕的,因为在软件更新后并行结构依然保持这种方式。

不要使用并行结构

并行结构必须使用元组或一些其他合适的集合来替代。

3.2. 映射到元组的值

以下是二元组映射的关键代码:

pythonclass_, rank_str= {
    1: (AceCard,'A'),
    11: (FaceCard,'J'),
    12: (FaceCard,'Q'),
    13: (FaceCard,'K'),
}.get(rank, (NumberCard, str(rank)))
return class_(rank_str, suit)

这是相当不错的。不需要过多的代码来分类打牌中的特殊情况。当我们需要改变Card类层次结构来添加额外的Card子类时,我们将看到它如何被修改或被扩展。

rank值映射到一个类对象的确让人感觉奇怪,且只有类初始化所需两个参数中的其中之一。将牌值映射到一个简单的类或没有提供一些混乱参数(但不是所有)的函数对象似乎会更合理。

3.3. partial函数解决方案

相比映射到二元组函数和参数之一,我们可以创建一个partial()函数。这是一个已经提供一些(但不是所有)参数的函数。我们将从functools库中使用partial()函数来创建一个带有rank参数的partial类。

以下是一个映射rankpartial()函数,可用于对象创建:

pythonfrom functools import partial
part_class= {
   1: partial(AceCard, 'A'),
   11: partial(FaceCard, 'J'),
   12: partial(FaceCard, 'Q'),
   13: partial(FaceCard, 'K'),
}.get(rank, partial(NumberCard, str(rank)))
return part_class(suit)

映射将rank对象与partial()函数联系在一起,并分配给part_class。这个partial()函数可以被应用到suit对象来创建最终的对象。partial()函数是一种常见的函数式编程技术。它在我们有一个函数来替代对象方法这一特定的情况下使用。

不过总体而言,partial()函数对于大多数面向对象编程并没有什么帮助。相比创建partial()函数,我们可以简单地更新类的方法来接受不同组合的参数。partial()函数类似于给对象构造创建一个连贯的接口。

3.4. 连贯的工厂类接口

在某些情况下,我们设计的类为方法的使用定义了顺序,衡量方法的顺序很像partial()函数。

在一个对象表示法中我们可能会有x.a() .b()。我们可以把它当成x(a, b)x.a()函数是等待b()的一类partial()函数。我们可以认为它就像x(a)(b)那样。

这里的想法是,Python给我们提供两种选择来管理状态。我们既可以更新对象又可以创建有状态性的(在某种程度上)partial()函数。由于这种等价,我们可以重写partial()函数到一个连贯的工厂对象中。我们使得rank对象的设置为一个连贯的方法来返回self。设置suit对象将真实的创建Card实例。

以下是一个连贯的Card工厂类,有两个方法函数,必须在特定顺序中使用:

pythonclass CardFactory:
    def rank(self, rank):
        self.class_, self.rank_str= {
                1: (AceCard, 'A'),
                11: (FaceCard,'J'),
                12: (FaceCard,'Q'),
                13: (FaceCard,'K'),
        }.get(rank, (NumberCard, str(rank)))
        return self
    def suit(self, suit):
        return self.class_(self.rank_str, suit)

rank()方法更新构造函数的状态,suit()方法真实的创建了最终的Card对象。

这个工厂类可以像下面这样使用:

pythoncard8 = CardFactory()
deck8 = [card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]

首先,我们创建一个工厂实例,然后我们使用那个实例创建Card实例。这并没有实质性改变__init__()本身在Card类层次结构中如何运作的。然而,它确实改变了我们客户端应用程序创建对象的方式。

2015年五月4日晚上 9:39:35 关于 this 的四类用法

this

在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。

在《javaScript语言精粹》这本书中,把 this 出现的场景分为四类,简单的说就是:

1) 函数有所属对象时:指向所属对象

函数有所属对象时,通常通过 . 表达式调用,这时 this 自然指向所属对象。比如下面的例子:

jsvar myObject = {value: 100};
myObject.getValue = function () {
  console.log(this.value);  // 输出 100

  // 输出 { value: 100, getValue: [Function] },
  // 其实就是 myObject 对象本身
  console.log(this);

  return this.value;
};

console.log(myObject.getValue()); // => 100

getValue() 属于对象 myObject,并由 myOjbect 进行 . 调用,因此 this 指向对象 myObject

2) 函数没有所属对象:指向全局对象

jsvar myObject = {value: 100};
myObject.getValue = function () {
  var foo = function () {
    console.log(this.value) // => undefined
    console.log(this);// 输出全局对象 global
  };

  foo();

  return this.value;
};

console.log(myObject.getValue()); // => 100

在上述代码块中,foo 函数虽然定义在 getValue 的函数体内,但实际上它既不属于 getValue 也不属于 myObjectfoo 并没有被绑定在任何对象上,所以当调用时,它的 this 指针指向了全局对象 global

据说这是个设计错误。

3) 构造器中的 this:指向新对象

js 中,我们通过 new 关键词来调用构造函数,此时 this 会绑定在该新对象上。

js
var SomeClass = function(){ this.value = 100; } var myCreate = new SomeClass(); console.log(myCreate.value); // 输出100

顺便说一句,在 js 中,构造函数、普通函数、对象方法、闭包,这四者没有明确界线。界线都在人的心中。

4) apply 和 call 调用以及 bind 绑定:指向绑定的对象

apply() 方法接受两个参数第一个是函数运行的作用域,另外一个是一个参数数组(arguments)。

call() 方法第一个参数的意义与 apply() 方法相同,只是其他的参数需要一个个列举出来。

简单来说,call 的方式更接近我们平时调用函数,而 apply 需要我们传递 Array 形式的数组给它。它们是可以互相转换的。

jsvar myObject = {value: 100};

var foo = function(){
  console.log(this);
};

foo(); // 全局变量 global
foo.apply(myObject); // { value: 100 }
foo.call(myObject); // { value: 100 }

var newFoo = foo.bind(myObject);
newFoo(); // { value: 100 }
2015年五月4日晚上 9:09:16 alsotang commented on commit cnodejs/nodeclub@67c098202b
alsotang commented on commit cnodejs/nodeclub@67c098202b
@alsotang

无论mongodb还是redis都有自动过期的策略,session 不会一直存 在 2015年5月4日 下午8:11,yanjixiong <notifications@github.com>写道:

2015年五月4日晚上 7:04:20 哪位大大能帮我看下,为啥BAE里我用mongoose close了db,然后重新open就 报错了

报错为: name: 'MongoError’, message: ‘only GSSAPI, PLAIN, MONGODB-X509, SCRAM-SHA-1 or MONGODB-CR is supported by authMechanism’

代码:var mongoose = require(‘mongoose’); var format = require(‘util’).format;

if(process.env.BAE_ENV_APPID){
var host ="mongo.duapp.com"; var port ="8908"; var username ="bfc5c2ab184a479292b3fd20b012a683"; var password ="d1b72bc45df94eb4b05a4ffc9f0416af"; var dbName ="aJZfHIoouaDARdxxYghA"; var constr ="mongodb://"+ username +":"+ password +"@"+ host +":"+ port +"/"+ dbName; }else{ var constr = “mongodb://localhost/ticket” var username =null; var password =null;

}

//constr = "mongodb://localhost/ticket";

var options = { db: { native_parser: true }, server: { poolSize:4, socketOptions: { keepAlive: 1 } }, user: username, pass: password }; db = mongoose.createConnection(); var userSchema = new mongoose.Schema({ name:String, password:String},{ collection : ‘users’ });

db.open(constr,options); var userModel = db.model('users’,userSchema);

function User(user){ this.name = user.name; this.password = user.password; } setInterval(function(){console.log(‘closing db now’);db.close();}, 1000601); setInterval(function(){console.log(‘opening db now’);db.open(constr,options,function(err) { if(err){console.log(err);} });}, 1010601);

db.on('open’,function(){ console.log('connection success open’+’mongooseConnection.readyState :’+db.readyState );

    //setTimeout(function(){console.log('closing db now');mongoose.disconnect();}, 1000*60*4);
});

db.on('close’,function(err){ console.log(‘closed’);

});

2015年五月4日晚上 6:30:53 alsotang commented on commit cnodejs/nodeclub@67c098202b
alsotang commented on commit cnodejs/nodeclub@67c098202b
@alsotang

yanjixiong,看这里 https://github.com/cnodejs/nodeclub/issues/421 redis 快 2015-05-04 13:02 GMT+08:00 yanjixiong <notifications@github.com>:

2015年五月4日下午 4:23:13 如何查看cluster开启多多少个之进程?

//fork:复制一个工作进程后触发该事件。 cluster.on(‘fork’, function (worker) { console.log( '[master] fork: worker’ + worker.id +" start"); console.log( cluster.workers.length ); }); 为何每次都是undefine?应该如何查看cluster开启了多少个子进程?

2015年五月4日下午 4:10:51 npm 卸载的包还可以恢复吗?

安装了一个全局包,然后直接在源码上修改了些东西,结果手贱不小心卸载(npm uninstall)了,请问还有什么方法可以恢复这个包吗?

2015年五月4日下午 4:07:07 Mongoose 使用 Population 填充'关联表'数据

MongooseMongoDBODM(Object Document Mapper)

因为MongoDB是文档型数据库,所以它没有关系型数据库[joins](http://zh.wikipedia.org/wiki/%E8%BF%9E%E6%8E%A5_(SQL)(数据库的两张表通过"外键",建立连接关系。) 特性。也就是在建立数据的关联时会比较麻烦。为了解决这个问题,Mongoose封装了一个Population功能。使用Population可以实现在一个 document 中填充其他 collection(s)document(s)

在定义Schema的时候,如果设置某个 field 关联另一个Schema,那么在获取 document 的时候就可以使用 Population 功能通过关联Schema的 field 找到关联的另一个 document,并且用被关联 document 的内容替换掉原来关联字段(field)的内容。

接下来分享下:Query#populate Model#populate Document#populate的用法

先建立三个SchemaModel:

javascriptvar mongoose = require('mongoose');
var Schema   = mongoose.Schema;

var UserSchema = new Schema({
    name  : { type: String, unique: true },
    posts : [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
var User = mongoose.model('User', UserSchema);

var PostSchema = new Schema({
    poster   : { type: Schema.Types.ObjectId, ref: 'User' },
    comments : [{ type: Schema.Types.ObjectId, ref: 'Comment' }],
    title    : String,
    content  : String
});
var Post = mongoose.model('Post', PostSchema);

var CommentSchema = new Schema({
    post      : { type: Schema.Types.ObjectId, ref: "Post" },
    commenter : { type: Schema.Types.ObjectId, ref: 'User' },
    content   : String
});
var Comment = mongoose.model('Comment', CommentSchema);

创建一些数据到数据库:

javascript// 连接数据库
mongoose.connect('mongodb://localhost/population-test', function (err){
    if (err) throw err;
    createData();
});

function createData() {

    var userIds    = [new ObjectId, new ObjectId, new ObjectId];
    var postIds    = [new ObjectId, new ObjectId, new ObjectId];
    var commentIds = [new ObjectId, new ObjectId, new ObjectId];

    var users    = [];
    var posts    = [];
    var comments = [];

    users.push({
        _id   : userIds[0],
        name  : 'aikin',
        posts : [postIds[0]]
    });
    users.push({
        _id   : userIds[1],
        name  : 'luna',
        posts : [postIds[1]]
    });
    users.push({
        _id   : userIds[2],
        name  : 'luajin',
        posts : [postIds[2]]
    });

    posts.push({
        _id      : postIds[0],
        title    : 'post-by-aikin',
        poster   : userIds[0],
        comments : [commentIds[0]]
    });
    posts.push({
        _id      : postIds[1],
        title    : 'post-by-luna',
        poster   : userIds[1],
        comments : [commentIds[1]]
    });
    posts.push({
        _id      : postIds[2],
        title    : 'post-by-luajin',
        poster   : userIds[2],
        comments : [commentIds[2]]
    });

    comments.push({
        _id       : commentIds[0],
        content   : 'comment-by-luna',
        commenter : userIds[1],
        post      : postIds[0]
    });
    comments.push({
        _id       : commentIds[1],
        content   : 'comment-by-luajin',
        commenter : userIds[2],
        post      : postIds[1]
    });
    comments.push({
        _id       : commentIds[2],
        content   : 'comment-by-aikin',
        commenter : userIds[1],
        post      : postIds[2]
    });

    User.create(users, function(err, docs) {
        Post.create(posts, function(err, docs) {
            Comment.create(comments, function(err, docs) {
            });
        });
    });
}

数据的准备就绪后,接下来就是探索populate方法:

1. Query#populate

什么Query? Query(查询),可以快速和简单的从MongooDB查找出相应的 document(s)。 Mongoose 封装了很多查询的方法,使得对数据库的操作变得简单啦。这里分享一下populate方法用法。

语法:
Query.populate(path, [select], [model], [match], [options])

参数:

path
  类型:StringObject
  String类型的时, 指定要填充的关联字段,要填充多个关联字段可以以空格分隔。
  Object类型的时,就是把 populate 的参数封装到一个对象里。当然也可以是个数组。下面的例子中将会实现。

select
  类型:ObjectString,可选,指定填充 document 中的哪些字段。
  Object类型的时,格式如:{name: 1, _id: 0},为0表示不填充,为1时表示填充。
  String类型的时,格式如:"name -_id",用空格分隔字段,在字段名前加上-表示不填充。详细语法介绍 query-select

model
  类型:Model,可选,指定关联字段的 model,如果没有指定就会使用Schemaref

match
  类型:Object,可选,指定附加的查询条件。

options
  类型:Object,可选,指定附加的其他查询选项,如排序以及条数限制等等。

javascript//填充所有 users 的 posts
User.find()
    .populate('posts', 'title', null, {sort: { title: -1 }})
    .exec(function(err, docs) {
        console.log(docs[0].posts[0].title); // post-by-aikin
    });

//填充 user 'luajin'的 posts
User.findOne({name: 'luajin'})
    .populate({path: 'posts', select: { title: 1 }, options: {sort: { title: -1 }}})
    .exec(function(err, doc) {
        console.log(doc.posts[0].title);  // post-by-luajin
    });

//这里的 populate 方法传入的参数形式不同,其实实现的功能是一样的,只是表示形式不一样。

javascriptPost.findOne({title: 'post-by-aikin'})
    .populate('poster comments', '-_id')
    .exec(function(err, doc) {
        console.log(doc.poster.name);           // aikin
        console.log(doc.poster._id);            // undefined

        console.log(doc.comments[0].content);  // comment-by-luna
        console.log(doc.comments[0]._id);      // undefined
    });

Post.findOne({title: 'post-by-aikin'})
    .populate({path: 'poster comments', select: '-_id'})
    .exec(function(err, doc) {
        console.log(doc.poster.name);           // aikin
        console.log(doc.poster._id);            // undefined

        console.log(doc.comments[0].content);  // comment-by-luna
        console.log(doc.comments[0]._id);      // undefined
    });

//上两种填充的方式实现的功能是一样的。就是给 populate 方法的参数不同。
//这里要注意,当两个关联的字段同时在一个 path 里面时, select 必须是 document(s)
//具有的相同字段。


//如果想要给单个关联的字段指定 select,可以传入数组的参数。如下:

Post.findOne({title: 'post-by-aikin'})
    .populate(['poster', 'comments'])
    .exec(function(err, doc) {
        console.log(doc.poster.name);          // aikin
        console.log(doc.comments[0].content);  // comment-by-luna
    });

Post.findOne({title: 'post-by-aikin'})
    .populate([
        {path:'poster',   select: '-_id'},
        {path:'comments', select: '-content'}
    ])
    .exec(function(err, doc) {
        console.log(doc.poster.name);          // aikin
        console.log(doc.poster._id);           // undefined

        console.log(doc.comments[0]._id);      // 会打印出对应的 comment id
        console.log(doc.comments[0].content);  // undefined
    });

2. Model#populate

Model(模型),是根据定义的 Schema 编译成的抽象的构造函数。models 的实例 documents,可以在数据库中被保存和检索。数据库所有 document 的创建和检索,都通过 models 处理。

语法:
Model.populate(docs, options, [cb(err,doc)])

参数:

docs
  类型:DocumentArray。单个需要被填充的 doucment 或者 document 的数组。

options
  类型:Object。以键值对的形式表示。
  keys:path select match model options,这些键对应值的类型和功能,与上述Query#populate方法的参数相同。

[cb(err,doc)]
  类型:Function,回调函数,接收两个参数,错误err和填充完的doc(s)

javacriptPost.find({title: 'post-by-aikin'})
    .populate('poster comments')
    .exec(function(err, docs) {

        var opts = [{
            path   : 'comments.commenter',
            select : 'name',
            model  : 'User'
        }];

        Post.populate(docs, opts, function(err, populatedDocs) {
            console.log(populatedDocs[0].poster.name);                  // aikin
            console.log(populatedDocs[0].comments[0].commenter.name);  // luna
        });
    });

3. Document#populate

Document,每个 document 都是其 Model 的一个实例,一对一的映射着 MongoDB 的 document。

语法:
Document.populate([path], [callback])

参数:

path
  类型:StringObject。与上述Query#populate`方法的 path 参数相同。

callback
  类型:Function。回调函数,接收两个参数,错误err和填充完的doc(s)

javascriptUser.findOne({name: 'aikin'})
    .exec(function(err, doc) {

        var opts = [{
            path   : 'posts',
            select : 'title'
        }];

        doc.populate(opts, function(err, populatedDoc) {
            console.log(populatedDoc.posts[0].title);  // post-by-aikin
        });
    });

博文涉及的完整例子在 gist 上。(ps: gist 被已墙了。)

参考

原文链接

2015年五月4日下午 4:01:58 Node crypto.final() 似乎导致与 PHP mcrypt_encrypt 两方加密结果不同?

加密演算法我使用的是 Triple DES,而以下会出现的 key,IV 我已经先行替换了。


首先我使用的是 Node.js crypto 做加密。

var secretKey  = new Buffer('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), // 48 chars
    iv         = new Buffer('bbbbbbbbbbbbbbbb', 'hex'); // 16 chars
var str        = 'This string will be encrypted.';
var cipher     = crypto.createCipheriv('des-ede3-cbc', secretKey, iv),
    cryptedStr = cipher.update(str, 'utf8', 'base64') + cipher.final('base64');

再來是协作方的系统,使用的是 PHP 的 mcrypt。

$key    = pack('H*', "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 
$iv     = pack('H*', "bbbbbbbbbbbbbbbb"); 
$string = 'This string will be encrypted.';
$text   = mcrypt_encrypt(MCRYPT_3DES, $key, $string, MCRYPT_MODE_CBC, $iv);
$text_base64 = base64_encode($text);

我遇到的问题是,明明使用相同的 Key / IV、演算法以及编码方式,但结果就是会有一部分不同。

而观察之下,不同处却是 cipher.final() 所导致?

// Node.js output.
UKBI17EIHKNM2EU48ygsjil5r58Eo1csByAIFp9GhUw=
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    Same part

// PHP output.
UKBI17EIHKNM2EU48ygsjil5r58Eo1csAY4C0JZoyco=
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    Same part

还请教各位大侠、前辈,究竟是为什么呢?

2015年五月4日下午 3:19:38 文件归档工具——category

我有个习惯就是定期将手机里的照片拷贝到电脑上,整理归档,然后上传到网盘。

整理之前是这样的:

Camera/
├── IMG_20150425_133502.jpg
├── IMG_20150426_134524.jpg
├── IMG_20150427_123602.jpg
└── IMG_20150427_221603.jpg
...

整理之后是这样的:

Camera/
├── 2015-04-25/
│   └── IMG_20150425_133502.jpg
├── 2015-04-26/
│   └── IMG_20150426_134524.jpg
└── 2015-04-27/
    ├── IMG_20150427_123602.jpg
    └── IMG_20150427_221603.jpg

话说以前每次都是手动整理的没用过啥软件 orz… ,由于很长时间没整理手机相册积攒了很多照片,于是昨天自己写了个归档的命令行工具。。。

传送门:https://github.com/nswbmw/node-category

2015年五月4日下午 2:50:28 使用Python解析nginx日志文件

项目的一个需求是解析nginx的日志文件。
简单的整理如下:


日志规则描述

首先要明确自己的Nginx的日志格式,这里采用默认Nginx日志格式:

 log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                   '$status $body_bytes_sent "$http_referer" '
                   '"$http_user_agent" "$http_x_forwarded_for"';

其中一条真实记录样例如下:

172.22.8.207 - - [16/Dec/2014:17:57:35 +0800] "GET /report?DOmjjuS6keWJp+WculSQAgdUkAIPODExMzAwMDJDN0FC HTTP/1.1" 200 0 "-" "XXXXXXX/1.0.16; iPhone/iOS 8.1.2; ; 8DA77E2F91D0"

其中,客户端型号信息用XXXXXXX代替。

项目中已经按照业务规则对Nginx日志文件进行了处理命名规则如下:

ID-ID-YYMMDD-hhmmss

并且所有的日志文件存放在统一路径下。

解决思路


获取所有日志文件path

这里使用Python的glob模块来获取日志文件path

import glob


def readfile(path):
    return glob.glob(path + '*-*-*-*')

获取日志文件中每一行的内容

使用Python的linecache模块来获取文件行的内容

import linecache


def readline(path):
    return linecache.getlines(path)

注意:linecache模块使用了缓存,所以存在以下问题:

  1. 在使用linecache模块读取文件内容以后,如果文件发生了变化,那么需要使用linecache.updatecache(filename)来更新缓存,以获取最新变化。

  2. linecache模块使用缓存,所以会耗费内存,耗费量与要解析的文件相关。最好在使用完毕后执行linecache.clearcache()清空一下缓存。

当然,作为优化,这里可以利用生成器来进行优化。暂且按下不表。

处理日志条目

一条日志信息就是一个特定格式的字符串,因此使用正则表达式来解析,这里使用Python的re模块。
下面,一条一条建立规则:

规则

    ip = r"?P<ip>[\d.]*"
    date = r"?P<date>\d+"
    month = r"?P<month>\w+"
    year = r"?P<year>\d+"
    log_time = r"?P<time>\S+"
    method = r"?P<method>\S+"
    request = r"?P<request>\S+"
    status = r"?P<status>\d+"
    bodyBytesSent = r"?P<bodyBytesSent>\d+"
    refer = r"""?P<refer>
             [^\"]*
             """
    userAgent=r"""?P<userAgent>
                .*
               """

解析

p = re.compile(r"(%s)\ -\ -\ \[(%s)/(%s)/(%s)\:(%s)\ [\S]+\]\ \"(%s)?[\s]?(%s)?.*?\"\ (%s)\ (%s)\ \"(%s)\"\ \"(%s).*?\"" %( ip, date, month, year, log_time, method, request, status, bodyBytesSent, refer, userAgent ), re.VERBOSE)
m = re.findall(p, logline)

这样,就可以得到日志条目中各个要素的原始数据。


格式及内容转化

得到日志原始数据之后,需要根据业务要求,对原始数据进行格式及内容转化。
这里需要处理的内容包括:时间,request,userAgent

时间格式转化

在日志信息原始数据中存在Dec这样的信息,利用Python的time模块可以方便的进行解析

import time


def parsetime(date, month, year, log_time):
    time_str = '%s%s%s %s' %(year, month, date, log_time)
    return time.strptime(time_str, '%Y%b%d %H:%M:%S')

解析request

在日志信息原始数据中得到的request的内容格式为:

/report?XXXXXX

这里只需要根据协议取出XXXXXX即可。
这里仍然采用Python的re模块

import re


def parserequest(rqst):
    param = r"?P<param>.*"
    p = re.compile(r"/report\?(%s)" %param, re.VERBOSE)
    return re.findall(p, rqst)

接下来需要根据业务协议解析参数内容。这里需要先利用base64模块解码,然后再利用struct模块解构内容:

import struct
import base64


def parseparam(param):
    decodeinfo = base64.b64decode(param)
    s = struct.Struct('!x' + bytes(len(decodeinfo) - (1 + 4 + 4 + 12)) + 'xii12x')
    return s.unpack(decodeinfo)

解析userAgent

在日志信息原始数据中userAgent数据的格式为:

XXX; XXX; XXX; XXX

根据业务要求,只需要取出最后一项即可。
这里采用re模块来解析。

import re


def parseuseragent(useragent):
    agent = r"?P<agent>.*"
    p = re.compile(r".*;.*;.*;(%s)" %agent, re.VERBOSE)
    return re.findall(p, useragent)

至此,nginx日志文件解析基本完成。
剩下的工作就是根据业务需要,对获得的基本信息进行处理。
(完)

2015年五月4日下午 12:36:05 客户-服务器程序设计方法

客户-服务器程序设计方法

《unix网络编程》第一卷中将客户服务器程序设计方法讲得透彻,这篇文章将其中编码的细节略去,通过伪代码的形式展现,主要介绍各种方法的思想;

示例是一个经典的TCP回射程序:
客户端发起连接请求,连接后发送一串数据;收到服务端的数据后输出到终端;
服务端收到客户端的数据后原样回写给客户端;

客户端伪代码:

sockfd = socket(AF_INET,SOCK_STREAM,0);
//与服务端建立连接
connect(sockfd);
//连接建立后从终端读入数据并发送到服务端;
//从服务端收到数据后回写到终端
while(fgets(sendline,MAXLINE,fileHandler)!= NULL){
    writen(sockfd,sendline,strlen(sendline));
    if(readline(sockfd,recvline,MAXLINE) == 0){
        cout << "recive over!";
    }
    fputs(recvline,stdout);
}

下面介绍服务端程序处理多个客户请求的开发范式;

多进程处理

对于多个客户请求,服务器端采用fork的方式创建新进程来处理;

处理流程:
1. 主进程绑定ip端口后,使用accept()等待新客户的请求;
2. 每一个新的用户请求到来,都创建一个新的子进程来处理具体的客户请求;
3. 子进程处理完用户请求,结束本进程;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
while(true){
    //服务器端在这里阻塞等待新客户连接
    connfd = accept(listenfd); 
    if( fork() ==0){//子进程
        close(listenfd);
        while(n=read(connfd,buf,MAXLINE)>0){
            writen(connfd,buf);
        }
    }
    close(connfd);
}

这种方法开发简单,但对操作系统而言,进程是一种昂贵的资源,对于每个新客户请求都使用一个进程处理,开销较大;
对于客户请求数不多的应用适用这种方法;

预先分配进程池,accept无上锁保护

上一种方法中,每来一个客户都创建一个进程处理请求,完毕后再释放;
不间断的创建和结束进程浪费系统资源;
使用进程池预先分配进程,通过进程复用,减少进程重复创建带来的系统消耗和时间等待;

优点:消除新客户请求到达来创建进程的开销;
缺点:需要预先估算客户请求的多少(确定进程池的大小)

源自Berkeley内核的系统,有以下特性:
派生的所有子进程各自调用accep()监听同一个套接字,在没有用户请求时都进入睡眠;
当有新客户请求到来时,所有的客户都被唤醒;内核从中选择一个进程处理请求,剩余的进程再次转入睡眠(回到进程池);

利用这个特性可以由操作系统来控制进程的分配;
内核调度算法会把各个连接请求均匀的分散到各个进程中;

处理流程:
1. 主进程预先分配进程池,所有子进程阻塞在accept()调用上;
2. 新用户请求到来,操作系统唤醒所有的阻塞在accpet上的进程,从其中选择一个建立连接;
3. 被选中的子进程处理用户请求,其它子进程回到睡眠;
4. 子进程处理完毕,再次阻塞在accept上;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
for(int i = 0;i< children;i++){
    if(fork() == 0){//子进程
        while(true){
            //所有子进程监听同一个套接字,等待用户请求
            int connfd = accept(listenfd);
            close(listenfd);
            //连接建立后处理用户请求,完毕后关闭连接
            while(n=read(connfd,buf,MAXLINE)>0){
                writen(connfd,buf);
            }
            close(connfd);
        }
    }
}

如何从进程池中取出进程?
所有的进程都通过accept()阻塞等待,等连接请求到来后,由内核从所有等待的进程中选择一个进程处理;

处理完的进程,如何放回到池子中?
子进程处理完客户请求后,通过无限循环,再次阻塞在accpet()上等待新的连接请求;

注意: 多个进程accept()阻塞会产生“惊群问题”:尽管只有一个进程将获得连接,但是所有的进程都被唤醒;这种每次有一个连接准备好却唤醒太多进程的做法会导致性能受损;

预先分配进程池,accept上锁(文件锁、线程锁)

上述不上锁的实现存在移植性的问题(只能在源自Berkeley的内核系统上)和惊群问题,
更为通用的做法是对accept上锁;即避免让多个进程阻塞在accpet调用上,而是都阻塞在获取锁的函数中;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
for(int i = 0;i< children;i++){
    if(fork() == 0){
        while(true){
            my_lock_wait();//获取锁
            int connfd = accept(listenfd);
            my_lock_release();//释放锁
            close(listenfd);
            while(n=read(connfd,buf,MAXLINE)>0){
                writen(connfd,buf);
            }
            close(connfd);
        }
    }
}

上锁可以使用文件上锁,线程上锁;
- 文件上锁的方式可移植到所有的操作系统,但其涉及到文件系统操作,可能比较耗时;
- 线程上锁的方式不仅适用不同线程之间的上锁,也适用于不同进程间的上锁;

关于上锁的编码细节详见《网络编程》第30章;

预先分配进程池,传递描述符;

与上面的每个进程各自accept接收监听请求不同,这个方法是在父进程中统一接收accpet()用户请求,在连接建立后,将连接描述符传递给子进程;

处理流程:
1. 主进程阻塞在accpet上等待用户请求,所有子进程不断轮询探查是否有可用的描述符;
2. 有新用户请求到来,主进程accpet建立连接后,从进程池中取出一个进程,通过字节流管道将连接描述符传递给子进程;
3. 子进程收到连接描述符,处理用户请求,处理完成后向父进程发送一个字节的内容(无实际意义),告知父进程我任务已完成;
4. 父进程收到子进程的单字节数据,将子进程放回到进程池;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
//预先建立子进程池
for(int i = 0;i< children;i++){
    //使用Unix域套接字创建一个字节流管道,用来传递描述符
    socketpair(AF_LOCAL,SOCK_STREAM,0,sockfd);
    if(fork() == 0){//预先创建子进程
        //子进程字节流到父进程
        dup2(sockfd[1],STDERR_FILENO);
        close(listenfd);
        while(true){
            //收到连接描述符
            if(read_fd(STDERR_FILENO,&connfd) ==0){; 
                continue;
            }
            while(n=read(connfd,buf,MAXLINE)>0){ //处理用户请求
                writen(connfd,buf);
            }
            close(connfd);
            //通知父进程处理完毕,本进程可以回到进程池
            write(STDERR_FILENO,"",1);
        }
    }
}

while(true){
    //监听listen套接字描述符和所有子进程的描述符
    select(maxfd+1,&rset,NULL,NULL,NULL);
    if(FD_ISSET(listenfd,&rset){//有客户连接请求
        connfd = accept(listenfd);//接收客户连接
        //从进程池中找到一个空闲的子进程
        for(int i = 0 ;i < children;i++){
            if(child_status[i] == 0)
                break;
        }
        child_status[i] = 1;//子进程从进程池中分配出去
        write_fd(childfd[i],connfd);//将描述符传递到子进程中
        close(connfd);
    }
    //检查子进程的描述符,有数据,表明已经子进程请求已处理完成,回收到进程池
    for(int i = 0 ;i < children;i++){
        if(FD_ISSET(childfd[i],&rset)){
            if(read(childfd[i])>0){
                child_status[i] = 0;
            }
        }
    }
}

多线程处理

为每个用户创建一个线程,这种方法比为每个用户创建一个进程要快出许多倍;

处理流程:
1. 主线程阻塞在accpet上等待用请求;
2. 有新用户请求时,主线程建立连接,然后创建一个新的线程,将连接描述符传递过去;
3. 子线程处理用户请求,完毕后线程结束;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
while(true){
    connfd = accept(listenfd);
        //连接建立后,创建新线程处理具体的用户请求
    pthread_create(&tid,NULL,&do_function,(void*)connfd);
    close(connfd);
}

--------------------
//具体的用户请求处理函数(子线程主体)
void * do_function(void * connfd){
    pthread_detach(pthread_self());
    while(n=read(connfd,buf,MAXLINE)>0){
        writen(connfd,buf);
    close((int)connfd);
}

预先创建线程池,每个线程各自accept

处理流程:
1. 主线程预先创建线程池,第一个创建的子线程获取到锁,阻塞在accept()上,其它子线程阻塞在线程锁上;
2. 用户请求到来,第一个子线程建立连接后释放锁,然后处理用户请求;完成后进入线程池,等待获取锁;
3. 第一个子线程释放锁之后,线程池中等待的线程有一个会获取到锁,阻塞在accept()等待用户请求;

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
//预先创建线程池,将监听描述符传给每个新创建的线程
for(int i = 0 ;i <threadnum;i++){
    pthread_create(&tid[i],NULL,&thread_function,(void*)connfd);
}

--------------------
//具体的用户请求处理
//通过锁保证任何时刻只有一个线程阻塞在accept上等待新用户的到来;其它的线程都
//在等锁;
void * thread_function(void * connfd){
    while(true){
        pthread_mutex_lock(&mlock); // 线程上锁
        connfd = accept(listenfd);
        pthread_mutex_unlock(&mlock);//线程解锁
        while(n=read(connfd,buf,MAXLINE)>0){
            writen(connfd,buf);
        close(connfd);
    }
}

使用源自Berkeley的内核的Unix系统时,我们不必为调用accept而上锁,
去掉上锁的两个步骤后,我们发现没有上锁的用户时间减少(因为上锁是在用户空间中执行的线程函数完成的),而系统时间却增加很多(每一个accept到达,所有的线程都变唤醒,引发内核的惊群问题,这个是在线程内核空间中完成的);
而我们的线程都需要互斥,让内核执行派遣还不让自己通过上锁来得快;

这里没有必要使用文件上锁,因为单个进程中的多个线程,总是可以通过线程互斥锁来达到同样目的;(文件锁更慢)

 预先创建线程池,主线程accept后传递描述符

处理流程:

  1. 主线程预先创建线程池,线程池中所有的线程都通过调用pthread_cond_wait()而处于睡眠状态(由于有锁的保证,是依次进入睡眠,而不会发生同时调用pthread_cond_wait引发竞争)
  2. 主线程阻塞在acppet调用上等待用户请求;
  3. 用户请求到来,主线程accpet建立建立,将连接句柄放入约定位置后,发送pthread_cond_signal激活一个等待该条件的线程;
  4. 线程激活后从约定位置取出连接句柄处理用户请求;完毕后再次进入睡眠(回到线程池);

激活条件等待的方式有两种:pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。

注:一般应用中条件变量需要和互斥锁一同使用;
在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
for(int i = 0 ;i <threadnum;i++){
    pthread_create(&tid[i],NULL,&thread_function,(void*)connfd);
}
while(true){
    connfd = accept(listenfd);
    pthread_mutex_lock(&mlock); // 线程上锁
    childfd[iput] = connfd;//将描述符的句柄放到数组中传给获取到锁的线程;
    if(++iput == MAX_THREAD_NUM)
        iput= 0;
    if(iput == iget)
        err_quit("thread num not enuough!");
    pthread_cond_signal(&clifd_cond);//发信号,唤醒一个睡眠线程(轮询唤醒其中的一个)
    pthread_mutex_unlock(&mlock);//线程解锁
}

--------------------
void * thread_function(void * connfd){
    while(true){
        pthread_mutex_lock(&mlock); // 线程上锁
        //当无没有收到连接句柄时,睡眠在条件变量上,并释放mlock锁
        //满足条件被唤醒后,重新加mlock锁
        while(iget == iput)
            pthread_cond_wait(&clifd_cond,&mlock);
        connfd = childfd[iget];
        if(++iget == MAX_THREAD_NUM)
            iget = 0;
        pthread_mutex_unlock(&mlock);//线程解锁
        //处理用户请求
        while(n=read(connfd,buf,MAXLINE)>0){
            writen(connfd,buf);
        close(connfd);
    }
}

测试表明这个版本的服务器要慢于每个线程各自accpet的版本,原因在于这个版本同时需要互斥锁和条件变量,而上一个版本只需要互斥锁;

线程描述符的传递和进程描述符的传递的区别?
在一个进程中打开的描述符对该进程中的所有线程都是可见的,引用计数也就是1;
所有线程访问这个描述符都只需要通过一个描述符的值(整型)访问;
而进程间的描述符传递,传递的是描述符的引用;(好比一个文件被2个进程打开,相应的这个文件的描述符引用计数增加2);

总结

参考资料

《unix网络编程》第一卷 套接字联网API

Posted by: 大CC
博客:blog.me115.com
微博:新浪微博

2015年五月4日下午 12:35:27 提交自己的包到bower、npm中

转载地址

bower

Bower 是 twitter 推出的一款包管理工具,基于nodejs的模块化思想,把功能分散到各个模块中,让模块和模块之间存在联系,通过 Bower 来管理模块间的这种联系。

bower官网

安装Bower

一旦你已经安装了上面所说的所有必要文件,键入以下命令安装Bower:

$ npm install -g bower

这行命令是Bower的全局安装,-g 操作表示全局。

使用bower

  1. 直接下载 git 库: bower install git://github.com/JSLite/JSLite.git
  2. github别名自动解析git库: bower install JSLite/JSLite
  3. 下载线上的任意文件: bower install http://foo.com/jquery.awesome-plugin.js
  4. 下载本地库: bower install ./repos/jquery

常用命令

$ bower install jquery --save 添加依赖并更新bower.json文件
$ bower cache clean 安装失败清除缓存
$ bower install storejs 安装storejs
$ bower uninstall storejs 卸载storejs

注册

添加配置文件

bower.json文件的使用可以让包的安装更容易,你可以在应用程序的根目录下创建一个名为 bower.json 的文件,并定义它的依赖关系。使用bower init 命令来创建bower.json文件:

$ bower init
? name: store.js
? version: 1.0.1
? description: "本地存储localstorage的封装,提供简单的AIP"
? authors: (kenny.wang <wowohoo@qq.co>)
? license: MIT
? homepage:
? set currently installed components as dependencies?: Yes
? add commonly ignored files to ignore list?: Yes
? would you like to mark this package as private which prevents it from being accidentally publis? would you like to mark this package as private which prevents it from being accidentally published to the registry?: No

{
  name: 'store.js',
  main: 'store.js',
  version: '1.0.1',
  authors: [
    '(kenny.wang <wowohoo@qq.co>)'
  ],
  description: '"本地存储localstorage的封装,提供简单的AIP"',
  moduleType: [
    'amd',
    'node'
  ],
  keywords: [
    'storejs'
  ],
  license: 'MIT',
  ignore: [
    '**/.*',
    'node_modules',
    'bower_components',
    'test',
    'tests'
  ]
}

? Looks good?: Yes

注册自己的包

可以注册自己的包,这样其他人也可以使用了,这个操作只是在服务器上保存了一个隐射,服务器本身不托管代码。

bower register storejs git://github.com/jaywcjlove/store.js.git

npm

npm全称Node Package Manager,是node.js的模块依赖管理工具。使用github管理NPM包的代码,并定期提交至NPM服务器;
npm官网

提交自己开发的NPM包

创建package.json文件

package.json文件的使用可以让包的安装更容易,你可以在应用程序的根目录下创建一个名为 package.json 的文件,并定义它的依赖关系。使用npm init 命令来创建package.json文件:

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (store.js)
version: (1.0.0)
description: Local storage localstorage package provides a simple API
entry point: (store.js)
test command: store.js
git repository: (https://github.com/jaywcjlove/store.js.git)
keywords: store.js
author: (kenny.wang <wowohoo@qq.co>)
license: (ISC) MIT
About to write to /Applications/XAMPP/xamppfiles/htdocs/git/github.com/myJS/store.js/package.json:

{
  "name": "store.js",
  "version": "1.0.0",
  "description": "Local storage localstorage package provides a simple API",
  "main": "store.js",
  "scripts": {
    "test": "store.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/jaywcjlove/store.js.git"
  },
  "keywords": [
    "store.js"
  ],
  "author": " <wowohoo@qq.co> (kenny.wang <wowohoo@qq.co>)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/jaywcjlove/store.js/issues"
  },
  "homepage": "https://github.com/jaywcjlove/store.js"
}


Is this ok? (yes) yes

发布到线上

添加用户

按照提示输入用户名,密码和邮箱

npm adduser

登陆用户

按照提示输入用户名,密码和邮箱

npm adduser

发布

npm publish

如果不带参数,则会在当前目录下查找package.json文件,按照该文件描述信息发布;
如果指定目录,就会在指定目录下查找package.json文件
测试是否发布成功,在官网搜索一下www.npmjs.com

注: package.json 中的name不要又特殊字符哦

版本更新

修改package.json里的版本号,重新npm publish

取消发布

npm unpublish

其它命令

npm install storejs 下载使用
npm config set registry https://registry.npm.taobao.org 更换镜像地址
npm config get registry 获取镜像地址
npm dist-tag ls jslite 查看当前版本
npm dedupe 尽量压平依赖树

国内优秀npm镜像

由于npm的源在国外,所以国内用户使用起来各种不方便。
利用kappa搭建私有NPM仓库

淘宝npm镜像

  1. 搜索地址:http://npm.taobao.org/
  2. registry地址:http://registry.npm.taobao.org/

cnpmjs镜像

  1. 搜索地址:http://cnpmjs.org/
  2. registry地址:http://r.cnpmjs.org/

临时使用

npm --registry https://registry.npm.taobao.org install express

持久使用

npm config set registry https://registry.npm.taobao.org
// 配置后可通过下面方式来验证是否成功
npm config get registry
// 或
npm info express

通过cnpm使用

npm install -g cnpm --registry=https://registry.npm.taobao.org

// 使用
cnpm install expresstall express

spmjs

spmjs

据说已经不更新了,日后如果有研究再补充!

2015年五月4日中午 12:11:31 【北京朝外·日坛国际公寓】急招web前端一枚,快到碗里来,发简历至vivian.sheng@123lian.com

我们的创始团队来自PwC,360等公司,在做的是帮助大家找人一起来玩体育运动的app - 找炼运动。我们的CEO是连续创业者,之前已经做过一家不大不小的公司。我们的主程去年才从美国跑到北京,因此整天挂在嘴上的都是一些很有逼格的新技术,什么React啊,Docker啊,Ansible啊。我们还有“需求一锤子敲定,不用天天改来改去的”产品经理,和她一起工作,保证是件身心愉悦的乐事。

我们的主程是Pivotal Labs的脑残粉,所以我们会和你:pair programming,这是我们所知对工程师来说提升很大的一种编程方法,对一个好的学习者来说,近距离观察别的程序员如何写测试,在代码中找到bug或者加新功能,是非常有效的学习过程。如果你是更资深的工程师,那就让我们从你那里学点东西吧:)当然,这过程中大屏幕的Mac是少不了的。

我们的办公室在朝阳门外,可以直接俯瞰日坛公园,好风景,工作也会有好心情。

我们需要你: 与设计师/产品经理/后端工程师沟通,完成微信和Web端应用的功能设计、开发和实现 与后端开发人员一起研讨技术实现方案,制定服务接口

我们的要求: 愿意在创业公司工作,有创造的意愿和热情 接触过Angular.js/React.js/Ember.js/Backbone.js等主流客户端框架中的一种 有良好的编码习惯

加分项: 熟悉移动端网站开发,了解兼容不同浏览器的最优前端解决方案 接触过任何一门后端语言(Node.js/Ruby/Python/PHP等) 有前端代码优化经验

我们的面试过程: 没有算法题,我们会和你一起编程,解决一个小问题。

2015年五月4日上午 10:48:21 北京海淀【功夫熊】-Node开发者(移动互联网O2O,弹性工作,扁平化管理,战斗力爆表~)

Node开发工程师: 主要工作:基于Node的产品研发 岗位职责: 1 参与讨论开发需求 2 负责手机web端和产品后台的研发 岗位需求: 1 熟悉Node,熟悉Mongodb 2 熟悉Html5,能够完成完整的web应用 3 学习能力强,有强烈的工作责任心,具有一定的沟通及协调能力

高级Node开发工程师: 主要工作:基于Node的产品研发 岗位职责: 1 参与讨论开发需求 2 负责系统后端全部体系开发 3 负责后台REST API设计及mongodb的结构设计 4 负责复杂数据的mongodb存储格式设计和算法及性能优化 岗位需求: 1 一年及以上的Node开发工作经验 2 知识体系全面,熟悉主流前后端技术,有复杂大型后端系统的开发经验 3 具备Express、async、underscore等框架的使用经验,熟悉HTTP,TCP/IP网络协议 4 有Restful API开发和NoSQL项目开发经验 5 学习能力强,有强烈的工作责任心,具有一定的沟通及协调能力

加分: 1 有高并发Web项目后端开发和海量数据处理经验 2 有敏捷或流行软件开发流程的经验

PS:O2O全新领域,百度系研发团队,战斗力爆表,弹性工作、无限量零食~~

公司主页:www.gfxiong.com 公司简介: 功夫熊——上门推拿第一大; O2O上门推拿互联网平台,为都市人提供专业便捷的上门服务; 推拿师全部具有5年以上工作经验,有的从迪拜阿联酋回来的,有的从中医院出来; 通过微信平台(gfxiong)即可下单预约推拿师。 2014年10月上线,100天做到行业第一,光顾过央视、东方卫视、教育电视台、台湾中天卫视等各种TV,现北京上海均已开通。

附上功夫熊的服务宣传链接: http://card.maka.im/mview/viewer.html?id=AQUX7YMZ&from=timeline&isappinstalled=0

请有意向者投递简历至邮箱:hr@gfxiong.com 邮件名称:姓名-Node-Cnode社区

2015年五月4日上午 9:41:52 Node.js 服务器提升CPU个数和提升RAM哪个对性能的提升影响大?

我有个疑惑。。Node.js是单线程的,意味着一个进程就只能有一个线程,也就意味着CPU一个核只能处理一个线程对吧?(如果说错了求指正。。)

所以说从单核CPU升级到4核CPU,意味着本来同一时刻只能处理一个用户请求变成可以同时处理4个用户同时的请求。所以性能是提升了4倍。

如果提升RAM,相当于提升了单个进程的处理速度,其实一样能大幅提升性能。

想问问大家都用什么样的server? AWS上自己配么?我建一个供企业内部使用的dashboard website,外加一些web services,我打算买这个服务: https://www.digitalocean.com/pricing/ 4GBMemory 2 CoreProcessor 60GBSSD Disk 4TBTransfer

大神们有什么见解么?

2015年五月4日上午 9:38:38 关于socket.io的问题

今天在看node.js实战(吴海星 译)的时候,里面有段代码 io.sockets.clients(room) 这是什么意思?我在官方文档里也没找着.clients 谁能帮我解释一下啊?

2015年五月4日早上 7:56:44 【译】PHP:40+开发工具推荐

PHP是为Web开发设计的服务器脚本语言,但也是一种通用的编程语言。超过2.4亿个索引域使用PHP,包括很多重要的网站,例如Facebook、Digg和WordPress。和其它脚本语言相比,例如Python和Ruby,Web开发者有很多不错的理由皮偏爱PHP。
对于PHP开发者,在互联网上有很多可用的开发工具,但是找到一个合适的PHP开发工具是很难的,需要花费很多努力和时间。今天,就为开发者介绍45个方便的PHP工具。

Plates

Plates是一个简单易用的PHP模板系统。Plates是为那些喜欢使用本地模板而不是编译模板的人设计的。

Laravel

Laravel是一个有着优雅表达语言的开源框架。

Parsedown

一个Laravel的Parsedown包装器,能够将markdown编译成HTML。Parsedown运行很快,并支持GitHub flavored markdown.

Guzzle

Guzzle是一个PHP版的HTTP客户端,让PHP很容易的和HTTP/1.1协议一起使用,并能减少Web服务带来的痛苦。

Hoa

Hoa是一组PHP库,它创建了工业和研究之间的桥梁。

PHP-CPP

PHP-CPP是一个C++写的PHP扩展库。它提供了一个良好的文档记录和易于使用的类的集合,可以使用和扩展构建本地PHP扩展。

Twig

Twig是一个快速、安全和稳定的PHP模板引擎。

Requests for PHP

Requests是用PHP写的HTTP库。

The Prettifier

Prettifier为一些编程语言,如CSS/HTMl/XML/PHP/SQL/Perl等,提供了一个在线编辑、格式和语言高亮的平台。

Geocoder PHP

Geocoder是一个构建geo应用很好的库,为geocoding操作提供了一个抽象层。

Slim Starter

Slim Starter由Xsanisty创建,是创建高级Web应用的解决方案。

Mink

Mink是一个PHP库,可以让你以交互的方式在浏览器中测试Web APP,它移除了两种浏览器模拟器之间的API差异,为你提供一个更准确的测试环境。

Forp

Forp是用C写的PHP分析器。Forp是轻量级的PHP扩展,它提供了一个简单的PHP数组或JSON输出,其包含了完整的脚本调用堆栈和CPU和内存使用情况。forp是非侵入性,并提供PHP注释来完成工作。

Belt

对PHP开发者来说,Belt是一个非常有用的工具,它提供了超过60个有用的函数。

Icon Generator for PHP

Icon Generator允许你生成基于彩色背景的Icon图标,这和Gmail的类似。

Rainloop

Rainloop是一个免费开源的PHP Web邮件应用,它有现代的用户接口,支持SMTP + IMAP。

Pattern Lab

Pattern Lab不仅是一个前端框架,也是一个PHP驱动的静态网站生成器、项目模式库和前端风格指南。

Composer

Composer是一个独立的PHP管理插件,在你项目的根目录创建一个组合器文件,再运行一个命令,则你所有的依赖都可以下载使用了。

Directus

Directus是用Backbone.js创建的免费开源的、客户端友好的数据库GUI,它提供了丰富的功能用户快速开发和自定义数据库解决方案管理。

PHP Debug Bar

Debug可以很容易的集成到任何项目中,并能显示来自应用任何部分的分析数据。它来自于PHP内置数据收集器的特性和受欢迎的项目。

Phalcon PHP

Phalcon PHP是C扩展的一个Web框架,提供了高性能和低资源消耗。

Pinboard

Pinboard是一个MySQL存储引擎,为PHP的MYSQL使用情况提供了实时监控/统计数据服务的只读接口。

Casebox

Casebox是一个开源的PHP/MYSQL驱动的Web应用,用于存储和管理记录、任务和文件。它有一个类似桌面的界面,我们可以创建一个unlimited-level目录用于优先存储结构化的东西。

Munee

Munee是一个一体化库,开源处理很多与Web资源优化和操作相关的事情。Munee也有很强大的缓存功能,可以在服务器和客户端缓存资源。

ImageWorkshop

ImageWorkshop是一个基于GD库的开源类,可以帮助你用PHP管理图像。这个类很像PS、GIMP一类的图像编辑软件:你可以添加许多层或层组,每一层都有一个背景图像。

Sylius

Sylius为PHP而设计的免费开源的电子商务解决方案(基于Symfony2),它能够管理任何规模的商店和复杂的产品类别。

Pico

Pico是一个开源的CMS应用,没有多余的东西,这才是最重要的。它使用平面文件作为数据库,用PHP构建。简单的说,不用设置什么,这个APP就能运行。

PHP MyFAQ

PHP MyFAQ是一个稳定开源的PHP F.A.Q. 应用,为构建一个很好的F.A.Q.系统提供了很多功能,并提供了强大的管理界面来管理类别、条目、用户和查看统计数据。A###PHP Documentor
PHP Documentor能读取代码的结构,文件系统结构、类、函数和介于两者之间的,并生成文档。

CakePHP

CakePHP是一个开源的Web应用框架,遵循MVC模式,并有PHP编写。它仿照Ruby on Rails的概念,在MIT许可下发布的。

CodeIgniter

CodeIgniter是一个强大的、开源的PHP框架。

Monsta FTP

Monsta FTP是一个PHP云件,并能将FTP文件管理放置在Web浏览器中,你可以在浏览器中进行文件的拖放。

XAMPP

XAMPP是一个免费和开源的跨平台web服务器解决方案,主要包括Apache HTTP服务器、MySQL数据库、PHP和Perl编写的脚本解释器。

NetBeans

NetBeans是开源的,并允许你使用Java, HTML5, PHP, C/C++等快速开发桌面、移动和Web应用。

Aura

Aura为PHP5.4+提供了独立的库包。这些包可以单独使用,具有一致性、也能自我组合成一个完整的框架。

PHPCheckstyle

PHPCheckstyle是一个开源功能,能帮助PHP程序员保持一致的编码风格。该工具检查输入PHP源代码和报告任何违反给定的标准。

PHP Mess Detector

PHP Mess Detector易于配置,前端用户友好。它能检查代码中的潜在问题,包括可能的错误,次优的代码,未使用的参数,等等。

Kohana

Kohana一个基于PHP5的优雅的、开源和面向对象HMVC框架,由一群志愿者维护和开发。它的目标是迅速,安全,和轻量。

Sabberworm

用PHP编写的一个CSS文件解析器。Sabberworm允许提取CSS文件到一个数据结构,操纵结构和输出(优化的)CSS。

Nette

Nette框架是一个PHPweb开发的工具。它被设计成尽可能友好、易用。它侧重于安全性和性能,绝对是最安全的PHP开发框架之一。

PHP Markdown

这是一个库包,包含了PHP Markdown解析器和额外的功能扩展。Markdown是一个text-to-html的转换工具。

Yii 2

Yii 2完整重写它的先前版本1.1,Yii也是最流行的PHP开发框架之一。Yii是一个高性能的PHP框架,最适合开发Web 2.0应用程序。

PHP Sandbox

PHP Sandbox利用PHPParser来防止沙箱运行不安全的代码。它利用FunctionParser分解传递到沙箱的调用,这样,即使没有转换成字符串,PHP调用也可以在沙箱中运行。

译文出处:http://www.ido321.com/1546.html

英文原文:40+ tools for writing better PHP

2015年五月3日晚上 8:57:23 HT for Web的HTML5树组件延迟加载技术实现

HT for WebHTML5树组件有延迟加载的功能,这个功能对于那些需要从服务器读取具有层级依赖关系数据时非常有用,需要获取数据的时候再向服务器发起请求,这样可减轻服务器压力,同时也减少了浏览器的等待时间,让页面的加载更加流畅,增强用户体验。
进入正题,今天用来做演示的Demo是,客户端请求服务器读取系统文件目录结构,通过HT for Web的HTML5树组件显示系统文件目录结构。
首先,我们先来设计下服务器,这次Demo的服务器采用Node.js,用到了Node.js的expresssocket.io、fs和http这四个模块,Node.js的相关知识,我在这里就不阐述了,网上的教材一堆,这里推荐下socket.io的相关入门http://socket.io/get-started/chat/
服务端代码代码:

var fs = require('fs'),
    express = require('express'),
    app = express(),
    server = require('http').createServer(app),
    io = require('socket.io')(server),
    root = ‘/Users/admin/Projects/ht-for-web/guide‘;

io.on('connection', function(socket){
    socket.on('explore', function(url){
        socket.emit('file', walk(url || root));
    });
});

app.use(express.static('/Users/admin/Projects/ht-for-web'));

server.listen(5000, function(){
    console.log('server is listening at port 5000');
});

io监听了connection事件,并获得一个socket;socket再监听一个叫explore的自定义事件,通过url参数获取到数据后,派发一个叫file的自定义事件,供客户端监听并做相应处理;通过app.use结合express.static设置项目路径;最后让server监听5000端口。
到此,一个简单的服务器就搭建好了,现在可以通过http://localhost:5000来访问服务器了。等等,好像缺了点什么。对了,获取系统文件目录结构的方法忘记给了,OK,那么我们就先来看看获取整站文件的代码是怎么写的:

function walk(pa) {
    var dirList = fs.readdirSync(pa),
        key = pa.substring(pa.lastIndexOf('/') + 1),
        obj = {
            name: key,
            path: pa,
            children: [],
            files: []
        };
    dirList.forEach(function(item) {
        var stats = fs.statSync(pa + '/' + item);
        if (stats.isDirectory()) {
            obj.children.push(walk(pa + '/' + item));
        }
        else {
            obj.files.push({name: item, dir: pa + '/' + item});
        }
    });

    return obj;
}

如大家所见,采用递归的方式,逐层遍历子目录,代码也没什么高深的地方,相信大家都看得懂。那我们来看看运行效果吧:
031953507553944.png
duang~文件目录结构出来了,是不是感觉酷酷的,这代码量不小吧。其实,代码并不多,贴出来大家瞅瞅:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>tree-loader</title>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/lib/core/ht.js"></script>
    <script>
        var socket = io(), idMap = {};
        function init() {
            var dm = window.dm = new ht.DataModel(),
                    tree = new ht.widget.TreeView(dm);

            tree.addToDOM();

            socket.on('file', function(data) {
                var root = dm.getDataById(idMap[data.path]);
                createChildren(data.children || [], root, dm);
                createFiles(data.files || [], root, dm);
            });
            socket.emit('explore');
        }

        function createChildren(children, parent, dm) {
            children.forEach(function(child) {
                var n = createData(child, parent);
                dm.add(n);
                createChildren(child.children || [], n, dm);
                createFiles(child.files || [], n, dm);
            });
        }

        function createFiles(files, parent, dm){
            files.forEach(function(file){
                var n = createData(file, parent);
                dm.add(n);
            });
        }

        function createData(data, parent){
            var n = new ht.Data();
            n.setName(data.name);
            n.setParent(parent);
            n.a('path', data.path);
            idMap[data.path] = n.getId();
            return n;
        }
    </script>
</head>
<body onload="init();">
</body>
</html>

这就是全部的HTML代码,加上空行总共也就50几行,怎么样,有没有感觉HT for Web很强大。废话不多说,来看看这些代码都干了些什么:

整体的思路是这样子的,当然这离我们要实现的树组件的延迟加载技术还有些差距,那么,HT for Web的HTML5树组件的延迟加载技术是怎么实现的呢?不要着急,马上开始探讨。
首先我们需要改造下获取文件目录的方法walk,因为前面介绍的方法中,使用的是加载整站文件目录,所以我们要将walk方法改造成只获取一级目录结构,改造起来很简单,就是将递归部分改造成获取当前节点就可以了,具体代码如下:

obj.children.push(walk(pa + '/' + item));
// 将上面对代码改成下面的代码
obj.children.push({name: item, path: pa + '/' + item});

这样子服务器就只请求当前请求路径下的第一级文件目录结构。接下来就是要调整下客户端代码了,首先需要给tree设置上loader:

tree.setLoader({
    load: function(data) {
        socket.emit('explore', data.a('path'));
        data.a('loaded', true);
    },
    isLoaded: function(data) {
        return data.a('loaded');
    }
});

loader包含了两个方法,load和isLoaded,这两个方法的功能分别是加载数据和判断数据是否已经加载,在load方法中,对socket派发explore事件,当前节点的path为参数,向服务器请求数据,之后将当前节点的loaded属性设置为true;在isLoaded方法中,返回当前节点的loaded属性,如果返回为true,那么tree将不会在执行load方法向服务器请求数据。
接下来需要移除createChildren的两个回调方法,并且在createFiles方法中为创建出来的节点的loaded属性设置成true,这样在不是目录的节点前就不会有展开的图标。createChildren和createFiles两个方法修改后的代码如下:

function createChildren(children, parent, dm) {
    children.forEach(function(child) {
        var n = createData(child, parent);
        dm.add(n);
    });
}

function createFiles(files, parent, dm){
    files.forEach(function(file){
        var n = createData(file, parent);
        n.a('loaded', true);
        dm.add(n);
    });
}

如此,HT for Web的HTML5树组件延迟加载技术就设计完成了,我在服务器的控制台打印出请求路径,看看这个延迟加载是不是真的,如下图:
031957283802225.png
031958228967071.png
看吧,控制台打印的是4条记录,第一条是请求跟目录时打印的,我在浏览器中展开里三个目录,在控制台打印了其对应的目录路径。
等等,现在这个目录看起来好烦,只有文字,除了位子前的展开图标可以用来区别文件和目录外,没有其他什么区别,所以我决定对其进行一番改造,让每一级目录都有图标,而且不同文件对应不同的图标,来看看效果吧:
031959440051625.png
怎么样,是不是一眼就能看出是什么文件,这个都是样式上面的问题,我就不再一一阐述了,直接上代码:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/build/ht-debug.js"></script>
    <script>
        var socket = io(), idMap = {};
        function init() {
            var icons = ['css', 'dir-open', 'dir', 'file', 'flash', 'gif', 'html', 'jar',
                'java', 'mp3', 'pdf', 'png', 'script', 'txt', 'video', 'xml', 'zip'];
            icons.forEach(function(c){
                ht.Default.setImage(c, 16, 16, '/test/wyl/images/' + c + '.png');
            });

            var dm = window.dm = new ht.DataModel(),
                    tree = new ht.widget.TreeView(dm);
            tree.setLoader({
                load: function(data) {
                    socket.emit('explore', data.a('path'));
                    data.a('loaded', true);
                },
                isLoaded: function(data) {
                    return data.a('loaded');
                }
            });
            tree.getLabelFont = function(data){
                return '13px Helvetica, Arial, sans-serif';
            };
            tree.getLabelColor = function (data) {
                return this.isSelected(data) ? 'white' : 'black';
            };
            tree.getSelectBackground = function (data) {
                return '#408EDB';
            };
            tree.getIcon = function (data) {
                var icon = data.getIcon() || 'file';
                if (data.a('isdir')) {
                    if (this.isExpanded(data)) {
                        icon = 'dir-open';
                    } else {
                        icon = 'dir';
                    }
                }
                return icon;
            };
            tree.addToDOM();

            socket.on('file', function(data) {
                var root = dm.getDataById(idMap[data.path]);
                createChildren(data.children || [], root, dm);
                createFiles(data.files || [], root, dm);
            });
            socket.emit('explore');
        }

        function createChildren(children, parent, dm) {
            children.forEach(function(child) {
                var n = createData(child, parent);
                n.a('isdir', true);
                dm.add(n);
            });
        }

        function createFiles(files, parent, dm){
            files.forEach(function(file){
                var n = createData(file, parent);
                n.a('loaded', true);
                dm.add(n);
            });
        }

        function createData(data, parent){
            var name = data.name,
                    icon = 'file';
            if (/.jar$/.test(name)) icon = 'jar';
            else if (/.css$/.test(name)) icon = 'css';
            else if (/.gif$/.test(name)) icon = 'gif';
            else if (/.png$/.test(name)) icon = 'png';
            else if (/.js$/.test(name)) icon = 'script';
            else if (/.html$/.test(name)) icon = 'html';
            else if (/.zip$/.test(name)) icon = 'zip';
            var n = new ht.Data();
            n.setName(data.name);
            n.setParent(parent);
            n.setIcon(icon);
            n.a('path', data.path);
            idMap[data.path] = n.getId();
            return n;
        }
    </script>
</head>
<body onload="init();">
</body>
</html>

在最后,附上完整的服务器代码:

var fs = require('fs'),
    express = require('express'),
    app = express(),
    server = require('http').createServer(app),
    io = require('socket.io')(server),
    root = '/Users/admin/Projects/ht-for-web/guide';

io.on('connection', function(socket){
    socket.on('explore', function(url){
        socket.emit('file', walk(url || root));
    });
});

app.use(express.static('/Users/admin/Projects/ht-for-web'));

server.listen(5000, function(){
    console.log('server is listening at port 5000');
});

function walk(pa) {
    var dirList = fs.readdirSync(pa),
        key = pa.substring(pa.lastIndexOf('/') + 1),
        obj = {
            name: key,
            path: pa,
            children: [],
            files: []
        };
    dirList.forEach(function(item) {
        var stats = fs.statSync(pa + '/' + item);
        if (stats.isDirectory()) {
            obj.children.push({name: item, path: pa + '/' + item});
        }
        else {
            obj.files.push({name: item, dir: pa + '/' + item});
        }
    });

    return obj;
}
2015年五月3日晚上 8:05:17 改造了一个Markdown在线编辑器,现在它终于让我感到完美了!

http://segmentfault.com/ 怎么老是莫名其妙地挂掉?网页会莫名其妙地打不开。
好吧,我且不说这事了。今天我花了一天时间在改造一个Markdown 在线编辑器,终于把它改造得满符合我的想法了。哈哈,好有成就感。我曾经在网上试用了很多markdown在线编辑器,发现绝大部分都有一个毛病:在输入框里敲下Tab键,它不是自动插入一个tab制表符,而是焦点自动跳到下一个链接处了。这对经常要写代码的我简直是抓狂。好在我终于找到了一个在线编辑器 http://lab.lepture.com/editor/,它对Tab键的处理恰好好处。但是我觉得还不够完美,于是自己动手改造它。
先说下我做了点什么修改,看图:
图片描述
我加了一几个按钮:插入视频,插入音乐,插入代码,并为它们一一分配了快捷键。并且还为Ctrl+S分配了快速提交功能。
其次是粘贴功能,这是我今天改造的重头戏。我觉得把网页上的内容粘贴到这个在线编辑器里,还得手工把它修改成Markdown代码,太费事了。于是希望能够自动完成。另外,上传图片,本来它是没有图片上传功能的,只能手工输入图片地址。费事啊!我也把这个功能集成到粘贴功能里了。
首先,需要在在线编辑器中绑定onPaste事件。
我看到那个editor.js中第1580行中有onKeyPress事件绑定。我先给它加了一个onPaste事件绑定。

javascript    on(d.input, "input", bind(fastPoll, cm));
    on(d.input, "keydown", operation(cm, onKeyDown));
    on(d.input, "keypress", operation(cm, onKeyPress));
    on(d.input, "paste", operation(cm,onPaste)); // 这句是我添加的
    on(d.input, "focus", bind(onFocus, cm));
    on(d.input, "blur", bind(onBlur, cm));

然后 ,需要写一个onPaste函数。我把它写在onKeyPress函数后面。
我先是想实现在粘贴时自动把HTML代码转换成Markdown的功能。于是写了这么一个函数。

javascript   function onPaste(e){
       if(!e.clipboardData)return true;
       //IE浏览器不支持e.clipboardData对象,无奈
       if(e.clipboardData.types=='text/plain')return true;
       // 如果剪贴板中的内容是纯文本内容,直接粘贴。
       else if(e.clipboardData.types=='text/plain,text/html'){
       // 如果剪贴板中的内容是HTML内容,则需要对它进行一番改造
       var html=e.clipboardData.getData('text/html');
       html=html.replace(/<html>(\r?\n)+<body>(\r?\n)+<!--StartFragment-->(.*?)<!--EndFragment-->(\r?\n)+<\/body>(\r?\n)+<\/html>/,"$3");
       html=toMarkdown(html);
       // toMarkdown函数 http://segmentfault.com/a/1190000002723901 在这里已经写了
       var cm=this;
        _replaceSelection(cm, false, html,'');
       e.preventDefault();
       }
     }

这里有一个很详细的剪贴板js原生对象的介绍:http://wizard.ae.krakow.pl/~jb/localio.html
本来这样算是大功告成了,但是我又觉得还有点不甘心,因为我希望以后粘贴图片方便点。
于是我继续修改这个onPaste函数,并加了一个图片上传功能。

javascript   function onPaste(e){
       if(!e.clipboardData)return true;
       if(e.clipboardData.types=='text/plain')return true;
       else if(e.clipboardData.types=='text/plain,text/html'){
       var html=e.clipboardData.getData('text/html');
       html=html.replace(/<html>(\r?\n)+<body>(\r?\n)+<!--StartFragment-->(.*?)<!--EndFragment-->(\r?\n)+<\/body>(\r?\n)+<\/html>/,"$3");
       html=toMarkdown(html);
       var cm=this;
        _replaceSelection(cm, false, html,'');
       e.preventDefault();
       }
       else if(e.clipboardData.types=='text/html,Files'){
        imgReader(e.clipboardData.items[1])
           e.preventDefault();
           }
        else if(e.clipboardData.types=='Files'){
           imgReader(e.clipboardData.items[0])
        }
      }

  function imgReader(item){
      if(item.kind=='file'&&item.type=='image/png'){
      var file = item.getAsFile(),reader = new FileReader();
      reader.onload = function( e ){
        var img = new Image();
        img.src = e.target.result;
        document.body.appendChild( img );
        // 把图片放在网页最下面,以便预览
        $.post('saveremoteimg.php',{'urls':e.target.result},function(data){
            _replaceSelection(editor.codemirror,false , '![', ']('+data+')\n');
            })
        };
    reader.readAsDataURL(file);
    }
};

saveremoteimg.php的源码是:

php<?php
header('Content-Type: text/html; charset=UTF-8');
$attachDir='upload';//上传文件保存路径,结尾不要带/
$dirType=1;//1:按天存入目录 2:按月存入目录 3:按扩展名存目录  建议使用按天存
$maxAttachSize=2097152;//最大上传大小,默认是2M
$upExt="jpg,jpeg,gif,png";//上传扩展名
ini_set('date.timezone','Asia/Shanghai');//时区

//保存远程文件
function saveRemoteImg($sUrl){
    global $upExt,$maxAttachSize;
    $reExt='('.str_replace(',','|',$upExt).')';
    if(substr($sUrl,0,10)=='data:image'){//base64编码的图片,可能出现在firefox粘贴,或者某些网站上,例如google图片
        if(!preg_match('/^data:image\/'.$reExt.'/i',$sUrl,$sExt))return false;
        $sExt=$sExt[1];
        $imgContent=base64_decode(substr($sUrl,strpos($sUrl,'base64,')+7));
    }
    else{//url图片
        if(!preg_match('/\.'.$reExt.'$/i',$sUrl,$sExt))return false;
        $sExt=$sExt[1];
        $imgContent=getUrl($sUrl);
    }
    if(strlen($imgContent)>$maxAttachSize)return false;//文件体积超过最大限制
    $sLocalFile=getLocalPath($sExt);
    file_put_contents($sLocalFile,$imgContent);
    //检查mime是否为图片,需要php.ini中开启gd2扩展
    $fileinfo= @getimagesize($sLocalFile);
    if(!$fileinfo||!preg_match("/image\/".$reExt."/i",$fileinfo['mime'])){
        @unlink($sLocalFile);
        return false;
    }
    return $sLocalFile;
}
//抓URL数据
function getUrl($sUrl,$jumpNums=0){
    $arrUrl = parse_url(trim($sUrl));
    if(!$arrUrl)return false;
    $host=$arrUrl['host'];
    $port=isset($arrUrl['port'])?$arrUrl['port']:80;
    $path=$arrUrl['path'].(isset($arrUrl['query'])?"?".$arrUrl['query']:"");
    $fp = @fsockopen($host,$port,$errno, $errstr, 30);
    if(!$fp)return false;
    $output="GET $path HTTP/1.0\r\nHost: $host\r\nReferer: $sUrl\r\nConnection: close\r\n\r\n";
    stream_set_timeout($fp, 60);
    @fputs($fp,$output);
    $Content='';
    while(!feof($fp))
    {
        $buffer = fgets($fp, 4096);
        $info = stream_get_meta_data($fp);
        if($info['timed_out'])return false;
        $Content.=$buffer;
    }
    @fclose($fp);
    global $jumpCount;//重定向
    if(preg_match("/^HTTP\/\d.\d (301|302)/is",$Content)&&$jumpNums<5)
    {
        if(preg_match("/Location:(.*?)\r\n/is",$Content,$murl))return getUrl($murl[1],$jumpNums+1);
    }
    if(!preg_match("/^HTTP\/\d.\d 200/is", $Content))return false;
    $Content=explode("\r\n\r\n",$Content,2);
    $Content=$Content[1];
    if($Content)return $Content;
    else return false;
}
//创建并返回本地文件路径
function getLocalPath($sExt){
    global $dirType,$attachDir;
    switch($dirType)
    {
        case 1: $attachSubDir = 'day_'.date('ymd'); break;
        case 2: $attachSubDir = 'month_'.date('ym'); break;
        case 3: $attachSubDir = 'ext_'.$sExt; break;
    }
    $newAttachDir = $attachDir.'/'.$attachSubDir;
    if(!is_dir($newAttachDir))
    {
        @mkdir($newAttachDir, 0777);
        @fclose(fopen($newAttachDir.'/index.htm', 'w'));
    }
    PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
    $newFilename=date("YmdHis").mt_rand(1000,9999).'.'.$sExt;
    $targetPath = $newAttachDir.'/'.$newFilename;
    return $targetPath;
}

$arrUrls=explode('|',$_POST['urls']);
$urlCount=count($arrUrls);
for($i=0;$i<$urlCount;$i++){
    $localUrl=saveRemoteImg($arrUrls[$i]);
    if($localUrl)$arrUrls[$i]=$localUrl;
}
echo implode('|',$arrUrls);
?>

想一想觉得还有点想改造的。在行内插入代码是需要在文字左右加两个点(键盘上Tab键上方的那个键),但是我发现在中文输入法中,它是自动打出·的,需要切换到英文输入状态才能打出想要的那个点。多敲一次键盘对我来说都是抓狂。我必须继续改造它,让它能像切换粗体或斜体那样用快捷键来实现。
这倒好办。再写一个 toggleCode函数,添加在toggleItalic 函数下面:

javascriptfunction toggleCode(editor) {
  var cm = editor.codemirror;
  var stat = getState(cm);

  var text;
  var start = '`';
  var end = '`';

  var startPoint = cm.getCursor('start');
  var endPoint = cm.getCursor('end');
  if (stat.code) {
    text = cm.getLine(startPoint.line);
    start = text.slice(0, startPoint.ch);
    end = text.slice(startPoint.ch);
    start = start.replace(/^(.*)?(`)(\S+.*)?$/, '$1$3');
    end = end.replace('`','');
    startPoint.ch -= 1;
    endPoint.ch -= 1;
    cm.setLine(startPoint.line, start + end);
  } else {
    text = cm.getSelection();
    cm.replaceSelection(start + text + end);

    startPoint.ch += 1;
    endPoint.ch += 1;
  }
  cm.setSelection(startPoint, endPoint);
  cm.focus();
}

然后在shortcuts数组中添加一项'Cmd-Y': toggleCode,改成这样子:

javascriptvar shortcuts = {
  'Cmd-B': toggleBold,
  'Cmd-I': toggleItalic,
  'Cmd-Y': toggleCode,  // 这项是我加的
  'Cmd-K': drawLink,
  'Cmd-Alt-I': drawImage,
  'Cmd-Q': drawCode, // 这项也是我加入的
  'Cmd-\'': toggleBlockquote,
  'Cmd-Alt-L': toggleOrderedList,
  'Cmd-L': toggleUnOrderedList,
  'Cmd-P': togglePreview
};

与此同时,getStatus函数需要改成这样:

javascriptfunction getState(cm, pos) {
  pos = pos || cm.getCursor('start');
  var stat = cm.getTokenAt(pos);
  if (!stat.type) return {};

  var types = stat.type.split(' ');

  var ret = {}, data, text;
  for (var i = 0; i < types.length; i++) {
    data = types[i];
    if (data === 'strong') {
      ret.bold = true;
    } else if (data === 'variable-2') {
      text = cm.getLine(pos.line);
      if (/^\s*\d+\.\s/.test(text)) {
        ret['ordered-list'] = true;
      } else {
        ret['unordered-list'] = true;
      }
    } else if (data === 'atom') {
      ret.quote = true;
    } else if (data === 'comment'){ // 这句是我加上去的
      ret.code = true;   // 这句也是我加上去的
    } else if (data === 'em') {
      ret.italic = true;
    }
  }
  return ret;
}

我觉得工具栏中没有按钮提示很不好。于是改改改~,改成下面这样:

javascriptvar toolbar = [
  {name: 'bold', action: toggleBold, shortcut:'Toggle Bold(Cmd-B)'},
  {name: 'italic', action: toggleItalic, shortcut:'Toggle Italic(Cmd-I)'},
  '|',

  {name: 'quote', action: toggleBlockquote, shortcut: 'toggle Blockquote(Cmd-\')'},
  {name: 'unordered-list', action: toggleUnOrderedList, shortcut:'Toggle UnorderList(Cmd-Alt-L)'},
  {name: 'ordered-list', action: toggleOrderedList, shortcut:'Toggle OrderList(Cmd-L)'},
  '|',

  {name: 'link', action: drawLink, shortcut:'Insert Link(Cmd-K)'},
  {name: 'image', action: drawImage, shortcut: 'Insert Image(Cmd-Alt-I)'},
  {name: 'play', action: drawVideo, shortcut: 'Insert Video'},
  {name: 'music', action: drawAudio, shortcut: 'Insert Audio'},
  {name: 'code', action: drawCode, shortcut: 'Insert Code(Cmd-Q)'},
  '|',

  {name: 'info', action: 'http://lab.lepture.com/editor/markdown'},
  {name: 'preview', action: togglePreview, shortcut: 'Toggle Preview'},
  {name: 'fullscreen', action: toggleFullScreen, shortcut: 'Toggle FullScreen'}
];

其实我发现原来的程序里有个小bug,就是用Ctrl+B或者Ctrl+I切换粗体、斜体的时候,第一次按Ctrl+B,会在选中块去的前后各加两个星号,而第二次按Ctrl+B的时候,前面的星号去掉了,后面的星号却没变化。我仔细看,发现原来的代码中正则表达式写错了。
我修改了toggleBoldtoggleItalic函数,现在总算正常了。

javascriptfunction toggleBold(editor) {
  var cm = editor.codemirror;
  var stat = getState(cm);

  var text;
  var start = '**';
  var end = '**';

  var startPoint = cm.getCursor('start');
  var endPoint = cm.getCursor('end');
  if (stat.bold) {
    text = cm.getLine(startPoint.line);
    start = text.slice(0, startPoint.ch);
    end = text.slice(startPoint.ch);

    start = start.replace(/^(.*)?(\*|\_){2}(\S+.*)?$/, '$1$3');
    end = end.replace(/(\*|\_){2}/, '');// 这句是我修改过的
    startPoint.ch -= 2;
    endPoint.ch -= 2;
    cm.setLine(startPoint.line, start + end);
  } else {
    text = cm.getSelection();
    cm.replaceSelection(start + text + end);

    startPoint.ch += 2;
    endPoint.ch += 2;
  }
  cm.setSelection(startPoint, endPoint);
  cm.focus();
}

function toggleItalic(editor) {
  var cm = editor.codemirror;
  var stat = getState(cm);

  var text;
  var start = '*';
  var end = '*';

  var startPoint = cm.getCursor('start');
  var endPoint = cm.getCursor('end');
  if (stat.italic) {
    text = cm.getLine(startPoint.line);
    start = text.slice(0, startPoint.ch);
    end = text.slice(startPoint.ch);

    start = start.replace(/^(.*)?(\*|\_)(\S+.*)?$/, '$1$3');
    end = end.replace(/(\*|\_)/, ''); // 这句是我修改过的
    startPoint.ch -= 1;
    endPoint.ch -= 1;
    cm.setLine(startPoint.line, start + end);
  } else {
    text = cm.getSelection();
    cm.replaceSelection(start + text + end);

    startPoint.ch += 1;
    endPoint.ch += 1;
  }
  cm.setSelection(startPoint, endPoint);
  cm.focus();
}

现在很疲惫,不过总算改得令自己满意了。掌柜的站长也改进一下segmentfault.com的在线编辑器吧。

2015年五月3日晚上 7:11:25 总是出问题的Crontab

最近用Python写了一些数据统计的脚本,并使用crontab自动执行,但是配置crontab总是要过几个坑才行的,这里总结一下这次遇到的坑。

输出

要将crontab命令的输出记录到日志文件中,可以使用重定向,不仅要重定向stdout也要重定向stderr,因为Python解释器会将异常输出到stderr。示例:

$HOME/path/to/script > $HOME/log/file 2>&1

环境变量

crontab会以用户的身份执行配置的命令,但是不会加载用户的环境变量,crontab会设置几个默认的环境变量,例如SHELL、PATH和HOME等,一定要注意PATH可不是用户自定义的PATH。

我们往往会在.bash_profile文件中定义一些全局的环境变量,但是crontab执行时并不会加载这个文件,所以你在shell中正常执行的程序,放到crontab里就不行了,很可能就是因为找不到环境变量了。要解决这个问题只能是自己加载环境变量了,可以在shell脚本中添加source $HOME/.bash_profile,或者直接添加到crontab中。

0 12 * * * source $HOME/.bash_profile && $HOME/path/to/script > $HOME/log/file 2>&1

路径

我们在写脚本时往往会使用相对路径,但是在crontab执行脚本时,由于工作目录不同,就会出现找不到文件或者目录不存在的问题。

解决方法是脚本中使用绝对路径或者在执行程序前切换工作目录,例如直接在crontab命令中切换工作目录:

0 12 * * * source $HOME/.bash_profile && cd $HOME/path/to/workdir && ./script > /HOME/log/file 2>&1

编码

我写的Python程序中输出了一些中文(编码是utf-8),在shell中直接执行没有问题,但是crontab执行时出现了UnicodeEncodeError的错误,Google了一下发现这个问题不仅仅是在crontab中会出现,在使用管道或者重定向的时候都会出现这个问题,原因是编码不同。

在终端中直接执行Python程序时,Python会将输出内容自动编码为终端所使用的编码,我使用的终端编码是utf-8,所以不会出错,输出的内容也是正常的。但是在使用管道或者重定向时,编码格式为ascii,Python会用ascii编码格式去encode输出的字符串,但是字符串的编码使用的时utf-8,所以会出现UnicodeEncodeError的错误。

解决方法:
方法一:在程序中输出的字符串都加上encode('utf-8')
方法二:在crontab中加上PYTHONIOENCODING=utf-8,将Python的stdout/stderr/stdin编码设置为utf-8。

2015年五月3日下午 5:22:24 关于 Monad 的学习笔记

假期终于看明白了 Monad, 这个关卡卡了好几年了, 终于过了
我现在只能说初步了解到 Monad, 不够深入, 打算留一点笔记下来

现在回头看, 如果从前学习得法的话, 最快可能几天或者几周就搞定的
比如说有 Node.js 那样成熟的社区跟教程, 或者公司里有就有人教的话
此前在 Haskell 中文论坛问过, 知乎问过, 微博私信问过, 英文教程也看了
总体上 Monad 就成了越来越吸引我注意力的一个概念

Rich Hichey 的影响

我强烈推荐 Rich Hickey 的演讲, 因为我觉得他非常有智慧
https://github.com/matthiasn/talk-transcripts/tree/master/Hickey_Rich
虽然很多是我听不懂的, 但让我能从更高的层次去理解函数式编程为什么好
比如说变量的问题, 他讲了好多例子, 讲清楚数据会发生改变是不可靠的
还有保持简单对于系统的可靠性会带来多大改善, 为什么面向对象有问题
好吧大部分是我听不懂, 但感觉很有启发

过程式编程是直观的, 但也是很存在问题的, 特别是学了函数式编程再回头看
比如说 null 值的问题, 看似自然而然, 实际却是考虑不够严谨
还有语句(或者说指令)按顺序执行的问题, 也很自然, 实际却考虑不足
这类问题导致我们在编写代码过程中不断发现有特殊的情况需要回头考虑
诚然迎合了新人学习编程所需的方便, 可代价却是对代码控制流的操作不够强大

我不否认有丰富经验跟能力的程序员能用过程式代码写出极为可靠的程序
然而引入函数式编程强大的复合能力, 有可能让程序变得更加简短清晰
而且如同 Haskell 这样搭配类型系统, 能让难以理解的过程稍微变得直观一些
当然, 函数式编程所需的抽象能力真的不是为新手准备的, 这带来巨大的门槛

纯函数

要理解 Monad 首先要对纯函数有足够的认识, 我假设读者有了解过 Haskell
相比过程式语言当中的函数(或者叫方法, procedure), Haskell 当中有很多不同:

最后一点跟流行编程语言区别尤其大, 即便跟 Lisp 的设计也差别很大
Lisp 虽然号称"一切皆表达式", 但在函数体, 在 begin 当中语句照样用:

racket(define (print-back)
  (define x (read))
  (print x))

比如这样的一段 Racket, 转化成 Haskell 看起来像是这样:

haskellprintBack :: IO ()
printBack = do
  x <- getLine
  print x

然而 do 表达式并不是 Haskell 真实的代码, 这是一套语法糖
执行过程会被转化为 >>= 或者 >> 函数, 就像是下面这样:

haskellprintBack = getLine >>= (\x -> print x)

或者把函数放到前面来, 这样看得就更明确了:

haskellprintBack = (>>=) getLine (\x -> print x)

就是说 getLine 的执行结果, 还有后面的函数, 都是 >>= 这个函数的参数
后边的 (\x -> print x) 几乎就是个回调函数, 对, 类似 Callback Hell
所以 do 表达式完全就是个障眼法, Haskell 里大量使用回调的写法
同时因为回调, 所以 Haskell 不会暗地里并行执行参数里的操作, 而是有明确的先后顺序
只不过 Haskell 语法灵活, 大量嵌套函数, 看起来还能跟没事一样, 看文档:
http://en.wikibooks.org/wiki/Haskell/do_notation

总结一下就是纯函数编程, 过程式语言常用的招数都被废掉了
整个 Haskell 的函数都往数学函数逼近, 比如 f(x) = x^2 + 2*x + 1
另外, 加上了一套代数化的类型系统, 能够容纳编程需要的各种类型

IO 的特殊性

IO 要特别梳理一下, 因为相较于过程式语言, 这里的 IO 处理很奇怪
https://wiki.haskell.org/IO_inside
通常编程语言的做法, 比如说常用的读取文件吧, 调用, 返回字符串, 很好理解:

jscontent = fs.readFileSync('filename', 'utf8') // Node.js
juliacontent = readall("filename") # Julia
racket(define content (file->string "filename")) ; Racket

但在纯函数语言当中有个大问题, 不是说好了参数一样, 返回值一样吗?
所以在 Haskell 当中 readFile 返回值并不是 String, 而是加上了 IO:

haskellreadFile :: IO String

结果就是处理文件内容时, 必需引入 Monad 的写法才行:

haskellmain = do
  content <- readFile "filename"
  putStr content

这个地方的 IO StringString 做了一层封装, 后面会遇到更多封装

代数类型系统

关于这一点, 我理解不准确, 但是举一些例子大概可以明白一些,
比如这是类似加法的方式定义新的类型:

haskelldata MySumType = Foo Bool | Bar Char

这是类似乘法的方式定义新的类型:

haskelldata MyProductType = Baz (Bool, Char)

这是以递归的方式定义新的类型:

haskelldata List a = Nil | Cons a (List a)

相比 C 或者 Go 通过 struct 定义新的类型, Haskell 显得很数学化
因为, 如果用在 Go 里定义类型是 A 或者 B, 怎么定义? 还有递归?

Haskell 当中关于类型的概念, 整理在一起就是一些关键字:

具体看这篇文章概括的, Haskell 当中类型, 类型类的一些操作
http://joelburget.com/data-newtype-instance-class/

这里的概念跟面向对象方面的, "类", "接口", "继承"有很多相似之处
但是看下例子, 这在 Haskell 当中是怎样使用的,
比如有一个叫做 Functor 的 Typeclass, 很多的 Type 都属于这个 Typeclass:

haskellclass Functor f where  
    fmap :: (a -> b) -> f a -> f b  

比如 Maybe Type 就是基于 Functor 实现, 首先用 data 定义 Maybe Type:

haskelldata Maybe a = Just a | Nothing
    deriving (Eq, Ord)

然后通过 instanceMaybe 上实现 Functor 约定的函数 fmap:

haskellinstance Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap f Nothing = Nothing

再比如 [] 也是, 那么首先 [] 大致可以这样定义
然后会有 [] 上实现的 Functor 约定的 fmap 方法:

haskelldata [a] = [] | a : [a] -- 演示代码, 可能有遗漏

instance Functor [] where
    fmap = map

还有一个例子比如说 Tree Type, 也可以同样实现 fmap 函数:

haskelldata Tree a = Node a [Tree a]

instance Functor Tree where
    fmap f (Leaf x) = Leaf (f x)
    fmap f (Branch left right) = Branch (fmap f left) (fmap f right)

就是说, Haskell 当中的类型, 是通过这样一套写法定义出来的
同样, Monad 也是个 Typeclass, 也就可以按上边这样理解
单看写法, Go 的 interface 定义看起来相似, 至少语法上可以理解

Functor, Applicative, Monad

Haskell 首先是我们熟悉的 Value 还有 Function 的世界
Functor, Applicative, Monad 在大谈封装的问题,
就是值会被装进一个盒子当中, 然后从盒子外边用这三种手法去操作,
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_...

首先难以理解的是, 这层封装是什么? 为什么硬生生造出一个其他语言没有的概念?
考虑到 Haskell 当中大量的 Category Theory(范畴论)的术语, 好像高等代数学到过..
范畴论群论依然是我无法理解的数学语言, 所以这我依然不能解释, 究竟为什么有一层封装?
没有办法, 只能先看一下这一层封装在 Haskell 当中派上了什么用场?

首先 Maybe Type 实现了 Monad, 那么看下 Maybe 典型的场景
注意下 Haskell 里 1 / 0 结果是 Infinity,, 这个大概也不是我们想要的
下面是封装过的除法, 0 不能作为被除数, 所以有了个 Nothing:

haskelldivide :: (Fractional a) => a -> a -> Maybe a
divide a 0 = Nothing
divide a b = Just $ a / b

考虑一下这样一个四则运算, 上面提示了, 一个情况 b 可能是 0, 除法有问题
但是作为例子, 很多 x / 0 在实际的编程当中我们会当成报错来处理,
好, 先认为报错, 那么整个程序就退出了

haskell((a / b) * c) + d

不过, 引入 Maybe Type 给出了一套不同的方案, 对应有报错和没有报错的情况:

haskell(Just 0.5 * Just 3) + Just 4
Just 1.5 + Just 4
Just 4.5
haskell((Just 1 / Just 0) * Just 3) + Just 4
(Nothing * Just 3) + Just 4
Nothing + Just 4
Nothing

没有报错, 一切正常. 如果有报错后边的结果都是 Nothing
这个就像 Railway Oriented Programming 给的那样, 增加了一套可能的流程:
http://fsharpforfunandprofit.com/posts/recipe-part2/

然后, List 也实现了 Monad, 就来看下例子, 下面一段代码打印了什么结果

haskellexample :: [(Int, Int, Int)]
example = do
  a <- [1,2]
  b <- [10,20]
  c <- [100,200]
  return (a,b,c)
-- [(1,10,100),(1,10,200),(1,20,100),(1,20,200),(2,10,100),(2,10,200),(2,20,100),(2,20,200)]

其实是列表解析, 如果按花哨的写法写, 应该是这样:

haskell[(a, b, c) | a <- [1,2], b <- [10,20], c <- [100,200]]

后面的两个例子难以理解, 但是大概看一看, (->) r 也实现了 Functor Typeclass
(->) r 是什么? 是函数, 一个参数的函数. 注意 Haskell 里的函数参数都是一个...

haskellinstance Functor ((->) r) where
    fmap = (.)

函数作为 fmap 第二个参数, 最后效果居然是实现了函数复合! f . g

haskellghci> :t fmap (*3) (+100)
fmap (*3) (+100) :: (Num a) => a -> a
ghci> fmap (*3) (+100) 1
303

更复杂的是实现了 Applicative Typeclass 的 sequenceA 函数

haskellsequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA = foldr (liftA2 (:)) (pure [])  

这个函数能把别的函数组合在一起用, 还能把 IO 操作组合在一起用,
而且这么密集的抽象... 3 个 IO 操作被排在一起了...

haskellghci> sequenceA [(>4),(<10),odd] 7  
[True,True,True]  
ghci> and $ sequenceA [(>4),(<10),odd] 7  
True  

ghci> sequenceA [getLine, getLine, getLine]  
heyh  
ho  
woo  
["heyh","ho","woo"]  

好, 回到上面的问题, Functor, Applicative, Monad 为什么有?
之前说函数是语言一切都是函数, 一些过程式的写法写不了了,
现在借助几个抽象, 好像又回来了, 而且花样还很多.. 连复合函数都构造了一遍
在这样的认识之下, 再看下 IO Monad 做了什么, 加上 do 表达式:

haskellmain :: IO ()
main = do putStrLn "What is your name: "
          name <- getLine
          putStrLn name

完全就是在模仿面向过程的编程, 或者说把面向过程里的一些东西重新造了一遍
当然我个人学到这里依然没明白设计思路, 但我知道是为什么要设计了
按照教程上的说法, 我可以整理一下几个函数之间的关联的递进:

首先, Haskell 通常的代码可以看作是对基础类型进行操作
比如我们有个函数 f, 有个数据 x, 通过 call 来调用:

haskellPrelude> let call f x = f x
Prelude> :t call
call :: (a -> b) -> a -> b

那么 call 的类型声明就是 (a -> b) -> a -> b

haskellclass Functor f where  
    fmap :: (a -> b) -> f a -> f b  

接着是 Functor, 注意类型声明变成的改变, 多了一层封装:

haskell(a -> b) -> a -> b -- call
(a -> b) -> f a -> f b -- fmap
haskellclass (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b  

到了 Applicative 呢, 又在前面加上了一层封装:

haskell(a -> b) -> a -> b -- call
(a -> b) -> f a -> f b -- fmap
f (a -> b) -> f a -> f b  -- <*>
haskellclass Monad m where  
    return :: a -> m a  

    (>>=) :: m a -> (a -> m b) -> m b  

    (>>) :: m a -> m b -> m b  
    x >> y = x >>= \_ -> y  

    fail :: String -> m a  
    fail msg = error msg  

到了 Monad, 参数顺序跟具体的封装又做了改进(m 写成 f 方便对比):

haskell(a -> b) -> a -> b -- call
(a -> b) -> f a -> f b -- fmap
f (a -> b) -> f a -> f b  -- (<*>)
f a -> (a -> f b) -> f b  -- (>>=)

大致上有个规律, 就是调用函数封装 f, 手段都是为了函数能超越封装使用
而且 f 会是什么? 有 Maybe [] ((->) r) IO, 还有其他很多
带来效果是什么? 有处理报错, 列表解析, 符合函数, 批量的 IO, 以及其他
Haskell 用纯函数补上了操作控制流和 IO 的功能, Monad 是其中一个手段

Monad 的写法

然后看下 Monad 去掉 do 表达式语法糖的时候怎么写, 原始的代码:
http://stackoverflow.com/q/16964732/883571

haskelldo num <- numberNode x
   nt1 <- numberTree t1
   nt2 <- numberTree t2
   return (Node num nt1 nt2)

去掉了语法糖, 是一串 >>= 函数连接在一起, 一层层的缩进:

haskellnumberNode x >>= \num ->
  numberTree t1 >>= \nt1 ->
    numberTree t2 >>= \nt2 ->
      return (Node num nt1 nt2)

还有一个 Applicative 的写法

haskellNode <$> numberNode x <*> numberTree t1 <*> numberTree t2

最后一个我得看老半天... 好吧, 总之, Haskell 就是提供了如此复杂的抽象
print("x") 在过程式语言中仅仅是指令, 在 Haskell 中却被处理为纯函数的调用
Haskell 将纯函数用于高阶的函数的转化以及操作, 变成很强大的控制流
前面说了, 实际上只是作为参数, 跟 Node.js 使用深度的回调很相似

不过还记得 Railway Oriented 那张图吗, 跟 Node.js 对比一下:

jsfs.readFile("filename", "utf8", function(err, content) {
  if (err) { throw err }
  console.log(content)
})

注意 err 的处理, Haskell 当中可没有写 err 而是在 >>= 内部处理掉了
而且 Haskell 也不会执行到这里就吐出返回值, 而是等全部执行完再返回
上边我用过 Callback Hell 打比方, 不过除了写法相似, 其他方面差别不小

总结

好了我不是在写 Monad 教程, 我也没全弄明白, 但是上边记录了我理解的思路:

我之前一直在想 Monad 会是数学结构当中某种强大的概念, 群论如何如何
但是回头看, 这更像是人为定义出来的方便编程语言使用的几个 Typeclass 而已
当新的数据类型被需要, 还可以自己定义, 用高阶函数玩转...
总之我不必为了弄懂 Monad 是什么回去把高等代数啃一遍...

不过呢, 过了这一关我还是不会写稍微复杂点的程序, 类型系统难点真挺多的

2015年五月3日下午 2:20:47 Lumen 初体验

介绍

Lumen:“为速度而生的 Laravel 框架”。

Lumen 是 Laravel 的作者(Taylor Otwell)的又一力作。简单、快速、优雅的它的特点,适合用于构建微服务架构和 API 应用。
官网:http://lumen.laravel.com
介绍:https://phphub.org/topics/701
中文文档:http://lumen.laravel-china.org/docs

安装

使用 composer 安装:

bashcomposer create-project laravel/lumen --prefer-dist

配置

Lumen 默认使用 .env 作为配置文件。.env.example 是官方给出的例子,直接拷贝命名为 .env

bashcd lumen
cp .env.example .env

调试模式

修改 .env 文件:

bashAPP_DEBUG=true

如果发现还是没有效果,再修改 lumen/bootstrap/app.php 文件,将 Dotenv::load 的注释移除掉。

疑问

1.为什么提示:not be found

访问:http://127.0.0.1/lumen/public/

显示:

bashSorry, the page you are looking for could not be found.

NotFoundHttpException in Application.php line 1121:

in Application.php line 1121
at Application->handleDispatcherResponse(array('0')) in Application.php line 1091
at Application->dispatch(null) in Application.php line 1026
at Application->run() in index.php line 28

查看路由文件 lumen/app/Http/routes.php

php$app->get('/', function() use ($app) {
    return $app->welcome();
});

感觉没有问题啊,和在 Laravel 中差不多的方式,那是哪里出了问题了?好的,先不管,尝试自己新定义一条路由规则试试看:

php$app->get('/test', function() use ($app) {
    return $app->welcome();
});

再访问:http://127.0.0.1/lumen/public/test

结果和刚才一样。

2.为什么会跳转

再尝试访问一下:http://127.0.0.1/lumen/public/test/
结果跳转到:http://127.0.0.1/test

解惑

我先来解释一下第 2 个问题,因为这是一个很多 Laravel 新手也经常问的问题。

原因何在?请看 lumen/public/.htaccess 文件:

bashRewriteRule ^(.*)/$ /$1 [L,R=301]

这是一条 Apache 路由重写规则(mod_rewrite 开启的情况下才有效),当请求的 URI 带有 /,就会匹配出 $1, 永久重定向(HTTP 状态码是 301)到根目录下的 $1。上面的例子中,匹配到 test(就是$1),就跳转至 /test 了。

如何来规避上面这个问题?注释这条 RewriteRule 吗?不是的。一般来说,我们应该避免使用末尾带斜杠的 URI。为什么 URI 末尾不应该带有斜杠呢?从语义是来说, test/ 表示目录,test 表示资源。还有,如果在 lumen/public 目录下真的有一个 test 目录,那么访问 http://127.0.0.1/lumen/public/test/,就会进入到 test 目录下面来,这不是我们想要的结果。(其实如果真的存在 test 目录并且不存在文件 test,那么,URI 末尾有没有斜杠都会进入到 test 目录中来,这是 Apache 决定的。因为它如果找不到文件,就会自动在末尾加个斜杠,尝试寻找目录下的 index.html 文件等等,具体是在 httpd.conf 中配置 DirectoryIndex。好吧,扯得太远了,拽回来)
总之,我还是建议 URI 末尾不要带 /,如果你非不听,那就注释上面那句 RewriteRule 吧,这样就不会重定向了。

关于第 1 个问题,我们也来分析一下发生的原因,这样才能对症下药。
根据错误提示,定位到文件 lumen/vendor/laravel/lumen-framework/src/Application.php 中:

php    /**
     * Dispatch the incoming request.
     *
     * @param  SymfonyRequest|null  $request
     * @return Response
     */
    public function dispatch($request = null)
    {
        if ($request) {
            $this->instance('Illuminate\Http\Request', $request);
            $this->ranServiceBinders['registerRequestBindings'] = true;

            $method = $request->getMethod();
            $pathInfo = $request->getPathInfo();
        } else {
            $method = $this->getMethod();
            $pathInfo = $this->getPathInfo();
        }        

        try {
            if (isset($this->routes[$method.$pathInfo])) {
                return $this->handleFoundRoute([true, $this->routes[$method.$pathInfo]['action'], []]);
            }

            return $this->handleDispatcherResponse(
                $this->createDispatcher()->dispatch($method, $pathInfo)
            );
        } catch (Exception $e) {
            return $this->sendExceptionToHandler($e);
        }
    }

匹配不到 route 的原因就在以上代码中。假设访问:http://127.0.0.1/lumen/public,那么 :

phpvar_dump($method);  // string(3) "GET"
var_dump($pathInfo);  // string(14) "/lumen/public/"

根据 lumen/app/Http/routes.php 中的定义,生成 $this->routes

phpvar_dump(array_keys($this->routes));  // array(2) { [0]=> string(4) "GET/" [1]=> string(8) "GET/test" }

由上可知, isset($this->routes[$method.$pathInfo]) 的结果就是 false,所以提示 not be found 了。
既然已经知道了原因,那问题就好解决了。解决的前提是不要改动框架的源代码,不然日后升级框架会多么蛋疼,你都把框架代码都修改,万一出了问题你咋办?你自己拆手机,官方是不保修的哦!当然,如果你是框架开发组的,你提交代码能被大家接受并被官方合并到主干代码中了,那你就改吧。

方案1:修改 DocumentRoot

修改 Apache 的配置文件 httpd.conf,将 DocumentRoot 指向 lumen/public

bashDocumentRoot "/sites/lumen/public"

重启 Apache。

但是,如果我还有其他站点也在这个 Apache 下面,改 DocumentRoot 就会导致其他的站点不能访问了。怎么办?请看方案 2

方案2:配置 Apache 虚拟主机

修改 httpd.conf,将下面这行的注释移除:

bashInclude etc/extra/httpd-vhosts.conf

修改 httpd-vhosts.conf

bash<VirtualHost *:80>
    DocumentRoot "/sites"
    ServerName 127.0.0.1
</VirtualHost>
<VirtualHost *:80>
    DocumentRoot "/sites/lumen/public"
    ServerName lumen.app
</VirtualHost>

重启 Apache。

修改主机的 etc/hosts,添加一行:

bash127.0.0.1 lumen.app

其中 127.0.0.1 应该换成你 lumen 应用存放的机器的 ip。

OK,这样就可以通过访问 http://lumen.app 来访问该 lumen 站点,通过 http://127.0.0.1 来访问其他站点。

但是,你压根不能修改 Apache 的配置,怎么办?请看方案 3

方案3.修改路由规则中的路径

改不了配置,就改代码喽(再强调一下,不是修改框架的源代码)。

修改路由文件 lumen/app/Http/routes.php

phpdefine('ROUTE_BASE', 'lumen/public/');

$app->get(ROUTE_BASE . '/index', function() use ($app) {
    return $app->welcome();
});
$app->get(ROUTE_BASE . '/test', function() use ($app) {
    return $app->welcome();
});

这样,如果以后有变化的话,你只需要修改 define('ROUTE_BASE', 'lumen/public/');就可以了(当然,把这个写到应用配置项中是最合适的,部署时修改配置就可以了)。

至于想以 'lumen/public/' 作为首页 URI 显然是不可以的,建议使用 'lumen/pulbic/index' 作为首页。如同上面代码定义的路由规则那样。

因为,无论你在路由规则的字符串末尾加了多少个斜杠, $this->routes 的键是不会带有斜杠的,最终还是不能匹配的。原因在框架源代码中 lumen/vendor/laravel/lumen-framework/src/Application.php

php    /**
     * Add a route to the collection.
     *
     * @param  string  $method
     * @param  string  $uri
     * @param  mixed  $action
     */
    protected function addRoute($method, $uri, $action)
    {
        $action = $this->parseAction($action);

        $uri = $uri === '/' ? $uri : '/'.trim($uri, '/');

        if (isset($action['as'])) {
            $this->namedRoutes[$action['as']] = $uri;
        }

        if (isset($this->groupAttributes)) {
            if (isset($this->groupAttributes['prefix'])) {
                $uri = rtrim('/'.trim($this->groupAttributes['prefix'], '/').$uri, '/');
            }

            $action = $this->mergeGroupAttributes($action);
        }

        $this->routes[$method.$uri] = ['method' => $method, 'uri' => $uri, 'action' => $action];
    }

对,就是它:$uri = $uri === '/' ? $uri : '/'.trim($uri, '/');
所有,URI 末尾还是不带斜杠的好。

2015年五月3日下午 1:07:20 求大神帮助!!!

Screen Shot 2015-05-03 at 1.00.20 PM.png

代码: Screen Shot 2015-05-03 at 1.00.41 PM.png

非常感谢

2015年五月3日中午 12:25:25 node express4.0 遇到一个偶发的问题,post有时会得到404 not found报错

在我编程OA系统中出现了一个偶发的问题,让我头痛。一个简单的数据update功能,在大多数情况下工作正常,偶尔会发生提交post数据不成功,浏览器报404 Not Found, 但nodejs没有报错,log里也没有记录。为此,我升级了mongodb到3.0, 也升级express到4.12.3,但这个问题还是存在。请各位高手帮忙诊断一下:

以下是Chrome控制台的记录404 Not Found信息:

Headers
General
Remote Address:123.56.132.188:2000
Request URL:http://ff.yinova.cn:2000/kafapiaoDetail
Request Method:POST
Status Code:404 Not Found

Response Headers
Connection:keep-alive
Content-Length:28
Content-Type:text/html; charset=utf-8
Date:Sun, 03 May 2015 03:07:38 GMT
ETag:W/"vXOLRVt8K03ixCcpAP5ICQ=="
X-Content-Type-Options:nosniff
X-Powered-By:Express

Request Headers
POST /kafapiaoDetail HTTP/1.1
Host: ff.yinova.cn:2000
Connection: keep-alive
Content-Length: 588
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://ff.yinova.cn:2000
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://ff.yinova.cn:2000/k/554203dba9e620351d690c70
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6
Cookie: connect.sid=s%3AJqC8e7eTZtAFxqRmX5juQ5t0.KkHhy5TSbYJF1stJm8zZGpP6sfyP2J92t2MN7a7u2tY

Form Data
item:XXXX投资有限公司
unit:会议费
price:343177
client:XX
address:北京市朝阳区XXXXXXXXXXXXXXX层
remark:发票备注
company:1
status:5
bank:1

过了一分钟,我再次提交这个数据,就成功了。 以下是Chrome控制台的记录信息:

Headers

General
Remote Address:123.56.132.188:2000
Request URL:http://ff.yinova.cn:2000/kafapiaoDetail
Request Method:POST
Status Code:302 Moved Temporarily

Response Headers
Connection:keep-alive
Content-Length:68
Content-Type:text/html; charset=utf-8
Date:Sun, 03 May 2015 03:24:28 GMT
Location:/kList
Vary:Accept
X-Powered-By:Express

Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,zh-TW;q=0.6
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:588
Content-Type:application/x-www-form-urlencoded
Cookie:connect.sid=s%3AJqC8e7eTZtAFxqRmX5juQ5t0.KkHhy5TSbYJF1stJm8zZGpP6sfyP2J92t2MN7a7u2tY
Host:ff.yinova.cn:2000
Origin:http://ff.yinova.cn:2000
Referer:http://ff.yinova.cn:2000/k/554203dba9e620351d690c70
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36

Form Data
item:XXXX投资有限公司
unit:会议费
price:343177
client:XX
address:北京市朝阳区XXXXXXXXXXXXXXX层
remark:发票备注
company:1
status:5
bank:1
oid:554203dba9e620351d690c70

router.js 的post代码:

router.post("/kafapiaoDetail",function(req,res) {
  var currentUser = req.session.user;
  var t = new Date();
  var y = t.getFullYear();
  var m = t.getMonth()+1;
  var d = t.getDate();
  var h = t.getHours();
  if (h < 10) {h = '0'+h;}
  var minute = t.getMinutes();
  if (minute<10) { min = '0'+min;}
  var insert_time = y + '-' + m + '-' +d +' '+ h +':'+minute;
  var status = req.body.status;
  var auditor = null;
  var auditor_time = null;
  var accountant = null;
  var accountant_time = null;
  if(currentUser.type === '3') {
    auditor = currentUser.username;
    auditor_time = insert_time;
  }
  if(currentUser.type === '2') {
    accountant = currentUser.username;
    accountant_time = insert_time;
  }
  var newAccount = {};
  newAccount.oid = req.body.oid;
  newAccount.item = req.body.item;
  newAccount.price = req.body.price;
  newAccount.unit = req.body.unit;
  newAccount.company = req.body.company;
  newAccount.status = status;
  newAccount.remark = req.body.remark;
  newAccount.address = req.body.address;
  newAccount.mobile = req.body.mobile;
  newAccount.client = req.body.client;
  newAccount.username = currentUser.username;
  newAccount.auditor = auditor;
  newAccount.auditor_time = auditor_time;
  newAccount.accountant = accountant;
  newAccount.accountant_time = accountant_time;
  newAccount.bank =req.body.bank;
  console.log('kafapiaoDetail Update:');  
  console.log(newAccount); 
  Accounting.update(newAccount.oid, newAccount, function(err, result) {
      console.log('update result:');
      if (err) {
        req.flash('error', err);
        return res.redirect('/k/'+newAccount.oid); 
      }
      if (result) {
        req.flash('success','数据已更新!');
        return res.redirect('/kList');
      }
      req.flash('error', '数据更新失败,请稍后再试!');
      res.redirect('/k/'+newAccount.oid); 
  });   
});

以下是jade页面代码:

extends bloglayout
block bcontent
  include narbar.jade
  include alert.jade
  form(method='post' role='form' action='/kafapiaoDetail')
    h2.form-signin-heading 发票审批
    - if(account.length>0||typeof(account) != 'undefined')
      - for(var i=0; i<account.length; i++)
        div.input-group  
          span.input-group-addon 抬头
          input(id='item' name='item' type='text' class='form-control' value=account[i].item)
        div.input-group  
          span.input-group-addon 项目
          input(id='unit' name='unit' type='text' class='form-control' value=account[i].unit)
        div.input-group  
          span.input-group-addon 金额
          input(id='price' name='price' type='text' class='form-control' value=account[i].price)
        div.input-group  
          span.input-group-addon 收件人
          input(id='client' name='client' type='text' class='form-control' value=account[i].client)
        div.input-group  
          span.input-group-addon 手机
          input(id='mobile' name='mobile' type='text' class='form-control' value=account[i].mobile)
        div.input-group  
          span.input-group-addon 地址
          input(id='address' name='address' type='text' class='form-control' value=account[i].address)
        div.input-group  
          span.input-group-addon 备注
          input(id='remark' name='remark' type='text' class='form-control' value=account[i].remark)
        div.input-group  
          span.input-group-addon 公司
          select(id='company' name='company' class='form-control')
            - if(account[i].company === '1')
              option(value='0') 请选择
              option(value='1'  selected='selected') 云动
              option(value='2') 会贰
              option(value='3') 会小二
              option(value='4') 会万
            - else if(account[i].company === '2')
              option(value='0') 请选择
              option(value='1') 云动
              option(value='2'  selected='selected') 会贰
              option(value='3') 会小二
              option(value='4') 会万
            - else if(account[i].company === '3')
              option(value='0') 请选择
              option(value='1') 云动
              option(value='2') 会贰
              option(value='3' selected='selected') 会小二
              option(value='4') 会万
            - else if(account[i].company === '4')
              option(value='0') 请选择
              option(value='1') 云动
              option(value='2') 会贰
              option(value='3') 会小二
              option(value='4' selected='selected') 会万
            - else
              option(value='0' selected='selected') 请选择
              option(value='1') 云动
              option(value='2') 会贰
              option(value='3') 会小二
              option(value='4') 会万
        div.input-group  
          span.input-group-addon 状态
          select(id='status' name='status' class='form-control')
            - if(account[i].status === '1')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1' selected='selected') 新建申请
              option(value='5') 已审批
            - if(account[i].status === '5')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5' selected='selected') 已审批
            - if(account[i].status === '2')
              option(value='0') 未开票取消
              option(value='2' selected='selected') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批       
            - if(account[i].status === '3')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3' selected='selected') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批
            - if(account[i].status === '4')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4' selected='selected') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批            
            - if(account[i].status === '0')
              option(value='0' selected='selected') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批     
        div.input-group  
          span.input-group-addon 类型
          select(id='bank' name='bank' class='form-control')
            - if(account[i].bank === '2')
              option(value='1') 增值税普票
              option(value='2' selected='selected') 增值税专票
            - else
              option(value='1' selected='selected') 增值税普票
              option(value='2') 增值税专票
        input(type='text' class='input_hide' id='oid' name='oid' value=account[i]._id readonly hidden)
        button(class='btn btn-lg btn-primary' type='submit') 修改
        a(href='/kList' class='btn btn-default' type='button') 返回
2015年五月3日中午 12:01:09 关于rsyslog和loganalyzer使用

系统日志太多太分散的话就需要整合,并且分析,所以就有了这样一套东西,这样就大大的减轻了系统管理员的压力,不过现在这篇只是小试牛刀,很多应用功能还是没有用到,例如自定义日志收集过滤, 日志分析图表,等等,不过原理大致都基本如下,是可以举一反三的。

关于rsyslog和loganalyzer的配置简略架构流程图

一、rsyslog

【客户端】rsyslog配置

1.安装rsyslog

yum -y install rsyslog

2.配置rsyslog

/etc/rsyslog.conf

配置很多,但是只需要注意几个

# rsyslog v5 configuration file

# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html

#### MODULES ####

$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)  
$ModLoad imklog   # provides kernel logging support (previously done by rklogd)  
#$ModLoad immark  # provides --MARK-- message capability

# Provides UDP syslog reception
$ModLoad imudp  --注意这个
$UDPServerRun 514   --注意这个

# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514


#### GLOBAL DIRECTIVES ####

# Use default timestamp format
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

# File syncing capability is disabled by default. This feature is usually not required,
# not useful and an extreme performance hit
#$ActionFileEnableSync on

# Include all config files in /etc/rsyslog.d/
$IncludeConfig /etc/rsyslog.d/*.conf


#### RULES ####

# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.*                                                 /dev/console

# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
#*.info;mail.none;authpriv.none;cron.none                /var/log/messages
*.*                                                      @主rsyslog服务器ip或者host   --注意这个

# The authpriv file has restricted access.
authpriv.*                                              /var/log/secure

# Log all the mail messages in one place.
mail.*                                                  -/var/log/maillog


# Log cron stuff
cron.*                                                  /var/log/cron

# Everybody gets emergency messages
*.emerg                                                 *

# Save news errors of level crit and higher in a special file.
uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log


# ### begin forwarding rule ###
# The statement between the begin ... end define a SINGLE forwarding
# rule. They belong together, do NOT split them. If you create multiple
# forwarding rules, duplicate the whole block!
# Remote Logging (we use TCP for reliable delivery)
#
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
#$WorkDirectory /var/lib/rsyslog # where to place spool files
#$ActionQueueFileName fwdRule1 # unique name prefix for spool files
#$ActionQueueMaxDiskSpace 1g   # 1gb space limit (use as much as possible)
#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
#$ActionQueueType LinkedList   # run asynchronously
#$ActionResumeRetryCount -1    # infinite retries if host is down
# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
#*.* @@remote-host:514
# ### end of the forwarding rule ###

1.$ModLoad imudp和$UDPServerRun 514 开启rsyslog的日志远程传输,使用udp模式,当然也可以使用tcp模式,而且tcp也比udp更可靠,防止日志在传输过程丢失,只是需要建立稳定连接,消耗资源,各有所长,各有所需。

2.#.info;mail.none;authpriv.none;cron.none /var/log/messages 注释掉这个语句,改为.* @主rsyslog服务器ip或者host,这是为了配置rsyslog日志传输的目标,另外rsyslog的格式是分为2个方面的,一个是facitlity一个是priority,这个需要一点篇幅来说明,详细可以参阅科普时间或者官网,目前现在这个配置的意思是将所有级别的日志都传输到某个服务器。

3.客户端的rsyslog只需要配置这样就足够了。需要注意的是,如果开启了防火墙的话,那么记得514端口是rsyslog的传输端口,也要放开访问。

3.重启rsyslog服务

service rsyslog restart

【服务端】rsyslog配置

1.安装rsyslog

yum -y install rsyslog

2.配置rsyslog存储数据库(数据库的安装在下面那里一起写了)

yum -y install rsyslog-mysql 

导入创库sql

cd /usr/share/doc/rsyslog-mysql-5.8.10/
[root@localhost rsyslog-mysql-5.8.10]# ls
createDB.sql
[root@localhost rsyslog-mysql-5.8.10]# mysql -u root -p < createDB.sql 

这个sql会自动帮你创建rsyslog存储在mysql中的数据的表,等下可以直接被loganalyzer读取数据,然后使用。

连接数据库检查

mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.1.73 Source distribution

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| Syslog             |
| mysql              |
| test               |
+--------------------+
4 rows in set (0.00 sec)


mysql> use Syslog
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+------------------------+
| Tables_in_Syslog       |
+------------------------+
| SystemEvents           |
| SystemEventsProperties |
+------------------------+
2 rows in set (0.00 sec)

配置rsyslog连接mysql账号和密码和授权

mysql> grant all on Syslog.* to 'rsysloga'@'localhost' identified by 'rsyslogp';  #设置用户访问数据库服务器中Syslog数据库的用户名和密码,因为rsyslog服务端和mysql数据库是在同一台机器上,所以只允许本机访问就可以了
Query OK, 0 rows affected (0.00 sec)

flush privileges;  #刷新权限,及时生效

3.配置服务端的rsyslog.conf(数据库的安装在下面那里一起写了)

cat /etc/rsyslog.conf 
# rsyslog v5 configuration file

# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html

#### MODULES ####

$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
$ModLoad imklog   # provides kernel logging support (previously done by rklogd)
#$ModLoad immark  # provides --MARK-- message capability
$ModLoad ommysql    --注意这个

# Provides UDP syslog reception
$ModLoad imudp      --注意这个
$UDPServerRun 514   --注意这个

# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514


#### GLOBAL DIRECTIVES ####

# Use default timestamp format
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

# File syncing capability is disabled by default. This feature is usually not required,
# not useful and an extreme performance hit
#$ActionFileEnableSync on

# Include all config files in /etc/rsyslog.d/
$IncludeConfig /etc/rsyslog.d/*.conf


#### RULES ####

# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.*                                                 /dev/console

# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
#*.info;mail.none;authpriv.none;cron.none                /var/log/messages      
*.*                                                     :ommysql:127.0.0.1,Syslog,rsysaloga,rsyslogp        --注意这个 

# The authpriv file has restricted access.
authpriv.*                                              /var/log/secure

# Log all the mail messages in one place.
mail.*                                                  -/var/log/maillog


# Log cron stuff
cron.*                                                  /var/log/cron

# Everybody gets emergency messages
*.emerg                                                 *

# Save news errors of level crit and higher in a special file.
uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log


# ### begin forwarding rule ###
# The statement between the begin ... end define a SINGLE forwarding
# rule. They belong together, do NOT split them. If you create multiple
# forwarding rules, duplicate the whole block!
# Remote Logging (we use TCP for reliable delivery)
#
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
#$WorkDirectory /var/lib/rsyslog # where to place spool files
#$ActionQueueFileName fwdRule1 # unique name prefix for spool files
#$ActionQueueMaxDiskSpace 1g   # 1gb space limit (use as much as possible)
#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
#$ActionQueueType LinkedList   # run asynchronously
#$ActionResumeRetryCount -1    # infinite retries if host is down
# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
#*.* @@remote-host:514
# ### end of the forwarding rule ###

1.增加了$ModLoad ommysql这个模块,这个就是rsyslog连接mysql使用的模块

2.. :ommysql:127.0.0.1,Syslog,rsysaloga,rsyslogp 这里意思是说使用某个可以连接mysql的账号和密码,连接mysql,将数据传输到mysql数据库里面去。

3.udp传输配置依然要开启,那是因为rsyslog客户端会将日志以udp的方式传输到rsyslog服务端,所以双方都要开启同样的传输方式才可以完成传输。

4.需要注意的是,如果开启了防火墙的话,那么记得514端口是rsyslog的传输端口,也要放开访问。

4.重启rsyslog服务

service rsyslog restart

至此,rsyslog部分已经完成配置,若要检查是否配置成功,可以检查数据库里是否有数据即可。

mysql> select * from SystemEvents;

二、loganalyzer

可以理解为loganalyzer其实就是一个web平台来展现日志数据的而已。

1.下载并安装http+php+mysql套件

yum -y install httpd php php-mysql php-gd mysql mysql-server

httpd用来提供web服务
php使apache支持php,因为loganalyzer是用php编写
php-mysql用于loganalyzer连接数据库
php-gd用于绘图
mysql 是loganalyzer存储数据的地方

设置MySQL的root用户设置密码,因为MySQL被安装时,它的root用户时没有设置密码的,所以可以直接连或者设置一个,但是不影响我们这次配置任务。mysql需要启动,这个需要注意。

2.配置apache+php,并启动apache和mysql

因为yum安装的关系,所有一切都已经配置好了

如:grep -E 'Document|Listen' /etc/httpd/conf/httpd.conf |grep -v '^#'  
Listen 80
DocumentRoot "/var/www/html"

如:grep -v '^#' /etc/httpd/conf.d/php.conf 
<IfModule prefork.c>
  LoadModule php5_module modules/libphp5.so
</IfModule>
<IfModule worker.c>
  LoadModule php5_module modules/libphp5-zts.so
</IfModule>

AddHandler php5-script .php
AddType text/html .php

DirectoryIndex index.php    

启动httpd

service httpd start

启动mysql

service mysqld start

3.下载loganalyzer

下载地址:http://download.adiscon.com/loganalyzer/loganalyzer-3.6.6.tar.gz

将其放置到配置好的apache的web目录里面/var/www/html

tar -zxpf loganalyzer-3.6.6.tar.gz -C /var/www/html 

cd /var/www/html/
[root@localhost html]# ls
loganalyzer-3.6.6

授权目录

chown -R apache.apache /var/www/html/loganalyzer-3.6.6/

4.配置loganalyzer

创建loganalyzer数据库和访问账号密码和授权

mysql> create database loganalyzer;
Query OK, 1 row affected (0.04 sec)
mysql> grant all on loganalyzer.* to loga@'localhost' identified by 'logp';
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

生成config.php

cd /var/www/html/loganalyzer-3.6.6/contrib

chmod +x *

./configure.sh 

在当前目录会生成config.php文件,然后将其放置到src目录去

cp config.php /var/www/html/loganalyzer-3.6.6/src/

在浏览器访问这台主机的80端口

http://服务端的ip/loganalyzer-3.6.6/src/install.php

备注:

1.step 2 会检查config.php的写入权限,如果没有请授权一下, chmod +w config.php
2.step 3 选择enable user database,使用自定义数据库,然后填写数据库访问信息,这里的数据库是指loganalyzer的用户数据库,而不是rsyslog日志存储的数据库,这里是需要注意的。并且选取require user to be login。
3.step 5 会将loganalyzer的相关用户表写入到数据库,可以检查loganalyzer的数据库就可以看到了。
4.step 6 配置loganalyzer的管理员账号,登录loganalyzer界面使用的。
5.step 7 是配置rsyslog的日志存储数据库的访问方法,在source type选择 mysql native,然后填写mysql的访问信息,记住,这里是rsyslog的日志存储数据库,不是loganalyzer的用户数据库。
6.完成后会自动跳转提示登录,登陆后就可以看到数据了。

三、科普时间

1.关于rsyslog的日志规则facitlity和priority

###rsyslog.conf中日志规则的定义的格式
facitlity.priority          Target
#facility: 日志设备(可以理解为日志类型):
==============================================================
auth         #pam产生的日志,认证日志
authpriv     #ssh,ftp等登录信息的验证信息,认证授权认证
cron         #时间任务相关
kern         #内核
lpr          #打印
mail         #邮件
mark(syslog) #rsyslog服务内部的信息,时间标识
news         #新闻组
user         #用户程序产生的相关信息
uucp         #unix to unix copy, unix主机之间相关的通讯
local 1~7    #自定义的日志设备
===============================================================
#priority: 级别日志级别:
=====================================================================
debug           #有调式信息的,日志信息最多
info            #一般信息的日志,最常用
notice          #最具有重要性的普通条件的信息
warning, warn   #警告级别
err, error      #错误级别,阻止某个功能或者模块不能正常工作的信息
crit            #严重级别,阻止整个系统或者整个软件不能正常工作的信息
alert           #需要立刻修改的信息
emerg, panic    #内核崩溃等严重信息
###从上到下,级别从低到高,记录的信息越来越少,如果设置的日志内性为err,则日志不会记录比err级别低的日志,只会记录比err更高级别的日志,也包括err本身的日志。
=====================================================================
Target:
  #文件, 如/var/log/messages
  #用户, root,*(表示所有用户)
  #日志服务器,@172.16.22.1
  #管道        | COMMAND

2.如果日志数量太大,内容太多,可以进行过滤记录日志

简单的方法可以在rsyslog客户端上的配置

:msg, !contains, "informational"  
*.*                 @主rsyslog服务器ip或者host

在传输配置的上一行增加一个过滤配置,格式是严格的,一定要在上一行增加过滤配置,这里的意思是日志内容出现informational的就不记录。详细的过滤方式在官网上有说,需要的话就要慢慢按照他的方式来使用。


参考文档:

1.http://litaotao.blog.51cto.com/6224470/1283871

2.http://ftp.ics.uci.edu/pub/centos0/ics-custom-build/BUILD/rsyslog-3.19...

3.http://www.cnblogs.com/tobeseeker/archive/2013/03/10/2953250.html


原文链接:
http://www.godblessyuan.com/2015/05/02/rsyslog_loganalyzer_setting/

2015年五月3日凌晨 1:14:59 【译】编写更好的CSS必备的40个工具

众所周知,CSS是非常棒的,它使网站看起来很漂亮,可以为网站添加动画,并让呈现和内容分离。去了解CSS的一切是非常难做到的,它只会变得更加困难,因为我们想让我们的代码跨浏览器兼容。
这里介绍了很多第三方工具,从简化工作流程到生成真正的CSS,这些工具都提供了我们需要的代码,并且比我们自己写出的代码运行的更快。

Pure

Pure并不是一个框架。相反,它只是集成一些已经应用到模块中的CSS代码,方便我们使用。只需要为你的项目抓取你想要的那部分CSS代码。当然,所有组件都是可用的。Pure中包含了网格系统、按钮、表格、表单和菜单,这些都是建立在normalize.css上的。

Magic Animations CSS3

Magic Animations CSS3集成了 CSS3 Animations,可以被应用在任何元素上,包含元素替换、滑出、变形和消褪等效果

Jeet Grid System

Jeet和semantic.gs有点类似,是以SASS为基础的网格系统。你可以在CSS中定义列(有时定义行),而不是为标记元素添加Class。Jeet使响应式布局更加容易,并进一步分离了内容和呈现。

10 Pure CSS Flat Mobile Devices

一个叫Oleg的人用纯CSS重绘并模拟了10种不同的移动设备(包括iPhone 6, iPad Mini, Nexus 5, and Lumia 920)

CodyHouse

可以给你的网站添加不相关的、独立组件的一个库。CodyHouse是用HTML、CSS和JavaScript建立的,你可以选择各种各样的导航、视差效果、分页、模态窗口、页面布局等等,每一个组件独有让你快速使用的教程。

Ratchet

如果你使用HTML、CSS和JavaScript是移动APP,Ratchet应该是一个不错的框架。每一个UI组件都是针对移动设备定制的,并且它有很多你在传统的HTML/CSS框架中看不到的功能。组件的默认效果都是非常棒的。

Animo.JS

Animo基于JQuery,能帮你更好的触发CSS动画。你可以叠加动画,或者第一次完成后触发第二个动画,并能同时利用CSS动画提供的硬件加速优点。

Adobe Extract

将一个Photoshop图层样式文件转换为CSS是一件很痛苦的事。幸运的是,Adobe做了一个工具(运行在浏览器中),允许你选择某个图层,将其属性转换为CSS代码。您也可以选择任何在线网站在PSD文件中使用的文本。

Sculpt

Sculpt基于SASS,是一个很好的框架。与其他已经发布的框架相比,Sculpt支持被遗弃的低版本浏览器。如果你用SASS开发移动优先的网站,并想要网站正常运行在低版本的IE上,可以考虑Sculpt。

CSS3 Generator

一个简单通用的CSS3属性生成器。它不是很新,但是当你忘了一些精确的语法时,它是非常有用的。

Bourbon Neat

SASS的最爱了,Bourbon Neat是一个简单的语义网格系统,可以单独使用,但它的设计是用 Bourbon mixin库。

Enjoy CSS

Enjoy CSS也是一个CSS3生成器,但有趣的是,它不仅仅是生成CSS3-related代码,你还可以选择你想要应用的元素:一个div,文本输入,一个按钮,等等,用一种简单可视的方式得到你想要的确切效果。

Keyframer

从这里开始创建你的keyframe-based CSS animations。只需要去这个网站学习一些教程。

Gumby

Gumby是一个HTML/CSS框架,为那些喜欢在Ruby环境中工作的人设计的。你可以单独下载它,当然,但也打包为一个Ruby gem,Ruby gem是由那些这种技术的人创建的。

CSShake

这有更多的CSS动画,重点是做出一些改变(知道我说什么吗?),但是,他们在炫耀他们的在线赚钱艺术(我不能容忍!)。

Bounce.JS

Bounce.JS结合了可视化(用于设计CSS3 动画)和JS库(用于实现),对于那些喜欢视觉设计的人来说,它的使用是非常简单的。

GridLover

需要一个简单、可视化的方式去调整字体大小吗?GridLover提供了一种简单的方式来预览排版、设置匀称的垂直和抓取CSS。你可以抓取CSS中字体的像素值、EMs, or REMs, 这些值会被格式成普通的CSS, SASS, LESS或其他代码风格。

ExtractCSS

想要快速设置CSS文件?一种方式是首先写HTML,然后设置ID、class等,将HTML代码粘贴到ExtractCSS,Web APP会列出所有的选择器,最后将它们放入CSS文件就行就可以了。

Kite

Kite是一个用于布局的CSS库,其设计用到了CSS Flex模块,但并不是完全使用Flex。Kite兼容IE8+。

Pesticide

需要确切地找出你的布局发生了什么?添加PesticideCSS文件。它将给页面上的每个元素添加边框,当元素作为子层次结构时,会巧妙地改变边框颜色。简单,但让人印象深刻。

Pleeease

疲惫的寻找不同的工具来对CSS进行预处理,添加特定的前缀,包括IE过滤器?不介意使用命令行吗?这是给你的。兼容SASS,LESS和Stylus

CSS Colours

CSS友好的颜色名称列表,包含了十六进制和rgba格式。

CSS Vocabulary

一个小应用程序,提供了一个方便的css相关的术语列表。选择其中一个,它将通过高亮一些示例代码来说明这个术语。

Tridiv

用纯CSS建立复杂的三维模型

Buttons

用SASS和Compass建立CSS按钮库

CSS Menu Maker

CSS Menu Maker能帮助你建立简单、响应式的导航

One% CSS Grid

One% CSS Grid是一个12列的流布局网格系统,它是为构建更快、成本更低的响应式布局而设计的。

Simptip

Simptip是由SASS制作的CSS提示框工具。不仅可以设置提示框的方向(上、右、下、左),还可以设置不同的颜色,例如成功色、信息色、警告色和危险色。

Myth

Myth是一个CSS预处理器,这样你只需要写CSS,不用去担心低版本浏览器的支持,甚至低版本规范的改进。

Hover CSS

集成了CSS悬浮效果的代码,可被用在链接、按钮、logos、SVG和特色图片等等。

CSS Animation Cheat Sheet

CSS Animation Cheat Sheet是一组预设、即插即用的动画CSS库.你只需要将样式表导入到你的网站,然后给你想要添加动画的元素添加类就行。

Spinkit

Spinkit包含了一些简单但非常棒的CSS动画加载效果

Typebase.CSS

Typebase.CSS是一个很小的、可定制的排版样式表。它同时又less和sass版本,因此可以很容易地修改和合并到现代web项目。

SpriteBox

使你的CSS imager sprites变成可拖放的编辑器,并让它为你写代码。

CSS Ratiocinator

CSS Ratiocinator是一个命令行工具,通过检查实际的呈现效果,会清除掉没用的CSS代码。它非常适合应用在一些CSS文件已经超出控制的大项目。

CSS Beautifier

美化CSS,如果你已经得到了一个缩小的文件但不能找到原始(或你只是有点混乱的代码)文件时,代码的美化可以通过适当的格式化和缩进修复。

CSScomb

在使用CSS Beautifier让你的代码变得可读之后,你可以使用CSScomb运行代码,确保所有的属性都按照字母表有规则的排序。记住,不是选择器,而是属性,例如宽度总是在字体声明之后等等

Anima

一个动画库,为了扩展CSS动画的功能而设计的,并且能同时为100个元素设置动画。

Recess

Recess是一个剥绒机程序,也可以作为一个编译器运行,目的是确保你的CSS符合一组规则并保持精简。每个规则可以单独禁以满足你的编码风格。

Bonus: A to Z CSS

Bonus: A to Z CSS不是一个工具,但是对于初学者来说是一个很好的资源。在A to Z,Guy Routledge为每一个CSS基本规则,如盒子模型及最常用的CSS属性,提供了坚实的课程。

译文出处:http://www.ido321.com/1545.html
英文原文:40 tools for writing better CSS

2015年五月2日晚上 11:36:15 Lua 学习笔记(下)

前面的部分见 Lua 学习笔记(上)

4 辅助库

辅助库为我们用 Lua 与 C 的通信提供了一些方便的函数。基础 API 提供 Lua 与 C 交互的所有原始函数。辅助库作为更高层次的函数来解决一些通用的问题。

辅助库的所有函数定义在头文件 luaxlib.h 中,函数带有前缀 luaL_

辅助库函数是建立在基础库之上的,所以基础库做不了的事情辅助库也做不了。

有一些函数是用来检查函数参数的,这些函数都有这样的前缀 luaL_check 或者 luaL_opt。这些函数在检查出问题后会抛出错误。由于抛出的错误消息表明是参数错误(例如,“bad argument #1”),因此不要把这些函数用在参数以外的 Lua 值上。

5 标准库

标准 Lua 库提供了许多有用的函数,这些函数都是直接用 C API 实现的。有一些函数提供了 Lua 语言本身所必要的服务(例如,typegetmetatable);有一些提供了通向“外部”的服务(例如,I/O);还有一些函数,可以由 Lua 进行实现,但是由于相当有用或者有重要的性能需求需要由 C 实现(例如 sort)。

所有的库都以 C 模块的形式分开提供。5.1中,Lua有以下几种标准库:

- 基础库
- 包库
- 字符串操作库
- 表操作库
- 数学功能库
- 输入输出库
- 操作系统工具库
- 调试工具库

除了基础库和包库,其他库都是作为全局表的域或者对象的方法提供。

[待补充]

5.1 基础库函数

基础库为 Lua 提供了一些核心函数。如果没有包含这个库,那么就可能需要自己来实现一些 Lua 语言特性了。

assert (v [, message])

如果其参数 v 的值为假(nilfalse), 它就调用 error; 否则,返回所有的参数。 在错误情况时, message 指那个错误对象; 如果不提供这个参数,参数默认为 "assertion failed!" 。

例子

assert(5==4,"Number Not Equal!")    --> 报错 Number Not Equal!
assert(nil)                         --> 报错 assertion failed!

collectgarbage (opt [, arg])

控制垃圾回收器的参数有两个,pause 和 step multipier。

参数 pause 控制了收集器在开始一个新的收集周期之前要等待多久。 随着数字的增大就导致收集器工作工作的不那么主动。 小于 1 的值意味着收集器在新的周期开始时不再等待。 当值为 2 的时候意味着在总使用内存数量达到原来的两倍时再开启新的周期。

参数 step multiplier 控制了收集器相对内存分配的速度。 更大的数字将导致收集器工作的更主动的同时,也使每步收集的尺寸增加。 小于 1 的值会使收集器工作的非常慢,可能导致收集器永远都结束不了当前周期。 缺省值为 2 ,这意味着收集器将以内存分配器的两倍速运行。

该函数是垃圾回收器的通用接口,根据 opt 参数的不同实现不同的功能。

例子

进行垃圾回收前后的内存占用情况:

x = collectgarbage("count")
print(x)            --> 27.5615234375
collectgarbage("collect")
x = collectgarbage("count")
print(x)            --> 26.7490234375

dofile (filename)

打开该名字的文件,并执行文件中的 Lua 代码块。 不带参数调用时, dofile 执行标准输入的内容(stdin)。 返回该代码块的所有返回值。 对于有错误的情况,dofile 将错误反馈给调用者 (即,dofile 没有运行在保护模式下)。

例子

同一目录下新建两个 Lua 文件,代码及输出:

-- another.lua
return "Message from another file!"

-- sample.lua
x = dofile("./another.lua")
print(x)    --> Message from another file!

dofile 在这里等价于

function dofile()
    function func()
        return "Message from another file!"
    end

    return func()
end

于是等价的输出为

print(dofile()) --> Message from another file!

error (message [, level])

终止所保护的函数,抛出 message 消息,不再返回。

通常这个函数会在抛出的消息前面加上发生错误的地址信息。堆栈等级决定添加哪个地址。如果堆栈等级为 0 ,不返回地址信息;如果为 1,返回 error 语句所在位置;如果为 2,返回调用 error所在函数的位置;依此类推。

定义

error([报错消息],[堆栈等级]=1)

例子

function division(a,b)
    if b == 0 then error("Divisor cannot be 0!",2) end      -- level 值为 1 时,错误信息指向这里
    return a / b
end

print(division(5,1))
print(division(5,0))        -- level 值为 2 时,错误信息指向这里

_G

_G 持有全局环境的变量,Lua 本身用不到这个变量,更改变量不会影响到全局环境;反过来也一样。

getfenv (f)

返回函数的环境。 f 可以是一个 Lua 函数,也可以是函数在堆栈中的等级。等级 1 代表调用 getfenv() 的那个函数。如果传入的函数不是 Lua 函数,或者 f 是 0,那么 getfenv 返回 全局环境。

~~[待补充]不是太明白,没能给出合适的代码~~
参考 Lua中的环境概念

定义

getfenv([目标函数]=1)

例子

获取全局环境:

for k,v in pairs(_G) do
print(k,v)
end

--string            table: 0x7ff200f02330
--xpcall            function: 0x7ff200d03cc0
--package           table: 0x7ff200e00000
--tostring          function: 0x7ff200d04560
--print             function: 0x7ff200d046a0
--os                table: 0x7ff200f01cb0
--unpack            function: 0x7ff200d04610
--require           function: 0x7ff200f006f0
--getfenv           function: 0x7ff200d042f0
--setmetatable      function: 0x7ff200d044a0
--next              function: 0x7ff200d04260
--assert            function: 0x7ff200d03fc0
--tonumber          function: 0x7ff200d04500
--io                table: 0x7ff200f014a0
--rawequal          function: 0x7ff200d046f0
--collectgarbage    function: 0x7ff200d04010
--arg               table: 0x7ff200e01360
--getmetatable      function: 0x7ff200d04340
--module            function: 0x7ff200f006a0
--rawset            function: 0x7ff200d047a0
--math              table: 0x7ff200e00040
--debug             table: 0x7ff200e00a00
--pcall             function: 0x7ff200d042b0
--table             table: 0x7ff200f00790
--newproxy          function: 0x7ff200d04820
--type              function: 0x7ff200d045c0
--coroutine         table: 0x7ff200d048c0
--_G                table: 0x7ff200d02fc0
--select            function: 0x7ff200d04400
--gcinfo            function: 0x7ff200d03000
--pairs             function: 0x7ff200d03e00
--rawget            function: 0x7ff200d04750
--loadstring        function: 0x7ff200d04200
--ipairs            function: 0x7ff200d03d70
--_VERSION          Lua 5.1
--dofile            function: 0x7ff200d04110
--setfenv           function: 0x7ff200d04450
--load              function: 0x7ff200d041b0
--error             function: 0x7ff200d04160
--loadfile          function: 0x7ff200d043a0

getmetatable (object)

如果对象没有元表,返回空;如果对象有 __metatable 域,返回对应的值;否则,返回对象的元表。

例子

对象有 __metatable 域的情况:

t = {num = "a table"}

mt = {__index = {x = 1,y = 2},__metatable = {__index = {x = 5,y = 6}}}
setmetatable(t, mt)

print(getmetatable(t).__index.x)  --> 5
print(t.x)                        --> 1

~~进行操作时的元表依旧是与值直接关联的那个元表,不知道这样子处理有什么作用?~~

ipairs (t)

返回三个值:迭代器、传入的表 t、值 0 。迭代器能够根据传入的表 t 和索引 i 得到 i+1 和 t[i+1] 的值。

其实现形式类似于这样:

function ipairs(t)

    function iterator(t,i)
        i = i + 1
        i = t[i] and i      -- 如果 t[i] == nil 则 i = nil;否则 i = i
        return i,t[i]
    end

    return iterator,t,0

end

例子

使用 ipairs 对表进行遍历,会从键值为 1 开始依次向后遍历,直到值为 nil。

t = {"1","2",nil,[4]="4"}
-- t = {"1","2",[4]="4"}   -- 使用该表会得到相同的输出

for i,v in ipairs(t) do
    print(i,v)
end

--> 1   1
--> 2   2

load (func [, chunkname])

通过传入函数 func 的返回值获取代码块片段。func 函数的后一次调用返回的字符串应当能与前一次调用返回的字符串衔接在一起,最终得到完整的代码块。函数返回 nil 或无返回值时表示代码块结束。

load 函数会将得到的代码块作为函数返回。返回函数的环境为全局环境。如果出现错误,load 会返回 nil 和 错误信息。

chunkname 作为该代码块的名称,用在错误信息与调试信息中。

例子

[待补充]

loadfile ([filename])

使用方式与 dofile 类似,函数内容与 load 类似。从文件中获取代码块,如果没有指定文件,则从标准输入中获取。

loadfile 把文件代码编译为中间码,以文件代码作为一个代码块(chunk),并返回包含此代码块的函数。
编译代码成中间码,并返回编译后的chunk作为一个函数。 如果出现错误,则返回 nil 以及错误信息。

使用 loadfile,可以一次编译多次运行;而每次使用 dofile,都会执行一次编译。

例子

同一目录下新建两个 Lua 文件,代码及输出:

-- sample.lua
f = loadfile("./sample.lua")
print(f())
--> Message from another file!
--> 0

-- another.lua
function fun()
 print("Message from another file!")
 return 0
end
res = fun()
return res

loadfile 在这里等价于

function loadfile()
    function fun()
     print("Message from another file!")
     return 0
    end
    res = fun()
    return res
end

loadstring (string [, chunkname])

与 load 类似,只不过是从字符串中获取代码块。

要想加载并运行所给的字符串,使用如下惯用形式:

assert(loadingstring(s))()

next (table [, index])

返回传入的表中下一个键值对。

定义

next([表],[键]=nil)

第一个参数是要操作的表,第二个参数是表中的某个键。如果传入的键值为 nil ,则函数返回第一个键值对。如果传入一个有效的键值,则输出下一对键值对。如果没有下一个键值对,返回 nil。

根据定义,可以使用 next 来判断一个表是否为空表。

注意:

键值对的遍历顺序是不一定的,即使是对数字索引也是如此。如果想要按照数字索引的顺序获取键值对,参见 ipairs (t) 函数。

例子

t = {"table",["a"] = 5, ["c"] = 6}

-- index 为 nil
print(next(t, nil))         --> 1   table

-- index 为 无效键
print(next(t,"d"))          --> 编译错误

-- index 为 数字索引
print(next(t,1))            --> a   5

-- index 为 一般键
print(next(t, "a"))         --> c   6

-- index 为 最后一个键
print(next(t,"c"))          --> nil

遍历顺序与定义顺序不一致的例子:

t = {[1]="table",b = 4,["a"] = 5, ["c"] = 6, func}

t.func = function (...)
    return true
end

for k,v in pairs(t) do
    print(k,v)
end
--> a   5
--> func    function: 0x7f7f63c0ad50
--> c   6
--> b   4

而且从上面的例子中可以看出 name = exp 的键值对形式会占用

pairs (t)

返回三个值:next 函数,表 t,nil。通常用来遍历表中的所有键值对。
如果 t 有元方法 __pairs ,将 t 作为参数 传入该函数并返回前三个返回值。

在使用 pairs 函数遍历表的过程中,可以删除域或者修改已有的域,但是如果添加新的域,可能出现无法预期的问题。

例子

t = {"table",["a"] = 5, ["c"] = 6}
for k,v in pairs(t) do
    print(k,v)
end
--> 1   table
--> a   5
--> c   6

在遍历表的过程中添加新的域导致问题出现的情况:

t = {"table",["a"] = 5, ["c"] = 6}

for k,v in pairs(t) do
    -- 添加一个新的域
    if k == 'a' then
        t[2] = 8
    end
    print(k,v)
end
--> 1   table
--> a   5

pcall (f [, arg1, ...])

以保护模式调用传入的函数,也就是说不会抛出错误。如果捕获到抛出的错误,第一个参数返回 false,第二个参数返回错误信息;如果没有出现错误,第一个参数返回 ture,后面的参数返回传入函数的返回值。

#

function fun(a,b)

    assert(not(b == 0), "divisor can't be 0 !")

    return a / b    
end

success, res = pcall(fun,5,0) --> false .../sample.lua:3: divisor can't be 0 !
success, res = pcall(fun,5,1) --> true  5

print (...)

仅作为快速查看某个值的工具,不用做格式化输出。正式的格式化输出见 string.format 与 io.write。

rawequal (v1, v2)

raw 作为前缀的函数均表示该方法在不触发任何元方法的情况下调用。

rawequal 检查 v1 是否与 v2 相等,返回比较结果。

例子

t = {"value"}
s = "value"
s2 = "value"
print(rawequal(t, s))     --> false
print(rawequal(s, s2))    --> true

rawget (table, index)

获取 table 中键 index 的关联值,table 参数必须是一个表,找不到返回 nil 。

例子

t = {"value",x = 5}

print(rawget(t, 1))     --> value
print(rawget(t, "x"))   --> 5
print(rawget(t, 2))     --> nil
print(rawget("value",1))--> bad argument #1 to 'rawget' (table expected, got string)

rawset (table, index, value)

将 table[index] 的值设置为 value 。table 必须是一张表,index 不能是 nil 或 NaN 。value 可以是任何值。返回修改后的 table 。

例子

t = {"value",x = 5}
t2 = {"sub table"}
rawset(t, 1,"new value")
rawset(t, "y", 6)
rawset(t, t2,"sub table")
rawset(t,NaN,"NaN")         --> table index is nil

print(t[1])                 --> new value
print(t.y)                  --> 6
print(t[t2])                --> sub table

select (index, ...)

index 可以是数字或者字符 '#' 。当 index 为数字时,返回第 index + 1 个参数及后面的参数(支持负数形式的 index);当 index 为 '#' 时,返回参数的个数(不包括第一个参数)。

例子

t = {"table",x = 5}
t2 = {"table2"}

print(select(  1, 1, t, t2))    --> 1  table: 0x7fad7bc0a830 table: 0x7fad7bc0ac20
print(select( -3, 1, t, t2))    --> 1  table: 0x7fad7bc0a830 table: 0x7fad7bc0ac20
print(select("#", 1, t, t2))    --> 3

setfenv (f, table)

设置函数 f 的环境表为 table 。f 可以是一个函数,或者是代表栈层级的数字。栈层级为 1 的函数是那个调用 setfenv 的函数,栈层级为 2 的函数就是更上一层的函数。 setfenv 返回 f。

特别的,如果 f 为 0,那么 setfenv 会把全局环境设置为 table 。并且不做任何返回。

Lua 的之后版本中去掉了 setfenv 和 getfenv 函数。

例子

使用栈层级操作 setfenv 的例子:

function foobar(...)

    -- 设置 foobar 的环境
    t = {}
    setmetatable(t, {__index = _G })
    setfenv(1,t)
    a = 1
    b = 2

    -- 输出 foobar 的环境
    for k,v in pairs(getfenv(1)) do
        print(k,v)
    end
    print()

    function foo(...)
        -- 设置 foo 的环境,继承 foobar 的环境
        local t = {}
        setmetatable(t, {__index = _G})
        setfenv(1,t)
        x = 3
        y = 4

        -- 输出 foo 的环境
        for k,v in pairs(getfenv(1)) do
            print(k,v)
        end
        print()

        -- 再次设置 foobar 的环境
        setfenv(2, t)
    end


    foo()

    -- 再次输出 foobar 的环境
    for k,v in pairs(getfenv(1)) do
        print(k,v)
    end
end

foobar()

--> a   1
--> b   2
--> 
--> y   4
--> x   3
--> 
--> y   4
--> x   3

将 setfenv 用于模块加载:

-- sample.lua 文件
local FuncEnv={}    -- 作为环境
setmetatable(FuncEnv, {__index = _G}) -- 为了能够访问原本全局环境的值,将全局环境表(_G)放在元表中

local func=loadfile("other.lua")    -- 返回一个函数,函数以 other 文件内容作为代码块
setfenv(func,FuncEnv)
func()                              -- 执行代码块,得到定义的 message 函数,该函数会存在环境中
FuncEnv.message()                   --通过环境调用函数,FuncEnv 此时就相当于一个独立模块
-- other.lua 文件
function message()
    print("Message from another file!")
end

本小节参考了 斯芬克斯设置函数环境——setfenvicydaylua5.1中的setfenv使用 两篇博客。

setmetatable (table, metatable)

给 table 关联元表 metatable 。返回参数 table 。

如果元表定义了 __metatable 域,会抛出错误。

metatable 参数为 nil 表示解除已经关联的元表。

例子

-- 关联一个定义了加法操作的元表
t = setmetatable({}, {__add = function(a,b)
    if type(a) == "table" and type(b) == "table" then
        return a.num + b.num
    end
end})

t.num = 5
t2 = {num = 6}
print(t+t2)         --> 11      -- 只要有一个表进行了关联就能够进行运算

setmetatable(t, nil)            

-- 解除关联后再进行加法运算会报错
print(t+t2)         --> attempt to perform arithmetic on global 't' (a table value)

tonumber (e [, base])

tonumber([值],[基数]=10)

尝试把 e 转换为十进制数值并返回。如果无法转换返回 nil 。

base 表示传入参数的进制,默认为 10 进制。base 的可输入范围 [2,36]。高于 10 的数字用字母表示,A-Z 分别表示 11-35 。

例子

print(tonumber(123))            --> 123
print(tonumber("123"))          --> 123
print(tonumber("abc"))          --> nil
print(tonumber("abc", 20))      --> 4232
print(tonumber("ABC", 20))      --> 4232

tostring (e)

能将任意类型的值转换为合适的字符串形式返回。要控制数字转换为字符串的方式,使用 string.format(formatstring,...)

如果值所关联的元表有 __tostring 域,则使用该域的元方法获取字符串。

例子

function func()
    print("this is a function")
end
t = {name = "table"}

print(tostring(123))        --> 123
print(tostring("abc"))      --> abc
print(tostring(func))       --> function: 0x7f86348013b0
print(tostring(t))          --> table: 0x7f86348013e0

type (v)

返回 v 的类型,类型以字符串形式返回。 有以下八种返回值: "nil" , "number", "string", "boolean", "table", "function", "thread", "userdata"。

例子

type(nil)                   --> "nil"
type(false)                 --> "boolean"
type(123)                   --> "number"
type("abc")                 --> "string"

print(type(nil) == "nil")   --> true

unpack (list [, i [, j]])

unpack([列表],[起始位置]=1,[返回个数]=[列表长度])

返回表中的各个域的值,等价于返回

return list[i], list[i+1], ···, list[j]

例子

t = {1,2,3,a = 4,b = 5}

print(unpack(t, 1, 4))      --> 1   2   3   nil

_VERSION

包含有当前解释器版本号的全局变量,当前版本的值为 "Lua 5.1"。

xpcall (f, err [, arg1, ...]

pcall (f, arg1, ...) 类似。不同的是,如果 f 函数抛出了错误,那么 xpcall 不会返回从 f 抛出的错误信息,而是使用 err 函数返回的错误信息。

例子

function fun(a,b)   -- 这里的参数没什么实际作用,就是展示下用法
    error("something wrong !!", 1)
end

-- pcall 
local success, res = pcall(fun,1,2)
print(success,res)      --> false   .../sample.lua:2: something wrong !!

-- xpcall
local success, res = xpcall(fun,function()
    return "an error occured !!"
end,1,2)
print(success,res)      --> false   an error occured !!

5.4 字符串操作

5.4.1 模式

字符类

字符类代表一组字符。可以用下列组合来表示一个字符类。

组合 代表字母 代表字符类型
x (变量 x) ^$()%.[]*+-?以外的任一字符
. (dot) 任意字符
%a (alphabet) 字母
%b (bracket) 对称字符以及字符间的内容
%c (control) 控制字符(即各类转义符)
%d (digits) 数字
%l (lowercase) 小写字母
%p (punctuation) 标点符号
%s (space) 空格
%u (uppercase) 大写字母
%w (word) 字母和数字
%x (hexadecimal) 十六进制字符
%z (zero) 值为 0 的字符,即 '\0'
%x (变量 x) 字母和数字以外的任一字符

如果组合中的字符写成大写形式(例如将 '%a' 写成 '%A'),相当于对原来所代表的字符类型取补集

例子:

前两行的数字标出每个字符的下标。find函数返回找出第一个符合查找条件的字符的下标。

-----------------00000000001111111112 222222222333333333344444444445555 5
-----------------12345678901234567890 123456789012345678901234567890123 4
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","i")
    --> 6
x = string.find("Tutu is a young man.\n His student number is 20230001.\0",".")
    --> 1
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%a")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%c")    --> 21 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%d")    --> 45 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%l")    --> 2   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%p")    --> 20 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%s")    --> 5   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%u")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%w")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%x")    --> 9   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%z")    --> 54 

() 表示捕捉,find的第三个参数返回被捕捉到的字符串,在这里即返回找到的那个字符。

x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%%)")   --> 1   1   %
x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%#)")   --> 7   7   #
x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%\")")  --> 16  16  "

下句中的 + 表示取一个或多个满足条件的连续字符。

                 --1 2 3 4 5 6 7 8
x,y = string.find("\a\b\f\n\r\t\v\0","%c+")     --> 1   7

上句基本列出了所有控制字符,并不是所有转义符都是控制字符,例如 \\\xff 不属于控制字符。

match 函数返回符合匹配条件的字符子串。

x = string.match("0123456789ABCDEFabcdefg","%x+")   --> 0123456789ABCDEFabcdef

输出的符号即为 %x 所支持的所有字符。

%b 的使用方法与前面的组合形式略有不同,其形式为 %bxy,使用示例如下:

---------------------00000000001111111112 22222222233333333334444444444555555 5
---------------------12345678901234567890 12345678901234567890123456789012345 6
x,y,z = string.find("Tutu is a young man.\n His student number is [20230001].\0","(%b[])")  --> 45  54  [20230001]
x,y,z = string.find("Tutu is a young man.\n His student number is _20230001_.\0","(%b__)")  --> 45  54  _20230001_
x,y,z = string.find("Tutu is a young man.\n His student number is _20230001_.\0","(%b21)")  --> 48  53  230001

[] 字符集

字符集操作是对字符类组合的一个扩展。可以通过 [] 制定出用户所需的一套字符选取范围。

---------------------0000000001111111111222222222
---------------------1234567890123456789012345678
x,y,z = string.find("[Email]: tangyikejun@163.com","([123])")           --> 22  22  1
x,y,z = string.find("[Email]: tangyikejun@163.com","([l]])")            --> 6   7   l]
x,y,z = string.find("[Email]: tangyikejun@163.com","([1-3])")           --> 22  22  1
x,y,z = string.find("[Email]: tangyikejun@163.com","([^1-3])")          --> 1   1   [
x,y,z = string.find("[Email]: tangyikejun@163.com","([^%d])")           --> 1   1   [
x,y,z = string.find("[Email]: tangyikejun@163.com","([0-9][%d][%d])")   --> 22  24  163
x,y,z = string.find("[Email]: tangyikejun@163.com","([0-9]+)")          --> 22  24  163

使用特点:

  1. 每个字符集仅限定一个字符的范围。
  2. 连字符 - 用于限定字符的范围,值域根据字符在ASCII码中对应的值得出,例如 [0-7] 代表字符范围为 0-7。
    x,y,z = string.find("!\"#$%&0123","([$-1]+)") --> 4 8 $%&01
  3. 添加 ^ 表示对指定的字符范围取补集。[^%d] 等价于 [%D]

模式项

模式项 作用
+ 匹配1个或多个字符,尽可能多地匹配
- 匹配0个或多个字符,尽可能少地匹配
* 匹配0个或多个字符,尽可能多地匹配
匹配0个或1个字符,尽可能多地匹配

使用特点:

  1. 模式项都是针对前一个字符而言的。例如 abc- 作用于字符 c
---------------------0000000001
---------------------1234567890
x,y,z = string.find("aaaabbbccc","(%a+)")       --> 1   10  aaaabbbccc
x,y,z = string.find("bbbccc","(a+)")            --> nil nil nil
x,y,z = string.find("aaaabbbccc","(ab-c)")      --> 4   8   abbbc
-- x,y,z = string.find("aaaaccc","(ab-c)")      --> 4   5   ac
-- x,y,z = string.find("aaaaccc","(ab*c)")      --> 4   5   ac
-- x,y,z = string.find("aaaabccc","(ab?c)")     --> 4   6   abc
-- x,y,z = string.find("aaaabccc","(ba?c)")     --> 5   6   bc
---------------------000000000111 111111122
---------------------123456789012 345678901
x,y,z = string.find("tangyikejun\0 163.com","(%z%s%w+)")    --> 12  16  
x,y,z = string.find("tangyikejun\0163.com","(%z%d%w+)")     --> nil nil     nil 

注意: \0 后面不能跟数字。而且用 find 返回的匹配字符串无法输出 \0 之后的部分。

模式

多个模式项组合形成模式

---------------------0000000001111111111222222222
---------------------1234567890123456789012345678
x,y,z = string.find("[Email]: tangyikejun@163.com","^(.%a+)")   -->1    6   [Email
x,y,z = string.find("[Email]: tangyikejun@163.com","(%a+)$")    -->26   28  com

()捕捉

捕捉是指将括号内的组合匹配结果保存起来,每个括号保存一个结果。
保存的数据的顺序按照左括号的顺序排列。

x,y,z,h,l = string.find("Tutu is a young man.\n His student number is _20230001_.\0","((%a+%s)(%a+%s)%b__)")    --> 35  54  number is _20230001_    number  is 

字符串模式匹配可参考Lua模式匹配

5.4.2 库函数

string.find(s,pattern[,init[,plain]])

查找字符串的子串,如果找到,返回子串的起始位置、结束位置;找不到返回 nil。
如果使用捕获(即对模式串用括号包裹起来),则一并返回匹配得到的字符串。

定义

string.find([字符串],[待查找字符串],[查找起始位置]=1,[禁用模式匹配]=false)

只有显式指定了 init 参数才能控制 plain 参数。

例子

x,y,z = string.find("1001 is a Robot", "Robot")
print(x,y,z)                                --> 11 15   nil
x,y,z = string.find("1001 is a Robot","1%d",1,true)
print(x,y,z)                                --> nil nil nil
x,y,z = string.find("1001 is a Robot","(%d+)",1,false)
print(x,y,z)                                --> 1   2   1001

string.match(s,pattern[,init])

string.find 类似,返回值不一样。string.match 查找字符串的子串,如果找到,返回子串;找不到返回 nil。

支持模式匹配。

定义

例子

x = string.match("1001 is a Robot","001")
print(x)                --> 001                             
x = string.match("1001 is a Robot","%d%d")
print(x)                --> 10      

string.gmatch(s,pattern)

返回一个迭代函数,该函数每执行一次,就返回下一个捕捉到的匹配(如果没有使用捕捉,就返回整个匹配结果)。

例子

for s in string.gmatch("I have a Dream.","%a+") do
    print(s)
end
--> I
--> have
--> a
--> Dream
t = {}
s = "name=tangyikejun, number=20250001"

-- 将捕获的两个子串分别作为键和值放到表t中
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
    t[k] = v
end

-- 输出表t
for k,v in pairs(t) do
    print(k,v)
end

--> name    tangyikejun
--> number  20250001

string.format(formatstring,...)

返回格式化之后的字符串。

定义

例子

string.format("My name is %s", "tangyikejun")   --> My name is tangyikejun

string.len(s)

返回字符串长度

string.lower(s)

返回小写字母的字符串

string.upper(s)

返回大写字母的字符串

string.rep(s,n)

对字符串进行重复

定义

string.rep([字符串],[重复次数])

例子

string.rep("Hello",4)   -- HelloHelloHelloHello

string.reverse(s)

返回反转后的字符串。

string.sub(s,i[,j])

返回子字符串。

定义

string.sub([字符串],[开始字符下标],[结束字符下标]=-1)

例子

x = string.sub("tangyikejun",7)
print(x)                --> kejun
x = string.sub("tangyikejun",1,-6)
print(x)                --> tangyi

string.gsub(s,pattern,repl[,n])

根据模式匹配对字符串中每一个匹配部分都做替换处理,返回替换后的字符串。

定义

string.gsub([字符串],[模式匹配],[替换字符],[最大替换次数] = 无限制)

repl 参数([替换字符])支持 字符串、表、函数。

如果 repl 是字符串,那么该字符串就是用于替换的字符串。同时支持 %n 转义符操作,n 的范围是 0-9。n 范围为 [1,9] 时表示第 n 个捕获的匹配字符串,%0 表示整个匹配的字符串,%% 表示替换为一个 %

如果 repl 是表,那么将捕获的第一个字符串作为键值(Key)进行查询(没有定义捕捉则以整个匹配的字符串为键值),查到的值作为替换的字符串。

如果 repl 是函数,那么每次匹配成功都会调用该函数,并以按序以所有捕捉作为参数传入函数。没有捕捉则以整个匹配的字符作为参数。

如果从表或函数得到是字符串或者是数字,就将其用于替换;如果得到的是 false 或 nil,那么匹配部分将不会发生变化。

例子

repl 为字符串

s = "Never say die."
x = string.gsub(s,"die","never")            --> Never say never.
x = string.gsub(s,"die","'%0'")             --> Never say 'die'.
x = string.gsub(s,"(%a+)%s%a+%s(%a+)","%2") --> die.

限制最大替换次数

s = "never say never."
x = string.gsub(s,"never","Never",1)    --> Never say never.

repl 是表

t = {name="Lua",version="5.1"}
x = string.gsub("$name-$version.tar.gz","$(%a+)",t) --> Lua-5.1.tar.gz

repl是函数

x = string.gsub("4+5 = $return 4+5$","%$(.-)%$",function(s)return loadstring(s)() end)  --> 4+5 = 9
x = string.gsub("23+45=$result", "((%d+)%+(%d+)=)%$%a+", function (s,a,b)
    sum = a+b
    return s..sum
end)    --> 23+45=68

~~注意:似乎只支持匿名函数。~~

从表或函数返回的是 false 或 nil

x = string.gsub("4+5 = $return 4+5$","%$(.-)%$",function(s)return nil end)  --> 4+5 = $return 4+5$
t = {name="Lua",version=false}
x = string.gsub("$name-$version.tar.gz","$(%a+)",t) --> Lua-$version.tar.gz

string.byte(s[,i[,j]])

返回字符的 ASCII 码值。

定义

string.byte([字符串],[起始下标]=1,[结束下标]=[起始下标])

例子

x,y,z = string.byte("abc",2)    --> 98  nil nil
x,y,z = string.byte("abc",1,3)  --> 97  98  99

string.char(...)

根据传入的 ASCII 编码值([0-255])得到对应的字符,传入多少编码值就返回多长的字符串。

例子

x = string.char(98,99,100)  --> bcd

如果输入字符超限会编译报错。

string.dump(function)

返回函数的二进制表示(字符串形式),把这个返回值传给 loadingstring 可以获得函数的一份拷贝(传入的函数必须是没有上值的 Lua 函数)。

例子

function sum(a,b)
    return a + b
end

s = string.dump(sum)
x = loadstring(s)(4,4) -- 8

参考链接

BNF范式简介 (简要介绍 BNF)
Lua入门系列-果冻想(对Lua进行了较为全面的介绍)
Lua快速入门(介绍 Lua 中最为重要的几个概念,为 C/C++ 程序员准备)
Lua 5.1 中文手册(全面的 Lua5.1 中文手册)
Lua 5.3 中文手册(云风花了6天写的,天哪,我看都要看6天的节奏呀)
Lua迭代器和泛型for(介绍 Lua 迭代器的详细原理以及使用)
How do JavaScript closures work?——StackOverflow(详细介绍了 Javascript 中闭包的概念)
Lua模式匹配(参考了此文中对 %b 的使用)
LuaSocket(LuaSocket 官方手册)
Lua loadfile的用法, 与其他函数的比较(loadfile的介绍部分引用了此文)
Lua 的元表(对元表的描述比较有条理,通俗易懂,本文元表部分参考了此文)
设置函数环境——setfenv(解释了如何方便地设置函数的环境,以及为什么要那样设置)
lua5.1中的setfenv使用(介绍了该环境的设置在实际中的一个应用)

2015年五月2日晚上 11:27:33 Lua 学习笔记(四)—— 元表与元方法

我们可以使用操作符对 Lua 的值进行运算,例如对数值类型的值进行加减乘除的运算操作以及对字符串的连接、取长操作等(在 Lua 学习笔记(三)—— 表达式 中介绍了许多类似的运算)。元表正是定义这些操作行为的地方。

元表本质上是一个普通 Lua 表。元表中的键用来指定操作,称为“事件名”;元表中键所关联的值称为“元方法”,定义操作的行为。

1 事件名与元方法

仅表(table)类型值对应的元表可由用户自行定义。其他类型的值所对应的元表仅能通过 Debug 库进行修改。

元表中的事件名均以两条下划线 __ 作为前缀,元表支持的事件名有如下几个:

__index     -- 'table[key]',取下标操作,用于访问表中的域
__newindex  -- 'table[key] = value',赋值操作,增改表中的域
__call      -- 'func(args)',函数调用,(参见 《Lua 学习笔记(三)—— 表达式》中的函数部分介绍)

-- 数学运算操作符
__add       -- '+'
__sub       -- '-'
__mul       -- '*'
__div       -- '/'
__mod       -- '%'
__pow       -- '^'
__unm       -- '-'

-- 连接操作符
__concat    -- '..'

-- 取长操作符
__len       -- '#'

-- 比较操作符
__eq        -- '=='
__lt        -- '<'      -- a > b 等价于 b < a
__le        -- '<='     -- a >= b 等价于 b <= a 

还有一些其他的事件,例如 __tostring__gc 等。

下面进行详细介绍。

2 元表与值

每个值都可以拥有一个元表。对 userdata 和 table 类型而言,其每个值都可以拥有独立的元表,也可以几个值共享一个元表。对于其他类型,一个类型的值共享一个元表。例如所有数值类型的值会共享一个元表。除了字符串类型,其他类型的值默认是没有元表的。

使用 getmetatable 函数可以获取任意值的元表。
使用 setmetatable 函数可以设置表类型值的元表。(这两个函数将在[基础函数库]部分进行介绍)

2.1 例子

只有字符串类型的值默认拥有元表:

a = "5"
b = 5
c = {5}
print(getmetatable(a))      --> table: 0x7fe221e06890
print(getmetatable(b))      --> nil
print(getmetatable(c))      --> nil

3 事件的具体介绍

事先提醒 Lua 使用 raw 前缀的函数来操作元方法,避免元方法的循环调用。

例如 Lua 获取对象 obj 中元方法的过程如下:

rawget(getmetatable(obj)or{}, "__"..event_name)

3.1 元方法 index

index 是元表中最常用的事件,用于值的下标访问 -- table[key]

事件 index 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。
当用户通过键值来访问表时,如果没有找到键对应的值,则会调用对应元表中的此事件。如果 index 使用表进行赋值,则在该表中查找传入键的对应值;如果 index 使用函数进行赋值,则调用该函数,并传入表和键。

Lua 对取下标操作的处理过程用伪码表示如下:

function gettable_event (table, key)
    -- h 代表元表中 index 的值
    local h     
    if type(table) == "table" then

        -- 访问成功
        local v = rawget(table, key)
        if v ~= nil then return v end

        -- 访问不成功则尝试调用元表的 index
        h = metatable(table).__index

        -- 元表不存在返回 nil
        if h == nil then return nil end
    else

        -- 不是对表进行访问则直接尝试元表
        h = metatable(table).__index

        -- 无法处理导致出错
        if h == nil then
            error(···);
        end
    end

    -- 根据 index 的值类型处理
    if type(h) == "function" then
        return h(table, key)            -- 调用处理器
    else 
        return h[key]                   -- 或是重复上述操作
    end
end

3.1.1 例子

使用表赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = {[3] = "pig", [4] = "cow", [5] = "duck"}})
print(t[3])             --> pig

使用函数赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = function (table,key)
    key = key % 2 + 1
    return table[key]
end})
print(t[3])             --> dog

3.2 元方法 newindex

newindex 用于赋值操作 -- talbe[key] = value

事件 newindex 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。

当操作类型不是表或者表中尚不存在传入的键时,会调用 newindex 的元方法。如果 newindex 关联的是一个函数类型以外的值,则再次对该值进行赋值操作。反之,直接调用函数。

~~不是太懂:一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)~~

Lua 进行赋值操作时的伪码如下:

function settable_event (table, key, value)
    local h
    if type(table) == "table" then

        -- 修改表中的 key 对应的 value
        local v = rawget(table, key)
        if v ~= nil then rawset(table, key, value); return end

        -- 
        h = metatable(table).__newindex

        -- 不存在元表,则直接添加一个域
        if h == nil then rawset(table, key, value); return end
    else
        h = metatable(table).__newindex
        if h == nil then
            error(···);
        end
    end

    if type(h) == "function" then
        return h(table, key,value)    -- 调用处理器
    else 


        h[key] = value             -- 或是重复上述操作
    end
end

3.2.1 例子

元方法为表类型:

t = {}
mt = {}

setmetatable(t, {__newindex = mt})
t.a = 5
print(t.a)      --> nil
print(mt.a)     --> 5

通过两次调用 newindex 元方法将新的域添加到了表 mt 。

+++

元方法为函数:

-- 对不同类型的 key 使用不同的赋值方式
t = {}
setmetatable(t, {__newindex = function (table,key,value)
    if type(key) == "number" then
        rawset(table, key, value*value)
    else
        rawset(table, key, value)
    end
end})
t.name = "product"
t[1] = 5
print(t.name)       --> product
print(t[1])         --> 25

3.3 元方法 call

call 事件用于函数调用 -- function(args)

Lua 进行函数调用操作时的伪代码:

function function_event (func, ...)

  if type(func) == "function" then
      return func(...)   -- 原生的调用
  else
      -- 如果不是函数类型,则使用 call 元方法进行函数调用
      local h = metatable(func).__call

      if h then
        return h(func, ...)
      else
        error(···)
      end
  end
end

3.3.1 例子

由于用户只能为表类型的值绑定自定义元表,因此,我们可以对表进行函数调用,而不能把其他类型的值当函数使用。

-- 把数据记录到表中,并返回数据处理结果
t = {}

setmetatable(t, {__call = function (t,a,b,factor)
  t.a = 1;t.b = 2;t.factor = factor
  return (a + b)*factor
end})

print(t(1,2,0.1))       --> 0.3

print(t.a)              --> 1
print(t.b)              --> 2
print(t.factor)         --> 0.1

3.4 运算操作符相关元方法

运算操作符相关元方法自然是用来定义运算的。

以 add 为例,Lua 在实现 add 操作时的伪码如下:

function add_event (op1, op2)
  -- 参数可转化为数字时,tonumber 返回数字,否则返回 nil
  local o1, o2 = tonumber(op1), tonumber(op2)
  if o1 and o2 then  -- 两个操作数都是数字?
    return o1 + o2   -- 这里的 '+' 是原生的 'add'
  else  -- 至少一个操作数不是数字时
    local h = getbinhandler(op1, op2, "__add") -- 该函数的介绍在下面
    if h then
      -- 以两个操作数来调用处理器
      return h(op1, op2)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

代码中的 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作。 在该函数中,首先,Lua 尝试第一个操作数。如果这个操作数所属类型没有定义这个操作的处理器,然后 Lua 会尝试第二个操作数。

 function getbinhandler (op1, op2, event)
   return metatable(op1)[event] or metatable(op2)[event]
 end

+++

对于一元操作符,例如取负,Lua 在实现 unm 操作时的伪码:

function unm_event (op)
  local o = tonumber(op)
  if o then  -- 操作数是数字?
    return -o  -- 这里的 '-' 是一个原生的 'unm'
  else  -- 操作数不是数字。
    -- 尝试从操作数中得到处理器
    local h = metatable(op).__unm
    if h then
      -- 以操作数为参数调用处理器
      return h(op)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

3.4.1 例子

加法的例子:

t = {}
setmetatable(t, {__add = function (a,b)
  if type(a) == "number" then
      return b.num + a
  elseif type(b) == "number" then
      return a.num + b
  else
      return a.num + b.num
  end
end})

t.num = 5

print(t + 3)  --> 8

取负的例子:

t = {}
setmetatable(t, {__unm = function (a)
  return -a.num
end})

t.num = 5

print(-t)  --> -5

3.5 元方法 tostring

对于 tostring 操作,元方法定义了值的字符串表示方式。

例子:

t = {num = "a table"}
print(t)              --> table: 0x7f8e83c0a820

mt = {__tostring = function(t)
  return t.num
end}
setmetatable(t, mt)

print(tostring(t))    --> a table
print(t)              --> a table

3.6 比较类元方法

对于三种比较类操作,均需要满足两个操作数为同类型,且关联同一个元表时才能使用元方法。

对于 eq (等于)比较操作,如果操作数所属类型没有原生的等于比较,则调用元方法。

对于 lt (小于)与 le (小于等于)两种比较操作,如果两个操作数同为数值或者同为字符串,则直接进行比较,否则使用元方法。

对于 le 操作,如果元方法 "le" 没有提供,Lua 就尝试 "lt",它假定 a <= b 等价于 not (b < a) 。

3.6.1 例子

等于比较操作:

t = {name="number",1,2,3}
t2 = {name = "number",4,5,6}
mt = {__eq = function (a,b)
    return a.name == b.name
end}
setmetatable(t,mt)              -- 必须要关联同一个元表才能比较
setmetatable(t2,mt)

print(t==t2)   --> true

3.7 其他事件的元方法

对于连接操作,当操作数中存在数值或字符串以外的类型时调用该元方法。

对于取长操作,如果操作数不是字符串类型,也不是表类型,则尝试使用元方法(这导致自定义的取长基本没有,在之后的版本中似乎做了改进)。

3.7.1 例子

取长操作:

t = {1,2,3,"one","two","three"}
setmetatable(t, {__len = function (t)
  local cnt = 0
  for k,v in pairs(t) do
    if type(v) == "number" then 
      cnt = cnt + 1
      print(k,v)
    end
  end
  return cnt
end})

-- 结果是 6 而不是预期中的 3
print(#t)   --> 6 
2015年五月2日晚上 11:19:16 防止表单多次提交

Node.js CSRF protection middleware

这是防止表单多次提交的,原理是利用cookie和session生产token,和java里的token都是一样的概念

官方提供的中间件,还不错

https://github.com/expressjs/csurf

ajax提交

如果是ajax提交,就disable button吧

2015年五月2日晚上 11:11:03 Lua 学习笔记(三)—— 表达式

1 数学运算操作符

1.1 % 操作符

Lua 中的 % 操作符与 C 语言中的操作符虽然都是取模的含义,但是取模的方式不一样。
在 C 语言中,取模操作是将两个操作数的绝对值取模后,在添加上第一个操作数的符号。
而在 Lua 中,仅仅是简单的对商相对负无穷向下取整后的余数。

+++

在 C 中,

a1 = abs(a);
b1 = abs(b);
c = a1 % b1 = a1 - floor(a1/b1)*b1;

a % b = (a >= 0) ? c : -c;

在 Lua 中,

a % b == a - math.floor(a/b)*b

Lua 是直接根据取模定义进行运算。 C 则对取模运算做了一点处理。

+++

举例:

在 C 中

int a = 5 % 6;
int b = 5 % -6;
int c = -5 % 6;
int d = -5 % -6;

printf("a,b,c,d");--5,5,-5,-5

在 Lua 中

a = 5 % 6
b = 5 % -6
c = -5 % 6
d = -5 % -6

x = {a,b,c,d}

for i,v in ipairs(x) do
    print(i,v)
end


--> 5
--> -1
--> 1
--> -5

可以看到,仅当操作数同号时,两种语言的取模结果相同。异号时,取模结果的符号与数值均不相等。

在 Lua 中的取模运算总结为:a % b,如果 a,b 同号,结果取 a,b 绝对值的模;异号,结果取 b 绝对值与绝对值取模后的差。取模后值的符号与 b 相同。

2 比较操作符

比较操作的结果是 boolean 型的,非 truefalse

支持的操作符有:

< <= ~= == > >=

不支持 ! 操作符。

+++

对于 == 操作,运算时先比较两个操作数的类型,如果不一致则结果为 false。此时数值与字符串之间并不会自动转换。

比较两个对象是否相等时,仅当指向同一内存区域时,判定为 true。·

a = 123
b = 233
c = "123"
d = "123"
e = {1,2,3}
f = e
g = {1,2,3}

print(a == b)       --> false
print(a == c)       --> false      -- 数字与字符串作为不同类型进行比较
print(c == d)       --> true       
print(e == f)       --> true       -- 引用指向相同的对象
print(e == g)       --> false      -- 虽然内容相同,但是是不同的对象
print(false == nil) --> false      -- false 是 boolean,nil 是 nil 型

方便标记,--> 代表前面表达式的结果。

+++

userdatatable 的比较方式可以通过元方法 eq 进行改变。

大小比较中,数字和字符串的比较与 C 语言一致。如果是其他类型的值,Lua会尝试调用元方法 ltle

3 逻辑操作符

and,or,not

仅认为 falsenil 为假。

3.1 not

取反操作 not 的结果为 boolean 类型。(andor 的结果则不一定为 boolean)

b = not a           -- a 为 nil,b 为 true
c = not not a       -- c 为 false

3.2 and

a and b,如果 a 为假,返回 a,如果 a 为真, 返回 b

注意,为什么 a 为假的时候要返回 a 呢?有什么意义?这是因为 a 可能是 false 或者 nil,这两个值虽然都为假,但是是有区别的。

3.3 or

a or b,如果 a 为假,返回 b,如果 a 为真, 返回 a。与 and 相反。

+++

提示: 当逻辑操作符用于得出一个 boolean 型结果时,不需要考虑逻辑运算后返回谁的问题,因为逻辑操作符的操作结果符合原本的逻辑含义。

举例

if (not (a > min and a < max)) then  -- 如果 a 不在范围内,则报错
    error() 
end

+++

3.4 其他

andor 遵循短路原则,第二个操作数仅在需要的时候会进行求值操作。

例子


a = 5 x = a or jjjj() -- 虽然后面的函数并没有定义,但是由于不会执行,因此不会报错。 print(a) -->5 print(x) -->5

通过上面这个例子,我们应当对于逻辑操作有所警觉,因为这可能会引入一些未能及时预料到的错误。

4 连接符

..
连接两个字符串(或者数字)成为新的字符串。对于其他类型,调用元方法 concat

5 取长度操作符

#

对于字符串,长度为字符串的字符个数。

对于表,通过寻找满足t[n] 不是 nil 而 t[n+1] 为 nil 的下标 n 作为表的长度。

~~对于其他类型呢?~~

5.1 例子

-- 字符串取长
print(#"abc\0")                         --> 4
-- 表取长
print(#{[1]=1,[2]=2,[3]=3,x=5,y=6})     --> 3
print(#{[1]=1,[2]=nil,[3]=3,x=5,y=6})   --> 1

6 优先级

由低到高:

or
and
 <     >     <=    >=    ~=    ==
 ..
 +     -
 *     /     %
 not   #     - (unary)
 ^

幂运算>单目运算>四则运算>连接符>比较操作符>and>or

7 Table 构造

Table 构造的 BNF 定义

tableconstructor ::= `{´ [fieldlist] `}´
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
fieldsep ::= `,´ | `;´

BNF 定义参考 BNF范式简介

举例:

a = {}
b = {["price"] = 5; cost = 4; 2+5}
c = { [1] = 2+5, [2] = 2, 8, price = "abc", ["cost"] = 4} -- b 和 c 构造的表是等价的


print(b["price"])   --> 5
print(b.cost)       --> 4
print(b[1])         --> 7       -- 未给出键值的,按序分配下标,下标从 1 开始

print(c["price"])   --> abc
print(c.cost)       --> 4
print(c[1])         --> 8       
print(c[2])         --> 2       

注意:

上面这两条的存在使得上面的例子中 c1 的输出值为 8。

+++

如果表中有相同的键,那么以靠后的那个值作为键对应的值。

a = {[1] = 5,[1] = 6} -- 那么 a[1] = 6

+++

如果表的最后一个域是表达式形式,并且是一个函数,那么这个函数的所有返回值都会加入到表中。

a = 1
function order()
    a = a + 1
    return 1,2,3,4
end

b = {order(); a; order(); }

c = {order(); a; (order());}

print(b[1])                     --> 1       
print(b[2])                     --> 2       -- 表中的值并不是一次把表达式都计算结束后再赋值的
print(b[3])                     --> 1       
print(b[4])                     --> 2       -- 表达式形式的多返回值函数

print(#b)                       --> 6       -- 表的长度为 6                 
print(#c)                       --> 3       -- 函数添加括号后表的长度为 3

8 函数

函数是一个表达式,其值为 function 类型的对象。函数每次执行都会被实例化。

8.1 函数定义

Lua 中实现一个函数可以有以下三种形式。

f = function() [block] end
local f; f = function() [block] end
a.f = function() [block] end

Lua 提供语法糖分别处理这三种函数定义。

function f() [block] end
local function f() [block] end
function a.f() [block] end

+++

上面 local 函数的定义之所以不是 local f = function() [block] end,是为了避免如下错误:

local f = function()
    print("local fun")
    if i==0 then 
        f()             -- 编译错误:attempt to call global 'f' (a nil value)
        i = i + 1
    end
end

8.2 函数的参数

形参会通过实参来初始化为局部变量。

参数列表的尾部添加 ... 表示函数能接受不定长参数。如果尾部不添加,那么函数的参数列表长度是固定的。

f(a,b)
g(a,b,...)
h(a,...,b)              -- 编译错误
f(1)                    --> a = 1, b = nil
f(1,2)                  --> a = 1, b = 2
f(1,2,3)                --> a = 1, b = 2

g(1,2)                  --> a = 1, b = 2, (nothing)
g(1,2,3)                --> a = 1, b = 2, (3)
g(1,f(4,5),3)           --> a = 1, b = 4, (3)
g(1,f(4,5))             --> a = 1, b = 4, (5)

+++

还有一种形参为self的函数的定义方式:

a.f = function (self, params) [block] end

其语法糖形式为:

function a:f(params) [block] end

使用举例:

a = {name = "唐衣可俊"}
function a:f()
    print(self.name)
end
a:f()                       --> 唐衣可俊   -- 如果这里使用 a.f(),那么 self.name 的地方会报错 attempt to index local 'self';此时应该写为 a.f(a)

: 的作用在于函数定义与调用的时候可以少写一个 self 参数。这种形式是对方法的模拟

8.3 函数调用

Lua 中的函数调用的BNF语法如下:

functioncall ::= prefixexp args

如果 prefixexp 的值的类型是 function, 那么这个函数就被用给出的参数调用。 否则 prefixexp 的元方法 "call" 就被调用, call 的第一个参数就是 prefixexp 的值,接下来的是 args 参数列表(参见 2.8 元表 | Metatable)。

函数调用根据是否传入 self 参数分为 . 调用和 : 调用。
函数调用根据传入参数的类型,可以分为参数列表调用、表调用、字符串调用

[待完善]

8.4 函数闭包

如果一个函数访问了它的外部变量,那么它就是一个闭包。

由于函数内部的变量均为局部变量,外界无法对其进行访问。这时如果外界想要改变局部变量的值,那么就可以使用闭包来实现这一目的。
具体的实现过程大致是这样,函数内部有能够改变局部变量的子函数,函数将这个子函数返回,那么外界就可以通过使用这个子函数来操作局部变量了。

例子:利用闭包来实现对局部变量进行改变

-- 实现一个迭代器

function begin(i)
    local cnt = i

    return function ()      -- 这是一个匿名函数,实现了自增的功能;同时它也是一个闭包,因为访问了外部变量 cnt
        cnt = cnt + 1
        return cnt
    end
end


iterator = begin(2)     -- 设置迭代器的初值为 2 ,返回一个迭代器函数

print(iterator())           -- 执行迭代
print(iterator())

提示: 关于闭包的更多说明可参考JavaScript 闭包是如何工作的?——StackOverflow


参考链接

BNF范式简介 (简要介绍 BNF)
How do JavaScript closures work?——StackOverflow(详细介绍了 Javascript 中闭包的概念)

2015年五月2日晚上 10:43:06 求问本地异步读文件的效率问题?

当用ajax发送多个post请求时,总时间取决于最慢的那个请求。 但当在本地异步读多个文件的时候,总时间还是取决于最慢的那个吗? 我在想在底层异步读多个文件会导致时间增长,并不会比并行读文件效率高多少,不知道实际情况应该怎么分析?

2015年五月2日晚上 10:41:34 详说 Cookie, LocalStorage 与 SessionStorage

本文最初发布于我的个人博客:咀嚼之味

最近在找暑期实习,其中百度、网易游戏、阿里的面试都问到一些关于HTML5的东西,问题大多是这样开头的:“你用过什么HTML5的技术呀?” 而后,每次都能扯到 Cookie 和 localStorage 有啥差别。这篇文章就旨在详细地阐述这部分内容,而具体 Web Storage API 的使用可以参考MDN的文档,就不在这篇文章中赘述了。

基本概念

Cookie

Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右,是网景公司的前雇员 Lou Montulli 在1993年3月的发明。它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的。

localStorage

localStorage 是 HTML5 标准中新加入的技术,它并不是什么划时代的新东西。早在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持,如果你的网站需要支持 IE6+,那以 userData 作为你的 polyfill 的方案是种不错的选择。

特性 Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
localStorage 4 3.5 8 10.50 4
sessionStorage 5 2 8 10.50 4

sessionStorage

sessionStorage 与 localStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。做过后端开发的同学应该知道 Session 这个词的意思,直译过来是“会话”。而 sessionStorage 是一个前端的概念,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空。

三者的异同

特性 Cookie localStorage sessionStorage
数据的生命期 可设置失效时间,默认是关闭浏览器后失效 除非被清除,否则永久保存 仅在当前会话下有效,关闭页面或浏览器后被清除
存放数据大小 4K左右 一般为5MB 一般为5MB
与服务器端通信 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 仅在客户端(即浏览器)中保存,不参与和服务器的通信 仅在客户端(即浏览器)中保存,不参与和服务器的通信
易用性 需要程序员自己封装,源生的Cookie接口不友好 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持

应用场景

有了对上面这些差别的直观理解,我们就可以讨论三者的应用场景了。

因为考虑到每个 HTTP 请求都会带着 Cookie 的信息,所以 Cookie 当然是能精简就精简啦,比较常用的一个应用场景就是判断用户是否登录。针对登录过的用户,服务器端会在他登录时往 Cookie 中插入一段加密过的唯一辨识单一用户的辨识码,下次只要读取这个值就可以判断当前用户是否登录啦。曾经还使用 Cookie 来保存用户在电商网站的购物车信息,如今有了 localStorage,似乎在这个方面也可以给 Cookie 放个假了~

而另一方面 localStorage 接替了 Cookie 管理购物车的工作,同时也能胜任其他一些工作。比如HTML5游戏通常会产生一些本地数据,localStorage 也是非常适用的。如果遇到一些内容特别多的表单,为了优化用户体验,我们可能要把表单页面拆分成多个子页面,然后按步骤引导用户填写。这时候 sessionStorage 的作用就发挥出来了。

安全性的考虑

需要注意的是,不是什么数据都适合放在 Cookie、localStorage 和 sessionStorage 中的。使用它们的时候,需要时刻注意是否有代码存在 XSS 注入的风险。因为只要打开控制台,你就随意修改它们的值,也就是说如果你的网站中有 XSS 的风险,它们就能对你的 localStorage 肆意妄为。所以千万不要用它们存储你系统中的敏感数据。

参考资料

2015年五月2日晚上 10:39:20 Lua 学习笔记(二)—— 语句

Lua 中的语句支持赋值,控制结构,函数调用,还有变量声明。

不允许空的语句段,因此 ;; 是非法的。

1 语句组 | chuncks

chunck ::= {stat[';']}

([';'] 应该是表示语句组后面 ; 是可选项。)

2 语句块 | blocks

block ::= chunck
stat ::= do block end

可以将一个语句块显式地写成语句组,可以用于控制局部变量的作用范围。

3 赋值 | assignment

Lua 支持多重赋值。

多重赋值时,按序将右边的表达式的值赋值给左值。右值不足补 nil,右值多余舍弃。

b = 1
a,b = 4 -- a = 4,b = nil 

+++

Lua 在进行赋值操作时,会一次性把右边的表达式都计算出来后进行赋值。

i = 5
i,a[i] = i+1, 7 -- i = 6 ,a[5] = 7

特别地,有

x,y = y,x -- 交换 x,y 的值

+++

对全局变量以及表的域的赋值操作含义可以在元表中更改。

4 控制结构

4.1 条件语句

if [exp]
    [block]
elseif [exp]
    [block]
else
    [block]
end

4.2 循环语句

while [exp]
    [block]
end

+++

repeat
    [block]
until [exp]

注意,由于 repeat 语句到 until 还未结束,因此在 until 之后的表达式中可以使用 block 中定义的局部变量。

例如:

a = 1
c = 5
repeat
    b = a + c
    c = c * 2
until b > 20
print(c)            -->     40

+++

4.3 breakreturn

breakreturn 只能写在语句块的最后一句,如果实在需要写在语句块中间,那么就在两个关键词外面包围 do end 语句块。

do break end

5 For 循环

for 循环的用法比较多,单独拎出来讲。

for 中的表达式会在循环开始前一次性求值,在循环过程中不再更新。

5.1 数字形式

for [Name] = [exp],[exp],[exp] do [block] end

三个 exp 分别代表初值,结束值,步进。exp 的值均需要是一个数字。
第三个 exp 默认为 1,可以省略。

a = 0

for i = 1,6,2 do
    a = a + i
end

等价于

int a = 0;
for (int i = 1; i <= 6;i += 2){ // 取到等号,如果步进是负的,那么会取 i >= 6
    a += i;
}

5.2 迭代器形式

迭代器形式输出一个表时,如果表中有函数,则输出的顺序及个数不确定(笔者测试得出的结果,具体原因未知)。

迭代器形式的 for 循环的实质

-- 依次返回 迭代器、状态表、迭代器初始值
function mypairs(t)

    function iterator(t,i)
        i = i + 1
        i = t[i] and i      -- 如果 t[i] == nil 则 i = nil;否则 i = i
        return i,t[i]
    end

    return iterator,t,0

end

-- 一个表
t = {[1]="1",[2]="2"}

-- 迭代形式 for 语句的 等价形式
do
local f, s, var = mypairs(t)
    while true do
        local var1, var2 = f(s, var)
        var = var1
        if var == nil then break end

        -- for 循环中添加的语句
        print(var1,var2)

    end
end

-- 迭代形式 for 语句
for var1,var2 in mypairs(t) do
    print(var1,var2)
end

--> 1   1
--> 2   2
--> 1   1
--> 2   2

5.2.1 数组形式

ary = {[1]=1,[2]=2,[5]=5}
for i,v in ipairs(ary) do
    print(v)                    --> 1 2
end

从1开始,直到数值型下标结束或者值为 nil 时结束。

5.2.2 表遍历

table = {[1]=1,[2]=2,[5]=5}
for k,v in pairs(table) do
    print(v)                    --> 1 2 5
end

遍历整个表的键值对。

关于迭代器的更多内容,可参考Lua 迭代器和泛型 for


参考链接

Lua迭代器和泛型for(介绍 Lua 迭代器的详细原理以及使用)

2015年五月2日晚上 10:29:56 Lua 学习笔记(一)—— 基本语法

1 简介

由 clean C 实现。需要被宿主程序调用,可以注入 C 函数。

2 语法约定

Lua 的语法基于 BNF 的语法规则。

Lua 对大小写敏感。

2.1 保留关键字

C 语言中没有的关键字有:

and elseif function
in nil local not or
repeat then until

规范:全局变量以下划线开头。

2.2 操作符

C 语言中没有的操作符:

^ 
~= 
//  -- 向下取整

Lua 中没有的操作符:

+=
-=

2.3 字符串定义

采用转义符:通过转义符表示那些有歧义的字符

字符表示

a           -- 代表字符 a
\97         -- 代表字符 a
\049        -- 代表数字字符 1 

其他转义符表示

\\n         -- 代表字符串 \n
\n          -- 代表换行

注意数字字符必须是三位。其他字符则不能超过三位。

采用长括号:长括号内的所有内容都作为普通字符处理。

[[]]        -- 0级长括号
[==[]==]    -- 2级长括号

3 值与类型

Lua 是动态语言,变量没有类型,值才有。值自身携带类型信息。

Lua 有八种基本数据类型:nil, boolean, number, string, function, userdata, thread, table

nilfalse 导致条件为假,其他均为真。

userdata 类型变量用于保存 C 数据。 Lua 只能对该类数据进行使用,而不能进行创建或修改,保证宿主程序完全掌握数据。

thread 用于实现协程(coroutine)。

table 用于实现关联数组。table 允许任何类型的数据做索引,也允许任何类型做 table 域中的值(前述
任何类型 不包含 nil)。table 是 Lua 中唯一的数据结构。
由于函数也是一种值,所以 table 中可以存放函数。

function, userdata, thread, table 这些类型的值都是对象。这些类型的变量都只是保存变量的引用,并且在进行赋值,参数传递,函数返回等操作时不会进行任何性质的拷贝。

库函数 type() 返回变量的类型描述信息。

3.1 强制转换

Lua 提供数字字符串间的自动转换。
可以使用 format 函数控制数字向字符串的转换。

4 变量

变量有三种类型:全局变量、局部变量、表中的域

函数外的变量默认为全局变量,除非用 local 显示声明。函数内变量与函数的参数默认为局部变量。

局部变量的作用域为从声明位置开始到所在语句块结束(或者是直到下一个同名局部变量的声明)。

变量的默认值均为 nil。


a = 5 -- 全局变量 local b = 5 -- 局部变量 function joke() c = 5 -- 局部变量 local d = 6 -- 局部变量 end print(c,d) --> nil nil do local a = 6 -- 局部变量 b = 6 -- 全局变量 print(a,b); --> 6 6 end print(a,b) --> 5 6

方便标记,--> 代表前面表达式的结果。

4.1 索引

对 table 的索引使用方括号 []。Lua使用语法糖提供 . 操作。

t[i]
t.i                 -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用

4.2 环境表

所有全局变量放在一个环境表里,该表的变量名为 _env 。对某个全局变量 a 的访问即 _env.a_env_ 只是为了方便说明)。

每个函数作为变量持有一个环境表的引用,里面包含该函数可调用的所有变量。
子函数会从父函数继承环境表。
可以通过函数 getfenv / setfenv 来读写环境表。

2015年五月2日晚上 10:10:54 Lua 学习笔记(上)

1 简介

由 clean C 实现。需要被宿主程序调用,可以注入 C 函数。

2 语法

采用基于 BNF 的语法规则。

2.1 语法约定

Lua 对大小写敏感。

2.1.1 保留关键字

C 语言中没有的关键字有:

and elseif function
in nil local not or
repeat then until

规范:全局变量以下划线开头。

2.1.2 操作符

C 语言中没有的操作符:

^ 
~= 
//  -- 向下取整

Lua 中没有的操作符:

+=
-=

2.1.3 字符串定义

采用转义符:通过转义符表示那些有歧义的字符

字符表示

a           -- 代表字符 a
\97         -- 代表字符 a
\049        -- 代表数字字符 1 

其他转义符表示

\\n         -- 代表字符串 \n
\n          -- 代表换行

注意数字字符必须是三位。其他字符则不能超过三位。

采用长括号:长括号内的所有内容都作为普通字符处理。

[[]]        -- 0级长括号
[==[]==]    -- 2级长括号

2.2 值与类型

Lua 是动态语言,变量没有类型,值才有。值自身携带类型信息。

Lua 有八种基本数据类型:nil, boolean, number, string, function, userdata, thread, table

nilfalse 导致条件为假,其他均为真。

userdata 类型变量用于保存 C 数据。 Lua 只能对该类数据进行使用,而不能进行创建或修改,保证宿主程序完全掌握数据。

thread 用于实现协程(coroutine)。

table 用于实现关联数组。table 允许任何类型的数据做索引,也允许任何类型做 table 域中的值(前述
任何类型 不包含 nil)。table 是 Lua 中唯一的数据结构。
由于函数也是一种值,所以 table 中可以存放函数。

function, userdata, thread, table 这些类型的值都是对象。这些类型的变量都只是保存变量的引用,并且在进行赋值,参数传递,函数返回等操作时不会进行任何性质的拷贝。

库函数 type() 返回变量的类型描述信息。

2.2.1 强制转换

Lua 提供数字字符串间的自动转换。
可以使用 format 函数控制数字向字符串的转换。

2.3 变量

变量有三种类型:全局变量、局部变量、表中的域

函数外的变量默认为全局变量,除非用 local 显示声明。函数内变量与函数的参数默认为局部变量。

局部变量的作用域为从声明位置开始到所在语句块结束(或者是直到下一个同名局部变量的声明)。

变量的默认值均为 nil。


a = 5 -- 全局变量 local b = 5 -- 局部变量 function joke() c = 5 -- 局部变量 local d = 6 -- 局部变量 end print(c,d) --> nil nil do local a = 6 -- 局部变量 b = 6 -- 全局变量 print(a,b); --> 6 6 end print(a,b) --> 5 6

方便标记,--> 代表前面表达式的结果。

2.3.1 索引

对 table 的索引使用方括号 []。Lua使用语法糖提供 . 操作。

t[i]
t.i                 -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用

2.3.2 环境表

所有全局变量放在一个环境表里,该表的变量名为 _env 。对某个全局变量 a 的访问即 _env.a_env_ 只是为了方便说明)。

每个函数作为变量持有一个环境表的引用,里面包含该函数可调用的所有变量。
子函数会从父函数继承环境表。
可以通过函数 getfenv / setfenv 来读写环境表。

2.4 语句 | statement

支持赋值,控制结构,函数调用,还有变量声明。

不允许空的语句段,因此 ;; 是非法的。

2.4.1 语句组 | chuncks

chunck ::= {stat[';']}

([';'] 应该是表示语句组后面 ; 是可选项。)

2.4.2 语句块 | blocks

block ::= chunck
stat ::= do block end

可以将一个语句块显式地写成语句组,可以用于控制局部变量的作用范围。

2.4.3 赋值 | assignment

Lua 支持多重赋值。

多重赋值时,按序将右边的表达式的值赋值给左值。右值不足补 nil,右值多余舍弃。

b = 1
a,b = 4 -- a = 4,b = nil 

+++

Lua 在进行赋值操作时,会一次性把右边的表达式都计算出来后进行赋值。

i = 5
i,a[i] = i+1, 7 -- i = 6 ,a[5] = 7

特别地,有

x,y = y,x -- 交换 x,y 的值

+++

对全局变量以及表的域的赋值操作含义可以在元表中更改。

2.4.4 控制结构

条件语句

if [exp]
    [block]
elseif [exp]
    [block]
else
    [block]
end

循环语句

while [exp]
    [block]
end

+++

repeat
    [block]
until [exp]

注意,由于 repeat 语句到 until 还未结束,因此在 until 之后的表达式中可以使用 block 中定义的局部变量。

例如:

a = 1
c = 5
repeat
    b = a + c
    c = c * 2
until b > 20
print(c)            -->     40

+++

breakreturn

breakreturn 只能写在语句块的最后一句,如果实在需要写在语句块中间,那么就在两个关键词外面包围 do end 语句块。

do break end

2.4.5 For 循环

for 循环的用法比较多,单独拎出来讲。

for 中的表达式会在循环开始前一次性求值,在循环过程中不再更新。

数字形式

for [Name] = [exp],[exp],[exp] do [block] end

三个 exp 分别代表初值,结束值,步进。exp 的值均需要是一个数字。
第三个 exp 默认为 1,可以省略。

a = 0

for i = 1,6,2 do
    a = a + i
end

等价于

int a = 0;
for (int i = 1; i <= 6;i += 2){ // 取到等号,如果步进是负的,那么会取 i >= 6
    a += i;
}

迭代器形式

迭代器形式输出一个表时,如果表中有函数,则输出的顺序及个数不确定(笔者测试得出的结果,具体原因未知)。

迭代器形式的 for 循环的实质

-- 依次返回 迭代器、状态表、迭代器初始值
function mypairs(t)

    function iterator(t,i)
        i = i + 1
        i = t[i] and i      -- 如果 t[i] == nil 则 i = nil;否则 i = i
        return i,t[i]
    end

    return iterator,t,0

end

-- 一个表
t = {[1]="1",[2]="2"}

-- 迭代形式 for 语句的 等价形式
do
local f, s, var = mypairs(t)
    while true do
        local var1, var2 = f(s, var)
        var = var1
        if var == nil then break end

        -- for 循环中添加的语句
        print(var1,var2)

    end
end

-- 迭代形式 for 语句
for var1,var2 in mypairs(t) do
    print(var1,var2)
end

--> 1   1
--> 2   2
--> 1   1
--> 2   2
数组形式
ary = {[1]=1,[2]=2,[5]=5}
for i,v in ipairs(ary) do
    print(v)                    --> 1 2
end

从1开始,直到数值型下标结束或者值为 nil 时结束。

表遍历
table = {[1]=1,[2]=2,[5]=5}
for k,v in pairs(table) do
    print(v)                    --> 1 2 5
end

遍历整个表的键值对。

关于迭代器的更多内容,可参考Lua 迭代器和泛型 for

2.5 表达式

2.5.1 数学运算操作符

% 操作符

Lua 中的 % 操作符与 C 语言中的操作符虽然都是取模的含义,但是取模的方式不一样。
在 C 语言中,取模操作是将两个操作数的绝对值取模后,在添加上第一个操作数的符号。
而在 Lua 中,仅仅是简单的对商相对负无穷向下取整后的余数。

+++

在 C 中,

a1 = abs(a);
b1 = abs(b);
c = a1 % b1 = a1 - floor(a1/b1)*b1;

a % b = (a >= 0) ? c : -c;

在 Lua 中,

a % b == a - math.floor(a/b)*b

Lua 是直接根据取模定义进行运算。 C 则对取模运算做了一点处理。

+++

举例:

在 C 中

int a = 5 % 6;
int b = 5 % -6;
int c = -5 % 6;
int d = -5 % -6;

printf("a,b,c,d");--5,5,-5,-5

在 Lua 中

a = 5 % 6
b = 5 % -6
c = -5 % 6
d = -5 % -6

x = {a,b,c,d}

for i,v in ipairs(x) do
    print(i,v)
end


--> 5
--> -1
--> 1
--> -5

可以看到,仅当操作数同号时,两种语言的取模结果相同。异号时,取模结果的符号与数值均不相等。

在 Lua 中的取模运算总结为:a % b,如果 a,b 同号,结果取 a,b 绝对值的模;异号,结果取 b 绝对值与绝对值取模后的差。取模后值的符号与 b 相同。

2.5.2 比较操作符

比较操作的结果是 boolean 型的,非 truefalse

支持的操作符有:

< <= ~= == > >=

不支持 ! 操作符。

+++

对于 == 操作,运算时先比较两个操作数的类型,如果不一致则结果为 false。此时数值与字符串之间并不会自动转换。

比较两个对象是否相等时,仅当指向同一内存区域时,判定为 true。·

a = 123
b = 233
c = "123"
d = "123"
e = {1,2,3}
f = e
g = {1,2,3}

print(a == b)       --> false
print(a == c)       --> false      -- 数字与字符串作为不同类型进行比较
print(c == d)       --> true       
print(e == f)       --> true       -- 引用指向相同的对象
print(e == g)       --> false      -- 虽然内容相同,但是是不同的对象
print(false == nil) --> false      -- false 是 boolean,nil 是 nil 型

方便标记,--> 代表前面表达式的结果。

+++

userdatatable 的比较方式可以通过元方法 eq 进行改变。

大小比较中,数字和字符串的比较与 C 语言一致。如果是其他类型的值,Lua会尝试调用元方法 ltle

2.5.3 逻辑操作符

and,or,not

仅认为 falsenil 为假。

not

取反操作 not 的结果为 boolean 类型。(andor 的结果则不一定为 boolean)

b = not a           -- a 为 nil,b 为 true
c = not not a       -- c 为 false

and

a and b,如果 a 为假,返回 a,如果 a 为真, 返回 b

注意,为什么 a 为假的时候要返回 a 呢?有什么意义?这是因为 a 可能是 false 或者 nil,这两个值虽然都为假,但是是有区别的。

or

a or b,如果 a 为假,返回 b,如果 a 为真, 返回 a。与 and 相反。

+++

提示: 当逻辑操作符用于得出一个 boolean 型结果时,不需要考虑逻辑运算后返回谁的问题,因为逻辑操作符的操作结果符合原本的逻辑含义。

举例

if (not (a > min and a < max)) then  -- 如果 a 不在范围内,则报错
    error() 
end

+++

其他

andor 遵循短路原则,第二个操作数仅在需要的时候会进行求值操作。

例子


a = 5 x = a or jjjj() -- 虽然后面的函数并没有定义,但是由于不会执行,因此不会报错。 print(a) -->5 print(x) -->5

通过上面这个例子,我们应当对于逻辑操作有所警觉,因为这可能会引入一些未能及时预料到的错误。

2.5.4 连接符

..
连接两个字符串(或者数字)成为新的字符串。对于其他类型,调用元方法 concat

2.5.5 取长度操作符

#

对于字符串,长度为字符串的字符个数。

对于表,通过寻找满足t[n] 不是 nil 而 t[n+1] 为 nil 的下标 n 作为表的长度。

~~对于其他类型呢?~~

例子

-- 字符串取长
print(#"abc\0")                         --> 4
-- 表取长
print(#{[1]=1,[2]=2,[3]=3,x=5,y=6})     --> 3
print(#{[1]=1,[2]=nil,[3]=3,x=5,y=6})   --> 1

2.5.6 优先级

由低到高:

or
and
 <     >     <=    >=    ~=    ==
 ..
 +     -
 *     /     %
 not   #     - (unary)
 ^

幂运算>单目运算>四则运算>连接符>比较操作符>and>or

2.5.7 Table 构造

Table 构造的 BNF 定义

tableconstructor ::= `{´ [fieldlist] `}´
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
fieldsep ::= `,´ | `;´

举例:

a = {}
b = {["price"] = 5; cost = 4; 2+5}
c = { [1] = 2+5, [2] = 2, 8, price = "abc", ["cost"] = 4} -- b 和 c 构造的表是等价的


print(b["price"])   --> 5
print(b.cost)       --> 4
print(b[1])         --> 7       -- 未给出键值的,按序分配下标,下标从 1 开始

print(c["price"])   --> abc
print(c.cost)       --> 4
print(c[1])         --> 8       
print(c[2])         --> 2       

注意:

上面这两条的存在使得上面的例子中 c1 的输出值为 8。

+++

如果表中有相同的键,那么以靠后的那个值作为键对应的值。

a = {[1] = 5,[1] = 6} -- 那么 a[1] = 6

+++

如果表的最后一个域是表达式形式,并且是一个函数,那么这个函数的所有返回值都会加入到表中。

a = 1
function order()
    a = a + 1
    return 1,2,3,4
end

b = {order(); a; order(); }

c = {order(); a; (order());}

print(b[1])                     --> 1       
print(b[2])                     --> 2       -- 表中的值并不是一次把表达式都计算结束后再赋值的
print(b[3])                     --> 1       
print(b[4])                     --> 2       -- 表达式形式的多返回值函数

print(#b)                       --> 6       -- 表的长度为 6                 
print(#c)                       --> 3       -- 函数添加括号后表的长度为 3

2.5.8 函数定义

函数是一个表达式,其值为 function 类型的对象。函数每次执行都会被实例化。

Lua 中实现一个函数可以有以下三种形式。

f = function() [block] end
local f; f = function() [block] end
a.f = function() [block] end

Lua 提供语法糖分别处理这三种函数定义。

function f() [block] end
local function f() [block] end
function a.f() [block] end

+++

上面 local 函数的定义之所以不是 local f = function() [block] end,是为了避免如下错误:

local f = function()
    print("local fun")
    if i==0 then 
        f()             -- 编译错误:attempt to call global 'f' (a nil value)
        i = i + 1
    end
end

函数的参数

形参会通过实参来初始化为局部变量。

参数列表的尾部添加 ... 表示函数能接受不定长参数。如果尾部不添加,那么函数的参数列表长度是固定的。

f(a,b)
g(a,b,...)
h(a,...,b)              -- 编译错误
f(1)                    --> a = 1, b = nil
f(1,2)                  --> a = 1, b = 2
f(1,2,3)                --> a = 1, b = 2

g(1,2)                  --> a = 1, b = 2, (nothing)
g(1,2,3)                --> a = 1, b = 2, (3)
g(1,f(4,5),3)           --> a = 1, b = 4, (3)
g(1,f(4,5))             --> a = 1, b = 4, (5)

+++

还有一种形参为self的函数的定义方式:

a.f = function (self, params) [block] end

其语法糖形式为:

function a:f(params) [block] end

使用举例:

a = {name = "唐衣可俊"}
function a:f()
    print(self.name)
end
a:f()                       --> 唐衣可俊   -- 如果这里使用 a.f(),那么 self.name 的地方会报错 attempt to index local 'self';此时应该写为 a.f(a)

: 的作用在于函数定义与调用的时候可以少写一个 self 参数。这种形式是对方法的模拟

2.5.9 函数调用

Lua 中的函数调用的BNF语法如下:

functioncall ::= prefixexp args

如果 prefixexp 的值的类型是 function, 那么这个函数就被用给出的参数调用。 否则 prefixexp 的元方法 "call" 就被调用, call 的第一个参数就是 prefixexp 的值,接下来的是 args 参数列表(参见 2.8 元表 | Metatable)。

函数调用根据是否传入 self 参数分为 . 调用和 : 调用。
函数调用根据传入参数的类型,可以分为参数列表调用、表调用、字符串调用

[待完善]

2.5.10 函数闭包

如果一个函数访问了它的外部变量,那么它就是一个闭包。

由于函数内部的变量均为局部变量,外界无法对其进行访问。这时如果外界想要改变局部变量的值,那么就可以使用闭包来实现这一目的。
具体的实现过程大致是这样,函数内部有能够改变局部变量的子函数,函数将这个子函数返回,那么外界就可以通过使用这个子函数来操作局部变量了。

例子:利用闭包来实现对局部变量进行改变

-- 实现一个迭代器

function begin(i)
    local cnt = i

    return function ()      -- 这是一个匿名函数,实现了自增的功能;同时它也是一个闭包,因为访问了外部变量 cnt
        cnt = cnt + 1
        return cnt
    end
end


iterator = begin(2)     -- 设置迭代器的初值为 2 ,返回一个迭代器函数

print(iterator())           -- 执行迭代
print(iterator())

提示: 关于闭包的更多说明可参考JavaScript 闭包是如何工作的?——StackOverflow

2.6 可视规则

即变量的作用域,见 2.3 变量 部分。

2.7 错误处理

[待补充]

2.8 元表 | Metatable

我们可以使用操作符对 Lua 的值进行运算,例如对数值类型的值进行加减乘除的运算操作以及对字符串的连接、取长操作等(在 2.5 表达式 这一节中介绍了许多类似的运算)。元表正是定义这些操作行为的地方。

元表本质上是一个普通 Lua 表。元表中的键用来指定操作,称为“事件名”;元表中键所关联的值称为“元方法”,定义操作的行为。

2.8.1 事件名与元方法

仅表(table)类型值对应的元表可由用户自行定义。其他类型的值所对应的元表仅能通过 Debug 库进行修改。

元表中的事件名均以两条下划线 __ 作为前缀,元表支持的事件名有如下几个:

__index     -- 'table[key]',取下标操作,用于访问表中的域
__newindex  -- 'table[key] = value',赋值操作,增改表中的域
__call      -- 'func(args)',函数调用,参见 [2.5.9 函数调用](#2-5-9)

-- 数学运算操作符
__add       -- '+'
__sub       -- '-'
__mul       -- '*'
__div       -- '/'
__mod       -- '%'
__pow       -- '^'
__unm       -- '-'

-- 连接操作符
__concat    -- '..'

-- 取长操作符
__len       -- '#'

-- 比较操作符
__eq        -- '=='
__lt        -- '<'      -- a > b 等价于 b < a
__le        -- '<='     -- a >= b 等价于 b <= a 

还有一些其他的事件,例如 __tostring__gc 等。

下面进行详细介绍。

2.8.2 元表与值

每个值都可以拥有一个元表。对 userdata 和 table 类型而言,其每个值都可以拥有独立的元表,也可以几个值共享一个元表。对于其他类型,一个类型的值共享一个元表。例如所有数值类型的值会共享一个元表。除了字符串类型,其他类型的值默认是没有元表的。

使用 getmetatable 函数可以获取任意值的元表。getmetatable (object)
使用 setmetatable 函数可以设置表类型值的元表。setmetatable (table, metatable)

例子

只有字符串类型的值默认拥有元表:

a = "5"
b = 5
c = {5}
print(getmetatable(a))      --> table: 0x7fe221e06890
print(getmetatable(b))      --> nil
print(getmetatable(c))      --> nil

2.8.3 事件的具体介绍

事先提醒 Lua 使用 raw 前缀的函数来操作元方法,避免元方法的循环调用。

例如 Lua 获取对象 obj 中元方法的过程如下:

rawget(getmetatable(obj)or{}, "__"..event_name)

元方法 index

index 是元表中最常用的事件,用于值的下标访问 -- table[key]

事件 index 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。
当用户通过键值来访问表时,如果没有找到键对应的值,则会调用对应元表中的此事件。如果 index 使用表进行赋值,则在该表中查找传入键的对应值;如果 index 使用函数进行赋值,则调用该函数,并传入表和键。

Lua 对取下标操作的处理过程用伪码表示如下:

function gettable_event (table, key)
    -- h 代表元表中 index 的值
    local h     
    if type(table) == "table" then

        -- 访问成功
        local v = rawget(table, key)
        if v ~= nil then return v end

        -- 访问不成功则尝试调用元表的 index
        h = metatable(table).__index

        -- 元表不存在返回 nil
        if h == nil then return nil end
    else

        -- 不是对表进行访问则直接尝试元表
        h = metatable(table).__index

        -- 无法处理导致出错
        if h == nil then
            error(···);
        end
    end

    -- 根据 index 的值类型处理
    if type(h) == "function" then
        return h(table, key)            -- 调用处理器
    else 
        return h[key]                   -- 或是重复上述操作
    end
end

例子:

使用表赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = {[3] = "pig", [4] = "cow", [5] = "duck"}})
print(t[3])             --> pig

使用函数赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = function (table,key)
    key = key % 2 + 1
    return table[key]
end})
print(t[3])             --> dog

元方法 newindex

newindex 用于赋值操作 -- talbe[key] = value

事件 newindex 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。

当操作类型不是表或者表中尚不存在传入的键时,会调用 newindex 的元方法。如果 newindex 关联的是一个函数类型以外的值,则再次对该值进行赋值操作。反之,直接调用函数。

~~不是太懂:一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)~~

Lua 进行赋值操作时的伪码如下:

function settable_event (table, key, value)
    local h
    if type(table) == "table" then

        -- 修改表中的 key 对应的 value
        local v = rawget(table, key)
        if v ~= nil then rawset(table, key, value); return end

        -- 
        h = metatable(table).__newindex

        -- 不存在元表,则直接添加一个域
        if h == nil then rawset(table, key, value); return end
    else
        h = metatable(table).__newindex
        if h == nil then
            error(···);
        end
    end

    if type(h) == "function" then
        return h(table, key,value)    -- 调用处理器
    else 


        h[key] = value             -- 或是重复上述操作
    end
end

例子:

元方法为表类型:

t = {}
mt = {}

setmetatable(t, {__newindex = mt})
t.a = 5
print(t.a)      --> nil
print(mt.a)     --> 5

通过两次调用 newindex 元方法将新的域添加到了表 mt 。

+++

元方法为函数:

-- 对不同类型的 key 使用不同的赋值方式
t = {}
setmetatable(t, {__newindex = function (table,key,value)
    if type(key) == "number" then
        rawset(table, key, value*value)
    else
        rawset(table, key, value)
    end
end})
t.name = "product"
t[1] = 5
print(t.name)       --> product
print(t[1])         --> 25

元方法 call

call 事件用于函数调用 -- function(args)

Lua 进行函数调用操作时的伪代码:

function function_event (func, ...)

  if type(func) == "function" then
      return func(...)   -- 原生的调用
  else
      -- 如果不是函数类型,则使用 call 元方法进行函数调用
      local h = metatable(func).__call

      if h then
        return h(func, ...)
      else
        error(···)
      end
  end
end

例子:

由于用户只能为表类型的值绑定自定义元表,因此,我们可以对表进行函数调用,而不能把其他类型的值当函数使用。

-- 把数据记录到表中,并返回数据处理结果
t = {}

setmetatable(t, {__call = function (t,a,b,factor)
  t.a = 1;t.b = 2;t.factor = factor
  return (a + b)*factor
end})

print(t(1,2,0.1))       --> 0.3

print(t.a)              --> 1
print(t.b)              --> 2
print(t.factor)         --> 0.1

运算操作符相关元方法

运算操作符相关元方法自然是用来定义运算的。

以 add 为例,Lua 在实现 add 操作时的伪码如下:

function add_event (op1, op2)
  -- 参数可转化为数字时,tonumber 返回数字,否则返回 nil
  local o1, o2 = tonumber(op1), tonumber(op2)
  if o1 and o2 then  -- 两个操作数都是数字?
    return o1 + o2   -- 这里的 '+' 是原生的 'add'
  else  -- 至少一个操作数不是数字时
    local h = getbinhandler(op1, op2, "__add") -- 该函数的介绍在下面
    if h then
      -- 以两个操作数来调用处理器
      return h(op1, op2)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

代码中的 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作。 在该函数中,首先,Lua 尝试第一个操作数。如果这个操作数所属类型没有定义这个操作的处理器,然后 Lua 会尝试第二个操作数。

 function getbinhandler (op1, op2, event)
   return metatable(op1)[event] or metatable(op2)[event]
 end

+++

对于一元操作符,例如取负,Lua 在实现 unm 操作时的伪码:

function unm_event (op)
  local o = tonumber(op)
  if o then  -- 操作数是数字?
    return -o  -- 这里的 '-' 是一个原生的 'unm'
  else  -- 操作数不是数字。
    -- 尝试从操作数中得到处理器
    local h = metatable(op).__unm
    if h then
      -- 以操作数为参数调用处理器
      return h(op)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

例子:

加法的例子:

t = {}
setmetatable(t, {__add = function (a,b)
  if type(a) == "number" then
      return b.num + a
  elseif type(b) == "number" then
      return a.num + b
  else
      return a.num + b.num
  end
end})

t.num = 5

print(t + 3)  --> 8

取负的例子:

t = {}
setmetatable(t, {__unm = function (a)
  return -a.num
end})

t.num = 5

print(-t)  --> -5

其他事件的元方法

对于连接操作,当操作数中存在数值或字符串以外的类型时调用该元方法。

对于取长操作,如果操作数不是字符串类型,也不是表类型,则尝试使用元方法(这导致自定义的取长基本没有,在之后的版本中似乎做了改进)。

对于三种比较类操作,均需要满足两个操作数为同类型,且关联同一个元表时才能使用元方法。

对于 eq (等于)比较操作,如果操作数所属类型没有原生的等于比较,则调用元方法。

对于 lt (小于)与 le (小于等于)两种比较操作,如果两个操作数同为数值或者同为字符串,则直接进行比较,否则使用元方法。

对于 le 操作,如果元方法 "le" 没有提供,Lua 就尝试 "lt",它假定 a <= b 等价于 not (b < a) 。

对于 tostring 操作,元方法定义了值的字符串表示方式。

例子:

取长操作:

t = {1,2,3,"one","two","three"}
setmetatable(t, {__len = function (t)
  local cnt = 0
  for k,v in pairs(t) do
    if type(v) == "number" then 
      cnt = cnt + 1
      print(k,v)
    end
  end
  return cnt
end})

-- 结果是 6 而不是预期中的 3
print(#t)   --> 6 

等于比较操作:

t = {name="number",1,2,3}
t2 = {name = "number",4,5,6}
mt = {__eq = function (a,b)
    return a.name == b.name
end}
setmetatable(t,mt)              -- 必须要关联同一个元表才能比较
setmetatable(t2,mt)

print(t==t2)   --> true

tostring 操作:

t = {num = "a table"}
print(t)              --> table: 0x7f8e83c0a820

mt = {__tostring = function(t)
  return t.num
end}
setmetatable(t, mt)

print(tostring(t))    --> a table
print(t)              --> a table

2.9 环境表

类型 threadfunctionuserdata 的对象除了能与元表建立关联外,还能关联一个环境表。

关联在线程上的环境表称为全局环境。
全局环境作为子线程及子函数的默认环境。
全局环境能够直接被 C 调用。

关联在 Lua 函数上的环境表接管函数对全局变量的所有访问。并且作为子函数的默认环境。

关联在 C 函数上的环境能直接被 C 调用。

关联在 userdata 上的环境没有实际的用途,只是为了方便程序员把一个表关联到 userdata 上。

2.10 垃圾回收

2.10.1 垃圾收集的元方法

[待补充]

2.10.2 弱表

弱表是包含弱引用的表。

弱表的弱引用方式有三种。键弱引用,值弱引用,键和值均弱引用

可以通过元表中的 __mode 域来设置一个表是否有弱引用,以及弱引用的方式。

a = {}
b = { __mode = "k"}  -- 引号中添加 k 表示 key 弱引用,v 表示 value 弱引用, kv 表示均弱引用。
setmetable(a,b)     -- b 是 a 的元表,绑定后就不能在更改 __mode 的值。

垃圾回收机制会把弱引用的部分回收。但是不论是哪种弱引用,回收机制都会把整个键值对从弱表中移除。

3 程序接口 (API)

这部分描述 Lua 的 C API,即用来与 Lua 进行通信的 C 函数,所有的函数和常量都定义在 lua.h 头文件里面。

有一部分 C 函数是用宏来实现的。~~为什么?:由于所有的宏只会使用他们的参数一次(除了第一个参数,即 Lua 状态机),所以不必担心宏展开带来的副作用。~~

默认情况下 Lua 在进行函数调用时不会检查函数的有效性和坚固性,如果想要进行检查,则使用 luaconf.h 中的 luai_apicheck() 函数开启。

3.1 堆栈

Lua 调用 C API 时使用一个虚拟栈来传递参数,栈中的所有元素都是 Lua 的类型(例如 booleantablenil等)。

Lua 调用 C 函数的时候都会新建一个虚拟栈,而不是使用旧栈或者其他的栈。同时在 C 函数中,对 Lua API 调用时,只能使用当前调用所对应栈中的元素,其他栈的元素是无法访问的。
虚拟栈中包含 C 函数所需的所有参数,函数的返回值也都放在该栈中。

这里所谓的栈概念并不是严格意义上的栈,可以通过下标对栈中的元素进行访问。1表示栈底,-1表示栈顶,又例如 3 表示从栈底开始的第三个元素。

3.2 堆栈尺寸

由于 Lua 的 C API 默认不做有效性和坚固性(鲁棒性)检测,因此开发人员有责任保证坚固性。特别要注意的是,不能让堆栈溢出。Lua 只保证栈大小会大于 LUA_MINSTACK(一般是 20)。开发人员可以使用 lua_checkstack 函数来手动设置栈的大小。

3.3 伪索引

除了用索引访问函数堆栈的 Lua 元素,C 代码还可以使用伪索引来访问堆栈以外的 Lua 元素,例如线程的环境、注册表、函数的环境 以及 C函数的 upvalue(上值)。可以通过特别声明来禁用伪索引。

线程的环境放在伪索引 LUA_GLOBALSINDEX 处,函数的环境放在伪索引 LUA_ENVIRONINDEX 处。

访问环境的方式跟访问表的方式是一致的,例如要访问全局变量的值,可以使用:

lua_getfield(L,LUA_GLOBALSINDEX,varname)

3.4 C 闭包

当我们把创建出来的函数和一些值关联在一起,就得到了一个闭包。那些关联起来的值称为 upvalue (上值)。

函数的上值都放在特定的伪索引处,可以通过 lua_upvalueindex 获取上值的伪索引。例如 lua_upvalueindex(3) 表示获取第三个关联值(按照关联顺序排列)对应的伪索引。

3.5 注册表

Lua 提供了一个注册表,C 代码可以用来存放想要存放的 Lua 值。注册表用伪索引 LUA_REGISTRYINDEX 定位。

为了避免命名冲突,一般采用包含库名的字符串作为键名。~~什么东西?:或者可以取你自己 C 代码 中的一个地址,以 light userdata 的形式做键。~~

注册表中的整数键有特定用途(用于实现补充库的引用系统),不建议用于其他用途。

3.6 C 中的错误处理

[待补充]

3.7 函数和类型

本节介绍 C API 中的函数和类型。

余下部分见 Lua 学习笔记(下)


参考链接

BNF范式简介 (简要介绍 BNF)
Lua入门系列-果冻想(对Lua进行了较为全面的介绍)
Lua快速入门(介绍 Lua 中最为重要的几个概念,为 C/C++ 程序员准备)
Lua 5.1 中文手册(全面的 Lua5.1 中文手册)
Lua 5.3 中文手册(云风花了6天写的,天哪,我看都要看6天的节奏呀)
Lua迭代器和泛型for(介绍 Lua 迭代器的详细原理以及使用)
How do JavaScript closures work?——StackOverflow(详细介绍了 Javascript 中闭包的概念)
Lua模式匹配(参考了此文中对 %b 的使用)
LuaSocket(LuaSocket 官方手册)
Lua loadfile的用法, 与其他函数的比较(loadfile的介绍部分引用了此文)
Lua 的元表(对元表的描述比较有条理,通俗易懂,本文元表部分参考了此文)
设置函数环境——setfenv(解释了如何方便地设置函数的环境,以及为什么要那样设置)
lua5.1中的setfenv使用(介绍了该环境的设置在实际中的一个应用)

2015年五月2日晚上 9:00:36 PHP数组操作详解

概述

要访问一个变量的内容,可以直接使用其名称。如果该变量是一个数组,可以使用变量名称和关键字或索引的组合来访问其内容。

像其他变量一样,使用运算符=可以改变数组元素的内容。数组单元可以通过 array[key] 语法来访问。

图片描述

数组的基本操作

php定义数组:

<?php  
    $array = array();  
    $array["key"] = "values";  
?> 

PHP中声明数组的方式主要有两种:

1.用array()函数声明数组,
2.直接为数组元素赋值。

<?php
    //array数组
    $users = array('phone','computer','dos','linux');
    echo $users;//只会打印出数据类型Array
    print_r($users);//Array ( [0] => phone [1] => computer [2] => dos [3] => linux )

    $numbers = range(1,5);//创建一个包含指定范围的数组
    print_r($numbers);//Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )
    print_r(true);//1
    var_dump(false);//bool(false)

//print_r可以把字符串和数字简单地打印出来,数组会以Array开头并已键值形式表示,print_r输出布尔值和null的结果没有意义,因此用var_dump更合适

//通过循环来显示数组里所有的值
    for($i = 0 ;$i < 5;$i++){
        echo $users[$i];
        echo '<br/>';
    }

//通过count/sizeof统计数组中单元数目或对象中的属性个数

    for($i = 0; $i < count($users);$i++){
        echo $users[$i];
        echo '<br/>';
    }
//还可以通过foreach循环来遍历数组,这种好处在于不需要考虑key
    foreach($users as $value){
        echo $value.'<br/>';//点号为字符串连接符号
    }
//foreach循环遍历 $key => $value;$key和$value是变量名,可以自行设置
    foreach($users as $key => $value){
        echo $key.'<br/>';//输出键
    }
?>

创建自定义键的数组

<?php

    //创建自定义键的数组
    $ceo = array('apple'=>'jobs','microsoft'=>'Nadella','Larry Page','Eric');
    //如果不去声明元素的key,它会从零开始
    print_r($ceo);//Array ( [apple] => jobs [microsoft] => Nadella [0] => Larry Page [1] => Eric )

    echo $ceo['apple'];//jobs

     //php5.4起的用法
    $array = [
        "foo" => "bar",
        "bar" => "foo",
    ];

    print_r($array);//Array ( [foo] => bar [bar] => foo ) 

?>    

php5.4 起可以使用短数组定义语法,用 [] 替代 array()。有点类似于javascript中数组的定义。

each()的使用

<?php
    //通过为数组元素赋值来创建数组
    $ages['trigkit4'] = 22;
    echo $ages.'<br/>';//Array
    //因为相关数组的索引不是数字,所以无法通过for循环来进行遍历操作,只能通过foreach循环或list()和each()结构

    //each的使用
    //each返回数组中当前的键/值对并将数组指针向前移动一步
    $users = array('trigkit4'=>22,'mike'=>20,'john'=>30);
    //print_r(each($users));//Array ( [1] => 22 [value] => 22 [0] => trigkit4 [key] => trigkit4 )

   //相当于:$a = array([0]=>trigkit4,[1]=>22,[value]=>22,[key]=>trigkit4);
    $a = each($users);//each把原来的数组的第一个元素拿出来包装成新数组后赋值给$a
    echo $a[0];//trigkit4

    //!!表示将真实存在的数据转换成布尔值
    echo !!each($users);//1

?>  

each的指针指向第一个键值对,并返回第一个数组元素,获取其键值对,并包装成新数组

list()的使用

list用来把数组用的值赋给一些变量,看下面例子:

<?php

    $a = ['2','abc','def'];
    list($var1,$var2) = $a;
    echo $var1.'<br/>';//2
    echo $var2;//abc

    $a = ['name'=>'trigkit4','age'=>22,'0'=>'boy'];
    //list只认识key为数字的索引
    list($var1,$var2) = $a;

    echo $var1;//boy

?>

注:list只认识key为数字的索引

数组元素的排序

反向排序:sort()、asort()和 ksort()都是正向排序,当然也有相对应的反向排序. 
实现反向:rsort()、arsort()和 krsort()。

array_unshift()函数将新元素添加到数组头,array_push()函数将每个新元素添加到数组 的末尾。
array_shift()删除数组头第一个元素,与其相反的函数是 array_pop(),删除并返回数组末 尾的一个元素。
array_rand()返回数组中的一个或多个键。

函数shuffle()将数组个元素进 行随机排序。
函数 array_reverse()给出一个原来数组的反向排序

数组的各类API的使用

count()和 sizeof()统计数组下标的个数 
array_count_values()统计数组内下标值的个数

<?php
    $numbers = array('100','2');
    sort($numbers,SORT_STRING);//按字符串排序,字符串只比较第一位大小
    print_r($numbers);//Array ( [0] => 100 [1] => 2 )

    $arr = array('trigkit4','banner','10');
    sort($arr,SORT_STRING);
    print_r($arr);//Array ( [0] => 10 [1] => banner [2] => trigkit4 )

    shuffle($arr);
    print_r($arr);//随机排序

    $array = array('a','b','c','d','0','1');
    array_reverse($array);
    print_r($array);//原数组的反向排序。 Array ( [0] => a [1] => b [2] => c [3] => d [4] => 0 [5] => 1 )


    //数组的拷贝
    $arr1  = array( '10' , 2);
    $arr2  =  &$arr1 ;
    $arr2 [] =  4 ;  // $arr2 被改变了,$arr1仍然是array('10', 3)
    print_r($arr2);//Array ( [0] => 10 [1] => 2 [2] => 4 )

    //asort的使用
    $arr3  = & $arr1 ;//现在arr1和arr3是一样的
    $arr3 [] =  '3' ;
    asort($arr3);//对数组进行排序并保留原始关系
    print_r($arr3);// Array ( [1] => 2 [2] => 3 [0] => 10 )

    //ksort的使用
    $fruits = array('c'=>'banana','a'=>'apple','d'=>'orange');
    ksort($fruits);
    print_r($fruits);//Array ( [a] => apple [c] => banana [d] => orange )

   //unshift的使用
    array_unshift($array,'z');//开头处添加一元素
    print_r($array);//Array ( [0] => z [1] => a [2] => b [3] => c [4] => d [5] => 0 [6] => 1 )  

    //current(pos)的使用
    echo current($array);//z;获取当前数组中的当前单元

    //next的使用
    echo next($array);//a;将数组中的内部指针向前移动一位

    //reset的使用
    echo reset($array);//z;将数组内部指针指向第一个单元

    //prev的使用
    echo next($array);//a;
    echo prev($array);//z;倒回一位

    //sizeof的使用
    echo sizeof($array);//7;统计数组元素的个数

    //array_count_values
    $num = array(10,20,30,10,20,1,0,10);//统计数组元素出现的次数
    print_r(array_count_values($num));//Array ( [10] => 3 [20] => 2 [30] => 1 [1] => 1 [0] => 1 ) 

?>    

current():每个数组都有一个内部指针指向他的当前单元,初始指向插入到数组中的第一个元素

for循环遍历

<?php
    $value = range(0,120,10);
    for($i=0;$i<count($value);$i++){
        print_r($value[$i].' ');//0 10 20 30 40 50 60 70 80 90 100 110 120 
    }
?>

数组的实例

array_pad函数的使用

<?php
    //array_pad函数,数组数组首尾选择性追加
    $num = array(1=>10,2=>20,3=>30);
    $num = array_pad($num,4,40);
    print_r($num);//Array ( [0] => 10 [1] => 20 [2] => 30 [3] => 40 )

    $num = array_pad($num,-5,50);//array_pad(array,size,value)
    print_r($num);//Array ( [0] => 50 [1] => 10 [2] => 20 [3] => 30 [4] => 40 ) 
?>

size:指定的长度。整数则填补到右侧,负数则填补到左侧。

unset()的使用

 <?php
    //unset()的使用
    $num = array_fill(0,5,rand(1,10));//rand(min,max)
    print_r($num);//Array ( [0] => 8 [1] => 8 [2] => 8 [3] => 8 [4] => 8 ) 
    echo '<br/>';

    unset($num[3]);
    print_r($num);//Array ( [0] => 8 [1] => 8 [2] => 8 [4] => 8 ) 
?>

array_fill()的使用

<?php
    //array_fill()的使用
    $num = range('a','e');
    $arrayFilled = array_fill(1,2,$num);//array_fill(start,number,value)
    echo '<pre>';

    print_r($arrayFilled);

?>

array_combine()的使用

<?PHP
    $number = array(1,2,3,4,5);
    $array = array("I","Am","A","PHP","er");
    $newArray = array_combine($number,$array);
    print_r($newArray);//Array ( [1] => I [2] => Am [3] => A [4] => PHP [5] => er ) 
?> 

array_splice()删除数组成员

<?php
    $color = array("red", "green", "blue", "yellow");
    count ($color); //得到4
    array_splice($color,1,1); //删除第二个元素
    print_r(count ($color)); //3
    echo $color[2]; //yellow
    echo $color[1]; //blue
?>  

array_unique删除数组中的重复值

<?php
    $color=array("red", "green", "blue", "yellow","blue","green");
    $result = array_unique($color);
    print_r($result);//Array ( [0] => red [1] => green [2] => blue [3] => yellow ) 
?> 

array_flip()交换数组的键值和值

<?PHP
    $array = array("red","blue","red","Black");
    print_r($array);
    echo "<br />";
    $array = array_flip($array);//
    print_r($array);//Array ( [red] => 2 [blue] => 1 [Black] => 3 ) 
?> 

array_search()搜索数值

<meta charset="utf-8">
<?php
   $array = array("red","blue","red","Black");
   $result=array_search("red",$array)//array_search(value,array,strict)
    if(($result === NULL)){
        echo "不存在数值red";
    }else{
        echo "存在数值 $result";//存在数值 0 
    }
?> 
2015年五月2日晚上 8:56:47 Linux 文件和文件夹的操作权限

由于 linux 是多用户操作系统,所以基于安全的考虑,需要具备保障个人隐私和系统安全的机制。因此在使用 linux 系统的时候,经常会出现权限的问题(比如: 删除文件、安装软件、运行应用等等),期初遇到这些问题的时候,大部分都使用sudo或者是sudo chmod 777 file(后面会讲解这个命令)来解决的。虽然这种方式可以解决问题,但是这样是不安全的,特别是在服务器上操作的时候,因为不是所有的文件和文件夹都可以被其它用户操作的,不是所有的用户都有root权限的,不是所有的应用都可以用root用户启动的。那么我们要如何正确的处理这些权限问题呢?那就让我们来学习一下 linux 权限相关的知识。

用户的权限

要确定一个用户对某个文件或文件夹是否具有相应的操作权限,先要明确该用户与文件或文件夹之间的关系。在 linux 系统中,定义了如下三种关系:

因为在 linux 下的文件和文件夹都有读取(r)写入(w)执行(x)的操作,所以上面描述的每种关系的用户分别都可以赋予这些操作权限。操作权限介绍:

权限 简写 对普通文件的作用 对文件夹的作用
读取 r 查看文件内容 列出文件夹中的文件(ls)
写入 w 修改文件内容 在文件夹中删除、添加或重命名文件(夹)
执行 x 文件可以作为程序执行 cd 到文件夹

文件或文件夹和用户的三种关系的基础操作权限

在 linux 使用ls -la命令可以查看文件夹内文件的属性,下面是我电脑上某个文件夹下文件的属性:

bash$ ls -la
drwxr-xr-x 14 root root     4096 Apr  3 18:47 .
drwxr-xr-x 23 root root     4096 Mar  2 05:48 ..
drwxr-xr-x  2 root root     4096 Apr  3 07:44 backups
drwxr-xr-x 17 root root     4096 Jul 22  2014 cache
drwxr-xr-x  2 root root     4096 Mar  2 04:26 docker-registry
lrwxrwxrwx  1 root root        9 Feb 25 13:31 lock -> /run/lock
drwxrwxr-x 15 root syslog   4096 Apr  3 07:44 log
-rw-r--r--  1 root root        0 Apr  3 18:47 test

特殊权限SUIDSGIDSticky

在 linux 系统中还有三种与用户身份无关的三个文件权限属性。即SUID、SGID和Sticky

修改文件或文件夹对应用户的操作权限

在 linux 系统中,可以使用chmod命令来修改文件或文件夹对应用户的操作权限,chmod命令也有两种方式修改,一种是使用代表相应操作权限的字母简写表示,另一种是使用代表相应操作权限的数字表示。

修改文件或文件夹的拥有者和所属的组

使用chown可以修改文件或文件夹的拥有者和所属的组。

创建组和用户

  了解 linux 用户操作权限,安全就掌握在手中。

参考

原文链接

2015年五月2日晚上 8:47:47 Fedora 21下Nvidia显卡的安装

最近由于工作和学习需要,把家用的两台电脑攒成了一台机器,用的是Fedora 21,安装过程比较傻瓜就不写了,因为显卡用的是比较搓的N卡,N卡的开源驱动nouveau又搓的要死,装了跟不装一事,所以装机后需要做的第一件事就是要安装N卡的官方驱动,过程不难但是背不下来,所以正好在这里记录一下,以后也好找。

简单来说:

这里GeForce GT730就是我这块网卡的型号

按照提示几个选项一路选下来,搜索得到的驱动里选择一个最新的,随便用什么工具下载下来

wget http://us.download.nvidia.com/XFree86/Linux-x86_64/346.59/NVIDIA-Linux-x86_64-346.59.run

到这里还不能直接安装驱动,下载下来的run文件在安装过程中会编译匹配我们当前系统版本的驱动出来。编译驱动需要用到kernel source,但如果是像我这样直接下了发行版来安装的话,默认是不包含kernel source的,所以我们需要安装对应当前系统版本的kernel-devel

sudo yum install gcc kernel-devel-$(uname -r) 

系统更新完成后,要重启新的kernel才会生效,不过没关系等等一起重启也可以,现在我们要做的是屏蔽nouveau驱动,直接

echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf

移除已经安装的开源驱动包

yum list | grep nouveau
yum remove xorg-x11-drv-nouveau.x86_64

设置默认启动进入字符界面

systemctl set-default multi-user.target

(效力等同于重启后在登录界面输入ctrl+alt+F2,这点还不熟悉的同学可以看看systemcl的几组user target的定义)

重启系统之后安装官方驱动

chmod u+x ./*.run
./NVIDIA-Linux-x86_64-346.59.run

跟着提示一路走下去即可,安装完成之后记得将启动级别改回到图形界面

systemctl set-default graphical.target

然后重启就可以了。

问题:
安装过程没遇到什么问题,有一点可以注意一下,如果你安装kernel-devel的时候没有指定uname -r,即当前版本,你更新到的kernel source会是最新版的,在编译官方驱动的时候会跟你抱怨找不到KDIR的。

2015年五月2日晚上 8:13:32 关于redis不同权限列表显示缓存问题-带分页

各位大神好,求助,由于对redis+mysql这种nosql+sql方式存储没有最佳实践,想求教下有这种经验的大神,最近用mysql+redis+nodejs做个大数据高并发东西,想要用redis缓存带分页列表信息减少mysql查询压力,当前端访问时候可以根据不同访问权限到redis提取数据,如果没有则从mysql查询.(比如:老师,学生,校长三个不同权限拉取数据不同,需要考虑数据更新,redis-mysql数据一致性问题).

2015年五月2日下午 3:07:27 T-SQL学习中--内联接,外连接,交叉连接

交叉连接可以表A和表B是同一张表取得笛卡尔乘积。
比如说下面这种写法:

SQLSELECT D.n AS theday, S.n AS shiftno  
FROM dbo.Nums AS D
  cross JOIN dbo.Nums AS S
WHERE D.n <= 7
  AND S.N <= 3
ORDER BY theday, shiftno;

当然也可以表A和表B是两张不同的表,取得笛卡尔乘积。

SQLSELECT D.n AS theday, S.empid AS shiftno  
FROM dbo.Nums AS D
  cross JOIN [HR].[Employees] AS S
WHERE D.n <= 7
  AND S.empid <= 3
ORDER BY theday, shiftno;

但是CROSS JOIN不能用ON条件,只能用WHERE条件。下面这句与上面的语句查询结果相同。

SQLSELECT D.n AS theday, S.empid AS shiftno  
FROM dbo.Nums AS D
  inner JOIN [HR].[Employees] AS S
on D.n <= 7
  AND S.empid <= 3
ORDER BY theday, shiftno;

内联接查询,表A和表B中的数据必须紧密对应,不可以是Null。下面的查询中,Production.Products表中没有商品记录的的日本供货商不会被列出来。INNER这个关键词是可以舍去的,如果只写JOIN就表示INNER JOIN

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  INNER JOIN Production.Products AS P
    ON S.supplierid = P.supplierid
WHERE S.country = N'Japan';

外连接查询有三种情况:左外连接,右外连接,全外连接。
下面这个查询与上面这个查询写法只差一点点(WHERE变成了AND),但是结果就有区别:

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  INNER JOIN Production.Products AS P
    ON S.supplierid = P.supplierid
    AND S.country = N'Japan';

而且Production.Products表中没有商品记录的的日本供货商同样也会被列出来,但是相关的P.productid, P.productname, P.unitprice都会被记为NULL。
下面这句:

SQLSELECT E.empid,
  E.firstname + N' ' + E.lastname AS emp,
  M.firstname + N' ' + M.lastname AS mgr
FROM HR.Employees AS E
  INNER JOIN HR.Employees AS M
    ON E.mgrid = M.empid;

用了内联接,则最高主管(CEO)不会被列出来,因为最高主管没有更高的主管了。
而改用左外连接

SQLSELECT E.empid,
  E.firstname + N' ' + E.lastname AS emp,
  M.firstname + N' ' + M.lastname AS mgr
FROM HR.Employees AS E
  LEFT OUTER JOIN HR.Employees AS M
    ON E.mgrid = M.empid;

则CEO也会被列出来,CEO对应的mgr会被记为NULL。
套用内联接的左外连接:

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice,
  C.categoryname
FROM Production.Suppliers AS S
  LEFT OUTER JOIN Production.Products AS P
    ON S.supplierid = P.supplierid
  INNER JOIN Production.Categories AS C
    ON C.categoryid = P.categoryid
WHERE S.country = N'Japan';

查询出日本供货商的所有的产品以及产品类别名。而且Production.Products表中没有商品记录的的日本供货商同样也会被列出来,但是相关的P.productid, P.productname, P.unitprice, C.categoryname都会被记为NULL。
上面的语句与下面带括号的语句等同:

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice,
  C.categoryname
FROM Production.Suppliers AS S
  LEFT OUTER JOIN 
    (Production.Products AS P
       INNER JOIN Production.Categories AS C
         ON C.categoryid = P.categoryid)
    ON S.supplierid = P.supplierid
WHERE S.country = N'Japan';

RIGHT OUTER JOIN则与LEFT OUTER JOIN相反,根据ON条件和WHERE条件查询表A和表B,查询结果可以表A中数据为NULL。
FULL OUTER JOIN则只要表A和表B中任一表中有数据,结果都会被显示出来。无论是表A为NULL,还是表B为NULL。
OUTER也是可以被省略的。LEFT JOIN就是LEFT OUTER JOIN的简写,相应的,RIGHT JOINRIGHT OUTER JOIN的简写,FULL JOINFULL OUTER JOIN的简写。

2015年五月2日下午 2:22:12 T-SQL学习中--取得部分检索数据记录

SELECT TOP(n) FROM _TableName_ ORDER BY _ColumnName_是一种非标准SQL语句,从数据表中最多检索出排在前面的n条数据来,但是它可以用SELECT TOP(n) PERCENT FROM _TABLENAME_ ORDER BY 这样的根据总数据量来按比例取得数据记录。
如果数据表中有560条数据,检索SELECT TOP(1) FROM _TableName_ ORDER BY _ColumnName_就会检索出6条数据来,总而言之,不是按四舍五入计的,而是按ceil向上取整法计数的。
如果不加ORDER BY,数据会以不确定的顺序检索出来。
这里括号可有可无,但是建议加括号。
n可以是常数,也可以是定义的变量。下面这种写法也是可以的:

SQLDECLARE @n AS BIGINT = 5;
SELECT TOP (@n) orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC;
GO

如果加了WITH TIE,比如说写成

SQLSELECT TOP (3) WITH TIES orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC;

orderdate相同的数据会被计作一条数据,总检索出的结果可能不止3条。

OFFSET FETCH语句是标准SQL语句。但是它有局限性,不能按百分比检索出数据结果。

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY;

表示跳过前50条数据,取得第51到第75条数据。

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 0 ROWS FETCH FIRST 25 ROWS ONLY;

表示取得第1到第25条数据。

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 50 ROWS;

表示跳过前50条数据,取得之后的全部数据。
OFFSET FETCH语句必须带有ORDER BY语句,但是如果不想指定用于排序的columnName,可以用下面这种这种语法,即用SELECT NULL作为排序列:

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY (SELECT NULL)
OFFSET 0 ROWS FETCH FIRST 3 ROWS ONLY;

OFFSET FETCH可用于分页检索,比如说下面这种写法:

SQLDECLARE @pagesize AS BIGINT = 25, @pagenum AS BIGINT = 3;

SELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET (@pagenum - 1) * @pagesize ROWS FETCH NEXT @pagesize ROWS ONLY;
2015年五月2日上午 11:09:04 GitLab 安装配置笔记

GitLab的安装方式

GitLab的两种安装方法:

由于公司只配备了一台阿里云服务器,并且没有分配任何的域名。该服务器上需要运行版本控制软件、bug管理软件、知识库等多套程序,只能采用ip的方式访问。原先采用GitLab+Apache+MySQL编译安装的方式,并且将GitLab配置为可通过xxx.xx.xxx.xx/gitlab的形式访问,由于bug管理软件(禅道)也运行于Apache之上,两套软件之间彼此有互斥的影响,找不到解决方法。同时,GitLab的注册需要邮箱验证,由于网上提供的配置方法都是基于域名的,在阿里云上多次进行配置都无法正常使用。

因此,只能放弃编译安装的方式,而采取rpm包的方式重新进行安装。

安装GitLab CE Omnibus包

  1. 在linux终端下,使用cat /etc/issue命令查询当前系统的发行版本,查询到阿里云所安装的linux版本为CentOS release 6.6 (Final)。

  2. 进入gitlab官方网站,选择对应的操作系统——CentOS 6 (and RedHat/Oracle/Scientific Linux 6),按照官方的提示进行安装:

    1. 安装配置必要的依赖

      在Centos 6 和 7 中,以下的命令将会打开HTTP和SSH在系统防火墙中的可访问权限。

      bashsudo yum install openssh-server
      
      sudo yum install postfix
      
      sudo yum install cronie
      
      sudo service postfix start
      
      sudo chkconfig postfix on
      
      sudo lokkit -s http -s ssh
      
      
    2. 下载Omnibus package包并安装

      bashcurl -O https://downloads-packages.s3.amazonaws.com/centos-6.6/gitlab-ce-7.10.0~omnibus.2-1.x86_64.rpm
      sudo rpm -i gitlab-ce-7.10.0~omnibus.2-1.x86_64.rpm
      
      Note:由于amazonaws的服务器被墙,下载这个包时可能需要翻墙下载。
      
    3. 配置并启动GitLab
      打开/etc/gitlab/gitlab.rb,将external_url = 'http://git.example.com'修改为自己的IP地址:http://xxx.xx.xxx.xx,,然后执行下面的命令,对GitLab进行编译。

      bashsudo gitlab-ctl reconfigure
      
    4. 登录GitLab

      Username: root 
      Password: 5iveL!fe
      

配置GitLab的默认发信邮箱

  1. GitLab中使用postfix进行邮件发送。因此,可以卸载系统中自带的sendmail
    使用yum list installed查看系统中是否存在sendmail,若存在,则使用yum remove sendmail指令进行卸载。
  2. 测试系统是否可以正常发送邮件。

    bashecho "Test mail from postfix" | mail -s "Test Postfix" xxx@xxx.com
    
    注:上面的xxx@xxx.com为你希望收到邮件的邮箱地址。
    

    当邮箱收到系统发送来的邮件时,将系统的地址复制下来,如:root@iZ23syflhhzZ.localdomain,打开/etc/gitlab/gitlab.rb,将

    # gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' 
    

    修改为

    gitlab_rails['gitlab_email_from'] = 'root@iZ23syflhhzZ.localdomain' 
    

    保存后,执行sudo gitlab-ctl reconfigure重新编译GitLab。如果邮箱的过滤功能较强,请添加系统的发件地址到邮箱的白名单中,防止邮件被过滤。

    Note:系统中邮件发送的日志可通过`tail /var/log/maillog`命令进行查看。
    

安装过程中出现的问题

  1. 在浏览器中访问GitLab出现502错误

    原因:内存不足。

    解决办法:检查系统的虚拟内存是否随机启动了,如果系统无虚拟内存,则增加虚拟内存,再重新启动系统。

  2. 80端口冲突

    原因:Nginx默认使用了80端口。

    解决办法:为了使Nginx与Apache能够共存,并且为了简化GitLab的URL地址,Nginx端口保持不变,修改Apache的端口为4040。这样就可以直接用使用ip访问Gitlab。而禅道则可以使用4040端口进行访问,像这样:xxx.xx.xxx.xx:4040/zentao。具体修改的地方在/etc/httpd/conf/httpd.conf这个文件中,找到Listen 80这一句并将之注释掉,在底下添加一句Listen 4040,保存后执行service httpd restart重启apache服务即可。

    #Listen 80 
    Listen 4040 
    
  3. 8080端口冲突

    原因:由于unicorn默认使用的是8080端口。

    解决办法:打开/etc/gitlab/gitlab.rb,打开# unicorn['port'] = 8080的注释,将8080修改为9090,保存后运行sudo gitlab-ctl reconfigure即可。

  4. STMP设置

    配置无效,暂时不知道原因。

  5. GitLab头像无法正常显示
    原因:gravatar被墙
    解决办法:
    编辑 /etc/gitlab/gitlab.rb,将

    #gitlab_rails['gravatar_plain_url'] = 'http://gravatar.duoshuo.com/avatar/%{hash}?s=%{size}&d=identicon'
    

    修改为:

    gitlab_rails['gravatar_plain_url'] = 'http://gravatar.duoshuo.com/avatar/%{hash}?s=%{size}&d=identicon'
    

    然后在命令行执行:

    bashsudo gitlab-ctl reconfigure 
    sudo gitlab-rake cache:clear RAILS_ENV=production
    

参考资料

GitLab 6.1 使用postfix发送email

Configure GitLab Omnibus installation alongside with Apache

解决Gitlab的Gravatar头像无法显示的问题

How To Set Up GitLab As Your Very Own Private GitHub Clone

2015年五月1日晚上 8:12:21 PHP 5.3 连接 Oracle 的客户端及 PDO_OCI 模块安装

php连接oracle数据库虽然不是最佳拍档,但组内开发确实有这样需求。如果没有参考合适的文档,这个过程还是挺折磨人的,下面是一个记录,原型是国外的一篇博客 Installing PDO_OCI and OCI8 PHP extensions on CentOS 6.4 64bit

假设你已经安装好php的环境,php版本为5.3,要连接的oracle服务器是 11g R2,操作系统版本CentOS 6.4 x86_64。如果没有安装php,可以通过以下命令安装:

# yum install php php-pdo
# yum install php-devel php-pear php-fpm php-gd php-ldap \
php-mbstring php-xml php-xmlrpc  php- zlib zlib-devel bc libaio glibc

假如web服务器使用apache。

1. 安装InstantClient

instantclient是oracle的连接数据库的简单客户端,不用安装一个500Moracle客户端就可以连接oracle数据库,有windows和linux版本。从 这里 选择需要的版本下载,只需Basic和Devel两个rpm包。

安装
# rpm -ivh oracle-instantclient11.2-basic-11.2.0.4.0-1.x86_64.rpm
# rpm -ivh oracle-instantclient11.2-devel-11.2.0.4.0-1.x86_64.rpm

软链接
# ln -s /usr/include/oracle/11.2/client64 /usr/include/oracle/11.2/client
# ln -s /usr/lib/oracle/11.2/client64 /usr/lib/oracle/11.2/client

64位系统需要创建32位的软链接,这里可能是一个遗留bug,不然后面编译会出问题。

接下来还要让系统能够找到oracle客户端的库文件,修改LD_LIBRARY_PATH:

# vi /etc/profile.d/oracle.sh
export ORACLE_HOME=/usr/lib/oracle/11.2/client64
export LD_LIBRARY_PATH=$ORACLE_HOME/lib

执行source /etc/profile.d/oracle.sh使环境变量生效。

2. 安装PDO_OCI

在连接互联网的情况下,通过pecl在线安装php的扩展非常简单,参考 How to install oracle instantclient and pdo_oci on ubuntu machine

https://pecl.php.net/package/PDO_OCI下载 PDO_OCI-1.0.tgz 源文件。

# wget https://pecl.php.net/get/PDO_OCI-1.0.tgz
# tar -xvf PDO_OCI-1.0.tgz
# cd PDO_OCI-1.0

由于PDO_OCI很久没有更新,所以下面需要编辑ODI_OCI-1.0文件夹里的config.m4文件来让它支持11g:

# 在第10行左右找到与下面类似的代码,添加这两行:
elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.11.2; then
  PDO_OCI_VERSION=11.2

# 在第101行左右添加这几行:
11.2)
  PHP_ADD_LIBRARY(clntsh, 1, PDO_OCI_SHARED_LIBADD)
  ;;

编译安装pdo_oci扩展:(安装完成后可在 /usr/lib64/php/modules/pdo_oci.so 找到这个模块)

$ phpize
$ ./configure --with-pdo-oci=instantclient,/usr,11.2
$ make
$ sudo make install

要启用这个扩展,在/etc/php.d/下新建一个pdo_oci.ini文件,内容:

extension=pdo_oci.so

验证安装成功:

# php -i|grep oci
看到类似下面的内容则安装成功:
/etc/php.d/pdo_oci.ini,
PDO drivers => oci, sqlite

或
# php -m

3. 安装OCI8

https://pecl.php.net/package/oci8 下载oci8-2.0.8.tgz源文件。

# wget https://pecl.php.net/get/oci8-2.0.8.tgz
# tar -xvf oci8-2.0.8.tgz
# cd oci8-2.0.8

编译安装oci8扩展:

# phpize
# ./configure --with-oci8=shared,instantclient,/usr/lib/oracle/11.2/client64/lib
# make
# make install

要启用这个扩展,在/etc/php.d/下新建一个oci8.ini文件,内容:

extension=oci8.so

验证安装成功:

# php -i|grep oci8
/etc/php.d/oci8.ini,
oci8
oci8.connection_class => no value => no value
oci8.default_prefetch => 100 => 100
oci8.events => Off => Off
oci8.max_persistent => -1 => -1
oci8.old_oci_close_semantics => Off => Off
oci8.persistent_timeout => -1 => -1
oci8.ping_interval => 60 => 60
oci8.privileged_connect => Off => Off
oci8.statement_cache_size => 20 => 20
OLDPWD => /usr/local/src/oci8-2.0.8
_SERVER["OLDPWD"] => /usr/local/src/oci8-2.0.8

最后别忘了重启逆web服务器如apache,可以通过phpinfo()来确保扩展是否成功安装。

4. 测试连接

在你web服务器如apache的php目录下创建testoci.php

<?php

$conn = oci_connect('username', 'password', '172.29.88.178/DBTEST');

$stid = oci_parse($conn, 'select table_name from user_tables');
oci_execute($stid);

echo "<table>\n";
while (($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) != false) {
    echo "<tr>\n";
    foreach ($row as $item) {
        echo "  <td>".($item !== null ? htmlentities($item, ENT_QUOTES) : "&nbsp;")."</td>\n";
    }
    echo "</tr>\n";
}
echo "</table>\n";

?>

访问这个页面就应该可以得到结果了。

参考


原文链接地址:http://seanlook.com/2015/03/10/install-pdo-oci-oci8-phpext/


2015年四月30日晚上 11:41:21 OpenWrt路由器开发

http://homeway.me

OpenWrt




0x01.About

第一次尝试开发路由器,发现并不是想象中那么难,和普通嵌入式开发一样,也是一块ARM板刷上Linux系统。

OpenWrt有很多好用的软件,附带流量监测。

OpenWrt主要开发语言为Python、Lua、Shell,还可以做深入研究写ipk软件包。

写了几个脚本,主要实现了openwrt下面GPIO控制、系统信息获取、wifi扫描器、定时发送邮件系统报警等功能,下面会介绍。

代码已经在Github开源: https://github.com/grasses/OpenWRT-Util



0x02.About OpenWrt

刷OpenWrt先要去https://downloads.openwrt.org/下载你想要的版本,包含aa型和bb型。

然后用Linux烧入命令烧入系统。

早MAC下面,先现将U盘插入电脑格式化,然后运行命令查看U盘编号:

diskUtil list

注意查看U盘编号,选择你的U盘,解除挂载:

diskUtil unmountDisk /dev/disk2

然后烧入系统:

dd if=/path/to/openwrt.img of=/dev/disk2 bs=2m

等待几分钟后烧入成功。

关于痛点:

第一次是在树莓派上安装OpenWrt,装好后,用有线把连进上级路由器的Lan口

然后,上级路由的包开始乱了,上级路由把OpenWrt当成路由器,OpenWrt把路由器当成上级路由器,然后就GG了。



0x03.About WRTnode

WRTnode是OpenWrt系统一个硬件解决方案,预先安装了OpenWrt相关软件包,并且内置两块无线网卡。

关于WRTnode,官方wiki已经介绍的很详细了:http://wiki.wrtnode.com/index.php?title=Main_Page/zh-cn

解析来的代码基本上是在WRTnode环境上开发的,主要包含了:

目前只能想起这3个,如果报错,该装什么再装好了。



0x04.WRTnode控制GPIO

GPIO控制可以很好地实现软件硬件之间的交互。

WRTnode GPIO

GPIO的控制也不难,wiki讲得很清晰了,就是文件输入输出http://wiki.wrtnode.com/index.php?title=The_user_space_gpio_calls/zh-c...

这里我写了一个Lua版的GPIO控制模块,文件保存为gpio.lua:

#!/usr/bin/lua
--[[
Copyright 2015 http://homeway.me
@author homeway
@version 15.04.29
@link http://homeway.me
@function OpenWRT gpio module
-- ]]--

local M = {}
M.id = ""
M.path = "/sys/class/gpio/gpio"
M.router = "/sys/class/gpio"

M.check = function(where)
    print("check path => "..where)
    local f=io.open(where, "r")
    if f~=nil then io.close(f) return true else return false end
end
-- set mode && check type
M.mode = function(id, mtype)
    M.id = id
    where = M.path..M.id
    -- if id not use
    if false==M.check(M.path..id..'/direction') then
        --M.writeFile(M.router.."/unexport",id)
        M.writeFile(M.router.."/export", id)
    end
    -- if type different 
    if mtype ~= M.readFile(M.path..id..'/direction') then
        print("type =>"..mtype.." direction=>"..M.readFile(M.path..id..'/direction').." different")
        M.writeFile(M.path..id..'/direction', mtype)
    end
end
-- file write
M.writeFile = function(where, what)
    print("write path => "..where.." data =>"..what)
    local fp=io.open(where, 'w')
    fp:write(what)
    fp:close()  
end
-- file read
M.readFile = function(where)
    print("read path => "..where)
    local fp=io.open(where, 'r')
    if fp~=nil then
        data = fp:read("*all")
        fp:close()
        return data
    end
    return nil
end
M.set = function(id)
    M.id = id
end
M.read = function()
    res = M.readFile(M.path..M.id..'/value')
    return res
end
M.write = function(value)
    res = M.writeFile(M.path..M.id..'/value', value)
end
M.close = function()
    print("sleep io => "..M.id)
    os.execute("sleep " .. tonumber(M.id))
end

return M

API很简单,先设置设置模式,GPIO.mode(id, "out/in")两种模式之一

如果为'out'即可调用GPIO.write(value)函数,写入当然id端口,如果为'in'模式,只能调用GPIO.read()读取数值。

这里数值只能是0或1,非0即为1.

调用方式如下,这个存在一个可忽略的问题,一旦调用mode,数值将被置为默认数值,即0:

#!/usr/bin/lua
x=require("gpio")
print("Please input io id =>")
id = io.read("*num")
x.mode(id, "out")-- 设置io的模式为输入还是输出 [in/out]
function readGPIO(id)
    value = x.read()
    print("read data from => `"..id.."` =>"..value)
end
function writeGPIO(id, data)
    x.write(data)
    print("write data to => `"..id.."` =>"..data)
end

count=1
repeat
    count=count+1
    print("Please input value =>")
    data = io.read("*num")
    writeGPIO(id, data)
    readGPIO(id)
until count>3



0x05.WRTnode获取系统信息

其实获取系统信息不属于WRTnode范围,因为这部分主要是调用Linux Shell获取系统信息,做个反馈。

这里我也写了个python脚本,主要检查系统信息,这个脚本在树莓派那里面也有:http://homeway.me/2014/10/09/raspberry-the-current-status-and-data/

这里我做了部分修改,添加系统ip、连接的ssid等信息:

#!/usr/bin/python
'''
    @author homeway
    @version 15.04.29
    @link http://homeway.me
    @function python get OpenWRT system info
'''
import os
# Return CPU temperature as a character string                                     
def getCPUtemperature():
    res = os.popen('vcgencmd measure_temp').readline()
    return(res.replace("temp=","").replace("'C\n",""))
# Return RAM information (unit=kb) in a list                                      
# Index 0: total RAM                                                              
# Index 1: used RAM                                                                
# Index 2: free RAM                                                                
def getRAMinfo():
    p = os.popen('free')
    i = 0
    while 1:
        i = i + 1
        line = p.readline()
        if i==2:
            return(line.split()[1:4])
# Return % of CPU used by user as a character string                               
def getCPUuse():
    return(str(os.popen("top -n1 | awk '/Cpu\(s\):/ {print $2}'").readline().strip()))

# Return information about disk space as a list (unit included)                    
# Index 0: total disk space                                                        
# Index 1: used disk space                                                        
# Index 2: remaining disk space                                                    
# Index 3: percentage of disk used                                                 
def getDiskSpace():
    p = os.popen("df -h /")
    i = 0
    while 1:
        i = i +1
        line = p.readline()
        if i==2:
            return(line.split()[1:5])
def getSystem():
    p = os.popen("uname -amnrspv")
    while 1:
        line = p.readline()
        return(line)
def getExtranetIp():
    p = os.popen('wget "http://www.ip138.com/ips1388.asp" -q -O - | sed -nr \'s/.*\[(([0-9]+\.){3}[0-9]+)\].*/\1/p\'')
    while 1:
        line = p.readline()
        print line
        return(line)
def getIntranetIp():
    p = os.popen('ifconfig apcli0 | grep inet\ addr')
    while 1:
        line = p.readline()
        return(line)
def getSsid():
    p = os.popen('uci get wireless.@wifi-iface[0].ApCliSsid')
    while 1:
        line = p.readline()
        return(line)
# CPU informatiom
CPU_temp = getCPUtemperature()
CPU_usage = getCPUuse()
# RAM information
# Output is in kb, here I convert it in Mb for readability
RAM_stats = getRAMinfo()
RAM_total = round(int(RAM_stats[0]) / 1000,1)
RAM_used = round(int(RAM_stats[1]) / 1000,1)
RAM_free = round(int(RAM_stats[2]) / 1000,1)
# Disk information
DISK_stats = getDiskSpace()
DISK_total = DISK_stats[0]
DISK_used = DISK_stats[1]
DISK_perc = DISK_stats[3]
# system info
SYSTEM_info = getSystem()
# NET infomation
NET_extranet_ip = getExtranetIp()
NET_internet_ip = getIntranetIp().lstrip('')
NET_connect_ssid = getSsid()

if __name__ == '__main__':
    print('-------------------------------------------')
    print("System info ="+str(SYSTEM_info))
    print('-------------------------------------------')
    print('RAM Total = '+str(RAM_total)+' MB')
    print('RAM Used = '+str(RAM_used)+' MB')
    print('RAM Free = '+str(RAM_free)+' MB')
    print('-------------------------------------------')
    print('DISK Total Space = '+str(DISK_total)+'B')
    print('DISK Used Space = '+str(DISK_used)+'B')
    print('DISK Used Percentage = '+str(DISK_perc))
    print('-------------------------------------------')
    print('NET Extranet Ip ='+str(NET_extranet_ip))
    print('NET Connect Ssid ='+str(NET_connect_ssid))
    print('NET Internet Wan Ip ='+str(NET_internet_ip))

直接调用python sysinfo.py:

系统信息



0x06.WRTnode发送邮件

好了,系统信息有了,GPIO信息有了,接下来就试试发送邮件了。

发送邮件3中法案都可以,Lua,Python,Shell,找了找资料,Python写了,但是缺少了一个包,Lua缺少Luasocket模块,Shell要安装模块。

最后,懵了,全都要依赖,尼玛,看了看,好像Lua安装个Luasocket最简单,一个包轻松: http://see.sl088.com/wiki/Luasocket

安装也不难,接下来就写写吧。

Lua发送邮件源码模块,设置文件名为email.lua

#!/usr/bin/lua
--[[
Copyright 2015 http://homeway.me
@author homeway
@version 15.04.29
@link http://homeway.me
@function lua email module
-- ]]--
local smtp = require("socket.smtp")
local M ={}
M.user = {["from"]="", ["to"]="", ["password"]=""}
M.mail = {["subject"]="", ["body"]=""}
M.sys = {["server"]=""}
M.set = function(data)
    M.user = data.user
    M.mail = data.mail
    M.sys = data.sys    
end
M.send = function()
    rcpt = {
        M.user["to"]
    }
    mesgt = {
        headers = {
            from = M.user["from"],
            to = M.user["to"], --收件人
            cc = "", --抄送 
            subject = M.mail["subject"] --主题
        },
        body = M.mail["body"]
    }
    r, e = smtp.send{
        from = M.user["from"],
        rcpt = rcpt,
        source = smtp.message(mesgt),
        server = M.sys["server"],
        port = M.sys["port"],
        user = M.user["from"],
        password = M.user["password"],
    }
    if not r then
        print(e)
    else
        print("send ok!")
    end
end
return M

下面是调用方式:

#!/usr/bin/lua
local mail = require("email")
local data = {}
data.user = {["from"]="sender@gmail.com", ["to"]="receiver@gmail.com", ["password"]="password"}
data.mail = {["subject"]="测试邮件模块", ["body"]="这是主体内容..."}
data.sys = {["server"]="smtp.gmail.com", ["port"]=587}

mail.set(data)
mail.send()

测试下,是可以接收到邮件的,注意GFW,还是别用非法gmail好了,别等半天收不到。



0x07.重要的东西放后面

嗯!看到这里,估计菊花也有点疼了,再看最后一点看完就擦洗擦洗去吧。

最后就是,设置定时器,让路由器定时发送系统信息给指定邮箱。

嗯...定时器,Linux的一个模块crontab命令,看看功能吧 crontab --help

关于定时器语法,看看这里吧 http://kvz.io/blog/2007/07/29/schedule-tasks-on-linux-using-crontab/

这里,我只做简单地,每隔10分钟发送一次系统信息给我邮箱。

具体怎么做,去下载这个脚本吧:https://github.com/grasses/OpenWRT-Util/blob/master/lua/crontab.lua

我的目录是这样的,用户是root:

~|--script
    |--schedule
    |--send
|--log
    |--sys.log
    |--crontab.log

先开一个定时器,定时跑Lua,Lua调用python读取系统信息,生成日志文件,Lua读取日志文件,发送邮箱。

how to use:
step1: configure you email information in this script
step2: mkdir /root/log && mkdir /root/script
step3: mv /path/to/crontab.lua /root/script/send
step4: chmod +x /root/script/send
step5: echo 10,20,30,40,50 * * * * /root/script/send > /root/script/schedule
step6: crontab /root/script/schedule

东西有点多,都是散乱的部件,这篇主要介绍细节信息,接下来会做大得模块。

如果打通路由器,各种嵌入式开发的联网问题就都解决了,所以路由器系统还是很重要的。




本文出自 夏日小草,转载请注明出处: http://homeway.me/2015/04/29/openwrt-develop-base-util/

by 小草

2015-04-30 23:59:20

2015年四月30日晚上 9:56:57 VSCode 初体验

Microsoft 今天在其 Build 开发者大会上正式宣布了 Visual Studio Code 项目:一个运行于 OS X,Windows 和 Linux 之上的,针对于编写现代web和云应用的跨平台编辑器。

作为编辑器控的我,得知消息后立马下载体验了一下。Windows上优秀的编辑器实在太多了,Sublime TextEditPlusNotepad++......还有诸如国产的EverEdit等后起之秀。所以这次我这次把测评的环境放在了编辑器相对匮乏的Linux桌面上。

环境&安装

主要对比对象是Sublime Text3

    wget http://download.microsoft.com/download/0/D/5/0D57186C-834B-463A-AECB-BC55A8E466AE/VSCode-linux-x64.zip

    //注意不要使用归档解压会报错
    unzip  unzip VSCode-linux-x64 -d VS

    //双击VS里的Code就能运行了

颜值

VSCode

可以看到VSCode颜值不算太糟糕,绿色的注释散发着一股浓浓的VS的风格,Theme里一共两款主题可以选择,另外一款是白色主题。题外话,我最喜欢的主题是Sublime Text的Monokai

性能

总体来说输入的体验比Sublime Text3稍微要差一点,但是比同类WEB IDE ATOMBrackets要快太多,ATOM、Brackets已经迭代很多个版本了,VSCode基于ATOM SHELL的,估计ATOM要哭晕在厕所。看到一些网友的测试,在打开大文件上,VSCode已经秒杀了Sublime Text3

特性

智能提示

VSCode提供了强大的自动补全、悬浮提示、定义跳转等功能,支持以下语言:

C++, jade, PHP, Python, XML, Batch, F#, DockerFile, Coffee Script, Java, HandleBars, R,Objective-C, PowerShell, Luna, Visual Basic, Markdown

我测试了下在Javascript、Typscript上体验不错,HTML还支持Angular标签,悬浮提示很详细包括了注解,但是试了下C#貌似没有什么效果,不知道是不是需要特殊的环境。不管怎样,在某些语言上的智能提示已经比其他的同类编辑器已经强太多了,可以和一些IDE媲美。

enter image description here

下面贴几张官网的示例图片:

参数提示:
enter image description here

定义跳转:
enter image description here

引用提示:
enter image description here

方法定位:
enter image description here

还有其他很酷炫的功能我没测试,大家官网看吧。

Markdown

在Linux桌面上,好用的Markdown编辑器可以说没有,ReText和记事本一样简陋,Sublime Text3虽然可以装插件支持,但是体验不是很好,不支持中文。因此我一直使用的在线Markdown代替。

这回VSCode支持Markdown重新让我看到了点希望。快捷键ctr+shift+v预览,可以看到这个布局还是非常人性化的。

enter image description here

但是缺点也很明显,首先中文支持不好,编辑器里的中文输入可以改,但是预览还是出现口口,目前找到解决方法。还有不支持快捷键输入,那种像写代码般的快感没有了。没有能自定义CSS的功能,不管在哪种Theme下,> 代码高亮都看不出有什么效果。

版本控制

自带了一个git工具,并且放在了一个比较显要的位置上,不过功能不是很全,只能commit等几个操作。自带了类似于git diff的文件比较功能:

enter image description here

Debug

Debug需要MONO,所以就没进行测试。详情大家看官网吧。

缺陷

中文支持

默认的字体是不支持中文的,输入中文的时候会出现口口。需要设置一下字体,我使用的是文泉驿,思源也行。

没安装的首先安装这个字体。

sudo apt-get install fonts-wqy-microhei fonts-wqy-zenhei
File -> Preference -> User Settings
//在右侧添加一句:
"editor.fontFamily": "WenQuanYi Micro Hei Mono"

不过这只能解决编辑器内的中文乱码问题,其他的比如标题栏,markdown预览,该口的还是口。对了还有一点需要注意的是输入法需要是Fctix或者基于Fctix的。

Sublime Text3同样有这问题,事实上Sublime Text3全平台对于中文的支持都不是很好。Linux桌面上的解决方法也是奇技淫巧

插件化

不过插件化已经提到议程上了,以微软的实力实现这个不难。

Markdown

缺陷在上面已经提到了

设置

用户设置是直接以JSON形式出现了,虽然说鼠标悬浮上去会看到详细的解释,但还是没有图形化来的简便,而且没有搜索的功能,想要搜索还得以文本的形式复制出来,修改起来略费劲。

结论

总体而言,VSCode表现出来的潜力还是不俗的,毕竟还是个预览版,我对接下来的版本比较看好,至少比Brackets要好吧。希望Sublime Text的作者能够更加上心一点,能解决中文问题那就最好了,喜欢Sublime Text3的童鞋们可以看我这篇博文《我的Sublime Text3设置》

最后,人生苦短,我用geany

参考

https://code.visualstudio.com/Docs
http://www.zhihu.com/question/29984607

2015年四月30日下午 3:54:11 45个必备的JavaScript Web开发工具

JavaScript是一种灵活多变的脚本语言,它在全世界被广泛的应用在Web页面和服务器应用中。你可以因为任何目的去使用它,但是你需要一些工具。幸运的是,为了完成独特的任务,无以计数的JavaScript工具已经被开发者发布。

这里有45个关于JavaScript的工具,所有这些工具将帮助您创建现代网站与用户所期望的所有特性。它们都提供了精简的设计和简单的接口。。。。

AngularJS

AngularJS
Google创建AngularJS,目的是提供一个稳定的、轻量级的框架在浏览器中呈现信息。它从服务器收集数据,然后在本地编译模板。换句话说,AngularJS以MVC框架形式来构建在浏览器中运行的HTML、JavaScript和CSS。

Odyssey.JS

Odyssey.JS
Odyssey 是一个将故事和地图结合,并绑定了交互文本的工具。图片显示为一个沙箱来构建与地图交互的故事。

PlayCanvas

PlayCanvas
PlayCanvas是一个围绕WebGL建立的游戏引擎。它把物理、照明、阴影、音频和更多其它特效结合到更一致的工具中,以创建被对象填充的世界。图像显示的是一个针对该框架的在线开发工具。

Gantt

Gantt
Gantt是一个基于JQuery构建的JavaScript组件,用于创建图标,任务树和用JSON格式输出结果数据的相关性。它提供了编辑、缩放、数据快捷键,CSS皮肤,等等。

Handy.JS

Handy.JS
Handy是一个Nodejs的Web应用模板。Handy提供了一个Web APP所有的基础功能,因此你可以把焦点放在开发让你的APP真正唯一的功能。

RegExr

RegExr
RegExr是一个在线编辑和测试正则表达式的工具。它提供了一个简单的正则表达式输入界面,并且能实时可视化匹配可编辑的源文本。同时它还提供了一个便捷的RegExp边栏用于描述案例用法。

TimelineJS

TimelineJS
TimelineJS是一个开源工具,允许任何人建立形象精美的时间轴。初学者可以可以不使用任何东西就能创建一个时间轴。

Responsive Nav

Responsive Nav
Responsive Nav是一个比较小的JavaScript插件,可以帮助你创建针对小屏幕的连续导航。它会利用touch事件和CSS3过渡带来最好的性能。

Sinon.JS

Sinon.JS
Sinon.JS是一个单独的测试应用,没有依赖关系,适用于任何单元测试框架。

Mocha

Mocha
Mocha是一个运行在Nodejs和浏览器上的功能多样的JavaScript测试框架,使异步测试变得简单有趣。

JS Bin

JS Bin
JS Bin是一个专门设计用于帮助JavaScript和CSS民间测试的代码片段,在某些上下文中,协作和调试代码的应用。jsbin允许编辑和测试JavaScript和HTML。

JSLitmus

JSLitmus
JSLitmus,一个轻量级框架,用于创建特别的JavaScript基准测试。

Bookmarkify

Bookmarkify
Bookmarkify使得创建书签工具变得非常简单,仅需要给书签命名,然后输入JavaScript并包含它就可以了。

Kreate.JS

Kreate.JS
Kreate.JS能够辅助JQuery快速以JQuery对象形式生成DOM元素。你可以“Kreate” 单个元素或者“Kreate”多个元素,直到浏览器奔溃。但多数情况下,Kreate创建单个元素或者多个元素都会比JQuery快。

YUI Compressor

YUI Compressor
YUI Compressor是用Java创建的命令行工具,用于压缩JavaScript文件。YUI Compressor是100%安全的,并且比其他工具的压缩比高。它也能压缩CSS文件。

Google Closure Compiler

Google Closure Compiler
Google Closure Compiler能使JavaScript的下载和运行变得更快。它是一个真正针对JavaScript编译的。Google Closure Compiler不是将源语言编译成机器代码,而是从JavaScript编译到更好的JavaScript。

JSMin

JSMin
JSMin会删除JavaScript文件中的注释和不必要的空白。它将减少文件一半的尺寸,带来更快的下载速度。它也鼓励更富有表现力的编程风格,因为它消除了下载在精简代码、自文档化方面的成本。

Packer

Packer
Packer是DeanEdwards创建的一个很流行的JavaScript压缩工具,它能自动创建一个压缩版本。只需要粘贴代码,然后点击 ‘Pack’ 按钮。它还能利用JavaScript运行时片进行超常规压缩和动态压缩。

Meteor

Meteor
MeteorWebApp框架为现代软件开发提供了一个坚实的基础。一些是很实用的,例如拥抱开源社区,促进插件的贡献。Meteor做到了。

Epoch

Epoch
Epoch是一个实时的、用于创建漂亮、平稳流畅和高性能可视化的图表库。
Web Starter Kit
Web Starter Kit
Web Starter Kit是一个致力于协助开发者支持多设备的项目。这意味着通过同步点击、必要时重新加载和保持一切尽可能精简来确保屏幕保持同步。

Reveal.JS

Reveal.JS
Reveal.JS是一个基于HTML5的、很灵活的组件,用于替代PPT。点击按钮,然后复杂的动画会依赖碎片信息而翻转,就跟PPT一样。但是它真正的表现力在与你如何你在你的网络策略中使用它。

RxJS

RxJS
RxJS是一个为鼠标和键盘添加平滑、反应性的和异步响应生成的事件流。图像显示代码绑定了一个搜索维基百科的事件。

NodeBB

NodeBB
基于节点演化的公告板隐喻是及时和可定制的,并提供实时流的对话。NodeBB的发展已经添加了更多现代主题,并支持小屏幕的手机和平板。

Gulp.JS

Gulp.JS
Gulp.JS是一个流构建系统。它使用流和代码配置创建更简单和直观的构建。宁愿选择代码配置,让简单的事情变得简单,使复杂的任务易于管理。

Contour

Contour
Contour是Forio的一个可视化库,用于提供一组核心的公共可视化功能。建立在受欢迎的D3引擎之上,轮廓让你轻松创建数据可视化和基于常用的图表等直观的抽象。

Nightwatch.JS

Nightwatch.JS
对基于浏览器的APP和网站,Nightwatch.JS能使用Node.js建立基于端到端的测试解决方案。它使用强大的Selenium WebDriver API在DOM元素上执行命令和断言。

EasyStar.JS

EasyStar.JS
EasyStar.JS是一个用JavaScript编写的异步A*寻路API,可应用在HTML5游戏和互动项目。这个项目的目标是使它容易和快速实现性能意识上的寻路。

Headroom.JS

Headroom.JS
Headroom.JS是一个轻量级、高性能javascript小部件,允许你对用户的滚动做出反应。这个网站的头部就是一个实例,当向下滚动时,头部会滑出视窗,向上滚动时又滑入视窗。

FileAPI

FileAPI
FileAPI是一组处理所有跟文件相关的工作的组件库。它提供了许多功能,文件上传(单个/多个)、拖放支持、图像裁剪、大小调整、应用过滤器和获取文件信息等等。

Unminify

Unminify
Unminify对于格式化JavaScript、CSS和HTML代码是很有用的工具,并且会让代码变得易读和漂亮。

HarpJS

HarpJS
HarpJS是一个静态服务器,在没有任何配置的情况下,也为Jade, Markdown, EJS, Less, Stylus, Sass, CoffeeScript asHTML, CSS和JavaScript 提供服务。它支持爱心式的布局/部分模式,并能灵活的遍历文件系统元数据、全局对象和注入定制数据模板。

JSHint

JSHint
JSHint是一个社区驱动的工具,用于检测JavaScript中的语法错误和潜在的问题,并执行你的团队的编码惯例。

GruntJS

GruntJS
GruntJS是一个基于任务的命令行JavaScript项目构建工具。下面的预定义的任务,可以直接在你的项目中使用:连接文件、用JSHint验证文件、用UglifyJS压缩文件和用节点单元运行单元测试。

ZeptoBuilder

ZeptoBuilder
ZeptoBuilder是Zepto的一个在线版本,从列表中选取你想包含的文件,就能得到你自定义的构建了。

Gif.JS

Gif.JS
Gif.JS是一个能运行在你的浏览器中的JavaScript GIF编码器。

Favico.JS

Favico.JS
Favico.JS可以让你为你的图标添加动画徽章,图片,甚至视频,或者从图像、视频,甚至从访问者的摄像头获取的现场图片创建一个图标。

Chart.JS

Chart.JS
Chart.JS生成简单,干净,和基于HTML5的JavaScript图表。它用一种简单的方式,能在你的网站上自由的包含动画、交互式图形。

AdminJS

AdminJS
AdminJS是一个独立包含Ember.js的应用,它的两个主要文件是adminjs.js和 adminjs.css。两者都需要和Ember.js和EPF.一起被包含在页面中。

Sir Trevor

Sir Trevor
[]Sir Trevor](http://madebymany.github.io/sir-trevor-js/)是一个会完全重绘网页内容的工具:直观的编辑网页内容而不用假定任何关于它是如何重绘的事。

Instano.JS

Instano.JS
页面加载之后,Instano.JS允许你及时检测JavaScript是否可用。它修改了标准的标记以致于不管JavaScript什么时候被禁用,里面的消息都能被显示。

Resumable.JS

Resumable.JS
Resumable.JS是一个JavaScript库,通过HTML5 API提供了稳定可恢复的多文件上传功能。


英文原文:40+ essential JavaScript tools for the Web
译文出处:http://www.ido321.com/1543.html

2015年四月30日下午 2:35:43 CentOS 安装 Subversion

安装依赖

命令:yum install mod_dav_svn subversion

貌似只要安装mod_dav_svn时,就会把subversion和Apache安装上。

Subversion's Apache 配置

命令如下:

[root@lucifer ~] cd /etc/httpd/conf.d/
[root@lucifer ~] vim subversion.conf

# 有需要的话,请确定你删除这两行的注释
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so

# 加入下列内容来支持基本验证,并将 Apache 指向实际放置版本库的地方。
<Location /repos>
        DAV svn
        SVNPath /var/www/svn/repos
        AuthType Basic
        AuthName "Subversion repos"
        AuthUserFile /etc/svn-auth-conf
        Require valid-user
</Location>

上面的位置是 Apache 在 URL 上使用的。举个例说:http://yourmachine/repos 指向你所指定的 SVNPath。上面只是一个样例,因此请按你的首选放置东西。请确定你在完成编辑后存储文件。

然后我们须要创建你在上一步所指定的口令档。开始时你要利用 -cm 这个选项。它会创建文件并用 MD5 将口令加密。如果你需要加用户,请确定你只使用 -m 选项,而不包含初次创建时的 -c。

设置你的版本库

你接著要做的事情就是创建你用来提交及取出文件的版本库。利用 svn 所包含的工具,这是很容易的。

[root@lucifer ~] cd /var/www/ —— 或者你在上面所指定的路径
[root@lucifer ~] mkdir svn
[root@lucifer ~] cd svn
[root@lucifer ~] svnadmin create repos
[root@lucifer ~] chown -R apache.apache repos  (这步很重要)
[root@lucifer ~] service httpd restart

现在去测试你能否通过网页浏览器访问你的版本库:http://yourmahcine/repos 。你应该取得一个对话框询问用户名称及口令。若然是这样,请输入你的凭证,然后你应该看见一版 Revision 0:/ 的页面。这样的话,版本库的设置便大工告成了。如果你须要多个版本库,请参考上面连结内的文档。这里只示范如何设置一个版本库及开始应用它。话说回来,让我们就这样做。

参考

英文原文:http://wiki.centos.org/HowTos/Subversion
中文翻译:http://wiki.centos.org/zh/HowTos/Subversion
CentOS搭建Nginx+Subversion环境:http://www.opstool.com/article/282
CentOS Linux搭建SVN Server配置详解:http://www.ha97.com/4467.html

2015年四月30日下午 2:20:27 美国大数据创业公司总结

最近调研了一下美国的大数据创业公司,总结如下,如有疏漏,欢迎反馈指正(boyang798@gmail.com)。

公司 成立时间 技术亮点 IPO或者收购
hortonworks.com June, 2011 三大主要Hadoop平台提供商之一, 提供Windows平台Hadoop支持 IPO,Dec 11, 2014
cloudera.com October, 2008 三大主要Hadoop平台提供商之一, 用户基数最大的Hadoop平台
mapr.com July, 2009 三大主要Hadoop平台提供商之一, 实现自己的Linux文件系统来提升Hadoop速度
databricks.com September,2013 创立Apache Spark,提升Hadoop速度10倍,同时提供优于MapReduce的编程模型
datameer.com September, 2009 提供端到端(从数据收集到数据可视化)的一站式大数据分析平台
palantir.com January, 2004 自有技术,着重于非机构化数据深度分析,初期以政府客户为主,后扩展到银行和金融领域
splunk.com October, 2003 大规模机器数据(日志)收集,存储,可视化分析 IPO,Apr 19, 2012
vertica.com May, 2005 基于列存储的数据库技术,提升数据仓库查询速度,注重MPP(massively parallel processing),企业级Hadoop方案和SQL on Hadoop 被Hewlett-Packard收购,February 14, 2011
autonomy.com January, 1996 自有非Hadoop大数据技术,非主流技术,但是比较有特色 被Hewlett-Packard收购,August 18, 2011,但是被业界认为是HP的一个失败收购案例
teradata.com July, 1979 老牌传统数据仓库提供商,扩展业务到Hadoop平台 December 1991被NCR收购,之后又由NCR公司剥离,作为单独的上市公司,Oct 5, 2007
jaspersoft.com June, 2001 侧重于商务数据分析报表,提供移动端的报表工具 被TIBCO Software收购,April 28, 2014
karmasphere.com April, 2010 基于Hadoop的解决方案和数据可视化分析 被FICO收购,April 2014
domo.com October, 2010 提供数据分析云服务平台
talend.com September, 2005 提供多种数据集成服务
qubole.com December, 2014 提供Hadoop云平台服务
treasuredata.com December, 2011 提供大数据存储,查询,分析云服务
platfora.com June, 2011 端到端一站式大数据平台解决方案,基于Hadoop和Spark
interana.com January, 2013 自服务数据分析平台,侧重于面向事件的数据
gridgain.com May, 2005 基于内存的大数据实时处理系统
metamarkets.com May, 2010 在线广告领域内数据实时处理分析平台
pivotal.io April, 2013 大数据集成产品,提供Hadoop,内存Non-SQL数据库,RabbitMQ,以及Greenplum MPP(massively parallel processing)等多种服务
fiscalnote.com April, 2013 使用大数据和人工智能技术预测立法机构的投票结果
dato.com May, 2013 专注于机器学习的数据处理平台,非hadoop技术,底层用C++实现,从GraphLab(graph based framework)发展而来

除了以上大数据公司外,还有很多各具特色的公司,比如专门提供Non-SQL数据库的公司:

Non-SQL数据库 公司
Cassandra datastax.com
MongoDB mongodb.com
Couchbase couchbase.com
FoundationDB foundationdb.com

其它还有很多提供商务数据分析,可视化报表,大数据平台的公司,就不详细例举了,包括:Tableau, GoodData, ZoomData, SpagoBI, Pentaho, Eclipse BIRT, birst, netezza, paraccel, Ayasdi, Trifecta, Clearstory, Alpine Data Labs, Altiscale, Trifacta, Splice Machine, DataTorrent, Continuuity, Xplenty, Aerospike, snowflake.net, SumAll, Tamr, wibidata


从对美国大数据市场的调研来看,我们可以得到一些启示:

  1. 美国的数据分析市场非常大,容纳超过30家公司,这得益于美国信息化的高度发达。

  2. 虽然有很多公司,但是大家很少有重复竞争,每一家都有自己的特色,在自己的领域内发展,这也符合美国公司注重差异化相关。

  3. 传统的商务数据分析公司在维持旧有客户和平台的情况下,在积极向大数据技术扩展。

  4. 新兴的大数据技术发展非常快,但是目前还没有到成熟阶段,除了Hadoop之外,没有其他统一的技术被各家公司采用。

  5. 新的大数据技术趋势是快速响应,开始追求数据的实时处理和快速查询。


相对于美国市场,中国的大数据市场还处于非常初期的阶段,这可能跟中国的信息化程度相关。做长期展望预测,如果中国的信息化发展到美国的阶段,并且公司普遍采用基于数据的量化决策机制,将会迎来一个大数据发展的爆发式增长。

扫描微信二维码联系作者
扫描微信二维码联系作者

2015年四月30日上午 10:49:50 提问的智慧

案例

我想要一个XX的完美实现,各位大神谁能说下怎样实现?

PS:我看到这个问题的内容,冲动的就想把它删了,根本都不会去考虑怎么回答。你这是问题吗?


以下省略1大段描述这里有个截图显示的是一些code,请问大家这样的错误是怎么回事?

PS:X。。。你这是想害死人的节奏啊,问题中的code用截图,是你省事了还是想害死给你解答问题的人?我们连在其他地方try一下的机会都没有了,除非按照截图一个字母一个字母的敲,这是有多大的仇?


mysql连接显示"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",我调用了XXXXXX,但还是出现这个错误,请帮忙解决。

PS:多点描述会死吗?

之后如果你有耐心的话,就和题主挤牙膏吧!问一句答一句,这是贴吧!不是问答。


请问各位大神,怎样实现XXXXXXX?……

我遇到了一个XXXXX问题……

PS:经常性的看到很多的问题,竟然找度娘都能搜到答案,可是为啥题主就不知道看看?


还遇到过不同的3个人,发的相同的3个问题,这是刷分的节奏吗?特地等了很久没看到有人会回复,试着回了一下,没有任何的相应。。这是什么意思?


难道说答题的人就没事了吗?不是!

碰到过一个问题,就上面截图的例子,我因为觉得奇葩,一个字母一个字母敲的,最后为了省事和题主的命名不太一样,写了例子发了截图来证明没问题。竟然被其他答复者踩,说我这样命名不对,和题主相同的命名才会有问题。好吧,我承认因为自己懒,这么做了有问题?就改一下试试,但我试过之后证明这个答复者说的话是错的,和题主相同命名也不会有问题。没有责任心的答复亏你发的出来!


警语

看到一个描述不明确的问题,现在懒得和挤牙膏一样慢慢的挤了,但是多天过去后还是没人答复问题,我总是会多余的问一下,不是想说自己怎么样,只是因为我有遇到紧急问题时的经历知道那是一种什么样的心态。

答复你的人,可能是正在工作,可能加班回家休息刚起床,又可能是个脱离了技术岗位的热心人士……总之答复你的人,没有任何义务在你提出问题后答复你问题!有人说有积分哦!积分算个P!多少人真不是看中积分才回答你的,大家都是从一个问题又一个问题走过来的,都明白遇到了解决不了问题的心情,技术注重的是交流,所以才会有这个平台的市场来供大家交流。

我想奉劝大家,提问要有智慧!哪怕你再着急,也要言之有物,越着急越应该把所有相关的信息列出来,这样才会让其他人在空闲的时间看到问题思考后,给你一个答复,而不是把所有的时间都费在"挤牙膏"身上!

2015年四月30日早上 7:18:00 你真的弄明白 new 了吗

好久没有写点东西了,总觉得自己应该写点牛逼的,却又不知道如何下笔。既然如此,还是回归最基本的吧,今天就来说一说这个new。关于javascript的new关键字的内容上网搜一搜还真不少,大家都说new干了3件事:

文字比较难懂,翻译成javascript:

javascriptfunction Base() {
    this.str = "aa";
}

// new Base()干了下面的事
var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

想想是这么回事哈,那就赶快试试:

javascriptvar b = new Base();
console.dir(b); // Base {str: 'aa', __proto__: Base}

好像是正确的,但是真的正确吗???

真的就3件事?

每个对象都有一个constructor属性,那么我们来试试看new出来的实例的constructor是什么吧。

javascriptconsole.dir(b.constructor); // [Function: Base]

可以看出实例b的constructor属性就是Base,那么我们可以猜测new是不是至少还做了第4件事:

javascriptb.constructor = Base;

以上结果看似正确,下面我们进行一点修改,这里我们修改掉原型的constructor属性:

javascriptBase.prototype.constructor = function Other(){ };
var b = new Base();
console.dir(b.constructor); // [Function: Other]

情况就不一样了,可以看出,之前的猜测是错误的,第4件事应该是这样的:

javascriptb.constructor = Base.prototype.constructor;

这里犯了一个错误,那就是没有理解好这个constructor的实质:当我们创建一个函数时,会自动生成对应的原型,这个原型包含一个constructor属性,使用new构造的实例,可以通过原型链查找到constructor。如下图所示:

constructor

这里非常感谢zonxin同学指出我的错误。

如果构造函数有返回值呢?

一般情况下构造函数没有返回值,但是我们依旧可以得到该对象的实例;如果构造函数有返回值,凭直觉来说情况应该会不一样。我们对于之前的构造函数进行一点点修改:

javascriptfunction Base() {
    this.str = "aa";
    return 1;
    // return "a";
    // return true;
}
var b = new Base();
console.dir(b); // { str: 'aa'}

我们在构造函数里设置的返回值好像没什么用,返回的还是原来对象的实例,换一些例子试试:

javascriptfunction Base() {
    this.str = "aa";
    return [1];
    // return {a:1};
}
var b = new Base();
console.dir(b); // [1] or {a: 1}

此时结果就不一样了,从上面的例子可以看出,如果构造函数返回的是原始值,那么这个返回值会被忽略,如果返回的是对象,就会覆盖构造的实例

new至少做了4件事

总结一下,new至少做了4件事:

javascript// new Base();

// 1.创建一个空对象 obj
var obj = {};
// 2.设置obj的__proto__为原型
obj.__proto__ = Base.prototype;
// 3.使用obj作为上下文调用Base函数
var ret = Base.call(obj);
// 4.如果构造函数返回的是原始值,那么这个返回值会被忽略,如果返回的是对象,就会覆盖构造的实例
if(typeof ret == 'object'){
    return ret;
} else {
    return obj;
}

new的不足

在《Javascript语言精粹》(Javascript: The Good Parts)中,道格拉斯认为应该避免使用new关键字:

If you forget to include the new prefix when calling a constructor function, then this will not be bound to the new object. Sadly, this will be bound to the global object, so instead of augmenting your new object, you will be clobbering global variables. That is really bad. There is no compile warning, and there is no runtime warning.

大意是说在应该使用new的时候如果忘了new关键字,会引发一些问题。最重要的问题就是影响了原型查找,原型查找是沿着__proto__进行的,而任何函数都是Function的实例,一旦没用使用new,你就会发现什么属性都查找不到了,因为相当于直接短路了。如下面例子所示,没有使用new来创建对象的话,就无法找到原型上的fa1属性了:

javascriptfunction F(){ }
F.prototype.fa1 = "fa1";

console.log(F.fa1);       // undefined
console.log(new F().fa1); // fa1

这里我配合一张图来说明其中原理,黄色的线为原型链,使用new构造的对象可以正常查找到属性fa1,没有使用new则完全走向了另外一条查找路径:

原型查找

以上的问题对于有继承的情况表现得更为明显,沿着原型链的方法和属性全都找不到,你能使用的只有短路之后的Function.prototype的属性和方法了。

当然了,遗忘使用任何关键字都会引起一系列的问题。再退一步说,这个问题是完全可以避免的:

javascriptfunction foo()
{   
   // 如果忘了使用关键字,这一步骤会悄悄帮你修复这个问题
   if ( !(this instanceof foo) )
      return new foo();

   // 构造函数的逻辑继续……
}

可以看出new并不是一个很好的实践,道格拉斯将这个问题描述为:

This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript’s constructor pattern did not appeal to the classical crowd. It also obscured JavaScript’s true prototypal nature. As a result, there are very few programmers who know how to use the language effectively.

简单来说,JavaScript是一种prototypical类型语言,在创建之初,是为了迎合市场的需要,让人们觉得它和Java是类似的,才引入了new关键字。Javascript本应通过它的Prototypical特性来实现实例化和继承,但new关键字让它变得不伦不类。

再说一点关于constructor的

虽然使用new创建新对象的时候用讨论了这个constructor属性,但是这个属性似乎并没有什么用,也许设置这个属性就是一种习惯,能够让其他人直观理解对象之间的关系。

欢迎光临小弟博客:Superlin's Blog
我的博客原文:你真的弄明白new了吗

参考

2015年四月29日晚上 9:08:52 地图移动应用实战:Ionic ElasticSearch 搜索服务

在上一篇《GIS 移动应用实战 —— Django Haystack ElasticSearch 构建》中,我们构建了我们的服务端,可以通过搜索搜索到结果,这一篇,我们来构建一个简单的搜索。

最后效果如下图所示:

Ionic ElasticSearch

开始之前

如果你没有Ionic的经验,可以参考一下之前的一些文章:《HTML5打造原生应用——Ionic框架简介与Ionic Hello World》

我们用到的库有:

将他们添加到bower.json,然后

bower install

Ionic ElasticSearch 创建页面

1.引入库

index.html中添加

<script src="lib/elasticsearch/elasticsearch.angular.min.js"></script>
<script src="lib/ngCordova/dist/ng-cordova.js"></script>

接着开始写我们的搜索模板tab-search.html

html    <ion-view view-title="搜索" ng-controller="SearchCtrl">
        <ion-content>
            <div id="search-bar">
                <div class="item item-input-inset">
                    <label class="item-input-wrapper" id="search-input">
                        <i class="icon ion-search placeholder-icon"></i>
                        <input type="search" placeholder="Search" ng-model="query" ng-change="search(query)" autocorrect="off">
                    </label>
                </div>
            </div>
        </ion-content>
    </ion-view>

显示部分

xml <ion-list>
                <ion-item class="item-remove-animate item-icon-right" ng-repeat="result in results">
                    <h2 class="icon-left">{{result.title}}</h2>
                    <p>简介: {{result.body}}</p>
                    <div class="icon-left ion-ios-home location_info">
                        {{result.location_info}}
                    </div>
                    <div class="button icon-left ion-ios-telephone button-calm button-outline">
                        <a ng-href="tel: {{result.phone_number}}">{{result.phone_number}}</a>
                    </div>
                </ion-item>
            </ion-list>

而我们期待的SearchCtrl则是这样的

$scope.query = "";
var doSearch = ionic.debounce(function(query) {
    ESService.search(query, 0).then(function(results){
        $scope.results = results;
    });
}, 500);

$scope.search = function(query) {
    doSearch(query);
}

当我们点下搜索的时候,调用 ESService.

Ionic ElasticSearch Service

接着我们就来构建我们的ESService,下面的部分来自网上:

angular.module('starter.services', ['ngCordova', 'elasticsearch'])

.factory('ESService',
  ['$q', 'esFactory', '$location', '$localstorage', function($q, elasticsearch, $location, $localstorage){
    var client = elasticsearch({
      host: $location.host() + ":9200"
    });

    var search = function(term, offset){
      var deferred = $q.defer(), query, sort;
      if(!term){
        query = {
          "match_all": {}
        };
      } else {
        query = {
          match: { title: term }
        }
      }

      var position = $localstorage.get('position');

      if(position){
        sort = [{
          "_geo_distance": {
            "location": position,
            "unit": "km"
          }
        }];
      } else {
        sort = [];
      }

      client.search({
        "index": 'haystack',
        "body": {
          "query": query,
          "sort": sort
        }
      }).then(function(result) {
        var ii = 0, hits_in, hits_out = [];
        hits_in = (result.hits || {}).hits || [];
        for(;ii < hits_in.length; ii++){
          var data = hits_in[ii]._source;
          var distance = {};
          if(hits_in[ii].sort){
            distance = {"distance": parseFloat(hits_in[ii].sort[0]).toFixed(1)}
          }
          angular.extend(data, distance);
          hits_out.push(data);
        }
        deferred.resolve(hits_out);
      }, deferred.reject);

      return deferred.promise;
    };


    return {
      "search": search
    };
  }]
);

这个Service主要做的是创建ElasitcSearch Query,然后返回解析结果。

运行

如果是要在真机上运行,需要处于同一网段,或者是部署到服务器上。

其他

服务端代码: https://github.com/phodal/django-elasticsearch
客户端代码: https://github.com/phodal/ionic-elasticsearch

2015年四月29日晚上 9:05:33 SegmentFault for Android.

我用尽一生的好运气去遇见你。
所幸,这运气够长够远,足够我陪你一辈子。

非常荣幸的告诉大家,SegmentFault For Android 1.0 已经在以下市场发布

  1. Google Play
  2. 豌豆荚
  3. 应用宝
  4. 小米
  5. 360

现在大家可以在已经发布的市场中搜索我们的App进行试用啦~

如果SegmentFault是一本书,您就是那唯一能领略它墨香的读者,只为您散尽芳华。
如果SegmentFault是一束向日葵,您就是那一缕明媚的阳光,只因日出盛放。
如果SegmentFault是一行诗,您就是那一壶陈酿,只为醇香刻下所有的时光。

反馈请点 https://github.com/SegmentFault/report

感谢有你。

Build By Developers.
Build For Developers.

2015年四月29日晚上 8:01:18 从外网 SSH 进局域网,反向代理+正向代理解决方案

相信很多同学都会碰到这样一个问题。在实验室有一台机器用于日常工作,当我回家了或者回宿舍之后因为没法进入内网,所以访问不到了。如果这个时候我需要 SSH 进去做一下工作,那么怎么解决这个问题呢?本文将给出一种使用 SSH 的代理功能的解决方案。

问题描述:

机器状况

机器号 IP 用户名 备注
A 192.168.0.A usr_a 目标服务器,在局域网中,可以访问 A
B B.B.B.B usr_b 代理服务器,在外网中,无法访问 A
C - - 可以直接访问 B,无法直接访问 A

目标

从 C 机器使用 SSH 访问 A

解决方案

在 A 机器上做到 B 机器的反向代理;在 B 机器上做正向代理本地端口转发

环境需求

实施步骤

  1. 建立 A 机器到 B 机器的反向代理【A 机器上操作】

    bashssh -fCNR <port_b1>:localhost:22 usr_b@B.B.B.B
    

    <port_b1> 为 B 机器上端口,用来与 A 机器上的22端口绑定。

  2. 建立 B 机器上的正向代理,用作本地转发。做这一步是因为绑定后的 端口只支持本地访问【B 机器上操作】

    bashssh -fCNL "*:<port_b2>:localhost:<port_b1>' localhost
    

    <port_b2> 为本地转发端口,用以和外网通信,并将数据转发到 <port_b1>,实现可以从其他机器访问。

    其中的*表示接受来自任意机器的访问。

  3. 现在在 C 机器上可以通过 B 机器 ssh 到 A 机器

    bashssh -p <portb2> usra@B.B.B.B
    

至此方案完成。

附:

SSH 参数解释

-f 后台运行
-C 允许压缩数据
-N 不执行任何命令
-R 将端口绑定到远程服务器,反向代理
-L 将端口绑定到本地客户端,正向代理
2015年四月29日晚上 6:05:19 使用Gulp来加速你的开发

Gulp与Grunt一样,也是一个自动任务运行器。它充分借鉴了Unix操作系统的管道(pipe)思想,在操作上,它要比Grunt简单。

安装

Gulp需要全局安装,然后再在项目的开发目录中安装为本地模块。先进入项目目录,运行下面的命令。

bashnpm install -g gulp
npm install --save-dev gulp

gulpfile.js

项目根目录中的gulpfile.js,是Gulp的配置文件。它大概是下面的样子。

javascriptvar gulp = require('gulp');
gulp.task('default', function () {
});

举个栗子,我们要实现js的压缩。

javascriptvar gulp = require('gulp'),
   uglify = require('gulp-uglify');

gulp.task('minify', function () {
   gulp.src('js/app.js')
      .pipe(uglify())
      .pipe(gulp.dest('app.min'))
});

上面代码中使用了gulp-uglify模块。在此之前,需要先安装这个模块。
记住在安装之前先 运行 npm init 来生成package.json,如果已经有了就不需要这一步了。

bashnpm install --save-dev gulp-uglify

Tips: --save-dev 会将 gulp-uglify 自动添加到package.json的devDependencies中;

gulpfile.js加载gulp和gulp-uglify模块之后,使用gulp模块的task方法指定任务。task方法有两个参数,第一个是任务名,第二个是任务函数。在任务函数中,使用gulp模块的src方法,指定所要处理的文件,然后使用pipe方法,将上一步的输出转为当前的输入,进行链式处理。

在上面代码中,使用两次pipe方法,也就是说做了两种处理。第一种处理是使用gulp-uglify模块,压缩源码;第二种处理是使用gulp模块的dest方法,将上一步的输出写入本地文件,这里是app.min.js(代码中省略了后缀名js)。

从上面的例子中可以看到,gulp充分使用了“管道”思想,就是一个数据流(stream):src方法读入文件产生数据流,dest方法将数据流写入文件,中间是一些中间步骤,每一步都对数据流进行一些处理。

gulp.src()

gulp模块的src方法,用于产生数据流。它的参数表示所要处理的文件,一般有以下几种形式。

src方法的参数还可以是一个数组,用来指定多个成员。

javascript
gulp.src(['js/**/*.js', 'css/**/*.css'])

gulp.task()

gulp模块的task方法,用于定义具体的任务。它的第一个参数是任务名,第二个参数是任务函数。下面是一个非常简单的任务函数。

javascript
gulp.task('test', function () { console.log('就测试下。'); });

task方法还可以指定按顺序运行的一组任务。

javascript
gulp.task('build', ['css', 'js', 'templates']);

上面代码先指定build任务,它按次序由css、js、templates三个任务所组成。注意,由于每个任务都是异步调用,所以没有办法保证js任务的开始运行的时间,正是css任务运行结束。

如果希望各个任务严格按次序运行,可以把前一个任务写成后一个任务的依赖模块。

javascript
gulp.task('css', ['templates'], function () { // Deal with CSS here });

上面代码表明,css任务依赖templates任务,所以css一定会在templates运行完成后再运行。

如果一个任务的名字为default,就表明它是“默认任务”,在命令行直接输入gulp命令,就会运行该任务。

javascript
gulp.task('default', function () { // Your default task });

gulp.watch()

gulp模块的watch方法,用于指定需要监视的文件。一旦这些文件发生变动,就运行指定任务。

javascript
gulp.task('watch', function () { gulp.watch('templates/*.tmpl.html', ['build']); });

上面代码指定,一旦templates目录中的模板文件发生变化,就运行build任务。

watch方法也可以用回调函数,代替指定的任务。

javascript
gulp.watch('templates/*.html', function (event) { console.log('Event type: ' + event.type); console.log('Event path: ' + event.path); });

另一种写法是watch方法所监控的文件发生变化时(修改、增加、删除文件),会触发change事件。可以对change事件指定回调函数。

javascript
var watcher = gulp.watch('templates/*.html', ['build']); watcher.on('change', function (event) { console.log('Event type: ' + event.type); console.log('Event path: ' + event.path); });

除了change事件,watch方法还可能触发以下事件。

watcher对象还包含其他一些方法。

gulp实现自动刷新 - gulp-livereload

gulp-livereload模块用于自动刷新浏览器,反映出源码的最新变化。它除了模块以外,还需要在浏览器中安装插件,用来配合源码变化。

javascript
var gulp = require('gulp'), livereload = require('gulp-livereload'); gulp.task('watch', function () { livereload.listen(); gulp.watch(['./asset/**/*.*','./templates/**/*.*'], function (file) { livereload.changed(file.path); }); });

上面代码监视asset和templates下的任何文件,一旦有变化,就自动刷新浏览器。
Tips: 调试css 很方遍,因为刷新css 不需要刷新这个页面,只需要重新加载css即可,赶紧双屏幕,三屏幕搞起来;代码敲得飞起。

还有很多实用的插件 可以到 NpmJs.org 去找。

2015年四月29日下午 5:45:17 alsotang starred node-modules/optimized
alsotang starred node-modules/optimized
2015年四月29日下午 4:54:48 各种设备的CSS3MediaQuery整理及爽歪歪写法

响应式布局

响应式布局麻烦之处就是每个尺寸的都要进行css定义,这个真的不是一般的蛋疼,下面有搜集到的各种尺寸css Media Query内容,搜集来源:media-queries-for-standard-devices好东西哦。

看了之后是不是非常之蛋疼呢,那么只有使用工具来写这些玩意儿了,俺用得最爽的就是 stylus ,真的爽yy了,如果 stylus 不会玩耍请看这里 stylus入门使用方法

stylus

// Media queries
mq-mobile = "screen and (max-width: 479px)"
mq-tablet = "screen and (min-width: 480px) and (max-width: 767px)"
mq-iPhones4 = "only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2)"
mq-normal = "screen and (min-width: 768px)"

.page-number
    display: inline-block
    @media mq-mobile
        display: none
    @media mq-tablet
        color:red
    @media mq-iPhones4
        font-size:12px
    @media mq-normal
        background:yellow

编译成

css.page-number {
  display: inline-block;
}
@media screen and (max-width: 479px) {
  .page-number {
    display: none;
  }
}
@media screen and (min-width: 480px) and (max-width: 767px) {
  .page-number {
    color: #f00;
  }
}
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) {
  .page-number {
    font-size: 12px;
  }
}
@media screen and (min-width: 768px) {
  .page-number {
    background: #ff0;
  }
}

Phones and Handhelds

iPhones

css/* ----------- iPhone 4 and 4S ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 480px)
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 480px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 480px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: landscape) {

}

/* ----------- iPhone 5 and 5S ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: landscape) {

}

/* ----------- iPhone 6 ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 375px) 
  and (max-device-width: 667px) 
  and (-webkit-min-device-pixel-ratio: 2) { 

}

/* Portrait */
@media only screen 
  and (min-device-width: 375px) 
  and (max-device-width: 667px) 
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: portrait) { 

}

/* Landscape */
@media only screen 
  and (min-device-width: 375px) 
  and (max-device-width: 667px) 
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: landscape) { 

}

/* ----------- iPhone 6+ ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 414px) 
  and (max-device-width: 736px) 
  and (-webkit-min-device-pixel-ratio: 3) { 

}

/* Portrait */
@media only screen 
  and (min-device-width: 414px) 
  and (max-device-width: 736px) 
  and (-webkit-min-device-pixel-ratio: 3)
  and (orientation: portrait) { 

}

/* Landscape */
@media only screen 
  and (min-device-width: 414px) 
  and (max-device-width: 736px) 
  and (-webkit-min-device-pixel-ratio: 3)
  and (orientation: landscape) { 

}

Galaxy Phones

css/* ----------- Galaxy S3 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 2) {

}

/* Portrait */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 2) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 2) 
  and (orientation: landscape) {

}

/* ----------- Galaxy S4 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) {

}

/* Portrait */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: landscape) {

}

/* ----------- Galaxy S5 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) {

}

/* Portrait */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: landscape) {

}

HTC Phones

css/* ----------- HTC One ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) {

}

/* Portrait */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: landscape) {

}

Tablets

iPads

css/* ----------- iPad mini ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: portrait) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: landscape) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* ----------- iPad 1 and 2 ----------- */
/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: portrait) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: landscape) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* ----------- iPad 3 and 4 ----------- */
/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: portrait) 
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: landscape) 
  and (-webkit-min-device-pixel-ratio: 2) {

}

Galaxy Tablets

css/* ----------- Galaxy Tab 10.1 ----------- */

/* Portrait and Landscape */
@media 
  (min-device-width: 800px) 
  and (max-device-width: 1280px) {

}

/* Portrait */
@media 
  (max-device-width: 800px) 
  and (orientation: portrait) { 

}

/* Landscape */
@media 
  (max-device-width: 1280px) 
  and (orientation: landscape) { 

}

Nexus Tablets

css/* ----------- Asus Nexus 7 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 601px) 
  and (device-height: 906px) 
  and (-webkit-min-device-pixel-ratio: 1.331) 
  and (-webkit-max-device-pixel-ratio: 1.332) {

}

/* Portrait */
@media screen 
  and (device-width: 601px) 
  and (device-height: 906px) 
  and (-webkit-min-device-pixel-ratio: 1.331) 
  and (-webkit-max-device-pixel-ratio: 1.332) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 601px) 
  and (device-height: 906px) 
  and (-webkit-min-device-pixel-ratio: 1.331) 
  and (-webkit-max-device-pixel-ratio: 1.332) 
  and (orientation: landscape) {

}

Kindle Fire

css/* ----------- Kindle Fire HD 7" ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 800px) 
  and (max-device-width: 1280px) 
  and (-webkit-min-device-pixel-ratio: 1.5) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 800px) 
  and (max-device-width: 1280px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 800px) 
  and (max-device-width: 1280px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: landscape) {

}

/* ----------- Kindle Fire HD 8.9" ----------- */
/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1.5) {

}
/* Portrait */
@media only screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: landscape) {

}

Laptops

css/* ----------- Non-Retina Screens ----------- */
@media screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1) { 
}
/* ----------- Retina Screens ----------- */
@media screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 2)
  and (min-resolution: 192dpi) { 
}

Wearables

Apple Watch

css/* ----------- Apple Watch ----------- */
@media
  (max-device-width: 42mm)
  and (min-device-width: 38mm) { 

}

Moto 360 Watch

css/* ----------- Moto 360 Watch ----------- */
@media 
  (max-device-width: 218px)
  and (max-device-height: 281px) { 

}
2015年四月29日下午 4:25:28 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年四月29日下午 4:24:27 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年四月29日上午 10:30:37 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年四月28日下午 3:23:19 alsotang starred ideawu/ssdb
alsotang starred ideawu/ssdb
2015年四月28日上午 10:48:03 alsotang starred phacility/xhprof
alsotang starred phacility/xhprof
2015年四月27日晚上 10:08:08 alsotang starred nuysoft/node-print
alsotang starred nuysoft/node-print
2015年四月27日中午 11:36:09 alsotang starred petitspois/docs.ren
alsotang starred petitspois/docs.ren
2015年四月24日晚上 10:01:44 alsotang commented on issue alsotang/node-lessons#34
alsotang commented on issue alsotang/node-lessons#34
@alsotang

lesson2 不涉及任何 css 的内容吧

2015年四月23日晚上 10:58:10 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年四月23日晚上 10:57:37 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年四月23日晚上 10:52:40 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年四月23日晚上 10:52:23 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年五月6日晚上 8:21:08 alsotang commented on issue cnodejs/nodeclub#522
alsotang commented on issue cnodejs/nodeclub#522
@alsotang

在 render 的时候应该是可以指定 layout 的吧?特定页面指定一下就好了。 不过还是不太懂你问的是什么。 2015-05-06 16:25 GMT+08:00 stiyes notifications@github.com: 我的已经是,模板文件layout.html,再加几个模板…

2015年五月6日晚上 7:56:10 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日晚上 7:55:36 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年五月6日晚上 7:54:17 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日下午 5:45:19 koa有什么优势么?昨天压测了下,结果略失望

node版本为0.11.14,以http和koa分别写简单server服务(输出hello world),高并发高请求下,http的qps要比koa高100左右(例如c300n10000时,http的qps为1623,koa为1527;c500n15000时,http的qps为1288,koa为1152)。且单个请求的平均响应时间,http的要比koa的快。 之前把一个服务重新用koa写了下,压测的时候发现qps反而降低了。。。打击啊:(

2015年五月6日下午 5:04:23 8 个你可能不知道的 Docker 知识

Docker 这个工具已经出现很长一段时间了,但是可能还有很多人对 Docker 的概念不太清楚,因此这次翻译 8 个你可能不知道的 Docker 知识 这篇文章,和大家介绍一下生产环境中的 Docker 用例。

自从上世纪 90 年代硬件虚拟化被主流的技术广泛普及之后,对数据中心而言,发生的最大的变革莫过于容器和容器管理工具,例如:Docker。在过去的一年内,Docker 技术已经逐渐走向成熟,并且推动了大型初创公司例如 Twitter 和 Airbnb 的发展,甚至在银行、连锁超市、甚至 NASA 的数据中心都赢得了一席之地。当我几年前第一次直到 Docker 的时候,我还对 Docker 的未来持怀疑的态度,我认为他们是把以前的 Linux 容器的概念拿出来包装了一番推向市场。但是使用 Docker 成功进行了几个项目 例如 Spantree 之后,我改变了我的看法:Docker 帮助我们节省了大量的时间和经历,并且已经成为我们技术团队中不可或缺的工具。

GitHub 上面每天都会催生出各式各样的工具、形态各异的语言和千奇百怪的概念。如果你和我一样,没有时间去把他们全部都测试一遍,甚至没有时间去亲自测试 Docker,那么你可以看一下我的这篇文章:我将会用我们在 Docker 中总结的经验来告诉你什么是 Docker、为什么 Docker 会这么火。

Docker 是容器管理工具

Docker 是一个轻量级、便携式、与外界隔离的容器,也是一个可以在容器中很方便地构建、传输、运行应用的引擎。和传统的虚拟化技术不同的是,Docker 引擎并不虚拟出一台虚拟机,而是直接使用宿主机的内核和硬件,直接在宿主机上运行容器内应用。也正是得益于此,Docker 容器内运行的应用和宿主机上运行的应用性能差距几乎可以忽略不计。

但是 Docker 本身并不是一个容器系统,而是一个基于原有的容器化工具 LXC 用来创建虚拟环境的工具。类似 LXC 的工具已经在生产环境中使用多年,Docker 则基于此提供了更加友好的镜像管理工具和部署工具。

Docker 不是虚拟化引擎

Docker 第一次发布的时候,很多人都拿 Docker 和虚拟机 VMware、KVM 和 VirtualBox 比较。尽管从功能上看,Docker 和虚拟化技术致力于解决的问题都差不多,但是 Docker 却是采取了另一种非常不同的方式。虚拟机是虚拟出一套硬件,虚拟机的系统进行的磁盘操作,其实都是在对虚拟出来的磁盘进行操作。当运行 CPU 密集型的任务时,是虚拟机把虚拟系统里的 CPU 指令“翻译”成宿主机的CPU指令并进行执行。两个磁盘层,两个处理器调度器,两个操作系统消耗的内存,所有虚拟出的这些都会带来相当多的性能损失,一台虚拟机所消耗的硬件资源和对应的硬件相当,一台主机上跑太多的虚拟机之后就会过载。而 Docker 就没有这种顾虑。Docker 运行应用采取的是“容器”的解决方案:使用 namespace 和 CGroup 进行资源限制,和宿主机共享内核,不虚拟磁盘,所有的容器磁盘操作其实都是对 /var/lib/docker/ 的操作。简言之,Docker 其实只是在宿主机中运行了一个受到限制的应用程序。

从上面不难看出,容器和虚拟机的概念并不相同,容器也并不能取代虚拟机。在容器力所不能及的地方,虚拟机可以大显身手。例如:宿主机是 Linux,只能通过虚拟机运行 Windows,Docker 便无法做到。再例如,宿主机是 Windows,Windows 并不能直接运行 Docker,Windows上的 Docker 其实是运行在 VirtualBox 虚拟机里的。

Docker 使用层级的文件系统

前面提到过,Docker 和现有容器技术 LXC 等相比,优势之一就是 Docker 提供了镜像管理。对于 Docker 而言,镜像是一个静态的、只读的容器文件系统的快照。然而不仅如此,Docker 中所有的磁盘操作都是对特定的Copy-On-Write文件系统进行的。下面通过一个例子解释一下这个问题。

例如我们要建立一个容器运行 JAVA Web 应用,那么我们应该使用一个已经安装了 JAVA 的镜像。在 Dockerfile(一个用于生成镜像的指令文件)中,应该指明“基于 JAVA 镜像”,这样 Docker 就会去 Docker Hub Registry 上下载提前构建好的 JAVA 镜像。然后再 Dockerfile 中指明下载并解压 Apache Tomcat 软件到 /opt/tomcat 文件夹中。这条命令并不会对原有的 JAVA 镜像产生任何影响,而仅仅是在原有镜像上面添加了一个改动层。当一个容器启动时,容器内的所有改动层都会启动,容器会从第一层中运行 /usr/bin/java 命令,并且调用另外一层中的 /opt/tomcat/bin 命令。实际上,Dockerfile 中每一条指令都会产生一个新的改动层,即便只有一个文件被改动。如果用过 Git 就能更清楚地认识这一点,每条指令就像是每次 commit,都会留下记录。但是对于 Docker 来说,这种文件系统提供了更大的灵活性,也可以更方便地管理应用程序。

我们Spantree的团队有一个自己维护的含有 Tomcat 的镜像。发布新版本也非常简单:使用 Dockerfile 将新版本拷贝进镜像从而创建一个新镜像,然后给新镜像贴上版本的标签。不同版本的镜像的不同之处仅仅是一个 90 MB 大小的 WAR 文件,他们所基于的主镜像都是相同的。如果使用虚拟机去维护这些不同的版本的话,还要消耗掉很多不同的磁盘去存储相同的系统,而使用 Docker 就只需要很小的磁盘空间。即便我们同时运行这个镜像的很多实例,我们也只需要一个基础的 JAVA / TOMCAT 镜像。

Docker 可以节约时间

很多年前我在为一个连锁餐厅开发软件时,仅仅是为了描述如何搭建环境都需要写一个 12 页的 Word 文档。例如本地 Oracle 数据库,特定版本的 JAVA,以及其他七七八八的系统工具和共享库、软件包。整个搭建过程浪费掉了我们团队每个人几乎一天的时间,如果用金钱衡量的话,花掉了我们上万美金的时间成本。虽然客户已经对这种事情习以为常,甚至认为这是引入新成员、让成员适应环境、让自己的员工适应我们的软件所必须的成本,但是相比较起来,我们宁愿把更多的时间花在为客户构建可以增进业务的功能上面。

如果当时有 Docker,那么构建环境就会像使用自动化搭建工具 Puppet / Chef / Salt / Ansible 一样简单,我们也可以把整个搭建时间周期从一天缩短为几分钟。但是和这些工具不同的地方在于,Docker 可以不仅仅可以搭建整个环境,还可以将整个环境保存成磁盘文件,然后复制到别的地方。需要从源码编译 Node.js 吗?Docker 做得到。Docker 不仅仅可以构建一个 Node.js 环境,还可以将整个环境做成镜像,然后保存到任何地方。当然,由于 Docker 是一个容器,所以不用担心容器内执行的东西会对宿主机产生任何的影响。

现在新加入我们团队的人只需要运行 docker-compose up 命令,便可以喝杯咖啡,然后开始工作了。

Docker 可以节省开销

当然,时间就是金钱。除了时间外,Docker 还可以节省在基础设施硬件上的开销。高德纳和麦肯锡的研究表明,数据中心的利用率在 6% - 12% 左右。不仅如此,如果采用虚拟机的话,你还需要被动地监控和设置每台虚拟机的 CPU 硬盘和内存的使用率,因为采用了静态分区(static partitioning)所以资源并不能完全被利用。。而容器可以解决这个问题:容器可以在实例之间进行内存和磁盘共享。你可以在同一台主机上运行多个服务、可以不用去限制容器所消耗的资源、可以去限制资源、可以在不需要的时候停止容器,也不用担心启动已经停止的程序时会带来过多的资源消耗。凌晨三点的时候只有很少的人会去访问你的网站,同时你需要比较多的资源执行夜间的批处理任务,那么可以很简单的便实现资源的交换。

虚拟机所消耗的内存、硬盘、CPU 都是固定的,一般动态调整都需要重启虚拟机。而用 Docker 的话,你可以进行资源限制,得益于 CGroup,可以很方便动态调整资源限制,让然也可以不进行资源限制。Docker 容器内的应用对宿主机而言只是两个隔离的应用程序,并不是两个虚拟机,所以宿主机也可以自行去分配资源。

Docker 有一个健壮的镜像托管系统

前面提到过,这个托管系统就叫做 Docker Hub Registry。截止到 2015年4月29日,互联网上大约有 14000 个公共的 Docker,而大部分都被托管在 Docker Hub 上面。和 Github 已经很大程度上成为开源项目的代表一样,Docker 官方的 Docker Hub 则已经是公共 Docker 镜像的代表。这些镜像可以作为你应用和数据服务的基础。

也正是得益于此,你可以随意尝试最新的技术:说不定有些人就把图形化数据库的实例打包成了 Docker 镜像托管在上面。再例如 Gitlab,手工搭建 Gitlab 非常困难,译者不建议普通用户去手工搭建,而如果使用 Docker Gitlab,这个镜像则会五秒内便搭建完成。再例如特定 Ruby 版本的 Rails 应用,再例如 Linux 上的 .NET 应用,这些都可以使用简单的一条 Docker 命令搭建完成。

Docker 官方镜像都有 official 标签,安全性可以保证。但是第三方镜像的安全性无法保证,所以请谨慎下载第三方镜像。生产环境下可以只使用第三方提供的 Dockerfile 构建镜像。

Docker Github 介绍:5 秒内搞定一个 Gitlab

关于 Linux 上的 .NET 应用和 Rails 应用,将会在以后的文章中做详细介绍。

Docker 可以避免产生 Bug

Spantree 一直是“固定基础设置”(immutable infrastructure)的狂热爱好者。换句话说,除非有心脏出血这种漏洞,我们尽量不对系统做升级,也尽量不去改变系统的设置。当添加新服务器的时候,我们也会从头构建服务器的系统,然后直接将镜像导入,将服务器放入负载均衡的集群里,然后对要退休的服务器进行健康检查,检查完毕后移除集群。得益于 Docker 镜像可以很轻松的导入导出,我们可以最大程度地减少因为环境和版本问题导致的不兼容,即便有不兼容了也可以很轻松地回滚。当然,有了 Docker,我们在生产、测试和开发中的运行环境得到统一。以前在协同开发时,会因为每个人开发的电脑配置不同而导致“在我的电脑上是能运行的,你的怎么不行”的情况,而如今 Docker 已经帮我们解决了这个问题。

Docker 目前只能运行在 Linux 上

前面也提到过,Docker 使用的是经过长时间生产环境检验的技术,虽然这些技术已经都出现很长时间了,但是大部分技术都还是 Linux 独有的,例如 LXC 和 Cgroup。也就是说,截止到现在,Docker 容器内只能在 Linux 上运行 Linux 上的服务和应用。Microsoft 正在和 Docker 紧密合作,并且已经宣布了下一个版本的 Windows Server 将会支持 Docker 容器,并且命名为 Windows Docker,估计采用的技术应该是Hyper-V Container,我们有望在未来的几年内看到这个版本。

除此之外,类似 boot2docker 和 Docker Machine 这种工具已经可以让我们在 Mac 和 Windows 下通过虚拟机运行 Docker 了。

后记

悄悄的说一句,前文中提到的 Docker 安装Docker 操作DockerfileDocker Hub、搭建 Rails 环境、甚至搭建 .NET 环境,SegmentFault 正在组织编写相关的文档,欢迎关注我们,及时获取更多最新的教程。

2015年五月6日下午 4:27:46 改写了memwatch的代码,支持v0.11之后的nodejs

具体位置是 https://github.com/crystaldust/node-memwatch/tree/higher-than-v10

npm 安装: npm install memwatch@git://github.com/crystaldust/node-memwatch.git#higher-than-v10

小弟没怎么弄过v8下的编程,所以也是一边找资料一边学习一边修改的。欢迎大家测试拍砖。附上截图: memwatch.jpg

memwatch2.jpg

2015年五月6日下午 4:00:21 websocket和HTTP使用不同端口可以吗

比如:HTTP监听端口80,websocket想分离到专门的服务器上监听端口6180 浏览器端会存在安全限制吗?

2015年五月6日下午 3:53:49 要记录用户点击不同链接的次数,是每次用户点击都去插入数据库还是有什么策略?

要记录用户点击不同链接的次数,是每次用户点击都去插入数据库还是有什么策略?

2015年五月6日下午 3:48:33 惊爆!Node.js 和 io.js 准备合并

因为对Node.js管理方Joyent公司不满,多位核心开发者自创门户建立了分支io.js,其开发非常活跃,甚至刚刚发布了 io.js 2.0.0。 而如今,不到半年时间,两个项目突然就化敌为友了。两个互相竞争的项目如今正在Node.js基金会的名义下准备合并,合并完成之后github.org/iojs项目的所有权将转移到Node.js基金会,iojs.org 和nodejs.org域名的所有权以及相关社交媒体账号也都将转移给Node.js基金会。

原文:http://www.solidot.org/story?sid=43951

2015年五月6日下午 3:06:36 用async并发执行几个函数,同时写入一个全局的变量,是否保证有序?

事情是这样的,产品的同事希望在一个用户列表中,按照不同的标准选取几类用户(假设有A, Bl两种用户),然后同时呈现。但是有些用户可能同时满足2个标准,比如某个用户既符合A标准,也符合B标准,这样返回的结果如果不加限定,就会出现同一个用户出现多次的情况。

现在我是这么考虑的,先去找A类的用户,查找结束后,把A类用户的_id传递给第二个函数,第二个函数在查找B类用户时候,把第所有A类用户的_id都剔除掉。程序结构大概是这样:



var exclude = [];

async.series( [
    function( callback ) {
        db.collection('users').find( { /*category A的条件*/} ).toArray( function( err, users ) {
            users.forEach( function( user ) {
                exclude.push( user._id );
            } );
            callback( null, users );
        } )
    },

    function( callback ) {
        db.collection('users').find( { /*category B的条件*/ _id : { $not : { $in : exclude } }/*限定不包含category A的用户*/ } ).toArray( function( err, users ) {
            callback( null, users );
        } )
    }

], function( err, result ) {
    var users_A = result[0];
    var users_B = result[1];
    // Handle category A and B...   
} )

为了保证不重复,需要A执行完后在执行B。顺序执行,响应时间太长。如果改成并发执行,那么每个函数去查找用户时,都要包含{$not : { $in : exclude } }条件。关键是是否会出现这样一种情况: 第一个函数先执行回调,然后向exclude写入id,还没有写完时,第二个函数执行回调了,但是exclude还是空的,那么exclude的限定在第二个函数里其实就没有作用了。

小弟表达能力较差,说的不清楚的地方,还请大家指出,期待各路高手给点儿意见啊,跪谢先 m( _ _ ) m

2015年五月6日下午 3:04:45 哪位大神 帮忙指点 uglifyjs 怎么批量压缩

已安装 1、node.js 2、uglifyjs

2015年五月6日下午 2:37:25 求bower转spm的回答

项目最近因为变动,计划从bower转spm。毕竟方便很多。但是项目本身bower 的很多文件在spm上没有。大神们怎么解决的? 直接复制过去,然后依次放到spmmodules的组件对应的版本目录,直接引用还是?对spm机制不太了解。特来求教。 引用的包有点多=,= 求解答。

"angular": "~1.3.11",
"angular-animate": "~1.3.11",
"angular-cookies": "~1.3.11",
"angular-resource": "~1.3.11",
"angular-sanitize": "~1.3.11",
"angular-touch": "~1.3.11",
"angular-translate": "~2.5.2",
"angular-translate-loader-static-files": "~2.5.2",
"angular-translate-storage-cookie": "~2.5.2",
"angular-translate-storage-local": "~2.5.2",
"angular-bootstrap": "~0.12.0",
"angular-bootstrap-nav-tree": "*",
"angular-ui-router": "~0.2.11",
"angular-ui-utils": "~0.2.1",
"angular-file-upload": "~1.1.1",
"angular-ui-select": "~0.8.3",
"angular-ui-calendar": "latest",
"angular-ui-grid": "~3.0.0-rc.16",
"angular-xeditable": "~0.1.8",
"angular-smart-table": "~1.4.9",
"angularjs-toaster": "~0.4.8",
"ng-grid": "~2.0.13",
"ngImgCrop": "~0.2.0",
"ngstorage": "~0.3.0",
"oclazyload": "~0.5.1",
"textAngular": "~1.2.2",
"venturocket-angular-slider": "~0.3.2",
"videogular": "~0.7.0",
"videogular-controls": "~0.7.0",
"videogular-buffering": "~0.7.0",
"videogular-overlay-play": "~0.7.0",
"videogular-poster": "~0.7.0",
"videogular-ima-ads": "~0.7.0",
"jquery": "~2.1.3",
"animate.css": "~3.2.0",
"bootstrap": "~3.3.0",
"bootstrap-filestyle": "~1.1.2",
"bootstrap-slider": "*",
"bootstrap-touchspin": "~3.0.1",
"bootstrap-wysiwyg": "*",
"bower-jvectormap": "~1.2.2",
"bootstrap-chosen": "~1.0.0",
"chosen": "https://github.com/harvesthq/chosen/releases/download/v1.3.0/chosen_v1.3.0.zip",
"datatables": "~1.10.4",
"plugins": "datatables/plugins#~1.0.1",
"footable": "~2.0.3",
"font-awesome": "~4.2.0",
"fullcalendar": "~2.2.6",
"html5sortable": "*",
"moment": "~2.8.3",
"nestable": "*",
"screenfull": "~1.2.1",
"slimscroll": "~1.3.3",
"simple-line-icons": "~0.1.1",
"jquery_appear": "~0.3.3",
"jquery.easy-pie-chart": "~2.1.6",
"jquery.sparkline": "~2.1.2",
"flot": "~0.8.3",
"flot.tooltip": "~0.8.4",
"flot.orderbars": "*",
"bootstrap-daterangepicker": "~1.3.17",
"bootstrap-tagsinput": "~0.4.2"
2015年五月6日下午 1:56:44 在本地页面通过一个按钮调用系统linux命令

在本地页面通过一个按钮调用系统linux命令,初学node不知道怎么用 一下是一段调用系统命令的node代码,单独用 node test.js 命令测试正常,现在我想加进一个本地的HTML代码中,请问怎么添加? var exec = require(‘child_process’).exec;

exec("python test.py", function(error, stdout, stderr){ if ( !error ) { console.log(stdout); } else { console.log(error); } });

2015年五月6日下午 12:44:09 正则表达式笔记(三)

String.replace

细心的读者可能会发现,上篇文章我们遗漏了 String.replace 这个方法。String.replace 在 JS 中有着更加强大的用法和灵活性,所以这里剥离出来单独介绍。

API

String.replace(searchValue, replacement)

String.replace 同时支持进行正则表达式或者字符串替换,并返回一个新的字符串。因为我们的主题是正则表达式,所以接下来的内容,会以正则替换的情况为主。

默认情况下,String.replace只进行一次替换。若设置了 g 模式,则所有匹配到的字符串都会替换

参数说明

用法

字符串替换

'I am back end developer'.replace('back','front');
//"I am front end developer"

直接把字符串中的 back 替换成 front。当字符串中有两个 back,情况回事怎样呢?

'I am back end developer, you are back end developer'.replace('back','front');
//"I am front end developer, you are back end developer"

可以看到,第2个 back,并没有被替换。如果需要把其他 back 也一起替换,这个时候就需要用到正则表达式。

正则表达式替换

设置了 g 模式,全局替换。

'I am back end developer, you are back end developer'.replace(/back/g,'front');
//"I am front end developer, you are front end developer"

replacement 字符串中,还有一些特殊的变量可以使用。

特殊变量 说明
$1,$2,$3...$n 表示对应捕获分组匹配的文本
$& 与正则相匹配的字符串
$$ '$' 字符
$` 匹配字符串左边的字符
$' 匹配字符串右边的字符

有趣的字符串替换

使用 $& 操作匹配的字符串。

var str = '有趣的字符串替换';
str.replace(/有趣的字符串/,'($&)');

//"(有趣的字符串)替换"

使用 $$ 声明 $ 字符。

var str = '这个商品的价格是12.99';
str.replace(/\d+\.\d{2}/,'$$$&');

//"这个商品的价格是$12.99"

使用 $` 和 $' 字符替换内容

'abc'.replace(/b/,"$`");//aac
'abc'.replace(/b/,"$'");//acc

使用分组匹配组合新的字符串

'2015-05-06'.replace(/(\d{4})-(\d{2})-(\d{2})/,"$3/$2/$1")
//"06/05/2015"

函数参数

replacement是一个函数参数的时候,对字符串操作的灵活性将有一个质的提高。

说明

'Stirng.replace'.replace(/(\w+)(\.)(\w+)/,function(){
    console.log(arguments) // ["Stirng.replace", "Stirng", ".", "replace", 0, "Stirng.replace"]
    return '返回值会替换掉匹配到的字符'
})
参数 说明
match 匹配到的字符串(此例为 String.replace)
p1, p2, ... 正则使用了分组匹配时分组文本,否则无此参数(此例为 "Stirng", ".", "replace")
offset 匹配字符串的对应索引位置 (此例为 0)
source 原始字符串(此例为 String.replace)

案例 -- 样式属性的转换

把驼峰字符转换为 - 连接符形式

'borderTop'.replace(/[A-Z]/g,function(m){
    return '-'+ m.toLowerCase()
})

//"border-top"

- 连接符形式转换为驼峰形式

'border-top'.replace(/-\w/g,function(m){
    return m.slice(1).toUpperCase()
})

//"borderTop"

最后的牛刀小试

交给阅读者发挥的内容:

需要将Hello-World使用正则替换成 HWeolrllod

2015年五月6日下午 12:34:48 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日下午 12:33:48 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月6日中午 12:08:31 推荐一款可以在iPhone和iPad上读nodejs官方文档的app
专门为nodejs doc开发的,比dash的阅读体验更好,比如代码的高亮,链接的处理等。目前只支持iOS8
下载地址

https://itunes.apple.com/cn/app/nodejs-api/id983630798?mt=8

下载.png

页面截图

首页

api 详情

分类

2015年五月6日中午 11:58:58 [产品更新] SegmentFault 全新文章专栏上线

经过半个多月的努力,全新的 SegmentFault 文章终于和大家见面了。这次的全新改版,我们提出了文章专栏的理念:

  1. 所有用户都可以拥有自己的专栏
  2. 将更加注重和鼓励原创且具有启发性的用户内容
  3. 对相关的声望值做出调整

关于原创和启发性

原创的文章是自己学习和探索的结果,独立的思考会给他人更大的启发,会引导他人去发现、实现可能更加有趣的事。

故此,我们将更加注重和鼓励原创:

新版文章将在专栏设置页增加版权信息注明,原创文章将不再受版权问题的困扰。同时,专栏的首篇文章也将会被官方审阅,这也是对于原创优秀内容的鼓励。

版权信息设置.jpg

私人的,也是所有人的专栏

新版文章所有用户都将可以开始撰写自己的专栏:已经开通过博客的老用户,博客将自动升级为的专栏;还没有开通博客的用户,现在可以直接开通专栏了。

我们后续会推出官方认证专栏,如果你的文章写得足够好,会出现在网站所有用户的时间线上,这便是私人的、也是所有人的专栏。

相关声望值调整

新版文章的声望值权重也会相应作出上调:文章被投推荐票 +10,文章被收藏 +5。另外,如果你的文章被推作官方推荐文章或者精选文章,都会有相应的声望值增加,当然,推荐与加精更多是对你内容价值的认可。



其他更多的细节与优化,由你们来发现。

这是一个简易的技术专栏,是学习与分享知识的工具。这将是你的私人文章,请认真地撰写;这也将会是所有人的文章,热心地与大家切磋共享。


相关的产品建议和反馈提交请到我们的社区建设:0x.sf.gg
相关的技术 bug 提交请到我们的 GitHub Report:SegmentFault Report

2015年五月6日中午 11:51:44 Cnode社区回复操作是否为bug?

我在一个话题下添加回复完毕后,想跳回上一页页面,第一次点击浏览器的回退的按钮,却回不到上一页面,第二次点击就可以,是故意这样设计的还是?我是小白,请大神指点

2015年五月6日中午 11:44:28 寻求一种方式重启node的方式

虽然说生产环境老是重启不是很安全,但每次更新版本时都需要找PE来重启node,感觉好麻烦,有没有类似LAMP那种方式,比如我修改完php代码上线后能立即运行,nodemon启动方式不算。求大神们指点迷津。

2015年五月6日中午 11:41:58 alsotang commented on commit cnodejs/nodeclub@0f6cc14f6b
alsotang commented on commit cnodejs/nodeclub@0f6cc14f6b
@alsotang

是的 2015-05-06 11:37 GMT+08:00 Jackson Tian <notifications@github.com>:

2015年五月6日上午 11:01:32 [北京] NodeJS 前端后端开发攻城狮1名 8K-16K

【关于我们】 公司成立于14年底,核心团队成员来自IBM,联想,百度,并具有10年以上的丰富经验。现从事校园生活相关的移动互联网项目,因团队高速发展需要,急招有志于用技术改变生活的nodejs 前后端攻城狮1名

【任职要求】 Requirements

* Strong Javascript skills
* Knowledge of Node.js packages (Express, Async, Mongoose, Socket.io, Request, etc.)
* Experience with message and job queuing services (RabbitMQ, Redis, etc.)
* Very strong ability to design and develop scalable systems on top of Node.js
* Experience working with MongoDB, Mysql and Redis.
* Disciplined approach to testing and quality assurance, knowledge of Javascript testing tools.
* High understanding of computer science concepts such as: common data structures and algorithms, profiling/optimization

Responsibilities

* Build and deploy robust, manageable and scalable back ends
* Integrate 3rd party services via RESTful and streaming APIs
* Design and implement RESTful interfaces that exposes our data
* Rapidly fix bugs and solve problems
* Work closely with front-end teams to create optimally integrated solutions
* Plus Point:  have experience ionic framework and wechat JS SDK

【关于职位】 NodeJS 前端&后端开发工程师 工作地点:北京.海淀.上地 工作年限:3年以上 最低学历:本科 招聘人数:1 职位月薪:¥8 ,000 - 16,000 英语:读写流利,英语六级

【联系我们】 有意者请将您的简历直接直接发送至hr@guagua2shou.com,我们会尽快回复您,谢谢!

2015年五月6日上午 9:52:37 nodejs对http协议的支持

http协议的method 安全方法:HEAD, GET, OPTIONS, and TRACE 不安全的方法:POST, PUT and DELETE 测试了一下发现其中的Trace方法返回error; 想问下各位大神。这个trace方法会返回什么。怎么开启看看。

2015年五月6日上午 9:36:35 在阿里云使用npm安装pm2一直加载

untitled1.png

2015年五月6日早上 8:37:59 io.js前天发布了2.0.0版本

io.js v2.0.0 changelog 大版本变化。 更稳定了么?

2015年五月6日早上 7:10:15 [成都]GamEver招2名手游服务器端Nodejs工程师7~15k

关于我们(热血,专注,快乐,成长,勇敢,创造力)

我们是一群对游戏非常热爱,专注于制作精品的单纯家伙 多年的打磨学会了在商业和艺术之间寻找平衡 平均从业经验5年以上, 聚焦海外市场 行业顶尖美术和金牌制作人,资深欧美制作经验 产品专注三个核心:Creative, Passionate, Imapctive

公司成立于2014年1月,规模25人,现在已有一款上市产品Age of warriors,(中国区暂时还没上线)获得了美国区苹果APPSTORE三次全球首页推荐。

我们期待的伙伴: 3d客户端程序:

至少熟悉一种3d游戏引擎。 熟悉openGL图形管线,能独立学习制作游戏需要的shader效果 有扎实的c++编码功底 爱好技术,踏实好学,对游戏有爱

服务器程序:

踏实靠谱,热爱游戏 不需要您一定要是技术大牛,只要你基础扎实愿意接受新技术,快速学习 1~2年编码经验,熟悉nodejs,mongodb,或者python,ruby,mysql,erlang 团队协作精神,自我管理良好,有工程师文化基因。 善于沟通,有同理心

系统/数值/UI策划:

踏实靠谱,心态开放成熟,渴望做出富有灵魂的作品 有激情,有动力,平时热衷于各种平台的游戏 游戏经验丰富,了解各类游戏的特色与设计思路 熟悉玩家的消费行为以及游戏行为,喜欢和玩家一起交流 良好的语言沟通和书面表达能力(熟悉策划案撰写,意图表达明确,条理清晰) 严密谨慎的逻辑能力,对RPG以及SLG类游戏数值平衡有独特兴趣和偏好 或者擅长UI设计,熟悉至少一种动画关卡编辑器 有较好的英文阅读和书面日常交流能力

游戏文案策划:

沟通能力好,主动性责任心强。 有激情,有动力,平时热衷于各种平台的游戏。 拥有游戏经验丰富,了解各类游戏的特色与设计思路 。 熟悉玩家的群体,喜欢和玩家一起交流。 良好的语言沟通和书面表达能力(能熟练撰写有想象力的文案剧情和幽默的人物对白)。 喜欢看美剧,看小说,熟悉玩家语言。

具有较好的英文阅读写作能力。

游戏UI设计美术:

有丰富的UI设计经验, 热爱游戏,对各种题材的UI又自己独到见解 3年以上工作经验为佳。

FlashUI美术:

熟悉flash,懂基本as2.0 熟悉各种动画制作和特效制作。 有1到2年flash制作经验 如果有个人业余小游戏作品,那是极好的。

我们将提供给您: 7~15k左右的基本薪资待遇(具体和能力挂钩) 每年最高4次项目奖金,年底双薪 节假日过节奖金,项目完成后的集体旅行 灵活可变的弹性上班时间,宽松自由的团队氛围 一群靠谱的伙伴,快乐生活,做好游戏 低层级公司,没有不做事混日子的伙伴。 每一个产品都会是精品设计,绝对不会开发浪费大家光阴的项目。 好的技术学习讨论氛围 macpro笔记本开发

如果您也对制作游戏充满热忱,渴望能有一群靠谱的伙伴一起快乐工作,一起旅行,一起开创未来!请联系xiao.wen@gamever.org screen696x696.jpeg

2015年五月6日早上 6:51:43 关于Express中使用req.pipe(busboy)的用途是什么?是多文件上传吗?

大家好:

我在看源码时,发现req.pipe方法,但在google上没有找到解释其具体是什么含义?(我猜是不是,多文件上传的意思?) express API上也没有找到。

坛子里的先闻道者,麻烦帮忙下,谢谢。

code: exports.upload = function (req, res, next) { req.busboy.on('file’, function (fieldname, file, filename, encoding, mimetype) { store.upload(file, {filename: filename}, function (err, result) { if (err) { return next(err); } res.json({ success: true, url: result.url, }); }); });

req.pipe(req.busboy); };

2015年五月6日凌晨 12:50:57 异步流程控制:7 行代码学会 co 模块

首先请原谅我的标题党(●—●),tj 大神的 co 模块源码200多行,显然不是我等屌丝能随便几行代码就能重写的。只是当今大家都喜欢《7天学会xx语言》之类的速效仙丹,于是我也弄个类似的名字《7行代码学会co模块》来博眼球。

为了避免被拖出去弹小JJ,还是先放出所谓的 7 行代码给大家压压惊:

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
} 

万恶的回调

对前端工程师来说,异步回调是再熟悉不过了,浏览器中的各种交互逻辑都是通过事件回调实现的,前端逻辑越来越复杂,导致回调函数越来越多,同时 nodejs 的流行也让 javascript 在后端的复杂场景中得到应用,在 nodejs 代码中更是经常看到层层嵌套。

以下是一个典型的异步场景:先通过异步请求获取页面数据,然后根据页面数据请求用户信息,最后根据用户信息请求用户的产品列表。过多的回调函数嵌套,使得程序难以维护,发展成万恶的回调

javascript$.get('/api/data', function(data) {
    console.log(data);
    $.get('/api/user', function(user) {
        console.log(user);
        $.get('/api/products', function(products) {
            console.log(products)
        });
    });
});

异步流程控制

$.get('/api/data')
.then(function(data) {
    console.log(data);
    return $.get('/api/user');
})
.then(function(user) {
    console.log(user);
    return $.get('/api/products');
})
.then(function(products) {
    console.log(products);
});
co(function *() {
    var data = yield $.get('/api/data');
    console.log(data);
    var user = yield $.get('/api/user');
    console.log(user);
    var products = yield $.get('/api/products');
    console.log(products);
});

co 模块

上文已经简单介绍了co 模块是能让我们以同步的形式编写异步代码的 nodejs 模块,主要得益于 ES6 的 generator。nodejs >= 0.11 版本可以加 --harmony 参数来体验 ES6 的 generator 特性,iojs 则已经默认开启了 generator 的支持。

要了解 co ,就不得不先简单了解下 ES6 的 generator 和 iterator。

Iterator

Iterator 迭代器是一个对象,知道如何从一个集合一次取出一项,而跟踪它的当前序列所在的位置,它提供了一个next()方法返回序列中的下一个项目。

javascriptvar lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
var pair = it.next(); 
console.log(pair); // ["name", "JavaScript"]
pair = it.next(); 
console.log(pair); // ["birthYear", 1995]
pair = it.next(); // A StopIteration exception is thrown

乍一看好像没什么奇特的,不就是一步步的取对象中的 key 和 value 吗,for ... in也能做到,但是把它跟 generator 结合起来就大有用途了。

Generator

Generator 生成器允许你通过写一个可以保存自己状态的的简单函数来定义一个迭代算法。
Generator 是一种可以停止并在之后重新进入的函数。生成器的环境(绑定的变量)会在每次执行后被保存,下次进入时可继续使用。generator 字面上是“生成器”的意思,在 ES6 里是迭代器生成器,用于生成一个迭代器对象。

function *gen() {
    yield 'hello';
    yield 'world';
    return true;
}

以上代码定义了一个简单的 generator,看起来就像一个普通的函数,区别是function关键字后面有个*号,函数体内可以使用yield语句进行流程控制。

javascriptvar iter = gen();
var a = iter.next();
console.log(a); // {value:'hello', done:false}
var b = iter.next();
console.log(b); // {value:'world', done:false}
var c = iter.next();
console.log(c); // {value:true, done:true}

当执行gen()的时候,并不执行 generator 函数体,而是返回一个迭代器。迭代器具有next()方法,每次调用 next() 方法,函数就执行到yield语句的地方。next() 方法返回一个对象,其中value属性表示 yield 关键词后面表达式的值,done 属性表示是否遍历结束。generator 生成器通过nextyield的配合实现流程控制,上面的代码执行了三次 next() ,generator 函数体才执行完毕。

co 模块思路

从上面的例子可以看出,generator 函数体可以停在 yield 语句处,直到下一次执行 next()。co 模块的思路就是利用 generator 的这个特性,将异步操作跟在 yield 后面,当异步操作完成并返回结果后,再触发下一次 next() 。当然,跟在 yield 后面的异步操作需要遵循一定的规范 thunks 和 promises。

yieldables

The yieldable objects currently supported are:
- promises
- thunks (functions)
- array (parallel execution)
- objects (parallel execution)
- generators (delegation)
- generator functions (delegation)

7行代码

再看看文章开头的7行代码:

javascriptfunction co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}

首先生成一个迭代器,然后执行一遍 next(),得到的 value 是一个 Promise 对象,Promise.then() 里面再执行 next()。当然这只是一个原理性的演示,很多错误处理和循环调用 next() 的逻辑都没有写出来。

下面做个简单对比:
传统方式,sayhello是一个异步函数,执行helloworld会先输出"world"再输出"hello"

function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
function helloworld() {
    sayhello();
    console.log('world');
}
helloworld();

输出

> "world"
> "hello"

co 的方式,会先输出"hello"再输出"world"

javascriptfunction co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}
function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
co(function *helloworld() {
    yield sayhello();
    console.log('world');
});

输出

javascript> "hello"
> "world"

消除回调金字塔

假设sayhello/sayworld/saybye是三个异步函数,用真正的 co 模块就可以这么写:

var co = require('co');
co(function *() {
    yield sayhello();
    yield sayworld();
    yield saybye();
});

输出

javascript> "hello"
> "world"
> "bye"

参考

《es7-async》 https://github.com/jaydson/es7-async
《Generator 函数的含义与用法》 http://www.ruanyifeng.com/blog/2015/04/generator.html
《Iterator》 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glob...

2015年五月6日凌晨 12:30:43 关于express中app.post的一个问题

var express = require(‘express’); var app = express(); app.listen(3000); var bodyParser = require(‘body-parser’) var hbs = require(‘hbs’); var spawn = require(‘child_process’).spawn;

app.set('view engine’, ‘html’); app.engine('html’, hbs.__express); app.use(express.static(‘public’)); app.use(bodyParser.urlencoded({ extended: false }));

ls = spawn(‘某程序’);

app.post('/’, function(req, res) { var info = req.body;//请求前端得到表单的值 ls.stdin.write(“calculate(“+info.xxx+”)"+ “\n”);//子线程调用程序完成一些计算 ls.stdout.on('data’, function(data) { //做一些数据的处理 res.render('…’,{data:…});//然后反馈呈现到前端页面上 }); }); 这样做的话提交一次表单运行正常,第二次就会报错说Can’t set headers after they are sent, 查了之后说是回调有问题,后来把res.render去掉,console一些需要的结果,发现第一次提交表单运算后输出一次,当第二次提交表单的时候输出两次,第三次提交输出三次。。。

下面是把ls.stdout.on拿到post外面,这样做的话提交一次表单运算后输出一次,不会出现上面重复的问题,但是这里就不能用res.render()去把结果呈现在前端上了,因为在post外面,做不了res的反馈。。。 app.post('/’, function(req, res) { var info = req.body; ls.stdin.write("calculate(info.xxx+ “\n”);//子线程调用程序完成一些计算 }); ls.stdout.on('data’, function(data) { //处理计算得到后的数据。console.log一些需要的结果,方便在终端查看结果 });

请教懂的小伙伴们,怎么做能一举两得呐?我是小白一枚。。。

2015年五月6日凌晨 12:05:52 小白请教一个简单的session问题

前端提交数据表单给nodejs,调用一个计算软件计算得到结果后反馈到前端页面上,在localhost:3000上能实现这个过程了,这是单用户情况。 现在要加多用户进去,用session模块,怎么设置能将调试的这台电脑作为服务器,然后让其他电脑访问完成提交数据运行计算软件得到结果的这个功能。

2015年五月5日晚上 11:46:21 [MySQL]查询学生选课的情况(一)

这是我工作遇到的问题,现在自己设计一个简化的类似场景,现实中这样的数据表设计可能有很多不合理的地方。
首先看表结构:

+--------+--------------+------+-----+---------+-------+
| Field  | Type         | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| id     | varchar(38)  | NO   | PRI | NULL    |       |
| name   | varchar(255) | YES  |     | NULL    |       |
| course | varchar(300) | YES  |     | NULL    |       |
+--------+--------------+------+-----+---------+-------+

这里只是记录学生的ID,名字,还有选课的科目,科目有很多,在没有关联表的情况下,这么多科目只保存在一个字段中,用逗号隔开。
再看一些数据:

+--------------------------------------+--------+--------------------------------+
| id                                   | name   | course                         |
+--------------------------------------+--------+--------------------------------+
| 32268995-f33d-11e4-a31d-089e0140e076 | 张三   | Math,English,Chinese           |
| 3d670ef2-f33d-11e4-a31d-089e0140e076 | 李四   | Math,English,Chinese,Algorithm |
| 475d51a6-f33d-11e4-a31d-089e0140e076 | 李五   | Math,English,Algorithm         |
| 547fdea0-f33d-11e4-a31d-089e0140e076 | 王小明 | Math,English,Japanese          |
| 656a247a-f33d-11e4-a31d-089e0140e076 | 曹达华 | Chesses                        |
+--------------------------------------+--------+--------------------------------+

那么如何查找到选择了Math课程的学生?

想想使用关联表的时候,张三, 李四, 李五, 王小明这四个人都一条选择了Math这门课的记录,还有其他不是Math的记录。此时要查找选择了Math课程的学生,一般使用IN语句就可以了:

select * from student_course where course IN ('Math');

如果要查找选择了MathAlgorithm课程的学生呢:

select * from student_course where course IN ('Math', 'Algorithm');

如此,回到原来的问题,如果我设计一个类似IN一样的函数,那么就可以解决这个问题了。
这个流程我们可以想象出来,是这样子的:
我们取张三的课程信息Math,English,Chinese,首先切割成Math, English,Chinese三个字段,然后分别与与查找条件做比较,类似'Math'.indexOf('Math');,'Math'.indexOf('English');...
只要找到一个就认为符合查找条件。
同样的,如果要查找选择了MathAlgorithm课程的学生,比较过程就变成了:
'Math,Algorithm'.indexOf('Math');,'Math,Algorithm'.indexOf('English');...

切割函数 getSplitTotalLength, getSplitString

CREATE DEFINER = `root`@`%` FUNCTION `getSplitTotalLength`(`f_string` varchar(500),`f_delimiter` varchar(5))
 RETURNS int(11)
BEGIN
    # 计算传入字符串能切分成多少段 
   return 1+(length(f_string) - length(replace(f_string,f_delimiter,''))); 

    RETURN 0;
END;
CREATE DEFINER = `root`@`%` FUNCTION `getSplitString`(`f_string` varchar(500),`f_delimiter` varchar(5),`f_order` int)
 RETURNS varchar(500)
BEGIN
    #拆分传入的字符串,分隔符,顺序,返回拆分所得的新字符串
    declare result varchar(500) default '';
    set result = reverse(substring_index(reverse(substring_index(f_string,f_delimiter,f_order)),f_delimiter,1));
    RETURN result;
END;

类似IN的那个函数 isInSearch

CREATE DEFINER=`root`@`%` FUNCTION `isInSearch`(f_course VARCHAR(300), f_string VARCHAR(300)) RETURNS INT
BEGIN

    DECLARE len INT DEFAULT 0;
    DECLARE idx INT DEFAULT 0;
    DECLARE item_code VARCHAR(300) DEFAULT '';
    DECLARE item_index INT DEFAULT 0;
    IF f_course IS NULL THEN
        RETURN 0;
    END IF;
    SELECT getSplitTotalLength(f_course, ',') INTO len;
    label: LOOP
        SET idx = idx + 1;
        IF idx > len THEN
            LEAVE label;
        END IF;
        SELECT getSplitString(f_course , ',', idx) INTO item_code;
        # f_string.indexOf(item_code) > -1 ?
        SELECT LOCATE(item_code, f_string) INTO item_index;
        IF item_index > 0 THEN
            RETURN 1; # got one
        END IF;
    END LOOP label;
    RETURN 0;
    END;

这里说下locate函数,locate(item_code, f_string),如果item_codef_string的子串,返回的结果大于0,是item_codef_string的起始下标(从1开始算起),这个一般的indexOf函数有些不同。

mysql> select locate('Math','Math,Algorithm');
+---------------------------------+
| locate('Math','Math,Algorithm') |
+---------------------------------+
|                               1 |
+---------------------------------+
mysql> select locate('Math','Chinese,Math,Algorithm');
+-----------------------------------------+
| locate('Math','Chinese,Math,Algorithm') |
+-----------------------------------------+
|                                       9 |
+-----------------------------------------+
mysql> select locate('Math','Chinese,Algorithm');
+------------------------------------+
| locate('Math','Chinese,Algorithm') |
+------------------------------------+
|                                  0 |
+------------------------------------+

可以看到isInSearch函数返回的是INT类似,因为MySQLIN也是这样的机制。

mysql> select 'Math' in ('Math','Algorightm');
+---------------------------------+
| 'Math' in ('Math','Algorightm') |
+---------------------------------+
|                               1 |
+---------------------------------+
mysql> select 'Math' in ('Chinese','Algorightm');
+------------------------------------+
| 'Math' in ('Chinese','Algorightm') |
+------------------------------------+
|                                  0 |
+------------------------------------+

如果存在返回1,不存在返回0。

在SELECT语句中使用自定义的函数

mysql> select * from student_course where isInSearch(course, 'Math');
+--------------------------------------+--------+--------------------------------+
| id                                   | name   | course                         |
+--------------------------------------+--------+--------------------------------+
| 32268995-f33d-11e4-a31d-089e0140e076 | 张三   | Math,English,Chinese           |
| 3d670ef2-f33d-11e4-a31d-089e0140e076 | 李四   | Math,English,Chinese,Algorithm |
| 475d51a6-f33d-11e4-a31d-089e0140e076 | 李五   | Math,English,Algorithm         |
| 547fdea0-f33d-11e4-a31d-089e0140e076 | 王小明 | Math,English,Japanese          |
+--------------------------------------+--------+--------------------------------+

mysql> select * from student_course where isInSearch(course, 'Chinese,Japanese');
+--------------------------------------+--------+--------------------------------+
| id                                   | name   | course                         |
+--------------------------------------+--------+--------------------------------+
| 32268995-f33d-11e4-a31d-089e0140e076 | 张三   | Math,English,Chinese           |
| 3d670ef2-f33d-11e4-a31d-089e0140e076 | 李四   | Math,English,Chinese,Algorithm |
| 547fdea0-f33d-11e4-a31d-089e0140e076 | 王小明 | Math,English,Japanese          |
+--------------------------------------+--------+--------------------------------+
2015年五月5日晚上 11:05:02 PHP 到 Node.js的路该如何走?

无js基础,php出身,该通过什么方式能够快速掌握Node.js?

2015年五月5日晚上 10:00:43 求大神给建立个符合要求mongodb集合

nodejs新手,想做个社交聊天的玩具(就是图个新鲜)现在遇见难题了,跪求大神帮助 我数据库用的是mongodb,建立了个用户表 var users=new Schema( { email:String,//邮箱 password:String,//密码 username:String,//用户昵称 avatar:String,//头像 sex:String,//性别 auth:String,//验证码 condition:Boolean,//账号是否激活 address:String,//地址 marry:String,//婚姻状况 proclammation:String,//个性宣言 friends:[{fid:Object,//朋友id remarks:String,//备注 group:String,//分组 allow:Boolean}] }); 我现在遇见的问题是如何通过fid获取朋友的信息, 我刚开始是这样做的 if(docs.friends.length>0){ docs.friends.forEach(function(b){ users.findById(b.fid,function(err,frids){ if(err){ console.log(“加载朋友时候失败了”); } var arr={ "userId":frids._id, "username":frids.username, "avatar":frids.avatar, "remarks": b.remarks, "group": b.group } frs.push(arr);

                    });
                    });
                     res.render('coze',{frs:frs});

但是我对回调函数,异步又不熟悉,设计上有问题,所以最后得不到frs 里的数据, 然后我尝试这用Population,发现friends 里关联的就是users ,而friends 就在users 中,怎么办 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 我的目的其实很简单,就是实现类似QQ那样加载好友信息,同时加载备注的功能,大神给个解决方案吧 参考资料也可以的

2015年五月5日晚上 8:33:45 alsotang commented on pull request cnodejs/nodeclub#521
alsotang commented on pull request cnodejs/nodeclub#521
@alsotang

域名还不在我手上,想去乌云认领都难。

2015年五月5日晚上 8:29:50 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 8:27:08 alsotang commented on pull request cnodejs/nodeclub#521
alsotang commented on pull request cnodejs/nodeclub#521
@alsotang

呃。不好意思刚看到。。。已经修复了。 0f6cc14

2015年五月5日晚上 8:27:08 alsotang closed pull request cnodejs/nodeclub#521
alsotang closed pull request cnodejs/nodeclub#521
@alsotang
security fix:wooyun-2010-0112230
2015年五月5日晚上 8:22:08 How to structure models in NodeJS

我想请问下,在nodeJS 中Model 的结构是咋样的? 我之前是做Java/C# 程序的, 在以往我的model 全部都是单独作为一个文件来存放(POJO),然后会有一个专门的数据访问类,或则一个Repository,例如:

– 纯model 文件 publi class User{ poublic int Id {get; set} public string FirstName { get; set; } public string LastName { get; set; } … } – 数据访问类 public class UserRep{ public IEnumerable<User> All(); public User Get(); … }

但是,我看网上有好些教程,大都是把 Model 的字段和方法放在同一个文件里头: var User = function(id, firstname, lastname) { this.Id=id; … } // 先是一些静态方法 User.findById = function() { } // 然后还有些 实例方法 User.prototype.save = function() {}

这样做我感觉怪怪的, 数据, 和数据访问层不应该分开吗?还有几个疑问(我不用Mongodb, 我用的是传统的RDBS):

  1. 如何做 data validation;
  2. 单元测试如何集成;
  3. 如果使用ORM 该怎么样用;
2015年五月5日晚上 7:46:08 iojs v2.0 开启 use strong 之后。。。

就可以和var说再见了~~一遍es6、7写下来,如果再使用flow,那感觉基本就不是js了⊙﹏⊙b。 iojs-new-features

2015年五月5日晚上 7:44:21 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 7:43:50 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 7:32:41 正则表达式笔记(二)

JS 中的正则表达式

概述

在 JS 中,正则表达式是个 RegExp 对象,具有 exectest 方法。而在 String 对象中,也存在 matchreplacesearchsplit 操作正则表达式的方法。

声明

JS 中的正则表达式有两种声明方式(对象字面量 / 构造函数),都会生产一个 RegExp 对象的实例。

/pattern/flags
new RegExp(pattern[, flags])

RegExp 对象

实例

var pattern = /quick\s(brown).+?(jumps)/ig;
var pattern = new RegExp("quick\\s(brown).+?(jumps)","ig");

实例之后的 pattern 对象就具有以下属性:

注意使用构造函数声明正则表达式的时候,需合理使用转义符。

方法

RegExp.exec 检索字符串中指定的值。返回一个结果数组。该方法总是返回单词匹配的结果。

在正则表达式设置了 g 模式的情况下,会同时更新 RegExp 对象的 lastIndex 属性。

var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
// console.log(result)
// ["Quick Brown Fox Jumps", "Brown", "Jumps"]

var pattern = /ab*/g;
var str = 'abbcdefabh';
var matchArray;
while ((matchArray = pattern.exec(str)) !== null) {
  var msg = 'Found ' + matchArray[0] + '. ';
  msg += 'Next match starts at ' + pattern.lastIndex;
  //console.log(msg);
  // Found abb. Next match starts at 3
  // Found ab. Next match starts at 9
}

使用循环的时候,不要把正则表达式的操作放置在循环体中(这样每次都会重新生成 RegExp 对象),同时必须设置全局模式,可能会造成死循环。

RegExp.test 执行检索字符串中指定值的操作。返回 truefalse

var pattern = /^\d{4}-\d{2}-\d{2}$/;
var result = re.test('2010-12-20');

console.log(result)
// true

在正则表达式设置了 g 模式的情况下,跟 RegExp.exec 一样,会同时更新 RegExp 对象的 lastIndex 属性。

var pattern = /^\d{4}-\d{2}-\d{2}$/g;
pattern.test('2010-12-20'); // true
pattern.test('2010-12-20'); // false

RegExp.test 在匹配成功之后,各个捕获分组的文本会保存下来,用 RegExp.$1RegExp.$2··· 就可以获得,不过,保存整个表达式匹配文本的 RegExp.$0 并不存在。

String 对象

方法

String.match 返回一个结果数组或null

在正则表达式设置了 g 模式的情况下,match 默认返回所有可能的匹配结果。

var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var re = /[A-E]/gi;
var matches = str.match(re);

console.log(matches);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']

如果正则表达式没有设置 g 模式,那么 match 方法返回的结果与 RegExp.exec() 一致。

var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var re = /[A-E]/i;
var matches_1 = str.match(re);
var matches_2 = re.exec(str)

console.log(matches_1, matches_2)

//[ 'A',
  index: 0,
  input: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' ]

该返回结果包含匹配的字符串,还有一个额外的 index 属性,表示匹配字符串的位置,input 属性即是所传入的字符串。

String.search 返回第一次匹配的位置或 -1

该方法用来寻找某个正则表达式在字符串中第一次匹配成功的位置,失败的时候返回 -1

这个方法只能找到“第一次”,即使设置了 g 模式,结果也不会有任何变化,所以如果需要使用一个正则表达式多次验证字符串,调用该方法判断结果是否为 0,是更好的办法。

"1234".search(/^\d+$/g) == 0 // true
"5678".search(/^\d+$/g) == 0 // true

var pattern = /^\d+$/g;
pattern.test("1234"); // true
pattern.test("5678"); // false

String.split 使用一个正则表达式来切割字符串。返回数组

正则表达式是否设置了g模式对结果没有影响。

var matchArray = "a b c".split(/\s+/);

console.log(matchArray);
// ["a", "b", "c"]

也可以设置第二个参数 limit,指定返回数组的数目。在 JS 中,该参数取值的结果如下

取值 结果
limit < 0 || limit > n 等同于未指定
0<=limit<=n 返回一个包含 n 元素的数组
var matchArray = "a b c".split(/\s+/,2);

console.log(matchArray);
// ["a", "b"]
2015年五月5日晚上 7:24:48 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月5日晚上 6:39:24 node js callback 的一些问题

小弟最近在练习node js 但总是遇到一个问题不知道该怎么解决 是关于callback的问题 举个例子来说 mysql 模塊中對mysql下query

var rs; connection.query('SELECT 1 + 1 AS solution’, function(err, rows, fields) { if (err) throw err; rs = rows; console.log('The solution is: ', rows[0].solution); });

根据官方文件 返回的结果 会传给rows 但我在外面宣告一个变数去里面把rows传给他 但之后console看却是undefine 不确定是不是callback函数的范围问题

如果我想要对结果作处理一定要在callback里面做吗? 得到的结果可以传出来吗?这个地方我一直有问题观念不是很懂 所以来这边请教一下各位

2015年五月5日晚上 6:16:05 关于MongoDB 和 缓存

MongoDB 号称查询已经非常快,而且热数据是在内存上的,那还有必要专门优化吗?

2015年五月5日晚上 6:07:28 求解答socket.io client代码处理方案

socket.io在客户端需要写相应的代码去请求server端的事件。那这样就相当于把client 的js代码暴露在浏览器,这样不就很危险,相当于用户可以通过编写js去请求我的server服务,想问问各位有什么好的处理方案。例如js客户端代码加密(初步方案而已)。

2015年五月5日下午 4:49:16 在用npm start 启动项目的时候,会出现如图情况。

cnodejs.png

启动就是用express生成的项目,在线等。

对了,补充一下。是window7系统

2015年五月5日下午 2:57:14 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年五月5日下午 2:13:42 来帮我做个小调研《Node使用情况小调查》

问卷地址在这里:http://survey.taobao.com/survey/AgT436qK

不用登陆。只有5个小题。么么哒。

调研已经结束,谢谢各位。

2015年五月5日下午 2:06:55 糗百的数据迁移实践

糗事百科(以下简称“糗百”)被誉为移动互联网时代的新娱乐手段,其上海量真实用户的糗事深受喜爱,每天有1亿次动态请求,峰值请求数为每秒30000次。面对如此高的并发访问量,糗百原来自建的平台越来越难以支撑,开始出现服务器过载、跨机房同步延时大、图片中心磁盘I/O成为瓶颈等问题。

为了解决这些刚性的服务压力,优化用户的服务体验,并考虑到七牛对静态资源存储的强大技术实力和优秀的解决方案,糗百决定将图片存储迁移到七牛平台上,并开始使用七牛提供的CDN服务。本文将结合糗百的数据迁移实践,来详细讲述如何在不中断服务的情况下,将海量数据平滑迁移到七牛平台的全过程。

qrsync+镜像存储打造平滑迁移方案

传统的数据迁移方案是:关掉网站原来的数据上传通道,所有数据变成只读,然后将所有数据上传到新的存储节点,再将上传入口改为新的存储节点,之后开放网站的上传功能。这样带来的问题是,数据迁移过程中,用户长时间不能进行上传操作,用户体验非常差。如何解决这个问题呢?

针对糗百这么大体量的应用,七牛提供的数据迁移方案——上传工具qrsync+镜像存储,很好地绕开了传统迁移方案所带来的问题。糗百先通过七牛的数据上传工具qrsync将大量冷数据传到七牛平台上,并将数据访问地址切换成七牛的域名。由于用户生成的大量热数据还在糗百自己的平台上,为了不出现数据丢失的情况,保证用户访问的流畅性,糗百选用了七牛的镜像存储服务。

七牛的镜像存储为整个数据迁移过程提供良好的过渡支持作用。当用户访问的数据不在七牛平台上时,镜像存储服务将回糗百源站抓取数据,并保存在七牛平台上。故此,镜像存储对每个资源只需回源一次,后续访问的时候就不再回源了。
图片描述

随后,为了进一步缓解糗百源站的I/O压力,糗百对旧有系统做了一次版本升级,将新系统的图片存储直接放在七牛平台上。新版本的用户可以顺畅地将数据上传到七牛平台上,并实现访问,而旧系统的App版本还会有一部分用户在使用。这时,就要在一段时间内保证两套系统可用。但旧系统的App用户所产生的数据还是会被上传到糗百的自建平台中,在用户第一次访问这些数据时,镜像存储服务对糗百源站做回源,很好地确保了这部分数据的可用性。由于目前App客户端的版本更新速度比较快,因此在所有用户都更新成新版系统,源站的回源流量逐渐趋于0时,就可以将镜像功能删除了。

就这样,在用户毫无感知的情况下,糗百轻松实现了对图片存储的迁移,平稳地解决了图片中心磁盘I/O的瓶颈问题。

镜像存储的使用方法

假设源站所有的图片,放在一个叫img.example.com的子域里。那么平滑迁移的方式是:

1、在七牛上建立一个镜像bucket,设定源站为img.example.com。假设镜像bucket是example-img,到空间设置的域名设置中即可找到形式为7xiuqc.com1.z0.glb.clouddn.com的七牛域名;
2、将所有对外使用的图片的域名改为7xiuqc.com1.z0.glb. clouddn.com;
3、如果网站数据是UGC(用户产生内容)的,调整上传流程,传到七牛的镜像 bucket,这样源站就变成只读;
4、使用qrsync同步工具将历史数据全部同步到七牛的镜像bucket。

如此就完成了整个迁移过程。此时img.example.com这个源站就可以废弃不用了。

结语

相信数据资源高速增长这样的“甜蜜负担”,是很多企业都会遇到的。而如何借助云服务来合理扩容,如何在不中断服务的前提下,平滑地实现数据迁移,将成为决定企业未来命运的关键一环。七牛云存储不仅能为企业用户稳定高效的底层存储平台,镜像存储等优质的服务更能在数据迁移过程中提供强大的助力。此外,完成数据迁移之后,七牛提供的丰富的图片、音视频处理功能也为包括糗百在内的诸多企业带来了很大的惊喜。后续我们将专门撰文分享这部分内容。

2015年五月5日中午 11:55:15 CentOS 6.x 升级 Git

准备

说明

公司服务器为centos,安装git后的默认版本是1.7.1,在执行git clone命令时报错如下:

    fatal: HTTP request failed

经过一番搜索终于找到可行的办法,即为升级git版本,升级时间比较长,需要比较好的网络支持.

git版本检测

CentOS下使用git --version 检测git的版本

# git --version
git version 1.7.1

系统检测

# cat /etc/redhat-release
CentOS release 6.5 (Final)
# uname -a
Linux rmhost 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

通过以上信息可知系统版本为 CentOS 6.5 64位

升级

1.导入证书

# rpm --import http://apt.sw.be/RPM-GPG-KEY.dag.txt

2.安装RPMForge源

这里查找对应的版本,比如我这里根据系统版本选择了rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm,右键拷贝地址, 粘贴到命令rpm -i命令后面执行

# rpm -i http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm

package rpmforge-release-0.5.3-1.el6.rf.x86_64 is already installed

符合系统版本的文件可能有多个,选一个较新的即可.

3.更新rpmforge-extra源

# yum --enablerepo=rpmforge-extras update

途中会有选项Is this ok [y/N]:询问是否下载, 如果选了y会更新所有的软件到匹配的最新版本,包括git,如果选N也可以手动安装git到最新版

这里建议选择N,选y需要较长时间(我当前网速下测试为一小时左右),一些软件升级后可能需要重新配置才能起作用, 比如MySQL从5.1升级到了5.5, 由于未配置直接导致我在线的两个项目无法运行, 只能手忙脚乱的赶紧修改配置,如果你不幸出了同样的问题, 可以到文章末尾找到解决办法

4.查找符合系统的git版本

通过下面的命令查找(推荐)
    # yum --enablerepo=rpmforge-extras provides git
    git-1.7.12.4-1.el6.rfx.x86_64 : Git core and tools
    Repo        : installed
    匹配来自于:
    Other       : 提供依赖满足:git
或者在软件库中查找

这里找到系统能支持的git最新版本

5.安装git

# yum --enablerepo=rpmforge-extras install perl-Git-1.7.12.4-1.el6.rfx.x86_64.rpm 
# git --version
git version 1.7.12.4

MySQL升级后不能启动的解决办法

系统使用rpm源升级了所有软件, MySQL从5.1升级到了5.5, 启动的时候抛出异常:

MySQL Daemon failed to start.
正在启动 mysqld:                                   [失败]

原因:

MySQL升级之后,由于配置文件/etc/my.cnf还是原来5.1的,对5.5已经不适用了,所以出错

解决办法:

用MySQL-5.5的配置文件替换原来的/etc/my.cnf,具体操作

cp /usr/share/mysql/my-medium.cnf /etc/my.cnf

注意:MySQL配置模板文件共有5个:my-huge.cnf、my-innodb-heavy-4G.cnf、my-large.cnf、mymedium.cnf、my-small.cnf,根据自己的服务器硬件配置选择相应的模板文件即可

参考

CentOS升级git:

MySQL无法启动:

2015年五月5日中午 11:39:23 有人用过apache的ab test测试post请求么?post请求的文件正确格式是啥?

求问,网上搜了很多都只介绍了ab test的基本用法,测post请求的还真难搜到,我想知道如果我post一个复杂的json数据(比如对象包含对象,包含数组这种结构的),post请求的file文件正确格式是啥?有什么工具或方法可以帮我把json数据转换成正确的post格式么?谢谢!

2015年五月5日中午 11:37:38 【北京】Melotic招聘 Nodejs/ops工程师(15k- 30k)

【关于我们】 Melotic成立于2014年年初,以香港为基地的数字资产交换和流通平台,在大陆、台湾、美国分别设有分公司,平台应用了革命性的金融技术,将迅速在全球多个国家开展业务。 团队已是获美国LIGHT SPEED VENTURE风投支持,CEO是斯坦福大学毕业并在美国硅谷创业多年的JACK WANG, 中英文双语工作环境,工作地点为团结湖地铁站附近的三里屯创业空间科技寺。 【关于职位】 Nodejs/ops工程师,后端工程师,国际平台,只要你有能力,一切都不是问题 工作地点:北京市 工作年限:3年以上 最低学历:本科 招聘人数:1 职位月薪:¥15,000 - 30,000 【职责&要求】 岗位职责 Responsibilities

任职要求 Requirements

Desired

【联系我们】 如果您对工作职位感兴趣,请将您的简历或情况发送到邮箱sindy@melotic.com联系我们,我们会尽快回复~

2015年五月5日中午 11:36:05 CentOS 7 安装 Gitlab

安装基本系统与依赖包

安装 Gitlab 依赖的工具

bashyum -y update
yum -y groupinstall 'Development Tools'
yum -y install readline readline-devel ncurses-devel gdbm-devel glibc-devel tcl-devel openssl-devel curl-devel expat-devel db4-devel byacc sqlite-devel libyaml libyaml-devel libffi libffi-devel libxml2 libxml2-devel libxslt libxslt-devel libicu libicu-devel system-config-firewall-tui git redis ruby sudo wget crontabs logwatch logrotate perl-Time-HiRes

安装 Redis

访问 http://www.redis.io/download,下载 Redis 源代码。

bashwget http://download.redis.io/releases/redis-3.0.0.tar.gz
tar zxvf redis-3.0.0.tar.gz
cd redis-3.0.0
make

若在编译过程中出错,则可以执行下面的命令:

bashsudo make test

安装:

bashsudo make install
sudo ./utils/install_server.sh

配置

创建 /etc/init.d/redis 并使用下面的代码作为启动脚本。

添加如下内容:

bash###########################
PATH=/usr/local/bin:/sbin:/usr/bin:/bin

REDISPORT=6379
EXEC=/usr/local/bin/redis-server
REDIS_CLI=/usr/local/bin/redis-cli

PIDFILE=/var/run/redis.pid
CONF="/etc/redis/6379.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        if [ "$?"="0" ]
        then
              echo "Redis is running..."
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $REDIS_CLI -p $REDISPORT SHUTDOWN
                while [ -x ${PIDFILE} ]
               do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
   restart|force-reload)
        ${0} stop
        ${0} start
        ;;
  *)
    echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
        exit 1
esac
##############################

保存后,添加可执行权限:

sudo chmod +x /etc/init.d/redis

确保 redis 能随系统启动:

vi /etc/rc.d/rc.local

在文件末尾添加下面这行:

service redis start

然后使用上面同样的命令启动 redis 服务:

service redis start

安装邮件服务器

yum -y install postfix

安装Git

先删除系统中原有的老版本 git

bashyum -y remove git
yum install zlib-devel perl-CPAN gettext curl-devel expat-devel gettext-devel openssl-devel

从官方网站下载源代码进行:

bashcurl --progress https://www.kernel.org/pub/software/scm/git/git-2.4.0.tar.gz | tar xz
cd git-2.4.0/
./configure
make
make prefix=/usr/local install

然后使用下面这个命令检测安装是否有效:

which git

安装 ruby

如果 ruby 的版本低于 2.0 的话,则需要重新安装 ruby

bashcd ~
curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.2/ruby-2.2.2.tar.gz | tar xz
cd ruby-2.2.2
./configure --disable-install-rdoc
make
make prefix=/usr/local install

为 Gitlab 添加系统用户

adduser --system --shell /bin/bash --comment 'GitLab' --create-home --home-dir /home/git/ git

为了包含/usr/local/bin到git用户的$PATH,一个方法是编辑超级用户文件。以管理员身份运行:

visudo

然后搜索:

Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin

将其改成:

Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

安装数据库

MySQL 已经不再包含在 CentOS 7 的源中,而改用了 MariaDB,先搜索 MariaDB 现有的包:

rpm -qa | grep mariadb

然后全部删除:

rpm -e --nodeps mariadb-*

然后创建 /etc/yum.repos.d/MariaDB.repo

vi /etc/yum.repos.d/MariaDB.repo

将以下内容添加至该文件中:

# MariaDB 10.0 CentOS repository list - created 2015-05-04 19:16 UTC
# http://mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.0/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

然后运行下面命令安装 MariaDB 10.0

sudo yum install MariaDB-server MariaDB-client

然后启动 MariaDB 服务:

service mysql start

接着运行 mysql_secure_installation

mysql_secure_installation

登录 MariaDB 并创建相应的数据库用户与数据库:

mysql -uroot -p
CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
SET storage_engine=INNODB;
CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'git'@'localhost';
\q

尝试使用新用户连接数据库:

sudo -u git -H mysql -u git -p -D gitlabhq_production
\q

安装 Gitlab

克隆源

sudo -u -git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-10-stable gitlab

配置

cd /home/git/gitlab

# Copy the example GitLab config
# 复制GitLab的示例配置文件
sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml

# Make sure to change "localhost" to the fully-qualified domain name of your host serving GitLab where necessary
# 确保修改“localhost”为你的GitLab主机的FQDN
#
# If you want to use https make sure that you set `https` to `true`. See #using-https for all necessary details.
# 如果你想要使用https确保你设置了`https`为`true`。具体必要的细节参见#using-https
#
# If you installed Git from source, change the git bin_path to /usr/local/bin/git
# 如果你从源代码安装了Git,修改git的bin_path为/usr/local/bin/git
sudo -u git -H editor config/gitlab.yml

# Make sure GitLab can write to the log/ and tmp/ directories
# 确保GitLab可以写入log/和temp/目录
chown -R git {log,tmp}
chmod -R u+rwX  {log,tmp}

# Create directory for satellites
# 为卫星(?)创建目录
sudo -u git -H mkdir /home/git/gitlab-satellites
chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites

# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
# 确保GitLab可以写入tmp/pids/和temp/sockets/目录
chmod -R u+rwX  tmp/{pids,sockets}

# Make sure GitLab can write to the public/uploads/ directory
# 确保GitLab可以写入public/uploads/目录
chmod -R u+rwX  public/uploads

# Copy the example Unicorn config
# 复制Unicorn的示例配置文件
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb

# Enable cluster mode if you expect to have a high load instance
# Ex. change amount of workers to 3 for 2GB RAM server
# 启用集群模式如果你期望拥有一个高负载实例
# 附:修改worker的数量到3用于2GB内存的服务器
sudo -u git -H editor config/unicorn.rb

# Copy the example Rack attack config
# 复制Rack attack的示例配置文件
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb

# Configure Git global settings for git user, useful when editing via web
# Edit user.email according to what is set in config/gitlab.yml
# 为git用户配置Git全局设定,当通过web修改时有用
# 修改user.email根据config/gitlab.yml中的设定
sudo -u git -H git config --global user.name "GitLab"
sudo -u git -H git config --global user.email "gitlab@localhost"
sudo -u git -H git config --global core.autocrlf input

数据库配置

# MySQL only:
# 仅限MySQL:
sudo -u git cp config/database.yml.mysql config/database.yml

# MySQL and remote PostgreSQL only:
# Update username/password in config/database.yml.
# You only need to adapt the production settings (first part).
# If you followed the database guide then please do as follows:
# Change 'secure password' with the value you have given to $password
# You can keep the double quotes around the password
# 仅限MySQL和远程PostgreSQL:
# 在config/database.yml中更新用户名/密码;
# 你只需要适配生产设定(第一部分);
# 如果你跟从数据库向导,请按以下操作:
# 修改'secure password'使用你刚才设定的$password;
# 你可以保留密码两端的双引号。
sudo -u git -H editor config/database.yml

# PostgreSQL and MySQL:
# Make config/database.yml readable to git only
# PostgreSQL和MySQL:
# 设置config/database.yml仅对git可读。
sudo -u git -H chmod o-rwx config/database.yml

安装 Gems

cd /home/git/gitlab

# For users from China mainland only
# 仅限中国大陆用户
nano /home/git/gitlab/Gemfile
source "http://ruby.taobao.org" // 原始 source "https://rubygems.org/"

# For MySQL (note, the option says "without ... postgres")
sudo -u git -H bundle install --deployment --without development test postgres aws

Install GitLab shell

安装GitLab Shell

GitLab Shell是一个专门为GitLab开发的SSH访问和源管理软件。

# Go to the Gitlab installation folder:
# 转到GitLab安装目录:
cd /home/git/gitlab

# For users from China mainland only
# 仅限中国大陆用户
nano /home/git/gitlab/Gemfile
source "http://ruby.taobao.org" // 原始 source "https://rubygems.org/"

# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
# 运行gitlab-shell的安装任务(替换`REDIS_URL`如果有需要的话):
sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.6] REDIS_URL=redis://localhost:6379 RAILS_ENV=production

# By default, the gitlab-shell config is generated from your main gitlab config.
# 默认的,gitlab-shell的配置文件是由你的gitlab主配置文件生成的。
#
# Note: When using GitLab with HTTPS please change the following:
# - Provide paths to the certificates under `ca_file` and `ca_path options.
# - The `gitlab_url` option must point to the https endpoint of GitLab.
# - In case you are using self signed certificate set `self_signed_cert` to `true`.
# See #using-https for all necessary details.
# 提示:当通过HTTPS使用GitLab时,请做出如下更改:
# - 提供证书的路径在`ca_file`和`ca_path`选项;
# - `gitlab_url`选项必须指向GitLab的https端点;
# - 如果你使用自签名的证书,设置`self-signed_cert`为`true`。
# 所有必需的具体细节参见#using-https
#
# You can review (and modify) it as follows:
# 你可以检查(并修改该)通过以下方法:
sudo -u git -H editor /home/git/gitlab-shell/config.yml

# Ensure the correct SELinux contexts are set
# Read http://wiki.centos.org/HowTos/Network/SecuringSSH
# 确保正确的SELinux上下文被设置
# 阅读http://wiki.centos.org/HowTos/Network/SecuringSSH
restorecon -Rv /home/git/.ssh

初始化数据库和激活高级功能

sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
# Type 'yes' to create the database tables.
# When done you see 'Administrator account created:'

提示:你可以设置管理员密码通过在环境变量GITLAB_ROOT_PASSWORD中提供,例如:

sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=newpassword

安装初始化脚本

下载初始化脚本(将放在/etc/init.d/gitlab):

sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
chmod +x /etc/init.d/gitlab
chkconfig --add gitlab

设置GitLab开机启动:

chkconfig gitlab on

设置日志翻转

cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab

检查应用状态

sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production

编译静态文件

sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production

启动实例

/etc/init.d/gitlab start
2015年五月5日上午 11:16:04 布隆过滤器 -- 空间效率很高的数据结构

哈希 hash

原理

Hash (哈希,或者散列)函数在计算机领域,尤其是数据快速查找领域,加密领域用的极广。

其作用是将一个大的数据集映射到一个小的数据集上面(这些小的数据集叫做哈希值,或者散列值)

一个应用是Hash table(散列表,也叫哈希表),是根据哈希值 (Key value) 而直接进行访问的数据结构。也就是说,它通过把哈希值映射到表中一个位置来访问记录,以加快查找的速度。下面是一个典型的 hash 函数 / 表示意图:

图片描述

哈希函数有以下两个特点:

缺点: 引用吴军博士的《数学之美》中所言,哈希表的空间效率还是不够高。如果用哈希表存储一亿个垃圾邮件地址,每个email地址 对应 8bytes, 而哈希表的存储效率一般只有50%,因此一个email地址需要占用16bytes. 因此一亿个email地址占用1.6GB,如果存储几十亿个email address则需要上百GB的内存。除非是超级计算机,一般的服务器是无法存储的。

所以要引入下面的 Bloom Filter。

布隆过滤器 Bloom Filter

原理

如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢。

Bloom Filter 是一种空间效率很高的随机数据结构,Bloom filter 可以看做是对 bit-map 的扩展, 它的原理是:

当一个元素被加入集合时,通过 KHash 函数将这个元素映射成一个位阵列(Bit array)中的 K 个点,把它们置为 1。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:

优点

It tells us that the element either definitely is not in the set or may be in the set.

它的优点是空间效率查询时间都远远超过一般的算法,布隆过滤器存储空间和插入 / 查询时间都是常数O(k)。另外, 散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

缺点

但是布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

(误判补救方法是:再建立一个小的白名单,存储那些可能被误判的信息。)

另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

Example

可以快速且空间效率高的判断一个元素是否属于一个集合;用来实现数据字典,或者集合求交集。

如: Google chrome 浏览器使用bloom filter识别恶意链接(能够用较少的存储空间表示较大的数据集合,简单的想就是把每一个URL都可以映射成为一个bit)
得多,并且误判率在万分之一以下。
又如: 检测垃圾邮件

假定我们存储一亿个电子邮件地址,我们先建立一个十六亿二进制(比特),即两亿字节的向量,然后将这十六亿个二进制全部设置为零。对于每一个电子邮件地址 X,我们用八个不同的随机数产生器(F1,F2, ...,F8) 产生八个信息指纹(f1, f2, ..., f8)。再用一个随机数产生器 G 把这八个信息指纹映射到 1 到十六亿中的八个自然数 g1, g2, ...,g8。现在我们把这八个位置的二进制全部设置为一。当我们对这一亿个 email 地址都进行这样的处理后。一个针对这些 email 地址的布隆过滤器就建成了。

再如此题:

A,B 两个文件,各存放 50 亿条 URL,每条 URL 占用 64 字节,内存限制是 4G,让你找出 A,B 文件共同的 URL。如果是三个乃至 n 个文件呢?

分析 :如果允许有一定的错误率,可以使用 Bloom filter,4G 内存大概可以表示 340 亿 bit。将其中一个文件中的 url 使用 Bloom filter 映射为这 340 亿 bit,然后挨个读取另外一个文件的 url,检查是否与 Bloom filter,如果是,那么该 url 应该是共同的 url(注意会有一定的错误率)。”

Network applications of Bloom Filter: a survey

2015年五月5日上午 11:05:30 node.js通过get访问该网址,返回302?

网址:http://search.jd.com/Search?keyword=%E7%AC%94%E8%AE%B0%E6%9C%AC&enc=utf-8 该网址是京东搜索笔记本的url;

2015年五月5日上午 10:52:28 alsotang starred coocood/freecache
alsotang starred coocood/freecache
2015年五月5日早上 7:55:36 命令行模式下的思维导图:mindmap

安装:

npm install mindmap

截图:

screenshot

用法:

  load [mindmap name] or l [mindmap name]
    Load or create a mindmap.
  add [id] [text] or a [id] [text]
    Add a child.
  insert [id] [text] or i [id] [text]
    Insert a node.
        insert -1 Animal
        insert 10 White house
  edit [id] [text] or e [id] [text]
    Edit a node.
  delete [id] or del [id] or d [id]
    Delete a node.
  help or h
    Help information.
  exit
    Exit program.
2015年五月4日晚上 11:57:41 请教这段代码cluster worker和net模块一起使用为何会出现阻塞?

很神奇的是,在master里直接调用socketHandler的话ab测试下不会阻塞

在worker里调用,ab测试并发数为1的时候不会阻塞

但像下面这样写,ab测试

ab -n 10 -c 2 http://localhost:10086

请求直接就阻塞了。。。

var net = require('net');
var http = require('http');
var cluster = require('cluster');

var recevice_socket_count = 0;
var recevice_http_count = 0;

var httpServer = http.createServer(function(req,res){
    console.log('recevice_http_count',++recevice_http_count);
    res.write('wow');
    res.end();
});

function socketHandler(socket){
  console.log('recevice_socket_count',++recevice_socket_count);
  socket.readable = socket.writeable = true;
  httpServer.emit('connection',socket);
  socket.emit('connect');
}


if(cluster.isMaster){
  var worker = cluster.fork();
  net.createServer(function(socket){
    worker.send("socket", socket);
    //socketHandler(socket);
  }).listen(10086, function() {
    console.log('netServer bound');
  });
}else{

  cluster.worker.on("message", function(data, socket) {
        socketHandler(socket);
  });
}

2015年五月4日晚上 10:43:37 做了个模板引擎 nging

安装:

npm install nging

用法:

var app = require("express")();
var nging = require("nging");
 
function Comment() {
    this.jml = function() {
        return ["div",{className:"comment"},
                    ["h2",{className:"commentAuthor", style:this.props.style},
                        this.props.author
                    ]
               ].concat(this.props.nodes);
    };
}
 
function CommentList() {
    this.jml = function() {
        var commentNodes = this.props.data.map(function (comment) {
          return [Comment, {author:comment.author}, comment.text];
        });
 
        return ["div", {className:"commentList"}].concat(commentNodes);
    };
}
 
var comments = [
  {"author": "Pete Hunt", "text": "This is one comment"},
  {"author": "Jordan Walke", "text": "This is *another* comment"}
];
 
var jml1 = ["div",{class:"yes"},["p",{style:"color:red"},"Hello!!!",["img",{src:"http://i.imgur.com/gEZsVCW.png",width:500}]]];
var jml2 = ["html",["body",["div","dfe"],["span","ddd"]]];
 
var jml3 = [Comment, {author:"John", style:"color:red"}, "Hello!"];
 
var jml4 = ["html",jml3,jml3,jml3,jml3,jml3,jml3,jml3];
 
var jml5 =["form",{className:"commentForm", onSubmit:"this.handleSubmit"},
            ["input", {type:"text", placeholder:"Your name", ref:"author"}],
            ["input", {type:"text", placeholder:"Say something...", ref:"text"}],
            ["input", {type:"submit", value:"Post"}]
        ];
 
var jml6 =[CommentList, {data:comments}];
 
var jml7 = ["html", jml1,jml2,jml3,jml4,jml5];
 
app.get("/", function(req, res) {
    res.send(nging.render(jml7));
});
 
app.listen(8080);

2015年五月4日晚上 10:27:23 请问有什么分析日志的好的方法或者工具么?

比如在我的node服务里,我把每一个请求完成的响应时间都写在了本地的log文件里,我想统计所有请求的平均响应时间,是不是只能靠编写复杂的shell脚本来实现?另外关于日志文件,有什么好的管理方法么?比如定期清理过时的日志文件应该怎么做呢?

2015年五月4日晚上 9:53:45 [译] Python 学习 —— __init__() 方法 2

通过工厂函数对 __init__() 加以利用

我们可以通过工厂函数来构建一副完整的扑克牌。这会比枚举所有52张扑克牌要好得多,在Python中,我们有如下两种常见的工厂方法:

在Python中,类并不是必须的。只是当有相关的工厂非常复杂的时候才会显现出优势。Python的优势就是当一个简单的函数可以做的更好的时候我们决不强迫使用类层次结构。

虽然这是一本关于面向对象编程的书,但函数真是一个好东西。这在Python中是常见的也是最地道的。

如果需要的话,我们总是可以将一个函数重写为适当的可调用对象。我们可以将一个可调用对象重构到我们的工厂类层次结构中。我们将在第五章《使用可调用对象和上下文》中学习可调用对象。

一般,类定义的优点是通过继承实现代码重用。工厂类的函数就是包装一些目标类层次结构和复杂对象的构造。如果我们有一个工厂类,当扩展目标类层次结构的时候,我们可以添加子类到工厂类中。这给我们提供了多态性工厂类;不同的工厂类定义具有相同的方法签名,可以交替使用。

这类水平的多态性对于静态编译语言如Java或C++非常有用。编译器可以解决类和方法生成代码的细节。

如果选择的工厂定义不能重用任何代码,则在Python中类层次结构不会有任何帮助。我们可以简单的使用具有相同签名的函数。

以下是我们各种Card子类的工厂函数:

pythondef card(rank, suit):
    if rank == 1:
        return AceCard('A', suit)
    elif 2 <= rank < 11: 
        return NumberCard(str(rank), suit)

    elif 11 <= rank < 14:
        name = {11: 'J', 12: 'Q', 13: 'K' }[rank]
        return FaceCard(name, suit)
    else:
        raise Exception("Rank out of range")

这个函数通过数值类型的ranksuit对象构建Card类。我们现在可以非常简单的构建牌了。我们已经封装构造问题到一个单一的工厂函数中,允许应用程序在不知道精确的类层次结构和多态设计是如何工作的情况下进行构建。

下面是一个如何通过这个工厂函数构建一副牌的示例:

pythondeck = [card(rank, suit) for rank in range(1,14) for suit in (Club, Diamond, Heart, Spade)]

它枚举了所有的牌值和花色来创建完整的52张牌。

1. 错误的工厂设计和模糊的else子句

注意card()函数里面的if语句结构。我们没有使用“包罗万象”的else子句来做任何处理;我们只是抛出异常。使用“包罗万象”的else子句会引出一个小小的辩论。

一方面,从属于else子句的条件不能不言而喻,因为它可能隐藏着微妙的设计错误。另一方面,一些else子句确实是显而易见的。

重要的是要避免含糊的else子句。

考虑下面工厂函数定义的变体:

pythondef card2(rank, suit):
    if rank == 1: 
        return AceCard('A', suit)
    elif 2 <= rank < 11: 
        return NumberCard(str(rank), suit)
    else:
        name = {11: 'J', 12: 'Q', 13: 'K'}[rank]
        return FaceCard(name, suit)

以下是当我们尝试创建整副牌将会发生的事情:

pythondeck2 = [card2(rank, suit) for rank in range(13) for suit in (Club, Diamond, Heart, Spade)]

它起作用了吗?如果if条件更复杂了呢?

一些程序员扫视的时候可以理解这个if语句。其他人将难以确定是否所有情况都正确执行了。

对于高级Python编程,我们不应该把它留给读者去演绎条件是否适用于else子句。对于菜鸟条件应该是显而易见的,至少也应该是显示的。

何时使用“包罗万象”的else

尽量的少使用。使用它只有当条件是显而易见的时候。当有疑问时,显式的并抛出异常。

避免含糊的else子句。

2. 简单一致的使用elif序列

我们的工厂函数card()是两种常见工厂设计模式的混合物:

为了简单起见,最好是专注于这些技术的一个而不是两个。

我们总是可以用映射来代替elif条件。(是的,总是。但相反是不正确的;改变elif条件为映射将是具有挑战性的。)

以下是没有映射的Card工厂:

pythondef card3(rank, suit):
    if rank == 1: 
        return AceCard('A', suit)
    elif 2 <= rank < 11: 
        return NumberCard(str(rank), suit)
    elif rank == 11:
        return FaceCard('J', suit)
    elif rank == 12:
        return FaceCard('Q', suit)
    elif rank == 13:
        return FaceCard('K', suit)
    else:
        raise Exception("Rank out of range")

我们重写了card()工厂函数。映射已经转化为额外的elif子句。这个函数有个优点就是它比之前的版本更加一致。

3. 简单的使用映射和类对象

在一些示例中,我们可以使用映射来代替一连串的elif条件。很可能发现条件太复杂,这个时候或许只有使用一连串的elif条件来表达才是明智的选择。对于简单示例,无论如何,映射可以做的更好且可读性更强。

因为class是最好的对象,我们可以很容易的映射rank参数到已经构造好的类中。

以下是仅使用映射的Card工厂:

python def card4(rank, suit):
    class_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard)
    return class_(rank, suit)

我们已经映射rank对象到类中。然后,我们传递rank值和suit值到类来创建最终的Card实例。

最好我们使用defaultdict类。无论如何,对于微不足道的静态映射不会比这更简单了。看起来像下面代码片段那样:

pythondefaultdict(lambda: NumberCard, {1: AceCard, 11: FaceCard, 12: FaceCard, 12: FaceCard})

注意:defaultdict默认必须是零参数的函数。我们已经使用了lambda创建必要的函数来封装常量。这个函数,无论如何,都有一些缺陷。对于我们之前版本中缺少1A13K的转换。当我们试图增加这些特性时,一定会出现问题的。

我们需要修改映射来提供可以和字符串版本的rank对象一样的Card子类。对于这两部分的映射我们还可以做什么?有四种常见解决方案:

我们来看看每一个具体的例子。

3.1. 两个并行映射

以下是两个并行映射解决方案的关键代码:

pythonclass_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard)
rank_str= {1:'A', 11:'J', 12:'Q', 13:'K'}.get(rank, str(rank))
return class_(rank_str, suit)

这并不可取的。它涉及到重复映射键1111213序列。重复是糟糕的,因为在软件更新后并行结构依然保持这种方式。

不要使用并行结构

并行结构必须使用元组或一些其他合适的集合来替代。

3.2. 映射到元组的值

以下是二元组映射的关键代码:

pythonclass_, rank_str= {
    1: (AceCard,'A'),
    11: (FaceCard,'J'),
    12: (FaceCard,'Q'),
    13: (FaceCard,'K'),
}.get(rank, (NumberCard, str(rank)))
return class_(rank_str, suit)

这是相当不错的。不需要过多的代码来分类打牌中的特殊情况。当我们需要改变Card类层次结构来添加额外的Card子类时,我们将看到它如何被修改或被扩展。

rank值映射到一个类对象的确让人感觉奇怪,且只有类初始化所需两个参数中的其中之一。将牌值映射到一个简单的类或没有提供一些混乱参数(但不是所有)的函数对象似乎会更合理。

3.3. partial函数解决方案

相比映射到二元组函数和参数之一,我们可以创建一个partial()函数。这是一个已经提供一些(但不是所有)参数的函数。我们将从functools库中使用partial()函数来创建一个带有rank参数的partial类。

以下是一个映射rankpartial()函数,可用于对象创建:

pythonfrom functools import partial
part_class= {
   1: partial(AceCard, 'A'),
   11: partial(FaceCard, 'J'),
   12: partial(FaceCard, 'Q'),
   13: partial(FaceCard, 'K'),
}.get(rank, partial(NumberCard, str(rank)))
return part_class(suit)

映射将rank对象与partial()函数联系在一起,并分配给part_class。这个partial()函数可以被应用到suit对象来创建最终的对象。partial()函数是一种常见的函数式编程技术。它在我们有一个函数来替代对象方法这一特定的情况下使用。

不过总体而言,partial()函数对于大多数面向对象编程并没有什么帮助。相比创建partial()函数,我们可以简单地更新类的方法来接受不同组合的参数。partial()函数类似于给对象构造创建一个连贯的接口。

3.4. 连贯的工厂类接口

在某些情况下,我们设计的类为方法的使用定义了顺序,衡量方法的顺序很像partial()函数。

在一个对象表示法中我们可能会有x.a() .b()。我们可以把它当成x(a, b)x.a()函数是等待b()的一类partial()函数。我们可以认为它就像x(a)(b)那样。

这里的想法是,Python给我们提供两种选择来管理状态。我们既可以更新对象又可以创建有状态性的(在某种程度上)partial()函数。由于这种等价,我们可以重写partial()函数到一个连贯的工厂对象中。我们使得rank对象的设置为一个连贯的方法来返回self。设置suit对象将真实的创建Card实例。

以下是一个连贯的Card工厂类,有两个方法函数,必须在特定顺序中使用:

pythonclass CardFactory:
    def rank(self, rank):
        self.class_, self.rank_str= {
                1: (AceCard, 'A'),
                11: (FaceCard,'J'),
                12: (FaceCard,'Q'),
                13: (FaceCard,'K'),
        }.get(rank, (NumberCard, str(rank)))
        return self
    def suit(self, suit):
        return self.class_(self.rank_str, suit)

rank()方法更新构造函数的状态,suit()方法真实的创建了最终的Card对象。

这个工厂类可以像下面这样使用:

pythoncard8 = CardFactory()
deck8 = [card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]

首先,我们创建一个工厂实例,然后我们使用那个实例创建Card实例。这并没有实质性改变__init__()本身在Card类层次结构中如何运作的。然而,它确实改变了我们客户端应用程序创建对象的方式。

2015年五月4日晚上 9:39:35 关于 this 的四类用法

this

在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断 this 所在的函数属于谁。

在《javaScript语言精粹》这本书中,把 this 出现的场景分为四类,简单的说就是:

1) 函数有所属对象时:指向所属对象

函数有所属对象时,通常通过 . 表达式调用,这时 this 自然指向所属对象。比如下面的例子:

jsvar myObject = {value: 100};
myObject.getValue = function () {
  console.log(this.value);  // 输出 100

  // 输出 { value: 100, getValue: [Function] },
  // 其实就是 myObject 对象本身
  console.log(this);

  return this.value;
};

console.log(myObject.getValue()); // => 100

getValue() 属于对象 myObject,并由 myOjbect 进行 . 调用,因此 this 指向对象 myObject

2) 函数没有所属对象:指向全局对象

jsvar myObject = {value: 100};
myObject.getValue = function () {
  var foo = function () {
    console.log(this.value) // => undefined
    console.log(this);// 输出全局对象 global
  };

  foo();

  return this.value;
};

console.log(myObject.getValue()); // => 100

在上述代码块中,foo 函数虽然定义在 getValue 的函数体内,但实际上它既不属于 getValue 也不属于 myObjectfoo 并没有被绑定在任何对象上,所以当调用时,它的 this 指针指向了全局对象 global

据说这是个设计错误。

3) 构造器中的 this:指向新对象

js 中,我们通过 new 关键词来调用构造函数,此时 this 会绑定在该新对象上。

js
var SomeClass = function(){ this.value = 100; } var myCreate = new SomeClass(); console.log(myCreate.value); // 输出100

顺便说一句,在 js 中,构造函数、普通函数、对象方法、闭包,这四者没有明确界线。界线都在人的心中。

4) apply 和 call 调用以及 bind 绑定:指向绑定的对象

apply() 方法接受两个参数第一个是函数运行的作用域,另外一个是一个参数数组(arguments)。

call() 方法第一个参数的意义与 apply() 方法相同,只是其他的参数需要一个个列举出来。

简单来说,call 的方式更接近我们平时调用函数,而 apply 需要我们传递 Array 形式的数组给它。它们是可以互相转换的。

jsvar myObject = {value: 100};

var foo = function(){
  console.log(this);
};

foo(); // 全局变量 global
foo.apply(myObject); // { value: 100 }
foo.call(myObject); // { value: 100 }

var newFoo = foo.bind(myObject);
newFoo(); // { value: 100 }
2015年五月4日晚上 9:09:16 alsotang commented on commit cnodejs/nodeclub@67c098202b
alsotang commented on commit cnodejs/nodeclub@67c098202b
@alsotang

无论mongodb还是redis都有自动过期的策略,session 不会一直存 在 2015年5月4日 下午8:11,yanjixiong <notifications@github.com>写道:

2015年五月4日晚上 7:04:20 哪位大大能帮我看下,为啥BAE里我用mongoose close了db,然后重新open就 报错了

报错为: name: 'MongoError’, message: ‘only GSSAPI, PLAIN, MONGODB-X509, SCRAM-SHA-1 or MONGODB-CR is supported by authMechanism’

代码:var mongoose = require(‘mongoose’); var format = require(‘util’).format;

if(process.env.BAE_ENV_APPID){
var host ="mongo.duapp.com"; var port ="8908"; var username ="bfc5c2ab184a479292b3fd20b012a683"; var password ="d1b72bc45df94eb4b05a4ffc9f0416af"; var dbName ="aJZfHIoouaDARdxxYghA"; var constr ="mongodb://"+ username +":"+ password +"@"+ host +":"+ port +"/"+ dbName; }else{ var constr = “mongodb://localhost/ticket” var username =null; var password =null;

}

//constr = "mongodb://localhost/ticket";

var options = { db: { native_parser: true }, server: { poolSize:4, socketOptions: { keepAlive: 1 } }, user: username, pass: password }; db = mongoose.createConnection(); var userSchema = new mongoose.Schema({ name:String, password:String},{ collection : ‘users’ });

db.open(constr,options); var userModel = db.model('users’,userSchema);

function User(user){ this.name = user.name; this.password = user.password; } setInterval(function(){console.log(‘closing db now’);db.close();}, 1000601); setInterval(function(){console.log(‘opening db now’);db.open(constr,options,function(err) { if(err){console.log(err);} });}, 1010601);

db.on('open’,function(){ console.log('connection success open’+’mongooseConnection.readyState :’+db.readyState );

    //setTimeout(function(){console.log('closing db now');mongoose.disconnect();}, 1000*60*4);
});

db.on('close’,function(err){ console.log(‘closed’);

});

2015年五月4日晚上 6:30:53 alsotang commented on commit cnodejs/nodeclub@67c098202b
alsotang commented on commit cnodejs/nodeclub@67c098202b
@alsotang

yanjixiong,看这里 https://github.com/cnodejs/nodeclub/issues/421 redis 快 2015-05-04 13:02 GMT+08:00 yanjixiong <notifications@github.com>:

2015年五月4日下午 4:23:13 如何查看cluster开启多多少个之进程?

//fork:复制一个工作进程后触发该事件。 cluster.on(‘fork’, function (worker) { console.log( '[master] fork: worker’ + worker.id +" start"); console.log( cluster.workers.length ); }); 为何每次都是undefine?应该如何查看cluster开启了多少个子进程?

2015年五月4日下午 4:10:51 npm 卸载的包还可以恢复吗?

安装了一个全局包,然后直接在源码上修改了些东西,结果手贱不小心卸载(npm uninstall)了,请问还有什么方法可以恢复这个包吗?

2015年五月4日下午 4:07:07 Mongoose 使用 Population 填充'关联表'数据

MongooseMongoDBODM(Object Document Mapper)

因为MongoDB是文档型数据库,所以它没有关系型数据库[joins](http://zh.wikipedia.org/wiki/%E8%BF%9E%E6%8E%A5_(SQL)(数据库的两张表通过"外键",建立连接关系。) 特性。也就是在建立数据的关联时会比较麻烦。为了解决这个问题,Mongoose封装了一个Population功能。使用Population可以实现在一个 document 中填充其他 collection(s)document(s)

在定义Schema的时候,如果设置某个 field 关联另一个Schema,那么在获取 document 的时候就可以使用 Population 功能通过关联Schema的 field 找到关联的另一个 document,并且用被关联 document 的内容替换掉原来关联字段(field)的内容。

接下来分享下:Query#populate Model#populate Document#populate的用法

先建立三个SchemaModel:

javascriptvar mongoose = require('mongoose');
var Schema   = mongoose.Schema;

var UserSchema = new Schema({
    name  : { type: String, unique: true },
    posts : [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
var User = mongoose.model('User', UserSchema);

var PostSchema = new Schema({
    poster   : { type: Schema.Types.ObjectId, ref: 'User' },
    comments : [{ type: Schema.Types.ObjectId, ref: 'Comment' }],
    title    : String,
    content  : String
});
var Post = mongoose.model('Post', PostSchema);

var CommentSchema = new Schema({
    post      : { type: Schema.Types.ObjectId, ref: "Post" },
    commenter : { type: Schema.Types.ObjectId, ref: 'User' },
    content   : String
});
var Comment = mongoose.model('Comment', CommentSchema);

创建一些数据到数据库:

javascript// 连接数据库
mongoose.connect('mongodb://localhost/population-test', function (err){
    if (err) throw err;
    createData();
});

function createData() {

    var userIds    = [new ObjectId, new ObjectId, new ObjectId];
    var postIds    = [new ObjectId, new ObjectId, new ObjectId];
    var commentIds = [new ObjectId, new ObjectId, new ObjectId];

    var users    = [];
    var posts    = [];
    var comments = [];

    users.push({
        _id   : userIds[0],
        name  : 'aikin',
        posts : [postIds[0]]
    });
    users.push({
        _id   : userIds[1],
        name  : 'luna',
        posts : [postIds[1]]
    });
    users.push({
        _id   : userIds[2],
        name  : 'luajin',
        posts : [postIds[2]]
    });

    posts.push({
        _id      : postIds[0],
        title    : 'post-by-aikin',
        poster   : userIds[0],
        comments : [commentIds[0]]
    });
    posts.push({
        _id      : postIds[1],
        title    : 'post-by-luna',
        poster   : userIds[1],
        comments : [commentIds[1]]
    });
    posts.push({
        _id      : postIds[2],
        title    : 'post-by-luajin',
        poster   : userIds[2],
        comments : [commentIds[2]]
    });

    comments.push({
        _id       : commentIds[0],
        content   : 'comment-by-luna',
        commenter : userIds[1],
        post      : postIds[0]
    });
    comments.push({
        _id       : commentIds[1],
        content   : 'comment-by-luajin',
        commenter : userIds[2],
        post      : postIds[1]
    });
    comments.push({
        _id       : commentIds[2],
        content   : 'comment-by-aikin',
        commenter : userIds[1],
        post      : postIds[2]
    });

    User.create(users, function(err, docs) {
        Post.create(posts, function(err, docs) {
            Comment.create(comments, function(err, docs) {
            });
        });
    });
}

数据的准备就绪后,接下来就是探索populate方法:

1. Query#populate

什么Query? Query(查询),可以快速和简单的从MongooDB查找出相应的 document(s)。 Mongoose 封装了很多查询的方法,使得对数据库的操作变得简单啦。这里分享一下populate方法用法。

语法:
Query.populate(path, [select], [model], [match], [options])

参数:

path
  类型:StringObject
  String类型的时, 指定要填充的关联字段,要填充多个关联字段可以以空格分隔。
  Object类型的时,就是把 populate 的参数封装到一个对象里。当然也可以是个数组。下面的例子中将会实现。

select
  类型:ObjectString,可选,指定填充 document 中的哪些字段。
  Object类型的时,格式如:{name: 1, _id: 0},为0表示不填充,为1时表示填充。
  String类型的时,格式如:"name -_id",用空格分隔字段,在字段名前加上-表示不填充。详细语法介绍 query-select

model
  类型:Model,可选,指定关联字段的 model,如果没有指定就会使用Schemaref

match
  类型:Object,可选,指定附加的查询条件。

options
  类型:Object,可选,指定附加的其他查询选项,如排序以及条数限制等等。

javascript//填充所有 users 的 posts
User.find()
    .populate('posts', 'title', null, {sort: { title: -1 }})
    .exec(function(err, docs) {
        console.log(docs[0].posts[0].title); // post-by-aikin
    });

//填充 user 'luajin'的 posts
User.findOne({name: 'luajin'})
    .populate({path: 'posts', select: { title: 1 }, options: {sort: { title: -1 }}})
    .exec(function(err, doc) {
        console.log(doc.posts[0].title);  // post-by-luajin
    });

//这里的 populate 方法传入的参数形式不同,其实实现的功能是一样的,只是表示形式不一样。

javascriptPost.findOne({title: 'post-by-aikin'})
    .populate('poster comments', '-_id')
    .exec(function(err, doc) {
        console.log(doc.poster.name);           // aikin
        console.log(doc.poster._id);            // undefined

        console.log(doc.comments[0].content);  // comment-by-luna
        console.log(doc.comments[0]._id);      // undefined
    });

Post.findOne({title: 'post-by-aikin'})
    .populate({path: 'poster comments', select: '-_id'})
    .exec(function(err, doc) {
        console.log(doc.poster.name);           // aikin
        console.log(doc.poster._id);            // undefined

        console.log(doc.comments[0].content);  // comment-by-luna
        console.log(doc.comments[0]._id);      // undefined
    });

//上两种填充的方式实现的功能是一样的。就是给 populate 方法的参数不同。
//这里要注意,当两个关联的字段同时在一个 path 里面时, select 必须是 document(s)
//具有的相同字段。


//如果想要给单个关联的字段指定 select,可以传入数组的参数。如下:

Post.findOne({title: 'post-by-aikin'})
    .populate(['poster', 'comments'])
    .exec(function(err, doc) {
        console.log(doc.poster.name);          // aikin
        console.log(doc.comments[0].content);  // comment-by-luna
    });

Post.findOne({title: 'post-by-aikin'})
    .populate([
        {path:'poster',   select: '-_id'},
        {path:'comments', select: '-content'}
    ])
    .exec(function(err, doc) {
        console.log(doc.poster.name);          // aikin
        console.log(doc.poster._id);           // undefined

        console.log(doc.comments[0]._id);      // 会打印出对应的 comment id
        console.log(doc.comments[0].content);  // undefined
    });

2. Model#populate

Model(模型),是根据定义的 Schema 编译成的抽象的构造函数。models 的实例 documents,可以在数据库中被保存和检索。数据库所有 document 的创建和检索,都通过 models 处理。

语法:
Model.populate(docs, options, [cb(err,doc)])

参数:

docs
  类型:DocumentArray。单个需要被填充的 doucment 或者 document 的数组。

options
  类型:Object。以键值对的形式表示。
  keys:path select match model options,这些键对应值的类型和功能,与上述Query#populate方法的参数相同。

[cb(err,doc)]
  类型:Function,回调函数,接收两个参数,错误err和填充完的doc(s)

javacriptPost.find({title: 'post-by-aikin'})
    .populate('poster comments')
    .exec(function(err, docs) {

        var opts = [{
            path   : 'comments.commenter',
            select : 'name',
            model  : 'User'
        }];

        Post.populate(docs, opts, function(err, populatedDocs) {
            console.log(populatedDocs[0].poster.name);                  // aikin
            console.log(populatedDocs[0].comments[0].commenter.name);  // luna
        });
    });

3. Document#populate

Document,每个 document 都是其 Model 的一个实例,一对一的映射着 MongoDB 的 document。

语法:
Document.populate([path], [callback])

参数:

path
  类型:StringObject。与上述Query#populate`方法的 path 参数相同。

callback
  类型:Function。回调函数,接收两个参数,错误err和填充完的doc(s)

javascriptUser.findOne({name: 'aikin'})
    .exec(function(err, doc) {

        var opts = [{
            path   : 'posts',
            select : 'title'
        }];

        doc.populate(opts, function(err, populatedDoc) {
            console.log(populatedDoc.posts[0].title);  // post-by-aikin
        });
    });

博文涉及的完整例子在 gist 上。(ps: gist 被已墙了。)

参考

原文链接

2015年五月4日下午 4:01:58 Node crypto.final() 似乎导致与 PHP mcrypt_encrypt 两方加密结果不同?

加密演算法我使用的是 Triple DES,而以下会出现的 key,IV 我已经先行替换了。


首先我使用的是 Node.js crypto 做加密。

var secretKey  = new Buffer('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), // 48 chars
    iv         = new Buffer('bbbbbbbbbbbbbbbb', 'hex'); // 16 chars
var str        = 'This string will be encrypted.';
var cipher     = crypto.createCipheriv('des-ede3-cbc', secretKey, iv),
    cryptedStr = cipher.update(str, 'utf8', 'base64') + cipher.final('base64');

再來是协作方的系统,使用的是 PHP 的 mcrypt。

$key    = pack('H*', "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 
$iv     = pack('H*', "bbbbbbbbbbbbbbbb"); 
$string = 'This string will be encrypted.';
$text   = mcrypt_encrypt(MCRYPT_3DES, $key, $string, MCRYPT_MODE_CBC, $iv);
$text_base64 = base64_encode($text);

我遇到的问题是,明明使用相同的 Key / IV、演算法以及编码方式,但结果就是会有一部分不同。

而观察之下,不同处却是 cipher.final() 所导致?

// Node.js output.
UKBI17EIHKNM2EU48ygsjil5r58Eo1csByAIFp9GhUw=
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    Same part

// PHP output.
UKBI17EIHKNM2EU48ygsjil5r58Eo1csAY4C0JZoyco=
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    Same part

还请教各位大侠、前辈,究竟是为什么呢?

2015年五月4日下午 3:19:38 文件归档工具——category

我有个习惯就是定期将手机里的照片拷贝到电脑上,整理归档,然后上传到网盘。

整理之前是这样的:

Camera/
├── IMG_20150425_133502.jpg
├── IMG_20150426_134524.jpg
├── IMG_20150427_123602.jpg
└── IMG_20150427_221603.jpg
...

整理之后是这样的:

Camera/
├── 2015-04-25/
│   └── IMG_20150425_133502.jpg
├── 2015-04-26/
│   └── IMG_20150426_134524.jpg
└── 2015-04-27/
    ├── IMG_20150427_123602.jpg
    └── IMG_20150427_221603.jpg

话说以前每次都是手动整理的没用过啥软件 orz… ,由于很长时间没整理手机相册积攒了很多照片,于是昨天自己写了个归档的命令行工具。。。

传送门:https://github.com/nswbmw/node-category

2015年五月4日下午 2:50:28 使用Python解析nginx日志文件

项目的一个需求是解析nginx的日志文件。
简单的整理如下:


日志规则描述

首先要明确自己的Nginx的日志格式,这里采用默认Nginx日志格式:

 log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                   '$status $body_bytes_sent "$http_referer" '
                   '"$http_user_agent" "$http_x_forwarded_for"';

其中一条真实记录样例如下:

172.22.8.207 - - [16/Dec/2014:17:57:35 +0800] "GET /report?DOmjjuS6keWJp+WculSQAgdUkAIPODExMzAwMDJDN0FC HTTP/1.1" 200 0 "-" "XXXXXXX/1.0.16; iPhone/iOS 8.1.2; ; 8DA77E2F91D0"

其中,客户端型号信息用XXXXXXX代替。

项目中已经按照业务规则对Nginx日志文件进行了处理命名规则如下:

ID-ID-YYMMDD-hhmmss

并且所有的日志文件存放在统一路径下。

解决思路


获取所有日志文件path

这里使用Python的glob模块来获取日志文件path

import glob


def readfile(path):
    return glob.glob(path + '*-*-*-*')

获取日志文件中每一行的内容

使用Python的linecache模块来获取文件行的内容

import linecache


def readline(path):
    return linecache.getlines(path)

注意:linecache模块使用了缓存,所以存在以下问题:

  1. 在使用linecache模块读取文件内容以后,如果文件发生了变化,那么需要使用linecache.updatecache(filename)来更新缓存,以获取最新变化。

  2. linecache模块使用缓存,所以会耗费内存,耗费量与要解析的文件相关。最好在使用完毕后执行linecache.clearcache()清空一下缓存。

当然,作为优化,这里可以利用生成器来进行优化。暂且按下不表。

处理日志条目

一条日志信息就是一个特定格式的字符串,因此使用正则表达式来解析,这里使用Python的re模块。
下面,一条一条建立规则:

规则

    ip = r"?P<ip>[\d.]*"
    date = r"?P<date>\d+"
    month = r"?P<month>\w+"
    year = r"?P<year>\d+"
    log_time = r"?P<time>\S+"
    method = r"?P<method>\S+"
    request = r"?P<request>\S+"
    status = r"?P<status>\d+"
    bodyBytesSent = r"?P<bodyBytesSent>\d+"
    refer = r"""?P<refer>
             [^\"]*
             """
    userAgent=r"""?P<userAgent>
                .*
               """

解析

p = re.compile(r"(%s)\ -\ -\ \[(%s)/(%s)/(%s)\:(%s)\ [\S]+\]\ \"(%s)?[\s]?(%s)?.*?\"\ (%s)\ (%s)\ \"(%s)\"\ \"(%s).*?\"" %( ip, date, month, year, log_time, method, request, status, bodyBytesSent, refer, userAgent ), re.VERBOSE)
m = re.findall(p, logline)

这样,就可以得到日志条目中各个要素的原始数据。


格式及内容转化

得到日志原始数据之后,需要根据业务要求,对原始数据进行格式及内容转化。
这里需要处理的内容包括:时间,request,userAgent

时间格式转化

在日志信息原始数据中存在Dec这样的信息,利用Python的time模块可以方便的进行解析

import time


def parsetime(date, month, year, log_time):
    time_str = '%s%s%s %s' %(year, month, date, log_time)
    return time.strptime(time_str, '%Y%b%d %H:%M:%S')

解析request

在日志信息原始数据中得到的request的内容格式为:

/report?XXXXXX

这里只需要根据协议取出XXXXXX即可。
这里仍然采用Python的re模块

import re


def parserequest(rqst):
    param = r"?P<param>.*"
    p = re.compile(r"/report\?(%s)" %param, re.VERBOSE)
    return re.findall(p, rqst)

接下来需要根据业务协议解析参数内容。这里需要先利用base64模块解码,然后再利用struct模块解构内容:

import struct
import base64


def parseparam(param):
    decodeinfo = base64.b64decode(param)
    s = struct.Struct('!x' + bytes(len(decodeinfo) - (1 + 4 + 4 + 12)) + 'xii12x')
    return s.unpack(decodeinfo)

解析userAgent

在日志信息原始数据中userAgent数据的格式为:

XXX; XXX; XXX; XXX

根据业务要求,只需要取出最后一项即可。
这里采用re模块来解析。

import re


def parseuseragent(useragent):
    agent = r"?P<agent>.*"
    p = re.compile(r".*;.*;.*;(%s)" %agent, re.VERBOSE)
    return re.findall(p, useragent)

至此,nginx日志文件解析基本完成。
剩下的工作就是根据业务需要,对获得的基本信息进行处理。
(完)

2015年五月4日下午 12:36:05 客户-服务器程序设计方法

客户-服务器程序设计方法

《unix网络编程》第一卷中将客户服务器程序设计方法讲得透彻,这篇文章将其中编码的细节略去,通过伪代码的形式展现,主要介绍各种方法的思想;

示例是一个经典的TCP回射程序:
客户端发起连接请求,连接后发送一串数据;收到服务端的数据后输出到终端;
服务端收到客户端的数据后原样回写给客户端;

客户端伪代码:

sockfd = socket(AF_INET,SOCK_STREAM,0);
//与服务端建立连接
connect(sockfd);
//连接建立后从终端读入数据并发送到服务端;
//从服务端收到数据后回写到终端
while(fgets(sendline,MAXLINE,fileHandler)!= NULL){
    writen(sockfd,sendline,strlen(sendline));
    if(readline(sockfd,recvline,MAXLINE) == 0){
        cout << "recive over!";
    }
    fputs(recvline,stdout);
}

下面介绍服务端程序处理多个客户请求的开发范式;

多进程处理

对于多个客户请求,服务器端采用fork的方式创建新进程来处理;

处理流程:
1. 主进程绑定ip端口后,使用accept()等待新客户的请求;
2. 每一个新的用户请求到来,都创建一个新的子进程来处理具体的客户请求;
3. 子进程处理完用户请求,结束本进程;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
while(true){
    //服务器端在这里阻塞等待新客户连接
    connfd = accept(listenfd); 
    if( fork() ==0){//子进程
        close(listenfd);
        while(n=read(connfd,buf,MAXLINE)>0){
            writen(connfd,buf);
        }
    }
    close(connfd);
}

这种方法开发简单,但对操作系统而言,进程是一种昂贵的资源,对于每个新客户请求都使用一个进程处理,开销较大;
对于客户请求数不多的应用适用这种方法;

预先分配进程池,accept无上锁保护

上一种方法中,每来一个客户都创建一个进程处理请求,完毕后再释放;
不间断的创建和结束进程浪费系统资源;
使用进程池预先分配进程,通过进程复用,减少进程重复创建带来的系统消耗和时间等待;

优点:消除新客户请求到达来创建进程的开销;
缺点:需要预先估算客户请求的多少(确定进程池的大小)

源自Berkeley内核的系统,有以下特性:
派生的所有子进程各自调用accep()监听同一个套接字,在没有用户请求时都进入睡眠;
当有新客户请求到来时,所有的客户都被唤醒;内核从中选择一个进程处理请求,剩余的进程再次转入睡眠(回到进程池);

利用这个特性可以由操作系统来控制进程的分配;
内核调度算法会把各个连接请求均匀的分散到各个进程中;

处理流程:
1. 主进程预先分配进程池,所有子进程阻塞在accept()调用上;
2. 新用户请求到来,操作系统唤醒所有的阻塞在accpet上的进程,从其中选择一个建立连接;
3. 被选中的子进程处理用户请求,其它子进程回到睡眠;
4. 子进程处理完毕,再次阻塞在accept上;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
for(int i = 0;i< children;i++){
    if(fork() == 0){//子进程
        while(true){
            //所有子进程监听同一个套接字,等待用户请求
            int connfd = accept(listenfd);
            close(listenfd);
            //连接建立后处理用户请求,完毕后关闭连接
            while(n=read(connfd,buf,MAXLINE)>0){
                writen(connfd,buf);
            }
            close(connfd);
        }
    }
}

如何从进程池中取出进程?
所有的进程都通过accept()阻塞等待,等连接请求到来后,由内核从所有等待的进程中选择一个进程处理;

处理完的进程,如何放回到池子中?
子进程处理完客户请求后,通过无限循环,再次阻塞在accpet()上等待新的连接请求;

注意: 多个进程accept()阻塞会产生“惊群问题”:尽管只有一个进程将获得连接,但是所有的进程都被唤醒;这种每次有一个连接准备好却唤醒太多进程的做法会导致性能受损;

预先分配进程池,accept上锁(文件锁、线程锁)

上述不上锁的实现存在移植性的问题(只能在源自Berkeley的内核系统上)和惊群问题,
更为通用的做法是对accept上锁;即避免让多个进程阻塞在accpet调用上,而是都阻塞在获取锁的函数中;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
for(int i = 0;i< children;i++){
    if(fork() == 0){
        while(true){
            my_lock_wait();//获取锁
            int connfd = accept(listenfd);
            my_lock_release();//释放锁
            close(listenfd);
            while(n=read(connfd,buf,MAXLINE)>0){
                writen(connfd,buf);
            }
            close(connfd);
        }
    }
}

上锁可以使用文件上锁,线程上锁;
- 文件上锁的方式可移植到所有的操作系统,但其涉及到文件系统操作,可能比较耗时;
- 线程上锁的方式不仅适用不同线程之间的上锁,也适用于不同进程间的上锁;

关于上锁的编码细节详见《网络编程》第30章;

预先分配进程池,传递描述符;

与上面的每个进程各自accept接收监听请求不同,这个方法是在父进程中统一接收accpet()用户请求,在连接建立后,将连接描述符传递给子进程;

处理流程:
1. 主进程阻塞在accpet上等待用户请求,所有子进程不断轮询探查是否有可用的描述符;
2. 有新用户请求到来,主进程accpet建立连接后,从进程池中取出一个进程,通过字节流管道将连接描述符传递给子进程;
3. 子进程收到连接描述符,处理用户请求,处理完成后向父进程发送一个字节的内容(无实际意义),告知父进程我任务已完成;
4. 父进程收到子进程的单字节数据,将子进程放回到进程池;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
//预先建立子进程池
for(int i = 0;i< children;i++){
    //使用Unix域套接字创建一个字节流管道,用来传递描述符
    socketpair(AF_LOCAL,SOCK_STREAM,0,sockfd);
    if(fork() == 0){//预先创建子进程
        //子进程字节流到父进程
        dup2(sockfd[1],STDERR_FILENO);
        close(listenfd);
        while(true){
            //收到连接描述符
            if(read_fd(STDERR_FILENO,&connfd) ==0){; 
                continue;
            }
            while(n=read(connfd,buf,MAXLINE)>0){ //处理用户请求
                writen(connfd,buf);
            }
            close(connfd);
            //通知父进程处理完毕,本进程可以回到进程池
            write(STDERR_FILENO,"",1);
        }
    }
}

while(true){
    //监听listen套接字描述符和所有子进程的描述符
    select(maxfd+1,&rset,NULL,NULL,NULL);
    if(FD_ISSET(listenfd,&rset){//有客户连接请求
        connfd = accept(listenfd);//接收客户连接
        //从进程池中找到一个空闲的子进程
        for(int i = 0 ;i < children;i++){
            if(child_status[i] == 0)
                break;
        }
        child_status[i] = 1;//子进程从进程池中分配出去
        write_fd(childfd[i],connfd);//将描述符传递到子进程中
        close(connfd);
    }
    //检查子进程的描述符,有数据,表明已经子进程请求已处理完成,回收到进程池
    for(int i = 0 ;i < children;i++){
        if(FD_ISSET(childfd[i],&rset)){
            if(read(childfd[i])>0){
                child_status[i] = 0;
            }
        }
    }
}

多线程处理

为每个用户创建一个线程,这种方法比为每个用户创建一个进程要快出许多倍;

处理流程:
1. 主线程阻塞在accpet上等待用请求;
2. 有新用户请求时,主线程建立连接,然后创建一个新的线程,将连接描述符传递过去;
3. 子线程处理用户请求,完毕后线程结束;

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
while(true){
    connfd = accept(listenfd);
        //连接建立后,创建新线程处理具体的用户请求
    pthread_create(&tid,NULL,&do_function,(void*)connfd);
    close(connfd);
}

--------------------
//具体的用户请求处理函数(子线程主体)
void * do_function(void * connfd){
    pthread_detach(pthread_self());
    while(n=read(connfd,buf,MAXLINE)>0){
        writen(connfd,buf);
    close((int)connfd);
}

预先创建线程池,每个线程各自accept

处理流程:
1. 主线程预先创建线程池,第一个创建的子线程获取到锁,阻塞在accept()上,其它子线程阻塞在线程锁上;
2. 用户请求到来,第一个子线程建立连接后释放锁,然后处理用户请求;完成后进入线程池,等待获取锁;
3. 第一个子线程释放锁之后,线程池中等待的线程有一个会获取到锁,阻塞在accept()等待用户请求;

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
//预先创建线程池,将监听描述符传给每个新创建的线程
for(int i = 0 ;i <threadnum;i++){
    pthread_create(&tid[i],NULL,&thread_function,(void*)connfd);
}

--------------------
//具体的用户请求处理
//通过锁保证任何时刻只有一个线程阻塞在accept上等待新用户的到来;其它的线程都
//在等锁;
void * thread_function(void * connfd){
    while(true){
        pthread_mutex_lock(&mlock); // 线程上锁
        connfd = accept(listenfd);
        pthread_mutex_unlock(&mlock);//线程解锁
        while(n=read(connfd,buf,MAXLINE)>0){
            writen(connfd,buf);
        close(connfd);
    }
}

使用源自Berkeley的内核的Unix系统时,我们不必为调用accept而上锁,
去掉上锁的两个步骤后,我们发现没有上锁的用户时间减少(因为上锁是在用户空间中执行的线程函数完成的),而系统时间却增加很多(每一个accept到达,所有的线程都变唤醒,引发内核的惊群问题,这个是在线程内核空间中完成的);
而我们的线程都需要互斥,让内核执行派遣还不让自己通过上锁来得快;

这里没有必要使用文件上锁,因为单个进程中的多个线程,总是可以通过线程互斥锁来达到同样目的;(文件锁更慢)

 预先创建线程池,主线程accept后传递描述符

处理流程:

  1. 主线程预先创建线程池,线程池中所有的线程都通过调用pthread_cond_wait()而处于睡眠状态(由于有锁的保证,是依次进入睡眠,而不会发生同时调用pthread_cond_wait引发竞争)
  2. 主线程阻塞在acppet调用上等待用户请求;
  3. 用户请求到来,主线程accpet建立建立,将连接句柄放入约定位置后,发送pthread_cond_signal激活一个等待该条件的线程;
  4. 线程激活后从约定位置取出连接句柄处理用户请求;完毕后再次进入睡眠(回到线程池);

激活条件等待的方式有两种:pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。

注:一般应用中条件变量需要和互斥锁一同使用;
在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

服务端伪代码:

listenFd = socket(AF_INET,SOCK_STREAM,0);
bind(listenFd,addR);
listen(listenFD);
for(int i = 0 ;i <threadnum;i++){
    pthread_create(&tid[i],NULL,&thread_function,(void*)connfd);
}
while(true){
    connfd = accept(listenfd);
    pthread_mutex_lock(&mlock); // 线程上锁
    childfd[iput] = connfd;//将描述符的句柄放到数组中传给获取到锁的线程;
    if(++iput == MAX_THREAD_NUM)
        iput= 0;
    if(iput == iget)
        err_quit("thread num not enuough!");
    pthread_cond_signal(&clifd_cond);//发信号,唤醒一个睡眠线程(轮询唤醒其中的一个)
    pthread_mutex_unlock(&mlock);//线程解锁
}

--------------------
void * thread_function(void * connfd){
    while(true){
        pthread_mutex_lock(&mlock); // 线程上锁
        //当无没有收到连接句柄时,睡眠在条件变量上,并释放mlock锁
        //满足条件被唤醒后,重新加mlock锁
        while(iget == iput)
            pthread_cond_wait(&clifd_cond,&mlock);
        connfd = childfd[iget];
        if(++iget == MAX_THREAD_NUM)
            iget = 0;
        pthread_mutex_unlock(&mlock);//线程解锁
        //处理用户请求
        while(n=read(connfd,buf,MAXLINE)>0){
            writen(connfd,buf);
        close(connfd);
    }
}

测试表明这个版本的服务器要慢于每个线程各自accpet的版本,原因在于这个版本同时需要互斥锁和条件变量,而上一个版本只需要互斥锁;

线程描述符的传递和进程描述符的传递的区别?
在一个进程中打开的描述符对该进程中的所有线程都是可见的,引用计数也就是1;
所有线程访问这个描述符都只需要通过一个描述符的值(整型)访问;
而进程间的描述符传递,传递的是描述符的引用;(好比一个文件被2个进程打开,相应的这个文件的描述符引用计数增加2);

总结

参考资料

《unix网络编程》第一卷 套接字联网API

Posted by: 大CC
博客:blog.me115.com
微博:新浪微博

2015年五月4日下午 12:35:27 提交自己的包到bower、npm中

转载地址

bower

Bower 是 twitter 推出的一款包管理工具,基于nodejs的模块化思想,把功能分散到各个模块中,让模块和模块之间存在联系,通过 Bower 来管理模块间的这种联系。

bower官网

安装Bower

一旦你已经安装了上面所说的所有必要文件,键入以下命令安装Bower:

$ npm install -g bower

这行命令是Bower的全局安装,-g 操作表示全局。

使用bower

  1. 直接下载 git 库: bower install git://github.com/JSLite/JSLite.git
  2. github别名自动解析git库: bower install JSLite/JSLite
  3. 下载线上的任意文件: bower install http://foo.com/jquery.awesome-plugin.js
  4. 下载本地库: bower install ./repos/jquery

常用命令

$ bower install jquery --save 添加依赖并更新bower.json文件
$ bower cache clean 安装失败清除缓存
$ bower install storejs 安装storejs
$ bower uninstall storejs 卸载storejs

注册

添加配置文件

bower.json文件的使用可以让包的安装更容易,你可以在应用程序的根目录下创建一个名为 bower.json 的文件,并定义它的依赖关系。使用bower init 命令来创建bower.json文件:

$ bower init
? name: store.js
? version: 1.0.1
? description: "本地存储localstorage的封装,提供简单的AIP"
? authors: (kenny.wang <wowohoo@qq.co>)
? license: MIT
? homepage:
? set currently installed components as dependencies?: Yes
? add commonly ignored files to ignore list?: Yes
? would you like to mark this package as private which prevents it from being accidentally publis? would you like to mark this package as private which prevents it from being accidentally published to the registry?: No

{
  name: 'store.js',
  main: 'store.js',
  version: '1.0.1',
  authors: [
    '(kenny.wang <wowohoo@qq.co>)'
  ],
  description: '"本地存储localstorage的封装,提供简单的AIP"',
  moduleType: [
    'amd',
    'node'
  ],
  keywords: [
    'storejs'
  ],
  license: 'MIT',
  ignore: [
    '**/.*',
    'node_modules',
    'bower_components',
    'test',
    'tests'
  ]
}

? Looks good?: Yes

注册自己的包

可以注册自己的包,这样其他人也可以使用了,这个操作只是在服务器上保存了一个隐射,服务器本身不托管代码。

bower register storejs git://github.com/jaywcjlove/store.js.git

npm

npm全称Node Package Manager,是node.js的模块依赖管理工具。使用github管理NPM包的代码,并定期提交至NPM服务器;
npm官网

提交自己开发的NPM包

创建package.json文件

package.json文件的使用可以让包的安装更容易,你可以在应用程序的根目录下创建一个名为 package.json 的文件,并定义它的依赖关系。使用npm init 命令来创建package.json文件:

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (store.js)
version: (1.0.0)
description: Local storage localstorage package provides a simple API
entry point: (store.js)
test command: store.js
git repository: (https://github.com/jaywcjlove/store.js.git)
keywords: store.js
author: (kenny.wang <wowohoo@qq.co>)
license: (ISC) MIT
About to write to /Applications/XAMPP/xamppfiles/htdocs/git/github.com/myJS/store.js/package.json:

{
  "name": "store.js",
  "version": "1.0.0",
  "description": "Local storage localstorage package provides a simple API",
  "main": "store.js",
  "scripts": {
    "test": "store.js"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/jaywcjlove/store.js.git"
  },
  "keywords": [
    "store.js"
  ],
  "author": " <wowohoo@qq.co> (kenny.wang <wowohoo@qq.co>)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/jaywcjlove/store.js/issues"
  },
  "homepage": "https://github.com/jaywcjlove/store.js"
}


Is this ok? (yes) yes

发布到线上

添加用户

按照提示输入用户名,密码和邮箱

npm adduser

登陆用户

按照提示输入用户名,密码和邮箱

npm adduser

发布

npm publish

如果不带参数,则会在当前目录下查找package.json文件,按照该文件描述信息发布;
如果指定目录,就会在指定目录下查找package.json文件
测试是否发布成功,在官网搜索一下www.npmjs.com

注: package.json 中的name不要又特殊字符哦

版本更新

修改package.json里的版本号,重新npm publish

取消发布

npm unpublish

其它命令

npm install storejs 下载使用
npm config set registry https://registry.npm.taobao.org 更换镜像地址
npm config get registry 获取镜像地址
npm dist-tag ls jslite 查看当前版本
npm dedupe 尽量压平依赖树

国内优秀npm镜像

由于npm的源在国外,所以国内用户使用起来各种不方便。
利用kappa搭建私有NPM仓库

淘宝npm镜像

  1. 搜索地址:http://npm.taobao.org/
  2. registry地址:http://registry.npm.taobao.org/

cnpmjs镜像

  1. 搜索地址:http://cnpmjs.org/
  2. registry地址:http://r.cnpmjs.org/

临时使用

npm --registry https://registry.npm.taobao.org install express

持久使用

npm config set registry https://registry.npm.taobao.org
// 配置后可通过下面方式来验证是否成功
npm config get registry
// 或
npm info express

通过cnpm使用

npm install -g cnpm --registry=https://registry.npm.taobao.org

// 使用
cnpm install expresstall express

spmjs

spmjs

据说已经不更新了,日后如果有研究再补充!

2015年五月4日中午 12:11:31 【北京朝外·日坛国际公寓】急招web前端一枚,快到碗里来,发简历至vivian.sheng@123lian.com

我们的创始团队来自PwC,360等公司,在做的是帮助大家找人一起来玩体育运动的app - 找炼运动。我们的CEO是连续创业者,之前已经做过一家不大不小的公司。我们的主程去年才从美国跑到北京,因此整天挂在嘴上的都是一些很有逼格的新技术,什么React啊,Docker啊,Ansible啊。我们还有“需求一锤子敲定,不用天天改来改去的”产品经理,和她一起工作,保证是件身心愉悦的乐事。

我们的主程是Pivotal Labs的脑残粉,所以我们会和你:pair programming,这是我们所知对工程师来说提升很大的一种编程方法,对一个好的学习者来说,近距离观察别的程序员如何写测试,在代码中找到bug或者加新功能,是非常有效的学习过程。如果你是更资深的工程师,那就让我们从你那里学点东西吧:)当然,这过程中大屏幕的Mac是少不了的。

我们的办公室在朝阳门外,可以直接俯瞰日坛公园,好风景,工作也会有好心情。

我们需要你: 与设计师/产品经理/后端工程师沟通,完成微信和Web端应用的功能设计、开发和实现 与后端开发人员一起研讨技术实现方案,制定服务接口

我们的要求: 愿意在创业公司工作,有创造的意愿和热情 接触过Angular.js/React.js/Ember.js/Backbone.js等主流客户端框架中的一种 有良好的编码习惯

加分项: 熟悉移动端网站开发,了解兼容不同浏览器的最优前端解决方案 接触过任何一门后端语言(Node.js/Ruby/Python/PHP等) 有前端代码优化经验

我们的面试过程: 没有算法题,我们会和你一起编程,解决一个小问题。

2015年五月4日上午 10:48:21 北京海淀【功夫熊】-Node开发者(移动互联网O2O,弹性工作,扁平化管理,战斗力爆表~)

Node开发工程师: 主要工作:基于Node的产品研发 岗位职责: 1 参与讨论开发需求 2 负责手机web端和产品后台的研发 岗位需求: 1 熟悉Node,熟悉Mongodb 2 熟悉Html5,能够完成完整的web应用 3 学习能力强,有强烈的工作责任心,具有一定的沟通及协调能力

高级Node开发工程师: 主要工作:基于Node的产品研发 岗位职责: 1 参与讨论开发需求 2 负责系统后端全部体系开发 3 负责后台REST API设计及mongodb的结构设计 4 负责复杂数据的mongodb存储格式设计和算法及性能优化 岗位需求: 1 一年及以上的Node开发工作经验 2 知识体系全面,熟悉主流前后端技术,有复杂大型后端系统的开发经验 3 具备Express、async、underscore等框架的使用经验,熟悉HTTP,TCP/IP网络协议 4 有Restful API开发和NoSQL项目开发经验 5 学习能力强,有强烈的工作责任心,具有一定的沟通及协调能力

加分: 1 有高并发Web项目后端开发和海量数据处理经验 2 有敏捷或流行软件开发流程的经验

PS:O2O全新领域,百度系研发团队,战斗力爆表,弹性工作、无限量零食~~

公司主页:www.gfxiong.com 公司简介: 功夫熊——上门推拿第一大; O2O上门推拿互联网平台,为都市人提供专业便捷的上门服务; 推拿师全部具有5年以上工作经验,有的从迪拜阿联酋回来的,有的从中医院出来; 通过微信平台(gfxiong)即可下单预约推拿师。 2014年10月上线,100天做到行业第一,光顾过央视、东方卫视、教育电视台、台湾中天卫视等各种TV,现北京上海均已开通。

附上功夫熊的服务宣传链接: http://card.maka.im/mview/viewer.html?id=AQUX7YMZ&from=timeline&isappinstalled=0

请有意向者投递简历至邮箱:hr@gfxiong.com 邮件名称:姓名-Node-Cnode社区

2015年五月4日上午 9:41:52 Node.js 服务器提升CPU个数和提升RAM哪个对性能的提升影响大?

我有个疑惑。。Node.js是单线程的,意味着一个进程就只能有一个线程,也就意味着CPU一个核只能处理一个线程对吧?(如果说错了求指正。。)

所以说从单核CPU升级到4核CPU,意味着本来同一时刻只能处理一个用户请求变成可以同时处理4个用户同时的请求。所以性能是提升了4倍。

如果提升RAM,相当于提升了单个进程的处理速度,其实一样能大幅提升性能。

想问问大家都用什么样的server? AWS上自己配么?我建一个供企业内部使用的dashboard website,外加一些web services,我打算买这个服务: https://www.digitalocean.com/pricing/ 4GBMemory 2 CoreProcessor 60GBSSD Disk 4TBTransfer

大神们有什么见解么?

2015年五月4日上午 9:38:38 关于socket.io的问题

今天在看node.js实战(吴海星 译)的时候,里面有段代码 io.sockets.clients(room) 这是什么意思?我在官方文档里也没找着.clients 谁能帮我解释一下啊?

2015年五月4日早上 7:56:44 【译】PHP:40+开发工具推荐

PHP是为Web开发设计的服务器脚本语言,但也是一种通用的编程语言。超过2.4亿个索引域使用PHP,包括很多重要的网站,例如Facebook、Digg和WordPress。和其它脚本语言相比,例如Python和Ruby,Web开发者有很多不错的理由皮偏爱PHP。
对于PHP开发者,在互联网上有很多可用的开发工具,但是找到一个合适的PHP开发工具是很难的,需要花费很多努力和时间。今天,就为开发者介绍45个方便的PHP工具。

Plates

Plates是一个简单易用的PHP模板系统。Plates是为那些喜欢使用本地模板而不是编译模板的人设计的。

Laravel

Laravel是一个有着优雅表达语言的开源框架。

Parsedown

一个Laravel的Parsedown包装器,能够将markdown编译成HTML。Parsedown运行很快,并支持GitHub flavored markdown.

Guzzle

Guzzle是一个PHP版的HTTP客户端,让PHP很容易的和HTTP/1.1协议一起使用,并能减少Web服务带来的痛苦。

Hoa

Hoa是一组PHP库,它创建了工业和研究之间的桥梁。

PHP-CPP

PHP-CPP是一个C++写的PHP扩展库。它提供了一个良好的文档记录和易于使用的类的集合,可以使用和扩展构建本地PHP扩展。

Twig

Twig是一个快速、安全和稳定的PHP模板引擎。

Requests for PHP

Requests是用PHP写的HTTP库。

The Prettifier

Prettifier为一些编程语言,如CSS/HTMl/XML/PHP/SQL/Perl等,提供了一个在线编辑、格式和语言高亮的平台。

Geocoder PHP

Geocoder是一个构建geo应用很好的库,为geocoding操作提供了一个抽象层。

Slim Starter

Slim Starter由Xsanisty创建,是创建高级Web应用的解决方案。

Mink

Mink是一个PHP库,可以让你以交互的方式在浏览器中测试Web APP,它移除了两种浏览器模拟器之间的API差异,为你提供一个更准确的测试环境。

Forp

Forp是用C写的PHP分析器。Forp是轻量级的PHP扩展,它提供了一个简单的PHP数组或JSON输出,其包含了完整的脚本调用堆栈和CPU和内存使用情况。forp是非侵入性,并提供PHP注释来完成工作。

Belt

对PHP开发者来说,Belt是一个非常有用的工具,它提供了超过60个有用的函数。

Icon Generator for PHP

Icon Generator允许你生成基于彩色背景的Icon图标,这和Gmail的类似。

Rainloop

Rainloop是一个免费开源的PHP Web邮件应用,它有现代的用户接口,支持SMTP + IMAP。

Pattern Lab

Pattern Lab不仅是一个前端框架,也是一个PHP驱动的静态网站生成器、项目模式库和前端风格指南。

Composer

Composer是一个独立的PHP管理插件,在你项目的根目录创建一个组合器文件,再运行一个命令,则你所有的依赖都可以下载使用了。

Directus

Directus是用Backbone.js创建的免费开源的、客户端友好的数据库GUI,它提供了丰富的功能用户快速开发和自定义数据库解决方案管理。

PHP Debug Bar

Debug可以很容易的集成到任何项目中,并能显示来自应用任何部分的分析数据。它来自于PHP内置数据收集器的特性和受欢迎的项目。

Phalcon PHP

Phalcon PHP是C扩展的一个Web框架,提供了高性能和低资源消耗。

Pinboard

Pinboard是一个MySQL存储引擎,为PHP的MYSQL使用情况提供了实时监控/统计数据服务的只读接口。

Casebox

Casebox是一个开源的PHP/MYSQL驱动的Web应用,用于存储和管理记录、任务和文件。它有一个类似桌面的界面,我们可以创建一个unlimited-level目录用于优先存储结构化的东西。

Munee

Munee是一个一体化库,开源处理很多与Web资源优化和操作相关的事情。Munee也有很强大的缓存功能,可以在服务器和客户端缓存资源。

ImageWorkshop

ImageWorkshop是一个基于GD库的开源类,可以帮助你用PHP管理图像。这个类很像PS、GIMP一类的图像编辑软件:你可以添加许多层或层组,每一层都有一个背景图像。

Sylius

Sylius为PHP而设计的免费开源的电子商务解决方案(基于Symfony2),它能够管理任何规模的商店和复杂的产品类别。

Pico

Pico是一个开源的CMS应用,没有多余的东西,这才是最重要的。它使用平面文件作为数据库,用PHP构建。简单的说,不用设置什么,这个APP就能运行。

PHP MyFAQ

PHP MyFAQ是一个稳定开源的PHP F.A.Q. 应用,为构建一个很好的F.A.Q.系统提供了很多功能,并提供了强大的管理界面来管理类别、条目、用户和查看统计数据。A###PHP Documentor
PHP Documentor能读取代码的结构,文件系统结构、类、函数和介于两者之间的,并生成文档。

CakePHP

CakePHP是一个开源的Web应用框架,遵循MVC模式,并有PHP编写。它仿照Ruby on Rails的概念,在MIT许可下发布的。

CodeIgniter

CodeIgniter是一个强大的、开源的PHP框架。

Monsta FTP

Monsta FTP是一个PHP云件,并能将FTP文件管理放置在Web浏览器中,你可以在浏览器中进行文件的拖放。

XAMPP

XAMPP是一个免费和开源的跨平台web服务器解决方案,主要包括Apache HTTP服务器、MySQL数据库、PHP和Perl编写的脚本解释器。

NetBeans

NetBeans是开源的,并允许你使用Java, HTML5, PHP, C/C++等快速开发桌面、移动和Web应用。

Aura

Aura为PHP5.4+提供了独立的库包。这些包可以单独使用,具有一致性、也能自我组合成一个完整的框架。

PHPCheckstyle

PHPCheckstyle是一个开源功能,能帮助PHP程序员保持一致的编码风格。该工具检查输入PHP源代码和报告任何违反给定的标准。

PHP Mess Detector

PHP Mess Detector易于配置,前端用户友好。它能检查代码中的潜在问题,包括可能的错误,次优的代码,未使用的参数,等等。

Kohana

Kohana一个基于PHP5的优雅的、开源和面向对象HMVC框架,由一群志愿者维护和开发。它的目标是迅速,安全,和轻量。

Sabberworm

用PHP编写的一个CSS文件解析器。Sabberworm允许提取CSS文件到一个数据结构,操纵结构和输出(优化的)CSS。

Nette

Nette框架是一个PHPweb开发的工具。它被设计成尽可能友好、易用。它侧重于安全性和性能,绝对是最安全的PHP开发框架之一。

PHP Markdown

这是一个库包,包含了PHP Markdown解析器和额外的功能扩展。Markdown是一个text-to-html的转换工具。

Yii 2

Yii 2完整重写它的先前版本1.1,Yii也是最流行的PHP开发框架之一。Yii是一个高性能的PHP框架,最适合开发Web 2.0应用程序。

PHP Sandbox

PHP Sandbox利用PHPParser来防止沙箱运行不安全的代码。它利用FunctionParser分解传递到沙箱的调用,这样,即使没有转换成字符串,PHP调用也可以在沙箱中运行。

译文出处:http://www.ido321.com/1546.html

英文原文:40+ tools for writing better PHP

2015年五月3日晚上 8:57:23 HT for Web的HTML5树组件延迟加载技术实现

HT for WebHTML5树组件有延迟加载的功能,这个功能对于那些需要从服务器读取具有层级依赖关系数据时非常有用,需要获取数据的时候再向服务器发起请求,这样可减轻服务器压力,同时也减少了浏览器的等待时间,让页面的加载更加流畅,增强用户体验。
进入正题,今天用来做演示的Demo是,客户端请求服务器读取系统文件目录结构,通过HT for Web的HTML5树组件显示系统文件目录结构。
首先,我们先来设计下服务器,这次Demo的服务器采用Node.js,用到了Node.js的expresssocket.io、fs和http这四个模块,Node.js的相关知识,我在这里就不阐述了,网上的教材一堆,这里推荐下socket.io的相关入门http://socket.io/get-started/chat/
服务端代码代码:

var fs = require('fs'),
    express = require('express'),
    app = express(),
    server = require('http').createServer(app),
    io = require('socket.io')(server),
    root = ‘/Users/admin/Projects/ht-for-web/guide‘;

io.on('connection', function(socket){
    socket.on('explore', function(url){
        socket.emit('file', walk(url || root));
    });
});

app.use(express.static('/Users/admin/Projects/ht-for-web'));

server.listen(5000, function(){
    console.log('server is listening at port 5000');
});

io监听了connection事件,并获得一个socket;socket再监听一个叫explore的自定义事件,通过url参数获取到数据后,派发一个叫file的自定义事件,供客户端监听并做相应处理;通过app.use结合express.static设置项目路径;最后让server监听5000端口。
到此,一个简单的服务器就搭建好了,现在可以通过http://localhost:5000来访问服务器了。等等,好像缺了点什么。对了,获取系统文件目录结构的方法忘记给了,OK,那么我们就先来看看获取整站文件的代码是怎么写的:

function walk(pa) {
    var dirList = fs.readdirSync(pa),
        key = pa.substring(pa.lastIndexOf('/') + 1),
        obj = {
            name: key,
            path: pa,
            children: [],
            files: []
        };
    dirList.forEach(function(item) {
        var stats = fs.statSync(pa + '/' + item);
        if (stats.isDirectory()) {
            obj.children.push(walk(pa + '/' + item));
        }
        else {
            obj.files.push({name: item, dir: pa + '/' + item});
        }
    });

    return obj;
}

如大家所见,采用递归的方式,逐层遍历子目录,代码也没什么高深的地方,相信大家都看得懂。那我们来看看运行效果吧:
031953507553944.png
duang~文件目录结构出来了,是不是感觉酷酷的,这代码量不小吧。其实,代码并不多,贴出来大家瞅瞅:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>tree-loader</title>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/lib/core/ht.js"></script>
    <script>
        var socket = io(), idMap = {};
        function init() {
            var dm = window.dm = new ht.DataModel(),
                    tree = new ht.widget.TreeView(dm);

            tree.addToDOM();

            socket.on('file', function(data) {
                var root = dm.getDataById(idMap[data.path]);
                createChildren(data.children || [], root, dm);
                createFiles(data.files || [], root, dm);
            });
            socket.emit('explore');
        }

        function createChildren(children, parent, dm) {
            children.forEach(function(child) {
                var n = createData(child, parent);
                dm.add(n);
                createChildren(child.children || [], n, dm);
                createFiles(child.files || [], n, dm);
            });
        }

        function createFiles(files, parent, dm){
            files.forEach(function(file){
                var n = createData(file, parent);
                dm.add(n);
            });
        }

        function createData(data, parent){
            var n = new ht.Data();
            n.setName(data.name);
            n.setParent(parent);
            n.a('path', data.path);
            idMap[data.path] = n.getId();
            return n;
        }
    </script>
</head>
<body onload="init();">
</body>
</html>

这就是全部的HTML代码,加上空行总共也就50几行,怎么样,有没有感觉HT for Web很强大。废话不多说,来看看这些代码都干了些什么:

整体的思路是这样子的,当然这离我们要实现的树组件的延迟加载技术还有些差距,那么,HT for Web的HTML5树组件的延迟加载技术是怎么实现的呢?不要着急,马上开始探讨。
首先我们需要改造下获取文件目录的方法walk,因为前面介绍的方法中,使用的是加载整站文件目录,所以我们要将walk方法改造成只获取一级目录结构,改造起来很简单,就是将递归部分改造成获取当前节点就可以了,具体代码如下:

obj.children.push(walk(pa + '/' + item));
// 将上面对代码改成下面的代码
obj.children.push({name: item, path: pa + '/' + item});

这样子服务器就只请求当前请求路径下的第一级文件目录结构。接下来就是要调整下客户端代码了,首先需要给tree设置上loader:

tree.setLoader({
    load: function(data) {
        socket.emit('explore', data.a('path'));
        data.a('loaded', true);
    },
    isLoaded: function(data) {
        return data.a('loaded');
    }
});

loader包含了两个方法,load和isLoaded,这两个方法的功能分别是加载数据和判断数据是否已经加载,在load方法中,对socket派发explore事件,当前节点的path为参数,向服务器请求数据,之后将当前节点的loaded属性设置为true;在isLoaded方法中,返回当前节点的loaded属性,如果返回为true,那么tree将不会在执行load方法向服务器请求数据。
接下来需要移除createChildren的两个回调方法,并且在createFiles方法中为创建出来的节点的loaded属性设置成true,这样在不是目录的节点前就不会有展开的图标。createChildren和createFiles两个方法修改后的代码如下:

function createChildren(children, parent, dm) {
    children.forEach(function(child) {
        var n = createData(child, parent);
        dm.add(n);
    });
}

function createFiles(files, parent, dm){
    files.forEach(function(file){
        var n = createData(file, parent);
        n.a('loaded', true);
        dm.add(n);
    });
}

如此,HT for Web的HTML5树组件延迟加载技术就设计完成了,我在服务器的控制台打印出请求路径,看看这个延迟加载是不是真的,如下图:
031957283802225.png
031958228967071.png
看吧,控制台打印的是4条记录,第一条是请求跟目录时打印的,我在浏览器中展开里三个目录,在控制台打印了其对应的目录路径。
等等,现在这个目录看起来好烦,只有文字,除了位子前的展开图标可以用来区别文件和目录外,没有其他什么区别,所以我决定对其进行一番改造,让每一级目录都有图标,而且不同文件对应不同的图标,来看看效果吧:
031959440051625.png
怎么样,是不是一眼就能看出是什么文件,这个都是样式上面的问题,我就不再一一阐述了,直接上代码:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/build/ht-debug.js"></script>
    <script>
        var socket = io(), idMap = {};
        function init() {
            var icons = ['css', 'dir-open', 'dir', 'file', 'flash', 'gif', 'html', 'jar',
                'java', 'mp3', 'pdf', 'png', 'script', 'txt', 'video', 'xml', 'zip'];
            icons.forEach(function(c){
                ht.Default.setImage(c, 16, 16, '/test/wyl/images/' + c + '.png');
            });

            var dm = window.dm = new ht.DataModel(),
                    tree = new ht.widget.TreeView(dm);
            tree.setLoader({
                load: function(data) {
                    socket.emit('explore', data.a('path'));
                    data.a('loaded', true);
                },
                isLoaded: function(data) {
                    return data.a('loaded');
                }
            });
            tree.getLabelFont = function(data){
                return '13px Helvetica, Arial, sans-serif';
            };
            tree.getLabelColor = function (data) {
                return this.isSelected(data) ? 'white' : 'black';
            };
            tree.getSelectBackground = function (data) {
                return '#408EDB';
            };
            tree.getIcon = function (data) {
                var icon = data.getIcon() || 'file';
                if (data.a('isdir')) {
                    if (this.isExpanded(data)) {
                        icon = 'dir-open';
                    } else {
                        icon = 'dir';
                    }
                }
                return icon;
            };
            tree.addToDOM();

            socket.on('file', function(data) {
                var root = dm.getDataById(idMap[data.path]);
                createChildren(data.children || [], root, dm);
                createFiles(data.files || [], root, dm);
            });
            socket.emit('explore');
        }

        function createChildren(children, parent, dm) {
            children.forEach(function(child) {
                var n = createData(child, parent);
                n.a('isdir', true);
                dm.add(n);
            });
        }

        function createFiles(files, parent, dm){
            files.forEach(function(file){
                var n = createData(file, parent);
                n.a('loaded', true);
                dm.add(n);
            });
        }

        function createData(data, parent){
            var name = data.name,
                    icon = 'file';
            if (/.jar$/.test(name)) icon = 'jar';
            else if (/.css$/.test(name)) icon = 'css';
            else if (/.gif$/.test(name)) icon = 'gif';
            else if (/.png$/.test(name)) icon = 'png';
            else if (/.js$/.test(name)) icon = 'script';
            else if (/.html$/.test(name)) icon = 'html';
            else if (/.zip$/.test(name)) icon = 'zip';
            var n = new ht.Data();
            n.setName(data.name);
            n.setParent(parent);
            n.setIcon(icon);
            n.a('path', data.path);
            idMap[data.path] = n.getId();
            return n;
        }
    </script>
</head>
<body onload="init();">
</body>
</html>

在最后,附上完整的服务器代码:

var fs = require('fs'),
    express = require('express'),
    app = express(),
    server = require('http').createServer(app),
    io = require('socket.io')(server),
    root = '/Users/admin/Projects/ht-for-web/guide';

io.on('connection', function(socket){
    socket.on('explore', function(url){
        socket.emit('file', walk(url || root));
    });
});

app.use(express.static('/Users/admin/Projects/ht-for-web'));

server.listen(5000, function(){
    console.log('server is listening at port 5000');
});

function walk(pa) {
    var dirList = fs.readdirSync(pa),
        key = pa.substring(pa.lastIndexOf('/') + 1),
        obj = {
            name: key,
            path: pa,
            children: [],
            files: []
        };
    dirList.forEach(function(item) {
        var stats = fs.statSync(pa + '/' + item);
        if (stats.isDirectory()) {
            obj.children.push({name: item, path: pa + '/' + item});
        }
        else {
            obj.files.push({name: item, dir: pa + '/' + item});
        }
    });

    return obj;
}
2015年五月3日晚上 8:05:17 改造了一个Markdown在线编辑器,现在它终于让我感到完美了!

http://segmentfault.com/ 怎么老是莫名其妙地挂掉?网页会莫名其妙地打不开。
好吧,我且不说这事了。今天我花了一天时间在改造一个Markdown 在线编辑器,终于把它改造得满符合我的想法了。哈哈,好有成就感。我曾经在网上试用了很多markdown在线编辑器,发现绝大部分都有一个毛病:在输入框里敲下Tab键,它不是自动插入一个tab制表符,而是焦点自动跳到下一个链接处了。这对经常要写代码的我简直是抓狂。好在我终于找到了一个在线编辑器 http://lab.lepture.com/editor/,它对Tab键的处理恰好好处。但是我觉得还不够完美,于是自己动手改造它。
先说下我做了点什么修改,看图:
图片描述
我加了一几个按钮:插入视频,插入音乐,插入代码,并为它们一一分配了快捷键。并且还为Ctrl+S分配了快速提交功能。
其次是粘贴功能,这是我今天改造的重头戏。我觉得把网页上的内容粘贴到这个在线编辑器里,还得手工把它修改成Markdown代码,太费事了。于是希望能够自动完成。另外,上传图片,本来它是没有图片上传功能的,只能手工输入图片地址。费事啊!我也把这个功能集成到粘贴功能里了。
首先,需要在在线编辑器中绑定onPaste事件。
我看到那个editor.js中第1580行中有onKeyPress事件绑定。我先给它加了一个onPaste事件绑定。

javascript    on(d.input, "input", bind(fastPoll, cm));
    on(d.input, "keydown", operation(cm, onKeyDown));
    on(d.input, "keypress", operation(cm, onKeyPress));
    on(d.input, "paste", operation(cm,onPaste)); // 这句是我添加的
    on(d.input, "focus", bind(onFocus, cm));
    on(d.input, "blur", bind(onBlur, cm));

然后 ,需要写一个onPaste函数。我把它写在onKeyPress函数后面。
我先是想实现在粘贴时自动把HTML代码转换成Markdown的功能。于是写了这么一个函数。

javascript   function onPaste(e){
       if(!e.clipboardData)return true;
       //IE浏览器不支持e.clipboardData对象,无奈
       if(e.clipboardData.types=='text/plain')return true;
       // 如果剪贴板中的内容是纯文本内容,直接粘贴。
       else if(e.clipboardData.types=='text/plain,text/html'){
       // 如果剪贴板中的内容是HTML内容,则需要对它进行一番改造
       var html=e.clipboardData.getData('text/html');
       html=html.replace(/<html>(\r?\n)+<body>(\r?\n)+<!--StartFragment-->(.*?)<!--EndFragment-->(\r?\n)+<\/body>(\r?\n)+<\/html>/,"$3");
       html=toMarkdown(html);
       // toMarkdown函数 http://segmentfault.com/a/1190000002723901 在这里已经写了
       var cm=this;
        _replaceSelection(cm, false, html,'');
       e.preventDefault();
       }
     }

这里有一个很详细的剪贴板js原生对象的介绍:http://wizard.ae.krakow.pl/~jb/localio.html
本来这样算是大功告成了,但是我又觉得还有点不甘心,因为我希望以后粘贴图片方便点。
于是我继续修改这个onPaste函数,并加了一个图片上传功能。

javascript   function onPaste(e){
       if(!e.clipboardData)return true;
       if(e.clipboardData.types=='text/plain')return true;
       else if(e.clipboardData.types=='text/plain,text/html'){
       var html=e.clipboardData.getData('text/html');
       html=html.replace(/<html>(\r?\n)+<body>(\r?\n)+<!--StartFragment-->(.*?)<!--EndFragment-->(\r?\n)+<\/body>(\r?\n)+<\/html>/,"$3");
       html=toMarkdown(html);
       var cm=this;
        _replaceSelection(cm, false, html,'');
       e.preventDefault();
       }
       else if(e.clipboardData.types=='text/html,Files'){
        imgReader(e.clipboardData.items[1])
           e.preventDefault();
           }
        else if(e.clipboardData.types=='Files'){
           imgReader(e.clipboardData.items[0])
        }
      }

  function imgReader(item){
      if(item.kind=='file'&&item.type=='image/png'){
      var file = item.getAsFile(),reader = new FileReader();
      reader.onload = function( e ){
        var img = new Image();
        img.src = e.target.result;
        document.body.appendChild( img );
        // 把图片放在网页最下面,以便预览
        $.post('saveremoteimg.php',{'urls':e.target.result},function(data){
            _replaceSelection(editor.codemirror,false , '![', ']('+data+')\n');
            })
        };
    reader.readAsDataURL(file);
    }
};

saveremoteimg.php的源码是:

php<?php
header('Content-Type: text/html; charset=UTF-8');
$attachDir='upload';//上传文件保存路径,结尾不要带/
$dirType=1;//1:按天存入目录 2:按月存入目录 3:按扩展名存目录  建议使用按天存
$maxAttachSize=2097152;//最大上传大小,默认是2M
$upExt="jpg,jpeg,gif,png";//上传扩展名
ini_set('date.timezone','Asia/Shanghai');//时区

//保存远程文件
function saveRemoteImg($sUrl){
    global $upExt,$maxAttachSize;
    $reExt='('.str_replace(',','|',$upExt).')';
    if(substr($sUrl,0,10)=='data:image'){//base64编码的图片,可能出现在firefox粘贴,或者某些网站上,例如google图片
        if(!preg_match('/^data:image\/'.$reExt.'/i',$sUrl,$sExt))return false;
        $sExt=$sExt[1];
        $imgContent=base64_decode(substr($sUrl,strpos($sUrl,'base64,')+7));
    }
    else{//url图片
        if(!preg_match('/\.'.$reExt.'$/i',$sUrl,$sExt))return false;
        $sExt=$sExt[1];
        $imgContent=getUrl($sUrl);
    }
    if(strlen($imgContent)>$maxAttachSize)return false;//文件体积超过最大限制
    $sLocalFile=getLocalPath($sExt);
    file_put_contents($sLocalFile,$imgContent);
    //检查mime是否为图片,需要php.ini中开启gd2扩展
    $fileinfo= @getimagesize($sLocalFile);
    if(!$fileinfo||!preg_match("/image\/".$reExt."/i",$fileinfo['mime'])){
        @unlink($sLocalFile);
        return false;
    }
    return $sLocalFile;
}
//抓URL数据
function getUrl($sUrl,$jumpNums=0){
    $arrUrl = parse_url(trim($sUrl));
    if(!$arrUrl)return false;
    $host=$arrUrl['host'];
    $port=isset($arrUrl['port'])?$arrUrl['port']:80;
    $path=$arrUrl['path'].(isset($arrUrl['query'])?"?".$arrUrl['query']:"");
    $fp = @fsockopen($host,$port,$errno, $errstr, 30);
    if(!$fp)return false;
    $output="GET $path HTTP/1.0\r\nHost: $host\r\nReferer: $sUrl\r\nConnection: close\r\n\r\n";
    stream_set_timeout($fp, 60);
    @fputs($fp,$output);
    $Content='';
    while(!feof($fp))
    {
        $buffer = fgets($fp, 4096);
        $info = stream_get_meta_data($fp);
        if($info['timed_out'])return false;
        $Content.=$buffer;
    }
    @fclose($fp);
    global $jumpCount;//重定向
    if(preg_match("/^HTTP\/\d.\d (301|302)/is",$Content)&&$jumpNums<5)
    {
        if(preg_match("/Location:(.*?)\r\n/is",$Content,$murl))return getUrl($murl[1],$jumpNums+1);
    }
    if(!preg_match("/^HTTP\/\d.\d 200/is", $Content))return false;
    $Content=explode("\r\n\r\n",$Content,2);
    $Content=$Content[1];
    if($Content)return $Content;
    else return false;
}
//创建并返回本地文件路径
function getLocalPath($sExt){
    global $dirType,$attachDir;
    switch($dirType)
    {
        case 1: $attachSubDir = 'day_'.date('ymd'); break;
        case 2: $attachSubDir = 'month_'.date('ym'); break;
        case 3: $attachSubDir = 'ext_'.$sExt; break;
    }
    $newAttachDir = $attachDir.'/'.$attachSubDir;
    if(!is_dir($newAttachDir))
    {
        @mkdir($newAttachDir, 0777);
        @fclose(fopen($newAttachDir.'/index.htm', 'w'));
    }
    PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
    $newFilename=date("YmdHis").mt_rand(1000,9999).'.'.$sExt;
    $targetPath = $newAttachDir.'/'.$newFilename;
    return $targetPath;
}

$arrUrls=explode('|',$_POST['urls']);
$urlCount=count($arrUrls);
for($i=0;$i<$urlCount;$i++){
    $localUrl=saveRemoteImg($arrUrls[$i]);
    if($localUrl)$arrUrls[$i]=$localUrl;
}
echo implode('|',$arrUrls);
?>

想一想觉得还有点想改造的。在行内插入代码是需要在文字左右加两个点(键盘上Tab键上方的那个键),但是我发现在中文输入法中,它是自动打出·的,需要切换到英文输入状态才能打出想要的那个点。多敲一次键盘对我来说都是抓狂。我必须继续改造它,让它能像切换粗体或斜体那样用快捷键来实现。
这倒好办。再写一个 toggleCode函数,添加在toggleItalic 函数下面:

javascriptfunction toggleCode(editor) {
  var cm = editor.codemirror;
  var stat = getState(cm);

  var text;
  var start = '`';
  var end = '`';

  var startPoint = cm.getCursor('start');
  var endPoint = cm.getCursor('end');
  if (stat.code) {
    text = cm.getLine(startPoint.line);
    start = text.slice(0, startPoint.ch);
    end = text.slice(startPoint.ch);
    start = start.replace(/^(.*)?(`)(\S+.*)?$/, '$1$3');
    end = end.replace('`','');
    startPoint.ch -= 1;
    endPoint.ch -= 1;
    cm.setLine(startPoint.line, start + end);
  } else {
    text = cm.getSelection();
    cm.replaceSelection(start + text + end);

    startPoint.ch += 1;
    endPoint.ch += 1;
  }
  cm.setSelection(startPoint, endPoint);
  cm.focus();
}

然后在shortcuts数组中添加一项'Cmd-Y': toggleCode,改成这样子:

javascriptvar shortcuts = {
  'Cmd-B': toggleBold,
  'Cmd-I': toggleItalic,
  'Cmd-Y': toggleCode,  // 这项是我加的
  'Cmd-K': drawLink,
  'Cmd-Alt-I': drawImage,
  'Cmd-Q': drawCode, // 这项也是我加入的
  'Cmd-\'': toggleBlockquote,
  'Cmd-Alt-L': toggleOrderedList,
  'Cmd-L': toggleUnOrderedList,
  'Cmd-P': togglePreview
};

与此同时,getStatus函数需要改成这样:

javascriptfunction getState(cm, pos) {
  pos = pos || cm.getCursor('start');
  var stat = cm.getTokenAt(pos);
  if (!stat.type) return {};

  var types = stat.type.split(' ');

  var ret = {}, data, text;
  for (var i = 0; i < types.length; i++) {
    data = types[i];
    if (data === 'strong') {
      ret.bold = true;
    } else if (data === 'variable-2') {
      text = cm.getLine(pos.line);
      if (/^\s*\d+\.\s/.test(text)) {
        ret['ordered-list'] = true;
      } else {
        ret['unordered-list'] = true;
      }
    } else if (data === 'atom') {
      ret.quote = true;
    } else if (data === 'comment'){ // 这句是我加上去的
      ret.code = true;   // 这句也是我加上去的
    } else if (data === 'em') {
      ret.italic = true;
    }
  }
  return ret;
}

我觉得工具栏中没有按钮提示很不好。于是改改改~,改成下面这样:

javascriptvar toolbar = [
  {name: 'bold', action: toggleBold, shortcut:'Toggle Bold(Cmd-B)'},
  {name: 'italic', action: toggleItalic, shortcut:'Toggle Italic(Cmd-I)'},
  '|',

  {name: 'quote', action: toggleBlockquote, shortcut: 'toggle Blockquote(Cmd-\')'},
  {name: 'unordered-list', action: toggleUnOrderedList, shortcut:'Toggle UnorderList(Cmd-Alt-L)'},
  {name: 'ordered-list', action: toggleOrderedList, shortcut:'Toggle OrderList(Cmd-L)'},
  '|',

  {name: 'link', action: drawLink, shortcut:'Insert Link(Cmd-K)'},
  {name: 'image', action: drawImage, shortcut: 'Insert Image(Cmd-Alt-I)'},
  {name: 'play', action: drawVideo, shortcut: 'Insert Video'},
  {name: 'music', action: drawAudio, shortcut: 'Insert Audio'},
  {name: 'code', action: drawCode, shortcut: 'Insert Code(Cmd-Q)'},
  '|',

  {name: 'info', action: 'http://lab.lepture.com/editor/markdown'},
  {name: 'preview', action: togglePreview, shortcut: 'Toggle Preview'},
  {name: 'fullscreen', action: toggleFullScreen, shortcut: 'Toggle FullScreen'}
];

其实我发现原来的程序里有个小bug,就是用Ctrl+B或者Ctrl+I切换粗体、斜体的时候,第一次按Ctrl+B,会在选中块去的前后各加两个星号,而第二次按Ctrl+B的时候,前面的星号去掉了,后面的星号却没变化。我仔细看,发现原来的代码中正则表达式写错了。
我修改了toggleBoldtoggleItalic函数,现在总算正常了。

javascriptfunction toggleBold(editor) {
  var cm = editor.codemirror;
  var stat = getState(cm);

  var text;
  var start = '**';
  var end = '**';

  var startPoint = cm.getCursor('start');
  var endPoint = cm.getCursor('end');
  if (stat.bold) {
    text = cm.getLine(startPoint.line);
    start = text.slice(0, startPoint.ch);
    end = text.slice(startPoint.ch);

    start = start.replace(/^(.*)?(\*|\_){2}(\S+.*)?$/, '$1$3');
    end = end.replace(/(\*|\_){2}/, '');// 这句是我修改过的
    startPoint.ch -= 2;
    endPoint.ch -= 2;
    cm.setLine(startPoint.line, start + end);
  } else {
    text = cm.getSelection();
    cm.replaceSelection(start + text + end);

    startPoint.ch += 2;
    endPoint.ch += 2;
  }
  cm.setSelection(startPoint, endPoint);
  cm.focus();
}

function toggleItalic(editor) {
  var cm = editor.codemirror;
  var stat = getState(cm);

  var text;
  var start = '*';
  var end = '*';

  var startPoint = cm.getCursor('start');
  var endPoint = cm.getCursor('end');
  if (stat.italic) {
    text = cm.getLine(startPoint.line);
    start = text.slice(0, startPoint.ch);
    end = text.slice(startPoint.ch);

    start = start.replace(/^(.*)?(\*|\_)(\S+.*)?$/, '$1$3');
    end = end.replace(/(\*|\_)/, ''); // 这句是我修改过的
    startPoint.ch -= 1;
    endPoint.ch -= 1;
    cm.setLine(startPoint.line, start + end);
  } else {
    text = cm.getSelection();
    cm.replaceSelection(start + text + end);

    startPoint.ch += 1;
    endPoint.ch += 1;
  }
  cm.setSelection(startPoint, endPoint);
  cm.focus();
}

现在很疲惫,不过总算改得令自己满意了。掌柜的站长也改进一下segmentfault.com的在线编辑器吧。

2015年五月3日晚上 7:11:25 总是出问题的Crontab

最近用Python写了一些数据统计的脚本,并使用crontab自动执行,但是配置crontab总是要过几个坑才行的,这里总结一下这次遇到的坑。

输出

要将crontab命令的输出记录到日志文件中,可以使用重定向,不仅要重定向stdout也要重定向stderr,因为Python解释器会将异常输出到stderr。示例:

$HOME/path/to/script > $HOME/log/file 2>&1

环境变量

crontab会以用户的身份执行配置的命令,但是不会加载用户的环境变量,crontab会设置几个默认的环境变量,例如SHELL、PATH和HOME等,一定要注意PATH可不是用户自定义的PATH。

我们往往会在.bash_profile文件中定义一些全局的环境变量,但是crontab执行时并不会加载这个文件,所以你在shell中正常执行的程序,放到crontab里就不行了,很可能就是因为找不到环境变量了。要解决这个问题只能是自己加载环境变量了,可以在shell脚本中添加source $HOME/.bash_profile,或者直接添加到crontab中。

0 12 * * * source $HOME/.bash_profile && $HOME/path/to/script > $HOME/log/file 2>&1

路径

我们在写脚本时往往会使用相对路径,但是在crontab执行脚本时,由于工作目录不同,就会出现找不到文件或者目录不存在的问题。

解决方法是脚本中使用绝对路径或者在执行程序前切换工作目录,例如直接在crontab命令中切换工作目录:

0 12 * * * source $HOME/.bash_profile && cd $HOME/path/to/workdir && ./script > /HOME/log/file 2>&1

编码

我写的Python程序中输出了一些中文(编码是utf-8),在shell中直接执行没有问题,但是crontab执行时出现了UnicodeEncodeError的错误,Google了一下发现这个问题不仅仅是在crontab中会出现,在使用管道或者重定向的时候都会出现这个问题,原因是编码不同。

在终端中直接执行Python程序时,Python会将输出内容自动编码为终端所使用的编码,我使用的终端编码是utf-8,所以不会出错,输出的内容也是正常的。但是在使用管道或者重定向时,编码格式为ascii,Python会用ascii编码格式去encode输出的字符串,但是字符串的编码使用的时utf-8,所以会出现UnicodeEncodeError的错误。

解决方法:
方法一:在程序中输出的字符串都加上encode('utf-8')
方法二:在crontab中加上PYTHONIOENCODING=utf-8,将Python的stdout/stderr/stdin编码设置为utf-8。

2015年五月3日下午 5:22:24 关于 Monad 的学习笔记

假期终于看明白了 Monad, 这个关卡卡了好几年了, 终于过了
我现在只能说初步了解到 Monad, 不够深入, 打算留一点笔记下来

现在回头看, 如果从前学习得法的话, 最快可能几天或者几周就搞定的
比如说有 Node.js 那样成熟的社区跟教程, 或者公司里有就有人教的话
此前在 Haskell 中文论坛问过, 知乎问过, 微博私信问过, 英文教程也看了
总体上 Monad 就成了越来越吸引我注意力的一个概念

Rich Hichey 的影响

我强烈推荐 Rich Hickey 的演讲, 因为我觉得他非常有智慧
https://github.com/matthiasn/talk-transcripts/tree/master/Hickey_Rich
虽然很多是我听不懂的, 但让我能从更高的层次去理解函数式编程为什么好
比如说变量的问题, 他讲了好多例子, 讲清楚数据会发生改变是不可靠的
还有保持简单对于系统的可靠性会带来多大改善, 为什么面向对象有问题
好吧大部分是我听不懂, 但感觉很有启发

过程式编程是直观的, 但也是很存在问题的, 特别是学了函数式编程再回头看
比如说 null 值的问题, 看似自然而然, 实际却是考虑不够严谨
还有语句(或者说指令)按顺序执行的问题, 也很自然, 实际却考虑不足
这类问题导致我们在编写代码过程中不断发现有特殊的情况需要回头考虑
诚然迎合了新人学习编程所需的方便, 可代价却是对代码控制流的操作不够强大

我不否认有丰富经验跟能力的程序员能用过程式代码写出极为可靠的程序
然而引入函数式编程强大的复合能力, 有可能让程序变得更加简短清晰
而且如同 Haskell 这样搭配类型系统, 能让难以理解的过程稍微变得直观一些
当然, 函数式编程所需的抽象能力真的不是为新手准备的, 这带来巨大的门槛

纯函数

要理解 Monad 首先要对纯函数有足够的认识, 我假设读者有了解过 Haskell
相比过程式语言当中的函数(或者叫方法, procedure), Haskell 当中有很多不同:

最后一点跟流行编程语言区别尤其大, 即便跟 Lisp 的设计也差别很大
Lisp 虽然号称"一切皆表达式", 但在函数体, 在 begin 当中语句照样用:

racket(define (print-back)
  (define x (read))
  (print x))

比如这样的一段 Racket, 转化成 Haskell 看起来像是这样:

haskellprintBack :: IO ()
printBack = do
  x <- getLine
  print x

然而 do 表达式并不是 Haskell 真实的代码, 这是一套语法糖
执行过程会被转化为 >>= 或者 >> 函数, 就像是下面这样:

haskellprintBack = getLine >>= (\x -> print x)

或者把函数放到前面来, 这样看得就更明确了:

haskellprintBack = (>>=) getLine (\x -> print x)

就是说 getLine 的执行结果, 还有后面的函数, 都是 >>= 这个函数的参数
后边的 (\x -> print x) 几乎就是个回调函数, 对, 类似 Callback Hell
所以 do 表达式完全就是个障眼法, Haskell 里大量使用回调的写法
同时因为回调, 所以 Haskell 不会暗地里并行执行参数里的操作, 而是有明确的先后顺序
只不过 Haskell 语法灵活, 大量嵌套函数, 看起来还能跟没事一样, 看文档:
http://en.wikibooks.org/wiki/Haskell/do_notation

总结一下就是纯函数编程, 过程式语言常用的招数都被废掉了
整个 Haskell 的函数都往数学函数逼近, 比如 f(x) = x^2 + 2*x + 1
另外, 加上了一套代数化的类型系统, 能够容纳编程需要的各种类型

IO 的特殊性

IO 要特别梳理一下, 因为相较于过程式语言, 这里的 IO 处理很奇怪
https://wiki.haskell.org/IO_inside
通常编程语言的做法, 比如说常用的读取文件吧, 调用, 返回字符串, 很好理解:

jscontent = fs.readFileSync('filename', 'utf8') // Node.js
juliacontent = readall("filename") # Julia
racket(define content (file->string "filename")) ; Racket

但在纯函数语言当中有个大问题, 不是说好了参数一样, 返回值一样吗?
所以在 Haskell 当中 readFile 返回值并不是 String, 而是加上了 IO:

haskellreadFile :: IO String

结果就是处理文件内容时, 必需引入 Monad 的写法才行:

haskellmain = do
  content <- readFile "filename"
  putStr content

这个地方的 IO StringString 做了一层封装, 后面会遇到更多封装

代数类型系统

关于这一点, 我理解不准确, 但是举一些例子大概可以明白一些,
比如这是类似加法的方式定义新的类型:

haskelldata MySumType = Foo Bool | Bar Char

这是类似乘法的方式定义新的类型:

haskelldata MyProductType = Baz (Bool, Char)

这是以递归的方式定义新的类型:

haskelldata List a = Nil | Cons a (List a)

相比 C 或者 Go 通过 struct 定义新的类型, Haskell 显得很数学化
因为, 如果用在 Go 里定义类型是 A 或者 B, 怎么定义? 还有递归?

Haskell 当中关于类型的概念, 整理在一起就是一些关键字:

具体看这篇文章概括的, Haskell 当中类型, 类型类的一些操作
http://joelburget.com/data-newtype-instance-class/

这里的概念跟面向对象方面的, "类", "接口", "继承"有很多相似之处
但是看下例子, 这在 Haskell 当中是怎样使用的,
比如有一个叫做 Functor 的 Typeclass, 很多的 Type 都属于这个 Typeclass:

haskellclass Functor f where  
    fmap :: (a -> b) -> f a -> f b  

比如 Maybe Type 就是基于 Functor 实现, 首先用 data 定义 Maybe Type:

haskelldata Maybe a = Just a | Nothing
    deriving (Eq, Ord)

然后通过 instanceMaybe 上实现 Functor 约定的函数 fmap:

haskellinstance Functor Maybe where
    fmap f (Just x) = Just (f x)
    fmap f Nothing = Nothing

再比如 [] 也是, 那么首先 [] 大致可以这样定义
然后会有 [] 上实现的 Functor 约定的 fmap 方法:

haskelldata [a] = [] | a : [a] -- 演示代码, 可能有遗漏

instance Functor [] where
    fmap = map

还有一个例子比如说 Tree Type, 也可以同样实现 fmap 函数:

haskelldata Tree a = Node a [Tree a]

instance Functor Tree where
    fmap f (Leaf x) = Leaf (f x)
    fmap f (Branch left right) = Branch (fmap f left) (fmap f right)

就是说, Haskell 当中的类型, 是通过这样一套写法定义出来的
同样, Monad 也是个 Typeclass, 也就可以按上边这样理解
单看写法, Go 的 interface 定义看起来相似, 至少语法上可以理解

Functor, Applicative, Monad

Haskell 首先是我们熟悉的 Value 还有 Function 的世界
Functor, Applicative, Monad 在大谈封装的问题,
就是值会被装进一个盒子当中, 然后从盒子外边用这三种手法去操作,
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_...

首先难以理解的是, 这层封装是什么? 为什么硬生生造出一个其他语言没有的概念?
考虑到 Haskell 当中大量的 Category Theory(范畴论)的术语, 好像高等代数学到过..
范畴论群论依然是我无法理解的数学语言, 所以这我依然不能解释, 究竟为什么有一层封装?
没有办法, 只能先看一下这一层封装在 Haskell 当中派上了什么用场?

首先 Maybe Type 实现了 Monad, 那么看下 Maybe 典型的场景
注意下 Haskell 里 1 / 0 结果是 Infinity,, 这个大概也不是我们想要的
下面是封装过的除法, 0 不能作为被除数, 所以有了个 Nothing:

haskelldivide :: (Fractional a) => a -> a -> Maybe a
divide a 0 = Nothing
divide a b = Just $ a / b

考虑一下这样一个四则运算, 上面提示了, 一个情况 b 可能是 0, 除法有问题
但是作为例子, 很多 x / 0 在实际的编程当中我们会当成报错来处理,
好, 先认为报错, 那么整个程序就退出了

haskell((a / b) * c) + d

不过, 引入 Maybe Type 给出了一套不同的方案, 对应有报错和没有报错的情况:

haskell(Just 0.5 * Just 3) + Just 4
Just 1.5 + Just 4
Just 4.5
haskell((Just 1 / Just 0) * Just 3) + Just 4
(Nothing * Just 3) + Just 4
Nothing + Just 4
Nothing

没有报错, 一切正常. 如果有报错后边的结果都是 Nothing
这个就像 Railway Oriented Programming 给的那样, 增加了一套可能的流程:
http://fsharpforfunandprofit.com/posts/recipe-part2/

然后, List 也实现了 Monad, 就来看下例子, 下面一段代码打印了什么结果

haskellexample :: [(Int, Int, Int)]
example = do
  a <- [1,2]
  b <- [10,20]
  c <- [100,200]
  return (a,b,c)
-- [(1,10,100),(1,10,200),(1,20,100),(1,20,200),(2,10,100),(2,10,200),(2,20,100),(2,20,200)]

其实是列表解析, 如果按花哨的写法写, 应该是这样:

haskell[(a, b, c) | a <- [1,2], b <- [10,20], c <- [100,200]]

后面的两个例子难以理解, 但是大概看一看, (->) r 也实现了 Functor Typeclass
(->) r 是什么? 是函数, 一个参数的函数. 注意 Haskell 里的函数参数都是一个...

haskellinstance Functor ((->) r) where
    fmap = (.)

函数作为 fmap 第二个参数, 最后效果居然是实现了函数复合! f . g

haskellghci> :t fmap (*3) (+100)
fmap (*3) (+100) :: (Num a) => a -> a
ghci> fmap (*3) (+100) 1
303

更复杂的是实现了 Applicative Typeclass 的 sequenceA 函数

haskellsequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA = foldr (liftA2 (:)) (pure [])  

这个函数能把别的函数组合在一起用, 还能把 IO 操作组合在一起用,
而且这么密集的抽象... 3 个 IO 操作被排在一起了...

haskellghci> sequenceA [(>4),(<10),odd] 7  
[True,True,True]  
ghci> and $ sequenceA [(>4),(<10),odd] 7  
True  

ghci> sequenceA [getLine, getLine, getLine]  
heyh  
ho  
woo  
["heyh","ho","woo"]  

好, 回到上面的问题, Functor, Applicative, Monad 为什么有?
之前说函数是语言一切都是函数, 一些过程式的写法写不了了,
现在借助几个抽象, 好像又回来了, 而且花样还很多.. 连复合函数都构造了一遍
在这样的认识之下, 再看下 IO Monad 做了什么, 加上 do 表达式:

haskellmain :: IO ()
main = do putStrLn "What is your name: "
          name <- getLine
          putStrLn name

完全就是在模仿面向过程的编程, 或者说把面向过程里的一些东西重新造了一遍
当然我个人学到这里依然没明白设计思路, 但我知道是为什么要设计了
按照教程上的说法, 我可以整理一下几个函数之间的关联的递进:

首先, Haskell 通常的代码可以看作是对基础类型进行操作
比如我们有个函数 f, 有个数据 x, 通过 call 来调用:

haskellPrelude> let call f x = f x
Prelude> :t call
call :: (a -> b) -> a -> b

那么 call 的类型声明就是 (a -> b) -> a -> b

haskellclass Functor f where  
    fmap :: (a -> b) -> f a -> f b  

接着是 Functor, 注意类型声明变成的改变, 多了一层封装:

haskell(a -> b) -> a -> b -- call
(a -> b) -> f a -> f b -- fmap
haskellclass (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b  

到了 Applicative 呢, 又在前面加上了一层封装:

haskell(a -> b) -> a -> b -- call
(a -> b) -> f a -> f b -- fmap
f (a -> b) -> f a -> f b  -- <*>
haskellclass Monad m where  
    return :: a -> m a  

    (>>=) :: m a -> (a -> m b) -> m b  

    (>>) :: m a -> m b -> m b  
    x >> y = x >>= \_ -> y  

    fail :: String -> m a  
    fail msg = error msg  

到了 Monad, 参数顺序跟具体的封装又做了改进(m 写成 f 方便对比):

haskell(a -> b) -> a -> b -- call
(a -> b) -> f a -> f b -- fmap
f (a -> b) -> f a -> f b  -- (<*>)
f a -> (a -> f b) -> f b  -- (>>=)

大致上有个规律, 就是调用函数封装 f, 手段都是为了函数能超越封装使用
而且 f 会是什么? 有 Maybe [] ((->) r) IO, 还有其他很多
带来效果是什么? 有处理报错, 列表解析, 符合函数, 批量的 IO, 以及其他
Haskell 用纯函数补上了操作控制流和 IO 的功能, Monad 是其中一个手段

Monad 的写法

然后看下 Monad 去掉 do 表达式语法糖的时候怎么写, 原始的代码:
http://stackoverflow.com/q/16964732/883571

haskelldo num <- numberNode x
   nt1 <- numberTree t1
   nt2 <- numberTree t2
   return (Node num nt1 nt2)

去掉了语法糖, 是一串 >>= 函数连接在一起, 一层层的缩进:

haskellnumberNode x >>= \num ->
  numberTree t1 >>= \nt1 ->
    numberTree t2 >>= \nt2 ->
      return (Node num nt1 nt2)

还有一个 Applicative 的写法

haskellNode <$> numberNode x <*> numberTree t1 <*> numberTree t2

最后一个我得看老半天... 好吧, 总之, Haskell 就是提供了如此复杂的抽象
print("x") 在过程式语言中仅仅是指令, 在 Haskell 中却被处理为纯函数的调用
Haskell 将纯函数用于高阶的函数的转化以及操作, 变成很强大的控制流
前面说了, 实际上只是作为参数, 跟 Node.js 使用深度的回调很相似

不过还记得 Railway Oriented 那张图吗, 跟 Node.js 对比一下:

jsfs.readFile("filename", "utf8", function(err, content) {
  if (err) { throw err }
  console.log(content)
})

注意 err 的处理, Haskell 当中可没有写 err 而是在 >>= 内部处理掉了
而且 Haskell 也不会执行到这里就吐出返回值, 而是等全部执行完再返回
上边我用过 Callback Hell 打比方, 不过除了写法相似, 其他方面差别不小

总结

好了我不是在写 Monad 教程, 我也没全弄明白, 但是上边记录了我理解的思路:

我之前一直在想 Monad 会是数学结构当中某种强大的概念, 群论如何如何
但是回头看, 这更像是人为定义出来的方便编程语言使用的几个 Typeclass 而已
当新的数据类型被需要, 还可以自己定义, 用高阶函数玩转...
总之我不必为了弄懂 Monad 是什么回去把高等代数啃一遍...

不过呢, 过了这一关我还是不会写稍微复杂点的程序, 类型系统难点真挺多的

2015年五月3日下午 2:20:47 Lumen 初体验

介绍

Lumen:“为速度而生的 Laravel 框架”。

Lumen 是 Laravel 的作者(Taylor Otwell)的又一力作。简单、快速、优雅的它的特点,适合用于构建微服务架构和 API 应用。
官网:http://lumen.laravel.com
介绍:https://phphub.org/topics/701
中文文档:http://lumen.laravel-china.org/docs

安装

使用 composer 安装:

bashcomposer create-project laravel/lumen --prefer-dist

配置

Lumen 默认使用 .env 作为配置文件。.env.example 是官方给出的例子,直接拷贝命名为 .env

bashcd lumen
cp .env.example .env

调试模式

修改 .env 文件:

bashAPP_DEBUG=true

如果发现还是没有效果,再修改 lumen/bootstrap/app.php 文件,将 Dotenv::load 的注释移除掉。

疑问

1.为什么提示:not be found

访问:http://127.0.0.1/lumen/public/

显示:

bashSorry, the page you are looking for could not be found.

NotFoundHttpException in Application.php line 1121:

in Application.php line 1121
at Application->handleDispatcherResponse(array('0')) in Application.php line 1091
at Application->dispatch(null) in Application.php line 1026
at Application->run() in index.php line 28

查看路由文件 lumen/app/Http/routes.php

php$app->get('/', function() use ($app) {
    return $app->welcome();
});

感觉没有问题啊,和在 Laravel 中差不多的方式,那是哪里出了问题了?好的,先不管,尝试自己新定义一条路由规则试试看:

php$app->get('/test', function() use ($app) {
    return $app->welcome();
});

再访问:http://127.0.0.1/lumen/public/test

结果和刚才一样。

2.为什么会跳转

再尝试访问一下:http://127.0.0.1/lumen/public/test/
结果跳转到:http://127.0.0.1/test

解惑

我先来解释一下第 2 个问题,因为这是一个很多 Laravel 新手也经常问的问题。

原因何在?请看 lumen/public/.htaccess 文件:

bashRewriteRule ^(.*)/$ /$1 [L,R=301]

这是一条 Apache 路由重写规则(mod_rewrite 开启的情况下才有效),当请求的 URI 带有 /,就会匹配出 $1, 永久重定向(HTTP 状态码是 301)到根目录下的 $1。上面的例子中,匹配到 test(就是$1),就跳转至 /test 了。

如何来规避上面这个问题?注释这条 RewriteRule 吗?不是的。一般来说,我们应该避免使用末尾带斜杠的 URI。为什么 URI 末尾不应该带有斜杠呢?从语义是来说, test/ 表示目录,test 表示资源。还有,如果在 lumen/public 目录下真的有一个 test 目录,那么访问 http://127.0.0.1/lumen/public/test/,就会进入到 test 目录下面来,这不是我们想要的结果。(其实如果真的存在 test 目录并且不存在文件 test,那么,URI 末尾有没有斜杠都会进入到 test 目录中来,这是 Apache 决定的。因为它如果找不到文件,就会自动在末尾加个斜杠,尝试寻找目录下的 index.html 文件等等,具体是在 httpd.conf 中配置 DirectoryIndex。好吧,扯得太远了,拽回来)
总之,我还是建议 URI 末尾不要带 /,如果你非不听,那就注释上面那句 RewriteRule 吧,这样就不会重定向了。

关于第 1 个问题,我们也来分析一下发生的原因,这样才能对症下药。
根据错误提示,定位到文件 lumen/vendor/laravel/lumen-framework/src/Application.php 中:

php    /**
     * Dispatch the incoming request.
     *
     * @param  SymfonyRequest|null  $request
     * @return Response
     */
    public function dispatch($request = null)
    {
        if ($request) {
            $this->instance('Illuminate\Http\Request', $request);
            $this->ranServiceBinders['registerRequestBindings'] = true;

            $method = $request->getMethod();
            $pathInfo = $request->getPathInfo();
        } else {
            $method = $this->getMethod();
            $pathInfo = $this->getPathInfo();
        }        

        try {
            if (isset($this->routes[$method.$pathInfo])) {
                return $this->handleFoundRoute([true, $this->routes[$method.$pathInfo]['action'], []]);
            }

            return $this->handleDispatcherResponse(
                $this->createDispatcher()->dispatch($method, $pathInfo)
            );
        } catch (Exception $e) {
            return $this->sendExceptionToHandler($e);
        }
    }

匹配不到 route 的原因就在以上代码中。假设访问:http://127.0.0.1/lumen/public,那么 :

phpvar_dump($method);  // string(3) "GET"
var_dump($pathInfo);  // string(14) "/lumen/public/"

根据 lumen/app/Http/routes.php 中的定义,生成 $this->routes

phpvar_dump(array_keys($this->routes));  // array(2) { [0]=> string(4) "GET/" [1]=> string(8) "GET/test" }

由上可知, isset($this->routes[$method.$pathInfo]) 的结果就是 false,所以提示 not be found 了。
既然已经知道了原因,那问题就好解决了。解决的前提是不要改动框架的源代码,不然日后升级框架会多么蛋疼,你都把框架代码都修改,万一出了问题你咋办?你自己拆手机,官方是不保修的哦!当然,如果你是框架开发组的,你提交代码能被大家接受并被官方合并到主干代码中了,那你就改吧。

方案1:修改 DocumentRoot

修改 Apache 的配置文件 httpd.conf,将 DocumentRoot 指向 lumen/public

bashDocumentRoot "/sites/lumen/public"

重启 Apache。

但是,如果我还有其他站点也在这个 Apache 下面,改 DocumentRoot 就会导致其他的站点不能访问了。怎么办?请看方案 2

方案2:配置 Apache 虚拟主机

修改 httpd.conf,将下面这行的注释移除:

bashInclude etc/extra/httpd-vhosts.conf

修改 httpd-vhosts.conf

bash<VirtualHost *:80>
    DocumentRoot "/sites"
    ServerName 127.0.0.1
</VirtualHost>
<VirtualHost *:80>
    DocumentRoot "/sites/lumen/public"
    ServerName lumen.app
</VirtualHost>

重启 Apache。

修改主机的 etc/hosts,添加一行:

bash127.0.0.1 lumen.app

其中 127.0.0.1 应该换成你 lumen 应用存放的机器的 ip。

OK,这样就可以通过访问 http://lumen.app 来访问该 lumen 站点,通过 http://127.0.0.1 来访问其他站点。

但是,你压根不能修改 Apache 的配置,怎么办?请看方案 3

方案3.修改路由规则中的路径

改不了配置,就改代码喽(再强调一下,不是修改框架的源代码)。

修改路由文件 lumen/app/Http/routes.php

phpdefine('ROUTE_BASE', 'lumen/public/');

$app->get(ROUTE_BASE . '/index', function() use ($app) {
    return $app->welcome();
});
$app->get(ROUTE_BASE . '/test', function() use ($app) {
    return $app->welcome();
});

这样,如果以后有变化的话,你只需要修改 define('ROUTE_BASE', 'lumen/public/');就可以了(当然,把这个写到应用配置项中是最合适的,部署时修改配置就可以了)。

至于想以 'lumen/public/' 作为首页 URI 显然是不可以的,建议使用 'lumen/pulbic/index' 作为首页。如同上面代码定义的路由规则那样。

因为,无论你在路由规则的字符串末尾加了多少个斜杠, $this->routes 的键是不会带有斜杠的,最终还是不能匹配的。原因在框架源代码中 lumen/vendor/laravel/lumen-framework/src/Application.php

php    /**
     * Add a route to the collection.
     *
     * @param  string  $method
     * @param  string  $uri
     * @param  mixed  $action
     */
    protected function addRoute($method, $uri, $action)
    {
        $action = $this->parseAction($action);

        $uri = $uri === '/' ? $uri : '/'.trim($uri, '/');

        if (isset($action['as'])) {
            $this->namedRoutes[$action['as']] = $uri;
        }

        if (isset($this->groupAttributes)) {
            if (isset($this->groupAttributes['prefix'])) {
                $uri = rtrim('/'.trim($this->groupAttributes['prefix'], '/').$uri, '/');
            }

            $action = $this->mergeGroupAttributes($action);
        }

        $this->routes[$method.$uri] = ['method' => $method, 'uri' => $uri, 'action' => $action];
    }

对,就是它:$uri = $uri === '/' ? $uri : '/'.trim($uri, '/');
所有,URI 末尾还是不带斜杠的好。

2015年五月3日下午 1:07:20 求大神帮助!!!

Screen Shot 2015-05-03 at 1.00.20 PM.png

代码: Screen Shot 2015-05-03 at 1.00.41 PM.png

非常感谢

2015年五月3日中午 12:25:25 node express4.0 遇到一个偶发的问题,post有时会得到404 not found报错

在我编程OA系统中出现了一个偶发的问题,让我头痛。一个简单的数据update功能,在大多数情况下工作正常,偶尔会发生提交post数据不成功,浏览器报404 Not Found, 但nodejs没有报错,log里也没有记录。为此,我升级了mongodb到3.0, 也升级express到4.12.3,但这个问题还是存在。请各位高手帮忙诊断一下:

以下是Chrome控制台的记录404 Not Found信息:

Headers
General
Remote Address:123.56.132.188:2000
Request URL:http://ff.yinova.cn:2000/kafapiaoDetail
Request Method:POST
Status Code:404 Not Found

Response Headers
Connection:keep-alive
Content-Length:28
Content-Type:text/html; charset=utf-8
Date:Sun, 03 May 2015 03:07:38 GMT
ETag:W/"vXOLRVt8K03ixCcpAP5ICQ=="
X-Content-Type-Options:nosniff
X-Powered-By:Express

Request Headers
POST /kafapiaoDetail HTTP/1.1
Host: ff.yinova.cn:2000
Connection: keep-alive
Content-Length: 588
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://ff.yinova.cn:2000
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://ff.yinova.cn:2000/k/554203dba9e620351d690c70
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6
Cookie: connect.sid=s%3AJqC8e7eTZtAFxqRmX5juQ5t0.KkHhy5TSbYJF1stJm8zZGpP6sfyP2J92t2MN7a7u2tY

Form Data
item:XXXX投资有限公司
unit:会议费
price:343177
client:XX
address:北京市朝阳区XXXXXXXXXXXXXXX层
remark:发票备注
company:1
status:5
bank:1

过了一分钟,我再次提交这个数据,就成功了。 以下是Chrome控制台的记录信息:

Headers

General
Remote Address:123.56.132.188:2000
Request URL:http://ff.yinova.cn:2000/kafapiaoDetail
Request Method:POST
Status Code:302 Moved Temporarily

Response Headers
Connection:keep-alive
Content-Length:68
Content-Type:text/html; charset=utf-8
Date:Sun, 03 May 2015 03:24:28 GMT
Location:/kList
Vary:Accept
X-Powered-By:Express

Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,zh-TW;q=0.6
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:588
Content-Type:application/x-www-form-urlencoded
Cookie:connect.sid=s%3AJqC8e7eTZtAFxqRmX5juQ5t0.KkHhy5TSbYJF1stJm8zZGpP6sfyP2J92t2MN7a7u2tY
Host:ff.yinova.cn:2000
Origin:http://ff.yinova.cn:2000
Referer:http://ff.yinova.cn:2000/k/554203dba9e620351d690c70
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36

Form Data
item:XXXX投资有限公司
unit:会议费
price:343177
client:XX
address:北京市朝阳区XXXXXXXXXXXXXXX层
remark:发票备注
company:1
status:5
bank:1
oid:554203dba9e620351d690c70

router.js 的post代码:

router.post("/kafapiaoDetail",function(req,res) {
  var currentUser = req.session.user;
  var t = new Date();
  var y = t.getFullYear();
  var m = t.getMonth()+1;
  var d = t.getDate();
  var h = t.getHours();
  if (h < 10) {h = '0'+h;}
  var minute = t.getMinutes();
  if (minute<10) { min = '0'+min;}
  var insert_time = y + '-' + m + '-' +d +' '+ h +':'+minute;
  var status = req.body.status;
  var auditor = null;
  var auditor_time = null;
  var accountant = null;
  var accountant_time = null;
  if(currentUser.type === '3') {
    auditor = currentUser.username;
    auditor_time = insert_time;
  }
  if(currentUser.type === '2') {
    accountant = currentUser.username;
    accountant_time = insert_time;
  }
  var newAccount = {};
  newAccount.oid = req.body.oid;
  newAccount.item = req.body.item;
  newAccount.price = req.body.price;
  newAccount.unit = req.body.unit;
  newAccount.company = req.body.company;
  newAccount.status = status;
  newAccount.remark = req.body.remark;
  newAccount.address = req.body.address;
  newAccount.mobile = req.body.mobile;
  newAccount.client = req.body.client;
  newAccount.username = currentUser.username;
  newAccount.auditor = auditor;
  newAccount.auditor_time = auditor_time;
  newAccount.accountant = accountant;
  newAccount.accountant_time = accountant_time;
  newAccount.bank =req.body.bank;
  console.log('kafapiaoDetail Update:');  
  console.log(newAccount); 
  Accounting.update(newAccount.oid, newAccount, function(err, result) {
      console.log('update result:');
      if (err) {
        req.flash('error', err);
        return res.redirect('/k/'+newAccount.oid); 
      }
      if (result) {
        req.flash('success','数据已更新!');
        return res.redirect('/kList');
      }
      req.flash('error', '数据更新失败,请稍后再试!');
      res.redirect('/k/'+newAccount.oid); 
  });   
});

以下是jade页面代码:

extends bloglayout
block bcontent
  include narbar.jade
  include alert.jade
  form(method='post' role='form' action='/kafapiaoDetail')
    h2.form-signin-heading 发票审批
    - if(account.length>0||typeof(account) != 'undefined')
      - for(var i=0; i<account.length; i++)
        div.input-group  
          span.input-group-addon 抬头
          input(id='item' name='item' type='text' class='form-control' value=account[i].item)
        div.input-group  
          span.input-group-addon 项目
          input(id='unit' name='unit' type='text' class='form-control' value=account[i].unit)
        div.input-group  
          span.input-group-addon 金额
          input(id='price' name='price' type='text' class='form-control' value=account[i].price)
        div.input-group  
          span.input-group-addon 收件人
          input(id='client' name='client' type='text' class='form-control' value=account[i].client)
        div.input-group  
          span.input-group-addon 手机
          input(id='mobile' name='mobile' type='text' class='form-control' value=account[i].mobile)
        div.input-group  
          span.input-group-addon 地址
          input(id='address' name='address' type='text' class='form-control' value=account[i].address)
        div.input-group  
          span.input-group-addon 备注
          input(id='remark' name='remark' type='text' class='form-control' value=account[i].remark)
        div.input-group  
          span.input-group-addon 公司
          select(id='company' name='company' class='form-control')
            - if(account[i].company === '1')
              option(value='0') 请选择
              option(value='1'  selected='selected') 云动
              option(value='2') 会贰
              option(value='3') 会小二
              option(value='4') 会万
            - else if(account[i].company === '2')
              option(value='0') 请选择
              option(value='1') 云动
              option(value='2'  selected='selected') 会贰
              option(value='3') 会小二
              option(value='4') 会万
            - else if(account[i].company === '3')
              option(value='0') 请选择
              option(value='1') 云动
              option(value='2') 会贰
              option(value='3' selected='selected') 会小二
              option(value='4') 会万
            - else if(account[i].company === '4')
              option(value='0') 请选择
              option(value='1') 云动
              option(value='2') 会贰
              option(value='3') 会小二
              option(value='4' selected='selected') 会万
            - else
              option(value='0' selected='selected') 请选择
              option(value='1') 云动
              option(value='2') 会贰
              option(value='3') 会小二
              option(value='4') 会万
        div.input-group  
          span.input-group-addon 状态
          select(id='status' name='status' class='form-control')
            - if(account[i].status === '1')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1' selected='selected') 新建申请
              option(value='5') 已审批
            - if(account[i].status === '5')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5' selected='selected') 已审批
            - if(account[i].status === '2')
              option(value='0') 未开票取消
              option(value='2' selected='selected') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批       
            - if(account[i].status === '3')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3' selected='selected') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批
            - if(account[i].status === '4')
              option(value='0') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4' selected='selected') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批            
            - if(account[i].status === '0')
              option(value='0' selected='selected') 未开票取消
              option(value='2') 已开票
              option(value='3') 当月退票
              option(value='4') 跨月退票
              //- if(user.type === '3')
              option(value='1') 新建申请
              option(value='5') 已审批     
        div.input-group  
          span.input-group-addon 类型
          select(id='bank' name='bank' class='form-control')
            - if(account[i].bank === '2')
              option(value='1') 增值税普票
              option(value='2' selected='selected') 增值税专票
            - else
              option(value='1' selected='selected') 增值税普票
              option(value='2') 增值税专票
        input(type='text' class='input_hide' id='oid' name='oid' value=account[i]._id readonly hidden)
        button(class='btn btn-lg btn-primary' type='submit') 修改
        a(href='/kList' class='btn btn-default' type='button') 返回
2015年五月3日中午 12:01:09 关于rsyslog和loganalyzer使用

系统日志太多太分散的话就需要整合,并且分析,所以就有了这样一套东西,这样就大大的减轻了系统管理员的压力,不过现在这篇只是小试牛刀,很多应用功能还是没有用到,例如自定义日志收集过滤, 日志分析图表,等等,不过原理大致都基本如下,是可以举一反三的。

关于rsyslog和loganalyzer的配置简略架构流程图

一、rsyslog

【客户端】rsyslog配置

1.安装rsyslog

yum -y install rsyslog

2.配置rsyslog

/etc/rsyslog.conf

配置很多,但是只需要注意几个

# rsyslog v5 configuration file

# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html

#### MODULES ####

$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)  
$ModLoad imklog   # provides kernel logging support (previously done by rklogd)  
#$ModLoad immark  # provides --MARK-- message capability

# Provides UDP syslog reception
$ModLoad imudp  --注意这个
$UDPServerRun 514   --注意这个

# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514


#### GLOBAL DIRECTIVES ####

# Use default timestamp format
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

# File syncing capability is disabled by default. This feature is usually not required,
# not useful and an extreme performance hit
#$ActionFileEnableSync on

# Include all config files in /etc/rsyslog.d/
$IncludeConfig /etc/rsyslog.d/*.conf


#### RULES ####

# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.*                                                 /dev/console

# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
#*.info;mail.none;authpriv.none;cron.none                /var/log/messages
*.*                                                      @主rsyslog服务器ip或者host   --注意这个

# The authpriv file has restricted access.
authpriv.*                                              /var/log/secure

# Log all the mail messages in one place.
mail.*                                                  -/var/log/maillog


# Log cron stuff
cron.*                                                  /var/log/cron

# Everybody gets emergency messages
*.emerg                                                 *

# Save news errors of level crit and higher in a special file.
uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log


# ### begin forwarding rule ###
# The statement between the begin ... end define a SINGLE forwarding
# rule. They belong together, do NOT split them. If you create multiple
# forwarding rules, duplicate the whole block!
# Remote Logging (we use TCP for reliable delivery)
#
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
#$WorkDirectory /var/lib/rsyslog # where to place spool files
#$ActionQueueFileName fwdRule1 # unique name prefix for spool files
#$ActionQueueMaxDiskSpace 1g   # 1gb space limit (use as much as possible)
#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
#$ActionQueueType LinkedList   # run asynchronously
#$ActionResumeRetryCount -1    # infinite retries if host is down
# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
#*.* @@remote-host:514
# ### end of the forwarding rule ###

1.$ModLoad imudp和$UDPServerRun 514 开启rsyslog的日志远程传输,使用udp模式,当然也可以使用tcp模式,而且tcp也比udp更可靠,防止日志在传输过程丢失,只是需要建立稳定连接,消耗资源,各有所长,各有所需。

2.#.info;mail.none;authpriv.none;cron.none /var/log/messages 注释掉这个语句,改为.* @主rsyslog服务器ip或者host,这是为了配置rsyslog日志传输的目标,另外rsyslog的格式是分为2个方面的,一个是facitlity一个是priority,这个需要一点篇幅来说明,详细可以参阅科普时间或者官网,目前现在这个配置的意思是将所有级别的日志都传输到某个服务器。

3.客户端的rsyslog只需要配置这样就足够了。需要注意的是,如果开启了防火墙的话,那么记得514端口是rsyslog的传输端口,也要放开访问。

3.重启rsyslog服务

service rsyslog restart

【服务端】rsyslog配置

1.安装rsyslog

yum -y install rsyslog

2.配置rsyslog存储数据库(数据库的安装在下面那里一起写了)

yum -y install rsyslog-mysql 

导入创库sql

cd /usr/share/doc/rsyslog-mysql-5.8.10/
[root@localhost rsyslog-mysql-5.8.10]# ls
createDB.sql
[root@localhost rsyslog-mysql-5.8.10]# mysql -u root -p < createDB.sql 

这个sql会自动帮你创建rsyslog存储在mysql中的数据的表,等下可以直接被loganalyzer读取数据,然后使用。

连接数据库检查

mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.1.73 Source distribution

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| Syslog             |
| mysql              |
| test               |
+--------------------+
4 rows in set (0.00 sec)


mysql> use Syslog
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+------------------------+
| Tables_in_Syslog       |
+------------------------+
| SystemEvents           |
| SystemEventsProperties |
+------------------------+
2 rows in set (0.00 sec)

配置rsyslog连接mysql账号和密码和授权

mysql> grant all on Syslog.* to 'rsysloga'@'localhost' identified by 'rsyslogp';  #设置用户访问数据库服务器中Syslog数据库的用户名和密码,因为rsyslog服务端和mysql数据库是在同一台机器上,所以只允许本机访问就可以了
Query OK, 0 rows affected (0.00 sec)

flush privileges;  #刷新权限,及时生效

3.配置服务端的rsyslog.conf(数据库的安装在下面那里一起写了)

cat /etc/rsyslog.conf 
# rsyslog v5 configuration file

# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html

#### MODULES ####

$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
$ModLoad imklog   # provides kernel logging support (previously done by rklogd)
#$ModLoad immark  # provides --MARK-- message capability
$ModLoad ommysql    --注意这个

# Provides UDP syslog reception
$ModLoad imudp      --注意这个
$UDPServerRun 514   --注意这个

# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514


#### GLOBAL DIRECTIVES ####

# Use default timestamp format
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

# File syncing capability is disabled by default. This feature is usually not required,
# not useful and an extreme performance hit
#$ActionFileEnableSync on

# Include all config files in /etc/rsyslog.d/
$IncludeConfig /etc/rsyslog.d/*.conf


#### RULES ####

# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.*                                                 /dev/console

# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
#*.info;mail.none;authpriv.none;cron.none                /var/log/messages      
*.*                                                     :ommysql:127.0.0.1,Syslog,rsysaloga,rsyslogp        --注意这个 

# The authpriv file has restricted access.
authpriv.*                                              /var/log/secure

# Log all the mail messages in one place.
mail.*                                                  -/var/log/maillog


# Log cron stuff
cron.*                                                  /var/log/cron

# Everybody gets emergency messages
*.emerg                                                 *

# Save news errors of level crit and higher in a special file.
uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log


# ### begin forwarding rule ###
# The statement between the begin ... end define a SINGLE forwarding
# rule. They belong together, do NOT split them. If you create multiple
# forwarding rules, duplicate the whole block!
# Remote Logging (we use TCP for reliable delivery)
#
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
#$WorkDirectory /var/lib/rsyslog # where to place spool files
#$ActionQueueFileName fwdRule1 # unique name prefix for spool files
#$ActionQueueMaxDiskSpace 1g   # 1gb space limit (use as much as possible)
#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
#$ActionQueueType LinkedList   # run asynchronously
#$ActionResumeRetryCount -1    # infinite retries if host is down
# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
#*.* @@remote-host:514
# ### end of the forwarding rule ###

1.增加了$ModLoad ommysql这个模块,这个就是rsyslog连接mysql使用的模块

2.. :ommysql:127.0.0.1,Syslog,rsysaloga,rsyslogp 这里意思是说使用某个可以连接mysql的账号和密码,连接mysql,将数据传输到mysql数据库里面去。

3.udp传输配置依然要开启,那是因为rsyslog客户端会将日志以udp的方式传输到rsyslog服务端,所以双方都要开启同样的传输方式才可以完成传输。

4.需要注意的是,如果开启了防火墙的话,那么记得514端口是rsyslog的传输端口,也要放开访问。

4.重启rsyslog服务

service rsyslog restart

至此,rsyslog部分已经完成配置,若要检查是否配置成功,可以检查数据库里是否有数据即可。

mysql> select * from SystemEvents;

二、loganalyzer

可以理解为loganalyzer其实就是一个web平台来展现日志数据的而已。

1.下载并安装http+php+mysql套件

yum -y install httpd php php-mysql php-gd mysql mysql-server

httpd用来提供web服务
php使apache支持php,因为loganalyzer是用php编写
php-mysql用于loganalyzer连接数据库
php-gd用于绘图
mysql 是loganalyzer存储数据的地方

设置MySQL的root用户设置密码,因为MySQL被安装时,它的root用户时没有设置密码的,所以可以直接连或者设置一个,但是不影响我们这次配置任务。mysql需要启动,这个需要注意。

2.配置apache+php,并启动apache和mysql

因为yum安装的关系,所有一切都已经配置好了

如:grep -E 'Document|Listen' /etc/httpd/conf/httpd.conf |grep -v '^#'  
Listen 80
DocumentRoot "/var/www/html"

如:grep -v '^#' /etc/httpd/conf.d/php.conf 
<IfModule prefork.c>
  LoadModule php5_module modules/libphp5.so
</IfModule>
<IfModule worker.c>
  LoadModule php5_module modules/libphp5-zts.so
</IfModule>

AddHandler php5-script .php
AddType text/html .php

DirectoryIndex index.php    

启动httpd

service httpd start

启动mysql

service mysqld start

3.下载loganalyzer

下载地址:http://download.adiscon.com/loganalyzer/loganalyzer-3.6.6.tar.gz

将其放置到配置好的apache的web目录里面/var/www/html

tar -zxpf loganalyzer-3.6.6.tar.gz -C /var/www/html 

cd /var/www/html/
[root@localhost html]# ls
loganalyzer-3.6.6

授权目录

chown -R apache.apache /var/www/html/loganalyzer-3.6.6/

4.配置loganalyzer

创建loganalyzer数据库和访问账号密码和授权

mysql> create database loganalyzer;
Query OK, 1 row affected (0.04 sec)
mysql> grant all on loganalyzer.* to loga@'localhost' identified by 'logp';
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

生成config.php

cd /var/www/html/loganalyzer-3.6.6/contrib

chmod +x *

./configure.sh 

在当前目录会生成config.php文件,然后将其放置到src目录去

cp config.php /var/www/html/loganalyzer-3.6.6/src/

在浏览器访问这台主机的80端口

http://服务端的ip/loganalyzer-3.6.6/src/install.php

备注:

1.step 2 会检查config.php的写入权限,如果没有请授权一下, chmod +w config.php
2.step 3 选择enable user database,使用自定义数据库,然后填写数据库访问信息,这里的数据库是指loganalyzer的用户数据库,而不是rsyslog日志存储的数据库,这里是需要注意的。并且选取require user to be login。
3.step 5 会将loganalyzer的相关用户表写入到数据库,可以检查loganalyzer的数据库就可以看到了。
4.step 6 配置loganalyzer的管理员账号,登录loganalyzer界面使用的。
5.step 7 是配置rsyslog的日志存储数据库的访问方法,在source type选择 mysql native,然后填写mysql的访问信息,记住,这里是rsyslog的日志存储数据库,不是loganalyzer的用户数据库。
6.完成后会自动跳转提示登录,登陆后就可以看到数据了。

三、科普时间

1.关于rsyslog的日志规则facitlity和priority

###rsyslog.conf中日志规则的定义的格式
facitlity.priority          Target
#facility: 日志设备(可以理解为日志类型):
==============================================================
auth         #pam产生的日志,认证日志
authpriv     #ssh,ftp等登录信息的验证信息,认证授权认证
cron         #时间任务相关
kern         #内核
lpr          #打印
mail         #邮件
mark(syslog) #rsyslog服务内部的信息,时间标识
news         #新闻组
user         #用户程序产生的相关信息
uucp         #unix to unix copy, unix主机之间相关的通讯
local 1~7    #自定义的日志设备
===============================================================
#priority: 级别日志级别:
=====================================================================
debug           #有调式信息的,日志信息最多
info            #一般信息的日志,最常用
notice          #最具有重要性的普通条件的信息
warning, warn   #警告级别
err, error      #错误级别,阻止某个功能或者模块不能正常工作的信息
crit            #严重级别,阻止整个系统或者整个软件不能正常工作的信息
alert           #需要立刻修改的信息
emerg, panic    #内核崩溃等严重信息
###从上到下,级别从低到高,记录的信息越来越少,如果设置的日志内性为err,则日志不会记录比err级别低的日志,只会记录比err更高级别的日志,也包括err本身的日志。
=====================================================================
Target:
  #文件, 如/var/log/messages
  #用户, root,*(表示所有用户)
  #日志服务器,@172.16.22.1
  #管道        | COMMAND

2.如果日志数量太大,内容太多,可以进行过滤记录日志

简单的方法可以在rsyslog客户端上的配置

:msg, !contains, "informational"  
*.*                 @主rsyslog服务器ip或者host

在传输配置的上一行增加一个过滤配置,格式是严格的,一定要在上一行增加过滤配置,这里的意思是日志内容出现informational的就不记录。详细的过滤方式在官网上有说,需要的话就要慢慢按照他的方式来使用。


参考文档:

1.http://litaotao.blog.51cto.com/6224470/1283871

2.http://ftp.ics.uci.edu/pub/centos0/ics-custom-build/BUILD/rsyslog-3.19...

3.http://www.cnblogs.com/tobeseeker/archive/2013/03/10/2953250.html


原文链接:
http://www.godblessyuan.com/2015/05/02/rsyslog_loganalyzer_setting/

2015年五月3日凌晨 1:14:59 【译】编写更好的CSS必备的40个工具

众所周知,CSS是非常棒的,它使网站看起来很漂亮,可以为网站添加动画,并让呈现和内容分离。去了解CSS的一切是非常难做到的,它只会变得更加困难,因为我们想让我们的代码跨浏览器兼容。
这里介绍了很多第三方工具,从简化工作流程到生成真正的CSS,这些工具都提供了我们需要的代码,并且比我们自己写出的代码运行的更快。

Pure

Pure并不是一个框架。相反,它只是集成一些已经应用到模块中的CSS代码,方便我们使用。只需要为你的项目抓取你想要的那部分CSS代码。当然,所有组件都是可用的。Pure中包含了网格系统、按钮、表格、表单和菜单,这些都是建立在normalize.css上的。

Magic Animations CSS3

Magic Animations CSS3集成了 CSS3 Animations,可以被应用在任何元素上,包含元素替换、滑出、变形和消褪等效果

Jeet Grid System

Jeet和semantic.gs有点类似,是以SASS为基础的网格系统。你可以在CSS中定义列(有时定义行),而不是为标记元素添加Class。Jeet使响应式布局更加容易,并进一步分离了内容和呈现。

10 Pure CSS Flat Mobile Devices

一个叫Oleg的人用纯CSS重绘并模拟了10种不同的移动设备(包括iPhone 6, iPad Mini, Nexus 5, and Lumia 920)

CodyHouse

可以给你的网站添加不相关的、独立组件的一个库。CodyHouse是用HTML、CSS和JavaScript建立的,你可以选择各种各样的导航、视差效果、分页、模态窗口、页面布局等等,每一个组件独有让你快速使用的教程。

Ratchet

如果你使用HTML、CSS和JavaScript是移动APP,Ratchet应该是一个不错的框架。每一个UI组件都是针对移动设备定制的,并且它有很多你在传统的HTML/CSS框架中看不到的功能。组件的默认效果都是非常棒的。

Animo.JS

Animo基于JQuery,能帮你更好的触发CSS动画。你可以叠加动画,或者第一次完成后触发第二个动画,并能同时利用CSS动画提供的硬件加速优点。

Adobe Extract

将一个Photoshop图层样式文件转换为CSS是一件很痛苦的事。幸运的是,Adobe做了一个工具(运行在浏览器中),允许你选择某个图层,将其属性转换为CSS代码。您也可以选择任何在线网站在PSD文件中使用的文本。

Sculpt

Sculpt基于SASS,是一个很好的框架。与其他已经发布的框架相比,Sculpt支持被遗弃的低版本浏览器。如果你用SASS开发移动优先的网站,并想要网站正常运行在低版本的IE上,可以考虑Sculpt。

CSS3 Generator

一个简单通用的CSS3属性生成器。它不是很新,但是当你忘了一些精确的语法时,它是非常有用的。

Bourbon Neat

SASS的最爱了,Bourbon Neat是一个简单的语义网格系统,可以单独使用,但它的设计是用 Bourbon mixin库。

Enjoy CSS

Enjoy CSS也是一个CSS3生成器,但有趣的是,它不仅仅是生成CSS3-related代码,你还可以选择你想要应用的元素:一个div,文本输入,一个按钮,等等,用一种简单可视的方式得到你想要的确切效果。

Keyframer

从这里开始创建你的keyframe-based CSS animations。只需要去这个网站学习一些教程。

Gumby

Gumby是一个HTML/CSS框架,为那些喜欢在Ruby环境中工作的人设计的。你可以单独下载它,当然,但也打包为一个Ruby gem,Ruby gem是由那些这种技术的人创建的。

CSShake

这有更多的CSS动画,重点是做出一些改变(知道我说什么吗?),但是,他们在炫耀他们的在线赚钱艺术(我不能容忍!)。

Bounce.JS

Bounce.JS结合了可视化(用于设计CSS3 动画)和JS库(用于实现),对于那些喜欢视觉设计的人来说,它的使用是非常简单的。

GridLover

需要一个简单、可视化的方式去调整字体大小吗?GridLover提供了一种简单的方式来预览排版、设置匀称的垂直和抓取CSS。你可以抓取CSS中字体的像素值、EMs, or REMs, 这些值会被格式成普通的CSS, SASS, LESS或其他代码风格。

ExtractCSS

想要快速设置CSS文件?一种方式是首先写HTML,然后设置ID、class等,将HTML代码粘贴到ExtractCSS,Web APP会列出所有的选择器,最后将它们放入CSS文件就行就可以了。

Kite

Kite是一个用于布局的CSS库,其设计用到了CSS Flex模块,但并不是完全使用Flex。Kite兼容IE8+。

Pesticide

需要确切地找出你的布局发生了什么?添加PesticideCSS文件。它将给页面上的每个元素添加边框,当元素作为子层次结构时,会巧妙地改变边框颜色。简单,但让人印象深刻。

Pleeease

疲惫的寻找不同的工具来对CSS进行预处理,添加特定的前缀,包括IE过滤器?不介意使用命令行吗?这是给你的。兼容SASS,LESS和Stylus

CSS Colours

CSS友好的颜色名称列表,包含了十六进制和rgba格式。

CSS Vocabulary

一个小应用程序,提供了一个方便的css相关的术语列表。选择其中一个,它将通过高亮一些示例代码来说明这个术语。

Tridiv

用纯CSS建立复杂的三维模型

Buttons

用SASS和Compass建立CSS按钮库

CSS Menu Maker

CSS Menu Maker能帮助你建立简单、响应式的导航

One% CSS Grid

One% CSS Grid是一个12列的流布局网格系统,它是为构建更快、成本更低的响应式布局而设计的。

Simptip

Simptip是由SASS制作的CSS提示框工具。不仅可以设置提示框的方向(上、右、下、左),还可以设置不同的颜色,例如成功色、信息色、警告色和危险色。

Myth

Myth是一个CSS预处理器,这样你只需要写CSS,不用去担心低版本浏览器的支持,甚至低版本规范的改进。

Hover CSS

集成了CSS悬浮效果的代码,可被用在链接、按钮、logos、SVG和特色图片等等。

CSS Animation Cheat Sheet

CSS Animation Cheat Sheet是一组预设、即插即用的动画CSS库.你只需要将样式表导入到你的网站,然后给你想要添加动画的元素添加类就行。

Spinkit

Spinkit包含了一些简单但非常棒的CSS动画加载效果

Typebase.CSS

Typebase.CSS是一个很小的、可定制的排版样式表。它同时又less和sass版本,因此可以很容易地修改和合并到现代web项目。

SpriteBox

使你的CSS imager sprites变成可拖放的编辑器,并让它为你写代码。

CSS Ratiocinator

CSS Ratiocinator是一个命令行工具,通过检查实际的呈现效果,会清除掉没用的CSS代码。它非常适合应用在一些CSS文件已经超出控制的大项目。

CSS Beautifier

美化CSS,如果你已经得到了一个缩小的文件但不能找到原始(或你只是有点混乱的代码)文件时,代码的美化可以通过适当的格式化和缩进修复。

CSScomb

在使用CSS Beautifier让你的代码变得可读之后,你可以使用CSScomb运行代码,确保所有的属性都按照字母表有规则的排序。记住,不是选择器,而是属性,例如宽度总是在字体声明之后等等

Anima

一个动画库,为了扩展CSS动画的功能而设计的,并且能同时为100个元素设置动画。

Recess

Recess是一个剥绒机程序,也可以作为一个编译器运行,目的是确保你的CSS符合一组规则并保持精简。每个规则可以单独禁以满足你的编码风格。

Bonus: A to Z CSS

Bonus: A to Z CSS不是一个工具,但是对于初学者来说是一个很好的资源。在A to Z,Guy Routledge为每一个CSS基本规则,如盒子模型及最常用的CSS属性,提供了坚实的课程。

译文出处:http://www.ido321.com/1545.html
英文原文:40 tools for writing better CSS

2015年五月2日晚上 11:36:15 Lua 学习笔记(下)

前面的部分见 Lua 学习笔记(上)

4 辅助库

辅助库为我们用 Lua 与 C 的通信提供了一些方便的函数。基础 API 提供 Lua 与 C 交互的所有原始函数。辅助库作为更高层次的函数来解决一些通用的问题。

辅助库的所有函数定义在头文件 luaxlib.h 中,函数带有前缀 luaL_

辅助库函数是建立在基础库之上的,所以基础库做不了的事情辅助库也做不了。

有一些函数是用来检查函数参数的,这些函数都有这样的前缀 luaL_check 或者 luaL_opt。这些函数在检查出问题后会抛出错误。由于抛出的错误消息表明是参数错误(例如,“bad argument #1”),因此不要把这些函数用在参数以外的 Lua 值上。

5 标准库

标准 Lua 库提供了许多有用的函数,这些函数都是直接用 C API 实现的。有一些函数提供了 Lua 语言本身所必要的服务(例如,typegetmetatable);有一些提供了通向“外部”的服务(例如,I/O);还有一些函数,可以由 Lua 进行实现,但是由于相当有用或者有重要的性能需求需要由 C 实现(例如 sort)。

所有的库都以 C 模块的形式分开提供。5.1中,Lua有以下几种标准库:

- 基础库
- 包库
- 字符串操作库
- 表操作库
- 数学功能库
- 输入输出库
- 操作系统工具库
- 调试工具库

除了基础库和包库,其他库都是作为全局表的域或者对象的方法提供。

[待补充]

5.1 基础库函数

基础库为 Lua 提供了一些核心函数。如果没有包含这个库,那么就可能需要自己来实现一些 Lua 语言特性了。

assert (v [, message])

如果其参数 v 的值为假(nilfalse), 它就调用 error; 否则,返回所有的参数。 在错误情况时, message 指那个错误对象; 如果不提供这个参数,参数默认为 "assertion failed!" 。

例子

assert(5==4,"Number Not Equal!")    --> 报错 Number Not Equal!
assert(nil)                         --> 报错 assertion failed!

collectgarbage (opt [, arg])

控制垃圾回收器的参数有两个,pause 和 step multipier。

参数 pause 控制了收集器在开始一个新的收集周期之前要等待多久。 随着数字的增大就导致收集器工作工作的不那么主动。 小于 1 的值意味着收集器在新的周期开始时不再等待。 当值为 2 的时候意味着在总使用内存数量达到原来的两倍时再开启新的周期。

参数 step multiplier 控制了收集器相对内存分配的速度。 更大的数字将导致收集器工作的更主动的同时,也使每步收集的尺寸增加。 小于 1 的值会使收集器工作的非常慢,可能导致收集器永远都结束不了当前周期。 缺省值为 2 ,这意味着收集器将以内存分配器的两倍速运行。

该函数是垃圾回收器的通用接口,根据 opt 参数的不同实现不同的功能。

例子

进行垃圾回收前后的内存占用情况:

x = collectgarbage("count")
print(x)            --> 27.5615234375
collectgarbage("collect")
x = collectgarbage("count")
print(x)            --> 26.7490234375

dofile (filename)

打开该名字的文件,并执行文件中的 Lua 代码块。 不带参数调用时, dofile 执行标准输入的内容(stdin)。 返回该代码块的所有返回值。 对于有错误的情况,dofile 将错误反馈给调用者 (即,dofile 没有运行在保护模式下)。

例子

同一目录下新建两个 Lua 文件,代码及输出:

-- another.lua
return "Message from another file!"

-- sample.lua
x = dofile("./another.lua")
print(x)    --> Message from another file!

dofile 在这里等价于

function dofile()
    function func()
        return "Message from another file!"
    end

    return func()
end

于是等价的输出为

print(dofile()) --> Message from another file!

error (message [, level])

终止所保护的函数,抛出 message 消息,不再返回。

通常这个函数会在抛出的消息前面加上发生错误的地址信息。堆栈等级决定添加哪个地址。如果堆栈等级为 0 ,不返回地址信息;如果为 1,返回 error 语句所在位置;如果为 2,返回调用 error所在函数的位置;依此类推。

定义

error([报错消息],[堆栈等级]=1)

例子

function division(a,b)
    if b == 0 then error("Divisor cannot be 0!",2) end      -- level 值为 1 时,错误信息指向这里
    return a / b
end

print(division(5,1))
print(division(5,0))        -- level 值为 2 时,错误信息指向这里

_G

_G 持有全局环境的变量,Lua 本身用不到这个变量,更改变量不会影响到全局环境;反过来也一样。

getfenv (f)

返回函数的环境。 f 可以是一个 Lua 函数,也可以是函数在堆栈中的等级。等级 1 代表调用 getfenv() 的那个函数。如果传入的函数不是 Lua 函数,或者 f 是 0,那么 getfenv 返回 全局环境。

~~[待补充]不是太明白,没能给出合适的代码~~
参考 Lua中的环境概念

定义

getfenv([目标函数]=1)

例子

获取全局环境:

for k,v in pairs(_G) do
print(k,v)
end

--string            table: 0x7ff200f02330
--xpcall            function: 0x7ff200d03cc0
--package           table: 0x7ff200e00000
--tostring          function: 0x7ff200d04560
--print             function: 0x7ff200d046a0
--os                table: 0x7ff200f01cb0
--unpack            function: 0x7ff200d04610
--require           function: 0x7ff200f006f0
--getfenv           function: 0x7ff200d042f0
--setmetatable      function: 0x7ff200d044a0
--next              function: 0x7ff200d04260
--assert            function: 0x7ff200d03fc0
--tonumber          function: 0x7ff200d04500
--io                table: 0x7ff200f014a0
--rawequal          function: 0x7ff200d046f0
--collectgarbage    function: 0x7ff200d04010
--arg               table: 0x7ff200e01360
--getmetatable      function: 0x7ff200d04340
--module            function: 0x7ff200f006a0
--rawset            function: 0x7ff200d047a0
--math              table: 0x7ff200e00040
--debug             table: 0x7ff200e00a00
--pcall             function: 0x7ff200d042b0
--table             table: 0x7ff200f00790
--newproxy          function: 0x7ff200d04820
--type              function: 0x7ff200d045c0
--coroutine         table: 0x7ff200d048c0
--_G                table: 0x7ff200d02fc0
--select            function: 0x7ff200d04400
--gcinfo            function: 0x7ff200d03000
--pairs             function: 0x7ff200d03e00
--rawget            function: 0x7ff200d04750
--loadstring        function: 0x7ff200d04200
--ipairs            function: 0x7ff200d03d70
--_VERSION          Lua 5.1
--dofile            function: 0x7ff200d04110
--setfenv           function: 0x7ff200d04450
--load              function: 0x7ff200d041b0
--error             function: 0x7ff200d04160
--loadfile          function: 0x7ff200d043a0

getmetatable (object)

如果对象没有元表,返回空;如果对象有 __metatable 域,返回对应的值;否则,返回对象的元表。

例子

对象有 __metatable 域的情况:

t = {num = "a table"}

mt = {__index = {x = 1,y = 2},__metatable = {__index = {x = 5,y = 6}}}
setmetatable(t, mt)

print(getmetatable(t).__index.x)  --> 5
print(t.x)                        --> 1

~~进行操作时的元表依旧是与值直接关联的那个元表,不知道这样子处理有什么作用?~~

ipairs (t)

返回三个值:迭代器、传入的表 t、值 0 。迭代器能够根据传入的表 t 和索引 i 得到 i+1 和 t[i+1] 的值。

其实现形式类似于这样:

function ipairs(t)

    function iterator(t,i)
        i = i + 1
        i = t[i] and i      -- 如果 t[i] == nil 则 i = nil;否则 i = i
        return i,t[i]
    end

    return iterator,t,0

end

例子

使用 ipairs 对表进行遍历,会从键值为 1 开始依次向后遍历,直到值为 nil。

t = {"1","2",nil,[4]="4"}
-- t = {"1","2",[4]="4"}   -- 使用该表会得到相同的输出

for i,v in ipairs(t) do
    print(i,v)
end

--> 1   1
--> 2   2

load (func [, chunkname])

通过传入函数 func 的返回值获取代码块片段。func 函数的后一次调用返回的字符串应当能与前一次调用返回的字符串衔接在一起,最终得到完整的代码块。函数返回 nil 或无返回值时表示代码块结束。

load 函数会将得到的代码块作为函数返回。返回函数的环境为全局环境。如果出现错误,load 会返回 nil 和 错误信息。

chunkname 作为该代码块的名称,用在错误信息与调试信息中。

例子

[待补充]

loadfile ([filename])

使用方式与 dofile 类似,函数内容与 load 类似。从文件中获取代码块,如果没有指定文件,则从标准输入中获取。

loadfile 把文件代码编译为中间码,以文件代码作为一个代码块(chunk),并返回包含此代码块的函数。
编译代码成中间码,并返回编译后的chunk作为一个函数。 如果出现错误,则返回 nil 以及错误信息。

使用 loadfile,可以一次编译多次运行;而每次使用 dofile,都会执行一次编译。

例子

同一目录下新建两个 Lua 文件,代码及输出:

-- sample.lua
f = loadfile("./sample.lua")
print(f())
--> Message from another file!
--> 0

-- another.lua
function fun()
 print("Message from another file!")
 return 0
end
res = fun()
return res

loadfile 在这里等价于

function loadfile()
    function fun()
     print("Message from another file!")
     return 0
    end
    res = fun()
    return res
end

loadstring (string [, chunkname])

与 load 类似,只不过是从字符串中获取代码块。

要想加载并运行所给的字符串,使用如下惯用形式:

assert(loadingstring(s))()

next (table [, index])

返回传入的表中下一个键值对。

定义

next([表],[键]=nil)

第一个参数是要操作的表,第二个参数是表中的某个键。如果传入的键值为 nil ,则函数返回第一个键值对。如果传入一个有效的键值,则输出下一对键值对。如果没有下一个键值对,返回 nil。

根据定义,可以使用 next 来判断一个表是否为空表。

注意:

键值对的遍历顺序是不一定的,即使是对数字索引也是如此。如果想要按照数字索引的顺序获取键值对,参见 ipairs (t) 函数。

例子

t = {"table",["a"] = 5, ["c"] = 6}

-- index 为 nil
print(next(t, nil))         --> 1   table

-- index 为 无效键
print(next(t,"d"))          --> 编译错误

-- index 为 数字索引
print(next(t,1))            --> a   5

-- index 为 一般键
print(next(t, "a"))         --> c   6

-- index 为 最后一个键
print(next(t,"c"))          --> nil

遍历顺序与定义顺序不一致的例子:

t = {[1]="table",b = 4,["a"] = 5, ["c"] = 6, func}

t.func = function (...)
    return true
end

for k,v in pairs(t) do
    print(k,v)
end
--> a   5
--> func    function: 0x7f7f63c0ad50
--> c   6
--> b   4

而且从上面的例子中可以看出 name = exp 的键值对形式会占用

pairs (t)

返回三个值:next 函数,表 t,nil。通常用来遍历表中的所有键值对。
如果 t 有元方法 __pairs ,将 t 作为参数 传入该函数并返回前三个返回值。

在使用 pairs 函数遍历表的过程中,可以删除域或者修改已有的域,但是如果添加新的域,可能出现无法预期的问题。

例子

t = {"table",["a"] = 5, ["c"] = 6}
for k,v in pairs(t) do
    print(k,v)
end
--> 1   table
--> a   5
--> c   6

在遍历表的过程中添加新的域导致问题出现的情况:

t = {"table",["a"] = 5, ["c"] = 6}

for k,v in pairs(t) do
    -- 添加一个新的域
    if k == 'a' then
        t[2] = 8
    end
    print(k,v)
end
--> 1   table
--> a   5

pcall (f [, arg1, ...])

以保护模式调用传入的函数,也就是说不会抛出错误。如果捕获到抛出的错误,第一个参数返回 false,第二个参数返回错误信息;如果没有出现错误,第一个参数返回 ture,后面的参数返回传入函数的返回值。

#

function fun(a,b)

    assert(not(b == 0), "divisor can't be 0 !")

    return a / b    
end

success, res = pcall(fun,5,0) --> false .../sample.lua:3: divisor can't be 0 !
success, res = pcall(fun,5,1) --> true  5

print (...)

仅作为快速查看某个值的工具,不用做格式化输出。正式的格式化输出见 string.format 与 io.write。

rawequal (v1, v2)

raw 作为前缀的函数均表示该方法在不触发任何元方法的情况下调用。

rawequal 检查 v1 是否与 v2 相等,返回比较结果。

例子

t = {"value"}
s = "value"
s2 = "value"
print(rawequal(t, s))     --> false
print(rawequal(s, s2))    --> true

rawget (table, index)

获取 table 中键 index 的关联值,table 参数必须是一个表,找不到返回 nil 。

例子

t = {"value",x = 5}

print(rawget(t, 1))     --> value
print(rawget(t, "x"))   --> 5
print(rawget(t, 2))     --> nil
print(rawget("value",1))--> bad argument #1 to 'rawget' (table expected, got string)

rawset (table, index, value)

将 table[index] 的值设置为 value 。table 必须是一张表,index 不能是 nil 或 NaN 。value 可以是任何值。返回修改后的 table 。

例子

t = {"value",x = 5}
t2 = {"sub table"}
rawset(t, 1,"new value")
rawset(t, "y", 6)
rawset(t, t2,"sub table")
rawset(t,NaN,"NaN")         --> table index is nil

print(t[1])                 --> new value
print(t.y)                  --> 6
print(t[t2])                --> sub table

select (index, ...)

index 可以是数字或者字符 '#' 。当 index 为数字时,返回第 index + 1 个参数及后面的参数(支持负数形式的 index);当 index 为 '#' 时,返回参数的个数(不包括第一个参数)。

例子

t = {"table",x = 5}
t2 = {"table2"}

print(select(  1, 1, t, t2))    --> 1  table: 0x7fad7bc0a830 table: 0x7fad7bc0ac20
print(select( -3, 1, t, t2))    --> 1  table: 0x7fad7bc0a830 table: 0x7fad7bc0ac20
print(select("#", 1, t, t2))    --> 3

setfenv (f, table)

设置函数 f 的环境表为 table 。f 可以是一个函数,或者是代表栈层级的数字。栈层级为 1 的函数是那个调用 setfenv 的函数,栈层级为 2 的函数就是更上一层的函数。 setfenv 返回 f。

特别的,如果 f 为 0,那么 setfenv 会把全局环境设置为 table 。并且不做任何返回。

Lua 的之后版本中去掉了 setfenv 和 getfenv 函数。

例子

使用栈层级操作 setfenv 的例子:

function foobar(...)

    -- 设置 foobar 的环境
    t = {}
    setmetatable(t, {__index = _G })
    setfenv(1,t)
    a = 1
    b = 2

    -- 输出 foobar 的环境
    for k,v in pairs(getfenv(1)) do
        print(k,v)
    end
    print()

    function foo(...)
        -- 设置 foo 的环境,继承 foobar 的环境
        local t = {}
        setmetatable(t, {__index = _G})
        setfenv(1,t)
        x = 3
        y = 4

        -- 输出 foo 的环境
        for k,v in pairs(getfenv(1)) do
            print(k,v)
        end
        print()

        -- 再次设置 foobar 的环境
        setfenv(2, t)
    end


    foo()

    -- 再次输出 foobar 的环境
    for k,v in pairs(getfenv(1)) do
        print(k,v)
    end
end

foobar()

--> a   1
--> b   2
--> 
--> y   4
--> x   3
--> 
--> y   4
--> x   3

将 setfenv 用于模块加载:

-- sample.lua 文件
local FuncEnv={}    -- 作为环境
setmetatable(FuncEnv, {__index = _G}) -- 为了能够访问原本全局环境的值,将全局环境表(_G)放在元表中

local func=loadfile("other.lua")    -- 返回一个函数,函数以 other 文件内容作为代码块
setfenv(func,FuncEnv)
func()                              -- 执行代码块,得到定义的 message 函数,该函数会存在环境中
FuncEnv.message()                   --通过环境调用函数,FuncEnv 此时就相当于一个独立模块
-- other.lua 文件
function message()
    print("Message from another file!")
end

本小节参考了 斯芬克斯设置函数环境——setfenvicydaylua5.1中的setfenv使用 两篇博客。

setmetatable (table, metatable)

给 table 关联元表 metatable 。返回参数 table 。

如果元表定义了 __metatable 域,会抛出错误。

metatable 参数为 nil 表示解除已经关联的元表。

例子

-- 关联一个定义了加法操作的元表
t = setmetatable({}, {__add = function(a,b)
    if type(a) == "table" and type(b) == "table" then
        return a.num + b.num
    end
end})

t.num = 5
t2 = {num = 6}
print(t+t2)         --> 11      -- 只要有一个表进行了关联就能够进行运算

setmetatable(t, nil)            

-- 解除关联后再进行加法运算会报错
print(t+t2)         --> attempt to perform arithmetic on global 't' (a table value)

tonumber (e [, base])

tonumber([值],[基数]=10)

尝试把 e 转换为十进制数值并返回。如果无法转换返回 nil 。

base 表示传入参数的进制,默认为 10 进制。base 的可输入范围 [2,36]。高于 10 的数字用字母表示,A-Z 分别表示 11-35 。

例子

print(tonumber(123))            --> 123
print(tonumber("123"))          --> 123
print(tonumber("abc"))          --> nil
print(tonumber("abc", 20))      --> 4232
print(tonumber("ABC", 20))      --> 4232

tostring (e)

能将任意类型的值转换为合适的字符串形式返回。要控制数字转换为字符串的方式,使用 string.format(formatstring,...)

如果值所关联的元表有 __tostring 域,则使用该域的元方法获取字符串。

例子

function func()
    print("this is a function")
end
t = {name = "table"}

print(tostring(123))        --> 123
print(tostring("abc"))      --> abc
print(tostring(func))       --> function: 0x7f86348013b0
print(tostring(t))          --> table: 0x7f86348013e0

type (v)

返回 v 的类型,类型以字符串形式返回。 有以下八种返回值: "nil" , "number", "string", "boolean", "table", "function", "thread", "userdata"。

例子

type(nil)                   --> "nil"
type(false)                 --> "boolean"
type(123)                   --> "number"
type("abc")                 --> "string"

print(type(nil) == "nil")   --> true

unpack (list [, i [, j]])

unpack([列表],[起始位置]=1,[返回个数]=[列表长度])

返回表中的各个域的值,等价于返回

return list[i], list[i+1], ···, list[j]

例子

t = {1,2,3,a = 4,b = 5}

print(unpack(t, 1, 4))      --> 1   2   3   nil

_VERSION

包含有当前解释器版本号的全局变量,当前版本的值为 "Lua 5.1"。

xpcall (f, err [, arg1, ...]

pcall (f, arg1, ...) 类似。不同的是,如果 f 函数抛出了错误,那么 xpcall 不会返回从 f 抛出的错误信息,而是使用 err 函数返回的错误信息。

例子

function fun(a,b)   -- 这里的参数没什么实际作用,就是展示下用法
    error("something wrong !!", 1)
end

-- pcall 
local success, res = pcall(fun,1,2)
print(success,res)      --> false   .../sample.lua:2: something wrong !!

-- xpcall
local success, res = xpcall(fun,function()
    return "an error occured !!"
end,1,2)
print(success,res)      --> false   an error occured !!

5.4 字符串操作

5.4.1 模式

字符类

字符类代表一组字符。可以用下列组合来表示一个字符类。

组合 代表字母 代表字符类型
x (变量 x) ^$()%.[]*+-?以外的任一字符
. (dot) 任意字符
%a (alphabet) 字母
%b (bracket) 对称字符以及字符间的内容
%c (control) 控制字符(即各类转义符)
%d (digits) 数字
%l (lowercase) 小写字母
%p (punctuation) 标点符号
%s (space) 空格
%u (uppercase) 大写字母
%w (word) 字母和数字
%x (hexadecimal) 十六进制字符
%z (zero) 值为 0 的字符,即 '\0'
%x (变量 x) 字母和数字以外的任一字符

如果组合中的字符写成大写形式(例如将 '%a' 写成 '%A'),相当于对原来所代表的字符类型取补集

例子:

前两行的数字标出每个字符的下标。find函数返回找出第一个符合查找条件的字符的下标。

-----------------00000000001111111112 222222222333333333344444444445555 5
-----------------12345678901234567890 123456789012345678901234567890123 4
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","i")
    --> 6
x = string.find("Tutu is a young man.\n His student number is 20230001.\0",".")
    --> 1
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%a")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%c")    --> 21 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%d")    --> 45 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%l")    --> 2   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%p")    --> 20 
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%s")    --> 5   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%u")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%w")    --> 1   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%x")    --> 9   
x = string.find("Tutu is a young man.\n His student number is 20230001.\0","%z")    --> 54 

() 表示捕捉,find的第三个参数返回被捕捉到的字符串,在这里即返回找到的那个字符。

x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%%)")   --> 1   1   %
x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%#)")   --> 7   7   #
x,y,z = string.find("%()~!@#$%^&*():\";\'?<>._[]","(%\")")  --> 16  16  "

下句中的 + 表示取一个或多个满足条件的连续字符。

                 --1 2 3 4 5 6 7 8
x,y = string.find("\a\b\f\n\r\t\v\0","%c+")     --> 1   7

上句基本列出了所有控制字符,并不是所有转义符都是控制字符,例如 \\\xff 不属于控制字符。

match 函数返回符合匹配条件的字符子串。

x = string.match("0123456789ABCDEFabcdefg","%x+")   --> 0123456789ABCDEFabcdef

输出的符号即为 %x 所支持的所有字符。

%b 的使用方法与前面的组合形式略有不同,其形式为 %bxy,使用示例如下:

---------------------00000000001111111112 22222222233333333334444444444555555 5
---------------------12345678901234567890 12345678901234567890123456789012345 6
x,y,z = string.find("Tutu is a young man.\n His student number is [20230001].\0","(%b[])")  --> 45  54  [20230001]
x,y,z = string.find("Tutu is a young man.\n His student number is _20230001_.\0","(%b__)")  --> 45  54  _20230001_
x,y,z = string.find("Tutu is a young man.\n His student number is _20230001_.\0","(%b21)")  --> 48  53  230001

[] 字符集

字符集操作是对字符类组合的一个扩展。可以通过 [] 制定出用户所需的一套字符选取范围。

---------------------0000000001111111111222222222
---------------------1234567890123456789012345678
x,y,z = string.find("[Email]: tangyikejun@163.com","([123])")           --> 22  22  1
x,y,z = string.find("[Email]: tangyikejun@163.com","([l]])")            --> 6   7   l]
x,y,z = string.find("[Email]: tangyikejun@163.com","([1-3])")           --> 22  22  1
x,y,z = string.find("[Email]: tangyikejun@163.com","([^1-3])")          --> 1   1   [
x,y,z = string.find("[Email]: tangyikejun@163.com","([^%d])")           --> 1   1   [
x,y,z = string.find("[Email]: tangyikejun@163.com","([0-9][%d][%d])")   --> 22  24  163
x,y,z = string.find("[Email]: tangyikejun@163.com","([0-9]+)")          --> 22  24  163

使用特点:

  1. 每个字符集仅限定一个字符的范围。
  2. 连字符 - 用于限定字符的范围,值域根据字符在ASCII码中对应的值得出,例如 [0-7] 代表字符范围为 0-7。
    x,y,z = string.find("!\"#$%&0123","([$-1]+)") --> 4 8 $%&01
  3. 添加 ^ 表示对指定的字符范围取补集。[^%d] 等价于 [%D]

模式项

模式项 作用
+ 匹配1个或多个字符,尽可能多地匹配
- 匹配0个或多个字符,尽可能少地匹配
* 匹配0个或多个字符,尽可能多地匹配
匹配0个或1个字符,尽可能多地匹配

使用特点:

  1. 模式项都是针对前一个字符而言的。例如 abc- 作用于字符 c
---------------------0000000001
---------------------1234567890
x,y,z = string.find("aaaabbbccc","(%a+)")       --> 1   10  aaaabbbccc
x,y,z = string.find("bbbccc","(a+)")            --> nil nil nil
x,y,z = string.find("aaaabbbccc","(ab-c)")      --> 4   8   abbbc
-- x,y,z = string.find("aaaaccc","(ab-c)")      --> 4   5   ac
-- x,y,z = string.find("aaaaccc","(ab*c)")      --> 4   5   ac
-- x,y,z = string.find("aaaabccc","(ab?c)")     --> 4   6   abc
-- x,y,z = string.find("aaaabccc","(ba?c)")     --> 5   6   bc
---------------------000000000111 111111122
---------------------123456789012 345678901
x,y,z = string.find("tangyikejun\0 163.com","(%z%s%w+)")    --> 12  16  
x,y,z = string.find("tangyikejun\0163.com","(%z%d%w+)")     --> nil nil     nil 

注意: \0 后面不能跟数字。而且用 find 返回的匹配字符串无法输出 \0 之后的部分。

模式

多个模式项组合形成模式

---------------------0000000001111111111222222222
---------------------1234567890123456789012345678
x,y,z = string.find("[Email]: tangyikejun@163.com","^(.%a+)")   -->1    6   [Email
x,y,z = string.find("[Email]: tangyikejun@163.com","(%a+)$")    -->26   28  com

()捕捉

捕捉是指将括号内的组合匹配结果保存起来,每个括号保存一个结果。
保存的数据的顺序按照左括号的顺序排列。

x,y,z,h,l = string.find("Tutu is a young man.\n His student number is _20230001_.\0","((%a+%s)(%a+%s)%b__)")    --> 35  54  number is _20230001_    number  is 

字符串模式匹配可参考Lua模式匹配

5.4.2 库函数

string.find(s,pattern[,init[,plain]])

查找字符串的子串,如果找到,返回子串的起始位置、结束位置;找不到返回 nil。
如果使用捕获(即对模式串用括号包裹起来),则一并返回匹配得到的字符串。

定义

string.find([字符串],[待查找字符串],[查找起始位置]=1,[禁用模式匹配]=false)

只有显式指定了 init 参数才能控制 plain 参数。

例子

x,y,z = string.find("1001 is a Robot", "Robot")
print(x,y,z)                                --> 11 15   nil
x,y,z = string.find("1001 is a Robot","1%d",1,true)
print(x,y,z)                                --> nil nil nil
x,y,z = string.find("1001 is a Robot","(%d+)",1,false)
print(x,y,z)                                --> 1   2   1001

string.match(s,pattern[,init])

string.find 类似,返回值不一样。string.match 查找字符串的子串,如果找到,返回子串;找不到返回 nil。

支持模式匹配。

定义

例子

x = string.match("1001 is a Robot","001")
print(x)                --> 001                             
x = string.match("1001 is a Robot","%d%d")
print(x)                --> 10      

string.gmatch(s,pattern)

返回一个迭代函数,该函数每执行一次,就返回下一个捕捉到的匹配(如果没有使用捕捉,就返回整个匹配结果)。

例子

for s in string.gmatch("I have a Dream.","%a+") do
    print(s)
end
--> I
--> have
--> a
--> Dream
t = {}
s = "name=tangyikejun, number=20250001"

-- 将捕获的两个子串分别作为键和值放到表t中
for k, v in string.gmatch(s, "(%w+)=(%w+)") do
    t[k] = v
end

-- 输出表t
for k,v in pairs(t) do
    print(k,v)
end

--> name    tangyikejun
--> number  20250001

string.format(formatstring,...)

返回格式化之后的字符串。

定义

例子

string.format("My name is %s", "tangyikejun")   --> My name is tangyikejun

string.len(s)

返回字符串长度

string.lower(s)

返回小写字母的字符串

string.upper(s)

返回大写字母的字符串

string.rep(s,n)

对字符串进行重复

定义

string.rep([字符串],[重复次数])

例子

string.rep("Hello",4)   -- HelloHelloHelloHello

string.reverse(s)

返回反转后的字符串。

string.sub(s,i[,j])

返回子字符串。

定义

string.sub([字符串],[开始字符下标],[结束字符下标]=-1)

例子

x = string.sub("tangyikejun",7)
print(x)                --> kejun
x = string.sub("tangyikejun",1,-6)
print(x)                --> tangyi

string.gsub(s,pattern,repl[,n])

根据模式匹配对字符串中每一个匹配部分都做替换处理,返回替换后的字符串。

定义

string.gsub([字符串],[模式匹配],[替换字符],[最大替换次数] = 无限制)

repl 参数([替换字符])支持 字符串、表、函数。

如果 repl 是字符串,那么该字符串就是用于替换的字符串。同时支持 %n 转义符操作,n 的范围是 0-9。n 范围为 [1,9] 时表示第 n 个捕获的匹配字符串,%0 表示整个匹配的字符串,%% 表示替换为一个 %

如果 repl 是表,那么将捕获的第一个字符串作为键值(Key)进行查询(没有定义捕捉则以整个匹配的字符串为键值),查到的值作为替换的字符串。

如果 repl 是函数,那么每次匹配成功都会调用该函数,并以按序以所有捕捉作为参数传入函数。没有捕捉则以整个匹配的字符作为参数。

如果从表或函数得到是字符串或者是数字,就将其用于替换;如果得到的是 false 或 nil,那么匹配部分将不会发生变化。

例子

repl 为字符串

s = "Never say die."
x = string.gsub(s,"die","never")            --> Never say never.
x = string.gsub(s,"die","'%0'")             --> Never say 'die'.
x = string.gsub(s,"(%a+)%s%a+%s(%a+)","%2") --> die.

限制最大替换次数

s = "never say never."
x = string.gsub(s,"never","Never",1)    --> Never say never.

repl 是表

t = {name="Lua",version="5.1"}
x = string.gsub("$name-$version.tar.gz","$(%a+)",t) --> Lua-5.1.tar.gz

repl是函数

x = string.gsub("4+5 = $return 4+5$","%$(.-)%$",function(s)return loadstring(s)() end)  --> 4+5 = 9
x = string.gsub("23+45=$result", "((%d+)%+(%d+)=)%$%a+", function (s,a,b)
    sum = a+b
    return s..sum
end)    --> 23+45=68

~~注意:似乎只支持匿名函数。~~

从表或函数返回的是 false 或 nil

x = string.gsub("4+5 = $return 4+5$","%$(.-)%$",function(s)return nil end)  --> 4+5 = $return 4+5$
t = {name="Lua",version=false}
x = string.gsub("$name-$version.tar.gz","$(%a+)",t) --> Lua-$version.tar.gz

string.byte(s[,i[,j]])

返回字符的 ASCII 码值。

定义

string.byte([字符串],[起始下标]=1,[结束下标]=[起始下标])

例子

x,y,z = string.byte("abc",2)    --> 98  nil nil
x,y,z = string.byte("abc",1,3)  --> 97  98  99

string.char(...)

根据传入的 ASCII 编码值([0-255])得到对应的字符,传入多少编码值就返回多长的字符串。

例子

x = string.char(98,99,100)  --> bcd

如果输入字符超限会编译报错。

string.dump(function)

返回函数的二进制表示(字符串形式),把这个返回值传给 loadingstring 可以获得函数的一份拷贝(传入的函数必须是没有上值的 Lua 函数)。

例子

function sum(a,b)
    return a + b
end

s = string.dump(sum)
x = loadstring(s)(4,4) -- 8

参考链接

BNF范式简介 (简要介绍 BNF)
Lua入门系列-果冻想(对Lua进行了较为全面的介绍)
Lua快速入门(介绍 Lua 中最为重要的几个概念,为 C/C++ 程序员准备)
Lua 5.1 中文手册(全面的 Lua5.1 中文手册)
Lua 5.3 中文手册(云风花了6天写的,天哪,我看都要看6天的节奏呀)
Lua迭代器和泛型for(介绍 Lua 迭代器的详细原理以及使用)
How do JavaScript closures work?——StackOverflow(详细介绍了 Javascript 中闭包的概念)
Lua模式匹配(参考了此文中对 %b 的使用)
LuaSocket(LuaSocket 官方手册)
Lua loadfile的用法, 与其他函数的比较(loadfile的介绍部分引用了此文)
Lua 的元表(对元表的描述比较有条理,通俗易懂,本文元表部分参考了此文)
设置函数环境——setfenv(解释了如何方便地设置函数的环境,以及为什么要那样设置)
lua5.1中的setfenv使用(介绍了该环境的设置在实际中的一个应用)

2015年五月2日晚上 11:27:33 Lua 学习笔记(四)—— 元表与元方法

我们可以使用操作符对 Lua 的值进行运算,例如对数值类型的值进行加减乘除的运算操作以及对字符串的连接、取长操作等(在 Lua 学习笔记(三)—— 表达式 中介绍了许多类似的运算)。元表正是定义这些操作行为的地方。

元表本质上是一个普通 Lua 表。元表中的键用来指定操作,称为“事件名”;元表中键所关联的值称为“元方法”,定义操作的行为。

1 事件名与元方法

仅表(table)类型值对应的元表可由用户自行定义。其他类型的值所对应的元表仅能通过 Debug 库进行修改。

元表中的事件名均以两条下划线 __ 作为前缀,元表支持的事件名有如下几个:

__index     -- 'table[key]',取下标操作,用于访问表中的域
__newindex  -- 'table[key] = value',赋值操作,增改表中的域
__call      -- 'func(args)',函数调用,(参见 《Lua 学习笔记(三)—— 表达式》中的函数部分介绍)

-- 数学运算操作符
__add       -- '+'
__sub       -- '-'
__mul       -- '*'
__div       -- '/'
__mod       -- '%'
__pow       -- '^'
__unm       -- '-'

-- 连接操作符
__concat    -- '..'

-- 取长操作符
__len       -- '#'

-- 比较操作符
__eq        -- '=='
__lt        -- '<'      -- a > b 等价于 b < a
__le        -- '<='     -- a >= b 等价于 b <= a 

还有一些其他的事件,例如 __tostring__gc 等。

下面进行详细介绍。

2 元表与值

每个值都可以拥有一个元表。对 userdata 和 table 类型而言,其每个值都可以拥有独立的元表,也可以几个值共享一个元表。对于其他类型,一个类型的值共享一个元表。例如所有数值类型的值会共享一个元表。除了字符串类型,其他类型的值默认是没有元表的。

使用 getmetatable 函数可以获取任意值的元表。
使用 setmetatable 函数可以设置表类型值的元表。(这两个函数将在[基础函数库]部分进行介绍)

2.1 例子

只有字符串类型的值默认拥有元表:

a = "5"
b = 5
c = {5}
print(getmetatable(a))      --> table: 0x7fe221e06890
print(getmetatable(b))      --> nil
print(getmetatable(c))      --> nil

3 事件的具体介绍

事先提醒 Lua 使用 raw 前缀的函数来操作元方法,避免元方法的循环调用。

例如 Lua 获取对象 obj 中元方法的过程如下:

rawget(getmetatable(obj)or{}, "__"..event_name)

3.1 元方法 index

index 是元表中最常用的事件,用于值的下标访问 -- table[key]

事件 index 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。
当用户通过键值来访问表时,如果没有找到键对应的值,则会调用对应元表中的此事件。如果 index 使用表进行赋值,则在该表中查找传入键的对应值;如果 index 使用函数进行赋值,则调用该函数,并传入表和键。

Lua 对取下标操作的处理过程用伪码表示如下:

function gettable_event (table, key)
    -- h 代表元表中 index 的值
    local h     
    if type(table) == "table" then

        -- 访问成功
        local v = rawget(table, key)
        if v ~= nil then return v end

        -- 访问不成功则尝试调用元表的 index
        h = metatable(table).__index

        -- 元表不存在返回 nil
        if h == nil then return nil end
    else

        -- 不是对表进行访问则直接尝试元表
        h = metatable(table).__index

        -- 无法处理导致出错
        if h == nil then
            error(···);
        end
    end

    -- 根据 index 的值类型处理
    if type(h) == "function" then
        return h(table, key)            -- 调用处理器
    else 
        return h[key]                   -- 或是重复上述操作
    end
end

3.1.1 例子

使用表赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = {[3] = "pig", [4] = "cow", [5] = "duck"}})
print(t[3])             --> pig

使用函数赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = function (table,key)
    key = key % 2 + 1
    return table[key]
end})
print(t[3])             --> dog

3.2 元方法 newindex

newindex 用于赋值操作 -- talbe[key] = value

事件 newindex 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。

当操作类型不是表或者表中尚不存在传入的键时,会调用 newindex 的元方法。如果 newindex 关联的是一个函数类型以外的值,则再次对该值进行赋值操作。反之,直接调用函数。

~~不是太懂:一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)~~

Lua 进行赋值操作时的伪码如下:

function settable_event (table, key, value)
    local h
    if type(table) == "table" then

        -- 修改表中的 key 对应的 value
        local v = rawget(table, key)
        if v ~= nil then rawset(table, key, value); return end

        -- 
        h = metatable(table).__newindex

        -- 不存在元表,则直接添加一个域
        if h == nil then rawset(table, key, value); return end
    else
        h = metatable(table).__newindex
        if h == nil then
            error(···);
        end
    end

    if type(h) == "function" then
        return h(table, key,value)    -- 调用处理器
    else 


        h[key] = value             -- 或是重复上述操作
    end
end

3.2.1 例子

元方法为表类型:

t = {}
mt = {}

setmetatable(t, {__newindex = mt})
t.a = 5
print(t.a)      --> nil
print(mt.a)     --> 5

通过两次调用 newindex 元方法将新的域添加到了表 mt 。

+++

元方法为函数:

-- 对不同类型的 key 使用不同的赋值方式
t = {}
setmetatable(t, {__newindex = function (table,key,value)
    if type(key) == "number" then
        rawset(table, key, value*value)
    else
        rawset(table, key, value)
    end
end})
t.name = "product"
t[1] = 5
print(t.name)       --> product
print(t[1])         --> 25

3.3 元方法 call

call 事件用于函数调用 -- function(args)

Lua 进行函数调用操作时的伪代码:

function function_event (func, ...)

  if type(func) == "function" then
      return func(...)   -- 原生的调用
  else
      -- 如果不是函数类型,则使用 call 元方法进行函数调用
      local h = metatable(func).__call

      if h then
        return h(func, ...)
      else
        error(···)
      end
  end
end

3.3.1 例子

由于用户只能为表类型的值绑定自定义元表,因此,我们可以对表进行函数调用,而不能把其他类型的值当函数使用。

-- 把数据记录到表中,并返回数据处理结果
t = {}

setmetatable(t, {__call = function (t,a,b,factor)
  t.a = 1;t.b = 2;t.factor = factor
  return (a + b)*factor
end})

print(t(1,2,0.1))       --> 0.3

print(t.a)              --> 1
print(t.b)              --> 2
print(t.factor)         --> 0.1

3.4 运算操作符相关元方法

运算操作符相关元方法自然是用来定义运算的。

以 add 为例,Lua 在实现 add 操作时的伪码如下:

function add_event (op1, op2)
  -- 参数可转化为数字时,tonumber 返回数字,否则返回 nil
  local o1, o2 = tonumber(op1), tonumber(op2)
  if o1 and o2 then  -- 两个操作数都是数字?
    return o1 + o2   -- 这里的 '+' 是原生的 'add'
  else  -- 至少一个操作数不是数字时
    local h = getbinhandler(op1, op2, "__add") -- 该函数的介绍在下面
    if h then
      -- 以两个操作数来调用处理器
      return h(op1, op2)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

代码中的 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作。 在该函数中,首先,Lua 尝试第一个操作数。如果这个操作数所属类型没有定义这个操作的处理器,然后 Lua 会尝试第二个操作数。

 function getbinhandler (op1, op2, event)
   return metatable(op1)[event] or metatable(op2)[event]
 end

+++

对于一元操作符,例如取负,Lua 在实现 unm 操作时的伪码:

function unm_event (op)
  local o = tonumber(op)
  if o then  -- 操作数是数字?
    return -o  -- 这里的 '-' 是一个原生的 'unm'
  else  -- 操作数不是数字。
    -- 尝试从操作数中得到处理器
    local h = metatable(op).__unm
    if h then
      -- 以操作数为参数调用处理器
      return h(op)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

3.4.1 例子

加法的例子:

t = {}
setmetatable(t, {__add = function (a,b)
  if type(a) == "number" then
      return b.num + a
  elseif type(b) == "number" then
      return a.num + b
  else
      return a.num + b.num
  end
end})

t.num = 5

print(t + 3)  --> 8

取负的例子:

t = {}
setmetatable(t, {__unm = function (a)
  return -a.num
end})

t.num = 5

print(-t)  --> -5

3.5 元方法 tostring

对于 tostring 操作,元方法定义了值的字符串表示方式。

例子:

t = {num = "a table"}
print(t)              --> table: 0x7f8e83c0a820

mt = {__tostring = function(t)
  return t.num
end}
setmetatable(t, mt)

print(tostring(t))    --> a table
print(t)              --> a table

3.6 比较类元方法

对于三种比较类操作,均需要满足两个操作数为同类型,且关联同一个元表时才能使用元方法。

对于 eq (等于)比较操作,如果操作数所属类型没有原生的等于比较,则调用元方法。

对于 lt (小于)与 le (小于等于)两种比较操作,如果两个操作数同为数值或者同为字符串,则直接进行比较,否则使用元方法。

对于 le 操作,如果元方法 "le" 没有提供,Lua 就尝试 "lt",它假定 a <= b 等价于 not (b < a) 。

3.6.1 例子

等于比较操作:

t = {name="number",1,2,3}
t2 = {name = "number",4,5,6}
mt = {__eq = function (a,b)
    return a.name == b.name
end}
setmetatable(t,mt)              -- 必须要关联同一个元表才能比较
setmetatable(t2,mt)

print(t==t2)   --> true

3.7 其他事件的元方法

对于连接操作,当操作数中存在数值或字符串以外的类型时调用该元方法。

对于取长操作,如果操作数不是字符串类型,也不是表类型,则尝试使用元方法(这导致自定义的取长基本没有,在之后的版本中似乎做了改进)。

3.7.1 例子

取长操作:

t = {1,2,3,"one","two","three"}
setmetatable(t, {__len = function (t)
  local cnt = 0
  for k,v in pairs(t) do
    if type(v) == "number" then 
      cnt = cnt + 1
      print(k,v)
    end
  end
  return cnt
end})

-- 结果是 6 而不是预期中的 3
print(#t)   --> 6 
2015年五月2日晚上 11:19:16 防止表单多次提交

Node.js CSRF protection middleware

这是防止表单多次提交的,原理是利用cookie和session生产token,和java里的token都是一样的概念

官方提供的中间件,还不错

https://github.com/expressjs/csurf

ajax提交

如果是ajax提交,就disable button吧

2015年五月2日晚上 11:11:03 Lua 学习笔记(三)—— 表达式

1 数学运算操作符

1.1 % 操作符

Lua 中的 % 操作符与 C 语言中的操作符虽然都是取模的含义,但是取模的方式不一样。
在 C 语言中,取模操作是将两个操作数的绝对值取模后,在添加上第一个操作数的符号。
而在 Lua 中,仅仅是简单的对商相对负无穷向下取整后的余数。

+++

在 C 中,

a1 = abs(a);
b1 = abs(b);
c = a1 % b1 = a1 - floor(a1/b1)*b1;

a % b = (a >= 0) ? c : -c;

在 Lua 中,

a % b == a - math.floor(a/b)*b

Lua 是直接根据取模定义进行运算。 C 则对取模运算做了一点处理。

+++

举例:

在 C 中

int a = 5 % 6;
int b = 5 % -6;
int c = -5 % 6;
int d = -5 % -6;

printf("a,b,c,d");--5,5,-5,-5

在 Lua 中

a = 5 % 6
b = 5 % -6
c = -5 % 6
d = -5 % -6

x = {a,b,c,d}

for i,v in ipairs(x) do
    print(i,v)
end


--> 5
--> -1
--> 1
--> -5

可以看到,仅当操作数同号时,两种语言的取模结果相同。异号时,取模结果的符号与数值均不相等。

在 Lua 中的取模运算总结为:a % b,如果 a,b 同号,结果取 a,b 绝对值的模;异号,结果取 b 绝对值与绝对值取模后的差。取模后值的符号与 b 相同。

2 比较操作符

比较操作的结果是 boolean 型的,非 truefalse

支持的操作符有:

< <= ~= == > >=

不支持 ! 操作符。

+++

对于 == 操作,运算时先比较两个操作数的类型,如果不一致则结果为 false。此时数值与字符串之间并不会自动转换。

比较两个对象是否相等时,仅当指向同一内存区域时,判定为 true。·

a = 123
b = 233
c = "123"
d = "123"
e = {1,2,3}
f = e
g = {1,2,3}

print(a == b)       --> false
print(a == c)       --> false      -- 数字与字符串作为不同类型进行比较
print(c == d)       --> true       
print(e == f)       --> true       -- 引用指向相同的对象
print(e == g)       --> false      -- 虽然内容相同,但是是不同的对象
print(false == nil) --> false      -- false 是 boolean,nil 是 nil 型

方便标记,--> 代表前面表达式的结果。

+++

userdatatable 的比较方式可以通过元方法 eq 进行改变。

大小比较中,数字和字符串的比较与 C 语言一致。如果是其他类型的值,Lua会尝试调用元方法 ltle

3 逻辑操作符

and,or,not

仅认为 falsenil 为假。

3.1 not

取反操作 not 的结果为 boolean 类型。(andor 的结果则不一定为 boolean)

b = not a           -- a 为 nil,b 为 true
c = not not a       -- c 为 false

3.2 and

a and b,如果 a 为假,返回 a,如果 a 为真, 返回 b

注意,为什么 a 为假的时候要返回 a 呢?有什么意义?这是因为 a 可能是 false 或者 nil,这两个值虽然都为假,但是是有区别的。

3.3 or

a or b,如果 a 为假,返回 b,如果 a 为真, 返回 a。与 and 相反。

+++

提示: 当逻辑操作符用于得出一个 boolean 型结果时,不需要考虑逻辑运算后返回谁的问题,因为逻辑操作符的操作结果符合原本的逻辑含义。

举例

if (not (a > min and a < max)) then  -- 如果 a 不在范围内,则报错
    error() 
end

+++

3.4 其他

andor 遵循短路原则,第二个操作数仅在需要的时候会进行求值操作。

例子


a = 5 x = a or jjjj() -- 虽然后面的函数并没有定义,但是由于不会执行,因此不会报错。 print(a) -->5 print(x) -->5

通过上面这个例子,我们应当对于逻辑操作有所警觉,因为这可能会引入一些未能及时预料到的错误。

4 连接符

..
连接两个字符串(或者数字)成为新的字符串。对于其他类型,调用元方法 concat

5 取长度操作符

#

对于字符串,长度为字符串的字符个数。

对于表,通过寻找满足t[n] 不是 nil 而 t[n+1] 为 nil 的下标 n 作为表的长度。

~~对于其他类型呢?~~

5.1 例子

-- 字符串取长
print(#"abc\0")                         --> 4
-- 表取长
print(#{[1]=1,[2]=2,[3]=3,x=5,y=6})     --> 3
print(#{[1]=1,[2]=nil,[3]=3,x=5,y=6})   --> 1

6 优先级

由低到高:

or
and
 <     >     <=    >=    ~=    ==
 ..
 +     -
 *     /     %
 not   #     - (unary)
 ^

幂运算>单目运算>四则运算>连接符>比较操作符>and>or

7 Table 构造

Table 构造的 BNF 定义

tableconstructor ::= `{´ [fieldlist] `}´
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
fieldsep ::= `,´ | `;´

BNF 定义参考 BNF范式简介

举例:

a = {}
b = {["price"] = 5; cost = 4; 2+5}
c = { [1] = 2+5, [2] = 2, 8, price = "abc", ["cost"] = 4} -- b 和 c 构造的表是等价的


print(b["price"])   --> 5
print(b.cost)       --> 4
print(b[1])         --> 7       -- 未给出键值的,按序分配下标,下标从 1 开始

print(c["price"])   --> abc
print(c.cost)       --> 4
print(c[1])         --> 8       
print(c[2])         --> 2       

注意:

上面这两条的存在使得上面的例子中 c1 的输出值为 8。

+++

如果表中有相同的键,那么以靠后的那个值作为键对应的值。

a = {[1] = 5,[1] = 6} -- 那么 a[1] = 6

+++

如果表的最后一个域是表达式形式,并且是一个函数,那么这个函数的所有返回值都会加入到表中。

a = 1
function order()
    a = a + 1
    return 1,2,3,4
end

b = {order(); a; order(); }

c = {order(); a; (order());}

print(b[1])                     --> 1       
print(b[2])                     --> 2       -- 表中的值并不是一次把表达式都计算结束后再赋值的
print(b[3])                     --> 1       
print(b[4])                     --> 2       -- 表达式形式的多返回值函数

print(#b)                       --> 6       -- 表的长度为 6                 
print(#c)                       --> 3       -- 函数添加括号后表的长度为 3

8 函数

函数是一个表达式,其值为 function 类型的对象。函数每次执行都会被实例化。

8.1 函数定义

Lua 中实现一个函数可以有以下三种形式。

f = function() [block] end
local f; f = function() [block] end
a.f = function() [block] end

Lua 提供语法糖分别处理这三种函数定义。

function f() [block] end
local function f() [block] end
function a.f() [block] end

+++

上面 local 函数的定义之所以不是 local f = function() [block] end,是为了避免如下错误:

local f = function()
    print("local fun")
    if i==0 then 
        f()             -- 编译错误:attempt to call global 'f' (a nil value)
        i = i + 1
    end
end

8.2 函数的参数

形参会通过实参来初始化为局部变量。

参数列表的尾部添加 ... 表示函数能接受不定长参数。如果尾部不添加,那么函数的参数列表长度是固定的。

f(a,b)
g(a,b,...)
h(a,...,b)              -- 编译错误
f(1)                    --> a = 1, b = nil
f(1,2)                  --> a = 1, b = 2
f(1,2,3)                --> a = 1, b = 2

g(1,2)                  --> a = 1, b = 2, (nothing)
g(1,2,3)                --> a = 1, b = 2, (3)
g(1,f(4,5),3)           --> a = 1, b = 4, (3)
g(1,f(4,5))             --> a = 1, b = 4, (5)

+++

还有一种形参为self的函数的定义方式:

a.f = function (self, params) [block] end

其语法糖形式为:

function a:f(params) [block] end

使用举例:

a = {name = "唐衣可俊"}
function a:f()
    print(self.name)
end
a:f()                       --> 唐衣可俊   -- 如果这里使用 a.f(),那么 self.name 的地方会报错 attempt to index local 'self';此时应该写为 a.f(a)

: 的作用在于函数定义与调用的时候可以少写一个 self 参数。这种形式是对方法的模拟

8.3 函数调用

Lua 中的函数调用的BNF语法如下:

functioncall ::= prefixexp args

如果 prefixexp 的值的类型是 function, 那么这个函数就被用给出的参数调用。 否则 prefixexp 的元方法 "call" 就被调用, call 的第一个参数就是 prefixexp 的值,接下来的是 args 参数列表(参见 2.8 元表 | Metatable)。

函数调用根据是否传入 self 参数分为 . 调用和 : 调用。
函数调用根据传入参数的类型,可以分为参数列表调用、表调用、字符串调用

[待完善]

8.4 函数闭包

如果一个函数访问了它的外部变量,那么它就是一个闭包。

由于函数内部的变量均为局部变量,外界无法对其进行访问。这时如果外界想要改变局部变量的值,那么就可以使用闭包来实现这一目的。
具体的实现过程大致是这样,函数内部有能够改变局部变量的子函数,函数将这个子函数返回,那么外界就可以通过使用这个子函数来操作局部变量了。

例子:利用闭包来实现对局部变量进行改变

-- 实现一个迭代器

function begin(i)
    local cnt = i

    return function ()      -- 这是一个匿名函数,实现了自增的功能;同时它也是一个闭包,因为访问了外部变量 cnt
        cnt = cnt + 1
        return cnt
    end
end


iterator = begin(2)     -- 设置迭代器的初值为 2 ,返回一个迭代器函数

print(iterator())           -- 执行迭代
print(iterator())

提示: 关于闭包的更多说明可参考JavaScript 闭包是如何工作的?——StackOverflow


参考链接

BNF范式简介 (简要介绍 BNF)
How do JavaScript closures work?——StackOverflow(详细介绍了 Javascript 中闭包的概念)

2015年五月2日晚上 10:43:06 求问本地异步读文件的效率问题?

当用ajax发送多个post请求时,总时间取决于最慢的那个请求。 但当在本地异步读多个文件的时候,总时间还是取决于最慢的那个吗? 我在想在底层异步读多个文件会导致时间增长,并不会比并行读文件效率高多少,不知道实际情况应该怎么分析?

2015年五月2日晚上 10:41:34 详说 Cookie, LocalStorage 与 SessionStorage

本文最初发布于我的个人博客:咀嚼之味

最近在找暑期实习,其中百度、网易游戏、阿里的面试都问到一些关于HTML5的东西,问题大多是这样开头的:“你用过什么HTML5的技术呀?” 而后,每次都能扯到 Cookie 和 localStorage 有啥差别。这篇文章就旨在详细地阐述这部分内容,而具体 Web Storage API 的使用可以参考MDN的文档,就不在这篇文章中赘述了。

基本概念

Cookie

Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右,是网景公司的前雇员 Lou Montulli 在1993年3月的发明。它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的。

localStorage

localStorage 是 HTML5 标准中新加入的技术,它并不是什么划时代的新东西。早在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持,如果你的网站需要支持 IE6+,那以 userData 作为你的 polyfill 的方案是种不错的选择。

特性 Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
localStorage 4 3.5 8 10.50 4
sessionStorage 5 2 8 10.50 4

sessionStorage

sessionStorage 与 localStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。做过后端开发的同学应该知道 Session 这个词的意思,直译过来是“会话”。而 sessionStorage 是一个前端的概念,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空。

三者的异同

特性 Cookie localStorage sessionStorage
数据的生命期 可设置失效时间,默认是关闭浏览器后失效 除非被清除,否则永久保存 仅在当前会话下有效,关闭页面或浏览器后被清除
存放数据大小 4K左右 一般为5MB 一般为5MB
与服务器端通信 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 仅在客户端(即浏览器)中保存,不参与和服务器的通信 仅在客户端(即浏览器)中保存,不参与和服务器的通信
易用性 需要程序员自己封装,源生的Cookie接口不友好 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持

应用场景

有了对上面这些差别的直观理解,我们就可以讨论三者的应用场景了。

因为考虑到每个 HTTP 请求都会带着 Cookie 的信息,所以 Cookie 当然是能精简就精简啦,比较常用的一个应用场景就是判断用户是否登录。针对登录过的用户,服务器端会在他登录时往 Cookie 中插入一段加密过的唯一辨识单一用户的辨识码,下次只要读取这个值就可以判断当前用户是否登录啦。曾经还使用 Cookie 来保存用户在电商网站的购物车信息,如今有了 localStorage,似乎在这个方面也可以给 Cookie 放个假了~

而另一方面 localStorage 接替了 Cookie 管理购物车的工作,同时也能胜任其他一些工作。比如HTML5游戏通常会产生一些本地数据,localStorage 也是非常适用的。如果遇到一些内容特别多的表单,为了优化用户体验,我们可能要把表单页面拆分成多个子页面,然后按步骤引导用户填写。这时候 sessionStorage 的作用就发挥出来了。

安全性的考虑

需要注意的是,不是什么数据都适合放在 Cookie、localStorage 和 sessionStorage 中的。使用它们的时候,需要时刻注意是否有代码存在 XSS 注入的风险。因为只要打开控制台,你就随意修改它们的值,也就是说如果你的网站中有 XSS 的风险,它们就能对你的 localStorage 肆意妄为。所以千万不要用它们存储你系统中的敏感数据。

参考资料

2015年五月2日晚上 10:39:20 Lua 学习笔记(二)—— 语句

Lua 中的语句支持赋值,控制结构,函数调用,还有变量声明。

不允许空的语句段,因此 ;; 是非法的。

1 语句组 | chuncks

chunck ::= {stat[';']}

([';'] 应该是表示语句组后面 ; 是可选项。)

2 语句块 | blocks

block ::= chunck
stat ::= do block end

可以将一个语句块显式地写成语句组,可以用于控制局部变量的作用范围。

3 赋值 | assignment

Lua 支持多重赋值。

多重赋值时,按序将右边的表达式的值赋值给左值。右值不足补 nil,右值多余舍弃。

b = 1
a,b = 4 -- a = 4,b = nil 

+++

Lua 在进行赋值操作时,会一次性把右边的表达式都计算出来后进行赋值。

i = 5
i,a[i] = i+1, 7 -- i = 6 ,a[5] = 7

特别地,有

x,y = y,x -- 交换 x,y 的值

+++

对全局变量以及表的域的赋值操作含义可以在元表中更改。

4 控制结构

4.1 条件语句

if [exp]
    [block]
elseif [exp]
    [block]
else
    [block]
end

4.2 循环语句

while [exp]
    [block]
end

+++

repeat
    [block]
until [exp]

注意,由于 repeat 语句到 until 还未结束,因此在 until 之后的表达式中可以使用 block 中定义的局部变量。

例如:

a = 1
c = 5
repeat
    b = a + c
    c = c * 2
until b > 20
print(c)            -->     40

+++

4.3 breakreturn

breakreturn 只能写在语句块的最后一句,如果实在需要写在语句块中间,那么就在两个关键词外面包围 do end 语句块。

do break end

5 For 循环

for 循环的用法比较多,单独拎出来讲。

for 中的表达式会在循环开始前一次性求值,在循环过程中不再更新。

5.1 数字形式

for [Name] = [exp],[exp],[exp] do [block] end

三个 exp 分别代表初值,结束值,步进。exp 的值均需要是一个数字。
第三个 exp 默认为 1,可以省略。

a = 0

for i = 1,6,2 do
    a = a + i
end

等价于

int a = 0;
for (int i = 1; i <= 6;i += 2){ // 取到等号,如果步进是负的,那么会取 i >= 6
    a += i;
}

5.2 迭代器形式

迭代器形式输出一个表时,如果表中有函数,则输出的顺序及个数不确定(笔者测试得出的结果,具体原因未知)。

迭代器形式的 for 循环的实质

-- 依次返回 迭代器、状态表、迭代器初始值
function mypairs(t)

    function iterator(t,i)
        i = i + 1
        i = t[i] and i      -- 如果 t[i] == nil 则 i = nil;否则 i = i
        return i,t[i]
    end

    return iterator,t,0

end

-- 一个表
t = {[1]="1",[2]="2"}

-- 迭代形式 for 语句的 等价形式
do
local f, s, var = mypairs(t)
    while true do
        local var1, var2 = f(s, var)
        var = var1
        if var == nil then break end

        -- for 循环中添加的语句
        print(var1,var2)

    end
end

-- 迭代形式 for 语句
for var1,var2 in mypairs(t) do
    print(var1,var2)
end

--> 1   1
--> 2   2
--> 1   1
--> 2   2

5.2.1 数组形式

ary = {[1]=1,[2]=2,[5]=5}
for i,v in ipairs(ary) do
    print(v)                    --> 1 2
end

从1开始,直到数值型下标结束或者值为 nil 时结束。

5.2.2 表遍历

table = {[1]=1,[2]=2,[5]=5}
for k,v in pairs(table) do
    print(v)                    --> 1 2 5
end

遍历整个表的键值对。

关于迭代器的更多内容,可参考Lua 迭代器和泛型 for


参考链接

Lua迭代器和泛型for(介绍 Lua 迭代器的详细原理以及使用)

2015年五月2日晚上 10:29:56 Lua 学习笔记(一)—— 基本语法

1 简介

由 clean C 实现。需要被宿主程序调用,可以注入 C 函数。

2 语法约定

Lua 的语法基于 BNF 的语法规则。

Lua 对大小写敏感。

2.1 保留关键字

C 语言中没有的关键字有:

and elseif function
in nil local not or
repeat then until

规范:全局变量以下划线开头。

2.2 操作符

C 语言中没有的操作符:

^ 
~= 
//  -- 向下取整

Lua 中没有的操作符:

+=
-=

2.3 字符串定义

采用转义符:通过转义符表示那些有歧义的字符

字符表示

a           -- 代表字符 a
\97         -- 代表字符 a
\049        -- 代表数字字符 1 

其他转义符表示

\\n         -- 代表字符串 \n
\n          -- 代表换行

注意数字字符必须是三位。其他字符则不能超过三位。

采用长括号:长括号内的所有内容都作为普通字符处理。

[[]]        -- 0级长括号
[==[]==]    -- 2级长括号

3 值与类型

Lua 是动态语言,变量没有类型,值才有。值自身携带类型信息。

Lua 有八种基本数据类型:nil, boolean, number, string, function, userdata, thread, table

nilfalse 导致条件为假,其他均为真。

userdata 类型变量用于保存 C 数据。 Lua 只能对该类数据进行使用,而不能进行创建或修改,保证宿主程序完全掌握数据。

thread 用于实现协程(coroutine)。

table 用于实现关联数组。table 允许任何类型的数据做索引,也允许任何类型做 table 域中的值(前述
任何类型 不包含 nil)。table 是 Lua 中唯一的数据结构。
由于函数也是一种值,所以 table 中可以存放函数。

function, userdata, thread, table 这些类型的值都是对象。这些类型的变量都只是保存变量的引用,并且在进行赋值,参数传递,函数返回等操作时不会进行任何性质的拷贝。

库函数 type() 返回变量的类型描述信息。

3.1 强制转换

Lua 提供数字字符串间的自动转换。
可以使用 format 函数控制数字向字符串的转换。

4 变量

变量有三种类型:全局变量、局部变量、表中的域

函数外的变量默认为全局变量,除非用 local 显示声明。函数内变量与函数的参数默认为局部变量。

局部变量的作用域为从声明位置开始到所在语句块结束(或者是直到下一个同名局部变量的声明)。

变量的默认值均为 nil。


a = 5 -- 全局变量 local b = 5 -- 局部变量 function joke() c = 5 -- 局部变量 local d = 6 -- 局部变量 end print(c,d) --> nil nil do local a = 6 -- 局部变量 b = 6 -- 全局变量 print(a,b); --> 6 6 end print(a,b) --> 5 6

方便标记,--> 代表前面表达式的结果。

4.1 索引

对 table 的索引使用方括号 []。Lua使用语法糖提供 . 操作。

t[i]
t.i                 -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用

4.2 环境表

所有全局变量放在一个环境表里,该表的变量名为 _env 。对某个全局变量 a 的访问即 _env.a_env_ 只是为了方便说明)。

每个函数作为变量持有一个环境表的引用,里面包含该函数可调用的所有变量。
子函数会从父函数继承环境表。
可以通过函数 getfenv / setfenv 来读写环境表。

2015年五月2日晚上 10:10:54 Lua 学习笔记(上)

1 简介

由 clean C 实现。需要被宿主程序调用,可以注入 C 函数。

2 语法

采用基于 BNF 的语法规则。

2.1 语法约定

Lua 对大小写敏感。

2.1.1 保留关键字

C 语言中没有的关键字有:

and elseif function
in nil local not or
repeat then until

规范:全局变量以下划线开头。

2.1.2 操作符

C 语言中没有的操作符:

^ 
~= 
//  -- 向下取整

Lua 中没有的操作符:

+=
-=

2.1.3 字符串定义

采用转义符:通过转义符表示那些有歧义的字符

字符表示

a           -- 代表字符 a
\97         -- 代表字符 a
\049        -- 代表数字字符 1 

其他转义符表示

\\n         -- 代表字符串 \n
\n          -- 代表换行

注意数字字符必须是三位。其他字符则不能超过三位。

采用长括号:长括号内的所有内容都作为普通字符处理。

[[]]        -- 0级长括号
[==[]==]    -- 2级长括号

2.2 值与类型

Lua 是动态语言,变量没有类型,值才有。值自身携带类型信息。

Lua 有八种基本数据类型:nil, boolean, number, string, function, userdata, thread, table

nilfalse 导致条件为假,其他均为真。

userdata 类型变量用于保存 C 数据。 Lua 只能对该类数据进行使用,而不能进行创建或修改,保证宿主程序完全掌握数据。

thread 用于实现协程(coroutine)。

table 用于实现关联数组。table 允许任何类型的数据做索引,也允许任何类型做 table 域中的值(前述
任何类型 不包含 nil)。table 是 Lua 中唯一的数据结构。
由于函数也是一种值,所以 table 中可以存放函数。

function, userdata, thread, table 这些类型的值都是对象。这些类型的变量都只是保存变量的引用,并且在进行赋值,参数传递,函数返回等操作时不会进行任何性质的拷贝。

库函数 type() 返回变量的类型描述信息。

2.2.1 强制转换

Lua 提供数字字符串间的自动转换。
可以使用 format 函数控制数字向字符串的转换。

2.3 变量

变量有三种类型:全局变量、局部变量、表中的域

函数外的变量默认为全局变量,除非用 local 显示声明。函数内变量与函数的参数默认为局部变量。

局部变量的作用域为从声明位置开始到所在语句块结束(或者是直到下一个同名局部变量的声明)。

变量的默认值均为 nil。


a = 5 -- 全局变量 local b = 5 -- 局部变量 function joke() c = 5 -- 局部变量 local d = 6 -- 局部变量 end print(c,d) --> nil nil do local a = 6 -- 局部变量 b = 6 -- 全局变量 print(a,b); --> 6 6 end print(a,b) --> 5 6

方便标记,--> 代表前面表达式的结果。

2.3.1 索引

对 table 的索引使用方括号 []。Lua使用语法糖提供 . 操作。

t[i]
t.i                 -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用

2.3.2 环境表

所有全局变量放在一个环境表里,该表的变量名为 _env 。对某个全局变量 a 的访问即 _env.a_env_ 只是为了方便说明)。

每个函数作为变量持有一个环境表的引用,里面包含该函数可调用的所有变量。
子函数会从父函数继承环境表。
可以通过函数 getfenv / setfenv 来读写环境表。

2.4 语句 | statement

支持赋值,控制结构,函数调用,还有变量声明。

不允许空的语句段,因此 ;; 是非法的。

2.4.1 语句组 | chuncks

chunck ::= {stat[';']}

([';'] 应该是表示语句组后面 ; 是可选项。)

2.4.2 语句块 | blocks

block ::= chunck
stat ::= do block end

可以将一个语句块显式地写成语句组,可以用于控制局部变量的作用范围。

2.4.3 赋值 | assignment

Lua 支持多重赋值。

多重赋值时,按序将右边的表达式的值赋值给左值。右值不足补 nil,右值多余舍弃。

b = 1
a,b = 4 -- a = 4,b = nil 

+++

Lua 在进行赋值操作时,会一次性把右边的表达式都计算出来后进行赋值。

i = 5
i,a[i] = i+1, 7 -- i = 6 ,a[5] = 7

特别地,有

x,y = y,x -- 交换 x,y 的值

+++

对全局变量以及表的域的赋值操作含义可以在元表中更改。

2.4.4 控制结构

条件语句

if [exp]
    [block]
elseif [exp]
    [block]
else
    [block]
end

循环语句

while [exp]
    [block]
end

+++

repeat
    [block]
until [exp]

注意,由于 repeat 语句到 until 还未结束,因此在 until 之后的表达式中可以使用 block 中定义的局部变量。

例如:

a = 1
c = 5
repeat
    b = a + c
    c = c * 2
until b > 20
print(c)            -->     40

+++

breakreturn

breakreturn 只能写在语句块的最后一句,如果实在需要写在语句块中间,那么就在两个关键词外面包围 do end 语句块。

do break end

2.4.5 For 循环

for 循环的用法比较多,单独拎出来讲。

for 中的表达式会在循环开始前一次性求值,在循环过程中不再更新。

数字形式

for [Name] = [exp],[exp],[exp] do [block] end

三个 exp 分别代表初值,结束值,步进。exp 的值均需要是一个数字。
第三个 exp 默认为 1,可以省略。

a = 0

for i = 1,6,2 do
    a = a + i
end

等价于

int a = 0;
for (int i = 1; i <= 6;i += 2){ // 取到等号,如果步进是负的,那么会取 i >= 6
    a += i;
}

迭代器形式

迭代器形式输出一个表时,如果表中有函数,则输出的顺序及个数不确定(笔者测试得出的结果,具体原因未知)。

迭代器形式的 for 循环的实质

-- 依次返回 迭代器、状态表、迭代器初始值
function mypairs(t)

    function iterator(t,i)
        i = i + 1
        i = t[i] and i      -- 如果 t[i] == nil 则 i = nil;否则 i = i
        return i,t[i]
    end

    return iterator,t,0

end

-- 一个表
t = {[1]="1",[2]="2"}

-- 迭代形式 for 语句的 等价形式
do
local f, s, var = mypairs(t)
    while true do
        local var1, var2 = f(s, var)
        var = var1
        if var == nil then break end

        -- for 循环中添加的语句
        print(var1,var2)

    end
end

-- 迭代形式 for 语句
for var1,var2 in mypairs(t) do
    print(var1,var2)
end

--> 1   1
--> 2   2
--> 1   1
--> 2   2
数组形式
ary = {[1]=1,[2]=2,[5]=5}
for i,v in ipairs(ary) do
    print(v)                    --> 1 2
end

从1开始,直到数值型下标结束或者值为 nil 时结束。

表遍历
table = {[1]=1,[2]=2,[5]=5}
for k,v in pairs(table) do
    print(v)                    --> 1 2 5
end

遍历整个表的键值对。

关于迭代器的更多内容,可参考Lua 迭代器和泛型 for

2.5 表达式

2.5.1 数学运算操作符

% 操作符

Lua 中的 % 操作符与 C 语言中的操作符虽然都是取模的含义,但是取模的方式不一样。
在 C 语言中,取模操作是将两个操作数的绝对值取模后,在添加上第一个操作数的符号。
而在 Lua 中,仅仅是简单的对商相对负无穷向下取整后的余数。

+++

在 C 中,

a1 = abs(a);
b1 = abs(b);
c = a1 % b1 = a1 - floor(a1/b1)*b1;

a % b = (a >= 0) ? c : -c;

在 Lua 中,

a % b == a - math.floor(a/b)*b

Lua 是直接根据取模定义进行运算。 C 则对取模运算做了一点处理。

+++

举例:

在 C 中

int a = 5 % 6;
int b = 5 % -6;
int c = -5 % 6;
int d = -5 % -6;

printf("a,b,c,d");--5,5,-5,-5

在 Lua 中

a = 5 % 6
b = 5 % -6
c = -5 % 6
d = -5 % -6

x = {a,b,c,d}

for i,v in ipairs(x) do
    print(i,v)
end


--> 5
--> -1
--> 1
--> -5

可以看到,仅当操作数同号时,两种语言的取模结果相同。异号时,取模结果的符号与数值均不相等。

在 Lua 中的取模运算总结为:a % b,如果 a,b 同号,结果取 a,b 绝对值的模;异号,结果取 b 绝对值与绝对值取模后的差。取模后值的符号与 b 相同。

2.5.2 比较操作符

比较操作的结果是 boolean 型的,非 truefalse

支持的操作符有:

< <= ~= == > >=

不支持 ! 操作符。

+++

对于 == 操作,运算时先比较两个操作数的类型,如果不一致则结果为 false。此时数值与字符串之间并不会自动转换。

比较两个对象是否相等时,仅当指向同一内存区域时,判定为 true。·

a = 123
b = 233
c = "123"
d = "123"
e = {1,2,3}
f = e
g = {1,2,3}

print(a == b)       --> false
print(a == c)       --> false      -- 数字与字符串作为不同类型进行比较
print(c == d)       --> true       
print(e == f)       --> true       -- 引用指向相同的对象
print(e == g)       --> false      -- 虽然内容相同,但是是不同的对象
print(false == nil) --> false      -- false 是 boolean,nil 是 nil 型

方便标记,--> 代表前面表达式的结果。

+++

userdatatable 的比较方式可以通过元方法 eq 进行改变。

大小比较中,数字和字符串的比较与 C 语言一致。如果是其他类型的值,Lua会尝试调用元方法 ltle

2.5.3 逻辑操作符

and,or,not

仅认为 falsenil 为假。

not

取反操作 not 的结果为 boolean 类型。(andor 的结果则不一定为 boolean)

b = not a           -- a 为 nil,b 为 true
c = not not a       -- c 为 false

and

a and b,如果 a 为假,返回 a,如果 a 为真, 返回 b

注意,为什么 a 为假的时候要返回 a 呢?有什么意义?这是因为 a 可能是 false 或者 nil,这两个值虽然都为假,但是是有区别的。

or

a or b,如果 a 为假,返回 b,如果 a 为真, 返回 a。与 and 相反。

+++

提示: 当逻辑操作符用于得出一个 boolean 型结果时,不需要考虑逻辑运算后返回谁的问题,因为逻辑操作符的操作结果符合原本的逻辑含义。

举例

if (not (a > min and a < max)) then  -- 如果 a 不在范围内,则报错
    error() 
end

+++

其他

andor 遵循短路原则,第二个操作数仅在需要的时候会进行求值操作。

例子


a = 5 x = a or jjjj() -- 虽然后面的函数并没有定义,但是由于不会执行,因此不会报错。 print(a) -->5 print(x) -->5

通过上面这个例子,我们应当对于逻辑操作有所警觉,因为这可能会引入一些未能及时预料到的错误。

2.5.4 连接符

..
连接两个字符串(或者数字)成为新的字符串。对于其他类型,调用元方法 concat

2.5.5 取长度操作符

#

对于字符串,长度为字符串的字符个数。

对于表,通过寻找满足t[n] 不是 nil 而 t[n+1] 为 nil 的下标 n 作为表的长度。

~~对于其他类型呢?~~

例子

-- 字符串取长
print(#"abc\0")                         --> 4
-- 表取长
print(#{[1]=1,[2]=2,[3]=3,x=5,y=6})     --> 3
print(#{[1]=1,[2]=nil,[3]=3,x=5,y=6})   --> 1

2.5.6 优先级

由低到高:

or
and
 <     >     <=    >=    ~=    ==
 ..
 +     -
 *     /     %
 not   #     - (unary)
 ^

幂运算>单目运算>四则运算>连接符>比较操作符>and>or

2.5.7 Table 构造

Table 构造的 BNF 定义

tableconstructor ::= `{´ [fieldlist] `}´
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
fieldsep ::= `,´ | `;´

举例:

a = {}
b = {["price"] = 5; cost = 4; 2+5}
c = { [1] = 2+5, [2] = 2, 8, price = "abc", ["cost"] = 4} -- b 和 c 构造的表是等价的


print(b["price"])   --> 5
print(b.cost)       --> 4
print(b[1])         --> 7       -- 未给出键值的,按序分配下标,下标从 1 开始

print(c["price"])   --> abc
print(c.cost)       --> 4
print(c[1])         --> 8       
print(c[2])         --> 2       

注意:

上面这两条的存在使得上面的例子中 c1 的输出值为 8。

+++

如果表中有相同的键,那么以靠后的那个值作为键对应的值。

a = {[1] = 5,[1] = 6} -- 那么 a[1] = 6

+++

如果表的最后一个域是表达式形式,并且是一个函数,那么这个函数的所有返回值都会加入到表中。

a = 1
function order()
    a = a + 1
    return 1,2,3,4
end

b = {order(); a; order(); }

c = {order(); a; (order());}

print(b[1])                     --> 1       
print(b[2])                     --> 2       -- 表中的值并不是一次把表达式都计算结束后再赋值的
print(b[3])                     --> 1       
print(b[4])                     --> 2       -- 表达式形式的多返回值函数

print(#b)                       --> 6       -- 表的长度为 6                 
print(#c)                       --> 3       -- 函数添加括号后表的长度为 3

2.5.8 函数定义

函数是一个表达式,其值为 function 类型的对象。函数每次执行都会被实例化。

Lua 中实现一个函数可以有以下三种形式。

f = function() [block] end
local f; f = function() [block] end
a.f = function() [block] end

Lua 提供语法糖分别处理这三种函数定义。

function f() [block] end
local function f() [block] end
function a.f() [block] end

+++

上面 local 函数的定义之所以不是 local f = function() [block] end,是为了避免如下错误:

local f = function()
    print("local fun")
    if i==0 then 
        f()             -- 编译错误:attempt to call global 'f' (a nil value)
        i = i + 1
    end
end

函数的参数

形参会通过实参来初始化为局部变量。

参数列表的尾部添加 ... 表示函数能接受不定长参数。如果尾部不添加,那么函数的参数列表长度是固定的。

f(a,b)
g(a,b,...)
h(a,...,b)              -- 编译错误
f(1)                    --> a = 1, b = nil
f(1,2)                  --> a = 1, b = 2
f(1,2,3)                --> a = 1, b = 2

g(1,2)                  --> a = 1, b = 2, (nothing)
g(1,2,3)                --> a = 1, b = 2, (3)
g(1,f(4,5),3)           --> a = 1, b = 4, (3)
g(1,f(4,5))             --> a = 1, b = 4, (5)

+++

还有一种形参为self的函数的定义方式:

a.f = function (self, params) [block] end

其语法糖形式为:

function a:f(params) [block] end

使用举例:

a = {name = "唐衣可俊"}
function a:f()
    print(self.name)
end
a:f()                       --> 唐衣可俊   -- 如果这里使用 a.f(),那么 self.name 的地方会报错 attempt to index local 'self';此时应该写为 a.f(a)

: 的作用在于函数定义与调用的时候可以少写一个 self 参数。这种形式是对方法的模拟

2.5.9 函数调用

Lua 中的函数调用的BNF语法如下:

functioncall ::= prefixexp args

如果 prefixexp 的值的类型是 function, 那么这个函数就被用给出的参数调用。 否则 prefixexp 的元方法 "call" 就被调用, call 的第一个参数就是 prefixexp 的值,接下来的是 args 参数列表(参见 2.8 元表 | Metatable)。

函数调用根据是否传入 self 参数分为 . 调用和 : 调用。
函数调用根据传入参数的类型,可以分为参数列表调用、表调用、字符串调用

[待完善]

2.5.10 函数闭包

如果一个函数访问了它的外部变量,那么它就是一个闭包。

由于函数内部的变量均为局部变量,外界无法对其进行访问。这时如果外界想要改变局部变量的值,那么就可以使用闭包来实现这一目的。
具体的实现过程大致是这样,函数内部有能够改变局部变量的子函数,函数将这个子函数返回,那么外界就可以通过使用这个子函数来操作局部变量了。

例子:利用闭包来实现对局部变量进行改变

-- 实现一个迭代器

function begin(i)
    local cnt = i

    return function ()      -- 这是一个匿名函数,实现了自增的功能;同时它也是一个闭包,因为访问了外部变量 cnt
        cnt = cnt + 1
        return cnt
    end
end


iterator = begin(2)     -- 设置迭代器的初值为 2 ,返回一个迭代器函数

print(iterator())           -- 执行迭代
print(iterator())

提示: 关于闭包的更多说明可参考JavaScript 闭包是如何工作的?——StackOverflow

2.6 可视规则

即变量的作用域,见 2.3 变量 部分。

2.7 错误处理

[待补充]

2.8 元表 | Metatable

我们可以使用操作符对 Lua 的值进行运算,例如对数值类型的值进行加减乘除的运算操作以及对字符串的连接、取长操作等(在 2.5 表达式 这一节中介绍了许多类似的运算)。元表正是定义这些操作行为的地方。

元表本质上是一个普通 Lua 表。元表中的键用来指定操作,称为“事件名”;元表中键所关联的值称为“元方法”,定义操作的行为。

2.8.1 事件名与元方法

仅表(table)类型值对应的元表可由用户自行定义。其他类型的值所对应的元表仅能通过 Debug 库进行修改。

元表中的事件名均以两条下划线 __ 作为前缀,元表支持的事件名有如下几个:

__index     -- 'table[key]',取下标操作,用于访问表中的域
__newindex  -- 'table[key] = value',赋值操作,增改表中的域
__call      -- 'func(args)',函数调用,参见 [2.5.9 函数调用](#2-5-9)

-- 数学运算操作符
__add       -- '+'
__sub       -- '-'
__mul       -- '*'
__div       -- '/'
__mod       -- '%'
__pow       -- '^'
__unm       -- '-'

-- 连接操作符
__concat    -- '..'

-- 取长操作符
__len       -- '#'

-- 比较操作符
__eq        -- '=='
__lt        -- '<'      -- a > b 等价于 b < a
__le        -- '<='     -- a >= b 等价于 b <= a 

还有一些其他的事件,例如 __tostring__gc 等。

下面进行详细介绍。

2.8.2 元表与值

每个值都可以拥有一个元表。对 userdata 和 table 类型而言,其每个值都可以拥有独立的元表,也可以几个值共享一个元表。对于其他类型,一个类型的值共享一个元表。例如所有数值类型的值会共享一个元表。除了字符串类型,其他类型的值默认是没有元表的。

使用 getmetatable 函数可以获取任意值的元表。getmetatable (object)
使用 setmetatable 函数可以设置表类型值的元表。setmetatable (table, metatable)

例子

只有字符串类型的值默认拥有元表:

a = "5"
b = 5
c = {5}
print(getmetatable(a))      --> table: 0x7fe221e06890
print(getmetatable(b))      --> nil
print(getmetatable(c))      --> nil

2.8.3 事件的具体介绍

事先提醒 Lua 使用 raw 前缀的函数来操作元方法,避免元方法的循环调用。

例如 Lua 获取对象 obj 中元方法的过程如下:

rawget(getmetatable(obj)or{}, "__"..event_name)

元方法 index

index 是元表中最常用的事件,用于值的下标访问 -- table[key]

事件 index 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。
当用户通过键值来访问表时,如果没有找到键对应的值,则会调用对应元表中的此事件。如果 index 使用表进行赋值,则在该表中查找传入键的对应值;如果 index 使用函数进行赋值,则调用该函数,并传入表和键。

Lua 对取下标操作的处理过程用伪码表示如下:

function gettable_event (table, key)
    -- h 代表元表中 index 的值
    local h     
    if type(table) == "table" then

        -- 访问成功
        local v = rawget(table, key)
        if v ~= nil then return v end

        -- 访问不成功则尝试调用元表的 index
        h = metatable(table).__index

        -- 元表不存在返回 nil
        if h == nil then return nil end
    else

        -- 不是对表进行访问则直接尝试元表
        h = metatable(table).__index

        -- 无法处理导致出错
        if h == nil then
            error(···);
        end
    end

    -- 根据 index 的值类型处理
    if type(h) == "function" then
        return h(table, key)            -- 调用处理器
    else 
        return h[key]                   -- 或是重复上述操作
    end
end

例子:

使用表赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = {[3] = "pig", [4] = "cow", [5] = "duck"}})
print(t[3])             --> pig

使用函数赋值:

t = {[1] = "cat",[2] = "dog"}
print(t[3])             --> nil
setmetatable(t, {__index = function (table,key)
    key = key % 2 + 1
    return table[key]
end})
print(t[3])             --> dog

元方法 newindex

newindex 用于赋值操作 -- talbe[key] = value

事件 newindex 的值可以是函数也可以是表。当使用表进行赋值时,元方法可能引发另一次元方法的调用,具体可见下面伪码介绍。

当操作类型不是表或者表中尚不存在传入的键时,会调用 newindex 的元方法。如果 newindex 关联的是一个函数类型以外的值,则再次对该值进行赋值操作。反之,直接调用函数。

~~不是太懂:一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)~~

Lua 进行赋值操作时的伪码如下:

function settable_event (table, key, value)
    local h
    if type(table) == "table" then

        -- 修改表中的 key 对应的 value
        local v = rawget(table, key)
        if v ~= nil then rawset(table, key, value); return end

        -- 
        h = metatable(table).__newindex

        -- 不存在元表,则直接添加一个域
        if h == nil then rawset(table, key, value); return end
    else
        h = metatable(table).__newindex
        if h == nil then
            error(···);
        end
    end

    if type(h) == "function" then
        return h(table, key,value)    -- 调用处理器
    else 


        h[key] = value             -- 或是重复上述操作
    end
end

例子:

元方法为表类型:

t = {}
mt = {}

setmetatable(t, {__newindex = mt})
t.a = 5
print(t.a)      --> nil
print(mt.a)     --> 5

通过两次调用 newindex 元方法将新的域添加到了表 mt 。

+++

元方法为函数:

-- 对不同类型的 key 使用不同的赋值方式
t = {}
setmetatable(t, {__newindex = function (table,key,value)
    if type(key) == "number" then
        rawset(table, key, value*value)
    else
        rawset(table, key, value)
    end
end})
t.name = "product"
t[1] = 5
print(t.name)       --> product
print(t[1])         --> 25

元方法 call

call 事件用于函数调用 -- function(args)

Lua 进行函数调用操作时的伪代码:

function function_event (func, ...)

  if type(func) == "function" then
      return func(...)   -- 原生的调用
  else
      -- 如果不是函数类型,则使用 call 元方法进行函数调用
      local h = metatable(func).__call

      if h then
        return h(func, ...)
      else
        error(···)
      end
  end
end

例子:

由于用户只能为表类型的值绑定自定义元表,因此,我们可以对表进行函数调用,而不能把其他类型的值当函数使用。

-- 把数据记录到表中,并返回数据处理结果
t = {}

setmetatable(t, {__call = function (t,a,b,factor)
  t.a = 1;t.b = 2;t.factor = factor
  return (a + b)*factor
end})

print(t(1,2,0.1))       --> 0.3

print(t.a)              --> 1
print(t.b)              --> 2
print(t.factor)         --> 0.1

运算操作符相关元方法

运算操作符相关元方法自然是用来定义运算的。

以 add 为例,Lua 在实现 add 操作时的伪码如下:

function add_event (op1, op2)
  -- 参数可转化为数字时,tonumber 返回数字,否则返回 nil
  local o1, o2 = tonumber(op1), tonumber(op2)
  if o1 and o2 then  -- 两个操作数都是数字?
    return o1 + o2   -- 这里的 '+' 是原生的 'add'
  else  -- 至少一个操作数不是数字时
    local h = getbinhandler(op1, op2, "__add") -- 该函数的介绍在下面
    if h then
      -- 以两个操作数来调用处理器
      return h(op1, op2)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

代码中的 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作。 在该函数中,首先,Lua 尝试第一个操作数。如果这个操作数所属类型没有定义这个操作的处理器,然后 Lua 会尝试第二个操作数。

 function getbinhandler (op1, op2, event)
   return metatable(op1)[event] or metatable(op2)[event]
 end

+++

对于一元操作符,例如取负,Lua 在实现 unm 操作时的伪码:

function unm_event (op)
  local o = tonumber(op)
  if o then  -- 操作数是数字?
    return -o  -- 这里的 '-' 是一个原生的 'unm'
  else  -- 操作数不是数字。
    -- 尝试从操作数中得到处理器
    local h = metatable(op).__unm
    if h then
      -- 以操作数为参数调用处理器
      return h(op)
    else  -- 没有处理器:缺省行为
      error(···)
    end
  end
end

例子:

加法的例子:

t = {}
setmetatable(t, {__add = function (a,b)
  if type(a) == "number" then
      return b.num + a
  elseif type(b) == "number" then
      return a.num + b
  else
      return a.num + b.num
  end
end})

t.num = 5

print(t + 3)  --> 8

取负的例子:

t = {}
setmetatable(t, {__unm = function (a)
  return -a.num
end})

t.num = 5

print(-t)  --> -5

其他事件的元方法

对于连接操作,当操作数中存在数值或字符串以外的类型时调用该元方法。

对于取长操作,如果操作数不是字符串类型,也不是表类型,则尝试使用元方法(这导致自定义的取长基本没有,在之后的版本中似乎做了改进)。

对于三种比较类操作,均需要满足两个操作数为同类型,且关联同一个元表时才能使用元方法。

对于 eq (等于)比较操作,如果操作数所属类型没有原生的等于比较,则调用元方法。

对于 lt (小于)与 le (小于等于)两种比较操作,如果两个操作数同为数值或者同为字符串,则直接进行比较,否则使用元方法。

对于 le 操作,如果元方法 "le" 没有提供,Lua 就尝试 "lt",它假定 a <= b 等价于 not (b < a) 。

对于 tostring 操作,元方法定义了值的字符串表示方式。

例子:

取长操作:

t = {1,2,3,"one","two","three"}
setmetatable(t, {__len = function (t)
  local cnt = 0
  for k,v in pairs(t) do
    if type(v) == "number" then 
      cnt = cnt + 1
      print(k,v)
    end
  end
  return cnt
end})

-- 结果是 6 而不是预期中的 3
print(#t)   --> 6 

等于比较操作:

t = {name="number",1,2,3}
t2 = {name = "number",4,5,6}
mt = {__eq = function (a,b)
    return a.name == b.name
end}
setmetatable(t,mt)              -- 必须要关联同一个元表才能比较
setmetatable(t2,mt)

print(t==t2)   --> true

tostring 操作:

t = {num = "a table"}
print(t)              --> table: 0x7f8e83c0a820

mt = {__tostring = function(t)
  return t.num
end}
setmetatable(t, mt)

print(tostring(t))    --> a table
print(t)              --> a table

2.9 环境表

类型 threadfunctionuserdata 的对象除了能与元表建立关联外,还能关联一个环境表。

关联在线程上的环境表称为全局环境。
全局环境作为子线程及子函数的默认环境。
全局环境能够直接被 C 调用。

关联在 Lua 函数上的环境表接管函数对全局变量的所有访问。并且作为子函数的默认环境。

关联在 C 函数上的环境能直接被 C 调用。

关联在 userdata 上的环境没有实际的用途,只是为了方便程序员把一个表关联到 userdata 上。

2.10 垃圾回收

2.10.1 垃圾收集的元方法

[待补充]

2.10.2 弱表

弱表是包含弱引用的表。

弱表的弱引用方式有三种。键弱引用,值弱引用,键和值均弱引用

可以通过元表中的 __mode 域来设置一个表是否有弱引用,以及弱引用的方式。

a = {}
b = { __mode = "k"}  -- 引号中添加 k 表示 key 弱引用,v 表示 value 弱引用, kv 表示均弱引用。
setmetable(a,b)     -- b 是 a 的元表,绑定后就不能在更改 __mode 的值。

垃圾回收机制会把弱引用的部分回收。但是不论是哪种弱引用,回收机制都会把整个键值对从弱表中移除。

3 程序接口 (API)

这部分描述 Lua 的 C API,即用来与 Lua 进行通信的 C 函数,所有的函数和常量都定义在 lua.h 头文件里面。

有一部分 C 函数是用宏来实现的。~~为什么?:由于所有的宏只会使用他们的参数一次(除了第一个参数,即 Lua 状态机),所以不必担心宏展开带来的副作用。~~

默认情况下 Lua 在进行函数调用时不会检查函数的有效性和坚固性,如果想要进行检查,则使用 luaconf.h 中的 luai_apicheck() 函数开启。

3.1 堆栈

Lua 调用 C API 时使用一个虚拟栈来传递参数,栈中的所有元素都是 Lua 的类型(例如 booleantablenil等)。

Lua 调用 C 函数的时候都会新建一个虚拟栈,而不是使用旧栈或者其他的栈。同时在 C 函数中,对 Lua API 调用时,只能使用当前调用所对应栈中的元素,其他栈的元素是无法访问的。
虚拟栈中包含 C 函数所需的所有参数,函数的返回值也都放在该栈中。

这里所谓的栈概念并不是严格意义上的栈,可以通过下标对栈中的元素进行访问。1表示栈底,-1表示栈顶,又例如 3 表示从栈底开始的第三个元素。

3.2 堆栈尺寸

由于 Lua 的 C API 默认不做有效性和坚固性(鲁棒性)检测,因此开发人员有责任保证坚固性。特别要注意的是,不能让堆栈溢出。Lua 只保证栈大小会大于 LUA_MINSTACK(一般是 20)。开发人员可以使用 lua_checkstack 函数来手动设置栈的大小。

3.3 伪索引

除了用索引访问函数堆栈的 Lua 元素,C 代码还可以使用伪索引来访问堆栈以外的 Lua 元素,例如线程的环境、注册表、函数的环境 以及 C函数的 upvalue(上值)。可以通过特别声明来禁用伪索引。

线程的环境放在伪索引 LUA_GLOBALSINDEX 处,函数的环境放在伪索引 LUA_ENVIRONINDEX 处。

访问环境的方式跟访问表的方式是一致的,例如要访问全局变量的值,可以使用:

lua_getfield(L,LUA_GLOBALSINDEX,varname)

3.4 C 闭包

当我们把创建出来的函数和一些值关联在一起,就得到了一个闭包。那些关联起来的值称为 upvalue (上值)。

函数的上值都放在特定的伪索引处,可以通过 lua_upvalueindex 获取上值的伪索引。例如 lua_upvalueindex(3) 表示获取第三个关联值(按照关联顺序排列)对应的伪索引。

3.5 注册表

Lua 提供了一个注册表,C 代码可以用来存放想要存放的 Lua 值。注册表用伪索引 LUA_REGISTRYINDEX 定位。

为了避免命名冲突,一般采用包含库名的字符串作为键名。~~什么东西?:或者可以取你自己 C 代码 中的一个地址,以 light userdata 的形式做键。~~

注册表中的整数键有特定用途(用于实现补充库的引用系统),不建议用于其他用途。

3.6 C 中的错误处理

[待补充]

3.7 函数和类型

本节介绍 C API 中的函数和类型。

余下部分见 Lua 学习笔记(下)


参考链接

BNF范式简介 (简要介绍 BNF)
Lua入门系列-果冻想(对Lua进行了较为全面的介绍)
Lua快速入门(介绍 Lua 中最为重要的几个概念,为 C/C++ 程序员准备)
Lua 5.1 中文手册(全面的 Lua5.1 中文手册)
Lua 5.3 中文手册(云风花了6天写的,天哪,我看都要看6天的节奏呀)
Lua迭代器和泛型for(介绍 Lua 迭代器的详细原理以及使用)
How do JavaScript closures work?——StackOverflow(详细介绍了 Javascript 中闭包的概念)
Lua模式匹配(参考了此文中对 %b 的使用)
LuaSocket(LuaSocket 官方手册)
Lua loadfile的用法, 与其他函数的比较(loadfile的介绍部分引用了此文)
Lua 的元表(对元表的描述比较有条理,通俗易懂,本文元表部分参考了此文)
设置函数环境——setfenv(解释了如何方便地设置函数的环境,以及为什么要那样设置)
lua5.1中的setfenv使用(介绍了该环境的设置在实际中的一个应用)

2015年五月2日晚上 9:00:36 PHP数组操作详解

概述

要访问一个变量的内容,可以直接使用其名称。如果该变量是一个数组,可以使用变量名称和关键字或索引的组合来访问其内容。

像其他变量一样,使用运算符=可以改变数组元素的内容。数组单元可以通过 array[key] 语法来访问。

图片描述

数组的基本操作

php定义数组:

<?php  
    $array = array();  
    $array["key"] = "values";  
?> 

PHP中声明数组的方式主要有两种:

1.用array()函数声明数组,
2.直接为数组元素赋值。

<?php
    //array数组
    $users = array('phone','computer','dos','linux');
    echo $users;//只会打印出数据类型Array
    print_r($users);//Array ( [0] => phone [1] => computer [2] => dos [3] => linux )

    $numbers = range(1,5);//创建一个包含指定范围的数组
    print_r($numbers);//Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )
    print_r(true);//1
    var_dump(false);//bool(false)

//print_r可以把字符串和数字简单地打印出来,数组会以Array开头并已键值形式表示,print_r输出布尔值和null的结果没有意义,因此用var_dump更合适

//通过循环来显示数组里所有的值
    for($i = 0 ;$i < 5;$i++){
        echo $users[$i];
        echo '<br/>';
    }

//通过count/sizeof统计数组中单元数目或对象中的属性个数

    for($i = 0; $i < count($users);$i++){
        echo $users[$i];
        echo '<br/>';
    }
//还可以通过foreach循环来遍历数组,这种好处在于不需要考虑key
    foreach($users as $value){
        echo $value.'<br/>';//点号为字符串连接符号
    }
//foreach循环遍历 $key => $value;$key和$value是变量名,可以自行设置
    foreach($users as $key => $value){
        echo $key.'<br/>';//输出键
    }
?>

创建自定义键的数组

<?php

    //创建自定义键的数组
    $ceo = array('apple'=>'jobs','microsoft'=>'Nadella','Larry Page','Eric');
    //如果不去声明元素的key,它会从零开始
    print_r($ceo);//Array ( [apple] => jobs [microsoft] => Nadella [0] => Larry Page [1] => Eric )

    echo $ceo['apple'];//jobs

     //php5.4起的用法
    $array = [
        "foo" => "bar",
        "bar" => "foo",
    ];

    print_r($array);//Array ( [foo] => bar [bar] => foo ) 

?>    

php5.4 起可以使用短数组定义语法,用 [] 替代 array()。有点类似于javascript中数组的定义。

each()的使用

<?php
    //通过为数组元素赋值来创建数组
    $ages['trigkit4'] = 22;
    echo $ages.'<br/>';//Array
    //因为相关数组的索引不是数字,所以无法通过for循环来进行遍历操作,只能通过foreach循环或list()和each()结构

    //each的使用
    //each返回数组中当前的键/值对并将数组指针向前移动一步
    $users = array('trigkit4'=>22,'mike'=>20,'john'=>30);
    //print_r(each($users));//Array ( [1] => 22 [value] => 22 [0] => trigkit4 [key] => trigkit4 )

   //相当于:$a = array([0]=>trigkit4,[1]=>22,[value]=>22,[key]=>trigkit4);
    $a = each($users);//each把原来的数组的第一个元素拿出来包装成新数组后赋值给$a
    echo $a[0];//trigkit4

    //!!表示将真实存在的数据转换成布尔值
    echo !!each($users);//1

?>  

each的指针指向第一个键值对,并返回第一个数组元素,获取其键值对,并包装成新数组

list()的使用

list用来把数组用的值赋给一些变量,看下面例子:

<?php

    $a = ['2','abc','def'];
    list($var1,$var2) = $a;
    echo $var1.'<br/>';//2
    echo $var2;//abc

    $a = ['name'=>'trigkit4','age'=>22,'0'=>'boy'];
    //list只认识key为数字的索引
    list($var1,$var2) = $a;

    echo $var1;//boy

?>

注:list只认识key为数字的索引

数组元素的排序

反向排序:sort()、asort()和 ksort()都是正向排序,当然也有相对应的反向排序. 
实现反向:rsort()、arsort()和 krsort()。

array_unshift()函数将新元素添加到数组头,array_push()函数将每个新元素添加到数组 的末尾。
array_shift()删除数组头第一个元素,与其相反的函数是 array_pop(),删除并返回数组末 尾的一个元素。
array_rand()返回数组中的一个或多个键。

函数shuffle()将数组个元素进 行随机排序。
函数 array_reverse()给出一个原来数组的反向排序

数组的各类API的使用

count()和 sizeof()统计数组下标的个数 
array_count_values()统计数组内下标值的个数

<?php
    $numbers = array('100','2');
    sort($numbers,SORT_STRING);//按字符串排序,字符串只比较第一位大小
    print_r($numbers);//Array ( [0] => 100 [1] => 2 )

    $arr = array('trigkit4','banner','10');
    sort($arr,SORT_STRING);
    print_r($arr);//Array ( [0] => 10 [1] => banner [2] => trigkit4 )

    shuffle($arr);
    print_r($arr);//随机排序

    $array = array('a','b','c','d','0','1');
    array_reverse($array);
    print_r($array);//原数组的反向排序。 Array ( [0] => a [1] => b [2] => c [3] => d [4] => 0 [5] => 1 )


    //数组的拷贝
    $arr1  = array( '10' , 2);
    $arr2  =  &$arr1 ;
    $arr2 [] =  4 ;  // $arr2 被改变了,$arr1仍然是array('10', 3)
    print_r($arr2);//Array ( [0] => 10 [1] => 2 [2] => 4 )

    //asort的使用
    $arr3  = & $arr1 ;//现在arr1和arr3是一样的
    $arr3 [] =  '3' ;
    asort($arr3);//对数组进行排序并保留原始关系
    print_r($arr3);// Array ( [1] => 2 [2] => 3 [0] => 10 )

    //ksort的使用
    $fruits = array('c'=>'banana','a'=>'apple','d'=>'orange');
    ksort($fruits);
    print_r($fruits);//Array ( [a] => apple [c] => banana [d] => orange )

   //unshift的使用
    array_unshift($array,'z');//开头处添加一元素
    print_r($array);//Array ( [0] => z [1] => a [2] => b [3] => c [4] => d [5] => 0 [6] => 1 )  

    //current(pos)的使用
    echo current($array);//z;获取当前数组中的当前单元

    //next的使用
    echo next($array);//a;将数组中的内部指针向前移动一位

    //reset的使用
    echo reset($array);//z;将数组内部指针指向第一个单元

    //prev的使用
    echo next($array);//a;
    echo prev($array);//z;倒回一位

    //sizeof的使用
    echo sizeof($array);//7;统计数组元素的个数

    //array_count_values
    $num = array(10,20,30,10,20,1,0,10);//统计数组元素出现的次数
    print_r(array_count_values($num));//Array ( [10] => 3 [20] => 2 [30] => 1 [1] => 1 [0] => 1 ) 

?>    

current():每个数组都有一个内部指针指向他的当前单元,初始指向插入到数组中的第一个元素

for循环遍历

<?php
    $value = range(0,120,10);
    for($i=0;$i<count($value);$i++){
        print_r($value[$i].' ');//0 10 20 30 40 50 60 70 80 90 100 110 120 
    }
?>

数组的实例

array_pad函数的使用

<?php
    //array_pad函数,数组数组首尾选择性追加
    $num = array(1=>10,2=>20,3=>30);
    $num = array_pad($num,4,40);
    print_r($num);//Array ( [0] => 10 [1] => 20 [2] => 30 [3] => 40 )

    $num = array_pad($num,-5,50);//array_pad(array,size,value)
    print_r($num);//Array ( [0] => 50 [1] => 10 [2] => 20 [3] => 30 [4] => 40 ) 
?>

size:指定的长度。整数则填补到右侧,负数则填补到左侧。

unset()的使用

 <?php
    //unset()的使用
    $num = array_fill(0,5,rand(1,10));//rand(min,max)
    print_r($num);//Array ( [0] => 8 [1] => 8 [2] => 8 [3] => 8 [4] => 8 ) 
    echo '<br/>';

    unset($num[3]);
    print_r($num);//Array ( [0] => 8 [1] => 8 [2] => 8 [4] => 8 ) 
?>

array_fill()的使用

<?php
    //array_fill()的使用
    $num = range('a','e');
    $arrayFilled = array_fill(1,2,$num);//array_fill(start,number,value)
    echo '<pre>';

    print_r($arrayFilled);

?>

array_combine()的使用

<?PHP
    $number = array(1,2,3,4,5);
    $array = array("I","Am","A","PHP","er");
    $newArray = array_combine($number,$array);
    print_r($newArray);//Array ( [1] => I [2] => Am [3] => A [4] => PHP [5] => er ) 
?> 

array_splice()删除数组成员

<?php
    $color = array("red", "green", "blue", "yellow");
    count ($color); //得到4
    array_splice($color,1,1); //删除第二个元素
    print_r(count ($color)); //3
    echo $color[2]; //yellow
    echo $color[1]; //blue
?>  

array_unique删除数组中的重复值

<?php
    $color=array("red", "green", "blue", "yellow","blue","green");
    $result = array_unique($color);
    print_r($result);//Array ( [0] => red [1] => green [2] => blue [3] => yellow ) 
?> 

array_flip()交换数组的键值和值

<?PHP
    $array = array("red","blue","red","Black");
    print_r($array);
    echo "<br />";
    $array = array_flip($array);//
    print_r($array);//Array ( [red] => 2 [blue] => 1 [Black] => 3 ) 
?> 

array_search()搜索数值

<meta charset="utf-8">
<?php
   $array = array("red","blue","red","Black");
   $result=array_search("red",$array)//array_search(value,array,strict)
    if(($result === NULL)){
        echo "不存在数值red";
    }else{
        echo "存在数值 $result";//存在数值 0 
    }
?> 
2015年五月2日晚上 8:56:47 Linux 文件和文件夹的操作权限

由于 linux 是多用户操作系统,所以基于安全的考虑,需要具备保障个人隐私和系统安全的机制。因此在使用 linux 系统的时候,经常会出现权限的问题(比如: 删除文件、安装软件、运行应用等等),期初遇到这些问题的时候,大部分都使用sudo或者是sudo chmod 777 file(后面会讲解这个命令)来解决的。虽然这种方式可以解决问题,但是这样是不安全的,特别是在服务器上操作的时候,因为不是所有的文件和文件夹都可以被其它用户操作的,不是所有的用户都有root权限的,不是所有的应用都可以用root用户启动的。那么我们要如何正确的处理这些权限问题呢?那就让我们来学习一下 linux 权限相关的知识。

用户的权限

要确定一个用户对某个文件或文件夹是否具有相应的操作权限,先要明确该用户与文件或文件夹之间的关系。在 linux 系统中,定义了如下三种关系:

因为在 linux 下的文件和文件夹都有读取(r)写入(w)执行(x)的操作,所以上面描述的每种关系的用户分别都可以赋予这些操作权限。操作权限介绍:

权限 简写 对普通文件的作用 对文件夹的作用
读取 r 查看文件内容 列出文件夹中的文件(ls)
写入 w 修改文件内容 在文件夹中删除、添加或重命名文件(夹)
执行 x 文件可以作为程序执行 cd 到文件夹

文件或文件夹和用户的三种关系的基础操作权限

在 linux 使用ls -la命令可以查看文件夹内文件的属性,下面是我电脑上某个文件夹下文件的属性:

bash$ ls -la
drwxr-xr-x 14 root root     4096 Apr  3 18:47 .
drwxr-xr-x 23 root root     4096 Mar  2 05:48 ..
drwxr-xr-x  2 root root     4096 Apr  3 07:44 backups
drwxr-xr-x 17 root root     4096 Jul 22  2014 cache
drwxr-xr-x  2 root root     4096 Mar  2 04:26 docker-registry
lrwxrwxrwx  1 root root        9 Feb 25 13:31 lock -> /run/lock
drwxrwxr-x 15 root syslog   4096 Apr  3 07:44 log
-rw-r--r--  1 root root        0 Apr  3 18:47 test

特殊权限SUIDSGIDSticky

在 linux 系统中还有三种与用户身份无关的三个文件权限属性。即SUID、SGID和Sticky

修改文件或文件夹对应用户的操作权限

在 linux 系统中,可以使用chmod命令来修改文件或文件夹对应用户的操作权限,chmod命令也有两种方式修改,一种是使用代表相应操作权限的字母简写表示,另一种是使用代表相应操作权限的数字表示。

修改文件或文件夹的拥有者和所属的组

使用chown可以修改文件或文件夹的拥有者和所属的组。

创建组和用户

  了解 linux 用户操作权限,安全就掌握在手中。

参考

原文链接

2015年五月2日晚上 8:47:47 Fedora 21下Nvidia显卡的安装

最近由于工作和学习需要,把家用的两台电脑攒成了一台机器,用的是Fedora 21,安装过程比较傻瓜就不写了,因为显卡用的是比较搓的N卡,N卡的开源驱动nouveau又搓的要死,装了跟不装一事,所以装机后需要做的第一件事就是要安装N卡的官方驱动,过程不难但是背不下来,所以正好在这里记录一下,以后也好找。

简单来说:

这里GeForce GT730就是我这块网卡的型号

按照提示几个选项一路选下来,搜索得到的驱动里选择一个最新的,随便用什么工具下载下来

wget http://us.download.nvidia.com/XFree86/Linux-x86_64/346.59/NVIDIA-Linux-x86_64-346.59.run

到这里还不能直接安装驱动,下载下来的run文件在安装过程中会编译匹配我们当前系统版本的驱动出来。编译驱动需要用到kernel source,但如果是像我这样直接下了发行版来安装的话,默认是不包含kernel source的,所以我们需要安装对应当前系统版本的kernel-devel

sudo yum install gcc kernel-devel-$(uname -r) 

系统更新完成后,要重启新的kernel才会生效,不过没关系等等一起重启也可以,现在我们要做的是屏蔽nouveau驱动,直接

echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf

移除已经安装的开源驱动包

yum list | grep nouveau
yum remove xorg-x11-drv-nouveau.x86_64

设置默认启动进入字符界面

systemctl set-default multi-user.target

(效力等同于重启后在登录界面输入ctrl+alt+F2,这点还不熟悉的同学可以看看systemcl的几组user target的定义)

重启系统之后安装官方驱动

chmod u+x ./*.run
./NVIDIA-Linux-x86_64-346.59.run

跟着提示一路走下去即可,安装完成之后记得将启动级别改回到图形界面

systemctl set-default graphical.target

然后重启就可以了。

问题:
安装过程没遇到什么问题,有一点可以注意一下,如果你安装kernel-devel的时候没有指定uname -r,即当前版本,你更新到的kernel source会是最新版的,在编译官方驱动的时候会跟你抱怨找不到KDIR的。

2015年五月2日晚上 8:13:32 关于redis不同权限列表显示缓存问题-带分页

各位大神好,求助,由于对redis+mysql这种nosql+sql方式存储没有最佳实践,想求教下有这种经验的大神,最近用mysql+redis+nodejs做个大数据高并发东西,想要用redis缓存带分页列表信息减少mysql查询压力,当前端访问时候可以根据不同访问权限到redis提取数据,如果没有则从mysql查询.(比如:老师,学生,校长三个不同权限拉取数据不同,需要考虑数据更新,redis-mysql数据一致性问题).

2015年五月2日下午 3:07:27 T-SQL学习中--内联接,外连接,交叉连接

交叉连接可以表A和表B是同一张表取得笛卡尔乘积。
比如说下面这种写法:

SQLSELECT D.n AS theday, S.n AS shiftno  
FROM dbo.Nums AS D
  cross JOIN dbo.Nums AS S
WHERE D.n <= 7
  AND S.N <= 3
ORDER BY theday, shiftno;

当然也可以表A和表B是两张不同的表,取得笛卡尔乘积。

SQLSELECT D.n AS theday, S.empid AS shiftno  
FROM dbo.Nums AS D
  cross JOIN [HR].[Employees] AS S
WHERE D.n <= 7
  AND S.empid <= 3
ORDER BY theday, shiftno;

但是CROSS JOIN不能用ON条件,只能用WHERE条件。下面这句与上面的语句查询结果相同。

SQLSELECT D.n AS theday, S.empid AS shiftno  
FROM dbo.Nums AS D
  inner JOIN [HR].[Employees] AS S
on D.n <= 7
  AND S.empid <= 3
ORDER BY theday, shiftno;

内联接查询,表A和表B中的数据必须紧密对应,不可以是Null。下面的查询中,Production.Products表中没有商品记录的的日本供货商不会被列出来。INNER这个关键词是可以舍去的,如果只写JOIN就表示INNER JOIN

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  INNER JOIN Production.Products AS P
    ON S.supplierid = P.supplierid
WHERE S.country = N'Japan';

外连接查询有三种情况:左外连接,右外连接,全外连接。
下面这个查询与上面这个查询写法只差一点点(WHERE变成了AND),但是结果就有区别:

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice
FROM Production.Suppliers AS S
  INNER JOIN Production.Products AS P
    ON S.supplierid = P.supplierid
    AND S.country = N'Japan';

而且Production.Products表中没有商品记录的的日本供货商同样也会被列出来,但是相关的P.productid, P.productname, P.unitprice都会被记为NULL。
下面这句:

SQLSELECT E.empid,
  E.firstname + N' ' + E.lastname AS emp,
  M.firstname + N' ' + M.lastname AS mgr
FROM HR.Employees AS E
  INNER JOIN HR.Employees AS M
    ON E.mgrid = M.empid;

用了内联接,则最高主管(CEO)不会被列出来,因为最高主管没有更高的主管了。
而改用左外连接

SQLSELECT E.empid,
  E.firstname + N' ' + E.lastname AS emp,
  M.firstname + N' ' + M.lastname AS mgr
FROM HR.Employees AS E
  LEFT OUTER JOIN HR.Employees AS M
    ON E.mgrid = M.empid;

则CEO也会被列出来,CEO对应的mgr会被记为NULL。
套用内联接的左外连接:

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice,
  C.categoryname
FROM Production.Suppliers AS S
  LEFT OUTER JOIN Production.Products AS P
    ON S.supplierid = P.supplierid
  INNER JOIN Production.Categories AS C
    ON C.categoryid = P.categoryid
WHERE S.country = N'Japan';

查询出日本供货商的所有的产品以及产品类别名。而且Production.Products表中没有商品记录的的日本供货商同样也会被列出来,但是相关的P.productid, P.productname, P.unitprice, C.categoryname都会被记为NULL。
上面的语句与下面带括号的语句等同:

SQLSELECT
  S.companyname AS supplier, S.country,
  P.productid, P.productname, P.unitprice,
  C.categoryname
FROM Production.Suppliers AS S
  LEFT OUTER JOIN 
    (Production.Products AS P
       INNER JOIN Production.Categories AS C
         ON C.categoryid = P.categoryid)
    ON S.supplierid = P.supplierid
WHERE S.country = N'Japan';

RIGHT OUTER JOIN则与LEFT OUTER JOIN相反,根据ON条件和WHERE条件查询表A和表B,查询结果可以表A中数据为NULL。
FULL OUTER JOIN则只要表A和表B中任一表中有数据,结果都会被显示出来。无论是表A为NULL,还是表B为NULL。
OUTER也是可以被省略的。LEFT JOIN就是LEFT OUTER JOIN的简写,相应的,RIGHT JOINRIGHT OUTER JOIN的简写,FULL JOINFULL OUTER JOIN的简写。

2015年五月2日下午 2:22:12 T-SQL学习中--取得部分检索数据记录

SELECT TOP(n) FROM _TableName_ ORDER BY _ColumnName_是一种非标准SQL语句,从数据表中最多检索出排在前面的n条数据来,但是它可以用SELECT TOP(n) PERCENT FROM _TABLENAME_ ORDER BY 这样的根据总数据量来按比例取得数据记录。
如果数据表中有560条数据,检索SELECT TOP(1) FROM _TableName_ ORDER BY _ColumnName_就会检索出6条数据来,总而言之,不是按四舍五入计的,而是按ceil向上取整法计数的。
如果不加ORDER BY,数据会以不确定的顺序检索出来。
这里括号可有可无,但是建议加括号。
n可以是常数,也可以是定义的变量。下面这种写法也是可以的:

SQLDECLARE @n AS BIGINT = 5;
SELECT TOP (@n) orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC;
GO

如果加了WITH TIE,比如说写成

SQLSELECT TOP (3) WITH TIES orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC;

orderdate相同的数据会被计作一条数据,总检索出的结果可能不止3条。

OFFSET FETCH语句是标准SQL语句。但是它有局限性,不能按百分比检索出数据结果。

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY;

表示跳过前50条数据,取得第51到第75条数据。

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 0 ROWS FETCH FIRST 25 ROWS ONLY;

表示取得第1到第25条数据。

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET 50 ROWS;

表示跳过前50条数据,取得之后的全部数据。
OFFSET FETCH语句必须带有ORDER BY语句,但是如果不想指定用于排序的columnName,可以用下面这种这种语法,即用SELECT NULL作为排序列:

SQLSELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY (SELECT NULL)
OFFSET 0 ROWS FETCH FIRST 3 ROWS ONLY;

OFFSET FETCH可用于分页检索,比如说下面这种写法:

SQLDECLARE @pagesize AS BIGINT = 25, @pagenum AS BIGINT = 3;

SELECT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC, orderid DESC
OFFSET (@pagenum - 1) * @pagesize ROWS FETCH NEXT @pagesize ROWS ONLY;
2015年五月2日上午 11:09:04 GitLab 安装配置笔记

GitLab的安装方式

GitLab的两种安装方法:

由于公司只配备了一台阿里云服务器,并且没有分配任何的域名。该服务器上需要运行版本控制软件、bug管理软件、知识库等多套程序,只能采用ip的方式访问。原先采用GitLab+Apache+MySQL编译安装的方式,并且将GitLab配置为可通过xxx.xx.xxx.xx/gitlab的形式访问,由于bug管理软件(禅道)也运行于Apache之上,两套软件之间彼此有互斥的影响,找不到解决方法。同时,GitLab的注册需要邮箱验证,由于网上提供的配置方法都是基于域名的,在阿里云上多次进行配置都无法正常使用。

因此,只能放弃编译安装的方式,而采取rpm包的方式重新进行安装。

安装GitLab CE Omnibus包

  1. 在linux终端下,使用cat /etc/issue命令查询当前系统的发行版本,查询到阿里云所安装的linux版本为CentOS release 6.6 (Final)。

  2. 进入gitlab官方网站,选择对应的操作系统——CentOS 6 (and RedHat/Oracle/Scientific Linux 6),按照官方的提示进行安装:

    1. 安装配置必要的依赖

      在Centos 6 和 7 中,以下的命令将会打开HTTP和SSH在系统防火墙中的可访问权限。

      bashsudo yum install openssh-server
      
      sudo yum install postfix
      
      sudo yum install cronie
      
      sudo service postfix start
      
      sudo chkconfig postfix on
      
      sudo lokkit -s http -s ssh
      
      
    2. 下载Omnibus package包并安装

      bashcurl -O https://downloads-packages.s3.amazonaws.com/centos-6.6/gitlab-ce-7.10.0~omnibus.2-1.x86_64.rpm
      sudo rpm -i gitlab-ce-7.10.0~omnibus.2-1.x86_64.rpm
      
      Note:由于amazonaws的服务器被墙,下载这个包时可能需要翻墙下载。
      
    3. 配置并启动GitLab
      打开/etc/gitlab/gitlab.rb,将external_url = 'http://git.example.com'修改为自己的IP地址:http://xxx.xx.xxx.xx,,然后执行下面的命令,对GitLab进行编译。

      bashsudo gitlab-ctl reconfigure
      
    4. 登录GitLab

      Username: root 
      Password: 5iveL!fe
      

配置GitLab的默认发信邮箱

  1. GitLab中使用postfix进行邮件发送。因此,可以卸载系统中自带的sendmail
    使用yum list installed查看系统中是否存在sendmail,若存在,则使用yum remove sendmail指令进行卸载。
  2. 测试系统是否可以正常发送邮件。

    bashecho "Test mail from postfix" | mail -s "Test Postfix" xxx@xxx.com
    
    注:上面的xxx@xxx.com为你希望收到邮件的邮箱地址。
    

    当邮箱收到系统发送来的邮件时,将系统的地址复制下来,如:root@iZ23syflhhzZ.localdomain,打开/etc/gitlab/gitlab.rb,将

    # gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' 
    

    修改为

    gitlab_rails['gitlab_email_from'] = 'root@iZ23syflhhzZ.localdomain' 
    

    保存后,执行sudo gitlab-ctl reconfigure重新编译GitLab。如果邮箱的过滤功能较强,请添加系统的发件地址到邮箱的白名单中,防止邮件被过滤。

    Note:系统中邮件发送的日志可通过`tail /var/log/maillog`命令进行查看。
    

安装过程中出现的问题

  1. 在浏览器中访问GitLab出现502错误

    原因:内存不足。

    解决办法:检查系统的虚拟内存是否随机启动了,如果系统无虚拟内存,则增加虚拟内存,再重新启动系统。

  2. 80端口冲突

    原因:Nginx默认使用了80端口。

    解决办法:为了使Nginx与Apache能够共存,并且为了简化GitLab的URL地址,Nginx端口保持不变,修改Apache的端口为4040。这样就可以直接用使用ip访问Gitlab。而禅道则可以使用4040端口进行访问,像这样:xxx.xx.xxx.xx:4040/zentao。具体修改的地方在/etc/httpd/conf/httpd.conf这个文件中,找到Listen 80这一句并将之注释掉,在底下添加一句Listen 4040,保存后执行service httpd restart重启apache服务即可。

    #Listen 80 
    Listen 4040 
    
  3. 8080端口冲突

    原因:由于unicorn默认使用的是8080端口。

    解决办法:打开/etc/gitlab/gitlab.rb,打开# unicorn['port'] = 8080的注释,将8080修改为9090,保存后运行sudo gitlab-ctl reconfigure即可。

  4. STMP设置

    配置无效,暂时不知道原因。

  5. GitLab头像无法正常显示
    原因:gravatar被墙
    解决办法:
    编辑 /etc/gitlab/gitlab.rb,将

    #gitlab_rails['gravatar_plain_url'] = 'http://gravatar.duoshuo.com/avatar/%{hash}?s=%{size}&d=identicon'
    

    修改为:

    gitlab_rails['gravatar_plain_url'] = 'http://gravatar.duoshuo.com/avatar/%{hash}?s=%{size}&d=identicon'
    

    然后在命令行执行:

    bashsudo gitlab-ctl reconfigure 
    sudo gitlab-rake cache:clear RAILS_ENV=production
    

参考资料

GitLab 6.1 使用postfix发送email

Configure GitLab Omnibus installation alongside with Apache

解决Gitlab的Gravatar头像无法显示的问题

How To Set Up GitLab As Your Very Own Private GitHub Clone

2015年五月1日晚上 8:12:21 PHP 5.3 连接 Oracle 的客户端及 PDO_OCI 模块安装

php连接oracle数据库虽然不是最佳拍档,但组内开发确实有这样需求。如果没有参考合适的文档,这个过程还是挺折磨人的,下面是一个记录,原型是国外的一篇博客 Installing PDO_OCI and OCI8 PHP extensions on CentOS 6.4 64bit

假设你已经安装好php的环境,php版本为5.3,要连接的oracle服务器是 11g R2,操作系统版本CentOS 6.4 x86_64。如果没有安装php,可以通过以下命令安装:

# yum install php php-pdo
# yum install php-devel php-pear php-fpm php-gd php-ldap \
php-mbstring php-xml php-xmlrpc  php- zlib zlib-devel bc libaio glibc

假如web服务器使用apache。

1. 安装InstantClient

instantclient是oracle的连接数据库的简单客户端,不用安装一个500Moracle客户端就可以连接oracle数据库,有windows和linux版本。从 这里 选择需要的版本下载,只需Basic和Devel两个rpm包。

安装
# rpm -ivh oracle-instantclient11.2-basic-11.2.0.4.0-1.x86_64.rpm
# rpm -ivh oracle-instantclient11.2-devel-11.2.0.4.0-1.x86_64.rpm

软链接
# ln -s /usr/include/oracle/11.2/client64 /usr/include/oracle/11.2/client
# ln -s /usr/lib/oracle/11.2/client64 /usr/lib/oracle/11.2/client

64位系统需要创建32位的软链接,这里可能是一个遗留bug,不然后面编译会出问题。

接下来还要让系统能够找到oracle客户端的库文件,修改LD_LIBRARY_PATH:

# vi /etc/profile.d/oracle.sh
export ORACLE_HOME=/usr/lib/oracle/11.2/client64
export LD_LIBRARY_PATH=$ORACLE_HOME/lib

执行source /etc/profile.d/oracle.sh使环境变量生效。

2. 安装PDO_OCI

在连接互联网的情况下,通过pecl在线安装php的扩展非常简单,参考 How to install oracle instantclient and pdo_oci on ubuntu machine

https://pecl.php.net/package/PDO_OCI下载 PDO_OCI-1.0.tgz 源文件。

# wget https://pecl.php.net/get/PDO_OCI-1.0.tgz
# tar -xvf PDO_OCI-1.0.tgz
# cd PDO_OCI-1.0

由于PDO_OCI很久没有更新,所以下面需要编辑ODI_OCI-1.0文件夹里的config.m4文件来让它支持11g:

# 在第10行左右找到与下面类似的代码,添加这两行:
elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.11.2; then
  PDO_OCI_VERSION=11.2

# 在第101行左右添加这几行:
11.2)
  PHP_ADD_LIBRARY(clntsh, 1, PDO_OCI_SHARED_LIBADD)
  ;;

编译安装pdo_oci扩展:(安装完成后可在 /usr/lib64/php/modules/pdo_oci.so 找到这个模块)

$ phpize
$ ./configure --with-pdo-oci=instantclient,/usr,11.2
$ make
$ sudo make install

要启用这个扩展,在/etc/php.d/下新建一个pdo_oci.ini文件,内容:

extension=pdo_oci.so

验证安装成功:

# php -i|grep oci
看到类似下面的内容则安装成功:
/etc/php.d/pdo_oci.ini,
PDO drivers => oci, sqlite

或
# php -m

3. 安装OCI8

https://pecl.php.net/package/oci8 下载oci8-2.0.8.tgz源文件。

# wget https://pecl.php.net/get/oci8-2.0.8.tgz
# tar -xvf oci8-2.0.8.tgz
# cd oci8-2.0.8

编译安装oci8扩展:

# phpize
# ./configure --with-oci8=shared,instantclient,/usr/lib/oracle/11.2/client64/lib
# make
# make install

要启用这个扩展,在/etc/php.d/下新建一个oci8.ini文件,内容:

extension=oci8.so

验证安装成功:

# php -i|grep oci8
/etc/php.d/oci8.ini,
oci8
oci8.connection_class => no value => no value
oci8.default_prefetch => 100 => 100
oci8.events => Off => Off
oci8.max_persistent => -1 => -1
oci8.old_oci_close_semantics => Off => Off
oci8.persistent_timeout => -1 => -1
oci8.ping_interval => 60 => 60
oci8.privileged_connect => Off => Off
oci8.statement_cache_size => 20 => 20
OLDPWD => /usr/local/src/oci8-2.0.8
_SERVER["OLDPWD"] => /usr/local/src/oci8-2.0.8

最后别忘了重启逆web服务器如apache,可以通过phpinfo()来确保扩展是否成功安装。

4. 测试连接

在你web服务器如apache的php目录下创建testoci.php

<?php

$conn = oci_connect('username', 'password', '172.29.88.178/DBTEST');

$stid = oci_parse($conn, 'select table_name from user_tables');
oci_execute($stid);

echo "<table>\n";
while (($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) != false) {
    echo "<tr>\n";
    foreach ($row as $item) {
        echo "  <td>".($item !== null ? htmlentities($item, ENT_QUOTES) : "&nbsp;")."</td>\n";
    }
    echo "</tr>\n";
}
echo "</table>\n";

?>

访问这个页面就应该可以得到结果了。

参考


原文链接地址:http://seanlook.com/2015/03/10/install-pdo-oci-oci8-phpext/


2015年四月30日晚上 11:41:21 OpenWrt路由器开发

http://homeway.me

OpenWrt




0x01.About

第一次尝试开发路由器,发现并不是想象中那么难,和普通嵌入式开发一样,也是一块ARM板刷上Linux系统。

OpenWrt有很多好用的软件,附带流量监测。

OpenWrt主要开发语言为Python、Lua、Shell,还可以做深入研究写ipk软件包。

写了几个脚本,主要实现了openwrt下面GPIO控制、系统信息获取、wifi扫描器、定时发送邮件系统报警等功能,下面会介绍。

代码已经在Github开源: https://github.com/grasses/OpenWRT-Util



0x02.About OpenWrt

刷OpenWrt先要去https://downloads.openwrt.org/下载你想要的版本,包含aa型和bb型。

然后用Linux烧入命令烧入系统。

早MAC下面,先现将U盘插入电脑格式化,然后运行命令查看U盘编号:

diskUtil list

注意查看U盘编号,选择你的U盘,解除挂载:

diskUtil unmountDisk /dev/disk2

然后烧入系统:

dd if=/path/to/openwrt.img of=/dev/disk2 bs=2m

等待几分钟后烧入成功。

关于痛点:

第一次是在树莓派上安装OpenWrt,装好后,用有线把连进上级路由器的Lan口

然后,上级路由的包开始乱了,上级路由把OpenWrt当成路由器,OpenWrt把路由器当成上级路由器,然后就GG了。



0x03.About WRTnode

WRTnode是OpenWrt系统一个硬件解决方案,预先安装了OpenWrt相关软件包,并且内置两块无线网卡。

关于WRTnode,官方wiki已经介绍的很详细了:http://wiki.wrtnode.com/index.php?title=Main_Page/zh-cn

解析来的代码基本上是在WRTnode环境上开发的,主要包含了:

目前只能想起这3个,如果报错,该装什么再装好了。



0x04.WRTnode控制GPIO

GPIO控制可以很好地实现软件硬件之间的交互。

WRTnode GPIO

GPIO的控制也不难,wiki讲得很清晰了,就是文件输入输出http://wiki.wrtnode.com/index.php?title=The_user_space_gpio_calls/zh-c...

这里我写了一个Lua版的GPIO控制模块,文件保存为gpio.lua:

#!/usr/bin/lua
--[[
Copyright 2015 http://homeway.me
@author homeway
@version 15.04.29
@link http://homeway.me
@function OpenWRT gpio module
-- ]]--

local M = {}
M.id = ""
M.path = "/sys/class/gpio/gpio"
M.router = "/sys/class/gpio"

M.check = function(where)
    print("check path => "..where)
    local f=io.open(where, "r")
    if f~=nil then io.close(f) return true else return false end
end
-- set mode && check type
M.mode = function(id, mtype)
    M.id = id
    where = M.path..M.id
    -- if id not use
    if false==M.check(M.path..id..'/direction') then
        --M.writeFile(M.router.."/unexport",id)
        M.writeFile(M.router.."/export", id)
    end
    -- if type different 
    if mtype ~= M.readFile(M.path..id..'/direction') then
        print("type =>"..mtype.." direction=>"..M.readFile(M.path..id..'/direction').." different")
        M.writeFile(M.path..id..'/direction', mtype)
    end
end
-- file write
M.writeFile = function(where, what)
    print("write path => "..where.." data =>"..what)
    local fp=io.open(where, 'w')
    fp:write(what)
    fp:close()  
end
-- file read
M.readFile = function(where)
    print("read path => "..where)
    local fp=io.open(where, 'r')
    if fp~=nil then
        data = fp:read("*all")
        fp:close()
        return data
    end
    return nil
end
M.set = function(id)
    M.id = id
end
M.read = function()
    res = M.readFile(M.path..M.id..'/value')
    return res
end
M.write = function(value)
    res = M.writeFile(M.path..M.id..'/value', value)
end
M.close = function()
    print("sleep io => "..M.id)
    os.execute("sleep " .. tonumber(M.id))
end

return M

API很简单,先设置设置模式,GPIO.mode(id, "out/in")两种模式之一

如果为'out'即可调用GPIO.write(value)函数,写入当然id端口,如果为'in'模式,只能调用GPIO.read()读取数值。

这里数值只能是0或1,非0即为1.

调用方式如下,这个存在一个可忽略的问题,一旦调用mode,数值将被置为默认数值,即0:

#!/usr/bin/lua
x=require("gpio")
print("Please input io id =>")
id = io.read("*num")
x.mode(id, "out")-- 设置io的模式为输入还是输出 [in/out]
function readGPIO(id)
    value = x.read()
    print("read data from => `"..id.."` =>"..value)
end
function writeGPIO(id, data)
    x.write(data)
    print("write data to => `"..id.."` =>"..data)
end

count=1
repeat
    count=count+1
    print("Please input value =>")
    data = io.read("*num")
    writeGPIO(id, data)
    readGPIO(id)
until count>3



0x05.WRTnode获取系统信息

其实获取系统信息不属于WRTnode范围,因为这部分主要是调用Linux Shell获取系统信息,做个反馈。

这里我也写了个python脚本,主要检查系统信息,这个脚本在树莓派那里面也有:http://homeway.me/2014/10/09/raspberry-the-current-status-and-data/

这里我做了部分修改,添加系统ip、连接的ssid等信息:

#!/usr/bin/python
'''
    @author homeway
    @version 15.04.29
    @link http://homeway.me
    @function python get OpenWRT system info
'''
import os
# Return CPU temperature as a character string                                     
def getCPUtemperature():
    res = os.popen('vcgencmd measure_temp').readline()
    return(res.replace("temp=","").replace("'C\n",""))
# Return RAM information (unit=kb) in a list                                      
# Index 0: total RAM                                                              
# Index 1: used RAM                                                                
# Index 2: free RAM                                                                
def getRAMinfo():
    p = os.popen('free')
    i = 0
    while 1:
        i = i + 1
        line = p.readline()
        if i==2:
            return(line.split()[1:4])
# Return % of CPU used by user as a character string                               
def getCPUuse():
    return(str(os.popen("top -n1 | awk '/Cpu\(s\):/ {print $2}'").readline().strip()))

# Return information about disk space as a list (unit included)                    
# Index 0: total disk space                                                        
# Index 1: used disk space                                                        
# Index 2: remaining disk space                                                    
# Index 3: percentage of disk used                                                 
def getDiskSpace():
    p = os.popen("df -h /")
    i = 0
    while 1:
        i = i +1
        line = p.readline()
        if i==2:
            return(line.split()[1:5])
def getSystem():
    p = os.popen("uname -amnrspv")
    while 1:
        line = p.readline()
        return(line)
def getExtranetIp():
    p = os.popen('wget "http://www.ip138.com/ips1388.asp" -q -O - | sed -nr \'s/.*\[(([0-9]+\.){3}[0-9]+)\].*/\1/p\'')
    while 1:
        line = p.readline()
        print line
        return(line)
def getIntranetIp():
    p = os.popen('ifconfig apcli0 | grep inet\ addr')
    while 1:
        line = p.readline()
        return(line)
def getSsid():
    p = os.popen('uci get wireless.@wifi-iface[0].ApCliSsid')
    while 1:
        line = p.readline()
        return(line)
# CPU informatiom
CPU_temp = getCPUtemperature()
CPU_usage = getCPUuse()
# RAM information
# Output is in kb, here I convert it in Mb for readability
RAM_stats = getRAMinfo()
RAM_total = round(int(RAM_stats[0]) / 1000,1)
RAM_used = round(int(RAM_stats[1]) / 1000,1)
RAM_free = round(int(RAM_stats[2]) / 1000,1)
# Disk information
DISK_stats = getDiskSpace()
DISK_total = DISK_stats[0]
DISK_used = DISK_stats[1]
DISK_perc = DISK_stats[3]
# system info
SYSTEM_info = getSystem()
# NET infomation
NET_extranet_ip = getExtranetIp()
NET_internet_ip = getIntranetIp().lstrip('')
NET_connect_ssid = getSsid()

if __name__ == '__main__':
    print('-------------------------------------------')
    print("System info ="+str(SYSTEM_info))
    print('-------------------------------------------')
    print('RAM Total = '+str(RAM_total)+' MB')
    print('RAM Used = '+str(RAM_used)+' MB')
    print('RAM Free = '+str(RAM_free)+' MB')
    print('-------------------------------------------')
    print('DISK Total Space = '+str(DISK_total)+'B')
    print('DISK Used Space = '+str(DISK_used)+'B')
    print('DISK Used Percentage = '+str(DISK_perc))
    print('-------------------------------------------')
    print('NET Extranet Ip ='+str(NET_extranet_ip))
    print('NET Connect Ssid ='+str(NET_connect_ssid))
    print('NET Internet Wan Ip ='+str(NET_internet_ip))

直接调用python sysinfo.py:

系统信息



0x06.WRTnode发送邮件

好了,系统信息有了,GPIO信息有了,接下来就试试发送邮件了。

发送邮件3中法案都可以,Lua,Python,Shell,找了找资料,Python写了,但是缺少了一个包,Lua缺少Luasocket模块,Shell要安装模块。

最后,懵了,全都要依赖,尼玛,看了看,好像Lua安装个Luasocket最简单,一个包轻松: http://see.sl088.com/wiki/Luasocket

安装也不难,接下来就写写吧。

Lua发送邮件源码模块,设置文件名为email.lua

#!/usr/bin/lua
--[[
Copyright 2015 http://homeway.me
@author homeway
@version 15.04.29
@link http://homeway.me
@function lua email module
-- ]]--
local smtp = require("socket.smtp")
local M ={}
M.user = {["from"]="", ["to"]="", ["password"]=""}
M.mail = {["subject"]="", ["body"]=""}
M.sys = {["server"]=""}
M.set = function(data)
    M.user = data.user
    M.mail = data.mail
    M.sys = data.sys    
end
M.send = function()
    rcpt = {
        M.user["to"]
    }
    mesgt = {
        headers = {
            from = M.user["from"],
            to = M.user["to"], --收件人
            cc = "", --抄送 
            subject = M.mail["subject"] --主题
        },
        body = M.mail["body"]
    }
    r, e = smtp.send{
        from = M.user["from"],
        rcpt = rcpt,
        source = smtp.message(mesgt),
        server = M.sys["server"],
        port = M.sys["port"],
        user = M.user["from"],
        password = M.user["password"],
    }
    if not r then
        print(e)
    else
        print("send ok!")
    end
end
return M

下面是调用方式:

#!/usr/bin/lua
local mail = require("email")
local data = {}
data.user = {["from"]="sender@gmail.com", ["to"]="receiver@gmail.com", ["password"]="password"}
data.mail = {["subject"]="测试邮件模块", ["body"]="这是主体内容..."}
data.sys = {["server"]="smtp.gmail.com", ["port"]=587}

mail.set(data)
mail.send()

测试下,是可以接收到邮件的,注意GFW,还是别用非法gmail好了,别等半天收不到。



0x07.重要的东西放后面

嗯!看到这里,估计菊花也有点疼了,再看最后一点看完就擦洗擦洗去吧。

最后就是,设置定时器,让路由器定时发送系统信息给指定邮箱。

嗯...定时器,Linux的一个模块crontab命令,看看功能吧 crontab --help

关于定时器语法,看看这里吧 http://kvz.io/blog/2007/07/29/schedule-tasks-on-linux-using-crontab/

这里,我只做简单地,每隔10分钟发送一次系统信息给我邮箱。

具体怎么做,去下载这个脚本吧:https://github.com/grasses/OpenWRT-Util/blob/master/lua/crontab.lua

我的目录是这样的,用户是root:

~|--script
    |--schedule
    |--send
|--log
    |--sys.log
    |--crontab.log

先开一个定时器,定时跑Lua,Lua调用python读取系统信息,生成日志文件,Lua读取日志文件,发送邮箱。

how to use:
step1: configure you email information in this script
step2: mkdir /root/log && mkdir /root/script
step3: mv /path/to/crontab.lua /root/script/send
step4: chmod +x /root/script/send
step5: echo 10,20,30,40,50 * * * * /root/script/send > /root/script/schedule
step6: crontab /root/script/schedule

东西有点多,都是散乱的部件,这篇主要介绍细节信息,接下来会做大得模块。

如果打通路由器,各种嵌入式开发的联网问题就都解决了,所以路由器系统还是很重要的。




本文出自 夏日小草,转载请注明出处: http://homeway.me/2015/04/29/openwrt-develop-base-util/

by 小草

2015-04-30 23:59:20

2015年四月30日晚上 9:56:57 VSCode 初体验

Microsoft 今天在其 Build 开发者大会上正式宣布了 Visual Studio Code 项目:一个运行于 OS X,Windows 和 Linux 之上的,针对于编写现代web和云应用的跨平台编辑器。

作为编辑器控的我,得知消息后立马下载体验了一下。Windows上优秀的编辑器实在太多了,Sublime TextEditPlusNotepad++......还有诸如国产的EverEdit等后起之秀。所以这次我这次把测评的环境放在了编辑器相对匮乏的Linux桌面上。

环境&安装

主要对比对象是Sublime Text3

    wget http://download.microsoft.com/download/0/D/5/0D57186C-834B-463A-AECB-BC55A8E466AE/VSCode-linux-x64.zip

    //注意不要使用归档解压会报错
    unzip  unzip VSCode-linux-x64 -d VS

    //双击VS里的Code就能运行了

颜值

VSCode

可以看到VSCode颜值不算太糟糕,绿色的注释散发着一股浓浓的VS的风格,Theme里一共两款主题可以选择,另外一款是白色主题。题外话,我最喜欢的主题是Sublime Text的Monokai

性能

总体来说输入的体验比Sublime Text3稍微要差一点,但是比同类WEB IDE ATOMBrackets要快太多,ATOM、Brackets已经迭代很多个版本了,VSCode基于ATOM SHELL的,估计ATOM要哭晕在厕所。看到一些网友的测试,在打开大文件上,VSCode已经秒杀了Sublime Text3

特性

智能提示

VSCode提供了强大的自动补全、悬浮提示、定义跳转等功能,支持以下语言:

C++, jade, PHP, Python, XML, Batch, F#, DockerFile, Coffee Script, Java, HandleBars, R,Objective-C, PowerShell, Luna, Visual Basic, Markdown

我测试了下在Javascript、Typscript上体验不错,HTML还支持Angular标签,悬浮提示很详细包括了注解,但是试了下C#貌似没有什么效果,不知道是不是需要特殊的环境。不管怎样,在某些语言上的智能提示已经比其他的同类编辑器已经强太多了,可以和一些IDE媲美。

enter image description here

下面贴几张官网的示例图片:

参数提示:
enter image description here

定义跳转:
enter image description here

引用提示:
enter image description here

方法定位:
enter image description here

还有其他很酷炫的功能我没测试,大家官网看吧。

Markdown

在Linux桌面上,好用的Markdown编辑器可以说没有,ReText和记事本一样简陋,Sublime Text3虽然可以装插件支持,但是体验不是很好,不支持中文。因此我一直使用的在线Markdown代替。

这回VSCode支持Markdown重新让我看到了点希望。快捷键ctr+shift+v预览,可以看到这个布局还是非常人性化的。

enter image description here

但是缺点也很明显,首先中文支持不好,编辑器里的中文输入可以改,但是预览还是出现口口,目前找到解决方法。还有不支持快捷键输入,那种像写代码般的快感没有了。没有能自定义CSS的功能,不管在哪种Theme下,> 代码高亮都看不出有什么效果。

版本控制

自带了一个git工具,并且放在了一个比较显要的位置上,不过功能不是很全,只能commit等几个操作。自带了类似于git diff的文件比较功能:

enter image description here

Debug

Debug需要MONO,所以就没进行测试。详情大家看官网吧。

缺陷

中文支持

默认的字体是不支持中文的,输入中文的时候会出现口口。需要设置一下字体,我使用的是文泉驿,思源也行。

没安装的首先安装这个字体。

sudo apt-get install fonts-wqy-microhei fonts-wqy-zenhei
File -> Preference -> User Settings
//在右侧添加一句:
"editor.fontFamily": "WenQuanYi Micro Hei Mono"

不过这只能解决编辑器内的中文乱码问题,其他的比如标题栏,markdown预览,该口的还是口。对了还有一点需要注意的是输入法需要是Fctix或者基于Fctix的。

Sublime Text3同样有这问题,事实上Sublime Text3全平台对于中文的支持都不是很好。Linux桌面上的解决方法也是奇技淫巧

插件化

不过插件化已经提到议程上了,以微软的实力实现这个不难。

Markdown

缺陷在上面已经提到了

设置

用户设置是直接以JSON形式出现了,虽然说鼠标悬浮上去会看到详细的解释,但还是没有图形化来的简便,而且没有搜索的功能,想要搜索还得以文本的形式复制出来,修改起来略费劲。

结论

总体而言,VSCode表现出来的潜力还是不俗的,毕竟还是个预览版,我对接下来的版本比较看好,至少比Brackets要好吧。希望Sublime Text的作者能够更加上心一点,能解决中文问题那就最好了,喜欢Sublime Text3的童鞋们可以看我这篇博文《我的Sublime Text3设置》

最后,人生苦短,我用geany

参考

https://code.visualstudio.com/Docs
http://www.zhihu.com/question/29984607

2015年四月30日下午 3:54:11 45个必备的JavaScript Web开发工具

JavaScript是一种灵活多变的脚本语言,它在全世界被广泛的应用在Web页面和服务器应用中。你可以因为任何目的去使用它,但是你需要一些工具。幸运的是,为了完成独特的任务,无以计数的JavaScript工具已经被开发者发布。

这里有45个关于JavaScript的工具,所有这些工具将帮助您创建现代网站与用户所期望的所有特性。它们都提供了精简的设计和简单的接口。。。。

AngularJS

AngularJS
Google创建AngularJS,目的是提供一个稳定的、轻量级的框架在浏览器中呈现信息。它从服务器收集数据,然后在本地编译模板。换句话说,AngularJS以MVC框架形式来构建在浏览器中运行的HTML、JavaScript和CSS。

Odyssey.JS

Odyssey.JS
Odyssey 是一个将故事和地图结合,并绑定了交互文本的工具。图片显示为一个沙箱来构建与地图交互的故事。

PlayCanvas

PlayCanvas
PlayCanvas是一个围绕WebGL建立的游戏引擎。它把物理、照明、阴影、音频和更多其它特效结合到更一致的工具中,以创建被对象填充的世界。图像显示的是一个针对该框架的在线开发工具。

Gantt

Gantt
Gantt是一个基于JQuery构建的JavaScript组件,用于创建图标,任务树和用JSON格式输出结果数据的相关性。它提供了编辑、缩放、数据快捷键,CSS皮肤,等等。

Handy.JS

Handy.JS
Handy是一个Nodejs的Web应用模板。Handy提供了一个Web APP所有的基础功能,因此你可以把焦点放在开发让你的APP真正唯一的功能。

RegExr

RegExr
RegExr是一个在线编辑和测试正则表达式的工具。它提供了一个简单的正则表达式输入界面,并且能实时可视化匹配可编辑的源文本。同时它还提供了一个便捷的RegExp边栏用于描述案例用法。

TimelineJS

TimelineJS
TimelineJS是一个开源工具,允许任何人建立形象精美的时间轴。初学者可以可以不使用任何东西就能创建一个时间轴。

Responsive Nav

Responsive Nav
Responsive Nav是一个比较小的JavaScript插件,可以帮助你创建针对小屏幕的连续导航。它会利用touch事件和CSS3过渡带来最好的性能。

Sinon.JS

Sinon.JS
Sinon.JS是一个单独的测试应用,没有依赖关系,适用于任何单元测试框架。

Mocha

Mocha
Mocha是一个运行在Nodejs和浏览器上的功能多样的JavaScript测试框架,使异步测试变得简单有趣。

JS Bin

JS Bin
JS Bin是一个专门设计用于帮助JavaScript和CSS民间测试的代码片段,在某些上下文中,协作和调试代码的应用。jsbin允许编辑和测试JavaScript和HTML。

JSLitmus

JSLitmus
JSLitmus,一个轻量级框架,用于创建特别的JavaScript基准测试。

Bookmarkify

Bookmarkify
Bookmarkify使得创建书签工具变得非常简单,仅需要给书签命名,然后输入JavaScript并包含它就可以了。

Kreate.JS

Kreate.JS
Kreate.JS能够辅助JQuery快速以JQuery对象形式生成DOM元素。你可以“Kreate” 单个元素或者“Kreate”多个元素,直到浏览器奔溃。但多数情况下,Kreate创建单个元素或者多个元素都会比JQuery快。

YUI Compressor

YUI Compressor
YUI Compressor是用Java创建的命令行工具,用于压缩JavaScript文件。YUI Compressor是100%安全的,并且比其他工具的压缩比高。它也能压缩CSS文件。

Google Closure Compiler

Google Closure Compiler
Google Closure Compiler能使JavaScript的下载和运行变得更快。它是一个真正针对JavaScript编译的。Google Closure Compiler不是将源语言编译成机器代码,而是从JavaScript编译到更好的JavaScript。

JSMin

JSMin
JSMin会删除JavaScript文件中的注释和不必要的空白。它将减少文件一半的尺寸,带来更快的下载速度。它也鼓励更富有表现力的编程风格,因为它消除了下载在精简代码、自文档化方面的成本。

Packer

Packer
Packer是DeanEdwards创建的一个很流行的JavaScript压缩工具,它能自动创建一个压缩版本。只需要粘贴代码,然后点击 ‘Pack’ 按钮。它还能利用JavaScript运行时片进行超常规压缩和动态压缩。

Meteor

Meteor
MeteorWebApp框架为现代软件开发提供了一个坚实的基础。一些是很实用的,例如拥抱开源社区,促进插件的贡献。Meteor做到了。

Epoch

Epoch
Epoch是一个实时的、用于创建漂亮、平稳流畅和高性能可视化的图表库。
Web Starter Kit
Web Starter Kit
Web Starter Kit是一个致力于协助开发者支持多设备的项目。这意味着通过同步点击、必要时重新加载和保持一切尽可能精简来确保屏幕保持同步。

Reveal.JS

Reveal.JS
Reveal.JS是一个基于HTML5的、很灵活的组件,用于替代PPT。点击按钮,然后复杂的动画会依赖碎片信息而翻转,就跟PPT一样。但是它真正的表现力在与你如何你在你的网络策略中使用它。

RxJS

RxJS
RxJS是一个为鼠标和键盘添加平滑、反应性的和异步响应生成的事件流。图像显示代码绑定了一个搜索维基百科的事件。

NodeBB

NodeBB
基于节点演化的公告板隐喻是及时和可定制的,并提供实时流的对话。NodeBB的发展已经添加了更多现代主题,并支持小屏幕的手机和平板。

Gulp.JS

Gulp.JS
Gulp.JS是一个流构建系统。它使用流和代码配置创建更简单和直观的构建。宁愿选择代码配置,让简单的事情变得简单,使复杂的任务易于管理。

Contour

Contour
Contour是Forio的一个可视化库,用于提供一组核心的公共可视化功能。建立在受欢迎的D3引擎之上,轮廓让你轻松创建数据可视化和基于常用的图表等直观的抽象。

Nightwatch.JS

Nightwatch.JS
对基于浏览器的APP和网站,Nightwatch.JS能使用Node.js建立基于端到端的测试解决方案。它使用强大的Selenium WebDriver API在DOM元素上执行命令和断言。

EasyStar.JS

EasyStar.JS
EasyStar.JS是一个用JavaScript编写的异步A*寻路API,可应用在HTML5游戏和互动项目。这个项目的目标是使它容易和快速实现性能意识上的寻路。

Headroom.JS

Headroom.JS
Headroom.JS是一个轻量级、高性能javascript小部件,允许你对用户的滚动做出反应。这个网站的头部就是一个实例,当向下滚动时,头部会滑出视窗,向上滚动时又滑入视窗。

FileAPI

FileAPI
FileAPI是一组处理所有跟文件相关的工作的组件库。它提供了许多功能,文件上传(单个/多个)、拖放支持、图像裁剪、大小调整、应用过滤器和获取文件信息等等。

Unminify

Unminify
Unminify对于格式化JavaScript、CSS和HTML代码是很有用的工具,并且会让代码变得易读和漂亮。

HarpJS

HarpJS
HarpJS是一个静态服务器,在没有任何配置的情况下,也为Jade, Markdown, EJS, Less, Stylus, Sass, CoffeeScript asHTML, CSS和JavaScript 提供服务。它支持爱心式的布局/部分模式,并能灵活的遍历文件系统元数据、全局对象和注入定制数据模板。

JSHint

JSHint
JSHint是一个社区驱动的工具,用于检测JavaScript中的语法错误和潜在的问题,并执行你的团队的编码惯例。

GruntJS

GruntJS
GruntJS是一个基于任务的命令行JavaScript项目构建工具。下面的预定义的任务,可以直接在你的项目中使用:连接文件、用JSHint验证文件、用UglifyJS压缩文件和用节点单元运行单元测试。

ZeptoBuilder

ZeptoBuilder
ZeptoBuilder是Zepto的一个在线版本,从列表中选取你想包含的文件,就能得到你自定义的构建了。

Gif.JS

Gif.JS
Gif.JS是一个能运行在你的浏览器中的JavaScript GIF编码器。

Favico.JS

Favico.JS
Favico.JS可以让你为你的图标添加动画徽章,图片,甚至视频,或者从图像、视频,甚至从访问者的摄像头获取的现场图片创建一个图标。

Chart.JS

Chart.JS
Chart.JS生成简单,干净,和基于HTML5的JavaScript图表。它用一种简单的方式,能在你的网站上自由的包含动画、交互式图形。

AdminJS

AdminJS
AdminJS是一个独立包含Ember.js的应用,它的两个主要文件是adminjs.js和 adminjs.css。两者都需要和Ember.js和EPF.一起被包含在页面中。

Sir Trevor

Sir Trevor
[]Sir Trevor](http://madebymany.github.io/sir-trevor-js/)是一个会完全重绘网页内容的工具:直观的编辑网页内容而不用假定任何关于它是如何重绘的事。

Instano.JS

Instano.JS
页面加载之后,Instano.JS允许你及时检测JavaScript是否可用。它修改了标准的标记以致于不管JavaScript什么时候被禁用,里面的消息都能被显示。

Resumable.JS

Resumable.JS
Resumable.JS是一个JavaScript库,通过HTML5 API提供了稳定可恢复的多文件上传功能。


英文原文:40+ essential JavaScript tools for the Web
译文出处:http://www.ido321.com/1543.html

2015年四月30日下午 2:35:43 CentOS 安装 Subversion

安装依赖

命令:yum install mod_dav_svn subversion

貌似只要安装mod_dav_svn时,就会把subversion和Apache安装上。

Subversion's Apache 配置

命令如下:

[root@lucifer ~] cd /etc/httpd/conf.d/
[root@lucifer ~] vim subversion.conf

# 有需要的话,请确定你删除这两行的注释
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so

# 加入下列内容来支持基本验证,并将 Apache 指向实际放置版本库的地方。
<Location /repos>
        DAV svn
        SVNPath /var/www/svn/repos
        AuthType Basic
        AuthName "Subversion repos"
        AuthUserFile /etc/svn-auth-conf
        Require valid-user
</Location>

上面的位置是 Apache 在 URL 上使用的。举个例说:http://yourmachine/repos 指向你所指定的 SVNPath。上面只是一个样例,因此请按你的首选放置东西。请确定你在完成编辑后存储文件。

然后我们须要创建你在上一步所指定的口令档。开始时你要利用 -cm 这个选项。它会创建文件并用 MD5 将口令加密。如果你需要加用户,请确定你只使用 -m 选项,而不包含初次创建时的 -c。

设置你的版本库

你接著要做的事情就是创建你用来提交及取出文件的版本库。利用 svn 所包含的工具,这是很容易的。

[root@lucifer ~] cd /var/www/ —— 或者你在上面所指定的路径
[root@lucifer ~] mkdir svn
[root@lucifer ~] cd svn
[root@lucifer ~] svnadmin create repos
[root@lucifer ~] chown -R apache.apache repos  (这步很重要)
[root@lucifer ~] service httpd restart

现在去测试你能否通过网页浏览器访问你的版本库:http://yourmahcine/repos 。你应该取得一个对话框询问用户名称及口令。若然是这样,请输入你的凭证,然后你应该看见一版 Revision 0:/ 的页面。这样的话,版本库的设置便大工告成了。如果你须要多个版本库,请参考上面连结内的文档。这里只示范如何设置一个版本库及开始应用它。话说回来,让我们就这样做。

参考

英文原文:http://wiki.centos.org/HowTos/Subversion
中文翻译:http://wiki.centos.org/zh/HowTos/Subversion
CentOS搭建Nginx+Subversion环境:http://www.opstool.com/article/282
CentOS Linux搭建SVN Server配置详解:http://www.ha97.com/4467.html

2015年四月30日下午 2:20:27 美国大数据创业公司总结

最近调研了一下美国的大数据创业公司,总结如下,如有疏漏,欢迎反馈指正(boyang798@gmail.com)。

公司 成立时间 技术亮点 IPO或者收购
hortonworks.com June, 2011 三大主要Hadoop平台提供商之一, 提供Windows平台Hadoop支持 IPO,Dec 11, 2014
cloudera.com October, 2008 三大主要Hadoop平台提供商之一, 用户基数最大的Hadoop平台
mapr.com July, 2009 三大主要Hadoop平台提供商之一, 实现自己的Linux文件系统来提升Hadoop速度
databricks.com September,2013 创立Apache Spark,提升Hadoop速度10倍,同时提供优于MapReduce的编程模型
datameer.com September, 2009 提供端到端(从数据收集到数据可视化)的一站式大数据分析平台
palantir.com January, 2004 自有技术,着重于非机构化数据深度分析,初期以政府客户为主,后扩展到银行和金融领域
splunk.com October, 2003 大规模机器数据(日志)收集,存储,可视化分析 IPO,Apr 19, 2012
vertica.com May, 2005 基于列存储的数据库技术,提升数据仓库查询速度,注重MPP(massively parallel processing),企业级Hadoop方案和SQL on Hadoop 被Hewlett-Packard收购,February 14, 2011
autonomy.com January, 1996 自有非Hadoop大数据技术,非主流技术,但是比较有特色 被Hewlett-Packard收购,August 18, 2011,但是被业界认为是HP的一个失败收购案例
teradata.com July, 1979 老牌传统数据仓库提供商,扩展业务到Hadoop平台 December 1991被NCR收购,之后又由NCR公司剥离,作为单独的上市公司,Oct 5, 2007
jaspersoft.com June, 2001 侧重于商务数据分析报表,提供移动端的报表工具 被TIBCO Software收购,April 28, 2014
karmasphere.com April, 2010 基于Hadoop的解决方案和数据可视化分析 被FICO收购,April 2014
domo.com October, 2010 提供数据分析云服务平台
talend.com September, 2005 提供多种数据集成服务
qubole.com December, 2014 提供Hadoop云平台服务
treasuredata.com December, 2011 提供大数据存储,查询,分析云服务
platfora.com June, 2011 端到端一站式大数据平台解决方案,基于Hadoop和Spark
interana.com January, 2013 自服务数据分析平台,侧重于面向事件的数据
gridgain.com May, 2005 基于内存的大数据实时处理系统
metamarkets.com May, 2010 在线广告领域内数据实时处理分析平台
pivotal.io April, 2013 大数据集成产品,提供Hadoop,内存Non-SQL数据库,RabbitMQ,以及Greenplum MPP(massively parallel processing)等多种服务
fiscalnote.com April, 2013 使用大数据和人工智能技术预测立法机构的投票结果
dato.com May, 2013 专注于机器学习的数据处理平台,非hadoop技术,底层用C++实现,从GraphLab(graph based framework)发展而来

除了以上大数据公司外,还有很多各具特色的公司,比如专门提供Non-SQL数据库的公司:

Non-SQL数据库 公司
Cassandra datastax.com
MongoDB mongodb.com
Couchbase couchbase.com
FoundationDB foundationdb.com

其它还有很多提供商务数据分析,可视化报表,大数据平台的公司,就不详细例举了,包括:Tableau, GoodData, ZoomData, SpagoBI, Pentaho, Eclipse BIRT, birst, netezza, paraccel, Ayasdi, Trifecta, Clearstory, Alpine Data Labs, Altiscale, Trifacta, Splice Machine, DataTorrent, Continuuity, Xplenty, Aerospike, snowflake.net, SumAll, Tamr, wibidata


从对美国大数据市场的调研来看,我们可以得到一些启示:

  1. 美国的数据分析市场非常大,容纳超过30家公司,这得益于美国信息化的高度发达。

  2. 虽然有很多公司,但是大家很少有重复竞争,每一家都有自己的特色,在自己的领域内发展,这也符合美国公司注重差异化相关。

  3. 传统的商务数据分析公司在维持旧有客户和平台的情况下,在积极向大数据技术扩展。

  4. 新兴的大数据技术发展非常快,但是目前还没有到成熟阶段,除了Hadoop之外,没有其他统一的技术被各家公司采用。

  5. 新的大数据技术趋势是快速响应,开始追求数据的实时处理和快速查询。


相对于美国市场,中国的大数据市场还处于非常初期的阶段,这可能跟中国的信息化程度相关。做长期展望预测,如果中国的信息化发展到美国的阶段,并且公司普遍采用基于数据的量化决策机制,将会迎来一个大数据发展的爆发式增长。

扫描微信二维码联系作者
扫描微信二维码联系作者

2015年四月30日上午 10:49:50 提问的智慧

案例

我想要一个XX的完美实现,各位大神谁能说下怎样实现?

PS:我看到这个问题的内容,冲动的就想把它删了,根本都不会去考虑怎么回答。你这是问题吗?


以下省略1大段描述这里有个截图显示的是一些code,请问大家这样的错误是怎么回事?

PS:X。。。你这是想害死人的节奏啊,问题中的code用截图,是你省事了还是想害死给你解答问题的人?我们连在其他地方try一下的机会都没有了,除非按照截图一个字母一个字母的敲,这是有多大的仇?


mysql连接显示"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",我调用了XXXXXX,但还是出现这个错误,请帮忙解决。

PS:多点描述会死吗?

之后如果你有耐心的话,就和题主挤牙膏吧!问一句答一句,这是贴吧!不是问答。


请问各位大神,怎样实现XXXXXXX?……

我遇到了一个XXXXX问题……

PS:经常性的看到很多的问题,竟然找度娘都能搜到答案,可是为啥题主就不知道看看?


还遇到过不同的3个人,发的相同的3个问题,这是刷分的节奏吗?特地等了很久没看到有人会回复,试着回了一下,没有任何的相应。。这是什么意思?


难道说答题的人就没事了吗?不是!

碰到过一个问题,就上面截图的例子,我因为觉得奇葩,一个字母一个字母敲的,最后为了省事和题主的命名不太一样,写了例子发了截图来证明没问题。竟然被其他答复者踩,说我这样命名不对,和题主相同的命名才会有问题。好吧,我承认因为自己懒,这么做了有问题?就改一下试试,但我试过之后证明这个答复者说的话是错的,和题主相同命名也不会有问题。没有责任心的答复亏你发的出来!


警语

看到一个描述不明确的问题,现在懒得和挤牙膏一样慢慢的挤了,但是多天过去后还是没人答复问题,我总是会多余的问一下,不是想说自己怎么样,只是因为我有遇到紧急问题时的经历知道那是一种什么样的心态。

答复你的人,可能是正在工作,可能加班回家休息刚起床,又可能是个脱离了技术岗位的热心人士……总之答复你的人,没有任何义务在你提出问题后答复你问题!有人说有积分哦!积分算个P!多少人真不是看中积分才回答你的,大家都是从一个问题又一个问题走过来的,都明白遇到了解决不了问题的心情,技术注重的是交流,所以才会有这个平台的市场来供大家交流。

我想奉劝大家,提问要有智慧!哪怕你再着急,也要言之有物,越着急越应该把所有相关的信息列出来,这样才会让其他人在空闲的时间看到问题思考后,给你一个答复,而不是把所有的时间都费在"挤牙膏"身上!

2015年四月30日早上 7:18:00 你真的弄明白 new 了吗

好久没有写点东西了,总觉得自己应该写点牛逼的,却又不知道如何下笔。既然如此,还是回归最基本的吧,今天就来说一说这个new。关于javascript的new关键字的内容上网搜一搜还真不少,大家都说new干了3件事:

文字比较难懂,翻译成javascript:

javascriptfunction Base() {
    this.str = "aa";
}

// new Base()干了下面的事
var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

想想是这么回事哈,那就赶快试试:

javascriptvar b = new Base();
console.dir(b); // Base {str: 'aa', __proto__: Base}

好像是正确的,但是真的正确吗???

真的就3件事?

每个对象都有一个constructor属性,那么我们来试试看new出来的实例的constructor是什么吧。

javascriptconsole.dir(b.constructor); // [Function: Base]

可以看出实例b的constructor属性就是Base,那么我们可以猜测new是不是至少还做了第4件事:

javascriptb.constructor = Base;

以上结果看似正确,下面我们进行一点修改,这里我们修改掉原型的constructor属性:

javascriptBase.prototype.constructor = function Other(){ };
var b = new Base();
console.dir(b.constructor); // [Function: Other]

情况就不一样了,可以看出,之前的猜测是错误的,第4件事应该是这样的:

javascriptb.constructor = Base.prototype.constructor;

这里犯了一个错误,那就是没有理解好这个constructor的实质:当我们创建一个函数时,会自动生成对应的原型,这个原型包含一个constructor属性,使用new构造的实例,可以通过原型链查找到constructor。如下图所示:

constructor

这里非常感谢zonxin同学指出我的错误。

如果构造函数有返回值呢?

一般情况下构造函数没有返回值,但是我们依旧可以得到该对象的实例;如果构造函数有返回值,凭直觉来说情况应该会不一样。我们对于之前的构造函数进行一点点修改:

javascriptfunction Base() {
    this.str = "aa";
    return 1;
    // return "a";
    // return true;
}
var b = new Base();
console.dir(b); // { str: 'aa'}

我们在构造函数里设置的返回值好像没什么用,返回的还是原来对象的实例,换一些例子试试:

javascriptfunction Base() {
    this.str = "aa";
    return [1];
    // return {a:1};
}
var b = new Base();
console.dir(b); // [1] or {a: 1}

此时结果就不一样了,从上面的例子可以看出,如果构造函数返回的是原始值,那么这个返回值会被忽略,如果返回的是对象,就会覆盖构造的实例

new至少做了4件事

总结一下,new至少做了4件事:

javascript// new Base();

// 1.创建一个空对象 obj
var obj = {};
// 2.设置obj的__proto__为原型
obj.__proto__ = Base.prototype;
// 3.使用obj作为上下文调用Base函数
var ret = Base.call(obj);
// 4.如果构造函数返回的是原始值,那么这个返回值会被忽略,如果返回的是对象,就会覆盖构造的实例
if(typeof ret == 'object'){
    return ret;
} else {
    return obj;
}

new的不足

在《Javascript语言精粹》(Javascript: The Good Parts)中,道格拉斯认为应该避免使用new关键字:

If you forget to include the new prefix when calling a constructor function, then this will not be bound to the new object. Sadly, this will be bound to the global object, so instead of augmenting your new object, you will be clobbering global variables. That is really bad. There is no compile warning, and there is no runtime warning.

大意是说在应该使用new的时候如果忘了new关键字,会引发一些问题。最重要的问题就是影响了原型查找,原型查找是沿着__proto__进行的,而任何函数都是Function的实例,一旦没用使用new,你就会发现什么属性都查找不到了,因为相当于直接短路了。如下面例子所示,没有使用new来创建对象的话,就无法找到原型上的fa1属性了:

javascriptfunction F(){ }
F.prototype.fa1 = "fa1";

console.log(F.fa1);       // undefined
console.log(new F().fa1); // fa1

这里我配合一张图来说明其中原理,黄色的线为原型链,使用new构造的对象可以正常查找到属性fa1,没有使用new则完全走向了另外一条查找路径:

原型查找

以上的问题对于有继承的情况表现得更为明显,沿着原型链的方法和属性全都找不到,你能使用的只有短路之后的Function.prototype的属性和方法了。

当然了,遗忘使用任何关键字都会引起一系列的问题。再退一步说,这个问题是完全可以避免的:

javascriptfunction foo()
{   
   // 如果忘了使用关键字,这一步骤会悄悄帮你修复这个问题
   if ( !(this instanceof foo) )
      return new foo();

   // 构造函数的逻辑继续……
}

可以看出new并不是一个很好的实践,道格拉斯将这个问题描述为:

This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript’s constructor pattern did not appeal to the classical crowd. It also obscured JavaScript’s true prototypal nature. As a result, there are very few programmers who know how to use the language effectively.

简单来说,JavaScript是一种prototypical类型语言,在创建之初,是为了迎合市场的需要,让人们觉得它和Java是类似的,才引入了new关键字。Javascript本应通过它的Prototypical特性来实现实例化和继承,但new关键字让它变得不伦不类。

再说一点关于constructor的

虽然使用new创建新对象的时候用讨论了这个constructor属性,但是这个属性似乎并没有什么用,也许设置这个属性就是一种习惯,能够让其他人直观理解对象之间的关系。

欢迎光临小弟博客:Superlin's Blog
我的博客原文:你真的弄明白new了吗

参考

2015年四月29日晚上 9:08:52 地图移动应用实战:Ionic ElasticSearch 搜索服务

在上一篇《GIS 移动应用实战 —— Django Haystack ElasticSearch 构建》中,我们构建了我们的服务端,可以通过搜索搜索到结果,这一篇,我们来构建一个简单的搜索。

最后效果如下图所示:

Ionic ElasticSearch

开始之前

如果你没有Ionic的经验,可以参考一下之前的一些文章:《HTML5打造原生应用——Ionic框架简介与Ionic Hello World》

我们用到的库有:

将他们添加到bower.json,然后

bower install

Ionic ElasticSearch 创建页面

1.引入库

index.html中添加

<script src="lib/elasticsearch/elasticsearch.angular.min.js"></script>
<script src="lib/ngCordova/dist/ng-cordova.js"></script>

接着开始写我们的搜索模板tab-search.html

html    <ion-view view-title="搜索" ng-controller="SearchCtrl">
        <ion-content>
            <div id="search-bar">
                <div class="item item-input-inset">
                    <label class="item-input-wrapper" id="search-input">
                        <i class="icon ion-search placeholder-icon"></i>
                        <input type="search" placeholder="Search" ng-model="query" ng-change="search(query)" autocorrect="off">
                    </label>
                </div>
            </div>
        </ion-content>
    </ion-view>

显示部分

xml <ion-list>
                <ion-item class="item-remove-animate item-icon-right" ng-repeat="result in results">
                    <h2 class="icon-left">{{result.title}}</h2>
                    <p>简介: {{result.body}}</p>
                    <div class="icon-left ion-ios-home location_info">
                        {{result.location_info}}
                    </div>
                    <div class="button icon-left ion-ios-telephone button-calm button-outline">
                        <a ng-href="tel: {{result.phone_number}}">{{result.phone_number}}</a>
                    </div>
                </ion-item>
            </ion-list>

而我们期待的SearchCtrl则是这样的

$scope.query = "";
var doSearch = ionic.debounce(function(query) {
    ESService.search(query, 0).then(function(results){
        $scope.results = results;
    });
}, 500);

$scope.search = function(query) {
    doSearch(query);
}

当我们点下搜索的时候,调用 ESService.

Ionic ElasticSearch Service

接着我们就来构建我们的ESService,下面的部分来自网上:

angular.module('starter.services', ['ngCordova', 'elasticsearch'])

.factory('ESService',
  ['$q', 'esFactory', '$location', '$localstorage', function($q, elasticsearch, $location, $localstorage){
    var client = elasticsearch({
      host: $location.host() + ":9200"
    });

    var search = function(term, offset){
      var deferred = $q.defer(), query, sort;
      if(!term){
        query = {
          "match_all": {}
        };
      } else {
        query = {
          match: { title: term }
        }
      }

      var position = $localstorage.get('position');

      if(position){
        sort = [{
          "_geo_distance": {
            "location": position,
            "unit": "km"
          }
        }];
      } else {
        sort = [];
      }

      client.search({
        "index": 'haystack',
        "body": {
          "query": query,
          "sort": sort
        }
      }).then(function(result) {
        var ii = 0, hits_in, hits_out = [];
        hits_in = (result.hits || {}).hits || [];
        for(;ii < hits_in.length; ii++){
          var data = hits_in[ii]._source;
          var distance = {};
          if(hits_in[ii].sort){
            distance = {"distance": parseFloat(hits_in[ii].sort[0]).toFixed(1)}
          }
          angular.extend(data, distance);
          hits_out.push(data);
        }
        deferred.resolve(hits_out);
      }, deferred.reject);

      return deferred.promise;
    };


    return {
      "search": search
    };
  }]
);

这个Service主要做的是创建ElasitcSearch Query,然后返回解析结果。

运行

如果是要在真机上运行,需要处于同一网段,或者是部署到服务器上。

其他

服务端代码: https://github.com/phodal/django-elasticsearch
客户端代码: https://github.com/phodal/ionic-elasticsearch

2015年四月29日晚上 9:05:33 SegmentFault for Android.

我用尽一生的好运气去遇见你。
所幸,这运气够长够远,足够我陪你一辈子。

非常荣幸的告诉大家,SegmentFault For Android 1.0 已经在以下市场发布

  1. Google Play
  2. 豌豆荚
  3. 应用宝
  4. 小米
  5. 360

现在大家可以在已经发布的市场中搜索我们的App进行试用啦~

如果SegmentFault是一本书,您就是那唯一能领略它墨香的读者,只为您散尽芳华。
如果SegmentFault是一束向日葵,您就是那一缕明媚的阳光,只因日出盛放。
如果SegmentFault是一行诗,您就是那一壶陈酿,只为醇香刻下所有的时光。

反馈请点 https://github.com/SegmentFault/report

感谢有你。

Build By Developers.
Build For Developers.

2015年四月29日晚上 8:01:18 从外网 SSH 进局域网,反向代理+正向代理解决方案

相信很多同学都会碰到这样一个问题。在实验室有一台机器用于日常工作,当我回家了或者回宿舍之后因为没法进入内网,所以访问不到了。如果这个时候我需要 SSH 进去做一下工作,那么怎么解决这个问题呢?本文将给出一种使用 SSH 的代理功能的解决方案。

问题描述:

机器状况

机器号 IP 用户名 备注
A 192.168.0.A usr_a 目标服务器,在局域网中,可以访问 A
B B.B.B.B usr_b 代理服务器,在外网中,无法访问 A
C - - 可以直接访问 B,无法直接访问 A

目标

从 C 机器使用 SSH 访问 A

解决方案

在 A 机器上做到 B 机器的反向代理;在 B 机器上做正向代理本地端口转发

环境需求

实施步骤

  1. 建立 A 机器到 B 机器的反向代理【A 机器上操作】

    bashssh -fCNR <port_b1>:localhost:22 usr_b@B.B.B.B
    

    <port_b1> 为 B 机器上端口,用来与 A 机器上的22端口绑定。

  2. 建立 B 机器上的正向代理,用作本地转发。做这一步是因为绑定后的 端口只支持本地访问【B 机器上操作】

    bashssh -fCNL "*:<port_b2>:localhost:<port_b1>' localhost
    

    <port_b2> 为本地转发端口,用以和外网通信,并将数据转发到 <port_b1>,实现可以从其他机器访问。

    其中的*表示接受来自任意机器的访问。

  3. 现在在 C 机器上可以通过 B 机器 ssh 到 A 机器

    bashssh -p <portb2> usra@B.B.B.B
    

至此方案完成。

附:

SSH 参数解释

-f 后台运行
-C 允许压缩数据
-N 不执行任何命令
-R 将端口绑定到远程服务器,反向代理
-L 将端口绑定到本地客户端,正向代理
2015年四月29日晚上 6:05:19 使用Gulp来加速你的开发

Gulp与Grunt一样,也是一个自动任务运行器。它充分借鉴了Unix操作系统的管道(pipe)思想,在操作上,它要比Grunt简单。

安装

Gulp需要全局安装,然后再在项目的开发目录中安装为本地模块。先进入项目目录,运行下面的命令。

bashnpm install -g gulp
npm install --save-dev gulp

gulpfile.js

项目根目录中的gulpfile.js,是Gulp的配置文件。它大概是下面的样子。

javascriptvar gulp = require('gulp');
gulp.task('default', function () {
});

举个栗子,我们要实现js的压缩。

javascriptvar gulp = require('gulp'),
   uglify = require('gulp-uglify');

gulp.task('minify', function () {
   gulp.src('js/app.js')
      .pipe(uglify())
      .pipe(gulp.dest('app.min'))
});

上面代码中使用了gulp-uglify模块。在此之前,需要先安装这个模块。
记住在安装之前先 运行 npm init 来生成package.json,如果已经有了就不需要这一步了。

bashnpm install --save-dev gulp-uglify

Tips: --save-dev 会将 gulp-uglify 自动添加到package.json的devDependencies中;

gulpfile.js加载gulp和gulp-uglify模块之后,使用gulp模块的task方法指定任务。task方法有两个参数,第一个是任务名,第二个是任务函数。在任务函数中,使用gulp模块的src方法,指定所要处理的文件,然后使用pipe方法,将上一步的输出转为当前的输入,进行链式处理。

在上面代码中,使用两次pipe方法,也就是说做了两种处理。第一种处理是使用gulp-uglify模块,压缩源码;第二种处理是使用gulp模块的dest方法,将上一步的输出写入本地文件,这里是app.min.js(代码中省略了后缀名js)。

从上面的例子中可以看到,gulp充分使用了“管道”思想,就是一个数据流(stream):src方法读入文件产生数据流,dest方法将数据流写入文件,中间是一些中间步骤,每一步都对数据流进行一些处理。

gulp.src()

gulp模块的src方法,用于产生数据流。它的参数表示所要处理的文件,一般有以下几种形式。

src方法的参数还可以是一个数组,用来指定多个成员。

javascript
gulp.src(['js/**/*.js', 'css/**/*.css'])

gulp.task()

gulp模块的task方法,用于定义具体的任务。它的第一个参数是任务名,第二个参数是任务函数。下面是一个非常简单的任务函数。

javascript
gulp.task('test', function () { console.log('就测试下。'); });

task方法还可以指定按顺序运行的一组任务。

javascript
gulp.task('build', ['css', 'js', 'templates']);

上面代码先指定build任务,它按次序由css、js、templates三个任务所组成。注意,由于每个任务都是异步调用,所以没有办法保证js任务的开始运行的时间,正是css任务运行结束。

如果希望各个任务严格按次序运行,可以把前一个任务写成后一个任务的依赖模块。

javascript
gulp.task('css', ['templates'], function () { // Deal with CSS here });

上面代码表明,css任务依赖templates任务,所以css一定会在templates运行完成后再运行。

如果一个任务的名字为default,就表明它是“默认任务”,在命令行直接输入gulp命令,就会运行该任务。

javascript
gulp.task('default', function () { // Your default task });

gulp.watch()

gulp模块的watch方法,用于指定需要监视的文件。一旦这些文件发生变动,就运行指定任务。

javascript
gulp.task('watch', function () { gulp.watch('templates/*.tmpl.html', ['build']); });

上面代码指定,一旦templates目录中的模板文件发生变化,就运行build任务。

watch方法也可以用回调函数,代替指定的任务。

javascript
gulp.watch('templates/*.html', function (event) { console.log('Event type: ' + event.type); console.log('Event path: ' + event.path); });

另一种写法是watch方法所监控的文件发生变化时(修改、增加、删除文件),会触发change事件。可以对change事件指定回调函数。

javascript
var watcher = gulp.watch('templates/*.html', ['build']); watcher.on('change', function (event) { console.log('Event type: ' + event.type); console.log('Event path: ' + event.path); });

除了change事件,watch方法还可能触发以下事件。

watcher对象还包含其他一些方法。

gulp实现自动刷新 - gulp-livereload

gulp-livereload模块用于自动刷新浏览器,反映出源码的最新变化。它除了模块以外,还需要在浏览器中安装插件,用来配合源码变化。

javascript
var gulp = require('gulp'), livereload = require('gulp-livereload'); gulp.task('watch', function () { livereload.listen(); gulp.watch(['./asset/**/*.*','./templates/**/*.*'], function (file) { livereload.changed(file.path); }); });

上面代码监视asset和templates下的任何文件,一旦有变化,就自动刷新浏览器。
Tips: 调试css 很方遍,因为刷新css 不需要刷新这个页面,只需要重新加载css即可,赶紧双屏幕,三屏幕搞起来;代码敲得飞起。

还有很多实用的插件 可以到 NpmJs.org 去找。

2015年四月29日下午 5:45:17 alsotang starred node-modules/optimized
alsotang starred node-modules/optimized
2015年四月29日下午 4:54:48 各种设备的CSS3MediaQuery整理及爽歪歪写法

响应式布局

响应式布局麻烦之处就是每个尺寸的都要进行css定义,这个真的不是一般的蛋疼,下面有搜集到的各种尺寸css Media Query内容,搜集来源:media-queries-for-standard-devices好东西哦。

看了之后是不是非常之蛋疼呢,那么只有使用工具来写这些玩意儿了,俺用得最爽的就是 stylus ,真的爽yy了,如果 stylus 不会玩耍请看这里 stylus入门使用方法

stylus

// Media queries
mq-mobile = "screen and (max-width: 479px)"
mq-tablet = "screen and (min-width: 480px) and (max-width: 767px)"
mq-iPhones4 = "only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2)"
mq-normal = "screen and (min-width: 768px)"

.page-number
    display: inline-block
    @media mq-mobile
        display: none
    @media mq-tablet
        color:red
    @media mq-iPhones4
        font-size:12px
    @media mq-normal
        background:yellow

编译成

css.page-number {
  display: inline-block;
}
@media screen and (max-width: 479px) {
  .page-number {
    display: none;
  }
}
@media screen and (min-width: 480px) and (max-width: 767px) {
  .page-number {
    color: #f00;
  }
}
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) {
  .page-number {
    font-size: 12px;
  }
}
@media screen and (min-width: 768px) {
  .page-number {
    background: #ff0;
  }
}

Phones and Handhelds

iPhones

css/* ----------- iPhone 4 and 4S ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 480px)
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 480px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 480px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: landscape) {

}

/* ----------- iPhone 5 and 5S ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: landscape) {

}

/* ----------- iPhone 6 ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 375px) 
  and (max-device-width: 667px) 
  and (-webkit-min-device-pixel-ratio: 2) { 

}

/* Portrait */
@media only screen 
  and (min-device-width: 375px) 
  and (max-device-width: 667px) 
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: portrait) { 

}

/* Landscape */
@media only screen 
  and (min-device-width: 375px) 
  and (max-device-width: 667px) 
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: landscape) { 

}

/* ----------- iPhone 6+ ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 414px) 
  and (max-device-width: 736px) 
  and (-webkit-min-device-pixel-ratio: 3) { 

}

/* Portrait */
@media only screen 
  and (min-device-width: 414px) 
  and (max-device-width: 736px) 
  and (-webkit-min-device-pixel-ratio: 3)
  and (orientation: portrait) { 

}

/* Landscape */
@media only screen 
  and (min-device-width: 414px) 
  and (max-device-width: 736px) 
  and (-webkit-min-device-pixel-ratio: 3)
  and (orientation: landscape) { 

}

Galaxy Phones

css/* ----------- Galaxy S3 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 2) {

}

/* Portrait */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 2) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 2) 
  and (orientation: landscape) {

}

/* ----------- Galaxy S4 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) {

}

/* Portrait */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 320px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: landscape) {

}

/* ----------- Galaxy S5 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) {

}

/* Portrait */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: landscape) {

}

HTC Phones

css/* ----------- HTC One ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) {

}

/* Portrait */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 360px) 
  and (device-height: 640px) 
  and (-webkit-device-pixel-ratio: 3) 
  and (orientation: landscape) {

}

Tablets

iPads

css/* ----------- iPad mini ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: portrait) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: landscape) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* ----------- iPad 1 and 2 ----------- */
/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: portrait) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: landscape) 
  and (-webkit-min-device-pixel-ratio: 1) {

}

/* ----------- iPad 3 and 4 ----------- */
/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: portrait) 
  and (-webkit-min-device-pixel-ratio: 2) {

}

/* Landscape */
@media only screen 
  and (min-device-width: 768px) 
  and (max-device-width: 1024px) 
  and (orientation: landscape) 
  and (-webkit-min-device-pixel-ratio: 2) {

}

Galaxy Tablets

css/* ----------- Galaxy Tab 10.1 ----------- */

/* Portrait and Landscape */
@media 
  (min-device-width: 800px) 
  and (max-device-width: 1280px) {

}

/* Portrait */
@media 
  (max-device-width: 800px) 
  and (orientation: portrait) { 

}

/* Landscape */
@media 
  (max-device-width: 1280px) 
  and (orientation: landscape) { 

}

Nexus Tablets

css/* ----------- Asus Nexus 7 ----------- */

/* Portrait and Landscape */
@media screen 
  and (device-width: 601px) 
  and (device-height: 906px) 
  and (-webkit-min-device-pixel-ratio: 1.331) 
  and (-webkit-max-device-pixel-ratio: 1.332) {

}

/* Portrait */
@media screen 
  and (device-width: 601px) 
  and (device-height: 906px) 
  and (-webkit-min-device-pixel-ratio: 1.331) 
  and (-webkit-max-device-pixel-ratio: 1.332) 
  and (orientation: portrait) {

}

/* Landscape */
@media screen 
  and (device-width: 601px) 
  and (device-height: 906px) 
  and (-webkit-min-device-pixel-ratio: 1.331) 
  and (-webkit-max-device-pixel-ratio: 1.332) 
  and (orientation: landscape) {

}

Kindle Fire

css/* ----------- Kindle Fire HD 7" ----------- */

/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 800px) 
  and (max-device-width: 1280px) 
  and (-webkit-min-device-pixel-ratio: 1.5) {

}

/* Portrait */
@media only screen 
  and (min-device-width: 800px) 
  and (max-device-width: 1280px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 800px) 
  and (max-device-width: 1280px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: landscape) {

}

/* ----------- Kindle Fire HD 8.9" ----------- */
/* Portrait and Landscape */
@media only screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1.5) {

}
/* Portrait */
@media only screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: portrait) {
}

/* Landscape */
@media only screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1.5) 
  and (orientation: landscape) {

}

Laptops

css/* ----------- Non-Retina Screens ----------- */
@media screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 1) { 
}
/* ----------- Retina Screens ----------- */
@media screen 
  and (min-device-width: 1200px) 
  and (max-device-width: 1600px) 
  and (-webkit-min-device-pixel-ratio: 2)
  and (min-resolution: 192dpi) { 
}

Wearables

Apple Watch

css/* ----------- Apple Watch ----------- */
@media
  (max-device-width: 42mm)
  and (min-device-width: 38mm) { 

}

Moto 360 Watch

css/* ----------- Moto 360 Watch ----------- */
@media 
  (max-device-width: 218px)
  and (max-device-height: 281px) { 

}
2015年四月29日下午 4:25:28 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年四月29日下午 4:24:27 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年四月29日上午 10:30:37 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年四月28日下午 3:23:19 alsotang starred ideawu/ssdb
alsotang starred ideawu/ssdb
2015年四月28日上午 10:48:03 alsotang starred phacility/xhprof
alsotang starred phacility/xhprof
2015年四月27日晚上 10:08:08 alsotang starred nuysoft/node-print
alsotang starred nuysoft/node-print
2015年四月27日中午 11:36:09 alsotang starred petitspois/docs.ren
alsotang starred petitspois/docs.ren
2015年四月24日晚上 10:01:44 alsotang commented on issue alsotang/node-lessons#34
alsotang commented on issue alsotang/node-lessons#34
@alsotang

lesson2 不涉及任何 css 的内容吧

2015年四月23日晚上 10:58:10 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang
2015年四月23日晚上 10:57:37 alsotang pushed to master at cnodejs/nodeclub
alsotang pushed to master at cnodejs/nodeclub
@alsotang
2015年四月23日晚上 10:52:40 alsotang pushed to online at cnodejs/nodeclub
alsotang pushed to online at cnodejs/nodeclub
@alsotang