1. 程式人生 > >Web---檔案上傳-用apache的工具處理、打散目錄、簡單檔案上傳進度

Web---檔案上傳-用apache的工具處理、打散目錄、簡單檔案上傳進度

我們需要先準備好2個apache的類:

上一個部落格文章只講了最簡單的入門,現在來開始慢慢加深。

先過渡一下:只上傳一個file項

index.jsp:

<h2>用apache的工具處理檔案上傳</h2>
    <!-- 先過渡一下:只上傳一個file項 -->
    <form action="<%= request.getContextPath() %>/upload" method="post" enctype="multipart/form-data">
        檔案:<input type
="file" name="file"/>
<br/> <input type="submit" value="提交"/> </form>

web.xml:

<servlet>
    <servlet-name>UploadServlet</servlet-name>
    <servlet-class>cn.hncu.servlets.upload.UploadServlet</servlet-class>
  </servlet>
  <servlet-mapping
>
<servlet-name>UploadServlet</servlet-name> <url-pattern>/upload</url-pattern> </servlet-mapping>

UploadServlet.java:

package cn.hncu.servlets.upload;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util
.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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.io.FileUtils; public class UploadServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //下面這句解決上傳檔名的中文亂碼 //注意。下面這句設定中文,如果是“multipart/form-data”表單,可以設定其中file元件的檔名,但對其中的普通表單元件無效 //如果是"application/x-www-form-urlencoded"表單,可以設定其中的普通表單元件 request.setCharacterEncoding("utf-8"); //先獲取所接收檔案要儲存的路徑 String path = getServletContext().getRealPath("/imgs"); //檔案上傳需要臨時目錄(如果不指定,那麼該目錄就是tomcat/temp) File tempDiv = new File("E:/a"); if(!tempDiv.exists()){ tempDiv.mkdir(); } DiskFileItemFactory fileFactory = new DiskFileItemFactory(1024*8, tempDiv); //建立用於解析檔案的工廠類,同時設定快取區的大小和位置 //####思路的入口: ServletFileUpload upload = new ServletFileUpload(fileFactory); upload.setFileSizeMax(1024*1024*5);//設定單個檔案上傳最大為5M upload.setSizeMax(1024*1024*8);//所有上傳檔案大小之和的最大值,此處設最多能上傳8M //setSizeMax方法用於設定請求訊息實體內容的最大允許大小,以防止客戶端故意通過上傳特大的檔案來塞滿伺服器端的儲存空間,單位為位元組。 //以下開始解析: //parseRequest是從查詢字串和請求體中獲取引數賦值到paramMap,然後格式化uri,填充Request物件例項 try { List<FileItem> list = upload.parseRequest(request); for(FileItem fi:list){ // isFormField()。isFormField方法用來判斷FileItem物件裡面封裝的資料是一個普通文字表單欄位,還是一個檔案表單欄位。 //如果是普通文字表單欄位,返回一個true否則返回一個false。 //因此可以用該方法判斷是否是普通表單域還是檔案上傳表單域。 if(fi.isFormField()){ //普通表單元件,如:<input type="text" name="name"/> String str = fi.getString("utf-8");//以指定編碼的方式獲取,來解決普通表單元件的中文亂碼問題 //將FileItem物件中儲存的資料流內容以一個字串返回。 System.out.println("普通表單元件:"+str); }else{//檔案元件 String fileName = fi.getName();//獲得上傳檔案的檔名 System.out.println("fileName:"+fileName); //由於上傳的檔案“名字”可能會有中文,而伺服器目錄當中的資源名稱不能夠用中文(帶中文的檔案在瀏覽器中無法訪問的),因此要把它轉換成非中文的檔名(要考慮檔名不能重複) //於是,我們用java自帶的UUID類,自動生成 String uuid = UUID.randomUUID().toString().replace("-", "");//去掉uuid中的'-' String ext = fileName.substring(fileName.lastIndexOf("."));//擷取檔案的副檔名: .* //System.out.println("ext:"+ext); String newFileName = uuid+ext;//本地伺服器儲存的檔名 //System.out.println("newFileName:"+newFileName); //真正的檔案內容在fi.getInputStream() 當中 FileUtils.copyInputStreamToFile(fi.getInputStream(), new File(path+"/"+newFileName));//拷貝的位元組從InputStream原始檔到目的地(file)。 } } } catch (FileUploadException e) { throw new RuntimeException(e); } } }

演示結果:

在這個上傳中,我們並沒有把uuid和檔名聯絡起來,這樣是不好的,必須用資料庫把uuid和其對應的檔名存起來。以後下載的時候還給客戶端一樣的名字,而不是給他uuid的名字。

上傳二個file項

index.jsp:


    <!-- 下面那個=號,代表整個輸出request.getContextPath()的值   -->
    <form action="<%= request.getContextPath() %>/upload" method="post" enctype="multipart/form-data">
        檔案1:<input type="file" name="file"/><br/>
        檔案1的說明:<input type="text" name="desc1"/><br/> 
        檔案2:<input type="file" name="file2"/><br/>
        檔案2的說明:<input type="text" name="desc2"/><br/>
        <input type="submit" value="提交"/>
    </form>

其他的相對前面的都沒改動~

演示結果:

上傳檔案最終版:

index.jsp:

<h2>進一步演示檔案上傳用法</h2>
    <form action="<%= request.getContextPath() %>/upload2" method="post" enctype="multipart/form-data">
        檔案1:<input type="file" name="file"/><br/>
        檔案1的說明:<input type="text" name="desc1"/><br/>
        檔案2:<input type="file" name="file2"/><br/>
        檔案2的說明:<input type="text" name="desc2"/><br/>
        <input type="submit" value="提交"/>
    </form>

web.xml:

 <servlet>
    <servlet-name>UploadServlet2</servlet-name>
    <servlet-class>cn.hncu.servlets.upload.UploadServlet2</servlet-class>
  </servlet>
<servlet-mapping>
    <servlet-name>UploadServlet2</servlet-name>
    <url-pattern>/upload2</url-pattern>
  </servlet-mapping>    

UploadServlet2.java:

package cn.hncu.servlets.upload;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

public class UploadServlet2 extends HttpServlet {

    //防黑1---在位址列直接提交的-我們要防住
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print("不支援GET方式上傳!!!");
    }


    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        final PrintWriter out = response.getWriter();//等會內部類需要用到這個變數,所以定義成final

        //防黑2--非multipart表單提交
        //手動方式
        String type = request.getContentType();
        if(!type.contains("multipart/form-data")){//如果此字串包含 s,則返回 true,否則返回 false 
            out.print("不支援普通表單提交");
            return;
        }

        DiskFileItemFactory fiFactory = new DiskFileItemFactory();
        fiFactory.setSizeThreshold(1024*8);//8k,快取區大小
        File file = new File("d:/a");
        if(!file.exists()){
            file.mkdir();
        }
        fiFactory.setRepository(file);//設定快取區
        /*
         ServletFileUpload類是Apache檔案上傳元件處理檔案上傳的核心高階類(所謂高階就是不需要管底層實現,暴露給使用者的簡單易用的介面)。
         使用其 parseRequest(HttpServletRequest) 方法可以將通過表單中每一個HTML標籤提交的資料封裝成一個FileItem物件,然後以List列表的形式返回。
         */
        ServletFileUpload upload = new ServletFileUpload(fiFactory);
        upload.setHeaderEncoding("utf-8");//用於設定檔名的編碼,相當於:request.setCharacterEncoding("utf-8");
        String path = getServletContext().getRealPath("/imgs");

        //檔案上傳進度功能---設定監聽器

        upload.setProgressListener(new ProgressListener() {
            private int pre=0;

            //引數解析---pBytesRead:已上傳位元組數 pContentLength:上傳的總位元組數 pItems:檔案序號(從1開始的)
            @Override
            public void update(long pBytesRead, long pContentLength, int pItems) {
                double d = pBytesRead*100.0/pContentLength;//計算百分比
                int dd = (int)d;
                if(pre!=dd){//防範輸出一樣的百分比
                    out.print(dd+"%<br/>");
                    pre=dd;
                }
            }
        });



        FileItem fi=null;
        try {
            List<FileItem> list = upload.parseRequest(request);
            for(FileItem fi2:list){
                fi=fi2;//相當於傳指標,同一個物件
                if(fi.isFormField()){//普通表單元件
                    String str = fi.getString("utf-8");
                    System.out.println("普通表單元件提交的內容:"+str);
                }else{//表單中的:file元件

                    //防黑3--在file元件中不選擇檔案
                    if(fi.getSize()==0){
                        continue;
                    }

                    //檔名
                    String fileName = fi.getName();
                    fileName = fileName.substring( fileName.lastIndexOf("\\")+1 );//這裡就是檔名(字尾名也在的)
                    String ext = fileName.substring( fileName.lastIndexOf(".") );// .*  字尾名

                    //檔名不能用中文,必須轉換成ascii碼的格式,而且檔名不能重複(必須保證唯一),因此採用UUID來實現
                    String newFileName = UUID.randomUUID().toString().replace("-", "");//去掉'-'
                    newFileName = newFileName+ext;


                    //打散目錄(因為對於普通的機器,一個資料夾如果儲存的檔案個數超過1000個,效能就會急劇下降!!!)、
                    String dir1 = Integer.toHexString( fileName.hashCode() & 0xf );
                    String dir2 = Integer.toHexString( (fileName.hashCode() & 0xf0)>>4 );//右移四位
                    String dir3 = Integer.toHexString( ( fileName.hashCode() & 0xf00 )>>8);
                    File dir = new File(path+"/"+dir1+"/"+dir2+"/"+dir3);//16*16*16個資料夾
                    if(!dir.exists()){
                        dir.mkdirs();
                    }
                    File f = new File(dir+"/"+newFileName);
                    fi.write(f);
                }
            }
        }catch (FileUploadException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally{
            if(fi!=null){
                fi.delete();//清臨時檔案
            }
        }
    }
}

演示結果:

進行了一個文字型的檔案上傳進度,沒辦法啊,現在還沒學AJax,做不了同步~~理解理解,後面會學到的。

注意看檔案的儲存目錄!!!!(0-f)16進位制的檔名~
我做了三層~

演示下中文路徑的檔案不能顯示的例項:

<img alt="中文路徑不行" src="/myServletDemo3/imgs/圖書1.jpg"/>

先移動這個圖片到這個目錄:

再看瀏覽器的訪問結果:


無法訪問到這個檔案!!!!!!

進度條前臺技術演示:

最後,我們自己來做個假的進度條看看:
其實只是少了aJax技術而已。

index.jsp:

 <a href="progress.jsp">進度條前臺技術演示</a>

propress.jsp:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <script type="text/javascript">
        var tm=0;
        function start(){
            a=0;
            if(tm!=0)
                window.clearInterval(tm);//也要防範一下,否則一直點啟動。會出現很多的定時器。a+的速度會越來越快
            tm = window.setInterval(run, 100);
        }
        //真正開發的時候,應該是在run()方法中利用aJax到後臺讀取當前的進度值,
        //用該進度值對頁面的進度條進行相應重新整理,由於Ajax技術還沒學,這裡就我們自己模擬吧....
        var a=0;
        function run(){
            a+=1;
            if(a>100){
                window.clearInterval(tm);
                return;
            }
            var div=document.getElementById("dataDiv");
            div.style.width = a+"%";//把裡面的div 對應的寬變長百分之一(背景色為紅)
        }
        function stop(){
            window.clearInterval(tm);
        }
        function resume(){
            window.clearInterval(tm);//必須先把前面那個給清了。否則會出現前面那個物件無法訪問到的情況
            tm = window.setInterval(run, 100);
        }
    </script>

  </head>

  <body>
    <h1>進度條前臺技術演示</h1>
    <div style="border:1px solid red;width:400px;height:30px;">
        <div id="dataDiv" style="background:red;width:0%;height:100%;"></div>
    </div>
    <button onclick="start()">啟動</button>
    <button onclick="stop()">停止</button>
    <button onclick="resume()">重新啟動</button>
  </body>
</html>

演示結果:

點啟動按鈕,就是從0%啟動,執行到全部填充完畢(100%)就停止。
點停止按鈕,就停止在當前進度,點重新啟動,就是恢復啟動~從暫停的地方繼續~~