美国如新是传销吗CL05361 是几几年几月生产的??

require&'formula'class&Tsocks&&&Formula&&#&The&original&is&http://tsocks.sourceforge.net/&&#&This&GitHub&repo&is&a&maintained&fork&with&OSX&support&&homepage&'http://github.com/pc/tsocks'&&head&'https://github.com/pc/tsocks.git'&&depends_on&'autoconf'&=&&:build&if&MacOS.xcode_version.to_f&&=&4.3&&def&install&&&&system&"autoconf",&"-v"&&&&system&"./configure",&"--prefix=#{prefix}",&"--disable-debug",&"--disable-dependency-tracking",&"--with-conf=#{config_file}"&&&&inreplace("tsocks")&{&|bin|&bin.change_make_var!&"LIBDIR",&lib&}&&&&system&"make"&&&&system&"make&install"&&&&etc.install&"tsocks.conf.simple.example"&=&&"tsocks.conf"&unless&config_file.exist?&&end&&def&test&&&&puts&'Your&current&public&ip&is:'&&&&ohai&`curl&-sS&ifconfig.me&2&&1`.chomp&&&&puts&"If&your&correctly&configured&#{config_file},&this&should&show&the&ip&you&have&trough&the&proxy"&&&&puts&'Your&ip&through&the&proxy&is:'&&&&ohai&`tsocks&curl&-sS&ifconfig.me&2&&1`.chomp&&end&&def&config_file&&&&etc&/&'tsocks.conf'&&endend有了上述formula之后,即可 brew install --HEAD tsocks 。
有了RPi当然免不了要玩玩XBMC,这里推荐XBian,安装配置过程如下:
1- 下载.img文件 (&&);
2- 用dd命令将.img(比如XBian_beta_2.img)写入SD卡;
3- 将中文字体(比如wqy-zenhei.ttc)重命名为arial.ttf,复制到SD卡的~/.xbmc/media/Fonts目录(XBian默认用户为xbian,密码raspberry);
4- 从& 下载资源插件并复制到SD卡;
5- 将SD卡插入RPi并连接HDMI到电视机/显示器;
6- RPi开机,进入XBian的配置页面,可以校准屏幕,配置网络连接等;
7- 在系统设置中选择字体为Arial,语言选为中文,此时应该看到界面可正常显示中文;
8- 安装扩展程序,选择之前下载到SD卡的repository.googlecode.xbmc-addons-chinese-eden.zip文件,扩展安装成功后即可在扩展程序列表中看到对应的资源条目,逐一安装即可。
额外提示:XBMC也可以通过网络共享播放视频,之前跟大家提到过XBMC显示中文字幕有时出现整行被方块覆盖的问题,解决方法是在字幕文件中查找并删除或替换"…"字符。Enjoy!
整理一下 IntelliJ IDEA 最常用的快捷键,按照便于记忆的方式排列:
Ctrl-N&&&&&&&&&&&&&&Class...&(find&by&name)
Ctrl-Shift-N&&&&&&&&File...&(find&by&name)
Ctrl-Shift-Alt-N&&&&Symbol...&(find&by&name)
Ctrl-G&&&&&&&&&&&&&&Line...&(goto&line)
Ctrl-H&&&&&&&&&&&&&&Type&hierarchy&(hierarchy)
Ctrl-Shift-H&&&&&&&&Method&hierarchy&(hierarchy)
Ctrl-Alt-H&&&&&&&&&&Call&hierarchy&(hierarchy)
Ctrl-Q&&&&&&&&&&&&&&Quick&documentation
Ctrl-Alt-I&&&&&&&&&&Auto-indent&lines&(indent)
Ctrl-Alt-L&&&&&&&&&&Reformat&code&(line&up)
Ctrl-Alt-O&&&&&&&&&&Optimize&imports&(optimize)
Ctrl-/&&&&&&&&&&&&&&Comment&with&line&comment&(//)
Ctrl-Shift-/&&&&&&&&Comment&with&block&comment&(/*...*/)
Ctrl-W&&&&&&&&&&&&&&Select&word&or&block&(word)
Ctrl-D&&&&&&&&&&&&&&Copy&line&(duplicate&line,&yyp)
Ctrl-X&&&&&&&&&&&&&&Cut&line&(dd)
Ctrl-U&&&&&&&&&&&&&&Uppercase/lowercase&(upper)
Ctrl-J&&&&&&&&&&&&&&Insert&live&template
Ctrl-Alt-J&&&&&&&&&&Surround&with&live&template
Ctrl-Alt-T&&&&&&&&&&Surround&with&(template)
Ctrl-Shift-J&&&&&&&&Join&lines&(join)
Ctrl-E&&&&&&&&&&&&&&Recent&files&(editions)
Ctrl-Shift-E&&&&&&&&Recently&changed&files&(editions)
Alt-Shift-C&&&&&&&&&Recent&changes&(changes)
Ctrl-B&&&&&&&&&&&&&&Delcaration
Ctrl-Shift-B&&&&&&&&Type&declaration
Ctrl-Alt-B&&&&&&&&&&Implementation(s)
Ctrl-P&&&&&&&&&&&&&&Parameter&info&(parameter)
Ctrl-Space&&&&&&&&&&Basic&completion
Ctrl-Shift-Space&&&&Smart&completion
Ctrl-Alt-Space&&&&&&Completion&lookup
Alt-Enter&&&&&&&&&&&Auto-complete
Alt-Insert&&&&&&&&&&Generate...
Ctrl-Shift-Up/Down&&Move&statement&up/down
Alt-Shift-Up/Down&&&Move&line&up/down
Ctrl-Up/Down&&&&&&&&Scroll&up/down
Alt-Up/Down&&&&&&&&&Previous/next&method
F2&&&&&&&&&&&&&&&&&&Next&highlighted&error
Shift-F2&&&&&&&&&&&&Previous&highlighted&error
Ctrl-F&&&&&&&&&&&&&&Find
Ctrl-R&&&&&&&&&&&&&&Replace
F3&&&&&&&&&&&&&&&&&&Next&match
Shift-F3&&&&&&&&&&&&Previous&match
F4&&&&&&&&&&&&&&&&&&Jump&to&source
Ctrl-Alt-Shift-T&&&&Refactor&this
F5&&&&&&&&&&&&&&&&&&Refactor&copy
F6&&&&&&&&&&&&&&&&&&Refactor&move
Ctrl-F6&&&&&&&&&&&&&Refactor&change&signature
Shift-F6&&&&&&&&&&&&Refactor&rename
Alt-Delete&&&&&&&&&&Refactor&safe&delete
Ctrl-Alt-V&&&&&&&&&&Refactor&extract&variable
Ctrl-Alt-F&&&&&&&&&&Refactor&extract&field
Ctrl-Alt-P&&&&&&&&&&Refactor&extract&parameter
Ctrl-Alt-M&&&&&&&&&&Refactor&extract&method
Ctrl-Alt-N&&&&&&&&&&Refactor&inline
不久前入了个 Raspberry Pi 也就是大家说的树梅派(以下简称RPi),拿来做蓝牙测试,用的蓝牙dongle是ORICO的BTA-403-BL&&。这里简单记录下安装过程:
首先是操作系统。作为Arch重度用户,当然选Archlinux ARM了:&&下载img文件,用dd写到SD卡上:
dd&bs=1M&if=/path/to/archlinux-hf-*.img&of=/dev/sdX
然后用GParted把SD卡上的分区拖满,充分利用空间。这样SD卡就准备好了,插到RPi上,接上Micro-USB的电源和网线,启动RPi,从路由器上找到RPi的IP地址(机器名默认是alarmpi),ssh上去(用户名root密码root),修改密码,创建非root账号,执行系统更新 sudo pacman -Syu ,这之后开始安装蓝牙相关工具:
sudo pacman&-S&bluez&bluez-utils
确保蓝牙dongle插到RPi的USB接口,通过 hciconfig 确认蓝牙设备被识别,输出应该类似下面这个样子:
[sean@alarmpi]$&hciconfig
hci0:&&&Type:&BR/EDR&&Bus:&USB
&&&&&&&&BD&Address:&84:A6:C8:DC:04:97& ACL&MTU:&310:10&&SCO&MTU:&64:8
&&&&&&&&DOWN&
&&&&&&&&RX&bytes:553&acl:0&sco:0&events:28&errors:0
&&&&&&&&TX&bytes:384&acl:0&sco:0&commands:27&errors:0
启动bluetooth:
sudo systemctl&start&bluetooth
完成以后,即可通过 bluetoothctl 命令打开蓝牙控制台,执行各项蓝牙相关操作,比如show、list、scan on、agent、info等等,这里不展开了。
如果想使用图形界面操作RPi,也很简单,基本步骤如下:
sudo&pacman&-S&xorg xorg-xinit lxdeecho&'exec&startlxde'&&&~/.xinitrc
startxEnjoy!
第11页(练习):
在Scala REPL中键入3,然后按Tab键 应为 在Scala REPL中键入3.,然后按Tab键
第19页(正文):
util方法返回一个并不包含上限的区间 应为 until方法返回一个并不包含上限的区间
第19页(代码):
0 util s.length 应为 0 until s.length
第31页(正文):
util是RichInt类的方法 应为 until是RichInt类的方法
第34页(代码):
ArraryBuffer&应为&ArrayBuffer
b.sorted(_ & _)&应为&b.sorted
b.sorted(_ & _)&应为&b.sortWith(_ & _)&
第44页(代码):
scala.collections.immutable.SortedMap&应为&scala.collection.immutable.SortedMap
第53页(正文):Scala对每个字端都提供getter和setter方法&应为&Scala对每个字段都提供getter和setter方法第107页(代码):
val tokens = source.mkString.split("\\S+")&应为&val tokens = source.mkString.split("\\s+")
第341页(代码):
var count: (Int =& Double) =& null&应为&var count: (Int =& Double) = null
// 本文仅作为信息发布窗口,如需讨论交流,请通过邮件
可能很多人不以为然,写作和编程能有什么关系。
首先,写作促进我们思考。程序员每天的工作,不论是学习新知识新技术、理解软件需求、阅读代码/文档、设计框架、还是实现业务逻辑,都离不开思考。相信很多人都有过这样的体验: 对于某个设计或知识点,你以为自己想清楚了,但真要让你解释给别人听,你又会觉得无从下手,几轮过后回想当初,之前所看到和相信的不过是错觉,当你完整的向其他人转述一遍之后,你才算是真正想明白了。其实这个道理和书桌上放一只橡皮鸭/填充玩偶(然后向它讲述你的想法)的做法是相通的,只不过写作本身不光整理了思路,还留下了文档,同时,写作的过程也是不断加深印象、提升信心的过程,可谓一举多得。
其次,和写作一样,编程的主要目的是与世界交流,不论这个世界是指的机器的世界还是人的世界。要有效地与机器世界交流,你编写的代码必须符合一定的语法和范式,必须逻辑上讲得通,这样才有意义。而基本的写作训练可以让我们养成主动留意错别字、格式、拼写错误、逻辑错误的习惯。如C. A. R. Haore所说,我们宁要明显无错误的代码,不要无明显错误的代码。这点洁癖是好程序员必须具备的修养。这也是为什么我看到有明显拼写错误的程序员简历时,会直接把他/她们拉黑。同样地,我一直坚持认为,如果程序员写不出结构优良的纯文本文档,那么我们也不必指望他/她能够写出优雅的代码。除此之外,我们还可以再稍微发散一下: 开源项目那么多,满足类似需要的往往不止一个,为什么有的很成功,有的却无人问津? 主创/灵魂人物的写作能力,不论是代码、文档、邮件还是PPT,是很重要的分野。我相信,改变世界、影响更多的人,是很多程序员梦寐以求的,而要做到这一点,离不开写作。
最后,写作通常是程序员的短板,亦即最容易低成本高产出的地方。我一直很不喜欢人为地给不同专业背景的人贴上文科和理科的标签,仿佛他们之间没有交集似的。这样做的最大问题在我看来是一方天然地觉得另一方的知识对自己没价值,以至于形成了类似"写作是文科生的菜"、"编程是理科才要学的东西"等谬误。就我的观察,计算机科班出身的同学,往往文字表达能力不够强,可能多少和这种心理暗示有关。如此明显的短板,如果能够引起广大程序员群体的重视,肯在写作上持续投入,效果一定是惊人的。
今天远程支持同事在Windows环境调优Apache服务器性能,记录在此,供需要的朋友参考。
Java EE应用,同时部署在两个Tomcat(5.5.27)实例上,前面放了个Apache(httpd-2.2.19-win32-x86-no_ssl),通过mod_jk(1.2.32)做负载均衡,同一台物理服务器,操作系统为64位的Windows Server 2003 SP2。现象是60+客户端,平均每个客户端每秒请求数2次,单个请求正常响应时间在500ms以内,即每秒冲进来120个请求,并发量最多在60上下,Apache就已不堪"重"负,静态资源响应时间都超过10s,同时Tomcat和数据库服务器均正常。凭我的经验,同样的压力直接压到Tomcat也不至于这么难看。看来问题出在Apache。
首先修改httpd.conf配置文件,打开status模块:
LoadModule status_module modules/mod_status.so
&Location /status&
&&SetHandler server-status
&&Order deny,allow
&&Deny from all
&&Allow from 127.0.0.1 #需要的话也可放开为all
&/Location&
这样我们就能通过/status页面查看服务器当前的状态信息,结果观察到worker(即线程)数仅为64!坑爹啊!难怪撑不住。我记得以前Windows下面的Apache默认线程数没这么小的。找准问题,接下来就好办了:
&IfModule mpm_winnt.c&
&nbsp&ThreadsPerChild 300
&&MaxRequestsPerChild 0
&/IfModule&
稍微解释一下:mpm_winnt.c是Apache为Windows NT提供的MPM (Multi-Processing Module),对应到Linux环境下,则有prefork.c(多进程/每进程1个线程)和worker.c(多进程+多线程)两种MPM可选。Windows下面只会有父与子两个进程,因此单个子进程能同时起多少线程(ThreadsPerChild)就成了调优的关键。另一个参数MaxRequestsPerChild的含义是单个子进程累计最多处理到少个请求,超过该值则退出重启,这是出于防止内存泄露慢慢拖垮整个服务器而做的防御性措施,0表示不做此限制。
新配置上线后,客户端数量顺利冲上200+。Case closed.
今天临时有个需求,那就是给某PDF文档切边,以方便在Kindle 3的6吋屏上阅读。
很久没碰Python了,不过我相信用Python一定有办法解决这个需求,于是经过简单的googling,便发现了这个pyPdf库 (&&) ,操作起来相当直接易懂,把代码贴在这儿,做个记录。
&1&from&pyPdf&import&PdfFileWriter,&PdfFileReader
&3&pdf&=&PdfFileReader(file('original.pdf',&'rb'))
&4&out&=&PdfFileWriter()
&6&for&page&in&pdf.pages:
&7&&&page.mediaBox.upperRight&=&(580,800)
&8&&&page.mediaBox.lowerLeft&=&(128,232)
&9&&&out.addPage(page)
11&ous&=&file('target.pdf',&'wb')
12&out.write(ous)
13&ous.close()
今天帮同事解决一个邮件乱码的问题,简记于此。
邮件正文就不贴了,有一段从上下文判断明显应该是"上海",却显示成了"涓...",于是自然而然的拿出"上"字和"涓"字来分析。
通过简单的Groovy脚本对这两个汉字分别按gbk和utf-8编码,并将得到的byte[]转换成二进制表示输出如下:
注意"上"字的第2排前两组和"涓"字第1排的两组byte正好相同,于是问题迎刃而解,乱码是由于邮件以utf-8编码后被错误的以gbk解码(随后又重新编码成utf-8)造成的。
关键代码:
void showBytes(String input) {
&&println("== " + input + " ==")
&&b = input.getBytes("gbk")
&&b.each { print Integer.toBinaryString(it).substring(24) + ' ' }
&&println()
&&b = input.getBytes("utf8")
&&b.each { print Integer.toBinaryString(it).substring(24) + ' ' }
&&println()
问题起因: 帮同事排查一个SVN资源库导入后无法使用的问题,checkout时报错&&Expected FS format '2'; found format '3'&&期待文件系统(FS)格式 &#21;;找到格式&#21;熟悉SVN的朋友应该知道,Subversion大版本更新时有可能会对其文件格式进行调整,因此内部有文件格式版本的说法,以上报错信息在网上搜一下就知道,版本2和版本3分别对应Subversion的1.4.x和1.5.x,于是问题归结为如何给资源库降级,从而得到与1.4兼容的dump文件。方法很简单,记录如下,供需要的朋友参考:首先在1.5.x的Subversion环境下新建一个与1.4.x兼容的资源库&&svnadmin --pre-1.5-compatible create project01然后将之前无法正常导入1.4.x的dump文件(dumpfile.old)导入&&svnadmin load project01 < dumpfile.old最后再做一次导出即可&&svnadmin dump project01 > dumpfule.new回到1.4.x的Subversion环境,新的dump文件(dumpfile.new)就可以正常导入和使用了。
设想一下这样的场景: 你出差在外,或者生病在家,有个紧急的需求要处理,涉及到数个源代码文件的改动,你亲自修改需要10分钟,电话和在公司的同伴沟通然后由他/她来修改则需要1小时。公司svn服务仅支持svn://协议,且仅限内网访问,而你只有一个ssh账号可以远程登录到公司某台Linux/UNIX服务器。
你暗自庆幸,幸好管理员有先见之明,为你留了个ssh口子,这样至少你还可以ssh上去通过命令行的方式在服务器上做svn checkout,vim ...和svn commit。不过如果你认为这就是全部,那就太小瞧ssh了。
ssh有个命令行参数 -D [地址:]端口,含义是在某个本地地址的某个端口上开SOCKS服务进行监听,把这个端口的数据通信以加密形式转发到ssh的另一端。你说好,我有了一个SOCKS服务器,但我又不是要上网走代理,svn也并不天然支持SOCKS啊,有什么用呢? 嗯,这正是tsocks的用武之地,它能透明的让普通应用程序也走SOCKS,安装方法很简单: 主流的Linux发行版,如Debian、Archlinux等的默认软件仓库已经自带了tsocks,通常只需要apt-get install或pacman -S即可,Mac OS X下则可以利用MacPorts安装,然后修改配置文件/etc/tsocks.conf(MacPorts会安装到/opt/local/etc目录),可以在样本文件tsocks.conf.sample的基础上修改,通常只要配置server = 127.0.0.1即可,其他都可以默认。
有了这些打底,剩下的就很简单了: 首先 ssh -D 1080 -f -N 用户名&#64;公司服务器的公网地址 在本机的1080端口开启SOCKS服务;然后按照你平时使用svn的习惯,只是在命令前加上tsocks,类似这样: tsocks svn up 或者 tsocks svn ci -m 'blahblahblah' 等等即可,本地的svn sandbox不需要任何修改。
这个例子可以说只是冰山一角,不论是ssh还是tsocks都还有更高级的用法,而这个通道一旦打通,它的效果就像是简化版的VPN,除了ping之类的少数命令外,几乎就跟你在公司做各种操作没有两样,所以,发挥你的想象力吧 :)
和Swing应用的直接跨平台不同,SWT/RCP应用要想同时支持不同平台,需要做些特殊的配置,不过并不复杂,记录在此,希望能帮到有需要的朋友。目前win32、32位Linux、64位Linux和Mac OS X基本上就覆盖了所有主流的桌面操作系统,本文将以同时支持这四种OS为例来进行讲解。
首先是下载对应版本的RCP框架在不同操作系统的插件,将它们放在同一个plugins目录,比如你可以用win32为基础,然后添加针对其他平台的如下插件:
org.eclipse.core.filesystemorg.eclipse.core.filesystem.win32.x86org.eclipse.core.filesystem.linux.x86org.eclipse.core.filesystem.linux.x86_64org.eclipse.core.filesystem.macosx
org.eclipse.core.netorg.eclipse.core.net.win32.x86org.eclipse.core.net.linux.x86
org.eclipse.core.resourcesorg.eclipse.core.resources.win32.x86
org.eclipse.equinox.launcherorg.eclipse.equinox.launcher.win32.win32.x86org.eclipse.equinox.launcher.gtk.linux.x86org.eclipse.equinox.launcher.gtk.linux.x86_64org.eclipse.equinox.launcher.carbon.macosx
org.eclipse.equinox.securityorg.eclipse.equinox.security.win32.x86org.eclipse.equinox.security.macosx
org.eclipse.swtorg.eclipse.swt.win32.win32.x86org.eclipse.swt.gtk.linux.x86org.eclipse.swt.gtk.linux.x86_64org.eclipse.swt.carbon.macosx
接下来将不同平台下的eclipse可执行文件(Windows下面是eclipe.exe,Linux下是eclipse,Mac OS X下面是Eclipse.app)放到不同的子目录下,当然,如果你的RCP应用有别的名称,也可以重命名eclipse可执行文件,按照不同平台的规范更换图标,然后修改.ini文件让它的-startup和-startup.libraray参数指向相对路径中正确版本的插件即可。
最后分享一下我们软件部署的机制: 按照前面介绍的方式打包的应用程序,交到用户手里并不是很友好,因为需要他/她自己再做一次判断,当前的操作系统是什么,然后打开不同的目录去点击不同的可执行文件。我们的做法是单独提供一个Swing程序,在客户端自动判断OS,然后自动调用不同版本的可执行文件,同时,这个Swing程序被做成Java Web Start,把整个RCP客户端的下载和同步也处理掉,这样,对用户而言,整个过程就透明了,只需要一个JNLP,剩下的工作完全自动化。
在Linux或其他UNIX和类UNIX环境下,ps命令想必大家都不陌生,我相信也有不少同学写过 ps aux | grep java | grep -v grep | awk '{print $2}' 这样的管道命令来找出Java进程的pid。常言道,Java并非真的"跨平台",它自己就是平台。作为平台,当然也有些基本的工具,让我们可以用更简单、更统一,同时又是非侵入的方式来查询进程相关信息。今天我们就来认识一下其中的两个。
顾名思义,它对应到UNIX的ps命令。用法如下:
jps [ options ] [ hostid ]
其中,options可以用 -q (安静) -m (输出传递给main方法的参数) -l (显示完整路径) -v (显示传递给JVM的命令行参数) -V (显示通过flag文件传递给JVM的参数) -J (和其他Java工具类似用于传递参数给命令本身要调用的java进程);hostid是主机id,默认localhost。
用于输出给定java进程的统计信息。用法如下:
jstat -options 可以列出当前JVM版本支持的选项,常见的有 -class (类加载器) -compiler (JIT) -gc (GC堆状态) -gccapacity (各区大小) -gccause (最近一次GC统计和原因) -gcnew (新区统计) -gcnewcapacity (新区大小) -gcold (老区统计) -gcoldcapacity (老区大小) -gcpermcapacity (永久区大小) -gcutil (GC统计汇总) -printcompilation (HotSpot编译统计)
假定你要监控的Java进程号是12345,那么
jstat -gcutil -t 0 即可每200毫秒连续打印300次带有时间戳的GC统计信息。
简单解释一下: -gcutil是传入的option;必选,-t是打印时间戳,是以目标JVM启动时间为起点计算的,可选;12345是vmid/pid,和我们从jps拿到的是一样的,必选;200是监控时间间隔,可选,不提供就意味着单次输出;300是最大输出次数,可选,不提供且监控时间间隔有值的话,就是无限期打印下去。
具体输出明细的解释请参考官方文档
在Vim中,我们可以通过set fencs=utf-8,gbk告诉它按照先utf-8后gbk的顺序自动识别打开文件的字符编码。Emacs也有一组字符编码相关指令,整理如下:
指令全名:set-buffer-file-coding-system
指令作用:改变当前buffer的编码
调用方法:(以目标编码gbk为例)
C-x &RET& f gbk &RET&
该指令还可以用于改变当前buffer的换行习惯(编码参数用dos或unix,对应\r\n或\n)。
指令全名:universal-coding-system-argument
指令作用:指定紧随其后的命令(如C-x C-f或C-x C-w等)所采用的编码
调用方法:(以目标编码gbk为例)
C-x &RET& c gbk &RET&
指令全名:revert-buffer-with-coding-system
指令作用:用指定编码重读当前buffer(如果打开时用错了编码)
调用方法:(以目标编码gbk为例)
C-x &RET& r gbk &RET&
指令全名:recode-region
指令作用:将以错误编码解码的选区以指定编码重新解码
调用方法:(以目标编码gbk为例)
M-x recode-region &RET& gbk &RET& utf-8 &RET&
除了这些操作外,还有一种方法,那就是在文件开始的部分给出如下形式的指令,直接告诉编辑器在打开和保存时应采用的编码:
-*- coding: gbk -*-
接触过Python的同学是不是觉得很眼熟?
[补充] 查看当前编码选择的命令为:
M-x describe-coding-system &RET& 或 C-h C &RET&
也可在回车前输入具体的编码名称(如gbk)以查看详细说明。
为克服拖延症,在此列出2011年要做的事,给未来的自己监督:1- 继续去年未完成的产品改造,换一种更稳妥的方式推进;2- 积极参与社区交流活动,不论线上还是线下;3- 深入学习Scala,辅以Clojure和Haskell;4- 系统学习PostgreSQL;5- 全面使用Emacs;6- 开始读Linux源码;7- 重读《红楼梦》;8- 至少读两本英文原著;9- 带儿子回一趟老家。就这样。
回想大学时代,我第一本从头到尾读完的英文小说是Arthur Hailey的《Airport》,是在大学门口的外研书店买的。当时的想法很单纯:要证明自己的英文阅读水准不差,光靠新闻、应用文和专业文献是不够的,小说占到native speaker们日常阅读相当大的比重。如果能够啃下一本真正原汁原味的长篇小说,至少说明他们读什么,我也能读、能欣赏。最终花了一个礼拜,利用课余时间读完了。这个看似不起眼的"成就",对我自信心的建立至关重要。没有这个打底,后面恐怕也不会在知识"原始积累"的时候能有心有力去大量地阅读原版或影印版的书籍,并且往往能比别人更准确地把握原著要表达的意思。这个阅读小说原著的习惯一直延续到今天,量虽然不大,一直都有意识地在进行,比如前几年的《The Da Vinci Code》,这几年的《Animal Farm》、《1984》、《Twilight》等等。就个人成长而言,光靠别人给你压力,给你找突破点是不够的。经验告诉我,影响自己最深远的,通常是那些自己给自己找的突破点。你能走多远,成为什么样的人,很大程度上取决于你自己。
相信大家对SOA这个词并不陌生,很多企业都在讲我们要上SOA,也有很多企业在呼应:我们能帮忙。但究竟什么是SOA,SOA能做什么,如何在企业中推行SOA,采用什么样的技术,这些都是摆在我们面前的现实问题。我为什么会对这本书产生兴趣?这要从我的工作说起。我从2004年开始投身到企业软件开发当中,具体而言,就是医院管理信息系统(HIS)。我们的团队,一直都是扮演独立软件厂商(ISV)的角色,为国内大中型医院提供高品质的HIS产品和服务。
接触过医院信息化的朋友都知道,医院对软件的需求是多方面的,通常很难有一家厂商能够提供从ICU/CCU、LIS、RIS/PACS到财务软件的全线产品和服务。而HIS在所有这些系统中,管理着医院的核心运营,贯穿医院业务的各个环节,经常需要和第三方系统进行通信。如何能更好地集成医院的各类资产,为医院这个特殊的企业提供优质的服务,与医院一同成长,就成为我们关注的焦点。
作为独立软件厂商,同时也考虑到医院的实际承受能力,我们很难说服自己和医院接受那些闭源SOA大厂高昂的产品服务价格。同时,出于对灵活配置和伸缩性的要求,我们在一开始就把目光锁定在开源产品上。
SOA为我们提供了架构设计丰富的营养和施展拳脚的平台。这本书要带给大家的,正是如何用开源的产品实现完整的SOA。在这个过程中,作者为我们分析了SOA的方方面面,对每个环节采用的技术都做了大量翔实的评估和介绍,对每个关键点都给出了详细的说明和完整的源代码。
如果你是企业主管、业务专家,相信你读完本书,会对SOA有更清楚的认识,对SOA能为你的企业带来什么样的价值会有更深的理解和体会。
如果你是架构师或程序员,相信你也和我一样,在阅读完本书之后,能更明白SOA的本质,掌握实际开发SOA的技能,懂得如何在企业或现有系统中引入SOA的思想。
得知博文视点引进并准备翻译这本书时,网上传来了质疑的声音,认为开源加上SOA,受众太小。我却不这么看。中国的软件产业,并非只剩下互联网和外包,除了叫得出名字的大公司,有大量中小型的独立软件厂商在暗自努力,他们在网上的曝光率很低,但都在踏踏实实地做事。他们是可爱、可敬的一批人,做的是幕后支撑企业运营的重要产品和服务,这本书也是为他们准备的。
在这里,我要特别感谢武汉博文视点的周筠老师,给我翻译本书的机会,与她相识是我的荣幸。感谢莫锡昌、成硕为审稿付出的宝贵时间。感谢编辑卢鸫翔、刘唯一和其他所有幕后工作者的辛勤劳动。
我还要感谢在事业上给予我重要帮助的两个人:毛颖和林勇,是他们的信任和鼓励让我走到今天。
最后要感谢我的家人,为了让我安心翻译这本书,牺牲了太多的周末和假期,谢谢你们对我的包容和支持,无私的爱当然要用无私的爱来回报,我爱你们!
在本书的翻译过程中,译者虽已尽力确保专业术语符合中文读者的习惯,也尽力将原著的真实意图以符合中文习惯的方式呈现给大家,毕竟水平有限,书中的问题和疏漏之处在所难免,恳请各位读者朋友批评指正,联系邮箱:gaoyuxiang.soa&#64;gmail.com。
2010年11月于上海
2010年即将过去,在这一年里,我做了什么?找到自我了吗?我问自己。
整个2010年,我大概有这么几件事值得一提:
&&&&首先,我去年向公司争取的产品改造计划,执行到中段,可耻地失败了;
&&&&其次,我有幸参与翻译了两本外版IT书:和,目前两本书均已上架;
&&&&再次,我下了很大的决心转行做出版,最终也可耻地失败了。
不过,我找到自我了吗?我想是的:命中注定做架构师。
这两天在读周国平的《人生哲思录》:
&#8220;古希腊哲人赫拉克利特说:&#8216;一个人的性格就是他的命运。&#8217;这句话包含两层意思:一,对于每个人来说,性格是与生俱来、伴随终身的,永远不可摆脱,如同不可摆脱命运一样;二,性格决定了一个人在此生此世的命运&#8230;&#8230;赫拉克利特的名言的真正含义是:一个人应该认清自己的天性,过最适合于他的天性的生活,而对他而言这就是最好的生活&#8230;&#8230;为别人对你的好感、承认、报偿做的事,如果别人不承认,便等于零。为自己的良心、才能、生命做的事,即使没有一个人承认,也丝毫无损。&#8221;
我想,这就是为什么人在抉择时要追随自己的内心和天性,因为这才是对自己最合理的安排。
这篇日记写给自己,发散式+意识流,赶时间的朋友请放心略过。进入2010年,头1个月,到今天是最后1天,家里的事暂不去提,这个月是我参加工作以来感觉最累的1个月。加入现在的这支创业团队4年,我们的产品,从无到有(1.0),到现在的2.x,倾注了很多人的心血,我们的团队,在后面的这2年多,也经历和见证了很多伤感的离别,"元老"级的人物,已经屈指可数。万事不能尽如人意,能有今天的结果,也许应该庆幸而不是哀伤,至少产品还在,至少团队还在运转。但未来会如何,谁也无法预知,我也不敢多想,我只知道,工作带给我的幸福感,正在不停的创造新低。创业型团队的一大特点,就是一个人要同时承担多个角色的职能,而且比其他团队更容易受到外部环境的冲击。为了生存,有时不得不做一些不相关的项目、不相关的事,活下来比什么都重要,你东西再好,技术再牛逼,没人要就是没人要,没资源就是没资源,哭爹喊娘,没人理你。去年年底,我们的老板带着我们一起,并入了现在这家集团公司,一个比我们大得多的团队。按说这应该是好事,我们也不必像原来那样担惊受怕,但事实证明这个想法非常天真,外部环境的冲击比以前更加真实的发生在我们身边,无时无刻不在提醒我们它的存在。以前我们卖力做事,一狠心一咬牙一跺脚,创业嘛,为整个团队付出,咱认了;现在我们卖力做事,不知道是在为谁付出:累的是你自己,收获的是别人。从创业的角度来说,我们失败了,我们为创业比别人多付出的那些汗水,除了回忆,现在看,也只有回忆了。对我们这个小团队来说,情形依然是:为了生存,有时不得不做一些不相关的项目、不相关的事,活下来比什么都重要,你东西再好,技术再牛逼,没人要就是没人要,没资源就是没资源,哭爹喊娘,没人理你。有人说,其实哪里都一样。这样想可能会让人更快乐,不那么痛苦,但我很清楚,这是精神毒药,如果事实果真如此,那就不仅仅是令人伤感苦闷,而是彻彻底底的让人绝望了。物以类聚,人以群分。我的群在哪儿?
周二的时候拿到了新的T400s,安装Linux(64位Karmic)的过程比预想的要曲折,趁周末有时间,整理记录于此,希望对遇到同样问题的人有所帮助。
T400s送到之前,我先简单的在网上查了查基本的配置信息,同时也看到有很多网友十分顺利的安装了Linux,不论是Ubuntu Karmic还是Arch Linux,基本都是除了指纹都是out-of-box就可直接工作的,这也符合我的预期。于是,拿着amd64版的Karmic盘,开始在T400s上安装。
基本系统的安装很顺利,跟着提示一路走完,Karmic就能够boot并正常login了,so far so good。不过登录进去之后,无线网卡不工作,只有有线连接,这是怎么回事?用lspci一看:
00:00.0 Host bridge: Intel Corporation Mobile 4 Series Chipset Memory Controller Hub (rev 07)
00:02.0 VGA compatible controller: Intel Corporation Mobile 4 Series Chipset Integrated Graphics Controller (rev 07)
00:02.1 Display controller: Intel Corporation Mobile 4 Series Chipset Integrated Graphics Controller (rev 07)
00:03.0 Communication controller: Intel Corporation Mobile 4 Series Chipset MEI Controller (rev 07)
00:03.3 Serial controller: Intel Corporation Mobile 4 Series Chipset AMT SOL Redirection (rev 07)
00:19.0 Ethernet controller: Intel Corporation 82567LM Gigabit Network Connection (rev 03)
00:1a.0 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #4 (rev 03)
00:1a.1 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #5 (rev 03)
00:1a.2 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #6 (rev 03)
00:1a.7 USB Controller: Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller #2 (rev 03)
00:1b.0 Audio device: Intel Corporation 82801I (ICH9 Family) HD Audio Controller (rev 03)
00:1c.0 PCI bridge: Intel Corporation 82801I (ICH9 Family) PCI Express Port 1 (rev 03)
00:1c.1 PCI bridge: Intel Corporation 82801I (ICH9 Family) PCI Express Port 2 (rev 03)
00:1c.2 PCI bridge: Intel Corporation 82801I (ICH9 Family) PCI Express Port 3 (rev 03)
00:1c.3 PCI bridge: Intel Corporation 82801I (ICH9 Family) PCI Express Port 4 (rev 03)
00:1d.0 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #1 (rev 03)
00:1d.1 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #2 (rev 03)
00:1d.2 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #3 (rev 03)
00:1d.7 USB Controller: Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller #1 (rev 03)
00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev 93)
00:1f.0 ISA bridge: Intel Corporation ICH9M-E LPC Interface Controller (rev 03)
00:1f.2 SATA controller: Intel Corporation ICH9M/M-E SATA AHCI Controller (rev 03)
00:1f.3 SMBus: Intel Corporation 82801I (ICH9 Family) SMBus Controller (rev 03)
03:00.0 Network controller: Realtek Semiconductor Co., Ltd. Device 8172 (rev 10)
04:00.0 Memory controller: Intel Corporation Turbo Memory Controller (rev 11)
05:00.0 SD Host controller: Ricoh Co Ltd Device e822 (rev 01)
05:00.1 System peripheral: Ricoh Co Ltd Device e230 (rev 01)
注意03:00.0这一行,竟然是Realtek的8172,对应Windows下的型号是8192,Lenovo什么时候把T400s原本的无线芯片配置(Intel WiMAX/WiFi Link 5xxx)换成了Realtek 8172的?难怪没有无线,这块网卡的驱动还没有被Linux内核直接支持,需要手工安装。网上搜到很多方案,比如ndiswrapper+win版驱动等等,但这个Ubuntu LaunchPad上的方案[1]看上去最靠谱,还等什么,开工吧。
首先下载驱动: http://launchpadlibrarian.net//rtl8192se_linux_2.6.09_64bit.tar.gz
解压后进入rtl8192se_linux_2.6.09_64bit子目录,make
成功后在HAL/rtl8192下能够找到r8192se_pci.ko文件,cp至/lib/modules/`uname -r`/kernel/drivers/net/wireless/
然后将固件相关文件,即firmware/RTL8192SE目录,cp至/lib/firmware/`uname -r`/
执行命令depmod -a重新扫描module依赖关系
执行命令modprobe r8192se_pci加载该module,这一步完成以后NetworkManager就可以搜到无线网络并进行连接了
好了,到此最大的拦路虎已经消灭,开始装别的软件吧。就在这时,新的问题出现了,dmesg可以看到重复出现的如下报错/警告信息:
[ 483.431670] DMA: Out of SW-IOMMU space for 9100 bytes at device .0
[ 483.435783] DMA: Out of SW-IOMMU space for 9100 bytes at device .0
[ 483.439867] DMA: Out of SW-IOMMU space for 9100 bytes at device .0
通常经过数分钟的积累,系统就死机了,只剩下CapsLock键上的小灯不停闪烁,典型的kernel panic? 根据网友提供的线索[2],由于Intel的64位实现虽是参考AMD64但细节有差异,所以在实际内存大于等于4G的环境下,某些未考虑到该特性的代码会leak memory,怎么办?增加内核启动参数mem=4G iommu=off,一方面限定内存范围,另一方面关闭IOMMU。
经过测试,这样的配置下系统和无线网络都能够持续稳定运行。系统安装到此,一个大的里程碑达成,唯一的缺憾是每次kernel升级,可能还需要手工做一些工作,且暂时不能既开无线又开4G以上内存,不过这样总好过没有无线,或者缴枪投降,不是吗?本想多骂几句Lenovo,好好的Intel芯片不用,要换Realtek,但转念一想,这已是既成事实,而且看到很多其他型号的ThinkPad,如R500、SL400等,也有不少用上了Realtek的这款无线芯片,让我们一起期待稳定可靠的驱动最终被Linux内核支持的那一天吧。
到处吹嘘Scala很长时间了,却一直没有在自己的blog上增加过任何相关内容,今天就来补一补。当然,Scala的基本特性就不多废话了,网上已经有相当多的资料,如果懒得google,只想看完本文,那么你只需要知道它是一门以JVM为目标运行环境的静态编程语言(当然,Scala官方也有.NET版,但已不是其重点),同时具备面向对象和函数式编程语言的特点。本文将通过一个简单的示例,展示Scala的FP能力,其中十分heavy的用到了implicit隐式转换和模式匹配。
代码是从guithub的gist上抄的,简单改了改,原始代码见:
import&scala.reflect.Manifest
def&simpleName(m:Manifest[_]):String&=&{
&&val&name&=&m.toString
&&name.substring(name.lastIndexOf('$')+1)
final&class&Succ[Pre&:Num]&extends&Num
final&class&_1&extends&Num
type&_2&=&Succ[_1]
type&_3&=&Succ[_2]
type&_4&=&Succ[_3]
case&class&Move[N&:Num,A,B,C]()
implicit&def&move1[A,B,C](implicit&a:Manifest[A],&b:Manifest[B])&:&Move[_1,A,B,C]&=&{
&&System.out.println("Move&from&"&+&simpleName(a)&+&"&to&"&+&simpleName(b))
implicit&def&moveN[P&:Num,A,B,C](implicit&m1:Move[P,A,C,B],&m2:Move[_1,A,B,C],&m3:Move[P,C,B,A])&:&Move[Succ[P],A,B,C]&=&null
def&run[N&:Num,A,B,C](implicit&m:Move[N,A,B,C])&=&null
case&class&Left()
case&class&Center()
case&class&Right()
run[_3,Left,Right,Center]
这段代码解决的是经典的汉诺塔问题,通过一根中柱,将左柱上64个大小依次叠加的圆盘全部移动到右柱,要求整个过程中每次只能移动一个圆盘,且每个圆盘只能独立占据一根柱子或是叠加在比自身更大的圆盘上。
简单分析一下就知道,这是一个递归问题(FP的英雄特长):
当只有1个圆盘时,不用通过中柱,直接可以从左柱移动到右柱;
当有2个圆盘时,将小盘移动到中柱,剩下的大盘移动到右柱,再从中柱把小盘移动到右柱;
当有3个圆盘时,先移动2个圆盘到中柱,再移动大盘到右柱,再移动2个圆盘到右柱;
很容易发现一个pattern,那就是移动N(N&1)个圆盘,可以通过以下三个步骤:
移动N-1个圆盘,从左柱到中柱;
移动剩下的1个圆盘,从左柱到右柱;
移动N-1个圆盘,从中柱到右柱。
在解释代码之前,先说说Scala的implicit隐式转换,这是一个非常powerful的idea,当Scala编译器发现类型不匹配,它不会直接fail,而是尝试从代码中指定的,在scope内的implicit转换定义,来替换问题对象或表达式以满足类型要求,当然,为了避免歧义,同一时刻Scala需要找到唯一的一个满足条件的implicit定义。
我们的代码首先定义了一个取得友好类名的方法,不去深究它,然后定义了一个正整数的序列,也不去深究它了,你只需要当作他们是正整数就好,接触过FP的同学应该对此类定义不陌生,接下来定义了如下3个支持implicit传入参数的方法:
implicit def move1[A,B,C](implicit a:Manifest[A], b:Manifest[B]) : Move[_1,A,B,C]
implicit def moveN[P&:Num,A,B,C]( implicit
m1:Move[P,A,C,B],
m2:Move[_1,A,B,C],
m3:Move[P,C,B,A]
) : Move[Succ[P],A,B,C]
def run[N&:Num,A,B,C](implicit m:Move[N,A,B,C])
含义分别是:
当需要Move[_1,A,B,C]值时,可以找我帮忙(我有个side-effect,那就是输出移动指令);
当需要Move[Succ[P],A,B,C]时,可以找我帮忙;
运行我,我接受Move[N,A,B,C]类型的参数。
简单说明:Scala用[]表示类型参数,区别于Java的&&,另外,Scala的类型声明在变量/函数定义之后。Move[_,A,B,C]的含义是通过C,从A移动圆盘到B。
我们来模拟运行一下,为了演示效果,用一个中等复杂的数目,3个圆盘,从Left移动到Right。
run[_3,Left,Right,Center],对应Move[Succ[_2],Left,Right,Center],于是展开成三个Move:
Move[_2,Left,Center,Right]即Move[Succ[_1],Left,Center,Right]
Move[_1,Left,Right,Center]
Move[_2,Center,Right,Left]即Move[Succ[_1],Center,Right,Left]
然后继续展开Move[_2,Left,Center,Right]和Move[_2,Center,Right,Left],得到:
Move[_1,Left,Right,Center]
Move[_1,Left,Center,Right]
Move[_1,Right,Center,Left]
--------------------------
Move[_1,Left,Right,Center]
--------------------------
Move[_1,Center,Left,Right]
Move[_1,Center,Right,Left]
Move[_1,Left,Right,Center]
这个时候已经全部都匹配Move[_1,A,B,C],于是我们很容易得到以下输出:
Move from Left to Right
Move from Left to Center
Move from Right to Center
Move from Left to Right
Move from Center to Left
Move from Center to Right
Move from Left to Right
希望本文能带给Scala初学者一些感性认识和启发。
前两天看到网友关于铁观音泡法的留言,接受建议,收了只手绘青花瓷盖碗,和一只公杯:
家里还有些去年的秋茶存货,开始练习用盖碗,右手的拇指、食指和中指被烫到不行,据说烫出一层老茧就好了,我觉得应该不至于那么夸张,慢慢来,挺好玩的。
最近围观一本JavaScript的书籍引发的争论,一不小心碰到一篇讲编程语言类型系统划分的帖子,回想起当年还在公司内部的Tech Session上主讲过这个话题,不过只涉及到静态/动态、强类型/弱类型,远没有这位仁兄总结的那么全面。
不多废话,直入正题:
[维度一] Static vs Dynamic Typing
静态类型和动态类型,区分的关键点为编译期或运行期确定类型:静态类型在编译期确定,动态类型在运行期确定。
静态类型代表 Java、Scala、Haskell
动态类型代表 Ruby、Python、Erlang
[维度二] Strong vs Weak Typing
强类型和弱类型,区分的关键点为运行时是否自动转换到与实际类型不符的类型:强类型要求手工类型转换,弱类型自动转换。
强类型代表 Java、Scala、Python
弱类型代表 C、Assembly、JavaScript
[维度三] Latent (Implicit) vs Manifest (Explicit) Typing
隐式类型和显式类型,区分的关键点为是否要在源码中声明类型:隐式类型不需要,显式类型需要。
隐式类型代表 Haskell、Erlang、Python
显式类型代表 C、C++、Java
[维度四] Nominal vs Structural Typing
名义类型和结构类型,区分的关键点为类型判定是根据标称还是根据内容:名义类型根据标称,结构类型根据内容。
名义类型代表 C、C++、Java
结构类型代表 Haskell、Erlang、Python
关于JavaScript书籍的争论,请移步如下网址:
1- 网友Hax的"炮轰"帖
2- 周爱民(aimingoo)的MSN空间
3- 火星常驻JE办事处相关帖
这个周末又去收了一只紫砂,这下除了龙井,我平常喝的所有品种的茶,都有了各自专用的壶。贴个"全家福":
从左到右依次为铁观音、高山乌龙、熟普洱和生普洱。目前只有第一只养成了,其余的三只还得多花些时间和心思呢。
我曾经一度是Twitter重度用户,直到上周五,&#64;laogao这个账号,被Twitter无端禁言了。被禁时,我follow 133人,被241人follow,共2260历史推。
Follow我的推友们,以及我经常&#64;的兄弟姐妹,抱歉了,尤其那些被我"忽悠"上Twitter的朋友。当你访问时,暂时只能看到"该用户被suspend"的信息。这并非是因为我是一个spammer,或者有什么所谓的"strange behaviour",至于被封的具体原因,直到今天,Twitter仍没有给予正面答复。
作为一个喜欢Twitter的简单、直接的人,我从来都很认真的对待我的每一推,尊重他人的意见,善待每个&#64;我的朋友,也在现实世界的朋友圈中推荐大家使用这个服务。但从今天开始,我将不再向人推荐Twitter,也无法确定什么时候&#64;laogao这个账号能够被reinstate或者再次被suspend,各位关注我的朋友,请移步。
对那些仍然天真的以为Twitter封号肯定是用户自己的问题的朋友,我很理解你们的想法,当初我看到别人被封时,也是这么想的。
各位推友保重!
周末去败了只全新的紫砂回来,宜兴红泥的,专门用来泡台湾高山乌龙。用之前,当然少不了开壶的过程,虽然比较耗时,但也挺有趣,在这里做个记录。
首先将新买的紫砂壶用清水洗净,浸泡一整天,其间换水2~3次,去除掉残余的泥土、灰尘等。然后放入锅内,加入清水煮上1个小时,继续消毒。
接下来,放置一边,冷却后,取老豆腐半块,放入清水中,再煮1小时,去火气。
第三步,用清水洗净,根据今后用这个紫砂壶泡什么类型的茶,选用单次用量的2~4倍茶叶,加清水,煮沸后,待茶汁完全浸出,捞起茶叶残渣,继续用茶汤煮上1个小时。
这是成品,可以正式开始享用了:
现在开始,从头养我的第二只紫砂,细细体味那份安静和从容,让每日忙碌和疲惫的身心,有一个休息和平复的空间。
各位童鞋们过节好啊,今天给大家带来的是在bash中DIY制表符键自动补全。
bash是大多数主流Linux发行版的默认shell,如果你用过bash,那么一定会接触到&tab&键自动补全的这个方便的功能,当你一个命令的头几个字符敲下去,按下&tab&,如果以此开头的命令只有1个, bash会直接帮你补全,如果有多个,则会有相应提示,而在后续的参数输入时,也会带有默认的自动补全文件路径的功能。当你习惯了&tab&,很难想象没有自动补全的日子会是什么样子。
bash默认支持常见的补全功能,如可执行命令、文件路径等,如果安装了bash-completion包,甚至连chown, man, svn, ssh这些也会带有相应的自动补全提示,而不是单纯的文件路径补全。好奇的你一定想知道是怎么实现的吧,其实很简单,我们举个例子来说:
假定你有一个命令,叫做abc,它又有自己的子命令,分别是build_all、compile和update,其中compile这个子命令需要的参数必须来自project.list这个文件中列出的值,怎么实现&tab&自动补全,让bash知道abc的合法子命令和compile子命令的合法参数列表呢?
在你的~/.bashrc或者任何一个启动bash时会被执行的文件中加入下面的代码:
function _abc() {
&&&&COMPREPLY=()
&&&&local cur=${COMP_WORDS[COMP_CWORD]};
&&&&local com=${COMP_WORDS[COMP_CWORD-1]};
&&&&case $com in
&&&&'abc')
&&&&&&&&COMPREPLY=($(compgen -W 'build_all compile update' -- $cur))
&&&&&&&&;;
&&&&'compile')
&&&&&&&&local pro=($(awk '{print $1}' project.list))
&&&&&&&&COMPREPLY=($(compgen -W '${pro[&#64;]}' -- $cur))
&&&&&&&&;;
&&&&&&&&;;
&&&&return 0
complete -F _abc abc
手动载入一下,或者重启bash,再敲abc命令,即可自动补全子命令,如果子命令是compile,还能自动补全相应的参数值。我们来简单分析一下这段代码。首先我们定义一个function _abc,这个函数先清空自动补全列表,根据当前输入位置前一个token判断目前需要自动补全的语境,如果是abc,则将自动补全内容设置为'build_all'、'compile'和'update',如果是'compile',则将project.list文件内容输出到补全列表,当然,这里我们也可以换成其他任何必要的方式。最后我们通过complete -F _abc abc将这段自动补全逻辑注册到abc这个主词上。这样当我们敲abc时,后续内容就能自动补全了。
本来是手写的一张草稿,清理台面的时候,正要扔,发现内容还有点意思,干脆吧,开个随笔记录一下,备忘,说不定还能帮到别人。在我们日常生活和工作中,尤其浏览含有中文的网站,时常会为乱码问题犯愁,一些天生Unicode支持不到位的编程语言和软件更是加重了这个现象。虽说已经是2009年了,我们时不时还是能碰到一些明显脑残的code,吐出一堆乱码,不论你用选什么字符集,似乎都无法还原最初的中文。比如"?·?",或者同一个页面,无法用同一个字符集显示,任何一种字符都至少有一部分显示不正确,不是这儿就是那儿。首先科普一下UTF-8。UTF-8是Unicode所有字符编码实现中,应用范围最广的一个,最大的特点是兼容ASCII码,在此基础上,通过1..4个byte来表示每一个Unicode字符。它是怎么做到的?其实很简单,看首个byte: ~
: 0~127 (ASCII, 单个byte) 表示Unicode范围\u0000 ~ \u007F ~
: (2个1打头,所以2个byte) 表示Unicode范围\u0080 ~ \u07FF ~
: (3个1打头,所以3个byte) 表示Unicode范围\u0800 ~ \uFFFF ~
: (4个1打头,所以4个byte) 表示Unicode范围\u10000 ~ \u10FFFF除了单个byte这个case,其他情况下,后续的byte均以10打头。这些打头的bit:10、110、,都不作为具体编码的一部分参与真正Unicode编码的计算。所以1..4个byte,其有效位数分别为:7、11、16、21,因此才有了\u007F、\u07FF、\uFFFF这样的临界值,且4个byte的空间还有富余。有了这个基础知识,我们就来具体看看这个"已"字,是怎么被UTF-8表示的,以及为什么处理不当的代码会最终输出"?·?"。"已"字,用Unicode表示法,是\u5DF2,按照2个byte拆开成二进制表示:10010如果用UTF-8编码,采用1110???? 10?????? 10??????这个格式,?号部分填入上述10010,得到10 这样3个byte。好了,这个时候脑残代码出现,假如它不认识UTF-8,那么他会看到这样3个前后没有关联的byte,在西欧Latin-1字符集(即ISO 8859-1)中,它们分别表示"?"、"·"、"?"这3个字符。如果把它们分别再按照UTF-8编码,就成了:10 10完美的UTF-8编码,只不过,这完全是假象,只能通过非常规手段才能恢复它原本的样子。
上周在北京参加QCon大会,回来以后一直没有成块的时间,把一些之前只是通过Twitter分享出来的信息汇总整理出来。已经有不少朋友都在各自的博客上记录了大会感闻,所以我还是抓紧些吧,不然真要成没有营养的废话了。QCon这次是首次进入中国,我有幸得以抽身参加。大会为期三天,由InfoQ中文站组织策划,包括
在内InfoQ中文站只有3个人,这是我之前没有想到的,感谢他们和志愿者的辛勤劳动,使得这次大会得以顺利举行。大会嘉宾包括了国内外许多大名鼎鼎的牛人,比如前TSS和后来InfoQ的主创Floyd Marinescu、ThoughtWorks首席科学家Martin Fowler、Spring之父Rod Johnson、eBay架构师Randy Shoup、《硝烟中的Scrum和XP》作者Henrik Kniberg、《大道至简》作者周爱民、支付宝数据库架构师冯大辉
等。大会第一天,上午和下午都安排了Martin Fowler的session,分别是DSL和Ruby,虽然内容算不上特别新,但幻灯片准备很到位,而大师就是大师,思路清晰,言简意赅,尺度分寸拿捏的很好。唯一感觉可能是大家都比较含蓄,或者热身不够,沟通和对话的环节显得不是很活跃,除了买书签名的,到后面Dojo的创始人Dylan Schiemann讲Open Web,气氛就开始暖和一些了。至于Amazon的Jeff Barr,不少朋友也都说了,基本上就是来打广告的,可惜QCon北京的赞助商中,没有发现Amazon。晚上的沙龙,国际讲师唱主角,分享关于软件开发趋势的见解。大会第二天,可能是最精彩的一天,尤其是下午
主持的网站架构这个Track。上午的大戏是Rod Johnson,不过也许是大家期望比较高,反倒是没觉出特别出彩的地方,Rod特别强调Simplicity和Lean,同时通批了一顿Java EE,不过把Tomcat作为simplicity和lean的代表举例,似乎大家都不是特别买账。后来才知道原来Tomcat 90%的commit都来自SpringSource,而稍后SpringSource会在此基础上推出功能更丰富的Spring tc server,以及(我猜)Spring 3.0。接下来eBay的Randy Shoup,带来了一场颇为精彩的演讲。下午来自支付宝、豆瓣网、网易有道和优酷网的架构师们共同创造了本次QCon大会最受欢迎的半天,尤其是豆瓣的
,内容相当务实、精彩、有货。说到这,插句题外话,如果没记错,
是这次大会中唯一一位使用Mac的国内讲师,国际讲师用Mac的好像也只有Martin Fowler (如果说的不对还请同学们指正)。到了这个时候,国际讲师们渐渐淡出,场子交给了国内的讲师们,会场气氛也更加热闹和随意起来,包括晚上的沙龙。大会第三天,从日程上看,并没有特别重量级的session,似乎除了云还是云,不过如果因此选择不参加的话,可能就会错过了两场很不错的演讲,分别是高焕堂的"提高架构质量的10个观点"和周爱民的"我之于架构的主要观点"。高焕堂的演讲让人耳目一新,明显感觉来自台湾的专家,比大陆的专家,对中国传统文化,有更深的理解和思考,他的"序"、"容"、"易"三角,让人印象深刻,另外他还对目前我们软件产业的结构性问题提出了批评。而周爱民的演讲同样很有深度,尤其是后面说到架构师的决策作用时,很引起我的共鸣:"架构师不需要让所有人都理解你的每一个决定和这些决定最终要达成的目标,去做就是了。"(可能原话不是这么说的,但意思是这样) 也许有些朋友觉得这两个session虚幻的成分比较重,但对我来说,收益颇丰,因为很多观点,都能够在我之前经历的一些项目和设计中找到支撑。至于其他几讲,关于云计算的,确实提不起什么兴趣,尤其是Azure,干的要命(小插曲: 现场演示一段ASP.NET代码,至少修改和重编译了3次才搞定)。大公司带着推销产品/服务的目的来参加开发会议,有点浪费大家的时间和银子啊。最后说说大会的组织相对还有待提高的地方:1- 第一天入场时,志愿者的引导不是很到位,报到的地方和其他展台秩序稍显混乱。2- 同声传译据说质量不高,白天没用,但是周围的朋友在用,耳机漏音比较严重,稍影响一些效果,晚上直接通过扩音设备翻译,就很明显的感觉到脱节了。3- 午餐和沙龙的地方,显得还是小了些,而且中午所有人都集中在一个时间去,不论排队打饭还是找位置,都比较吃力,以至于第二天中午有一小撮脑筋比较活络的同学,提前下课去打饭吃。4- 现场网络环境比较差,害我只能用手机上twittai。总的来说,这次的QCon还是挺不错的,希望明年的QCon会继续在中国举办,更多的嘉宾,更丰富的内容,更有深度的主题,更加精彩。
其实很早就听说有这个东东,只是一直没玩过,最近公司调整PC服务器,正好找个空闲实战了一把。基本的配置步骤如下(时间有限,挑简单的说,假定你要Wake-on-LAN的机器是Windows,控制服务器是Linux):[被控制方]1- 正常开机进入BIOS设置2- 找到Wake-on-LAN的选项enable它(如果是Dell的机器这个选项叫Remote Wake Up)3- 进入OS,在需要配置Wake-on-LAN的网络端口的配置项中(网络连接属性-&配置-&电源管理),选择允许此设备使计算机脱离待机状态4- 记录网卡的MAC地址5- 正常关机// 如果被控制方是Linux/Ubuntu,OS的配置方法参考链接[4][控制方]1- 安装wakeonlan,可以选择(如果有的选) apt-get install wakeonlan,或者从链接[3]获取源码手工安装(是Perl写的)2- 通过wakeonlan+MAC地址的命令行方式控制需要wake up的机器,如 wakeonlan 01:23:45:67:89:AB// 更高级的用法包括编写脚本,添加到cron,以及通过-f指定一个包含多个MAC地址的文件同时操作等[基本原理]Wake-on-LAN的相关通信协议位于OSI七层模型中的数据链路层,比IP需要的网络层还要低一层,在局域网范围发送广播,数据包格式为:FF FF FF FF FF FF $MAC*16即 FF FF FF FF FF FF然后重复16次对方的MAC地址,被戏称为"magic packet"。配置成功后,只要被控制方正常关机、挂起、休眠,且环境始终不掉电,任何时候在局域网中广播"magic packet",指定网卡的机器就可以被唤醒。链接:[1] [2] [3] [4]
周末花了一个下午和一个晚上把Scott Rosenberg的Dreaming in Code从头到尾看了一遍。最直接的感受是这本书把我之前很多记忆碎片串在了一起,成为一个生动而"完整"的故事,虽然书的内容本身组织的多少还是有点散。我本人对Chandler这个项目并不陌生:之前出于对Python/wxWidget和开源本身的兴趣,陆续用过几个0.x的版本,一开始并不是十分友好,性能上也有问题,甚至会莫名的吃掉我机器上的数百兆(或者上G?)空间。后来的版本在性能和可用性上确实提高了不少,但一直感觉这个项目缺少必要的、以及许多开源项目应有的momentum。Phillip J. Eby对Chandler开发人员不懂Python的批评,当时我的印象也很深。而项目中出现的人物,包括Mitchell Kapor、Ted Leung,也都在Chandler这个范畴之外follow过。其他细节包括:Chandler和Cosmo这两个名称的由来、Chandler项目组中女性成员相对高的比例、一些熟悉的人物及其观点(Alan Kay, Bill Joy, Frederick Brooks, Donald Knuth、Linus Torvalds, Ward Cunningham, Larry Wall, Guido van Rossum, Joel Spolsky, etc.)、一些公司的分分和和以及人员流动等等。感觉挺亲切的。可能更重要、也更深刻的原因是:尽管书中一再提到"There's no such thing as a typical software project, every project is different",我仍然深深的感觉到,Chandler遇到的这些问题,其实也是我亲历的很多项目的种种问题的一个缩影。对这些问题,作者并没有给出解决方案,其实谁也没有标准答案。软件开发是一项非常具有挑战性的工作,也正是像我们这样有热情、有涵养的专业人士生存的空间和价值所在。
以下是一段视频,Ward Cunningham针对Debt Metaphor这个隐喻的由来和人们对它的一些误解进行了澄清:我最感兴趣的是Burden这一段:Cunningham解释说,经常看到有些开发团队,他们快速的开发出软件产品推向市场,不断的往里面添加新的特性,在这个过程中,不断的学习,但从不把学到的东西、总结的经验教训应用回去,这就像是从银行借贷,但从不想着有一天需要偿还(是不是有点像是在说引发这次次贷危机的美国人的消费习惯和观念?),到最后,你所有的收入都将用于偿还利息,你的购买力也将降为零。映射回软件开发的语境,如果我们在一个较长的时间跨度内,开发一个软件,不断的增加feature,但从不回过头去整理和重构,把对这个软件和这些特性的新的理解和认知写回去,那么最终这个软件、这些feature将不再会有任何实际的价值,对它们做任何事,都将花费越来越多的时间和精力,开发进度也就因此下降为零。
经历了有史以来最忙碌的一周,当然要好好放松一下,除了听上乘的古典音乐,沏上一壶上等的乌龙细细品味,也是一种享受。乌龙茶和紫砂壶可是绝配,如果是安溪的铁观音,加上做工精良的宜兴紫砂壶,那滋味,唇齿留香,别提多惬意了。好的紫砂壶是需要"养"的,今天专程去茶城败了一只回来,开始"喂"铁观音,哈哈。
先简单介绍一下问题的语境。手头有个开发了3年的Spring+iBATIS应用(经典三层架构),最近尝试用Hibernate实现原有SQLMap版的部分CRUD操作。除开混合事务和其他一些底层配置细节(如TransactionAwareDataSource、禁用lazy-load等)之外,最大的一个"pattern-mismatch"是:Model层和持久层采用了dirty flag机制,即每次INSERT和UPDATE操作,都会根据每个字段的dirty与否决定是否参加INSERT/UPDATE,而这些dirty flag可以被外部重置,所以业务层的代码,经常可以看到类似这样的pattern:1- new一个model类并setId() (或者在已有的model上重置dirty flag)2- set需要update的字段(通常都只是部分字段)3- 丢给DAO层去update最终的效果是某张表某个ID的某条记录的某些字段被更新了,而其他字段不受影响,这就是我在标题中提到的所谓"暴力"update,虽不elegant,但却也简单实用,至少很"直接"。为了让Hibernate版的DAO(默认除Trasient之外全体字段参加INSERT和UPDATE)继续支持这样的"use-pattern",除了按照Hibernate的习惯去配置映射和SessionFactory等之外,我们需要做一些额外的工作:1- 在BO/Entity类上追加注解&#64;org.hibernate.annotations.Entity(dynamicInsert=true, dynamicUpdate=true)2- 实现org.hibernate.Interceptor接口的findDirty()方法,Hibernate提供了一个EmptyInterceptor可以作为起点,方法签名如下:public int[] findDirty(
Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types);返回的int[]包含所有应该被认为是dirty的字段索引,返回null表示默认处理,传入的entity是持久对象,字段列表会通过propertyNames参数传给你。3- 注入到Spring的Application Context中,类似这样:&bean id="findDirtyInterceptor" class="gao.sean.hybrid.interceptor.FindDirtyInterceptor"/&&bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"&
&property name="entityInterceptor"&&ref bean="findDirtyInterceptor"/&&/property&
&/bean&在这样的配置下,业务层的代码就可以继续"暴力"update了。
如果你使用早前版本的Spring,又恰好采用了Annotation注解方式(而非传统XML方式)配置Hibernate对象关系映射,那么在通过org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean配置sessionFactory时,你一定对annotatedClasses、annotatedPackages有一种说不出的胸闷的感觉,如此以高配置性见长的Spring,怎么在这一个小小的环节上就不能做得再灵活些呢,一定要一个个手写Class路径么?估计有不少人无奈选择了从AnnotationSessionFactoryBean继承一个自定义的子类,自己实现扫描逻辑,找出&#64;Entity注解过的类清单配置进去。Spring 2.5.6里有个不怎么起眼的改进,那就是在AnnotationSessionFactoryBean上增加了一个新的方法:有了这个方法,我们不再需要自己动手去实现实体类的扫描了,直接在Spring配置文件中AnnotationSessionFactoryBean这个section上增加类似如下的一个property即可(假定你需要加载的实体类所在的包名match这个字符串"com.**.bo"):&property name="packagesToScan" value="com.**.bo"/&你也可以以清单的方式指定多于1条的匹配字串,如:&property name="packagesToScan"&
&value&com.abc.core.bo&/value&
&value&com.abc.auditing.bo&/value&
&/list&&/property&
Pylons是一个典型的MVC Web框架,在之前的几篇随笔中,我们一起了解了Pylons的安装、默认项目结构、Routes和controller("C")以及SQLAlchemy("M"),在这个系列的最后,我们一起来看看"V"。在我们的controller代码中,每个action在return的时候,可以选择:1- 直接return字符串2- 通过render()函数将输出交给页面模板引擎处理3- redirect_to()重定向到其他URL直接return太简单,redirect_to也没有特别需要介绍的,重点看render()。如果你一直follow这个系列,那么在你的controllers/hello.py中,可以看到这样一行import:from newapp.lib.base import BaseController, render从lib.base引入了一个render函数,跟到lib/base代码里查看,我们会知道:from pylons.templating import render_mako as render其实我们用到的render()函数,是pylons.templating.render_mako的别名。注: 这里假定你在paster create时选择了默认的mako,其他Pylons原生支持的页面模板引擎还有结构相对更层次化的Genshi和更接近Django实现的Jinja。render_mako()函数签名如下:render_mako(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None)最基本的用法是给出template文件名,然后通过extra_vars传入参数,Pylons默认查找页面模板文件是在项目的templates子目录,这个路径也可以在config/environment.py中修改。在Pylons中,被推荐的传参做法是使用tmpl_context,在生成controller的时候,已经自动import了tmpl_context并别名为c,这样,我们只需要在c上绑上我们需要传递给模板的数据,模板在解析时,也就能够通过c得到这些数据了。像这样:c.name = u'Pylons n00b'return render('hello.mako')然后,在hello.mako中:&h3&Hello &b&${c.name}&/b&!&/h3&在模板代码中,有些Pylons系统变量/函数是可以直接访问的,包括:tmpl_context (c) - 用于controller和template传递数据config - 配置信息app_globals (g) - 应用的全局变量h - WebHelpers,包括h.form、h.link_to、h.url_for等等实用函数request - 请求response - 应答session - 会话信息translator、ungettext()、_()、N_() - 处理国际化除了基本的${}变量替代语法之外,类似JSP,Mako还支持注释、if/else/for控制逻辑、代码片段、return、标签等,具体的可以扫一眼官方说明:很精简,也非常容易理解,这里就不详细说明了。至此,我们已经了解了Pylons最核心的几个组件,足够我们搭建常规的Web应用了。其他值得大家继续挖掘的内容包括:国际化、表单验证(FormEncode)、用户验证和权限(AuthKit、repoze.who、repoze.what)、AJAX、Python 3.0、WSGI基础架构等。本文是该系列最后一篇,希望通过简单的介绍和学习,大家能够喜欢并顺利的上手Pylons,也希望越来越多的人关注这个优秀的Python Web应用框架。
在前面的4篇随笔中,我们简要的介绍了SQLAlchemy,不过SQLAlchemy如何被集成到Pylons应用中呢?首先我们看一下自动生成代码中的model子目录,其中有两个文件__init__.py和meta.py,其中meta.py定义了engine、Session和metadata三个公用变量,而__init__.py提供了一个核心的init_model(engine)方法,该方法分别将数据库engine和经过sessionmaker和scoped_session包装的Session对象植入到meta中,像这样:
sm = orm.sessionmaker(autoflush=True, autocommit=True, bind=engine)
meta.engine = engine
meta.Session = orm.scoped_session(sm)这样一来,整个背后的"magic"就还剩下最后一块"拼图":谁来把engine初始化好并调用init_model方法呢?看看config/environment.py就清楚了: 1 """Pylons environment configuration""" 2 import os 3
4 from mako.lookup import TemplateLookup 5 from pylons.error import handle_mako_error 6 from pylons import config 7 from sqlalchemy import engine_from_config 8
9 import newapp.lib.app_globals as app_globals10 import newapp.lib.helpers11 from newapp.config.routing import make_map12 from newapp.model import init_model13 14 def load_environment(global_conf, app_conf):15
"""Configure the Pylons environment via the ``pylons.config``16
# Pylons paths19
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))20
paths = dict(root=root,21
controllers=os.path.join(root, 'controllers'),22
static_files=os.path.join(root, 'public'),23
templates=[os.path.join(root, 'templates')])24 25
# Initialize config with the basic options26
config.init_app(global_conf, app_conf, package='newapp', paths=paths)27 28
config['routes.map'] = make_map()29
config['pylons.app_globals'] = app_globals.Globals()30
config['pylons.h'] = newapp.lib.helpers31 32
# Create the Mako TemplateLookup, with the default auto-escaping33
config['pylons.app_globals'].mako_lookup = TemplateLookup(34
directories=paths['templates'],35
error_handler=handle_mako_error,36
module_directory=os.path.join(app_conf['cache_dir'], 'templates'),37
input_encoding='utf-8', output_encoding='utf-8',38
imports=['from webhelpers.html import escape'],39
default_filters=['escape'])40
# Setup SQLAlchemy database engine42
engine = engine_from_config(config, 'sqlalchemy.')43
init_model(engine)44
# CONFIGURATION OPTIONS HERE (note: all config options will override46
# any Pylons config options)注意第7行的import和第42、43行代码,是不是豁然开朗?Pylons在初始化运行环境时,从config中读取sqlalchemy相关的配置信息,然后通过这些配置信息创建数据库engine,并调用init_model()方法初始化SQLAlchemy功能的核心对象:metadata和Session。有了meta.Session,我们就可以方便的在代码中执行对model层/数据库的访问了。
接着前面的例子说,我们定义了book_table和author_table,接下来: 1 class Book(object): 2
def __init__(self, title): 3
self.title = title 4
def __repr__(self): 5
return "&Book('%s')&" % self.title 6
7 class Author(object): 8
def __init__(self, name): 9
self.name = name10
def __repr__(self):11
return "&Author('%s')&" % self.name这里我们定义两个类,继承自object,类似JavaBeans或者POJO,这里的__init__方法和__repr__方法不是必须的,只是为了创建对象和输出对象内容比较方便。然后就可以用SQLAlchemy的mapper和sessionmaker来建立映射关系并处理持久和查询等操作: 1 from sqlalchemy.orm import mapper,sessionmaker 2
3 mapper(Book, book_table) 4 mapper(Author, author_table) 5
6 Session = sessionmaker(bind=engine) 7 session = Session() 8
9 gia = Book(u'Groovy in Action')10 ag = Author(u'Andrew Glover')11 12 session.add(gia)13 session.add(ag)14 session.add_all([Book('Hibernate in Action'), Author('Gavin King')])15 s_gia = session.query(Book).filter_by(title=u'Groovy in Action').first()16 s_gia.title =u'Groovy in Action Updated'17 18 print "[DIRTY]", session.dirty1920 session.commit() # or session.rollback()如果你用过Hibernate,那么这些代码对你来说,理解起来应该没有任何难度。假如我告诉你,每次都要像这样先定义Table(schema),再定义class,然后用mapper建立对照,是不是有点那啥?SQLAlchemy的开发者们也意识到这一点,所以从0.5开始,SQLAlchemy可以通过sqlalchemy.ext.declarative支持我们实现更紧凑的model/schema定义: 1 from sqlalchemy.schema import Table, Column, ForeignKey, Sequence 2 from sqlalchemy.types import * 3 from sqlalchemy.orm import relation 4 from sqlalchemy.ext.declarative import declarative_base 5
6 Base = declarative_base() 7 metadata = Base.metadata 8
9 bookauthor_table = Table('bookauthor', metadata,10
Column('book_id', Integer, ForeignKey('book.id'), nullable=False),11
Column('author_id', Integer, ForeignKey('author.id'), nullable=False),12 )13 14 class Book(Base):15
__tablename__ = 'book'16
id = Column(Integer, Sequence('seq_pk'), primary_key=True)17
title = Column(Unicode(255), nullable=False)18
authors = relation('Author', secondary=bookauthor_table)19 20 21 class Author(Base):22
__tablename__ = 'author'23
id = Column(Integer, Sequence('seq_pk'), primary_key=True)24
name = Column(Unicode(255), nullable=False)25
books = relation('Book', secondary=bookauthor_table)这里我们用到了many-to-many关系,其他的常见用法还包括many-to-one、one-to-many、JOIN、子查询、EXISTS、Lazy/Eager Load、Cascade (all/delete/delete-orphan)等等,大家可以根据需要查阅官方文档。
在介绍SQLAlchemy最核心最有价值的ORM部分之前,我们再简单过一遍SQLAlchemy提供的SQL Expression Language用法,就从最基本的CRUD来举例说明吧(接着上一篇的示例): 1 from sqlalchemy import select,update,delete 2
3 conn = engine.connect() 4 book_ins = book_table.insert(values=dict(title=u'Groovy in Action')) 5 author_ins = author_table.insert(values=dict(name=u'Andrew Glover')) 6 conn.execute(book_ins) 7 conn.execute(author_ins) 8 book = conn.execute(select([book_table], book_table.c.title.like(u'Groovy%'))).fetchone() 9 author = conn.execute(select([author_table])).fetchone()10 bookauthor_ins = bookauthor_table.insert(values=dict(book_id=book[0],author_id=author[0]))11 conn.execute(bookauthor_ins)12 conn.execute(update(book_table,book_table.c.title==u'Groovy in Action'), title=u'Groovy in Action (中文版)')13 conn.execute(delete(bookauthor_table))14 conn.close()简单说明一下代码逻辑:首先从engine建立连接,然后做两个insert动作,分别insert一条book记录(title为'Groovy in Action')和一条author记录(name为'Andrew Glover'),这之后分别再做两次select,得到刚insert的这两条记录,其中book记录的select用到了过滤条件,相当于"WHERE book.title like 'Groovy%'",然后构建一条新的insert语句,用于insert一条bookauthor关系记录,接下来,做一次update,将book.title为'Groovy in Action'的更新为'Groovy in Action (中文版)',最后,在关闭连接之前,做一次delete,删除bookauthor中的记录。在指定WHERE条件时,.c是.columns的简写,所以book_table.c.title指代的就是book表的title列。更高级的用法是采用"&"、"|"、"!"三个符号,分别表示AND、OR和NOT,加上必要的"("和")"实现复杂的条件定义。由于传递给select()的第一个参数是个list,所以你应该已经猜到了,我们也可以多张表做关联查询。
在sqlalchemy.schema和sqlalchemy.types这两个module中,包含了定义数据库schema所需要的所有类,如Table、Column、String、Text、Date、Time、Boolean等。还是来看一个例子: 1 from sqlalchemy.engine import create_engine 2 from sqlalchemy.schema import MetaData, Table, Column, ForeignKey, Sequence 3 from sqlalchemy.types import * 4
5 engine = create_engine('postgres://test:test&#64;localhost/test', echo=True) 6
7 metadata = MetaData() 8 metadata.bind = engine 9 10 book_table = Table('book', metadata,11
Column('id', Integer, Sequence('seq_pk'), primary_key=True),12
Column('title', Unicode(255), nullable=False),13 )14 15 author_table = Table('author', metadata,16
Column('id', Integer, Sequence('seq_pk'), primary_key=True),17
Column('name', Unicode(255), nullable=False),18 )19 20 bookauthor_table = Table('bookauthor', metadata,21
Column('book_id', Integer, ForeignKey('book.id'), nullable=False),22
Column('author_id', Integer, ForeignKey('author.id'), nullable=False),23)2425metadata.create_all(checkfirst=True)首先我们还是create_engine,然后新建一个MetaData对象,把engine绑上去,接下来,开始在metadata中定义表结构(metadata由Table构造函数传入),我们这里定义了3张表,分别是book、author和bookauthor关系表(“多对多”),其中新建一个Sequence对象,专门处理主键生成。最后我们通过执行metadata.create_all()创建数据库表,参数checkfirst=True表示如果数据库相关对象已经存在,则不重复执行创建。对于已经存在于数据库中的表,我们可以通过传入autoload=True参数到Table构造函数的方式来加载现有的表结构到metadata中,而不必挨个儿再写一遍Column清单。看到这儿,你也许觉得挺麻烦,不是么?Django和RoR都是可以直接定义数据model类,顺带就把schema也定义了,而不是像这样单独去写表结构的schema,显得很"底层"。确实,这样用SQLAlchemy并不是最优化的,SQLAlchemy本身并不会自动的帮你做很多事,但它基础打得很牢。如果你感兴趣,也可以先去看一下SQLAlchemy的扩展模块Elixir,通过Elixir,你可以像Ruby on Rails那样定义出实体和关系("Active Record")。在稍后的第4部分中,我们会去了解如何以声明方式来更紧凑的定义我们的model/schema(0.5新特性)。鉴于笔者倾向于更强的控制力,所以在这个系列中就不去介绍SQLAlchemy的其他扩展模块了,如Elixir、SQLSoup等,大家可以根据需要去找下官方文档。
ORM是个大话题,大到可能好几本书都说不完。SQLAlchemy,别看它刚出到0.5.2,已然是Python世界ORM的事实标准,受到众多开发者和无数框架的青睐。如果之前没有或很少接触SQLAlchemy,那么学习Pylons可能有相当一部分时间都会花在SQLAlchemy上。通常,人们选择Pylons或者TurboGears而不是Django,SQLAlchemy在这些决定的背后有着很重的分量。作为Pylons学习笔记的一部分,接下来我将分4篇随笔介绍SQLAlchemy,分别是Engine API、Schema Management (MetaData/Types)、SQL Expression Language和Object Relational Mapper。此文为第1篇,重点介绍Engine API。类似Java的JDBC,Python也有一个类似的数据库访问接口规范,那就是DB-API(目前是2.0),不同的常见RDBMS都有符合DB-API标准的Python库,比如PostgreSQL有psycopg2,Oracle有cx_Oracle等。有了这个基础,SQLAlchemy也就得以方便的通过DB-API连接不同的数据库。以PostgreSQL为例,通过SQLAlchemy的Engine API访问数据库的代码可以这样来写: 1 from sqlalchemy.engine import create_engine 2
3 engine = create_engine('postgres://user:pass&#64;localhost/testdb') 4 connection = engine.connect() 5 connection.execute( 6
CREATE TABLE book 8
id serial NOT NULL,10
title character varying(30) NOT NULL,11
CONSTRAINT pk_book PRIMARY KEY (id)12
"""14 )15 connection.execute(16
INSERT INTO book (title) VALUES (%s);18
"The Art of UNIX Programming"20 )21 rs = connection.execute("SELECT title FROM book")22 for row in rs:23
print "Book Title: ", row['title']24 connection.close()基本步骤就是create_engine、connect、execute和close,没有很特别的地方,不过SQLAlchemy的Engine API,并不是简单的DBAPI调用,而是包装了其他内容,如数据库连接池,我们可以在create_engine的时候指定连接池参数和其他额外配置,类似这样:engine = create_engine('postgres://user:pass&#64;localhost/testdb', pool_size=10, convert_unicode=True)类似Spring的JdbcTemplate,更多的时候,统一使用SQLAlchemy的Engine API而不是DB-API能给我们带来更大的灵活性,通常也更方便、更安全。
在开始之前,说点提外话,随着对Pylons了解的深入,你可能时不时需要看看相关组件/软件包是否有更新出来,方法也不复杂,通过"easy_install -U [组件名]"即可,在学习或者是开发过程中,最好是保持环境相对较新,直到出现相对大的release或者即将进入产品部署阶段。继续介绍Pylons组件,先看个例子。首先用"paster controller hello"增加一个controller,路径中会增加出以下两个文件:controllers/hello.pytests/functional/test_hello.py分别对应新增的controller类HelloController和功能测试类TestHelloController,它们分别继承自WSGIController-&BaseController和TestCase-&TestController。我们主要看hello.py,默认内容如下: 1 import logging 2
3 from pylons import request, response, session, tmpl_context as c 4 from pylons.controllers.util import abort, redirect_to 5
6 from newapp.lib.base import BaseController, render 7 #from newapp import model 8
9 log = logging.getLogger(__name__)10 11 class HelloController(BaseController):12 13
def index(self):14
# Return a rendered template15
return render('/template.mako')16
# or, Return a response17
return 'Hello World'如果你的服务器没有Ctrl-C停掉,那么这个时候你已经可以通过看到该controller的处理结果了(Hello World)。简单改造一下17行:
from pylons import config
return '&br/&'.join(config.keys())我们就可以在返回页面上显示出所有可以通过pylons.config访问到的参数列表。出了返回文本,也可以通过render()方法交给页面模板引擎生成页面,也可以通过redirect_to()跳转到其他URL。Pylons是如何找到该请求应该由HelloController的index方法来处理的呢?这背后发生了什么?答案就是Routes。Routes的作者是Ben Bangert,是Pylons框架三个主要作者/维护者之一,早期的版本主要是仿照Ruby on Rails的routes.rb开发的,有RoR经验的朋友可能一眼就能发现它们之间的相似之处。目前Routes的最新版是1.10.2。Pylons应用中,routing的配置在config/routing.py,默认生成的内容如下: 1 """Routes configuration 2
3 The more specific and detailed routes should be defined first so they 4 may take precedent over the more generic routes. For more information 5 refer to the routes manual at http://routes.groovie.org/docs/ 6 """ 7 from pylons import config 8 from routes import Mapper 9 10 def make_map():11
"""Create, configure and return the routes Mapper"""12
map = Mapper(directory=config['pylons.paths']['controllers'],13
always_scan=config['debug'])14
map.minimization = False15
# The ErrorController route (handles 404/500 error pages); it should17
# likely stay at the top, ensuring it can always be resolved18
map.connect('/error/{action}', controller='error')19
map.connect('/error/{action}/{id}', controller='error')20 21
# CUSTOM ROUTES HERE22 23
map.connect('/{controller}/{action}')24
map.connect('/{controller}/{action}/{id}')25 26
return map在这个配置中,对我们刚才的实例起到决定性作用的是第23行,我们的输入URL为"http://127.0.0.1:5000/hello/index",其中"/hello/index"通过"/{controller}/{action}"这个表达式match出controller为hello而action为index的解析结果,从而在controllers目录找到hello.py,和其中HelloController的index方法,进行调用。map.connect()在上面代码中体现出两种用法:map.connect('pattern', key=value) - 指定默认的controller、action、id等map.connect('pattern') - 直接指定patternpattern字符串允许通配符,通常在最后一个元素上,比如'/{controller}/{action}/{*url}',将后面的整个URL片段交给前面指定的controller/action处理。除此以外,map.connect()还支持1- "路径别名",如:map.connect('name', 'pattern', [_static=True])如果_static设为"True",表示为"静态命名路径"。2- 额外的匹配条件,如:map.connect('/{controller}/{action}/{id}', requirements={'year': '\d+',})map.connect('/{controller}/{action}/{id}', conditions=dict(method=['GET','POST']))所有的route优先级为从上到下。Routes除了提供解析进来的URL的逻辑,在我们的controlle

我要回帖

更多关于 美国发如新官网 的文章

 

随机推荐