文件上传下载

来源:互联网 发布:宅女轩轩淘宝 编辑:程序博客网 时间:2024/05/21 21:42

文件上传

文件上传操作通常会附加一些限制,如:文件类型、上传文件总大小、每个文件的最大大小等。除此以外,作为一个通用组件还需要考虑更多的问题,如:支持自定义文件保存目录、支持相对路径和绝对路径、支持自定义保存的文件的文件名称、支持上传进度反馈和上传失败清理等。另外,本座也不想重新造车轮,本组件是基于 Commons File Upload 实现,省却了本座大量的工作 ^_^ 下面先从一个具体的使用例子讲起:

  • 上传请求界面及代码

 

  1. <form action="checkupload.action" method="post" enctype="multipart/form-data">  
  2.     First Name: <input type="text" name="firstName" value="丑">  
  3.     <br>  
  4.     Last Name: <input type="text" name="lastName" value="怪兽">  
  5.     <br>  
  6.     Birthday: <input type="text" name="birthday" value="1978-11-03">  
  7.     <br>  
  8.     Gender: 男 <input type="radio"" name="gender" value="false">  
  9.         &nbsp;女 <input type="radio"" name="gender" value="true" checked="checked">  
  10.     <br>  
  11.     Working age: <select name="workingAge">  
  12.         <option value="-1">-请选择-</option>  
  13.         <option value="3">三年</option>  
  14.         <option value="5" selected="selected">五年</option>  
  15.         <option value="10">十年</option>  
  16.         <option value="20">二十年</option>  
  17.     </select>  
  18.     <br>  
  19.     Interest: 游泳 <input type="checkbox" name="interest" value="1" checked="checked">  
  20.         &nbsp;打球 <input type="checkbox" name="interest" value="2" checked="checked">  
  21.         &nbsp;下棋 <input type="checkbox" name="interest" value="3">  
  22.         &nbsp;打麻将 <input type="checkbox" name="interest" value="4">  
  23.         &nbsp;看书 <input type="checkbox" name="interest" value="5" checked="checked">  
  24.     <br>  
  25.     Photo 1.1: <input type="file"" name="photo-1">  
  26.     <br>  
  27.     Photo 1.2: <input type="file"" name="photo-1">  
  28.     <br>  
  29.     Photo 2.1: <input type="file"" name="photo-2">  
  30.     <br>  
  31.     <br>  
  32.     <input type="submit" value="确 定">&nbsp;&nbsp;<input type="reset" value="重 置">  
  33. </form> 

从上面的 HTML 代码可以看出,表单有 6 个普通域和 3 个文件域,其中前两个文件域的 name 属性相同。

  • 上传处理代码
  1. import com.bruce.util.BeanHelper;  
  2. import com.bruce.util.Logger;  
  3. import com.bruce.util.http.FileUploader;  
  4. import com.bruce.util.http.FileUploader.FileInfo;  
  5.  
  6. import static com.bruce.util.http.FileUploader.*;  
  7.  
  8. @SuppressWarnings("unused")  
  9. public class CheckUpload extends ActionSupport  
  10. {  
  11.     // 上传路径  
  12.     private static final String UPLOAD_PATH        = "upload";  
  13.     // 可接受的文件类型  
  14.     private static final String[] ACCEPT_TYPES    = {"txt""pdf""doc"".Jpg""*.zip""*.RAR"};  
  15.     // 总上传文件大小限制  
  16.     private static final long MAX_SIZE            = 1024 * 1024 * 100;  
  17.     // 单个传文件大小限制  
  18.     private static final long MAX_FILE_SIZE        = 1024 * 1024 * 10;  
  19.       
  20.     @Override 
  21.     public String execute()  
  22.     {  
  23.         // 创建 FileUploader 对象  
  24.         FileUploader fu = new FileUploader(UPLOAD_PATH, ACCEPT_TYPES, MAX_SIZE, MAX_FILE_SIZE);  
  25.           
  26.         // 根据实际情况设置对象属性(可选)  
  27.         /*
  28.         fu.setFileNameGenerator(new FileNameGenerator() 
  29.         {  
  30.               
  31.             @Override  
  32.             public String generate(FileItem item, String suffix) 
  33.             {  
  34.                 return String.format("%d_%d", item.hashCode(), item.get().hashCode()); 
  35.             }  
  36.         });  
  37.           
  38.         fu.setServletProgressListener(new ProgressListener() 
  39.         {  
  40.               
  41.             @Override  
  42.             public void update(long pBytesRead, long pContentLength, int pItems) 
  43.             {  
  44.                 System.out.printf("%d: length -> %d, read -> %d.\n", pItems, pContentLength, pBytesRead); 
  45.             }  
  46.         });  
  47.         */ 
  48.           
  49.         // 执行上传并获取操作结果  
  50.         Result result = fu.upload(getRequest(), getResponse());  
  51.           
  52.         // 检查操作结果  
  53.         if(result != FileUploader.Result.SUCCESS)  
  54.         {  
  55.             // 设置 request attribute 
  56.             setRequestAttribute("error", fu.getCause());  
  57.             // 记录日志  
  58.             Logger.exception(fu.getCause(), "upload file fail", Level.ERROR, false);  
  59.               
  60.             return ERROR;  
  61.         }  
  62.           
  63.         // 通过非文件表单域创建 Form Bean 
  64.         Persion persion = BeanHelper.createBean(Persion.class, fu.getParamFields());  
  65.         // 图片保存路径的列表  
  66.         List<String> photos    = new ArrayList<String>();  
  67.           
  68.         /* 轮询文件表单域,填充 photos */ 
  69.         Set<String> keys = fu.getFileFields().keySet();  
  70.         for(String key : keys)  
  71.         {  
  72.             FileInfo[] ffs = fu.getFileFields().get(key);  
  73.             for(FileInfo ff : ffs)  
  74.             {  
  75.                 photos.add(String.format("(%s) %s%s%s", key, fu.getSavePath(), File.separator, ff.getSaveFile().getName()));  
  76.             }  
  77.         }  
  78.           
  79.         // 设置 Form Bean 的 photos 属性 
  80.         persion.setPhotos(photos);  
  81.         // 设置 request attribute 
  82.         setRequestAttribute("persion", persion);  
  83.           
  84.         return SUCCESS;  
  85.     }  
  1. public class Persion  
  2. {  
  3.     private String firstName;  
  4.     private String lastName;  
  5.     private Date birthday;  
  6.     private boolean gender;  
  7.     private int workingAge;  
  8.     private int[] interest;  
  9.     private List<String> photos;  
  1. public static class FileInfo  
  2. {  
  3.     private String uploadFileName;  
  4.     private File saveFile;  

分析下上面的 Java 代码,本例先根据保存目录、文件大小限制和文件类型限制创建一个 FileUploader 对象,然后调用该对象的 upload() 方法执行上传并返回操作结果,如果上传成功则 通过 getParamFields() 方法获取所有非文件表单域内容,并交由 BeanHelper 进行解析(若想了解更多关于 BeanHelper 的内容请猛击这里 ^_^)创建 Form Bean,再调用 getFileFields() 方法获取所有文件表单域的 FileInfo(FileInfo 包含上传文件的原始名称和被保存文件的 File 对象),最后完成 Form Bean 所有字段的填充并把 Form Bean 设置为 request 属性。

  • 上传结果界面及代码

  1. <table border="1">  
  2. <caption>Persion Attributs</caption>  
  3.  
  4.     <tr><td>Name</td><td><c:out value="${persion.firstName} ${persion.lastName}"/>&nbsp;</td></tr>  
  5.     <tr><td>Brithday</td><td><c:out value="${persion.birthday}"/>&nbsp;</td></tr>  
  6.     <tr><td>Gender</td><td><c:out value="${persion.gender}"/>&nbsp;</td></tr>  
  7.     <tr><td>Working Age</td><td><c:out value="${persion.workingAge}"/>&nbsp;</td></tr>  
  8.     <tr><td>Interest</td><td><c:forEach var="its" items="${persion.interest}">  
  9.                                  <c:out value="${its}" /> &nbsp;  
  10.                           </c:forEach>&nbsp;</td></tr>  
  11.     <tr><td>Photos</td><td><c:forEach var="p" items="${persion.photos}">  
  12.                                  <c:out value="${p}" /><br>  
  13.                           </c:forEach>&nbsp;</td></tr>  
  14. </table> 

从上面的处理结果可以看出,文件上传组件 FileUploader 正确地处理了表单的所有文件域和非文件域名,并且,整个文件上传操作过程非常简单,无需用户过多参与。下面我们来详细看看组件的主要实现代码:

  1. /** 文件上传器 */ 
  2. public class FileUploader  
  3. {  
  4.     /** 不限制文件上传总大小的 Size Max 常量 */ 
  5.     public static final long NO_LIMIT_SIZE_MAX        = -1;  
  6.     /** 不限制文件上传单个文件大小的 File Size Max 常量 */ 
  7.     public static final long NO_LIMIT_FILE_SIZE_MAX    = -1;  
  8.     /** 默认的写文件阀值 */ 
  9.     public static final int DEFAULT_SIZE_THRESHOLD    = DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD;  
  10.     /** 默认的文件名生成器 */ 
  11.     public static final FileNameGenerator DEFAULT_FILE_NAME_GENERATOR = new CommonFileNameGenerator();  
  12.       
  13.     /** 设置上传文件的保存路径(不包含文件名)
  14.      *   
  15.      * 文件路径,可能是绝对路径或相对路径<br>  
  16.      *     1) 绝对路径:以根目录符开始(如:'/'、'D:\'),是服务器文件系统的路径<br> 
  17.      *     2) 相对路径:不以根目录符开始,是相对于 WEB 应用程序 Context 的路径,(如:mydir 是指 
  18.      *         '${WEB-APP-DIR}/mydir')<br>  
  19.      *     3) 规则:上传文件前会检查该路径是否存在,如果不存在则会尝试生成该路径,如果生成失败则 
  20.      *         上传失败并返回 {@link Result#INVALID_SAVE_PATH} 
  21.      *   
  22.      */ 
  23.     private String savePath;  
  24.     /** 文件上传的总文件大小限制 */ 
  25.     private long sizeMax                        = NO_LIMIT_SIZE_MAX;  
  26.     /** 文件上传的单个文件大小限制 */ 
  27.     private long fileSizeMax                    = NO_LIMIT_FILE_SIZE_MAX;  
  28.     /** 可接受的上传文件类型集合,默认:不限制 */ 
  29.     private Set<String> acceptTypes                = new LStrSet();  
  30.     /** 非文件表单域的映射 */ 
  31.     private Map<String, String[]> paramFields    = new HashMap<String, String[]>();  
  32.     /** 文件表单域的映射 */ 
  33.     private Map<String, FileInfo[]> fileFields    = new HashMap<String, FileInfo[]>();  
  34.     /** 文件名生成器 */ 
  35.     private FileNameGenerator fileNameGenerator    = DEFAULT_FILE_NAME_GENERATOR;  
  36.       
  37.     // commons file upload 相关属性 
  38.     private int factorySizeThreshold            = DEFAULT_SIZE_THRESHOLD;  
  39.     private String factoryRepository;  
  40.     private FileCleaningTracker factoryCleaningTracker;  
  41.     private String servletHeaderencoding;  
  42.     private ProgressListener servletProgressListener;  
  43.       
  44.     /** 文件上传失败的原因(文件上传失败时使用) */ 
  45.     private Throwable cause;  
  46.       
  47.     /** 执行上传
  48.      *   
  49.      * @param request    : {@link HttpServletRequest} 对象 
  50.      * @param response    : {@link HttpServletResponse} 对象 
  51.      *   
  52.      * @return            : 成功:返回 {@link Result#SUCCESS} ,失败:返回其他结果, 
  53.      *                       失败原因通过 {@link FileUploader#getCause()} 获取 
  54.      *   
  55.      */ 
  56.     @SuppressWarnings("unchecked")  
  57.     public Result upload(HttpServletRequest request, HttpServletResponse response)  
  58.     {  
  59.         reset();  
  60.  
  61.         // 获取上传目录绝对路径  
  62.         String absolutePath     = getAbsoluteSavePath(request);  
  63.         if(absolutePath == null)  
  64.         {  
  65.             cause = new FileNotFoundException(String.format("path '%s' not found or is not directory", savePath));  
  66.             return Result.INVALID_SAVE_PATH;  
  67.         }  
  68.           
  69.         ServletFileUpload sfu    = getFileUploadComponent();  
  70.         List<FileItemInfo> fiis    = new ArrayList<FileItemInfo>();  
  71.           
  72.         List<FileItem> items    = null;  
  73.         Result result            = Result.SUCCESS;  
  74.           
  75.         // 获取文件名生成器  
  76.         String encoding                    = servletHeaderencoding != null ? servletHeaderencoding : request.getCharacterEncoding();  
  77.         FileNameGenerator fnGenerator    = fileNameGenerator != null ? fileNameGenerator : DEFAULT_FILE_NAME_GENERATOR;  
  78.           
  79.         try 
  80.         {  
  81.             // 执行上传操作  
  82.             items = (List<FileItem>)sfu.parseRequest(request);  
  83.         }  
  84.         catch (FileUploadException e)  
  85.         {  
  86.             cause = e;  
  87.               
  88.             if(e instanceof FileSizeLimitExceededException)        result = Result.FILE_SIZE_EXCEEDED;  
  89.             else if(e instanceof SizeLimitExceededException)    result = Result.SIZE_EXCEEDED;  
  90.             else if(e instanceof InvalidContentTypeException)    result = Result.INVALID_CONTENT_TYPE;  
  91.             else if(e instanceof IOFileUploadException)            result = Result.FILE_UPLOAD_IO_EXCEPTION;  
  92.             else                                                result = Result.OTHER_PARSE_REQUEST_EXCEPTION;  
  93.         }  
  94.           
  95.         if(result == Result.SUCCESS)  
  96.         {  
  97.             // 解析所有表单域  
  98.             result = parseFileItems(items, fnGenerator, absolutePath, encoding, fiis);      
  99.             if(result == Result.SUCCESS)  
  100.                 // 保存文件  
  101.                 result = writeFiles(fiis);  
  102.         }  
  103.           
  104.         return result;  
  105.     }  
  106.  
  107.     // 解析所有表单域  
  108.     private Result parseFileItems(List<FileItem> items, FileNameGenerator fnGenerator, String absolutePath, String encoding, List<FileItemInfo> fiis)  
  109.     {  
  110.         for(FileItem item : items)  
  111.         {  
  112.             if(item.isFormField())  
  113.                 // 解析非文件表单域  
  114.                 parseFormField(item, encoding);  
  115.             else 
  116.             {  
  117.                 if(item.getSize() == 0)  
  118.                     continue;  
  119.                   
  120.                 // 解析文件表单域  
  121.                 Result result = parseFileField(item, absolutePath, fnGenerator, fiis);  
  122.                 if(result != Result.SUCCESS)  
  123.                 {  
  124.                     reset();  
  125.                       
  126.                     cause = new InvalidParameterException(String.format("file '%s' not accepted", item.getName()));  
  127.                     return result;  
  128.                 }  
  129.             }  
  130.         }  
  131.           
  132.         return Result.SUCCESS;  
  133.     }  
  134.  
  135.     // 解析文件表单域  
  136.     private Result parseFileField(FileItem item, String absolutePath, FileNameGenerator fnGenerator, List<FileItemInfo> fiis)  
  137.     {  
  138.         String suffix            = null;  
  139.         String uploadFileName    = item.getName();  
  140.         boolean isAcceptType    = acceptTypes.isEmpty();  
  141.           
  142.         if(!isAcceptType)  
  143.         {  
  144.             suffix = null;  
  145.             int stuffPos = uploadFileName.lastIndexOf(".");  
  146.             if(stuffPos != -1)  
  147.             {  
  148.                 suffix = uploadFileName.substring(stuffPos, uploadFileName.length()).toLowerCase();  
  149.                 isAcceptType = acceptTypes.contains(suffix);  
  150.             }  
  151.         }  
  152.           
  153.         if(!isAcceptType)  
  154.             return Result.INVALID_FILE_TYPE;  
  155.           
  156.         // 通过文件名生成器获取文件名  
  157.         String saveFileName = fnGenerator.generate(item, suffix);  
  158.         if(!saveFileName.endsWith(suffix))  
  159.             saveFileName += suffix;  
  160.           
  161.         String fullFileName    = absolutePath + File.separator + saveFileName;  
  162.         File saveFile        = new File(fullFileName);  
  163.         FileInfo info        = new FileInfo(uploadFileName, saveFile);  
  164.           
  165.         // 添加表单域文件信息  
  166.         fiis.add(new FileItemInfo(item, saveFile));  
  167.         addFileField(item.getFieldName(), info);  
  168.           
  169.         return Result.SUCCESS;  
  170.     }  
  171.  
  172.     private void parseFormField(FileItem item, String encoding)  
  173.     {  
  174.         String name = item.getFieldName();  
  175.         String value = item.getString();  
  176.           
  177.         // 字符串编码转换  
  178.         if(!GeneralHelper.isStrEmpty(value) && encoding != null)  
  179.         {  
  180.             try 
  181.             {  
  182.                 value = new String(value.getBytes("ISO-8859-1"), encoding);  
  183.             }  
  184.             catch(UnsupportedEncodingException e)  
  185.             {  
  186.       
  187.             }  
  188.         }  
  189.           
  190.         // 添加表单域名/值映射  
  191.         addParamField(name, value);  
  192.     }  
  193.  
  194.     /** 文件名生成器接口 </SP< span>
0 0