導言:在實際web 項目中,我們常常會碰到這種情況,在後臺管理平臺上,後臺管理人員需要上傳一些文件,例如促銷活動圖片以及活動文案文件到服務器上,然後推廣到產品宣傳首頁,完成動態部署產品相關銷售活動的功能。通常,在大流量情況下,我們會考慮把文件存放到單獨的文件服務器上,利用第三方分佈式文件系統完成相關文件服務器的部署工作,例如我個人做過利用FastDFS+Solr+zeroMQ框架完成了從服務器端到客戶端的分佈式文件系統管理系統的搭建工作。那麼在硬件環境相對匱乏的情況下,如何去處理這些文件的存放與查找呢?這就是本文所要描述的。
web文件管理,主要涉及到以下幾個方面:
- 文件的上傳
- 文件存儲的組織方式
- web文件獲取方式
其中,尤其以文件存儲的組織方式爲要。
首先定義一個類,用於存儲網絡文件到本地,建立起合適的文件組織結構,並提供web訪問接口。該類命名爲FileManager.java,首先定義類的構造方式,與類的私有變量等
public class FileManager {
public static final String ERROR = "ERROR";
public static final String NONE = "NONE";
//根目錄
private String basePath;
/*//自定義存儲根路徑
public FileManager(String basePath){
this.basePath = basePath;
}*/
//默認存儲根路徑
public FileManager(){
this.basePath = this.getClass().getResource("/").getPath().split("webapps")[0]+"webapps/file";
}
public String getBasePath(){
return this.basePath;
}
.
.
.
}
這裏講自定義存儲路徑的方式註釋了,而是採用固定的文件夾來存儲文件,此處是指tomcat/webapps/file目錄。需要說明的是,此處的file目錄爲一個空的web工程部署文件夾,如果只是新建一個空的文件夾用於存儲文件,是無法通過鏈接訪問到其中的文件的。
再就提到存儲文件的組織方式了,爲了不使單個文件夾下的文件數目過多,採用文件名的哈希值的後16位構建深度爲4的文件夾結構來存儲各類文件。原則上,此處飽和情況下可創建2^16個子文件夾用於存儲文件。根據文件名構建文件存儲路徑的方法代碼如下:
/**
* 根據hash算法,確定存放目錄
* @param filename
* @return
*/
private String getPath(String filename){
try{
//得到文件名的hashCode的值,取二進制後16位,文件夾深度爲4
int hashcode = filename.hashCode();
String dir_suffix = "/"+(hashcode & 0xf)+"/"+((hashcode & 0xf0) >> 4)+"/"+((hashcode & 0xf00) >> 8)+"/"+((hashcode & 0xf000) >> 12);
//構造新的保存目錄
String dir = basePath + dir_suffix;
//File既可以代表文件也可以代表目錄
File file = new File(dir);
//如果目錄不存在
if (!file.exists()) {
//創建目錄
file.mkdirs();
}
return dir;
}catch (Exception e){
e.printStackTrace();
return basePath+"/error";
}
}
由於FileUpload插件讀取的web文件可獲得一個ImputStream,此處,傳入該ImputStream的輸入流,將文件存儲到上一步構建的子文件夾下:
/**
* 保存文件
* @return 返回訪問鏈接
*/
public String save(String filename, String ext, String operator, InputStream in) throws IOException{
FileOutputStream fos = null;
try{
String path = this.getPath(filename);
fos = new FileOutputStream(path+"/"+filename+"."+ext);
byte [] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer))>0){
fos.write(buffer,0,length);
}
return path+"/"+filename+"."+ext;
}catch (Exception e){
e.printStackTrace();
return ERROR;
}finally {
if(null != in){
in.close();
}
if(null != fos){
fos.close();
}
}
}
同時,我們需要爲存儲在服務器上的文件提供訪問鏈接。如下方法中,根據存儲規則,由文件名計算出文件的訪問路徑,而不是去遍歷文件夾,這種方式便顯得尤爲高效。
/**
* 根據文件名及其擴展名,返回一個文件鏈接
* @param filename
* @param ext
* @return
*/
public String getFileURI(String filename,String ext){
int hashcode = filename.hashCode();
String dir_suffix = "/"+(hashcode & 0xf)+"/"+((hashcode & 0xf0) >> 4)+"/"+((hashcode & 0xf00) >> 8)+"/"+((hashcode & 0xf000) >> 12);
String realPath = this.getPath(filename)+"/"+filename+"."+ext;
File file = new File(realPath);
if(file.exists()){
String domain = SystemUtil.getProperty("domain");
return domain.substring(0,domain.lastIndexOf("/"))+"/file"+dir_suffix+"/"+filename+"."+ext;
}else return NONE;
}
這種根據文件名構建存儲路徑及計算得到外部訪問路徑的方式,要求系統有一套嚴格的文件命名規則。或者,開發者也可以在數據庫不同數據表中記錄下文件名與其對應的文件屬性,當然,比較而言,遵循共同的命名規則,更爲簡潔高效。
另外,在web Controller層,利用fileupload插件獲取前端上傳文件form各輸入控件信息的代碼如下:
@RequestMapping(value = "/file")
public void file (HttpServletRequest request,HttpServletResponse response) throws IOException{
response.setContentType("application/json;charset=utf-8");
try{
//使用Apache文件上傳組件處理文件上傳步驟:
//1、創建一個DiskFileItemFactory工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
//設置工廠的緩衝區的大小,當上傳的文件大小超過緩衝區的大小時,就會生成一個臨時文件存放到指定的臨時目錄當中。
factory.setSizeThreshold(1024*100);//設置緩衝區的大小爲100KB,如果不指定,那麼緩衝區的大小默認是10KB
/*//設置上傳時生成的臨時文件的保存目錄
factory.setRepository(tmpFile);*/
//2、創建一個文件上傳解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//監聽文件上傳進度
upload.setProgressListener(new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int arg2) {
System.out.println("文件大小爲:" + pContentLength + ",當前已處理:" + pBytesRead);
}
});
//解決上傳文件名的中文亂碼
upload.setHeaderEncoding("UTF-8");
//3、判斷提交上來的數據是否是上傳表單的數據
if(!ServletFileUpload.isMultipartContent(request)){
//按照傳統方式獲取數據
return;
}
//設置上傳單個文件的大小的最大值,目前是設置爲1024*1024字節,也就是1MB
upload.setFileSizeMax(1024*1024*10);
//設置上傳文件總量的最大值,最大值=同時上傳的多個文件的大小的最大值的和,目前設置爲10MB
upload.setSizeMax(1024*1024*30);
//4、使用ServletFileUpload解析器解析上傳數據,解析結果返回的是一個List<FileItem>集合,每一個FileItem對應一個Form表單的輸入項
List<FileItem> list = upload.parseRequest(request);
for(FileItem item : list){
//如果fileitem中封裝的是普通輸入項的數據
if(item.isFormField()){
String name = item.getFieldName();
//解決普通輸入項的數據的中文亂碼問題
String value = item.getString("UTF-8");
//value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
}else{//如果fileitem中封裝的是上傳文件
//得到上傳的文件名稱,
String filename = item.getName();
System.out.println(filename);
if(filename==null || filename.trim().equals("")){
continue;
}
//注意:不同的瀏覽器提交的文件名是不一樣的,有些瀏覽器提交上來的文件名是帶有路徑的,如: c:\a\b\1.txt,而有些只是單純的文件名,如:1.txt
//處理獲取到的上傳文件的文件名的路徑部分,只保留文件名部分
filename = filename.substring(filename.lastIndexOf("\\")+1);
//得到上傳文件的擴展名
String fileExtName = filename.substring(filename.lastIndexOf(".")+1);
filename = filename.substring(0,filename.lastIndexOf("."));
//如果需要限制上傳的文件類型,那麼可以通過文件的擴展名來判斷上傳的文件類型是否合法
System.out.println("上傳的文件的擴展名是:"+fileExtName);
//保存文件
FileManager fileManager = new FileManager();
if(FileManager.ERROR.equals(fileManager.save(filename,fileExtName,"song",item.getInputStream()))){
response.getWriter().write(JsonUtil.statusResponse(1,"上傳文件失敗",null));
}else response.getWriter().write(JsonUtil.statusResponse(0,"上傳文件成功",fileManager.getFileURI(filename,fileExtName)));
}
}
}catch (FileUploadBase.FileSizeLimitExceededException e) {
e.printStackTrace();
response.getWriter().write(JsonUtil.statusResponse(1,"單個文件超出最大值!!!",null));
}catch (FileUploadBase.SizeLimitExceededException e) {
e.printStackTrace();
response.getWriter().write(JsonUtil.statusResponse(1,"上傳文件的總的大小超出限制的最大值!!!",null));
}catch (Exception e) {
e.printStackTrace();
response.getWriter().write(JsonUtil.statusResponse(1,"其他異常,上傳失敗!!!",null));
}
return;
}
對應前端測試頁面代碼如下:
<body>
<form action="file" enctype="multipart/form-data"
method="post">
上傳用戶:<input type="text" name="username"><br/>
上傳文件:<input type="file" name="file2"><br/>
<input type="submit" value="提交">
</form>
</body>
其中fileupload插件的使用見上述代碼,具體的參考網上其他資料或相關手冊。
把這一套走完之後,發現其實利用FastDFS+Solr搭建起來的分佈式文件系統的基本原理與此類似,FastDFS也採用多層次文件夾結構來存儲文件,利用Solr根據特定的關鍵詞進行搜索對應文件,而不是直接搜索繁雜的文件名。而在本系統中,這兩種特性的實現都得到了體現。不同的在於FastDFS提供了更完善的數據同步與災難響應等措施,同時實現文件服務器與應用服務器分開,提高應用服務器的響應能力,那就都是外話了。
最後附上參考鏈接:http://www.cnblogs.com/xdp-gacl/p/4200090.html