出家如初,成佛有余

用UrlRewriteFilter实现不同WebContext间的url rewrite

Posted in Uncategorized by chuanliang on 2008/04/30

    在新平台中,由于需要保持与原有CP、SP的接口的不变(目前的接口都为基于http协议的),例如原来与CP的接口为:

http://www.yeeach.com/platform/retail.do?act=verifyIp。

为了保证系统平滑的迁移,老的平台需要保留一段时间,新的平台需要以新的Webcontext方式进行部署,例如新的应用的web路径为:

http://www.yeeach.com/platformnew,与cp的接口假定新平台的处理接口为http://www.yeeach.com/platformnew/ retail.action?act=verifyIp,也即需要进行如下的迁移处理:

http://www.yeeach.com/platform/retail.do?act=verifyIp        –>         http://www.yeeach.com/platformnew/retail.action?act=verifyIp

    此种情况下与为了搜索引擎优化而进行的URL优化(也即现在流行的所谓的REST)将

http://www.yeeach.com/platform/retail.do/act/verifyIP –> http://www.yeeach.com/platform/retail.do?act=verifyIp

的场景并不相同

    同时由于跨了不同的Web Context,因此采用strtus2的struts.action.extension=action,do来实现action不同后缀的方式也行不通。

    可行的解决方案有两种:

1、  利用前端的apache或lighttpd的mod_rewrite来实现

2、  利用java开源项目UrlRewriteFilter http://tuckey.org/urlrewrite/来实现类似mod_rewrite的功能

    由于目前尚未有太多的静态页面内容,因此尚未部署apache或lighttpd(做门户时候再基于架设lighttpd),同时考虑到相对独立性,因此先采用纯java的UrlRewriteFilter解决方案来实现,简单描述一下实现方法:

1、  下载及安装UrlRewriteFilter

http://urlrewritefilter.googlecode.com/files/urlrewritefilter-3.1.0.zip 下载 UrlRewriteFilter 3.1.0版本

2、将urlrewrite-3.1.0.jar部署到旧的platform/WEB-INF/lib下

3、修改web.xml,增加如下内容

        <filter>

            <filter-name>UrlRewriteFilter</filter-name>

            <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>

            <init-param>

                <param-name>logLevel</param-name>

                <param-value>WARN</param-value>

            </init-param>

        </filter>

        <filter-mapping>

            <filter-name>UrlRewriteFilter</filter-name>

            <url-pattern>/*</url-pattern>

        </filter-mapping>

4、在旧的webapp platform中部署urlrewrite.xml到WEB-INF/下,内容如下

<?xml version=”1.0″ encoding=”utf-8″?>

<!DOCTYPE urlrewrite PUBLIC “-//tuckey.org//DTD UrlRewrite 3.1//EN”

        “http://tuckey.org/res/dtds/urlrewrite3.1.dtd”>

<urlrewrite use-query-string=”true”>

    <rule>

        <from>^/retail.do?act=(.*)$</from>

        <to type=”forward”>/platformnew/retail.action?act=$1</to>

    </rule>

</urlrewrite>

注意事项:

1)、实例的请求形式为:

http://www.yeeach.com/platform/retail.do?act=verifyIp         –>         http://www.yeeach.com/platformnew/retail.action?act=verifyIp

2)、对于to type可以为redirect、forward等,由于forward不能跨不同context之间,因此只能采用redirect方式。对于在统一Webcontext请求可以采用forward方式

3)、from和to都支持类似于apache mod_rewrite正则表达式用法,具体参看文档

4)、对于urlrewritefilter 3.x版本,对于诸如/retail.action?act=verifyIp这样的参数字符串,缺省情况下urlrewritefilter并不处理,由参数use-query-string决定,缺省为false。需要显性设置为true:<urlrewrite use-query-string=”true”>

摘自:http://urlrewritefilter.googlecode.com/svn/trunk/src/doc/manual/3.1/index.html

use-query-string    (optional) 

  false (default)       The query string will not be appended to the url that the “from” element matches against.

  true                     The query string will be appended to the url that the “from” element matches against.

 

5)、一定要注意在from 的查询url中的?需要进行转义处理:?,不然会报错。因为?字符在正则表达式是0或1多个字符。to中不需要转义

6)、对url中&字符在xml文件中,用:&amp;

 

Advertisements

支付系统风控系统建设思考

Posted in Uncategorized by chuanliang on 2008/04/27

    第三方电子支付是一个高风险的行业,这就意味着第三方电子支付公司必然要与各种不确定性相伴。从风险受益的角度来看,第三方电子支付公司存在的价值不在于其能消灭不确定性,消灭风险,而在于其能在对风险有较深入认识的基础上控制和管理风险,将风险配置到愿意并能承担风险的主体,并使其获得收益。风险控制系统目标是实现对各个业务部门、渠道和产品线和相关人员监测,通过对运营业务交易的实时分析、事中和事后分析、跟踪和处理的方法实现欺诈风险预警的自动化。通过对交易的监测,可以识别那些是高风险交易,以及早发现其欺诈的可能性,并及时采取各种防范措施,由此来降低交易带来的损失。

1. 管理框架

    第三方电子支付风险管理解决方案由风险战略,组织架构和管理流程组成。其中,风险战略的确定是欺诈风险管理业务体制和运作机制设计的基础;组织架构确立风险管理运作机制和相应的组织管理模式,明确相关部门、人员、关键岗位分工和职责;管理流程则是一个完整的风险管理过程所包含的各个环节。各种风险管理的机制和体制需要通过统一的管理平台来实现。统一的欺诈风险管理平台包括监测模块、分析模块和案件管理模块。构建风险管理 平台的关键技术是以合理规范的数据模型,建立整合的风险数据平台;以及针对内部程序、人员和外部事件,实施有效的业务运营监测。

风险管理机制和体制

                                        风险控制系统管理框架

    统一的风险控制系统包括三大模块:监测模块、分析模块和案件管理模块。

    监测模块对支付平台的运营作业进行全面监测,将可疑行为信息和相关信息发送到分析模块进行分析确认,当发现异常行为时发出警报,将警报和与警报相关的信息都发送到分析模块和案件管理模块。

    分析模块能对各种数据源进行集成,对各种历史数据进行学习分析,通过数据挖掘建模定义出典型的行为特征,建立行为模式、场景,并制定欺诈风险监测规则。

    案件管理模块最终将案件处理的数据反馈到监测模块,从而增强监测模块对风险的监测和识别能力。

2. 监测模块

    交易监控系统要及时对交易进行处理,在最短的时间内对可能存在的交易风险进行判别,准确的报告欺诈等高风险交易,在第一时间提供详尽的信息以协助工作人员对可疑交易进行识别、处理。为保证这种效能,交易监控系统需要采用准实时、分布式的模式进行交易处理。在部署上交易监控系统和交易系统应当支持分布式部署,在不同的主机系统上部署,系统间通过可靠的消息中间件进行信息的传递,降低系统间的耦合度,保证交易系统的高性能,同时也可以通过前置机方式来降低交易系统对信息处理的负荷。

    交易系统在接收到主机的交易信息,及时地发送至交易监控系统;而交易监控系统的接收端也要及时地处理发送至交易监控系统的分析审查信息。当一笔交易进入交易系统后,系统会根据业务规则进行处理,完成以后系统会给这笔交易返回一个结果代码(批准或拒绝)。在整个交易处理完成之后,主机将交易信息下传至前置系统。

    交易系统接到联机交易后,通过消息队列将此笔交易信息传送到交易监控系统,在交易监控系统内部进行交易情况分析及报警处理。交易系统除了向交易监控系统传送交易信息外,还需提供客户的基本信息例如状态等资料。交易监控系统将从前置系统的jms消息队列中接收交易系统主机发送的交易信息。上述交易信息将由前置系统所在平台上的接口程序与帐户资料、卡片资料等信息组合后形成一个XML报文,通过jms消息队列传送给运行在交易系统平台上的交易监控系统接口程序,经监控系统接口处理后通过jms队列送入交易监控系统。为保证交易监控系统和交易系统间信息的同步,可采用数据库的replication机制来保证数据的实时同步;也可以采用程序或存储过程来实时同步核心数据(例如客户资料的变动),定时同步其他数据的方式。

3. 分析模块

    分析模块采用商业智能技术来构建风险控制系统的风险分析引擎,采用商业智能技术可以对海量数据快捷的存储和提取,基于数据的分析、操纵,建模,稳定的报表能力,多用户支持能力,再结合有效的信息权限控制、风险预警模型、风险预测、信息整合等,就能够有效进行电子支付系统的风险管理。

    商业智能在风险控制中所起的作用:

  • 数据准备:

数据准备主要是从源数据中,提取有效的指标数据、预算数据、交易汇总数据等,并转换到总体的数据仓库或风险管理的数据集市中,其本质是实现从操作型数据源到分析型数据变换。

  • 分析模版定义:

对各种分析内容进行分类,同时明确风险管理方面的各个数据主题模版定义,包括风险评级,风险分析,风险预测几个模版定义,提供给风险分析内容进行调用。

  • 风险分析:

目前已有多种风险分析度量模型,如基本指标法、标准化方法、内部衡量法、损失分布法、极值理论模型等,可根据银行业务需求建立。这一阶段主要是运用这些模型来对风险进行分析,确定数据的维度、事实表、量度等信息,根据维度来分析各个指标和预测信息。

  • 自动化分析:

    利用商业智能软件提供的功能,并根据已经定义的风险分析内容,由系统自动到数据仓库系统进行优化分析,加载与钻取这些风险内容。

  • 定量、定性分析报告:

    根据系统的自动化分析,生成各种定量和定性的分析指标报告。

4. 案件管理

    案例管理系统采用神经元网络技术,构筑有学习能力的交易模式识别的案例库。神经元网络技术对于交易模式的变化,可再造学习模型和案例库。用户可以通过对其应用模型的更新,来适应新的交易模式和欺诈形式的变化,提高对欺诈交易的识别和控制能力。

    案件管理模块最终将案件处理的数据反馈到监测模块,从而增强监测模块对欺诈风险的监测和识别能力。一旦某个交易被确认为是欺诈行为,案件管理模块会对这些相关数据、潜在的客户联系和影响进行管理以及对客户账户
进行必要的调整。该模块作为面向业务用户的展现层,将实现全面有效的案件管理,完成对欺诈的预防和调查。

5、风险监控系统架构

风控系统架构

6、参考资料

IBM:整合管理平台,规避欺诈风险:银行业全面提升操作风险管理水平

Hibernate Criteria使用实例

Posted in Uncategorized by chuanliang on 2008/04/26

在使用jmesa作为组件来实现分页、导入、排序、过滤组件时候,对paging、sort、filter的处理,使用的是Hibernate的Criteria函数,对于单表使用Criteria方法相对容易,但对于多表操作,手册上没有现成的样例可以借鉴。总结一下Criteria的一些用法,以方便在对多表数据复杂操作时候也能够使用jmesa,简化分页、导出等日常操作。

1、目前使用Criteria用于取总数及排序过滤的用法例子

public int getPersonCountWithFilter(final HibernateFilter filter) {

Integer count = (Integer) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

Criteria criteria = session.createCriteria(Person.class);

criteria.add(Expression.eq(“id”,27));

criteria = filter.execute(criteria);

criteria.setProjection(Projections.rowCount()).uniqueResult();

return criteria.uniqueResult();

}

});

return count.intValue();

}

public List<Person> getPersonWithFilterAndSort(final HibernateFilter filter, final HibernateSort sort, final int rowStart, final int rowEnd) {

List applications = (List) getHibernateTemplate().execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

Criteria criteria = session.createCriteria(Person.class);

criteria = filter.execute(criteria);

criteria = sort.execute(criteria);

criteria.setFirstResult(rowStart);

criteria.setMaxResults(rowEnd – rowStart);

return criteria.list();

}

});

return applications;

}

2、数据库表结构,以sys_user和sys_user_role为例子

CREATE TABLE `sys_user_role` (

`user_id` int(11) NOT NULL,

`role_id` int(11) NOT NULL,

`user_name` varchar(100) default NULL,

`role_name` varchar(100) default NULL,

PRIMARY KEY (`role_id`,`user_id`)

) ;

INSERT INTO `sys_user_role` VALUES (‘1’, ‘1’, ‘liang1’, ‘admin1’);

INSERT INTO `sys_user_role` VALUES (‘2’, ‘2’, ‘liang2’, ‘admin2’);

INSERT INTO `sys_user_role` VALUES (‘3’, ‘3’, ‘liang3’, ‘admin3’);

INSERT INTO `sys_user_role` VALUES (‘4’, ‘4’, ‘liang4’, ‘admin4’);

INSERT INTO `sys_user_role` VALUES (‘5’, ‘5’, ‘liang5’, ‘anonymous’);

INSERT INTO `sys_user_role` VALUES (‘1’, ‘5’, ‘liang1’, ‘admin5’);

CREATE TABLE `sys_user` (

`user_id` int(11) NOT NULL,

`mobile` varchar(15) default NULL,

`imei` varchar(20) default NULL,

`user_name` varchar(100) NOT NULL,

`password` varchar(50) default NULL,

`user_type` varchar(40) default ‘normal’ ,

`login_type` varchar(20) default NULL,

`customer_id` int(11) default NULL,

`customer_name` varchar(200) default NULL,

`root_company_id` int(11) default NULL,

`root_company_name` varchar(255) default NULL,

`compayn_id` int(11) default NULL,

`company_name` varchar(255) default NULL,

`email` varchar(100) default NULL,

`email2` varchar(100) default NULL,

`nickname` varchar(100) default NULL,

`sex` varchar(10) default NULL ,

`status` varchar(50) default NULL,

`credit_amount` decimal(10,2) default NULL,

`credit_rank` varchar(20) default NULL,

`money` decimal(10,2) default NULL,

`integral` decimal(10,2) default ‘0.00’,

`website` varchar(200) default NULL,

`pwd_modify_date` datetime default NULL,

`pwd_duration` varchar(10) default NULL,

`signature` text,

`twitter` varchar(255) default NULL,

`qq` varchar(20) default NULL,

`msn` varchar(50) default NULL,

`icq` varchar(50) default NULL,

`yahoo` varchar(30) default NULL,

`gtalk` varchar(30) default NULL,

`blog` varchar(255) default NULL,

`interest` text,

`safe_question` varchar(100) default NULL,

`safe_answer` varchar(100) default NULL,

`safe_question2` varchar(100) default NULL,

`safe_answer2` varchar(100) default NULL,

`safe_question3` varchar(100) default NULL,

`safe_answer3` varchar(100) default NULL,

`icon` varchar(100) default NULL,

`icon2` varchar(100) default NULL,

`icon3` varchar(100) default NULL,

`is_test` tinyint(1) default NULL ,

`is_admin` tinyint(1) default NULL,

`fax` varchar(20) default NULL,

`home_phone` varchar(50) default NULL,

`office_phone` varchar(20) default NULL,

`birthday` char(19) default NULL,

`vocation` varchar(20) default NULL,

`education` varchar(50) default NULL,

`address` varchar(255) default NULL,

`postcode` varchar(20) default NULL,

`description` text,

`creator` varchar(40) default NULL,

`begin_date` datetime default NULL,

`end_date` datetime default NULL,

`create_date` datetime default NULL,

`modify_user` varchar(40) default NULL,

`modify_date` datetime default NULL,

`last_login_type` varchar(20) default NULL,

`last_login_id` varchar(20) default NULL,

`last_login_date` datetime default NULL,

PRIMARY KEY (`user_id`)

) ;

INSERT INTO `sys_user` VALUES (‘1’, ‘13911111111’, null, ‘liang1’, ‘liang1’, ‘normal’, null, null, ‘liang1’, null, null, null, null, null, null, null, null, null, null, null, null, ‘0.00’, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (‘2’, ‘13922222222’, null, ‘liang2’, ‘liang2’, ‘normal’, null, null, ‘liang2’, null, null, null, null, null, null, null, null, null, null, null, null, ‘0.00’, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (‘3’, ‘13933333333’, null, ‘liang3’, ‘liang3’, ‘normal’, null, null, ‘liang3’, null, null, null, null, null, null, null, null, null, null, null, null, ‘0.00’, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (‘4’, ‘13944444444’, null, ‘liang4’, ‘liang4’, ‘normal’, null, null, ‘liang4’, null, null, null, ‘li’, null, null, null, null, null, null, null, null, ‘0.00’, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

INSERT INTO `sys_user` VALUES (‘5’, ‘13955555555’, null, ‘liang5’, ‘liang5’, ‘normal’, null, null, ‘liang5’, null, null, null, null, null, null, null, null, null, null, null, null, ‘0.00’, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);

3、hbm映射文件

注意:

由于在目前的数据模型及程序中并没有使用数据库的外键约束,直接通过程序来控制外键约束关系。因此在所有的hbm中并没有使用hibernate 的one-to-many关联。要使用Criteria实现多表较为复杂的操作,需要加上one-to-many映射。但这与目前的程序实现存在冲突,解决方法如下:

由于目前使用Criteria只用于查询及统计分析部分,可以单独建立一个映射文件及映射类,用于查询及统计分析操作,例如对于SysUser表,可以建立一个SysUser-jmesa.hbm.xml,对此映射文件,将<class name=”com.mobilesoft.esales.model.SysUser” table=”sys_user” catalog=”mysql”>
改为:

<class name=”com.mobilesoft.esales.model.SysUserJmesa” table=”sys_user” >

one-to-many:

<set name=”userRoles” table=”sys_user_role” >

<key column=”role_id” />

<one-to-many class=”com.mobilesoft.esales.model.SysUserRole” />

</set>

由于只是演示,简单起见,直接用的是原有的映射文件及映射类。

3.1、SysUser.hbm.xml

<?xml version=”1.0″ encoding=”utf-8″?>

<!DOCTYPE hibernate-mapping PUBLIC “-//Hibernate/Hibernate Mapping DTD 3.0//EN”

http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd”&gt;

<hibernate-mapping>

<class name=”com.mobilesoft.esales.model.SysUser” table=”sys_user” catalog=”mysql”>

<id name=”userId” type=”java.lang.Integer”>

<column name=”user_id” />

<generator class=”native” />

</id>

<property name=”mobile” type=”java.lang.String”>

<column name=”mobile” length=”15″ />

</property>

<!—

省略掉其他内容

–>

<set name=”userRoles” table=”sys_user_role” >

<key column=”user_id” />

<one-to-many class=”com.mobilesoft.esales.model.SysUserRole” />

</set>

</class>

</hibernate-mapping>

3.2、SysUserRole.hbm.xml

<?xml version=”1.0″ encoding=”utf-8″?>

<!DOCTYPE hibernate-mapping PUBLIC “-//Hibernate/Hibernate Mapping DTD 3.0//EN”

http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd”&gt;

<!–

Mapping file autogenerated by MyEclipse Persistence Tools

–>

<hibernate-mapping>

<class name=”com.mobilesoft.esales.model.SysUserRole” table=”sys_user_role” catalog=”mysql”>

<composite-id name=”id” class=”com.mobilesoft.esales.model.SysUserRoleId”>

<key-property name=”roleId” type=”java.lang.Integer”>

<column name=”role_id” />

</key-property>

<key-property name=”userId” type=”java.lang.Integer”>

<column name=”user_id” />

</key-property>

</composite-id>

<property name=”userName” type=”java.lang.String”>

<column name=”user_name” length=”100″ />

</property>

<property name=”roleName” type=”java.lang.String”>

<column name=”role_name” length=”100″ />

</property>

</class>

</hibernate-mapping>

4、测试用例

import java.util.Iterator;

import java.util.List;

import junit.framework.TestCase;

import org.apache.log4j.Logger;

import org.hibernate.FetchMode;

import org.hibernate.criterion.Projections;

import org.hibernate.criterion.Restrictions;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mobilesoft.esales.dao.hibernate.SysUserDAO;

import com.mobilesoft.esales.model.SysUser;

import com.mobilesoft.esales.model.SysUserRole;

/**

* Hibernate Criteria用法测试用例

*

* @author liangchuan@mobile-soft.cn

*

*/

public class TestCriteria extends TestCase {

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

private static ClassPathXmlApplicationContext context = null;

private static SysUserDAO dao;

static {

context = new ClassPathXmlApplicationContext(new String[] {

“applicationContext.xml”, “applicationContext-resources.xml”,

“applicationContext-dao.xml”, “applicationContext-service.xml” });

}

protected void setUp() throws Exception {

}

/**

* 演示使用Criteria实现:select userId from SysUser as user

*/

public void testSelectId() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setProjection(

Projections.projectionList().add(

Projections.property(“userId”)

)

).list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

logger.fatal(“id is :”+iterator.next());

}

}

/**

* 演示使用Criteria实现:select user.*,userRole.* from SysUser as user ,SysUserRole userRole where user.userId=userRole.id

*/

public void testJoin1() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN).list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

SysUser user=(SysUser)iterator.next();

logger.fatal(“testJoin1:userid is :”+user.getUserId()+” userName is “+user.getUserName());

}

}

/**

* 演示使用Criteria实现:select user.* ,userRole.* from SysUser as user ,SysUserRole userRole

* where user.userId=userRole.id and user.userId=1

*/

public void testJoin2() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN)

.add(Restrictions.eq(“userId”,1))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

SysUser user=(SysUser)iterator.next();

logger.fatal(“testJoin2:userid is :”+user.getUserId()+” userName is “+user.getUserName());

}

}

/**

* 演示使用Criteria实现:select user.* ,userRole.* from SysUser as user ,SysUserRole userRole

* where user.userId=userRole.id and userRole.id.roleId=2

* 演示createAlias的使用

*/

public void testJoin3() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN)

.createAlias(“userRoles”, “b”)

.add(Restrictions.eq(“b.id.roleId”,2))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

SysUser user=(SysUser)iterator.next();

logger.fatal(“testJoin3:userid is :”+user.getUserId()+” userName is “+user.getUserName());

}

}

/**

* 演示使用Criteria实现:select count(userId) from SysUser as user ,SysUserRole userRole

* where user.userId=userRole.id

* 同时演示createAlias的使用

*/

public void testJoin4() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUser.class)

.setFetchMode(“userRoles”,FetchMode.JOIN)

.setProjection( Projections.projectionList().add( Projections.count(“userId”) ))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

logger.fatal(“testJoin4:count(userId) is :”+iterator.next());

}

}

/**

* 演示使用Criteria实现:select count(id.roleId) from SysUserRole userRole

* 同时演示createAlias的使用

*/

public void testJoin5() {

dao = (SysUserDAO) context.getBean(“SysUserDAO”);

List mylist=dao.getHibernateTemplate().getSessionFactory().openSession().createCriteria(SysUserRole.class)

.setProjection( Projections.projectionList().add( Projections.count(“id.roleId”) ))

.list();

Iterator iterator=mylist.iterator();

while(iterator.hasNext()){

logger.fatal(“testJoin5:count(roleId) is :”+iterator.next());

}

}

}

5、Model

5.1、SysUser.java

没有什么特别的,直接用myeclipse生成,然后在SysUser中添加上:

private java.util.Set userRoles = new HashSet();

public java.util.Set getUserRoles() {

return userRoles;

}

public void setUserRoles(java.util.Set userRoles) {

this.userRoles = userRoles;

}

5.2、SysUserRole.java

package com.mobilesoft.esales.model;

/**

* SysUserRole entity.

*

* @author MyEclipse Persistence Tools

*/

public class SysUserRole implements java.io.Serializable {

// Fields

private SysUserRoleId id;

private String userName;

private String roleName;

// Constructors

/** default constructor */

public SysUserRole() {

}

/** minimal constructor */

public SysUserRole(SysUserRoleId id) {

this.id = id;

}

/** full constructor */

public SysUserRole(SysUserRoleId id, String userName, String roleName) {

this.id = id;

this.userName = userName;

this.roleName = roleName;

}

// Property accessors

public SysUserRoleId getId() {

return this.id;

}

public void setId(SysUserRoleId id) {

this.id = id;

}

public String getUserName() {

return this.userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

public String getRoleName() {

return this.roleName;

}

public void setRoleName(String roleName) {

this.roleName = roleName;

}

}

6、参考文档

http://www.devarticles.com/c/a/Java/Hibernate-Criteria-Queries-in-Depth/

http://www.devx.com/Java/Article/28754/1954

 

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

业务平台部署方案思考

Posted in Uncategorized by chuanliang on 2008/04/17

Struts2中Validation和Type Conversion

Posted in Uncategorized by chuanliang on 2008/04/10

    在Struts2中,采用Validation Interceptor来完成校验支持,在struts-default.xml中,先调用conversionError interceptor进行type conversion操作,然后调用params 、prepare、validation interceptor完成对字段校验,interceptor的执行顺序是

  1. 型别转换 (type conversion)
  2. 获取参数信心(params )
  3. 为验证等作准备(prepare)
  4. 参数验证 (validation)

    如果在进行type conversion时候发生错误,struts2对同样的字段就不会进行验证,会抛出异常,例如页面一输入字段希望进行是否为整数验证,由于页面输入值缺省都为String类型,因此会在后台抛出类似的错误:

ERROR (com.opensymphony.xwork2.interceptor.ParametersInterceptor:204) – ParametersInterceptor – [setParameters]: Unexpected Exception caught setting ‘integerTest’ on ‘class com.mobilesoft.esales.webapp.action.MyValidationAction: Error setting expression ‘integerTest’ with value ‘[Ljava.lang.String;@11e170c’

    而在验证页面,会抛出如下的错误信息:

    Invalid field value for field “integerTest”

   怎么把”Invalid field value for …” 这样的信息转化成我们指定的信息,方法有几个:

1、自定义转换类

将页面非String 类型的property转换成指定的类型,这样进行validation验证

  参看http://struts.apache.org/2.0.11.1/docs/type-conversion.html

  对于页面输入有意义的validator实际上只有required、date、int几个有意义,其它的意义都不大;同时由于要做单独的类型转换类,比较麻烦,不采用此种方式

2、采用struts2的i18n支持

又有几种方法

2.1、在资源文件中设定全局的xwork.default.invalid.fieldvalue 属性

xwork.default.invalid.fieldvalue=数据格式不正确

2.2、在资源文件中设定invalid.fieldvalue.字段名称属性

invalid.fieldvalue.integerTest=数据格式不正确

2.3、定义针对每一个Action的properties文件

例如在MyValidationAction.properties(与MyValidationAction放在同一目录中,而不是在classes下)中定义

invalid.fieldvalue.integerTest=数据格式不正确

注意:

  • 目前在架构中,资源文件命名为ApplicationResources.properties,ApplicationResources_zh.properties,在web.xml中定义的:

<context-param>
    <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
    <param-value>ApplicationResources</param-value>
</context-param>

  • struts2缺省提供了一些type conversion 类,能够完成一些基本的转换操作:

String

boolean / Boolean

char / Character

int / Integer, float / Float, long / Long, double / Double

dates – uses the SHORT format for the Locale associated with the current request

arrays – assuming the individual strings can be coverted to the individual items

collections – if not object type can be determined, it is assumed to be a String and a new ArrayList is created

  • 要完成客户端校验,注意在<s:form >上添加validate=”true”

  <s:form action =”myValidationAction” validate=”true” >

 

3、例子

3.1、validationInput.jsp

<%@ taglib prefix=”s” uri=”/struts-tags” %>
<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>

<html>
<head>
    <title> Validation测试 </title>
    <s:head />
</head>
<body>
    <s:form action =”myValidationAction” validate=”true” >           
        <s:textfield name =”strTest” label =”String类型测试” />
        <s:textfield name =”integerTest” label =”Integer类型测试” />
        <s:textfield name =”emailTest” label =”email类型测试” />
        <s:submit />
    </s:form>   
</body>
</html>

3.2、validationOutput.jsp

<%@ taglib prefix=”s” uri=”/struts-tags” %>
<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>

<html>
<head>
    <title> Hello World </title>
</head>
<body>
    String类型测试: <s:property value =”strTest” />   <br/>
    Integer类型测试: <s:property value =”integerTest” />  <br/> 
    email类型测试: <s:property value =”emailTest” />    <br/>
</body>
</html>

 

3.3、MyValidationAction-validation.xml

<!DOCTYPE validators PUBLIC “-//OpenSymphony Group//XWork Validator 1.0//EN”
    “http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd”>
<validators>
    <field name=”strTest”>
        <field-validator type=”requiredstring”>
            <message>字段不能为空</message>
        </field-validator>
    </field>

  <field name=”integerTest”>
<field-validator type=”conversion”> 
     <message>conversion</message> 
</field-validator>
      <field-validator type=”required”>
          <message>字段不能为空</message>
      </field-validator>
      <field-validator type=”int”>
          <param name=”min”>6</param>
          <param name=”max”>10</param>
          <message>值必须在 [ ${min}, ${max}], 当前的值是: ${integerTest}.</message>
      </field-validator>
  </field>
    <field name=”emailTest”>
          <field-validator type=”required”>
          <message>字段不能为空</message>
      </field-validator>
        <field-validator type=”email”>
            <message>字段必须为邮件地址</message>
        </field-validator>
    </field>
</validators>

3.4、MyValidationAction.java

package com.mobilesoft.esales.webapp.action;

public class MyValidationAction extends BaseAction {
    private Stri
ng strTest;
    private Integer integerTest;
    private String emailTest;
    public String execute(){
        return SUCCESS;
    }
    public String getStrTest() {
        return strTest;
    }
    public void setStrTest(String strTest) {
        this.strTest = strTest;
    }
    public Integer getIntegerTest() {
        return integerTest;
    }
    public void setIntegerTest(Integer integerTest) {
        this.integerTest = integerTest;
    }
    public String getEmailTest() {
        return emailTest;
    }
    public void setEmailTest(String emailTest) {
        this.emailTest = emailTest;
    }

}

 

3.5、struts.xml

<action name=”myValidationAction” method=”execute” class=”com.mobilesoft.esales.webapp.action.MyValidationAction”>
    <result name=”success”>validationOutput.jsp</result>
    <result name=”input”>validationInput.jsp</result>
</action>    

4、参考文档

http://struts.apache.org/2.x/docs/validation.html

http://struts.apache.org/2.0.11.1/docs/type-conversion.html

http://struts.apache.org/2.x/docs/localizing-output.html

 

学习电信BOSS系统好榜样-电子商务系统建设思考6:eSales分销平台技术接口思考

Posted in Uncategorized by chuanliang on 2008/04/09

Struts2 中下拉框中采用树形结构实现

Posted in Uncategorized by chuanliang on 2008/04/03

  在项目中,有大量的诸如“产品类型”这样的下拉选择框,为了保证项目后期的可维护性,除了相对固化的类别可以在代码中直接写死外,这些下拉选择框应当尽量从系统的数据字典表中进行动态读取和展现。但在展现时候存在一个问题,如果分类存在多级时候,一种方案是采用几个下拉框进行级联操作,也即所谓的DoubleSelect,但这种情况只适用与两级级联菜单的情况,在处理上也不具有通用性。例如典型的需求:

对于产品类别,结构如下:

产品类别(顶级节点:root,类型:product_type):

 |-娱乐(产品大类1:product_type1,类型:product_type)

   |-音乐(产品分类11,product_type11)

     |-流行音乐(子分类111,product_type111)

     |-摇滚音乐(子分类112,product_type112)

   |-产品分类12

 |-商务(产品大类2)

 

1、数据库存储策略

 

鉴于此,在项目中,对于这样的需求的实现方法如下:

对于一种类别(例如对于产品类别product_type)的子类别在oss_category(类别表)中存储方案如下:

1、在oss_category表中插入一条记录用于标识“产品类别”对应的顶级节点,其

category_id=1,parent_id=-1,category_type=”product_type”,category_name=”root”

2、在oss_category表中插入一条记录用于标识“product_type11”2级节点,其

category_id=2,parent_id=1,category_type=”product_type”,category_name=”product_type1”

3、在oss_category表中插入一条记录用于标识“product_type11”3级节点,其

category_id=3,parent_id=2,category_type=”product_type”,category_name=”product_type11”

整个数据库脚本如下:

CREATE TABLE `oss_category` (

`category_id` int(11) NOT NULL auto_increment,

`parent_id` int(11) default ‘-1’,

`level` smallint(6) default NULL,

`is_leaf` tinyint(1) default NULL,

`category_title` varchar(100) default NULL,

`category_name` varchar(100) default NULL,

`category_code` varchar(100) default NULL,

`category_type` varchar(30) default NULL,

`image` varchar(255) default NULL,

`status` varchar(20) default NULL,

`creator` varchar(50) default NULL,

`create_date` datetime default NULL,

`modify_user` varchar(50) default NULL,

`modify_date` datetime default NULL,

`description` text,

PRIMARY KEY(`category_id`)

) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=gbk;

INSERT INTO `oss_category` VALUES (‘1’, ‘-1’, ‘1’, ‘0’, ‘root’, ‘root’, ‘root’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘2’, ‘1’, ‘2’, ‘0’, ‘<input type=’checkbox’ name=’product_type1’id=’product_type1’/><b>product_type1</b>’, ‘product_type1’, ‘product_type1’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘3’, ‘1’, ‘2’, ‘0’, ‘<input type=’checkbox’ name=’product_type2′ id=’product_type2’class=’treeCheckBox’/><b>product_typ’, ‘product_type2’, ‘product_type2’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘4’, ‘3’, ‘3’, ‘0’, ‘<input type=’checkbox’ name=’product_type21’id=’product_type21’/><b>product_type21</b>’, ‘product_type21’, ‘product_type21’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘5’, ‘2’, ‘3’, ‘0’, ‘<input type=’checkbox’ name=’product_type11’id=’product_type11’/><b>product_type11</b>’, ‘product_type11’, ‘product_type11’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘6’, ‘5’, ‘4’, ‘1’, ‘<input type=’checkbox’ name=’product_type111’id=’product_type111’/><b>product_type111</b>’, ‘product_type111’, ‘product_type111’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘7’, ‘3’, ‘2’, ‘0’, ‘<input type=’checkbox’ name=’product_type22′ id=’product_type22’/><b>product_type22</b>’, ‘product_type22’, ‘product_type22’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘8’, ‘2’, ‘3’, ‘0’, ‘<input type=’checkbox’ name=’product_type12’id=’product_type12’/><b>product_type12</b>’, ‘product_type12’, ‘product_type12’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (‘9’, ‘4’, ‘4’, ‘1’, ‘<input type=’checkbox’ name=’product_type211’id=’product_type211’/><b>product_type211</b>’, ‘product_type211’, ‘product_type211’, ‘product_type’, null, ‘1’, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’10’, ‘7’, ‘4’, ‘1’, ‘<input type=’checkbox’ name=’product_type221’id=’product_type221’/><b>product_type221</b>’, ‘product_type221’, ‘product_type221’, ‘product_type’, null, ‘1’, null, null, null, null, null);

2、下拉框展现树形结构策略

编辑段落

为了保证跨浏览器的兼容性,不采用任何控件来实现在下拉框中展现树形结构,而采用最为原始的拼凑出如下形式的select 框

<select>

<option value=”1″>1</option>

<option value=”11″>&nbsp;&nbsp;|-11</option>

<option value=”12″>&nbsp;&nbsp;|-12</option>

<option value=”2″>2</option>

<option value=”21″>&nbsp;&nbsp;|-21</option>

<option value=”22″>&nbsp;&nbsp;|-22</option>

</select>

也即用&nbsp;来实现缩进来达到树形结构的效果,在实现上只需要在程序中拼凑出这样的字符串到界面展现即可。

在Struts2中,采用标签展现结果时候,注意property的选项escape=”false”,让Struts2不要对字符串进行转义。

<s:property value=”#request.selectResult” escape=”false”/>

3、代码实现

编辑段落

代码基本上沿用了原来的树形结构的代码,由于原来与OssCategory及ossCategoryDAO存在冲突,拷贝OssCategory.hbm.xml为Tree.hbm.xml。

3.1、Model层

编辑段落

package com.mobilesoft.framework.tree.model;

import java.util.Date;

import java.util.List;

import java.util.Set;

import com.mobilesoft.framework.common.model.BaseObject;

/**

* OssCategory entity.

*

* @author MyEclipse Persistence Tools

*/

public class Tree extends BaseObject

implements java.io.Serializable {

// Fields

private Integer categoryId;

private Integer parentId;

private Short level;

private Byte isLeaf;

private String categoryTitle;

private String categoryName;

private String categoryCode;

private String categoryType;

private String image;

private String status;

private String creator;

private Date createDate;

private String modifyUser;

private Date modifyDate;

private String description;

private Tree[] childCategories;

// Constructors

/** default constructor */

public Tree() {

}

/** full constructor */

public Tree(Integer parentId, Short level, Byte isLeaf,

String categoryTitle, String categoryName, String categoryCode,

String categoryType, String image, String status, String creator,

Date createDate, String modifyUser, Date modifyDate,

String description) {

this.parentId = parentId;

this.level = level;

this.isLeaf = isLeaf;

this.categoryTitle = categoryTitle;

this.categoryName = categoryName;

this.categoryCode = categoryCode;

this.categoryType = categoryType;

this.image = image;

this.status = status;

this.creator = creator;

this.createDate = createDate;

this.modifyUser = modifyUser;

this.modifyDate = modifyDate;

this.description = description;

}

// Property accessors

public Integer getCategoryId() {

return this.categoryId;

}

public void setCategoryId(Integer categoryId) {

this.categoryId = categoryId;

}

public Integer getParentId() {

return this.parentId;

}

public void setParentId(Integer parentId) {

this.parentId = parentId;

}

public Short getLevel() {

return this.level;

}

public void setLevel(Short level) {

this.level = level;

}

public Byte getIsLeaf() {

return this.isLeaf;

}

public void setIsLeaf(Byte isLeaf) {

this.isLeaf = isLeaf;

}

public String getCategoryTitle() {

return this.categoryTitle;

}

public void setCategoryTitle(String categoryTitle) {

this.categoryTitle = categoryTitle;

}

public String getCategoryName() {

return this.categoryName;

}

public void setCategoryName(String categoryName) {

this.categoryName = categoryName;

}

public String getCategoryCode() {

return this.categoryCode;

}

public void setCategoryCode(String categoryCode) {

this.categoryCode = categoryCode;

}

public String getCategoryType() {

return this.categoryType;

}

public void setCategoryType(String categoryType) {

this.categoryType = categoryType;

}

public String getImage() {

return this.image;

}

public void setImage(String image) {

this.image = image;

}

public String getStatus() {

return this.status;

}

public void setStatus(String status) {

this.status = status;

}

public String getCreator() {

return this.creator;

}

public void setCreator(String creator) {

this.creator = creator;

}

public Date getCreateDate() {

return this.createDate;

}

public void setCreateDate(Date createDate) {

this.createDate = createDate;

}

public String getModifyUser() {

return this.modifyUser;

}

public void setModifyUser(String modifyUser) {

this.modifyUser = modifyUser;

}

public Date getModifyDate() {

return this.modifyDate;

}

public void setModifyDate(Date modifyDate) {

this.modifyDate = modifyDate;

}

public String getDescription() {

return this.description;

}

public void setDescription(String description) {

this.description = description;

}

public Tree[] getChildCategories() {

return childCategories;

}

public void setChildCategories(Tree[] childCategories) {

this.childCategories = childCategories;

}

}

3.2、DAO层

编辑段落

package com.mobilesoft.framework.tree.dao.hibernate;

import org.apache.log4j.Logger;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.hibernate.LockMode;

import org.springframework.context.ApplicationContext;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.mobilesoft.framework.tree.model.Tree;

/**

* A data access object (DAO) providing persistence and search support for

* Tree entities. Transaction control of the save(), update() and

* delete() operations can directly support Spring container-managed

* transactions or they can be augmented to handle user-managed Spring

* transactions. Each of these methods provides additional information for how

* to configure it for the desired type of transaction control.

*

* @see com.mobilesoft.esales.model.Tree

* @author MyEclipse Persistence Tools

*/

public class TreeDAO extends HibernateDaoSupport {

/**

* Logger for this class

*/

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

private static final Log log = LogFactory.getLog(TreeDAO.class);

// property constants

public static final String PARENT_ID = “parentId”;

public static final String LEVEL = “level”;

public static final String IS_LEAF = “isLeaf”;

public static final String tree_TITLE = “categoryTitle”;

public static final String tree_NAME = “categoryName”;

public static final String tree_CODE = “categoryCode”;

public static final String tree_TYPE = “categoryType”;

public static final String IMAGE = “image”;

public static final String STATUS = “status”;

public static final String CREATOR = “creator”;

public static final String MODIFY_USER = “modifyUser”;

public static final String DESCRIPTION = “description”;

public String delimiter;

protected void initDao() {

// do nothing

}

/*

省略掉其他方法

*/

public static TreeDAO getFromApplicationContext(

ApplicationContext ctx) {

return (TreeDAO) ctx.getBean(“treeDAO”);

}

public Tree[] getAlltree() {

ArrayList<Tree> resultList=new ArrayList<Tree>();

String queryString=”from Tree as tree where tree.parentId=1″;

List<Tree> queryList =getHibernateTemplate().find(queryString);

Iterator iterator=queryList.iterator();

while(iterator.hasNext()){

Tree tree=(Tree)iterator.next();

Tree[] childrenArray=getChildCategoriesById(tree.getCategoryId());

logger.debug(“getAlltree() – Tree[] childrenList=” + childrenArray.length);

tree.setChildCategories(childrenArray);

resultList.add(tree);

}

Tree[] resultArray=(Tree[])resultList.toArray(new Tree[resultList.size()]);

return resultArray;

}

public String getChildrenByType(String type,boolean includeRoot,String delimiter ){

this.delimiter=delimiter;

StringBuffer resultBuffer=new StringBuffer();

resultBuffer.append(“<select>”);

StringBuffer queryBuffer=new StringBuffer(“from Tree as tree where tree.categoryType='”);

queryBuffer.append(type);

queryBuffer.append(“‘ order by tree.categoryId asc “);

logger.fatal(queryBuffer);

List<Tree> queryList =getHibernateTemplate().find(queryBuffer.toString());

if(queryList.size()>0){

Tree[] treeArray=(Tree[])queryList.toArray(new Tree[queryList.size()]);

int start;

if(includeRoot)

start=0;

else

start=1;

resultBuffer.append(“<option value='”);

resultBuffer.append(treeArray[start].getCategoryId());

resultBuffer.append(“‘>”);

resultBuffer.append(treeArray[start].getCategoryName());

resultBuffer.append(“</option>”);

String childrenStr=getChildCategories4Select(treeArray[start].getCategoryId(),treeArray[start].getLevel());

logger.debug(“getChildrenByType( String ) – start String=” + start +”,level is :”+treeArray[start].getLevel());

resultBuffer.append(childrenStr);

logger.fatal(“getChildrenByType(String) – StringBuffer resultBuffer11111=” + resultBuffer);

resultBuffer.append(“</select>”);

return resultBuffer.toString();

}else{

return “</select>”;

}

}

public Tree[] getChildCategoriesById(int treeId) {

String queryString=”from Tree as tree where tree.parentId=”+treeId;

List<Tree> queryList =getHibernateTemplate().find(queryString);

logger.debug(“getChildCategoriesById(int) – List<Tree> queryList=” + queryList.size());

ArrayList<Tree> resultList=new ArrayList<Tree>();

Iterator iterator=queryList.iterator();

while(iterator.hasNext()){

Tree tree=(Tree)iterator.next();

Tree[] childrenList=getChildCategoriesById(tree.getCategoryId());

logger.debug(“getChildCategoriesById(int) – Tree[] childrenList=” + childrenList+”parentid is “+treeId);

tree.setChildCategories(childrenList);

resultList.add(tree);

}

Tree[] resultArray=(Tree[])resultList.toArray(new Tree[resultList.size()]);

return resultArray;

}

/*

* @return 要拼凑出如下形式的select 框

* <select>

* <option value=”1″>1</option>

* <option value=”11″>&nbsp;&nbsp;|-11</option>

* <option value=”12″>&nbsp;&nbsp;|-12</option>

* <option value=”2″>2</option>

* <option value=”21″>&nbsp;&nbsp;|-21</option>

* <option value=”22″>&nbsp;&nbsp;|-22</option>

* </select>

*

* @param treeId节点的id号

* @param rootLevel所在类别子树根节点在整棵树中的层级

*/

public String getChildCategories4Select(int treeId,int rootLevel) {

StringBuffer childBuffer=new StringBuffer();

String queryString=”from Tree as tree where tree.parentId=”+treeId;

List<Tree> queryList =getHibernateTemplate().find(queryString);

logger.debug(“getChildCategories4Select(int,int) – List<Tree> queryList=” + queryList.size());

ArrayList<Tree> resultList=new ArrayList<Tree>();

Iterator iterator=queryList.iterator();

while(iterator.hasNext()){

Tree tree=(Tree)iterator.next();

int childLevel=tree.getLevel();

childBuffer.append(“<option value='”);

childBuffer.append(tree.getCategoryId());

childBuffer.append(” ‘>”);

for(int i=0;i<=childLevel-rootLevel;i++)

childBuffer.append(“&nbsp;&nbsp;”);

childBuffer.append(this.delimiter+tree.getCategoryName());

childBuffer.append(“</option>”);

String childrenStr=getChildCategories4Select(tree.getCategoryId(),rootLevel);

childBuffer.append(childrenStr);

logger.debug(“getChildCategories4Select(int,int) – childBuffer=” + childBuffer);

}

return childBuffer.toString();

}

}

3.3、Service层

编辑段落

TreeService.java

package com.mobilesoft.framework.tree.service;

import java.util.List;

import java.util.Set;

import com.mobilesoft.framework.tree.model.Tree;

public interface TreeService {

/**

* @return 获取下级子节点

*/

//public Set getChildren(int rootId);

/**

* @return 递归指定级别的所有子节点

*

*/

//public Set getChildrenByLevel(int rootId,int level);

/**

* 用于获取数据字典中指定类别的所有类型的节点,

* 类别表节点的树形结构

* |– ROOT 根类别(-1)

*  |–类别类型:套餐类别 bundle_type

*   |–3 套餐类别1

*   |–4 套餐类别2

* |–类别类型:产品类别 product_type

*   |–产品大类1

*    |–产品大类11

*     |—产品大类别111

*     |—产品大类别112

*    |–产品大类12

* @return 获取指定类型的所有子节点

* 在实现上,对于诸如”产品类别“等下拉框可能为树形结构的元素,

* 为了避免由于浏览器兼容性问题,在形式上是树形结构,

* 实际上就是从字典表(目前树形结构仍然采用oss_category作为字典表)动态取出数据,然后拼凑成字符串形式展现,例如:

* <select>

* <option value=”1″>1</option>

*<option value=”11″>&nbsp;&nbsp;|-11</option>

*<option value=”12″>&nbsp;&nbsp;|-12</option>

*<option value=”2″>2</option>

*<option value=”21″>&nbsp;&nbsp;|-21</option>

*<option value=”22″>&nbsp;&nbsp;|-22</option>

*</select>

* @param type:节点的类型

* @param levle:节点相对于所属类型的节点的级别 -1表示获取所属类型的根节点的所有级别的子节点

*/

public String getChildrenByType( String type);

public String getChildrenByType( String type,boolean includeRoot);

public String getChildrenByType( String type,boolean includeRoot,String delimiter);

/**

* @return 获取指定根节点的所有子节点

*/

public Tree[]getAllTree();

}

TreeServiceImpl.java

package com.mobilesoft.framework.tree.service.impl;

import java.util.List;

import java.util.Set;

import com.mobilesoft.framework.tree.dao.hibernate.TreeDAO;

import com.mobilesoft.framework.tree.model.Tree;

import com.mobilesoft.framework.tree.service.TreeService;

public class TreeServiceImpl implements TreeService {

TreeDAO treeDAO;

String delimiter;

public Set getChildren(int rootId) {

// TODO Auto-generated method stub

return null;

}

public Set getLevelChildren(int rootId, int level) {

// TODO Auto-generated method stub

return null;

}

public String getChildrenByType( String type){

return treeDAO.getChildrenByType(type,true,this.delimiter);

}

public String getChildrenByType( String type,boolean includeRoot){

return treeDAO.getChildrenByType(type,includeRoot,this.delimiter);

}

public String getChildrenByType( String type,boolean includeRoot,String delimiter){

return treeDAO.getChildrenByType(type,includeRoot,delimiter);

}

public Tree[]getAllTree(){

return treeDAO.getAlltree();

}

public TreeDAO gettreeDAO() {

return treeDAO;

}

public void settreeDAO(TreeDAO treeDAO) {

this.treeDAO = treeDAO;

}

public String getDelimiter() {

return delimiter;

}

public void setDelimiter(String delimiter) {

this.delimiter = delimiter;

}

}

3.4、调用方法

编辑段落

以Action中为例:

private TreeService treeService;

public String getChildrenByType(){

String selectResult=treeService.getChildrenByType(“product_type”);

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

returnSUCCESS;

}

public TreeService getTreeService() {

returntreeService;

}

publicvoid setTreeService(TreeService treeService) {

this.treeService = treeService;

}

注意:getChildrenByType方法有三个重载(overload)方法,

public String getChildrenByType( String type);

public String getChildrenByType( String type,boolean includeRoot);

public String getChildrenByType( String type,boolean includeRoot,String delimiter);

但实际上最后还是调用的是getChildrenByType( String type,boolean includeRoot,String delimiter),

参数说明:

type: 类别的类型,例如product_type

includeRoot:是否包含所在
类别的根节点,例如查询product_type是否,是否需要导出所在类别的root节点

delimiter:在下拉框选择时候,分隔符类型,缺省为“|-”,可以在applicationContext-service.xml中修改或调用时候传递参数。

3.5、配置文件

编辑段落

applicationContext-resources.xml

<property name=”mappingResources”>

<list>

<value>com/mobilesoft/framework/tree/model/Tree.hbm.xml</value>

</list>

</property>

applicationContext-dao.xml

<bean id=”treeDAO”

class=”com.mobilesoft.framework.tree.dao.hibernate.TreeDAO”>

<property name=”sessionFactory”>

<ref bean=”sessionFactory” />

</property>

</bean>

applicationContext-service.xml

<bean id=”treeService”

class=”com.mobilesoft.framework.tree.service.impl.TreeServiceImpl”>

<property name=”treeDAO”>

<ref bean=”treeDAO” />

</property>

<property name=”delimiter” value=”|-“/>

</bean>

action-servlet.xml

<bean id=”treeAction” scope=”prototype”

class=”com.mobilesoft.esales.webapp.action.TreeAction”>

<property name=”treeService”>

<ref bean=”treeService” />

</property>

</bean>

struts.xml

<action name=”treeAction” method=”execute” class=”treeAction”>

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

</action>

 

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

Struts2 OptionTransferSelect标签使用

Posted in Uncategorized by chuanliang on 2008/04/02

 

有时候需要在两个Select框中双向挪动数据,此种在Struts2叫OptionTransferSelect,效果如下:

optiontransferselect

1. Action层

TransferSelect.java

package com.mobilesoft.esales.webapp.action;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

import java.util.Map;

import java.util.Set;

import org.apache.log4j.Logger;

public class TransferSelect extends BaseAction {

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

    String[] leftRoles;

    String[] rightRoles;

public String execute(){   

return SUCCESS;

    }

public String transferSelect(){

       ArrayList<Roles> leftList=new ArrayList<Roles> ();     

       Roles leftRoles1=new Roles();

       leftRoles1.setRoleId(1);

       leftRoles1.setRoleName(“角色1”);

       Roles leftRoles2=new Roles();

       leftRoles2.setRoleId(2);

       leftRoles2.setRoleName(“角色2”);

       Roles leftRoles3=new Roles();

       leftRoles3.setRoleId(3);

       leftRoles3.setRoleName(“角色3”);

       Roles leftRoles4=new Roles();

       leftRoles4.setRoleId(4);

       leftRoles4.setRoleName(“角色4”);

       leftList.add(leftRoles1);

       leftList.add(leftRoles2);

       leftList.add(leftRoles3);

       leftList.add(leftRoles4);

       ArrayList<Roles>  rightList=new ArrayList();     

       Roles rightRoles1=new Roles();

       rightRoles1.setRoleId(1);

       rightRoles1.setRoleName(“角色5”);

       Roles rightRoles2=new Roles();

       rightRoles2.setRoleId(2);

       rightRoles2.setRoleName(“角色6”);

       Roles rightRoles3=new Roles();

       rightRoles3.setRoleId(3);

       rightRoles3.setRoleName(“角色7”);

       Roles rightRoles4=new Roles();

       rightRoles4.setRoleId(4);

       rightRoles4.setRoleName(“角色8”);

       rightList.add(rightRoles1);

       rightList.add(rightRoles2);

       rightList.add(rightRoles3);

       rightList.add(rightRoles4);    

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

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

return SUCCESS;

    }

public String transferSelectPost(){

       Map map=getRequest().getParameterMap();

       Set set=map.entrySet();

       Iterator iterator=set.iterator();

while(iterator.hasNext()){

           Map.Entry mapEntry=(Map.Entry)iterator.next();

if(mapEntry.getValue() instanceof String[]){

              String[] selectValues=(String[])mapEntry.getValue();

for(int i=0;i<selectValues.length;i++){

logger.fatal(“For map test,The key is: “+mapEntry.getKey()+”,value is :”+selectValues[i]); 

              }

           }

       }

for(int i=0;i<leftRoles.length;i++){

logger.fatal(“For Array test,The leftRoles roleName is: “+leftRoles[i]); 

       }

for(int i=0;i<rightRoles.length;i++){

logger.fatal(“For Array test,The rightRoles roleName is: “+rightRoles[i]);  

       }     

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

return SUCCESS;

    }

public void setLeftRoles(String[] leftRoles) {

this.leftRoles = leftRoles;

    }

public void setRightRoles(String[] rightRoles) {

this.rightRoles = rightRoles;

    }  

}

Roles.java

package com.mobilesoft.esales.webapp.action;

public class Roles {
    private Integer roleId;
    private String roleName;
    public Roles(){
    }
    public Integer getRoleId() {
        return roleId;
    }
    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }
    public String getRoleName() {
        return roleName;
    }
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
}

2. Web层

transferselect.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 – OptionTransferSelect</title>

<s:head theme=”ajax”/>

</head>

<body>

<h2>用户角色授权</h2>

<s:form action=”transferSelectPost” name=”form1″ >

<s:optiontransferselect

label=”测试-Action数据”

name=”leftRoles”

            leftTitle=”用户已授权角色”

            list=”#request.leftList”

            listKey=”roleId”

            listValue=”roleName”

multiple=”true”

            headerKey=”headerKey”

            headerValue=”— 请选择用户
角色 —”

            emptyOption=”false”

            allowUpDownOnLeft=”false”

            cssStyle=”width:200px;height:300px;”

            rightTitle=”用户未授权的角色”

            doubleList=”#request.rightList”

            doubleListKey=”roleId”

            doubleListValue=”roleName”

            doubleName=”rightRoles”

            doubleHeaderKey=”doubleHeaderKey”

            doubleHeaderValue=”— 请选择用户角色 —”

            doubleEmptyOption=”false”

            doubleMultiple=”true”

            allowUpDownOnRight=”false”

            doubleCssStyle=”width:200px;height:300px;”

/>

<s:submit align=”left”/>

</s:form>

</body>

</html>

transferselectpost.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 – OptionTransferSelect</title>

<s:head />

</head>

<body>

<h2>选择结果</h2>

<s:form action=”transferSelect” name=”form1″ >

<s:iterator value=”#request.map” status=”mystatus”>

<tr>

<td>

<s:property value=”key” />:<s:property value=”value” />,             

</td>

</tr>

</s:iterator>

<s:submit align=”left”/>

</s:form>

</body>

</html>

3. struts.xml

<action name=”transferSelect” method=”transferSelect” class=”com.mobilesoft.esales.webapp.action.TransferSelect”>

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

</action>

<action name=”transferSelectPost” method=”transferSelectPost” class=”com.mobilesoft.esales.webapp.action.TransferSelect”>

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

</action>

 

Struts2 DoubleSelect标签的使用

Posted in Uncategorized by chuanliang on 2008/04/02

Struts2本身提供了级联下拉选择框标签,这对简化诸如省份/城市这样的典型需求的操作是大有好处的,同时也有利于架构的统一,没有必要引入一大堆的javascript代码,使用方法如下:

1、Action层

DoubleListAction.java

package com.mobilesoft.esales.webapp.action;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.mobilesoft.framework.tree.model.FileWrapper;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;

public class DoubleListAction extends BaseAction {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(DoubleListAction.class);

    public String execute() {
        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);
        getRequest().setAttribute("map", map);
        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);
        getRequest().setAttribute("map", map);
        return SUCCESS;
    }
    public String doubleSelectPost(){

        Map map=getRequest().getParameterMap();
        Set set=map.entrySet();
        Iterator iterator=set.iterator();
        while(iterator.hasNext()){
            Map.Entry mapEntry=(Map.Entry)iterator.next();
            logger.fatal("The key is: "+mapEntry.getKey()+",value is :"+mapEntry.getValue());
        }
        getRequest().setAttribute("map", map);
        return SUCCESS;
    }

}

2、页面

doubleslect.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 - <s:doubeselect/ ></title>
    <s:head />
</head>
<body>
<h2>Doubleselect Tag Example</h2>
<s:form action="doubleSelectPost" name="form1">
Doubleselect演示1
<s:set name="technology"
     value="#{
         'Java': {'Spring', 'Hibernate', 'Struts 2'},
         '.Net': {'Linq', ' ASP.NET 2.0'},
         'Database': {'Oracle', 'SQL Server', 'DB2', 'MySQL'}
         }" />
<s:doubleselect label="技术选择"
list="#technology.keySet()"
name="doubleselect1"
doubleName="techdetail"
formName="form1"
doubleList="#technology[top]" />
<br/><br/>
Doubleselect演示数据演示2:
<s:set name="mymap" value="#request.map"/>
<s:doubleselect list="#request.map.keySet()"
                doubleName="mymap1"
                name="doubleselect2"
                formName="form1"
                doubleList="#request.map[top]"
                label="Map Test" />

<s:submit/>
</s:form>
</body>
</html>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } doubleselectpost.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 - <s:doubeselect/ ></title>
    <s:head />
</head>
<body>
<h2>Doubleselect Tag Example</h2>
<s:form name="form1">
<table border="0" cellspacing="0" cellpadding="1">
<tr>
  <th>参数值</th>
</tr>
<s:iterator value="#request.map.keySet()" status="keys">
    <tr>
        <td><s:property/></td>
    </tr>
</s:iterator>
</s:form>
</body>
</html>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

3、struts.xml配置文件

<action name="doubleSelectTest" method="doubleSelectTest" class="com.mobilesoft.esales.webapp.action.DoubleListAction">
    <result name="success">test/doubleselect.jsp</result>
</action>
<action name="doubleSelectPost" method="doubleSelectPost" class="com.mobilesoft.esales.webapp.action.DoubleListAction">
    <result name="success">test/doubleselectpost.jsp</result>
</action>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

4、级联下拉框间的换行<br >问题

缺省情况下,两个级联下拉框间是换行的,Struts2采用Freemarker来做模板,可以通过修改模板来定制标签缺省的树形。参看“Struts2中Datetimepicker控件的中文问题”中的方法,也即:

  • 修改struts.mxl,增加如下内容。

<constant name=”struts.serve.static” value=”false” />

<constant name=”struts.ui.templateDir” value=”struts” />

注意在struts2-core-2.0.11.jar/org/apache/struts2/default.properties中有几个与Freemarker对应的配置参数:

struts.ui.theme=xhtml
struts.ui.templateDir=template
#sets the default template type. Either ftl, vm, or jsp
struts.ui.templateSuffix=ftl

  • 覆盖缺省的静态文件

在resource目录(与WEB-INF同级或WEB-INF下)创建struts目录,并:

解压struts2-core-2.0.11.jar:/org/apache/struts2/static/ to /struts/

解压struts2-core-2.0.11.jar:/template/simple/ to /struts/simple/

解压struts2-core-2.0.11.jar:/template/xhtml/ to /struts/xhtml/

解压struts2-core-2.0.11.jar:/template/css_xhtml/ to /struts/css_xhtml/

解压struts2-core-2.0.11.jar:/template/ajax/ to /struts/ajax/

解压struts2-core-2.0.11.jar:/template/archive/ to /struts/archive

最后的目录结构如下:

struts/
– ajax/
– archive/
– css_xhtml/
– dojo/
– nls/
– src/
– animation/
– …
– xml/
– struts/
– widget/
– widgets/
– niftycorners/
– simple/
– xhtml/

  • 修改doubleselect的模板doubleselect.ftl

修改struts/simple/doubleselect.ftl去除其中的<br />

Technorati 标签: ,,,

Tagged with: , , ,

Struts2 Tree标签的使用

Posted in Uncategorized by chuanliang on 2008/04/01

  在项目中有很多地方使用了树形结构,主要有几个地方:素材和软件等软件资源表,人员权限的权限资源表、人员权限的角色,套餐及产品包,现以类别表为例,说明树形结构的数据库存储、利用Struts2 中dojo的Tree标签来完成前端展现。

1、 数据库结构

CREATE TABLE `oss_category` (
  `category_id` int(11) NOT NULL auto_increment,
  `parent_id` int(11) default '-1',
  `level` smallint(6) default NULL ,
  `is_leaf` tinyint(1) default NULL,
  `category_title` varchar(100) default NULL,
  `category_name` varchar(100) default NULL,
  `category_code` varchar(100) default NULL,
  `category_type` varchar(30) default NULL,
  `image` varchar(255) default NULL,
  `status` varchar(20) default NULL,
  `creator` varchar(50) default NULL,
  `create_date` datetime default NULL,
  `modify_user` varchar(50) default NULL,
  `modify_date` datetime default NULL,
  `description` text,
  PRIMARY KEY  (`category_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=gbk;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

测试数据:

INSERT INTO `oss_category` VALUES ('1', '-1', '1', '0', 'root', 'root', 'root', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('2', '1', '2', '0', '<input type='checkbox' name='product_type1'  id='product_type1'  /><b>product_type1</b>', 'product_type1', 'product_type1', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('3', '1', '2', '0', '<input type='checkbox' name='product_type2' id='product_type2'  class='treeCheckBox'/><b>product_typ', 'product_type2', 'product_type2', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('4', '3', '3', '0', '<input type='checkbox' name='product_type21'  id='product_type21'  /><b>product_type21</b>', 'product_type21', 'product_type21', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('5', '2', '3', '0', '<input type='checkbox' name='product_type11'  id='product_type11'  /><b>product_type11</b>', 'product_type11', 'product_type11', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('6', '5', '4', '1', '<input type='checkbox' name='product_type111'  id='product_type111'  /><b>product_type111</b>', 'product_type111', 'product_type111', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('7', '3', '2', '0', '<input type='checkbox' name='product_type22'  id='product_type22'  /><b>product_type22</b>', 'product_type22', 'product_type22', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('8', '2', '3', '0', '<input type='checkbox' name='product_type12'  id='product_type12'  /><b>product_type12</b>', 'product_type12', 'product_type12', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('9', '4', '4', '1', '<input type='checkbox' name='product_type211'  id='product_type211'  /><b>product_type211</b>', 'product_type211', 'product_type211', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('10', '7', '4', '1', '<input type='checkbox' name='product_type221'  id='product_type221'  /><b>product_type221</b>', 'product_type221', 'product_type221', 'product_type', null, '1', null, null, null, null, null);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

注意:

  • 为了支持在树形结构的节点前有checkbox框,需要在树形结构的title(对应s:tree中的nodeTitleProperty)前增加checkbox树形,因此在数据库中category_title字段的格式采用如上的形式,例如:

<input type=’checkbox’ name=’product_type1′  id=’product_type1′  /><b>product_type1</b>

  • 对于有序的树形结构,为了标识left和right,可以增加ordering字段,或者采用类似嵌套树的结构,例如
CREATE TABLE nested_category (
 category_id INT AUTO_INCREMENT PRIMARY KEY,
 name VARCHAR(20) NOT NULL,
 lft INT NOT NULL,
 rgt INT NOT NULL
);
但采用嵌套树的方式,在变动一个节点时候需要对相关的节点进行更新,不是很适合。但效率较高。可以参看:
http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
http://www.sitepoint.com/print/hierarchical-data-database
  • mysql对树形结构没有原生的支持,Oracle和DB2可以支持,Oracle通过CONNECT BY,DB2通过With方式。

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

2、Model层

OssCategory.java

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

直接用myeclipse生成后,增加了如下内容:

    private OssCategory[] childCategories;
    private OssCategory parentCategory;

    public OssCategory[] getChildCategories() {
        return childCategories;
    }

    public void setChildCategories(OssCategory[] childCategories) {
        this.childCategories = childCategories;
    }

    public OssCategory getParentCategory() {
        return parentCategory;
    }

    public void setParentCategory(OssCategory parentCategory) {
        this.parentCategory = parentCategory;
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

3、DAO层

OssCategoryDAO.java

直接用myeclipse生成,没有调优,增加如下内容:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

    public OssCategory[] getAllCategory() {
        ArrayList<OssCategory> resultList=new ArrayList<OssCategory>();
        String queryString="from OssCategory as category where category.parentId=1";
        List<OssCategory> queryList =getHibernateTemplate().find(queryString);
        Iterator iterator=queryList.iterator();
        while(iterator.hasNext()){
            OssCategory ossCategory=(OssCategory)iterator.next();
            OssCategory[] childrenArray=getChildCategoriesById(ossCategory.getCategoryId());
            logger.debug("getAllCategory() - OssCategory[] childrenList=" + childrenArray.length);
            ossCategory.setChildCategories(childrenArray);
            resultList.add(ossCategory);

        }

        OssCategory[] resultArray=(OssCategory[])resultList.toArray(new OssCategory[resultList.size()]);
        return resultArray;

    }

    public OssCategory[] getChildCategoriesById(int categoryId) {
        String queryString="from OssCategory as category where category.parentId="+categoryId;
        List<OssCategory> queryList =getHibernateTemplate().find(queryString);
        logger.debug("getChildCategoriesById(int) - List<OssCategory> queryList=" + queryList.size());
        ArrayList<OssCategory> resultList=new ArrayList<OssCategory>();
        Iterator iterator=queryList.iterator();
        while(iterator.hasNext()){
            OssCategory ossCategory=(OssCategory)iterator.next();
            OssCategory[] childrenList=getChildCategoriesById(ossCategory.getCategoryId());
            logger.debug("getChildCategoriesById(int) - OssCategory[] childrenList=" + childrenList+"parentid is "+categoryId); //$NON-NLS-1$
            ossCategory.setChildCategories(childrenList);
            resultList.add(ossCategory);

        }
        OssCategory[] resultArray=(OssCategory[])resultList.toArray(new OssCategory[resultList.size()]);
        return resultArray;
    }
注意:
  •  getAllCategory和getChildCategoriesById采用了递归调用方式,应该可以一次性取出指定节点的所有子节点到结果集合List,然后对List在内存中进行遍历,但比较麻烦,先采用此种方式。
  •  struts2的tree应该支持List,而不一定要采用数组方式,只不过没有测试
 

4、Service层

TreeService.java

有些方法尚未实现

package com.mobilesoft.framework.tree.service;

import java.util.List;
import java.util.Set;

import com.mobilesoft.framework.tree.model.OssCategory;

public interface TreeService {


    /**
     * @return 获取下级子节点
     */
  //  public Set getChildren(int rootId);

    /**
     * @return 递归指定级别的所有子节点
     *      
     */
  //  public Set getChildrenByLevel(int rootId,int level);


    /**
     * @return 获取指定类型的所有子节点
     * @param type:节点的类型
     * @param levle:节点相对于所属类型的节点的级别 -1表示获取所属类型的根节点的所有级别的子节点
     */
 //   public Set getChildrenByType( String type,int level);

    /**
     * @return 获取指定根节点的所有子节点
     */
    public OssCategory[]  getAllCategory();

}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

TreeServiceImpl.java

 

package com.mobilesoft.framework.tree.service.impl;

import java.util.List;
import java.util.Set;

import com.mobilesoft.framework.tree.dao.hibernate.OssCategoryDAO;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;

public class TreeServiceImpl implements TreeService {
    OssCategoryDAO ossCategoryDAO;
    public Set getChildren(int rootId) {
        // TODO Auto-generated method stub
        return null;
    }

    public Set getLevelChildren(int rootId, int level) {
        // TODO Auto-generated method stub
        return null;
    }

    public OssCategory[]   getAllCategory(){
        return ossCategoryDAO.getAllCategory();
    }
    public OssCategoryDAO getOssCategoryDAO() {
        return ossCategoryDAO;
    }

    public void setOssCategoryDAO(OssCategoryDAO ossCategoryDAO) {
        this.ossCategoryDAO = ossCategoryDAO;
    }

}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

5、Action层

package com.mobilesoft.esales.webapp.action;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.mobilesoft.framework.tree.model.FileWrapper;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;

public class TreeAction extends BaseAction {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(TreeAction.class);

    private OssCategory root;
    private TreeService treeService;
    private FileWrapper fileroot;

    public String execute() {
        root = new OssCategory();
        root.setCategoryId(new Integer(1));
        root.setParentId(-1);
        root.setCategoryTitle("Root");
        root.setCategoryName("Root");
        OssCategory[] childCategories = treeService.getAllCategory();

        root.setChildCategories(childCategories);
        visitTree(childCategories);
        getRequest().setAttribute("root", root);
        // fileroot = new FileWrapper(getSession().getServletContext().getRealPath("/"));
        return SUCCESS;
    }

    public String treeWithCheckbox() {
        Map map = getRequest().getParameterMap();
        logger.fatal("the tree map");
        Set set = map.entrySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();

            logger.fatal("the tree key is: " + entry.getKey() + " ,and value is: "
                    + entry.getValue());
        }

        return SUCCESS;
    }

    public TreeService getTreeService() {
        return treeService;
    }

    public void setTreeService(TreeService treeService) {
        this.treeService = treeService;
    }


    public OssCategory getRoot() {
        return root;
    }

    public void setRoot(OssCategory root) {
        this.root = root;
    }

    public void visitTree(OssCategory[] tree) {

        for (int i = 0; i < tree.length; i++) {
            OssCategory node = (OssCategory) tree[i];
            logger.fatal("visitTree:The treenode parentid is :"
                    + node.getParentId() + " ,treenode id is "
                    + node.getCategoryId() + ",and the treenode name is "
                    + node.getCategoryName());
            visitTree(node.getChildCategories());
        }

    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

6、页面

treetest.jsp

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page language="java" errorPage="/error.jsp" pageEncoding="GBK" contentType="text/html;charset=GBK" %>
<html>
<head>
    <title>tag list</title>
    <%@ include file="/common/meta.jsp" %>

<style>
    .treeCheckBox {
        height: 14px;
        margin: 0px;
        padding: 0px;
        border: 1px;
        vertical-align: middle;
    }
</style>
<s:head theme="ajax" debug="true" />
</head>
<body >


<script type="text/javascript">
function treeNodeSelected(arg) {
    //alert("id["+arg.source.widgetId+"], name["+ arg.source.title+ "] selected");
}

function treeNodeExpanded(arg) {
    //alert("id["+arg.source.widgetId+"], name["+ arg.source.title+ "] expanded");
}

function treeNodeCollapsed(arg) {
    //alert("id["+arg.source.widgetId+"], name["+ arg.source.title+ "] collapsed");
}

dojo.addOnLoad(function() {
    var t = dojo.widget.byId('root');
    dojo.event.topic.subscribe(t.eventNames.expand, treeNodeExpanded);
    dojo.event.topic.subscribe(t.eventNames.collapse, treeNodeCollapsed);

    var s = t.selector;


});

</script>

<s:form name="form1" action="treeWithCheckbox">
<div style="float:left; margin-right: 50px;" >
<s:tree
    theme="ajax"
    id="root"
    rootNode="#request.root"
    childCollectionProperty="childCategories"
    nodeIdProperty="categoryId"
    nodeTitleProperty="categoryTitle"
    treeSelectedTopic="treeSelected">
</s:tree>
</div>

<div style="float:left; margin-right: 50px;">
<s:submit/>
</div>
</s:form>


</body>
</html>
 

注意:

  • 在s:tree中不能使用label标签,使用后树形结构出不来。
  • childCollectionProperty=”childCategories”对应Category.java中的
    private OssCategory[] childCategories;
    
    public OssCategory[] getChildCategories() {
        return childCategories;
    }
    
    public void setChildCategories(OssCategory[] childCategories) {
        this.childCategories = childCategories;
    }
    

    .csharpcode, .csharpcode pre
    {
    font-size: small;
    color: black;
    font-family: consolas, “Courier New”, courier, monospace;
    background-color: #ffffff;
    /*white-space: pre;*/
    }
    .csharpcode pre { margin: 0em; }
    .csharpcode .rem { color: #008000; }
    .csharpcode .kwrd { color: #0000ff; }
    .csharpcode .str { color: #006080; }
    .csharpcode .op { color: #0000c0; }
    .csharpcode .preproc { color: #cc6633; }
    .csharpcode .asp { background-color: #ffff00; }
    .csharpcode .html { color: #800000; }
    .csharpcode .attr { color: #ff0000; }
    .csharpcode .alt
    {
    background-color: #f4f4f4;
    width: 100%;
    margin: 0em;
    }
    .csharpcode .lnum { color: #606060; }

  • nodeIdProperty=”categoryId”和nodeTitleProperty=”categoryTitle” 对应OssCategory[] childCategories中每一个Category的categoryId和categoryTitle

  • rootNode=”#request.root”,采用rootNode=”root”方式好像取不到数据,按理说应该可以通过ognl自动调用getRoot方法得到

  • 结果提交后,根据对应checkbox的name的on属性来判断是否选中,因此在数据库中对节点的命名需要有一定规则,以便方便选择

尚需要完善的地方:

  • 增加对下拉框树形结构实现的支持,以处理项目中大量诸如产品类型、资源类型等类型。

在实现上,为了避免由于浏览器兼容性问题,在形式上是树形结构,实际上就是从字典表(目前树形结构仍然采用oss_category作为字典表)动态取出数据,然后拼凑成字符串形式展现,例如:

<select>
    <option value="1">1</option>
    <option value="11">&nbsp;&nbsp;|-11</option>
    <option value="12">&nbsp;&nbsp;|-12</option>
    <option value="2">2</option>
    <option value="21">&nbsp;&nbsp;|-21</option>
    <option value="22">&nbsp;&nbsp;|-22</option>
</select>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

  • 进一步优化递归算法,一次性从数据库取出需要的数据,在内存中完成树形结构的构造,避免对数据库的查询,在目前情况下,采用递归方式问题也不大。
  • 由于Struts2.0采用dojo来实现其标签,尽管dojo与jquery相比较很重,但由于与Struts2.0较好支持,在架构统一上和使用上还是较为方便的,因此有空需要研究一下dojo库。

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

7、参考资料

http://www.ajaxtree.com/

http://struts.apache.org/2.x/docs/tree.html

http://ait.web.psi.ch/js/dojo/tests/widget/tree/

http://exjava.spaces.live.com/blog/cns!A43FBDAF9245A0BC!199.entry

http://www.stack.be/~roel/blog/archives/the-dojo-tree-control-for-beginners-part-1/

http://www.stack.be/~roel/blog/archives/the-dojo-tree-control-for-beginners-part-2

http://www.codepencil.com/index.php/struts2-dojo-dynamic-tree/

http://dojotoolkit.org/book/dojo-book-0-9/part-2-dijit/advanced-editing-and-display/tree

 

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