背景:有机会从0-1架构设计智慧消防领域的解决方案项目,涉及到文件的存储,果断放弃了传统的服务器直接文件存储和ftp存储,选择使用国内比较热的fastdfs,新的项目肯定选择使用最新的版本V6.07,记录下Fastdfs的搭建及项目中封装使用

开发环境:springboot(2.3.5.RELEASE)+fastdfs(V6.07)+ubuntu

一、安装fdfs

1、下载最新版本的fastdfs,FastDFS 发行版 - Gitee.com,上传到linux指定目录

springboot中fdfs项目转hdfs_fastdfs

2、进入tar包所在目录,执行tar -zxvf libfastcommon-1.0.53.tar.gz

springboot中fdfs项目转hdfs_java_02

3、解压完成后,进入解压目录libfastcommon-1.0.53,执行./make.sh install

springboot中fdfs项目转hdfs_fastdfs_03

如果报错,安装gcc即可

springboot中fdfs项目转hdfs_spring boot_04

说明:如果不知道需要安装哪个版本软件,可以执行 sudo apt-cache search gcc(软件名),查询远程仓库有哪些版本可以按照,然后执行apt-get install安装即可

安装make

springboot中fdfs项目转hdfs_java_05

执行make 

springboot中fdfs项目转hdfs_spring boot_06

执行sudo ./make.sh install

springboot中fdfs项目转hdfs_spring_07

4、 进入fastdfs-6.07目录,执行sudo ./make.sh

springboot中fdfs项目转hdfs_java_08

执行sudo ./make.sh install

springboot中fdfs项目转hdfs_java_09

5、配置tracker

进入/etc/fdfs目录,有三个.sample后缀的文件(自动生成的fdfs模板配置文件),通过cp命令cp tracker.conf.sample tracker.conf,创建tracker配置文件

springboot中fdfs项目转hdfs_linux_10

编辑tracker.conf:vi tracker.conf,修改相关参数

springboot中fdfs项目转hdfs_spring boot_11

启动tracker执行命令:fdfs_trackerd /etc/fdfs/tracker.conf start

springboot中fdfs项目转hdfs_spring boot_12

查看tracker启动日志 和端口

springboot中fdfs项目转hdfs_spring boot_13

springboot中fdfs项目转hdfs_java_14

6、配置storage 

进入/etc/fdfs目录,cp storage.conf.sample storage.conf,创建storage配置文件,执行vi storage.conf,修改相关参数

springboot中fdfs项目转hdfs_java_15

springboot中fdfs项目转hdfs_fastdfs_16

 启动storage,执行fdfs_storaged /etc/fdfs/storage.conf start

springboot中fdfs项目转hdfs_fastdfs_17

查看启动日志

springboot中fdfs项目转hdfs_spring boot_18

通过monitor来查看storage是否成功绑定,执行命令:fdfs_monitor /etc/fdfs/storage.conf

springboot中fdfs项目转hdfs_fastdfs_19

至此fastdfs单机版安装完毕,如果需要部署集群,可以参考下官网的搭建文件 

二、nginx配置,用于前端直接通过ngnix访问文件或者图片,在nginx配置目录下(默认安装/etc/nginx/conf.d),新建fdfs.conf文件,内容如下

server {
        listen       18811;
        server_name  localhost 192.168.103.202;

        location /group1/M00{
        alias /usr/local/env/fastdfs/storage/data/;
         autoindex on;

       }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        root   html;
       }
    }

 重启ngnix,执行ngnix -s reload命令

三、springboot项目fastdfs工具类封装及使用,直接上可执行代码

import com.github.tobato.fastdfs.domain.fdfs.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.Charset;

/**
 * Fdfs配置类
 *
 * @author wangfenglei
 */
@Component
public class FdfsUtil {
    private static Logger log = LoggerFactory.getLogger(FdfsUtil.class);
    @Autowired
    private FastFileStorageClient storageClient;

    @Value("${fdfs.resHost}")
    private String resHost;

    /**
     * 上传文件
     *
     * @param file 文件对象
     * @return 文件访问地址
     * @throws Exception 异常
     */
    public String uploadFile(MultipartFile file) throws Exception {
        StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
        return getResAccessUrl(storePath);
    }

    /**
     * 上传图片
     *
     * @param bufferedImage  图片缓冲流
     * @param imageExtension 图片扩展名,如png
     * @return 文件访问地址
     * @throws Exception 异常
     */
    public String uploadImage(BufferedImage bufferedImage, String imageExtension) throws Exception {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, imageExtension, os);
        InputStream input = new ByteArrayInputStream(os.toByteArray());
        StorePath storePath = storageClient.uploadFile(input, os.size(), imageExtension, null);
        return getResAccessUrl(storePath);
    }


    /**
     * 上传文件
     *
     * @param filePath 文件全路径
     * @return 文件可访问地址
     * @throws Exception 异常
     */
    public String uploadFile(String filePath) throws Exception {
        File file = new File(filePath);
        StorePath storePath = storageClient.uploadFile(new FileInputStream(file), file.length(), FilenameUtils.getExtension(filePath), 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);
    }

    /**
     * 下载文件
     *
     * @param fileUrl 文件路径,http全路径和group开头
     * @return 文件二进制数组
     * @throws Exception 异常
     */
    public byte[] downloadFile(String fileUrl) throws Exception {
        int index = fileUrl.indexOf("group");
        String groupUrl = fileUrl.substring(fileUrl.indexOf("group"));
        String group = groupUrl.substring(0, fileUrl.indexOf("/") + (index > 0 ? 1 : 0));
        String path = groupUrl.substring(fileUrl.indexOf("/") + (index > 0 ? 2 : 1));
        DownloadByteArray downloadByteArray = new DownloadByteArray();
        byte[] bytes = storageClient.downloadFile(group, path, downloadByteArray);
        return bytes;
    }

    /**
     * 查看文件的信息
     *
     * @param groupName 组
     * @param path      路径
     * @return 文件信息
     */
    public FileInfo getFile(String groupName, String path) {
        FileInfo fileInfo = null;
        log.info("文件信息入参,groupName:{},path:{}", groupName, path);
        try {
            fileInfo = storageClient.queryFileInfo(groupName, path);
            log.info("文件信息:{}", fileInfo.toString());
        } catch (Exception e) {
            log.error("Non IO Exception: Get File from Fast DFS failed", e);
        }
        return fileInfo;
    }

    /**
     * 上传图片并且生成缩略图
     *
     * @param file 上传文件
     * @return 文件访问路径
     * @throws Exception 异常
     */
    public String uploadImageAndCrtThumbImage(MultipartFile file) throws Exception {
        StorePath storePath = storageClient.uploadImageAndCrtThumbImage(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
        return getResAccessUrl(storePath);
    }

    /**
     * 删除文件
     *
     * @param fileUrl 文件访问地址
     */
    public void deleteFile(String fileUrl) {
        if (null == fileUrl || "".equals(fileUrl)) {
            return;
        }
        try {
            StorePath storePath = StorePath.parseFromUrl(fileUrl);
            storageClient.deleteFile(storePath.getGroup(), storePath.getPath());

        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 可通过浏览器访问路径
     *
     * @param storePath 存储路径
     * @return 可访问路径
     */
    private String getResAccessUrl(StorePath storePath) {
        String fileUrl = resHost + "/" + storePath.getFullPath();
        return fileUrl;
    }

    public static void main(String[] args) {
        String fileUrl = "group1/M00/00/00/wKhnymELkZGAYe3NAAABG6VUfqA418.png";
        System.out.println(fileUrl.indexOf("group"));
        String groupUrl = fileUrl.substring(fileUrl.indexOf("group"));
        String group = groupUrl.substring(0, fileUrl.indexOf("/") + 1);

        String path = groupUrl.substring(fileUrl.indexOf("/") + 2);

        System.out.println(groupUrl);
        System.out.println(group);
        System.out.println(path);
    }
}

application.yml文件配置

fdfs:
  soTimeout: 1500
  connectTimeout: 600
  #缩略图生成参数
  thumbImage:
    width: 150
    height: 150
  #TrackerList参数,支持多个
  trackerList:
    - 192.168.103.202:22122
    #- 192.168.103.202:22122
  pool:
    #不限制
    max-total: -1
  #通过nginx 访问地址
  resHost: http://192.168.103.202:18811

 文件上传

import com.wfl.common.constant.CommonConstant;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.wfl.firefighting.util.FdfsUtil;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import sun.misc.BASE64Decoder;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 文件上传控制类
 *
 * @author wangfenglei
 */
@Api(tags = "文件上传")
@RestController
@RequestMapping("/upload")
@Slf4j
public class UploadController {
    @Autowired
    private FdfsUtil fdfsUtil;

    /**
     * 上传单个文件
     *
     * @param request http请求
     * @return 文件路径
     */
    @ApiOperation(value = "文件上传-上传单个文件", notes = "上传多个文件,返回文件可访问路径")
    @PostMapping("/uploadFile")
    public Result<?> uploadFile(HttpServletRequest request) {
        String filePath = "";
        try {
            MultipartHttpServletRequest servletRequest = (MultipartHttpServletRequest) request;
            Map<String, MultipartFile> fileMap = servletRequest.getFileMap();
            for (Map.Entry<String, MultipartFile> entry : fileMap.entrySet()) {
                MultipartFile file = entry.getValue();
                filePath = fdfsUtil.uploadFile(file);
                break;
            }
        } catch (Exception e) {
            log.error(e.toString(), e);
            return Result.error("上传失败");
        }

        return Result.OK(filePath);
    }
}

 文件下载

直接前端调用window.open(url),浏览器默认下载功能,也可以自己封装个文件下载通用工具类,以下为本人封装的文件操作工具类,可用于文件下载

import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
/**
 * 文件工具类
 *
 * @author wangfenglei
 */
@Slf4j
public class FileUtil {
    /**
     * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
     *
     * @param destPath 存放目录
     */
    public static void mkdirs(String destPath) {
        File file = new File(destPath);
        //当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }

    /**
     * 获取系统临时目录
     *
     * @return 系统临时目录
     */
    public static String getSystemTempDirPath() {
        return System.getProperty("java.io.tmpdir");
    }

    /**
     * 获得指定文件的byte数组
     *
     * @param filePath 文件绝对路径
     * @return 数组
     */
    public static byte[] fileToByte(String filePath) {
        ByteArrayOutputStream byteArrayOutputStream = null;
        BufferedInputStream bufferedInputStream = null;

        try {
            File file = new File(filePath);
            if (!file.exists()) {
                throw new FileNotFoundException("file not exists");
            }

            byteArrayOutputStream = new ByteArrayOutputStream((int) file.length());
            bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
            int buf_size = 1024;
            byte[] buffer = new byte[buf_size];
            int len = 0;

            while (-1 != (len = bufferedInputStream.read(buffer, 0, buf_size))) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            log.error(e.toString(), e);
            return null;
        } finally {
            try {
                if (null != byteArrayOutputStream) {
                    byteArrayOutputStream.close();
                }
                if (null != bufferedInputStream) {
                    bufferedInputStream.close();
                }
            } catch (Exception e) {
            }
        }
    }

    /**
     * 根据byte数组,生成文件
     *
     * @param bfile    文件数组
     * @param filePath 文件存放路径
     * @param fileName 文件名称
     */
    public static void byteToFile(byte[] bfile, String filePath, String fileName) {
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file;
        try {
            File dir = new File(filePath);
            //判断文件目录是否存在
            if (!dir.exists() && !dir.isDirectory()) {
                dir.mkdirs();
            }

            file = new File(filePath + File.separator + fileName);
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);
            bos.write(bfile);
        } catch (Exception e) {
            log.error(e.toString(), e);
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (Exception e) {
            }
        }
    }

    /**
     * 文件下载
     *
     * @param response     响应
     * @param FileFullPath 文件全路径
     */
    public static void downFile(HttpServletResponse response, String FileFullPath) {
        InputStream ins = null;
        BufferedInputStream bins = null;
        OutputStream outs = null;
        BufferedOutputStream bouts = null;
        try {

            File file = new File(FileFullPath);
            if (file.exists()) {
                ins = new FileInputStream(FileFullPath);
                bins = new BufferedInputStream(ins);
                outs = response.getOutputStream();
                bouts = new BufferedOutputStream(outs);
                response.setContentType("application/x-download");
                response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(FileFullPath, "UTF-8"));
                int bytesRead = 0;
                byte[] buffer = new byte[8192];
                while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) {
                    bouts.write(buffer, 0, bytesRead);
                }

                bouts.flush();
            }
        } catch (Exception e) {
            log.error("文件下载出错", e);
        } finally {
            if (null != bins) {
                try {
                    bins.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != ins) {
                try {
                    ins.close();
                } catch (IOException e) {
                }
            }
            if (null != bouts) {
                try {
                    bouts.close();
                } catch (IOException e) {
                }
            }

            if (null != outs) {
                try {
                    outs.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 文件下载
     *
     * @param response    响应
     * @param inputStream 文件输入刘
     * @param fileName    文件名
     */
    public static void downFile(HttpServletResponse response, InputStream inputStream, String fileName) {
        BufferedInputStream bins = null;
        OutputStream outs = null;
        BufferedOutputStream bouts = null;

        try {
            bins = new BufferedInputStream(inputStream);
            outs = response.getOutputStream();
            bouts = new BufferedOutputStream(outs);
            response.setContentType("application/x-download");
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) {
                bouts.write(buffer, 0, bytesRead);
            }
            bouts.flush();

        } catch (Exception e) {
            log.error("文件下载出错", e);
        } finally {
            if (null != bins) {
                try {
                    bins.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
            if (null != bouts) {
                try {
                    bouts.close();
                } catch (IOException e) {
                }
            }

            if (null != outs) {
                try {
                    outs.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

配合FdfsUtil封装,实现文件inputStream流下载

byte[] byteArr = fdfsUtil.downloadFile(device.getQrcode());
 FileUtil.downFile(response,new ByteArrayInputStream(byteArr),"downloadFileName.txt");