javaweb 文件上传(fileupload) 下载

1 文件上传

html中通过<input type="file"/>可以向服务器上传文件。不过后台需要手动解析请求,比较复杂,所以可以使用smartupload或apache的fileupload组件进行文件的上传。smartupload据网友测试,在上传大文件时不稳定,所以还是使用fileupload的吧,毕竟apache出品。

本例子中使用的jar包:

  1. commons-fileupload-1.3.2.jar
  2. commons-io-2.5.jar(fileupload依赖)

前端jsp页面:

    <div class="upload">
        <form action="UploadFileServlet" method="POST" enctype="multipart/form-data">
            上传文件:<input type="file" name="uploadFile">
        <input type="submit" value="upload">上传
        <input type="reset" value="reset">重置
        </form>
        
    </div>

其中有几个需要注意的点:

  1. form表单的enctype必须为"multipart/form-data"。
  2. <input type="file" name="uploadFile"/> 中必须有name属性,因为在fileupload中会根据fieldName解析上传的文件。
  3. method必须为POST方法。
  4. 如果多文件上传的话,file类型的name必须为不同的名称。

服务端需要建立一个UploadFileServlet来处理请求。
关键的doPost方法:

protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub

        // 首先检测是否是文件上传,主要依据enctype的值来判定
        if (!ServletFileUpload.isMultipartContent(request)) {
            PrintWriter writer = response.getWriter();
            writer.write("Error 不是文件上传,表单必须包含 enctype='multipart/form-data'");
            writer.flush();
            writer.close();
            return;
        }

        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 设置在内存中的缓存大小,如果超过了则保存到临时文件。
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        System.err.println(System.getProperty("java.io.tmpdir"));
        // 设置临时文件夹的目录
        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 设置单个文件大小的最大值
        upload.setFileSizeMax(MAX_FILE_SIZE);
        // 设置上传文件总量的最大值,包括所有文件和表单的总和
        upload.setSizeMax(MAX_REQUEST_SIZE);
        File uploadDir = new File(UPLOAD_PATH);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }
        try {
            // 解析request
            List<FileItem> formItems = upload.parseRequest(request);
            if (formItems != null && formItems.size() > 0) {
                for (FileItem item : formItems) {
                    // 如果不是普通的formField则就是上传的文件
                    if (!item.isFormField()) {
                        String fileName = item.getName();
                        File storeFile = new File(UPLOAD_PATH + File.separator
                                + fileName);
                        System.err.println(storeFile.getAbsolutePath());
                        item.write(storeFile);
                        item.delete();

                    } else {
                        System.err.println(item.getString());
                    }
                }
            }
            request.setAttribute("message", "成功");
            request.getRequestDispatcher("result.jsp").forward(request,
                    response);

        } catch (Exception e) {
            // TODO: handle exception
            request.setAttribute("message", e.getMessage());
            request.getRequestDispatcher("result.jsp").forward(request,
                    response);
            e.printStackTrace();
            return;
        }
    }

这样即可实现文件的上传。

需要改进的地方
  1. 没有上传进度
  2. 限制文件大小
  3. 限制文件类型

对于第2点,可以利用fileupload的抛出异常解决。

catch(FileUploadBase.FileSizeLimitExceededException e){
            request.setAttribute("message", "单个文件大小超过限制");
            request.getRequestDispatcher("result.jsp").forward(request,
                    response);
            e.printStackTrace();
            return;
        
        } catch(FileUploadBase.SizeLimitExceededException e){
            request.setAttribute("message", "上传文件总大小超过限制");
            request.getRequestDispatcher("result.jsp").forward(request,
                    response);
            e.printStackTrace();
            return;
        
        }

对于第3点,可以在页面用js进行校验。


    <form action="UploadFileServlet" method="POST"
        enctype="multipart/form-data" onsubmit="return check_file()">
        上传文件:<input type="file" name="uploadFile" id="uploadFile"> <input
            type="submit" value="upload">上传 <input type="reset"
            value="reset">重置

    </form>
    <script>
        function check_file() {
            var fileName = document.getElementById("uploadFile").value;
            var suffix = fileName.substr(fileName.lastIndexOf(".") + 1);
            if (suffix !== "exe") {
                alert("只能上传exe文件");
                return false;
            }

        }
    </script>

对于第1点,fileupload可以添加监听器,监听上传进度。

//设置上传进度的监听器
        ProgressListener progressListener = new ProgressListener() {
            public void update(long pBytesRead, long pContentLength, int pItems) {
                System.out.println("We are currently reading item " + pItems);

                if (pContentLength == -1) {
                    System.out.println("So far, " + pBytesRead
                            + " bytes have been read.");

                } else {
                    System.out.println("So far, " + pBytesRead + " of "
                            + pContentLength + " bytes have been read.");
                    uploadPercent = (double) pBytesRead / pContentLength;
                    System.err.println(uploadPercent);

                }

            }
        };
        upload.setProgressListener(progressListener);

虽然服务端添加了监听器,可以在console或者Log里打印上传进度,但我们想要的是让用户看到上传进度。所以需要把进度返回给用户。
要解决的问题主要有两个:

  1. 进度信息如何保存
  2. 前台如何获取
    其中的一种方案是利用session。我们将上传进度保存在session里,前台通过ajax方法定时获取上传的进度,因为每个用户是一个session,不同的用户session不同。这样当不同的用户同时上传文件时,依然可以正确的获得上传进
    度,不会获取到其他用户上传文件的进度。
    具体首先有一个简单的保存上传进度的实体类:
public class UploadStatus {
    private double percent;

    public double getPercent() {
        return percent;
    }

    public void setPercent(double percent) {
        this.percent = percent;
    }
}

然后有一个监听的类,实现了ProgressListener接口

public class UploadListener implements ProgressListener{
    private UploadStatus status;
    public  UploadListener(UploadStatus status) {
        
        // TODO Auto-generated constructor stub
        this.status = status;
    }
    @Override
    public void update(long pBytesRead, long pContentLength, int pItems) {
        
        // TODO Auto-generated method stub
        double uploadPercent = (double) pBytesRead / pContentLength;
        status.setPercent(uploadPercent);
    }

}

在doPost方法中:

//设置上传进度的监听器
        UploadStatus status = new UploadStatus();
        UploadListener listener = new UploadListener(status);
        upload.setProgressListener(listener);
        request.getSession(true).setAttribute("uploadStatus", status);

最后在doGet方法中,返回上传进度。

protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "no-store");
        response.setDateHeader("Expires", 0);
        response.setHeader("Pragrma", "no-cache");
        PrintWriter writer = response.getWriter();
        UploadStatus status = (UploadStatus)(request.getSession().getAttribute("uploadStatus"));
        if(status != null){
            writer.write("上传进度:" + status.getPercent());
        }else{
            writer.write("没有上传信息");
        }
        writer.flush();
        writer.close();
    }

当用户上传时可以访问doGet方法即可获取进度。
前台页面可以这样写:
其中form的target属性可以防止页面跳转。

<body>
<iframe width=0 height=0 name="uploadFrame"></iframe>
    <form action="UploadFileServlet" method="POST"
        enctype="multipart/form-data" target="uploadFrame" onsubmit="getStatus()">
        上传文件:<input type="file" name="uploadFile" id="uploadFile"> <input
            type="submit" value="upload">上传 <input type="reset"
            value="reset">重置

    </form>
    <span>上传进度:</span><span id="progress"></span>
    <script src="jquery-1.11.2.js"></script>
    <script>
    var finished ;
        function check_file() {
            var fileName = document.getElementById("uploadFile").value;
            var suffix = fileName.substr(fileName.lastIndexOf(".") + 1);
            if (suffix !== "exe") {
                //alert("只能上传exe文件");
                //return false;
            }
            finished = false;
            return true;

        }
        function getStatus(){
            finished = false;
            console.log("finished = " + finished)

            showStatus();
        }
        
        function showStatus(){
            console.log("showstatus finished = " + finished)

            if(finished === true) return;
            $.ajax({
                url:'UploadFileServlet',
                type:'GET',
                success:function(data){
                    $('#progress').text(data);
                    if(data == '1.0'){
                        finished = true;
                    }
                        
                },
                error:function(data){
                    alert(data);
                }
            
            });
            setTimeout(showStatus,1000);
        }
        
    </script>
</body>

最后 完整的程序UploadFile.zip
除了使用form表单上传文件这种方式,也可以利用html5的特性使用ajax上传文件。
下面是主要代码。

           var formData = new FormData();
            formData.append('file', $('#input')[0].files[0]);
          $.ajax({
                url: contextPath + '/file/upload?savedName=' + savedName,
                type: 'POST',
                data: formData,
                cache: false,
                contentType: false,
                processData: false,
                xhr: function () {
                    var xhr = $.ajaxSettings.xhr();
                    if (xhr.upload) {
                        xhr.upload.addEventListener("progress", function (evt) {
                            var percent = parseInt((evt.loaded / evt.total) * 100) + '%';
                            console.log(percent);
                        }, false);
                        return xhr;
                    }
                }
            }).done(function(e){})

首先初始化formData对象,然后将真正的file添加到formData里,然后创建一个ajax请求,type是POST,

               data: formData,
                cache: false,
                contentType: false,
                processData: false,

这几个属性很重要,具体解释请百度之。
上传进度则使用如下的方式获得:

 xhr: function () {
                    var xhr = $.ajaxSettings.xhr();
                    if (xhr.upload) {
                        xhr.upload.addEventListener("progress", function (evt) {
                            var percent = parseInt((evt.loaded / evt.total) * 100) + '%';
                            console.log(percent);
                        }, false);
                        return xhr;
                    }
                }

这里注意这个是浏览器上传的进度,在服务端还有写入磁盘的时间,所以即便达到100%了服务端可能也并没有完全完成保存操作,不过这个时间差可以忽略。

注意如果出于页面美化的目的,需要隐藏input框,那么一般会使用display:none这个css属性,但由于浏览器安全性的限制,这种方式无法奏效,这里提供一个tricky的方法,给input框设置一个position:absolute;top:-9999px;,这样既可以隐藏input,又可以触发选择文件的操作。

2 文件下载

文件下载相对比较简单

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        String id = request.getParameter("id");
        //app.properties 路径
        DFileItem dao = new DFileItem();
        String basePath = request.getSession().getServletContext().getRealPath("");
        String configPath = basePath+Constants.CONFIG_PATH;
        String uploadPath = Configuration.getInstance(configPath).getProperty(Constants.FILE_STORAGE_PATH);
        FileItem item = dao.queryFileItem(id);
        if(item == null){
            PrintWriter writer = response.getWriter();

            writer.write("文件已经被删除");
            writer.close();
            return;
        }
        
        String name = item.getFileName();
        response.setContentType("application/octet-stream");
        String downloadName = URLEncoder.encode(name,"UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'"+downloadName);
        File file = new File(uploadPath+item.getUploadTime());
        response.setContentLength((int)file.length());
        FileInputStream in = new FileInputStream(file);
        OutputStream out = response.getOutputStream();
        byte[] buffer = new byte[8192];
        int len = 0;
        while((len = in.read(buffer)) != -1){
            out.write(buffer, 0, len);
        }
        out.flush();
        out.close();
        in.close();
    }

注意response 的contentType和header的设置即可。
最终一个完整的文件上传下载的例子FileServer

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容