• 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:  # 缩略图生成参数
           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浏览器中请求即可。
  • 相关阅读:
    再学 GDI+[11]: DrawCurve 绘制曲线
    再学 GDI+[7]: DrawLines 绘制一组直线
    再学 GDI+[9]: DrawPolygon 绘制多边形
    再学 GDI+[10]: DrawClosedCurve 绘制闭合曲线
    再学 GDI+[13]: DrawBezier 绘制贝塞尔线
    再学 GDI+[8]: DrawRectangles 绘制一组矩形
    再学 GDI+[5]: DrawArc 绘制弧线
    再学 GDI+[12]: 准备工作 矩形命中
    再学 GDI+[6]: DrawPie 绘制饼形
    判断字符串中子串个数的函数
  • 原文地址:https://www.cnblogs.com/NyanKoSenSei/p/15915224.html
Copyright © 2020-2023  润新知