出家如初,成佛有余

Struts2中Datetimepicker控件的中文问题

Posted in Uncategorized by chuanliang on 2008/03/30

为了与现有数据库字符集GBK统一,在Struts2中采用GBK作为缺省的编码方式,与此对应的编码相关的配置:

1、JVM中缺省字符集

    或者在环境变量中配置JAVA_OPTS=%JAVA_OPT%;-Dfile.encoding=GBK -Duser.language=zh_CN
    或者在Tomcat的启动脚本中配置JAVA_OPTS=%JAVA_OPT%;-Dfile.encoding=GBK -Duser.language=zh_CN

2、Tomcat的URIEncoding ,处理GET方式的乱码问题

在server.xml中配置:

<Connector port="8080" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true"
               URIEncoding="GBK"

3、Struts2的Locale配置

在Struts.xml中配置
    <constant name=”struts.locale” value=”zh_CN” />
    <constant name=”struts.i18n.encoding” value=”GBK” />

4、采用Spring的过滤器对POST页面编码

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>
        org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>GBK</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

5、页面统一编码为GBK

在common/meta.jsp中

<meta http-equiv=”Cache-Control” content=”no-store”/>
<!– HTTP 1.0 –>
<meta http-equiv=”Pragma” content=”no-cache”/>
<!– Prevents caching at the Proxy Server –>
<meta http-equiv=”Expires” content=”0″/>
<meta http-equiv=”Content-Type” content=”text/html; charset=GBK”/>
<meta name=”generator” content=”Mobile-soft.cn” />
<meta http-equiv=”keywords” content=”mobile,payment,telecommunication,internet”>
<meta http-equiv=”description” content=”mobile-soft”>
同时在各页面中包含meta.jsp页面及设定pageEncoding:

<%@ include file=”/common/meta.jsp” %>

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

6、数据库编码

数据库建库时候字符集编码采用GBK

在applicationContext-resources.xml中,mysql的配置

    <bean id=”dataSource” class=”org.apache.commons.dbcp.BasicDataSource”
        destroy-method=”close”>
        <property name=”driverClassName” value=”com.mysql.jdbc.Driver”/>
        <property name=”url”
            value=”jdbc:mysql://localhost/mysql?useUnicode=true&amp;characterEncoding=gbk”/>
        <property name=”username” value=”root”/>
        <property name=”password” value=””/>
        <property name=”maxActive” value=”100″/>
        <property name=”maxIdle” value=”30″/>
        <property name=”maxWait” value=”1000″/>
        <property name=”defaultAutoCommit” value=”true”/>
        <property name=”removeAbandoned” value=”true”/>
        <property name=”removeAbandonedTimeout” value=”60″/>
        <property name=”validationQuery” value=”SELECT 1″/>
        <property name=”testOnBorrow” value=”true”/>
    </bean>

    采用以上步骤基本上搞定了乱码问题,但在使用Struts2的datetimepicker控件时候出现了乱码问题,Struts2的javascript标签库缺省是采用庞大的dojo库(为何不采用jquery这样清爽的javascript库),因此怀疑是dojo的i18n问题,解决步骤如下:

1、修改struts.serve.static的缺省配置

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

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

在struts2-core-2.0.11.jar/org/apache/struts2/default.properties中,struts.serve.static缺省值为true,也即从struts2-core-2.0.11.jar查找Struts2的静态文件,这参数的命名太晦涩。

### Used by FilterDispatcher
### If true then Struts serves static content from inside its jar.
### If false then the static content must be available at <context_path>/struts
struts.serve.static=true

注意这里的<context_path>/struts实际指的就是jsp页面所在目录。

2、覆盖缺省的静态文件

    在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/dojoRequire.js to /struts/simple/

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

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

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

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

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

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

    注意:以上所说的解压并不是把整个包都解压,只是把需要的内容勇winrar直接拽出来,对于struts2-core-2.0.11.jar包还是保持原状,不要做任何改动,以方便后期的升级。

3、修改js文件的编码方式

    用记事本打开struts/dojo/src/i18n/calendar/nls/zh/gregorian.js并以ASSI格式另存为gregorian.js

    用记事本打开struts/dojo/src/i18n/calendar/nls/zh/gregorianExtras.js并以ASSI格式另存为gregorianExtras.js

    用记事本打开struts/dojo/src/i18n/calendar/nls/zh-cn/gregorian.js并以ASSI格式另存为gregorian.js

  以上几个文件,原来的格式是UTF-8的

4、页面实例

<%@ 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” %>

 <s:head/>

</head>
<body>
 <s:form name=”form1″ action=”test2″>
 <s:datetimepicker name=”start_date” language=”zh-cn”  label=”计费开始时间” displayFormat=”yyyy-MM-dd” />
 <s:submit/>
</s:form>

</body>
</html>

注意这里language为zh-cn(也可以为zh),而不是zh_CN,这与java中不同。dojo官方在Internationalization (i18n)文档中强调:

Locale

dojo.locale

The locale is a short string, defined by the host environment, which conforms to RFC 3066 used in the HTML specification. It consists of short identifiers, typically two characters long which are case-insensitive. Note that Dojo uses dash separators, not underscores like Java (e.g. “en-us”, not “en_US”). Typically country codes are used in the optional second identifier, and additional variants may be specified. For example, Japanese is “ja”; Japanese in Japan is “ja-jp”. Notice that the lower case is intentional — while Dojo will often convert all locales to lowercase to normalize them, it is the lowercase that must be used when defining your resources.

The locale in the browser is typically set during install and is not easily configurable. Note that this is not the same locale in the preferences dialog which can be used to accompany HTTP requests; there is unfortunately no way to access that locale from the client without a server round-trip.

The locale Dojo uses on a page may be overridden by setting djConfig.locale. This may be done to accomodate applications with a known user profile or server pages which do manual assembly and assume a certain locale. You may also set djConfig.extraLocale to load localizations in addition to your own, in case you want to specify a particular translation or have multiple languages appear on your page.

5、参考文档

http://cwiki.apache.org/S2WIKI/creating-a-custom-dojo-profile-for-struts-20x.html

http://dojo.jot.com/InternationalizationOverview

http://cwiki.apache.org/WW/datetimepicker.html

 

Advertisements

在Hibernate中使用日期函数获取数据库时间

Posted in Uncategorized by chuanliang on 2008/03/27

     为了避免在应用服务器与数据库服务器分别部署时候,由于应用服务器时间与数据库时钟不同步导致的问题(这在交易系统中尤为重要),在编程时候应当尽量采用数据库时间戳而不要使用应用服务器的时间戳,另外为了避免直接使用特定数据库时间函数而导致的与数据库类型绑定,在后期数据库切换时候带来的修改成本,尽量充分利用Hibernate来完成对数据库时间戳的获取,做到对数据库类型及函数的隔离。在Hibernate中有如下几种方案可以采用:

1、 利用Hibernate本身提供的current_date,current_timestamp,current_time函数

由于Hibernate在HSQL中必须有对象,因此使用这些函数时候必须依托某个vo对象。例如有一个VO对象BssProduct。

select current_timestamp() from BssProduct

2、 利用Hibernate的formula表达式来做影射

例如在BssProduct.hbm.xml中通过formula 的表达式来影射字段

   <property name=”mydate” type=”java.utl.date”  formula=”( select now() from bss_product bs ) ” />

注意:使用formula时候sql语句中的table名称及字段名称一定要是数据库中的表名称,而不能用表影射的java类。

然后在BssProduct.java VO中增加mydate的get,set方法

    private Date mydate;

    public Date getMydate() {

        return mydate;

    }

    public void setMydate(Date mydate) {

        this.mydate = mydate;

    }

然后在HSQL中就可以把mydate作为普通的字段使用

select product.mydate from BssProduct product

这样在切换数据库时候只需要修改配置文件即可,而不用修改代码。

当然与此类似,可以通过NamedSQLQuery的方式来配置sql,调用数据库函数,然后迁移数据库时候也只需要修改对应的sql,而不用修改代码。

3、通过数据库对视图的支持,然后Hibernate来做影射,通过视图来隔离数据库函数的不同。

但此种方式对于不支持视图的数据库无效(5.0以前的Mysql不支持视图)

CREATE VIEW my_timestamp AS SELECT CURRENT_TIMESTAMP AS my_timestamp

一般情况采用current_timestamp等缺省函数,特殊情况采用formula方案,有空可以仔细研究一下formula的用法,实际上可以完成很多功能。

Compass 入门指南

Posted in Uncategorized by chuanliang on 2008/03/23

    在新架构中打算选择Compass或Hibernate Search作为搜索引擎框架,比较后,感觉Hibernate Search上还是没有Compass成熟,另外考虑到后期对网页的爬取及搜索需求,决定还是基于Compass来作为架构缺省的搜索引擎。网上关于Compass的文档很多,但说得相对完整其详细的入门文档基本上没有,Compass的官方文档倒是说得很详细,但是例子一塌糊涂,存在很大问题。记录一下搭建的过程,作为入门的指南。

    Compass 通过OSEM(Object/Search Engine Mapping)允许把应用对象的领域模型映射到搜索引擎,最终通过访问common meta data来达到访问对象的目的。

1、几个核心概念

1.1、annotation vs. xml配置文件

   Compass的配置文件主要分成三类:

    第一类:*.cmd.xml文件*

      .cmd.xml文件是对common meta data进行定义,定义了最终搜索的结果中的最基本的元数据。

    第二类:*.cpm.xml文件

      *.cpm.xml是Object/Search Engine Mapping,提供了POJO到common meta data的映射。

    第三类:*.cfg.xml文件

      Compass的*.cfg.xml定义了Compass的Index存放路径、搜索引擎分词等相关信息。

    与采用xml配置文件相比较,采用Annonation方式还是相对简单,尤其是采用Spring时候,不用写*.cmd.xml文件、*.cpm.xml、*.cfg.xml,相对很方便,而且不像Hibernate的Annonation很多,Compass的Annonation的核心标注只有@Searchable、@SearchableId、@SearchableProperty、@SearchableComponent个,很容易记忆。因此推荐使用Annonation方式

1.2、Compass核心API

Compass的核心API借鉴了Hibernate的术语,因此在操作上基本上与Hibernate类似,以下为Compass的几个核心接口:

    CompassConfiguration(类似Hibernate Configuration):用来在一些设置参数、配置文件和映射定义上配置Compass。通常用来创建Compass接口。
    Compass(类似Hibernate SessionFactory):为单线程使用,创建线程安全的实例来打开Compass Seesion。同样还提供了一些搜索引擎索引级别的操作。
    CompassSesssion(类似Hibernate Session):用来执行像保存、删除、查找、装载这样的搜索操作。很轻量但是并不是线程安全的。
    CompassTransaction(类似Hibernate Transaction):管理Compass事务的接口。使用它并不需要事务管理环境(像Spring、JTA)。

1.3、Compass与Spring集成

Compass已经对对spring集成做了很好的封装,同时与Spring对Hibernate的支持类似,Compass也提供了CompassTemplate来简化诸如对Session、Transaction、Exception等操作,尽量充分使用此工具,可以有效提高效率。例如:

CompassTemplate ct = (CompassTemplate) context.getBean(“compassTemplate”);

Article article = new Article();
article.setTitle(“Compass Test”);
article.setPublishDate(new Date());
article.setAuthor(1);

ct.save(article); //存储对象需要索引的数据到Compass的索引中。

 

2、软件环境

Spring :2.5

Compas:1.2.1

Hibernate:3.2.5

Mysql :5.0.5

3、数据库脚本

CREATE TABLE `article` (

`Id` int(11) NOT NULL auto_increment,

`title` varchar(40) NOT NULL default '',

`author` int(11) default '0',

`publish_date` date NOT NULL default '0000-00-00',

PRIMARY KEY (`Id`) ) TYPE=MyISAM;

CREATE TABLE `author` (

`Id` int(11) NOT NULL auto_increment,

`username` varchar(20) NOT NULL default '',

`password` varchar(20) NOT NULL default '',

`age` smallint(6) default '0',

PRIMARY KEY (`Id`) ) TYPE=MyISAM; 

4、测试用例

从测试用例讲起比较容易把关系理清楚,不然一堆术语和概念很让人晕乎。

import org.apache.log4j.Logger;
import java.util.Date;

import junit.framework.TestCase;

import org.compass.core.Compass;
import org.compass.core.CompassDetachedHits;
import org.compass.core.CompassHit;
import org.compass.core.CompassHits;
import org.compass.core.CompassSession;
import org.compass.core.CompassTemplate;
import org.compass.core.CompassTransaction;
import org.compass.core.support.search.CompassSearchCommand;
import org.compass.core.support.search.CompassSearchResults;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mobilesoft.esales.dao.hibernate.ArticleDAO;
import com.mobilesoft.esales.dao.hibernate.AuthorDAO;
import com.mobilesoft.esales.model.Article;
import com.mobilesoft.esales.model.Author;
import com.mobilesoft.framework.search.service.CompassSearchService;

/**
 * Compass服务使用的测试用例
 * 
 * @author liangchuan@mobile-soft.cn
 * 
 */

public class TestCompass extends TestCase {

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

    private static ClassPathXmlApplicationContext context = null;
    private static CompassTemplate ct;

    static {
        context = new ClassPathXmlApplicationContext(new String[] {
                "applicationContext.xml", "applicationContext-resources.xml",
                "applicationContext-dao.xml", "applicationContext-service.xml",
                "applicationContext-compass.xml" });
        ct = (CompassTemplate) context.getBean("compassTemplate");
    }

    protected void setUp() throws Exception {

    }

    /**
     * 插入测试数据
     */
    public void testInsert() {

        ArticleDAO articleDao = (ArticleDAO) context.getBean("articleDAO");
        AuthorDAO authorDao = (AuthorDAO) context.getBean("authorDAO");
        Article article = new Article();
        Author author = new Author();
        author.setAge((short) 27);
        author.setUsername("liangchuan");
        author.setPassword("liangchuan");
        article.setTitle("Compass Test");
        article.setPublishDate(new Date());
        article.setAuthor(1);
        authorDao.save(author);
        articleDao.save(article);
        ct.save(article);
        ct.save(author);
    }

    /**
     * 用于测试使用CompassTransaction事务方式
     */
    public void testTransactionalFind() {

        Compass compass = ct.getCompass();
        CompassSession session = compass.openSession();
        CompassTransaction tx = null;
        try {
            tx = session.beginTransaction();
            CompassHits hits = session.find("Compass*");

            logger.error("testTransactionalFind() - CompassHits hits="
                    + hits.getLength());
            for (int i = 0; i < hits.getLength(); i++) {
                Object hit = hits.data(i);
                if (hit instanceof Article) {
                    Article item = (Article) hit;
                    logger.error("testTransactionalFind() - article     hits="
                            + item.getTitle());
                } else if (hit instanceof Author) {
                    Author item = (Author) hit;
                    logger.error("testTransactionalFind() - author hits="
                            + item.getUsername());
                } else {
                    logger.error("testTransactionalFind() - error hits=");
                }
            }
            tx.commit();
        } catch (Exception e) {
            if (tx != null) {
                tx.rollback();
            }
        } finally {
            session.close();
        }
    }

    /**
     * 用于演示CompassDetachedHits的使用。
     * 由于CompassTempalte得到的结果集必须在transactionalcontext中才能使用,
     * 因此必须使用CompassDetachedHits方式测试CompassDetachedHits方式
     */
    public void testDetachedFind() {

        // 由于CompassTempalte得到的结果集必须在transactional
        // context中才能使用,因此必须使用CompassDetachedHits方式
        // 测试CompassDetachedHits方式
        CompassDetachedHits hits = ct.findWithDetach("Compass*");

        logger.error("testDetachedFind() - CompassHits hits="
                + hits.getLength());
        for (int i = 0; i < hits.getLength(); i++) {
            Object hit = hits.data(i);
            if (hit instanceof Article) {
                Article item = (Article) hit;
                logger.error("testDetachedFind() - article     hits="
                        + item.getTitle());
            } else if (hit instanceof Author) {
                Author item = (Author) hit;
                logger.error("testDetachedFind() - author hits="
                        + item.getUsername());
            } else {
                logger.error("testDetachedFind() - error hits=");
            }
        }

    }

    /**
     * 用于演示com.mobilesoft.framework.search.service.CompassSearchService的使用
     * 
     */
    class CompassSearch extends CompassSearchService{
        CompassSearch(){
            Compass compass = ct.getCompass();
            CompassSession session = compass.openSession();
            CompassTransaction tx = null;

            try {
                tx = session.beginTransaction();
                CompassSearchCommand command = new CompassSearchCommand();
                command.setQuery("Compass");
                CompassSearchResults results= performSearch(command,session);
                logger.error("CompassSearch() - CompassHit TotalHits value=" +results.getTotalHits());

                for (int i = 0; i < results.getHits().length; i++) {
                    CompassHit hits=results.getHits()[i];
                    Object hit=hits.getData();
                    logger.error("CompassSearch() - CompassHit hit=" + hit); //$NON-NLS-1$

                    if (hit instanceof Article) {
                        Article item = (Article) hit;
                        logger.error("testCompassSearchService() - article     hits="
                                + item.getTitle());
                    } else if (hit instanceof Author) {
                        Author item = (Author) hit;
                        logger.error("testCompassSearchService() - author hits="
                                + item.getUsername());
                    } else {
                        logger.error("testCompassSearchService() - error hits=");
                    }

                    tx.commit();
                }
            } catch (Exception e) {
                if (tx != null) {
                    tx.rollback();
                }
            } finally {
                session.close();
            }

        }

    }
    public void testCompassSearchService() {
        new CompassSearch();
        }

    protected void tearDown() throws Exception {
    }
}

.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、配置文件

applicationContext-compass.xml

<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
    "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans default-lazy-init="true">
    <bean id="compassTemplate" class="org.compass.core.CompassTemplate">
        <property name="compass" ref="compass"/>
    </bean>
    <bean id="annotationConfiguration"
        class="org.compass.annotations.config.CompassAnnotationsConfiguration">
    </bean>
    <bean id="compass" class="org.compass.spring.LocalCompassBean">
        <property name="classMappings">
            <list>
                <value>com.mobilesoft.esales.model.Article</value>
                <value>com.mobilesoft.esales.model.Author</value>
            </list>
        </property>
        <property name="compassConfiguration" ref="annotationConfiguration"/>

        <property name="compassSettings">
            <props>
                <prop key="compass.engine.connection"> file://compass </prop>
                <prop key="compass.transaction.factory">
                    org.compass.spring.transaction.SpringSyncTransactionFactory
                    </prop>
                <prop
                    key="compass.engine.highlighter.default.formatter.simple.pre">
                    <![CDATA[<font color="red"><b>]]>
                </prop>
                <prop
                    key="compass.engine.highlighter.default.formatter.simple.post">
                    <![CDATA[</b></font>]]>
                </prop>
            </props>
        </property>

        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <bean id="hibernateGpsDevice"
        class="org.compass.gps.device.hibernate.HibernateGpsDevice">
        <property name="name">
            <value>hibernateDevice</value>
        </property>
        <property name="sessionFactory" ref="sessionFactory"/>
        <property name="mirrorDataChanges">
            <value>true</value>
        </property>
    </bean>
    <bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps"
        init-method="start" destroy-method="stop">
        <property name="compass" ref="compass"/>
        <property name="gpsDevices">
            <list>
                <bean
                    class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">
                    <property name="gpsDevice" ref="hibernateGpsDevice"/>
                </bean>
            </list>
        </property>
    </bean>
    <bean id="compassSearchService" class="com.mobilesoft.framework.search.service.CompassSearchService">
        <property name="compass" ref="compass"/>
        <property name="pageSize" value="15"/>
    </bean>

    <!-- 定时重建索引(利用quartz)或随Spring ApplicationContext启动而重建索引 -->
    <bean id="compassIndexBuilder" class="com.mobilesoft.framework.search.service.CompassIndexBuilder" lazy-init="false">
        <property name="compassGps" ref="compassGps"/>
        <property name="buildIndex" value="false"/>
        <property name="lazyTime" value="10"/>
    </bean>


</beans>

.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; }

applicationContext-dao.xml、applicationContext-service.xml、applicationContext-resources.xml等略去。

 

6、Service层(参考了SpringSide实现)

.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; }

AdvancedSearchCommand.java

package com.mobilesoft.framework.search.service;

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.compass.core.CompassQuery.SortDirection;
import org.compass.core.CompassQuery.SortPropertyType;
import org.compass.core.support.search.CompassSearchCommand;

import org.springframework.util.Assert;

public class AdvancedSearchCommand extends CompassSearchCommand {

    /**
     * 封装基于Compass 的排序参数.
     */
    class CompassSort {

        private String name;

        private SortPropertyType type;

        private SortDirection direction;

        public CompassSort() {
        }

        public CompassSort(String sortParamName, String paramType,
                           boolean isAscend) {
            Assert.isTrue(StringUtils.isNotBlank(sortParamName));
            setName(sortParamName);

            if ("int".equalsIgnoreCase(paramType)) {
                setType(SortPropertyType.INT);
            } else if ("float".equalsIgnoreCase(paramType)) {
                setType(SortPropertyType.FLOAT);
            } else if ("string".equalsIgnoreCase(paramType)) {
                setType(SortPropertyType.STRING);
            } else {
                setType(SortPropertyType.AUTO);
            }

            if (isAscend) {
                setDirection(SortDirection.AUTO);
            } else {
                setDirection(SortDirection.REVERSE);
            }
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public SortPropertyType getType() {
            return type;
        }

        public void setType(SortPropertyType type) {
            this.type = type;
        }

        public SortDirection getDirection() {
            return direction;
        }

        public void setDirection(SortDirection direction) {
            this.direction = direction;
        }
    }

    /**
     * 搜索结果排序表.
     */
    private Set<CompassSort> sortMap = new HashSet<CompassSort>();

    private String[] highlightFields;

    /**
     * @param paramType 现定义了三种类型: int string 以及 float。<br>
     *                  除去这三种外,其他会被自动定义为SortPropertyType.AUTO 具体的可见{@link org.compass.core.CompassQuery.SortPropertyType}
     * @param isAscend  顺序还是倒序排序
     * @see org.compass.core.CompassQuery.SortPropertyType#AUTO
     * @see org.compass.core.CompassQuery.SortPropertyType#INT
     * @see org.compass.core.CompassQuery.SortPropertyType#STRING
     * @see org.compass.core.CompassQuery.SortPropertyType#FLOAT
     * @see org.compass.core.CompassQuery.SortDirection#AUTO
     * @see org.compass.core.CompassQuery.SortDirection#REVERSE
     */
    public void addSort(String sortParamName, String paramType, boolean isAscend) {
        this.sortMap.add(new CompassSort(sortParamName, paramType, isAscend));
    }

    public Set<CompassSort> getSortMap() {
        return sortMap;
    }

    public void setSortMap(Set<CompassSort> sortMap) {
        this.sortMap = sortMap;
    }

    public String[] getHighlightFields() {
        return highlightFields;
    }

    public void setHighlightFields(String[] highlightFields) {
        this.highlightFields = highlightFields;
    }
}

CompassIndexBuilder.java

package com.mobilesoft.framework.search.service;

import org.apache.log4j.Logger;
import org.compass.gps.CompassGps;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

/**
 * 通过quartz定时调度定时重建索引或自动随Spring ApplicationContext启动而重建索引的Builder.
 * 会启动后延时数秒新开线程调用compassGps.index()函数.
 * 默认会在Web应用每次启动时重建索引,可以设置buildIndex属性为false来禁止此功能.
 * 也可以不用本Builder, 编写手动调用compassGps.index()的代码.
 *
 */
public class CompassIndexBuilder implements InitializingBean {

    private static final Logger log = Logger.getLogger(CompassIndexBuilder.class);

    // 是否需要建立索引,可被设置为false使本Builder失效.
    private boolean buildIndex = false;

    // 索引操作线程延时启动的时间,单位为秒
    private int lazyTime = 10;

    // Compass封装
    private CompassGps compassGps;

    // 索引线程
    private Thread indexThread = new Thread() {

        @Override
        public void run() {
            try {
                Thread.sleep(lazyTime * 1000);

                log.info("begin compass index...");
                long beginTime = System.currentTimeMillis();
                // 重建索引.
                // 如果compass实体中定义的索引文件已存在,索引过程中会建立临时索引,
                // 索引完成后再进行覆盖.
                compassGps.index();
                long costTime = System.currentTimeMillis() - beginTime;
                log.info("compss index finished.");
                log.info("costed " + costTime + " milliseconds");
            } catch (InterruptedException e) {
                // simply proceed
            }
        }
    };

    /**
     * 实现<code>InitializingBean</code>接口,在完成注入后调用启动索引线程.
     *
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        if (buildIndex) {
            Assert.notNull(compassGps, "CompassIndexBuilder not set CompassGps yet.");
            indexThread.setDaemon(true);
            indexThread.setName("Compass Indexer");
            indexThread.start();
        }
    }

    public void setBuildIndex(boolean buildIndex) {
        this.buildIndex = buildIndex;
    }

    public void setLazyTime(int lazyTime) {
        this.lazyTime = lazyTime;
    }

    public void setCompassGps(CompassGps compassGps) {
        this.compassGps = compassGps;
    }
}

CompassSearchService.java

package com.mobilesoft.framework.search.service;

import org.compass.core.Compass;
import org.compass.core.CompassCallback;
import org.compass.core.CompassDetachedHits;
import org.compass.core.CompassHits;
import org.compass.core.CompassQuery;
import org.compass.core.CompassSession;
import org.compass.core.CompassTemplate;
import org.compass.core.CompassTransaction;
import org.compass.core.support.search.CompassSearchCommand;
import org.compass.core.support.search.CompassSearchResults;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

import com.mobilesoft.framework.search.service.AdvancedSearchCommand.CompassSort;
/**
 * 仿照 {@link org.compass.spring.web.mvc.CompassSearchController}
 * 中的代码,构建了一个Service,方便不使用Spring MVC 
 *
 * @see org.compass.spring.web.mvc.CompassSearchController
 * @see org.compass.spring.web.mvc.AbstractCompassCommandController
 */
public class CompassSearchService implements InitializingBean {

    //每页显示的条目数量
    private Integer pageSize = 15;

    private Compass compass;

    private CompassTemplate compassTemplate;

    /**
     * 公开的搜索接口,返回匹配的搜索结果,与
     * {@link org.compass.spring.web.mvc.CompassSearchController#handle(javax.servlet.http.HttpServletRequest,
     *javax.servlet.http.HttpServletResponse,Object,org.springframework.validation.BindException) 处理相似
     *
     * @see org.compass.spring.web.mvc.CompassSearchController#handle(javax.servlet.http.HttpServletRequest,
     *javax.servlet.http.HttpServletResponse,java.lang.Object,org.springframework.validation.BindException)
     */
    public CompassSearchResults search(final CompassSearchCommand command) {
        return (CompassSearchResults) getCompassTemplate().execute(
                CompassTransaction.TransactionIsolation.READ_ONLY_READ_COMMITTED, new CompassCallback() {
            public Object doInCompass(CompassSession session) {
                return performSearch(command, session);
            }
        });
    }

    /**
     * 通过此方法调用搜索引擎,进行结果匹配搜索.
     *
     * @see org.compass.spring.web.mvc.CompassSearchController#performSearch(
     *org.compass.spring.web.mvc.CompassSearchCommand,org.compass.core.CompassSession)
     */
    protected CompassSearchResults performSearch(CompassSearchCommand searchCommand, CompassSession session) {
        long time = System.currentTimeMillis();
        CompassQuery query = buildQuery(searchCommand, session);
        CompassHits hits = query.hits();
        CompassDetachedHits detachedHits;
        CompassSearchResults.Page[] pages = null;
        if (pageSize == null) {
            doProcessBeforeDetach(searchCommand, session, hits, -1, -1);
            detachedHits = hits.detach();
        } else {
            int iPageSize = pageSize;
            int page = 0;
            int hitsLength = hits.getLength();
            if (searchCommand.getPage() != null) {
                page = searchCommand.getPage();
            }
            int from = page * iPageSize;

            if (from > hits.getLength()) {

                // 如果起始的条目大于搜索到的条目
                from = hits.getLength() - iPageSize;
                doProcessBeforeDetach(searchCommand, session, hits, from, hitsLength);
                detachedHits = hits.detach(from, hitsLength);
            } else if ((from + iPageSize) > hitsLength) {

                // 结束的条目大于搜索到的结果
                doProcessBeforeDetach(searchCommand, session, hits, from, hitsLength);
                detachedHits = hits.detach(from, hitsLength);
            } else {

                // 中间的页码,直接取出相应的条目
                doProcessBeforeDetach(searchCommand, session, hits, from, iPageSize);
                detachedHits = hits.detach(from, iPageSize);
            }
            doProcessAfterDetach(searchCommand, session, detachedHits);
            int numberOfPages = (int) Math.ceil((float) hitsLength / iPageSize);
            pages = new CompassSearchResults.Page[numberOfPages];
            for (int i = 0; i < pages.length; i++) {
                pages[i] = new CompassSearchResults.Page();
                pages[i].setFrom(i * iPageSize + 1);
                pages[i].setSize(iPageSize);
                pages[i].setTo((i + 1) * iPageSize);
                if (from >= (pages[i].getFrom() - 1) && from < pages[i].getTo()) {
                    pages[i].setSelected(true);
                } else {
                    pages[i].setSelected(false);
                }
            }
            if (numberOfPages > 0) {
                CompassSearchResults.Page lastPage = pages[numberOfPages - 1];
                if (lastPage.getTo() > hitsLength) {
                    lastPage.setSize(hitsLength - lastPage.getFrom());
                    lastPage.setTo(hitsLength);
                }
            }
        }
        time = System.currentTimeMillis() - time;
        CompassSearchResults searchResults = new CompassSearchResults(detachedHits.getHits(), time, pageSize);
        searchResults.setPages(pages);
        return searchResults;
    }

    /**
     * 构建Lucene搜索器.
     */
    protected CompassQuery buildQuery(CompassSearchCommand searchCommand, CompassSession session) {
        CompassQuery query = session.queryBuilder().queryString(searchCommand.getQuery().trim()).toQuery();

        if (AdvancedSearchCommand.class.isAssignableFrom(searchCommand.getClass())) {
            AdvancedSearchCommand advancedSearchCommand = (AdvancedSearchCommand) searchCommand;

            for (CompassSort sort : advancedSearchCommand.getSortMap()) {
                query.addSort(sort.getName(), sort.getType(), sort.getDirection());
            }
        }
        return query;
    }

    /**
     * 在detach 之前,可以做一些操作。比如highlighting...
     *
     * @param from 需要注意的是,如果pageSize 没有指定,那么这里传入的参数为-1
     */
    protected void doProcessBeforeDetach(CompassSearchCommand searchCommand, CompassSession session, CompassHits hits,
                                         int from, int size) {
        if (AdvancedSearchCommand.class.isAssignableFrom(searchCommand.getClass())) {
            if (from < 0) {
                from = 0;
                size = hits.getLength();
            }
            String[] highlightFields = ((AdvancedSearchCommand) searchCommand).getHighlightFields();

            if (highlightFields == null) {
                return;
            }

            // highlight fields
            for (int i = from; i < size; i++) {
                for (String highlightField : highlightFields) {
                    hits.highlighter(i).fragment(highlightField);
                }
            }
        }
    }

    /**
     * An option to perform any type of processing before the hits are detached.
     */
    protected void doProcessAfterDetach(CompassSearchCommand searchCommand, CompassSession session,
                                        CompassDetachedHits hits) {

    }

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(compass, "Must set compass property");
        this.compassTemplate = new CompassTemplate(compass);
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public void setCompass(Compass compass) {
        this.compass = compass;
    }

    protected CompassTemplate getCompassTemplate() {
        return this.compassTemplate;
    }

}

.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; }

8、Model层

@SearchableId 声明Document的id列;

@SearchableProperty 声明要索引的field;

@SearchableComponent 声明要索引的其他关联对象。

Article.java

package com.mobilesoft.esales.model;

import java.util.Date;

import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;
import org.compass.core.CompassTemplate;


@Searchable
public class Article  implements java.io.Serializable {

    @SearchableId
    private Integer id;
    @SearchableProperty(name="title")
    private String title;
    @SearchableProperty(name="author")
    private Integer author;
    @SearchableProperty(name="publishDate")
    private Date publishDate;

    /** default constructor */
    public Article() {
    }

    /** minimal constructor */
    public Article(String title, Date publishDate) {
        this.title = title;
        this.publishDate = publishDate;
    }

    /** full constructor */
    public Article(String title, Integer author, Date publishDate) {
        this.title = title;
        this.author = author;
        this.publishDate = publishDate;
    }

    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return this.title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Integer getAuthor() {
        return this.author;
    }

    public void setAuthor(Integer author) {
        this.author = author;
    }

    public Date getPublishDate() {
        return this.publishDate;
    }

    public void setPublishDate(Date publishDate) {
        this.publishDate = publishDate;
    }

}

.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; }

Author.java

package com.mobilesoft.esales.model;

import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;
import org.compass.core.CompassTemplate;


@Searchable
public class Author  implements java.io.Serializable {


    @SearchableId
    private Integer id;
    @SearchableProperty(name="username")
    private String username;
    private String password;
    @SearchableProperty(name="age")
    private Short age;

    public Author() {
    }

    /** minimal constructor */
    public Author(String username, String password) {
        this.username = username;
        this.password = password;
    }

    /** full constructor */
    public Author(String username, String password, Short age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }

    // Property accessors

    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Short getAge() {
        return this.age;
    }

    public void setAge(Short age) {
        this.age = age;
    }


}

.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; }

9、DAO层

ArticleDAO.java和AuthorDAO.java省略

直接用MyEclipse生成的,没有什么特别的。

10、参考文档

http://www.compass-project.org/docs/1.2.1/reference/html/

The Compass Framework Search made easy.pdf

Compass TSSJS Europe 06.pdf

Hello World Tutorial

InfoQ:  Compass: Integrate Search into your apps

InfoQ: Compass: Simplifying and Extending Lucene to Provide Google-like Search

InfoQ: Compass: 在你的应用中集成搜索功能

Compass 指南

http://www.kimchy.org/

 

学习电信BOSS系统好榜样-电子商务系统建设思考5:MISC(Mobile Information Service Center)系统

Posted in Uncategorized by chuanliang on 2008/03/15

    最近研究移动MISC(Mobile Information Service Center)平台,以考察其业务模式及系统架构设计思路,希望能够对平台的规划和建设有所帮助。发觉其有很多不错的思路值得我们在平台建设时候学习。

    MISC(Mobile Information Service Center) 是为运营商提供的可快速启动各种数据业务的平台,是一个强大的资源调度平台。它连接运营商的基础网络及业务支撑系统(客服系统、营业系统、计费系统),为 SPs提供各种电信资源(如短消息中心资源、用户信息资源、语音资源、传真资源等)、功能资源(如单点认证、计费、漫游控制、个性化等)及各种公共的资源引擎(如TTS、地理位置、语音交互、推送信息等)。MISC平台在移动运营商和内容提供商之间,起到一个桥梁纽带的作用,使得运营商以最快速、最简单的模式开通新业务;各内容提供商不用关心网络层的详细技术,能够专注于内容、应用的开发,以更低廉的成本、在最短的时间内提供新业务;各类移动用户可以通过任意终端(WAP手机、PDAPC、普通电话、传真机等),采用SMSWAPWEB、话音/传真方式,在任何地方就近接入移动梦网门户,使用新业务。

1、 移动MISC平台的核心价值

  • 大规模的用户群
  • 通道资源:包括手机终端、营业厅等
  • 内容计费功能:其MISC+BOSS系统完成对用户内容的订购管理和内容计费
  • 支付功能:通过其手机话费代收费,帮助SP等增值业务合作伙伴收取用户费用,解决小额支付问题。
  • 整合价值链的强势能力

    移动MISC(Mobile Information Service Center)平台建设的目的在于:

  • 建立统一的业务管理平台,为运营商提供统一的用户管理、订购关系管理、内容管理、内容审核、合作管理以及运营及营销管理;
  • 提供以内容为核心的业务生成能力,为业务开发提供各种接入方式的支撑;
  • 提供开放的合作接口,为合作伙伴的内容聚合,业务聚合提供支撑;
  • 为用户访问业务提供方便获取信息的各种门户,以及统一的客户服务接口;
  • 通过统一的业务接入,简化和各业务网元及支撑系统的接口;

MISC平台移动梦网业务逻辑图

                 MISC平台移动梦网业务逻辑图

 

 

2、 MISC平台对eSales平台建设的启示

  • 内容资源的管理、计费、运营管理平台

    由于移动在价值链中的强势地位,其政策导向是加强对产业链条的主导控制权,导致对众多CP、SP厂商生存空间愈来愈小,这些CP、SP拥有海量的数字内容和服务,需要另外一条渠道通路帮助把这些产品和服务分销到最终用户的手中,这是eSales系统的价值之一。从本质来说,eSales平台实际上就是要搭建类似于移动MISC系统的平台,利用我们传统渠道通路的优势及移动对合作伙伴打压的机会,吸引众多更多的合作伙伴及代理商来依赖我们平台销售其产品,最终形成以我们为主导的增值业务的管理、计费及运营平台。

  • 支付平台

    众多SP、CP之所以对移动形成了依赖关系,除了移动到海量用户的通路优势外,最大的原因就是其通过手机费代缴增值业务的小额支付功能,尤其是在国内信用体系及支付环境不健全的情况下,因此提供便捷的支付工具是eSales平台成功的关键之一。

  • 技术平台建设

    在平台建设上,像MISC平台号称是Open-VCS(开放式价值链的管理体系),但由于移动的强势地位,因此所谓的开放平台只能是在强权之下的开放,我觉得Facebook能够在短短时间内能够发展起来,一个至关重要的就是开放的平台化战略(Facebook如何击败Myspace,Yahoo!和Google) 。对于我们而言,通过与合作伙伴间的“对等共赢”的合作策略及开放的平台化战略,吸引他们围绕我们的平台搭建各种应用,最终形成良性的互联网社区,构筑起竞争的门槛。另外对于移动这样的运营商而言,对用户体验的关注并不是其强项,反而是其互联网时代最致命的短板,因此我们在建设上必须“用户体验至上”,这也是我们的核心竞争力。

外部内容的注入极可能使Facebook变得更为强大。

Facebook的主页有一个巨大的内在优势:社会性网络已经建立。是的,另一种方式则是整合GmailBBC头条,YouTube视频搜索以及播客目录。若将社会关系与这些内容融合起来,不论如何,此举将使Facebook立即击败其主要竞争对手。

  • 商业模式上:

    移动与产业链中的CP、SP合作,采用收入分成的商业模型。从互联网商业盈利模式来看,此种模式并不是互联网企业盈利的主要动力。另外互联网上典型的盈利模式或者是像QQ这样的公司围绕核心产品靠增值业务来收取费用;或者是像Google、Baidu这样的公司主要依靠广告方式来收取费用;或者是像Amazhon、阿里巴巴这样的电子商务的公司,这些厂商核心盈利模式是通过减少交易过程中的中间环节,使得交易成本大大节省,从中获取理论。对于我们而言,分成模式应该也不是我们最终核心盈利模式,后面几种都应该成为我们的盈利模式。

3、参考资料

移动数据业务:从业务管理走向内容运营

卓望内容管理平台产品思路浅谈

数据业务:从业务管理走向内容运营

 

无线互联网建设思考

Posted in Uncategorized by chuanliang on 2008/03/09

对于无线互联网的建设上思路,我觉得有几条主线:

1、 围绕通路相关的业务:看看成功的互联网企业,所谓渠道为王、终端为王的口号在互联网时代一样通用,谁能够打通无线互联网、互联网、传统渠道的通路,那就成功了一半。目前重点考虑手机客户端、传统销售渠道、数字内容分销平台

2、围绕电子商务所展开的相关业务:目前重点考虑手机支付平台、手机垂直电子商务(B2B、B2C)

3、 围绕用户社区展开的业务:包括用户社区、商家社区,以及围绕社区所进行的社会化商务。社区的建设重点是信用体系及积分体系的建设。

    在目前的建设阶段,由于人力资源的有效,门户社区及客户端的建设上不可能面面俱到,也不可能靠互联网自己的口碑传播来逐步形成用户社区,提高用户的粘度。所谓要集中优势兵力打歼灭战,因此我觉得关键还是要找到一两个具有病毒式营销或能够引爆流行的可运营的产品或服务,以此产品或服务为切入点吸引用户。要找到这样的产品或服务,我觉得首先需要对用户进行准确的细分,挖掘每一种细分用户的核心的关注点和兴趣所在,按照我自己的一些理解,可以粗浅分为普通大众和商务人群两种。

1、普通大众的需求及特点:

  • 通信需求是最基本需求
  • 渴望成名,展现自我
  • 结交更多的朋友
  • 能够有便捷的工具,帮助其与外界沟通,跟上流行和时尚
  • 一夜暴富
  • 隐私
  • 等等

2、商务人群的需求及特点:

  • 能够及时了解商业机会
  • 结交更多对事业有所帮助的商业伙伴
  • 协同需求
  • 等等

一些想到的方向,供后续思考:

1、 Twitter微博客模式:

定位:

    满足普通大众无聊消磨时光及其展现自我的需求。从目前应用来看,手机客户端基于twitter模式的社区比纯粹的小圈子更为具有吸引力。

   对于社区,可以重点考虑基于位置的社区,同时借鉴Facebook开放平台及社区建设经验。

服务提供商

    http://twitter.com/

    http://www.taotao.com/

    http://fanfou.com/

2、 基于gprs的发短信方式

定位:

    满足所有人免费发短信(只收gprs流量)的需求

    可以利用gprs包月服务来发短信。像目前诸如飞信、做做客、pingco等提供的免费短信功能都通过此种途径。通过此种模式实际上还可以打电话。

    针对商务人士可以提供国内发往国外的短信服务。

    但这种模式与我们目前靠短信赚钱的模式有一定冲突,需要从产品定位及包装上区别开来。

服务提供商

    http://www.zozoc.cn

    http://www.pingco.com/

     http://world.china.com/worldchina/index.htm

    http://www.10166.com.cn/

    http://www.fetion.com.cn/

3、 基于syncml协议的类RSS内容订购服务

定位

    提供经过精心筛选的最新的资讯(重点为普通大众感兴趣的内容,而不是很宽泛的内容,例如图片等),可以与标签或rss结合,这样用户进来后可以迅速找到自己喜欢的精选内容,不用再去一些大而全的wap站点。

服务提供商:

    Mytt中的“频道”中提供了此种初步功能(我猜想是基于syncml协议做的),但没有持续更新和维护。

4、 基于回拨形式的电话

定位:

    提供相对低廉的通信服务,低廉的通信服务是所有用户共同的需求。

    由于国内voip政策风险,可以通过回拨方式来提供voip服务,或者与国外有一堆低廉的voip服务提供商合作来提供此种服务。国内像http://www.vivame.cn/http://www.pingco.com都提供此类服务。

服务提供商:

    http://www.talkonaut.com/

    http://www.rebtel.com

    http://www.jajah.com

    http://www.fring.com/

    http://www.voipdiscount.com

    http://www.voipstunt.com

    http://www.vivame.cn/

    http://www.pingco.com

5、 站点导航(聚合门户)

定位:

    为广大手机用户提供各种诸如互联网上hao123、265这样的导航服务,最终形成手机内容的聚合门户。

服务提供商:

    http://www.datuu.com/

    http://www.haodewap.com/

6、 个人理财

    由于股票的买卖对于我们而言,只能卖客户端,不具有太大的运营价值。

    在个人理财服务上,彩票网上代购合买是一个值得密切关注的方向。只是由于去年年底的五部委发文禁止网上代购合买业务,目前政策形式不是很明朗。但值得我们关注,预计此业务还是会以诸如牌照等形式推出的。此种业务很适合做运营,也适合作为一个服务嵌入客户端,可持续观察。

服务提供商:

    http://www.500wan.com/

    http://www.wozhongle.com

    http://www.zhcw.com/

 

jmesa 使用指南

Posted in Uncategorized by chuanliang on 2008/03/09

1、关于jmesa

  开始搭建新的基于struts2+spring+hibernate的技术架构,基于以前对eXtremeTable的好感,决定继续采用extremetable,而不选用displaytag和valuelist。用google搜索发现eXtremeTable的作者己不再更新eXtremeTable,其把精力转移到了新的项目-JMesa。

  However, now that JMesa is up to release 2.1 I am only focusing my efforts on that library. The JMesa API has really turned into the library I have always wanted to create. With the introduction of the tags and facade in release 2.1 building tables is now easier than ever. I would encourage developers that are able to run in a JDK1.5 and JSP2.0 container to start using JMesa. If you are interested in a full explanation about how JMesa came about you can read more on the JMesa site. If you are more interested in what JMesa offers over the eXtremeTable read the features list.

 

2、基于struts2+spring+hibernate+jquery的jmesa分页实现样例

2.1、需求场景

  基于Jmesa,从数据库表Person中查询出记录,能够实现分页、排序、导出功能。同时结合Jquery,利用ajax实现对数据的删除操作。

2.2、环境说明

Jmesa: 2.3

Struts2 :2.0.11

Spring:2.5

Hibernate:3.2.5

Jquery:jquery-1.2.1.pack,jquery.bgiframe.pack

Tomcat:5.5

Mysql :5.0

数据库、页面、JVM编码统一为GBK

2.3、数据库表结构

CREATE TABLE `person` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `firstName` varchar(45) NOT NULL,
  `lastName` varchar(45) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=76 DEFAULT CHARSET=latin1; 

.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.4、页面list.jsp

<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ page language="java" errorPage="/error.jsp" pageEncoding="GBK" contentType="text/html;charset=GBK" %>
<html>
<head>
<link rel="stylesheet" type="text/css" href="<%= request.getContextPath() %>/css/web.css"></link>
<link rel="stylesheet" type="text/css" href="<%= request.getContextPath() %>/css/jmesa.css"></link>
<!--
对jmesa.js脚本的应用必须放到头部,而不能放到尾部
-->
<script type="text/javascript" src="<%= request.getContextPath() %>/js/jmesa.js"></script>
</head>
<body>
<p>Jmesa表单组件使用演示样例</p>

<form name="personForm" action="<%= request.getContextPath() %>/list.action" method="post">
<div id="persons">
<%
    out.println(request.getAttribute("myhtml"));
%>
</div>
</form>

<script type="text/javascript">
function onInvokeAction(id) {
    setExportToLimit(id, '');
    createHiddenInputFieldsForLimitAndSubmit(id);
}
function onInvokeExportAction(id) {
    var parameterString = createParameterStringForLimit(id);
    location.href = '<%= request.getContextPath() %>/list.action?' + parameterString;
}


function delUser(tableId,rowId) {
    var parameterString = createParameterStringForLimit(tableId);
    $.get("<%= request.getContextPath() %>/ajax.action?id="+rowId+"&"+parameterString, function(data) {
        $("#persons").html(data)
    });

}
</script>
<script type="text/javascript" src="<%= request.getContextPath() %>/js/jquery-1.2.1.pack.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/js/jquery.bgiframe.pack.js"></script>

</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; }

2.4、Action代码

package com.mobilesoft.esales.webapp.action;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import jxl.demo.CSV;

import org.apache.commons.beanutils.BeanUtils;
import org.jmesa.facade.TableFacade;
import org.jmesa.facade.TableFacadeImpl;
import org.jmesa.limit.Limit;

import com.mobilesoft.esales.dao.hibernate.Person;
import com.mobilesoft.esales.service.PersonService;
import com.octo.captcha.service.CaptchaServiceException;
import com.opensymphony.xwork2.Action;
import static org.jmesa.limit.ExportType.CSV;
import static org.jmesa.limit.ExportType.JEXCEL;
import static org.jmesa.limit.ExportType.PDF;
import org.jmesa.core.filter.DateFilterMatcher;
import org.jmesa.core.filter.MatcherKey;
import org.jmesa.facade.TableFacade;
import org.jmesa.facade.TableFacadeImpl;
import org.jmesa.limit.Limit;
import org.jmesa.util.ItemUtils;
import org.jmesa.view.component.Column;
import org.jmesa.view.component.Row;
import org.jmesa.view.component.Table;
import org.jmesa.view.editor.BasicCellEditor;
import org.jmesa.view.editor.CellEditor;
import org.jmesa.view.editor.DateCellEditor;
import org.jmesa.view.html.HtmlBuilder;
import org.jmesa.view.html.component.HtmlColumn;
import org.jmesa.view.html.component.HtmlRow;
import org.jmesa.view.html.component.HtmlTable;
import org.jmesa.view.html.editor.DroplistFilterEditor;

import sun.text.CompactShortArray.Iterator;

/**
 * 用于演示基于jmesa(http://code.google.com/p/jmesa/)的分页、排序组件的使用方法,
 * 在src/java/com/mobilesoft/esales/dao/hibernate/person.sql有Person表的测试数据
 * @author <a href="mailto:liangchuan@mobile-soft.cn">liangchuan</a> 
 * @since 2008-03
 */
public class PersonAction extends BaseAction {
    private PersonService personService;
    private List<Person> persons;
    private Person person;
    private Integer id;
    private String tableId;



    public String execute() {
        this.persons = personService.findAll();
        //创建id为tableId为表单
        //<table id="tableId"  border="0"  cellpadding="0"  cellspacing="0"  class="table"  width="600px" >
        TableFacade tableFacade = new TableFacadeImpl("tableId", getRequest());
        //设定页面分页数据
        tableFacade.setItems(persons);
        //设定支持的查询结果导出格式为csv,excel,pdf格式
        tableFacade.setExportTypes(getResponse(), CSV, JEXCEL, PDF);
        tableFacade.setStateAttr("restore");
        Limit limit = tableFacade.getLimit();
        if (limit.isExported()) {
            export(tableFacade);
            return null;
        } else {
            String html = html(tableFacade);
            getRequest().setAttribute("myhtml", html);
        }
        return Action.SUCCESS;
    }

    private String html(TableFacade tableFacade) {
        // 设定表格属性,注意此处的url用于诸如增加、删除、修改、查询操作,并不是实际的数据库表属性,
        //但表单需要有对应的po对新,因此需要在Person中增加此属性
        tableFacade.setColumnProperties("id", "firstName", "lastName", "url");

        HtmlTable table = (HtmlTable) tableFacade.getTable();
        table.setCaption("测试用户信息列表");
        table.getTableRenderer().setWidth("600px");

        HtmlRow row = table.getRow();

        HtmlColumn id = row.getColumn("id");
        id.setTitle("id");

        HtmlColumn firstName = row.getColumn("firstName");
        firstName.setTitle("属性1");

        HtmlColumn lastName = row.getColumn("lastName");
        lastName.setTitle("属性2");

        HtmlColumn deleteAction = row.getColumn("url");
        deleteAction.setTitle("操作");

        // Using an anonymous class to implement a custom editor.
        // 用于演示在表格中增加超链接
        firstName.getCellRenderer().setCellEditor(new CellEditor() {
            public Object getValue(Object item, String property, int rowcount) {
                Object value = new BasicCellEditor().getValue(item, property,
                        rowcount);
                HtmlBuilder html = new HtmlBuilder();
                html.a().href().quote().append("http://www.mobile-soft.cn")
                        .quote().close();
                html.append(value);
                html.aEnd();
                return html.toString();
            }
        });

        // Using an anonymous class to implement a custom editor.
        //用于演示在表格中增加javascript操作,通过jquery来实现ajax式的删除操作
        deleteAction.getCellRenderer().setCellEditor(new CellEditor() {
            public Object getValue(Object item, String property, int rowcount) {
                Object value = new BasicCellEditor().getValue(item, property,
                        rowcount);
                HtmlBuilder html = new HtmlBuilder();
                //取得每一行的id号
                Object id = ItemUtils.getItemValue(item, "id");
                String js=" onclick='javascript:del("tableId","+id+") '";
                html.a().append(js).href().quote().append(getRequest().getContextPath()+"/remove.action?id="+id).quote().close();
                html.append("删除");
                html.aEnd();
                return html.toString();
            }
        });

        return tableFacade.render(); // Return the Html.
    }

    private void export(TableFacade tableFacade) {
        tableFacade.setColumnProperties("id", "firstName", "lastName");

        Table table = tableFacade.getTable();
        table.setCaption("Persons Test");

        Row row = table.getRow();

        Column id = row.getColumn("id");
        id.setTitle("id");

        Column firstName = row.getColumn("firstName");
        firstName.setTitle("First Name");

        Column lastName = row.getColumn("lastName");
        lastName.setTitle("Last Name");

        tableFacade.render();
    }

    public String login() {
        Boolean isResponseCorrect = Boolean.FALSE;
        // remenber that we need an id to validate!
        String captchaId = getSession().getId();
        // retrieve the response
        String response = getRequest().getParameter("j_captcha_response");
        // Call the Service method
        try {
            isResponseCorrect = CaptchaServiceSingleton.getInstance()
                    .validateResponseForID(captchaId, response);
        } catch (CaptchaServiceException e) {
            // should not happen, may be thrown if the id is not valid
        }
        if (!isResponseCorrect) {
            return Action.LOGIN;
        }
        return execute();
    }

    public String save() {
        this.personService.save(person);
        this.person = new Person();
        return execute();
    }
    /**
     * 用于演示ajax方式删除操作,参看pages/list.jsp
     * @return
     */
    public String remove() {
        String deleteId = getRequest().getParameter("id");

        if (deleteId != null) {
            personService.remove(Integer.parseInt(deleteId));
        }
        this.persons = personService.findAll();
        TableFacade tableFacade = new TableFacadeImpl("tableId", getRequest());
        tableFacade.setItems(persons); // set the items
        tableFacade.setExportTypes(getResponse(), CSV, JEXCEL, PDF);
        tableFacade.setStateAttr("restore");
        Limit limit = tableFacade.getLimit();
        if (limit.isExported()) {
            export(tableFacade);
            return null;
        } else {
            String html = html(tableFacade);
            getRequest().setAttribute("myhtml", html);
        }
        return Action.SUCCESS;

    }

    public List<Person> getPersons() {
        return persons;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void prepare() throws Exception {
        if (id != null)
            person = personService.find(id);
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    /**
     * @return the personService
     */
    public PersonService getPersonService() {
        return personService;
    }

    /**
     * @param personService
     *            the personService to set
     */
    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    /**
     * @return the tableId
     */
    public String getTableId() {
        return tableId;
    }

    /**
     * @param tableId
     *            the tableId to set
     */
    public void setTableId(String tableId) {
        this.tableId = tableId;
    }

}

.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.5、Service、DAO层代码

  无特别的,省略

2.6、Model代码

 

package com.mobilesoft.esales.dao.hibernate;

/**
 * Person entity.
 * 
 * @author <a href="mailto:liangchuan@mobile-soft.cn">liangchuan</a>
 */

public class Person implements java.io.Serializable {

    // Fields

    private Integer id;
    private String firstName;
    private String lastName;
    private String url="";

    // Constructors

    /** default constructor */
    public Person() {
    }

    /** full constructor */
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Property accessors

    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    /**
     * @return the url
     */
    public String getUrl() {
        return url;
    }

    /**
     * @param url the url to set
     */
    public void setUrl(String url) {
        this.url = url;
    }

}

2.7、Struts.xml

 

.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; }

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <constant name="struts.objectFactory" value="spring"/>
    <constant name="struts.devMode" value="true"/>
    <constant name="struts.i18n.encoding" value="GBK"/>
    <constant name="struts.action.extension" value="action"/>
    <constant name="struts.custom.i18n.resources" value="ApplicationResources,errors"/>
    <constant name="struts.multipart.maxSize" value="2097152"/>
    <constant name="struts.multipart.saveDir" value="/resources"/>
    <constant name="struts.ui.theme" value="css_xhtml"/>
    <constant name="struts.enable.SlashesInActionNames" value="true"/>

    <package name="person" extends="struts-default">
     <interceptors>
            <!-- Copied from struts-default.xml and changed validation exclude methods -->
            <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servlet-config"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="debugging"/>
                <interceptor-ref name="profiling"/>
                <interceptor-ref name="scoped-model-driven"/>
                <interceptor-ref name="model-driven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="static-params"/>
                <interceptor-ref name="params">
                    <param name="excludeParams">dojo..*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">cancel,execute,delete,edit,list</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
            </interceptor-stack>
            <interceptor-stack name="fileUploadStack">
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="defaultStack"/>
            </interceptor-stack>
        </interceptors>
        <global-results>
            <result name="mainMenu" type="redirect">mainMenu.html</result>
            <result name="dataAccessFailure">pages/dataAccessFailure.jsp</result>
        </global-results>

        <global-exception-mappings>
            <exception-mapping exception="org.springframework.dao.DataAccessException" result="dataAccessFailure"/>
        </global-exception-mappings>

        <action name="list" method="execute" class="personAction">
            <result name="success">pages/list.jsp</result>
            <result name="input">pages/list.jsp</result>
        </action>

        <action name="remove" class="personAction" method="remove">
            <result name="success">pages/list.jsp</result>
            <result name="input">pages/list.jsp</result>
        </action>

        <action name="save" class="personAction" method="save">
            <result name="success">pages/list.jsp</result>
            <result name="input">pages/list.jsp</result>
        </action>

        <action name="login" class="personAction" method="execute">
            <result name="success">pages/list.jsp</result>
            <result name="login">/index.jsp</result>
        </action>

        <action name="uploadFile"
            class="com.mobilesoft.esales.webapp.action.FileUploadAction">
            <interceptor-ref name="fileUploadStack"/>
            <result name="input">pages/uploadForm.jsp</result>
            <result name="success">pages/uploadDisplay.jsp</result>
            <result name="cancel" type="redirect">/index.jsp</result>
        </action>

    </package>

</struts>

.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、jmesa研究

<TBC>

 

4、参考资料

http://code.google.com/p/jmesa/w/list

http://blog.csdn.net/jockCreate/archive/2008/02/20/2110310.aspx

http://blog.csdn.net/czg18596/archive/2007/09/06/1774827.aspx

http://www.javaeye.com/post/362673