TXWeb帮助

一.简介

TXWeb (Template XML Web 的意思)是模仿xWork的一个事件驱动构架.后期又扩展了resetfull的思想。 设计思路差不多,但运行方式完全不同,WebWork2的内核就是xWork.而TXWeb 在xWork的基础上再简化,并且完全的丢掉了JSP。 早期使用Freemarker来呈现页面,但发现Freemarker的扩展很麻烦,还有就是语法上不标准,等问题改用了scriptmark模版。 换句话就是,不需要JSP环境也可以独立的使用TXWeb 。也没有了JSP标签,完全是模板.在JAVA内部完全的生成网页后,才交给WEB服务器显示页面。 借助了Servlet 容器来提供Web请求和基本的HTTP支持.本构架必须在jdk 1.6以上版本,本构架还是遵从小而精,减少开发工作量。并且让你的代码优美。 页面生成流程图 响应流程

二. 功能及配置说明

1. 功能简介

jdk1.5的标签配置跳转,这样就可以实现零配置使用了。但从多年的开发经历来看零配置并没多大意义,项目一大标签方式会很繁琐。 反而统一配置的灵活性更好。关键的问题是,你的配置不要设计得太过繁琐,简化灵活才是关键。 也可象Struts2(webwork2)那样配置跳转,配置action映射。action的生成是sioc控制,你可以自己配置是否使用单列,Aop等。TXWeb据有拦截器等功能。 内置了ajax支持,ajax传输使用了xml,json方式,推荐使用json方式,因为javascript调用json就一家人在传输给浏览器还是一家人,都支持,很方便。 配置也支持通配符和缓存配置, 表示调用类的方法为method变量,表示调用的类方法为当前的url名,即action名。前者在后台设计上更加方便,后者能够实现resetfull方式,比较适合前台一些。 模版语言生成页面,模板的使用方法可以参见scriptmark文档。 ab dome首页并发能力测试,开发pc机cpu 1230v2,5.4版本测试
Concurrency Level:      1000
Time taken for tests:   12.706 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      1273000 bytes
HTML transferred:       1041000 bytes
Requests per second:    78.70 [#/sec] (mean)
Time per request:       12706.095 [ms] (mean)
Time per request:       12.706 [ms] (mean, across all concurrent requests)
Transfer rate:          97.84 [Kbytes/sec] received
1000并发下,每秒应答能够达到78次。
Concurrency Level:      1500
Time taken for tests:   21.376 seconds
Complete requests:      1500
Failed requests:        0
Total transferred:      1909500 bytes
HTML transferred:       1561500 bytes
Requests per second:    70.17 [#/sec] (mean)
Time per request:       21376.346 [ms] (mean)
Time per request:       14.251 [ms] (mean, across all concurrent requests)
Transfer rate:          87.23 [Kbytes/sec] received
1500并发下,每秒应答能够达到70次。 所以本框架下,性能是相当不错了。而实际应用中的性能影响往往是数据库。

2. 基本配置说明

  • 基本的sioc配置
TXWeb 调用对象都在sioc里边统一配置,sioc配置的基本结构如下,也可以看成是空项目的开始配置。 注释标签说明 文件名称:jspx.net.xml 其中的变量,如 ${xxx} 配置在 jspx.properties 文件中.
<?xml version="1.0" encoding="UTF-8"?>
<sioc namespace="global">

    <!--xweb 模板支持 begin-->
    <bean id="globalAction" class="com.jspx.txweb.support.DefaultTemplateAction" singleton="false" />
    <!--xweb 模板支持 end-->

    <!--png 附加码-->
    <bean id="validate" class="com.jspx.txweb.view.ValidateImgView">
        <int name="length">3</int>
    </bean>
    <!--png 附加码-->

    <!--连接池配置 begin -->
    <bean id="jspxDataSource" class="com.jspx.datasource.JspxDataSource" destroy="close" singleton="true">
         <string name="driverClass">${driverClassName}</string>
         <string name="jdbcUrl"><![CDATA[${jdbcUrl}]]></string>
         <string name="user">${username}</string>
         <string name="password"><![CDATA[${password}]]></string>
         <int name="maxPoolSize">${maxPoolSize}</int>
         <int name="readWrite">0</int>
     </bean>
    <!--连接池配置 end -->

    <!--Sober ORM 配置begin -->
    <bean id="jspxSoberFactory" class="com.jspx.sober.config.SoberMappingBean" singleton="true">
        <!--数据库适配器,更具数据库生成不同的SQL-->
        <string name="databaseName">${dialect}</string>
        <!--注意先后顺序如果是auto必须在setDataSource前-->
        <!--
    weblogic:javax.transaction.UserTransaction
        resin:java:comp/UserTransaction
        <string name="userTransaction">java:comp/UserTransaction</string>
        -->
        <!--是否自动提交
        <boolean name="autoCommit">true</boolean>
        -->
        <!--是否使用缓存,如果为false 将关闭所有缓存-->
        <boolean name="useCache">${useCache}</boolean>
        <!--是否显示SQL -->
        <string name="showsql">${show_sql}</string>
        <!--载入sql map-->
        <array name="mappingResources" class="string">
            <value>*.sqlmap.xml</value>
        </array>
    </bean>
    <!--Sober ORM 配置 end -->

    <!--默认载入语言和配置,每个命名空间都必须配置这部分begin-->
     <bean id="config" class="com.jspx.txweb.bundle.provider.DBBundleProvider" singleton="true">
            <ref name="soberFactory">jspxSoberFactory</ref>
            <string name="dataType">config</string>
            <string name="namespace">global</string>
     </bean>

     <bean id="language" class="com.jspx.txweb.bundle.provider.DBBundleProvider" singleton="true">
            <ref name="soberFactory">jspxSoberFactory</ref>
            <string name="namespace">global</string>
            <string name="dataType">zh_CN</string>
     </bean>
    <!--默认载入语言和配置end-->


    <!--jspx.net 默认提供 begin-->
    <bean id="genericDAO" class="com.jspx.txweb.dao.impl.GenericDAOImpl" singleton="true">
        <ref name="soberFactory">jspxSoberFactory</ref>
    </bean>
    <bean id="actionLogInterceptor" class="com.jspx.txweb.interceptor.ActionLogInterceptor" singleton="true" />

    <!--在线和用户-->
    <bean id="baseUserAction" class="com.jspx.txweb.action.BaseUserAction" singleton="false" />
    <bean id="onlineManager" class="com.jspx.txweb.online.impl.OnlineManagerImpl" singleton="true" init="init" >
        <string name="drug">www.jspx.net chenYuan</string>
    </bean>

    <!-- 包含其他sioc文件.xml 采用通配符扫描配置目录-->
    <include file="*.sioc.xml" />
</sioc>

3. 全局配置和多语言配置支持

以上配置中,config 和 language 是必须配置的,可以配置在每个命名空间,这里是一个全局的,主要作用:
  • config中保存全局的配置信息
  • language中保存全局的语言,相当于I18N作为"国际化"支持
多个语言的配置例子如下:
     <!--默认语言-->
     <bean id="language" class="com.jspx.txweb.bundle.provider.DBBundleProvider" singleton="true">
            <ref name="soberFactory">jspxSoberFactory</ref>
            <string name="namespace">global</string>
            <string name="dataType">zh_CN</string>
     </bean>
      <!--zh_CN 部分 可以是 en_US 等语言配置-->
  • action 中默认有两个ioc载人注释标签
    protected Bundle language;
    @Ref(name = "language", test = true)
    public void setLanguage(Bundle language) {
        this.language = language;
    }

    protected Bundle config;
    @Ref(name = "config", test = true)
    public void setConfig(Bundle config) {
        this.config = config;
    }
这两个配置会自动载入,在action中可以直接使用变量,config和language 得到相关数据 本构架提供下边两个类来帮助你保存配置
  • com.jspx.txweb.bundle.action.EditConfigAction
  • com.jspx.txweb.bundle.action.EditLanguageAction
程序会根据你提交的数据,使用变量名对应值的关系就会保存在数据库中。

4. TXWeb配置结构

  • 这是一个基本的空配置
include 将会搜索默认目录,载入*.txweb.xml的所有文件
<?xml version="1.0" encoding="UTF-8"?>
<txweb>
    <package namespace="global">
        <action name="*" class="globalAction"/>
    </package>
    <!-- 包含其他txweb文件.xml 采用通配符扫描配置目录-->
    <include file="*.txweb.xml" />
</txweb>
特殊情况说明,一个类可以支持多个页面,也会和多个url映射,所以名称可以使用标准表达式。 另外注意method="@method",是动态配置执行方法的方式,提交的变量method,的变量值就是要调用的方法。 这里和stucts2是不同的。
<action name="\w+stepwise|designer|多个文件名" class="stepwiseAction"  method="@method"/>
另外配置一个命名空间,actrion名称配置为'*',就是 resetfull 方式的URL了。 下边是一个模板说明 jcms/vote 表示根目录下的目录 jcms是父子目录关系,在软件结构上就形成了继承关系。 interceptor-ref 配置拦截器和stucts2拦截器差不多。
<package name="vote" extends="jcms" namespace="jcms/vote">
        <default>
            <interceptor-ref name="defInterceptor"/>
            <result name="返回标识" type="redirect">url</result>
        </default>
    <!--这部分以后扩展出来 -->
        <action name="这里支持通配符" class="sioc中的bean名称" pass="要跳过的拦截器" secret="true强制ROC加密方式" mobile="true手机设备会载入手机模板" />
        <param name="参数设置方法">参数</param>
            <result name="*">模板文件名</result>
        <!-- 没有指定类型表示模板文件,讲话自动读取这个模板 -->
       <result name="返回标识" type="redirect">URL地址</result>
        </action>
    </package>
mobile为true的时候,会判断是否为手持设备,如果是手机,那么会载入 actionName.mobile.ftl 的模板. 输出编码控制,例如,你想使用本引擎输出 js,xml等html头不痛的文件,你可以将文件命名为 jsname.js.ftl,xmlname.xml.ftl 这样使用url打开jsname.js.jhtml,xmlname.xml.jhtml 的时候就会自动为你设置请求头的文件类型。

5. Action说明

Action是完成动作和显示类总称,需要继承ActionSupport,ActionSupport提供了必要的功能支持;其中包含了上边提到的全局配置和语言支持; 下边说明一下继承ActionSupport后的函数方法用途
    //这两个是为了提示信息 可以在页面上显示 begin
    /*

      添加提示信息或者错误错误类型
      @param keys 错误类型
      @param msg 信息
     */
    public void addFieldInfo(String keys, String msg);

    /*
     @param msg 说明信息,用于成功的提示信息
     */
    public void addActionMessage(String msg);
     //这两个是为了提示信息 end


    //这两个是为了保存日志begin

    /*
     可以通过getActionLog()取出日志对象存到数据库 
     @param value 放入日志标题
     */
    public void setActionLogTitle(String value);

    /*
     @param value 放入日志日志信息
     */
    public void setActionLogContent(Serializable value);

    /*
         在拦截器里边放入如下代码就可以保存日志了
         actionLog.setCaption(actionInvocation.getCaption());
         actionLog.setClassMethod(operation);
         actionLog.setMethodCaption(TXWebUtil.getMethodCaption(getClass(),operation));
         actionLog.setNamespace(JUWebIoc.namespace);
         actionLog.setPutName(userSession.getName());
         actionLog.setPutUid(userSession.getUid());
         @return 得到一个已经初始化好的动作日志
     */
    public ActionLog getActionLog();


    ////这两个是为了保存日志 end

     /*
     @return 得到用户在线信息
     */
    public Object getUserSession();


    /*
      @return 防止重复提交,在提交的参数中放入这个值,名称为formHash
      提交后可以使用 isRepeatPost() 方法判断是否为重复提交
     */
    public long getFormHash(); 


     /*
      @return 返回模版文件的当前路径
     */
    public String getTemplatePath();

    /*
      @return 返回模版文件的名称,只有文件名称
     */
    public String getTemplateFile();

     /*
     @param actionResult 放入 execute 返回标识
     */
    public void setActionResult(String actionResult);

    /*
      @param value 当返回为excel等需要处理的数据是,这里放入数据 对象
     */
    public void setResult(Object value);
系统默认提供的返回表示
返回标识 说明
NONE 什么也不处理 并且不会调用 Redirect
ERROR 发生错误
INPUT 录入信息不完整,返回显示警告说明
LOGIN 跳转到登录,表示当前没有登录
PASSWORD 需要密码
UNTITLED 无权利的浏览或者动作
MESSAGE 返回信息,在ajax方式的提示上用得多
TEMPLATE 返回信息 ,最基本的模板返回
HtmlImg html转换为png 图片
HtmlPdf html转换为pdf文档,需要IText等包支持
Markdown 将Markdown文件转换为html显示
chain 级联方式,联系的执行下一个动作页面
ROC 5.4后提供的ROC协议,返回json或XML
细节说明,表示执行的为result模版,URL保持不变, action 中可以通过,getActionUrlNumber() 得到r-后边的数字,满足restFul需求,这种方式主要用作不同id显示不同的内容
        <action name="r-\S*" class="actionName" >
            <result name="*">result.${templateSuffix}</result>
        </action>
表示级联方式,得到的actionName为result,action.getActionUrlNumber() 不能得到级联前一级的url
        <action name="r-\S*" class="actionName" >
            <result name="*" type="chain">result.${templateSuffix}</result>
        </action>

5. Action中可使的变量

struts2中获得request对象: 这种方式主要是利用了com.opensymphony.xwork2.ActionContext类以及org.apache.struts2.ServletActionContext类,具体的方法如下所示。
A.    HttpServletRequest request = ServletActionContext.getRequest ();
B.    ActionContext ct= ActionContext.getContext()
    HttpServletRequest request=(HttpServletRequest)ct.get(ServletActionContext. HTTP_REQUEST); 
继承ActionSupport后一下几个变量就可以使用了:
名称 说明
session web session
request web request
response web response
config 全局配置
language 语言(根据命名空间确定)
如果要得到提交的变量,直接取就可以了,比上边的struts2简洁了很多。
String str = getString("变量名称");
int num =getInt("变量名称");
Date data =getDate("变量名称");

三.简单例子

  • 一个简单的hello world例子
本例子可以在发布的war包里边找到代码WEB-INF/src目录 例子里边有两个hello,下边可以看看如何分别得到.通过这个例子我们可以明白页面如何同bean进行交互数据.
< code >文件:com.jspx.example.HelloWordAction.java
    import com.jspx.txweb.support.ActionSupport;  
    @HttpMethod(caption="hello例子")
    public class HelloWordAction extends ActionSupport  
    {  
        public HelloWordAction()  
        {  

        }  

        public String getHello()  
        {  
            return "use bean get Method get Hello Word ";  
        }  

        public String execute() throws Exception  
        {  
            put("hello", "use put var get Hello Word");  
            return SUCCESS;  
        }  
    }  
sioc 配置文件,方便注入TXWeb中使用,当然也可以不配置这一步,直接在TXWeb中直接配置,但推荐这样做.
    <bean id="helloWordAction" class="com.jspx.example.HelloWordAction" singleton="false" />  
这里是TXWeb的配置文件.
    <action name="hello" class="helloWordAction" />
< code >helloWordAction 为sioc中bean id,配置完成后我们可以看看如何调用配置的这个bean. 我们可以在根目录下创建一个hello.ftl的文件.(模板文件可以配置为不同的后缀). 
但名称要和上边的hello一样,这样TXWeb就会自动的载入这个helloWordAction这个bean. 默认名称为 action 运行一样就能够看到效果了. 模版文件 hello.ftl
     调用put进来的数据:${hello}  
     调用getHello方法返回数据:${action.hello}  
运行程序后我们就可以看到 ${hello} 变成我们的数据了。 其他情况说明:我们有时候文件名称不一定和上边的action name一样,这时候要载入声明载入对象bean.你可以使用下边的方法.
    <#assign x="hello".action()/> <!--x为当前页面的bean名称,action() 表示从TXWeb中取出对象-->  
    <#assign x="helloWordAction".sioc() /> <!--x为当前页面的bean名称,sioc() 表示从Sioc中取出对象-->      
    <!--为了XML的格式更加规范你可以用如下方式,更加标准-->      
    <#assign x='"hello".action()'/>   
    <#assign?x='"helloWordAction".sioc()' />   
  • action参数说明
参数结构形式:"txweb中的配置名称".action(参数1,参数2,....)
  1. 参数1 :是否载入request参数.
  2. 参数2 :是否要执行一个方法。
  3. 其他参数,使用文本方式可以设置方法例如要设置 setOld(int old) 方法可以这样写, "old=12", 命名空间,也是文件放置的目录,对应的action也在sioc对应的命名空间查找,如果找不到在再sioc的global 命名空间查找。 也许你感觉对应的逻辑关系有点复杂,不过就是应为这样的逻辑对应关系存在,才大大的简化了代码。 我在描述一下,目录名称就是命名空间,在你的sioc,和txweb配置中都要对应,url的文件名称就是你配置的实例名称,这样当你通过浏览器打开一个url页面的时候, 程序就能够通过这个对应关系找到你要执行的程序,并且执行后返回数据给页面。

四.翻页的例子

翻页有两种请求方式:
  1. 是传统的使用url参数来分页,就是如下这种,@TurnPage 标签来显示。
  2. 使用ajax方式让表格或者ajax请求得到jso数据,分页显示。更多工作是ajax控件来完成。
@TurnPage 标签是第一种方式
    import com.jspx.txweb.support.ActionSupport;  
    import com.jspx.txweb.annotation.TurnPage;  

    import java.util.List;  
    import java.util.ArrayList;  

    /** 
     * 演示翻页 
     */  
    public class TurnPageListAction extends ActionSupport  
    {  
        private static List<String> list = new ArrayList<String>();  
        static  
        {  
            list.add("one");  
            list.add("two");  
            list.add("three");  
            list.add("four");  
            list.add("five");  
            list.add("six");  
            list.add("seven");  
            list.add("eight");  
            list.add("nine");  
            list.add("ten");  
            list.add("1");  
            list.add("2");  
            list.add("3");  
            list.add("4");  
            list.add("5");  
            list.add("6");  
            list.add("7");  
            list.add("8");  
            list.add("9");  
        }  
        public TurnPageListAction()  
        {  

        }  

        /** 
         * 数据库的查询列表 
         * 
         * @return 
         */  
        public List<String> getDataBaseList()  
        {  

            return list;  
        }  

        public int getCurrentPage()  
        {  
            return currentPage;  
        }  

        public void setCurrentPage(int currentPage)  
        {  
            this.currentPage = currentPage;  
        }  

        private int currentPage = 1;  

        /** 
         * 如果使用sober 查询数据库的话,sober 已经为为你完成了,页面计算,还会更加简单一些
         */  
        public List<String> getList()  
        {  
            if (currentPage <= 0) currentPage = 1;  
            int ibegin = currentPage * getDefalutRows() - getDefalutRows();  

            List<String> list = new ArrayList<String>();  
            List<String> databaseList = getDataBaseList();  
            for (int i = ibegin; i < ibegin + getDefalutRows() && i < databaseList.size(); i++)  
            {  
                list.add(databaseList.get(i));  
            }  
            return list;  
        }  

        /** 
         * 得到总行数 
         */  
        public int getTotalCount()  
        {  
            return getDataBaseList().size();  
        }  

        /** 
         * 一页显示行数 
         */  
        public int getDefalutRows()  
        {  
            //实际使用的时候可以从配置文件中得到,用户就可以自己定义每页显示记录条数
            return 4;  
        }  

        private String turnPage = "";  
        //file=翻页模版(默认turnpage.ftl),params=翻页记录的参数多个使用分号隔开,totalCount=得到总行数的方法,rows=一页行数的方法,enable表示是否运行翻页处理,可以动态设置
        //@表示从本类,方法中得到数据.  
        @TurnPage(params="", totalCount = "@totalCount", rows = "@defalutRows", enable = "true")  
        public void setTurnPage(String turnPage)  
        {  
            //TXWeb会根据你的模板生成翻页代码保存在这里  
            this.turnPage = turnPage;  
        }  

        public String getTurnPage()  
        {  
            return turnPage;  
        }  

        public String execute() throws Exception  
        {  
            return SUCCESS;  
        }  
    }  
Sioc配置如下
    <bean id="turnPageListAction" class="com.jspx.example.TurnPageListAction" />  
TXWeb配置如下
    <action name="turnlist" class="turnPageListAction"/>  
页面代码文件turnlist.ftl
    <h1> Template turn pageList</h1>  
    <hr />  
    <#list v=action.list >  
        <li>${v}</li>  
    </#list>  
    <br/>  
    <div class="turnPage">${action.turnPage}<div>
好了, 到这里一个翻页的例子已经完成。运行后就能够得到一个简单的列表页面,还有翻页按钮
<#list v=action.list >  
        <li>${v}</li>  
</#list>  
这里是模版循环,也就是for语句

五.拦截器的例子

下边在做一个拦截器的例子,相对复杂些,演示了拦截器的执行流程和作用。 fmAction.java 文件完成显示的对应动作,得到显示数据
    import com.jspx.txweb.support.TemplateSupport;  
    import java.util.Map;  
    import java.util.List;  
    import java.util.ArrayList;  

    public class fmAction extends TemplateSupport  
    {  
        public fmAction()  
        {  

        }  
        private String text = "xxx";  

        public String getText()  
        {  
            return text;  
        }  
        public void setText(String text)  
        {  
            this.text = text;  
        }  
        public String execute() throws Exception  
        {  
             //得到环境值Map  
             //放入显示模板中你要的数据  
             put("hello","123456789");  
             put("ok","成功");  
             return NONE;  
        }  
    }  
拦截器代码FmInterceptor.java
    import com.jspx.txweb.ActionInvocation;  
    import com.jspx.txweb.interceptor.InterceptorSupport;  
    import org.apache.commons.logging.Log;  
    import org.apache.commons.logging.LogFactory;  

    public class FmInterceptor extends InterceptorSupport  
    {  
        private static final Log log = LogFactory.getLog(FmInterceptor.class);  
        public void destroy()  
        {  
            log.debug("--------FmInterceptor-----destroy");  
        }  

        public void init()  
        {  
            log.debug("--------FmInterceptor-----init");  
        }  

        public String intercept(ActionInvocation actionInvocation) throws Exception  
        {  
            log.debug("--------FmInterceptor-----intercept");  
            //执行下一个动作,可能是下一个拦截器,也可能是action取决你的配置  
            return actionInvocation.invoke();  
            //也可以 return Action.ERROR; 终止action的运行  
        }  
    }  
Sioc配置
    <!--可以给TXWeb调用-->  
    <bean id="fmAction" class="com.jspx.txweb.test.fmAction" singleton="false">  
    </bean>  
    <!--TXWeb的拦截器,原理和webwork2一样,在Action之前拦截-->  
    <bean id="fmInter" class="com.jspx.txweb.test.FmInterceptor" singleton="true">  
    </bean>  
TXWeb配置
    <action id="fm" class="fmInter">  
     <interceptor-ref name="userInterceptor"/>  
    </action>  
当我们在访问fm.ftl页面的时候就可以拦截了,日志里边会打印出执行的顺序,这一步我们在拦截器里边可以完成权限,日志等功能比较方便.

六.动作跳转

下边我们做一个按钮的例子,并且可以使用标签,点击不同的动作后,执行不同动作的程序 ResultTestAction.java
    import com.jspx.txweb.annotation.Redirect;  
    import com.jspx.txweb.annotation.Operate;  
    import com.jspx.txweb.annotation.Safety;  
    import com.jspx.txweb.support.ActionSupport;  
    import com.jspx.io.WriteFile;  
    import com.jspx.io.AbstractWrite;  
    import com.jspx.utils.DateUtil;  
    import java.util.List;  
    import java.util.ArrayList;  

    public class ResultTestAction extends ActionSupport  
    {  
        public ResultTestAction()  
        {  

        }  

        private String url = null;  

        public String getUrl()  
        {  
            return url;  
        }  

        @Safety //当请求载入的时候屏蔽sql注入  
        public void setUrl(String url)  
        {  
            this.url = url;  
        }  

        public List<String> getList()  
        {  
            List<String> list = new ArrayList<String>();  
            list.add("one");  
            list.add("two");  
            list.add("three");  
            list.add("four");  
            list.add("five");  
            list.add("six");  
            list.add("seven");  
            list.add("eight");  
            list.add("nine");  
            list.add("ten");  
            return list;  
        }  

        //表示完成这个方法后将跳转到edit.html页面      
        @Redirect(contentType = "text/html; charset=UTF-8", location = "edit.html")  
        //表示提交按钮名称为operation,值为edit的时候执行这个方法.  
        @Operate(caption = "编辑")  
        public void edit()  
        {  
            AbstractWrite aw = new WriteFile();  
            aw.setEncode("UTF-8");  
            aw.setFile(getTemplatePath() + "edit.html");  
            aw.setContent("<hr>edit Operate and Redirect edit.html time is " + DateUtil.getDateTimeST(), false);  
        }  


        @Redirect(contentType = "text/html; charset=UTF-8", location = "save.html")  
        @Operate(caption = "保存")  
        public void save()  
        {  
            AbstractWrite aw = new WriteFile();  
            aw.setEncode("UTF-8");  
            aw.setFile(getTemplatePath() + "save.html");  
            aw.setContent("<hr>save Operate and redirect to file,time is " + DateUtil.getDateTimeST(), false);  
        }  

        public String execute() throws Exception  
        {  
            put("helloList", getList());  
            return SUCCESS;  
        }  
    }  
下边例子不使用sioc配置,直接配置TXWeb来演示,实际使用中推荐使用sioc. TXWeb直接配置例子如下,type="class"表示是直接载入class不是从sioc中载入, 默认sioc中载入.注意@operation,和上边代码的@Operate(caption = "保存")部分对应表示提交按钮的名称为method, 当提交的value为Operate的数值value相等的时候执行这个方法.如果不没有value表示. 不为空就执行这个方.
    <action name="testh" class="com.jspx.example.ResultTestAction" type="class" method="@method" >  
    </action>  
调用代码为 testh.ftl 点击按钮后会生成页面,并跳转到页面。
    <h5>演示按钮触发同一个action里边不同的方法</h5>  
    <p>本构架MVC 概念中 Action 和 View 可以在同一个bean中。</p>  
    <p>这是同一个bean中显示列表,同时提供两个不同的动作</p>  
    <p>点击按钮后显示的也慢是动态生成的。</p>  

    <#list m=helloList >  
    <li>${m}</li>  
    </#list>  
    <br />  
    <form method="post">  
    run com.jspx.example.ResultTestAction  save Method:  
    <input name="method" type="submit" value="save" />  
    <br />  
    <br />  
    run com.jspx.example.ResultTestAction edit Method:  
    <input name="method" type="submit" value="edit" />  
    </form>  

七.模版页面

在这里新建一TestBean来演示模板标签的反向请求变量
    package com.jspx.txweb.test;  

    public class TestBean  
    {  
        private String name;  
        private int old;  

        public String getName()  
        {  
            return name;  
        }  

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

        public int getOld()  
        {  
            return old;  
        }  

        public void setOld(int old)  
        {  
            this.old = old;  
        }  
    }  
注意:前边我们的配置是,,*表示如果没有找到对应的action 名,就调用fmAction这个sioc的 test.html,这里也可以使用标准表达式 test/test.html 目录和上边的命名空间对应,虽然不一定要求对应 载入的时候同样使用 <#assign testBean="fmAction@test"sioc()/> @前表示sioc中配置的名称.后边表示命名空间. 如果没有写命名空间的话将使用当前的目录作为命名空间,如果也没有,最后在global命名空间里边查找.
    <p>  
    这里输出一段文字<br/>  
    测试输出:${hello}  
    </p>  
    <p>  
    这里载入sioc中配置的bean,可以使用<br/>  
    <#assign testBean="fmAction".ioc()/>  
    ${testBean.setName("名称")}  
    ${testBean.setOld(40)}  
    载入bean的使用<br/>  
    name:${testBean.name}<br/>  
    old:${testBean.old}  
    </p>  

    本页对应action调用:${action.setText("西西")}  
    调用输出:${action.text}<br/>  
    调研完成:${ok}<br/>  
运行程序web服务器,根据你的配置 suffix=htm 输入URL地址,默认是htm,(注意如果有apache,你必须配置让apache 让出htm的请求转给java web服务器,比如resin) 例如地址 http://localhost:8080/txweb/test.htm 你就看到下边的效果了。
    这里输出一段文字
    测试输出:123456789

    这里载入其他的bean,也可以是sioc中配置的bean
    name:名称
    old:40
    本页对应action调用: 调用输出:西西
    调研完成:成功
看看生成的HTML代码 ,是不是很干净漂亮。

八.annotation参数说明

annotation标签名称 参数说明 功能说明 MulRequest 上传二进制配置
String covering() default "false"; //是否覆盖文件

String saveDirectory() default "saveDirectory"; //保存目录

String fileTypes() default "*"; //允许上传的文件类型

String maxPostSize() default "800944751"; //最大上传限制
上传方式,本构架已经整合上传组件,并且解决了中文问题,兼容flash和kindeditor的上传。 Operate 动作控制,常用
//提交按钮名称
String submit() default "";

//是否需要提交后在执行 POST 提交才执行 GET不需要提交
boolean submit() default true;

//和 hasFieldInfo 相等的时候运行,fieldInfo保存错误信息
boolean hasFieldInfo() default false;

//动作名称描述
String caption() default "";
动作绑定bean方法
Redirect 跳转配置,推荐使用xml配置,不常用
//跳转方式 location redirect forward
String type() default "redirect";

//text/html; charset=UTF-8
String contentType() default "";

String location() default "";
页面跳转,如果存在跳转将不执行其他任何方法包括execute Safety 判断是否过滤sql注入
     /只对integer long 类型有效
    int min() default -1;

     //只对integer long 类型有效
     int max() default 100;

     //如果不完全设置为"",如果为false 也将过滤不安全字符后载入
     boolean empty() default true;

    //分三个安全等级,1:表示基本的特殊字符<>,2:表示特殊的sql关键字和html特殊脚本;3:html中的脚本和事件
    //上一级包含了下一级;例如3里边包含了2和1
    //没有判断到的特殊支付可以中evasive里边配置,统一过滤
    int level() default 1;

    //接受外部参数字符串的最大长度
    int length() default 200;

    //是否接受请求方式的参数
    boolean request() default true;
例如,配置在set方法接收外部参数
    @Safety(empty = false)
    public void setTerm(String term) {
        this.term = term;
    }
TurnPage 翻页标签 翻页标签是传统翻页方式的支持,如果使用ajax,滚动加载等方式可不使用
    //模版文件名 turnpage.ftl
    String file() default "turnpage.ftl";
    //得到总行数方法        
    String totalCount() default "@totalCount";
    //默认行数
    String rows() default "@count";

    //翻页的脚本模板
    String currentPage() default "@currentPage";

    //显示个数
    String count() default "@count";

    //翻页按钮个数
    String bound() default "3";

    //请求中的参数querystring,翻页会自动保留
    String params() default "";

    //开关,为了提高性能,提交时候传递参数判断释放运行,当为auto的时候 如果action name 里边有list 就为true
    //正规表达式使用 [ ] 挂起表示
    String enable() default "[list|\\w+list|list\\S*]";
使用方法
    @Safety(request = false)
    @TurnPage(params = "field;find;matterId;term;replyType")
    public void setTurnPage(String turnPage) {
        this.turnPage = turnPage;
    }

    public String getTurnPage() {
        return turnPage;
    }
turnpage.ftl 翻页模板例子
<ul class="pagination">
<#if where="currentPage!=1" >
<li><a href="?currentPage=1<#if querystring!=''>&${querystring}</#if>" >1</a></li>
   <#else>
    <li class="currentState">1</li>
   </#else>
</#if>

<#if where="currentPage gt 1" >
    <li class="previousPage"><a href="?currentPage=${currentPage-1}<#if where=querystring >&${querystring}</#if>">上一页</a></li>
</#if>

<#list i=beginPage..endPage equals="false" >
  <#if where="i==currentPage">
   <li class="currentState">${i}</li>
  <#else>
       <li><a href="?currentPage=${i}<#if where=querystring>&${querystring}</#if>">${i}</a></li>
  </#else>
  </#if>
</#list>

<#if where="totalPage gt currentPage" >
 <li class="nextPage"><a href="?currentPage=${currentPage+1}<#if where=querystring>&${querystring}</#if>" >下一页</a></li>
 <!--# li class="noStyle">..</li #-->
</#if>
 <li><a href="?currentPage=${totalPage}<#if where=querystring>&${querystring}</#if>" title="每页显示${count}条,共${totalPage}页">${currentPage}/${totalPage}</a></li>
</ul>
翻页标签,使用的本页输出标签 如果使用本UI的grid,ajax调用可以不使用本标签 Validate 数据验证标签
//返回信息类型
String infoType() default ValidatorDataType.all;
//formId 校验Form ID

String formId() default "";
//sioc id

String name() default "";
//sioc 命名空间

String namespace() default Sioc.global;
//配置文件

String submit() default "submit";
验证,本验证使用在页面上和sober的验证java代码核心是一个, 但有所不同,这里的使用javascript完成的。 name为验证bean.
<bean id="validator" class="com.jspx.txweb.support.ValidatorAction" singleton="true">
<string name="configFile">验证配置文件.validator.xml</string>
</bean>
validator.xml 验证文件例子 roleForm 是提交的from id 号
 <validator formId="roleForm">
        <validation dataType="isLengthBetween(1,50)" field="name" noteId="nameMsg" needed="true">
            <note>角色名称。</note>
            <error>必须填写1-50个字符</error>
        </validation>
        <validation dataType="isBetween(0,102400)" field="uploadSize" noteId="uploadSizeMsg" needed="false">
            <note>单位为K</note>
            <error>单位为K,范围0-102400,最大1G</error>
        </validation>
</validator>

九.上传文件

1. 基本原理

要上传文件的时候继承extends MultipartSupport, 并且留出multipartRequest的接口就可以了。 下边的代码已经在构架中,当理有其他需求的时候,可以自己继承后设置相关参数就可以了。 DefaultUploadAction.java
    import com.jspx.upload.MultipartRequest;  
    import com.jspx.upload.UploadedFile;  
    import com.jspx.txweb.annotation.MulRequest;  
    import com.jspx.txweb.annotation.Operate;  

    public class DefaultUploadAction extends MultipartSupport  
    {  

        //看annotation表中相关参数说明,这里是MultipartSupport的入口  
        @MulRequest(covering = "false", saveDirectory = "@saveDirectory", fileTypes = "@fileTypes", maxPostSize = "@maxPostSize")  
        public void setMultipartRequest(MultipartRequest multipartRequest)  
        {  
            this.multipartRequest = multipartRequest;  
        }  

        //当点击submit按钮后执行这个动作  
        @Operate(submit = "submit")  
        public void save() throws Exception  
        {  
            for (UploadedFile uploadFile : multipartRequest.getFiles())  
            {  
                addActionMessage("upload file:" + uploadFile.getFileName() + "  form " + uploadFile.getOriginal());  
            }  
        }  

        public String execute() throws Exception  
        {  
            return SUCCESS;  
        }  
    }  
下边我们看看sioc配置 com.jspx.txweb.support.UploadStatusAction 是TXWeb中已经有的,主要是得到上传时候的流量信息。 能够实现ajax方式显示上传进度条。这里查看 swfupload 上传,本上传组件能很好的支持。
       <bean id="uploadAction" class="com.jspx.txweb.support.DefaultUploadAction">  
       </bean>  
    <bean id="uploadStatus" class="com.jspx.txweb.support.UploadStatusAction">  
       </bean>  
下边是TXWeb的配置代码
    <action name="upload" class="uploadAction" method="save" />
下边为页面代码upload.jsp 如果想通过ajax得到上传状态信息,可以看演示。
    <form name="form1" method="post" enctype="multipart/form-data" action="upload.${suffix}" >  
       上传文件保存目录:d:/upload 你可以在ioc中配置 saveDirectory,或者继承extends MultipartSupport动态配置<br>  
        是否覆盖:covering<br>  
        大小限制:maxPostSize<br>  
        文件类型:fileTypes,多个使用分号分割<br>  
        <p>  
            <input name="file" type="file">  
        </p>  

        <p>  
            <input name="file" type="file">  
        </p>  

        <p><input name="file" type="file">  
        </p>  
        <p>  
            <input type="submit" name="submit" value="上传">  
        </p>  
    </form>  
上边是一个简单的演示,和基本的上传方式差不多。内置的上传组件支持swfupload,百度上传,plupload含分包上传功能。 下边是plupload上传的js,url就是上传的入口地址
   //缩图上传begin
   var thumbnailUploader = new plupload.Uploader({
       browse_button : 'thumbnailPickfiles', // you can pass an id...
       container: document.getElementById('thumbnailContainer'), // ... or DOM Element itself
       url : "${request.get('requestURIPath')}upload.${suffix}",
       flash_swf_url : '/script/plupload/Moxie.swf',
       silverlight_xap_url : '/script/plupload/Moxie.xap',
       console:'thumbnailConsole',
       resize : {
                 width : ('${Config.getInt("maxImageWidth",1280)}'-0),
                 height : ('${Config.getInt("maxImageHeight",1280)}'-0),
                 quality : 90,
                 crop: true // crop to exact dimensions
       },
       filters : {
            max_file_size : '${role.uploadImageSize}kb',
            mime_types:JSON.decode('${role.getJsonUploadTypes("image")}')
       },
       auto_start: true,
       multi_selection: false,
       init: {
           PostInit: function() {
               $('images').addEvent('change', function() {
                   $('imagesBox').src = this.get('value');
               });
           },
           BeforeUpload:function(up,file){
              this.setOption("multipart_params",
              {
                "thumbnail" :  $('thumbnail').checked?1:0,
                "sessionId" : "${IUserSession.sid}"
              });
            }, 
           FilesAdded: function(up, files) {
              this.start();
           },
           UploadProgress: function(up, file) {

               pluploadPrintProgress(up,file);              
           },
           FileUploaded: function(up, file, result) {
               var son = JSON.decode(result.response);
               if (son.success)
               {
                   if (son.thumbnail)
                   {
                       $('images').set('value',son.thumbnailUrl);
                       $('imagesBox').set('src',son.thumbnailUrl);
                   } else {
                       $('images').set('value',son.url);
                       $('imagesBox').set('src',son.url);
                   }
                   pluploadPrintInfo(up,file,son);
               }
               else if(son.error) alert(son.message);
           },
           Error: function(up, err) {
              pluploadPrintError(up,err);
           }
       }
   });
    thumbnailUploader.init();
   //缩图上传end

2. 正式使用例子

下边的“softName” 表示软件命名空间. sioc 配置
        <!--上传 begin-->
        <bean id="uploadFileDAO" class="com.jspx.txweb.dao.impl.TemplateDAOImpl" singleton="true">
            <ref name="soberFactory">jspxSoberFactory</ref>
            <string name="namespace">softName</string>
            <string name="className">corp.softName.table.UploadFile</string>
        </bean>
        <bean id="uploadFileView" class="com.jspx.txweb.view.UploadFileView" singleton="false" >
            <ref name="uploadFileDAO">uploadFileDAO</ref>
        </bean>
        <bean id="uploadFileJsonView" class="com.jspx.txweb.view.UploadFileJsonView" singleton="false"/>
        <bean id="uploadFileAction" class="com.jspx.txweb.action.UploadFileAction" />
        <bean id="downloadFileView" class="com.jspx.txweb.view.DownloadFileView" singleton="false" />
        <bean id="uploadFileManageAction" class="com.jspx.txweb.action.UploadFileManageAction" singleton="false">
            <ref name="uploadFileDAO">uploadFileDAO</ref>
        </bean>
        <!--上传 end-->
corp.softName.table.UploadFile.java Table注释,表示数据库表名称test_upload_file,上传成功后会记录上传信息在这个表里边
@Table(name="test_upload_file",caption="附件")
public class UploadFile extends IUploadFile
{
    public UploadFile()
    {

    }
}
TXweb 配置
        <!--Upload begin-->
    <!--是提供给 kindeditor或者ajax处理-->
        <action name="uploadfilejson" class="uploadFileJsonView" />
    <!--kindeditor或者ajax处理-->
        <action name="selectfile" class="uploadFileView" >
            <param name="pageEnable">true</param>
        </action>
    <!--上传接口-->
        <action name="upload" class="uploadFileAction"  />
    <!--上传后的文件管理-->
        <action name="uploadfilemanage" class="uploadFileManageAction" method="@method"/>
        <!--Upload end-->
上传环境参数说明 参数配置在config 中,上传会自动读取配置的信息:
变量名称 说明
uploadPath 上传保存路径
setupPath 软件的安装路径
uploadCovering 同名文件是否覆盖
allowedTypes 允许上传的文件类型*允许所有文件,多个使用分号分割
uploadMaxSize 允许上传的文件大小, 单位k
调用定义的配置,config.getString(Environment.UploadPath) ,config.getString(Environment.SetupPath) 上传返回为json格式,变量说明:
变量名称 说明
fileName 上传保存路径
name 文件名称,不保护后缀
url 预览地址
original 文件来源名称
dir 目录
namespace 命名空间
success 是否成功 bool类型
info 错误信息
你可以更具返回,实现自己的特殊要求 KindEditor调用例子
        var editor = KindEditor.create('textarea[name="content"]',{
                sessionId : "${session.get('id')}",
                allowFileManager : true,
                uploadJson : '${request.get("requestURIPath")}upload.${suffix}',
                fileManagerJson : '${request.get("requestURIPath")}uploadfilejson.${suffix}',
                afterCreate : function() {
                    var self = this;
                    KindEditor.ctrl(document, 13, function() {
                        self.sync();
                        document.forms['aematterForm'].submit();
                    });
                    KindEditor.ctrl(self.edit.doc, 13, function() {
                        self.sync();
                        document.forms['aematterForm'].submit();
                    });
                }
        });
swfupload 方式上传例子
 var settings = {  
        flash_url : "${scriptPath}/swfupload/swfupload.swf",  
        upload_url: "${request.get('requestURIPath')}upload.${suffix}",  
        file_size_limit : "10 MB",  
        file_types : "*.jpg;*.jpeg;*.gif;*.bmp,*.png;",  
        file_types_description : "*.jpg;*.jpeg;*.gif;*.png;*.bmp",  
        file_upload_limit : 2,  
        file_queue_limit : 0,  
        post_params : {  
            "thumbnail" :1, //1:表示生成缩图  
            "sessionId" : "${session.get('id')}"  
        },  
        custom_settings : {  
            progressTarget : "fsUploadProgress"  
        },  
        debug: false,  
        auto_upload:true,  

        // Button settings  
        button_width: "48",  
        button_height: "24",  
        button_image_url : "${scriptPath}/swfupload/images/BlackImageNoText_65x29.png",  
        button_placeholder_id: "spanButtonPlaceHolder",  
        button_text: '<span class="theFont">选择</span>',  
        button_text_style: ".theFont {font-size: 12;}",  
        button_text_left_padding: 12,  
        button_text_top_padding: 3,  

        input_id:'images',     //输入框  
        status_id:'divStatus', //状态框  

        // The event handler functions are defined in handlers.js  
        file_queued_handler : fileQueued,  
        file_queue_error_handler : fileQueueError,  
        file_dialog_complete_handler : fileDialogComplete,  

        upload_start_handler : uploadStart,  
        upload_progress_handler : uploadProgress,  
        upload_error_handler : uploadError,  
        upload_success_handler : uploadSuccess,  
        upload_complete_handler : uploadComplete,  
        queue_complete_handler : queueComplete, // Queue plugin event  
        swfupload_pre_load_handler : swfUploadPreLoad,  
        swfupload_load_failed_handler : swfUploadLoadFailed  
};  
new SWFUpload(settings);  
KindEditor 输入框参数,input_id:'editor'
  • 以下为上传接口代码,方便你理解
package com.jspx.txweb.action;

import com.jspx.boot.environment.Environment;

import com.jspx.json.JSONArray;
import com.jspx.lucene.ChineseAnalyzer;
import com.jspx.json.JSONObject;
import com.jspx.sioc.annotation.Ref;
import com.jspx.txweb.IUserSession;
import com.jspx.txweb.annotation.HttpMethod;
import com.jspx.txweb.annotation.MulRequest;
import com.jspx.txweb.annotation.Operate;
import com.jspx.txweb.annotation.Safety;
import com.jspx.txweb.bundle.Bundle;
import com.jspx.txweb.dao.UploadFileDAO;
import com.jspx.txweb.support.MultipartSupport;
import com.jspx.txweb.table.IUploadFile;
import com.jspx.txweb.util.RequestUtil;
import com.jspx.upload.MultipartRequest;
import com.jspx.upload.UploadedFile;
import com.jspx.util.StringMap;
import com.jspx.utils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;

/**
 * Created by IntelliJ IDEA.
 * User: chenYuan
 * Date: 12-2-4
 * Time: 下午7:48
 * 上传机制说明, 如果采用flash方式上传,每次只是一个上传文件,
 * 如果使用html type=file 方式上传,为多个文件方式
 * 上传后的图片会自动压缩
 * <p/>
 * 图片 如果大于最大宽度就缩小到最大宽度,这张图作为原图
 * 1.参数thumbnail  成立,生成_s缩图
 * 2.所有图都生成_m手机图片
 * 3.如果已经存在的图片,将已经存在的图片返回
 */
@HttpMethod(caption = "上传文件")
public class UploadFileAction extends MultipartSupport {
    final private static Logger log = LoggerFactory.getLogger(UploadFileAction.class);
    final static private String[] officeFileTypes = FileSuffixUtil.officeTypes;
    final static private String[] stopExs = new String[]{"php", "jsp", "ftl", "html", "htm", "exe", "com", "bat", "asp", "aspx", "sh", "jar", "js", "dll"};

    private JSONObject json = new JSONObject();
    // 状态
    public static String hashType = "MD5";
    private Object uploadFile = null;
    private boolean useOriginalDate = false;

    public UploadFileAction() {
        setActionResult(NONE);
    }

    //分片上传,返回显示分片状态,满足断点续传,优化速度
    private boolean chunkSate = false;

    public void setChunkSate(boolean chunkSate) {
        this.chunkSate = chunkSate;
    }


    @Safety(request = false)
    public void setUseOriginalDate(boolean useOriginalDate) {
        this.useOriginalDate = useOriginalDate;
    }

    @Safety(request = false)
    public void setHashType(String hashType) {
        UploadFileAction.hashType = hashType;
    }


    /**
     * @return 得到配置允许上传的文件类型
     */
    public String getFileTypes() {
        IUserSession userSession = (IUserSession) getUserSession();
        if (userSession != null) {
            fileTypes = userSession.getRole(uploadFileDAO.getNamespace()).getUploadFileTypes();
        }
        if (StringUtil.isNULL(fileTypes) && config != null)
            fileTypes = config.getString(Environment.allowedTypes);


        if ("*".equalsIgnoreCase(fileTypes)) {
            String[] uploadTypes = new String[0];
            uploadTypes = ArrayUtil.join(uploadTypes, FileSuffixUtil.imageTypes);
            uploadTypes = ArrayUtil.join(uploadTypes, FileSuffixUtil.zipTypes);
            uploadTypes = ArrayUtil.join(uploadTypes, FileSuffixUtil.videoTypes);
            uploadTypes = ArrayUtil.join(uploadTypes, FileSuffixUtil.officeTypes);
            fileTypes = ArrayUtil.toString(uploadTypes, StringUtil.COMMAS);
        }

        fileTypes = StringUtil.replace(fileTypes, StringUtil.COMMAS, StringUtil.SEMICOLON);
        return fileTypes;

    }

    /**
     * @return 最大上传限制
     */
    public int getMaxPostSize() {
        IUserSession userSession = (IUserSession) getUserSession();
        if (userSession != null) {
            maxPostSize = userSession.getRole(uploadFileDAO.getNamespace()).getUploadSize() * 1024;
        }
        if (maxPostSize < 0 && config != null)
            maxPostSize = config.getInt(Environment.uploadMaxSize) * 1024;
        return maxPostSize;
    }

    /**
     * @return 得到上传路径
     */
    public String getSaveDirectory() {
        return getUploadDirectory(config);
    }

    /**
     * @return 得到安装路径
     * @throws Exception
     */
    private String getSetupPath() throws Exception {
        String setupPath = FileUtil.mendPath(config.getString(Environment.setupPath));
        if (!FileUtil.isDirectory(setupPath)) {
            setupPath = FileUtil.mendPath(FileUtil.getParentPath(getTemplatePath()));
            config.save(Environment.setupPath, setupPath);
            addFieldInfo(Environment.warningInfo, language.getLang(Environment.lan_setupConfigPathError) + ":" + setupPath);
        }
        return setupPath;
    }

    /**
     * @param config 配置接口
     * @return 上传路径的计算方式配置方式
     */
    @Safety(request = false)
    public static String getUploadDirectory(Bundle config) {
        String saveDirectory = FileUtil.mendPath(config.getString(Environment.uploadPath));
        if (!FileUtil.isDirectory(saveDirectory))
            saveDirectory = FileUtil.mendPath(config.getString(Environment.setupPath)) + FileUtil.mendPath(config.getString(Environment.uploadPath));
        boolean uploadPathType = config.getBoolean(Environment.uploadPathType);
        if (uploadPathType) {
            saveDirectory = FileUtil.mendPath(saveDirectory) + DateUtil.toString("yyyyMM") + "/";
        } else saveDirectory = FileUtil.mendPath(saveDirectory) + DateUtil.toString("yyyy") + "/";
        return saveDirectory;
    }

    /**
     * @param config 配置
     * @param name   名称
     * @return 得到文件
     */
    @Safety(request = false)
    public static File getUploadFile(Bundle config, String name) {
        String searchPathList = config.getString(Environment.searchPaths);
        String[] searchPaths = StringUtil.split(StringUtil.convertCR(searchPathList), StringUtil.CR);
        searchPaths = ArrayUtil.add(searchPaths, config.getString(Environment.setupPath));
        searchPaths = ArrayUtil.add(searchPaths, config.getString(Environment.setupPath) + "/" + config.getString(Environment.uploadPath));
        searchPaths = ArrayUtil.add(searchPaths, (new File(config.getString(Environment.setupPath))).getParent());
        return FileUtil.getFile(searchPaths, name);
    }

    /**
     * @return 是否覆盖
     */
    public boolean getCovering() {
        return config.getBoolean(Environment.uploadCovering);
    }

    /**
     * @param multipartRequest 请求接口
     */
    @Safety(request = false)
    @MulRequest(covering = "@covering", saveDirectory = "@saveDirectory", fileTypes = "@fileTypes", maxPostSize = "@maxPostSize")
    public void setMultipartRequest(MultipartRequest multipartRequest) {
        request = this.multipartRequest = multipartRequest;
    }

    /**
     * DAO 对象
     */
    private UploadFileDAO uploadFileDAO;
    @Safety(request = false)
    public void setUploadFileDAO(UploadFileDAO uploadFileDAO) {
        this.uploadFileDAO = uploadFileDAO;
    }

    /**
     * 中文分词
     */
    private ChineseAnalyzer chineseAnalyzer;

    @Safety(request = false)
    @Ref(name = Environment.chineseAnalyzer)
    public void setChineseAnalyzer(ChineseAnalyzer chineseAnalyzer) {
        this.chineseAnalyzer = chineseAnalyzer;
    }

    private String hash = StringUtil.empty;

    public void setHash(String hash) {
        this.hash = hash;
    }

    @Operate(caption = "hash验证")
    public void hasHash() throws Exception {
        json.put("OK", 0);
        json.put("success", false);
        if (isGuest()) {
            json.put(Environment.message, language.getLang(Environment.lan_needLogin));
            setResult(json);
            return;
        }

        if (StringUtil.isNULL(hash)) {
            json.put(Environment.message, language.getLang(Environment.lan_invalidParameter));
            setResult(json);
            return;
        }

        Object alreadyUploadFile = uploadFileDAO.getForHash(hash);
        IUploadFile checkUploadFile = (IUploadFile) alreadyUploadFile;
        if (checkUploadFile != null && hash.equalsIgnoreCase(checkUploadFile.getHash())) {
            json.put(Environment.message, language.getLang(Environment.lan_alreadyExist));
            json.put("success", true);
            json.put("hash", hash);
            json.put("hashType", hashType);
            json.put("OK", 1);
            setResult(json);
            return;
        }
        setResult(json);
    }

    @Operate(caption = "秒传")
    public void fastUpload() throws Exception {
        json.put("OK", 0);
        json.put("success", false);
        if (isGuest()) {
            json.put(Environment.message, language.getLang(Environment.lan_needLogin));
            setResult(json);
            return;
        }

        if (StringUtil.isNULL(hash)) {
            json.put(Environment.message, language.getLang(Environment.lan_invalidParameter));
            setResult(json);
            return;
        }

        Object alreadyUploadFile = uploadFileDAO.getForHash(hash);
        IUploadFile checkUploadFile = (IUploadFile) alreadyUploadFile;
        if (checkUploadFile != null && !StringUtil.isNULL(hash) && checkUploadFile.getHash().equals(hash)) {
            File file = getUploadFile(config, checkUploadFile.getFileName());
            if (file == null || !file.exists()) {
                json.put(Environment.message, language.getLang(Environment.lan_fileNotFind));
                setResult(json);
                return;
            }
            IUserSession userSession = (IUserSession) getUserSession();
            if (checkUploadFile.getPutUid() != userSession.getUid()) {
                //不同用户发布的,转存一份
                checkUploadFile.setId(0);
                checkUploadFile.setPutName(userSession.getName());
                checkUploadFile.setPutUid(userSession.getUid());
                checkUploadFile.setIp(getRemoteAddr());
                checkUploadFile.setDownTimes(0);
                uploadFileDAO.save(alreadyUploadFile);
                //验证一遍文件,如果后发布的文件和先发布的不同,用后发布的文件覆盖一次先发布的文件

            } else {
                checkUploadFile.setLastDate(new Date());
                checkUploadFile.setSortDate(new Date());
                uploadFileDAO.update(alreadyUploadFile, new String[]{"lastDate", "sortDate"});
            }

            //已经上传过的文件,在这里就直接返回上次的上传数据
            json.put("fileName", checkUploadFile.getFileName());
            json.put("name", FileUtil.getNamePart(checkUploadFile.getTitle()));

            if (checkUploadFile.getFileName() != null && checkUploadFile.getFileName().startsWith('/' + uploadFileDAO.getNamespace() + '/'))
                json.put("url", checkUploadFile.getFileName());
            else
                json.put("url", '/' + uploadFileDAO.getNamespace() + '/' + checkUploadFile.getFileName());
            json.put("title", checkUploadFile.getTitle());
            json.put("original", checkUploadFile.getTitle());
            json.put("dir", FileUtil.mendPath(FileUtil.getDecrease(getSetupPath(), file.getParent())));
            json.put("id", checkUploadFile.getId());
            json.put("size", checkUploadFile.getFileSize());
            json.put("type", checkUploadFile.getFileType());
            json.put("hash", checkUploadFile.getHash());
            json.put("error", 0);
            json.put("fileType", checkUploadFile.getFileType());
            json.put("success", true);
            json.put("state", "SUCCESS");
            json.put("successed", true);
            json.put(Environment.message, language.getLang(Environment.lan_success));
            //兼容 plupload  begin
            json.put("OK", 1);
            uploadFileDAO.evict(uploadFileDAO.getClassType());
            //兼容 plupload  end
            setResult(json);
        }
    }

    private String fileName = StringUtil.empty;

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

    @Operate(caption = "续传判断")
    public void lastChunk() throws Exception {
        json.put("OK", 0);
        json.put("success", false);
        if (isGuest()) {
            json.put(Environment.message, language.getLang(Environment.lan_needLogin));
            setResult(json);
            return;
        }
        IUserSession userSession = (IUserSession) getUserSession();
        UploadedFile uf = new UploadedFile(fileName, getUploadDirectory(config), fileName, fileName, "application/octet-stream", FileUtil.getTypePart(fileName));
        int diskChunk = uf.getLastChunk(NumberUtil.toString(userSession.getUid()));
        json.put("lastChunk", diskChunk);
        json.put("chunks", uf.getChunks());
        //兼容 plupload  begin
        if (diskChunk > 1) {
            json.put("OK", 1);
            json.put("success", true);
        }
        json.put(Environment.message, "chunk upload " + language.getLang(Environment.lan_success));
        //兼容 plupload  end
        setResult(json);
    }

    /**
     * 兼容swfupload 和 kindeditor, 和UEditor 1.2.0 的返回结果,当然普通页面上传也没问题
     *
     * @return json 返回结果
     * @throws Exception
     */
    @Operate(caption = "上传")
    public String execute() throws Exception {
        if (getResult() != null) return ROC;
        //--------------------------------------------------------------------------------------------------------------
        if (uploadFileDAO == null) {
            json.put("repair", false);
            json.put("success", false);
            json.put("error", 1);
            json.put("state", "error");
            json.put("thumbnail", 0);
            json.put(Environment.message, "not config uploadFileDAO");
            json.put("namespace", getRootNamespace());
            return ROC;
        }
        json.put("repair", false);
        json.put("success", false);
        json.put("error", 1);
        json.put("state", "error");
        json.put("thumbnail", 0);
        json.put("namespace", uploadFileDAO.getNamespace());

        //兼容 plupload  begin
        json.put("OK", 0);
        json.put(Environment.message, language.getLang(Environment.lan_invalidParameter));
        //兼容 plupload  end

        if (multipartRequest == null && !RequestUtil.isMultipart(request)) {
            json.put("fileName", "");
            json.put("url", "");
            json.put("original", "");
            json.put("dir", "");
            json.put("name", "");
            json.put("thumbnail", 0);

            //兼容 plupload  begin
            json.put("OK", 0);
            //兼容 plupload  end

            json.put(Environment.message, language.getLang(Environment.lan_uploadRequestError));
            setResult(json);
            return ROC;
        }

        if (isGuest()) {
            json.put("fileName", "");
            json.put("url", "");
            json.put("original", "");
            json.put("dir", "");
            json.put("name", "");
            json.put("thumbnail", 0);

            //兼容 plupload  begin
            json.put("OK", 0);
            json.put(Environment.message, language.getLang(Environment.lan_needLogin));
            //兼容 plupload  end

            for (UploadedFile uf : multipartRequest.getFiles()) {
                FileUtil.delete(uf.getFile());
            }
            setResult(json);
            return ROC;
        }

        IUserSession userSession = (IUserSession) getUserSession();
        boolean useUploadConverter = config.getBoolean(Environment.useUploadConverterTxt);
        String setupPath = getSetupPath();
        boolean thumbnail = getBoolean("thumbnail");
        int maxImageWidth = config.getInt(Environment.maxImageWidth, 1280);
        JSONArray fileJson = new JSONArray();
        for (UploadedFile uf : multipartRequest.getFiles()) {
            if (ArrayUtil.inArray(stopExs, uf.getFileType(), true)) {
                json.put("fileName", StringUtil.empty);
                json.put("url", StringUtil.empty);
                json.put("original", StringUtil.empty);

                json.put("title", uf.getOriginal());
                json.put("size", uf.getLength());
                json.put("type", uf.getFileType());

                json.put("dir", StringUtil.empty);
                json.put("name", StringUtil.empty);
                //兼容 plupload  begin
                json.put("OK", 0);
                //兼容 plupload  end
                json.put(Environment.message, language.getLang(Environment.lan_notAllowedFileType) + ":" + uf.getFileType());
                FileUtil.delete(uf.getFile());
                continue;
            }
            if (!uf.isUpload()) {
                json.put("fileName", uf.getFileName());
                json.put("title", uf.getOriginal());
                json.put("size", uf.getLength());
                json.put("type", uf.getFileType());
                json.put("original", uf.getOriginal());
                json.put("url", StringUtil.empty);
                json.put("dir", StringUtil.empty);
                json.put("name", StringUtil.empty);

                //兼容 plupload  begin
                json.put("OK", 0);
                //兼容 plupload  end
                json.put(Environment.message, language.getLang(Environment.lan_notAllowedFileTypeOrUploadError));
                FileUtil.delete(uf.getFile());
                continue;
            }

            if (!uf.isUpload()) {
                json.put("original", uf.getOriginal());
                json.put("fileName", "");
                json.put("title", uf.getOriginal());
                json.put("size", uf.getLength());
                json.put("type", uf.getFileType());

                json.put("url", "");
                json.put("dir", "");
                json.put("name", FileUtil.getNamePart(uf.getOriginal()));
                //兼容 plupload  begin
                json.put("OK", 0);

                //兼容 plupload  end
                json.put(Environment.message, language.getLang(Environment.lan_notAllowedFileTypeOrUploadError));
                //FileUtil.delete(uf.getFile());
                continue;
            }

            //分片上传 begin
            if (uf.isChunkUpload()) {
                json.put("uploadType", "chunk");
                if (uf.moveToChunkFolder(NumberUtil.toString(userSession.getUid()))) {
                    //如果是分片上传,追加进去后直接返回
                    json.put("fileName", uf.getFileName());
                    json.put("url", "");
                    json.put("name", FileUtil.getNamePart(uf.getOriginal()));
                    json.put("original", uf.getOriginal());
                    json.put("title", uf.getOriginal());
                    json.put("size", uf.getLength());
                    json.put("type", uf.getFileType());
                    json.put("dir", "");

                    //添加入最大的分片,判断是否断点续传

                    if (uf.isFolderChunkFull(NumberUtil.toString(userSession.getUid()))) {
                        //已经上传完毕
                        if (uf.mergeChunks(NumberUtil.toString(userSession.getUid()))) {
                            json.put("state", "SUCCESS");
                            json.put("successed", true);
                            //兼容 plupload  begin
                            json.put("OK", 1);
                            json.put(Environment.message, language.getLang(Environment.lan_success));
                            //兼容 plupload  end
                        } else {
                            json.put("OK", 0);
                            json.put(Environment.message, language.getLang(Environment.lan_saveFailure));
                            continue;
                        }
                    } else {
                        if (chunkSate) {
                            int diskChunk = uf.getLastChunk(NumberUtil.toString(userSession.getUid()));
                            if (diskChunk > 0 && diskChunk > uf.getChunk()) {
                                json.put("lastChunk", diskChunk);
                                json.put("chunk", uf.getChunk());
                                json.put("chunkSize", uf.getLength());
                                json.put("offset", diskChunk * uf.getLength());
                            }
                        }
                        //兼容 plupload  begin
                        json.put("OK", 1);
                        json.put(Environment.message, "chunk upload " + language.getLang(Environment.lan_success));
                        //兼容 plupload  end
                        continue;
                    }

                } else {
                    json.put("OK", 0);
                    json.put(Environment.message, language.getLang(Environment.lan_saveFailure));
                    continue;
                }
            }
            //分片上传 end

            json.put("uploadType", "file");
            if (uf.moveToTypeDir()) {
                uploadFile = uploadFileDAO.getClassType().newInstance();
                IUploadFile upFile = (IUploadFile) uploadFile;
                upFile.setTitle(FileUtil.getNamePart(uf.getOriginal()));
                if ("Filedata".equalsIgnoreCase(upFile.getTitle()) || "blob".equalsIgnoreCase(upFile.getTitle())) {
                    upFile.setTitle(FileUtil.getNamePart(uf.getFileName()));
                }
                upFile.setFileName(FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName());
                upFile.setFileSize(uf.getFile().length());
                upFile.setFileType(uf.getFileType());

                if (useUploadConverter && ArrayUtil.inArray(officeFileTypes, uf.getFileType(), true)) {
                    //文档转换,为了方便安卓下编译
                    try {
                        Object readOfficeFile = ClassUtil.newInstance("com.jspx.document.io.AutoReadOfficeFile");
                        BeanUtil.setSimpleProperty(readOfficeFile, "file", uf.getFile());
                        String content = (String) BeanUtil.getProperty(readOfficeFile, "content");
                        content = StringUtil.cut(content, 5000, StringUtil.empty);
                        upFile.setContent(content);
                        upFile.setTags(chineseAnalyzer.getTag(content, StringUtil.space, 6, true));
                    } catch (Exception e) {
                        upFile.setContent(uf.getOriginal());
                        upFile.setTags(chineseAnalyzer.getTag(uf.getOriginal(), StringUtil.space, 3, true));
                    }
                } else {
                    upFile.setContent(FileUtil.getNamePart(uf.getOriginal()));
                    upFile.setTags(chineseAnalyzer.getTag(uf.getOriginal(), StringUtil.space, 3, true));
                }

                upFile.setPutName(userSession.getName());
                upFile.setPutUid(userSession.getUid());
                upFile.setIp(getRemoteAddr());
                upFile.setHash(FileUtil.getHash(uf.getFile(), hashType));
                json.put("hash", upFile.getHash());
                Object alreadyUploadFile = null;
                if (!StringUtil.isNULL(upFile.getHash()))
                    alreadyUploadFile = uploadFileDAO.getForHash(upFile.getHash());
                IUploadFile checkUploadFile = (IUploadFile) alreadyUploadFile;
                if (checkUploadFile != null && !StringUtil.isNULL(upFile.getHash()) && checkUploadFile.getHash().equals(upFile.getHash())) {
                    //当前上传的文件
                    File file = getUploadFile(config, upFile.getFileName());
                    //以前的文件
                    File checkFile = getUploadFile(config, checkUploadFile.getFileName());
                    //如果两个文件大小不同
                    if (checkFile == null) {
                        checkFile = file;
                        checkUploadFile.setFileName(FileUtil.mendPath(FileUtil.getDecrease(setupPath, getUploadDirectory(config))) + file.getName());
                        uploadFileDAO.update(alreadyUploadFile, new String[]{"fileName"});
                    }
                    if (!checkFile.exists() || file.length() != checkFile.length()) {
                        if (FileUtil.copy(file, checkFile, true)) {
                            if (!file.equals(checkFile)) {
                                FileUtil.delete(file);
                                file = checkFile;
                            }
                        }
                    }

                    if (checkUploadFile.getPutUid() != userSession.getUid()) {
                        //不同用户发布的,转存一份
                        checkUploadFile.setId(0);
                        checkUploadFile.setPutName(userSession.getName());
                        checkUploadFile.setPutUid(userSession.getUid());
                        checkUploadFile.setIp(getRemoteAddr());
                        checkUploadFile.setDownTimes(0);
                        uploadFileDAO.save(alreadyUploadFile);
                        //验证一遍文件,如果后发布的文件和先发布的不同,用后发布的文件覆盖一次先发布的文件
                    } else {
                        checkUploadFile.setLastDate(new Date());
                        checkUploadFile.setSortDate(new Date());
                        uploadFileDAO.update(alreadyUploadFile, new String[]{"lastDate", "sortDate"});
                    }

                    //判断缩图是否存在 begin
                    String thumbnailImg = FileUtil.getThumbnailFileName(file.getName());
                    File thumbnailFile = new File(file.getParentFile(), thumbnailImg);
                    if (thumbnailFile.isFile()) {
                        json.put("thumbnail", 1);
                        String thumbnailPath = FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + thumbnailImg;
                        if (thumbnailPath.startsWith('/' + uploadFileDAO.getNamespace() + '/'))
                            json.put("thumbnailUrl", thumbnailPath);
                        else
                            json.put("thumbnailUrl", '/' + uploadFileDAO.getNamespace() + '/' + thumbnailPath);
                    }
                    //判断缩图是否存在 end

                    //已经上传过的文件,在这里就直接返回上次的上传数据
                    json.put("fileName", checkUploadFile.getFileName());
                    json.put("name", FileUtil.getNamePart(uf.getOriginal()));

                    if (checkUploadFile.getFileName() != null && checkUploadFile.getFileName().startsWith('/' + uploadFileDAO.getNamespace() + '/'))
                        json.put("url", checkUploadFile.getFileName());
                    else
                        json.put("url", '/' + uploadFileDAO.getNamespace() + '/' + checkUploadFile.getFileName());
                    json.put("title", checkUploadFile.getTitle());
                    json.put("original", uf.getOriginal());
                    json.put("dir", FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())));
                    json.put("id", checkUploadFile.getId());
                    json.put("size", uf.getLength());
                    json.put("type", uf.getFileType());
                    json.put("error", 0);
                    json.put("fileType", checkUploadFile.getFileType());
                    json.put(Environment.message, language.getLang(Environment.lan_success));
                    json.put("success", true);
                    json.put("state", "SUCCESS");
                    json.put("successed", true);
                    //兼容 plupload  begin
                    json.put("OK", 1);
                    //兼容 plupload  end

                } else if (uploadFileDAO != null) {
                    //已经上传成功的,还没有的上传上去的就上传上去
                    IUploadFile thumbnailUploadFile = null;
                    IUploadFile mobileUploadFile = null;
                    if (FileSuffixUtil.isImageSuffix(uf.getFileType())) {
                        File file = uf.getFile();

                        //如果是图片就得到宽高
                        BufferedImage image = null;
                        try {
                            image = ImageIO.read(file);
                        } catch (IOException e1) {
                            json.put("state", ERROR);
                            json.put("success", false);
                            //兼容 plupload  begin
                            json.put("OK", 0);
                            json.put(Environment.message, "不能识别的图片格式");
                            setResult(json);
                            log.info("未知不能识别的图片格式:"+ file.getAbsolutePath());
                            return ROC;
                        }
                        int w = image.getWidth();
                        int h = image.getHeight();

                        if (config.getBoolean(Environment.EXIF_SATE)) {
                            StringMap map = ImageUtil.parsePhotoExif(file, ImageUtil.simpleExifTags);
                            upFile.setAttributes(map.toString());
                        } else
                            upFile.setAttributes("width=" + w + "\r\nheight=" + h);

                        if (image.getWidth() > maxImageWidth) {
                            h = (maxImageWidth / w) * h;
                            if (!file.canWrite()) Thread.sleep(200);
                            boolean repair = ImageUtil.thumbnail(image, new FileOutputStream(file), uf.getFileType(), maxImageWidth, h);
                            json.put("repair", repair);
                        }

                        String thumbnailImg = FileUtil.getThumbnailFileName(uf.getFileName());
                        int width = config.getInt("thumbnailWidth", 400);
                        int height = config.getInt("thumbnailHeight", 400);
                        if (thumbnail) {
                            //创建缩图
                            File thumbnailFile = new File(file.getParent(), thumbnailImg);
                            if (ImageUtil.thumbnail(image, new FileOutputStream(thumbnailFile), uf.getFileType(), width, height)) {
                                json.put("fileName", thumbnailImg);
                                json.put("name", FileUtil.getNamePart(thumbnailImg));
                                json.put("thumbnail", 1);
                                String thumbnailPath = FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + thumbnailImg;
                                if (thumbnailPath.startsWith('/' + uploadFileDAO.getNamespace() + '/'))
                                    json.put("thumbnailUrl", thumbnailPath);
                                else
                                    json.put("thumbnailUrl", '/' + uploadFileDAO.getNamespace() + '/' + thumbnailPath);

                                thumbnailUploadFile = (IUploadFile) uploadFileDAO.getClassType().newInstance();
                                thumbnailUploadFile.setHash(FileUtil.getHash(thumbnailFile, hashType));
                                thumbnailUploadFile.setFileSize(thumbnailFile.length());
                            }
                        }

                        //创建手机图片
                        int mobileWidth = config.getInt("mobileWidth", 480);
                        int mobileHeight = config.getInt("mobileHeight", 480);
                        String mobileImg = FileUtil.getMobileFileName(uf.getFileName());
                        File mobileFile = new File(file.getParent(), mobileImg);
                        if (ImageUtil.thumbnail(image, new FileOutputStream(mobileFile), uf.getFileType(), mobileWidth, mobileHeight)) {
                            json.put("name", FileUtil.getNamePart(mobileImg));
                            json.put("mobile", 1);

                            String mobilePath = FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + mobileImg;
                            if (mobilePath.startsWith('/' + uploadFileDAO.getNamespace() + '/'))
                                json.put("mobileUrl", mobilePath);
                            else
                                json.put("mobileUrl", '/' + uploadFileDAO.getNamespace() + '/' + mobilePath);
                            mobileUploadFile = (IUploadFile) uploadFileDAO.getClassType().newInstance();
                            mobileUploadFile.setHash(FileUtil.getHash(mobileFile, hashType));
                            mobileUploadFile.setFileSize(mobileFile.length());
                        }
                    }

                    //导入日期
                    Date createDate = new Date();
                    if (useOriginalDate && uf.getOriginal().startsWith("[") && uf.getOriginal().contains("]")) {
                        String dataStr = StringUtil.substringBetween(uf.getOriginal(), "[", "]");
                        if (!StringUtil.isNULL(dataStr)) {
                            try {
                                createDate = StringUtil.toDate(dataStr);
                            } catch (Exception e) {
                                createDate = new Date();
                            }
                        }
                    }
                    upFile.setCreateDate(createDate);

                    if (uploadFileDAO.save(uploadFile) >= 0) {

                        if (thumbnailUploadFile != null) {
                            String thumbnailHash = thumbnailUploadFile.getHash();
                            long thumbnailSize = thumbnailUploadFile.getFileSize();
                            BeanUtil.copySimpleProperty(thumbnailUploadFile, upFile);
                            thumbnailUploadFile.setHash(thumbnailHash);
                            thumbnailUploadFile.setFileSize(thumbnailSize);
                            thumbnailUploadFile.setId(0);
                            thumbnailUploadFile.setTitle(upFile.getTitle());
                            thumbnailUploadFile.setTags("缩图");
                            thumbnailUploadFile.setPid(upFile.getId());
                            thumbnailUploadFile.setFileName(FileUtil.getThumbnailFileName(thumbnailUploadFile.getFileName()));
                            uploadFileDAO.save(thumbnailUploadFile);
                        }

                        if (mobileUploadFile != null) {
                            String mobileHash = mobileUploadFile.getHash();
                            long mobileSize = mobileUploadFile.getFileSize();
                            BeanUtil.copySimpleProperty(mobileUploadFile, upFile);
                            mobileUploadFile.setHash(mobileHash);
                            mobileUploadFile.setFileSize(mobileSize);
                            mobileUploadFile.setId(0);
                            mobileUploadFile.setTitle(upFile.getTitle());
                            mobileUploadFile.setTags("手机");
                            mobileUploadFile.setPid(upFile.getId());
                            mobileUploadFile.setFileName(FileUtil.getMobileFileName(mobileUploadFile.getFileName()));
                            uploadFileDAO.save(mobileUploadFile);
                        }

                        json.put("fileName", uf.getFileName());
                        json.put("name", FileUtil.getNamePart(uf.getOriginal()));

                        String urlPath = FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName();
                        if (urlPath.startsWith('/' + uploadFileDAO.getNamespace() + '/'))
                            json.put("url", FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName());
                        else
                            json.put("url", '/' + uploadFileDAO.getNamespace() + '/' + FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName());

                        json.put("purl", uf.getFileName());
                        json.put("name", FileUtil.getNamePart(uf.getOriginal()));
                        json.put("original", uf.getOriginal());
                        json.put("title", uf.getOriginal());
                        json.put("size", uf.getLength());
                        json.put("type", uf.getFileType());
                        json.put("dir", FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())));
                        json.put("id", upFile.getId());
                        json.put("error", 0);
                        json.put("fileType", uf.getFileType());
                        json.put("state", SUCCESS);
                        json.put("success", true);
                        //兼容 plupload  begin
                        json.put("OK", 1);
                        //兼容 plupload  end
                        json.put(Environment.message, language.getLang(Environment.lan_success));
                        setActionLogTitle("上传文件," + uf.getOriginal());
                        setActionLogContent(BeanUtil.toXml(upFile));
                        fileJson.put(json.clone());
                    } else {
                        json.put(Environment.message, language.getLang(Environment.lan_databaseSaveError));
                    }
                } else {
                    json.put("fileName", uf.getFileName());
                    json.put("name", FileUtil.getNamePart(uf.getOriginal()));
                    json.put("fileType", uf.getFileType());

                    String urlPath = FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName();
                    if (urlPath.startsWith('/' + uploadFileDAO.getNamespace()))
                        json.put("url", FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName());
                    else
                        json.put("url", '/' + uploadFileDAO.getNamespace() + '/' + FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName());

                    json.put("original", uf.getOriginal());
                    json.put("dir", FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())));
                    json.put("name", FileUtil.getNamePart(uf.getOriginal()));
                    json.put(Environment.message, language.getLang(Environment.lan_folderWriteError));
                }
            } else {
                //没有移动成功的文件
                json.put("fileType", uf.getFileType());
                json.put("fileName", uf.getFileName());
                json.put("name", FileUtil.getNamePart(uf.getOriginal()));
                json.put("title", uf.getOriginal());
                json.put("size", uf.getLength());
                json.put("type", uf.getFileType());

                String urlPaht = FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName();
                if (urlPaht.startsWith('/' + uploadFileDAO.getNamespace()))
                    json.put("url", FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName());
                else
                    json.put("url", '/' + uploadFileDAO.getNamespace() + '/' + FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())) + uf.getFileName());
                json.put("original", uf.getOriginal());
                json.put("dir", FileUtil.mendPath(FileUtil.getDecrease(setupPath, uf.getDir())));
                json.put(Environment.message, language.getLang(Environment.lan_folderWriteError));
            }
        }
        uploadFileDAO.evict(uploadFileDAO.getClassType());
        json.put("list", fileJson);
        setResult(json);
        return ROC;
    }


    public long getSize() {
        if (uploadFile == null) return 0;
        IUploadFile upFile = (IUploadFile) uploadFile;
        return upFile.getFileSize();
    }

    public String getUrl() {
        if (uploadFile == null) return StringUtil.empty;
        IUploadFile upFile = (IUploadFile) uploadFile;
        return upFile.getFileName();
    }

    public String getFileName() {
        if (uploadFile == null) return StringUtil.empty;
        IUploadFile upFile = (IUploadFile) uploadFile;
        return upFile.getFileName();
    }

    public String getState() {
        return json.getString(Environment.message);
    }

    public String getTitle() {
        if (uploadFile == null) return StringUtil.empty;
        IUploadFile upFile = (IUploadFile) uploadFile;
        return upFile.getTitle();
    }

    public String getType() {
        if (uploadFile == null) return StringUtil.empty;
        IUploadFile upFile = (IUploadFile) uploadFile;
        return upFile.getFileType();
    }

    public String getOriginalName() {
        if (uploadFile == null) return StringUtil.empty;
        IUploadFile upFile = (IUploadFile) uploadFile;
        return upFile.getTitle();
    }
}

十.数据验证

TXWeb部分使用的是javascript来验证,sober部分当然不会是javascript了,但都是同一部分模块。 下边我们配置一个要验证的form,假设里边有两个输入框需要验证,我们配置如下,dataType="验证的条件" field="要验证的输入框ID",msgId="显示消息的层id",needed="是否必须填写字段" 保存为user.validator.xml文件。 dataType在程序中已经内带了大部分的验证方式。只需要调用就可以。 如果有特别需要库自己扩展。验证函数很多,你可以打开,com\jspx\scriptmark\jslib.js 看里边的函数,是判断类型的都可以在验证里边使用,所有的函数均可以在模板里边使用。
     <validator formId="reguserForm">
        <!--/user/validator/{field}.${suffix}?value={value} 是通过url方式,返回验证是否用户名已经被占用-->
        <validation dataType="isGoodName" field="name"  url="/user/validator/{field}.${suffix}?value={value}" msgId="nameMsg" needed="true">
            <message>会员登录名4-20个英文字母或数字组成,不能使用符号。</message>
            <error>错误,长度不正确或者使用了特殊符号</error>
            <success>验证通过</success>
        </validation>
    <!--相当于javascript的 password.isLengthBetween(4,20) -->
        <validation dataType="isLengthBetween(4,20)" field="password" msgId="passwordMsg" needed="true">
            <message>密码由4-20个英文字母或数字组成,难猜的英文数字。</message>
            <error>错误</error>
            <success>验证通过</success>
        </validation>
    <!--判断password 和 rePassword 是否相同-->
        <validation dataType="equal($('password').value)" field="rePassword" msgId="rePasswordMsg" needed="true">
            <message>请再输一次您上面输入的密码,以免输入错误。</message>
            <error>不能为空,两次密码必须相同</error>
            <success>验证通过</success>
        </validation>
        <validation dataType="isLengthBetween(0,50)" field="validate" url="/user/validator/{field}.${suffix}?value={value}" msgId="validateMsg" needed="false">
            <message>验证码。</message>
            <error>错误长度不能超过50字</error>
            <success>验证通过</success>
        </validation>
        <validation dataType="isEmail" field="mail" msgId="mailMsg" url="/user/validator/{field}.${suffix}?value={value}" needed="true">
            <message>重要!请填写您常用的邮箱。</message>
            <error>邮箱填写错误</error>
            <success>验证通过</success>
        </validation>
    </validator>
sioc中配置载入验证接口,和要验证的user.validator.xml文件
    <bean id="validator" class="com.jspx.txweb.support.ValidatorAction" singleton="true">  
         <string name="configFile">user.validator.xml</string>  
     </bean>  
TXWeb中配置载入验证动作,使用ajax方式
    <action name="validator" class="validator" type="ajax">  
        <result name="*" type="none"/>  
    </action>  
adduser.ftl
    <!--引入验证要使用的javascript-->  
    <link rel="stylesheet" type="text/css" href="script/jspxnet-ui.css"/>  
    <script type="text/javascript" src="script/mootools.js"></script>  
    <script type="text/javascript" src="script/mootools-more.js"></script>  
    <script type="text/javascript" src="script/jspxnet.js"></script>  
    <script type="text/javascript" src="script/jspxnet-ui.js"></script>  
      <script>  
       window.addEvent('domready', function() {  
         new Request.Validator("validator.${suffix}", "adduserForm");  
         //adduserForm表示form Id 提交的表单ID  

       });  

    </script>  
      <!--form ID必须对应配置中的id-->  
      <form id="adduserForm" action="adduser.${suffix}" method="post"  name="adduserForm">  
           <!--ID必须对应配置中的field-->  
            <input id="name" name="name" type="text"  value="${request.get('name')}"/>  
            <!--ID必须对应msgId-->  
            <div id="nameMsg">会根据验证结果返回信息</div>  


           <!--ID必须对应配置中的field-->  
            <input name="oicq" type="text" id="oicq" value="${request.get('oicq')}" />  
            <!--ID必须对应msgId-->  
            <div id="oicqMsg">会根据验证结果返回信息</div>  
            <br />  
             <!--onclick在点击的时候判断是否验证通过-->  
            <input id="submit" name="submit" value="submit" type="submit" />  
       </form >  
虽然以上验证方式已经很丰富,但在实际应用中并不能满足要求,比如验证一个用户是否已经注册,验证码填写是否正确, 下边演示一个服务器端验证的例子,验证配置如下
    <validation dataType="isGoodName" field="name"  url="/user/validator/{field}.${suffix}?value={value}" msgId="nameMsg" needed="true">  
             <message>会员登录名4-20个英文字母或数字组成,不能使用符号。</message>  
             <error>错误,长度不正确或者使用了特殊符号</error>  
             <success>验证通过</success>  
    </validation>
TXWeb配置URL入口,采用RestFull方式
    <package name="userValidator" extends="user" namespace="user/validator">  
        <action name="*" class="validServiceAction" type="true" method="@" pass="userInterceptor" />  
    </package>  
验证代码如下
    @HttpMethod(caption = "服务器验证")  
    public class ValidServiceAction extends ValidAction {  

        private MemberDAO memberDAO;  
        @Ref(name = JUserIoc.memberDAO, namespace = JUserIoc.namespace)  
        public void setMemberDAO(MemberDAO memberDAO) {  
            this.memberDAO = memberDAO;  
        }  

        @Operate(caption="验证用户名")  
        public void name() throws SQLException {  
            if (!StringUtil.checkGoodName(value,4)) {  
                setResult(config.get("errorUserName", "此用户名不要使用特殊符号,长度不能小于4"));  
                return;  
            }  
            Member member = memberDAO.getForName(value);  
            if (member == null)  
                setResult(DataTypeValidator.success);  
            else  
                setResult(config.get("errorUsedName", "此用户名已经被使用"));  
        }  
    }
HTML页面调用不变,new Request.Validator("validator.${suffix}", "adduserForm"), 就可以实现服务器方式验证了, 原理很简单,看验证配置的多了url="/user/validator/{field}.${suffix}?value={value}" ,对应的就是url入口, js将会自动替换{field}和{value}发送到服务器,服务器返回验证的情况
  • 特殊的两个验证配置
    <!-checkbox表示id号分别为checkbox1;checkbox2;checkbox3;checkbox4的几个多选框,必须选择一个-->
        <validation dataType="checkbox" field="checkbox1;checkbox2;checkbox3;checkbox4" msgId="tipDivMsg" needed="true">
            <message>多项选择</message>
            <error>服务对象,最少选择一个</error>
            <success>验证通过</success>
        </validation>
    <!-radio表示名称为registerType的单选框,必须选择一个-->
        <validation dataType="radio" field="registerType" msgId="registerTypeMsg" needed="true">
            <message>选择一个</message>
            <error>选择一个</error>
            <success>验证通过</success>
        </validation>
  1. TXWeb内带的返回类型包括了ajax的使用,必须在配置中注明type="ajax"才允许外部ajax调用。
  2. 如何使用request,response? 继承ActionSupport后,你就直接可以直接使用这两个对象了。
  3. 如何得到请求参数?继承ActionSupport类对,可以通过getString("变量名称"),getInt("变量名称")...得到相应类型的请求参数.
  4. addActionMessage保存消息,addFieldInfo 保存错误信息。
  5. 返回类型支持xml,json等多种类型,已经足够使用,如果要返回其他类型你可以使用response直接输出。
  6. 继承ActionSupport后你可以通过,getTemplatePath()得到当前页面所在的目录,getTemplateFile() 可以得到当前模板文件的所在路径。
  7. TXWeb和struts2一样的配置方式可以支持 。
    <action name="和模板文件名对应" class="sioc中的bean名称" method="@method">  
         <param name="bean方法名称">设置参数</param>  
         <interceptor-ref name="sioc中的拦截器"/>  
         <result name="bean的返回" type="返回类型">error.${suffix}</result>  
       </action>
以上 配置表示,调用 ,bean的类的方法 method,method为变量,是request传递过来的。 如果是 method="@" 这样表示调用方法为url的文件名称部分作为方法名。 8.得到配置环境中的动作列表。"user" 表示命名空间
    WebConfigManager webConfigManager = TXWebConfigManager.getInstance();  
          Collection<OperateVO> vos = webConfigManager.getOperateList("user");  
          for (OperateVO vo : vos) {  
              System.out.println(vo.getActionName() + "  " + vo.getCaption());  
          }  
          //这些命名空间数据是在 Operate 是在jvm里边,即内存里边所以得到是很快的  
9.得到所有动作配置列表
    public static void testLoadActionConfigOperate() throws Exception  
        {  
            long startTime = System.currentTimeMillis();  
            WebConfigManager webConfigManager = TXWebConfigManager.getInstance();  
            for (String namespace : webConfigManager.getNamespaceList())  
            {  
                System.out.println("命名空间:" + namespace);  
                Map<String, ActionConfigBean> list = webConfigManager.getActionMap(namespace);  
                Assert.assertEquals(list.size() > 0, true);  
                for (String key : list.keySet())  
                {  
                    ActionConfigBean actionBean = list.get(key);  
                    System.out.println("ActionConfigBean ID:" + key);  
                    Map<String, Operate> operateMap = actionBean.getOperateMap();  
                    for (String methodName : operateMap.keySet())  
                    {  
                        Operate op = operateMap.get(methodName);  
                        System.out.println(actionBean.getClassName() + "  methodName=" + methodName + " " + op.caption());  
                    }  
                }  
            }  
            System.out.println("时间:" + (System.currentTimeMillis() - startTime));  
        }  
< code >   
这样你就可用控制动作映射了,这些工作都已经整合在TXWeb, com.jspx.txweb.view.PermissionView 10.不要使用如下变量方法传递外部参数,因为已经被默认占用
    {"session","request","response","language","config","env","actionLogTitle","actionLogContent","result","templatePath","actionResult"};  
11.com.jspx.txweb.view.TreeView,提供了树结构和整合数据库的功能。 12.虽然功能和使用上都很相像struts2,但运行方式和运行流程上完全不同的。学习的有一定的struts2基础可以帮助的更快熟悉本构架,但运行方式上是完全不同的两个东西。 因为这个构架的是模板方式运行不需要编译,运行方式与php比较相似。具备了php语言的简便,保留了java的优点。 13.TXWeb可以使用任意的模板文件后缀,访问URL也可以使用任意的文件后缀。是根据jspx.properties中设置的参数决定的。所以你在模板文件中是用后缀的时候统一使用 .${suffix}来代替后缀。配置的时候也可以。 14.本构架是一个java和javascript交叉运行的一个构架,所以在不使用java的地方就是使用javascript。这样很方便。

十一.使用jsp或php方式运行

quercus 对于编码的支持并不好,本人编译了一个版本的quercus修复了UTF-8编码下的中文问题.使用UTF-8编码下中文和数据库查询就不会出现乱码问题. 下载 php 例子(对于熟悉php的人员) 功能说明 脚本 显示标题列表
 <?php
 $squery = getAction("*"); //得到xml中配置的action的bean.
 $mlist = $squery->list;
 foreach ($mlist as $value)
 {
    echo "标题:" . $value->title . "<br />";
 }
 ?>
jsp例子(WebCont为数据对象bean,SiteQueryView是包含DAO完整查询的数据对象) 功能说明 脚本 显示标题列表
 <%@ page import="com.jspx.txweb.module.JspxModule" %>
 <%@ page import="com.jspx.ftpd.view.SiteQueryView" %>
 <%@ page import="com.jspx.ftpd.table.WebCont" %>
 <%@ page import="java.util.List" %>
 <%
 //squery是TXWeb配置中的Action名称
 SiteQueryView squery = (SiteQueryView)JspxModule.getAction(request,response,"squery");
 List list = squery.getList();
 for (int i=0; i<list.size(); i++)
 {
    WebCont cont = (WebCont)list.get(i);
     out.println("标题输出:" + cont.getTitle() + "<br/>");
 }
 %> 

十二. 内置常用程序

    <!--xweb 模板支持 begin-->
    <bean id="globalAction" class="com.jspx.txweb.support.DefaultTemplateAction" singleton="false" />
    <bean id="rrsRead" class="com.jspx.txweb.view.RRSReadView" />
    <bean id="ssqlQuery" class="com.jspx.txweb.support.SSqlQueryTemplate" singleton="false">
        <ref name="soberFactory">jspxSoberFactory</ref>
    </bean>
    <!--xweb 返回类型,模板支持 end-->

    <!--component begin-->
    <!--png 附加码-->
    <bean id="validate" class="com.jspx.txweb.view.ValidateExpImgView" singleton="false"/>
    <!--gif 附加码-->
    <!-- 传统方式
      <bean id="validate" class="com.jspx.txweb.view.ValidateImgView" singleton="false">
          <int name="length">3</int>
      </bean>
    -->
    <!--png 字符串加密转图片 begin-->
    <bean id="transPhotoView" class="com.jspx.txweb.view.TransPhotoView" singleton="false"/>
    <!--png 字符串加密转图片 end-->

    <!--png 二维码图片生成 begin-->
    <bean id="qrcodeView" class="com.jspx.txweb.view.QRCodeView" singleton="false"/>

    <!--中国省市列表 begin-->
    <bean id="countryModule" class="com.jspx.txweb.view.StringTreeView" singleton="true">
        <string name="fileName">
            ${resPath}table/city.jzb!/country.table
        </string>
    </bean>

    <bean id="provinceModule" class="com.jspx.txweb.view.StringTreeView" singleton="true">
        <string name="fileName">${resPath}table/city.jzb!/province.table</string>
    </bean>

    <bean id="cityModule" class="com.jspx.txweb.view.StringTreeView" singleton="true">
        <string name="fileName">${resPath}table/city.jzb!/province.table</string>
    </bean>

    <bean id="regionModule" class="com.jspx.txweb.view.StringTreeView" singleton="true">
        <string name="fileName">${resPath}table/city.jzb!/region.table</string>
    </bean>
    <!--中国省市列表 end-->


    <!--默认提供DAO,大多使用在模板DAO中-->
    <bean id="genericDAO" class="com.jspx.txweb.dao.impl.GenericDAOImpl" singleton="true">
        <ref name="soberFactory">jspxSoberFactory</ref>
    </bean>

    <!--动作日志拦截器-->
    <bean id="actionLogInterceptor" class="com.jspx.txweb.interceptor.ActionLogInterceptor" singleton="true" />

    <!--在线和用户-->
    <bean id="baseUserAction" class="com.jspx.txweb.action.BaseUserAction" singleton="false" />
    <bean id="onlineManager" class="com.jspx.txweb.online.impl.OnlineManagerImpl" singleton="true" init="init" >
        <string name="drug">迷药</string>
    </bean>

    <!--查询IP定位 begin-->
    <bean id="ipLocationDAO" class="com.jspx.txweb.dao.impl.IpLocationDAOImpl" singleton="true" >
        <ref name="soberFactory">jspxSoberFactory</ref>
        <string name="fileName">${resPath}table/ips.jzb!/ipaddress.table</string>
    </bean>
    <bean id="ipLocationModule" class="com.jspx.txweb.view.IpLocationView" singleton="true" />
    <bean id="ipLocationAction" class="com.jspx.txweb.action.IpLocationAction" singleton="false" />
    <!--查询IP定位 end-->

    <!--基本字典库   参考国家民政数据标准.  begin-->
    <bean id="optionDAO" class="com.jspx.txweb.dao.impl.OptionDAOImpl" singleton="false" >
        <ref name="soberFactory">jspxSoberFactory</ref>
        <string name="folder">${resPath}option/</string>
    </bean>
    <bean id="optionListView" class="com.jspx.txweb.view.OptionListView" singleton="false" />
    <bean id="option" class="com.jspx.txweb.view.OptionProvider" singleton="false" />
    <bean id="optionAction" class="com.jspx.txweb.action.OptionAction" singleton="false" />
    <!--基本字典库 end--

十三. 一些内置功能用法

将字符串转换为图片输出,一般是为了避免网络爬虫盗取联系方式使用
 <img src="transphoto.${suffix}?txt=${'xxxx联系方式'.getUTFEncode()}&width=180&height=20" /> 
验证码生成,session 保存名称 jspx_validate
 <img src="validate.${suffix}" /> 
二维码,条码生成,需要zxing.jar 包支持,我编译一一个支持中文更好的版本jspx-zxing-qrcode.jar
<img src="qrcode.${suffix}?format=11&txt=${'http://www.jspx.net'.getUTFEncode()}" >
<hr>
<img src="qrcode.${suffix}?format=4&txt=${'ABC-cnaidc-1234'.getUTFEncode()}" >
lucene目前4.8版本 Sioc 配置例子
        <bean id="lucene" class="com.jspx.lucene.impl.LuceneImpl" singleton="false">
            <string name="filePath">
                ${lucenePath}目录/
            </string>
        </bean>
本地调用封装例子
import com.jspx.lucene.ChineseAnalyzer;
import com.jspx.lucene.Lucene;
import com.jspx.lucene.LuceneVO;
import com.jspx.lucene.SearchResult;
import com.jspx.lucene.impl.ChineseAnalyzerImpl;
import com.jspx.lucene.impl.LuceneImpl;
import com.jspx.utils.FileUtil;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.List;



/**
 * Created by IntelliJ IDEA.
 * User:chenYuan (mail:39793751@qq.com)
 * Date: 2007-11-18
 * Time: 10:11:09
 */
public class TestLucene
{

    static final String fileDir = "d:\\website\\webapps\\root\\WEB-INF\\lucene\\temp\\";
    static  final Lucene lucene = new LuceneImpl();
    @BeforeClass
    public static void init() {

        System.out.println("------------开始");
        FileUtil.makeDirectory(fileDir);
        lucene.setFilePath(fileDir);

    }

    @AfterClass
    public void afterExit() throws ScriptException {
        System.out.println("------------结束");
    }

    //测试分词
    @Test
    public static void ChineseAnalyzer() throws Exception
    {
        String text = "同样是昨天,受Win8禁令消息的刺激,软件服务股集体暴涨。其中,创意信息、安硕信息、天源迪科涨停,卫宁软件、中国软件、赢时胜、东方通涨超8%,绿盟科技、新大陆涨超7%。有分析称,网络安全概念股也因此有望受益,包括蓝盾股份、方正科技、任子行、启明星辰、美亚柏科、长城电脑等。";
        ChineseAnalyzer chineseAnalyzer = new ChineseAnalyzerImpl();

        //对中文分词
        //String words = chineseAnalyzer.getAnalyzerWord(text," ");
        //得到Tag 关键词,算法,为计算出现最多的词
        String tag = chineseAnalyzer.getTag(text," ", 3,true);
        Assert.assertEquals(tag, "软件 信息 科技");
    }


    //创建索引
    @Test
    public static void createIndex() throws Exception
    {
        List<LuceneVO> slist = new ArrayList<LuceneVO>();
        LuceneVO lto = new LuceneVO();
        lto.setId(1);
        lto.setTitle("查询一");
        lto.setContent("在需索引文件数不大的情况下,合并因子对性能影响并不大。至少在万级水平,较大的合并因子才会对性能带来显著改进。这与我对合并因子的理解基本上一致。\n" +
                "\n" +
                "内存索引在某些情况下居然会比硬盘索引慢,可能的原因是:1、内存在堆内频繁分配释放,浪费时间;2、本地文件读取在这次测试中耗时最多,索引时间较少,故而结果受到本地文件读取速度影响较大,所以测试时间颠簸超过了内存和硬盘的速度差。\n" +
                "\n" +
                "总之可以证明硬盘索引效率很高跟内存效率差异不大。内存如果作为缓冲,而且尺寸选择得当应该会大幅提高效率,时间不足,就不做更细节的测试了。");

        slist.add(lto);
        lto = new LuceneVO();
        lto.setId(2);
        lto.setTitle("查询二");
        lto.setContent("以上程序,在我的MacBook上面开发,第一个测试在我的MacBook进行,Mac book的jdk版本为1.5,很稳定。第二个在我们的linux服务器上进行。Linux服务器原安装的是jdk 1.6,程序频繁无故锁死,找不到原因。后换为jdk 1.43,程序很稳定运行下去了。\n" +
                "\n" +
                "我的估计是jdk 1.6还不够稳定,霍炬估计是程序中有跟jdk 1.6不兼容的部分,最终的检索结果可以直接从一存疑与此。\n" +
                "\n" +
                "update:在服务器安装了jdk 1.5也会频繁发生锁死,看来出现的问题可能跟1.5以后的机制改变有关,继续存疑。");
        slist.add(lto);
        lto = new LuceneVO();
        lto.setId(3);
        lto.setTitle("感性真实故事时尚");
        lto.setContent("【感性真实,故事时尚】影响原因本作品布局错落有致,最终的检索结果可以直接从一主线清晰明了,紧贴现实生活,以第一人称用简练的语言描述了一场场“爱情游戏”,释全了“爱情游戏”的真正含义。故事情节如云似水般释出,围绕几段感情一一展开,在朴实的叙事中荡起一个又一个的高潮。虽然只是“游戏”,但在“游戏”的背后却是深深的感动,字里行间情感的自然流露让人在不经意间与之融为一体,暗自伤神。作者文笔功底深厚,巧妙地驾驭着朴实的文字。一种看似平常简单的叙述手法,在作者笔下已然超凡脱俗。字里行间流露着文雅的气息。娓娓叙述出身边的故事,读起来使人更觉得真实、贴切,略带夸张的语言形象生动地刻画出了人物个性,也将人物形象深入人心,整篇小说犹如一池温润的碧水,漾着点点星波,悄然涟漪着每一位读者的心...... 该文已由凤凰出版传媒集团-江苏美术出版社正式出版(ISBN 978-7-5344-2396-3)。");
        slist.add(lto);


        lto = new LuceneVO();
        lto.setId(4);
        lto.setTitle("查询四最终的检索结果可以直接从");
        lto.setContent("最后,执行scorer.score(collector);,最终的检索结果可以直接从一个HitCollector collector中提取出来Lucene现在使用Java 7中的文件系统函数,比如即使在索引打开的时候,也可以删除索引文件。");
        slist.add(lto);

        lto = new LuceneVO();
        lto.setId(5);
        lto.setTitle("查询四A");
        lto.setContent("人们认为谷歌可能会简单地对摩托罗拉的制造业务进行分拆。Lucene现在使用Java 7中的文件系统函数,比如即使在索引打开的时候,也可以删除索引文件。");
        slist.add(lto);

        Assert.assertEquals(lucene.save(slist, true), true);


    }

    //删除所有
    @Test
    public static void delete() throws Exception
    {
         lucene.deleteAll();

    }

    //删除安ID
    @Test
    public static void deleteForId() throws Exception
    {
        Assert.assertEquals(lucene.delete(3), 0);


    }

    //简单的查询正文
    @Test
    public static void simpleQuery() throws Exception
    {
        SearchResult result = lucene.Search("摩托罗拉", 100, 0, 10);
        List<LuceneVO> list =  result.getList();
        Assert.assertEquals(result.getTotalCount(),1);
        for (LuceneVO luceneTO:list)
        {
            Assert.assertEquals(luceneTO.getId(), 5);
        }

    }

    //d多个条件满足一个就查询出来
    @Test
    public static void query() throws Exception
    {
        SearchResult result = lucene.Search(new String[]{"title"},new String[]{"最终"},100,0,10);
        List<LuceneVO> list = result.getList();
        Assert.assertEquals(result.getTotalCount(),1);
        for (LuceneVO luceneTO:list)
        {
            Assert.assertEquals(luceneTO.getId(),4);
            System.out.println("id:" + luceneTO.getId());
            System.out.println("title:" + luceneTO.getTitle());
            System.out.println("content:" + luceneTO.getContent());
        }
    }

    //查询ID
    @Test
    public static void queryId() throws Exception
    {
        SearchResult result = lucene.Search(new String[]{"id"},new String[]{"3"},100,0,10);
        List<LuceneVO> list = result.getList();
        Assert.assertEquals(result.getTotalCount(),1);
        for (LuceneVO luceneTO:list)
        {
            Assert.assertEquals(luceneTO.getId(),3);
            System.out.println("id:" + luceneTO.getId());
            System.out.println("title:" + luceneTO.getTitle());
            System.out.println("content:" + luceneTO.getContent());
        }
    }


    //完整的测试
    @Test
    public static void queryAll() throws Exception
    {
        lucene.deleteAll();
        createIndex();
        query();
        queryId();
        deleteForId();
        SearchResult result = lucene.Search(new String[]{"id"},new String[]{"3"},100,0,10);
        Assert.assertEquals(result.getTotalCount(),0);

    }
}

十四.rpc 调用支持

更多ajx,josn,rpc,xml调用,加密传输请看

十五.网站安全控制

apache有个模块evasive2能够限制用户ip到访问量。但对于某个数据到,或者页面遭到不断刷新访问很难控制。因为它没有和你的业务逻辑配合起来。 本人在做投票软件到时候就碰到这个问题,刷票到人太多,如果不控制,服务器都会被刷死。(瓶颈在数据库的update有lock已经应答不过来来,有人说用删除在添加到方法,那是坑,因为高并发下,最后结果完全不正确), 为了解决这个问题,如果在程序里边判断。也只能真对某一个软件或者部分,很不通用。于是就产生了TXweb的evasive功能,它能控制每一个页面到get,post等的访问频率, 并且通过简单到配置就能管理好TXWeb项目下的所有页面。是针对apache下evasive2模块的细粒度补充。 evasive的控制配置说明看这里

十五.开启调试模式

有时代吗发生错误,在脚本中确定的错误位置不是很精确,这时候可以使用调试模式。 配置文件中设置 jspxDebug=true,就开启了。脚本中可以中控制台输出
${console.println("输出你的调试信息");}

十六.分布式支持

分布式支持目前提供两种方式,一般系统也应该是两种方式。 1.底层数据的分布式,一般不提供给第三方,验证逻辑简单。 2.上层的开放式对外接口,提供给第三方调用支持,验证逻辑根据系统复杂情况来确定。

底层分布式Hessian协议支持

dubbo也是使用hessian是基于binary-rpc协议,应该说没多少差别,差别就是Hessian 的老板caucho没能像阿里一样把它的名气搞大,反而是dubbo来带动了hessian的名气。 本构架对hessian的支持主要就是,配置在ioc中的有接口对象,比如:DAO,就可以直接分布式调用了。 下边看看如果通过简单的配置就实现了分布式调用。 ioc配置
    <sioc namespace="user/service" extends="user">
        <bean id="serviceAction" class="com.jspx.txweb.support.ServiceAction" singleton="false">
            <string name="verifyKey">验证码</string>
            <string name="ips">127.0.0.1;192.168.0.*;允许调用的ip</string>
            <array class="string" name="allowName">
                <value>memberDAO</value>
        <value>memberService</value>
                <value>允许外部调用的ioc名称</value>
            </array>
        </bean>
        <bean id="memberService" class="jspx.user.service.impl.MemberServiceImpl" singleton="false"/>
    </sioc>
com.jspx.txweb.support.ServiceAction,就是提供外部调用的接口服务 TXWeb配置
  <package extends="user" namespace="user/service">
       <action name="*" caption="远程接口" class="serviceAction" pass="defInterceptor;licenseInterceptor" />
  </package>
MemberServiceImpl.java 例子实现
package jspx.user.service.impl;

import jspx.user.service.MemberService;

public class MemberServiceImpl implements MemberService {
    public String sayHello(String name) {
        return "Hello " + name;
    }
}
MemberService.java
package jspx.user.service;

public interface MemberService {
    public String sayHello(String name);

}
客户端调用例子
    /*
     * Hessian 的原生调用方式一个小例子
     * @throws Exception
     */
    @Test
    public static void testBaseCall() throws Exception {
        String url = "http://127.0.0.1/user/service/memberService.jhtml?key=服务端配置的验证码";
        HessianProxyFactory factory = new HessianProxyFactory();
        MemberService memberService =(MemberService)factory.create(url);
        String out = memberService.sayHello("xxxxxxxxxx");
        System.out.println("---------------out=" + out);
    }
如果你调用的是服务器上的DAO对象,可以是下边的样子。 memberDAO 是你配置的DAO名称 namespace=user 是ioc中配置的命名空间
    @Test
    public static void testDaoCall() throws Exception {
        String url = "http://127.0.0.1/user/service/memberDAO.jhtml?namespace=user&key=服务端配置的验证码";
        HessianProxyFactory factory = new HessianProxyFactory();
        MemberDAO memberDAO =(MemberDAO)factory.create(url);
        List<Member> members = memberDAO.getList(null,null,null,null,null,1,10,false);
        for (Member member:members)
        {
            System.out.println("---------------out=" + member.toString());
        }
    }

上层分布式RPC协议支持

上层的分布式就是ROC了,同时支持XML和JSON方式,不过用下来,还是JSON方式更好。 调用方式参考ROC,内部提供了 HttpClientFactory.createRocHttpClient(url) 来提供调用支持。 这里可以调用Action,和View两个对象,是不是很方便。
    @Test
    public static void testRocCall() throws Exception {
        String url = "http://127.0.0.1/jcms/userlist.jhtml";
        HttpClient httpClient = HttpClientFactory.createRocHttpClient(url);
        JSONObject json = new JSONObject();
        json.put("version","3.0");
        JSONObject param = new JSONObject();
        param.put("id","userlist");
        json.put("params",param);
        JSONArray result = new JSONArray();
        result.add("member");
        json.put("result",result);
        String out = httpClient.post(json);
        System.out.println("----------out=" + out);
    }
下边在描述一下参数, 上边的JSONObject就是为了生成下边的参数格式
{
    "id": "userlist",
     "version": "3.0",
    "method": {
        "name": "调用的方法"
    },
    "params": {
        "参数1": 1,
        "参数2": 2
    },
   "result": ["list","currentPage","要返回的数据方法"]
}
加密传输:如果请求头里边包含,那么将采用加密传输方式。X-Requested-With:jspx.net-secret-roc 下边是加密传输的js代吗,采用mootools实现,你可以采用任何语言实现发送请求都可以实现加密传输。 迷药和偏移量这些,必须和配置在jspx.properties中社相对应。
Request.SECRET = new Class({
    Extends: Request.JSON,
    options: {
        method: 'POST', //必须
        encoding: 'UTF-8',
        async:true,
        link:'chain',
        headers: {'X-Requested-With': 'jspx.net-secret-roc'}
    },
    initialize: function (options) {
        var enData = null;
        if (typeof(options.data) != 'string')
        {
            enData = JSON.encode(options.data);
        }
        var k1 = getRandom(16)+'';
        var iv = getRandom(16)+'';
        data = encryptAEC_CBC_Pkcs7(enData,k1,iv);
        var ps = k1+'-'+iv;

        var key = encryptRSA(ps,pubK);
        var posts = {
            "keyType": "rsa",
            "dataType":"aes",
            "key": key,
            "data": data
        };
        options.data = JSON.encode(posts);
        options.onError=function(text, error)
        {
            document.body.innerHTML=
                '<!DOCTYPE html><html lang="zh"><head><meta charset="UTF-8"><title>'+ error+
                '</title></head><body><div class="container">'+
                text + '</div></body></html>';
        }
        this.parent(options);
    }
});
也许你测试的时候不成功,因为还有权限问题,当你使用本构架的时候已经自动的融入了权限控制。 在应用的后台需要设置这个userlist的url权限为游客可执行。就可以了。 生产环境使用的时候,应该先使用登录认证,得到用户的jsessionId,然后在参数里边一起传递给服务器。 这样服务器知道你的角色后,就可以正确的判断哪些方法,和哪些数据你可以调用。

SOAP协议支持

< code >SOAP协议支持分两种方式,一种是合并的80端口,一种的独立的端口,两种方式差别很大。
独立的方式简单很多,依赖包也少

独立的端口方式

这里是sioc配置
<?xml version="1.0" encoding="UTF-8"?>
<sioc namespace="soap" extends="global" application="Web Service">
    <!--aop 启动项 begin-->
    <bean id="jspxNetWebFactory" class="com.jspx.txweb.service.web.JspxNetWebFactoryImpl" singleton="false" />
    <bean id="aopEndpointPublish" class="com.jspx.sioc.aop.AopEndpointPublishServices" singleton="true">
        <string name="host">${soapPublishUrl}</string>
        <map name="serviceMap" >
            <value key="service/jspxnetwebfactory">jspxNetWebFactory@soap</value>
        </map>
    </bean>
    <!--aop 启动项 end-->
</sioc>
可以看到开关在jspx.properties文件中,开启后就可以调用了,加入sioc配置主要是方便接口的统一管理 当然自己直接也可以
Endpoint.publish("http://127.0.0.1:8081/service/jspxnetwebfactory", new JspxNetWebFactoryImpl());
#是否开启发布接口
aopEndpointPublish=false
soapPublishUrl=http://127.0.0.1:8081
soapNamespace=soap

合并80端口方式

目前测试依赖jdk8比较严重。也需要很多包jaxws-rt stax2 streambuffer istack FastInoset gmbal等, 这种方式并不依赖本构架。更多详细可以自己在网上找。 sun-jaxws.xml
<?xml version="1.0" encoding="UTF-8"?>
<endpoints  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"  version="2.0">
    <endpoint name="JspxNetWebFactory"
        implementation="com.jspx.txweb.service.web.JspxNetWebFactoryImpl" 
        interface="com.jspx.txweb.service.web.WebBeanFactory"
        url-pattern="/service/jspxnetwebfactory"/>  
</endpoints>
如何实现加密传输,因为soap控制请求头不方便。 所以协议格式为 protocol 例如
{
     "id": "调用的id",
     "version": "3.0",
     "protocol": "jspx.net-secret-roc",
    "method": {
        "name": "调用的方法"
    },
    "params": {
        "参数1": 1,
        "参数2": 2
    },
   "result": ["list","currentPage","要返回的数据方法"]
}
下边是一个SOAP调用的例子,JspxnetwebfactoryLocator 是SOAP生成命令生成的。 http://127.0.0.1/service/jspxnetwebfactory?wsdl apache+tomcat配置中不要忘记把service目录分配到tomcat中处理。 jspxNetWebFactory,为本构架提供。 私秘保存在 ConnectApp 数据库表中,构架中提供了相应的view和action.
  public static void main(String[] args) throws java.lang.Exception {
        //先要注册,审核通过后可以得到appId,和 私秘privateKey
        long appId = 2;
        String privateKey = "私秘";
        long timeMillis = System.currentTimeMillis();

        //SOAP wsdl 命令工具自动生成 JspxnetwebfactoryLocator
        JspxnetwebfactoryLocator jspxnetwebfactoryLocator = new JspxnetwebfactoryLocator();
        JspxNetWebFactoryImpl jspxNetWebFactory = jspxnetwebfactoryLocator.getJspxNetWebFactoryImplPort();
        String publicKey = jspxNetWebFactory.getNetPublicKey();

        //验证码生成算法,jspx.net 包中提供
        String verify = TXWebUtil.getAppHashVerify(appId, publicKey, privateKey, timeMillis);

        //app方式登陆,登陆成功后能给得到一个sessionId,以后调用都将使用这个sessionId,权限将是申请这个app的人的权限,
        //如果调用提示权限不够,说明是申请接口这个账号的权限不够,需要中角色的配置,给予权限
        String result = jspxNetWebFactory.login(appId,timeMillis,verify);

        JSONObject resultJson = new JSONObject(result);
        String sessionId = resultJson.getString("sessionId");

        //如果登陆失败,这里会显示失败信息,成功将会出现sessionId
        System.out.println("登陆信息返回----out=" + result);

        //将sessionId 保存好,以后调用不用再次登陆,直接使用 sessionId,
        //除非系统提示需要登陆,默认有效期为30分钟,和tomcat的session配置有关联
        jspxNetWebFactory.setSessionId(sessionId);

        //调用的提交写法,你可以输出字符串看看
        JSONObject json = new JSONObject();
        json.put("version","3.0");
        json.put("protocol","jspx.net-roc");
        json.put("id","userlist@jcms"); //调用的方法
        JSONObject param = new JSONObject(); //参数
        param.put("id",10001);
        json.put("params",param);
        JSONArray resultV = new JSONArray();
        resultV.add("member");
        json.put("result",resultV);

        //调用成功后返回的数据,json的调用就返回json,XML格式的调用就返回XML
        String resultP = jspxNetWebFactory.process(json.toString());
        System.out.println(resultP );
    }

加密传输格式说明

data为上边未加密格式,转换的字符串加密传输
{
     "id": "调用的id",
     "version": "3.0",
     "protocol": "jspx.net-secret-roc",
     "keyType": "rsa",
     "dataType":"aes",
     "key": key,
     "data": data
}
rsa只加密aes密码,aes加密数据,key包含密码和偏移量