FastDFS

第一章 FastDFS入门

1.1 分布式文件系统

分布式文件系统是一个软件/软件服务器,这个软件管理的文件通常不是在一个服务器节点上,而是在多个服务器节点上,这些服务器节点通过网络相连构成一个庞大的文件存储服务器集群,这些服务器都用于存储文件资源,通过分布式文件系统来管理这些服务器上的文件。

常见的DFS有:FastDFS,GFS,HDFS,Lustre,Ceph,GridFS,mogileFS,TFS等。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JzQySTLn-1666595816577)(C:\Users\ADMINI~1\AppData\Local\Temp\WeChat Files\0dbff8dfe04b1b87496ffece6de7422.jpg)]

1.2 FastDFS简介

FastDFS是Alibaba开发的,C语言开发的。

FastDFS对文件进行管理,功能包括:文件存储,文件同步,文件访问(文件上传,文件下载,文件删除)等,解决了大容量文件存储的问题。

FastDFS文件系统由两大部分组成,一个是客户端,一个是服务器。

服务端由两个部分组成,一个是跟踪器tracker,一个是存储节点storage。

跟踪器主要做调度工作,在内存中记录集群中存储节点storage的状态信息。

存储节点用于存储文件,包括文件和文件属性(meta data) 都保存到存储服务器磁盘上,完成文件管理的所有功能:文件存储,文件同步,文件访问等等。

1.3 FastDFS 安装

  1. 首先需要依赖

    gcc
    libevent
    libevent-devel
    
    #centos中,查看是否安装
    yum list installed | grep gcc
    yum list installed|grep libevent
    yum list installed|grep libevent-devel
    #如果没安装,则需要安装
    yum install gcc libevent libevent-devel -y
    
  2. 从github上下载fastdfds,一共四个文件

    fastdfs-5.11.tar.gz	#fastdfs
    fastdfs-client-java-master.zip	#源码
    fastdfs-nginx-module-master.zip	#nginx扩展模块
    libfastcommon-1.0.36.tar.gz	#公共库
    
    首先要安装第四个文件,然后安装第一个文件
    #在程序目录下
    ./make.sh #编译
    ./make.sh install #安装
    
    #安装结束后
    #fastdfs的所有配置文件都在/etc/fdfs目录下,里面一共有四个文件
    client.conf.sample	#客户端连接文件
    storage.conf.sample	#storage server
    storage_ids.conf.sample	#记录id用的
    tracker.conf.sample	#tracker.server
    
    #将fastdfs安装目录下的conf中的http.conf mime.types拷贝到/etc/fdfs中
    

1.4 FastDFS配置和启动

主要配置两个配置文件:tracker.conf和storage.conf

tracker.conf

# the tracker server port
port=22122

# the base path to store data and log files
#path must exists
base_path=/opt/fastdfs/tracker

storage.conf
# comment or remove this item for fetching from tracker server,
# in this case, use_storage_id must set to true in tracker.conf,
# and storage_ids.conf must be configed correctly.
group_name=group1

# the storage server port
port=23000

# the base path to store data and log files
base_path=/opt/fastdfs/storage

# store_path#, based 0, if store_path0 not exists, it's value is base_path
# the paths must be exist
#存放文件的磁盘路径
store_path0=/opt/fastdfs/storage/files
#store_path1=/home/yuqing/fastdfs2

# tracker_server can ocur more than once, and tracker_server format is
#  "host:port", host can be hostname or ip address
tracker_server=192.168.213.133:22122


#启动fastdfs tracker
fdfs_trackerd <config_file> [start | stop | restart]

#启动fastdfs storage
fdfs_storaged <config_file> [start | stop | restart]

1.5 fastdfs文件处理

启动后,在之前storage设置的files文件夹下会生成data文件夹,其中有256个文件夹,每个文件夹下又有256个文件夹,fastdfs就用这65536个文件夹来存储文件,之所以创建这么多文件夹是因为如果都放在一个文件夹中,读取会变慢

#测试fdfs上传文件
fdfs_test <config_file> <operation>
	operation:upload,download,getmeta,setmeta,delete and query_servers
#在上传前,需要设置client.conf的日志存放位置和里面的ip地址。
fdfs_test /etc/fdfs/client.conf upload aa.txt

#上传后的打印信息
tracker_query_storage_store_list_without_group: 
	server 1. group_name=, ip_addr=192.168.213.133, port=23000

group_name=group1, ip_addr=192.168.213.135, port=23000
storage_upload_by_filename
#文件上传到哪个组中,组决定存到哪个机器中去,远程文件名称决定存放在哪个磁盘目录下,M00/00/00指第一块磁盘的00文件夹的00文件夹下
group_name=group1, remote_filename=M00/00/00/wKjVhWKRgrWAXSwdAAAAAAAAAAA352.txt
source ip address: 192.168.213.133
file timestamp=2022-05-27 19:02:29
file size=0
file crc32=0
example file url: http://192.168.213.133/group1/M00/00/00/wKjVhWKRgrWAXSwdAAAAAAAAAAA352.txt
storage_upload_slave_by_filename
group_name=group1, remote_filename=M00/00/00/wKjVhWKRgrWAXSwdAAAAAAAAAAA352_big.txt
source ip address: 192.168.213.133
file timestamp=2022-05-27 19:02:29
file size=0
file crc32=0
example file url: http://192.168.213.133/group1/M00/00/00/wKjVhWKRgrWAXSwdAAAAAAAAAAA352_big.txt
#下载文件
fdfs_test <config_file> download <group_name> <remote_filename>
#会下载到当前目录下
fdfs_test /etc/fdfs/client.conf download group1 wKjVhWKRgrWAXSwdAAAAAAAAAAA352.txt

第二章 fastDFS整合nginx

fastDFS提供了一个nginx扩展模块,利用该模块,可以通过Nginx访问已经上传到fastDFS的文件。

fastdfs-nginx-module-master.zip #扩展模块

因为这个模块必须要在Nginx的安装过程中才能添加,所以需要重新安装nginx,为了和原来的nginx区分,取名为nginx_fdfs

2.1 安装整合了fastDFS的Nginx

  1. 安装nginx时,使用如下配置

    #在nginx的解压缩目录下,执行上述配置
    ./configure --prefix=/usr/local/nginx_fdfs --add-module=/home/sunshine/software/fastdfs-nginx-module-master/src
    
  2. 安装完成后,配置nginx

    将/home/sunshine/software/fastdfs-nginx-module-master/src目录下的mod_fastdfds.conf文件拷贝到/etc/fdfs/目录下,才能正常启动nginx
    
    修改配置文件mod_fastdfs.conf
    
    # the base path to store log files
    base_path=/opt/fastdfs/nginx_mod
    
    # FastDFS tracker_server can ocur more than once, and tracker_server format is
    #  "host:port", host can be hostname or ip address
    # valid only when load_fdfs_parameters_from_tracker is true
    tracker_server=192.168.213.133:22122
    
    # if the url / uri including the group name
    # set to false when uri like /M00/00/00/xxx
    # set to true when uri like ${group_name}/M00/00/00/xxx, such as group1/M00/xxx
    # default value is false
    url_have_group_name = true
    
    # path(disk or mount point) count, default value is 1
    # must same as storage.conf
    store_path_count=1
    
    # store_path#, based 0, if store_path0 not exists, it's value is base_path
    # the paths must be exist
    # must same as storage.conf
    store_path0=/opt/fastdfs/storage/files
    #store_path1=/home/yuqing/fastdfs1
    
    
    配置nginx的配置文件
    location ~/group[1-9]/M[0-9]{
    	ngx_fastdfs_module;
    }
    
    
  3. 启动nginx

    ngx_http_fastdfs_set pid=9337 #这句话表示扩展模块启动成功-·················································································
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4J2UCtke-1666595816578)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220528112337265.png)]

2.2 通过java程序处理文件

2.2.1 上传文件

public static void upload(){
    TrackerServer ts = null;
    StorageServer ss = null;
    try {
        //读取fastdfs的配置文件用于将所有的tracker的地址读取到内存中
        ClientGlobal.init("fastdfs.conf");
        TrackerClient tc = new TrackerClient();
        ts = tc.getConnection();
        ss = tc.getStoreStorage(ts);

        //定义Storage的客户端对象,需要使用这个对象来完成具体的文件上传下载和删除操作
        StorageClient sc = new StorageClient(ts,ss);
        /*文件上传
            * 参数1:为需要上传文件的绝对路径
            * 参数2:需要上传文件的扩展名
            * 参数3:文件的属性文件,通常不上传
            * 返回一个String数组,这个数据十分重要,建议存入数据库
            * */
        String[] result = sc.upload_file("D:\\java\\java_study\\spring_framework\\springsession\\ch03-fastdfs-java\\src\\main\\resources\\test.jpg","jpg",null);
        /*返回值是个数组,
            * 数组有两个元素
            * 第一个元素是文件所在的组名
            * 第二个元素是文件所在的远程路径名称
            * */

        for(String str:result){
            System.out.println(str);
        }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (MyException e) {
            e.printStackTrace();
        }
        finally {
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                ts.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}

2.2.2 下载文件

private static void download() {
    TrackerServer ts = null;
    StorageServer ss = null;
    try {
        //读取fastdfs的配置文件用于将所有的tracker的地址读取到内存中
        ClientGlobal.init("fastdfs.conf");
        TrackerClient tc = new TrackerClient();
        ts = tc.getConnection();
        ss = tc.getStoreStorage(ts);

        //定义Storage的客户端对象,需要使用这个对象来完成具体的文件上传下载和删除操作
        StorageClient sc = new StorageClient(ts,ss);
        /*
            * 文件下载
            * 参数1:文件所在组名
            * 参数2:文件远程文件名
            * 参数3:需要保存的本地文件名称
            * 返回一个int类型的数据,返回0表示文件下载成功。其他值都表示失败。
            * */
        String group_name = "group1";
        String remote_filename = "M00/00/00/wKjVhWKYFMCAZDG1AAAGcNcjGq8790.jpg";
        String local_filename = "D:\\java\\java_study\\spring_framework\\springsession\\ch03-fastdfs-java\\src\\main\\resources\\test_download.jpg";
        int result = sc.download_file(group_name,remote_filename,local_filename);
        System.out.println(result);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (MyException e) {
        e.printStackTrace();
    }
    finally {
        try {
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            ts.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.2.3 文件删除

private static void delete(){
    TrackerServer ts = null;
    StorageServer ss = null;
    try {
        ClientGlobal.init("fastdfs.conf");
        TrackerClient tc = new TrackerClient();
        ts = tc.getConnection();
        ss = tc.getStoreStorage(ts);

        StorageClient sc = new StorageClient(ts,ss);
        /*
            * 文件删除:
            * 参数1:文件所在组名
            * 参数2:文件远程文件名
            * 返回值:0删除成功,其他值删除失败
            * */
        String group_name = "group1";
        String remote_filename = "M00/00/00/wKjVhWKYFMCAZDG1AAAGcNcjGq8790.jpg";
        int result = sc.delete_file(group_name,remote_filename);
        System.out.println(result);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (MyException e) {
        e.printStackTrace();
    }
}

2.3 热部署插件GeneratorMapper.xml

<!--pom文件逆向工程插件-->
<plugin>
    <!--mybatis代码自动生成插件-->
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.6</version>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>
    </dependencies>
    <configuration>
        <!--配置文件的位置-->
        <configurationFile>GeneratorMapper.xml</configurationFile>
        <verbose>true</verbose>
        <overwrite>true</overwrite>
    </configuration>
</plugin>
GeneratorMapper.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!-- 指定连接数据库的 JDBC 驱动包所在位置,指定到你本机的完整路径 -->
    <classPathEntry location="C:\Users\Administrator\.m2\repository\mysql\mysql-connector-java\8.0.29\mysql-connector-java-8.0.29.jar
"/>
    <!-- 配置 table 表信息内容体,targetRuntime 指定采用 MyBatis3 的版本 -->
    <context id="tables" targetRuntime="MyBatis3">
        <!-- 抑制生成注释,由于生成的注释都是英文的,可以不让它生成 -->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!-- 配置数据库连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://192.168.213.135:3306/fastdfs?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8"
                        userId="root"
                        password="syp960722..">
        </jdbcConnection>
        <!-- 生成 model 类,targetPackage 指定 model 类的包名, targetProject 指定
        生成的 model 放在 idea 的哪个工程下面-->
        <javaModelGenerator targetPackage="com.bjpowernode.model"
                            targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
            <property name="trimStrings" value="false"/>
        </javaModelGenerator>
        <!-- 生成 MyBatis 的 Mapper.xml 文件,targetPackage 指定 mapper.xml 文件的
        包名, targetProject 指定生成的 mapper.xml 放在 idea 的哪个工程下面 -->
        <sqlMapGenerator targetPackage="mapper"
                         targetProject="src/main/resources">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>
        <!-- 生成 MyBatis 的 Mapper 接口类文件,targetPackage 指定 Mapper 接口类的包
        名, targetProject 指定生成的 Mapper 接口放在 eclipse 的哪个工程下面 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.bjpowernode.dao" targetProject="src/main/java">
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>

        <!-- 数据库表名及对应的 Java 模型类名 -->
        <table tableName="creditor_info" domainObjectName="CreditorInfo"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false"/>
    </context>
</generatorConfiguration>

最后执行生成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-39Sf716Z-1666595816579)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220614102620913.png)]

2.4 上传文件

@Controller
public class CreditorController {
    @Resource
    private CreditorService creditorService;
    @RequestMapping("/")
    public String creditors(Model model){
        List<CreditorInfo> list = creditorService.selectAll();
        model.addAttribute("creditorList",list);
        return "creditors";
    }

    @GetMapping("/upload/{id}")
    public String toUpload(@PathVariable Integer id,Model model){   //通过 @PathVariable 可以将 URL 中占位符参数传入方法中
        CreditorInfo creditorInfo = creditorService.selectById(id);
        model.addAttribute("creditorInfo",creditorInfo);
        return "upload";
    }
    /**
     * 文件上传
     * 参数MultipartFile 为spring提供的一个类,专门用于封装请求中的文件数据
     * 属性名必须与表单中文件域的名字完全相同否则无法获取文件数据
     * */
    
    
    @PostMapping("/upload")
    public String upload(Integer id, MultipartFile myfile, Model model) throws IOException {
//        System.out.println(myfile.getBytes());//获取文件对应字节数组
//        System.out.println(myfile.getInputStream());//获取文件对应输入流
//        System.out.println(myfile.getContentType());//获取文件类型
//        System.out.println(myfile.getName());//获取表单元素名
//        System.out.println(myfile.getOriginalFilename());//获取文件名
//        System.out.println(myfile.getSize());//获取文件大小
//        System.out.println(myfile.isEmpty());//判断文件是否为空,如果没有上传文件或文件大小为0,这个值都为true

        //获取文件对应字节数组
        byte[] buffFile = myfile.getBytes();
        //获取文件名
        String fileName = myfile.getOriginalFilename();
        //获取文件扩展名,但有的文件可能没有文件扩展名,需要做逻辑判断
        String fileExtrName = fileName.substring(fileName.lastIndexOf(".")+1);
        String[] result = FastDFSUtil.upload(buffFile,fileExtrName);
        System.out.println(result);
        CreditorInfo creditorInfo = new CreditorInfo();
        creditorInfo.setId(id);
        creditorInfo.setFileSize(myfile.getSize());
        creditorInfo.setFileType(myfile.getContentType());
        creditorInfo.setOldFilename(myfile.getOriginalFilename());
        creditorInfo.setGroupName(result[0]);
        creditorInfo.setRemoteFilepath(result[1]);
        creditorService.updateFileInfo(creditorInfo);
        //上传成功后页面不刷新
        model.addAttribute("message","文件上传成功,点击确定返回列表界面");
        model.addAttribute("url","/");
        return "success";
    }
}
<!--creditors页面-->

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table>
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>性别</td>
        <td>电话</td>
        <td>地址</td>
        <td></td>
    </tr>

    <tr th:each="creditor:${creditorList}">
        <td th:text="${creditorStat.count}">序号</td>
        <td th:text="${creditor.realName}">姓名</td>
        <td th:text="${creditor.sex==1?'男':'女'}">性别</td>
        <td th:text="${creditor.phone}">电话</td>
        <td th:text="${creditor.address}">地址</td>
        <td>
            <span th:if="${creditor.remoteFilepath==null||creditor.remoteFilepath==''}">
                <a th:href="@{|/upload/${creditor.id}|}">上传</a>
            </span>
            <span th:if="${creditor.remoteFilepath!=null&&creditor.remoteFilepath!=''}">
                <a th:href="@{|/download|}">下载</a>
                <a th:href="@{|/delete|}">删除</a>
            </span>

        </td>
    </tr>
</table>
</body>
</html>
<!--success页面-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <script>
        if(confirm("[[${message}]]")){	//这一部分作用如下图所示
            window.top.location.href="[[${url}]]"
        }
    </script>
</head>
<body>

</body>
</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWG29syg-1666595816579)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220621110653832.png)]

<!--upload页面-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <!--上传文件的表单,method必须为post  enctype必须指定为multipart/form-data,让表单以二进制的方式进行传递-->
    <form th:action="@{|/upload|}" target="success" method="post" enctype="multipart/form-data">
        姓名:<span th:text="${creditorInfo.realName}"></span><br>
        性别:<span th:text="${creditorInfo.sex==1?'男':'女'}"></span><br>
        电话:<span th:text="${creditorInfo.phone}"></span><br>
        地址:<span th:text="${creditorInfo.address}"></span><br>
        文件:<input type="file" name="myfile">
        <input type="hidden" name="id" th:value="${creditorInfo.id}"><br>
        <input type="submit" value="上传文件">
    </form>
<iframe name="success" style="display: none"></iframe>
</body>
</html>

2.5 下载文件

@RequestMapping("/download/{id}")
    public ResponseEntity<byte[]> download(@PathVariable Integer id){
        /*
        * 创建响应实体对象,spring会将这个对象返回给浏览器,作为响应数据
        * 参数1 为响应的具体数据
        * 参数2 为响应时的头文件信息
        * 参数3 为响应时的状态码
        * */
        CreditorInfo creditorInfo = creditorService.selectById(id);

        byte[] bufferFile = FastDFSUtil.download(creditorInfo.getGroupName(),creditorInfo.getRemoteFilepath());
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);//设置响应类型为文件类型
        headers.setContentLength(creditorInfo.getFileSize());//设置响应时的文件大小用于自动提供下载进度
        headers.setContentDispositionFormData("attachment",creditorInfo.getOldFilename());//设置下载时的默认文件名
        System.out.println(creditorInfo.getOldFilename());
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bufferFile,headers,HttpStatus.OK);
        return responseEntity;//会自动下载
    }

2.6 删除文件

@RequestMapping("/delete/{id}")
public String delete(@PathVariable Integer id){
    creditorService.deleteFileById(id);
    return "redirect:/";
}

@Override
public void deleteFileById(Integer id) {
    CreditorInfo creditorInfo = creditorInfoMapper.selectByPrimaryKey(id);
    FastDFSUtil.delete(creditorInfo.getGroupName(),creditorInfo.getRemoteFilepath());
    creditorInfo.setGroupName("");
    creditorInfo.setRemoteFilepath("");
    creditorInfo.setOldFilename("");
    creditorInfo.setFileType("");
    creditorInfo.setFileSize(0L);
    creditorInfoMapper.updateByPrimaryKeySelective(creditorInfo);
}
ava
@RequestMapping("/delete/{id}")
public String delete(@PathVariable Integer id){
    creditorService.deleteFileById(id);
    return "redirect:/";
}

@Override
public void deleteFileById(Integer id) {
    CreditorInfo creditorInfo = creditorInfoMapper.selectByPrimaryKey(id);
    FastDFSUtil.delete(creditorInfo.getGroupName(),creditorInfo.getRemoteFilepath());
    creditorInfo.setGroupName("");
    creditorInfo.setRemoteFilepath("");
    creditorInfo.setOldFilename("");
    creditorInfo.setFileType("");
    creditorInfo.setFileSize(0L);
    creditorInfoMapper.updateByPrimaryKeySelective(creditorInfo);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值