Spring文件上传,包括一次选中多个文件

为方便演示, 所有处理逻辑全部放在Controller完成, 不再写Service等各层接口及实现. 如需在Service层处理, 思路及方法也是完全一样的.


先说前台. 运行以后就是这样子的. 一个非常简单的表单页面, 两个文件上传按钮, 一个提交

Spring文件上传,包括一次选中多个文件


其中单个文件上传, 即只能选择一个文件, 无法同时选择多个

Spring文件上传,包括一次选中多个文件

相对的, 多个文件就是可以同时选择多个文件了

Spring文件上传,包括一次选中多个文件


文件选择以后就是这个样子

Spring文件上传,包括一次选中多个文件


代码如下: 一个form, 文件上传就是一个<input>输入, 属性type="file". 此时只能选择单个文件. 而后面加一个multiple, 即可同时选择多个文件


action属性中的路径后缀为.htm, 是因为我的环境中配置了映射, 所以要在Controller中指定的路径后添加一个.htm后缀, 否则系统会报404. 如果没有配置该项则无需添加后缀

[html] view plain copy
  1. <body>  
  2.     <form action="${pageContext.request.contextPath}/test/upload.htm" enctype="multipart/form-data" method="post">  
  3.         单个文件: <input type="file" name="fileTest"><br/>  
  4.         多个文件: <input type="file" name="fileList" multiple/></br/>  
  5.         <input type="submit" value="提交" />  
  6.     </form>  
  7. </body>  

另一点需要注意的是, 要实现文件上传, form中必须指定属性enctype="multipart/form-data". method属性为"post"


前台就这些东西了, 没什么特殊的. 然后再看后台


首先Spring配置文件中加这么一个bean

[html] view plain copy
  1. <bean id="multipartResolver"  
  2.     class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
  3.     <!-- 默认编码 -->  
  4.     <property name="defaultEncoding" value="utf-8" />  
  5.     <!-- 文件大小最大值 -->  
  6.     <property name="maxUploadSize" value="10485760" />  
  7.     <!-- 内存中的最大值 -->  
  8.     <property name="maxInMemorySize" value="40960" />  
  9. </bean>  


也可以在代码中直接创建对象

[java] view plain copy
  1. CommonsMultipartResolver commonsMultipartResolver = new   CommonsMultipartResolver(request.getSession().getServletContext());  

但是个人认为配置以后使用比较方便, 各位根据实际需要来吧


其中maxUploadSize属性用来设计上传文件的最大值, 单位是字节. 注意这里是总的上传限制, 比如设置为10M, 上传了4个3M的文件. 虽然单个文件都在10M以内, 但是总大小已经超过10M限制, 会抛出MaxUploadSizeExceededException异常


[java] view plain copy
  1. package com.test.controller;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.util.Iterator;  
  6. import java.util.List;  
  7.   
  8. import org.springframework.stereotype.Controller;  
  9. import org.springframework.web.bind.annotation.RequestMapping;  
  10. import org.springframework.web.bind.annotation.ResponseBody;  
  11. import org.springframework.web.multipart.MultipartFile;  
  12. import org.springframework.web.multipart.MultipartHttpServletRequest;  
  13.   
  14. /** 
  15.  * 文件上传测试类 
  16.  */  
  17. @Controller  
  18. @RequestMapping("/test")  
  19. public class FileUploadController {  
  20.   
  21.     @ResponseBody  
  22.     @RequestMapping(value="upload")  
  23.     public void testUpload(MultipartHttpServletRequest request) throws IOException {  
  24.         /* 
  25.          * MultipartHttpServletRequest: 继承于HttpServletRequest以及MultipartRequest. 
  26.          * 其中MultipartRequest中定义了相关的访问操作. MultipartHttpServletRequest重写 
  27.          * 了HttpServletRequest中的方法, 并进行了扩展. 如果以HttpServletRequest来接收参 
  28.          * 数, 则需要先将其转为MultipartHttpServletRequest类型 
  29.          * MultipartHttpServletRequest request = (MultipartHttpServletRequest) HttpServletRequest; 
  30.          */  
  31.           
  32.         /* 
  33.          * 再说回刚才的form, 假设我们在单个文件选框中上传了文件1, 多个文件选框中上传了文件2, 3, 4. 
  34.          * 那么对于后台接收到的, 可以这么理解, 就是一个Map的形式(实际上它后台真的是以Map来存储的). 
  35.          * 这个Map的Key是什么呢? 就是上面<input>标签中的name=""属性. Value则是我们刚才上传的 
  36.          * 文件, 通过下面的示例可以看出每一个Value就是一个包含对应文件集合的List 
  37.          *  
  38.          * 传到后台接收到的Map就是这样: 
  39.          * fileTest: 文件1 
  40.          * fileList: 文件2, 文件3, 文件4 
  41.          *  
  42.          * 虽然从方法名的表面意义来看是得到文件名, 但实际上这个文件名跟上传的文件本身并没有什么关系. 
  43.          * 刚才说了这个Map的Key就是<input>标签中的name=""属性, 所以得到的也就是这个属性的值 
  44.          */  
  45.         Iterator<String> fileNames = request.getFileNames();  
  46.           
  47.         while (fileNames.hasNext()) {  
  48.               
  49.             //把fileNames集合中的值打出来  
  50.             String fileName=fileNames.next();  
  51.             System.out.println("fileName: "+fileName);  
  52.               
  53.             /* 
  54.              * request.getFiles(fileName)方法即通过fileName这个Key, 得到对应的文件 
  55.              * 集合列表. 只是在这个Map中, 文件被包装成MultipartFile类型 
  56.              */  
  57.             List<MultipartFile> fileList=request.getFiles(fileName);  
  58.               
  59.             if (fileList.size()>0) {  
  60.                   
  61.                 //遍历文件列表  
  62.                 Iterator<MultipartFile> fileIte=fileList.iterator();  
  63.                   
  64.                 while (fileIte.hasNext()) {  
  65.                       
  66.                     //获得每一个文件  
  67.                     MultipartFile multipartFile=fileIte.next();  
  68.                       
  69.                     //获得原文件名  
  70.                     String originalFilename = multipartFile.getOriginalFilename();  
  71.                     System.out.println("originalFilename: "+originalFilename);  
  72.                       
  73.                     //设置保存路径.   
  74.                     String path ="G:/testUpload/";  
  75.                       
  76.                     //检查该路径对应的目录是否存在. 如果不存在则创建目录  
  77.                     File dir=new File(path);  
  78.                     if (!dir.exists()) {  
  79.                         dir.mkdirs();  
  80.                     }  
  81.                       
  82.                     String filePath = path + originalFilename;  
  83.                     System.out.println("filePath: "+filePath);  
  84.                       
  85.                     //保存文件  
  86.                     File dest = new File(filePath);  
  87.                     if (!(dest.exists())) {  
  88.                         /* 
  89.                          * MultipartFile提供了void transferTo(File dest)方法, 
  90.                          * 将获取到的文件以File形式传输至指定路径. 
  91.                          */  
  92.                         multipartFile.transferTo(dest);  
  93.                           
  94.                         /* 
  95.                          * 如果需对文件进行其他操作, MultipartFile也提供了 
  96.                          * InputStream getInputStream()方法获取文件的输入流 
  97.                          *  
  98.                          * 例如下面的语句即为通过 
  99.                          * org.apache.commons.io.FileUtils提供的 
  100.                          * void copyInputStreamToFile(InputStream source, File destination) 
  101.                          * 方法, 获取输入流后将其保存至指定路径 
  102.                          */  
  103.                         //FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), dest);  
  104.                     }  
  105.                       
  106.                     //MultipartFile也提供了其他一些方法, 用来获取文件的部分属性  
  107.                       
  108.                     //获取文件contentType  
  109.                     String contentType=multipartFile.getContentType();  
  110.                     System.out.println("contentType: "+contentType);  
  111.                       
  112.                     /* 
  113.                      * 获取name 
  114.                      * 其实这个name跟上面提到的getFileName值是一样的, 
  115.                      * 就是Map中Key的值. 即前台页面<input>中name="" 
  116.                      * 属性. 但是上面的getFileName只是得到这个Map的Key, 
  117.                      * 而Spring在处理上传文件的时候会把这个值以name属性 
  118.                      * 记录到对应的每一个文件. 如果需要从文件层面获取这个 
  119.                      * 值, 则可以使用该方法  
  120.                      */  
  121.                     String name=multipartFile.getName();  
  122.                     System.out.println("name: "+name);  
  123.                       
  124.                     //获取文件大小, 单位为字节  
  125.                     long size=multipartFile.getSize();  
  126.                     System.out.println("size: "+size);  
  127.                       
  128.                     System.out.println("---------------------------------------------------");  
  129.                 }  
  130.             }  
  131.         }  
  132.     }  
  133. }  

<%@ page contentType="text/html;charset=UTF-8"%>
<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
<html>
<head>
<title>用户管理</title>
<meta name="decorator" content="default" />
<script type="text/javascript">
    $(document).ready(function(){
        $("#btnImport").click(function(){
            $.jBox($("#importBox").html(),
            {
                title:"导入数据",
                buttons:
                {
                    "关闭":true
                },
                bottomText:"导入文件不能超过5M,仅允许导入“xls”或“xlsx”格式文件!"
            });
        });
    });
</script>
</head>
<body>
<div id="importBox">
<form id="importForm" action="${ctx}/sys/hrSendMail/import" method="post" enctype="multipart/form-data" class="form-search" style="padding-left: 20px; text-align: center;" onsubmit="loading('正在导入,请稍等...');">
<br /> 
<input id="uploadFile" name="files" type="file" style="width: 330px" multiple/>
<br /> <br />
<input id="btnImportSubmit" class="btn btn-primary" type="submit" value=" 发 送  " /> 
</form>
</div>
<sys:message content="${message}" />
</body>
</html>

/* 发送工资条数据

* @param file
* @param redirectAttributes
* @return
*/
@RequestMapping(value = "import", method = RequestMethod.POST)
public String importFile(@RequestParam("files") MultipartFile[] files,
RedirectAttributes redirectAttributes) {
if (files != null && files.length > 0) {
try {
int successNum = 0;
int failureNum = 0;
StringBuilder failureMsg = new StringBuilder();
ImportExcel ei = null;
List<Payroll> list = null;
List<String> messageList = null;
for (MultipartFile file : files) {
ei = new ImportExcel(file, 0, 0);
list = ei.getHrDataList(Payroll.class);
for (Payroll Payroll : list) {
try {
if (emailFormat(Payroll.getMail())) {
BeanValidators.validateWithException(validator,
Payroll);
SendMailUtil.sendInlineMail(Payroll);
successNum++;
} else {
failureMsg.append("<br/>邮箱 "
+ Payroll.getMail() + " 不符合邮箱规范  ");
failureNum++;
}
} catch (ConstraintViolationException ex) {
failureMsg.append("<br/>工资条" + Payroll.getMail()
+ " 发送失败:");
messageList = BeanValidators
.extractPropertyAndMessageAsList(ex, ": ");
for (String message : messageList) {
failureMsg.append(message + "; ");
failureNum++;
}
} catch (Exception ex) {
failureMsg.append("<br/>工资条 " + Payroll.getMail()
+ " 发送失败:" + ex.getMessage());
}
}
}
if (failureNum > 0) {
failureMsg.insert(0, ",失败 " + failureNum + " 条工资条,发送信息如下:");
}
addMessage(redirectAttributes, "已成功发送 " + successNum + " 条工资条"
+ failureMsg);
} catch (Exception e) {
addMessage(redirectAttributes, "发送工资条失败!失败信息:" + e.getMessage());
}
}
return "redirect:" + adminPath + "/sys/hrSendMail/index?repage";
}

/**
* 验证输入的邮箱格式是否符合

* @param email
* @return 是否合法
*/
public boolean emailFormat(String email) {
boolean tag = true;
if (!email.matches("[\\w\\.\\-][email protected]([\\w\\-]+\\.)+[\\w\\-]+")) {
tag = false;
}
return tag;
}