出家如初,成佛有余

Struts2中静态页面生成策略

Posted in Uncategorized by chuanliang on 2008/05/26

    利用Struts2生成静态页面其实很灵活,很强大,尤其是利用Struts2对Freemarker较好的支持,充分利用Freemarker的模板功能来生成静态页面。

    基本思路为:利用Struts2对自定义result type的支持,自定义能够生成静态页面的result type,结合模板引擎Freemarker可以实现大批量静态页面的生成。

    参看org.apache.struts2.views.freemarker.FreemarkerResult的代码实现,自定义了自己的生成静态页面的result type。此种方案不单纯用于生成静态页面,其实也可以用于生成诸如wml、xhtml等内容,具体可以参考Struts2缺省提供的各种result type的实现。

1、com.mobilesoft.esales.webapp.action.FreemarkerResult

package com.mobilesoft.esales.webapp.action;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Locale;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsResultSupport;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.util.ResourceUtil;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;

import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;

public class FreemarkerResult extends StrutsResultSupport {

    private static final long serialVersionUID = -3778230771704661631L;

    protected ActionInvocation invocation;
    protected Configuration configuration;
    protected ObjectWrapper wrapper;
    protected FreemarkerManager freemarkerManager;
    private Writer writer;
    protected String location;
    private String pContentType = “text/html”;

    protected String fileName; // 要生成的静态页面名称
    protected String filePath; // 要生成的静态页面的路径
    protected String staticTemplate; // 用于生成静态页面Freemarker模板的路径

    public FreemarkerResult() {
        super();
    }

    public FreemarkerResult(String location) {
        super(location);
    }

    @Inject
    public void setFreemarkerManager(FreemarkerManager mgr) {
        this.freemarkerManager = mgr;
    }

    public void setContentType(String aContentType) {
        pContentType = aContentType;
    }

    public String getContentType() {
        return pContentType;
    }

    public void doExecute(String location, ActionInvocation invocation)
            throws IOException, TemplateException {
        this.location = location;
        this.invocation = invocation;
        this.configuration = getConfiguration();
        this.wrapper = getObjectWrapper();

        this.fileName = (String) conditionalParse(fileName, invocation);
        this.staticTemplate = (String) conditionalParse(staticTemplate, invocation);
        this.filePath = ((String) conditionalParse(filePath, invocation)) == null ? “”
                : ((String) conditionalParse(filePath, invocation));

        if (!location.startsWith(“/”)) {
            ActionContext ctx = invocation.getInvocationContext();
            HttpServletRequest req = (HttpServletRequest) ctx
                    .get(ServletActionContext.HTTP_REQUEST);
            String base = ResourceUtil.getResourceBase(req);
            location = base + “/” + location;
        }

        //生成html页面的模板类
        Template template = configuration.getTemplate(location, deduceLocale());
        // 生成静态页面的的模板类
        Template staticTemplate = configuration.getTemplate(this.staticTemplate,
                deduceLocale());

        TemplateModel model = createModel();
        String path = ServletActionContext.getServletContext().getRealPath(
                filePath)
                + File.separator;
        Writer out = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(path + fileName)));

        if (preTemplateProcess(template, model)) {
            try {
                staticTemplate.process(model, out);
                template.process(model, getWriter());
            } finally {
                postTemplateProcess(template, model);
                postTemplateProcess(staticTemplate, model);
            }
        }
    }

    protected Configuration getConfiguration() throws TemplateException {
        return freemarkerManager.getConfiguration(ServletActionContext
                .getServletContext());
    }

    protected ObjectWrapper getObjectWrapper() {
        return configuration.getObjectWrapper();
    }

    public void setWriter(Writer writer) {
        this.writer = writer;
 &
nbsp;  }

    protected Writer getWriter() throws IOException {
        if (writer != null) {
            return writer;
        }
        return ServletActionContext.getResponse().getWriter();
    }

    protected TemplateModel createModel() throws TemplateModelException {
        ServletContext servletContext = ServletActionContext
                .getServletContext();
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        ValueStack stack = ServletActionContext.getContext().getValueStack();

        Object action = null;
        if (invocation != null)
            action = invocation.getAction(); // Added for NullPointException
        return freemarkerManager.buildTemplateModel(stack, action,
                servletContext, request, response, wrapper);
    }

    protected Locale deduceLocale() {
        if (invocation.getAction() instanceof LocaleProvider) {
            return ((LocaleProvider) invocation.getAction()).getLocale();
        } else {
            return configuration.getLocale();
        }
    }

    protected void postTemplateProcess(Template template, TemplateModel data)
            throws IOException {
    }

    protected boolean preTemplateProcess(Template template, TemplateModel model)
            throws IOException {
        Object attrContentType = template.getCustomAttribute(“content_type”);

        if (attrContentType != null) {
            ServletActionContext.getResponse().setContentType(
                    attrContentType.toString());
        } else {
            String contentType = getContentType();

            if (contentType == null) {
                contentType = “text/html”;
            }

            String encoding = template.getEncoding();

            if (encoding != null) {
                contentType = contentType + “; charset=” + encoding;
            }

            ServletActionContext.getResponse().setContentType(contentType);
        }

        return true;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getStaticTemplate() {
        return staticTemplate;
    }

    public void setStaticTemplate(String staticTemplate) {
        this.staticTemplate = staticTemplate;
    }
}

 

2、struts.xml

        <action name=”staticViewAction” class=”com.mobilesoft.esales.webapp.action.StaticViewtAction”>
            <result name=”success” type=”staticview”>
                <param name=”location”>test/freemarkertest.ftl</param>
                <param name=”contentType”>text/html</param>
                 <param name=”fileName”>${filename}</param>
                <param name=”staticTemplate”>test/freemarkertest.ftl</param>
                <param name=”filePath”>static</param>
            </result>                   
        </action>

 

Advertisements

无线增值业务门户建设技术思考

Posted in Uncategorized by chuanliang on 2008/05/25

 

    eSales这样的业务运营、支撑系统由于大部分内容都是动态的且由于并发用户数相对较少、压力也相对较低,在设计合理的情况下,性能并不是最大的瓶颈,因此此种情况下采用动态页面的方式是比较恰当。对于门户社区而言,高并发、高负载、高性能、高可用性是第一位的,需要采用各种手段来提高其性能。关于网站优化最好的方法论是Yahoo 的Best Practices for Speeding Up Your Web Site ,技术层面细节的优化策略参看Yahoo的方法论。

    同时这是一个用户为中心(user centered)的年代,诸如“以用户为中心的设计”、“以用户为中心的系统”、“用户为中心的营销”等等。 但是怎样才能够让门户设计中充分考虑用户体验,避免致命性的坏体验(bad smell)?这是门户建设需要重点考虑的问题,这一点上所谓的交互设计模式对于我们还是有所益处的。关于用户交互设计的模式:Yahoo Design Pattern LibraryInteraction Design Pattern Library

    此处重点从技术层面谈一下在门户开发时候需要考虑的重点内容:

    Web2.0化:除了充分使用诸如TAG、RSS、DIGG、SNS、UGC这些典型的Web2.0的元素外,“用户体验”是门户建设的重中之重,以用户体验为中心,把这些web2.0元素恰当地融入无线增值业务门户中,相信我们才会造就一个伟大的无线互联网门户,否则只是一堆与别人雷同的舶来品。

    无线门户、互联网门户一体化:WAP门户及互联网门户采用同样的技术架构,在整个系统的基础架构仍然沿用目前的Struts2+Spring+Hibernate(ibatis)的架构,但在View层不使用JSP,而是采用Freemarker,充分利用Freemarker对模板支持及对xml较好支持,将对WML(WAP1.0)、XHTML(WAP2.0)、HTML(Internet)的处理都统一到同一架构下。

    REST(Representational State Transfer):遵循REST设计原则,尤其是无状态通信(statelessness)。

    页面静态化:对门户社区页面都尽量采用页面静态化方案,这样能够充分利用cache机制及实现replication、load balance及镜像(例如南北电信部署)。为了实现页面静态化策略,采用Freemarker+FMPP方案来实现页面静态化的策略。

    SEO:在设计时候一定要首先重点考虑搜索引擎友好及针对google、baidu搜索引擎进行优化,主要是Meta Tag部分内容及网站架构,所有的页面url遵循RSET模式,对无法遵循REST模式的,采用lighhttpd的mod_rewrite来实现。

    爬虫:简单的垂直爬虫主要采用httpclient+htmlparser方案实现,复杂爬虫策略采用Heritrix(或Nutch)实现。

    搜索引擎:采用Nutch+Lucene+Compass方案,对门户定时索引,提供全站搜索功能。

    AJAX:在eSales后台的ajax主要采用struts2的dojo实现,可以充分利用struts2的标签,保持架构的统一。在门户实现时候,由于主要采用静态页面化方案,对于需要动态内容的地方,采用ajax来实现动态数据的状态。在ajax库选择上,不再采用dojo,采用jquery方案。

    CSS:在eSales后台主要还是采用frame、table方式来实现页面布局,在门户开发时候完全采用CSS方案,以保证页面布局的灵活性及页面大小。

    Cache:尽量使用诸如memcache和squid的cache机制,提高性能

     镜像采用rsync来实现对静态页面内容的镜像及同步,解决因不同运营商(移动(铁通)、电信、联通(网通)、教育网、有线网)及地域用户访问速度上差异。

    其他的部署策略参看下图:

    平台系统部署方案

 

参考资料:

  • 性能方面:

http://developer.yahoo.com/performance/rules.html

http://highscalability.com/

High Performance Web Sites

Building Scalable Web Sites

http://www.sitepoint.com/print/web-site-optimization-steps

 

  • 用户体验方面

http://www.welie.com/patterns/index.php

http://developer.yahoo.com/ypatterns/atoz.php

http://en.wikipedia.org/wiki/Interaction_design_pattern

http://www.visi.com/~snowfall/InteractionPatterns.html

Freemarker ObjectWrapper使用测试

Posted in Uncategorized by chuanliang on 2008/05/24

    先学习Freemarker Programmer Guide中对ObjectWrapper的说明:

    FreeMarker 数据容器(root)可以放置任意的对象,而不一定就是实现了TemplateModel 接口的对象。这是为什么呢?!因为FreeMarker 提供的容器实例会在其内部把放置在其中的对象自动转换成实现了TemplateModel 接口的对象。比如说,
如果你放置一个String 对象在容器中, 它就会把String 对象在内部自动转换成SimpleScalar。
    至于何时发生转换,这是容器自身逻辑的问题。但是最晚也会在获取子变量的时候进行转换,因为获取子变量方法会返回TemplateModel 对象而不是Object 对象。例如,SimpleHash,SimpleSequence 和SimpleCollection 使用延迟转换策略(laziest
strategy);它们会在第一次获取子变量的时候把其他类型的对象转换成TemplateModel类型。
    至于什么类型的对象可以被转换,以及具体转换成何种类型,一方面容器自身可以处理,另一方面也可以把它委托给ObjectWrapper 实例去处理。ObjectWrapper 是一个接口具有一个方法TemplateModel wrap(java.lang.Object obj)。用户可以传递一个Object 对象,它就会返回一个与之对应的TemplateModel 对象,或者抛出异常。这些转换规则是写死在ObjectWrapper 实现里面的。
    FreeMarker 提供的ObjectWrapper 重要的实现有:
ObjectWrapper.DEFAULT_WRAPPER :它可以把String 转换成SimpleScalar ,Number 转换成SimpleNumber,List 和array 转换成SimpleSequence,Map 转换成SimpleHash,Boolean 转换成TemplaeBooleanModel.TRUE/FALSE 等等。(对于其他的类型对象的转换就要调用BEANS_WRAPPER)
ObjectWrapper.BEANS_WRAPPER:它可以使用反射访问任意JavaBean 的属性

    对于Freemarker中如果使用HashMap(或SimpleHash)时候,如果HashMap的键值对(key,value)的value是普通的Scalar对象(String、Double等),此种情况下,对于ObjectWrapper可以直接使用DEFAULT_WRAPPER,在Freemarker模板文件中使用也相对简单,只需要采用如下方式即可:

<#list scalarMap?keys as mykey>
    Scalar Map key is :${mykey}
    Scalar Map value is:${scalarMap[mykey]}
</#list>

    但如果Map的value是JavaBean对象(例如JavaBean为User,有userId和userName两个属性),如果需要在Freemarker模板文件中使用类似el表达式的方式获取JavaBean的属性值,也即:${testmap[key].userId},此种情况下不能采用缺省的DEFAULT_WRAPPER,需要使用ObjectWrapper.BEANS_WRAPPER。

    当然如果在Freemarker模板文件中不需要获取JavaBean对象的属性值,也即只需要获取对象本身:${testmap[key]},则也可以不使用ObjectWrapper.BEANS_WRAPPER。

1、测试用例

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.SimpleSequence;
import freemarker.template.Template;
public class FreeMarkerTest {
    public static void main(String[] args){
        FreeMarkerTest test = new FreeMarkerTest();
        test.sayHelloWorld();
    }
    public void sayHelloWorld(){
        Configuration freemarkerCfg = new Configuration();
        freemarkerCfg.setClassForTemplateLoading(this.getClass(), “/”);
        freemarkerCfg.setEncoding(Locale.getDefault(), “GBK”);
        Template template;
        Locale.setDefault(Locale.SIMPLIFIED_CHINESE);
        try{
            template = freemarkerCfg.getTemplate(“HelloWorld.ftl”);
            template.setEncoding(“GBK”);

            User user1=new User();
            user1.setUserId(“1”);
            user1.setUserName(“1”);

            User user2=new User();
            user2.setUserId(“2”);
            user2.setUserName(“2”);

            User user3=new User();
            user3.setUserId(“3”);
            user3.setUserName(“3”);

            User user4=new User();
            user4.setUserId(“4”);
            user4.setUserName(“4”);
            User user5=new User();
            user5.setUserId(“5”);
            user5.setUserName(“5”);
            User user6=new User();
            user6.setUserId(“6”);
            user6.setUserName(“6”);

            List scalarList = new ArrayList();
            scalarList.add(“red”);
            scalarList.add(“green”);
            scalarList.add(“blue”);
            SimpleHash root = new SimpleHash(ObjectWrapper.BEANS_WRAPPER);
            root.put(“scalarString”, “Scalar String Test”);
            root.put(“scalarNumber”, new Integer(3));
            root.put(“scalarObject”, new User(“33″,”33”));
            root.put(“scalarList”, scalarList);
            SimpleHash scalarMap=new SimpleHash(ObjectWrapper.BEANS_WRAPPER);   
            root.put(“scalarMap”, scalarMap);
            scalarMap.put(“anotherString”, “aaaaaaaa”);
            scalarMap.put(“anotherNumber”, new Double(3.14));
            SimpleSequence userList=new SimpleSequence(ObjectWrapper.BEANS_WRAPPER);
            root.put(“userList”, userList);
            userList.add(user1);
            userList.add(user2);
       &nbs
p;    userList.add(user3);
            userList.add(user4);           
            userList.add(user5);
            userList.add(user6);
            SimpleHash userMap=new SimpleHash(ObjectWrapper.BEANS_WRAPPER);       
            root.put(“userMap”, userMap);
            userMap.put(“1”, user1);
            userMap.put(“2”, user2);           
            userMap.put(“3”, user3);
            userMap.put(“4”, user4);
            userMap.put(“5”, user5);
            userMap.put(“6”, user6);

            StringWriter writer = new StringWriter();
            template.process(root, writer);
            System.out.println(writer.toString());
        }catch(Exception e){
            e.printStackTrace();
        }}
}

2、HelloWorld.ftl

Scalar String:${scalarString}   
Scalar Number:${scalarNumber}
Object is:${scalarObject}

List使用样例-List元素为Scalar对象:

<#list scalarList as value0>
    Scalar List值:${value0}
</#list>

List使用样例-List元素为User对象:

<#list userList as listUser>
    List对象User Id值:${listUser.userId}
</#list>

Map使用样例-Map Values元素为Scalar :

<#list scalarMap?keys as mykey>
    Scalar Map key is :${mykey}
    Scalar Map value is:${scalarMap[mykey]}
</#list>

Map使用样例-Map Values元素为User对象:

<#list userMap?keys as key1>
    <#assign mapUser=”${userMap[key1]}” >
    User Object is :${mapUser}
    <#–
    以下方法有问题
    User is :${mapUser.userId} <br>
    –>
</#list>

3、User.java

public class User {
    private String userId;
    private String userName;
    public User(){
    }
    public User(String userId,String userName){
        this.userId = userId;
        this.userName = userName;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
}

 

Struts2 doubleselect标签中select框缺省selected实现

Posted in Uncategorized by chuanliang on 2008/05/22

    在项目中,省市下拉框联动采用的是Struts2的doubleselect标签,需要根据业务需求实现两个下拉框动态的缺省值(selected)。

业务场景:

    在代理商管理中,增加代理商时候选择代理商所属的省市,然后增加代理商的销售人员,但代理商销售人员销售产品,如果客户在客户库中没有相关信息,需要增加客户,此时侯应当缺省根据代理商所属的省市信息,在增加客户时候,客户所在省市的缺省selected的值应当为代理商所在的省市信息。

 

主要实现逻辑如下:

    采用doubleselect标签的value和doublevalue属性,在action中定义两个select框缺省值参数(例子中是defaultItem、doubleDefaultItem)的get、set方法,在action方法中根据业务逻辑(在增加客户时候,客户所在省市缺省为销售员所在省市)调用set方法设定两个select框的缺省值,然后在页面通过value和doublevalue方法获取设定的缺省值。

实现样例如下:

1. Action

package com.mobilesoft.esales.webapp.action;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

import org.apache.log4j.Logger;

public class DoubleListAction extends BaseAction {

private static final Logger logger = Logger.getLogger(DoubleListAction.class);

private String defaultItem;

private String doubleDefaultItem;

public String execute() {

return SUCCESS;

    }

public String doubleSelectTest(){

       Map map=new HashMap();

       ArrayList list1=new ArrayList();

       list1.add(“11”);

       list1.add(“12”);

       list1.add(“13”);

       map.put(“1”, list1);

       ArrayList list2=new ArrayList();

       list2.add(“21”);

       list2.add(“22”);

       list2.add(“23”);

       map.put(“2”, list2);

       ArrayList list3=new ArrayList();

       list3.add(“31”);

       list3.add(“32”);

       list3.add(“33”);

       map.put(“3”, list3);

       setDefaultItem(“2”);

       setDoubleDefaultItem(“23”);

       getRequest().setAttribute(“defaultItem”, getDefaultItem());

       getRequest().setAttribute(“doubleDefaultItem”, getDoubleDefaultItem());

       getRequest().setAttribute(“map”, map);

return SUCCESS;

    }

public String getDefaultItem() {

return defaultItem;

    }

public void setDefaultItem(String defaultItem) {

this.defaultItem = defaultItem;

    }

public String getDoubleDefaultItem() {

return doubleDefaultItem;

    }

public void setDoubleDefaultItem(String doubleDefaultItem) {

this.doubleDefaultItem = doubleDefaultItem;

    }

}

2. doubleselect.jsp

<%@ taglib prefix=”s” uri=”/struts-tags” %>

<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>

<html>

<head>

<title>Struts 2 Cool Tags – &lt;s:doubeselect/ &gt;</title>

<s:head />

</head>

<body>

<h2>Doubleselect 缺省值selected使用数据演示:</h2>

<s:form name=”form1″>

<s:doubleselect label=”缺省值测试”

    list=”#request.map.keySet()”        doubleList=”#request.map[top]”

name=”doubleselect1″                doubleName=”doubleselect2″

value=”#request.defaultItem”       doubleValue=”#request.doubleDefaultItem”

    formName=”form1″

/>

</s:form>

</body>

</html>

3. struts.xml

<action name=”doubleSelectTest” method=”doubleSelectTest” class=”com.mobilesoft.esales.webapp.action.DoubleListAction”>

<result name=”success”>test/doubleselect.jsp</result>

</action>

 

Technorati 标签: ,,,
Tagged with: , , ,

Subversion库迁移及备份方案

Posted in Uncategorized by chuanliang on 2008/05/20

在做迁移操作前,请停止对svn进行提交操作。

1. 迁移方案(采用dump -load方案):

源SVN服务器:192.168.1.200,Windows服务器

目标SVN服务器:192.168.1.201,Windows服务器。采用CollabNet Subversion Server,假定subversion安装在D:Program FilesCollabNet Subversion Server上,SVN的Repository为d:Subversionsvnbackup

也即Windows服务中,可执行文件的路径为:

“d:Program FilesCollabNet Subversion Serversvnserve.exe” –service -r “d:Subversionsvnbackup” –listen-port “3690”

由于目前在subversion服务器上实际上只有svn://192.168.1.200/rd目录下才有内容,因此只需要迁移svn://192.168.1.201/rd下的内容,步骤如下:

1、 在源服务器192.168.1.200上执行dump操作

注意此处实际上把repository中所有的目录都备份了,需要在load时候采用svndumpfilter命令过滤需要的目录。

svnadmin dump D:Subversionsvnworkspacebak >svn_all_20080520.dump

2、 在192.168.1.201上创建svnbackup Repository

svnadmin create d:Subversionsvnbackup

3、 下载一个windows 版本gnu 工具(例如http://sourceforge.net/projects/gnuwin32/),主要是使用cat方法

4、 将dump文件拷贝到上并执行load操作

        cat svn_all_20080520.dump | svndumpfilter --include:rd >svn_rd_20080520.dump

5、 执行svnadmin load

        svnadmin load d:Subversionsvnbackup < svn_rd_20080520.dump

6、 在192.168.1.201上配置svnserve.conf、passwd、authz文件

2. 迁移方案(采用svnsync方案)

从subversion 1.4.4开始,提供了svnsync命令,可用于Subversion的库迁移和备份,这里我们用于备份操作的初始化同步。

假定从源服务器192.168.1.201备份到192.168.1.88

SVN服务器:192.168.1.201,Windows服务器,采用CollabNet Subversion Server,假定subversion安装在D:Program FilesCollabNet Subversion Server上,SVN的Repository为d:Subversionsvnbackup。

备份服务器: 192.168.1.88,Redhat As 4服务器

采用svnsync进行数据迁移,方法如下:

1、 在备份服务器192.168.1.88上创建源服务器192.168.1.201上对应的备份库目录

mkdir /opt/subversion

svnadmin create  /opt/subversion/svnbackup

2、在备份服务器192.168.1.88上启用钩子文件

cd  /opt/subversion/svnbackup/hooks

echo “#!/bin/sh”> pre-revprop-change

chmod 755 pre-revprop-change

3、在备份服务器192.168.1.88上运行svnsync init命令

svnsync init file:////opt/subversion/svnbackup  svn://192.168.1.201 –username username –password password

注意,svnsync的语法为:svnsync init DEST SOURCE

4、在备份服务器192.168.1.88上执行同步操作

svnsync sync file:////opt/subversion/svnbackup

由于svnsyc只能同步整个svn库,并不能同步指定的项目,因此建议迁移时候使用dump-load方案,备份时候采用svnsync方案

3. 备份方案:

为保证svn服务器的安全,由脚本每天定时对svn库进行备份,以保证svn库的安全性。备份仍然采用svnsync来完成。

1. 在192.168.1.88  上安装subversion 服务器端

2. 在192.168.1.88上创建备份用户帐号svnsync,以供192.168.1.201能够以此帐号实时把变更的同步到192.168.1.88上

配置文件svnserve.conf

[general]

anon-access = none

auth-access = write

password-db = passwd

authz-db = authz

配置文件passwd

svnsync=svnsync

配置文件authz

[groups]

developer = svnsync

[/]

@developer=rw

* =

3. 在备份机上开启iptables的3690端口

4. 在备份机192.168.1.88上创建备份库目录

svnadmin create /opt/subversion/svnbackup

chown –R svnsync:svnsync  /opt/subversion/svnbackup

5. 按照上述采用svnsync方案的步骤,将库同步到192.168.1.88上,初始化svn库

cd  /opt/subversion/svnbackup/hooks

echo “#!/bin/sh”> pre-revprop-change

chmod 755 pre-revprop-change

svnsync init file:////opt/subversion/svnbackup  svn://192.168.1.201 –username username –password password

svnsync sync file:////opt/subversion/svnbackup

6. 在源服务器192.168.1.201上,创建钩子文件,保证192.168.1.201上的变动实时同步到192.168.1.88上:

post-commit
# Propagate the data to the remote repository
D:Program FilesCollabNet Subversion Serversvnsync synchronize --username svnsync --password svnsync  svn:// 192.168.1.88
post-rev-changes
# Propagating changes to the remote repository.
D:Program FilesCollabNet Subversion Serverbinsvnsync copy-revprops --username svnsync --password svnsync  svn:// 192.168.1.88 $REV  
4. 参考文档:

http://blog.notreally.org/articles/2006/11/30/setting-up-a-subversion-mirror-repository-using-svnsync/

http://whynotwiki.com/How_I_moved_my_code_repository_to_Google_Code

Technorati 标签: ,,,
Tagged with: , , ,

修改Dreamhost虚拟主机的php.ini的upload_max_filesize参数限制

Posted in Uncategorized by chuanliang on 2008/05/19

    在写完 htmlparser使用指南后,通过Windows Live Writer发布到Live Spaces上,能够正确发布http://chuanliang2007.spaces.live.com/上。但发布到http://www.yeeach.com 上的Blog时候总是有问题,发布的文章没有内容,只有标题和标签。www.yeeach.com寄存在Dreamhost的虚拟主机上,Blog用的是Wordpress。怀疑是Wordpress的API接口对最大文本有限制,但找了以便接口也没有发现什么东西。后来想起会不会是php的最大上传文件限制惹得祸。按照如下文章在dreamhost中修改虚拟主机的php.ini试验了一下修改Dreamhost虚拟主机的php.ini的upload_max_filesize参数.

1、mkdir ~/yeeach.com/cgi-bin/

2、touch ~/php-copy.sh  ,内容如下:

#!/bin/sh
CGIFILE=”$HOME/yeeach.com/cgi-bin/php.cgi”
INIFILE=”$HOME/yeeach.com/cgi-bin/php.ini”
rsync -a /dh/cgi-system/php5.cgi “$CGIFILE”
# REMOVE THE FOLLOWING LINE TO CREATE THE UPDATE-ONLY SCRIPT:
cp /etc/php5/cgi/php.ini “$INIFILE”

perl -p -i -e ‘
s/.*post_max_size.*/post_max_size = 100M/;
s/.*upload_max_filesize.*/upload_max_filesize = 100M/;
‘ “$INIFILE”

 

用的是php5,最大上传文件大小限定为100M

3、chmod +x php-copy.sh

4、./php-copy.sh

5、touch ~/yeeach.com/.htaccess ,内容如下

Options +ExecCGI
AddHandler php-cgi .php
Action php-cgi /cgi-bin/php.cgi

 

6、每周检查并升级php

cp php-copy.sh php-update.sh
php-update.sh的内容如下:

#!/bin/sh
CGIFILE=”$HOME/yeeach.com/cgi-bin/php.cgi”
INIFILE=”$HOME/yeeach.com/cgi-bin/php.ini”
rsync -a /dh/cgi-system/php5.cgi “$CGIFILE”
perl -p -i -e ‘
s/.*post_max_size.*/post_max_size = 100M/;
s/.*upload_max_filesize.*/upload_max_filesize = 100M/;
‘ “$INIFILE”

实际上就是删除了如下两行:

# REMOVE THE FOLLOWING LINE TO CREATE THE UPDATE-ONLY SCRIPT:
cp /etc/php5/cgi/php.ini “$INIFILE”

7、配置crontab

crontab -e
在crontab中增加如下内容:
@weekly /home/myusername/php-update.sh
修改后,重新用Windows Live Writer试验了一下,可以正确发布很长
内容的文档了。
 
 

htmlparser使用指南

Posted in Uncategorized by chuanliang on 2008/05/19

需要做一个垂直搜索引擎,比较了nekohtml和htmlparser 的功能,尽管nekohtml在容错性、性能等方面的口碑好像比htmlparser好(htmlunit也用的是nekohtml),但感觉 nekohtml的测试用例和文档都比htmlparser都少,而且htmlparser基本上能够满足垂直搜索引擎页面处理分析的需求,因此先研究一 下htmlparser的使用,有空再研究nekohtml和mozilla html parser的使用。    html的功能还是官方说得最为清楚,

HTML Parser is a Java library used to parse HTML in either a linear or nested fashion. Primarily used for transformation or extraction, it features filters, visitors, custom tags and easy to use JavaBeans. It is a fast, robust and well tested package.

The two fundamental use-cases that are handled by the parser are extraction and transformation (the syntheses use-case, where HTML pages are created from scratch, is better handled by other tools closer to the source of data). While prior versions concentrated on data extraction from web pages, Version 1.4 of the HTMLParser has substantial improvements in the area of transforming web pages, with simplified tag creation and editing, and verbatim toHtml() method output.

研究的重点还是extraction的使用,有空再研究transformation的使用。

1、htmlparser对html页面处理的数据结构

node

如图所示,HtmlParser采用了经典的Composite模式,通过RemarkNode、TextNode、TagNode、AbstractNode和Tag来描述HTML页面各元素。

  • org.htmlparser.Node:

Node接口定义了进行树形结构节点操作的各种典型操作方法,包括:

节点到html文本、text文本的方法:toPlainTextString、toHtml

典型树形结构遍历的方法:getParent、getChildren、getFirstChild、getLastChild、getPreviousSibling、getNextSibling、getText

获取节点对应的树形结构结构的顶级节点Page对象方法:getPage

获取节点起始位置的方法:getStartPosition、getEndPosition

Visitor方法遍历节点时候方法:accept (NodeVisitor visitor)

Filter方法:collectInto (NodeList list, NodeFilter filter)

Object方法:toString、clone

  • org.htmlparser.nodes.AbstractNode

AbstractNode是形成HTML树形结构抽象基类,实现了Node接口。

在htmlparser中,Node分成三类:

RemarkNode:代表Html中的注释

TagNode:标签节点。

TextNode:文本节点

这三类节点都继承AbstractNode。

  • org.htmlparser.nodes.TagNode:

TagNode包含了对HTML处理的核心的各个类,是所有TAG的基类,其中有分为包含其他TAG的复合节点ComositeTag和不包含其他TAG的叶子节点Tag。

复合节点CompositeTag:

AppletTag,BodyTag,Bullet,BulletList,DefinitionList,DefinitionListBullet,Div,FormTag,FrameSetTag,HeadingTag,

HeadTag,Html,LabelTag,LinkTag,ObjectTag,ParagraphTag,ScriptTag,SelectTag,Span,StyleTag,TableColumn,

TableHeader,TableRow,TableTag,TextareaTag,TitleTag

叶子节点TAG:

BaseHrefTag,DoctypeTag,FrameTag,ImageTag,InputTag,JspTag,MetaTag,ProcessingInstructionTag,

2、htmlparser对html页面处理的算法

主要是如下几种方式

  • 采用Visitor方式访问Html

try {
Parser parser = new Parser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeVisitor visitor = new NodeVisitor() {
public void visitTag(Tag tag) {
logger.fatal(“testVisitorAll()  Tag name is :”
+ tag.getTagName() + ” n Class is :”
+ tag.getClass());
}

};

parser.visitAllNodesWith(visitor);
} catch (ParserException e) {
e.printStackTrace();
}

  • 采用Filter方式访问html

try {

NodeFilter filter = new NodeClassFilter(LinkTag.class);
Parser parser = new Parser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeList list = parser.extractAllNodesThatMatch(filter);
for (int i = 0; i < list.size(); i++) {
LinkTag node = (LinkTag) list.elementAt(i);
logger.fatal(“testLinkTag() Link is :” + node.extractLink());
}
} catch (Exception e) {
e.printStackTrace();
}

  • 采用org.htmlparser.beans方式

另外htmlparser 还在org.htmlparser.beans中对一些常用的方法进行了封装,以简化操作,例如:

Parser parser = new Parser();

LinkBean linkBean = new LinkBean();
linkBean.setURL(“http://www.google.com”);
URL[] urls = linkBean.getLinks();

for (int i = 0; i < urls.length; i++) {
URL url = urls[i];
logger.fatal(“testLinkBean() -url  is :” + url);
}

3、htmlparser关键包结构说明

htmlparser其实核心代码并不多,好好研究一下其代码,弥补文档不足的问题。同时htmlparser的代码注释和单元测试用例还是很齐全的,也有助于了解htmlparser的用法。

3.1、org.htmlparser

定义了htmlparser的一些基础类。其中最为重要的是Parser类。

Parser是htmlparser的最核心的类,其构造函数提供了如下:Parser.createParser (String html, String charset)、 Parser ()、Parser (Lexer lexer, ParserFeedback fb)、Parser (URLConnection connection, ParserFeedback fb)、Parser (String resource, ParserFeedback feedback)、 Parser (String resource)

各构造函数的具体用法及含义可以查看其代码,很容易理解。

Parser常用的几个方法:

  •   elements获取元素

Parser parser = new Parser (“http://www.google.com”);
for (NodeIterator i = parser.elements (); i.hasMoreElements (); )
processMyNodes (i.nextNode ());

  • parse (NodeFilter filter):通过NodeFilter方式获取
  • visitAllNodesWith (NodeVisitor visitor):通过Nodevisitor方式
  • extractAllNodesThatMatch (NodeFilter filter):通过NodeFilter方式

3.2、org.htmlparser.beans

对Visitor和Filter的方法进行了封装,定义了针对一些常用html元素操作的bean,简化对常用元素的提取操作。

包括:FilterBean、HTMLLinkBean、HTMLTextBean、LinkBean、StringBean、BeanyBaby等。

3.3、org.htmlparser.nodes

定义了基础的node,包括:AbstractNode、RemarkNode、TagNode、TextNode等。

3.4、org.htmlparser.tags

定义了htmlparser的各种tag。

3.5、org.htmlparser.filters

定义了htmlparser所提供的各种filter,主要通过extractAllNodesThatMatch (NodeFilter filter)来对html页面指定类型的元素进行过滤,包括:AndFilter、CssSelectorNodeFilter、 HasAttributeFilter、HasChildFilter、HasParentFilter、HasSiblingFilter、 IsEqualFilter、LinkRegexFilter、LinkStringFilter、NodeClassFilter、 NotFilter、OrFilter、RegexFilter、StringFilter、TagNameFilter、XorFilter

3.6、org.htmlparser.visitors

定义了htmlparser所提供的各种visitor,主要通过visitAllNodesWith (NodeVisitor visitor)来对html页面元素进行遍历,包括:HtmlPage、LinkFindingVisitor、NodeVisitor、 ObjectFindingVisitor、StringFindingVisitor、TagFindingVisitor、 TextExtractingVisitor、UrlModifyingVisitor

3.7、org.htmlparser.parserapplications

定义了一些实用的工具,包括LinkExtractor、SiteCapturer、StringExtractor、WikiCapturer,这几个类也可以作为htmlparser使用样例。

3.8、org.htmlparser.tests

对各种功能的单元测试用例,也可以作为htmlparser使用的样例。

4、htmlparser的使用样例

import java.net.URL;

import junit.framework.TestCase;

import org.apache.log4j.Logger;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.Tag;
import org.htmlparser.beans.LinkBean;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.OrFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.tags.HeadTag;
import org.htmlparser.tags.ImageTag;
import org.htmlparser.tags.InputTag;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.tags.OptionTag;
import org.htmlparser.tags.SelectTag;
import org.htmlparser.tags.TableColumn;
import org.htmlparser.tags.TableRow;
import org.htmlparser.tags.TableTag;
import org.htmlparser.tags.TitleTag;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.visitors.HtmlPage;
import org.htmlparser.visitors.NodeVisitor;
import org.htmlparser.visitors.ObjectFindingVisitor;

public class ParserTestCase extends TestCase {

private static final Logger logger = Logger.getLogger(ParserTestCase.class);

public ParserTestCase(String name) {
super(name);
}
/*
* 测试ObjectFindVisitor的用法
*/
public void testImageVisitor() {
try {
ImageTag imgLink;
ObjectFindingVisitor visitor = new ObjectFindingVisitor(
ImageTag.class);
Parser parser = new Parser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
parser.visitAllNodesWith(visitor);
Node[] nodes = visitor.getTags();
for (int i = 0; i < nodes.length; i++) {
imgLink = (ImageTag) nodes[i];
logger.fatal(“testImageVisitor() ImageURL = ”
+ imgLink.getImageURL());
logger.fatal(“testImageVisitor() ImageLocation = ”
+ imgLink.extractImageLocn());
logger.fatal(“testImageVisitor() SRC = ”
+ imgLink.getAttribute(“SRC”));
}
}
catch (Exception e) {
e.printStackTrace();
}
}
/*
* 测试TagNameFilter用法
*/
public void testNodeFilter() {
try {
NodeFilter filter = new TagNameFilter(“IMG”);
Parser parser = new Parser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeList list = parser.extractAllNodesThatMatch(filter);
for (int i = 0; i < list.size(); i++) {
logger.fatal(“testNodeFilter() ” + list.elementAt(i).toHtml());
}
} catch (Exception e) {
e.printStackTrace();
}

}
/*
* 测试NodeClassFilter用法
*/
public void testLinkTag() {
try {

NodeFilter filter = new NodeClassFilter(LinkTag.class);
Parser parser = new Parser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeList list = parser.extractAllNodesThatMatch(filter);
for (int i = 0; i < list.size(); i++) {
LinkTag node = (LinkTag) list.elementAt(i);
logger.fatal(“testLinkTag() Link is :” + node.extractLink());
}
} catch (Exception e) {
e.printStackTrace();
}

}
/*
* 测试<link href=” text=’text/css’ rel=’stylesheet’ />用法
*/
public void testLinkCSS() {
try {

Parser parser = new Parser();
parser
.setInputHTML(“<head><title>Link Test</title>”
+ “<link href=’/test01/css.css’ text=’text/css’ rel=’stylesheet’ />”
+ “<link href=’/test02/css.css’ text=’text/css’ rel=’stylesheet’ />”
+ “</head>” + “<body>”);
parser.setEncoding(parser.getEncoding());
NodeList nodeList = null;

for (NodeIterator e = parser.elements(); e.hasMoreNodes();) {
Node node = e.nextNode();
logger
.fatal(“testLinkCSS()” + node.getText()
+ node.getClass());

}
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 测试OrFilter的用法
*/
public void testOrFilter() {
NodeFilter inputFilter = new NodeClassFilter(InputTag.class);
NodeFilter selectFilter = new NodeClassFilter(SelectTag.class);
Parser myParser;
NodeList nodeList = null;

try {
Parser parser = new Parser();
parser
.setInputHTML(“<head><title>OrFilter Test</title>”
+ “<link href=’/test01/css.css’ text=’text/css’ rel=’stylesheet’ />”
+ “<link href=’/test02/css.css’ text=’text/css’ rel=’stylesheet’ />”
+ “</head>”
+ “<body>”
+ “<input type=’text’ value=’text1′ name=’text1’/>”
+ “<input type=’text’ value=’text2′ name=’text2’/>”
+ “<select><option id=’1′>1</option><option id=’2′>2</option><option id=’3′></option></select>”
+ “<a href=’http://www.yeeach.com’>yeeach.com</a>”
+ “</body>”);

parser.setEncoding(parser.getEncoding());
OrFilter lastFilter = new OrFilter();
lastFilter.setPredicates(new NodeFilter[] { selectFilter,
inputFilter });
nodeList = parser.parse(lastFilter);
for (int i = 0; i <= nodeList.size(); i++) {
if (nodeList.elementAt(i) instanceof InputTag) {
InputTag tag = (InputTag) nodeList.elementAt(i);
logger.fatal(“OrFilter tag name is :” + tag.getTagName()
+ ” ,tag value is:” + tag.getAttribute(“value”));
}
if (nodeList.elementAt(i) instanceof SelectTag) {
SelectTag tag = (SelectTag) nodeList.elementAt(i);
NodeList list = tag.getChildren();

for (int j = 0; j < list.size(); j++) {
OptionTag option = (OptionTag) list.elementAt(j);
logger
.fatal(“OrFilter Option”
+ option.getOptionText());
}

}
}

} catch (ParserException e) {
e.printStackTrace();
}
}
/*
* 测试对<table><tr><td></td></tr></table>的解析
*/
public void testTable() {
Parser myParser;
NodeList nodeList = null;
myParser = Parser.createParser(“<body> ” + “<table id=’table1′ >”
+ “<tr><td>1-11</td><td>1-12</td><td>1-13</td>”
+ “<tr><td>1-21</td><td>1-22</td><td>1-23</td>”
+ “<tr><td>1-31</td><td>1-32</td><td>1-33</td></table>”
+ “<table id=’table2′ >”
+ “<tr><td>2-11</td><td>2-12</td><td>2-13</td>”
+ “<tr><td>2-21</td><td>2-22</td><td>2-23</td>”
+ “<tr><td>2-31</td><td>2-32</td><td>2-33</td></table>”
+ “</body>”, “GBK”);
NodeFilter tableFilter = new NodeClassFilter(TableTag.class);
OrFilter lastFilter = new OrFilter();
lastFilter.setPredicates(new NodeFilter[] { tableFilter });
try {
nodeList = myParser.parse(lastFilter);
for (int i = 0; i <= nodeList.size(); i++) {
if (nodeList.elementAt(i) instanceof TableTag) {
TableTag tag = (TableTag) nodeList.elementAt(i);
TableRow[] rows = tag.getRows();

for (int j = 0; j < rows.length; j++) {
TableRow tr = (TableRow) rows[j];
TableColumn[] td = tr.getColumns();
for (int k = 0; k < td.length; k++) {
logger.fatal(“<td>” + td[k].toPlainTextString());
}

}

}
}

} catch (ParserException e) {
e.printStackTrace();
}
}
/*
* 测试NodeVisitor的用法,遍历所有节点
*/
public void testVisitorAll() {
try {
Parser parser = new Parser();
parser.setURL(“http://www.google.com”);
parser.setEncoding(parser.getEncoding());
NodeVisitor visitor = new NodeVisitor() {
public void visitTag(Tag tag) {
logger.fatal(“testVisitorAll()  Tag name is :”
+ tag.getTagName() + ” n Class is :”
+ tag.getClass());
}

};

parser.visitAllNodesWith(visitor);
} catch (ParserException e) {
e.printStackTrace();
}
}
/*
* 测试对指定Tag的NodeVisitor的用法
*/
public void testTagVisitor() {
try {

Parser parser = new Parser(
“<head><title>dddd</title>”
+ “<link href=’/test01/css.css’ text=’text/css’ rel=’stylesheet’ />”
+ “<link href=’/test02/css.css’ text=’text/css’ rel=’stylesheet’ />”
+ “</head>” + “<body>”
+ “<a href=’http://www.yeeach.com’>yeeach.com</a>”
+ “</body>”);
NodeVisitor visitor = new NodeVisitor() {
public void visitTag(Tag tag) {
if (tag instanceof HeadTag) {
logger.fatal(“visitTag() HeadTag : Tag name is :”
+ tag.getTagName() + ” n Class is :”
+ tag.getClass() + “n Text is :”
+ tag.getText());
} else if (tag instanceof TitleTag) {
logger.fatal(“visitTag() TitleTag : Tag name is :”
+ tag.getTagName() + ” n Class is :”
+ tag.getClass() + “n Text is :”
+ tag.getText());

} else if (tag instanceof LinkTag) {
logger.fatal(“visitTag() LinkTag : Tag name is :”
+ tag.getTagName() + ” n Class is :”
+ tag.getClass() + “n Text is :”
+ tag.getText() + ” n getAttribute is :”
+ tag.getAttribute(“href”));
} else {
logger.fatal(“visitTag() : Tag name is :”
+ tag.getTagName() + ” n Class is :”
+ tag.getClass() + “n Text is :”
+ tag.getText());
}

}

};

parser.visitAllNodesWith(visitor);
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 测试HtmlPage的用法
*/
public void testHtmlPage() {
String inputHTML = “<html>” + “<head>”
+ “<title>Welcome to the HTMLParser website</title>”
+ “</head>” + “<body>” + “Welcome to HTMLParser”
+ “<table id=’table1′ >”
+ “<tr><td>1-11</td><td>1-12</td><td>1-13</td>”
+ “<tr><td>1-21</td><td>1-22</td><td>1-23</td>”
+ “<tr><td>1-31</td><td>1-32</td><td>1-33</td></table>”
+ “<table id=’table2′ >”
+ “<tr><td>2-11</td><td>2-12</td><td>2-13</td>”
+ “<tr><td>2-21</td><td>2-22</td><td>2-23</td>”
+ “<tr><td>2-31</td><td>2-32</td><td>2-33</td></table>”
+ “</body>” + “</html>”;
Parser parser = new Parser();
try {
parser.setInputHTML(inputHTML);
parser.setEncoding(parser.getURL());
HtmlPage page = new HtmlPage(parser);
parser.visitAllNodesWith(page);
logger.fatal(“testHtmlPage -title is :” + page.getTitle());
NodeList list = page.getBody();

for (NodeIterator iterator = list.elements(); iterator
.hasMoreNodes();) {
Node node = iterator.nextNode();
logger.fatal(“testHtmlPage -node  is :” + node.toHtml());
}

} catch (ParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* 测试LinkBean的用法
*/
public void testLinkBean() {
Parser parser = new Parser();

LinkBean linkBean = new LinkBean();
linkBean.setURL(“http://www.google.com”);
URL[] urls = linkBean.getLinks();

for (int i = 0; i < urls.length; i++) {
URL url = urls[i];
logger.fatal(“testLinkBean() -url  is :” + url);
}

}

}

5、相关的项目

nekohtml :评价比htmlparser好,把html正规化标准的xml文档,用xerces处理,但文档较少。

mozilla htmlparserhttp://www.dapper.net/网站采用的html解析器,开源了,基于mozilla的解析器,值得研究一下。

http://jerichohtml.sourceforge.net/

http://htmlcleaner.sourceforge.net/

http://html.xamjwg.org/cobra.jsp

http://jrex.mozdev.org/

https://xhtmlrenderer.dev.java.net

其他一些html parser可以参考相关的汇总文章:

http://www.manageability.org/blog/stuff/screen-scraping-tools-written-in-java/view

http://java-source.net/open-source/html-parsers

http://www.open-open.com/30.htm

6、参考文档

http://www.blogjava.net/lostfire/archive/2006/07/02/56212.html

http://blog.csdn.net/scud/archive/2005/08/11/451397.aspx

http://chasethedevil.blogspot.com/2006/05/java-html-parsing-example-with.html

http://javaboutique.internet.com/tutorials/HTMLParser/

Technorati 标签: ,,,,,,

无线互联网垂直电子商务平台各系统内容运维策略思考

Posted in Uncategorized by chuanliang on 2008/05/16

    在平台的eSales系统、BSS/OSS系统、支付平台、门户这几大平台中,门户及eSales系统中有大量的关于手机软件、手机素材、手机型号参数、手机相关专业知识等相关的资源,这些内容的完备与否是各平台能否成功的关键所在。但作为一家以“渠道通路”为核心竞争力的初创性的互联网公司,内容本身的运维、运营并不是我们自己最为擅长的地方,近期也不可能招聘一批专职的内容运营人员来做网站内容本身的运营。从技术角度谈一下门户及eSales系统的内容运维策略,以更好支撑运营部门的日常运营。

1. 平台(门户、eSales系统)内容运维基本原则

  • 门户定位:手机增值业务垂直性门户
  • 内容运维基本原则:采用搜索引擎全自动或搜索引擎+人工(人肉搜索)方式对内容进行爬取入库
  • 内容演进过程:垂直搜索引擎自动爬取的内容基础库(无原创内容,无筛选)->垂直搜索引擎爬取+内容运营人员编辑形成的内容库(较少原创内容,人工参与筛选)->垂直搜索引擎爬取+内容运营人员编辑+社区原创内容(具有部分原创内容)->以社区原创性内容为主的内容(UGC)。

第一阶段:垂直搜索引擎自动爬取的内容基础库

    由垂直搜索引擎的对互联网上海量的手机内容进行自动爬取入库,形成自身的基础内容库;在内容上无太多的原创内容,也无内容编辑人员对爬取的内容进行筛选和过滤。这属于内容运维的第一阶段,也是近期的技术开发重点。

第二阶段:垂直搜索引擎爬取+内容运营人员编辑形成的内容库

    在第一阶段的基础上,由内容运营人员对爬取内容进行审核、编辑,保证内容库的质量

第三阶段:垂直搜索引擎爬取+内容运营人员编辑+社区原创内容

    在第三阶段基础上,将内容平台逐步开放,并将社区人员来参与到内容平台的建设中,充分发挥群体智慧的力量。

第四阶段:以社区原创性内容为主的内容

    在社区逐步成熟后,在此阶段,平台的核心内容只要是用户参与产生的内容(UGC),这也是门户的核心价值。

2. 技术架构指导原则:

    技术架构的统一:各平台核心数据模型、业务模型、技术架构必须遵循平台统一的架构,保证平台各系统的内容资源是完全复用的。

    垂直搜索引擎的建设:尽管垂直门户的建设是门户的核心内容,但围绕“渠道通路”的建设才是的核心竞争力,这包括支付通路、内容分销通路、手机通路、互联网通路等,近期的建设重点并不是社区门户的建设,因此在开发上不能投入太多的开发资源到垂直搜索引擎的开发上,在满足未来扩展性的基础上,采用相对快捷的方式开发垂直搜索引擎。

3. 垂直搜索引擎技术实现

    近期垂直搜索引擎的建设重点是爬虫,与普通的垂直性搜索引擎不同,我们是对网站内容进行爬取,而不对内容进行索引。而在内容爬取上,重点是对指定网站页面内容(例如北斗手机网)所需要内容的定向解析。

    爬取整站内容或复杂爬取需求选用的爬虫框架:Heritrix、Nutch。但这两个框架都较重,初期我们并不需要处理诸如爬取层次、增量爬取等策略,因此这两个框架后期再采用。

    对Javascript的解析:采用Rhino(SpiderMonkey)

    爬取指定内容选用的爬虫框架(目前使用方式):httpclient+htmlparser(nekohtml)。采用httpclient完成对网站内容指定页面的爬取,采用nekohtml或htmlparser包来对页面内容进行定向解析并爬取。在实现上可以参考httpunit对Rhino+httpclient+nekohtml的封装和实现。

    对采用AJAX方式生成内容的爬取:采用Cobra(http://lobobrowser.org/cobra.jsp

    搜索引擎:采用Lucene+Compass

 

Jboss虚拟主机安装部署指南

Posted in Uncategorized by chuanliang on 2008/05/16
1. 环境说明:

x.x.x.137:应用服务器,内网地址:192.168.1.137

x.x.x.180:数据库服务器,内网地址:192.168.1.180

数据库服务器和应用服务器之间通信通过内网地址通信。

2. 操作系统

操作系统版本:Redhat AS 5

安装:操作系统按照缺省的安装方式安装,无特殊要求。

由于没有硬件防火墙,因此需要安装iptables,建议不要安装selinux。

相关应用安装到/opt目录下,包括jdk、应用等。

3. 防火墙配置

防火墙策略:

只允许应用服务器x.x.x.137(192.168.1.137)访问数据库服务器x.x.x.180(192.168.1.180)的3306端口,且允许数据库服务器 x.x.x.180(192.168.1.180)能够访问应用服务器的x.x.x.137(192.168.1.137)的[1024,65535]端口(socket连接的随机端口)。

配置方法:

在数据库服务器x.x.x.180上执行如下操作:

1) 直接修改iptables的配置文件/etc/sysconfig/iptables

在:RH-Firewall-1-INPUT – [0:0]增加如下内容:

-A INPUT -p tcp -s x.x.x.137 –sport 1024:65535 -d x.x.x.180 –dport 3306 -m state –state NEW,ESTABLISHED -j ACCEPT

-A OUTPUT -p tcp -s x.x.x.180 –sport 3306 -d x.x.x.137 –dport 1024:65535 -m state –state ESTABLISHED -j ACCEPT

-A INPUT -p tcp -s 192.168.1.137 –sport 1024:65535 -d 192.168.1.180 –dport 3306 -m state –state NEW,ESTABLISHED -j ACCEPT

-A OUTPUT -p tcp -s 192.168.1.180 –sport 3306 -d 192.168.1.137 –dport 1024:65535 -m state –state ESTABLISHED -j ACCEPT

2) 重启iptables

service iptables restart

注意还要确认应用服务器(x.x.x.137)上开通了80端口,允许从公网访问:

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

4. 在域名服务器上做域名地址映射

在域名服务器上做映射,将e.yeeach.com映射到x.x.x.137地址,没有做URL指向转发

为了简化代理商销售人员记忆,避免记忆ip及复杂域名,因此采用二级域名+虚拟主机方式来实现此目标。

5. 安装JDK

使用Java SE 6 Update 6版本

wget -O jdk-6u6-linux-i586.bin

http://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/VerifyItem-Start/jdk-6u6-linux-i586.bin?BundledLineItemUUID=A_JIBe.mKjcAAAEZ.0gv8eBL&OrderID=dy5IBe.mH10AAAEZ70gv8eBL&ProductID=VXZIBe.ootIAAAEZTrBAkQve&FileName=/jdk-6u6-linux-i586.bin

chmod 755 jdk-6u6-linux-i586.bin

./jdk-6u6-linux-i586.bin  

mv  jdk1.6.0_06/ /opt/jdk

修改/etc/bashrc(或者也可以修改用户的bash配置文件:~/.bashrc_profile) ,在尾部增加如下内容:

export JAVA_HOME=/opt/jdk

export PATH=$JAVA_HOME/bin:$PATH

6. 安装Jboss 4.2.2

wget http://jaist.dl.sourceforge.net/sourceforge/jboss/jboss-4.2.2.GA.zip

unzip jboss-4.2.2.GA.zip

mv jboss-4.2.2.GA  /opt

7. 增加Jboss APR支持

参看使用APR( Apache Portable Runtime)来提升jboss性能

wget http://www.jboss.org/file-access/default/members/jbossweb/freezone/dist/2.0.4.GA/jboss-native-2.0.4-linux2-x86-ssl.tar.gz

mkdir jboss-native

mv  jboss-native-2.0.4-linux2-x86-ssl.tar.gz  jboss-native/

cd jboss-native/

tar zxvf jboss-native-2.0.4-linux2-x86-ssl.tar.gz

mv  bin/META-INF/bin/linux2/x86/*  /opt/jboss-4.2.2.GA/bin/

mkdir /opt/jboss-4.2.2.GA/bin/native

cp –r bin/META-INF/lib/linux2/x86/*   /opt/jboss-4.2.2.GA/bin/native

8. 修改Jboss 参数

1) 修改JVM参数

修改bin/run.conf,修改参数:

if [ “x$JAVA_OPTS” = “x” ]; then

         JAVA_OPTS=”-Xms512m -Xmx1024m -server -XX:MaxPermSize=300m -XX:MaxNewSize=300m -server -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000″

fi

2) 修改jboss-4.2.2.GA/server/default/deploy/jboss-web.deployer/server.xml,将

将8080修改为80

将${jboss.bind.address}修改为0.0.0.0,允许从eth0,eth1,lo都能够访问jboss 80端口

  也可以在启动参数中指定ip地址选项,指定绑定的ip地址:run.sh -b x.x.x.137
9. 去除jboss的冲突包

由于jboss自身带的hibernate-annotations.jar版本与项目使用的版本存在冲突,因此去除hibernate-annotations.jar包

mv /opt/jboss-4.2.2.GA/server/default/lib/hibernate-annotations.jar /opt/jboss-4.2.2.GA/server/default/lib/hibernate-annotations.jar.bak

 
10. 部署esales.war到/opt/jboss-4.2.2.GA/server/default/deploy

修改数据库连接池等应用相关参数

11. 增加虚拟主机Host节点

在/opt/jboss-4.2.2.GA/server/default/deploy/jboss-web.deployer/server.xml 中的 Engine加一个 Host 节点,内容如下:

<Host name=”esales” autoDeploy=”false”         

      deployOnStartup=”false” deployXML=”false”>

    <Alias>e.yeeach.com</Alias>

    <Valve className=”org.apache.catalina.valves.AccessLogValve”

           prefix=”esales” suffix=”.log” pattern=”common”         

           directory=”${jboss.server.home.dir}/log”/>

    <DefaultContext cookies=”true” crossContext=”true” override=”true”/>

</Host>

12. 在eSales应用中配置虚拟主机

在/opt/jboss-4.2.2.GA/server/default/deploy/esales.war/WEB-INF下创建文件jboss-web.xml,内容如下:

<jboss-web>

    <context-root>/</context-root>

    <virtual-host>e.yeeach.com</virtual-host>

</jboss-web>

 

 

使用APR( Apache Portable Runtime)来提升jboss性能

Posted in Uncategorized by chuanliang on 2008/05/16

从tomcat5.0后,可以用 Apache Portable Runtime 来通过jni来使用native接口,提升Tomcat及Jboss的性能,同时可以提升Web静态页面的处理能力,从理论上可以不再需要专门的Web Server来处理静态页面了。当然考虑到负载均衡、rewrite、虚拟主机等支持的考虑,还会采用lighttpd来作为前端的Web Server。

在对eSales系统部署时候,采用Jboss 4.2.2.GA,简单描述一下Jboss-Tomcat 使用APR的方法,关于Tomcat支持APR的参看相关文档。

1、下载boss-4.2.2.GA,最新稳定版本为4.2.2

wget http://jaist.dl.sourceforge.net/sourceforge/jboss/jboss-4.2.2.GA.zip

2、下载APR,由于Jboss对APR有一些特别的改动,因此需要从Jboss网站下载:

wget http://www.jboss.org/file-access/default/members/jbossweb/freezone/dist/2.0.4.GA/jboss-native-2.0.4-linux2-x86-ssl.tar.gz

3、解压jboss,假设安装路径为/opt

unzip jboss-4.2.2.GA.zip

mv jboss-4.2.2.GA  /opt

4、启动jboss,测试一下Jboss安装情况

cd jboss-4.2.0.GA/bin
./run.sh
 
注意console输出中类似如下信息:
21:51:56,325 INFO  [AprLifecycleListener] The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /opt/jdk/jre/lib/i386/server:/opt/jdk/jre/lib/i386:/opt/jdk/jre/../lib/i386:/usr/java/packages/lib/i386:/lib:/usr/lib

退出Jboss

5、解压jboss-native-2.0.4-linux2-x86-ssl.tar.gz

mkdir jboss-native

mv  jboss-native-2.0.4-linux2-x86-ssl.tar.gz  jboss-native/

cd jboss-native/

tar zxvf jboss-native-2.0.4-linux2-x86-ssl.tar.gz

mv  bin/META-INF/bin/linux2/x86/*  /opt/jboss-4.2.2.GA/bin/

mkdir /opt/jboss-4.2.2.GA/bin/native

cp –r bin/META-INF/lib/linux2/x86/*   /opt/jboss-4.2.2.GA/bin/native

6、再次启动jboss

/opt//jboss-4.2.2.GA/bin/run.sh &

注意console输出的如下信息:

21:57:36,150 INFO  [AprLifecycleListener] Loaded Apache Tomcat Native library 1.1.13.

21:57:36,156 INFO  [AprLifecycleListener] APR capabilities: IPv6 [true], sendfile [true], accept filters [false], random [true].

21:57:44,286 INFO  [Http11AprProtocol] Initializing Coyote HTTP/1.1 on http-127.0.0.1-8080

21:57:44,288 INFO  [AjpAprProtocol] Initializing Coyote AJP/1.3 on ajp-127.0.0.1-8009

说明apr已经正常启用

参考文档:

http://wiki.jboss.org/wiki/HowToAddAprToJBoss

 

Technorati 标签: ,,,
Tagged with: , , ,