
技术论坛:http://bbs.chinaunix.net/forum-240-1.html
资源地址:https://sourceforge.net/projects/fastdfs/
源码地址:https://github.com/happyfish100
FastDFS中的文件标识分为两个部分:卷名和文件名,二者缺一不可。

解释:
写入
假设我现在client要上传文件,我要找到跟踪器tracker,tracker找到client,然后tracker找到存储节点,看看存储节点那个卷下面的节点比较空闲,能放得下这个文件,然后写入进去,生成一个文件名
读取
如果我们client要下载文件,不需要与tracker再做交互,直接与storage打交道,根据我们当时上传文件tracker给我们提供的文件名,我们加上服务器名字和端口号再加上完整的文件名即可读取下载成功
主备切换
我们storage存储节点是由多个卷构成的一个大的集群,比方说c d e盘构成一个硬盘,每个卷又分成主和子,他们两者文件类型一致,如果主服务器崩了,子服务器马上可以顶上


client可以直接去到Storage进行在线的读取和下载,这个在线读取和下载前提是我们知道它的一个ip地址和端口号,后面跟上卷名,再跟上文件名,我们如果知道这个完整路径的话,可以直接去到我们存储节点进行在线预览,或者是在线下载;如果我们现在只知道一个卷名和文件名,我们并不知道ip和端口,我们也可以去找我们的tracker,拿着我们的卷名和文件名去找我们的跟踪器,跟踪器就会找到对应的卷名和文件名所在的节点,会把这个存储节点的ip地址和端口号返回给我们的客户端,然后我们client再次通过我们的ip端口卷名文件名,然后直接通过我们的存储节点进行我们的读取和下载操作,一般我们如果考虑效率问题的话,肯定是我们直接拿着ip端口卷名文件名直接去存储节点,读取我们的一个文件,如果实在是不知道ip和端口情况下,可能就需要通过我们tracker,但一般情况下,我们tracker上传的时候,我们tracker会返回一个完整的卷名和文件名加ip和端口,我们一般呢会把返回的相对路径存放到数据库里面去,那我们需要进行文件预览和下载的时候呢,我们一般从数据库拿到我们带有ip和端口的卷名和文件名这一整串信息的数据直接去storage进行一个下载 ,效率更高一点!!!
| 指标 | FastDFS | NFS | 集中存储设备加 NetApp、NAS |
|---|---|---|---|
| 线性扩容性 | 高【扩容好】 | 差 | 差 |
| 文件高并发访问性能 | 高【速度快】 | 差 | 一般 |
| 文件访问方式 | 专用API【有自己的AIP】 | POSIX【可移植操作系统接口】 | POSIX |
| 硬件成本 | 较低【成本低】 | 中等【硬盘成本高】 | 高【硬盘成本高】 |
| 相同内容文件只保存一份 | 支持【相同文件只有只保留一份】 | 不支持 | 不支持 |
| 指标 | FastDFS | mogileFS |
|---|---|---|
| 系统简洁性 | 简洁 只有两个角色:tracker和storage | 一般有三个角色:trocker、storage和存储文件信息的mysql db |
| 系统性能 | 很高(没有使用数据库,文件同步直接点对点,不经过tracker中转) | 高(使用mysql来存储文件索引信息,文件同步通过tracker调度和中转) |
| 系统稳定性 | 高(C语言开发,可以支持高并发和高负载) | 一般(Perl语言开发,高并发和高负载支持一般)【没有C语言高并发高负载好】 |
| RAID方式 | 分组(组内冗余),灵活性较大 | 动态冗余,灵活性一般 |
| 通信协议 | 专用协议,下载文件支持http【专用协议最大的好处就是写的操作效率跟高,在tcp/ip之上】 | http |
| 技术文档 | 较详细 | 较少 |
| 文件附加属性(meta data) | 支持【文件相关属性存储在storage】 | 不支持 |
| 相同内容只保存一份 | 支持【根据文件相关属性判断,如果属性一致则覆盖】 | 不支持 |
| 下载文件时支持文件偏移量【断点续传, 从指定位置向前向后移动的字节数,比如我们下载一个文件一半点暂停,然后再点开始会从你已经下载好的进度开始,而有的文件你点了暂停可能就让你从头开始下载】 | 支持 | 不支持 |
有没有比FastDFS更好的呢?当然有,那就是我们的hdfs,hdfs更多的是大数据方面去用,它的性能会比FastDFS更好一点
FastDFS主要是两个角色,一个tracker和storage,它们本质上都是一个FastDFS一个包,它们通过对应不同配置来确认它们不同的角色,所以它们通用的安装都是FastDFS的安装包
我们这边也准备了两台服务器,一台是安装我们tracker,一台安装storage,它们只是对应角色配置不一样

如果电脑配置比较差的话,也可以直接安装在一台服务器上,只需修改对应角色配置即可,安装包都是一样,也可以实现!!!
上传所选安装包

我们fastdfs是根据C语言进行开发的,所以我们需要安装C++相关依赖
yum -y install cmake gcc-c++

安装完成之后,我们还需要安装我们fastdfs核心库

这个核心库呢其实是从fastdfs和fastdht中提取出来的公用的C函数的库,fastdfs和fastdht是同样一个作者去写的两个产品,都是C语言编写,然后里面会有一些公用函数库,作者把它提取出来当成一个专门核心库
如果我们后缀名是.zip,需要安装一个zip解压插件
yum -y install unzip

我们把需要安装的fastdfs所以文件放在一个文件夹方便管理
mkdir -p /usr/local/fastdfs
然后我们就可以去解压了
zip后缀
unzip libfastcommon-1.0.43.zip
tar后缀
tar -zxvf libfastcommon-1.0.43.zip

我们进行之后看见有一个make.sh执行脚本,我们就通过这个脚本去进行一个编译和安装

.make.sh
这样就会进行编译

编译好后我们就可以进行安装了
.make.sh install

还有一个问题就是我们fastdfs主程序的lib目录是在/usr/local/lib下面的,所以我们需要去创建一些软链接,这些软链接就相当于快捷方式,把它的一个快捷方式从原本指向的目录改成我们想要的指向的目录
这个意思相当把前面原本指向的路径指定到我们后面需要我们指定路径
ln -s /usr/1ib64/1ibfastcommon.so /usr/local/lib/libfastcommon.so
ln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
ln -s /usr/local/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so

tar -zxvf fastdfs-6.06.tar.gz
我们发现这里也有make.sh

我们可以做一个可选的操作,就是说我们可以把它的一个路径改成指定的路径,因为它默认安装路径是在/usr下面,我们可以把它改成/usr/local下面去,当然在集群下面就不要去改,那我们现在是安装的一个单节点,可以去尝试的改一下
我们进入fastdfs
vim make.sh
我们搜索/TAGE_PREFIX

我们把它改到/usr/local

然后我们再次去安装
./make.sh 先编译

再安装
./make.sh install
安装后,FastDFS主程序所在的位置是:

安装好了之后,我们可以去看一下服务脚本所在位置
cd /etc/init.d/

然后我们可以看一下我们配置文件的模板所在位置
cd /etc/fdfs/

如果我们这台服务器当做tracker来用的话,只需拷贝tracker配置拷贝过去进行修改,把后面的.sample删掉就可以用了,建议不要拿源文件直接用,最好是拷贝过去再进行修改,因为如果改错了还有备份!!!
我们还可查看内置命令所在目录
cd /usr/local/bin/
这个就是fastdfs内置命令,包括重启,启动,停止,还有测试,跟踪等等。。。

到这里,我们整个tracker服务器的fastdfs安装好了!!!
**同理,我们还要去安装storage,拿storage的安装和tracker是一模一样的,只是配置文件不同,其它安装包都一样,所以我们如果在同一台服务器部署只需修改配置文件即可!!! **
我们首先进入配置文件模板
cd /etc/fdfs
我们看到里有个tracker.conf.sample

然后我们拷贝一下,到conf就行
cp tacker..sample tracker.conf
然后我们就可以进行配置了
vim tracker.conf

我们简单了解一下里面的属性
这里我们就配置完成了,我们主要配置base_path 根目录即可!!!
然后我们别忘了创建刚才自定义的目录
mkdir -p /fastdfs/tracker
我们创建完目录呢,就可以启动tracker了,来到启动目录
cd /etc/init.d/

我们看到有两个文件 fdfs_trackerd和fdfs_storaged这两个就是我们启动的文件
我们之前安装的时候,我们修改过它的一个目录,所以呢我们这边启动的时候呢,也去修改它的一个目录,如果我们没有去修改它的目录,这边可以直接启动!!!
我们进入配置文件修改目录即可!!!
vim fdfs_trackerd
我们修改之后应该是/usr/local/bin/fdfs_trackerd,

保存并退出, 启动即可
./fdfs_trackerd start

怎么去看有没有启动成功呢?
两种方法:
查看状态
./fdfs_trackerd status

查看进程
ps -ef|grep fdfs

停止
.fdfs_trackerd stop
重启
/etc/init.d/fdfs_trackerd restart
开机启动
我们进入文件
vim /etc/rc.d/rc.local
添加启动文件

/etc/init.d/fdfs_tracked start
我们现在已经启动了tracker包括我们相应的一些配置,其实真正要做的配置没有,默认的端口都没改,只是把它的base_path 根目录进行了一个修改,当然这个修改也是可有可无的,但是记住一定要创建,有一个负载均衡,大部分默认都是一个轮询的方式,其它的也没什么!!!
跟tracker一样
cd /etc/fdfs

同样,拷贝一份
cp storage.conf.sample storage.conf
修改storage
vim storage.conf
也是一样,我们观察一下里面的属性

保存并退出即可!!!
创建我们刚才自定义的目录
mkdir -p /fastdfs/storage/base
mkdir -p /fastdfs/storage/store
我们安装的时候也修改了目录,所以我们这里也要修改一下
vim /etc/init.d/fdfs_storaged
修改PRG=/usr/local/bin/fdfs_storaged

保存并退出,启动storage
/etc/init.d/fdfs_storaged start

查看状态
/etc/init.d/fdfs_storaged status

停止
/etc/init.d/fdfs_storaged stop
重启
/etc/init.d/fdfs_storaged restart
开启启动
我们进入文件
vim /etc/rc.d/rc.local
添加启动文件

启动之后我们可以去看看我们刚才创建的两个目录
cd /fastdfs storage/

base目录【基础数据目录】
可以看到有一个data和logs 基础数据和日志

可以看到有对应的一个日志

进入data目录
里面有对应我们storage的一个进程号,然后启动的一个数据和**同步相应的相关信息 **
我们回去看我们store
cd ../../store
这里也有一个data,这个data存放我们上传文件的目录

我们发现这边使用16进制,从00一直到FF,一共是256个目录,然后我们每一个目录下面还有256个子目录

我们cd 00 再 ls
我们发现还是有 00 -FF 256个目录

再00就没了!!!再往下这边就会存放我们的文件了,至于我们文件上传上来之后会存放在那个目录下面,这个不需要我们去关系,storage会自己去存放,并且我们的tracker会去把我们的一个完整的卷名加文件名,这个文件名就是从data到00再到00再到下面的具体文件名一整个完整路径返回给我们,我们直接能拿到,它具体存放在着256个目录那个目录这个不需要我们操心,这是storage自定义去完成的,我们这里storage开机启动前提是必须先启动tracker再启动storage,不然会报错,因为我们storage配置文件里面配置了tracker_server,所以我们这边如果想要stroage开机自启,必须设置tracker也开机自启,不然不建议storage自启!!!
客户端配置不是必需的,因为客户端配置完相当于用命令行去测试我们的fastdfs,所以我们如果不准备用命令行测试,而是准备用代码测试的话,我们完全可以跳过!!!
我们可以把Client放在tracker和storage任意服务器下,并不影响,把它配置改一下即可!!!
首先进入我们配置模板
cd /etc/fdfs
我们可以看到这边有一个client.conf.sample
同样,我们先拷贝
cp client.conf.sample client.conf
进入配置文件
vim client.conf
可以看下它的属性,更改的地方加粗
到这里,client配置就完成了,我们保存并退出,创建刚刚自定义的目录
mkdir -p /fastdfs/client
我们看到root目录下面有个图片,我们可以把这个上传上去
怎么去上传呢?上传的话因为我们安装的时候改过对应的一个目录,我们的命令目录呢在/usr/local/bin
我们上传就是fdfs_upload_file

./fdfs_upload_file /etc/fdfs/client.conf ~/cat-114782_640 (1).jpg
上传命令+客户端配置+上传的文件
返回上传的完整卷名加文件名,它的文件名是重新命名的


我们可以进入storage存放文件目录去查看我们上传的文件

我们要记住M00是一个虚拟目录,有点相当于我们windows的快捷方式,它的引用主要是引用到我们的data目录下面,data就对应M00,快捷方式的一个意思
./fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKj4ZWQJdC-AOV6HAAKSU_3XkA0484.jpg
删除命令+client配置文件+完整的卷名和文件名
这里要注意,因为我们现在操作没有ip和端口,所以我们需要根据追踪器,根据这一整串完整的文件名去获取stroage的ip和端口,才会去进行一个删除这也就是我们client为什么要去配置tracker服务器
这样则表示删除成功

我们可以进入data-00-00查看是否删除成功
我们发现后最484的文件名没有了

为什么要安装nginx呢?因为我们fastdfs是一个文件系统,那可以存放很多类型的文件,比如说存放图片或者其它一个类型,图片的话我们可以通过网页直接去预览,而不需要通过我们现在这个操作,通过client拿到一个完整的卷名加文件名,通过tracker拿到对应的ip地址和端口再去下载预览,太麻烦,我们可以通过http协议直接在我们的url里面输入我们的一个ip端口,卷名文件名直接在浏览器里面可以访问这张图片或预览,那这个时候呢我们fastdfs没法实现,就要安装fastdfs_module_nginx和nginx进行代理

解压
tar zxvf fastdfs-nginx-module-1.22
cd 进入目录

我们来到src源码目录

源码目录有相应的一个配置,我们需要修改相应的一个配置
vim config
为什么要改配置呢?因为我们去安装我们的组件之后呢,会去安装我们的nginx,安装nginx的时候需要把我们的一个module模块加进去,加进去之后会寻找我们的一个fastdfs对应的一个安装的目录,如果目录不正确可能就安装失败了!!!所以我们需要去更改我们的一个目录路径

**修改的时候也有区别,因为我们装fastdfs的时候修改了我们安装目录,所以我们这串目录是改过目录之后的,如果我们没有修改过fastdfs的安装目录的,我们改的是另一个目录 **
/usr/local/include/fastdfs /usr/include/fastcommon/
fastdfs位置+核心库,这两个改呢,是因为我们安装改过,如果没有的话呢,我们改的不是这两个,就应该是把local删掉就行了
保存并退出,我们的模块就改好了,改好了之后并不代表它已经安装了,怎么去安装呢,就是我们去安装nginx的时候,把模块添加上去即可,所以我们还要去安装我们的nginx

依赖
安装之前我们还需要安装对应依赖
yum install -y gcc gcc-c++ make automake autoconf libtool pcre pcre-develzlib zlib-devel
openss1 openss1-devel

解压nginx

tar -zxvf nginx-1.16.1.tar.gz
查看目录

我们需要去更改一下它的目录
更改目录
先准备一个目录
mkdir -p /var/temp/nginx
配置nginx安装信息
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi\
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi \
--add-module=/usr/local/fastdfs/fastdfs-nginx-module-1.22/src
下面这些信息放入我们刚才创建的文件
这样我们nginx安装信息就配置完成

接下来我们就可以进行安装了
预编译
make
编译加安装
make install
同理跟上面操作一样我们先把fastdfs_nginx_module模块配置文件修改
cd /usr/local/fastdfs/fastdfs-nginx-module-1.22/src/
这里面有一个我们对应的一个配置文件mod_fastdfs.conf

cd mod_fastdfs.conf /etc/fdfs

然后我们进行一个相应的修改

保存并退出
这两个文件是因为我们nginx在网页上直接看图片的时候呢,是用的一个http协议,所以我们需要去拷贝一下
http
cp /usr/local/fastdfs/fastdfs-6.06/conf/http.conf /etc/fdfs/
请求头
cp /usr/local/fastdfs/fastdfs-6.06/conf/mime.types /etc/fdfs/
因为我们nginx启动的时候呢,会在默认的/usr/lib64目录下面去查找所需的so文件,那因为我们安装fastdfs的时候呢,已经修改了它的目录,所以我们一定要创建它的软链接,不然会失败
ln-s /usr/local/lib64/libfdfsclient.so /usr/lib64/libfdfsclient.so
这个软链接呢,我们的追踪服务返回的里面有一个M00,这个M00其实就是一个data,它是相当于windows的一个快捷方式,那我们需要把这个M00指定到data下面
ln -s /fastdfs/storage/store/data//fastdfs/storage/store/data/M00
我们可以去看一下我们创建的软连接是否生效
cd /fastdfs/storage/store/data
我们可以看到这边有个M00

它指定的目录呢就是我们data目录下

cd /usr/local/nginx/
进入conf目录
cd conf
修改nginx.conf

vim nginx.conf

修改哪些东西呢?
加入以下配置
location ~/group[0-9]/M00{
ngx_fastdfs_module;
}
当我们去访问8888端口时候呢,它就会找到我们的本地的group0/M00,然后通过我们的ngx_fastdfs_module这样的一个模块去找到我们storage上面存储的一个对应文件就可以预览了!!!
保存并退出,我们nginx配置就完成了,我们需要重启一下我们的storage并且要启动nginx
/etc/init.d/fdfs_storaged restart

我们来到nginx-sbin目录启动nginx
./nginx
看到端口表示启动成功!!!

查看启动状态
ps -ef |grep nginx

我们通过客户端上传图片

/usr/local/bin/fdfs_upload_file /etc/fdfs/client.conf cat-114782_640.jpg
同理,**上传命令+client配置+文件名即可 **
查看上传的文件
cd /fastdfs/storage/store/data/00/00/
根据tracker给出的路径通过网页进行访问,ip+端口+卷名和文件名

<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.30-SNAPSHOT</version>
</dependency>
注意:这边我们maven仓库是没有这个依赖的,我们需要自己去下载然后安装到仓库



用于加载配置文件的公共客户端工具。
常用方法:
注意:使用conf或prooerties进行客户端参数配置时,参数key命名不同。
跟踪器客户端类型。创建此类型对象时,需传递跟踪器组,就是跟踪器的访问地址信息。无参构造方法默认使用ClientGlobal_tracker_group 常量作为跟踪器组来构造对象。【就是我们上面ClientGlobal定义的常量】
创建对象的方式为:
new TrackerClient();或new TrackerClient(ClientGlobal.g_tracker_group)
跟踪器服务类型,此类型的对象是通过跟踪器客户端对象创建的。实质上就是一个FastDFS Tracker Server的链接对象。是代码中与Tracker Server链接的工具
构建对象的方式为:
trackerClient.getTrackerServer();
存储服务类型。此类型的对象是通过跟踪器客户端对象创建的。实质上就是一个FastDFS Storage Server的链接对象。是代码中与StorageServer链接的工具。获取具体存储服务链接,是由Tracker Server分配的,所以构建存储服务对象时,需要依赖跟踪器服务对象
构建对象的方式为:
trackerClient.getStorage(trackerServer);
存储客户端类型,此类型对象是通过构造方法创建的。创建时,需传递跟踪服务对象和存储服务对象。此对象实质上就是一个访问FastDFS Storage Server的客户端对象,用于实现文件的读写操作
创建对象的方式为:
new StorageClient(trackerServer,storageServer);
常用方法有:
创建fastdfsdemo,加上web和thymeleaf依赖

<!--thylemeaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--fastdfs-->
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.30-SNAPSHOT</version>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
#连接超时
connect_timeout = 2
#网络超时
network_timeout = 30
#编码格式
charset = utf-8
#tracke端口号
http.tracker_http_port = 8888
#防盗链功能
http.anti_steal_token = no
#秘钥
http.secret_key = FastDFS1234567890
#tracker ip :端口号
tracker_server = 192.168.248.101:22122
#连接池配置
connection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000
//日志
private static Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
//初始化FastDFS,ClientGlobal.init方法会读取配置文件,并初始化对应的属性
static {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
} catch (IOException | MyException e) {
logger.error("FastDFS Client init Fail!", e);
e.printStackTrace();
}
}
/**
* 生成Storage客户端
* @return
* @throws IOException
*/
private static StorageClient getStorageClient() throws IOException {
TrackerServer trackerServer = getTrackerServer();
//我们可以通过trackerServer拿到StorageServer,不设置也能拿到StorageClient
return new StorageClient(trackerServer, null);
}
/**
* 生成Tracker服务器端
* @return
* @throws IOException
*/
private static TrackerServer getTrackerServer() throws IOException {
TrackerClient trackerClient = new TrackerClient();
return trackerClient.getTrackerServer();
}
/**
* 上传
*
* @param file 文件对象
* @return
*/
private static String[] upload(FastDFSFile file) {
//打印相关信息
logger.info("File Name:" + file.getName() + ",File lenght" + file.getContent().length);
//文件属性信息
NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair("author", file.getAuthor());
//上传时间
long startTime = System.currentTimeMillis();
//返回一个String数组
String[] uploadResults = null;
StorageClient storageClient = null;
try {
//拿到客户端
storageClient = getStorageClient();
//文件数组;后缀名;文件属性相关数组
uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
} catch (IOException | MyException e) {
e.printStackTrace();
//上传失败打印文件名
logger.error("上传失败 File Name:" + file.getName(), e);
}
//上传时间
logger.info("上传时间:" + (System.currentTimeMillis() - startTime + "ms"));
//验证上传结果
if (uploadResults == null && storageClient != null) {//判断上传结果是否为空
logger.error("上传失败" + storageClient.getErrorCode());//上传失败拿到错误结果
}
//上传成功会返回相应信息
logger.info("上传成功,group_name:" + uploadResults[0] + "remoteFileName:" + uploadResults[1]);//打印卷名和完整名字
return uploadResults;
}
/**
* 下载文件
*
* @param groupName 卷
* @param remoteFileName 完整文件名
* @return
*/
public static InputStream downFile(String groupName, String remoteFileName) {
try {
//创建storage客户端对象
StorageClient storageClient = getStorageClient();
//调用下载方法,返回一个数组
byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
//输入要下载的文件
InputStream ins = new ByteArrayInputStream(fileByte);
return ins;
} catch (IOException | MyException e) {
logger.error("下载失败", e);
}
return null;
}
/**
* 删除文件
*
* @param groupName 卷
* @param remoteFileName 完整文件名
*/
public static void deleteFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getStorageClient();
int i = storageClient.delete_file(groupName, remoteFileName);
logger.info("删除成功" + i);
} catch (IOException | MyException e) {
logger.error("删除失败", e);
}
}
/**
* 查看文件信息
* @param groupName 卷名
* @param remoteFileName 完整文件名
* @return
*/
public static FileInfo getFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getStorageClient();
//获取文件方法
FileInfo file_info = storageClient.get_file_info(groupName, remoteFileName);
return file_info;
} catch (Exception e) {
logger.error("查看文件信息失败", e);
}
return null;
}
/**
* 获取文件路径,上传至后返回的完整路径
* @return
* @throws IOException
*/
public static String getTrackerUrl() throws IOException {
return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":8888/";
}
package com.zhang.fastdfsdemo.utils;
import com.zhang.fastdfsdemo.pojo.FastDFSFile;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author zb
* @version 1.0
* @date 2023/3/9 18:22
* @ClassName FastDFSClient
* @Description FastDFS工具类
* StorageServer也可以通过Tracker拿到,我们直接那StorageClient即可,
* 因为我们后期操作我们的文件,不管是上传、下载、删除都是通过我们storageClient进行操作的
*/
public class FastDFSClient {
//日志
private static Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
//初始化FastDFS,ClientGlobal.init方法会读取配置文件,并初始化对应的属性
static {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
} catch (IOException | MyException e) {
logger.error("FastDFS Client init Fail!", e);
e.printStackTrace();
}
}
/**
* 生成Storage客户端
*
* @return
* @throws IOException
*/
private static StorageClient getStorageClient() throws IOException {
TrackerServer trackerServer = getTrackerServer();
//我们可以通过trackerServer拿到StorageServer,不设置也能拿到StorageClient
return new StorageClient(trackerServer, null);
}
/**
* 生成Tracker服务器端
*
* @return
* @throws IOException
*/
private static TrackerServer getTrackerServer() throws IOException {
TrackerClient trackerClient = new TrackerClient();
return trackerClient.getTrackerServer();
}
/**
* 上传
*
* @param file 文件对象
* @return
*/
private static String[] upload(FastDFSFile file) {
//打印相关信息
logger.info("File Name:" + file.getName() + ",File lenght" + file.getContent().length);
//文件属性信息
NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair("author", file.getAuthor());
//上传时间
long startTime = System.currentTimeMillis();
//返回一个String数组
String[] uploadResults = null;
StorageClient storageClient = null;
try {
//拿到客户端
storageClient = getStorageClient();
//文件数组;后缀名;文件属性相关数组
uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
} catch (IOException | MyException e) {
e.printStackTrace();
//上传失败打印文件名
logger.error("上传失败 File Name:" + file.getName(), e);
}
//上传时间
logger.info("上传时间:" + (System.currentTimeMillis() - startTime + "ms"));
//验证上传结果
if (uploadResults == null && storageClient != null) {//判断上传结果是否为空
logger.error("上传失败" + storageClient.getErrorCode());//上传失败拿到错误结果
}
//上传成功会返回相应信息
logger.info("上传成功,group_name:" + uploadResults[0] + "remoteFileName:" + uploadResults[1]);//打印卷名和完整名字
return uploadResults;
}
/**
* 下载文件
*
* @param groupName 卷
* @param remoteFileName 完整文件名
* @return
*/
public static InputStream downFile(String groupName, String remoteFileName) {
try {
//创建storage客户端对象
StorageClient storageClient = getStorageClient();
//调用下载方法,返回一个数组
byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
//输入要下载的文件
InputStream ins = new ByteArrayInputStream(fileByte);
return ins;
} catch (IOException | MyException e) {
logger.error("下载失败", e);
}
return null;
}
/**
* 删除文件
*
* @param groupName 卷
* @param remoteFileName 完整文件名
*/
public static void deleteFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getStorageClient();
int i = storageClient.delete_file(groupName, remoteFileName);
logger.info("删除成功" + i);
} catch (IOException | MyException e) {
logger.error("删除失败", e);
}
}
/**
* 查看文件信息
* @param groupName 卷名
* @param remoteFileName 完整文件名
* @return
*/
public static FileInfo getFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getStorageClient();
//获取文件方法
FileInfo file_info = storageClient.get_file_info(groupName, remoteFileName);
return file_info;
} catch (Exception e) {
logger.error("查看文件信息失败", e);
}
return null;
}
/**
* 获取文件路径,上传至后返回的完整路径
* @return
* @throws IOException
*/
public static String getTrackerUrl() throws IOException {
return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":8888/";
}
}
我们写一个contrller测试即可,service我们不写直接跳过,正常情况下应该要写service,service调我们工具类方法,contrller再调service,我们这里只是演示!!!
controller
package com.zhang.fastdfsdemo.controller;
import com.zhang.fastdfsdemo.pojo.FastDFSFile;
import com.zhang.fastdfsdemo.utils.FastDFSClient;
import org.csource.common.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.IOException;
import java.io.InputStream;
/**
* @author zb
* @version 1.0
* @date 2023/3/9 21:12
* @ClassName FileContrller
* @Description TODD 测试文件上传、下载、删除、查看
*/
@Controller
public class FileContrller {
private static Logger logger = LoggerFactory.getLogger(FileContrller.class);
/**
* 上传文件
* 我们现在入参是 MultipartFile ,但我们真正要传进去的fastdfs file,那我们可以把它提取出来调用
*
* @param file
* @param redirectAttributes
* @return
*/
@PostMapping("upload")
public String singFile(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {//判断上传文件是否为空
redirectAttributes.addFlashAttribute("message", "请选择一个文件上传");
return "redirect:uploadStatus";
}
//拿到客户端
try {
//上传文件,拿到返回文件路径
String path = saveFile(file);
redirectAttributes.addFlashAttribute("message", "上传成功" + file.getOriginalFilename());
redirectAttributes.addFlashAttribute("path", "路径:" + path);
} catch (IOException | MyException e) {
e.printStackTrace();
}
return "redirect:uploadStatus";
}
/**
* 跳转上传文件状态也
*
* @return
*/
@GetMapping("/uploadStatus")
public String uploadStatus() {
return "uploadStatus";
}
/**
* 跳转文件上传页
*
* @return
*/
@GetMapping("/")
public String upload() {
return "upload";
}
/**
* MultipartFile转fastdfsfile
*
* @param multipartFile
* @return
* @throws IOException
*/
public String saveFile(MultipartFile multipartFile) throws IOException, MyException {
String[] fileAbsolutePath = {};
String fileName = multipartFile.getOriginalFilename();//获取文件名
String ext = fileName.substring(fileName.lastIndexOf(".") + 1);//截取后缀
byte[] file_buff = null;
InputStream inputStream = multipartFile.getInputStream();
if (inputStream != null) {
int len1 = inputStream.available();//拿到长度
file_buff = new byte[len1];
inputStream.read(file_buff);
}
inputStream.close();
FastDFSFile file = new FastDFSFile(fileName, file_buff, ext);//转成fastdfs文件类型
//上传,//返回组名和文件名
fileAbsolutePath = FastDFSClient.upload(file);
if (fileAbsolutePath == null) {
logger.error("上传失败");
}
String path = FastDFSClient.getTrackerUrl() + fileAbsolutePath[0] + "/" + fileAbsolutePath[1];//返回完整路径
return path;
}
}
html
上传文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>Spring Boot file upload example</h1>
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="file"><br><br>
<input type="submit" value="submit">
</form>
</body>
</html>
上传状态
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>SpringBoot - Upload Status</h1>
<div th:if="${message}">
<h2 th:text="${message}"></h2>
</div>
<div th:if="${path}">
<h2 th:text="${path}"></h2>
</div>
</body>
</html>
测试
上传成功

流览图

我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A
我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只