分布式RPC调用和分布式文件存储_FastDFS

FastDFS

一、 简介

技术论坛: http://bbs.chinaunix.net/forum-240-1.html
资源地址: https://sourceforge.net/projects/fastdfs/
源码资源: https://github.com/happyfish100

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

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

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

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

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

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

在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增 服务器切换到线上提供服务。

当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将 它们配置为一个新的卷,这样就扩大了存储系统的容量。

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

1 架构图

在这里插入图片描述

2 上传流程

在这里插入图片描述
client 询问 tracker 上传到的 storage,不需要附加参数;

tracker 返回一台可用的 storage;

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

3 下载流程

在这里插入图片描述
client 询问 tracker 下载文件的 storage,参数为文件标识(组名和文件名);

tracker 返回一台可用的 storage;

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

4 术语简介

Tracker Server:跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。记录 storage server 的状态,是连接 Client 和 Storage server 的枢纽。
Storage Server:存储服务器,文件和 meta data 都保存到存储服务器上
group:组,也称为卷。同组内服务器上的文件是完全相同的
文件标识:包括两部分:组名和文件名(包含路径)
meta data:文件相关属性,键值对(Key Value Pair)方式,如:width=1024,heigth=768

5 同步机制

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

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

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

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

6 FastDFS 运行时目录结构

6.1 Tracker Server 目录

在这里插入图片描述

6.2 Storage Server 目录

在这里插入图片描述

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

7.1 FastDFS 和集中存储方式对比

在这里插入图片描述

7.2 FastDFS 和 mogileFS 对比

在这里插入图片描述
在这里插入图片描述

二、 安装

准备两个 Linux 服务器。两个服务器分别作为 tracker 服务器(跟踪服务器)和 storage 服务器(存储服务器)。
Tracker 服务器 : 192.168.2.109
Storage 服务器 : 192.168.2.110
以上环境中,所有的安装包都存放在/root/upload目录中。解压后的安装包根据具体命 令查看。(/usr/local/fastdfs

1 核心安装

Tracker Server 和 Storage Server 完全一致的安装。
安装 FastDFS 5.08 版本

1.1安装 FastDFS 依赖

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

1.2解压 FastDFS 核心库

libfastcommon 是从 FastDFS 和 FastDHT 中提取出来的公共 C 函数库
cd /root/upload
unzip libfastcommon-master.zip -d /usr/local/fastdfs

1.3进入解压后的目录

cd /usr/local/fastdfs/libfastcommon-master

1.4编译安装

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

编译
./make.sh
安装
./make.sh install
有固定的默认安装位置。在/usr/lib64/usr/include/fastcommon两个目录中。

1.5创建软连接

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

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

1.6解压 FastDFS 主程序

本案例使用 5.08 版本 FastDFS。
cd /root/upload
tar -zxf FastDFS_v5.08.tar.gz -C /usr/local/fastdfs

1.7进入 FastDFS 主程序源码目录

cd /usr/local/fastdfs/FastDFS

1.8修改安装路径

此操作可选。在集群环境中使用默认安装路径安装。两种安装方式都尝试一下。

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

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

1.9编译安装

 ./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 中。

1.10 FastDFS 安装后资源简介

1.10.1 服务脚本

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

ls /etc/init.d/ | grep fdfs 

1.10.2 配置文件模板

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

ls /etc/fdfs/ 
tracker.conf.sample - 跟踪器服务配置文件模板 
storage.conf.sample - 存储服务器配置文件模板 
client.conf.sample - FastDFS 提供的命令行客户端配置文件模板。可以通过命令行测试 FastDFS 有效性。

1.10.3 内置命令

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

 ls /usr/local/bin/ | grep fdfs

在这里插入图片描述
以上为通用安装,FastDFS 的跟踪服务和存储服务是通过配置实现的。后续内容为跟 踪服务和存储服务的具体配置。

2 tracker 基础配置

只在 tracker server 节点中配置。192.168.2.109

2.1创建跟踪服务配置文件

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

cd /etc/fdfs 
cp tracker.conf.sample tracker.conf

2.2修改配置文件

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

vi /etc/fdfs/tracker.conf 
port=22122 # 默认服务端口 
base_path=/home/yuqing/fastdfs -> base_path=/fastdfs/tracker(自定义目录)
base_path 是 FastDFSTracker 启动后使用的根目录。也就是 data 和 logs 所在位置。

2.3创建自定义目录

为配置文件中定义的 base_path 变量创建对应的目录。 
mkdir -p /fastdfs/tracker

2.4修改启动服务脚本

vi /etc/init.d/fdfs_trackerd 
将 PRG=/usr/bin/fdfs_trackerd 修改为 PRG=/usr/local/bin/fdfs_trackerd

2.5启动服务

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

2.6查看服务状态

/etc/init.d/fdfs_trackerd status 
ps aux | grep fdfs

2.7停止服务

/etc/init.d/fdfs_trackerd stop 

2.8重启服务

/etc/init.d/fdfs_trackerd restart

2.9设置开机自启

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

3 storage 基础配置

只在 storage server 中配置。192.168.2.100

3.1创建存储服务配置文件

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

cd /etc/fdfs 
cp storage.conf.sample storage.conf

3.2修改配置文件

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

vi /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 和端口。

3.3创建自定义目录

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

3.4修改服务脚本

vi /etc/init.d/fdfs_storaged 
将 PRG=/usr/bin/fdfs_storaged 修改为 PRG=/usr/local/bin/fdfs_storaged

3.5启动服务(要求 tracker 服务必须已启动)

/etc/init.d/fdfs_storaged start
启动成功后,配置文件中 base_path 指向的目录中出现 FastDFS 服务相关数据目录(data 目录、logs 目录),配置文件中的 store_path0 指向的目录中同样出现 FastDFS 存储相关数 据录(data 目录)。其中$store_path0/data/目录中默认创建若干子孙目录(两级目录层级总计 256*256个目录),是用于存储具体文件数据的。 Storage 服务器启动比较慢,因为第一次启动的时候,需要创建 256*256 个目录。

3.6查看服务状态

 /etc/init.d/fdfs_storaged status
  ps aux | grep fdfs 

3.7停止服务

  /etc/init.d/fdfs_storaged stop 

3.8重启服务

  /etc/init.d/fdfs_storaged restart 

3.9设置开机自启

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

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

4 客户端基础配置

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

4.1创建客户端配置文件

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

cd /etc/fdfs 
cp client.conf.sample client.conf

4.2修改配置文件

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

vi /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 - 就是客户端命令行执行过程时临时数据存储位置。

4.3创建自定义目录

mkdir -p /fastdfs/client

5 控制台测试 FastDFS

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

5.1上传文件

/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/wKgCbltTYaeACDWgAAVCLEPUQWI.tar.gz 
  卷名 : group1 
  文件名 : M00/00/00/wKgCbltTYaeACDWgAAVCLEPUQWI.tar.gz 
  其中 M00 是一个虚拟目录,相当于 windows 中的快捷方式,引用的是$store_path0/data 目录。

5.2删除文件

 /usr/local/bin/fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/xxxxxxx.xxx 
 删除结束后,检查$store_path0/data/00/00/目录中是否还有文件。

6 安装 Nginx 组件

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

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

需要安装两部分内容。

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

6.1解压 fastdfs-nginx-module_v1.16.tar.gz

tar -zxf fastdfs-nginx-module_v1.16.tar.gz -C /usr/local/fastdfs

6.2修改 fastdfs-nginx-module_v1.16.tar.gz 源文件中的配置

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

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

 vi /usr/local/fastdfs/fastdfs-nginx-module/src/config 
参数是用于配置安装 nginx 中的 FastDFS 组件的时候,在什么位置查找 FastDFS 核心代 码。

源数据:

ngx_addon_name=ngx_http_fastdfs_module 
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/local/include/fastcommon/" 

CORE_LIBS="$CORE_LIBS -L/usr/local/lib -lfastcommon -lfdfsclient" 
CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 
-DFDFS_OUTPUT_CHUNK_SIZE='256*1024' 
-DFDFS_MOD_CONF_FILENAME='\"/etc/fdfs/mod_fastdfs.conf\"'"

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

ngx_addon_name=ngx_http_fastdfs_module 
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 -L/usr/local/lib -lfastcommon -lfdfsclient" 
CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 
-DFDFS_OUTPUT_CHUNK_SIZE='256*1024' 
-DFDFS_MOD_CONF_FILENAME='\"/etc/fdfs/mod_fastdfs.conf\"'"

6.3安装 Nginx

6.3.1安装 Nginx 需要的依赖

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

6.3.2使用 SFTP 上传文件到 Linux

进入到本地的应用所在目录。
cd /xxxx 目录
sftp 192.168.2.110
输入用户名和密码。使用哪一个用户登录,上传的文件就在该用户对应的主目录下。如: root 用户,上传的文件在/root 目录中。

上传的命令:
put 文件名
下载的命令:
get 文件名

6.3.3解压 Nginx

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

6.3.4进入 Nginx 源码目录

cd /usr/local/fastdfs/nginx-1.8.0/

6.3.5配置 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/src

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

6.3.6创建目录

Nginx 运行时需要创建若干临时文件,如果默认安装不需创建此目录。
mkdir -p /var/temp/nginx

6.3.7编译安装

  make 
  make install

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

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

cd /etc/fdfs/ 
修改配置文件 mod_fastdfs.conf 
vi 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

6.3.9提供 FastDFS 需要的 HTTP 配置文件

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

cp /usr/local/fastdfs/FastDFS/conf/http.conf /etc/fdfs/ 
cp /usr/local/fastdfs/FastDFS/conf/mime.types /etc/fdfs/

6.3.10 创建 nginx 启动需要的软连接

创建软连接 
ln -s /usr/local/lib64/libfdfsclient.so /usr/lib64/libfdfsclient.so 

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

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

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 保存数据时使用的虚拟目录,需要将这个虚拟目录定 位到真实数据目录上。

6.3.12 修改 nginx 配置文件

cd /usr/local/nginx/conf 
vi 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; 
 			} 
 	}

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

 使用浏览器查看 FastDFS 中保存的文件: 
 http://ip:8888/group1/M00/00/00/x xxxxxx.xxx 
 
 测试上传的文件: group1/M00/00/00/wKgCbltTmv-ASTG2AAAmTx4ns0s172.jpg 
 测试 WEB 访问地址: 
 http://192.168.2.110:8888/group1/M00/00/00/wKgCbltTmv-ASTG2AAAmTx4ns0s172.jpg

三、 Java 客户端 API 简单应用

1 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjsxt</groupId>
    <artifactId>fdfs</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>cn.bestwu</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

</project>

2 创建客户端

2.1根据 conf 文件创建客户端

2.1.1 conf 配置文件

# 配置连接超时
connect_timeout=10
# 网络工作的超时, 一次连接的超时时长。
network_timeout=30
# 连接中的使用字符集
charset=UTF-8
# tracker的http协议端口,和FastDFS中的tracker服务的配置文件tracker.conf中的http.server_port一致
http.tracker_http_port=8080

# tracker服务器的所在地址。 就是IP:port
tracker_server=192.168.89.152:22122

2.1.2代码

package com.bjsxt.fdfs.test;

import org.csource.common.MyException;
import org.csource.fastdfs.*;

import java.io.IOException;

public class TestInitClient {
    
    

    public static void main(String[] args) {
    
    
        // 创建FastDFS  Java 客户端。
        try {
    
    
            // 加载客户端配置文件,就是刚定义的conf配置文件。
            ClientGlobal.init("src/main/resources/test/fdfs.conf");

            // 创建Tracker服务器的客户端连接对象。 无参构造其中默认会调用一个有参构造
            // new TrackerClient(TrackerGlobal.g_tracker_group), 绑定tracker服务器的IP地址和端口。
            // 绑定配置文件中的tracker_server属性信息。
            TrackerClient trackerClient = new TrackerClient();
            // 通过Tracker客户端对象,获取Tracker服务端对象。
            TrackerServer trackerServer = trackerClient.getConnection();
            // 通过Tracker客户端和服务端对象,获取Storage服务端对象。
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            // 通过Tracker服务端对象和Storage服务端对象,来创建Storage客户端访问对象,实现文件的上传和下载。
            // 最终使用的,用于文件处理的对象。
            StorageClient storageClient = new StorageClient(trackerServer, storageServer);

            System.out.println("trackerClient : " + trackerClient);
            System.out.println("trackerServer : " + trackerServer);
            System.out.println("storageServer : " + storageServer);
            System.out.println("storageClient : " + storageClient);
        }catch(IOException e1){
    
     // 配置文件找不到之类的 IO异常。
            e1.printStackTrace();
        }catch(MyException e2){
    
     // FastDFS 的java客户端API定义的自定义异常。
            // 如:tracker服务器连接错误, Storage服务器连接错误
            e2.printStackTrace();
        }
    }

}

在这里插入图片描述

2.2根据 properties 文件创建客户端

2.2.1properties 配置文件

# 连接超时
fastdfs.connect_timeout_in_seconds=10
# 网络超时
fastdfs.network_timeout_in_seconds=30
# 连接的字符集
fastdfs.charset=UTF-8
# tracker服务器的配置文件中的http端口配置
fastdfs.http_tracker_http_port=8080

# tracker服务器的IP:端口
fastdfs.tracker_servers=192.168.89.152:22122

2.2.2代码

package com.bjsxt.fdfs.test;

import org.csource.common.MyException;
import org.csource.fastdfs.*;

import java.io.IOException;
import java.util.Properties;

public class TestInitClientByProperties {
    
    
    public static void main(String[] args) {
    
    
        // 加载properties配置文件
        Properties properties = new Properties();
        try {
    
    
            properties.load(TestInitClientByProperties.class.getClassLoader().getResourceAsStream("test/fdfs.properties"));
        }catch (IOException e){
    
    
            e.printStackTrace();
            System.out.println("Properties配置文件加载错误");
            return;
        }

        try{
    
    
            // 通过ClientGlobal对象加载配置内容
            ClientGlobal.initByProperties(properties);

            TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
            TrackerServer trackerServer = trackerClient.getConnection();
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            StorageClient storageClient = new StorageClient(trackerServer, storageServer);

            System.out.println("trackerClient : " + trackerClient);
            System.out.println("trackerServer : " + trackerServer);
            System.out.println("storageServer : " + storageServer);
            System.out.println("storageClient : " + storageClient);
        }catch(IOException e1){
    
    
            e1.printStackTrace();
        }catch(MyException e2){
    
    
            e2.printStackTrace();
        }
    }
}

在这里插入图片描述

3 定义 FastDFS 工具类

package com.bjsxt.fdfs.test;

import org.csource.fastdfs.*;

public class FastDFSClientUtils {
    
    
    // 定义FastDFS配置文件地址路径
    private static final String configFile = "src/main/resources/test/fdfs.conf";
    // 要使用的FastDFS客户端对象。
    private static StorageClient storageClient;
    private static TrackerClient trackerClient;
    private static TrackerServer trackerServer;
    private static StorageServer storageServer;

    static{
    
    
        // 初始化代码块, 初始化需要使用的客户端对象。
        try {
    
    
            ClientGlobal.init(configFile);
            trackerClient = new TrackerClient();
            trackerServer = trackerClient.getConnection();
            storageServer = trackerClient.getStoreStorage(trackerServer);
            storageClient = new StorageClient(trackerServer, storageServer);
        }catch(Exception e){
    
    
            e.printStackTrace();
            // 初始化错误。
            throw new ExceptionInInitializerError(e);
        }
    }

    // 获取Storage客户端对象的方法。 工具方法。
    public static StorageClient getStorageClient(){
    
    
        return storageClient;
    }

    public static void main(String[] args) {
    
    
        StorageClient storageClient = FastDFSClientUtils.getStorageClient();
        System.out.println("storageClient : " + storageClient);
    }
}

4 文件上传

4.1代码

package com.bjsxt.fdfs.test;

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.StorageClient;

import java.io.IOException;

// 文件上传到FastDFS
public class TestUpload {
    
    

    public static void main(String[] args) {
    
    
        // 获取Storage客户端对象
        StorageClient storageClient = FastDFSClientUtils.getStorageClient();

        // 准备上传文件需要的信息
        String local_file = "testFile/1.jpg";
        String ext_file_name = "jpg";
        NameValuePair[] meta_list = new NameValuePair[]{
    
    
                new NameValuePair("fileName", "1.jpg"),
                new NameValuePair("uploadUser", "bjsxt")
        };

        // 上传文件到FastDFS
        /*
         * String[] upload_file(String local_file, String ext_file_name, NameValuePair[] meta_list);
         * local_file - 要上传的本地文件的地址
         * ext_file_name - 要上传的文件的后缀名。FastDFS使用uuid管理上传的文件命名,需要提供文件的后缀名。
         * meta_list - 要上传的文件的元数据。如:文件的原始名称,文件的大小,文件的创建者等。
         *   可有可无的,如果不需要FastDFS记录,则传递null即可。
         *
         * 返回值:返回上传后的文件在FastDFS中的卷名和文件名。 如:group1 , 00/00/xxxxx.jpg
         */
        String[] fileIds = null;
        try{
    
    
            fileIds = storageClient.upload_file(local_file, ext_file_name, meta_list);
        }catch(IOException e){
    
    
            e.printStackTrace();
        }catch(MyException e){
    
    
            e.printStackTrace();
        }

        // 解析上传的结果
        System.out.println("返回字符串数组长度 - " + fileIds.length);
        System.out.println("0下标位置 - " + fileIds[0]);
        System.out.println("1下标位置 - " + fileIds[1]);

    }

}

4.2结果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

5 文件下载

5.1代码

package com.bjsxt.fdfs.test;

import org.apache.commons.io.IOUtils;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.StorageClient;

import java.io.FileOutputStream;
import java.io.IOException;

// 下载文件
public class TestDownload {
    
    
    public static void main(String[] args) {
    
    
        // 获取Storage客户端对象
        StorageClient storageClient = FastDFSClientUtils.getStorageClient();

        // 准备下载文件的信息资源
        String groupName = "group1";
        String remoteFileName = "M00/00/00/wKhZmV2m1tiAFtwTAADW-MWxcrw364.jpg";

        // 下载文件
        /*
         * byte[] download_file(String group_name, String remote_file_name);
         * group_name - 要下载的文件的卷名
         * remote_file_name - 要下载的文件的远程名称,就是FastDFS服务器中的文件名称。
         * 返回值:就是要下载的文件的字节数组。
         */
        byte[] datas = null;
        try{
    
    
            datas = storageClient.download_file(groupName,remoteFileName);
        }catch (IOException e){
    
    
            e.printStackTrace();
        }catch (MyException e){
    
    
            e.printStackTrace();
        }

        // 查询要下载的文件的元数据,获取文件的原始名称,作为下载文件的文件名。
        /*
         * NameValuePair[] get_metadata(String group_name, String remote_file_name)
         * group_name - 要获取元数据的文件的卷名
         * remote_file_name - 要获取元数据的文件的远程文件名
         * 返回值:文件的元数据数组。
         */
        NameValuePair[] metaList = null;
        try{
    
    
            metaList = storageClient.get_metadata(groupName, remoteFileName);
        }catch(IOException e){
    
    
            e.printStackTrace();
        }catch (MyException e){
    
    
            e.printStackTrace();
        }

        String fileName = "";
        for(NameValuePair nvp : metaList){
    
    
            if(nvp.getName().equals("fileName")){
    
    
                fileName = nvp.getValue();
            }
        }

        // 处理下载后的数据信息
        try{
    
    
            IOUtils.write(datas, new FileOutputStream("downloadFile/"+fileName));
        }catch(IOException e){
    
    
            e.printStackTrace();
        }

        System.out.println("文件下载成功,下载文件名称是:" + fileName);
    }
}

5.2结果

在这里插入图片描述
在这里插入图片描述

6 删除文件

6.1代码

package com.bjsxt.fdfs.test;

import org.csource.common.MyException;
import org.csource.fastdfs.StorageClient;

import java.io.IOException;

// 删除文件
public class TestDelete {
    
    
    public static void main(String[] args) {
    
    
        // 获取Storage客户端对象
        StorageClient storageClient = FastDFSClientUtils.getStorageClient();

        // 准备要删除的文件的信息
        String groupName = "group1";
        String remoteFileName = "M00/00/00/wKhZmV2m1tiAFtwTAADW-MWxcrw364.jpg";

        // 删除文件
        /*
         * int delete_file(String group_name, String remote_file_name)
         * group_name - 要删除的文件的卷名
         * remote_file_name - 要删除的文件的文件名,FastDFS中的文件名。
         * 返回值: 是否删除文件成功。 0 代表删除成功, 其他代表删除失败。
         */
        int flag = 0;
        try{
    
    
            flag = storageClient.delete_file(groupName, remoteFileName);
        }catch(IOException e){
    
    
            e.printStackTrace();
        }catch (MyException e){
    
    
            e.printStackTrace();
        }

        // 处理删除结果
        System.out.println(flag == 0 ? "删除文件成功" : "删除文件失败");
    }
}

6.2结果

在这里插入图片描述

四、 WEB 应用中使用 FastDFS

1 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjsxt</groupId>
    <artifactId>fdfs</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>cn.bestwu</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!-- mybatis整合spring插件 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- mysql数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>4.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                </plugin>
            </plugins>
        </pluginManagement>

        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <port>80</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2 数据库建模

drop table if exists t_files; 
create table t_files( 
	id bigint not null auto_increment, 
	file_name varchar(255), -- 文件的原始名称 
	group_name varchar(32), -- 文件在 FastDFS 中的卷名 
	remote_file_name varchar(255), -- 文件在 FastDFS 中的文件名, UUID 
	file_path varchar(255), -- 文件在 FastDFS 中的路径, 就是卷名+远程文件名 
	primary key(id) );

3 FastDFS 配置

# 配置连接超时
connect_timeout=10
# 网络工作的超时, 一次连接的超时时长。
network_timeout=30
# 连接中的使用字符集
charset=UTF-8
# tracker的http协议端口,和FastDFS中的tracker服务的配置文件tracker.conf中的http.server_port一致
http.tracker_http_port=8080

# tracker服务器的所在地址。 就是IP:port
tracker_server=192.168.89.152:22122

4 Spring 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <mvc:annotation-driven/>

    <context:component-scan base-package="com.bjsxt"></context:component-scan>

    <!-- 文件上传的转换器 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 限制文件上传大小 -->
        <property name="maxUploadSize" value="2048000" />
        <!-- 内存缓存大小 -->
        <property name="maxInMemorySize" value="2048" />
        <!-- 处理文本文件的字符集 -->
        <property name="defaultEncoding" value="UTF-8" />
    </bean>

    <!-- 连接池数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/fdfsfile" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>

    <!-- 会话工厂 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations">
            <array>
                <value>classpath:com/bjsxt/mapper/*.xml</value>
            </array>
        </property>
    </bean>

    <!-- Mapper工厂 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.bjsxt.mapper" />
    </bean>

    <!-- 事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
            rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>

    <!-- 事务切面 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution( * com.bjsxt.service.*.*(..))"/>
    </aop:config>

</beans>

5 web.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">

    <welcome-file-list>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>
    
    <filter>
        <filter-name>charsetFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>charsetFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
</web-app>

6 FastDFS 工具类定义

package com.bjsxt.fdfs;

import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

// FastDFS的工具类
public class FastDFSCommonsUtils {
    
    
    private static StorageClient storageClient =  null;
    static{
    
    
        try{
    
    
            ClientGlobal.init(
                    Thread.currentThread().getContextClassLoader().getResource("").getPath()
                            +"test/fdfs.conf");
            TrackerClient trackerClient = new TrackerClient();
            TrackerServer trackerServer = trackerClient.getConnection();
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            storageClient = new StorageClient(trackerServer, storageServer);
        }catch(Exception e){
    
    
            e.printStackTrace();
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 上传文件的方法
     * @param inputStream 要上传的文件的输入流,用于读取文件内容
     * @param fileName 要上传的文件的原始文件名
     * @return 上传的结果
     */
    public static String[] upload(InputStream inputStream, String fileName){
    
    
        try {
    
    
            NameValuePair[] metaList = new NameValuePair[]{
    
    
                    new NameValuePair("fileName", fileName)
            };
            String extFileName = fileName.substring(fileName.lastIndexOf(".") + 1);
            byte[] datas = new byte[inputStream.available()];
            inputStream.read(datas);
            String[] result = storageClient.upload_file(datas, extFileName, metaList);
            return result;
        }catch(Exception e){
    
    
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 下载文件
     * @return 要下载的文件的输入流
     */
    public static InputStream download(String groupName, String remoteFileName){
    
    
        try{
    
    
            byte[] datas = storageClient.download_file(groupName, remoteFileName);
            return new ByteArrayInputStream(datas);
        }catch(Exception e){
    
    
            e.printStackTrace();
            return null;
        }
    }
}

7 实体类型

package com.bjsxt.entity;

import java.io.Serializable;
import java.util.Objects;

// 文件相关信息的实体类
public class FileInfo implements Serializable {
    
    
    private Long id;
    private String fileName;
    private String groupName;
    private String remoteFileName;
    private String filePath;

    @Override
    public boolean equals(Object o) {
    
    
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        FileInfo fileInfo = (FileInfo) o;
        return Objects.equals(id, fileInfo.id) &&
                Objects.equals(fileName, fileInfo.fileName) &&
                Objects.equals(groupName, fileInfo.groupName) &&
                Objects.equals(remoteFileName, fileInfo.remoteFileName) &&
                Objects.equals(filePath, fileInfo.filePath);
    }

    @Override
    public int hashCode() {
    
    
        return Objects.hash(id, fileName, groupName, remoteFileName, filePath);
    }

    public Long getId() {
    
    
        return id;
    }

    public void setId(Long id) {
    
    
        this.id = id;
    }

    public String getFileName() {
    
    
        return fileName;
    }

    public void setFileName(String fileName) {
    
    
        this.fileName = fileName;
    }

    public String getGroupName() {
    
    
        return groupName;
    }

    public void setGroupName(String groupName) {
    
    
        this.groupName = groupName;
    }

    public String getRemoteFileName() {
    
    
        return remoteFileName;
    }

    public void setRemoteFileName(String remoteFileName) {
    
    
        this.remoteFileName = remoteFileName;
    }

    public String getFilePath() {
    
    
        return filePath;
    }

    public void setFilePath(String filePath) {
    
    
        this.filePath = filePath;
    }
}

8 Mapper 接口定义

package com.bjsxt.mapper;

import com.bjsxt.entity.FileInfo;

import java.util.List;

// 文件数据的数据访问接口
public interface FileInfoMapper {
    
    
    /**
     * 新增文件相关数据到数据库
     * @param fileInfo 要新增的数据
     */
    void insertFileInfo(FileInfo fileInfo);

    /**
     * 查询所有的文件相关数据
     * @return
     */
    List<FileInfo> selectFileInfos();

    /**
     * 主键查询详情
     * @param id
     * @return
     */
    FileInfo selectById(Long id);
}

9 Mapper 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjsxt.mapper.FileInfoMapper">
    <insert id="insertFileInfo">
        insert into t_files(id, file_name, group_name, remote_file_name, file_path)
        values(default, #{fileName}, #{groupName}, #{remoteFileName}, #{filePath})
    </insert>
    <select id="selectFileInfos" resultType="com.bjsxt.entity.FileInfo">
        select id, file_name as fileName, group_name as groupName, remote_file_name as remoteFileName,
            file_path as filePath
        from t_files
    </select>
    <select id="selectById" resultType="com.bjsxt.entity.FileInfo">
        select id, file_name as fileName, group_name as groupName, remote_file_name as remoteFileName,
            file_path as filePath
        from t_files
        where id = #{id}
    </select>
</mapper>

10 服务接口

package com.bjsxt.service;

import com.bjsxt.entity.FileInfo;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.List;

// 文件处理服务接口
public interface FileService {
    
    
    /**
     * 上传文件到FastDFS中,把文件的一些相关数据保存到数据库中。
     * @param file - 要上传的文件
     * @return true - 成功 ; false - 失败
     */
    boolean uploadFile(MultipartFile file);

    /**
     * 查询所有的可下载的文件的数据
     * @return 数据库中的数据集合
     */
    List<FileInfo> getFileInfos();

    /**
     * 获取要下载的文件
     * @param fileInfoId 文件在数据库中的主键
     * @return 要下载的文件的输入流
     */
    InputStream getFile(Long fileInfoId);

    FileInfo getFileInfoById(Long fileInfoId);
}

11 服务实现

package com.bjsxt.service.impl;

import com.bjsxt.entity.FileInfo;
import com.bjsxt.fdfs.FastDFSCommonsUtils;
import com.bjsxt.mapper.FileInfoMapper;
import com.bjsxt.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.Arrays;
import java.util.List;

// 文件处理服务实现
@Service
public class FileServiceImpl implements FileService {
    
    
    @Autowired
    private FileInfoMapper fileInfoMapper;
    @Override
    public boolean uploadFile(MultipartFile file) {
    
    
        try {
    
    
            String[] result = FastDFSCommonsUtils.upload(file.getInputStream(), file.getOriginalFilename());
            if(result == null){
    
    
                // 上传失败
                return false;
            }
            // System.out.println(Arrays.toString(result));
            // 将文件相关信息保存到数据库
            FileInfo fileInfo = new FileInfo();
            fileInfo.setFileName(file.getOriginalFilename());
            fileInfo.setGroupName(result[0]);
            fileInfo.setRemoteFileName(result[1]);
            fileInfo.setFilePath(result[0]+"/"+result[1]);
            this.fileInfoMapper.insertFileInfo(fileInfo);
            return true;
        }catch (Exception e){
    
     // 上传失败
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public List<FileInfo> getFileInfos() {
    
    
        return this.fileInfoMapper.selectFileInfos();
    }

    @Override
    public InputStream getFile(Long fileInfoId) {
    
    
        // 访问数据库,获取要下载的文件的详情信息
        FileInfo fileInfo = this.fileInfoMapper.selectById(fileInfoId);

        // 访问FastDFS,获取要下载的文件的具体内容
        InputStream inputStream = FastDFSCommonsUtils.download(fileInfo.getGroupName(), fileInfo.getRemoteFileName());

        // 返回
        return inputStream;
    }

    @Override
    public FileInfo getFileInfoById(Long fileInfoId) {
    
    
        return this.fileInfoMapper.selectById(fileInfoId);
    }
}

12 控制器

package com.bjsxt.controller;

import com.bjsxt.entity.FileInfo;
import com.bjsxt.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

// 文件控制器,实现上传、下载功能
@Controller
public class FileController {
    
    
    @Autowired
    private FileService fileService;
    /**
     * 文件上传
     * @param uploadFile 要上传的文件
     * @return
     */
    @RequestMapping("/uploadFile")
    public String uploadFile(MultipartFile uploadFile){
    
    
//        System.out.println("上传文件的原始名称 - " + uploadFile.getOriginalFilename());
//        System.out.println("上传文件的大小 - " + uploadFile.getSize());
        // 上传文件到FastDFS中
        boolean flag = this.fileService.uploadFile(uploadFile);
        System.out.println(flag ? "上传成功" : "上传失败");

        return "redirect:/index";
    }

    /**
     * 进入到首页面的方法。
     * 在进入首页面之前,需要先访问数据库,查询可以下载的文件的相关信息。
     * @return
     */
    @RequestMapping(value = {
    
    "/", "/index"})
    public String toIndex(Model model){
    
    
        List<FileInfo> list = this.fileService.getFileInfos();
        model.addAttribute("list", list);
        return "forward:/index.jsp";
    }

    /**
     * 下载文件
     * @param fileInfoId 要下载的文件在数据库中的相关数据主键
     */
    @RequestMapping(value="/downloadFile/{fileInfoId}")
    public void downloadFile(@PathVariable("fileInfoId") Long fileInfoId, HttpServletResponse response){
    
    
        // 获取要传递给客户端的文件数据
        InputStream inputStream = null;
        try {
    
    
            inputStream = this.fileService.getFile(fileInfoId);
            FileInfo fileInfo = this.fileService.getFileInfoById(fileInfoId);
            // 设置响应头, 响应为下载,下载的文件名是什么
            response.setContentType("application/octet-stream");
            response.setHeader("content-disposition", "attachement;filename="+fileInfo.getFileName());

            // 通过输出流输出文件内容到客户端
            OutputStream outputStream = response.getOutputStream();

            byte[] temp = new byte[512];
            int len = 0;
            while((len = inputStream.read(temp)) != -1){
    
    
                outputStream.write(temp, 0, len);
            }

            // 刷新缓存
            outputStream.flush();
        }catch(Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            if(inputStream != null){
    
     // 回收资源
                try {
    
    
                    inputStream.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}

13 页面 index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head>
    <title>FastDFS WEB应用</title>
</head>
<body>
    <center>
        <form method="post" enctype="multipart/form-data" action="/uploadFile">
            选择要上传的文件:<input type="file" name="uploadFile">
            &nbsp;&nbsp;&nbsp;
            <input type="submit" value="上传">
        </form>
        <table border="1" style="width: 800px">
            <thead>
            <tr>
                <th>文件原始名称</th>
                <th>文件卷标名称</th>
                <th>文件远程名称</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            <c:forEach items="${list}" var="fileInfo">
                <tr>
                    <th>${
    
    fileInfo.fileName}</th>
                    <th>${
    
    fileInfo.groupName}</th>
                    <th>${
    
    fileInfo.remoteFileName}</th>
                    <th><a href="/downloadFile/${fileInfo.id}">下载</a>&nbsp;&nbsp;&nbsp;
                        <a href="http://192.168.89.153:8888/${fileInfo.filePath}" target="_blank">预览</a></th>
                </tr>
            </c:forEach>
            </tbody>
        </table>
    </center>
</body>
</html>

在这里插入图片描述
Swagger

猜你喜欢

转载自blog.csdn.net/qq_42588990/article/details/121228904