Web---檔案上傳-用apache的工具處理、打散目錄、簡單檔案上傳進度
阿新 • • 發佈:2019-01-25
我們需要先準備好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%)就停止。
點停止按鈕,就停止在當前進度,點重新啟動,就是恢復啟動~從暫停的地方繼續~~