java web專案 使用elfinder 實現檔案管理器

待到楓葉紅成海發表於2019-05-19

  目的在客戶端(瀏覽器)上像操作window系統中的檔案/資料夾一樣,操作伺服器上的某些指定檔案/資料夾

  效果圖:

  

  框架:jsp + springMVC + Tomcat

  前臺使用 elfinder

  這是一個很好用的開源web檔案管理器外掛,用jquery+jquery-ui寫的,在網上一搜文件好像也挺多的,於是準備搬到專案中來(挖坑開始),瞭解過後發現作者附帶的後臺demo是php寫的,大多文件資料也是php的,java的特別少,出了問題也不知道是為什麼,急死個人,前後折騰了兩天才勉強能用了,在這裡記錄一下,以供大家參考,本人菜鳥,如果有什麼理解不對的地方,歡迎各位指正。

  開啟官網,把程式碼dow下來:

  

  開啟壓縮包:把選中的這些檔案拷到專案中:

  

  選中的可能有用,沒選中的肯定沒用(為什麼?因為這沒拷進專案裡他也能跑,而且沒問題...)

  

  後臺使用的是一個大神開源的基於java實現demo  elfinder-2.x-servlet

  這個目前還在持續更新中

  接下來開始配置吧,基礎版:

  Maven專案中新增依賴項

1 <!-- web資料夾管理器jar包 -->
2     <dependency>
3         <groupId>com.github.bluejoe2008</groupId>
4         <artifactId>elfinder-servlet-2</artifactId>
5         <version>1.2</version>
6         <classifier>classes</classifier>
7     </dependency>

  或者直接點選下載 elfinder-servlet-2.jar 包放到lib目錄下

  接下來在servlet.xml中配置需要spring管理的各物件

 1 <!-- find appropriate  command executor for given command-->
 2 <bean id="commandExecutorFactory"
 3     class="cn.bluejoe.elfinder.controller.executor.DefaultCommandExecutorFactory">
 4     <property name="classNamePattern"
 5         value="cn.bluejoe.elfinder.controller.executors.%sCommandExecutor" />
 6     <property name="map">
 7         <map>
 8         <!-- 
 9             <entry key="tree">
10                 <bean class="cn.bluejoe.elfinder.controller.executors.TreeCommandExecutor" />
11             </entry>
12         -->
13         </map>
14     </property>
15 </bean>
16 
17 <!-- FsService is often retrieved from HttpRequest -->
18 <!-- while a static FsService is defined here -->
19 <bean id="fsServiceFactory" class="cn.bluejoe.elfinder.impl.StaticFsServiceFactory">
20     <property name="fsService">
21         <bean class="cn.bluejoe.elfinder.impl.DefaultFsService">
22             <property name="serviceConfig">
23                 <bean class="cn.bluejoe.elfinder.impl.DefaultFsServiceConfig">
24                     <property name="tmbWidth" value="80" />
25                 </bean>
26             </property>
27             <property name="volumeMap">
28                 <!-- two volumes are mounted here -->
29                 <map>
30                     <entry key="A">
31                         <bean class="cn.bluejoe.elfinder.localfs.LocalFsVolume">
32                             <property name="name" value="MyFiles" />
33                             <property name="rootDir" value="/tmp/a" />
34                         </bean>
35                     </entry>
36                     <entry key="B">
37                         <bean class="cn.bluejoe.elfinder.localfs.LocalFsVolume">
38                             <property name="name" value="Shared" />
39                             <property name="rootDir" value="/tmp/b" />
40                         </bean>
41                     </entry>
42                 </map>
43             </property>
44             <property name="securityChecker">
45                 <bean class="cn.bluejoe.elfinder.impl.FsSecurityCheckerChain">
46                     <property name="filterMappings">
47                         <list>
48                             <bean class="cn.bluejoe.elfinder.impl.FsSecurityCheckFilterMapping">
49                                 <property name="pattern" value="A_.*" />
50                                 <property name="checker">
51                                     <bean class="cn.bluejoe.elfinder.impl.FsSecurityCheckForAll">
52                                         <property name="readable" value="true" />
53                                         <property name="writable" value="true" />
54                                     </bean>
55                                 </property>
56                             </bean>
57                             <bean class="cn.bluejoe.elfinder.impl.FsSecurityCheckFilterMapping">
58                                 <property name="pattern" value="B_.*" />
59                                 <property name="checker">
60                                     <bean class="cn.bluejoe.elfinder.impl.FsSecurityCheckForAll">
61                                         <property name="readable" value="true" />
62                                         <property name="writable" value="false" />
63                                     </bean>
64                                 </property>
65                             </bean>
66                         </list>
67                     </property>
68                 </bean>
69             </property>
70         </bean>
71     </property>
72 </bean>

   這裡配置就是伺服器上的資料夾名稱,伺服器上是在你有tomcat所在盤的根目錄下建一個叫tmp的資料夾,但在客戶端(瀏覽器)上顯示的就是你配置的名稱:MyFiles

   

  載入jar包後,為了檢視後臺接收資料的url,需要載入原始檔(我給的jar包壓縮包裡)

  

  我們開啟這個類cn.bluejoe.elfinder.controller.ConnectorController可以看到對映路徑為”connector”

  

  

  這就是前臺請求後臺時的url路徑(先暫時記住)

  接著開始寫前臺頁面(我用的是jsp頁面):

  可以直接拿elfinder那個包裡的elfinder.html改,但他裡面沒有引入js和css,所以還是自己來寫吧

  最好按照下面給出的順序匯入,因為在最開始我沒有注意,導致很多樣式是亂的,響應到了錯誤的地方

  匯入jquery.js,版本稍高的好,因為我發現他的裡面用的是jquery-3.*的版本,這個根據自己的路徑來導  

<script src="${pageContext.request.contextPath}/js/jquery-3.2.1.min.js" type="text/javascript" charset="utf-8"></script>

  匯入jquery-ui.js jquery-ui.css ,接下來的這些檔案的路徑都是根據最開始拷到專案中的elfinder包裡去找

<link href="${pageContext.request.contextPath}/elfinder/jquery/jquery-ui-1.12.0.css" rel="stylesheet" type="text/css" media="screen" charset="utf-8"> 
<script src="${pageContext.request.contextPath}/elfinder/jquery/jquery-ui-1.12.0.js" type="text/javascript" charset="utf-8"></script>

  匯入elfinder.css、theme.css

<link rel="stylesheet" href="${pageContext.request.contextPath}/elfinder/css/elfinder.min.css" type="text/css" media="screen" charset="utf-8">
<link rel="stylesheet" href="${pageContext.request.contextPath}/elfinder/css/theme.css" type="text/css" media="screen" charset="utf-8">

  匯入elfinder.js

<script src="${pageContext.request.contextPath}/elfinder/js/elfinder.min.js" type="text/javascript" charset="utf-8"></script>

  匯入中文語言包elfinder.zh_CN.js,elfinder是支援國際化的,從2.0版本開始可以完美支援中文了,如果這裡不匯入,不配置,預設是英文的

<script src="${pageContext.request.contextPath}/elfinder/js/i18n/elfinder.ru.js" type="text/javascript" charset="utf-8"></script>
<script src="${pageContext.request.contextPath}/elfinder/js/i18n/elfinder.zh_CN.js" type="text/javascript" charset="utf-8"></script>

  在html標籤中宣告容器:

<div id="elfinder" ></div>

  Js程式碼:

<script type="text/javascript" charset="utf-8">
    $(document).ready(function() {
        $('#elfinder').elfinder({
            url : '${pageContext.request.contextPath}/connector',    //這裡的請求地址對應controller中的地址
            lang : 'zh_CN',        //配置預設語言為中文
            height : parseInt(window.screen.availHeight * 0.7)        //配置高度為瀏覽器高度的0.7
        });
    });
</script>

  此時啟動專案應該能看到以下頁面了

  此時一個坑出現了,我傳什麼檔案都提示“未知的命令:null”,google了幾個小時才發現是因數servlet.xml中配置了

<bean id="multipartResolver" class="com.sctbyc.sware.controller.resourceLibrary.filter.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8" />
        <property name="maxUploadSize" value="104857600" />
        <property name="maxInMemorySize" value="2048" />
</bean>

  兩個衝突了,去掉CommonsMultipartResolver就可以,但是專案中其他地方用到的上傳就無法使用了,度娘了很久說衝突可以寫一個代理來解決(不太會),直接把別人的程式碼拿來(好幾百行),發現並沒鳥用,氣死個人,於是又開始瘋狂搜尋...

  最後發現根本不用什麼代理,CommonsMultipartResolver這個類中有一個public boolean isMultipart(HttpServletRequest request)方法,我們繼承這個類,重寫這個isMultipart方法返回true和false就可以達到是否使用這個類來處理上傳了

  此處使用攔截器來判斷其是我們的elfinder的上傳檔案或是其他上傳方式,這裡主要是用請求url的方式來判斷是否為elfinder的請求,分三個類,程式碼如下:

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class MultipartContextFileter implements Filter  {

    FilterConfig config;
    
    @Override
    public void destroy() {
        
    }

    @Override
    public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain chain)
            throws IOException, ServletException {
        boolean isData = false;
        HttpServletRequest req = (HttpServletRequest)srequest;
         
        // 根據web.xml中的配置,判斷當前url是否跳過此過濾器
        String excludeURL = config.getInitParameter("excludeURL");
        if (excludeURL != null && !"".equals(excludeURL)) {
            if (req.getRequestURI().indexOf(excludeURL) != -1) {
                isData = true;
            }
        }
         
        if (isData) {
            String content_type = req.getContentType();
            if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
                MyMultiPartRequest jakarta = new MyMultiPartRequest(req);
                jakarta.isData = true;
                req = jakarta;
            }
        }
         
        chain.doFilter(req, sresponse);
        
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        config = arg0;
        
    }

}
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
 /**
  * 繼承request,對其進行包裝,以儲存更多資訊,用於儲存判斷是否是elfinder的請求,後面執行時可以判斷是否跳過CommonsMultipartResolver的處理
  */ public class MyMultiPartRequest extends HttpServletRequestWrapper { public boolean isData = false; //是否執行自定義的CommonsMultipartResolver public MyMultiPartRequest(HttpServletRequest request) { super(request); } }
public class CommonsMultipartResolver extends org.springframework.web.multipart.commons.CommonsMultipartResolver {

    /**
     * 這裡是處理Multipart http的方法。如果這個返回值為true,那麼Multipart http
     * body就會MyMultipartResolver 消耗掉.如果這裡返回false
     * 那麼就會交給後面的自己寫的處理函式處理例如剛才elfinder請求
     * */
    @Override
    public boolean isMultipart(HttpServletRequest request) {
        if(request instanceof MyMultiPartRequest){
            MyMultiPartRequest trequest = (MyMultiPartRequest)request;
            if(trequest.isData){
                return false;
            }
        }
        return super.isMultipart(request);
    }
}

  然後在web.xml中配置攔截器,使其生效

<filter>
    <filter-name>MultiPartFilter</filter-name>
    <filter-class>com.sctbyc.sware.controller.resourceLibrary.filter.MultipartContextFileter</filter-class>
    <init-param>
        <param-name>excludeURL</param-name>
        <param-value>connector</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>MultiPartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

  好了,這下就可以正常使用了上傳檔案了,好TM開心,趕緊各種建資料夾,上傳檔案

  此時第二個坑出現了(檔案超過2M傳上去就是一個blob檔案,且只有幾十k到1M多不等),第一反應這應該是個不完整的二進位制檔案,但為什麼呢?F12開啟瀏覽器,看了下發現上傳檔案時它一直在不停的發請求,原來是這個前端框架使用的大檔案分段上傳的技術,就是把一個檔案切成很多小塊,一直髮請求,一點點的上傳,而後臺似乎並沒有這樣實現,所以造成了這種情況,相當於多大的檔案,最後都只儲存了最後一次上傳的那一塊,知道了原因,開始查elfinder的文件看看他怎麼說:果然還真有這樣一個配置項:

  他說預設是10M,這尋思也沒有啊,我的檔案超過2M就不行了,於是我就配置了一個這個,再在後面加了兩個0,約等於1G了,再試,還是不行啊,超過2M就截斷了,又開始查文件,以為是自己配置的姿勢沒對,弄了很久,不行,沒辦法,只能看他的elfinder.full.js了,看看是不是這其中有什麼鬼,果然我發現了一個東西

  這裡預設為2M-8K的大小,和我們配置的大小中取一個,但使用的是Math.min,取得是其中小的一個,難怪我們的大了他就不用了,所能我們把他改成Math.max就可以使用我們配置的大小了,媽媽再也不用擔心我給的容量不夠了,注意這裡檢視的是elfinder.full.js(即原版),但我們引入的時候是引入的elfinder.mini.js(壓縮版),所以要去mini.js中修改才有用,(因為mini版沒有格式,不好找,這裡告訴大家一個小技巧,可以Ctrl+F開啟搜尋框,搜尋2097152,也就是上圖裡的數字,一下就找到了)(這裡測試的時候因為本地tomcat給的空間不夠,所報了一個OutMemoryError,記憶體溢位,不過不用擔心,生產環境給的是16個G,隨便他傳)

  還有一個問題就是上傳時有一個選擇目錄,但好像支援得不太好,傳不上去,也不知道怎麼改,所以我索性就在elfinder.js中把這個給遮蔽了,過程如下:

  瀏覽器中檢查這個按鈕,發現他的html程式碼為:

<div class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only elfinder-tabstop elfinder-focus"><span class="ui-button-text">

  所以去elfinder.mini.js中搜尋出來,在他之前加上一個判斷,如果是選擇目錄,就返回一個空:

if(i=='selectFolder')return '<span></span>';

  這裡等於selectFolder是因為在elfinder.zh_CN.js 中可以找到 "選擇目錄"對應的英文就是“selectFolder”

 

  到這裡,基本配置就結束了,從使用上來說幾乎是沒有問題了。

  剩下的就該考慮到部分需要優化的內容了:還記得我們最開始的時候說過,後臺的jar包中給定了請求的url了,但只有一個,這很容易衝突,特別是專案大了過後,更大概率會出現了,所以我們就需要自己來定義url是最好的了,其次是許可權的問題,特別是專案中涉及到一部分人能操作,一部分人只能檢視、下載的問題,這個等下一篇再寫了。。。(拖延一下……^-^)

 

相關文章