1. 上传文件
大部分项目避免不了要上传文件.
struts2提供了封闭的上传文件的入口, 网络上也存在大量的插件用于网页表单中上传文件.
由于自己习惯用SSH框架, 所以介绍一下struts2中文件上传的要点.
struts2对文件上传的格式,及上传文件的大小有很好的限制.
#Constants
#struts.locale=zh_CN
struts.i18n.encoding=UTF-8
struts.action.extension=,
struts.objectFactory=spring
struts.custom.i18n.resources=messages
#1G
struts.multipart.maxSize=1048576000
struts.ui.theme=css_xhtml
#struts.enable.SlashesInActionNames=true
struts.devMode=false
struts.i18n.reload=true
struts.configuration.xml.reload=true
struts.serve.static.browserCache=false
struts.ui.theme=simple
上传时的页面 DOM
<s:file cssClass="doc" name="documents[0].actionFile" />
这样上传的文件会自动map到对象的属性上, 或者我们使用File []fileArray来预存表单提交到action的文件队列.
不过通过此方法上传的文件队列是没有文件类型和文件名的,且在服务器以临时文件存在, 而且我们仍需要对等的使用 String []fileArrayFileName来存取对应的文件名.当然还有ContentType.
所以到了action阶段, 需要再对文件进行IO重写操作,以保证文件以正确的格式存在合适的地方.
以下代码用于读取存文件.
public static JSONObject handlerFileUpload(String dir, File file, String fileName) throws IOException { JSONObject jo = new JSONObject(); String realpath = ServletActionContext.getServletContext().getRealPath( dir);//获取根目录+存放目录 System.out.println("realpath: " + realpath); File doc; if (file != null) { doc = new File(realpath, fileName);//创建文件 if (!doc.exists()) { if (!doc.getParentFile().exists()) { doc.getParentFile().mkdirs();//判断目录 } OutputStream os = new FileOutputStream(new File(realpath, fileName)); InputStream is = new FileInputStream(file); byte[] buf = new byte[1024]; int length = 0; while (-1 != (length = is.read(buf))) { os.write(buf, 0, length);//写文件 } is.close(); os.close(); } jo.put("size", "" + file.length() / 1000 / 1000.0);//获取文件信息 jo.put("fileName", fileName); jo.put("type", new MimetypesFileTypeMap().getContentType(file)); } System.err.println("file:" + jo); return jo;//返回文件信息Json }
2. 上传图片
上传图片使用了最新的Ueditor的文件上传功能, Ueditor是个强大的富文本编辑器,可以编辑HTML或者纯文本, 且能带格式,字体,字号,表格,段落,附件,插图,link.
文件目录:
基本上把
WebContent/ueditor/jsp/lib
下的jar包考到项目的lib下, 就可以使用啦.
不过, 若需要配置上传的路径, 附件, 插图, 涂鸦, 文件, 视频的上传到指定路径则需要配置一个json文件: /WebContent/ueditor/jsp/config.json
/* 上传图片配置项 */ "imageActionName": "uploadimage", /* 执行上传图片的action名称 */ "imageFieldName": "upfile", /* 提交的图片表单名称 */ "imageMaxSize": 2048000, /* 上传大小限制,单位B */ "imageAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 上传图片格式显示 */ "imageCompressEnable": true, /* 是否压缩图片,默认是true */ "imageCompressBorder": 1600, /* 图片压缩最长边限制 */ "imageInsertAlign": "none", /* 插入的图片浮动方式 */ "imageUrlPrefix": "", /* 图片访问路径前缀 */ "imagePathFormat": "/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */ /* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */ /* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */ /* {time} 会替换成时间戳 */ /* {yyyy} 会替换成四位年份 */ /* {yy} 会替换成两位年份 */ /* {mm} 会替换成两位月份 */ /* {dd} 会替换成两位日期 */ /* {hh} 会替换成两位小时 */ /* {ii} 会替换成两位分钟 */ /* {ss} 会替换成两位秒 */ /* 非法字符 : * ? " < > | */ /* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */
需要注意的是根路径指向的是tomcat的webapps,并不是项目目录.
页面调用:
- 引用js包
<script type="text/javascript" src="../ueditor/ueditor.config.js"></script> <script type="text/javascript" src="../ueditor/ueditor.all.js"></script> <script type="text/javascript" src="../ueditor/lang/en/en.js"></script> <script type="text/javascript" src="../resources/Admin/js/imageUploadModel.js"></script>
- 页面DOM
<img id="preview" alt="The picture is missing or still not upload" src="" class="bannerimg" /> <s:textfield id="picture" name="headerImg" onclick="upImage()" cssClass="scanimg input-sm" /> <button type="button" class="input-sm" href="javascript:void(0);" onclick="clearImage();">Clear Image</button>
- JS实现
var _editor = UE.getEditor('upload_ue'); _editor.ready(function() { // 设置编辑器不可用 // _editor.setDisabled(); 这个地方要注意 一定要屏蔽 // 隐藏编辑器,因为不会用到这个编辑器实例,所以要隐藏 _editor.hide(); // 侦听图片上传 _editor.addListener('beforeinsertimage', function(t, arg) { // 将地址赋值给相应的input,只去第一张图片的路径 var imgs = ''; for ( var a in arg) { imgs += arg[a].src + ','; } imgs.toString().replace(/(,$)/g,'') $("#picture").attr("value", arg[0].src); // 图片预览 $("#preview").attr("src", arg[0].src); }) }); // 弹出图片上传的对话框 function upImage() { var myImage = _editor.getDialog("insertimage"); myImage.open(); } //清除图片 function clearImage() { $("#picture").attr("value", ""); // 图片预览 $("#preview").attr("src", ""); } //预览图片功能 $(".scanimg").hover(function() { $thisImg = $(this).prev("img"); var imgObj = new Image(); $widthU = $thisImg.css('width'); $width = $widthU.substring(0, $widthU.indexOf('px')); $scanleft = $(this).position().left; $scantop = $(this).position().top; $thisImg.css({ "left" : ($scanleft + $width * 1.4) + "px", "top" : ($scantop - $width / 3) + "px" }); $thisImg.show(); }, function() { $(this).prev("img").hide(); });
- 预览css
.scanimg { cursor: pointer; } .scanimg:HOVER { color: red; text-decoration: underline; } .bannerimg { display: none; position: absolute; width: 200px; min-height: 50px; background-color: rgba(66,139,202,1); /* min- 500px; */ /* max- 600px; */ }
至此, 所有相关的要素准备完毕.
预览样式:
3. validation
表单都需要验证,一般有三种方式来处理验证表单的问题
- jQuery validation.js来做
$(document).ready(function() { $('#user').next().slideDown(0); validate(); }); function validate() { var rules = { email : { required : true, email : true, uniquedEmail : true }, username : { required : true, maxlength: 16 }, password : { required : true, rangelength : [ 8, 16 ] }, company : { required : true, maxlength : 16 } } formValidate.validate('myform', rules); } formValidate = { validate : function(formId, rules) { validateForm(formId, rules); } } function validateForm(formId, rules, message) { $('#' + formId) .validate( { debug : true, // 调试模式取消submit的默认提交功能 errorClass : "error-msg", // 默认为错误的样式类为:error focusInvalid : false, // 当为false时,验证无效时,没有焦点响应 onkeyup : false, submitHandler : function(form) { // 表单提交句柄,为一回调函数,带一个参数:form form.submit(); // 提交表单 }, rules : rules, messages : { password : { rangelength : 'The length of the password you input must be between 8 and 16 characters' }, oldPwd : { rangelength : 'The length of the password you input must be between 8 and 16 characters' }, newPwd : { rangelength : 'The length of the password you input must be between 8 and 16 characters' }, confirmPwd : { rangelength : 'The length of the password you input must be between 8 and 16 characters' } } }); } jQuery.validator.addMethod("tele", function(value, element) { var tel = /^d{3,4}-?d{7,9}$/; return this.optional(element) || (tel.test(value)); }, "Please write right tele number"); jQuery.validator.addMethod("uniquedEmail", function(value, element) { var dataJson = new Object(); $.ajax({ url : 'isRegEmailExist', type : 'post', data : { email : value }, async : false, // 默认为true 异步 error : function() { dataJson.status = 502; }, success : function(data) { dataJson = JSON.parse(data); } }); return this.optional(element) || (dataJson.status == 200); }, "The email is existed, please input other one!");
<s:form action="reg" method="post" enctype="multipart/form-data" id="myform">
.error-msg { color: #ff0000; }
上述的方式在于灵活多变,不便的地方也看到了,需要调用ajax来请求服务器验证,且不同的浏览器对ajax返回的内容有不同的解释. 默认校验规则
(1)required:true 必输字段
(2)remote:"check.php" 使用ajax方法调用check.php验证输入值
(3)email:true 必须输入正确格式的电子邮件
(4)url:true 必须输入正确格式的网址
(5)date:true 必须输入正确格式的日期 日期校验ie6出错,慎用
(6)dateISO:true 必须输入正确格式的日期(ISO),例如:2009-06-23,1998/01/22 只验证格式,不验证有效性
(7)number:true 必须输入合法的数字(负数,小数)
(8)digits:true 必须输入整数
(9)creditcard: 必须输入合法的信用卡号
(10)equalTo:"#field" 输入值必须和#field相同
(11)accept: 输入拥有合法后缀名的字符串(上传文件的后缀)
(12)maxlength:5 输入长度最多是5的字符串(汉字算一个字符)
(13)minlength:10 输入长度最小是10的字符串(汉字算一个字符)
(14)rangelength:[5,10] 输入长度必须介于 5 和 10 之间的字符串")(汉字算一个字符)
(15)range:[5,10] 输入值必须介于 5 和 10 之间
(16)max:5 输入值不能大于5
(17)min:10 输入值不能小于10 - 编码方式校验
1) Action一定要继承自ActionSupport
2) 针对某个要进行校验的请求处理方法编写一个 public void validateXxx()方法,在方法内部进行表单数据校验.
3) 也可针对所有的请求处理方法编写public void validate()方法。
4) 在校验方法中,可以通过addFieldError()方法来添加字段校验错误消息。
5) 当校验失败时,Struts框架会自动跳转到name为input的Result页面。在校验失败页面中,可以使用<s:fielderror/>来显示错误消息
6) 简单,灵活。但重用性不高。
-
XML配置方式校验。在编码方式之前被执行。
1) 针对要校验的Action类,在同包下编写一个名为:Action类名-validation.xml校验规则文件。
2) 在校验规则文件中添加校验规则:具体的校验器名,参数可参看Struts2的reference或Struts2的API。
-
a) Field校验:针对Action类中每个非自定义类型的Field进行校验的规则。
-
<field name="要校验的Field名">
<field-validator type="校验规则器名" short-circuit="是否要短路径校验(默认是false)">
<param name="校验器要使用的参数名">值</param>
<message>校验失败时的提示消息</message>
</field-validator>
<!-- 还可添加其它的校验规则 -->
</field>
b) 非Field校验:针对Action类的某些Field使用OGNL表达进行组合校验。
<validator type="fieldexpression">
<param name="fieldName">pwd</param>
<param name="fieldName">pwd2</param>
<param name="expression"><![CDATA[pwd==pwd2]]></param><!-- OGNL表达式 -->
<message>确认密码和密码输入不一致</message>
</validator>
c) visitor校验:主要是用来校验Action类中的自定义类型Field。(针对使用模型驱动方式时)
i) 在Action类的的校验规则文件中针对自定义类型Field使用visitor校验规则。
<!-- 针对自定义Field使用visitor校验 -->
<field name="user">
<field-validator type="required" short-circuit="true">
<message>用户的信息必填</message><!-- 消息前缀 -->
</field-validator>
<field-validator type="visitor"><!-- 指定为visitor校验规则 -->
<param name="context">userContext</param><!-- 指定本visitor校验的上下文名 -->
<param name="appendPrefix">true</param><!-- 是否要添加校验失败消息的前缀 -->
<message>用户的</message><!-- 消息前缀 -->
</field-validator>
</field>
ii) 针对visitor的Field编写一个校验规则文件.文件名为: visitor字段类型名[-visitor校验的上下文名]-validation.xml. 例如: 本例中的文件名为User-userContext-validation.xml
注意: 此文件要存放到visitor字段类型所在的包下.
iii) 在visitor的Field校验规则文件中针对要校验的Field添加校验规则.
3) 在校验失败页面(名为input的result页面)中,可以使用<s:fielderror/>来显示错误消息。
4) 默认情况下,XML的校验规则对Action中所有的请求处理方法生效.此时应该只针对每个要校验的请求处理方法指定校验。有两种方式:
i) 只为Action中的指定方法指定校验规则文件,配置文件命名为:Action类型名-别名-validation.xml,
别名是要校验的方法对应的Action标签的name属性值。
如:UserAction在struts2.xml的配置为:
<package name="my" extends="struts-default" namespace="/">
<action name="user_*" class="com.javacrazyer.web.action.UserAction" method="{1}">
<result name="success">/info.jsp</result>
<result name="input">/user_{1}.jsp</result>
</action>
</package>
● UserAction中有registe方法和login方法,要对registe方法进行校验,则它的校验规则文件名为:UserAction-user_registe-validation.xml。
● 如果使用visitor校验器,必需指定visitor校验的上下文名。
ii) 在校验拦截器中指定要验证的方法。不太实用。
<action name="user_*" class="com.javacrazyer.web.action.UserAction" method="{1}">
<result name="success">/info.jsp</result>
<result name="input">/user_{1}.jsp</result>
<interceptor-ref name="defaultStack">
<!-- 给校验拦截器指定不进行校验的方法列表:用逗号隔开 -->
<param name="validation.excludeMethods">*</param>
<!-- 给校验拦截器指定要进行校验的方法列表:用逗号隔开 -->
<param name="validation.includeMethods">regist</param>
</interceptor-ref>
</action>
5) 同时使用客户端校验和服务器端校验
i) 设置<s:form>标签的validate属性:
false:默认值。校验框架只执行服务器端校验。
true:先执行客户端校验,然后再执行服务器端校验。
form标签会根据你在服务器端配置的验证规则生成对应的JavaScript验证代码。
目前支持的内置校验器:required、requiredstring、stringlength、regex validator、email、url、int、double
ii) 不太好用,不建议使用。建议使用jQuery进行页面表单校验。
6) 自定义校验器:
i) 继承自FieldValidatorSupport抽象类。重写validate(Object obj)方法
ii) 注册校验器类. 在应用程序的classpath下新建一校验器注册文件。名为validators.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator Config 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-config-1.0.dtd">
<validators>
<validator name="校验器名" class="校验器类的全限定名"/>
</validators>
-
4. Annotation方式校验: Struts2提供了注解的方式校验
-
1) @Validation 指明这个类或者接口将使用基于注解的校验。Struts2.1中已被标识为过时。
2) @Validations() 在同一个方法上要使用多个注解校验时。
3) @SkipValidation 指定某个方法不需要校验。否则所有方法都会使用校验。也可以在检验拦截器中使用validateAnnotatedMethodOnly
4) 13个内置校验器的注解版本:(注:这些注解都只能用在方法级别上) 具体参数参见Struts2的API或Reference。
@RequiredFieldValidator
@RequiredStringValidator
@StringLengthFieldValidator
@IntRangeFieldValidator
@DoubleRangeFieldValidator
@DateRangeFieldValidator
@ExpressionValidator
@FieldExpressionValidator
@RegexFieldValidator
@EmailValidator
@UrlValidator
@VisitorFieldValidator
@ConversionErrorFieldValidatoracc_registe.jsp
-
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <%@ taglib uri="/struts-tags" prefix="s" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Struts2中基于XML配置式的校验器使用示例</title> </head> <body> <h3>XML配置式校验器---注册页面</h3><hr/> <div style="color:red"><s:fielderror/></div> <form action="acc_registe.action" method="post"> <table> <tr> <td>ID</td> <td><input type="text" name="id" value="${param.id}"/></td> </tr> <tr> <td>登录名</td> <td><input type="text" name="name" value="${param.name}"/></td> </tr> <tr> <td>密码</td> <td><input type="password" name="pwd"/></td> </tr> <tr> <td>重复密码</td> <td><input type="password" name="pwd2"/></td> </tr> <tr> <td>时间</td> <td><input type="text" name="registed_date" value="${param.registed_date}"/></td> </tr> <tr> <td>email</td> <td><input type="text" name="email" value="${param.email}"/></td> </tr> <tr> <td>考试成绩</td> <td><input type="text" name="score" value="${param.score}"/></td> </tr> <tr> <td colspan="2"><input type="submit" value=" 提交 "/></td> </tr> </table> </form> </body> </html>
-
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN" "http://struts.apache.org/dtds/struts-2.1.7.dtd"> <struts> <!-- 请求参数的编码方式 --> <constant name="struts.i18n.encoding" value="UTF-8"/> <!-- 指定被struts2处理的请求后缀类型。多个用逗号隔开 --> <constant name="struts.action.extension" value="action,do,go,xkk"/> <!-- 当struts.xml改动后,是否重新加载。默认值为false(生产环境下使用),开发阶段最好打开 --> <constant name="struts.configuration.xml.reload" value="true"/> <!-- 是否使用struts的开发模式。开发模式会有更多的调试信息。默认值为false(生产环境下使用),开发阶段最好打开 --> <constant name="struts.devMode" value="false"/> <!-- 设置浏览器是否缓存静态内容。默认值为true(生产环境下使用),开发阶段最好关闭 --> <constant name="struts.serve.static.browserCache" value="false" /> <!-- 是否允许在OGNL表达式中调用静态方法,默认值为false --> <constant name="struts.ognl.allowStaticMethodAccess" value="true"/> <!-- 指定由spring负责action对象的创建 <constant name="struts.objectFactory" value="spring" /> --> <!-- 是否开启动态方法调用 --> <constant name="struts.enable.DynamicMethodInvocation" value="false"/> <package name="my" extends="struts-default" namespace="/"> <action name="acc_*" class="com.javacrazyer.web.action.AccountAction" method="{1}"> <result name="success">/info.jsp</result> <result name="input">/acc_{1}.jsp</result> </action> </package> </struts>
-
AccountAction.java
package com.javacrazyer.web.action; import java.util.Date; import com.opensymphony.xwork2.ActionSupport; public class AccountAction extends ActionSupport { private static final long serialVersionUID = -1418893621512812472L; private Integer id; private String name; private String pwd; private String pwd2; private Double score; private Date registed_date; private String email; public String registe() throws Exception{ System.out.println("registe-------------------"); return SUCCESS; } public String login()throws Exception{ return SUCCESS; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getScore() { return score; } public void setScore(Double score) { this.score = score; } public Date getRegisted_date() { return registed_date; } public void setRegisted_date(Date registedDate) { registed_date = registedDate; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public String getPwd2() { return pwd2; } public void setPwd2(String pwd2) { this.pwd2 = pwd2; } }
AccountAction-validation.xml [与AccountAction同目录]
user_login.jsp
UserAction.java
UserAction-user_registe-validation.xml
src/struts.xml
-
-
4. 过滤
过滤的问题源于URLEncode/URLDecode。字符在从前端到后台, GET方式会自动将URL行Encode,这就会导致空格,加号等字符被转码,所以后台需要再次解码。不过大部分的时候,Encode后并非一定能正确的Decode成当初的字符,所以又出现了BASE64,POST方式的代替方案。
5. 阻止浏览器自动填充password/username表单域
6. 富文本编辑器
7. namespace
8.表单重复提交
9. button/input/submit 提交的方式
10. struts2 Action返回result方式处理
11. 下载文件