FastDFS

 FastDFS 是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。

 FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

 FastDFS 服务端有两个角色:跟踪器(tracker)和存储节点(storage)。跟踪器主要做调度工作,在访问上起负载均衡的作用。

 存储节点存储文件,完成文件管理的所有功能:就是这样的存储、同步和提供存取接口,FastDFS 同时对文件的 metadata进行管理。所谓文件的metadata 就是文件的相关属性,以键值对(key value)方式表示,如:width=1024,其中的 key 为 width,value 为 1024。文件metadata是文件属性列表,可以包含多个键值对。

 跟踪器和存储节点都可以由一台或多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。

 为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。

 在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。

 FastDFS 中的文件标识分为两个部分:卷名和文件名,二者缺一不可。

架构图


上传流程

client询问tracker上传到的 storage,不需要附加参数;

tracker返回一台可用的storage;

client直接和storage 通讯完成文件上传

下载流程

client询问tracker下载文件的storage,参数为文件标识(组名和文件名);

tracker返回一台可用的storage;

client直接和storage通讯完成文件下载。

术语介绍

TrackerServer:跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。记录storage server 的状态,是连接 Client 和 Storage server 的枢纽。

Storage Server:存储服务器,文件和meta data 都保存到存储服务器上

group:组,也称为卷。同组内服务器上的文件是完全相同的

文件标识:包括两部分:组名和文件名(包含路径)

meta data:文件相关属性,键值对(Key Value Pair)方式,如:width=1024,heigth=768

同步机制

 同一组内的 storage server 之间是对等的,文件上传、删除等操作可以在任意一台 storage server 上进行;

 文件同步只在同组内的 storage server 之间进行,采用 push 方式,即源服务器同步给目标服务器;

 源头数据才需要同步,备份数据不需要再次同步,否则就构成环路了;

 上述第二条规则有个例外,就是新增加一台storage server时,由已有的一台storage server将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器

FastDFS 运行时目录结构

Tracker Server 目录

${base_path}

|__data

| |__storage_groups.dat:存储分组信息

| |__storage_servers.dat:存储服务器列表

|__logs

|__trackerd.log:tracker server 日志文件

Storage Server 目录

${base_path}

|__data

| |__.data_init_flag:当前 storage server 初始化信息

| |__storage_stat.dat:当前 storage server 统计信息

| |__sync:存放数据同步相关文件

| | |__binlog.index:当前的 binlog 文件索引号

| | |__binlog.###:存放更新操作记录(日志)

| | |__${ip_addr}_${port}.mark:存放同步的完成情况

| |

| |__一级目录:256 个存放数据文件的目录,如:00, 1F

| |__二级目录:256 个存放数据文件的目录

|__logs

|__storaged.log:storage server 日志文件

FastDFS 和其他文件存储的简单对比

FastDFS 和集中存储方式对比

指标FastDFSNFS集中存储设备如  NetApp、NAS

线性扩容性高差差

文件高并发访问性能高差一般

文件访问方式专用APIPOSIXPOSIX

硬件成本较低中等高

相同内容文件只保存一份支持不支持不支持

FastDFS 和 和 mogileFS 对比

指标FastDFSmogileFS

系统简洁性简洁  只有两个角色:tracker和storage一般  有三个角色:tracker、storage和存储文件信息的mysql db

系统性能很高(没有使用数据库,文件同步直接点对点,不经过tracker中转)高(使用mysql来存储文件索引信息,文件同步通过tracker调度和中转)

系统稳定性高(C语言开发,可以支持高并发和高负载)一般(Perl语言开发,高并发和高负载支持一般)

RAID方式分组(组内冗余),灵活性较大动态冗余,灵活性一般

通信协议专用协议,下载文件支持HTTPHTTP

技术文档较详细较少

文件附加属性(meta data)支持不支持

相同内容文件只保存一份支持不支持

下载文件时支持文件偏移量支持不支持

安装

安装FastDFS

安装FastDFS依赖

FastDFS是C语言开发的应用。安装必须使用make、cmake和gcc编译器。

yum install-ymakecmakegccgcc-c++

解压FastDFS核心库

libfastcommon是从FastDFS和FastDHT中提取出来的公共C函数库

unzip libfastcommon-1.0.43.zip-d/usr/local/fastdfs

编译安装

libfastcommon没有提供make命令安装文件。使用的是shell脚本执行编译和安装。shell脚本为 make.sh

编译

./make.sh

安装

./make.sh install

有固定的默认安装位置。在/usr/lib64 和/usr/include/fastcommon 两个目录中。

cd/usr/local/fastdfs/libfastcommon-1.0.43

./make.sh  

./make.sh install  

创建软连接

因为FastDFS主程序设置的lib目录是/usr/local/lib,所以需要创建软链接

ln-s/usr/lib64/libfastcommon.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

解压FastDFS并安装

解压

tar -zxf fastdfs-6.06.tar.gz -C /usr/local/fastdfs 

修改安装路径(可选)

vi /usr/local/fastdfs/FastDFS/make.sh 

TARGET_PREFIX=$DESTDIR/usr -> TARGET_PREFIX=$DESTDIR/usr/local 

编译安装

./make.sh 

./make.sh  install 

安装后,FastDFS主程序所在的位置是:

/usr/local/bin可执行文件所在位置。默认安装在/usr/bin 中。

/etc/fdfs配置文件所在位置。就是默认位置。

/usr/local/lib64主程序代码所在位置。默认在/usr/bin 中。

/usr/local/include/fastdfs包含的一些插件组所在位置。默认在/usr/include/fastdfs 中。

FastDFS安装后资源简介

服务脚本

/etc/init.d/目录中,脚本文件是 fdfs-storaged 和 fdfs-trackerd

配置文件模板

/etc/fdfs/目录中,配置文件是client.conf.sample 、storage.conf.sample 和tracker.conf.sample

tracker.conf.sample跟踪器服务配置文件模板

storage.conf.sample存储服务器配置文件模板

client.conf.sampleFastDFS 提供的命令行客户端配置文件模板。可以通过命令行测试FastDFS有效性。

内置命令

/usr/local/bin/目录中。命令有若干。可通过命令在控制台访问 FastDFS。

tracker基础配置

创建跟踪服务配置文件

FastDFS 提供了配置文件模板,可以根据模板创建需要使用的配置文件。

cd /etc/fdfs 

cp  tracker.conf.sample tracker.conf 

修改配置文件

tracker.conf 配置文件用于描述跟踪服务的行为,需要进行下述修改:

vim /etc/fdfs/tracker.conf 

port=22122  # 默认服务端口 

base_path=/home/yuqing/fastdfs -> base_path=/fastdfs/tracker(自定义目录) 

base_path是FastDFSTracker启动后使用的根目录。也就是data和logs所在位置。

创建自定义目录

为配置文件中定义的 base_path 变量创建对应的目录。

mkdir -p /fastdfs/tracker 

启动服务

vim /etc/init.d/fdfs_trackerd 

#将  PRG=/usr/bin/fdfs_trackerd 修改为 PRG=/usr/local/bin/fdfs_trackerd

/etc/init.d/fdfs_trackerd  start 

启动成功后,配置文件中base_path指向的目录中出现 FastDFS服务相关数据目录( data目录、logs 目录)

查看服务状态

/etc/init.d/fdfs_trackerd status 

ps aux | grep fdfs 

停止服务

/etc/init.d/fdfs_trackerd stop

重启服务

/etc/init.d/fdfs_trackerd restart 

设置开机自启

vi /etc/rc.d/rc.local 

新增内容 - /etc/init.d/fdfs_trackerd start 

storage基础配置

创建存储服务配置文件

FastDFS提供了配置文件模板,可以根据模板创建需要使用的配置文件。

cd /etc/fdfs 

cp storage.conf.sample storage.conf 

修改配置文件

storage.conf 配置文件用于描述存储服务的行为,需要进行下述修改:

vim /etc/fdfs/storage.conf 

base_path=/home/yuqing/fastdfs -> base_path=/fastdfs/storage/base (自定义目录) store_path0=/home/yuqing/fastdfs -> store_path0=/fastdfs/storage/store (自定义目录 ) tracker_server=192.168.2.109:22122 -> tracker_server=tracker服务IP:22122 

base_path基础路径。用于保存 storage server 基础数据内容和日志内容的目录。

store_path0存储路径。是用于保存 FastDFS 中存储文件的目录,就是要创建 256*256个子目录的位置。base_path 和 store_path0 可以使用同一个目录。

tracker_server跟踪服务器位置。就是跟踪服务器的 ip 和端口。

创建自定义迷路

mkdir -p /fastdfs/storage/base 

mkdir -p /fastdfs/storage/store 

启动服务

要求tracker服务必须已启动

vim /etc/init.d/fdfs_storaged 

将 PRG=/usr/bin/fdfs_storaged 修改为  PRG=/usr/local/bin/fdfs_storaged

/etc/init.d/fdfs_storaged  start

 启动成功后,配置文件中 base_path 指向的目录中出现 FastDFS 服务相关数据目录(data目录、logs 目录),配置文件中的store_path0指向的目录中同样出现 FastDFS 存储相关数据目录(data 目录)。其中$store_path0/data/目录中默认创建若干子孙目录(两级目录层级总计256*256 个目录),是用于存储具体文件数据的。

 Storage 服务器启动比较慢,因为第一次启动的时候,需要创建 256*256 个目录。

查看服务状态

/etc/init.d/fdfs_storaged status 

ps aux | grep fdfs 

停止服务

/etc/init.d/fdfs_storaged stop 

重启服务

/etc/init.d/fdfs_storaged restart

设置开机自启

vi /etc/rc.d/rc.local 

新增内容 - /etc/init.d/fdfs_storaged start 

因启动前提为 tracker 服务必须已启动,不推荐开启自启。

客户端基础配置

不是必须的。就是用于使用命令行测试 FastDFS 才需要配置的。

创建客户端配置文件

在tracker服务结点所在服务器中配置客户端。同样通过配置文件模板创建对应配置文件。

cd /etc/fdfs

cp client.conf.sample client.conf 

修改配置文件

client.conf 配置文件中主要描述客户端的行为,需要进行下述修改:

vim /etc/fdfs/client.conf 

base_path=/home/yuqing/fastdfs -> base_path=/fastdfs/client (自定义目录) tracker_server=192.168.2.109:22122 -> tracker_server=tracker 服务 IP:22122

base_path就是客户端命令行执行过程时临时数据存储位置。

创建自定义目录

mkdir -p /fastdfs/client 

控制台测试FastDFS

 命令所在:/usr/local/bin目录。(如果在安装FastDFS过程中,没有修改make.sh文件中的 TARGET_PREFIX 属性值,命令所在为/usr/bin目录)

上传文件

/usr/local/bin/fdfs_upload_file  /etc/fdfs/client.conf    /要上传的文件 

 上传结束后,返回 group1/M00/00/00/xxxxxxxxxx.xxx,检查 storage 服务结点中的$store_path0/data/00/00/目录中是否有上传的文件(一般情况上传的文件按顺序保存在$store_path0/data/00/00/目录中,不能完全保证 )。

 测试的上传文件结果:group1/M00/00/00/wKgKZF4dXl6AHrE5ACBzEWqC2Sc349.jpg

 卷名:group1

 文件名:M00/00/00/wKgKZF4dXl6AHrE5ACBzEWqC2Sc349.jpg

 其中M00是一个虚拟目录,相当于 windows 中的快捷方式,引用的是$store_path0/data目录。

删除文件

/usr/local/bin/fdfs_delete_file  /etc/fdfs/client.conf group1/M00/00/00/xxxxxxx.xxx 

删除结束后,检查$store_path0/data/00/00/目录中是否还有文件。

安装Nginx组件

 如果FastDFS中保存的是图片信息。希望在 WEB 应用中可以直接访问FastDFS中的图片进行显示。如果操作?

 安装 Nginx是为了WEB应用中可以使用 HTTP协议直接访问Storage 服务中存储的文件。在 storage 结点所在服务器安装 Nginx 组件。

 需要安装两部分内容。

Nginx 应用

在安装 nginx 应用的时候,同时要在 nginx 中增加一个 FastDFS 的组件Module。

解压fastdfs-nginx-module-1.22.tar.gz

tar -zxf fastdfs-nginx-module-1.22.tar.gz  -C /usr/local/fastdfs 

修改fastdfs-nginx-module-1.22.tar.gz源文件中的配置

此操作必须修改,否则Nginx编译会报错。

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

vim /usr/local/fastdfs/fastdfs-nginx-module-1.22/src/config 

参数是用于配置安装nginx中的FastDFS组件的时候,在什么位置查找FastDFS核心代码。

源数据:

ngx_addon_name=ngx_http_fastdfs_module

if test -n "${ngx_module_link}"; then

    ngx_module_type=HTTP

    ngx_module_name=$ngx_addon_name

    ngx_module_incs="/usr/local/include"

    ngx_module_libs="-lfastcommon -lfdfsclient"

    ngx_module_srcs="$ngx_addon_dir/ngx_http_fastdfs_module.c"

    ngx_module_deps=

    CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE='256*1024' -DFDFS_MOD_CONF_FILENAME='\"/etc/fdfs/mod_fastdfs.conf\"'"

    . auto/module

else

    HTTP_MODULES="$HTTP_MODULES ngx_http_fastdfs_module"

    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_fastdfs_module.c"

    CORE_INCS="$CORE_INCS /usr/local/include"

    CORE_LIBS="$CORE_LIBS -lfastcommon -lfdfsclient"

    CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE='256*1024' -DFDFS_MOD_CONF_FILENAME='\"/etc/fdfs/mod_fastdfs.conf\"'"

fi

修改后内容:(如果安装FastDFS时,没有修改make.sh文件,则改为:CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"

ngx_addon_name=ngx_http_fastdfs_module

if test -n "${ngx_module_link}"; then

    ngx_module_type=HTTP

    ngx_module_name=$ngx_addon_name

    ngx_module_incs="/usr/local/include/fastdfs /usr/include/fastcommon/"

    ngx_module_libs="-lfastcommon -lfdfsclient"

    ngx_module_srcs="$ngx_addon_dir/ngx_http_fastdfs_module.c"

    ngx_module_deps=

    CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE='256*1024' -DFDFS_MOD_CONF_FILENAME='\"/etc/fdfs/mod_fastdfs.conf\"'"

    . auto/module

else

    HTTP_MODULES="$HTTP_MODULES ngx_http_fastdfs_module"

    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_fastdfs_module.c"

    CORE_INCS="$CORE_INCS /usr/local/include/fastdfs /usr/include/fastcommon/"

    CORE_LIBS="$CORE_LIBS -lfastcommon -lfdfsclient"

    CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE='256*1024' -DFDFS_MOD_CONF_FILENAME='\"/etc/fdfs/mod_fastdfs.conf\"'"

fi

安装Nginx

安装依赖

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

解压Ngxin

tar -zxf nginx-1.16.1.tar.gz -C /usr/local/fastdfs/ 

配置Nginx安装信息

cd /usr/local/fastdfs/nginx-1.16.1/

mkdir -p /var/temp/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

--add-module 必须定义,此配置信息是用于指定安装 Nginx时需要加载的模块,如果未指定, Nginx安装过程不会加载fastdfs-nginx-module 模块,后续功能无法实现。

安装

make

make install 

配置 fastdfs-nginx-module 模块配置文件

复制配置文件/usr/local/fastdfs/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf到/etc/fdfs目录中

cp /usr/local/fastdfs/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf /etc/fdfs/ 

cd /etc/fdfs/ 

#修改配置文件 mod_fastdfs.conf 

vim mod_fastdfs.conf 

源配置:

connect_timeout=2 #连接超时时间,单位秒

tracker_server=tracker:22122 #tracker 服务结点

url_have_group_name = false #URL中是否包含group名称

store_path0=/home/yuqing/fastdfs # storage 服务结点的存储位置,与配置storage结点一致

参考修改值:

connect_timeout=10 

tracker_server=192.168.2.109:22122 

url_have_group_name = true 

store_path0=/fastdfs/storage/store 

提供 FastDFS需要的HTTP配置文件

复制 FastDFS安装包中的两个配置文件(http.conf和 mime.types)到/etc/fdfs 目录中

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

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

创建nginx启动需要的软连接

创建软连接

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

nginx启动后,会在默认的/usr/lib64目录中查找需要的so文件。如果在安装FastDFS时,修改了make.sh文件中的 TARGET_PREFIX参数,则必须创建此软连接

创建网络访问存储服务的软连接

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

 在上传文件到 FastDFS 后,FastDFS 会返回 group1/M00/00/00/xxxxxxxxxx.xxx。其中 group1是卷名,在mod_fastdfs.conf配置文件中已配置了url_have_group_name,以保证URL解析正确。而其中的 M00是FastDFS保存数据时使用的虚拟目录,需要将这个虚拟目录定位到真实数据目录上。

修改 nginx 配置文件

cd /usr/local/nginx/conf

vim nginx.conf 

参考修改配置:( 部分配置信息,不要完整复制 )

user root; # Nginx 需要访问 linux文件系统,必须有文件系统的权限。User root代表nginx 访问文件系统的权限是root用户权限。如果不开启权限,可能有404访问错误。

server{

listen 8888; # storage 配置中,有 http.server_port=8888 的配置信息,必须一致。配置文件是/etc/fdfs/storaged.conf

server_name localhost;

location ~/group([0-9])/M00{

    ngx_fastdfs_module;

  }

}

重启storage,启动nginx

/etc/init.d/fdfs_storaged restart

/usr/local/nginx/sbin/nginx

测试WEB访问存储服务中的文件

使用浏览器查看 FastDFS 中保存的文件:

http://ip:8888/group1/M00/00/00/xxxxxxx.xxx

测试上传的文件:group1/M00/00/00/wKgKZF4dasWAFxh7ACBzEWqC2Sc193.jpg

测试 WEB 访问地址:

http://192.168.10.100:8888/group1/M00/00/00/wKgKZF4dasWAFxh7ACBzEWqC2Sc193.jpg

Java API

依赖

<!--FastDFS依赖-->

<dependency>

  <groupId>org.csource</groupId>

  <artifactId>fastdfs-client-java</artifactId>

  <version>1.29-SNAPSHOT</version>

</dependency>

常用类

CLientGlobal

用于加载配置文件的公共客户端工具。

常用方法:

init(String conf_filename);根据配置文件路径及命名,加载配置文件并设置客户端公共参数,配置文件类型为 conf 文件。可以使用绝对路径或相对路径加载。

initByProperties(Properties props);根据 Properties 对象设置客户端公共参数。

注意:使用 conf 或 properties 进行客户端参数配置时,参数 key 命名不同。

TrackerClient

 跟踪器客户端类型。创建此类型对象时,需传递跟踪器组,就是跟踪器的访问地址信息。无参构造方法默认使用 ClientGlobal.g_tracker_group 常量作为跟踪器组来构造对象。

创建对象的方式为:

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

TrackerServer

 跟踪器服务类型。此类型的对象是通过跟踪器客户端对象构建的。实质上就是一个与FastDFS Tracker Server 的链接对象。是代码中与 Tracker Server 链接的工具。

构建对象的方式为:

trackerClient. getTrackerServer ();

StorageServer

 存储服务类型。此类型的对象是通过跟踪器客户端对象构建的。实质上就是一个与FastDFS Storage Server 的链接对象。是代码中与 StroageServer 链接的工具。获取的具体存储服务链接,是由 Tracker Server 分配的,所以构建存储服务对象时,需要依赖跟踪器服务对象。

构建对象的方式为:

trackerClient.getStoreStorage(trackerServer);

StorageClient

 存储客户端类型。此类型的对象是通过构造方法创建的。创建时,需传递跟踪服务对象和存储服务对象。此对象实质上就是一个访问 FastDFS Storage Server 的客户端对象,用于实现文件的读写操作。

创建对象的方式为:

new StorageClient(trackerServer, storageServer);

常用方法有:

upload_file(String local_filename, String file_ext_name, 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方法参数。

Demo

创建项目

添加依赖

<!--FastDFS依赖-->

<dependency>

  <groupId>org.csource</groupId>

  <artifactId>fastdfs-client-java</artifactId>

  <version>1.29-SNAPSHOT</version>

</dependency>

<!--thymeleaf 依赖-->

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

在resources目录下添加配置文件

fdfs_client.conf

注1:tracker_server指向您自己IP地址和端口,1-n个

注2:除了tracker_server,其它配置项都是可选的

#连接超时

connect_timeout = 2

#网络超时

network_timeout = 30

#编码格式

charset = UTF-8

#tracker端口号

http.tracker_http_port = 8080

#防盗链功能

http.anti_steal_token = no

#秘钥

http.secret_key = FastDFS1234567890

#tracker ip:端口号

tracker_server = 192.168.10.100: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

添加FastDFS文件对象

FastDFSFile.java

package com.xxxx.fastdfsdemo;

import java.util.Arrays;

/**

* 文件上传对象类

* @author zhoubin

* @since 1.0.0

*/

public class FastDFSFile {

    private String name;

    private byte[] content;

    private String ext;

    private String md5;

    private String author;

    private String height;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public byte[] getContent() {

        return content;

    }

    public void setContent(byte[] content) {

        this.content = content;

    }

    public String getExt() {

        return ext;

    }

    public void setExt(String ext) {

        this.ext = ext;

    }

    public String getMd5() {

        return md5;

    }

    public void setMd5(String md5) {

        this.md5 = md5;

    }

    public String getAuthor() {

        return author;

    }

    public void setAuthor(String author) {

        this.author = author;

    }

    public String getHeight() {

        return height;

    }

    public void setHeight(String height) {

        this.height = height;

    }

    public FastDFSFile() {

    }

    public FastDFSFile(String name, byte[] content, String ext) {

        this.name = name;

        this.content = content;

        this.ext = ext;

    }

    @Override

    public String toString() {

        return "FastDFSFile{" +

                "name='" + name + '\'' +

                ", content=" + Arrays.toString(content) +

                ", ext='" + ext + '\'' +

                ", md5='" + md5 + '\'' +

                ", author='" + author + '\'' +

                ", height='" + height + '\'' +

                '}';

    }

}

编写FastDFS工具类

FastDFSClient.java

package com.xxxx.fastdfsdemo;

import org.csource.common.NameValuePair;

import org.csource.fastdfs.ClientGlobal;

import org.csource.fastdfs.FileInfo;

import org.csource.fastdfs.StorageClient;

import org.csource.fastdfs.TrackerClient;

import org.csource.fastdfs.TrackerServer;

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 zhoubin

* @since 1.0.0

*/

public class FastDFSClient {

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

  //ClientGlobal.init 方法会读取配置文件,并初始化对应的属性。

  static{

      try {

        String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();

        ClientGlobal.init(filePath);

      } catch (Exception e) {

        logger.error("FastDFS Client Init Fail!",e);

      }

  }

  /**

    * 上传文件

    * @param file

    * @return

    */

  public static String[] upload(FastDFSFile file) {

      logger.info("File Name: " + file.getName() + "File Length:" + file.getContent().length);

      //文件属性信息

      NameValuePair[] meta_list = new NameValuePair[1];

      meta_list[0] = new NameValuePair("author", file.getAuthor());

      long startTime = System.currentTimeMillis();

      String[] uploadResults = null;

      StorageClient storageClient=null;

      try {

        //获取storage客户端

        storageClient = getStorageClient();

        //上传

        uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);

      } catch (IOException e) {

        logger.error("IO Exception when uploadind the file:" + file.getName(), e);

      } catch (Exception e) {

        logger.error("Non IO Exception when uploadind the file:" + file.getName(), e);

      }

      logger.info("upload_file time used:" + (System.currentTimeMillis() - startTime) + " ms");

      //验证上传结果

      if (uploadResults == null && storageClient!=null) {

        logger.error("upload file fail, error code:" + storageClient.getErrorCode());

      }

      //上传文件成功会返回 groupName。

      logger.info("upload file successfully!!!" + "group_name:" + uploadResults[0] + ", remoteFileName:" + " " + uploadResults[1]);

      return uploadResults;

  }

  /**

    * 获取文件信息

    * @param groupName

    * @param remoteFileName

    * @return

    */

  public static FileInfo getFile(String groupName, String remoteFileName) {

      try {

        StorageClient storageClient = getStorageClient();

        return storageClient.get_file_info(groupName, remoteFileName);

      } catch (IOException e) {

        logger.error("IO Exception: Get File from Fast DFS failed", e);

      } catch (Exception e) {

        logger.error("Non IO Exception: Get File from Fast DFS failed", e);

      }

      return null;

  }

  /**

    * 下载文件

    * @param groupName

    * @param remoteFileName

    * @return

    */

  public static InputStream downFile(String groupName, String remoteFileName) {

      try {

        StorageClient storageClient = getStorageClient();

        byte[] fileByte = storageClient.download_file(groupName, remoteFileName);

        InputStream ins = new ByteArrayInputStream(fileByte);

        return ins;

      } catch (IOException e) {

        logger.error("IO Exception: Get File from Fast DFS failed", e);

      } catch (Exception e) {

        logger.error("Non IO Exception: Get File from Fast DFS failed", e);

      }

      return null;

  }

  /**

    * 删除文件

    * @param groupName

    * @param remoteFileName

    * @throws Exception

    */

  public static void deleteFile(String groupName, String remoteFileName)

        throws Exception {

      StorageClient storageClient = getStorageClient();

      int i = storageClient.delete_file(groupName, remoteFileName);

      logger.info("delete file successfully!!!" + i);

  }

  /**

    * 生成Storage客户端

    * @return

    * @throws IOException

    */

  private static StorageClient getStorageClient() throws IOException {

      TrackerServer trackerServer = getTrackerServer();

      StorageClient storageClient = new StorageClient(trackerServer, null);

      return  storageClient;

  }

  /**

    * 生成Tracker服务器端

    * @return

    * @throws IOException

    */

  private static TrackerServer getTrackerServer() throws IOException {

      TrackerClient trackerClient = new TrackerClient();

      TrackerServer trackerServer = trackerClient.getTrackerServer();

      return  trackerServer;

  }

  /**

    * 获取文件路径

    * @return

    * @throws IOException

    */

  public static String getTrackerUrl() throws Exception {

    TrackerClient trackerClient = new TrackerClient();

    TrackerServer trackerServer = trackerClient.getTrackerServer();

    StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer);

    return "http://"+storeStorage.getInetSocketAddress().getHostString()+":8888/";

  }

}

编写Controller

package com.xxxx.fastdfsdemo;

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;

/**

* 文件上传Controller

* @author zhoubin

*/

@Controller

public class UploadController {

    private static Logger logger = LoggerFactory.getLogger(UploadController.class);

    /**

    * 页面跳转

    * @return

    */

    @GetMapping("/")

    public String index() {

        return "upload";

    }

    /**

    * 上传文件

    * @param file

    * @param redirectAttributes

    * @return

    */

    @PostMapping("/upload")

    public String singleFileUpload(@RequestParam("file") MultipartFile file,

                                  RedirectAttributes redirectAttributes) {

        if (file.isEmpty()) {

            redirectAttributes.addFlashAttribute("message", "Please select a file to upload");

            return "redirect:uploadStatus";

        }

        try {

            // 上传文件拿到返回的文件路径

            String path=saveFile(file);

            redirectAttributes.addFlashAttribute("message",

                    "You successfully uploaded '" + file.getOriginalFilename() + "'");

            redirectAttributes.addFlashAttribute("path",

                    "file path url '" + path + "'");

        } catch (Exception e) {

            logger.error("upload file failed",e);

        }

        return "redirect:/uploadStatus";

    }

    /**

    * 页面跳转

    * @return

    */

    @GetMapping("/uploadStatus")

    public String uploadStatus() {

        return "uploadStatus";

    }

    /**

    * 上传文件

    * @param multipartFile

    * @return

    * @throws IOException

    */

    public String saveFile(MultipartFile multipartFile) throws Exception {

        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);

        try {

            //上传文件

            fileAbsolutePath = FastDFSClient.upload(file);

        } catch (Exception e) {

            logger.error("upload file Exception!",e);

        }

        if (fileAbsolutePath==null) {

            logger.error("upload file failed,please upload again!");

        }

        String path=FastDFSClient.getTrackerUrl()+fileAbsolutePath[0]+ "/"+fileAbsolutePath[1];

        return path;

    }

}

添加前端页面

upload.html

<!DOCTYPE html>

<html>

<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="Submit" />

</form>

</body>

</html>

uploadStatus.html

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<body>

<h1>Spring Boot - Upload Status</h1>

<div th:if="${message}">

    <h2 th:text="${message}"/>

</div>

<div th:if="${path}">

    <h2 th:text="${path}"/>

</div>

</body>

</html>

测试

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349