介绍
FastDFS是一个轻量的分布式文件系统,对文件进行管理,因为是轻量型,所以不适合视频网站那样的单文件大容量存储
FastDFS有两个角色,跟踪器(tracker)主要做调度工作(负载均衡),存储节点(storage)存储文件,存储节点采用了分卷的形式(类似磁盘的CDEF盘),卷与卷之间是相互独立的,一个卷可能有多个服务器,起到了冗余备份和负载均衡作用,当存储空间不足时,可以增加新的卷来扩充容量
架构图
客户端上传文件时,给到Tracker,由Tracker来判断存储容量、存储位置、生成存储路径到DB等操作,如果能存储的话,将ip 端口 路径等信息给回client,然后client直接去找Storage cluster进行写入
客户端下载文件时,页面中有从DB中拿出来的路径(存储时Ttracker生成的),但不知道ip和端口,通过携带FastDFS中的 **文件标识(卷名+文件名)**去Tracker,Tracker给返回ip和端口,然后通过ip 端口 路径 直接访问存储节点。如果客户端知道ip和端口可以直接去访问storage。
存储节点的集群中有多个卷,每个卷又有多个主备,起到负载均衡和冗余备份的作用
一个卷下服务器的同步机制:一个卷内的服务器写入客户端文件时,向其他服务器进行广播,然后其他服务器进行同步,只有源头数据才广播,备份数据的写入不用广播。特殊的,当增加新的服务器时,由其中一台服务器将所有内容给这台新服务器
FastDFS搭建
基础配置
用两个Linux服务器,分别作为tracker和storage
安装包都放到/root/upload下 安装到/usr/local/fastdfs下 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
如果出现下面错误
(1)运行如下代码: yum -y install zlib zlib-devel pcre pcre-devel gcc gcc-c++ openssl openssl-devel libevent libevent-devel perl unzip net-tools wget
(2)需要清除编译,安装好以上运行环境,重新运行即可。 清除编译代码: ./make.sh clean
重新./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.10FastDFS 安装后资源简介
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 的跟踪服务和存储服务是通过配置实现的。后续内容为跟踪服务和存储服务的具体配置。
刚开始两者都需要搭建同样的环境,不同的是后边配置文件的配置
tracker 配置
只在 tracker server 节点中配置。
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
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/目录中默认创建若干子孙目录(两级目录层级总
计 256256 个目录),是用于存储具体文件数据的。 Storage 服务器启动比较慢,因为第一次启动的时候,需要创建 256256 个目录。
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 服务必须已启动,不推荐开启自启。
客户端基础配置
不是必须的。就是用于使用命令行测试 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/wKjniGD3ez-AFeyzAAeItTLD40o493.zip 其中 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/目录中是否还有文件。
安装Nginx
nginx+FastDFS ,nginx作为反向代理作为FastDFS的代理服务器来处理web的HTTP请求
在安装 nginx 应用的时候,同时要在 nginx 中增加一个 FastDFS 的组件。 Module。
tar -zxf fastdfs-nginx-module_v1.16.tar.gz -C /usr/local/fastdfs
cd /usr/local/fastdfs/fastdfs-nginx-module/src
vi /usr/local/fastdfs/fastdfs-nginx-module/src/config
CORE_INCS="$CORE_INCS /usr/local/include/fastdfs /usr/include/fastcommon/"
安装 Nginx 需要的依赖
yum install -y gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel
解压nginx
tar -zxf nginx-1.8.0.tar.gz -C /usr/local/fastdfs/
进入nginx
cd /usr/local/fastdfs/nginx-1.8.0/
在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
创建目录 Nginx 运行时需要创建若干临时文件,如果默认安装不需创建此目录。 mkdir -p /var/temp/nginx
将/nginx目录下/objs/Makefile中的 -Werrori 删除。
vim objs/Makefile
在nginx目录下输入命令:vim src/os/unix/ngx_user.c,将下图内容注释,退出保存即可正常编译
编译安装 make make install
配置 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 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=tracker的ip: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/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 参数,则必须创建此软连接
创建网络访问存储服务的软连接
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 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;
}
}
测试WEB 访问存储服务中的文件 上传一个图片到storage group1/M00/00/00/wKjniGD3iSGAazfTAAMRtuDuhdI540.jpg
重启fastdfs,启动nginx
/etc/init.d/fdfs_storaged restart
使用浏览器查看 FastDFS 中保存的文件: http://192.168.231.136:8888/group1/M00/00/00/wKjniGD3iSGAazfTAAMRtuDuhdI540.jpg
Java客户端
conf配置
<?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>org.example</groupId>
<artifactId>fastdfs</artifactId>
<version>1.0-SNAPSHOT</version>
<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>
</dependencies>
</project>
# 连接超时, 单位是秒
connect_timeout = 10
# 网络超时, 单位是秒
network_timeout = 30
# 字 符 集 设 置
charset = UTF-8
# 必须和tracker server 中的/etc/fdfs/tracker.conf 配置文件中的http.server_port=8080 配置一致
http.tracker_http_port = 8080
# tracker server 的访问 IP 和 Port。
tracker_server = 192.168.231.135:22122
package cn.wit.test;
import org.csource.common.MyException;
import org.csource.fastdfs.*;
import org.csource.common.MyException; import org.csource.fastdfs.*;
import java.io.IOException;
public class TestInitClient {
private static final String configFile = "src/main/resources/test/fdfs.conf";
public static void main(String[] args) throws IOException, MyException {
ClientGlobal.init(configFile);
TrackerClient trackerClient=new TrackerClient();
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);
}
}
properties配置
fdfs.properties
# 连接超时, 单位是秒
fastdfs.connect_timeout_in_seconds=10
# 网络超时, 单位是秒
fastdfs.network_timeout_in_seconds=30
# 字符集设置
fastdfs.charset=UTF-8
# 必须和tracker server 中的/etc/fdfs/tracker.conf 配置文件中的http.server_port=8080 配置
一致
fastdfs.http_tracker_http_port=8080
# tracker server 的访问 IP 和 Port。
fastdfs.tracker_servers=192.168.231.135:22122
package cn.wit.test;
import org.csource.common.MyException;
import org.csource.fastdfs.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class TestInitClientByProperties {
private final static Properties props = new Properties();
public static void main(String[] args) throws IOException, MyException {
InputStream in=TestInitClientByProperties.class.getClassLoader().getResourceAsStream("test/fdfs.properties");
props.load(in);
ClientGlobal.initByProperties(props);
TrackerClient trackerClient =new TrackerClient();
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);
}
}
工具类
用fdfs.conf配置
public class fdfsUtils {
private static final String configFile = "src/main/resources/test/fdfs.conf";
private static StorageClient storageClient;
static{
try {
ClientGlobal.init(configFile);
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);
}
}
public static StorageClient getStorageClient(){ return storageClient;
}
public static void main(String[] args) { System.out.println(getStorageClient());
}
}
上传文件
package cn.wit.test;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.StorageClient;
public class Upload {
public static void main(String[] args) {
String fileIds[] = testUpload("testFile", "b.jpg", "jpg");
if(fileIds == null){
System.out.println("文件上传失败!");
}
System.out.println(fileIds.length);
System.out.println("组名/卷名:" + fileIds[0]);
System.out.println("路径: " + fileIds[1]);
}
public static String[] testUpload(String dirName, String fileName, String fileType){
StorageClient storageClient = fdfsUtils.getStorageClient();
try{
NameValuePair nvp [] = new NameValuePair[]{ new NameValuePair("filename", fileName)};
String fileIds[] = storageClient.upload_file(dirName+"/"+fileName, fileType, nvp);
return fileIds;
}catch(Exception e){
e.printStackTrace();
}
return null;
}
}
下载文件
加载上面上传的文件
package cn.wit.test;
import org.apache.commons.io.IOUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.StorageClient;
import java.io.FileOutputStream;
public class Download {
public static void main(String[] args) {
testDownload("group1", "M00/00/00/wKjniGD4IAaADlTJAAAm5FZR7yg812.jpg");
}
public static void testDownload(String groupName, String remoteFileName){
StorageClient storageClient = fdfsUtils.getStorageClient();
try{
byte[] buff = storageClient.download_file(groupName, remoteFileName);
NameValuePair[] metaList=storageClient.get_metadata(groupName,remoteFileName);
String fileName = null;
for(int i = 0; i < metaList.length; i++){
NameValuePair nvp = metaList[i];
if(nvp.getName().equals("filename"))
fileName = nvp.getValue();
}
IOUtils.write(buff, new FileOutputStream("downloadFile/"+ fileName));
System.out.println("下载完成");
}
catch(Exception e){
e.printStackTrace();
}
}
}
删除文件
package cn.wit.test;
import org.csource.fastdfs.StorageClient;
public class Delete {
public static void main(String[] args) {
testDelete("group1", "M00/00/00/wKjniGD4IAaADlTJAAAm5FZR7yg812.jpg");
}
public static void testDelete(String groupName, String remoteFileName){
StorageClient storageClient = fdfsUtils.getStorageClient();
try{
int flag = storageClient.delete_file(groupName, remoteFileName);
System.out.println(flag == 0 ? "删除成功" : "删除失败");
}catch(Exception e){
e.printStackTrace();
}
}
}
web中使用
ssm+maven
<?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>cn.wit</groupId>
<artifactId>webfdfs</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>webfdfs Maven Webapp</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<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>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<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>
<finalName>webfdfs</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
数据库
drop table if exists t_files;
create table t_files(
id bigint not null auto_increment primary key,
file_name varchar(255),
group_name varchar(32),
remote_file_name varchar(255),
file_path varchar(255)
);
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<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">
<display-name>Archetype Created Web Application</display-name>
<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>
applicationContext 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="cn.wit"></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/login?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="wityy" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations">
<array>
<value>classpath:cn.wit.mapper/FileInfoMapper.xml</value>
</array>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="cn.wit.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( * cn.wit.service.*.*(..))"/>
</aop:config>
</beans>
conf配置文件
# 连接超时, 单位是秒
connect_timeout = 10
# 网络超时, 单位是秒
network_timeout = 30
# 字 符 集 设 置
charset = UTF-8
# 必须和tracker server 中的/etc/fdfs/tracker.conf 配置文件中的http.server_port=8080 配置一致
http.tracker_http_port = 8080
# tracker server 的访问 IP 和 Port。
tracker_server = 192.168.231.135:22122
工具类
package cn.wit.utils;
import org.csource.common.NameValuePair; import org.csource.fastdfs.*;
import java.io.ByteArrayInputStream; import java.io.InputStream;
public class FastDFSCommonsUtils {
private static StorageClient storageClient =null;
static{
try{
ClientGlobal.init(Thread.currentThread().getContextClassLoader().getResource("").getPath()
+"/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);
}
}
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);
System.out.println(result[0].toString());
System.out.println(result[1].toString());
return result;
}catch(Exception e){
e.printStackTrace();
return null;
}
}
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;
}
}
}
文件实体类
package cn.wit.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;
}
}
mapper层
package cn.wit.mapper;
import cn.wit.entity.FileInfo;
import java.util.List;
public interface FileInfoMapper {
void insertFileInfo(FileInfo fileInfo);
List<FileInfo> selectFileInfos();
FileInfo selectById(Long id);
}
<?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="cn.wit.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="cn.wit.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="cn.wit.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>
service
package cn.wit.service;
import cn.wit.entity.FileInfo;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
public interface FileService {
boolean uploadFile(MultipartFile file);
List<FileInfo> getFileInfos();
InputStream getFile(Long fileInfoId);
FileInfo getFileInfoById(Long fileInfoId);
}
service.impl
package cn.wit.service.impl;
import cn.wit.entity.FileInfo;
import cn.wit.mapper.FileInfoMapper;
import cn.wit.service.FileService;
import cn.wit.utils.FastDFSCommonsUtils;
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.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;
}
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);
InputStream inputStream= FastDFSCommonsUtils.download(fileInfo.getGroupName(), fileInfo.getRemoteFileName());
return inputStream;
}
@Override
public FileInfo getFileInfoById(Long fileInfoId) {
return this.fileInfoMapper.selectById(fileInfoId);
}
}
controller
package cn.wit.controller;
import cn.wit.entity.FileInfo;
import cn.wit.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;
@RequestMapping("/uploadFile")
public String uploadFile(MultipartFile uploadFile){
boolean flag = this.fileService.uploadFile(uploadFile);
System.out.println(flag ? "上传成功" : "上传失败");
return "redirect:/index";
}
@RequestMapping(value = {"/", "/index"})
public String toIndex(Model model){
List<FileInfo> list = this.fileService.getFileInfos();
model.addAttribute("list", list);
return "forward:/index.jsp";
}
@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();
}
}
}
}
}
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">
<input type="submit" value="上传">
</form>
<table border="1" style="width: 500px">
<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>
<a href="http://192.168.231.136:8888/${fileInfo.filePath}"target="_blank">预览</a></th>
</tr>
</c:forEach>
</tbody>
</table>
</center>
</body>
</html>
预览
下载
过程中报错
nginx重启报错 nginx: [emerg] open() “/var/run/nginx/nginx.pid” failed (2: No such file or directory)
解决方案
配置注意
maven项目是以resources文件夹为根目录,配置文件都要放在resources下
spring配置文件在resources下 resource下cn.wit.mapper包下 mapper配置文件
|