nighty

折腾的年华
posts - 37, comments - 143, trackbacks - 0, articles - 0

2008年6月11日

系统为ubuntu server,二台机器A和B,IP为A 192.168.1.111,B 192.168.1.222
A为rsync server,启动为守护进程,B为备份机,做为rsync client,最后用crontab做一个简单的作业,定时在B上执行同步文件的功能
A的安装和配置如下:
1.  apt-get install rsync   可能提示系统已经安装有了
2. 配置文件/etc/rsyncd.conf
    默认安装时是不会有这个配置文件的,但是可以 cp /usr/share/doc/rsync/examples/rsyncd.conf /etc  把它示例中的配置文件拷贝过来
    vi /etc/rsyncd.conf    这里参数有点多,但是有些可以先不管,关注重点的
    [ftp]  这里是模块,可以配置多个,这个是系统默认给出的一个配置,下面给一个本机上的配置示例:
--------------------------------------------------------------------------------------------------
# so omit the "pid file" line completely in that case.
pid file=/var/run/rsyncd.pid
#syslog facility=daemon
#socket options=
# MODULE OPTIONS
[share]
comment = public archive
path = /var/www/pub
use chroot = no
max connections=2
# lock file = /var/lock/rsyncd
# the default for read only is yes...
read only = no
list = yes
uid = nobody
gid = nogroup
# exclude = 
# exclude from = 
# include =
# include from =
auth users = rsync
secrets file = /etc/rsyncd.secrets
strict modes = yes
hosts allow = 192.168.1.222
# hosts deny =
ignore errors = yes
ignore nonreadable = yes
transfer logging = yes
log format = %t: host %h (%a) %o %f (%l bytes). Total %b bytes.
timeout = 600
refuse options = checksum dry-run
dont compress = *.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz
---------------------------------------------------------------------------------------------
这里,最上面的是pid文件位置。然后配置了一个模块名叫做share,最大连接数是2,read only = no,指定为非只读(不然同步时会有权限问题)
而后面的auth users = rsync 是指定一个同步的账户名叫做rsync,这个账户的认证文件是/etc/rsyncd.secrets,当然我们要创建这个文件

3.  创建 /etc/rsyncd.secrets文件,内容为: rsync:123  表示rsync这个用户的密码是123 然后修改文件的权限 chmod 600 /etc/rsyncd.secrets

4.  rsync server做为守护进程
     vi /etc/default/rsync
     可以看到开头处这样声明:
------------------------------------
# start rsync in daemon mode from init.d script?
#  only allowed values are "true", "false", and "inetd"
#  Use "inetd" if you want to start the rsyncd from inetd,
#  all this does is prevent the init.d script from printing a message
#  about not starting rsyncd (you still need to modify inetd's config yourself).
RSYNC_ENABLE=inetd
-------------------------------------------
   做为守护进程,可以设置为true或是xinetd方式来启动。于是我们安装inetd   sudo apt-get install xinetd
   安装好后配置inetd的配置文件  vi /etc/xinetd.d/rsync ,输入如下内容:
---------------------------------------------------
service rsync
{
    disable = no
    socket_type = stream
    wait = no
    user = root
    server = /usr/bin/rsync
    server_args = --daemon
    log_on_failure += USERID
}
-------------------------------------------------------
然后启动xinetd,/etc/init.d/xinetd restart,A服务器的rsyncd server就完成了!

5.  B服务器由于是client,不需要配置,也不需要安装xinetd,直接可以通过命令行执行
rsync --delete -azvv rsync@192.168.1.111::share /var/www/pub/
这个命令就可以直接连接到192.168.111的rsync账户,它会提示你输入密码,就是A中的secrets文件中的密码,然后同步share模块到本机的/var/www/pub目录,你可以事前在A机器上创建一个文件如test.txt,随便写点内容,然后执行些命令,看是不是B上多了这样一个文件?如果是,则表示已经连接成功。你接下来就可以做crontab了!

posted @ 2013-04-12 12:23 寒武纪 阅读(1330) | 评论 (0)编辑 收藏

二台服务器,A的内网IP为192.168.1.111,B的内网IP为192.168.1.222,A做为master,B做为Slave
1.  配置A的Mysql
     (1)  vim /etc/mysql/my.cnf
           去掉[mysqld]段中 server_id =1 和log_bin=/var/log/mysql/mysql-bin.log的#注释
           加上  binlog-do-db = s3     s3就是要同步的数据库的名称,如果没有这一行,表示同步所有的数据,另外 binlog_ignore_db = mysql。要表示忽略同步的数据库名称为mysql,如果有多个要指定同步或是忽略同步的数据,就配置多行,保存退出。
     (2) 创建一个复制用的账户(名称为repl,允许从远程连接,密码为123456):
          GRANT REPLICATION SLAVE, RELOAD,SUPER, NO *.* TO repl@'%' IDENTIFIED BY '123456';
        FLUSH PRIVILEGES;
     (3) 重启mysql服务,或是直接reboot机器也可以
     (4) 进入mysql,然后用 show master status\G  查看二进制日志的状态,看到类似以下的结果:
          +------------------+----------+--------------+------------------+
          | File                      | Position  | Binlog_Do_DB | Binlog_Ignore_DB |
          +------------------+----------+--------------+------------------+
          | mysql-bin.000003 |     1376  | s3                  |                           |
          +------------------+----------+--------------+------------------+
2.  配置B的Mysql
      (1) vim /etc/mysql/my.cnf
           去掉[mysqld]段中 server_id =1 和log_bin=/var/log/mysql/mysql-bin.log的#注释,把server_id改为2,要和master机器的不一样。并增加以下内容:
           binlog_do_db=s3
           log-slave-updates
          保存退出
     (2) 重启mysql服务
     (3) 进入mysql,执行
          CHANGE MASTER TO MASTER_HOST='192.168.1.111', MASTER_USER='repl',Master_Port=3306,MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=1376;
          SLAVE START;
          注意上面的CHANGE语句中,MASTER_LOG_FILE和MASTER_LOG_POS就是上面1.4中提到的show master status命令得到的结果,指定二进制文件的名称和开始同步的位置。
     (4) 查看SLAVE状态:    show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.1.111
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000003
          Read_Master_Log_Pos: 1376
               Relay_Log_File: mysqld-relay-bin.000002
                Relay_Log_Pos: 1355
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 1376
              Relay_Log_Space: 1512
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
    上面的红色二行如果为YES则表示已经正常连接,可以进行复制了。

posted @ 2013-04-09 20:33 寒武纪 阅读(1718) | 评论 (0)编辑 收藏

    许久没用服务器上装的那个postgresql,其实是用来做redmine的数据库的,考虑到mysql可能经常升级,而rails的连接组件在安装上有点麻烦,所以当初就装成postgresql。
    今天准备备份一下,用的phppgadmin,刚开始是提示其中的pg_dump执行路径没有配置,重新配置好后,导出的结果却是空的损坏文件。于是想算了,还是转到pg的安装目录下执行pg_dump.
    服务器装的是centos 6.2,忘了当初是建了一个用户postgres.postgres进行安装的,用其它用户切换到pg的安装目录下bin/pg_dump是执行不了的,提示在指定目标下生成导出文件。故猜测应该是postgres这个用户的权限不足!
    cat /etc/passwd查看一下当前有多少用户,的确有postgres.postgres用户,密码多少?忘了!反正有root,直接passwd修改成新的密码吧,于是就立马修改了该用户的密码,可以正确切换到postgres用户了,还需要root为postgres指定一个目录有操作权限
    chown -R postgres.postgres /var/xxxx   
    然后再回到pg的bin目录下,执行pg_dump redmine > /var/xxx/redmine.bak
    这下终于正常了,别忘了还得去redmine安装目录下,备份下files文件夹。
    
    仅以此为笔记,以后可以查阅使用

posted @ 2013-03-27 22:39 寒武纪 阅读(1389) | 评论 (0)编辑 收藏

      用Flex做企业应用将近有一年时间了,这个过程很累,在国内这方面的积累不多,真正有参考意义的资料的确非常少。经过一段长时间的摸索后,多少也积累了一点经验,就最近的关于单元测试的想法做一点总结,由于涉及的知识较多,这里也只是给出个人的一种思路。
     众所周知,Flex的缺点是开发调试效率较低,而且它只是表现层的一种解决方案。在企业应用中最需要解决的是编译生成的swf体积问题,我想任何客户都很难接受一个企业应用全部打包在一个swf里,几MB甚至几十MB的初始化过程谁都无法接受,所以都必不可少地采用Module的加载方式,把不同的业务功能编译成独立的swf,需要用的时候再去加载。把核心功能、通信机制、公共组件设计成库项目,编译成swc做为RSL让业务模块共享调用,达到尽量减少业务模块编译体积的目的。在这方面如果用心优化的话,基本上可以控制到每个swf体积大概在200KB以内,这样就算是互联网方式部署,客户访问仍是可以接受的。
    

     该结构图给出了一种整体的设计方案,Flex的启动肯定得有Application,这个是用户初登录后第一个加载的swf(登录就不要用flex了,用jsp或是模板实现吧)。所以它负责加载你设计的整个框架,包含模块加载机制、通信代理方式、基础库初始化等等,而和Java端的通信目前比较有效的仍然是blazeds,这个技术需要的介绍内容不在本文的范围之内。关于通信接口的实现有一种非常有用的方式就是借用Java的动态代理理念,Spring有一个flex的扩展子项目叫做springactionscript,而这个项目又引用了as3commons的库(类似于apache commons的一些公共组件)。为什么提及这个,因为flex本身的反射功能api非常难用,所以as3commons就做了扩展,它大大简化了反射的使用,而且提供了一个bytecode的工具用于操作字节码,它是实现动态代理的关键。至于为什么要动态代理?目的就是达到在写和Java对接的接口时,可以只声明接口,不需要实现类(得减少多少重复代码呀?),而和Java对接接口我们又可以开发一个工具让java code 自动转成 as code,如果懂得Eclipse插件开发的话还可以进一步做一个插件,达到Java只写一次就可以自动生成对应的flex接口,提高开发效率。
     转入正题,关于单元测试的概念,Flash Builder在4.5已经把flex unit作为内置库了,这点和Eclipse把junit内置类似,而flex unit的使用网上有大量的资料介绍,这里也不多说。flex unit在测试as代码还是不错的,和junit测试一样,提供了一些简单的Assert断言,但是你最痛苦的却不是as的测试。企业开发的特点就是数据量不大,但是需求坑爹,经常变来变去,而且结构复杂,往往一张表很多字段,关联子表,层级属性多。而你如果选择了Flex做了展示层的技术,那必定是看中它比HTML + CSS + JS更强的界面交互功能。的确,这点不容质疑,Flex Spark的皮肤机制的确提供了很多优秀的特点,不过如果你想纯熟掌握它的整个机制,恐怕得花很多时间阅读源代码才行,而皮肤的制作整对别想让美工独自实现,它同样是需要技术积累的,介绍它需要用几个篇幅才足够。任何技术方案都一样,BS、CS、AIR在实现复杂界面时,对于开发人员来说,最痛苦的莫过于界面的单元测试。
     痛苦在哪里?回看上面那幅架构图,业务功能界面实现在Flex,业务逻辑在后台Java,那么当二个团队同时进行工作的时候,沟通就是最大的成本。解决沟通的问题就必须在先前设计时约定好接口和数据结构,那是会影响双方团队协调的关键因素。当双方同时进行开发的时候,势必存在前台依赖后台的情况,因为它能到达界面的前提得在整个框架载入后(并且可以初始化一堆数据,发生了通信),Java后台还好说,依赖于spring和junit可以做到很好的单元测试。而flex就痛苦了,我没有通讯啥都做不了呀!
     如何设计单元测试?最大的出发点就是如何切掉和后台通信连接,看下面的简单结构图

     实现思路介绍:
     1.  简化Application加载过程   --   可以套用你主程序中的加载过程,但是不需要你的主界面其它多余的元素,达到减少到达测试界面的多余步骤(尽可能少地减少鼠标和键盘操作)
     2.  定义测试配置   --   测试哪个模块?哪个工作流程?你得通过配置的方式来定义,而不是每次都手写代码,才能方便你的成员使用你这个工具
     3.  模拟后台接口实现  --  记得上面说的动态代理吗?其实是为接口动态生成一个实现类,然后注入真正通信的实现代码,例如WebService、HTTP,既然可以注入这些通信渠道,当然就可以注入本地实现类啦
     4.  对象查看器    --   这个是神马?因为你都不要Java后台了,每次操作一个界面后得提交数据吧?没有后台了,提交到哪里?你得必须把你的提交对象用界面展示出来吧?好吧,这个可是个难点!

     我想这四个方面的原则无非就是:减少单元测试需要进行的步骤(最快到达测试界面),脱离后台依赖(自己简单模拟后台实现,可惜flex没有类似java的mock库,悲剧!),如何查看提交到后台的结果。 单元测试的目标:界面能正常加载、提交数据正常,如果二者都没问题,那么联调的时候就可以非常容易定位到是Flex的问题还是Java的问题!达到介分责任的目标,当然,如果你所在团队是按模块分的,也就是说flex和java都是同一个人做,那么就不存在责任问题。

     怎么实现上面的四个步骤呢?简要地介绍一下吧。
     第1简化application加载,其实你可以把第一张图中的application加载机制拷贝过来,只是主界面可以做得非常简单,比如不需要多余的控件(比如过长的菜单、当前登录人、时间、一陀设置按钮等),只留下最核心的能到达你测试界面的入口,至于怎么设计这个简化版的application,那得发挥你本人的创造力,另外还得看具体的业务。

    第2定义测试配置。模块如何加载?通信接口本地模拟实现类定义?通过配置显示在appliation做为触发控件,这些你都得自定义一套xml之类的文件来配置吧,这个就需要技巧了,不能设计得太复杂,因为你的开发人员需要沿用你定义的规范来定义它需要测试的模块,关于这方面的知识,可以参考spring加载配置文件方式、struts2加载定义文件等理念,有一个概念我比较推荐,就是struts2中的include配置文件,允许配置文件分散,让大家提供代码和文件时减少冲突,又可以套用你正常的加载机制。

    第3模拟后台接口实现。这个是比较烦的,模拟机制本身通过动态代理倒是不难实现,恶心的是你得自己动手用flex简单实现一次后台生成数据、处理数据的逻辑。这里我有个实践的总结经验分享,在前期你调试完的后台接口证明是没有问题的,那么可以混合使用,一部分调试过的接口可以直接用后台,而新接口才本地模拟。一个原则就是后台有的,已经证明稳定的就用后台,没有的或是后台还没有完成的你就自己模拟。

    第4对象查看器。想想flex不能操作数据库、由于安全限制不允许直接操作文件、无法读取本地文件目录。而你的测试数据也许会有关联(特别是在工作流方面),所以你得想一个方案来保存你的对象结果,而且得以一种人性化的方式查看对象内容。且抛开数据存储的问题,这个对象查看器如何设计就够你头疼的了,首先是对象得定义成一种格式,一种人可以看得懂的格式,比如xml,可以支持序列化和反序列化,你得去掉多余的无用属性和访问器。又得回到反射机制上了,序列化其实不难,难的是反序列化时如何正确地转成原来的对象。列一种本人设计的结构:
     <xxx   type="com.xx.oo.XXClass">
          <aa type="String">aaa</aa>
          <bb type="Boolean">true</bb>
          <list type="mx.collection.ArrayCollenction">
              ....
          </list>
     </xxx>
     对象分简单对象、复杂对象、动态对象等,如何表达这种结构和保证序列化时不丢失数据需要细心考虑。那么最后如何实现查看器呢?其实有一个参考的范例,就是Eclipse的“大纲”视图,经过实践的扩展,把树视图换成表格树(这种控件原生没有,有第三方的可以拿来修改),看个样图吧!
 
     因为你关注的对象内容无法就是这三个方面,属性名、值、和类型,又支持以树方式导航对象,已经足够你人眼分辩内容了。至于如何有效的保存测试数据,并且组织好结构,这个方面我目前也仍在思考中,未有较好的思路。
     以上内容仅是出于本人的一种方案,也许有更好的实现方案,只是水平不足以超过这种认识,希望后续能进一步思考能实现更加完美的单元测试框架。
     ST测试更关注的界面的自动化测试,这方面涉及的知识更多,一般公司是很难有财力和技术去支持做自动化测试,属于比较高端的范围,实现是很多回归都靠测试团队人肉在实现。

posted @ 2012-04-28 12:03 寒武纪 阅读(1523) | 评论 (1)编辑 收藏

      采用Spark进行Flex的Web应用开发的时候,往往会用一个固定栏,比如说类似Windows的任务栏,我们想把它固定在顶部或是底部,用于某些场景存放一些控件。<s:Application>组件中有一个<s:controlBarContent>属性和<s:controlBarLayout>,用于设置Application的一个组件区域(我猜官方起的这个名称大概用意在于此吧),下面这段话就是官方的中文注释:
Title包含在 Application 容器控件栏区域中的组件集。Application 容器控件栏区域的位置和外观由 spark.skins.spark.ApplicationSkin 类确定。默认情况下,ApplicationSkin 类定义以灰色背景显示在 Application 容器内容区域顶部的控件栏区域。创建自定义外观以更改控件栏的默认外观。
      于是可以简单的给<s:controlBarContent>区域添加各种控件,以及设置它内在的布局(controlBarLayout)。但是,发现它只能显示在Application的顶部!
      这是怎么回事,根据上面的注释,感觉应该原因在它的皮肤,得跟进代码,看一下究竟。
      首先找到<s:Application>控件的标准Skin,可以在Flash Builder中直接查看。
      
      双击打开ApplicationSkin,里面的代码包含了各种SVG图形学的实现api调用,flex管这些库叫FVG,大意就是SVG的Flex实现版本,该库实现得还算简洁!
      Application标准皮肤的就是先画一个矩形,然后用一个Group来包含不同的形状,最后一段<s:Group id="contentGroup" width="100%" height="100%" minWidth="0" minHeight="0" />,代表Application的内容区域,Flex从设计上区分了控件树和布局树,有些复杂,可以参考官方的文档。因为Application是属于容器,所以必须在皮肤中包含这一句,不然程序运行时就看不到它包含的子控件。
     而前面长长一串<s:Group id="topGroup">中是画了顶栏的外观,它用FVG库画了四层:
     <!-- layer 0: control bar highlight -->     底部高亮线(用画笔填充一个矩形)
     <!-- layer 1: control bar fill -->              背景填充线性渐变颜色
     <!-- layer 2: control bar divider line -->  分割线
     <!-- layer 3: control bar -->                  controlBar的具体内容容器
     当然这个controlBar不是自己会出现的,只有当你填充了内容控件的时候它才显示,所以有includeIn="normalWithControlBar, disabledWithControlBar",表示在这二个State下才显示,什么时候State才包含这二个呢?当然,就得去看Application.as的实现原理,具体篇幅就不描述。
     那么回到最初的问题:我想改变controlBar的位置在下方怎么处理?
     从上面的分析可知其实controlBar的摆放位置是在Skin中定义的,而它是什么布局,它显示不显示是通过Application.as本身的代码控制的,那么我们就只要自定义Application.as的皮肤就可以,新建一个外观mxml,直接复制官方的ApplicationSkin.mxml的代码,然后,把<s:Group id="contentGroup" width="100%" height="100%" minWidth="0" minHeight="0" />代码移到<s:Group id="topGroup">代码的上面,在你的Application中强制指定skinClass为你自定义的ApplicationSkin,或是通过css设定即可。
     再扩展思维一下,其实我们完全可以把controlBar放到左边或是右边,甚至任何位置,都取决你在Skin的定义和Application.as的逻辑控制(你可以继承Application扩展)。
     那么controlBar有什么作用?其实spark的Panel从及它的子类TitleWindow都有controlBar的概念,它能独立于你容器外的区域,对于你容器本身包含的组件和布局不会产生干扰,以及你设置了width、height为100%时也不产生影响。如果你不要controlBar,直接在Application中用布局添加一个Group也是可以实现,但是它从根本上是属于布局树中contentGroup的内容,会受限于布局。
     Spark Skin的设计的确有高明的地方,对比Flex3的外观,它提供给设计人员的空间实在大得多,你可以组合图片和FVG库的功能自定义各种外观,当然,我就建议你多熟悉一下FVG库的应用,毕竟从外加载图片对flex来说是一个消耗,你的程序也会增肥。

posted @ 2011-10-13 18:14 寒武纪 阅读(2466) | 评论 (0)编辑 收藏

      问题描述:
          服务器操作系统是Centos5.5,此前已经有多套系统跑在上面,且装有PHP5.2.10。Centos5.X系统的稳定yum安装源版本是5.1.6,并不符合最新版本的phpmyadmin(5.2以上版本),下载了最新版本phpmyadmin安装后提示缺少mysql支持模块。
查看了一下发现的确是安装php的时候没有装上php-mysql模块。直接重新编译php源码安装比较麻烦。
     一个比较方便的方法:
    为yum添加第三方的源,然后直接用yum -y install php-mysql进行安装
    以下方法为从网络搜索到的,做个记录,方面须用之时查阅。
          导入地址:    rpm --import http://www.jasonlitka.com/media/RPM-GPG-KEY-jlitka
          编辑yum源: vi  /etc/yum.repos.d/CentOS-Base.repo
          在最下面添加如下信息:
                   [utterramblings]
                   name=Jason's Utter Ramblings Repo
                   baseurl=http://www.jasonlitka.com/media/EL$releasever/$basearch/
                   enabled=1
                   gpgcheck=1
                   gpgkey=http://www.jasonlitka.com/media/RPM-GPG-KEY-jlitka

      
    然后执行安装命令: yum -y install php-mysql    系统就会自动从上面添加的源中读取合适该版本的php-mysl模块,安装完成后重启httpd服务,再访问就OK了。
    该方法同样适合于默认安装的php5.1.6版本安装成功后再进行升级到 5.2版本。

posted @ 2011-08-14 15:53 寒武纪 阅读(2086) | 评论 (2)编辑 收藏

    freemarker几天前才发布了2.3.17版本,5月21号又发布了2.3.18,距2.3.16已经一年多了。老的编辑器已经不能安装在新版本的eclipse和myeclipse上面了,最新的官方编辑器仍在开发阶段,有网友貌似知道最新的源代码链接位置,不过肯定是不稳定的。
    另一个可选的编辑器就是JBoss Tools 3.2中的FreeMarker编辑器,值得安慰,启动MyEclipse9.0后进入MyEclipse Configuration Center --> Software --> add site,输入Name: freemarker,    
    URL:   http://download.jboss.org/jbosstools/updates/stable/helios/  然后在All JBoss Tools 3.2.0下选择FreeMarker IDE,其它的大概你不需要都不用管,也不影响下载时间,然后一路确认安装就OK!

posted @ 2011-05-22 22:15 寒武纪 阅读(2822) | 评论 (0)编辑 收藏

       上周服务器的一个PHP软件不能访问,查看原因是CentOS的PCRE模块未用utf-8编码引起的,由是搜索了一些资料照着变更,没有效果。
       当时和另一个朋友L共同尝试删除后重装,由于对Linux系统不熟悉,只会使用常规的命令进行一些皮毛的操作,就直接用yum remove进行删除,系统当时还提示是否remove掉相关联的700多个组件或模块。当时也没有多想就直接回车!结果------悲剧了,屏幕狂刷,我意识到pcre是基础模块,所有关联它的或是它关联都删除掉,系统将遭遇一个灾难式的破坏。等刷完屏幕的字符,最终发现:所有的bin目录下的命令全部不见了!!!天哪,连ls命令都没有,唯独剩下一个cd命令。幸运的是,当时运行在服务器的几个应用还能访问,比如说phpmyadmin。

        而后想了想,尝试了各种方式去恢复,都没有办法,那么,唯一的办法就是尽量备份原有的数据和文件,重装系统进行环境的重新搭建。还好有其它方式,可以先把里面重要的文件都提取了出来。然后在幸存的phpmyadmin上赶紧进行mysql相关数据库的备份。(注:因为是个人的服务器,所以并没有像公司一样做好运营和备份计划)

        第二天决定重装系统了,管理员用了3个小时才搭建起CentOS5.5和SSH远程服务端。轮到我和L需要用SSH进行远程的环境搭建。接下来的三天晚上,真是折腾又折腾。计划安装的几个主要软件是:Mysql、PHP、Apache、JDK、Tomcat、Ruby on Rails、Redmine、PHPMyAdmin等。

        第一个晚上,灾难之前装在上面的上述各种软件版本都有点低,所以想干脆直接上最新的,而CentOS5.5的yum库是取不到这些最新的软件的。所以朋友 L 大量地采用了make install和rpm方式来安装,而CentOS本身集成的就只有Apache的版本满足。装了MySQL5.5+phpMyAdmin3.4,运行起来了却发现和MySQL5.5和redmine1.1.1有冲突,主要是RoR环境的Mysql驱动有问题,在网上能找到的都是从http://www.tmtm.org/en/ruby/mysql/ 下载的0.2.6版本,这个版本我只试在MySQL5.0情况下正常连接。现在换成5.5,就无法运行了,为此我还特地把Ruby环境从1.8.6提高到1.8.7,同样装了redmine官网要求的各种Ruby工具和Rails组件,折腾无果!最后,我还把异常信息拿出来,给ruby-mysql的日本作者发了一个email,第二天作者回复我,原来ruby-mysql已经挂到GitHub去了,而且现在已经是3.0alpha状态了,但是他不确定能否工作在ruby1.8.6下。 最后感觉还是不行,切换回MySQL5.0,同样高版本的ruby-mysql驱动也无法连接低版本的MySQL。第一天以失败告终!

       第二天晚上继续折腾,再不停地重新安装,这时才想起一个问题:应该退回到系统宕机前的所有软件版本状态,这样原先备份的数据才能正常恢复,不会带来额外的版本冲突麻烦。于是折腾到接近晚上12:00时发觉方向错了,无奈,和 L 打了个招呼,计划明天让管理员再重装一次系统,现在目前的系统又被我们搞乱了!

       第三天。就着原来的思路,重装恢复到以前的版本,又用yum的原来方式装回原来的版本,这次进行顺利。用了二个小时,就把常用的软件恢复了,同时恢复了数据库。最后只剩下一个问题,redmine的密钥恢复后,仍然无法连接原来的用户密码,我想可能得去查阅redmine的用户管理模块,看看它究竟是怎么生成密码和检验登录的,有趣的事是发现网友找到另一个方法,就是直接在redmine下建ruby脚本,调用ActiveReord的User.save(),存一个自己的新密码,不晓得可不可行,找个时间做个实验看看。

      回头一想。其实规划、整理、理清服务器的管理工作,比精通Linux系统、各种软解决技术更为重要!

posted @ 2011-05-20 09:24 寒武纪 阅读(7773) | 评论 (2)编辑 收藏

    Phusion Passenger模块使得Rails应用可以像PHP模块一样运行在Apache上,非常方便。
   准备条件:CentOS服务器已经装好了Apache2.2和Redmine应用        

    1. 安装Passenger模块
            gem install passenger
            passenger-install-apache2-module
        
            第二个命令是安装passenger的apache2模块,它已经做得很智能,会提示你确认安装以及最后怎么配置模块到apache中。
            摘出配置段的内容如下:
                Please edit your Apache configuration file, and add these lines:

                LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-3.0.7/ext/apache2/mod_passenger.so
                PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-3.0.7
                PassengerRuby /usr/bin/ruby

            把红色字体部分拷贝到apache的配置文件,可以是主配置文件/etc/httpd/conf/httpd.conf,也可以是在/etc/httpd/conf.d/目录下新建一个子文件命名为ruby.conf,推荐第二种方式,更为简洁,不会影响主文件的配置。
      
    2. 先测试一下passenger是否安装正常

            切换到redmine的安装目录下,passenger start  命令尝试一下是否正常能以paasenger方式启动,如果没有异常,恭喜,已经安装完成,剩下的工作就是配置一个虚拟主机和子给你的redmine,这样可以转到更为常用的80端口上。

    3. 配置rails应用做为sub URI模式
             在passenger的官方文档中其实有好几种配置的方式,可以是域名、域名子URI等,很多时候你可能只有一个域名,那么利用sub URI来挂不同的应用就显得比较
            首先配置一段虚拟主机如下:

        <VirtualHost *:80>
            ServerName www.phusion.nl
            DocumentRoot /websites/phusion
            <Directory /websites/phusion>
        Allow from all
            </Directory>
        </VirtualHost>

           解释一下过程大致是先创建一个硬链接,如下:

        ln -s /webapps/mycook/public /websites/phusion/rails 
 
     /webapps/mycook/public是你的rails的应用目录下面的public目录,例如你的redmine安装在/var/www/html,这个目录就是
/var/www/html/redmine/public,后面就是你Apache主目录下的创建的一个链接地址rails,意思就是把/var/www/html/redmine/public
链接到/var/www/html/rails,而/var/www/html/rails是实际上不存在的。
然后再配置子目录如下:

<VirtualHost *:80> ServerName www.phusion.nl DocumentRoot /websites/phusion <Directory /websites/phusion> Allow from all </Directory>
RailsBaseURI /rails # <-- These lines have <Directory /websites/phusion/rails> # <-- been added. Options -MultiViews # <-- </Directory> # <-- </VirtualHost>

 最后四句带#注释说明是重点,应用RailsBaseURI命令把rails子URI指定到rails的应用目录,而rails目录就是我们上面链接的目录,而实际上会跳到我们的直接redmine目录。

 

posted @ 2011-05-19 15:10 寒武纪 阅读(2529) | 评论 (1)编辑 收藏

        引言:最近又用到dbutils,之前一直用Map映射的方式取出select的结果再手工做转换。有写过一篇文章说MapHandler方式的一个缺陷:关于commons dbutils组件的一个小缺陷分析 ,用这种方式,在项目不大的情况下,写一些Map到JavaBean的转换代码工作量不大,但是在数据库表过多并且表中的字段过多的情况下,这种重复的setter感觉有点烦。于是又重新思考了BeanHandler和BeanListHandler的情况,dbutils底层映射用的反射,性能上肯定有损失,不过在大多数项目规模不是很大的情况下,这点损失可以忽略,带来的代码减少却是比较可观。
        问题在哪里?先看一段官方的示例代码:

QueryRunner run = new QueryRunner(dataSource);

// Use the BeanHandler implementation to convert the first
// ResultSet row into a Person JavaBean.
ResultSetHandler<Person> h = new BeanHandler<Person>(Person.class);

// Execute the SQL statement with one replacement parameter and
// return the results in a new Person object generated by the BeanHandler.
Person p = run.query(
    
"SELECT * FROM Person WHERE name=?", h, "John Doe");

        这里有个地方有约束,就是要求示例中的JavaBean类Person中的字段定义要和数据库的字段定义一致。Java的命名习惯一般是骆峰写法,例如userId,那么数据库中就必须定义为userId,而问题在于:有时候我们需要数据库中字段的定义格式与JavaBean的命名不一样,比如数据库定义为:user_id,而JavaBean则定义为userId
        看源代码可能有点费时间,在官方的example页面的最下面果然有一段关于自定义BeanProcessor的指引。摘录出来:

      BasicRowProcessor uses a BeanProcessor to convert ResultSet columns into JavaBean properties. You can subclass and override processing steps to handle datatype mapping specific to your application. The provided implementation delegates datatype conversion to the JDBC driver.
      BeanProcessor maps columns to bean properties as documented in the BeanProcessor.toBean() javadoc. Column names must match the bean's property names case insensitively. For example, the firstname column would be stored in the bean by calling its setFirstName() method. However, many database column names include characters that either can't be used or are not typically used in Java method names. You can do one of the following to map these columns to bean properties:
      1. Alias the column names in the SQL so they match the Java names: select social_sec# as socialSecurityNumber from person
      2. Subclass BeanProcessor and override the mapColumnsToProperties() method to strip out the offending characters.


      大概意思就是提供二种方式:一种就是最直接的,用as关键字把colName重命名,另一种方式就是继承BeanProcessor类,重写mapColumnsToProperties()方法。
      那当然是第二种方式更加具有代表性。尝试了一下。代码如下:
    
 1public class CustomBeanProcessor extends BeanProcessor {
 2    
 3    @Override
 4    protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
 5            PropertyDescriptor[] props) throws SQLException {
 6        int cols = rsmd.getColumnCount();
 7        int columnToProperty[] = new int[cols + 1];
 8        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
 9
10        for (int col = 1; col <= cols; col++{
11            String columnName = rsmd.getColumnLabel(col); 
12            if (null == columnName || 0 == columnName.length()) {
13              columnName = rsmd.getColumnName(col);
14            }

15            columnName = colNameConvent(columnName); // 在这里进行数据库表columnName的特殊处理
16            for (int i = 0; i < props.length; i++{
17
18                if (columnName.equalsIgnoreCase(props[i].getName())) {
19                    columnToProperty[col] = i;
20                    break;
21                }

22            }

23        }

24        return columnToProperty;
25    }

26
27    /**
28     * 数据库列名重新约定
29     * @param columnName
30     * @return
31     */

32    private String colNameConvent(String columnName) {
33        String[] strs = columnName.split("_");
34        String conventName = "";
35        for (int i = 0; i < strs.length; i++{
36            conventName += StringUtils.capitalize(strs[i]);
37        }

38        StringUtils.uncapitalize(conventName);
39        return conventName;
40    }

41}

        注意mapColumnsToProperties方法的逻辑是从父类的方法中直接复制出来的,然后在第15行那里变了个戏法,这里的columnName就是从数据库中读出来的,自定义一个private方法用于转换命名,这里你就可以添加自己的命名约束。例如上面就是把 user_id 转化为Java的骆峰写法:userId
       再深入一层思考,你可以在这里进行更多扩展,以便让自己可以选择不同的命名转换方式。定义了这个Processor之后,下面看看如何调用:
Connection conn = ConnectionManager.getInstance().getConnection();
QueryRunner qr 
= new QueryRunner();
CustomBeanProcessor convert 
= new CustomBeanProcessor();
RowProcessor rp 
= new BasicRowProcessor(convert);
BeanHandler
<User> bh = new BeanHandler<User>(User.class, rp);
User u 
= qr.query(conn, sql, bh, params);
DbUtils.close(conn);
      是不是非常灵活?如果是想返回List结果的,就把BeanHandler替换成BeanListHander类,还可以再进一步封装这些操作,抽象到公共模块中去,让外部直接传入sql语句和Class就能直接返回想要的结果,当然你得增加泛型的定义。同样举一个封装的例子:
 1protected <T> List<T> selectBeanList(Connection conn, String sql, Class<T> type,
 2            Object[] params) throws Exception {
 3        log.debug("select sql:[" + sql + "]");
 4        QueryRunner qr = new QueryRunner();
 5        CustomBeanProcessor convert = new CustomBeanProcessor();
 6        RowProcessor rp = new BasicRowProcessor(convert);
 7        ResultSetHandler<List<T>> bh = new BeanListHandler<T>(type, rp);
 8        List<T> list = qr.query(conn, sql, bh, params);
 9        return list;
10    }

        至于为什么扩展这个方法就可以实现这个逻辑就得去跟源代码看它的内部实现,用了一些JavaBean的处理和反映的技巧来做的。具体就不说。
        总结:commons组件都设计得非常好,可扩展性和实用性都非常高。虽然上面举例实现了转换逻辑的替换,但是仍然需要开发人员在设计数据库的时候和写JavaBean时都要严格做好规范,避免产生不必要的问题。这方面Ruby On Rails就直接内部实现,动态语言的优点特别能体现,同时强制你在设计时必须用这种方式,典型的约定优于配置原则。当然,在dbutils里你愿意二种字段名都一样也无可厚非。
       缺点:BeanProcessor是不支持关联查询的,所以上面的方式也只能局限于单表的转换,这点就不如myBatis和Hibernate,当然用这二个就引入了一些复杂性,如何权衡需要自己衡量,哪个用得好都一样。本人就不喜欢myBatis那种把SQL写到XML中的方式,见过太复杂的SQL最终在XML里面变得面目全非,如果是接手别人的代码,是很痛苦的,而且你无法避免只修改XML而不改Java,既然二者都要改,那直接写Java里又有什么区别?简单就是美。格式和注释写好一点同样很容易理解!

posted @ 2011-04-26 16:41 寒武纪 阅读(4252) | 评论 (0)编辑 收藏

posted @ 2011-03-09 16:48 寒武纪 阅读(13743) | 评论 (4)编辑 收藏

1.  Redmine安装前提条件

官方的安装指南:http://www.redmine.org/projects/redmine/wiki/RedmineInstall

Notes:

Ruby 1.9 is not supported yet. You have to use Ruby 1.8.x as stated above.
RubyGems 1.3.1 or higher is required (Rails 2.3.5 will fail with RubyGems 1.5.0 and later, stick to previous versions of RubyGems)
Rake 0.8.3 or higher is required
Rack 1.0.1 is required. If you don't have this exact version, database migration would fail.
I18n 0.4.2 is required for Redmine >= 1.0.5

 安装主要版本选择:ruby 1.8.6rubygems1.3.5rake 0.8.3rack 1.0.1I18n 0.4.2rails 2.3.5

     
2.  yum安装ruby

使用yum安装ruby相关的软件

yum -y install ruby ruby-devel ruby-libs ruby-irb ruby-rdoc ruby-mysql

如果没有ruby-mysql则从http://www.tmtm.org/en/ruby/mysql/ 下载手动安装

3.
升级ruby
1.8.6版本

/etc/yum.repos.d/ 目录下创建yum源文件ruby.repo,内容如下:

--------------------------------------------------------------------------------

[ruby] 
name=ruby 
baseurl=http://repo.premiumhelp.eu/ruby/ 
gpgcheck=0 
enabled=0 

--------------------------------------------------------------------------------

升级ruby

yum --enablerepo=ruby update ruby

4.  安装rubygems1.3.5

因为直接通过yum安装的rubygems0.9.4,所以选择手工下载安装的方式

wget http://rubyforge.org/frs/download.php/60718/rubygems-1.3.5.tgz
tar xzvf rubygems-1.3.5.tgz
cd rubygems-1.3.5
ruby setup.rb

安装完后运行gem –v检查一下版本是否正常,(当然还可以直接用yum安装,然后通过gem本身的更新来实现,那从rubyforge下载的就应该是.gem结束的升级文件)

5.  安装rails 2.3.5

gem install rails –v=2.3.5

6.  安装Rack 1.0.1

gem install rack –v=1.0.1

7.  安装Rake 0.8.3

gem install rake –v=0.8.3

8. 安装I18n 0.4.2

gem install -v=0.4.2 i18n

9.  下载和安装redmine1.1.1

wget http://rubyforge.org/frs/download.php/74128/redmine-1.1.1.tar.gz

拷贝压缩文件到要安装的目录,比如 /var/www目录下,解压

tar xzvf redmine-1.1.1.tar.gz

cd redmine-1.1.1

配置数据库连接yml文件(redmine目录下进行如下操作)

cd config

cp database.yml.example database.yml

vi database.yml

添加如下内容:

production:
adapter: mysql
database: redmine
host: localhost
username: root
password: xxx
socket: /var/lib/mysql/mysql.sock

数据库要预先创建好,如果你装好了mysql,直接运行mysql -uroot -p 登录,然后create database redmine,主机名、用户名、密码也要写对。

10.   生成会话密钥

 rake config/initializers/session_store.rb

11.  rails数据库生成和数据初始化

rake db:migrate RAILS_ENV=production
rake redmine:load_default_data RAILS_ENV=production

12.  运行测试

如果没有异常,在redmine安装目录下执行启动服务器的命令:

ruby script/server -e production &

这样redmine就会侦听本机IP3000端口,输入URLhttp://IP:3000 就可以看到登陆界面,如果是本机就直接 http://localhost:3000

但这样只是以独立的方式启动redmine的服务器,在后台执行,有些不足,因为客户端的访问日志会在终端上直接显示。并且你退出终端时,服务器进程也会跟着关闭,后面再介绍启动和关闭脚本的编写,以及如何用nginx做反向代理,或是用Apache也可以,这个网上可以搜索到很多资料。

13.  附注:redmine默认端口是3000,如果你是远程操作,直接访问主机的IP或是域名是无法打开主页的,因为centosiptables默认是没有开通3000端口的,所以需要开放端口。

打开iptablesvi /etc/sysconfig/iptables

添加下面一行到文件里面

-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 3000 -j ACCEPT

然后重启iptables

/sbin/service iptables restart

到此为止就完成redmine的安装,过程比较繁琐,主要是各种组件和模块的版本匹配问题。大多数情况下根据安装的错误提示和Google就可以解决滴。

 

posted @ 2011-03-01 09:47 寒武纪 阅读(3087) | 评论 (1)编辑 收藏

        非常喜欢这种轻量级的JDBC封装,比起Hibernate和iBatis,可以非常自由和灵活地运用和自行二次封装,由于dbutils的BeanHandler转换方式采取了反射技术,在性能上肯定有所损失,所以项目中基本上都使用MapHandler方式来转换数据,当然就是自己写的代码多一点,也无所谓。一般的查询、子查询、联合查询、包括视图查询等等都很正常,但是发现一个比较小的问题,就是在使用聚合函数的场所,例如:select user_type, count(*) as count from `user` group by user_type这种类型查询的时候,MapHandler方式不起作用,as列都变成key为空串的K-V对,导致有许多地方使用map.get("")代码的情况出现,这种写法当然是不太好的,容易出问题。
        鉴于前面没有时间了解,就都粗略使用了上面那种粗暴的map.get("")来处理,最好的情况是让dbutils组件能自动识别到as类型的列名。于是有空了就专门看了看它的源代码,发现最主要的一段代码如下:
 1public Map<String, Object> toMap(ResultSet rs) throws SQLException {
 2        Map<String, Object> result = new CaseInsensitiveHashMap();
 3        ResultSetMetaData rsmd = rs.getMetaData();
 4        int cols = rsmd.getColumnCount();
 5
 6        for (int i = 1; i <= cols; i++{
 7            result.put(rsmd.getColumnName(i), rs.getObject(i));
 8        }

 9
10        return result;
11    }
        CaseInsensitiveHashMap是dbutils自定义的一个Map,忽略键大小写的K-V字典,但是key使用的是ResultSetMetaData.getColumnName(),我想问题大概出在这里,于是认真翻了翻java的api文档(开发做久了容易遗忘基础),果然,原来getColumnName()是:获取指定列的名称;而as关键字之后,使列名称变成用于显示的意义,这个时候应该使用getColumnLabel():获取用于打印输出和显示的指定列的建议标题。建议标题通常由 SQL AS 子句来指定。如果未指定 SQL AS,则从 getColumnLabel 返回的值将和 getColumnName 方法返回的值相同。自己手动试验了一下,果然如所料,问题就出在这里。
        所以呢,如果想要dbutils在自动转换Map及MapList时能识别聚合函数的列名,那么最好的做法就是重载这种方式,懒一点的,你就干脆修改上面那段代码,让它判断是否使用了as关键字。个人暂时搞不清楚官方为什么没有考虑这一步,有时间再思考一下!

posted @ 2011-02-12 17:33 寒武纪 阅读(3013) | 评论 (7)编辑 收藏

    在项目中刚好有一个地方需要在服务器端处理一个请求后,重定向到另一个Action,这样浏览器的url才会变成另一个url,用户重新刷新时,才不会弹出一个对话框问你是不是要重新提交form。于是就自然而然地用了redirectAction。大概如下:
   
1<result name="myInfoSuccess" type="redirectAction">
2    myapp_myInfo.action?msg=${msg}
3</result>

    因为重定义会丢失所有的请求参数和值栈,所以这里转向时,加了一个请求参数msg,msg在要重定向的action中设置。
    问题来了,重定向到myapp_myInfo.action时,这个Action里面取出msg参数时变成乱码!!! 不论中文或是英语还是数字,全是乱码,折腾了一翻,URLEncoderURLDEncoder进行URL Base64编码和解码处理,包括new String(msg.getBtye("ISO-8859-1"), "UTF-8")这种处理方式仍无法奏效。google了一下并且抱起书本认真看了看struts2重定向问题后。大概有了个思路。
    所有的重定向操作都会丢失所有的请求参数、请求属性等,当然包括Action的处理结果也会丢失。 
    首先搞清楚redirect、redirectAction的区别:
    1. redirect类型struts2是调用HttpServletResponse的sendRedirect(String)方法来重定向到指定的资源,可以是一个视图结果,也可以是其它类型的Action;
    2. redirectAction同样是重新生成一个全新的请求。但是struts2内部却是使用ActionMapperFactory提供的ActionMapper来重定向,它只能跳转到另外一个Action;

         由于redirectAction使用的是ActionMapper来重定向,也就同时使用ActionMapper的编码方式重新进行编码,这就导致了后面在取出参数时变成乱码,没有具体阅读它的源代码,但是多次不同的编码再想重新还原出来就有点麻烦了。而redirect是使用HttpServletResponse来重定向,就不存在上面的问题。最后改为redirect来重定向,结果如下:
1<result name="myInfoSuccess" type="redirect">
2    <param name="location">myapp_myInfo.action?msg=${msg}</param>
3    <param name="encode">true</param>
4</result>
        注意:在myapp_myInfo.action对应的Action必须对msg参数做一次转码,因为前面的Action过来时就做了URL base64编码,如果直接发给浏览器,就会在浏览器看到一串带%的URL base64编码字符,所以要加上
1String msg = URLDecoder.decode(getMsg(), "UTF-8");
2setMsg(msg);
       把它设置回为中文,浏览器才能正常。

       还得提到另一个重定向类型chain,它是Action链,还能维持当前的值栈不变。不过用它重定向后,虽然跳到其它Action,但是在浏览器端的URL是不会变化的,这样开头提到的那个问题仍是无法解决的!

posted @ 2011-01-14 16:41 寒武纪 阅读(6758) | 评论 (3)编辑 收藏

     摘要: 项目中使用了FreeMarker做为视图技术,相对来说因为freemarker在视图上有一些逻辑处理功能,某些地方就显得比较方便,特别是macro的使用,当然也不是说JSP就没有这个功能,只是以前用JSP写起来没有这么顺手.......  阅读全文

posted @ 2010-11-10 15:43 寒武纪 阅读(3082) | 评论 (3)编辑 收藏

     摘要: 都说roller在国外是二次开发博客系统的首选,但是下载源代码之后发现是它是基于netbeans项目结构开发的。平时用习惯了Eclipse,所以想搬到MyEclipse上面,但没有想到居然那么不容易,折腾了整整一天,才最终跑起来。网上的参考信息太少,大概都是那二三篇的转贴,全部结合起来就差不多可以解决,借此总结一下,希望用到的朋友有参考作用!  阅读全文

posted @ 2009-11-22 21:04 寒武纪 阅读(2241) | 评论 (2)编辑 收藏

     摘要:   阅读全文

posted @ 2009-10-14 17:41 寒武纪 阅读(2160) | 评论 (1)编辑 收藏

    最近的二个项目,由于规模较小,都是十张表之内,而且表关联非常少。所以用了一下iBatis做为数据库关系映射,本着减少手写JDBC代码的目的,想着可以减少工作量。但是却遇到了二个令人郁闷的问题。由于环境的限制,使用了jdk1.4.x编译的iBatis2.3版本,没有使用最近的。

    第一问题:  其中的一个项目,有一个表为它配置了sql Map的一个delete操作,非常简单,大概就是delete from xxx where id=#value#这样的语句,然后用sqlMapClient进行操作,日志打印完全正常,没有报任何Exception,返回影响记录数也是正确的。但是进数据库一看,巍然不动!左查查,右查查,查不出任何毛病。更奇怪的是,数据库表之间的所有关联和索引全部取消,还是存在这问题。其它的三个字段比较少的表,这样配置,同样的api调用却正常!这个出问题的数据库表字段大概20+个左右。

    第二个问题:另一个项目,是二期重构,本来一期也不复杂,全部是使用JDBC实现的,只是有些表的字段太多,JDBC写到烦,特别是处理一些NULL的插入,还有批处理时异常日志的详细处理也有点烦。近期做二期升级,就算采用iBatis来减少一些代码量,于于喜涮涮地搞上去了,代码的确减少了许多。单元测试也能通过,后来就设置了比较复杂的数据。发现问题的现场如下:在一个业务接口中,一个事务中包含了许多SQL操作,有delete,也有insert,大概十个sql语句左右,全部放在一个batch中执行,整个batch提交一个事务。测试环境提供了31个类似的业务数据,总共执行31个事务,采用for的循环执行调用,每逢索引 i = 10*n  的时候就会卡住,这个操作得花很长时间,最后能通过。后来进行跟踪,发现是在执行第一个语句delete一个记录(delete from xxx where id='xx')同样也是单表删除。搜索了google,baidu,没有任何资源,翻遍了文档没有任何说明,查了网站FAQ也没有办法。于是,只能.......郁闷!

   为什么遇到delete都会有这个问题?不晓得有没有高手遇到同样的问题,这里算是总结的同时也提问,希望有遇到相同类型的高手给个解决的方案。如果不行,就得倒回去用JDBC实现,就此iBatis的体验使用也就搁置,估计以后也不会碰它了。Hibernate就不用了,有点小题大作。
   google了才知道,原来iBatis的书籍、论坛、资料、讨论等等相比Hibernate要少很多。学习是很简单,但是遇到这种细节的时候,又不太愿意花时间去研究源代码(都是现实所逼,有个球时间呀?)。所以选框架要慎重!!!

posted @ 2009-08-21 16:52 寒武纪 阅读(4308) | 评论 (24)编辑 收藏

     摘要:   阅读全文

posted @ 2009-07-31 15:58 寒武纪 阅读(2151) | 评论 (6)编辑 收藏

Java环境安装
    1.  从sun主页下载JDK for Linux版本。这里下载的是jdk-6u6-linux-i586.bin。
    2. 用root用户登录ubuntu,或是在普通用户下用su命令切换用户。切换到所需的安装目录。类型:cd <目录路径名>   例如,要在 /usr/java/ 目录中  安装软件,请键入:cd /usr/java/,把jdk-6u6-linux-i586.bin文件拷贝这个目录里面,设置权限为可执行类型:chmod a+x jre-6u6-linux-i586.bin
    3. 启动安装过程。键入:./jre-6u<version>-linux-i586.bin。接下来会提示二进制的许可协议,键入yes回车即可。安装过程如果遇到一些问题,都同样键入yes就可以。
    4. 一路下来,最后看到Done字样,就完成了Java环境的安装。安装的位置就是当前目录 /usr/java,当然你可以选择在别的位置。可以用ls命令查看一下是否正常。

环境变量配置
   上面安装完毕后,直接在shell里面输入java是不起作用的,需要先配置一下环境变量。一般都会用export命令,不过这样设置只对当前shell起作用,重启或是切换到别的shell会话就不起作用了。可以选择配置 .bashr文件。用vi或是gedit打开,在末尾添加下面的内容
   export JAVA_HOME=/usr/java/jdk1.6.0_06
   export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
   export PATH=$PATH:$JAVA_HOME/bin
   然后保存。再在shell里面试验一下是否安装成功。echo一下各个变量是否正常,然后输入java -version看看。
  还有一种方式是修改/etc/profile,这样相当于修改系统配置文件,对所有用户都有影响,我在尝试的过程可能输入了一些异常字符,导致整个ubuntu无法用GUI登录,无奈只能用文本登录,然后再次把profile改回来才正常。
   好了之后可以先试一下用vi新建一个Hello的Java文件,然后编译一下试运行。

Eclipse安装
   Linux下面的Eclipse在ubuntu的界面渲染下看起来非常漂亮。先从Eclipse社区下载一个Linux版本的,这里下载的是europa版本的gz包。只要解压到一个目录就可以,这里选择/opt/eclipse下面。Eclipse是解压就可以使用的。不过为了方便,我们在桌面做一个启动器把启动目标指向到Eclipse的安装目的地,选择里面已有的图标文件,这样就完成了。不过默认Eclipse是找不到Java执行路径的,有网友写了这样一个脚本eclipse.sh 放到/usr/local/bin目录下,记得加上775权限。然后把启动器位置指到这里eclispe.sh。下面是eclipse.sh的内容:
    #!/bin/bash
    #
    # 执行 eclipse 2.1.1
    #
    export JAVA_HOME=/usr/java/XXX
    export CLASSPATH=/usr/java/XXX/lib
    /opt/eclipse/eclipse -vm /usr/java/XXX/bin/java -data ~/workspace &
    # -vm 参数用以指定使用哪一个 jvm 来执行Eclipse,
    # -date参数用以指定Eclipse的数据目录。在此指定其存在用户根目录(~)下的workspace目录中
  
   还有一个比较笨的方法,Eclipse默认会去找它自己目录下是不是有jre存在,如果有,它就可以启动,那么你可以直接把先前安装好的JDK里面的JRE目录整个复制到Eclipse里面。然后就可以直接运行了。下面看一下效果
  

posted @ 2008-08-29 17:07 寒武纪 阅读(38176) | 评论 (6)编辑 收藏

    ActionSet是Eclipse RCP里面一非常重要的概念,因为菜单、工具栏、上下文菜单、状态栏很多操作都是共享的,所以Action就是用来处理重复出现的东西。至于Eclipse里面定义ActionSet有非常多的技巧,可能无法一一列举,而且使用方法也多种多样。下面介绍的是RssOwl2项目的ui源代码部分的一小块。
   1.  菜单的插入点 -- GroupMarker和Separator的使用
        ApplicationActionBarAdvisor类是定义全局所有Action插入点和入口,查看fillMenuBar(IMenuManager)方法,为了简化,以其中的辅助方法createFileMenu(IMenuManager)为例,讲述一下实现菜单“文件”的内容,先看一下菜单的结构

       像Close,Import...之类的非常简单,看一下它是如何实现New这个子菜单的。首先看一下它的源代码如何定义插入点
     
/* Menu: File */
  
private void createFileMenu(IMenuManager menuBar) {
    MenuManager fileMenu 
= new MenuManager("&File", IWorkbenchActionConstants.M_FILE);
    menuBar.add(fileMenu);

    fileMenu.add(
new GroupMarker(IWorkbenchActionConstants.FILE_START));
    fileMenu.add(
new GroupMarker(IWorkbenchActionConstants.NEW_EXT));
    fileMenu.add(
new Separator());

    fileMenu.add(getAction(ActionFactory.CLOSE.getId()));
    fileMenu.add(getAction(ActionFactory.CLOSE_ALL.getId()));
    fileMenu.add(
new GroupMarker(IWorkbenchActionConstants.CLOSE_EXT));
    fileMenu.add(
new Separator());
    fileMenu.add(getAction(ActionFactory.SAVE_AS.getId()));
    fileMenu.add(
new GroupMarker(IWorkbenchActionConstants.SAVE_EXT));
    fileMenu.add(
new Separator());
    fileMenu.add(getAction(ActionFactory.PRINT.getId()));

    fileMenu.add(
new Separator());
    fileMenu.add(
new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));

    fileMenu.add(fReopenEditors); 
// TODO Consider moving into a "Go" Menu!

    fileMenu.add(
new Separator());
    fileMenu.add(
new GroupMarker(IWorkbenchActionConstants.FILE_END));
    fileMenu.add(
new Separator());

    fileMenu.add(getAction(ActionFactory.QUIT.getId()));
  }
       其中有一行fileMenu.add(new GroupMarker(IWorkbenchActionConstants.NEW_EXT)); 这里是定义一个GroupMarker作为组标记,把子菜单New容纳进来。这个NEW_EXT的值是:new.ext
      现在跳回到plugin.xml去看一下它的ActionSet定义,结构如下:
,点击New(menu),它的path值为:file/new.ext,这个路径就是在createFileMenu方法定义的路径,第一个是“File”本身的ID。也就是把子菜单New(menu)插入到指定的那个GroupMarker,ID为new.ext。然后定义了三个ID分别为bookmark,newsbin,searchmark,的groupmarker和一个folder的separator,这个三ID分别就对应上面actionSet定义的三个action,以其中的Bookmark(action)为例,它的menubarPath为:file/new_sub/bookmark,代表插入到"File"主菜单中定义的new_sub子菜单中,new_sub是New(menu)的ID。因为folder是定义为separator,所以它会有一条分隔线。这只是RssOwl的定义方法,其实以前自己做开发的时候是没有这样定义的,而且把子菜单New也写在方法fillMenuBar中的,菜单把ID都写在里面,ActionSet的配置就没有子菜单出现了,但是这样定义看起来就比较乱。采用这种写法感觉比较简洁。
    2.  Action的实现
       仍以bookmark为例,它的实现类是NewBookMarkAction,实现了IWorkbenchWindowActionDelegate, IObjectActionDelegate二个接口,第一个是ActionSet指定实现接口,第二个是对象操作菜单要求实现的接口(但事实发现没有再定义它的配置,可能是internal版本的原因),也就是说这个Action是多功能,它将会出现在主菜单,工具栏,和局部的右键菜单上。主菜单和工具栏的位置都在ActionSet配置定义了,看看它的右键菜单实现是在哪里的,这个右键是在视图Bookmarks定义的,那么跳转到org.rssowl.ui.internal.views.explorer.BookMarkExplorer类去看看。里面有一个hookContextualMenu()方法,就是定义它的右键菜单的,看一下代码实现:
private void hookContextualMenu() {
    MenuManager manager 
= new MenuManager();

    
/* New Menu */
    MenuManager newMenu 
= new MenuManager("New");
    manager.add(newMenu);

    
/* New BookMark */
    newMenu.add(
new Action("Bookmark"{
      @Override
      
public void run() {
        IStructuredSelection selection 
= (IStructuredSelection) fViewer.getSelection();
        IFolder parent 
= getParent(selection);
        IMark position 
= (IMark) ((selection.getFirstElement() instanceof IMark) ? selection.getFirstElement() : null);
        
new NewBookMarkAction(fViewSite.getShell(), parent, position).run(null);
      }


      @Override
      
public ImageDescriptor getImageDescriptor() {
        
return OwlUI.BOOKMARK;
      }

    }
);

   
//其它定义
}
     原来实现也很简单,只是往MenuManager里面添加一个Action而已,而且run方法就是直接调用定义好的NewBookMarkAction的run方法,但是把选中对象做为参数传进去,因为这个new是涉及当前上下文选择对象的。
    3. 下拉类型的工具按钮定义
    非常常见的Dropdown类型的工具栏按钮可以把功能类型的按钮归为一类,做成一个下拉菜单形式,有默认的按下功能,也有可以选择其它类似功能的下三角形式,样子如下:

    这个dropdown的Action是定义在ActionSet配置里的。style是pulldown类型的,所以实现类NewTypeDropdownAction实现了IWorkbenchWindowPulldownDelegate接口,它的run方法就是定义默认点击不做选择时的事情,这个下拉菜单是实现getMenu(Control parent)方法而来,它定义了如何生成这个菜单,这就用到了最原始的SWT中的MenuItem了,并且为它们添加SelectionListener,方法实现,不用说都知道了,又是New一个先前定义好的NewBookMarkAction类,然后又是调用它的run方法。所以总结一下,Action的重用不一定是这个类的重用,关键是它的run方法的重用,在不同的场景下它的外在表现形式可能会多种多样,但是它的run内容是一致的。像添加这种添加的run大部分时候都是弹出一个对话框,而对话框大都又是Winzard类型的,因为Winzard可以共享放到dialog里面。所以这种复用的思想在Eclipse里面随处可见。
   归结一下,其实这些技巧都是次要的,因为做GUI一个比较痛苦的事情就是经常要写很多重复类似的代码,抽取的不好,可能就变得不伦不类了。怎么利用它的这种思想,把复用的代码都抽取在一起,而阅读起来又比较轻松才是关键。
   知道的就这些,先介绍到这里,下次再谈谈其它新的发现。

posted @ 2008-08-21 11:29 寒武纪 阅读(1786) | 评论 (0)编辑 收藏

     摘要:     许久没有弄RCP了,刚好近来闲暇一点,找来个RSSOwl的源代码看看,有点收获。RssOwl非常出名,只是可能很多人不知道它是用Java做的。以前看过RssOwl第一版的源码,没有详细研究,down下来之后放上公司的共享CVS服务器,倒是几个同事饶有兴趣地研究起来。第一个版本写得较早,可能Eclipse的RCP框架都还没有出来,所以全部采用的SWT/JFace...  阅读全文

posted @ 2008-07-31 15:13 寒武纪 阅读(2078) | 评论 (5)编辑 收藏

    最近一个程序出了点问题,对于中文参数的GET请求,服务器无法解析出正确的参数。刚好服务器的那端是另一个项目组负责,是异构系统,当初测试的时候也是走流程化,涉及到很多工作上的协调就比较麻烦,测试也不充分,像赶鸭子上架一样就上线了,催说是项目紧急。当然这是话外,不多废话。
    httpClient的GetMethod类加入参数的方法是如下:
void setQueryString(NameValuePair[] params)
          Sets the query string of this HTTP method.
 void setQueryString(String queryString)
   跟踪一下httpClient的GetMethod的源代码,继承自HttpMethodBase,源码如下:
  
public void setQueryString(String queryString) {
   
this.queryString = queryString;
}

    
public void setQueryString(NameValuePair[] params) {
   LOG.trace(
"enter HttpMethodBase.setQueryString(NameValuePair[])");
   queryString 
= EncodingUtil.formUrlEncode(params, "UTF-8");
}
   EncodingUtil是httpClient定义的一个编码工具类,由于默认设置的是UTF-8,所以对于一些系统可能就无法识别。可以在外部这样更改:
method.setQueryString(EncodingUtil.formUrlEncode(pair, "GB2312"));另外,注意请求头也要修改为对应的一致编码方式,method.addRequestHeader("Content-type" , "text/html; charset=GB2312");如果这二个编码不一致,就会引起乱码。
   刚开始的时候尝试过都使用一致的UTF-8,但是发现还是乱码,这应该是服务器的原因。IE默认的就是采用操作系统Windows的中文编码去进行Encoder的,服务器原先基本上都为IE服务的,所以改为GB2312就能正常识别得到。
   另外,上面提到的EncodingUtil这个工具是从apache的另一个组件codec包装而来的,而非SUN的URLEncoder。有兴趣的可以研读一下源代码。

posted @ 2008-07-16 10:31 寒武纪 阅读(5195) | 评论 (2)编辑 收藏

Jakarta的httpclient3.1是最新版本,项目中需要用程序模拟浏览器的GET和POST动作。在使用过程中遇到不少问题。
1. 带附件的POST提交
    最开始都是使用MultipartPostMethod这个类,现在已经废弃这个类了。API说明:Deprecated. Use MultipartRequestEntity in conjunction with PostMethod instead.   使用PostMethod可以实现的功能,就没有必要再弄一个MultipartPostMethod了。下面是一段最简单的示例:

PostMethod post = new PostMethod();
        NameValuePair[] pairs 
= new NameValuePair[2];
        pairs[
0= new NameValuePair("para1""value1");
        pairs[
0= new NameValuePair("para2""value2");
        post.setRequestBody(pairs);
        HttpClient client 
= new HttpClient();
        
try {
            client.executeMethod(post);
        }
 catch (HttpException e) {
            e.printStackTrace();
        }
 catch (IOException e) {
            e.printStackTrace();
        }
   这是针对一般的form形式的提交,而且这个form里面不带附件的。如果带附件,那么这种方法就不起作用,附件上传的参数和普通参数无法一同在服务器获取到。org.apache.commons.httpclient.methods.multipart 这个包就是为处理文件上传这种多形式参数的情况的。最主要的类是Part(代表一种post object),它有二个比较重要的子类:FilePart和StringPart,一个是文件的参数,另一个就是普通的文本参数。它的典型使用方法如下:
String url = "http://localhost:8080/HttpTest/Test";
         PostMethod postMethod 
= new PostMethod(url);
         
         StringPart sp 
= new StringPart("TEXT""testValue");
         FilePart fp 
= new FilePart("file""test.txt"new File("./temp/test.txt"));
         
         MultipartRequestEntity mrp
= new MultipartRequestEntity(new Part[]{sp, fp}, postMethod
                 .getParams());
         postMethod.setRequestEntity(mrp);
         
         
//执行postMethod
         HttpClient httpClient = new HttpClient();
         
try {
            httpClient.executeMethod(postMethod);
        }
 catch (HttpException e) {
            e.printStackTrace();
        }
 catch (IOException e) {
            e.printStackTrace();
        }
    在第二行PostMethod postMethod = new PostMethod();后面,有人说需要使用postMehtod.setRequestHeader("Content-type", "multipart/form-data"); Content-type的请求类型进行更改。但是我在使用过程没有加上这一句,查了一下httpCleint的默认Content-type是application/octet-stream。应该是没有影响的。对于MIME类型的请求,httpclient建议全用MulitPartRequestEntity进行包装,就是上面的用法。

2.  参数中文的处理问题
    httpclient的默认编码都是ISO-8859-1,那肯定就无法支持中文参数了。引用一下这篇文章:http://thinkbase.net/w/main/Wiki?HttpClient+POST+%E7%9A%84+UTF-8+%E7%BC%96%E7%A0%81%E9%97%AE%E9%A2%98 ,按照作者的说法,就可以正常解决中文编码的问题。其中最关键的是修改EncodingUtil这个类的一个方法实现。另外,FilePart和StringPart的构造方法都有一个带编码指定的参数,为了减少问题的出现,建议所有的都带上统一的编码,包括postMethod.getParams()。示例如下:
String url = "http://localhost:8080/HttpTest/Test";
         PostMethod postMethod 
= new PostMethod(url);
         
         StringPart sp 
= new StringPart("TEXT""testValue""GB2312");
         FilePart fp 
= new FilePart("file""test.txt"new File("./temp/test.txt"), null"GB2312");
         
         postMethod.getParams().setContentCharset(
"GB2312");
         MultipartRequestEntity mrp
= new MultipartRequestEntity(new Part[]{sp, fp}, postMethod
                 .getParams());
         postMethod.setRequestEntity(mrp);
         
         
//执行postMethod
         HttpClient httpClient = new HttpClient();
         
try {
            httpClient.executeMethod(postMethod);
        }
 catch (HttpException e) {
            e.printStackTrace();
        }
 catch (IOException e) {
            e.printStackTrace();
        }

posted @ 2008-06-11 15:18 寒武纪 阅读(6907) | 评论 (8)编辑 收藏