- 浏览: 79460 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
尹超5200:
你TM写点文字能死啊
java图片上传回显(火狐待测) -
shibin_1109:
如果冲突没更新下来的话也可以使用git同步来解决.
Eclips GIT冲突解决
概 述
文件上传和下载是 Web 应用中的一个常见功能,相信各位或多或少都曾写过这方面相关的代码。但本座看过不少人在实现上传或下载功能时总是不知不觉间与程序的业务逻辑纠缠在一起,因此,当其他地方要用到这些功能时则无可避免地 Copy / Pase,然后再进行修改。这样丑陋不堪的做法导致非常容易出错不说,更大的问题是严重浪费时间不断做重复类似的工作,这是本座绝不能容忍的。哎,人生苦短啊,浪费时间在这些重复工作身上实在是不值得,何不把这些时间省出来打几盘罗马或者踢一场球?为此,本座利用一些闲暇之时光编写了一个通用的文件上传和文件下载组件,实现方法纯粹是基于 JSP,没有太高的技术难度,总之老少咸宜 ^_^。现把设计的思路和实现的方法向各位娓娓道来,希望能起到抛砖引玉的效果,激发大家的创造性思维。
任何公共组件的设计都必须考虑下面两个问题:
一、如何才能重用?
答:首先,重用的组件必须有用,也就是说,是功能完备的。但另一方面,如果组件负责的职能太多也会影响重用。试想,一个文件上传组件同时还要负责插入数据库记录,一个文件下载组件还要负责解析下载请求并查找要下载的文件那可真是太悲哀了,叫别人如何重用?也就是说,重用组件必须是功能完备并职责单一的,绝对不能越界参与应用业务逻辑处理,不在其位不谋其政。
另外,要重用的组件绝不能反向依赖于上层使用者。通常,重用组件以接口或类的形式提供给上层使用者调用,并放在单独的包中。也就是说,上层使用者所在的包依赖于组件所在的包,如果组件再反向依赖于上层使用者,则两个包之间就存在依赖环,严重违反了面向对象设计原则中的无环依赖原则(若想了解更多关于设计原则的内容请猛击这里 ^_^)。试想,通用的文件上传或下载组件如果需要读取一个在应用程序的某个地方定义的字符串常量来确定文件上传或下载的目录,那是囧了。
本文件上传和下载组件在设计时充分考虑到上述问题,只负责单纯的上传和下载工作,所需要的外部信息均通过相应方法进行设置,不会依赖于任何使用者。
二、组件的可用性如何?
答:这是一个相当有深度的问题 ^_^ 所谓可用性通俗来说就是好不好用,使用起来是否方便。作为公共组件来说,可用性是一个十分重要的质量指标。如果组件十分复杂难用,倒不如自己花点时间自己写一个来得舒坦。本文件上传和下载组件在设计的过程中十分注重可用性目标,操作组件的代码行数不超过 10 行,只需几个步骤:
生成组件实例
设置实例属性
调用上传/下载方法
处理调用结果
水吹得已经够多了,下面让我们来看看文件上传和下载组件分别是如何实现的。
文件上传
文件上传操作通常会附加一些限制,如:文件类型、上传文件总大小、每个文件的最大大小等。除此以外,作为一个通用组件还需要考虑更多的问题,如:支持自定义文件保存目录、支持相对路径和绝对路径、支持自定义保存的文件的文件名称、支持上传进度反馈和上传失败清理等。另外,本座也不想重新造车轮,本组件是基于 Commons File Upload 实现,省却了本座大量的工作 ^_^ 下面先从一个具体的使用例子讲起:
上传请求界面及代码
从上面的 HTML 代码可以看出,表单有 6 个普通域和 3 个文件域,其中前两个文件域的 name 属性相同。
上传处理代码
分析下上面的 Java 代码,本例先根据保存目录、文件大小限制和文件类型限制创建一个 FileUploader 对象,然后调用该对象的 upload() 方法执行上传并返回操作结果,如果上传成功则 通过 getParamFields() 方法获取所有非文件表单域内容,并交由 BeanHelper 进行解析(若想了解更多关于 BeanHelper 的内容请猛击这里 ^_^)创建 Form Bean,再调用 getFileFields() 方法获取所有文件表单域的 FileInfo(FileInfo 包含上传文件的原始名称和被保存文件的 File 对象),最后完成 Form Bean 所有字段的填充并把 Form Bean 设置为 request 属性。
上传结果界面及代码
从上面的处理结果可以看出,文件上传组件 FileUploader 正确地处理了表单的所有文件域和非文件域名,并且,整个文件上传操作过程非常简单,无需用户过多参与。下面我们来详细看看组件的主要实现代码:
具体的实现细节就不多描述了,大家可以下载附件慢慢研究。这里只说明两点:
1、应用可以实现自己的 FileNameGenerator 类替代默认的文件名生成器。
2、上传操作通过 FileUploader.Result 返回结果,并没有采用抛出异常的方式,因为本座认为在这里采用异常方式报告结果其实并不方便使用;另一方面,程序可以通过 getCause() 获取详细的错误信息。
文件下载
相对于文件上传,文件下载则简单很多,主要实现流程是根据文件名找到实际文件,并利用 Java 的相关类对 I/O 流进行读写。下面先看看一个使用示例:
从这个示例可以看出,文件下载组件的使用方法更简单,因为它不需要对下载结果进行很多处理。可以看出该组件也支持相对路径和绝对路径。下面我们来详细看看组件的主要实现代码:
/** 文件下载器 */
原文出处:http://www.cnblogs.com/ldcsaa/archive/2012/02/23/2364036.html
文件上传和下载是 Web 应用中的一个常见功能,相信各位或多或少都曾写过这方面相关的代码。但本座看过不少人在实现上传或下载功能时总是不知不觉间与程序的业务逻辑纠缠在一起,因此,当其他地方要用到这些功能时则无可避免地 Copy / Pase,然后再进行修改。这样丑陋不堪的做法导致非常容易出错不说,更大的问题是严重浪费时间不断做重复类似的工作,这是本座绝不能容忍的。哎,人生苦短啊,浪费时间在这些重复工作身上实在是不值得,何不把这些时间省出来打几盘罗马或者踢一场球?为此,本座利用一些闲暇之时光编写了一个通用的文件上传和文件下载组件,实现方法纯粹是基于 JSP,没有太高的技术难度,总之老少咸宜 ^_^。现把设计的思路和实现的方法向各位娓娓道来,希望能起到抛砖引玉的效果,激发大家的创造性思维。
任何公共组件的设计都必须考虑下面两个问题:
一、如何才能重用?
答:首先,重用的组件必须有用,也就是说,是功能完备的。但另一方面,如果组件负责的职能太多也会影响重用。试想,一个文件上传组件同时还要负责插入数据库记录,一个文件下载组件还要负责解析下载请求并查找要下载的文件那可真是太悲哀了,叫别人如何重用?也就是说,重用组件必须是功能完备并职责单一的,绝对不能越界参与应用业务逻辑处理,不在其位不谋其政。
另外,要重用的组件绝不能反向依赖于上层使用者。通常,重用组件以接口或类的形式提供给上层使用者调用,并放在单独的包中。也就是说,上层使用者所在的包依赖于组件所在的包,如果组件再反向依赖于上层使用者,则两个包之间就存在依赖环,严重违反了面向对象设计原则中的无环依赖原则(若想了解更多关于设计原则的内容请猛击这里 ^_^)。试想,通用的文件上传或下载组件如果需要读取一个在应用程序的某个地方定义的字符串常量来确定文件上传或下载的目录,那是囧了。
本文件上传和下载组件在设计时充分考虑到上述问题,只负责单纯的上传和下载工作,所需要的外部信息均通过相应方法进行设置,不会依赖于任何使用者。
二、组件的可用性如何?
答:这是一个相当有深度的问题 ^_^ 所谓可用性通俗来说就是好不好用,使用起来是否方便。作为公共组件来说,可用性是一个十分重要的质量指标。如果组件十分复杂难用,倒不如自己花点时间自己写一个来得舒坦。本文件上传和下载组件在设计的过程中十分注重可用性目标,操作组件的代码行数不超过 10 行,只需几个步骤:
生成组件实例
设置实例属性
调用上传/下载方法
处理调用结果
水吹得已经够多了,下面让我们来看看文件上传和下载组件分别是如何实现的。
文件上传
文件上传操作通常会附加一些限制,如:文件类型、上传文件总大小、每个文件的最大大小等。除此以外,作为一个通用组件还需要考虑更多的问题,如:支持自定义文件保存目录、支持相对路径和绝对路径、支持自定义保存的文件的文件名称、支持上传进度反馈和上传失败清理等。另外,本座也不想重新造车轮,本组件是基于 Commons File Upload 实现,省却了本座大量的工作 ^_^ 下面先从一个具体的使用例子讲起:
上传请求界面及代码
<form action="checkupload.action" method="post" enctype="multipart/form-data"> First Name: <input type="text" name="firstName" value="丑"> <br> Last Name: <input type="text" name="lastName" value="怪兽"> <br> Birthday: <input type="text" name="birthday" value="1978-11-03"> <br> Gender: 男 <input type="radio"" name="gender" value="false"> 女 <input type="radio"" name="gender" value="true" checked="checked"> <br> Working age: <select name="workingAge"> <option value="-1">-请选择-</option> <option value="3">三年</option> <option value="5" selected="selected">五年</option> <option value="10">十年</option> <option value="20">二十年</option> </select> <br> Interest: 游泳 <input type="checkbox" name="interest" value="1" checked="checked"> 打球 <input type="checkbox" name="interest" value="2" checked="checked"> 下棋 <input type="checkbox" name="interest" value="3"> 打麻将 <input type="checkbox" name="interest" value="4"> 看书 <input type="checkbox" name="interest" value="5" checked="checked"> <br> Photo 1.1: <input type="file"" name="photo-1"> <br> Photo 1.2: <input type="file"" name="photo-1"> <br> Photo 2.1: <input type="file"" name="photo-2"> <br> <br> <input type="submit" value="确 定"> <input type="reset" value="重 置"> </form>
从上面的 HTML 代码可以看出,表单有 6 个普通域和 3 个文件域,其中前两个文件域的 name 属性相同。
上传处理代码
import com.bruce.util.BeanHelper; import com.bruce.util.Logger; import com.bruce.util.http.FileUploader; import com.bruce.util.http.FileUploader.FileInfo; import static com.bruce.util.http.FileUploader.*; @SuppressWarnings("unused") public class CheckUpload extends ActionSupport { // 上传路径 private static final String UPLOAD_PATH = "upload"; // 可接受的文件类型 private static final String[] ACCEPT_TYPES = {"txt", "pdf", "doc", ".Jpg", "*.zip", "*.RAR"}; // 总上传文件大小限制 private static final long MAX_SIZE = 1024 * 1024 * 100; // 单个传文件大小限制 private static final long MAX_FILE_SIZE = 1024 * 1024 * 10; @Override public String execute() { // 创建 FileUploader 对象 FileUploader fu = new FileUploader(UPLOAD_PATH, ACCEPT_TYPES, MAX_SIZE, MAX_FILE_SIZE); // 根据实际情况设置对象属性(可选) /* fu.setFileNameGenerator(new FileNameGenerator() { @Override public String generate(FileItem item, String suffix) { return String.format("%d_%d", item.hashCode(), item.get().hashCode()); } }); fu.setServletProgressListener(new ProgressListener() { @Override public void update(long pBytesRead, long pContentLength, int pItems) { System.out.printf("%d: length -> %d, read -> %d.\n", pItems, pContentLength, pBytesRead); } }); */ // 执行上传并获取操作结果 Result result = fu.upload(getRequest(), getResponse()); // 检查操作结果 if(result != FileUploader.Result.SUCCESS) { // 设置 request attribute setRequestAttribute("error", fu.getCause()); // 记录日志 Logger.exception(fu.getCause(), "upload file fail", Level.ERROR, false); return ERROR; } // 通过非文件表单域创建 Form Bean Persion persion = BeanHelper.createBean(Persion.class, fu.getParamFields()); // 图片保存路径的列表 List<String> photos = new ArrayList<String>(); /* 轮询文件表单域,填充 photos */ Set<String> keys = fu.getFileFields().keySet(); for(String key : keys) { FileInfo[] ffs = fu.getFileFields().get(key); for(FileInfo ff : ffs) { photos.add(String.format("(%s) %s%s%s", key, fu.getSavePath(), File.separator, ff.getSaveFile().getName())); } } // 设置 Form Bean 的 photos 属性 persion.setPhotos(photos); // 设置 request attribute setRequestAttribute("persion", persion); return SUCCESS; } }
public class Persion { private String firstName; private String lastName; private Date birthday; private boolean gender; private int workingAge; private int[] interest; private List<String> photos; }
public static class FileInfo { private String uploadFileName; private File saveFile; }
分析下上面的 Java 代码,本例先根据保存目录、文件大小限制和文件类型限制创建一个 FileUploader 对象,然后调用该对象的 upload() 方法执行上传并返回操作结果,如果上传成功则 通过 getParamFields() 方法获取所有非文件表单域内容,并交由 BeanHelper 进行解析(若想了解更多关于 BeanHelper 的内容请猛击这里 ^_^)创建 Form Bean,再调用 getFileFields() 方法获取所有文件表单域的 FileInfo(FileInfo 包含上传文件的原始名称和被保存文件的 File 对象),最后完成 Form Bean 所有字段的填充并把 Form Bean 设置为 request 属性。
上传结果界面及代码
<table border="1"> <caption>Persion Attributs</caption> <tr><td>Name</td><td><c:out value="${persion.firstName} ${persion.lastName}"/> </td></tr> <tr><td>Brithday</td><td><c:out value="${persion.birthday}"/> </td></tr> <tr><td>Gender</td><td><c:out value="${persion.gender}"/> </td></tr> <tr><td>Working Age</td><td><c:out value="${persion.workingAge}"/> </td></tr> <tr><td>Interest</td><td><c:forEach var="its" items="${persion.interest}"> <c:out value="${its}" /> </c:forEach> </td></tr> <tr><td>Photos</td><td><c:forEach var="p" items="${persion.photos}"> <c:out value="${p}" /><br> </c:forEach> </td></tr> </table>
从上面的处理结果可以看出,文件上传组件 FileUploader 正确地处理了表单的所有文件域和非文件域名,并且,整个文件上传操作过程非常简单,无需用户过多参与。下面我们来详细看看组件的主要实现代码:
/** 文件上传器 */ public class FileUploader { /** 不限制文件上传总大小的 Size Max 常量 */ public static final long NO_LIMIT_SIZE_MAX = -1; /** 不限制文件上传单个文件大小的 File Size Max 常量 */ public static final long NO_LIMIT_FILE_SIZE_MAX = -1; /** 默认的写文件阀值 */ public static final int DEFAULT_SIZE_THRESHOLD = DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD; /** 默认的文件名生成器 */ public static final FileNameGenerator DEFAULT_FILE_NAME_GENERATOR = new CommonFileNameGenerator(); /** 设置上传文件的保存路径(不包含文件名) * * 文件路径,可能是绝对路径或相对路径<br> * 1) 绝对路径:以根目录符开始(如:'/'、'D:\'),是服务器文件系统的路径<br> * 2) 相对路径:不以根目录符开始,是相对于 WEB 应用程序 Context 的路径,(如:mydir 是指 * '${WEB-APP-DIR}/mydir')<br> * 3) 规则:上传文件前会检查该路径是否存在,如果不存在则会尝试生成该路径,如果生成失败则 * 上传失败并返回 {@link Result#INVALID_SAVE_PATH} * */ private String savePath; /** 文件上传的总文件大小限制 */ private long sizeMax = NO_LIMIT_SIZE_MAX; /** 文件上传的单个文件大小限制 */ private long fileSizeMax = NO_LIMIT_FILE_SIZE_MAX; /** 可接受的上传文件类型集合,默认:不限制 */ private Set<String> acceptTypes = new LStrSet(); /** 非文件表单域的映射 */ private Map<String, String[]> paramFields = new HashMap<String, String[]>(); /** 文件表单域的映射 */ private Map<String, FileInfo[]> fileFields = new HashMap<String, FileInfo[]>(); /** 文件名生成器 */ private FileNameGenerator fileNameGenerator = DEFAULT_FILE_NAME_GENERATOR; // commons file upload 相关属性 private int factorySizeThreshold = DEFAULT_SIZE_THRESHOLD; private String factoryRepository; private FileCleaningTracker factoryCleaningTracker; private String servletHeaderencoding; private ProgressListener servletProgressListener; /** 文件上传失败的原因(文件上传失败时使用) */ private Throwable cause; /** 执行上传 * * @param request : {@link HttpServletRequest} 对象 * @param response : {@link HttpServletResponse} 对象 * * @return : 成功:返回 {@link Result#SUCCESS} ,失败:返回其他结果, * 失败原因通过 {@link FileUploader#getCause()} 获取 * */ @SuppressWarnings("unchecked") public Result upload(HttpServletRequest request, HttpServletResponse response) { reset(); // 获取上传目录绝对路径 String absolutePath = getAbsoluteSavePath(request); if(absolutePath == null) { cause = new FileNotFoundException(String.format("path '%s' not found or is not directory", savePath)); return Result.INVALID_SAVE_PATH; } ServletFileUpload sfu = getFileUploadComponent(); List<FileItemInfo> fiis = new ArrayList<FileItemInfo>(); List<FileItem> items = null; Result result = Result.SUCCESS; // 获取文件名生成器 String encoding = servletHeaderencoding != null ? servletHeaderencoding : request.getCharacterEncoding(); FileNameGenerator fnGenerator = fileNameGenerator != null ? fileNameGenerator : DEFAULT_FILE_NAME_GENERATOR; try { // 执行上传操作 items = (List<FileItem>)sfu.parseRequest(request); } catch (FileUploadException e) { cause = e; if(e instanceof FileSizeLimitExceededException) result = Result.FILE_SIZE_EXCEEDED; else if(e instanceof SizeLimitExceededException) result = Result.SIZE_EXCEEDED; else if(e instanceof InvalidContentTypeException) result = Result.INVALID_CONTENT_TYPE; else if(e instanceof IOFileUploadException) result = Result.FILE_UPLOAD_IO_EXCEPTION; else result = Result.OTHER_PARSE_REQUEST_EXCEPTION; } if(result == Result.SUCCESS) { // 解析所有表单域 result = parseFileItems(items, fnGenerator, absolutePath, encoding, fiis); if(result == Result.SUCCESS) // 保存文件 result = writeFiles(fiis); } return result; } // 解析所有表单域 private Result parseFileItems(List<FileItem> items, FileNameGenerator fnGenerator, String absolutePath, String encoding, List<FileItemInfo> fiis) { for(FileItem item : items) { if(item.isFormField()) // 解析非文件表单域 parseFormField(item, encoding); else { if(item.getSize() == 0) continue; // 解析文件表单域 Result result = parseFileField(item, absolutePath, fnGenerator, fiis); if(result != Result.SUCCESS) { reset(); cause = new InvalidParameterException(String.format("file '%s' not accepted", item.getName())); return result; } } } return Result.SUCCESS; } // 解析文件表单域 private Result parseFileField(FileItem item, String absolutePath, FileNameGenerator fnGenerator, List<FileItemInfo> fiis) { String suffix = null; String uploadFileName = item.getName(); boolean isAcceptType = acceptTypes.isEmpty(); if(!isAcceptType) { suffix = null; int stuffPos = uploadFileName.lastIndexOf("."); if(stuffPos != -1) { suffix = uploadFileName.substring(stuffPos, uploadFileName.length()).toLowerCase(); isAcceptType = acceptTypes.contains(suffix); } } if(!isAcceptType) return Result.INVALID_FILE_TYPE; // 通过文件名生成器获取文件名 String saveFileName = fnGenerator.generate(item, suffix); if(!saveFileName.endsWith(suffix)) saveFileName += suffix; String fullFileName = absolutePath + File.separator + saveFileName; File saveFile = new File(fullFileName); FileInfo info = new FileInfo(uploadFileName, saveFile); // 添加表单域文件信息 fiis.add(new FileItemInfo(item, saveFile)); addFileField(item.getFieldName(), info); return Result.SUCCESS; } private void parseFormField(FileItem item, String encoding) { String name = item.getFieldName(); String value = item.getString(); // 字符串编码转换 if(!GeneralHelper.isStrEmpty(value) && encoding != null) { try { value = new String(value.getBytes("ISO-8859-1"), encoding); } catch(UnsupportedEncodingException e) { } } // 添加表单域名/值映射 addParamField(name, value); } /** 文件名生成器接口 * * 每次保存一个上传文件前都需要调用该接口的 {@link FileNameGenerator#generate} 方法生成要保存的文件名 * */ public static interface FileNameGenerator { /** 文件名生成方法 * * @param item : 上传文件对应的 {@link FileItem} 对象 * @param suffix : 上传文件的后缀名 * */ String generate(FileItem item, String suffix); }
/** 默认通用文件名生成器 * * 实现 {@link FileNameGenerator} 接口,根据序列值和时间生成唯一文件名 * */ public static class CommonFileNameGenerator implements FileNameGenerator { private static final int MAX_SERIAL = 999999; private static final AtomicInteger atomic = new AtomicInteger(); private static int getNextInteger() { int value = atomic.incrementAndGet(); if(value >= MAX_SERIAL) atomic.set(0); return value; } /** 根据序列值和时间生成 'XXXXXX_YYYYYYYYYYYYY' 格式的唯一文件名 */ @Override public String generate(FileItem item, String suffix) { int serial = getNextInteger(); long millsec = System.currentTimeMillis(); return String.format("%06d_%013d", serial, millsec); } }
/** 上传文件信息结构体 */ public static class FileInfo { private String uploadFileName; private File saveFile; // getters and setters ... }
private class FileItemInfo { FileItem item; File file; // getters and setters ... }
/** 文件上传结果枚举值 */ public static enum Result { /** 成功 */ SUCCESS, /** 失败:文件总大小超过限制 */ SIZE_EXCEEDED, /** 失败:单个文件大小超过限制 */ FILE_SIZE_EXCEEDED, /** 失败:请求表单类型不正确 */ INVALID_CONTENT_TYPE, /** 失败:文件上传 IO 错误 */ FILE_UPLOAD_IO_EXCEPTION, /** 失败:解析上传请求其他异常 */ OTHER_PARSE_REQUEST_EXCEPTION, /** 失败:文件类型不正确 */ INVALID_FILE_TYPE, /** 失败:文件写入失败 */ WRITE_FILE_FAIL, /** 失败:文件的保存路径不正确 */ INVALID_SAVE_PATH; } }
具体的实现细节就不多描述了,大家可以下载附件慢慢研究。这里只说明两点:
1、应用可以实现自己的 FileNameGenerator 类替代默认的文件名生成器。
2、上传操作通过 FileUploader.Result 返回结果,并没有采用抛出异常的方式,因为本座认为在这里采用异常方式报告结果其实并不方便使用;另一方面,程序可以通过 getCause() 获取详细的错误信息。
文件下载
相对于文件上传,文件下载则简单很多,主要实现流程是根据文件名找到实际文件,并利用 Java 的相关类对 I/O 流进行读写。下面先看看一个使用示例:
import static com.bruce.util.http.FileDownloader.*; public class TestDownload extends ActionSupport { // 绝对路径 private static final String ABSOLUTE_PATH = "/Server/apache-tomcat-6.0.32/webapps/portal/download/下载测试 - 文本文件.txt"; // 相对路径 private static final String RELATE_PATH = "download/下载测试 - 项目框架.jpg"; @Override public String execute() { int type = getIntParam("type", 1); String filePath = (type == 1 ? ABSOLUTE_PATH : RELATE_PATH); // 创建 FileDownloader 对象 FileDownloader fdl = new FileDownloader(filePath); // 执行下载 Result result = fdl.downLoad(getRequest(), getResponse()); // 检查下载结果 if(result != Result.SUCCESS) { // 记录日志 Logger.exception(fdl.getCause(), String.format("download file '%s' fail", fdl.getFilePath()), Level.ERROR, false); } return NONE; } }
从这个示例可以看出,文件下载组件的使用方法更简单,因为它不需要对下载结果进行很多处理。可以看出该组件也支持相对路径和绝对路径。下面我们来详细看看组件的主要实现代码:
/** 文件下载器 */
public class FileDownloader { /** 默认字节交换缓冲区大小 */ public static final int DEFAULT_BUFFER_SIZE = 1024 * 4; /** 下载文件的默认 Mime Type */ public static final String DEFAULT_CONTENT_TYPE = "application/force-download"; /** 设置下载文件的路径(包含文件名) * * filePath : 文件路径,可能是绝对路径或相对路径<br> * 1) 绝对路径:以根目录符开始(如:'/'、'D:\'),是服务器文件系统的路径<br> * 2) 相对路径:不以根目录符开始,是相对于 WEB 应用程序 Context 的路径,(如:mydir/myfile 是指 '${WEB-APP-DIR}/mydir/myfile') */ private String filePath; /** 显示在浏览器的下载对话框中的文件名称,默认与 filePath 参数中的文件名一致 */ private String saveFileName; /** 下载文件的 Mime Type,默认:{@link FileDownloader#DEFAULT_CONTENT_TYPE} */ private String contentType = DEFAULT_CONTENT_TYPE; /** 字节缓冲区大小,默认:{@link FileDownloader#DEFAULT_CONTENT_TYPE} */ private int bufferSize = DEFAULT_BUFFER_SIZE; /** 获取文件下载失败的原因(文件下载失败时使用) */ private Throwable cause; /** 执行下载 * * @param request : {@link HttpServletRequest} 对象 * @param response : {@link HttpServletResponse} 对象 * * @return : 成功:返回 {@link Result#SUCCESS} ,失败:返回其他结果, * 失败原因通过 {@link FileDownloader#getCause()} 获取 * */ public Result downLoad(HttpServletRequest request, HttpServletResponse response) { reset(); try { // 获取要下载的文件的 File 对象 File file = getFile(request); // 执行下载操作 downLoadFile(request, response, file); } catch(Exception e) { cause = e; if(e instanceof FileNotFoundException) return Result.FILE_NOT_FOUND; if(e instanceof IOException) return Result.READ_WRITE_FAIL; return Result.UNKNOWN_EXCEPTION; } return Result.SUCCESS; } // 执行下载操作 private void downLoadFile(HttpServletRequest request, HttpServletResponse response, File file) throws IOException { String fileName = new String(saveFileName.getBytes(), "ISO-8859-1"); // 解析 HTTP 请求头,获取文件的读取范围 Range<Integer> range = parseDownloadRange(request); int length = (int)file.length(); int begin = 0; int end = length - 1; // 设置 HTTP 响应头 response.setContentType(contentType); response.setContentLength(length); response.setHeader("Content-Disposition", "attachment;filename=" + fileName); // 确定文件的读取范围(用于断点续传) if(range != null) { if(range.getBegin() != null) { begin = range.getBegin(); if(range.getEnd() != null) end = range.getEnd(); } else { if(range.getEnd() != null) begin = end + range.getEnd() + 1; } String contentRange = String.format("bytes %d-%d/%d", begin, end, length); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("Content-Range", contentRange); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); } // 实际执行下载操作 doDownloadFile(response, file, begin, end); } // 实际执行下载操作 private void doDownloadFile(HttpServletResponse response, File file, int begin, int end) throws IOException { InputStream is = null; OutputStream os = null; try { byte[] b = new byte[bufferSize]; is = new BufferedInputStream(new FileInputStream(file)); os = new BufferedOutputStream(response.getOutputStream()); // 跳过已下载的文件内容 is.skip(begin); // I/O 读写 for(int i, left = end - begin + 1; left > 0 && ((i = is.read(b, 0, Math.min(b.length, left))) != -1); left -= i) os.write(b, 0, i); os.flush(); } finally { if(is != null) {try{is.close();} catch(IOException e) {}} if(os != null) {try{os.close();} catch(IOException e) {}} } }
/** 文件下载结果枚举值 */ public static enum Result { /** 成功 */ SUCCESS, /** 失败:文件不存在 */ FILE_NOT_FOUND, /** 失败:读写操作失败 */ READ_WRITE_FAIL, /** 失败:未知异常 */ UNKNOWN_EXCEPTION; } }
原文出处:http://www.cnblogs.com/ldcsaa/archive/2012/02/23/2364036.html
发表评论
-
java注解详解
2014-09-22 10:12 701Java中提供了四种元注解,专门负责注解其他的注解,分别如下 ... -
log4j配置文件详解
2014-09-03 16:57 689在实际编程时,要使Log4 ... -
java获取请求用户的ip
2014-09-03 10:48 966/** * 获取请求用户的Ip * @par ... -
java常见的错误
2014-07-31 11:28 585原来见过的一些常见错 ... -
jsp中request详解
2014-07-28 15:10 674System.out.println("Protoc ... -
redis命令总结
2014-07-22 14:27 906public class Redis2 { priva ... -
深入理解JVM 内存模型
2014-07-21 10:54 678http://gotowqj.iteye.com/blog/2 ... -
java拷贝文件
2014-07-17 18:04 609package com.zuidaima.util.fil ... -
java对redis的操作
2014-07-17 17:59 563http://xuelianbobo.iteye.com/bl ... -
java中String Date Timestamp Calendar 之间的关系及转换
2014-07-17 17:34 604SimpleDateFormat sdf = new Simp ... -
java面试三大框架
2014-07-17 16:22 1231------------------------------- ... -
算法,java实现选择排序
2014-06-19 22:57 652一、基本思路: 选择排序和冒泡排序差不多,只是冒泡排序在发 ... -
算法,java实现冒泡排序
2014-06-19 22:54 719一、基本思路: 冒泡排序是一种简单的交换类排序。其基本思路是 ... -
java使用正则判断字符串
2014-06-16 11:19 393String str="http://www.jb5 ... -
struts2和springmvc比较
2014-06-12 00:14 583我们用struts2时采用的传统的配置文件的方式,并没有使用传 ... -
hibernate和mybatis的区别
2014-06-11 23:53 906以前没怎么用过mybatis,只知道与hibernate一样是 ... -
map的四种遍历方法
2014-06-11 23:20 614public static void main(String[ ... -
java模拟post方式提交表单实现图片上传
2014-04-18 15:57 3759package com.yanek.util; im ... -
java遍历文件夹
2014-04-18 15:25 611使用递归: import java.io.File; ... -
javahttp请求
2014-04-17 17:55 627package com.expai.utils; i ...
相关推荐
在java代码中实现文件的上传和下载,通过页面的file文件上传到java代码段,获取文件的大小和名字
在Java编程语言中,文件上传和下载是网络应用中常见的功能,特别是在Web应用程序中。这里,我们将深入探讨如何实现这两个核心操作,以及相关的技术、工具和最佳实践。 首先,文件上传通常涉及到用户通过Web表单将...
这里我们将深入探讨这些功能的实现,并结合标签`java xpdf java实现pdf`来讨论XPDF库在Java中的应用。 1. **PDF上传**: PDF上传通常涉及用户通过Web界面或API接口提交PDF文件。Java中,可以使用Apache Commons ...
利用java图形化界面和网络编程相结合实现的--文件上传。 运行步骤: (1)分别运行工程两个包中的两个.java文件(UploadClient.java和UploadServer.java)分别会弹出“上传客服端”和“上传服务器”两个窗口。 ...
在本文中,我们将介绍如何使用 Java 实现文件上传和下载,具体来说是使用 Struts2 框架在 Tomcat 服务器上实现文件上传和下载。 环境准备 首先,我们需要准备一个 Java 开发环境,我使用的是 MyEclipse 8.5,并且...
以上就是使用Java实现文件上传和下载的基本步骤及注意事项。通过结合源码和适当的工具,你可以构建健壮且易于维护的文件管理功能。在实际项目中,应根据具体需求进行调整和优化,确保系统的稳定性和用户体验。
在Java开发中,实现视频上传是一项常见的任务,尤其在构建Web应用或云存储服务时。这个Demo涵盖了几个关键的技术点,包括文件上传、视频转码和播放。让我们逐一深入探讨这些知识点。 首先,**文件上传**是Web应用...
在Java开发中,文件上传和下载是常见的功能需求,尤其...以上就是关于“Java实现文件上传与下载”以及使用Struts2框架进行操作的详细内容。理解并掌握这些知识点,将有助于在实际项目中实现高效、安全的文件管理功能。
以下将详细介绍如何使用Java实现FTP文件上传和下载的步骤,以及涉及到的关键知识点。 首先,Java通过`java.net`和`javax.net`包提供了FTP功能,但这些原生API并不易用。因此,大多数开发人员会使用第三方库如Apache...
Java实现COS(Cloud Object Storage)上传与下载是云计算服务中的常见操作,主要涉及对象存储的概念、Java SDK的使用以及文件I/O操作。COS通常由云服务提供商提供,用于存储大量的非结构化数据,如图片、视频、文档...
在Java开发中,文件和图片的上传下载是常见的功能需求,尤其在Web应用中更为重要。本篇文章将详细探讨三种不同的实现方式,分别是使用JSP+Servlet、SmartUpload库以及Apache的FileUpload组件。 首先,我们来看第一...
在Java开发中,文件批量上传是一项常见的功能,尤其在企业级应用中,用户可能需要上传大量数据或文件。本教程将介绍如何利用SWF(Simple Workflow)和EXT库来实现这一功能。SWF是一个用于创建富互联网应用程序的前端...
总的来说,实现Linux与Windows之间的文件上传下载以及网页版的文件管理界面,需要结合Java的文件操作、网络通信、SSH连接和HTML前端技术。通过合理的设计和实现,可以构建出高效、安全的跨平台文件传输系统。
在Java编程环境中,处理PDF(Portable Document Format)文件是一项常见的任务,这通常涉及到文件的上传、下载、在线预览、删除以及修改等操作。以下是对这些功能的详细说明: 1. **PDF上传**: PDF文件的上传主要...
在Java开发中,多文件上传是一项常见的功能,尤其在Web应用中,用户可能需要一次性上传多个文件,如图片、文档等。本知识点将详细介绍如何在Java中实现这一功能,以及结合Flash实现上传界面并显示上传进度条。 1. *...
### Java Spring Boot应用程序中实现文件上传和下载功能 在现代Web开发中,文件上传与下载是常见的需求之一。Spring Boot框架提供了简洁的方式帮助开发者轻松实现这些功能。本文将详细介绍如何在Spring Boot项目中...
JSch(Java Secure Channel)是一个SSH2的纯Java实现。它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成它的功能到你自己的应用程序。 下面是JSch库的API介绍: 1. put...
本文将深入探讨如何使用Java实现FTP文件上传和下载,包括基本概念、核心类库、实现步骤以及测试用例。 首先,Java通过`java.net`和`org.apache.commons.net.ftp`两个主要库支持FTP操作。`java.net`库中的`FTPClient...