3. 畅购商城之FastDFS微服务搭建

第3章 分布式文件存储FastDFS

1. FastDFS介绍

1.1 FastDFS简介

FastDFS是一个以C语言开发的一项开源轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。

FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

1.2 FastDFS体系结构

FastDFS 架构包括Tracker Server和Storage Server。客户端请求Tracker Server 进行文件上传、下载,通过Tracker Server调度最终由Storage Server完成文件上传和下载。

Tracker Server 作用是负载均衡和调度,通过Tracker Server在文件上传时可以根据一些策略找到Storage Server提供文件上传服务。可以将Tracker称为追踪服务器或调度服务器。

Storage Server作用是文件存储,负责文件上传、下载、修改和删除等功能。客户端上传的文件最终存储在Storage服务器上,Storage Server没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将Storage称为存储服务器。在这里插入图片描述

1.3 上传流程

在这里插入图片描述

  1. Storage服务器定时向Tracker服务器发送上传信息
  2. 客服端向Tracker发送上传请求
  3. Tracker服务器查询可用的Storage
  4. Tracker服务器向客户端返回Storage服务器的ip和端口
  5. 客户端向Storage服务器上传文件
  6. Storage服务器生成file_id
  7. Storage服务器将上传内容写入磁盘
  8. Storage服务器向客户端返回file_id
  9. 客户端存储文件信息

其中file_id用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

1.4 FastDFS路径说明

group1/M00/00/00/rBGdZl5ak4yAHTNeAAxv6ziU5pA742.png

  • 组名(/group1):文件上传后所在的Storage组名称。在文件上传成功后由Storage 服务器返回,需要客户端自行保存。

  • 虚拟磁盘路径(/M00):Storage配置的虚拟路径,指向Storage组中某个节点的硬盘地址。与磁盘选项store_path*对应。如果配置了store_path0则是M00,如果配置了store_path1 则是M01,以此类推。

  • 数据两级目录(/00/00):Storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据。该数值采用算法计算得出

  • 文件名:与文件上传时的文件名称不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

2. FastDFS搭建

2.1 安装FastDFS镜像

本文采用Docker容器进行FastDFS的环境搭建。

1、查询FastDFS镜像

docker search fastdfs

2、拉取镜像

docker pull morunchang/fastdfs

3、运行tracker

docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh

4、运行storage

docker run -d --name storage --net=host -e TRACKER_IP=服务器IP:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh

参数说明:

  • -d 后台运行
  • –name 别名。–name 空格 别名 --name=别名
  • 使用的网络模式是–net=host, 告诉容器使用主机网络堆栈
  • -e添加到环境变量中
  • group1是组名,即Storage的组
  • 如果想要增加新的Storage服务器,再次运行该命令,注意更换新组名

2.2 修改配置

1、 进入storage中

docker exec -it storage  /bin/bash

2.、配置nginx.conf

vi /etc/nginx/conf/nginx.conf

修改以下内容

    server {
        listen       9000;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location ~ /M00 {
                    root /data/fast_data/data;
                    ngx_fastdfs_module;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

3、配置storage.conf

vi /etc/fdfs/storage.conf

修改以下内容

# 创建storage时的ip
tracker_server=服务器IP:22122

#推荐与Nignx配置的端口相同
http.server_port=9000

4、退出容器

exit

5、重启storage容器

docker restart storage

6、查看启动的容器

docker ps -a

7、开启启动设置

docker update --restart=always tracker
docker update --restart=always storage

8、开放端口
使用阿里云进行部署时,需要在安全组中开放9000、23000、22122端口,否则会造成无法访问

3. 文件存储微服务

thanksong-springcloud-provider模块下创建thankson-provider-fastdfs微服务,该服务主要用于实现文件上传、删除等功能。

3.1 pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>thanksong-springcloud-provider</artifactId>
        <groupId>com.thankson.springcloud</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>thankson-provider-fastdfs</artifactId>

    <dependencies>
        <!--FastFDS-->
        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.27.2</version>
        </dependency>
    </dependencies>
</project>

3.2 application.yml配置

在resources文件夹下创建application.yml

server:
  port: 9000
spring:
  application:
    name: FastDFS
  servlet:
    multipart:
      max-request-size: 10MB
      max-file-size: 10MB
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://www.xiexun.top:3306/changgou_sys?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  instance:
    prefer-ip-address: true

fdfs:
  # 读取时间
  so-timeout: 1000
  # 连接超时时间
  connect-timeout: 60
  # 缩略图
  thumbImage:
    # 宽
    width: 150
    # 高
    height: 150
  # tracker列表
  tracker-list:            #TrackerList参数,支持多个
    - www.xiexun.top:22122

com:
  thankson:
    springcloud:
      FastDFS:
        pathPrefix: www.xiexun.top:9000

3.3 启动类

com.thankson.fastdfs包下创建启动类FastDFSApplication.java

@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {"com.thankson.fastdfs.dao"})
@ComponentScan(value = {"com.thankson.common.util","com.thankson.fastdfs"})
public class FastDFSApplication {

    public static void main(String[] args) {
        SpringApplication.run(FastDFSApplication.class, args);
    }
}

3.4 代码编写

3.4.1 实体类

com.thankson.fastdfs.pojo包下创建FileMessage实体类

@Table(name = "tb_file_message")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FileMessage {
    /**
     * 自增ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    /**
     * 文件名称
     */
    @Column(name = "file_name")
    private String fileName;

    /**
     * 文件类型
     */
    @Column(name = "file_type")
    @NotBlank(message = "文件类型不能为空")
    private String fileType;

    /**
     * 文件扩展名
     */
    @Column(name = "file_extension")
    private String fileExtension;

    /**
     * 文件大小
     */
    @Column(name = "file_size")
    private Long fileSize;

    /**
     * 文件路径
     */
    @Column(name = "file_path")
    private String filePath;

    /**
     * 文件描述
     */
    @Column(name = "description")
    @NotBlank(message = "文件描述不能为空")
    private String description;

    /**
     * 归属系统标识
     */
    @Column(name = "sys_flag")
    @NotBlank(message = "系统标识不能为空")
    private String sysFlag;

    /**
     * 上传时间
     */
    @Column(name = "create_date")
    private Date createDate;

    /**
     * 上传人
     */
    @Column(name = "uploader")
    private String uploader;

    /**
     * 删除标识
     */
    @Column(name = "del_flag")
    private String delFlag;
}

3.4.2 数据访问层

com.thankson.fastdfs.dao包下创建FileMessageDao接口。
该接口需要继承tk.mybatis.mapper.common.Mapper类

public interface FileMessageDao extends Mapper<FileMessage> {
}

3.4.3 业务层

1、业务层接口
com.thankson.fastdfs.service包下创建FileService接口

public interface FileService {
    
    /**
     * 上传文件至FastDFS
     *
     * @param fileMessage 文件信息
     * @author Thankson
     * @date 2020年5月5日
     */
    Result<Object> uploadFile(InputStream inputStream, FileMessage fileMessage);
}

2、业务层实现类
com.thankson.fastdfs.service.impl包下创建FileServiceImpl类

@Service
public class FileServiceImpl implements FileService {
    @Autowired
    private FileMessageDao fileMessageDao;

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    @Value(value = "${com.thankson.springcloud.FastDFS.pathPrefix}")
    private String pathPrefix;

    @Override
    public Result<Object> uploadFile(InputStream inputStream, FileMessage fileMessage) {
        try {
            //上传文件至FastDFS
            StorePath storePath = fastFileStorageClient.uploadFile(inputStream, fileMessage.getFileSize(), fileMessage.getFileExtension(), null);
            if (storePath == null) {
                return new Result<>(false, 500, "文件上传失败");
            }
            //文件访问路径
            String path = pathPrefix + "/" + storePath.getFullPath();
            fileMessage.setFilePath(path);
            //保存文件信息
            int i = fileMessageDao.insertSelective(fileMessage);
            if (i < 1) {
                return new Result<>(false, 500, "保存文件信息失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new Result<>(false, 500, "上传文件时发生异常");
        }
        return new Result<>(true, 200, "上传成功", fileMessage);
    }
}

3.4.4 控制层

com.thankson.fastdfs.controller包下创建FileController类

@RestController
@RequestMapping(value = "/file")
public class FileController {

    @Autowired
    private FileService fileService;

    /**
     * 上传文件
     *
     * @param file        文件
     * @param fileMessage 文件信息
     * @author Thankson
     * @date 2020年5月5日
     */
    @PostMapping(value = "/uploadFile")
    public Result<Object> uploadFile(@RequestPart MultipartFile file, @RequestPart @Validated FileMessage fileMessage) {
        fileMessage.setFileName(FilenameUtils.getBaseName(file.getOriginalFilename()));
        fileMessage.setCreateDate(new Date());
        fileMessage.setFileExtension(FilenameUtils.getExtension(file.getOriginalFilename()));
        fileMessage.setFileSize(file.getSize());
        fileMessage.setUploader("test");
        InputStream inputStream;
        try {
            inputStream = file.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
            return new Result<>(false, 500, "上传文件流获取失败");
        }
        return fileService.uploadFile(inputStream,fileMessage);
    }
}

3.4.5 处理器

thankson-common-util工程com.thankson.common.util.handler包下创建BaseExceptionHandler类

/**
 * 异常处理
 *
 * @author Thankson
 * @date 2020年5月5日
 */
@RestControllerAdvice
public class BaseExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
    }

    @ExceptionHandler(ValidationException.class)
    public Result<Object> handleValidationException(ValidationException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, e.getCause().getMessage());
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Object> handleConstraintViolationException(ConstraintViolationException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<Object> handlerNoFoundException(Exception e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 404, "路径不存在,请检查路径是否正确");
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Result<Object> handleDuplicateKeyException(DuplicateKeyException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, "数据重复,请检查后提交");
    }


    @ExceptionHandler(Exception.class)
    public Result<Object> handleException(Exception e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, "系统繁忙,请稍后再试");
    }
}

3.5 测试

采用IDEA中自带的HTTP Client进行测试

3.5.1 创建FastDFS.http

在这里插入图片描述

3.5.2 添加请求

### 上传文件
POST http://localhost:9000/file/uploadFile
Content-Type: multipart/form-data; boundary=WebAppBoundary

--WebAppBoundary
Content-Disposition: form-data; name="file"; filename="新建文本文档.txt"

< C:\Users\69303\Desktop\新建文本文档.txt
--WebAppBoundary
Content-Disposition: form-data; name="fileMessage"
Content-Type: application/json

{
"author":"aaa",
"fileType": "1",
"description":"description",
"sysFlag":"test"
}
--WebAppBoundary--

3.5.3运行http后结果如下

在这里插入图片描述

3.5.4 访问链接

www.xiexun.top:9000/group1/M00/00/00/rBGdZl6xhfaAWrW6ABjs7y0zlQQ091.png
在这里插入图片描述

3.5.5 查看服务器

docker exec -it storage  /bin/bash
ls /data/fast_data/data/00/00

在这里插入图片描述

4. 结束语

至此,FastDFS的上传功能已经完成。至于其他功能在后续使用时会进行开发

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章