,*表示如果没有找到对应的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>
- TXWeb内带的返回类型包括了ajax的使用,必须在配置中注明type="ajax"才允许外部ajax调用。
- 如何使用request,response? 继承ActionSupport后,你就直接可以直接使用这两个对象了。
- 如何得到请求参数?继承ActionSupport类对,可以通过getString("变量名称"),getInt("变量名称")...得到相应类型的请求参数.
- addActionMessage保存消息,addFieldInfo 保存错误信息。
- 返回类型支持xml,json等多种类型,已经足够使用,如果要返回其他类型你可以使用response直接输出。
- 继承ActionSupport后你可以通过,getTemplatePath()得到当前页面所在的目录,getTemplateFile() 可以得到当前模板文件的所在路径。
- 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包含密码和偏移量