欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

WEB文件上传之SpringMVC+ajaxfileupload使用(三)

程序员文章站 2022-06-07 21:08:03
...

 

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源码见附件