我和我四次版本的上傳模塊

我和我四次版本的上傳模塊

功能說明

我已經做了很多個功能模塊了,現在想來整理一下做過的功能,一些web應用的基礎功能。
第一個就是上傳模塊,說起來,上傳看起來一件簡單的事情,如果是以前,能上傳就是一個了不起的功能了。可是現在不一樣了,我們要給用戶更好的體驗,所以就有了我四個版本的上傳模塊,分別如下:
1. 簡單
2. 異步
3. 進度條
4. 斷點

簡單

簡單上傳就是一個傳統的form表單上傳,一個簡單粗暴的上傳方式。用戶必須開着頁面,等待上傳,而且上傳極有可能不成功,特別是大文件的時候。
但是也是有優點的,那就是代碼量特少。這並不好,我一直覺得寧願我多寫兩行代碼,讓用戶更加方便,也不要省那兩行。

上傳頁面

simple_upload.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>簡單提交頁面</title>
</head>
<body>
    <form method="post" action="simple_upload" enctype="multipart/form-data">
        <input name="file" type="file" accept="image/gif,image.jpg"/>
        <input name="token" type="hidden"/>
        <input type="submit" value="提交"/>
    </form>
</body>
</html>

說明教程外鏈:W3School

後臺處理

我主要提供兩種後端處理的代碼,第一種就是使用org.apache.commons.fileupload.*

package com.chen.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

/**
 * Created by CHEN on 2016/8/10.
 */
@WebServlet("/simple_upload")
public class SimpleUploadFile extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //檢查token,要是錯誤的token,禁止上傳,返回錯誤頁面之類的
        //TODO 很多代碼

        //磁盤文件項目工廠
        DiskFileItemFactory factory=new DiskFileItemFactory();
        //設置文件閥值,當文件超過5M的時候,產生臨時文件並存儲在臨時的目錄中
        factory.setSizeThreshold(5*1024*1024);
        //設置緩存路徑
        factory.setRepository(new File(req.getServletContext().getRealPath("/temp")));

        //上傳的工具
        ServletFileUpload upload=new ServletFileUpload(factory);
        //設置上傳的文件最大值
        upload.setSizeMax(50*1024*1024);

        try {
            List<FileItem> items=upload.parseRequest(req);
            if(items.size()<0) { //一個表單項都沒有

            } else {
                for(FileItem item:items) {
                    if(item.isFormField()) {//表單項,而不是文件上傳項

                    }else {//文件項
                        String filename=item.getName();
                        //後綴檢查
                        String filesuffix=filename.substring(filename.lastIndexOf("."));
                        //TODO 很多代碼

                        StringBuffer buffer=new StringBuffer(req.getServletContext().getRealPath("/file"));
                        buffer.append("/");
                        buffer.append(UUID.randomUUID().toString());
                        buffer.append(filesuffix);
                        filename= buffer.toString();
                        //
                        FileOutputStream out=new FileOutputStream(filename);
                        InputStream in=item.getInputStream();
                        //開始寫文件
                        byte[] b=new byte[1024];
                        int len=0;
                        while((len=in.read(b))>0) {
                            out.write(b,0,len);
                        }
                        in.close();
                        out.close();
                        item.delete();//如果有臨時文件就刪除
                    }
                }
            }
        } catch (FileUploadException e) {
            //TODO 異常處理之類的
            e.printStackTrace();
        }
        //成功的頁面
        resp.sendRedirect("success_page");

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //跳轉鏈接
        resp.sendRedirect("simple_upload.html");
    }
}

像上面的代碼,是一個簡單的上傳模塊。

異步

有時候,用戶可不想在那裏傻傻的等待你跳轉頁面,他想要有其他事情同時可以做。那這時候,就要使用異步的方式,但是其實異步也和之前的無兩樣,只是把表單提取出來,提交而已。

上傳頁面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>異步上傳頁面</title>
</head>
<body>
    <form>
        <span id="message"></span>
        <input id="file" name="file" type="file"/>
        <input id="token" name="token" type="hidden"/>
    </form>
    <script src="https://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
    <script src="/js/ajaxfileupload.js"></script>
    <script>
        $("#file").on("change",function() {

            $.ajaxFileUpload({
                url: "ajax_upload",
                fileElementId : 'file',//上傳文件的ID
                dataType:"json",//返回數據格式
                success :function(data) {
                    $("#message").html(data['message']);
                },
                error:function(status) {

                }
            });

        });


    </script>
</body>
</html>

後臺處理

後臺處理基本差不多,就是把跳轉換成了輸出JSON。

package com.chen.servlet;

import net.sf.json.JSONObject;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.UUID;

/**
 * Created by CHEN on 2016/8/10.
 */
@WebServlet("/ajax_upload")
public class AjaxUploadFile extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        //檢查token,要是錯誤的token,禁止上傳,返回錯誤頁面之類的
        //TODO 很多代碼

        //磁盤文件項目工廠
        DiskFileItemFactory factory=new DiskFileItemFactory();
        //設置文件閥值,當文件超過5M的時候,產生臨時文件並存儲在臨時的目錄中
        factory.setSizeThreshold(5*1024*1024);
        //設置緩存路徑
        factory.setRepository(new File(req.getServletContext().getRealPath("/temp")));

        //上傳的工具
        ServletFileUpload upload=new ServletFileUpload(factory);
        //設置上傳的文件最大值
        upload.setSizeMax(50*1024*1024);

        try {
            List<FileItem> items=upload.parseRequest(req);
            if(items.size()<0) { //一個表單項都沒有

            } else {
                for(FileItem item:items) {
                    if(item.isFormField()) {//表單項,而不是文件上傳項

                    }else {//文件項
                        String filename=item.getName();
                        //後綴檢查
                        String filesuffix=filename.substring(filename.lastIndexOf("."));
                        //TODO 很多代碼

                        StringBuffer buffer=new StringBuffer(req.getServletContext().getRealPath("/file"));
                        buffer.append("/");
                        buffer.append(UUID.randomUUID().toString());
                        buffer.append(filesuffix);
                        filename= buffer.toString();
                        //
                        FileOutputStream out=new FileOutputStream(filename);
                        InputStream in=item.getInputStream();
                        //開始寫文件
                        byte[] b=new byte[1024];
                        int len=0;
                        while((len=in.read(b))>0) {
                            out.write(b,0,len);
                        }
                        in.close();
                        out.close();
                        item.delete();//如果有臨時文件就刪除
                    }
                }
            }
        } catch (FileUploadException e) {
            //TODO 異常處理之類的
            e.printStackTrace();
        }
        //成功提示
        JSONObject data=new JSONObject();
        data.put("message","success");
        PrintWriter out=resp.getWriter();
        out.print(data);
        out.close();

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //跳轉鏈接
        resp.sendRedirect("simple_upload.html");
    }
}

進度條

異步模塊看起來已經是不錯的了,但是 沒有一個進度條之類的,就讓用戶在那裏等着,直到上傳成功,就突然跳出一句上傳成功,感覺這樣的交互,不好,所以在第三次中,我增加了一個進度條的功能,使用的是jQuery中的Uploadify。
詳細文檔:Uploadify

上傳頁面

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
    <head>
        <title>Upload</title>
        <!--裝載文件-->
        <link href="uploadify-v3.1/uploadify.css" type="text/css" rel="stylesheet" />
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript" src="uploadify-v3.1/jquery.uploadify-3.1.js"></script>
        <script type="text/javascript" src="uploadify-v3.1/swfobject.js"></script>
        <!--ready事件-->
        <script type="text/javascript">
            $(document).ready(function () {
                    $("#uploadify").uploadify({
                     'debug':'true',
                     'method':'post',
                     'formData':{'chen':'chen'},//發送給服務端的參數,格式:{key1:value1,key2:value2}
                     'swf': 'uploadify-v3.1/uploadify.swf',
                      'uploader':'file_upload',
                     //'script':'upload!doUpload.action?name=yangxiang',
                     //'script': 'servlet/Upload?name=yangxiang',  
                     //'cancel' : 'uploadify-v3.1/uploadify-cancel.png',                  
                     'queueID' : 'fileQueue', //和存放隊列的DIV的id一致  
                     //'fileDataName': 'fileupload', //必須,和以下input的name屬性一致                   
                     'auto'  : false, //是否自動開始  
                     'multi': true, //是否支持多文件上傳  
                      'buttonText': '選擇文件', //按鈕上的文字
                     'simUploadLimit' : 1, //一次同步上傳的文件數目
                     //'sizeLimit': 19871202, //設置單個文件大小限制,單位爲byte
                     'fileSizeLimit' : '6000MB',
                      'queueSizeLimit' : 10, //隊列中同時存在的文件個數限制  
                     //'fileTypeExts': '*.jpg;*.gif;*.jpeg;*.png;*.bmp;*.iso',//允許的格式
                      //'fileTypeDesc': '支持格式:jpg/gif/jpeg/png/bmp/iso.', //如果配置了以下的'fileExt'屬性,那麼這個屬性是必須的  
                     'onUploadSuccess': function ( fileObj, response, data) {  
                            alert("文件:" + fileObj.name + "上傳成功");
                     },  
                     'onUploadError': function(event, queueID, fileObj) {  
                            alert("文件:" + fileObj.name + "上傳失敗");  
                     },  
                     'onCancel': function(event, queueID, fileObj){  
                            alert("取消了" + fileObj.name);  
                       } 
                 });
            });
        </script>
    </head>

    <body>
        <div id="fileQueue" style="width: 30%"></div>
        <input type="file" name="file" id="uploadify" />

        <p>
            <a href="javascript:jQuery('#uploadify').uploadify('upload','*')">開始上傳</a>&nbsp;

            <a href="javascript:$('#uploadify').uploadify('cancel',$('.uploadifive-queue-item').first().data('file'))">取消上傳</a>

            <a href="javascript:$('#uploadify').uploadify('cancel','*')">清空所有的上傳文件</a>

            <a href="javascript:$('#uploadify').uploadify('stop','*')">暫停</a>

            <!-- 如果填入true則表示禁用上傳按鈕 -->
            <a href="javascript:$('#uploadify').uploadify('disable','true')">禁用</a>

            <a href="javascript:$('#uploadify').uploadify('debug')">調試</a>

        </p>
    </body>
</html>

後臺處理

import net.sf.json.JSONObject;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by CHEN on 2016/8/8.
 */
@WebServlet("/file_upload")
public class FileServlet extends HttpServlet {

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Map map = new HashMap();
        request.setCharacterEncoding("utf-8");
        DiskFileItemFactory factory = new DiskFileItemFactory();

        factory.setRepository(new File("I:\\file\\temp"));
        factory.setSizeThreshold(1024*1024) ;
        ServletFileUpload upload = new ServletFileUpload(factory);
        try {
            //可以上傳多個文件
            List<FileItem> list = (List<FileItem>)upload.parseRequest(request);

            if(list.size()<0) {

            } else {
                for(FileItem item : list){
                    if(!item.isFormField()){
                        String name = item.getName() ;
                        String fileSuffix  = name.substring(name.lastIndexOf(".")+1,name.length());
                        String oldName = name.replaceAll("." + fileSuffix,"");
                        String fileName = ""+System.currentTimeMillis();
                        String newName = fileName + "." + fileSuffix;
                        OutputStream out = new FileOutputStream(new File("I:\\file\\",newName));
                        InputStream in = item.getInputStream() ;
                        int length = 0 ;
                        byte [] buf = new byte[1024] ;
                        while( (length = in.read(buf) ) != -1){
                            out.write(buf, 0, length);
                        }
                        in.close();
                        out.close();
                        /**將上傳處理後的數據返回**/
                        map.put("fileSuffix",fileSuffix);
                        map.put("fileName",oldName);
                        map.put("filePath",fileName);
                        item.delete();
                        break;

                    }
                }

            }
        }catch (Exception e) {
            System.out.println("出錯了:" + e.getMessage());
        }
        response.setContentType("text/xml; charset=UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        PrintWriter out = response.getWriter();
        JSONObject jsonObject = JSONObject.fromObject(map);
        String msg =  jsonObject.toString();
        out.print(msg);
        out.close();
    }

    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }
}

上面的代碼順帶解決了前端傳參給servlet的難題。

斷點續傳

總的來說,這個功能看起來已經不錯了,都有進度條了,很友好。可是還是不夠了,假如用戶上傳2G的文件呢,上傳到差不多90%了,可是手一抖,關了瀏覽器,那就什麼都沒有了。
在博客園,看到了一個很好的博文,呂大豹的《支持斷點續傳的文件上傳插件——Huploadify-V2.0來了
提出了很好的解決方案,那就是對上傳的文件進行分割,每次只上傳一部分,最後進行追加,合併。
代碼如下

上傳頁面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" type="text/css" href="Huploadify.css"/>

</head>

<body>
<div id="upload"></div>
<button id="btn1">暫停</button>
<button id="btn2">上傳</button>
<button id="btn3">取消</button>
<button id="btn4">disable</button>
<button id="btn5">ennable</button>
<button id="btn6">destroy</button>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery.Huploadify.js"></script>
<style type="text/css">
</style>
<script type="text/javascript">
    $(function(){

        var up = $('#upload').Huploadify({
            auto:true,
            fileTypeExts:'*.vmfk;*.jpg;*.png;*.exe;*.mp3;*.mp4;*.zip;*.doc;*.docx;*.ppt;*.pptx;*.xls;*.xlsx;*.pdf',
            multi:true,
            fileSizeLimit:99999999,
            breakPoints:true,
            saveInfoLocal:true,
            showUploadedPercent:true,//是否實時顯示上傳的百分比,如20%
            showUploadedSize:true,
            removeTimeout:9999999,
            uploader:'uploadfile',
            onUploadStart:function(){
                //up.settings('formData', {aaaaa:'1111111',bb:'2222'});
                var filename=$('.up_filename').eq(0).text();
                up.Huploadify('settings','formData', {'filename':filename});

            },
            onUploadSuccess:function(file){
                //alert('上傳成功');
            },
            onUploadComplete:function(){
                //alert('上傳完成');
            },
            /*getUploadedSize:function(file){
             var data = {
             data : {
             fileName : file.name,
             lastModifiedDate : file.lastModifiedDate.getTime()
             }
             };
             var url = 'http://49.4.132.173:8080/admin/uploadfile/index/';
             var uploadedSize = 0;
             $.ajax({
             url : url,
             data : data,
             async : false,
             type : 'POST',
             success : function(returnData){
             returnData = JSON.parse(returnData);
             uploadedSize = returnData.uploadedSize;
             }
             });
             return uploadedSize;
             }  */
        });

        $('#btn1').click(function(){
            up.stop();
        });
        $('#btn2').click(function(){
            up.upload('*');
        });
        $('#btn3').click(function(){
            up.cancel('*');
        });
        $('#btn4').click(function(){
            //up.disable();
            up.Huploadify('disable');
        });
        $('#btn5').click(function(){
            up.ennable();
        });
        $('#btn6').click(function(){
            up.destroy();
        });

    });
</script>
</body>
</html>

基本是他的例子,有一點特殊的就是,我向後臺傳了一個文件名的參數。

後臺處理

import net.sf.json.JSONObject;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by CHEN on 2016/8/8.
 */
@WebServlet("/hu_load/uploadfile")
public class UploadFile extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        Map map=new HashMap();
        DiskFileItemFactory factory=new DiskFileItemFactory();
        factory.setSizeThreshold(10*1024);
        factory.setRepository(new File(req.getServletContext().getRealPath("/temp")));

        ServletFileUpload upload=new ServletFileUpload(factory);
        upload.setHeaderEncoding("utf-8");

        List<FileItem> list= null;
        try {
            list = upload.parseRequest(new ServletRequestContext(req));
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
        if(list.size()<0) {

        }else {
            String filename="";
            for(FileItem item:list) {
                if("".equals(filename)&&"filename".equals(item.getFieldName())) {
                    filename=new String(item.getString().getBytes("iso-8859-1"),"utf-8");
                    break;
                }
            }
            for(FileItem item:list) {

                if(!item.isFormField()) {
                    File file=new File("I:\\file\\"+filename);
                    OutputStream out;
                    InputStream in = item.getInputStream() ;
                    if(file.exists()) {
                        out=new FileOutputStream(file,true);
                    }else {
                        out = new FileOutputStream(file);
                    }
                    int length = 0 ;
                    byte [] buf = new byte[1024] ;
                    while( (length = in.read(buf) ) != -1){
                        out.write(buf, 0, length);
                    }
                    in.close();
                    out.close();
                    /**將上傳處理後的數據返回**/
              /*  map.put("fileSuffix",fileSuffix);
                map.put("fileName",oldName);
                map.put("filePath",fileName);*/
                    item.delete();
                    break;
                }
            }
        }

        //可有可無
        resp.setContentType("text/xml; charset=UTF-8");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setHeader("Pragma", "no-cache");
        PrintWriter out = resp.getWriter();
        JSONObject jsonObject = JSONObject.fromObject(map);
        String msg =  jsonObject.toString();
        out.print(msg);
        out.close();

    }
}

一開始我也沒有辦法獲得前端傳來的參數,後來我打開他的源碼,看了這樣的一個函數

//發送文件塊函數
    var sendBlob = function(url,xhr,file,formdata){
        xhr.open(option.method, url, true);
        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

        var fd = new FormData();
        fd.append(option.fileObjName,file);
        if(formdata){
        for(key in formdata){
            fd.append(key,formdata[key]);
        }
      }
        xhr.send(fd);
    }

一開始我對他進行了修改,改成了xhr.send(“filename”,filename),然後後臺用getPart()去接受參數,經過了很多次折騰。後臺終於能拿到前端參數了,就像我寫的那樣,首先要遍歷所有的FileItem,拿到文件名,然後纔開始寫文件。

後言

上傳功能算是結束了嗎,不,還沒有。還有類如拍照上傳、拖拽上傳等等沒有實現,這些就留着你去思考了。提示一下,基本要依靠HTML5的特性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章