1. 页面使用Jquery ajaxfileupload插件的实现的基础上(见WEB文件上传之JQuery ajaxfileupload插件使用(二)),服务器端结合采用springMVC来实现
2. 实现技术点:
springMVC中正常处理时JSON数据的返回
springMVC中异常统一拦截处理时JSON数据的返回
springMVC中文件上传进度监听的实现
3. 具体实现:
UploadController.java
package com.test.controller;
import java.io.File;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import com.test.servlet.NoSupportExtensionException;
import com.test.servlet.State;
@Controller
@RequestMapping(value = "/mvc")
public class UploadController {
/** 日志对象*/
private Log logger = LogFactory.getLog(this.getClass());
private static final long serialVersionUID = 1L;
/** 上传目录名*/
private static final String uploadFolderName = "uploadFiles";
/** 允许上传的扩展名*/
private static final String [] extensionPermit = {"txt", "xls", "zip"};
@RequestMapping(value = "/upload.do", method = RequestMethod.POST)
public @ResponseBody Map<String, Object> fileUpload(@RequestParam("file") CommonsMultipartFile file,
HttpSession session, HttpServletRequest request, HttpServletResponse response) throws Exception{
logger.info("UploadController#fileUpload() start");
//清除上次上传进度信息
String curProjectPath = session.getServletContext().getRealPath("/");
String saveDirectoryPath = curProjectPath + "/" + uploadFolderName;
File saveDirectory = new File(saveDirectoryPath);
logger.debug("Project real path [" + saveDirectory.getAbsolutePath() + "]");
// 判断文件是否存在
if (!file.isEmpty()) {
String fileName = file.getOriginalFilename();
String fileExtension = FilenameUtils.getExtension(fileName);
if(!ArrayUtils.contains(extensionPermit, fileExtension)) {
throw new NoSupportExtensionException("No Support extension.");
}
file.transferTo(new File(saveDirectory, fileName));
}
logger.info("UploadController#fileUpload() end");
return State.OK.toMap();
}
}
自定义CommonsMultipartResolver类
CustomCommonsMultipartResolver.java
package com.test.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import com.test.servlet.FileProcessListener;
public class CustomCommonsMultipartResolver extends CommonsMultipartResolver {
@Override
protected MultipartParsingResult parseRequest(HttpServletRequest request)
throws MultipartException {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
// 加入文件进度监听器 (原Source上添加) start
FileProcessListener processListener = new FileProcessListener(
request.getSession());
fileUpload.setProgressListener(processListener);
// 加入文件进度监听器 (原Source上添加) end
try {
List<FileItem> fileItems = ((ServletFileUpload) fileUpload)
.parseRequest(request);
return parseFileItems(fileItems, encoding);
} catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(),
ex);
} catch (FileUploadException ex) {
throw new MultipartException(
"Could not parse multipart servlet request", ex);
}
}
}
统一异常处理类
package com.test.controller;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.core.Ordered;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import com.test.servlet.NoSupportExtensionException;
import com.test.servlet.State;
public class ExceptionHandler implements HandlerExceptionResolver, Ordered {
private Log logger = LogFactory.getLog(this.getClass());
public int getOrder() {
return Integer.MIN_VALUE;
}
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
logger.info("ExceptionHandler#resolveException() start");
Map<String, Object> errorMap = null;
if(ex instanceof NoSupportExtensionException) {
errorMap = State.NO_SUPPORT_EXTENSION.toMap();
} else if(ex instanceof MaxUploadSizeExceededException){ //spring 中对apache common组件中抛出的FileSizeLimitExceededException做了转换
errorMap = State.OVER_FILE_LIMIT.toMap();
} else {
errorMap = State.ERROR.toMap();
}
//这里牵扯到spring mvc的异常处理,这里暂时做一个简单处理,不做深究
try {
ObjectMapper objectMapper = new ObjectMapper();
response.setContentType("text/html;charset=UTF-8");
JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), JsonEncoding.UTF8);
objectMapper.writeValue(jsonGenerator, errorMap);
} catch(Exception e) {
logger.error(e.getMessage(), e);
}
logger.info("ExceptionHandler#resolveException() end");
return null;
}
}
spring-context.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- configure the annotation scan base package --> <context:component-scan base-package="com.test.controller" /> <!-- open MVC annotation function --> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <!-- define the exception interceptor--> <bean class="com.test.controller.ExceptionHandler" /> <!-- define the upload config --> <bean id="multipartResolver" class="com.test.controller.CustomCommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8" /> <property name="maxUploadSize" value="31457280" /> <property name="maxInMemorySize" value="40960" /> <property name="uploadTempDir" value="tempFiles" /> </bean> </beans>
web.xml配置
<!-- spring context配置 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 编码过滤器配置 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- spring mvc 配置 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextAttribute</param-name> <param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <servlet>
4. 总结
实现过程中遇到的问题
问题1:ajaxfileupload插件是通过监听iframe的onload事件类实现文件上传的无刷新异步上传,实际就是在onload触发时获取iframe的body体中的文本,在响应头的content-Type为application/json的时候会出错,chrome/firefox的情况下会出现iframe获取出的json文本出现前后加入<pre></pre>标签的情况,导致ajaxfileupload插件解析json数据出错,原因应该是chrome/firefox浏览器处理content-Type为applicatioin/json自动做了html的转换,所以自动加入了pre标签。而在IE浏览器下,直接不支持content-Type为application/json的情况提示下载文件。
解决:在返回json数据时将响应头content-Type设置为text/html;charset=UTF-8,通过配置spring配置文件实现
<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
结论:ajaxfileupload插件只支持content-Type=text/html的响应头,进行数据返回。
问题2:@ResponseBody注解只能针对正常处理时的json数据返回,异常处理时无作用
解决: 通过自定义HandlerExceptionResolver类解决,不过这里的解决方案不是最佳,无法根据是否使用@ResponseBody动态变化是否返回JSON数据
问题3: springMVC文件上传进度监听器没有默认的实现
解决: 直接使用apache common的文件上传进度监听接口,并通过重写CommonsMultipartResolver类的parseRequest方法进行和Fileupload类进行绑定
Demo源码见附件