主頁 > 後端開發 > 爆肝兩萬字,詳解fastdfs分布式檔案系統

爆肝兩萬字,詳解fastdfs分布式檔案系統

2023-03-10 07:19:38 後端開發

1.學習目標

image.png

2.簡介

技術論壇:http://bbs.chinaunix.net/forum-240-1.html
資源地址:https://sourceforge.net/projects/fastdfs/
原始碼地址:https://github.com/happyfish100

  1. FastDFS是一個開源的輕量級分布式檔案系統,它對檔案進行管理,功能包括:檔案存盤、檔案同步、檔案訪問(檔案上傳、檔案下載)等,解決了大容量存盤和負載均衡的問題,特別適合以檔案為載體的在線服務,如相冊網站、視頻網站等等,
  2. FastDFS為互聯網量身定制,充分考慮了冗余備份、負載均衡、線性擴容等機制,并注重高可用、高性能等指標,使用FastDFS很容易搭建一套高性能的檔案服務器集群提供檔案上傳、下載等服務,
  3. FastDFS服務端有兩個角色:跟蹤器(tracker)和存盤節點(storage),跟蹤器主要做調度作業,在訪問上起負載均衡的作用,
  4. 存盤節點存盤檔案,完成檔案管理的所有功能:就是這樣的存盤、同步和提供存取介面,FastDFS同時對檔案metadata進行管理,所謂檔案的metadata就是檔案的相關屬性,以鍵值對(key value)方式表示,如:width=1024,其中的key為width,value為1024,檔案metadata是檔案屬性串列,可以包含多個鍵值對,
  5. 跟蹤器和存盤節點都可以由一臺或多臺服務器構成,跟蹤器和存盤節點中的服務器均可以隨時增加或下線而不會影響線上服務,其中跟蹤器中的所有服務器都是對等的,可以根據服務器的壓力情況隨時增加或減少,
  6. 為了支持大容量,存盤節點(服務器)采用了分卷(或分組)的組織方式,存盤系統由一個或多個卷組成,卷與卷之間的檔案是相互獨立的,所有卷的檔案容量累加就是整個存盤系統中的檔案容量,一個卷可以由一臺或多臺存盤服務器組成,一個卷下的存盤服務器中的檔案都是相同的,卷中的多臺存盤服務器起到了冗余備份和負載均衡的作用
  7. 在卷中增加服務器時,同步已有的檔案由系統自動完成,同步完成后,系統自動將新增服務器切換到線上提供服務,當存盤空間不足或即將耗盡時,可以動態添加卷,只需要增加一臺或多臺服務器,并將它們配置為一個新的卷,這樣就擴大了存盤系統的容量,

FastDFS中的檔案標識分為兩個部分:卷名和檔案名,二者缺一不可,

2.1.架構圖

image.png
解釋:
寫入
假設我現在client要上傳檔案,我要找到跟蹤器tracker,tracker找到client,然后tracker找到存盤節點,看看存盤節點那個卷下面的節點比較空閑,能放得下這個檔案,然后寫入進去,生成一個檔案名
讀取
如果我們client要下載檔案,不需要與tracker再做互動,直接與storage打交道,根據我們當時上傳檔案tracker給我們提供的檔案名,我們加上服務器名字和埠號再加上完整的檔案名即可讀取下載成功
主備切換
我們storage存盤節點是由多個卷構成的一個大的集群,比方說c d e盤構成一個硬碟,每個卷又分成主和子,他們兩者檔案型別一致,如果主服務器崩了,子服務器馬上可以頂上

2.2.上傳流程

image.png

  1. client詢問tracker上傳到的storage,不需要附加引數
  2. tracker回傳一臺可用的storage;
  3. client直接和storage通訊完成檔案上傳

2.3.下載流程

image.png

  1. client詢問tracker下載檔案的storage,引數為檔案標識(組名和檔案名);
  2. tracker回傳一臺可用的storage;
  3. client直接和storage通訊完成檔案下載,

client可以直接去到Storage進行在線的讀取和下載,這個在線讀取和下載前提是我們知道它的一個ip地址和埠號,后面跟上卷名,再跟上檔案名,我們如果知道這個完整路徑的話,可以直接去到我們存盤節點進行在線預覽,或者是在線下載;如果我們現在只知道一個卷名和檔案名,我們并不知道ip和埠,我們也可以去找我們的tracker,拿著我們的卷名和檔案名去找我們的跟蹤器,跟蹤器就會找到對應的卷名和檔案名所在的節點,會把這個存盤節點的ip地址和埠號回傳給我們的客戶端,然后我們client再次通過我們的ip埠卷名檔案名,然后直接通過我們的存盤節點進行我們的讀取和下載操作,一般我們如果考慮效率問題的話,肯定是我們直接拿著ip埠卷名檔案名直接去存盤節點,讀取我們的一個檔案,如果實在是不知道ip和埠情況下,可能就需要通過我們tracker,但一般情況下,我們tracker上傳的時候,我們tracker會回傳一個完整的卷名和檔案名加ip和埠,我們一般呢會把回傳的相對路徑存放到資料庫里面去,那我們需要進行檔案預覽和下載的時候呢,我們一般從資料庫拿到我們帶有ip和埠的卷名和檔案名這一整串資訊的資料直接去storage進行一個下載 ,效率更高一點!!!

2.4.術語介紹

  1. TrackerServer:跟蹤服務器,主要做調度作業,在訪問上起負載均衡的作用,記錄storage server的狀態,是連接Client和Storage server的樞紐,
  2. Storage Server:存盤服務器,檔案和meta data都保存到存盤服務器上
  3. group:組,也稱為卷,同組內服務器上的檔案是完全相同的【主崩子接】
  4. 檔案標識:包括兩部分:組名和檔案名(包含路徑)
  5. meta data:檔案相關屬性,鍵值對(Key Value Pair)方式,如:width1024,height=768

2.5.同步機制

  1. 同一組內的storage server之間是對等的,檔案上傳、洗掉等操作可以在任意一臺storage server上進行;【比方我們client去進行寫操作,會根據tracker自己去調度,假設三臺服務器,會根據tracker調度結果,回傳里面任意一臺的IP地址和埠,主要是看storage那一臺服務器符合調度的一個規則,比方說那一臺storage它現在是空閑的,那一臺storage它的一個剩余磁盤容量能放下這個檔案,它回傳的并不一定是某一臺的ip地址,可能是三臺里面隨機選一個ip地址或者埠進行回傳】
  2. 檔案同步只在組內的storage server之間進行,采用push方式,即源服務器同步給目標服務器;【采用廣播方式,比如說我們現在是一個高并發,每一臺都正好在進行寫操作,傳統的我們一個服務器新增了資料量,它們之間要進行一個相互通信,一直通信到最后一個服務器,再進行相應的資料同步,表示我這里現在新增了資料,你和我同步一下,這是傳統的,我們發現它們相互通信的次數非常頻繁;所以我們一般采用廣播模式,比方說我們某個服務器寫了一條資料,它就會廣播告訴其它服務器,表示我這里新增了資料,你們也新增一下;】
  3. 源頭資料才需要同步,備份資料不需要再次同步,否則就構成環路了;【被寫入資料稱為源服務器;我們源頭資料才需要進行一個同步,如果是備份資料不需要廣播同步,一直不停廣播就形成環路了!!!】
  4. 上述第二條規則有個例外,就是新增加一臺storage server時,由已有的一臺storage server將已有的所有資料(包括源頭資料和備份資料)同步給新增服務器 【假設我們有三臺服務器,增加了一臺,加的這一臺就會從我們原有的三臺里面,隨機選一臺作為一個源頭資料,然后就會把這個源頭資料和我們的備份資料同步到新增的服務器里面】

2.6.FastDFS運行時目錄結構

2.6.1.Tracker Server目錄

2.7.FastDFS和其它檔案存盤的簡單對比

2.7.1.FastDFS和集中存盤方式對比

指標 FastDFS NFS 集中存盤設備加 NetApp、NAS
線性擴容性 高【擴容好】
檔案高并發訪問性能 高【速度快】 一般
檔案訪問方式 專用API【有自己的AIP】 POSIX【可移植作業系統介面】 POSIX
硬體成本 較低【成本低】 中等【硬碟成本高】 高【硬碟成本高】
相同內容檔案只保存一份 支持【相同檔案只有只保留一份】 不支持 不支持

2.7.2.FastDFS和mogileFS對比

指標 FastDFS mogileFS
系統簡潔性 簡潔 只有兩個角色:tracker和storage 一般有三個角色:trocker、storage和存盤檔案資訊的mysql db
系統性能 很高(沒有使用資料庫,檔案同步直接點對點,不經過tracker中轉) 高(使用mysql來存盤檔案索引資訊,檔案同步通過tracker調度和中轉)
系統穩定性 高(C語言開發,可以支持高并發和高負載) 一般(Perl語言開發,高并發和高負載支持一般)【沒有C語言高并發高負載好】
RAID方式 分組(組內冗余),靈活性較大 動態冗余,靈活性一般
通信協議 專用協議,下載檔案支持http【專用協議最大的好處就是寫的操作效率跟高,在tcp/ip之上】 http
技術檔案 較詳細 較少
檔案附加屬性(meta data) 支持【檔案相關屬性存盤在storage】 不支持
相同內容只保存一份 支持【根據檔案相關屬性判斷,如果屬性一致則覆寫】 不支持
下載檔案時支持檔案偏移量【斷點續傳, 從指定位置向前向后移動的位元組數,比如我們下載一個檔案一半點暫停,然后再點開始會從你已經下載好的進度開始,而有的檔案你點了暫停可能就讓你從頭開始下載】 支持 不支持

有沒有比FastDFS更好的呢?當然有,那就是我們的hdfs,hdfs更多的是大資料方面去用,它的性能會比FastDFS更好一點

3.安裝

3.1.安裝簡介

FastDFS主要是兩個角色,一個tracker和storage,它們本質上都是一個FastDFS一個包,它們通過對應不同配置來確認它們不同的角色,所以它們通用的安裝都是FastDFS的安裝包
我們這邊也準備了兩臺服務器,一臺是安裝我們tracker,一臺安裝storage,它們只是對應角色配置不一樣
image.png
如果電腦配置比較差的話,也可以直接安裝在一臺服務器上,只需修改對應角色配置即可,安裝包都是一樣,也可以實作!!!

3.2.FastDFS安裝包

上傳所選安裝包

  1. 第一個就是fastdfs安裝包
  2. 第二個對應client安裝包
  3. 第三個是nginx模塊包
  4. 第四個就是fastdfs公用的庫
  5. 第五個fastdfs和nginx整合

image.png

3.3.安裝依賴

3.3.1.安裝c++相關依賴

我們fastdfs是根據C語言進行開發的,所以我們需要安裝C++相關依賴

yum -y install cmake gcc-c++

image.png

3.3.2.安裝fastdfs核心庫

安裝完成之后,我們還需要安裝我們fastdfs核心庫
image.png
這個核心庫呢其實是從fastdfs和fastdht中提取出來的公用的C函式的庫,fastdfs和fastdht是同樣一個作者去寫的兩個產品,都是C語言撰寫,然后里面會有一些公用函式庫,作者把它提取出來當成一個專門核心庫
如果我們后綴名是.zip,需要安裝一個zip解壓插件

yum -y install unzip

image.png
我們把需要安裝的fastdfs所以檔案放在一個檔案夾方便管理

mkdir -p /usr/local/fastdfs

然后我們就可以去解壓了
zip后綴

unzip libfastcommon-1.0.43.zip

tar后綴

tar -zxvf libfastcommon-1.0.43.zip

image.png
我們進行之后看見有一個make.sh執行腳本,我們就通過這個腳本去進行一個編譯和安裝
image.png

.make.sh

這樣就會進行編譯
image.png
編譯好后我們就可以進行安裝了

.make.sh install

image.png
還有一個問題就是我們fastdfs主程式的lib目錄是在/usr/local/lib下面的,所以我們需要去創建一些軟鏈接,這些軟鏈接就相當于快捷方式,把它的一個快捷方式從原本指向的目錄改成我們想要的指向的目錄
這個意思相當把前面原本指向的路徑指定到我們后面需要我們指定路徑

ln -s /usr/1ib64/1ibfastcommon.so /usr/local/lib/libfastcommon.so
ln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
ln -s /usr/local/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so

3.3.3.安裝fastdfs

image.png

tar -zxvf fastdfs-6.06.tar.gz

我們發現這里也有make.sh
image.png
我們可以做一個可選的操作,就是說我們可以把它的一個路徑改成指定的路徑,因為它默認安裝路徑是在/usr下面,我們可以把它改成/usr/local下面去,當然在集群下面就不要去改,那我們現在是安裝的一個單節點,可以去嘗試的改一下
我們進入fastdfs

vim make.sh

我們搜索/TAGE_PREFIX
image.png
我們把它改到/usr/local
image.png
然后我們再次去安裝

./make.sh 先編譯

image.png
再安裝

./make.sh install

安裝后,FastDFS主程式所在的位置是:

  • /usr/local/bin可執行檔案所在位置,默認安裝在/usr/bin中,
  • /etc/fdfs組態檔所在位置,就是默認位置,
  • /usr/1oca1/1ib64主程式代碼所在位置,默認在usr/bin中,
  • /usr/local/irclude/fastdfs包含的一些插件組所在位置,默認在/usr/include/fastdfs中,

image.png
安裝好了之后,我們可以去看一下服務腳本所在位置

cd /etc/init.d/

image.png
然后我們可以看一下我們組態檔的模板所在位置

cd /etc/fdfs/

image.png
如果我們這臺服務器當做tracker來用的話,只需拷貝tracker配置拷貝過去進行修改,把后面的.sample刪掉就可以用了,建議不要拿源檔案直接用,最好是拷貝過去再進行修改,因為如果改錯了還有備份!!!
我們還可查看內置命令所在目錄

cd /usr/local/bin/

這個就是fastdfs內置命令,包括重啟,啟動,停止,還有測驗,跟蹤等等,,,
image.png
到這里,我們整個tracker服務器的fastdfs安裝好了!!!
**同理,我們還要去安裝storage,拿storage的安裝和tracker是一模一樣的,只是組態檔不同,其它安裝包都一樣,所以我們如果在同一臺服務器部署只需修改組態檔即可!!! **

4.配置tracker

4.1.拷貝tracker.conf.sample

我們首先進入組態檔模板

cd /etc/fdfs

我們看到里有個tracker.conf.sample
image.png
然后我們拷貝一下,到conf就行

cp tacker..sample tracker.conf

4.2.配置tracker.conf

然后我們就可以進行配置了

vim tracker.conf

image.png
我們簡單了解一下里面的屬性

  • bind_addr = 系結的ip地址
  • port = 22122 埠號
  • connect_timeout = 5 連接超時
  • network_timeout = 60 網路超時
  • base_path = /home/yuqing/fastdfs【這個是fastdfs我們一個tracker啟動之后使用的一個根目錄,它呢也要去存盤一些資訊,它存盤的就是我們卷里面的一些storage的存盤節點,包括我們卷1 卷2 卷3 每個卷下面有哪些storage,每個storage它的ip和它的埠都要去存盤一下,因為它是做一個中轉調度操作,client發起一個寫操作的時候,我們的一個tracker就會去調度找到哪一個節點可以給我們去寫操作,然后會回傳ip和埠,tracker需要把我們卷下面的所有的一個存盤節點ip和埠存盤一下,這邊我們可以把這個目錄修改一下,base_path = /fastdfs/tracker,這個目錄是我們自定義的,待會我們要去創建這個目錄
  • max_connections = 1024 最大連接
  • accept_threads = 1 接收的執行緒
  • work_threads = 4 作業的執行緒
  • min_buff_size = 8KB 最小緩沖8KB
  • max_buff_size = 128KB 最大的緩沖
  • store_server = 0 負載均衡策略,0表示輪詢機制,如果是1的話,就是第一個服務器通過我們ip地址查找到的第一個服務器,如果是2的話查找出來的也是第一個服務器,不是通過ip地址
  • store_path = 0 默認輪詢
  • download_server = 0 下載服務默認輪詢

這里我們就配置完成了,我們主要配置base_path 根目錄即可!!!
然后我們別忘了創建剛才自定義的目錄

mkdir -p /fastdfs/tracker

4.3.啟動tracker

我們創建完目錄呢,就可以啟動tracker了,來到啟動目錄

cd /etc/init.d/

image.png
我們看到有兩個檔案 fdfs_trackerd和fdfs_storaged這兩個就是我們啟動的檔案
我們之前安裝的時候,我們修改過它的一個目錄,所以呢我們這邊啟動的時候呢,也去修改它的一個目錄,如果我們沒有去修改它的目錄,這邊可以直接啟動!!!
我們進入組態檔修改目錄即可!!!

vim fdfs_trackerd

我們修改之后應該是/usr/local/bin/fdfs_trackerd,
image.png
保存并退出, 啟動即可

./fdfs_trackerd start

image.png
怎么去看有沒有啟動成功呢?
兩種方法:
查看狀態

./fdfs_trackerd status

image.png
查看行程

ps -ef|grep fdfs

image.png
停止

.fdfs_trackerd stop

重啟

/etc/init.d/fdfs_trackerd restart

開機啟動
我們進入檔案

vim /etc/rc.d/rc.local

添加啟動檔案
image.png

/etc/init.d/fdfs_tracked start

4.4.小結

我們現在已經啟動了tracker包括我們相應的一些配置,其實真正要做的配置沒有,默認的埠都沒改,只是把它的base_path 根目錄進行了一個修改,當然這個修改也是可有可無的,但是記住一定要創建,有一個負載均衡,大部分默認都是一個輪詢的方式,其它的也沒什么!!!

5.配置Storage

5.1.拷貝storage.conf.sample

跟tracker一樣

cd /etc/fdfs

image.png
同樣,拷貝一份

cp storage.conf.sample storage.conf

5.2.配置storage.conf

修改storage

vim storage.conf

也是一樣,我們觀察一下里面的屬性
image.png

  • group_name = group1 默認組名,也是卷名
  • bind_addr = 系結的ip地址
  • client_bind = true 是否允許客戶端訪問
  • port = 23000 埠號
  • connect_timeout = 5 連接超時
  • network_timeout = 60 網路超時
  • base_path = /home/yuqing/fastdfs【同樣這里也是存放storage_server它里面基礎資料的內容,以及日志內容的目錄,比如說啟動的行程號、同步的相應資訊,我們也可以進行修改,/fastdfs/storage/base
  • max_connections =1024 最大連接數
  • buff_size = 256KB 緩沖大小
  • accept_threads = 1接收的執行緒
  • work_threads = 4 作業的執行緒
  • store_path0 = /home/yuqing/fastdfs【這個目錄是我們真正存放檔案的目錄,我們也更改一下 /fastdfs/storage/store,這個目錄會在我們storage啟動的時候,它會去生成256x256目錄,當然我們base_path和store_path0可以用同一個目錄也是沒有問題的,一般建議分開好區分】
  • tracker_server = 192.168.209.121:22122【這里也是我們需要修改的地方,有兩個我們只需要一個即可,為什么有兩個,因為我們tracker也是可以搞集群的,可以配置多個,這里我們直接寫我們tracker追蹤服務器ip地址,如果是同一臺服務器寫本機ip即可,埠不變!!!】

保存并退出即可!!!
創建我們剛才自定義的目錄

mkdir -p /fastdfs/storage/base
mkdir -p /fastdfs/storage/store

我們安裝的時候也修改了目錄,所以我們這里也要修改一下

vim /etc/init.d/fdfs_storaged

修改PRG=/usr/local/bin/fdfs_storaged
image.png

5.3.啟動storage

保存并退出,啟動storage

/etc/init.d/fdfs_storaged start	 

image.png
查看狀態

/etc/init.d/fdfs_storaged status

image.png
停止

/etc/init.d/fdfs_storaged stop

重啟

/etc/init.d/fdfs_storaged restart

開啟啟動
我們進入檔案

vim /etc/rc.d/rc.local

添加啟動檔案
image.png
啟動之后我們可以去看看我們剛才創建的兩個目錄

cd /fastdfs storage/

image.png
base目錄【基礎資料目錄】
可以看到有一個data和logs 基礎資料和日志
image.png
可以看到有對應的一個日志
image.png
進入data目錄image.png
里面有對應我們storage的一個行程號,然后啟動的一個資料和**同步相應的相關資訊 **
我們回去看我們store

cd ../../store

這里也有一個data,這個data存放我們上傳檔案的目錄
image.png
我們發現這邊使用16進制,從00一直到FF,一共是256個目錄,然后我們每一個目錄下面還有256個子目錄
image.png
我們cd 00 再 ls
我們發現還是有 00 -FF 256個目錄
image.png
再00就沒了!!!再往下這邊就會存放我們的檔案了,至于我們檔案上傳上來之后會存放在那個目錄下面,這個不需要我們去關系,storage會自己去存放,并且我們的tracker會去把我們的一個完整的卷名加檔案名,這個檔案名就是從data到00再到00再到下面的具體檔案名一整個完整路徑回傳給我們,我們直接能拿到,它具體存放在著256個目錄那個目錄這個不需要我們操心,這是storage自定義去完成的,我們這里storage開機啟動前提是必須先啟動tracker再啟動storage,不然會報錯,因為我們storage組態檔里面配置了tracker_server,所以我們這邊如果想要stroage開機自啟,必須設定tracker也開機自啟,不然不建議storage自啟!!!

6.Client配置【可選】

客戶端配置不是必需的,因為客戶端配置完相當于用命令列去測驗我們的fastdfs,所以我們如果不準備用命令列測驗,而是準備用代碼測驗的話,我們完全可以跳過!!!
我們可以把Client放在tracker和storage任意服務器下,并不影響,把它配置改一下即可!!!

6.1.拷貝client.conf.sample

首先進入我們配置模板

cd /etc/fdfs

我們可以看到這邊有一個client.conf.sample
image.png同樣,我們先拷貝

cp client.conf.sample client.conf

6.2.配置client.conf

進入組態檔

vim client.conf

可以看下它的屬性,更改的地方加粗

  • connect_timeout =5 連接超時
  • network_timeout = 60 網路超時
  • base_path = /home/yuqing/fastdfs 基礎目錄 【我們更改一些,/fastdfs/client,這里也是放客戶端運行所產生的一些相應的資料】
  • tracker_server = 192.168.0.196:22122【我們之前說過,我們如果是客戶端進行一個上傳和下載的話,中間都要經過一個tracker,我們上傳的時候呢,根據tracker找到storage的ip和埠進行上傳,如果我們是下載的話,客戶端也需要通過我們的tracker找到我們的一個ip和埠進行一個下載,所以我們這邊要配置一個tracker,改成本機地址 192.168.248.101】

到這里,client配置就完成了,我們保存并退出,創建剛剛自定義的目錄

mkdir -p /fastdfs/client

6.3.上傳檔案

我們看到root目錄下面有個圖片,我們可以把這個上傳上去
image.png怎么去上傳呢?上傳的話因為我們安裝的時候改過對應的一個目錄,我們的命令目錄呢在/usr/local/bin
我們上傳就是fdfs_upload_file
image.png

./fdfs_upload_file /etc/fdfs/client.conf ~/cat-114782_640 (1).jpg

上傳命令+客戶端配置+上傳的檔案
回傳上傳的完整卷名加檔案名,它的檔案名是重新命名的
image.png

  • group1是我們storage里的配置,配的一個卷名的名稱

image.png

  • M00 這是一個虛擬目錄
  • 00和00表示它放在data-00-00目錄下,當然一般情況下是按照順序保存的,但我們也不能完全保證,storage有自己的一個規則,上傳到哪一個目錄下面去
  • wKj4ZWQJdC-AOV6HAAKSU_3XkA0484.jpg 檔案名 重新命名了,防止檔案名重復!!!

我們可以進入storage存放檔案目錄去查看我們上傳的檔案
image.png

小結

我們要記住M00是一個虛擬目錄,有點相當于我們windows的快捷方式,它的參考主要是參考到我們的data目錄下面,data就對應M00,快捷方式的一個意思

6.4.洗掉檔案

./fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKj4ZWQJdC-AOV6HAAKSU_3XkA0484.jpg

洗掉命令+client組態檔+完整的卷名和檔案名
這里要注意,因為我們現在操作沒有ip和埠,所以我們需要根據追蹤器,根據這一整串完整的檔案名去獲取stroage的ip和埠,才會去進行一個洗掉這也就是我們client為什么要去配置tracker服務器
這樣則表示洗掉成功
image.png
我們可以進入data-00-00查看是否洗掉成功
我們發現后最484的檔案名沒有了
image.png

7.安裝nginx和fastdfs_nginx_module

為什么要安裝nginx呢?因為我們fastdfs是一個檔案系統,那可以存放很多型別的檔案,比如說存放圖片或者其它一個型別,圖片的話我們可以通過網頁直接去預覽,而不需要通過我們現在這個操作,通過client拿到一個完整的卷名加檔案名,通過tracker拿到對應的ip地址和埠再去下載預覽,太麻煩,我們可以通過http協議直接在我們的url里面輸入我們的一個ip埠,卷名檔案名直接在瀏覽器里面可以訪問這張圖片或預覽,那這個時候呢我們fastdfs沒法實作,就要安裝fastdfs_module_nginx和nginx進行代理
image.png

7.1.安裝組件fastdfs-nginx-module

解壓

tar zxvf fastdfs-nginx-module-1.22

cd 進入目錄
image.png

  • HISTOPY 歷史檔案
  • INSTALL 安裝
  • src 原始碼

我們來到src原始碼目錄
image.png
原始碼目錄有相應的一個配置,我們需要修改相應的一個配置

vim config

為什么要改配置呢?因為我們去安裝我們的組件之后呢,會去安裝我們的nginx,安裝nginx的時候需要把我們的一個module模塊加進去,加進去之后會尋找我們的一個fastdfs對應的一個安裝的目錄,如果目錄不正確可能就安裝失敗了!!!所以我們需要去更改我們的一個目錄路徑
image.png
**修改的時候也有區別,因為我們裝fastdfs的時候修改了我們安裝目錄,所以我們這串目錄是改過目錄之后的,如果我們沒有修改過fastdfs的安裝目錄的,我們改的是另一個目錄 **

/usr/local/include/fastdfs /usr/include/fastcommon/

fastdfs位置+核心庫,這兩個改呢,是因為我們安裝改過,如果沒有的話呢,我們改的不是這兩個,就應該是把local刪掉就行了
保存并退出,我們的模塊就改好了,改好了之后并不代表它已經安裝了,怎么去安裝呢,就是我們去安裝nginx的時候,把模塊添加上去即可,所以我們還要去安裝我們的nginx
image.png

7.2.安裝nginx

依賴
安裝之前我們還需要安裝對應依賴

yum install -y gcc gcc-c++ make automake autoconf libtool pcre pcre-develzlib zlib-devel
openss1 openss1-devel

image.png
解壓nginx
image.png

tar -zxvf nginx-1.16.1.tar.gz

查看目錄
image.png
我們需要去更改一下它的目錄
更改目錄
先準備一個目錄

mkdir -p /var/temp/nginx

配置nginx安裝資訊

./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi\
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi \
--add-module=/usr/local/fastdfs/fastdfs-nginx-module-1.22/src
  • --prefix=/usr/local/nginx \ 安裝路徑

下面這些資訊放入我們剛才創建的檔案

  • --http-client-body-temp-path=/var/temp/nginx/client \
  • --http-proxy-temp-path=/var/temp/nginx/proxy \
  • --http-fastcgi-temp-path=/var/temp/nginx/fastcgi\
  • --http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
  • --http-scgi-temp-path=/var/temp/nginx/scgi \
  • --add-module=/usr/local/fastdfs/fastdfs-nginx-module-1.22/src【這個是必須定義的,這個是我們安裝nginx的時候需要加載的一個模塊,如果沒有指定的話呢,nginx安裝程序中呢,就不會去加載fastdfs_nginx_module模塊,如果nginx組態檔添加了這個模塊,nginx啟動會報錯,后續功能無法實作!!!】

這樣我們nginx安裝資訊就配置完成
image.png
接下來我們就可以進行安裝了
預編譯

make

編譯加安裝

make install

8.配置nginx模塊

8.1.拷貝mod_fastdfs.conf

同理跟上面操作一樣我們先把fastdfs_nginx_module模塊組態檔修改

cd /usr/local/fastdfs/fastdfs-nginx-module-1.22/src/

這里面有一個我們對應的一個組態檔mod_fastdfs.conf
image.png

cd mod_fastdfs.conf /etc/fdfs

image.png
然后我們進行一個相應的修改

8.2.修改mod_fastdfs.conf

image.png

  • connect_timeout = 2 連接超時 ,可改可不改,改成10
  • network_timeout = 30 網路超時
  • base_path 基礎目錄
  • tracker_server = 192.168.248.101:22122【這里也需要我們tracker服務器,改成本機跟蹤服務器ip即可】
  • storage_server_port = 23000 埠號
  • url_have_group_name = false 【表示我的url中是否要包含group名字,這里我們改成true
  • 包含即可 】
  • store_path0 = /home/yuqing/fastdfs【我們這里要改成我們之前存放檔案的目錄 /fastdfs/store】

保存并退出

8.3.拷貝http配置

這兩個檔案是因為我們nginx在網頁上直接看圖片的時候呢,是用的一個http協議,所以我們需要去拷貝一下
http

cp /usr/local/fastdfs/fastdfs-6.06/conf/http.conf /etc/fdfs/

請求頭

cp /usr/local/fastdfs/fastdfs-6.06/conf/mime.types /etc/fdfs/

8.4.創建nginx

8.4.1.啟動軟連接

因為我們nginx啟動的時候呢,會在默認的/usr/lib64目錄下面去查找所需的so檔案,那因為我們安裝fastdfs的時候呢,已經修改了它的目錄,所以我們一定要創建它的軟鏈接,不然會失敗

ln-s /usr/local/lib64/libfdfsclient.so /usr/lib64/libfdfsclient.so

8.4.2.網路服務啟動軟鏈接

這個軟鏈接呢,我們的追蹤服務回傳的里面有一個M00,這個M00其實就是一個data,它是相當于windows的一個快捷方式,那我們需要把這個M00指定到data下面

ln -s /fastdfs/storage/store/data//fastdfs/storage/store/data/M00

我們可以去看一下我們創建的軟連接是否生效

cd /fastdfs/storage/store/data

我們可以看到這邊有個M00
image.png
它指定的目錄呢就是我們data目錄下
image.png

9.配置nginx

9.1.修改nginx.conf

cd /usr/local/nginx/

進入conf目錄

cd conf

修改nginx.conf
image.png

vim nginx.conf	

image.png
修改哪些東西呢?

  • **user nobby **【因為是學習原因,我們這邊直接配置root用戶即可,正式作業中肯定是根據實際情況來決定的,我們user改成root用戶意思是nginx訪問alias必須要有檔案系統的權限,那我們改成root就表示檔案系統的一個權限是root用戶的一個權限,如果我們不開啟這個可能會出現404錯誤,那實際作業中,我們肯定不可能把這個root用戶權限放出來,可能會自己創建一個用戶指派一些權限,所以我們基于學習的一個目的直接開放即可!!!】
  • listen:8888【為什么是8888呢,我們cd /etc/fdfs,vim storage.conf,這邊有一個htpp.server_port埠,它這邊就是8888,如果我們這邊改了,我們對應nginx組態檔也需要更改!!!】

加入以下配置

location ~/group[0-9]/M00{
    ngx_fastdfs_module;
}
  • ~/group[0-9]表示我們group0-9組都可以
  • M00表示data
  • ngx_fastdfs_module 是我們fastdfs_nginx_module配置的nginx模塊

當我們去訪問8888埠時候呢,它就會找到我們的本地的group0/M00,然后通過我們的ngx_fastdfs_module這樣的一個模塊去找到我們storage上面存盤的一個對應檔案就可以預覽了!!!

9.2.重啟storage并啟動nginx

保存并退出,我們nginx配置就完成了,我們需要重啟一下我們的storage并且要啟動nginx

/etc/init.d/fdfs_storaged restart

image.png
我們來到nginx-sbin目錄啟動nginx

./nginx

看到埠表示啟動成功!!!
image.png
查看啟動狀態

ps -ef |grep nginx

image.png

9.3.上傳圖片通過網頁訪問

我們通過客戶端上傳圖片
image.png

/usr/local/bin/fdfs_upload_file /etc/fdfs/client.conf  cat-114782_640.jpg

同理,**上傳命令+client配置+檔案名即可 **
image.png查看上傳的檔案

cd /fastdfs/storage/store/data/00/00/

image.png 根據tracker給出的路徑通過網頁進行訪問,ip+埠+卷名和檔案名
image.png

10.Java API

10.1.依賴

<dependency>
  <groupId>org.csource</groupId>
  <artifactId>fastdfs-client-java</artifactId>
  <version>1.30-SNAPSHOT</version>
</dependency>

注意:這邊我們maven倉庫是沒有這個依賴的,我們需要自己去下載然后安裝到倉庫

  • 首先確保我們環境依賴沒有問題

image.png

  • 獲取fastdfs依賴https://gitcode.net/mirrors/happyfish100/fastdfs-client-java?utm_source=csdn_github_accelerator
  • 進入解壓后的fastdfs-client-java-master檔案夾,并在此路徑打開cmd命令列視窗,執行命令mvn clean install

image.png

  • 這里安裝在了我本地的maven倉庫里,如果沒有配置maven本地倉庫,那么他會默認下載到默認的本地倉庫C:${user.home}.m2\repository
  • 這是本地已經安裝好的fastdfs-client-java-master依賴

image.png

  • 然后重新加載maven依賴就可以啦,注意版本要對應

10.2.常用類

10.2.1.CLientGlobal【加載組態檔】

用于加載組態檔的公共客戶端工具,
常用方法:

  • init(String conf_filename);根據組態檔路徑及命名,加載組態檔并設定客戶端公共引數,組態檔型別為conf檔案,可以使用絕對路徑或相對路徑加載,
  • initByProperties(Properties props);根據Properties物件設定客戶端公共引數,

注意:使用conf或prooerties進行客戶端引數配置時,引數key命名不同,

10.2.2.TrckerClient【跟蹤器客戶端型別】

跟蹤器客戶端型別,創建此型別物件時,需傳遞跟蹤器組,就是跟蹤器的訪問地址資訊,無參構造方法默認使用ClientGlobal_tracker_group 常量作為跟蹤器組來構造物件,【就是我們上面ClientGlobal定義的常量】
創建物件的方式為:

new TrackerClient();或new TrackerClient(ClientGlobal.g_tracker_group)

10.2.3.TrackerServer【跟蹤器服務型別】

跟蹤器服務型別,此型別的物件是通過跟蹤器客戶端物件創建的,實質上就是一個FastDFS Tracker Server的鏈接物件,是代碼中與Tracker Server鏈接的工具
構建物件的方式為:

trackerClient.getTrackerServer();

10.2.4.StorageServer【存盤服務型別】

存盤服務型別,此型別的物件是通過跟蹤器客戶端物件創建的,實質上就是一個FastDFS Storage Server的鏈接物件,是代碼中與StorageServer鏈接的工具,獲取具體存盤服務鏈接,是由Tracker Server分配的,所以構建存盤服務物件時,需要依賴跟蹤器服務物件
構建物件的方式為:

trackerClient.getStorage(trackerServer);

10.2.5.StorageClient【真正去操作檔案的客戶端型別,需要傳入tracker和storage】

存盤客戶端型別,此型別物件是通過構造方法創建的,創建時,需傳遞跟蹤服務物件和存盤服務物件,此物件實質上就是一個訪問FastDFS Storage Server的客戶端物件,用于實作檔案的讀寫操作
創建物件的方式為:

new StorageClient(trackerServer,storageServer);

常用方法有:

  • upload_file(String local_filename【檔案路徑】,String file_ext_name【檔案擴展名,如果我們這里傳null的話,那我們fastdfs就會自動決議檔案擴展名,自動找.后面的一串 】,NameValuePair[] meta_list【檔案屬性集合】);上傳檔案的方法,引數local_filename為要上傳的本地檔案路徑及檔案名,可使用絕對路徑或相對路徑;引數file_ext_name為上傳檔案的擴展名,如果擴展null,則自動決議檔案擴展名;引數meta_list是用于設定上傳檔案的源資料,如上傳用戶、上傳描述等,
  • download_file(String group_name【卷名】,String remote_file_name【下載的檔案名】);下載檔案的方法,引數group為組名/卷名,就是Storage Server中/etc/fdfs/storage.conf組態檔中配置的group_name引數值,也是要下載的檔案所在組/卷的命名;引數remote_file_name為要下載的檔案路徑及檔案名,
  • delete_file(String group_name【卷名】,String remote_file_name【洗掉的檔案名】);洗掉檔案的方法,引數含義同download_file方法引數

11.Demo

11.1.創建專案

創建fastdfsdemo,加上web和thymeleaf依賴
image.png

11.2.匯入相關依賴

<!--thylemeaf-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--fastdfs-->
<dependency>
  <groupId>org.csource</groupId>
  <artifactId>fastdfs-client-java</artifactId>
  <version>1.30-SNAPSHOT</version>
</dependency>  
<!--test-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

11.3.創建fdfs_client.conf組態檔

#連接超時
connect_timeout = 2
#網路超時
network_timeout = 30
#編碼格式	
charset = utf-8
#tracke埠號
http.tracker_http_port = 8888
#防盜鏈功能
http.anti_steal_token = no
#秘鑰
http.secret_key = FastDFS1234567890
#tracker ip :埠號
tracker_server = 192.168.248.101:22122
#連接池配置
connection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000

11.4.加載組態檔,生成服務端和客戶端

//日志
private static Logger logger = LoggerFactory.getLogger(FastDFSClient.class);

//初始化FastDFS,ClientGlobal.init方法會讀取組態檔,并初始化對應的屬性
static {
    try {
        String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
        ClientGlobal.init(filePath);
    } catch (IOException | MyException e) {
        logger.error("FastDFS Client init Fail!", e);
        e.printStackTrace();
    }
}

/**
* 生成Storage客戶端
* @return
* @throws IOException
*/
private static StorageClient getStorageClient() throws IOException {
    TrackerServer trackerServer = getTrackerServer();
//我們可以通過trackerServer拿到StorageServer,不設定也能拿到StorageClient
return new StorageClient(trackerServer, null);
}

/**
* 生成Tracker服務器端
* @return
* @throws IOException
*/
private static TrackerServer getTrackerServer() throws IOException {
    TrackerClient trackerClient = new TrackerClient();
return trackerClient.getTrackerServer();
}

11.5.上傳、下載、洗掉、查看檔案

   /**
     * 上傳
     *
     * @param file 檔案物件
     * @return
     */
    private static String[] upload(FastDFSFile file) {
        //列印相關資訊
        logger.info("File Name:" + file.getName() + ",File lenght" + file.getContent().length);
        //檔案屬性資訊
        NameValuePair[] meta_list = new NameValuePair[1];
        meta_list[0] = new NameValuePair("author", file.getAuthor());
        //上傳時間
        long startTime = System.currentTimeMillis();
        //回傳一個String陣列
        String[] uploadResults = null;
        StorageClient storageClient = null;
        try {
            //拿到客戶端
            storageClient = getStorageClient();
            //檔案陣列;后綴名;檔案屬性相關陣列
            uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
        } catch (IOException | MyException e) {
            e.printStackTrace();
            //上傳失敗列印檔案名
            logger.error("上傳失敗 File Name:" + file.getName(), e);
        }
        //上傳時間
        logger.info("上傳時間:" + (System.currentTimeMillis() - startTime + "ms"));
        //驗證上傳結果
        if (uploadResults == null && storageClient != null) {//判斷上傳結果是否為空
            logger.error("上傳失敗" + storageClient.getErrorCode());//上傳失敗拿到錯誤結果
        }
        //上傳成功會回傳相應資訊
        logger.info("上傳成功,group_name:" + uploadResults[0] + "remoteFileName:" + uploadResults[1]);//列印卷名和完整名字
        return uploadResults;
    }

    /**
     * 下載檔案
     *
     * @param groupName      卷
     * @param remoteFileName 完整檔案名
     * @return
     */
    public static InputStream downFile(String groupName, String remoteFileName) {
        try {
            //創建storage客戶端物件
            StorageClient storageClient = getStorageClient();
            //呼叫下載方法,回傳一個陣列
            byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
            //輸入要下載的檔案
            InputStream ins = new ByteArrayInputStream(fileByte);
            return ins;
        } catch (IOException | MyException e) {
            logger.error("下載失敗", e);
        }
        return null;
    }

    /**
     * 洗掉檔案
     *
     * @param groupName      卷
     * @param remoteFileName 完整檔案名
     */
    public static void deleteFile(String groupName, String remoteFileName) {
        try {
            StorageClient storageClient = getStorageClient();
            int i = storageClient.delete_file(groupName, remoteFileName);
            logger.info("洗掉成功" + i);
        } catch (IOException | MyException e) {
            logger.error("洗掉失敗", e);
        }
    }

    /**
     * 查看檔案資訊
     * @param groupName      卷名
     * @param remoteFileName 完整檔案名
     * @return
     */
    public static FileInfo getFile(String groupName, String remoteFileName) {
        try {
            StorageClient storageClient = getStorageClient();
            //獲取檔案方法
            FileInfo file_info = storageClient.get_file_info(groupName, remoteFileName);
            return file_info;
        } catch (Exception e) {
            logger.error("查看檔案資訊失敗", e);
        }
        return null;
    }

11.6.網頁查看圖片方法


    /**
     * 獲取檔案路徑,上傳至后回傳的完整路徑
     * @return
     * @throws IOException
     */
    public static String getTrackerUrl() throws IOException {
        return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":8888/";
    }

11.7.完整代碼

package com.zhang.fastdfsdemo.utils;
import com.zhang.fastdfsdemo.pojo.FastDFSFile;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
 * @author zb
 * @version 1.0
 * @date 2023/3/9 18:22
 * @ClassName FastDFSClient
 * @Description FastDFS工具類
 * StorageServer也可以通過Tracker拿到,我們直接那StorageClient即可,
 * 因為我們后期操作我們的檔案,不管是上傳、下載、洗掉都是通過我們storageClient進行操作的
 */
public class FastDFSClient {
    //日志
    private static Logger logger = LoggerFactory.getLogger(FastDFSClient.class);

    //初始化FastDFS,ClientGlobal.init方法會讀取組態檔,并初始化對應的屬性
    static {
        try {
            String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
            ClientGlobal.init(filePath);
        } catch (IOException | MyException e) {
            logger.error("FastDFS Client init Fail!", e);
            e.printStackTrace();
        }
    }

    /**
     * 生成Storage客戶端
     *
     * @return
     * @throws IOException
     */
    private static StorageClient getStorageClient() throws IOException {
        TrackerServer trackerServer = getTrackerServer();
        //我們可以通過trackerServer拿到StorageServer,不設定也能拿到StorageClient
        return new StorageClient(trackerServer, null);
    }

    /**
     * 生成Tracker服務器端
     *
     * @return
     * @throws IOException
     */
    private static TrackerServer getTrackerServer() throws IOException {
        TrackerClient trackerClient = new TrackerClient();
        return trackerClient.getTrackerServer();
    }

    /**
     * 上傳
     *
     * @param file 檔案物件
     * @return
     */
    private static String[] upload(FastDFSFile file) {
        //列印相關資訊
        logger.info("File Name:" + file.getName() + ",File lenght" + file.getContent().length);
        //檔案屬性資訊
        NameValuePair[] meta_list = new NameValuePair[1];
        meta_list[0] = new NameValuePair("author", file.getAuthor());
        //上傳時間
        long startTime = System.currentTimeMillis();
        //回傳一個String陣列
        String[] uploadResults = null;
        StorageClient storageClient = null;
        try {
            //拿到客戶端
            storageClient = getStorageClient();
            //檔案陣列;后綴名;檔案屬性相關陣列
            uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
        } catch (IOException | MyException e) {
            e.printStackTrace();
            //上傳失敗列印檔案名
            logger.error("上傳失敗 File Name:" + file.getName(), e);
        }
        //上傳時間
        logger.info("上傳時間:" + (System.currentTimeMillis() - startTime + "ms"));
        //驗證上傳結果
        if (uploadResults == null && storageClient != null) {//判斷上傳結果是否為空
            logger.error("上傳失敗" + storageClient.getErrorCode());//上傳失敗拿到錯誤結果
        }
        //上傳成功會回傳相應資訊
        logger.info("上傳成功,group_name:" + uploadResults[0] + "remoteFileName:" + uploadResults[1]);//列印卷名和完整名字
        return uploadResults;
    }

    /**
     * 下載檔案
     *
     * @param groupName      卷
     * @param remoteFileName 完整檔案名
     * @return
     */
    public static InputStream downFile(String groupName, String remoteFileName) {
        try {
            //創建storage客戶端物件
            StorageClient storageClient = getStorageClient();
            //呼叫下載方法,回傳一個陣列
            byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
            //輸入要下載的檔案
            InputStream ins = new ByteArrayInputStream(fileByte);
            return ins;
        } catch (IOException | MyException e) {
            logger.error("下載失敗", e);
        }
        return null;
    }

    /**
     * 洗掉檔案
     *
     * @param groupName      卷
     * @param remoteFileName 完整檔案名
     */
    public static void deleteFile(String groupName, String remoteFileName) {
        try {
            StorageClient storageClient = getStorageClient();
            int i = storageClient.delete_file(groupName, remoteFileName);
            logger.info("洗掉成功" + i);
        } catch (IOException | MyException e) {
            logger.error("洗掉失敗", e);
        }
    }

    /**
     * 查看檔案資訊
     * @param groupName      卷名
     * @param remoteFileName 完整檔案名
     * @return
     */
    public static FileInfo getFile(String groupName, String remoteFileName) {
        try {
            StorageClient storageClient = getStorageClient();
            //獲取檔案方法
            FileInfo file_info = storageClient.get_file_info(groupName, remoteFileName);
            return file_info;
        } catch (Exception e) {
            logger.error("查看檔案資訊失敗", e);
        }
        return null;
    }

    /**
     * 獲取檔案路徑,上傳至后回傳的完整路徑
     * @return
     * @throws IOException
     */
    public static String getTrackerUrl() throws IOException {
        return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":8888/";
    }
}

11.8.前端測驗

我們寫一個contrller測驗即可,service我們不寫直接跳過,正常情況下應該要寫service,service調我們工具類方法,contrller再調service,我們這里只是演示!!!
controller

package com.zhang.fastdfsdemo.controller;
import com.zhang.fastdfsdemo.pojo.FastDFSFile;
import com.zhang.fastdfsdemo.utils.FastDFSClient;
import org.csource.common.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author zb
 * @version 1.0
 * @date 2023/3/9 21:12
 * @ClassName FileContrller
 * @Description TODD 測驗檔案上傳、下載、洗掉、查看
 */
@Controller
public class FileContrller {
    private static Logger logger = LoggerFactory.getLogger(FileContrller.class);

    /**
     * 上傳檔案
     * 我們現在入參是 MultipartFile ,但我們真正要傳進去的fastdfs file,那我們可以把它提取出來呼叫
     *
     * @param file
     * @param redirectAttributes
     * @return
     */
    @PostMapping("upload")
    public String singFile(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
        if (file.isEmpty()) {//判斷上傳檔案是否為空
            redirectAttributes.addFlashAttribute("message", "請選擇一個檔案上傳");
            return "redirect:uploadStatus";
        }
        //拿到客戶端
        try {
            //上傳檔案,拿到回傳檔案路徑
            String path = saveFile(file);
            redirectAttributes.addFlashAttribute("message", "上傳成功" + file.getOriginalFilename());
            redirectAttributes.addFlashAttribute("path", "路徑:" + path);
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return "redirect:uploadStatus";
    }

    /**
     * 跳轉上傳檔案狀態也
     *
     * @return
     */
    @GetMapping("/uploadStatus")
    public String uploadStatus() {
        return "uploadStatus";
    }

    /**
     * 跳轉檔案上傳頁
     *
     * @return
     */
    @GetMapping("/")
    public String upload() {
        return "upload";
    }

    /**
     * MultipartFile轉fastdfsfile
     *
     * @param multipartFile
     * @return
     * @throws IOException
     */
    public String saveFile(MultipartFile multipartFile) throws IOException, MyException {
        String[] fileAbsolutePath = {};
        String fileName = multipartFile.getOriginalFilename();//獲取檔案名
        String ext = fileName.substring(fileName.lastIndexOf(".") + 1);//截取后綴
        byte[] file_buff = null;
        InputStream inputStream = multipartFile.getInputStream();
        if (inputStream != null) {
            int len1 = inputStream.available();//拿到長度
            file_buff = new byte[len1];
            inputStream.read(file_buff);
        }
        inputStream.close();
        FastDFSFile file = new FastDFSFile(fileName, file_buff, ext);//轉成fastdfs檔案型別
        //上傳,//回傳組名和檔案名
        fileAbsolutePath = FastDFSClient.upload(file);
        if (fileAbsolutePath == null) {
            logger.error("上傳失敗");
        }
        String path = FastDFSClient.getTrackerUrl() + fileAbsolutePath[0] + "/" + fileAbsolutePath[1];//回傳完整路徑
        return path;
    }
}

html
上傳檔案

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <h1>Spring Boot file upload example</h1>
    <form method="post" action="/upload" enctype="multipart/form-data">
      <input type="file" name="file"><br><br>
      <input type="submit" value="https://www.cnblogs.com/smallroll/p/submit">
    </form>
  </body>
</html>

上傳狀態

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>SpringBoot - Upload Status</h1>
<div th:if="${message}">
    <h2 th:text="${message}"></h2>
</div>
<div th:if="${path}">
    <h2 th:text="${path}"></h2>
</div>
</body>
</html>

測驗
上傳成功
image.png
流覽圖
image.png

本文來自博客園,作者:SmallRoll(小卷),轉載請注明原文鏈接:https://www.cnblogs.com/smallroll/p/17201761.html

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

標籤:Java

上一篇:maven 多模塊專案的測驗覆寫率分析 - jacoco 聚合分析

下一篇:SpringBoot Controller

標籤雲
其他(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