分类 技术笔记 下的文章

Window 7 安装 Apache 2.4 和 PHP 5.4 过程

现在使用UI向导安装AMP(Apache,MySQL和PHP)已经变得非常容易,比如XAMPP,WAMP。但是使用这些集成的包安装你只能获取很少的知识,对于其配置却一无所知,安装完之后也无法按照自己的方式去修改。因此我推荐手动去安装配置它们。

一、下载地址

注意,VC9线程安全版本中已经包含了PHP和Apache connector DLL,因此无需下载此DLL。

二、配置

1. Apache

使用任意编辑器打开 apache2.4/conf/httpd.conf 文件开始配置。

1.1 设置 Apache 位置

ServerRoot "D:/Program Files/apache2.4"

1.2 启用使用的模块

我只去掉了 mod_rewrite 模块的注释。

1.3 在模块内容下增加以下内容

LoadModule php5_module "D:/Program Files/PHP5.4/php5apache2_4.dll"
AddHandler application/x-httpd-php .php
PHPIniDir "D:/Program Files/PHP5.4"

1.4 修改服务器管理员邮件地址

ServerAdmin info@yoursite.com

1.5 修改文档根目录

DocumentRoot "E:/www"
<Directory "E:/www">

1.6 找到一下内容替换实际的路径

ScriptAlias /cgi-bin/ "D:/Program Files/apache2.4/cgi-bin/"
<Directory "D:/Program Files/apache2.4/cgi-bin">

1.7 如果你想启用 .htaccess 请修改 <Directory “D:/www”> 下内容

AllowOverride All

1.8 添加 index.php 到 index 目录中

DirectoryIndex index.html index.php

1. PHP

1.1 重命名 php.ini-development 为 php.ini

1.2 修改扩展路径

extension_dir = "D:/Program Files/PHP5.4/ext"

1.3 取消以下行的注释

extension=php_curl.dll
extension=php_mysql.dll
extension=php_mysqli.dll
extension=php_pdo_mysql.dll
extension=php_soap.dll

1.4 如果你使用 PHP 的邮件功能请修改下面内容

SMTP = smtp.yoursite.com
smtp_port = 25
sendmail_from = youremail@sender.com

1.5 最后设置下时区

date.timezone = PRC

三、安装

需要将 Apache 2.4 的服务安装到系统服务中,使用以下命令(需要管理员权限):

cd D:/Program Files/apache2.4/bin
httpd -k install

编写一个 index.php 文件,内容为 <?php phpinfo() ?>, 启动apache服务,访问以下 http://localhost/ 吧。

基于静态文件的新博客

周五浏览网页时偶然看到别人用 Liquidluck 搭建的静态博客站点:利用 GitHub 的webhook功能进行自动部署,同时支持 Markdown 文件,可将其自动输出为静态文件,此外还支持一些博客常见的功能,如标签、分类、归档等,非常吸引人。

于是,周末两天折腾,部署上之后,自己还做了套主题 liquidluck-theme-moodpo ,就是现在这个样子。期间遇到各种问题,趁还没忘记录下来。

一、安装过程

在安装 Liquidluck 之前需要安装 distribute 和 seuptools ,以便使用 pip 安装 Liquidluck。

$ wget http://pypi.python.org/packages/source/d/distribute/distribute-0.6.28.tar.gz

$ tar xvzf distribute-0.6.28.tar.gz

$ cd distribute-0.6.28

$ python setup.py install


$ wget https://pypi.python.org/packages/2.4/s/setuptools/setuptools-0.6c11-py2.4.egg

$ wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py

$ python get-pip.py

$ pip -U install liquidluck

二、部署 Liquidluck

部署的过程相对简单,但还是有一些细节 Liquidluck 文档中没有写:

1. 在 GitHub 上建立博客的 repo

使用 webhook 的话一般直接把整个部署目录都放在 hook 范围之内,因此先使用 git 把目录下载下来,当然在此之前你应该在 GitHub 上先建立一个 repo,repo的大致内容如下:

blog
    -- content
        -- helloword.md
        -- media
            -- author.jpg
    -- settings.py # 你可以到 Liquidluck 的 GitHub 地址去下载一份 然后改一下相关配置
    -- README.md

然后 git 这个 repo:

$ git clone git://github.com/user/repo blog

2. 配置 webhook

首先在 GitHub 上配置好 webhook,具体操作可以看这里,然后启动本地 webhook 服务:

$ cd blog

$ liquidluck webhook start -p 9876

这样就完成了 webhook 的配置,你可以先 push 一个文件到 GitHub 上,看有没有同步过来。

需要注意的一点是,如果你要安装主题,不要再 webhook 的目录内 git ,否则 webhook 功能将失效

3. 部署 Liquidluck

部署并启动服务:

$ cd blog

$ liquidluck bulid

$ liquidluck server -p 80

$ nohup liquidluck server -p 80 > access.log 2>&1 &             #后台运行

关于 Liquidluck 的安装与部署的细节就是这些,更详细的内容请看官方文档

Update: Liquidluck 文件名、标签和分类不支持中文!

解决Ubuntu更新源Hash Sum mismatch错误

有时候在更新Ubuntu时会发生如下错误:

Fetched 9,718 kB in 36s (268 kB/s)                                             
W: Failed to fetch bzip2:/var/lib/apt/lists/partial/mirrors.163.com_ubuntu_dists_precise_restricted_source_Sources  Hash Sum mismatch

W: Failed to fetch gzip:/var/lib/apt/lists/partial/mirrors.163.com_ubuntu_dists_precise-updates_restricted_source_Sources  Hash Sum mismatch

E: Some index files failed to download. They have been ignored, or old ones used instead.

这是由于一些文件被墙的原因导致更新源服务器中的文件不完整,此时可以用Goagent代理去下载:

执行命令:

sudo apt-get -o Acquire::http::proxy="http://127.0.0.1:8087/" update

Hash Sum mismatch Error.png

Struts 2 的国际化

本节我们将快速浏览Struts 2的国际化机制,重点在于怎样使用国际化方法满足我们的各种需求,而不深究其内部详细的原理。

1. Struts 2 框架和Java i18n

Java平台早已内置了对i18n的支持,Struts 2提供了一个高层的、极其方便的本地Java对i18n支持的封装。首先我们先简要介绍下Java的基础概念。

1.1 使用ResourceBundle和Locale取得本地化文本

1. 在ResourceBundle中存储资源

Java的ResourceBundle是一个抽象类,其子类的实现管理包中包含了资源。ResourceBundle的子类能按照它们喜欢的任意方式管理它们的资源。Java平台提供了两个方便的子类供你使用,其中最常用的是PropertyResourceBundle,这个类从普通文本属性文件中加载资源,这些资源包括了各种版本内容的文件。

2. 使用本地Java ResourceBundle

如果我们想去的一个本地化文本,必须先获取对包含这个文本的包的引用。以下代码展示了这个功能:

Locale currentLocale = new Locale( "tr");
ResourceBundle myMessages = ResourceBundle.getBundle("EmailClientMessages",currentLocale);
String greetingLabel = myMessages.getString( "greeting");

getBundle()方法获取参数后会在其拥有的资源中进行查找,这个过程包含两个阶段。首先,它查找与接收到的包名参数匹配的属性文件,之后它会查找包的Java类实现。这些类实现由与属性文件命名相似的Java类来实现,例如EmailClientMessages_tr.java,最后她会发现这些属性文件,并使用这些属性文件创建一个ResourceBundle实例。

1.2 Struts 2如何解决本地Java对i18n支持的问题

Struts 2框架为i18n付出了很多努力。框架仍然使用我们刚刚看过的Java类,但事情变得更容易了。首先,不需要实例化ResourceBundle,Struts 2会自动创建它,并处理确定需要哪个包的所有琐碎内容。另一方面,框架也处理确定正确地域的过程,它通过检查来源于浏览器的HTTP头信息自动确定当前的Locale。当然,你也可以覆盖这个行为,并使用其他方式确定地域信息。

Struts 2包创建的过程使用约定和配置混合的方法来定位属性文件,它是一个双向的通道。Struts 2告诉你它在哪里查找属性文件,同时你也可以告诉它在哪里查找属性文件。

1.3 Struts 2的i18n如何工作

框架驱动国际化功能的机制由如下步骤完成:

  1. 让动作扩展ActionSupport,以便它们继承默认的TextProvider实现,
  2. 将一些属性文件放在默认的TextProvider可以发现的位置,
  3. 使用Struts 2的text标签或者通过OGNL直接调用getText()方法将消息取到页面中。

1.4 Struts 2 默认的TextProvider ResourceBundle搜索算法

TextProvider的默认实现会在几个常见的地方搜索开发人员创建的属性文件以创建ResourceBundle,这些位置中有很多都遵循与超类或实现的接口的命名相似的模式。以下展示了Struts 2尝试填充的ResourceBundle的名字和出处:

  1. ActionClass——是否有一个ResourceBundle与当前动作类的名字相同?换句话说就是是否有一系列的属性文件命名为ActionClass.properties、ActionClass*es.properties等?
  2. MyInterface——如果动作实现了任何接口,有没有与这些接口关联的ResourceBundle?也就是说,如果当前类实现了MyInterface接口,是否有一系列的属性文件命名为MyInterface.properties、MyInterface*es.properties等?并且每一个接口的超接口也会被查找,更具体的接口优先于更高层的超接口。
  3. MySuperClass——如果动作扩展了一个父类,那么是否有一个ResourceBundle与这个超类关联?这和接口的查找类似。但在继承链上低级别类的ResourceBundle优先于高级别类的ResourceBundle。也就是说,如果Object.properties存在,那么它是最后一个。
  4. 如果动作实现了ModelDriven接口,那么会使用模型对象的类查找ResourceBundle。也就是说,如果模型对象是User类,那么User.properties文件会被加载。
  5. package.properties——接着,搜索过程会尝试加载当前动作类所在包的ResourceBundle,以及这个链上的每一个父包。注意这些属性文件都叫做package.properties。
  6. 通过关键字引用的ValueStack上的域模型对象,这与第4步的ModelDriven很相似。对于一个给定的关键字,如user.username,如果ValueStack上公开了一个叫做user的属性,那么这个属性的类会用来加载ResourceBundle。
  7. 默认的ResourceBundle——Struts 2允许指定全局包,这个包总是可以访问的。

包搜索顺序中的前6步列出了Struts 2搜索属性文件过程中基于的约定位置,如果基于约定的包都不存在,那么就会使用默认包,也就是第7步所说的全局包。我们可以在struts.properties文件中或者某一个XML配置文件(例如struts.xml或者它包含的一个文件)的constant元素中设置这个属性和所有Struts 2属性。以下是具体的使用方法;

<constant name="struts.custom.i18n.resources" value="global-messages" />

以下是在struts.properties文件中配置的相同内容:

struts.custom.i18n.resources=global-messages

可以指定使用逗号分隔的一系列包,这些包按照给定的顺序被搜索,也可以使用包空间指定包的位置,如下所示:

struts.custom.i18n.resources=global-messages,manning.utils.otherBundle  

注意,如果同时使用这两种方式,struts.properties文件优于XML文件中的constant元素。

2. 从包中取得消息文本

在前面几节的讲解中我们已经捎带着说明了怎样从保重取得消息文本的方法,包括:使用UI组件标签的key属性、验证框架中使用、validate()方法中使用getText()方法、页面中使用text标签等,就剩下本地化类型转换错误消息的国际化了,下面我们就讲解它。

本地化类型转换错误消息的国际化

例如页面中有一个Age字段,当用户输入字母在后台会发生类型转换错误,为了给Age字段追加自定义类型转换错误消息,你只需要在某个可访问的保重追加一个属性,这个属性遵循的命名约定为invalid.fieldvalue.fieldname。以下属性为Age字段定义的一个错误消息:

invalid.fieldvalue.age=Please enter a numerical value for your age.

只需把上述内容追加到Register动作可访问的包(例如Register.properties文件)就可以使用了。此外,你可能想改变本地化默认的类型转换消息。你可以在默认的global-message保重追加以下属性:

xwork.default.invalid.fieldvalue=We can not convert that to a Java type.

Struts 2 的验证框架(二)

前一节中我们介绍了Struts2的验证框架及怎样自定义验证器,本节将介绍一些使用验证框架的高级主题。一些高级主题只研究了验证机制的细微差别,而另一些高级主题则展示了如何将验证框架应用到一些更特殊的Struts 2开发模式。这些内容将涵盖验证器的继承,将验证映射到域模型对象而不是动作,验证器失败时短路跳出验证等。

1. 在域对象级别验证

在域对象级别是指动作把整个User对象作为JavaBean属性公开出来。在域对象级别验证,首先需要做的是定义元数据,以下是User-validation.xml文件的全部内容:

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
       "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
  <field name="password">
      <field-validator type="stringlength">
         <param name="maxLength">10</param>
         <param name="minLength">6</param>
         <message>Your password should be 6-10 characters.</message>
      </field-validator>
      <field-validator type="passwordintegrity">
          <param name="specialCharacters">$!@#?</param>
          <message>Your password must contain one letter, one number, and one of the following "${specialCharacters}".</message>
      </field-validator>
  </field>
  <field name="username">
      <field-validator type="stringlength">
         <param name="maxLength">8</param>
         <param name="minLength">5</param>
         <message>While ${username} is a nice name, a valid username must be between ${minLength} and ${maxLength} characters long. </message>
     </field-validator>
  </field>
  <field name="email">
      <field-validator type="requiredstring">
          <message>You must enter a value for email.</message>
      </field-validator>
       <field-validator type="email">
         <message key="email.invalid"/>
      </field-validator>
   </field>
  <validator type="expression">
      <param name="expression">username != password</param>
      <message>Username and password can't be the same.</message>
  </validator>
</validators>

这个文件与之前的Register-validation.xml文件完全相同,但是我们需要把它放在User类同级目录下。User验证数据就绪后,我们需要把使用User的动作和这些验证元数据连接起来。这由visitor验证器完成。以下代码片段展示了UpdateAccount-validation.xml文件的简要内容,此文件将放在与UpdateAccount动作相同目录下:

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
       "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
   <field name="user">
      <field-validator type="visitor">
         <message>User:  </message>     
      </field-validator>    
  </field>
</validators>

这个文件仅仅使用visitor验证器将验证细节全部转交给在这个指定的字段名的类上定义的验证元数据,即指定了user字段。visitor验证器使用这个信息找到User-validation.xml并使用这个文件描述的验证逻辑验证user属性上所有的数据。而在message元素上我们只定义了验证产生的错误信息的前缀。

ModelDriven动作中使用验证框架

当动作实现ModelDriven接口时,也可以使用上述技术,对于UpdateAccount-validation.xml文件我们只需要做一下轻微的改变:

<validators>
   <field name="model">
      <field-validator type="visitor">
        <param name="appendPrefix">false</param>
        <message>User:  </message>      
      </field-validator>    
  </field>
</validators>

做了两处变更,首先,由于ModelDriven动作的域对象通过getModel()获取方法公开出来,所以现在我们需要改变指向model的字段名。其次,我们需要添加appendPrefix参数告诉visitor验证器不需要在字段前添加user前缀来获取字段值。

2. 使用验证上下文优化验证

有时候可能需要一种更加细粒度的对什么时候运行哪些验证的控制级别,为了控制这些内容,验证框架引入了上下文的概念。数据验证上下文提供了一种简单的方法来识别我们想验证的数据,在使用这些数据的应用程序中的具体位置。

如果一个动作类中包含了多个方法作为动作的路口点时,我们就需要使用验证上下文。具体的做法是为这几个方法分别建立遵循:类名+-+方法名+-+validation.xml这个规则的文件,并在文件中定义验证逻辑。如果你仍然定义了一个类名+-+validation.xml的文件,那么这个文件中定义的验证也会被加载。

与visitor验证器和域对象一起使用验证上下文时,方法与此相同,比如只需在User类所在的目录下定义类名+-+方法名+-+validation.xml的文件分别添加验证规则即可。

显然这种方式很可能会发生冲突而不适合,幸运的是,我们可以在visitor验证器中使用context属性定义一个上下文名称来替换之前文件名中的方法名。使用如下:

<field name="user">
    <field-validator type="visitor">
        <param name="context">admin</param>
        <message>User: </message>
    </field-validator>
</field>

于是我们就可以使用User-admin-validation.xml的名称命名文件而避免冲突。

3. 验证继承

我们已经知道验证可以被声明在不同级别的不同上下文中,现在我们简要的描述一下验证声明的继承。下面展示了当框架开始处理时收集验证文件位置的顺序:

  • SuperClass-validation.xml
  • SuperClass-aliasName-validation.xml
  • Interface-validation.xml
  • Interface-aliasName-validation.xml
  • ActionClass-validation.xml
  • ActionClass-aliasName-validation.xml

在定义验证时,应该基于这个结构在这个搜索列表的更高层定义通用的验证,这样允许你重用这些定义。

4. 验证短路效应

验证框架的一个有用特性是当一个给定的验证失败时,它能够像短路一样停止后续验证。下面是一个用例:

<field name="password">
    <field-validator type="stringlength" short-circuit="true">
        <param name="maxLength">10</param>
        <param name="minLength">6</param>
        <message>Your password should be 6-10 characters.</message>
    </field-validator>
    <field-validator type="passwordintegrity">
        <param name="specialCharacters">$!@#?</param>
        <message>Your password must contain one letter, one number, and one of the following "${specialCharacters}".
        </message>
    </field-validator>
</field> 

这里我们追加的唯一内容是short-circuit属性,把它设置为true。这样做的目的是想在stringlength检查失败的情况下不让passwordintegrity检查运行。注意,虽然这个short-circuit定义在一个字段验证器上,但是这个字段剩余的验证都会成为短路的。如果在动作级别定义短路,那么所有的验证都将成为短路的。

5. 使用注解声明验证

使用注解声明验证和使用XML没有什么不同,下面是一个完整示例:

@Validation

public class RegisterValidationAnnotated extends ActionSupport implements SessionAware {

    @ExpressionValidator(expression = "username != password", message = "Username and password can't be the same.")
    public String execute(){
        /*
         * Create and move the data onto our application domain object, user.
         */
        User user = new User();
        user.setPassword( getPassword() );
        Portfolio newPort = new Portfolio();
        newPort.setName( getPortfolioName() );
        user.getPortfolios().add( newPort );
        user.setUsername( getUsername() );

        /* Login the newly created user */
        getPortfolioService().persistUser( user );
        session.put( Struts2PortfolioConstants.USER, user );

        return SUCCESS;
    }

    /* JavaBeans Properties to Receive Request Parameters */
    private String username;
    private String password;
    private String portfolioName;
    private boolean receiveJunkMail;
    private String email;

    @RequiredStringValidator(type = ValidatorType.FIELD, message="Email is required.")
    @EmailValidator(type = ValidatorType.FIELD, key="email.invalid", message="Email no good.")
    public void setEmail(String email) {
        this.email = email;
    }
    public String getEmail() {
        return email;
    }

    @RequiredStringValidator(type = ValidatorType.FIELD, message = "Portfolio name is required.")
    public String getPortfolioName() {
        return portfolioName;
    }
    public void setPortfolioName(String portfolioName) {
        this.portfolioName = portfolioName;
    }

    @StringLengthFieldValidator(type = ValidatorType.FIELD, minLength="5" , maxLength = "8",  message = "Password must be between ${minLength} and ${maxLength} characters.")
    @RequiredStringValidator(type = ValidatorType.FIELD, message = "Password is required.")
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    @RequiredStringValidator(type = ValidatorType.FIELD, message = "Username is required.")
    @StringLengthFieldValidator(type = ValidatorType.FIELD, minLength="5" , maxLength = "8",  message = "Username must be between ${minLength} and ${maxLength} characters.")
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

    public boolean isReceiveJunkMail() {
        return receiveJunkMail;
    }
    public void setReceiveJunkMail(boolean receiveJunkMail) {
        this.receiveJunkMail = receiveJunkMail;
    }

    private PortfolioServiceInterface portfolioService;

    public PortfolioServiceInterface getPortfolioService( )     {

        return portfolioService;

    }

    public void setPortfolioService( PortfolioServiceInterface portService){
        portfolioService = portService;
    }

    private Map session;

    public void setSession(Map session) {

        this.session = session;
    }

}

一个细微的不同是消息处理方式。message属性是这些注解必须的属性。如果想使用来自于属性文件资源包的消息,你不需要把关键字作为message属性的值。相反,向注解添加key属性即可,但你仍然需要在message中指定消息的值,当key查找失败时,将使用它作为默认消息。