`
mixer_a
  • 浏览: 359048 次
社区版块
存档分类
最新评论

Java 文件上传和下载组件的设计与实现

    博客分类:
  • java
阅读更多

概 述

  文件上传和下载是 Web 应用中的一个常见功能,相信各位或多或少都曾写过这方面相关的代码。但本座看过不少人在实现上传或下载功能时总是不知不觉间与程序的业务逻辑纠缠在一起,因此,当其他地方要用到这些功能时则无可避免地 Copy / Pase,然后再进行修改。这样丑陋不堪的做法导致非常容易出错不说,更大的问题是严重浪费时间不断做重复类似的工作,这是本座绝不能容忍的。哎,人生苦短啊,浪费时间在这些重复工作身上实在是不值得,何不把这些时间省出来打几盘罗马或者踢一场球?为此,本座利用一些闲暇之时光编写了一个通用的文件上传和文件下载组件,实现方法纯粹是基于 JSP,没有太高的技术难度,总之老少咸宜 ^_^。现把设计的思路和实现的方法向各位娓娓道来,希望能起到抛砖引玉的效果,激发大家的创造性思维。

  任何公共组件的设计都必须考虑下面两个问题:

  一、如何才能重用?

  答:首先,重用的组件必须有用,也就是说,是功能完备的。但另一方面,如果组件负责的职能太多也会影响重用。试想,一个文件上传组件同时还要负责插入数据库记录,一个文件下载组件还要负责解析下载请求并查找要下载的文件那可真是太悲哀了,叫别人如何重用?也就是说,重用组件必须是功能完备并职责单一的,绝对不能越界参与应用业务逻辑处理,不在其位不谋其政。

  另外,要重用的组件绝不能反向依赖于上层使用者。通常,重用组件以接口或类的形式提供给上层使用者调用,并放在单独的包中。也就是说,上层使用者所在的包依赖于组件所在的包,如果组件再反向依赖于上层使用者,则两个包之间就存在依赖环,严重违反了面向对象设计原则中的无环依赖原则(若想了解更多关于设计原则的内容请猛击这里 ^_^)。试想,通用的文件上传或下载组件如果需要读取一个在应用程序的某个地方定义的字符串常量来确定文件上传或下载的目录,那是囧了。

  本文件上传和下载组件在设计时充分考虑到上述问题,只负责单纯的上传和下载工作,所需要的外部信息均通过相应方法进行设置,不会依赖于任何使用者。

 

  二、组件的可用性如何?

  答:这是一个相当有深度的问题 ^_^ 所谓可用性通俗来说就是好不好用,使用起来是否方便。作为公共组件来说,可用性是一个十分重要的质量指标。如果组件十分复杂难用,倒不如自己花点时间自己写一个来得舒坦。本文件上传和下载组件在设计的过程中十分注重可用性目标,操作组件的代码行数不超过 10 行,只需几个步骤:

    1. 生成组件实例
    2. 设置实例属性
    3. 调用上传/下载方法
    4. 处理调用结果

  水吹得已经够多了,下面让我们来看看文件上传和下载组件分别是如何实现的。


文件上传

  文件上传操作通常会附加一些限制,如:文件类型、上传文件总大小、每个文件的最大大小等。除此以外,作为一个通用组件还需要考虑更多的问题,如:支持自定义文件保存目录、支持相对路径和绝对路径、支持自定义保存的文件的文件名称、支持上传进度反馈和上传失败清理等。另外,本座也不想重新造车轮,本组件是基于 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">
        &nbsp;<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">
        &nbsp;打球 <input type="checkbox" name="interest" value="2" checked="checked">
        &nbsp;下棋 <input type="checkbox" name="interest" value="3">
        &nbsp;打麻将 <input type="checkbox" name="interest" value="4">
        &nbsp;看书 <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="确 定">&nbsp;&nbsp;<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}"/>&nbsp;</td></tr>
    <tr><td>Brithday</td><td><c:out value="${persion.birthday}"/>&nbsp;</td></tr>
    <tr><td>Gender</td><td><c:out value="${persion.gender}"/>&nbsp;</td></tr>
    <tr><td>Working Age</td><td><c:out value="${persion.workingAge}"/>&nbsp;</td></tr>
    <tr><td>Interest</td><td><c:forEach var="its" items="${persion.interest}">
                                 <c:out value="${its}" /> &nbsp;
                          </c:forEach>&nbsp;</td></tr>
    <tr><td>Photos</td><td><c:forEach var="p" items="${persion.photos}">
                                 <c:out value="${p}" /><br>
                          </c:forEach>&nbsp;</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;
    }
}

 

分享到:
评论

相关推荐

    java上传,下载组件

    总的来说,"java上传,下载组件"是为了简化Java Web应用中的文件交互操作,通过提供高效、安全、易用的工具,使得开发者能够专注于业务逻辑,而不是底层实现。这个组件的实现可能包含了上述提到的多种技术和最佳实践...

    性能最好的Java文件上传组件

    "性能最好的Java文件上传组件"是指一种优化了上传速度和资源消耗的工具,它能够有效地处理大文件上传、多文件并发上传等问题,提高应用的用户体验。在这个场景中,我们提到了基于"COS"的Java文件上传组件。COS通常指...

    java文件上传组件包

    在Java编程语言中,文件上传是一项常见的...总的来说,"java文件上传组件包"提供了一系列工具和方法,帮助开发者高效、安全地处理文件上传任务。通过理解并熟练运用这些知识点,你可以构建出稳定可靠的文件上传系统。

    带进度条的文件上传下载组件(JAVA)

    在这个组件中,Java被用来编写服务器端的逻辑,处理文件的上传和下载请求,以及进度条状态的更新。 2. **JSP(JavaServer Pages)**:JSP是Java Web开发中的一个重要组成部分,它允许开发者在HTML页面中嵌入Java...

    Java实现文件与图片的上传下载---三种方式

    SmartUpload是一个Java上传组件,它简化了文件上传的处理过程。在JSP页面中,我们可以使用SmartUpload提供的标签库来创建上传界面。在Servlet中,初始化SmartUpload对象,调用其`upload()`方法解析上传请求,然后...

    java实现文件批量上传

    在Java开发中,文件批量上传是一项常见的功能,尤其在企业级应用中,用户可能需要上传大量数据或文件。本教程将介绍如何利用SWF(Simple Workflow)和EXT库来实现这一功能。SWF是一个用于创建富互联网应用程序的前端...

    java实现pdf上传,下载,在线预览,删除,修改等功能

    在Java开发中,处理PDF文件是一项常见的任务,包括上传、下载、在线预览、删除以及修改等操作。这里我们将深入探讨这些功能的实现,并结合标签`java xpdf java实现pdf`来讨论XPDF库在Java中的应用。 1. **PDF上传**...

    JAVA文件上传组件包

    一款很好用的JAVA文件上传组件包,能够轻松实现文件的上传和下载,很好用的

    java实现文件上传与下载

    在Java开发中,文件上传和下载是常见的功能需求,尤其在Web应用中,例如用户上传个人照片、下载文档等。Struts2是一个流行的Java Web框架,它提供了方便的文件上传和下载支持。以下是对这个主题的详细讲解。 一、...

    ajax实现java文件下载

    本话题将详细探讨如何通过Ajax实现Java文件的下载,并介绍相关的核心概念和技术。 1. **Ajax**(Asynchronous JavaScript and XML)是一种在不重新加载整个网页的情况下,能够更新部分网页的技术。它通过JavaScript...

    基于Java Swing 的带有文件上传和下载的聊天室

    【Java Swing 文件上传和下载聊天室】是一个使用Java Swing库构建的本地客户端-服务器应用程序,它允许用户进行实时聊天并交换文件。这个项目的核心技术包括Java的网络编程、图形用户界面设计以及文件处理。 首先,...

    java实现上传和下载

    在Java编程语言中,实现文件上传和下载是常见的任务,主要应用于Web应用程序,例如网站、云存储服务等。本项目提供了这些功能的实现,包括单个文件上传、多个文件上传(借助HTML5技术)以及文件下载。下面我们将深入...

    通用Java文件上传和下载组件的设计与实现

    发布于2012-5-7文件上传和下载是Web应用中的一个常见功能,相信各位或多或少都曾写过...为此,本座利用一些闲暇之时光编写了一个通用的文件上传和文件下载组件,实现方法纯粹是基于JSP,没有太高的技术难度,总之老少咸

    上传下载组件 java文件上传控件

    本主题将详细探讨一个名为"上传下载组件"的Java文件上传控件,该控件支持用户友好的文件上传和下载操作,并带有进度条显示,以提供更好的用户体验。这个组件的实现主要依赖于JavaScript(js)技术,同时也可能结合了...

    Java无组件上传Java无组件上传

    Java无组件上传是指在进行文件上传时,不依赖如Apache Commons FileUpload等第三方库的情况下来实现文件上传的功能。这种方式虽然相对复杂,但能够更好地控制程序的行为,并减少对外部依赖的需要。 ### 无组件上传...

    java文件上传组件包(fileupload)

    Java文件上传组件包(FileUpload)是Apache软件基金会的一个开源项目,主要负责处理HTTP协议中的文件上传功能。这个组件在Web应用开发中扮演着重要角色,尤其在处理用户通过表单上传大文件时,提供了高效且灵活的...

    java文件上传下载

    本教程将详细介绍如何利用`commons-fileupload`组件与Spring MVC结合实现文件上传,以及如何通过Servlet和`response`输出流实现文件下载。 首先,`commons-fileupload`是一个用于处理HTTP请求中的多部分数据(如...

    javaweb文件上传与下载模块源代码

    这个"javaweb文件上传与下载模块源代码"提供了一个实现这些功能的基础框架,对于初学者和Java程序员来说,这是一个很好的学习资源。下面将详细讲解其中涉及的关键知识点。 1. **Servlet和HTTP协议**: - 在Java ...

    java 文件上传(SWFUPLOAD组件)多文件上传 异步上传

    在IT行业中,文件上传是网站和应用程序中常见的一项功能,特别是在处理用户提交的图片、文档或其他类型的数据时。本文将详细讲解如何使用Java配合SWFUPLOAD组件实现多文件的异步上传。 首先,让我们理解一下...

Global site tag (gtag.js) - Google Analytics