javaweb 文件上傳(fileupload) 下載

1 文件上傳

html中通過<input type="file"/>可以向服務(wù)器上傳文件旬盯。不過后臺(tái)需要手動(dòng)解析請求,比較復(fù)雜,所以可以使用smartupload或apache的fileupload組件進(jìn)行文件的上傳。smartupload據(jù)網(wǎng)友測試辑鲤,在上傳大文件時(shí)不穩(wěn)定,所以還是使用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>

其中有幾個(gè)需要注意的點(diǎn):

  1. form表單的enctype必須為"multipart/form-data"。
  2. <input type="file" name="uploadFile"/> 中必須有name屬性瓢喉,因?yàn)樵趂ileupload中會(huì)根據(jù)fieldName解析上傳的文件宁赤。
  3. method必須為POST方法。
  4. 如果多文件上傳的話栓票,file類型的name必須為不同的名稱决左。

服務(wù)端需要建立一個(gè)UploadFileServlet來處理請求。
關(guān)鍵的doPost方法:

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

        // 首先檢測是否是文件上傳走贪,主要依據(jù)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();
        // 設(shè)置在內(nèi)存中的緩存大小哆窿,如果超過了則保存到臨時(shí)文件。
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        System.err.println(System.getProperty("java.io.tmpdir"));
        // 設(shè)置臨時(shí)文件夾的目錄
        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 設(shè)置單個(gè)文件大小的最大值
        upload.setFileSizeMax(MAX_FILE_SIZE);
        // 設(shè)置上傳文件總量的最大值厉斟,包括所有文件和表單的總和
        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;
        }
    }

這樣即可實(shí)現(xiàn)文件的上傳。

需要改進(jìn)的地方
  1. 沒有上傳進(jìn)度
  2. 限制文件大小
  3. 限制文件類型

對于第2點(diǎn)强衡,可以利用fileupload的拋出異常解決擦秽。

catch(FileUploadBase.FileSizeLimitExceededException e){
            request.setAttribute("message", "單個(gè)文件大小超過限制");
            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點(diǎn),可以在頁面用js進(jìn)行校驗(yàn)漩勤。


    <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點(diǎn)感挥,fileupload可以添加監(jiān)聽器,監(jiān)聽上傳進(jìn)度越败。

//設(shè)置上傳進(jìn)度的監(jiān)聽器
        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);

雖然服務(wù)端添加了監(jiān)聽器触幼,可以在console或者Log里打印上傳進(jìn)度,但我們想要的是讓用戶看到上傳進(jìn)度究飞。所以需要把進(jìn)度返回給用戶置谦。
要解決的問題主要有兩個(gè):

  1. 進(jìn)度信息如何保存
  2. 前臺(tái)如何獲取
    其中的一種方案是利用session。我們將上傳進(jìn)度保存在session里亿傅,前臺(tái)通過ajax方法定時(shí)獲取上傳的進(jìn)度媒峡,因?yàn)槊總€(gè)用戶是一個(gè)session,不同的用戶session不同。這樣當(dāng)不同的用戶同時(shí)上傳文件時(shí)葵擎,依然可以正確的獲得上傳進(jìn)
    度谅阿,不會(huì)獲取到其他用戶上傳文件的進(jìn)度。
    具體首先有一個(gè)簡單的保存上傳進(jìn)度的實(shí)體類:
public class UploadStatus {
    private double percent;

    public double getPercent() {
        return percent;
    }

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

然后有一個(gè)監(jiān)聽的類,實(shí)現(xiàn)了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方法中:

//設(shè)置上傳進(jìn)度的監(jiān)聽器
        UploadStatus status = new UploadStatus();
        UploadListener listener = new UploadListener(status);
        upload.setProgressListener(listener);
        request.getSession(true).setAttribute("uploadStatus", status);

最后在doGet方法中签餐,返回上傳進(jìn)度寓涨。

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("上傳進(jìn)度:" + status.getPercent());
        }else{
            writer.write("沒有上傳信息");
        }
        writer.flush();
        writer.close();
    }

當(dāng)用戶上傳時(shí)可以訪問doGet方法即可獲取進(jìn)度。
前臺(tái)頁面可以這樣寫:
其中form的target屬性可以防止頁面跳轉(zhuǎn)氯檐。

<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>上傳進(jìn)度:</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里,然后創(chuàng)建一個(gè)ajax請求,type是POST耗拓,

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

這幾個(gè)屬性很重要拇颅,具體解釋請百度之。
上傳進(jìn)度則使用如下的方式獲得:

 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;
                    }
                }

這里注意這個(gè)是瀏覽器上傳的進(jìn)度乔询,在服務(wù)端還有寫入磁盤的時(shí)間樟插,所以即便達(dá)到100%了服務(wù)端可能也并沒有完全完成保存操作,不過這個(gè)時(shí)間差可以忽略竿刁。

注意如果出于頁面美化的目的黄锤,需要隱藏input框,那么一般會(huì)使用display:none這個(gè)css屬性食拜,但由于瀏覽器安全性的限制鸵熟,這種方式無法奏效,這里提供一個(gè)tricky的方法负甸,給input框設(shè)置一個(gè)position:absolute;top:-9999px;,這樣既可以隱藏input流强,又可以觸發(fā)選擇文件的操作。

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("文件已經(jīng)被刪除");
            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的設(shè)置即可呻待。
最終一個(gè)完整的文件上傳下載的例子FileServer

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末打月,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蚕捉,更是在濱河造成了極大的恐慌奏篙,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迫淹,死亡現(xiàn)場離奇詭異秘通,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)千绪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門充易,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荸型,你說我怎么就攤上這事盹靴≌耄” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵稿静,是天一觀的道長梭冠。 經(jīng)常有香客問我,道長改备,這世上最難降的妖魔是什么控漠? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮悬钳,結(jié)果婚禮上盐捷,老公的妹妹穿的比我還像新娘。我一直安慰自己默勾,他們只是感情好碉渡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著母剥,像睡著了一般滞诺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上环疼,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天习霹,我揣著相機(jī)與錄音,去河邊找鬼炫隶。 笑死淋叶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伪阶。 我是一名探鬼主播爸吮,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼望门!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锰霜,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤筹误,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后癣缅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厨剪,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年友存,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祷膳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屡立,死狀恐怖直晨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤勇皇,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布罩句,位于F島的核電站,受9級特大地震影響敛摘,放射性物質(zhì)發(fā)生泄漏门烂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一兄淫、第九天 我趴在偏房一處隱蔽的房頂上張望屯远。 院中可真熱鬧,春花似錦捕虽、人聲如沸慨丐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咖气。三九已至,卻和暖如春挖滤,著一層夾襖步出監(jiān)牢的瞬間崩溪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工斩松, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伶唯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓惧盹,卻偏偏與公主長得像乳幸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子钧椰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容