fastDFS全家桶

配置 同时被 3 个专栏收录
12 篇文章 0 订阅
66 篇文章 0 订阅
1 篇文章 0 订阅

前言:
fastDFS是一个使用十分广泛的分布式文件存储系统,很多公司都在用它。
有幸我的业务中也涉及到,从开始的研究,到后来的使用,中间踩过许多坑,现将我的经验总结,便于大家和我以后查阅,防止踩坑。
参考:https://www.mongona.com/blog/5
参考连接:https://www.mongona.com/blog/5

1. docker 安装fastDFS

只需要将下面的111.111.111.111替换为你的服务器的IP即可,其他都不要动。

docker pull delron/fastdfs
docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker
docker run -dti --network=host --name storage -e TRACKER_SERVER=111.111.111.111:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage

2. 项目配置fastDFS

2.1 yml配置

application.yml配置参数

# 设置fastdfs
fdfs:
  so-timeout: 1501
  connect-timeout: 1601
  thumb-image:
    width: 150
    height: 150
  tracker-list: ${tracker_list:111.111.111.111:22122} // 用于配置文件调度
  file_server_url: ${file_server_url:111.111.111.111:8888} // 自定义,用于从fdfs获取保存文件

2.2 fdsf_client.conf

文件分片上传不需要这个配置文件,普通文件上传才需要这个配置,将他放到resource同yml平级

connect_timeout=30
network_timeout=60
base_path=/home/fastdfs
log_level=info
use_connection_pool = false
connection_pool_max_idle_time = 3600
load_fdfs_parameters_from_tracker=false
use_storage_id = false
storage_ids_filename = storage_ids.conf
http.tracker_server_port=80

3. fastDFS 上传普通文件

具体项目,文章最后,我会放到github上。

3.1 普通上传的主要方法

controller
业务需要,附加了许多参数

    /**
     * 将各种类型的文件,上传到fastdfs
     *
     * @param formId      formId
     * @param tenantId    企业id
     * @param fieldName   字段名称
     * @param imageWidth  图片宽度
     * @param imageHeight 图片高度
     * @param file        非图片文件
     * @return 文件传输对象
     * @throws Exception 异常
     */
    @PostMapping(value = "/all/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ApiOperation(value = "将各种类型的文件,上传到fastdfs")
    @CrossOrigin // 解决前端跨域问题
    public BaseVO<FileTransferVo> uploadAllKindsOfFile(
            @ApiParam(value = "表单id") @RequestParam(value = "form_id", required = false) Long formId,
            @ApiParam(value = "企业id") @RequestParam(value = "tenant_id", required = false) Long tenantId,
            @ApiParam(value = "filedName") @RequestParam(value = "field_name", required = false) String fieldName,
            @ApiParam(value = "图片长度") @RequestParam(value = "image_width", required = false) Integer imageWidth,
            @ApiParam(value = "图片高度") @RequestParam(value = "image_height", required = false) Integer imageHeight,
            @ApiParam(value = "文件") @RequestBody MultipartFile file
    ) {

        if (file == null || file.isEmpty()) {
            return new BaseVO(BaseVO.ILLEGAL_PARAM_CODE, "参数不合法");
        }
        //获取文件类型
        String originalFilename = file.getOriginalFilename();
        String fileType = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        FieldBaseVO field = null;
        if (!StringUtils.isEmpty(fieldName) && !(originalFilename.endsWith("xls") || originalFilename.endsWith("xlsx")
                || originalFilename.endsWith("zip"))) {

            field = metadataClient.getField(tenantId, formId, fieldName);
            if (field == null) {
                return new BaseVO(BaseVO.ILLEGAL_PARAM_CODE, "参数不合法");
            }
            if ("image".equals(field.getType()) && (imageHeight == null || imageWidth == null)) {
                return new BaseVO(BaseVO.ILLEGAL_PARAM_CODE, "参数不合法");
            }

            FileVO fileVO = (FileVO) field;
            if (!CollectionUtils.isEmpty(fileVO.getExtensions()) && !fileVO.getExtensions().contains(fileType)) {
                return new BaseVO(BaseVO.ILLEGAL_PARAM_CODE, "参数不合法");
            }
            if (fileVO.getSize() != null && fileVO.getSize() * KB_NUMBER * KB_NUMBER < file.getSize()) {
                return new BaseVO(BaseVO.ILLEGAL_PARAM_CODE, "参数不合法");
            }
        }

        FileTransferVo fileTransferVo = setFileTransfer(field, imageWidth, imageHeight, fileType, file);

        if (fileTransferVo != null) {
            if (field != null && fileTransferVo.getIsImage() == 1) {
                return fileService.uploadImage(fileTransferVo);
            } else {
                return fileService.uploadFile(fileTransferVo);
            }
        } else {
            return new BaseVO(BaseVO.OTHER_CODE, "将各种类型的文件,上传到fastdfs失败");
        }
    }

    /**
     * 重新处理传输数据
     *
     * @param fieldBaseVO 文件对象
     * @param imageWidth  图片宽
     * @param imageHeight 图片高
     * @param type        文件类型
     * @param file        非图片文件
     * @return 封装后的文件对象
     * @throws Exception 抛出异常
     */
    private FileTransferVo setFileTransfer(FieldBaseVO fieldBaseVO, Integer imageWidth, Integer imageHeight, String type,
                                           MultipartFile file) {

        FileTransferVo fileTransferVo = new FileTransferVo();
        try {
            List<Long> longs = snowflakeClient.uniqueIds(TWO);
            fileTransferVo.setId(longs.get(0));
            fileTransferVo.setUniqueName(longs.get(1).toString());
            fileTransferVo.setFileName(file.getOriginalFilename());
            fileTransferVo.setFileType(type);
            fileTransferVo.setBytes(chunkUtil.toByteArray(file.getInputStream()));
            //图片
            if (!(fieldBaseVO == null || !"image".equals(fieldBaseVO.getType()))) {
                fileTransferVo.setIsImage(1);
                fileTransferVo.setImageHeight(imageHeight);
                fileTransferVo.setImageWidth(imageWidth);
                fileTransferVo.setCompress(((ImageVO) fieldBaseVO).getCompress());
                fileTransferVo.setImageString(fastDFSUtil.imageToBase64(file));
                fileTransferVo.setInputStream(file.getInputStream());
            }
        } catch (IOException e) {
            log.error("setFileTransfer", e.getMessage());
            return null;
        }
        return fileTransferVo;
    }

service实现

import cn.cncommdata.file.vo.FileTransferVo;
import org.csource.common.MyException;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;

    /**
     * 上传图片
     *
     * @param fileTransferVo 上传图片对象
     * @return 上传后返回地址
     * @throws Exception 抛出异常
     */
    public String uploadImage(FileTransferVo fileTransferVo) throws Exception {

        StringBuffer path = new StringBuffer();

        try {
            String[] strings = getStorageClient().upload_file(inputToByte(fileTransferVo.getInputStream()),
                    fileTransferVo.getFileType(), null);

            path.append(strings[0]).append("/").append(strings[1]);
        } catch (MyException e) {
            throw new Exception("获取连接失败!");
        } catch (IOException e) {
            throw new Exception("获取连接失败!");
        }
        return path.toString();
    }

    /**
     * 获取fastDFS的storageClient
     *
     * @return storageClient
     * @throws Exception 抛出异常
     */
    private StorageClient getStorageClient() throws Exception {
        // 初始化文件资源
        ClassPathResource cpr = new ClassPathResource("fdfs_client.conf");
        ClientGlobal.init(cpr.getClassLoader().getResource("fdfs_client.conf").getPath());
        // 链接FastDFS服务器,创建tracker和Storage
        TrackerClient trackerClient = new TrackerClient();

        // 3、使用 TrackerClient 对象创建连接,获得一个 TrackerServer 对象。
        TrackerServer trackerServer = trackerClient.getConnection();
        // 4、创建一个 StorageServer 的引用,值为 null
        StorageServer storageServer = null;
        // 5、创建一个 StorageClient 对象,需要两个参数 TrackerServer 对象、StorageServer 的引用
        return new StorageClient(trackerServer, storageServer);
    }

    /**
     * inputStream 转 bytes数组
     *
     * @param inStream 流
     * @return bytes数组
     * @throws IOException 抛出异常
     */
    public byte[] inputToByte(InputStream inStream)
            throws IOException {
        final int len = 100;
        ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
        byte[] buff = new byte[len];
        int rc = 0;
        while ((rc = inStream.read(buff, 0, len)) > 0) {
            swapStream.write(buff, 0, rc);
        }
        byte[] bytes = swapStream.toByteArray();
        return bytes;
    }
3.2 VO
package cn.cncommdata.file.vo;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.InputStream;
import java.io.Serializable;
import java.util.List;

/**
 * @author: leimin
 * @description:
 * @create: on 2019/03/26
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileTransferVo implements Serializable {

    /**
     * 主键id
     */
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 文件唯一名称
     */
    private String uniqueName;

    /**
     * 缩略图
     */
    private String thumbnail;

    /**
     * 文件保存路径
     */
    private String path;

    /**
     * 创建时间
     */
    private Long createTime;

    /**
     * 用于接收文件的bytes[]数组/图片base64string
     */
    private String imageString;
    /**
     * 用于接收文件的输入流
     */
    private InputStream inputStream;
  /**
     * 生成缩略图的最大边长,等比缩放
     */
    private int compress;

    /**
     * 传输图片的限制大小,超出提示
     */
    private int imageMaxMemory;

    /**
     * 前端传图片宽度
     */
    private int imageWidth;

    /**
     * 前端传图片高度
     */
    private int imageHeight;

    /**
     * 文件类型,例如:jpg,mp4等。
     */
    private String fileType;

    /**
     * 用于解析文件是不是图片,0:否,1:是
     */
    private int isImage;

    /**
     * 用于封装,分片
     */
    private List<byte[]> bytes;

    /**
     * 是否上传成功,0:否,1:是
     */
    private int  isUpload;
}

4. fastDFS 分片上传大文件

4.1添加自定义配置
  # 将文件按10m大小等分分片
upload:
  chunkSize: 10485760 # 10M
4.2 分片上传代码

    @Override
    public BaseVO<FileTransferVo> uploadFile(FileTransferVo fileTransferVo) {

        //1.redis记录分片信息,当前分片下标
        String uploadKey = "fileKey:" + fileTransferVo.getId();
        String currChunkKey = uploadKey + ":currChunkKey";

        try {
            //3.分片上传
            uploadChunk(fileTransferVo, uploadKey, currChunkKey);
        } catch (Exception e) {
            log.info(e.getMessage());
            return new BaseVO(BaseVO.OTHER_CODE, "上传文件失败!");
        }
        //设置当前时间戳
        fileTransferVo.setCreateTime(System.currentTimeMillis());

        fileTransferVo.setInputStream(null);
        fileTransferVo.setImageString(null);
        fileTransferVo.setBytes(null);
        return new BaseVO(BaseVO.SUCCESS_CODE, "操作成功!", fileTransferVo);
    }

    /**
     * 上传文件的分片信息,所有分片上传完成后保存到数据库
     *
     * @param fileTransferVo 上传对象
     * @param uploadKey      上传key
     * @param currChunkKey   分片下标
     * @return 上传对象
     * @throws Exception 抛出异常
     */
    private FileTransferVo uploadChunk(FileTransferVo fileTransferVo, String uploadKey, String currChunkKey) throws Exception {

        String fdfsPathKey = uploadKey + ":fdfsPathKey";
        StorePath path = new StorePath();
        //1.循环遍历文件,开始上传,
        List<byte[]> list = fileTransferVo.getBytes();
        for (int i = 0; i < list.size(); i++) {

            if (returnActualLength(list.get(i)) == 0 && i < list.size() - 1) {
                throw new Exception("传输的数据有问题!");
            }
            //获取当前上传分片编号
            Object cNum = redisTemplate.opsForValue().get(currChunkKey);
            int chunkCurrNumber = cNum != null ? (int) cNum : 0;
            if (i == chunkCurrNumber) {

                //2.开始上传分片
                InputStream inputStream = new ByteArrayInputStream(list.get(i));
                if (i == 0) {
                    path = appendFileStorageClient.uploadAppenderFile("group1", inputStream, list.get(i).length,
                            FileUtil.extName(fileTransferVo.getFileName()));
                    if (path == null) {
                        throw new Exception("获取远程文件路径出错!");
                    }
                    redisTemplate.opsForValue().set(fdfsPathKey, path.getPath());

                } else {
                    String noGroupPath = (String) redisTemplate.opsForValue().get(fdfsPathKey);
                    if (StringUtils.isEmpty(noGroupPath)) {
                        throw new Exception("无法获取上传远程服务器文件出错!");
                    }
                    appendFileStorageClient.modifyFile("group1", noGroupPath, inputStream, list.get(i).length,
                            (long) chunkCurrNumber * chunkSize);
                }

                //3.一个分片上传完成
                if (i + 1 < list.size()) {
                    redisTemplate.opsForValue().set(currChunkKey, "" + (i + 1));
                } else if (i + 1 == list.size()) {
                    fileTransferVo.setPath(path.getGroup() + "/" + path.getPath());
                    //拼接访问ip地址
                    fileTransferVo.setPath(fileServerUrl + "/" + fileTransferVo.getPath());
                    saveFileTransferInDB(fileTransferVo);
//                    System.out.println(fileTransferVo.getPath());
                }
            }
        }
        //设置当前时间戳
        fileTransferVo.setCreateTime(System.currentTimeMillis());
        return fileTransferVo;
    }

5. github

https://github.com/LM917178900/cnFile

  • 1
    点赞
  • 0
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值