主頁 > 後端開發 > FastDFS

FastDFS

2022-11-03 06:10:44 後端開發

FastDFS 入門

分布式檔案系統

  • 分布式檔案系統 (Distributed File System) 是一個軟體/軟體服務器,這個軟體可以用來管理檔案,但這個軟體所管理的檔案通常不是在一個服務器節點上,而是在多個服務器節點上,這些服務器節點通過網路相連構成一個龐大的檔案存盤服務器集群,這些服務器都用于存盤檔案資源,通過分布式檔案系統來管理這些服務器上的檔案,

  • 常見的分布式檔案系統有:FastDFS、GFS、HDFS、Lustre 、Ceph 、GridFS 、mogileFS、TFS 等,

分布式檔案系統與傳統檔案系統對比:

image-20220411115654215

image-20220411120607299

傳統方式弊端:

  • 如果用戶數量多,IO 操作比較多,對磁盤訪問壓力很大

  • 如果磁盤發生故障,會造成資料丟失

  • 存盤容量有限

分布式檔案系統優點:

  • 解決單點故障問題,多服務器節點保證檔案讀寫
  • 提供資料備份
  • 提供擴容等

FastDFS 簡介

  • FastDFS 是一個開源的輕量級分布式檔案系統,為互聯網應用量身定做,簡單、靈活、高效,采用 C 語言開發,由阿里巴巴開發并開源,

  • FastDFS 對檔案進行管理,功能包括:檔案存盤、檔案同步、檔案訪問(檔案上傳、檔案下載、檔案洗掉)等,解決了大容量檔案存盤的問題,特別適合以檔案為載體的在線服務,如相冊網站、檔案網站、圖片網站、視頻網站等等,

  • FastDFS 充分考慮了冗余備份、線性擴容等機制,并注重高可用、高性能等指標,使用 FastDFS 很容易搭建一套高性能的檔案服務器集群提供檔案上傳、下載等服務,

  • FastDFS 代碼托管在 github 上:https://github.com/happyfish100/fastdfs

FastDFS 整體架構

  • FastDFS 檔案系統由兩大部分構成,一個是客戶端,一個是服務端

  • 客戶端通常指我們的程式,比如我們的 Java 程式去連接 FastDFS、操作 FastDFS,那我們的 Java 程式就是一個客戶端,FastDFS 提供專有 API 訪問,目前提供了 C、Java 和 PHP 幾種編程語言的 API,用來訪問 FastDFS 檔案系統,

  • 服務端由兩個部分構成:一個是跟蹤器(tracker),一個是存盤節點(storage)

    • 跟蹤器(tracker)主要做調度作業,在記憶體中記錄集群中存盤節點 storage 的狀態資訊,是前端 Client 和后端存盤節點 storage的樞紐,因為相關資訊全部在記憶體中,Tracker server 的性能非常高,一個較大的集群(比如上百個group)中有3臺就足夠了,

    • 存盤節點(storage)用于存盤檔案,包括檔案和檔案屬性(meta data)都保存到存盤服務器磁盤上,完成檔案管理的所有功能:檔案存盤、檔案同步和提供檔案訪問等,

FastDFS 環境搭建

資源準備

資源下載地址(或百度云盤直取):https://github.com/happyfish100

image-20220411172455965

資源介紹:

  • fastdfs-6.08.tar.gz:FastDFS 的安裝包(只能在 Linux 上安裝)
  • fastdfs-client-java-1.28.zip:FastDFS 的 Java 客戶端源代碼
  • fastdfs-nginx-module-1.22.zip:FastDFS-Nginx 的擴展模塊
  • libfastcommon-1.0.56.tar.gz:FastDFS 的公共函式庫(Linux 上安裝)

FastDFS 安裝

FastDFS 沒有 Windows 版本,不能在 Windows 下使用,

FastDFS 需要安裝部署在 Linux 環境下,

  1. 安裝 gcc、libevent、libevent-devel 前置依賴

    yum install gcc libevent libevent-devel -y
    
  2. 將 FastDFS 的安裝包 fastdfs-6.08.tar.gz 和 FastDFS 的公共函式庫 libfastcommon-1.0.56.tar.gz 上傳到 /home/soft/ 下

  3. 解壓縮公共函式庫 tar -zxvf libfastcommon-1.0.56.tar.gz

  4. 進入解壓縮后的公共函式庫根目錄,執行編譯命令:./make.sh

  5. 編譯完成后,執行安裝命令:./make.sh install,以上,公共函式庫安裝完畢

  6. 同樣,按照此步驟,將 FastDFS 先后進行解壓,編譯,安裝!

  7. 至此 FastDFS 安裝完成!

    所有編譯出來的檔案存放在 /usr/bin 目錄下(放在 /usr/bin 下的命令,我們可在任何地方使用,不用切目錄)

    所有組態檔存放在 /etc/fdfs 目錄下,如下:(后面會使用到這些組態檔)

    client.conf
    storage.conf
    storage_ids.conf
    tracker.conf
    
  8. 注意:還需要將 /home/soft/fastdfs-6.08/conf/下的 http.conf 和 mime.types 兩個檔案拷貝到 /etc/fdfs/ 目錄下;

    后期程式會使用到這兩個檔案,避免報錯;

    cp /home/soft/fastdfs-6.08/conf/http.conf /etc/fdfs/
    cp /home/soft/fastdfs-6.08/conf/mime.types /etc/fdfs/
    

FastDFS 配置

  1. 如果你使用的安裝版本中/etc/fdfs下的組態檔有類似 .sample 擴展名,則其為示例組態檔,需要去掉 .sample 后綴才能生效,這點需要注意下,

  2. 將 storage.conf 和 tracker.conf 下載到本機,進行相關配置

  3. 配置 tracker.conf 檔案:(跟蹤器組態檔)

    找到 base_path 引數,配置 tracker 存盤資料的目錄并手動創建:(需要確保該目錄存在,目錄需要我們手動創建好)

    base_path:配置 tracker 存盤資料的目錄

    base_path = /opt/fastdfs/tracker
    
  4. 配置 storage.conf 檔案:(存盤節點組態檔)

    找到 base_path 引數,配置 storage 存盤資料的目錄并手動創建:(需要確保該目錄存在,目錄需要我們手動創建好)

    base_path:配置 storage 存盤資料的目錄

    path_path = /opt/fastdfs/storage
    

    找到 store_path0 引數,配置 storage 的磁盤存放路徑,即真正存放檔案的目錄:(需確保該目錄存在,需我們手動創建好)

    store_path0:真正存放檔案的目錄

    store_path0 = /opt/fastdfs/storage/files
    

    找到 tracker_server 引數,配置當前存盤節點的跟蹤器地址:(只需要配置 IP 即可,埠不要改動)

    tracker_server:注冊當前存盤節點的跟蹤器地址

    tracker_server = 192.168.154.129:22122
    
  5. 將在 Windows 中配置完成并保存的 tracker.conf 和 storage.conf 兩個檔案上傳到 /etc/fdfs目錄下,并覆寫原檔案;

    上傳并覆寫的命令:rz -y

FastDFS 啟動

FastDFS 服務啟動需要啟動兩個腳本:tracker 服務和 storage 服務

命令語法參考:
fdfs_trackerd <config_file> [start | stop | restart]
fdfs_storaged <config_file> [start | stop | restart]
  • 確保需要手動創建的目錄存在的情況下,分別執行下列命令啟動 FastDFS:(不加 start 默認是啟動)
fdfs_trackerd /etc/fdfs/tracker.conf 
fdfs_storaged /etc/fdfs/storage.conf
ps -ef|grep fdfs 查看相關行程,是否啟動成功

FastDFS 重啟

  • 分別執行下列命令重啟 FastDFS:
fdfs_trackerd /etc/fdfs/tracker.conf restart
fdfs_storaged /etc/fdfs/storage.conf restart
ps -ef|grep fdfs 查看相關行程,是否重啟成功

FastDFS 關閉

  • 分別執行下列命令關閉 FastDFS:(其實也可使用 kill 關閉,但為了防止上傳檔案中資料丟失,不建議使用 kill 方式)
fdfs_trackerd /etc/fdfs/tracker.conf stop
fdfs_storaged /etc/fdfs/storage.conf stop
ps -ef|grep fdfs 查看相關行程,是否成功關閉

FastDFS 測驗

此處示例的上傳下載操作其實開發中用不到,主要是感受下 FastDFS 的執行;重點是上傳成功后組名和遠程檔案名的解讀!

測驗說明:測驗檔案上傳和洗掉

測驗前配置修改

  • 需要修改 /etc/fdfs/client.conf 組態檔的兩個引數(自行修改,步驟不再復述;需確保所填的 client 目錄存在

    修改小技巧:直接在該檔案目錄下打開 xftp 傳輸,直接在傳輸頁面打開檔案,修改并保存!

    注意:client.conf 檔案只在測驗的時候才會用到!

    base_path = /opt/fastdfs/client
    tracker_server = 192.168.154.129:22122
    

測驗檔案上傳

測驗命令語法參考:
Usage: fdfs_test <config_file> <operation>
	operation: upload, download, getmeta, setmeta, delete and query_servers
  1. 啟動或重啟 FastDFS 服務:

    fdfs_trackerd /etc/fdfs/tracker.conf restart
    fdfs_storaged /etc/fdfs/storage.conf restart
    ps -ef|grep fdfs 查看相關行程,是否重啟成功
    
  2. 在 /home 下創建一個用于測驗的檔案 vim test.txt

  3. 上傳該檔案,執行:

    fdfs_test /etc/fdfs/client.conf upload /home/test.txt
    
  4. 上傳成功后會顯示一些相關資訊,其中最重要的有如下兩個資訊:【重點!】

    group_name:組名(決定檔案存到哪個機器)

    remote_filename:遠程檔案名(決定檔案存放到哪個磁盤目錄下)

    FastDFS 存盤檔案時會自動將檔案重命名

    該檔案存盤的路徑:/opt/fastdfs/storage/files/data/00/00/wKiagGJUGnWAGvVNAAAAFU1LnYY803.txt

    group_name=group1, remote_filename=M00/00/00/wKiagGJUGnWAGvVNAAAAFU1LnYY803.txt
    

image-20220411203101646

測驗檔案下載

下載命令格式:
Usage: fdfs_test <config_file> download <group_name> <remote_filename>
  1. 在 /home 目錄下執行下列命令:

    fdfs_test /etc/fdfs/client.conf download group1 M00/00/00/wKiagGJUGnWAGvVNAAAAFU1LnYY803.txt
    
  2. 在 /home 當前目錄下看到下載成功的 wKiagGJUGnWAGvVNAAAAFU1LnYY803.txt 檔案

測驗檔案洗掉

洗掉命令格式:
Usage: fdfs_test <config_file> delete <group_name> <remote_filename>
  1. 直接執行:

    fdfs_test /etc/fdfs/client.conf delete group1 M00/00/00/wKiagGJUGnWAGvVNAAAAFU1LnYY803.txt
    
  2. 可在 /opt/fastdfs/storage/files/data/00/00路徑下看到對應的原檔案已經被洗掉

注意

  • 沒有搭建集群默認只有一個組 group1
  • FastDFS 存盤檔案的同時會存盤一個與其對應的屬性檔案(備份檔案也是,一個備份檔案對應一個屬性檔案)
  • 后綴名包含 -m 的為屬性檔案(meta)
  • 在Linux中并沒有磁盤一說,是虛擬的

分布式檔案系統 FastDFS 的 HTTP 訪問

  • 在檔案上傳的時候,上傳成功的資訊中有提示我們可以通過某個路徑去訪問上傳的檔案,但是我們直接訪問這個路徑,卻不可以,那么已經上傳到 FastDFS 檔案系統中的檔案,我們如何在瀏覽器中訪問呢?FastDFS 提供了一個 Nginx 擴展模塊,利用該模塊,我們可以通過 Nginx 訪問已經上傳到 FastDFS 上的檔案,

步驟示例:

1.前期準備

  1. 將 FastDFS-Nginx 的擴展模塊 fastdfs-nginx-module-1.22.zip 上傳到 /home/soft 下;
  2. 解壓:unzip fastdfs-nginx-module-1.22.zip
  3. 將解壓縮的 /home/soft/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf 檔案下載到 Windows 中,后邊會進行配置使用;
  4. 將解壓縮的擴展模塊源代碼的 src 路徑 /home/soft/fastdfs-nginx-module-1.22/src拷貝下來,先存著,后面配置重新安裝的 Nginx 時會用到這個路徑;

2.重裝 Nginx

注意:Nginx 的安裝需要 Linux 安裝相關的幾個庫,否則編譯會出現錯誤;

如果之前沒有安裝過 Nginx,則先執行下列命令安裝!如果之前安裝過 Nginx,下面這相關庫的安裝步驟就可直接跳過!

yum install gcc openssl openssl-devel pcre pcre-devel zlib zlib-devel –y
  1. 接下來重新安裝 Nginx:因為擴展模塊必須在 Nginx 的安裝程序中才能添加,所以我們需要重新安裝一個 nginx,為了和原來已安裝的 Nginx 進行區分,我們把新安裝的 Nginx 取名為 nginx_fdfs(指定 nginx 安裝路徑時進行取名)

  2. 將 Nginx 壓縮包上傳到 /home/soft/ 目錄下,并進行解壓縮;

  3. 進入解壓縮后的 Nginx 目錄下,按照下面的說明檢查路徑,并執行下列配置命令:

    ./configure --prefix=/usr/local/nginx_fdfs --add-module=/home/soft/fastdfs-nginx-module-1.22/src
    	--prefix 是指定nginx安裝路徑
    	--add-module 指定fastDFS的nginx模塊(即擴展模塊)的源代碼路徑(之前已讓拷貝存著了)
    
  4. 執行編譯命令:make

  5. 執行安裝命令make install,完成 Nginx 的重新安裝

3.FastDFS 的 Nginx 訪問配置

  1. 對之前下載下來的擴展模塊 src 下的 mod_fastdfs.conf 檔案進行如下四項配置:(nginx_mod 目錄需要手動創建)

    創建目錄可使用:mkdir -p /opt/fastdfs/nginx_mod命令

    base_path=/opt/fastdfs/nginx_mod
    tracker_server=192.168.154.129:22122
    url_have_group_name = true
    store_path0=/opt/fastdfs/storage/files
    
  2. 使用rz -y命令將配置后的 mod_fastdfs.conf 檔案上傳到 /etc/fdfs目錄下

  3. /usr/local/nginx_fdfs/conf目錄下,使用 sz nginx.conf命令,將 nginx.conf 檔案下載到 Windows 中,進行如下配置:(注意:是在 server 下新建一個 location 進行配置,千萬不要直接在原有的 location 中進行修改!)

    下面正則使用固定,可直接復制!

    #攔截請求路徑中包含 /group[1-9]/M0[0-9] 的請求,用 fastdfs的Nginx 模塊進行轉發
    location ~ /group[1-9]/M0[0-9] {	
         ngx_fastdfs_module;  
    }
    
  4. /usr/local/nginx_fdfs/conf路徑下輸入 rz -y上傳配置好的 nginx.conf 進行原檔案覆寫;

  5. 檢查組態檔是否有語法錯誤后,啟動 Nginx

    注意:檢查語法錯誤這步驟可以省略,這里復習一下

    /usr/local/nginx_fdfs/sbin/nginx -c /usr/local/nginx_fdfs/conf/nginx.conf -t
    /usr/local/nginx_fdfs/sbin/nginx -c /usr/local/nginx_fdfs/conf/nginx.conf
    
  6. 查看是否啟動成功;

    如果 master 和 worker 行程都啟動成功,則啟動成功;

    若上面兩個缺少一個,則有可能是之前的配置出錯!可能是 nginx_mod 目錄沒有創建,或 nginx.conf 中配置資訊有問題!

    ps -ef|grep nginx
    
  7. 啟動成功后,則可通過 Nginx 訪問已經上傳到 FastDFS 上的檔案,

4.瀏覽器訪問測驗

  1. 再進行一次檔案上傳:在 /home/路徑下創建一個檔案,如 t.text,隨便寫進去一些內容;

    上傳到 FastDFS 中,執行:

    fdfs_test /etc/fdfs/client.conf upload t.text 
    
  2. 復制控制臺中回傳的示例檔案地址,進行訪問測驗即可(如果訪問時直接是下載檔案了,而不是顯示檔案內容;解決這個問題,可參考https://blog.csdn.net/qq_21457601/article/details/51853248?utm_source=blogxgwz3;其實問題不大,目的已經達到了,即使是直接下載了檔案,訪問測驗也是成功的)

    http://192.168.154.129/group1/M00/00/00/wKiagGJURUuAMfHuAAAAHUAYZwU38.text
    

訪問流程

訪問流程/擴展模塊執行流程圖:

image-20220411233903328

FastDFS 在普通 Java 專案中開發示例

在實際專案開發,FastDFS 提供的主要功能

  • upload:上傳檔案
  • download:下載檔案
  • delete:洗掉檔案

FastDFS 檔案系統的 Java 客戶端

  • FastDFS 檔案系統 Java 客戶端是指采用 Java 語言撰寫的一套程式,專門用來訪問 fastDFS 檔案系統,其實就是一個 jar 包,
  • 它并沒有添加到 maven 的中央倉庫中,我們要使用,需要手動下載源代碼,編譯到自己的本地 maven 倉庫,【現在中央倉庫好像有了?自己看著選擇吧,可以用自己編譯的,也可以直接使用中央倉庫的
  • 下載地址:https://github.com/happyfish100/fastdfs(其實之前準備資源,已經讓下載過)

將 FastDFS 檔案系統 Java 客戶端編譯到本地 maven 倉庫步驟:

  1. 下載 FastDFS 客戶端源代碼 fastdfs-client-java-1.28.zip,解壓縮;

  2. 進入解壓縮的根目錄,在檔案路徑地址欄輸入 cmd 命令,進入 dos 視窗,輸入 mvn clean install命令,即可將其編譯到自己的本地 maven 倉庫中,在專案中配置其依賴后可直接使用,

    [INFO] Installing C:\Users\linwe\Downloads\fastdfs-client-java-1.28\pom.xml to D:\Java\maven\repository\org\csource\fastdfs-client-java\1.28-SNAPSHOT\fastdfs-client-java-1.28-SNAPSHOT.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    

注意:當在專案 pom 中引入該 maven 依賴時,若無法識別出來,則可以在 IDEA 中設定更新下本地倉庫,

image-20220412091139752

Java 專案中使用 FastDFS(用的少,一般在web專案中用)

需求:在普通 Java 專案中使用 FastDFS 檔案系統實作對檔案的上傳,下載和洗掉操作

步驟示例:

  1. 創建普通 maven java 專案,不需要使用 archetype 原型模板

  2. 在 pom 中添加 FastDFS 客戶端依賴,并重繪 pom(可用自己編譯的,也可在中央倉庫中自己找)

    <dependencies>
        <!--中央倉庫中的FastDFS依賴-->
        <!--<dependency>-->
        <!--    <groupId>com.github.tobato</groupId>-->
        <!--    <artifactId>fastdfs-client</artifactId>-->
        <!--    <version>1.27.2</version>-->
        <!--</dependency>-->
    
        <!--自己手動編譯的本地倉庫中FastDFS依賴-->
        <dependency>
            <groupId>org.csource</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.28-SNAPSHOT</version>
        </dependency>
    </dependencies>
    
  3. 在 resources 下創建一個 fastdfs.conf 檔案,配置 tracker 跟蹤器服務,配置自己的 IP

    tracker_server=192.168.154.129:22122
    
  4. 此處直接創建并將相關方法封裝為工具類,改引數進行測驗呼叫

    public class FastDFSUtil {
    
        public static void main(String[] args) {
            upload();
            // download();
            // delete();
        }
    
        /**
         * 檔案上傳
         */
        public static void upload() {
    
            try {
                //讀取FastDFS組態檔,用于將所有的tracker的地址讀取到記憶體中
                ClientGlobal.init("fastdfs.conf");
                // 使用無參構造方式創建tracker客戶端物件(配合組態檔使用),用于獲取tracker和storage的服務端物件
                TrackerClient tc = new TrackerClient();
    
                // 通過tracker客戶端物件獲取tracker和storage的服務端物件
                TrackerServer ts = tc.getTrackerServer();
                StorageServer ss = tc.getStoreStorage(ts);
    
                // 通過tracker和storage的服務端物件創建Storage客戶端物件,來操作資料
                StorageClient sc = new StorageClient(ts, ss);
                /**
                 * 檔案上傳
                 *  引數1:需上傳檔案的絕對路徑
                 *  引數2:檔案擴展名
                 *  引數3:檔案的屬性檔案(通常不用上傳)
                 *  回傳值:回傳一個String陣列,此資料非常重要,必須妥善管理,建議存入資料庫
                 *      陣列中第一個元素為檔案所在組名
                 *      陣列中第二個元素為檔案所在遠程路徑名
                 */
                String[] result = sc.upload_file("D:/image/love.png", "png", null);
    
                for (String str : result) {
                    System.out.println(str);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (MyException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 檔案下載
         */
        public static void download() {
    
            try {
                //讀取FastDFS組態檔,用于將所有的tracker的地址讀取到記憶體中
                ClientGlobal.init("fastdfs.conf");
                // 使用無參構造方式創建tracker客戶端物件(配合組態檔使用),用于獲取tracker和storage的服務端物件
                TrackerClient tc = new TrackerClient();
    
                // 通過tracker客戶端物件獲取tracker和storage的服務端物件
                TrackerServer ts = tc.getTrackerServer();
                StorageServer ss = tc.getStoreStorage(ts);
    
                // 通過tracker和storage的服務端物件創建Storage客戶端物件,來操作資料
                StorageClient sc = new StorageClient(ts, ss);
                /**
                 * 檔案下載
                 *  引數1:需下載檔案的組名
                 *  引數2:需下載檔案的遠程檔案名
                 *  引數3:需要保存的本地檔案名(絕對路徑+檔案名)
                 *  回傳值:回傳0則下載成功;其他值,則下載失敗
                 */
                String groupName = "group1";
                String remoteFileName = "M00/00/00/wKiagWJU6gyEWTbdAAAAAD_4r6Y944.png";
                String localFileName = "D:/temp/a.png";
                int ret = sc.download_file(groupName, remoteFileName, localFileName);
                System.out.println("download========> " + ret);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (MyException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 檔案洗掉
         */
        p static void delete() {
    
            try {
                //讀取FastDFS組態檔,用于將所有的tracker的地址讀取到記憶體中
                ClientGlobal.init("fastdfs.conf");
                // 使用無參構造方式創建tracker客戶端物件(配合組態檔使用),用于獲取tracker和storage的服務端物件
                TrackerClient tc = new TrackerClient();
    
                // 通過tracker客戶端物件獲取tracker和storage的服務端物件
                TrackerServer ts = tc.getTrackerServer();
                StorageServer ss = tc.getStoreStorage(ts);
    
                // 通過tracker和storage的服務端物件創建Storage客戶端物件,來操作資料
                StorageClient sc = new StorageClient(ts, ss);
                /**
                 * 檔案洗掉
                 *  引數1:需洗掉檔案的組名
                 *  引數2:需洗掉檔案的遠程檔案名
                 *  回傳值:回傳0則洗掉成功;其他值,則洗掉失敗
                 */
                String groupName = "group1";
                String remoteFileName = "M00/00/00/wKiagWJU6gyEWTbdAAAAAD_4r6Y944.png";
                int ret = sc.delete_file(groupName, remoteFileName);
                System.out.println("delete========> " + ret);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (MyException e) {
                e.printStackTrace();
            }
        }
    }
    
  5. 在瀏覽器中輸入訪問地址進行資源訪問測驗的時候注意下地址中資源拼接方式:

    上傳成功回傳String陣列中兩個引數:
    group1
    M00/00/00/wKiagWJU-t-EJfOFAAAAAD_4r6Y931.png
    
    則訪問地址格式為:ip/group1/M00/00/00/wKiagWJU-t-EJfOFAAAAAD_4r6Y931.png
    
    即:
    192.168.154.129/group1/M00/00/00/wKiagWJU-t-EJfOFAAAAAD_4r6Y931.png
    

FastDFS 在 web 專案中開發示例

需求:對專案進行管理,在 WEB 專案中實作對檔案的上傳下載和洗掉操作,

流程分析:將檔案從本地上傳到 tomcat 中,在 tomcat 中拿到檔案流,將檔案流傳到 FastDFS 中,并將上傳回傳的資料存入資料庫中,

步驟:

1.資料庫環境搭建

  1. 創建 fastdfs 資料庫

  2. 新建查詢,執行下列命令,創建 creditor_info 表

    示例表中:只有 id、group_name、remote_file_path、old_file_name、file_size、file_type 這幾個是重要欄位!

    -- ----------------------------
    -- Table structure for creditor_info
    -- ----------------------------
    DROP TABLE IF EXISTS `creditor_info`;
    CREATE TABLE `creditor_info` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
      `real_name` varchar(35) DEFAULT NULL COMMENT '債權借款人姓名',
      `id_card` varchar(18) DEFAULT NULL COMMENT '債權借款人身份證',
      `address` varchar(150) DEFAULT NULL COMMENT '債權借款人地址',
      `sex` int(1) DEFAULT NULL COMMENT '1男2女',
      `phone` varchar(11) DEFAULT NULL COMMENT '債權借款人電話',
      `money` decimal(10,2) DEFAULT NULL COMMENT '債權借款人借款金額',
      `group_name` varchar(10) DEFAULT NULL COMMENT '債權合同所在組',
      `remote_file_path` varchar(150) DEFAULT NULL COMMENT '債權合同所在路徑',
      `old_file_name` varchar(255) DEFAULT NULL COMMENT '檔案上傳前的名字,用于下載檔案時指定默認檔案名使用',
      `file_size` bigint(20) DEFAULT NULL COMMENT '檔案大小,用于下載檔案啊時提供下載進度',
      `file_type` varchar(255) DEFAULT NULL COMMENT '檔案型別,用于顯示與檔案型別相同的圖示,作用不是很大',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
    
    -- ----------------------------
    -- Records of creditor_info
    -- ----------------------------
    INSERT INTO `creditor_info` VALUES ('1', '張三', '123412341', '北京', '1', '23412341', '12433.00', '1', '1', '1', '1', '1');
    INSERT INTO `creditor_info` VALUES ('2', '李四', '132414123', '上海', '0', '12344312', '21343.00', null, null, null, null, null);
    
  3. 此處用的是 Linux 中的 mysql:用戶名 root,密碼 123

2.Web 專案環境搭建

  1. 創建 Spring Boot 專案,選擇 Spring Boot DevTools(熱部署工具)、Spring Web、Thymeleaf、MySQL、MyBatis 依賴;

    注意:如果組態檔中使用的是低版本的 mysql 驅動配置,pom 中的 mysql 需要添加下低版本的版本號對應;

  2. 配置 application.properties

    # 配置資料庫連接資訊 注意mysql新舊版本配置不同(如果用的低版本的驅動,pom里需要將mysql也指定下使用低版本的版本號)
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://192.168.154.129:3306/fastdfs?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    spring.datasource.username=root
    spring.datasource.password=123
    
    # 開發階段,關閉thymeleaf的快取,不然無法看到實時頁面
    spring.thymeleaf.cache=false
    # 去掉html5的語法驗證(thymeleaf對html的標簽約束非常嚴格,所有的標簽必須有開有閉,比如<br></br>或者<br/>是可以的,但是<br>會報錯,
    # 配置spring.thymeleaf.mode=LEGACYHTML5 目的就是為了解決這個問題,可以使頁面松校驗,)
    spring.thymeleaf.mode=LEGACYHTML5
    
    #配置mapper檔案路徑
    mybatis.mapper-locations=classpath:mapper/*.xml
    #開啟日志
    #mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    #檔案上傳大小參考配置
    #設定SpringMVC允許上傳的單個檔案大小,默認為1MB
    #spring.servlet.multipart.max-file-size=1MB
    #設定SpringMVC表單請求中允許上傳檔案的總大小,默認為10MB
    #spring.servlet.multipart.max-request-size=10MB
    
  3. 在 templates 下創建 creditors.html 測驗頁面

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        1111111111111111111
    </body>
    </html>
    
  4. 創建控制器類

    @Controller
    public class CreditorInfoController {
    
        @RequestMapping("/")
        public String creditors() {
            return "creditors";
        }
    }
    
  5. 運行主啟動類,訪問測驗 localhost:8080/

3.使用mybatis生成器(逆向工程生成代碼)

PS:使用 mybatis 自動代碼生成器,可自動生成 model 物體類, dao 介面以及 mapper 映射檔案,

此處使用 mapper 檔案和 dao 介面分開管理的方式,

  1. 在 pom 的 plugins 標簽下添加 mybatis 自動代碼生成插件

    <!--mybatis自動代碼生成插件-->
    <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.6</version>
        <configuration>
            <!--組態檔的位置:在專案的根目錄下,和src平級-->
            <configurationFile>GeneratorMapper.xml</configurationFile>
            <verbose>true</verbose>
            <overwrite>true</overwrite>
        </configuration>
    </plugin>
    
  2. 在 pom 的 build 標簽下添加資源插件,處理資源檔案

    <!--   處理資源檔案     -->
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/**.*</include>
            </includes>
        </resource>
    </resources>
    
  3. 在專案根目錄下創建 GeneratorMapper.xml檔案,其與 src 平級;將下列內容拷貝到檔案中;依照注釋修改配置,修改完后,點開右側 maven 快捷視窗,使用當前專案下 plugins 下 mybatis-genetate 下的 mybatis 自動代碼生成插件進行生成,

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
    
        <!-- 指定連接資料庫的JDBC驅動包所在位置,指定到你本機的完整路徑 -->
        <classPathEntry location="D:\Java\tools\mysql-connector-java-8.0.28.jar"/>
    
        <!-- 配置table表資訊內容體,targetRuntime指定采用MyBatis3的版本 -->
        <context id="tables" targetRuntime="MyBatis3">
    
            <!-- 抑制生成注釋,由于生成的注釋都是英文的,可以不讓它生成 -->
            <commentGenerator>
                <property name="suppressAllComments" value="https://www.cnblogs.com/luisblog/p/true" />
            </commentGenerator>
    
            <!-- 配置資料庫連接資訊 -->
            <jdbcConnection driver
                            connectionURL="jdbc:mysql://192.168.154.129:3306/fastdfs?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8"
                            userId="root"
                            password="123">
                <!--MySQL 不支持 schema 或者 catalog 所以需要添加這個-->
                <!--設定原因參考:https://blog.csdn.net/qq_40233736/article/details/83314596-->
                <property name="nullCatalogMeansCurrent" value="https://www.cnblogs.com/luisblog/p/true" />
            </jdbcConnection>
    
            <!-- 生成model類,targetPackage指定model類的包名, targetProject指定生成的model放在eclipse的哪個工程下面-->
            <javaModelGenerator targetPackage="com.luis.model"
                                targetProject="D:\1a-Projects\test-projects\fastdfs\springboot-web-fastdfs\src\main\java">
                <property name="enableSubPackages" value="https://www.cnblogs.com/luisblog/p/false" />
                <property name="trimStrings" value="https://www.cnblogs.com/luisblog/p/false" />
            </javaModelGenerator>
    
            <!-- 生成MyBatis的Mapper.xml檔案,targetPackage指定mapper.xml檔案的包名, targetProject指定生成的mapper.xml放在eclipse的哪個工程下面 -->
            <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
                <property name="enableSubPackages" value="https://www.cnblogs.com/luisblog/p/false" />
            </sqlMapGenerator>
    
            <!-- 生成MyBatis的Mapper介面類檔案,targetPackage指定Mapper介面類的包名, targetProject指定生成的Mapper介面放在eclipse的哪個工程下面 -->
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.luis.dao" targetProject="src/main/java">
                <property name="enableSubPackages" value="https://www.cnblogs.com/luisblog/p/false" />
            </javaClientGenerator>
    
            <!-- 資料庫表名及對應的Java模型類名 -->
            <table tableName="creditor_info" domainObjectName="CreditorInfo"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
            <table tableName="b_bid_info" domainObjectName="BidInfo"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="b_income_record" domainObjectName="IncomeRecord"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="b_recharge_record" domainObjectName="RechargeRecord"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="u_user" domainObjectName="User"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
    
    
            <table tableName="u_finance_account" domainObjectName="FinanceAccount"
                   enableCountByExample="false"
                   enableUpdateByExample="false"
                   enableDeleteByExample="false"
                   enableSelectByExample="false"
                   selectByExampleQueryId="false"/>
        </context>
    
    </generatorConfiguration>
    
  4. application.properties 中添加 mybatis 相關配置

    #配置mapper檔案路徑
    mybatis.mapper-locations=classpath:mapper/*.xml
    #開啟日志
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
  5. 在主啟動類上添加包掃描注解@MapperScan,掃描 dao 所在的包(防止后面忘記了,先加上)

    作用:掃描 dao 下的所有 mapper 類作為 Mapper 映射檔案,

    @SpringBootApplication
    @MapperScan(basePackages = "com.luis.dao")
    public class SpringbootWebFastdfsApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringbootWebFastdfsApplication.class, args);
        }
    }
    

4.顯示資料串列

  1. 控制器類補充查詢功能

    @Controller
    public class CreditorInfoController {
    
        @Resource
        private CreditorInfoServer creditorInfoServer;
    
        @RequestMapping("/")
        public String creditors(Model model) {
            List<CreditorInfo> creditorInfoList = creditorInfoServer.queryAll();
            model.addAttribute("creditorInfoList", creditorInfoList);
            return "creditors";
        }
    }
    
  2. 補充 service 介面和實作類

    public interface CreditorInfoServer {
        List<CreditorInfo> queryAll();
    }
    
    @Service
    public class CreditorInfoServiceImpl implements CreditorInfoServer {
    
        @Resource
        private CreditorInfoMapper creditorInfoMapper;
    
        @Override
        public List<CreditorInfo> queryAll() {
            List<CreditorInfo> list = creditorInfoMapper.selectAll();
            return list;
        }
    }
    
  3. dao 中添加全查方法

    public interface CreditorInfoMapper {
        int deleteByPrimaryKey(Integer id);
    
        int insert(CreditorInfo record);
    
        int insertSelective(CreditorInfo record);
    
        CreditorInfo selectByPrimaryKey(Integer id);
    
        int updateByPrimaryKeySelective(CreditorInfo record);
    
        int updateByPrimaryKey(CreditorInfo record);
        
        // 新增的全查方法
        List<CreditorInfo> selectAll();
    }
    
  4. mapper 中補充全查的 SQL

    <select id="selectAll" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from creditor_info
    </select>
    
  5. 完善顯示資料的頁面

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>test</title>
    </head>
    <body>
        <table>
            <tr>
                <th>序號</th>
                <th>姓名</th>
                <th>性別</th>
                <th>電話</th>
                <th>地址</th>
                <th></th>
            </tr>
            <tr th:each="creditorInfo:${creditorInfoList}">
                <td th:text="${creditorInfo.id}">序號</td>
                <td th:text="${creditorInfo.realName}">姓名</td>
                <td th:text="${creditorInfo.sex == 1?'男':'女'}">性別</td>
                <td th:text="${creditorInfo.phone}">電話</th>
                <td th:text="${creditorInfo.address}">地址</td>
                <td>
                    <span th:if="${creditorInfo.remoteFilePath == null || creditorInfo.remoteFilePath == ''}">
                        <a th:href="https://www.cnblogs.com/luisblog/p/@{|/upload/${creditorInfo.id}|}">上傳</a>
                    </span>
                    <span th:if="${creditorInfo.remoteFilePath != null && creditorInfo.remoteFilePath != ''}">
                        <a th:href="https://www.cnblogs.com/luisblog/p/@{|/download|}">下載</a>
                        <a th:href="https://www.cnblogs.com/luisblog/p/@{|/delete|}">洗掉</a>
                    </span>
                </td>
            </tr>
        </table>
    </body>
    </html>
    
  6. 運行主啟動類,測驗

image-20220412174502775

5.Web 中操作 FastDFS 完成上傳

6.Web 中操作 FastDFS 完成下載

7.Web 中操作 FastDFS 完成洗掉

由于操作不好記錄,以下相關實作提供相關實作參考:

相關頁面參考:

creditors.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>creditors.html</title>
</head>
<body>
    <table>
        <tr>
            <th>序號</th>
            <th>姓名</th>
            <th>性別</th>
            <th>電話</th>
            <th>地址</th>
            <th></th>
        </tr>
        <tr th:each="creditorInfo:${creditorInfoList}">
            <td th:text="${creditorInfo.id}">序號</td>
            <td th:text="${creditorInfo.realName}">姓名</td>
            <td th:text="${creditorInfo.sex == 1?'男':'女'}">性別</td>
            <td th:text="${creditorInfo.phone}">電話</th>
            <td th:text="${creditorInfo.address}">地址</td>
            <td>
                <span th:if="${creditorInfo.remoteFilePath == null || creditorInfo.remoteFilePath == ''}">
                    <a th:href="https://www.cnblogs.com/luisblog/p/@{|/upload/${creditorInfo.id}|}">上傳</a>
                </span>
                <span th:if="${creditorInfo.remoteFilePath != null && creditorInfo.remoteFilePath != ''}">
                    <a th:href="https://www.cnblogs.com/luisblog/p/@{|/download/${creditorInfo.id}|}">下載</a>
                    <a th:href="https://www.cnblogs.com/luisblog/p/@{|/delete/${creditorInfo.id}|}">洗掉</a>
                </span>
            </td>
        </tr>
    </table>
</body>
</html>

upload.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>upload.html</title>
</head>
<body>
    <!--  上傳檔案的表單,method必須為post,并且必須指定enctype屬性為multipart/form-data  -->
    <!--  enctype="multipart/form-data" 簡單說明:讓表單以二進制的形式傳遞資料,支持流檔案型別  -->
    <form th:action="@{|/upload|}" target="curPage" method="post" enctype="multipart/form-data">
        姓名:<span th:text="${creditorInfo.realName}"></span><br/>
        性別:<span th:text="${creditorInfo.sex==1?'男':'女'}"></span><br/>
        電話:<span th:text="${creditorInfo.phone}"></span><br/>
        地址:<span th:text="${creditorInfo.address}"></span><br/>
        檔案:<input type="file" name="myFile" /><br/>
        <input type="hidden" name="id" th:value="https://www.cnblogs.com/luisblog/p/${creditorInfo.id}" /><br/>
        <input type="submit" value="https://www.cnblogs.com/luisblog/p/上傳檔案"><br/>
    </form>

    <!--  嵌入式框架框架  -->
    <iframe name="curPage" style="display: none"></iframe>

</body>
</html>

success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <script type="text/javascript">
      if (confirm("[[${msg}]]")) {
        window.top.location.href = "https://www.cnblogs.com/luisblog/p/[[${url}]]";
      }
  </script>
</head>
<body>

</body>
</html>

工具類參考:

public class FastDFSUtil {

    private FastDFSUtil() {
    }

    /**
     * 檔案上傳
     *
     * @param fileBuff    上傳檔案的位元組陣列
     * @param fileExtName 上傳檔案的擴展名
     * @return String陣列
     * 陣列中第一個元素為檔案所在組名
     * 陣列中第二個元素為檔案所在遠程路徑名
     */
    public static String[] upload(byte[] fileBuff, String fileExtName) {

        try {
            //讀取FastDFS組態檔,用于將所有的tracker的地址讀取到記憶體中
            ClientGlobal.init("fastdfs.conf");
            // 使用無參構造方式創建tracker客戶端物件(配合組態檔使用),用于獲取tracker和storage的服務端物件
            TrackerClient tc = new TrackerClient();

            // 通過tracker客戶端物件獲取tracker和storage的服務端物件
            TrackerServer ts = tc.getTrackerServer();
            StorageServer ss = tc.getStoreStorage(ts);

            // 通過tracker和storage的服務端物件創建Storage客戶端物件,來操作資料
            StorageClient sc = new StorageClient(ts, ss);
            /**
             * 檔案上傳
             *  引數1:需上傳檔案的位元組陣列
             *  引數2:檔案擴展名
             *  引數3:檔案的屬性檔案(通常不用上傳)
             *  回傳值:回傳一個String陣列,此資料非常重要,必須妥善管理,建議存入資料庫
             *      陣列中第一個元素為檔案所在組名
             *      陣列中第二個元素為檔案所在遠程路徑名
             */
            return sc.upload_file(fileBuff, fileExtName, null);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 檔案下載
     *
     * @param groupName      需下載檔案的組名
     * @param remoteFileName 需下載檔案的遠程檔案名
     * @return byte[] 需下載檔案的位元組陣列
     */
    public static byte[] download(String groupName, String remoteFileName) {

        try {
            //讀取FastDFS組態檔,用于將所有的tracker的地址讀取到記憶體中
            ClientGlobal.init("fastdfs.conf");
            // 使用無參構造方式創建tracker客戶端物件(配合組態檔使用),用于獲取tracker和storage的服務端物件
            TrackerClient tc = new TrackerClient();

            // 通過tracker客戶端物件獲取tracker和storage的服務端物件
            TrackerServer ts = tc.getTrackerServer();
            StorageServer ss = tc.getStoreStorage(ts);

            // 通過tracker和storage的服務端物件創建Storage客戶端物件,來操作資料
            StorageClient sc = new StorageClient(ts, ss);

            return sc.download_file(groupName, remoteFileName);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 檔案洗掉
     * @param groupName 需洗掉檔案的組名
     * @param remoteFileName 需洗掉檔案的遠程檔案名
     */
    public static void delete(String groupName, String remoteFileName) {

        try {
            //讀取FastDFS組態檔,用于將所有的tracker的地址讀取到記憶體中
            ClientGlobal.init("fastdfs.conf");
            // 使用無參構造方式創建tracker客戶端物件(配合組態檔使用),用于獲取tracker和storage的服務端物件
            TrackerClient tc = new TrackerClient();

            // 通過tracker客戶端物件獲取tracker和storage的服務端物件
            TrackerServer ts = tc.getTrackerServer();
            StorageServer ss = tc.getStoreStorage(ts);

            // 通過tracker和storage的服務端物件創建Storage客戶端物件,來操作資料
            StorageClient sc = new StorageClient(ts, ss);

            sc.delete_file(groupName, remoteFileName);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}

控制器類參考:

@Controller
public class CreditorInfoController {

    @Resource
    private CreditorInfoServer creditorInfoServer;

    @RequestMapping("/")
    public String creditors(Model model) {
        List<CreditorInfo> creditorInfoList = creditorInfoServer.queryAll();
        model.addAttribute("creditorInfoList", creditorInfoList);
        return "creditors";
    }

    @GetMapping("/upload/{id}")
    public String toUpload(@PathVariable Integer id, Model model) {
        CreditorInfo creditorInfo = creditorInfoServer.findById(id);
        model.addAttribute("creditorInfo", creditorInfo);
        return "upload";
    }

    /**
     * 檔案上傳
     *      引數MultipartFile:為Spring提供的一個類,專門用于封裝請求中的檔案資料,
     *      其屬性名必須與表單中檔案域的名字完全相同!否則無法獲取檔案資料!
     */
    @PostMapping("/upload")
    public String upload(Integer id, MultipartFile myFile, Model model) throws IOException {
        // System.out.println(myFile.getBytes()); // 獲取檔案對應位元組陣列
        // System.out.println(myFile.getContentType()); // 獲取檔案型別
        // System.out.println(myFile.getInputStream()); // 獲取檔案對應的輸入流
        // System.out.println(myFile.getName()); // 獲取表單元素名
        // System.out.println(myFile.getOriginalFilename()); // 獲取文件名
        // System.out.println(myFile.getSize()); // 獲取檔案大小
        // System.out.println(myFile.isEmpty()); //判斷檔案是否為空;檔案沒有上傳或檔案大小為0,這個值都是true

        // 獲取上傳檔案對應的位元組陣列
        byte[] fileBuff = myFile.getBytes();
        // 獲取上傳檔案的檔案名
        String fileName = myFile.getOriginalFilename();
        // 獲取上傳檔案的擴展名
        // #TODO 可能會出現問題,因為有的檔案有可能沒有擴展名,所以必要時需要做邏輯控制
        String fileExtName = fileName.substring(fileName.lastIndexOf(".") + 1);
        Long fileSize = myFile.getSize(); // 檔案大小
        String fileType = myFile.getContentType(); // 檔案型別

        // ================進行檔案上傳========上傳到FastDFS檔案系統中=============
        String[] result = FastDFSUtil.upload(fileBuff, fileExtName);

        CreditorInfo creditorInfo = new CreditorInfo();
        creditorInfo.setId(id); // id
        creditorInfo.setGroupName(result[0]); // 組名
        creditorInfo.setRemoteFilePath(result[1]); // 遠程檔案地址
        creditorInfo.setOldFileName(fileName); // 檔案名
        creditorInfo.setFileSize(fileSize); // 檔案大小
        creditorInfo.setFileType(fileType); // 檔案型別

        // ==========更新資料庫資訊==========
        creditorInfoServer.updateFileInfo(creditorInfo);
        model.addAttribute("msg", "上傳成功,是否回傳串列頁面?");
        model.addAttribute("url", "/");
        return "success";
    }

    /**
     * 檔案下載
     * @param id 需要下載的檔案主鍵
     * @return ResponseEntity 表示一個回應的物體,這個類是Spring提供的一個類,是Spring回應資料時的一個物件,
     *          這個物件有包含著回應時的編碼如404 202 等,以及回應的頭檔案資訊,以及回應時的具體資料;
     *          這個物件可以是一段html代碼,也可是是一段JS,也可以是一段普通字串,也可以是一個檔案的流
     */
    @RequestMapping("/download/{id}")
    public ResponseEntity<byte[]> download(@PathVariable Integer id) {

        // 獲取回應資料(即下載后成功回傳的位元組陣列)
        CreditorInfo info = creditorInfoServer.findById(id);
        String groupName = info.getGroupName();
        String remoteFilePath = info.getRemoteFilePath();
        byte[] buffFile = FastDFSUtil.download(groupName, remoteFilePath);

        // 設定回應時的頭檔案資訊
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 設定回應型別為檔案型別
        headers.setContentLength(info.getFileSize()); // 設定回應時的檔案大小,用于提供下載進度顯示使用

        // 設定下載時的默認檔案名
        headers.setContentDispositionFormData("attachment", info.getOldFileName());
        /**
         * 創建回應物體物件,Spring會將這個物件回傳給瀏覽器,作為回應資料;(我們需要填充資料)
         *      引數1:回應時的具體資料
         *      引數2:回應時的頭檔案資訊
         *      引數3:回應時的狀態碼
         */
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(buffFile, headers, HttpStatus.OK);
        return responseEntity;
    }

    /**
     * 檔案洗掉
     *      檔案洗掉操作選擇在業務層進行,因為需要先查資料,才能進行洗掉,減少方法呼叫,
     * @param id
     * @return
     */
    @RequestMapping("/delete/{id}")
    public String delete(@PathVariable Integer id) {
        creditorInfoServer.deleteFileById(id);
        return "redirect:/";
    }
}

service 介面實作類參考:

@Service
public class CreditorInfoServiceImpl implements CreditorInfoServer {

    @Resource
    private CreditorInfoMapper creditorInfoMapper;

    @Override
    public List<CreditorInfo> queryAll() {
        List<CreditorInfo> list = creditorInfoMapper.selectAll();
        return list;
    }

    @Override
    public CreditorInfo findById(Integer id) {
        CreditorInfo creditorInfo = creditorInfoMapper.selectByPrimaryKey(id);
        return creditorInfo;
    }

    @Override
    public int updateFileInfo(CreditorInfo creditorInfo) {
        int ret = creditorInfoMapper.updateByPrimaryKeySelective(creditorInfo);
        return ret;
    }

    @Override
    public void deleteFileById(Integer id) {
        CreditorInfo creditorInfo = creditorInfoMapper.selectByPrimaryKey(id);
        String groupName = creditorInfo.getGroupName();
        String remoteFilePath = creditorInfo.getRemoteFilePath();

        // 先洗掉遠程檔案
        FastDFSUtil.delete(groupName, remoteFilePath);

        // 再修改資料庫檔案資料
        creditorInfo.setGroupName("");
        creditorInfo.setRemoteFilePath("");
        creditorInfo.setOldFileName("");
        creditorInfo.setFileSize(0L);
        creditorInfo.setFileType("");
        creditorInfoMapper.updateByPrimaryKeySelective(creditorInfo);
    }
}

gitee 上完整示例專案參考:

Web 中操作 FastDFS 完成上傳,下載,洗掉相關步驟,參考下列完整的示例專案:

https://gitee.com/lw2gitee/springboot-web-fastdfs

可克隆下來,在本地查看實作流程以及步驟,

FastDFS 分布式檔案系統集群

其實之前做的集群都算是偽集群,因為之前都是在一臺機器上搭建的集群服務;

下面示例的 FastDFS 分布式檔案系統集群是在七個虛擬機中進行搭建,相當于七臺機器,是真正的集群架構,

架構圖

image-20220413105559831

  • 同一個組中資料相同,機器之間有資料備份,建議同一個組中所有機器性能和配置要相同
  • 不同組中存的資料不同
  • 如果有4臺機器存盤資料,每個機器有1G存盤空間,那么我們檔案的存盤上限是2G(因為每臺機器對應一個備份機器)

環境搭建步驟

PS:建議如果實際操作,不要直接按照下面步驟來!其中有很多細節需要注意,建議跟著視頻操作!

視頻鏈接:https://www.bilibili.com/video/BV1ta4y1v7Kw?p=25

FastDFS分布式檔案系統集群環境搭建-操作步驟手冊

搭建一個FastDFS分布式檔案系統集群,推薦至少部署6個服務器節點;
================================搭建FastDFS的集群==============================
第一步:安裝6個迷你版的Linux,迷你版Linux沒有圖形界面,占用磁盤及資源小,企業里面使用的Linux都是沒有圖形界面的Linux;

第二步:由于迷你版Linux缺少一些常用的工具庫,操作起來不方便,推薦安裝如下的工具庫:(不要分別安裝,一次全安裝)
1、安裝lrzsz, yum install lrzsz -y
2、安裝wget, yum install wget -y
4、安裝vim, yum install vim -y
5、安裝unzip,yum install unzip -y
6、安裝ifconfig,yum install net-tools -y

一次性全部安裝:    
yum install lrzsz wget vim unzip net-tools -y

7、安裝nginx及fastdfs需要的庫依賴:
yum install gcc perl openssl openssl-devel pcre pcre-devel zlib zlib-devel libevent libevent-devel -y

第三步 安裝fastdfs 
   1、 上傳fastdfs的安裝包和libfastcommon的安裝包
   2、 解壓libfastcommon 安裝libfastcommon
   3、 解壓fastdfs 安裝fastdfs
   4、 拷貝fastdfs目錄中的http.conf和mime.types到/etc/fdfs 目錄中
注:6臺機器全部執行這些操作


第四步:部署兩個tracker server服務器,需要做的作業:

    修改兩個tracker服務器的組態檔:
    tracker.conf: 修改一個地方:
    base_path=/opt/fastdfs/tracker   #設定tracker的資料檔案和日志目錄(需預先創建)
    啟動tracker服務器 fdfs_trackerd /etc/fdfs/tracker.conf


第五步 修改兩個組中的4臺storage中storage.conf檔案
    第一組group1的第一個storage server(修改storage.conf組態檔):
    group_name=group1   #組名,根據實際情況修改,值為 group1 或 group2
    base_path=/opt/fastdfs/storage   #設定storage的日志目錄(需預先創建)
    store_path0=/opt/fastdfs/storage/files    #存盤路徑
    tracker_server=192.168.171.135:22122  #tracker服務器的IP地址以及埠號
    tracker_server=192.168.171.136:22122

    第二組group2的第一個storage server(修改storage.conf組態檔):
    group_name=group2   #組名,根據實際情況修改,值為 group1 或 group2
    base_path=/opt/fastdfs/storage   #設定storage的日志目錄(需預先創建)
    store_path0=/opt/fastdfs/storage/files    #存盤路徑
    tracker_server=192.168.171.135:22122  #tracker服務器的IP地址以及埠號
    tracker_server=192.168.171.136:22122
   
    啟動storage服務器
    使用之前的Java代碼測驗FastDFS的6臺機器是否可以上傳檔案
注意:FastDFS默認是帶有負載均衡策略的可以在tracker的2臺機器中修改tracker.conf檔案
    store_lookup=1

    0 隨機存放策略
    1 指定組
    2 選擇磁盤空間的優先存放 默認值

    修改后重啟服務
    fdfs_trackerd /etc/fdfs/tracker.conf restart


======================使用Nginx進行負載均衡==============================


第六步 安裝 nginx ,使用nginx 對fastdfs 進行負載均衡 
    上傳 nginx-1.12.2.tar.gz以及 nginx的fastdfs擴展模塊安裝包fastdfs-nginx-module-master.zip
    添加nginx的安裝依賴
       yum install gcc openssl openssl-devel pcre pcre-devel zlib zlib-devel -y
    解壓nginx
       tar -zxvf  nginx-1.12.2.tar.gz
    解壓fastdfs擴展模塊
       unzip fastdfs-nginx-module-master.zip
    配置nginx的安裝資訊
       2臺tracker服務器的配置資訊(不需要fastdfs模塊)
         ./configure --prefix=/usr/local/nginx_fdfs
       4臺storage服務器其的配置資訊(需要使用fastdfs模塊)
         ./configure --prefix=/usr/local/nginx_fdfs --add-module=/root/fastdfs-nginx-module-master/src
    編譯并安裝nginx
       ./make
       ./make install

    4臺storage的服務器需要拷貝mod_fastdfs檔案
    將/root/fastdfs-nginx-module-master/src目錄下的mod_fastdfs.conf檔案拷貝到 /etc/fdfs/目錄下,這樣才能正常啟動Nginx;



第七步 配置tracker 的兩臺機器的nginx
    進入安裝目錄
    cd /usr/local/nginx_fdfs

    添加一個location 對請求進行攔截,配置一個正則規則 攔截fastdfs的檔案路徑, 并將請求轉發到其余的4臺storage服務器(修改 conf目錄下nginx.conf 檔案)
    #nginx攔截請求路徑:
    location ~ /group[1-9]/M0[0-9] {   
        proxy_pass http://fastdfs_group_server; 
    }



    添加一個upstream 執行服務的IP為 另外的4臺stroage 的地址
    #部署配置nginx負載均衡:
    upstream fastdfs_group_server {  
        server 192.168.171.137:80;  
        server 192.168.171.138:80;
        server 192.168.171.139:80;  
        server 192.168.171.140:80;  
    }


第八步 配置另外4臺storage的nginx添加http訪問的請求路徑攔截
    進入安裝目錄
    cd /usr/local/nginx_fdfs
    添加一個location 對請求進行攔截,配置一個正則規則 攔截fastdfs的檔案路徑,使用fastdfs的nginx模塊轉發請求(修改 conf目錄下nginx.conf 檔案)
    #nginx攔截請求路徑:
    location ~ /group[1-9]/M0[0-9] {   
        ngx_fastdfs_module;
    }


第九步 分別修改4臺storage服務器的mod_fasfdfs.conf檔案(/etc/fdfs/mod_fastdfs.conf)
    #修改基本路徑,并在指定路徑創建對應檔案夾
    base_path=/opt/fastdfs/nginx_mod #保存日志目錄
    #指定兩臺tracker服務器的ip和埠
    tracker_server=192.168.171.135:22122  #tracker服務器的IP地址以及埠號
    tracker_server=192.168.171.136:22122
    #指定storage服務器的埠號
    storage_server_port=23000 #通常情況不需要修改
    #指定當前的storage服務器所屬的組名 (當前案例03和04為group1 05和06為group2)
    group_name=group1  #當前服務器的group名
    #指定url路徑中是否包含組名 (當前案例url包含組名)
    url_have_group_name=true     #檔案url中是否有group名
    store_path_count=1           #存盤路徑個數,需要和store_path個數匹配(一般不用改)
    store_path0=/opt/fastdfs/storage/files    #存盤路徑
    #指定組個數,根據實際配置決定,(當前案例擁有2個組group1和group2)
    group_count = 2                   #設定組的個數



    在末尾增加2個組的具體資訊:
    [group1]
    group_name=group1
    storage_server_port=23000
    store_path_count=1
    store_path0=/opt/fastdfs/storage/files

    [group2]
    group_name=group2
    storage_server_port=23000
    store_path_count=1
    store_path0=/opt/fastdfs/storage/files

    第一個組的第二個storage按照相同的步驟操作;

    另外一個組的兩個storage也按照相同的步驟操作;

    #測驗nginx的組態檔是否正確(測驗全部6臺服務器)
       /usr/local/nginx_fdfs/sbin/nginx -c /usr/local/nginx_fdfs/conf/nginx.conf -t
    #啟動nginx服務器(全部6臺服務器)
      /usr/local/nginx_fdfs/sbin/nginx -c /usr/local/nginx_fdfs/conf/nginx.conf



測驗:使用瀏覽器分別訪問 6臺 服務器中的fastdfs檔案


第十步:部署前端用戶訪問入口服務器,即訪問192.168.230.128上的Nginx,該Nginx負載均衡到后端2個tracker server;
    配置nginx.conf檔案
    location ~ /group[1-9]/M0[0-9] {   
        proxy_pass http://fastdfs_group_server; 
    }


    添加一個upstream 執行服務的IP為 2臺tracker 的地址
    #部署配置nginx負載均衡:
    upstream fastdfs_group_server {  
        server 192.168.171.135:80;  
        server 192.168.171.136:80; 
    }

測驗:使用瀏覽器訪問128(唯一入口的nginx服務器)服務器中的fastdfs檔案
注意:由于之前128的nginx中可能擁有靜態資源攔截會導致訪問不到檔案,這時可以注釋或洗掉這些靜態資源攔截


==============================補充資料============================================
最后,為了讓服務能正常連接tracker,請關閉所有機器的防火墻:
systemctl status firewalld   查看防火墻狀態
systemctl disable firewalld  禁用開機啟動防火墻
systemctl stop firewalld     停止防火墻
systemctl  restart  network  重啟網路
systemctl  start network     啟動網路
systemctl  stop  network     停止網路

可能安裝的linux(無圖形的)沒有開啟網卡服務,可以修改/etc/sysconfig/network-scripts 下的網卡組態檔設定 ONBOOT=yse 
表示開機啟動網卡,然后啟動網路服務即可

Keepalived當主nginx出現故障后會自動切換到備用nginx服務器的一款軟體 通常由運維人員進行使用

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/525855.html

標籤:Java

上一篇:golang中的錯誤處理

下一篇:判斷執行緒池是否全部完成的 5 種方法,還有誰不會?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more