Docker - 部署FastDFS及SpringBoot文件上传下载实现

一. 前言

  1. 本文参考了以下文本,将其中内容进行个人化统合和补充:
    1. Docker中搭建FastDFS文件系统(多图教程)
    2. SpringBoot集成FastDFS依赖实现文件上传的示例
  2. 本地环境
    项目 版本
    docker宿主机系统 openSUSE Leap 15.3 x64
    docker版本 20.10.6-ce
    FastDFS镜像版本 season/fastdfs:1.2
    开发环境 macOS Catalina 10.15.5
    Spring Boot 版本 2.1.8.RELEASE
    JDK Version Java SE 1.8.0_281
    包依赖管理工具 Apache Maven 3.6.3

注:因为某些地区和网络供应商下,无法访问DockerHub、mvnrepository等服务器,本文默认在无墙环境下操作,如因网络原因导致。


二. 部署FastDFS

  1. FastDFS简介
    FastDFS是阿里余庆大神做的一个个人项目,以 C 语言开发的一项开源轻量级分布式文件系统,提供了文件存储、文件同步、文件访问(文件上传/下载)等功能。注:FastDFS适合存储海量的小文件,对于大文件(超过100M)存储管理,不建议使用FastDFS。
    FastDFS由两部分组成:Tracker Server(跟踪服务器)、Storage Server(存储服务器)。跟踪服务器,主要做调度工作,起到均衡的作用,负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳;存储服务器,主要提供容量和备份服务,以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。
  2. 拉取镜像并创建容器
    1. 首先这里选择的镜像版本是最新(CREATED 6 years ago)的纯净版(docker hub)
      # 拉取命令
      docker pull season/fastdfs:1.2
      # 等待下载完毕后见下方提示为成功
      # Downloaded newer image for season/fastdfs:1.2
      # docker.io/season/fastdfs:1.2
      
    2. 创建Tracker Server和Storage Server挂载目录
      # 目录可调整,但是后续操作需要根据当前创建的挂载目录进行修改
      mkdir -p /usr/local/server/fastdfs/tracker/data
      mkdir -p /usr/local/server/fastdfs/storage/data
      mkdir -p /usr/local/server/fastdfs/storage/path
      
    3. 创建Tracker Server容器
      # 这里对下方启动参数进行说明
      # -id == -i -d :交互模式运行容器 & 后台运行容器
      # --name :为启动的容器命名
      # --restart=always :开机自启
      # --net host :宿主机和容器共享networknamespace,也就是使用同一个网络环境,Tracker Server默认占用22122端口
      # -v :挂载目录(宿主机目录:容器目录),这里其实可以多加一个容器的/etc/fdfs/目录挂载,方便后期修改配置文件
      docker run -id --name tracker \
      --restart=always --net host \
      -v /usr/local/server/fastdfs/tracker/data:/fastdfs/tracker/data \
      season/fastdfs:1.2 tracker
      
      注:在这里创建的容器使用host网络模式目的是让Tracker Server容器和Storage Server容器在同一网段下,这样才方便连接。如果使用默认的bridge桥接模式启动,会在各自容器内创建独立的虚拟网卡,在Tracker Server和Storage Server连接的时候出现无法找到问题。有关docker网络模式解释详见:docker --net详解_Docker网络通信
    4. 创建Storage Server容器
      # 对下方命令进行解释(重复的见上方解释)
      # -e TRACKER_SERVER :指定Tracker Server的 ip地址:端口
      # Storage Server容器也使用host网络模式,默认占用23000端口
      docker run -id --name storage \
      --restart=always --net host \
      -v /usr/local/server/fastdfs/data/storage:/fastdfs/store_path \
      -e TRACKER_SERVER="{Tracker Server主机ip}:22122" \
      season/fastdfs:1.2 storage
      
  3. 修改Tracker Server配置文件
    1. Tracker Server容器中自带了Tracker Server和Client客户端工具,用来连接Tracker Server,而Client客户端工具默认的配置文件/etc/fdfs/client.conf中配置的默认Tracker Server地址大部分是错误的,需要手动修改配置文件用来连接正确的Tracker Server。
    2. 因为根据上面对Tracker Server容器运行时命令并为创建映射目录,且在伪终端中无法执行编辑命令,故拷出配置文件在宿主机中修改后再拷入容器。
      # 拷出到 /usr/local/server/fastdfs/ 目录下
      docker cp trakcer:/etc/fdfs/client.conf /usr/local/server/fastdfs/
      # 编辑
      nano /usr/local/server/fastdfs/client.conf
      # 修改tracker_server为Tracker Server的ip和端口,例如:
      tracker_server=192.168.123.200:22122
      # 保存后退出编辑器,再将文件覆盖到容器中
      docker cp /usr/local/server/fastdfs/client.conf tracker:/etc/fdfs
      
    3. 验证一下FastDFS是否部署成功
      # docker 命令检查容器是否正常启动
      docker ps
      # 进入Tracker Server容器
      docker exec -it tracker bash
      # 检查是否能连接到Storage Server,成功的话会打印宿主机信息和各Storage Server信息
      fdfs_monitor /etc/fdfs/client.conf
      # 创建测试文件
      echo "test" > test.txt
      # 使用Client客户端向Storage Server发送测试文件
      fdfs_upload_file /etc/fdfs/client.conf test.txt
      # 如果成功的话,Console会打印一串文件路径,例如:
      # group1/M00/00/00/wKh7yGIQxuuAH6baAAAACBfWGpM819.txt
      # 退出伪终端
      exit
      
      注:Storage Server映射的data路径是:/usr/local/server/fastdfs/data/storage/data
  4. 修改宿主机防火墙
    这一步是为了Spring Boot集成Client可以正常访问而做的操作。
    # 检查防火墙状态,建议保持启动,开放需要端口即可
    systemctl status firewalld
    # 检查已开启端口,如果已经存在 22122/tcp 或 23000/tcp,则无需执行对应端口的开放操作
    firewall-cmd --zone=public --list-ports
    # 开放22122/tcp 和 23000/tcp,console打印success即为成功
    firewall-cmd --permanent --zone=public --add-port=22122/tcp
    firewall-cmd --permanent --zone=public --add-port=23000/tcp
    # 刷新防火墙配置
    firewall-cmd --reload
    
    执行操作前,可以检查FastDFS所占用的端口。
    # 查看当前已占用的所有端口和进程
    lsof -i
    # 检查对应端口的进程
    lsof -i:22122
    lsof -i:23000
    

三. 部署配套的Nginx(非必须)

  1. 因为部署的FastDFS只能在宿主机使用,这肯定是不方便的,此时就需要Nginx进行代理,实现web访问。
  2. 创建Nginx映射目录
    mkdir -p /usr/local/server/fastdfs/nginx/
    
  3. Storage Server自带了一份nginx配置文件,使用这个即可
    # 拷出到映射目录中
    docker cp storage:/etc/nginx/conf/nginx.conf /usr/local/server/fastdfs/nginx/
    # 编辑配置文件
    nano /usr/local/server/fastdfs/nginx/nginx.conf
    # 修改配置文件中local节点内容为:
    location / {
       root /fastdfs/store_path/data;
       ngx_fastdfs_module;
    }
    # 保存并退出编辑器
    
  4. 创建nginx容器
    # 映射宿主机的8888端口,这个可以随意改动
    # -e TRACKER_SERVER 需要改为Tracker Server的ip地址
    docker run -id --name fastdfs_nginx \
    --restart=always \
    -v /usr/local/server/fastdfs/data/storage:/fastdfs/store_path \
    -v /usr/local/server/fastdfs/nginx/nginx.conf:/etc/nginx/conf/nginx.conf \
    -p 8888:80 \
    -e TRACKER_SERVER={Tracker Server主机ip}:22122 \
    season/fastdfs:1.2 nginx
    
  5. 测试查看之前上传的文件
    # 通过Nginx代理访问FastDFS
    # URL构成为:http://{宿主机ip}:{Nginx代理端口}/{Storage Server返回的文件路径},例如:
    curl -i http://127.0.0.1:8888/group1/M00/00/00/wKh7yGIQxuuAH6baAAAACBfWGpM819.txt
    # 如果成功,则会打印 http response 的头信息和文件内容
    # 同时,也可将URL在web浏览器中访问
    

四. Spring Boot整合FastDFS_Client

  1. 给项目添加依赖(pom.xml):可以将依赖添加到common工程中
    详情详见:https://github.com/tobato/FastDFS_Client

    <!-- FastDFS Client -->
    <dependency>
        <groupId>com.github.tobato</groupId>
        <artifactId>fastdfs-client</artifactId>
        <version>1.27.2</version>
    </dependency>
    
  2. 在需要使用FastDFS的工程的启动类(Application)添加FastDFS Java客户端注解:

    @Import(FdfsClientConfig.class)
    @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
    
  3. 添加配置(application.yml),例如:

    fdfs:
      so-timeout: 1500  # 读取时间
      connect-timeout: 600  #连接时间
      thumb-image:  # 缩略图生成参数
        width: 150
        height: 150
      tracker-list:  # Tracker Server主机
        - 192.168.123.200:22122
      web-server-url: http://192.168.123.200:8888/
    
  4. 可以创建一个工具类,方便上传下载:

    点击查看代码
    import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
    import com.github.tobato.fastdfs.domain.fdfs.StorePath;
    import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
    import com.github.tobato.fastdfs.exception.FdfsUnsupportStorePathException;
    import com.github.tobato.fastdfs.service.FastFileStorageClient;
    import org.apache.commons.io.FilenameUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.ByteArrayInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.charset.Charset;
    
    /**
     * FastDFS工具类
     **/
    @Component
    public class FastDFSClient {
        @Autowired
        private FastFileStorageClient storageClient;
    
        @Autowired
        private FdfsWebServer fdfsWebServer;
    
        /**
         * 上传文件
         *
         * @param file 文件对象
         * @return 文件访问地址
         * @throws IOException
         */
        public String uploadFile(MultipartFile file) throws IOException {
            StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(),
                    FilenameUtils.getExtension(file.getOriginalFilename()), null);
            return getResAccessUrl(storePath);
        }
    
        /**
         * 上传文件
         *
         * @param file 文件对象
         * @return 文件访问地址
         * @throws IOException
         */
        public String uploadFile(File file) throws IOException {
            FileInputStream inputStream = new FileInputStream(file);
            StorePath storePath = storageClient.uploadFile(inputStream, file.length(),
                    FilenameUtils.getExtension(file.getName()), null);
            return getResAccessUrl(storePath);
        }
    
        /**
         * 将一段字符串生成一个文件上传
         *
         * @param content       文件内容
         * @param fileExtension
         * @return
         */
        public String uploadFile(String content, String fileExtension) {
            byte[] buff = content.getBytes(Charset.forName("UTF-8"));
            ByteArrayInputStream stream = new ByteArrayInputStream(buff);
            StorePath storePath = storageClient.uploadFile(stream, buff.length, fileExtension, null);
            return getResAccessUrl(storePath);
        }
    
        /**
         * 封装图片完整URL地址
         */
        private String getResAccessUrl(StorePath storePath) {
            String fileUrl = fdfsWebServer.getWebServerUrl() + storePath.getFullPath();
            return fileUrl;
        }
    
        /**
         * 删除文件
         *
         * @param fileUrl 文件访问地址
         * @return
         */
        public void deleteFile(String fileUrl) {
            if (StringUtils.isEmpty(fileUrl)) {
                return;
            }
            try {
                StorePath storePath = StorePath.parseFromUrl(fileUrl);
                storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
            } catch (FdfsUnsupportStorePathException e) {
                System.out.println(e.getMessage());
                /** TODO 只是测试,所以未使用,logger,正式环境请修改打印方式 **/
            }
        }
    
        /**
         * 下载文件
         *
         * @param fileUrl 文件URL
         * @return 文件字节
         * @throws IOException
         */
        public byte[] downloadFile(String fileUrl) throws IOException {
            String group = fileUrl.substring(0, fileUrl.indexOf("/"));
            String path = fileUrl.substring(fileUrl.indexOf("/") + 1);
            DownloadByteArray downloadByteArray = new DownloadByteArray();
            byte[] bytes = storageClient.downloadFile(group, path, downloadByteArray);
            return bytes;
        }
    }
    
  5. 创建上传和下载方法

    点击查看代码
    @RestController
    @RequestMapping("/file")
    public class FileUploadController {
        @Autowired
        private FastDFSClient fastDFSClient;
    
        /**
         * 上传
         * @param file
         * @return
         * @throws IOException
         */
        @RequestMapping("/upload")
        public String uploadFile(MultipartFile file) throws IOException {
            }return fastDFSClient.uploadFile(file);
        }
    
        /**
         * 下载
         * @param fileUrl
         * @param response
         * @throws IOException
         */
        @RequestMapping("/download")
        public void downloadFile(String fileUrl, HttpServletResponse response) throws IOException {
            byte[] bytes = fastDFSClient.downloadFile(fileUrl);
            response.setHeader("Content-disposition",
                    "attachment;filename=" + URLEncoder.encode(fileUrl.substring(fileUrl.lastIndexOf("/") + 1), "UTF-8"));
            response.setCharacterEncoding("UTF-8");
            ServletOutputStream outputStream = null;
            try {
                outputStream = response.getOutputStream();
                outputStream.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    注:上传时可以使用postman发送一个form-data的post请求,将文件发送到FastDFS主机,成功时会得到一个文件全路径URL串。下载时直接通过返回的URL在web浏览器中请求即可。
posted @ 2022-02-20 21:35  苍凉温暖  阅读(695)  评论(0编辑  收藏  举报