学成在线笔记六:分布式文件系统
分布式文件系统

FastDFS相关

FastDFS简介

参考:http://www.xushuai.fun/2018/12/22/FastDFS简介/

FastDFS安装及配置

参考:http://www.xushuai.fun/2018/12/22/FastDFS安装/

文件上传微服务

需求分析

在很多系统都有上传图片/上传文件的需求,比如:上传课程图片、上传课程资料、上传用户头像等,为了提供系统的可重用性专门设立文件系统服务承担图片/文件的管理,文件系统服务实现对文件的上传、删除、查询等功能进行管理。

各各子系统不再开发上传文件的请求,各各子系统通过文件系统服务进行文件的上传、删除等操作。文件系统服务最终会将文件存储到fastDSF文件系统中。

下图是各各子系统与文件系统服务之间的关系:

下图是课程管理中上传图片处理流程:

执行流程如下:

  1. 管理员进入教学管理前端,点击上传图片。
  2. 图片上传至文件系统服务,文件系统请求fastDFS上传文件。
  3. 文件系统将文件入库,存储到文件系统服务数据库中。
  4. 文件系统服务向前端返回文件上传结果,如果成功则包括文件的Url路径。
  5. 课程管理前端请求课程管理进行保存课程图片信息到课程数据库。
  6. 课程管理服务将课程图片保存在课程数据库。

工程导入(省略)

我这里用的FastDFS客户端不是教程里面的这个,我使用的是

依赖:

1
2
3
4
5
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.1-RELEASE</version>
</dependency>

配置:

1
2
3
4
5
6
7
8
fdfs:
so-timeout: 1501
connect-timeout: 601
thumb-image: #缩略图生成参数
width: 150
height: 150
tracker-list: #TrackerList参数,支持多个
- 192.168.136.110:22122 # 以实际你的机器为准

Java配置类:配置了过后,会自动帮你注入用于操作文件的API类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.xuecheng.filesystem.config;

import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;

/**
* 导入FastDFS-Client组件
*
* @author tobato
*
*/
@Configuration
@Import(FdfsClientConfig.class)
// 解决jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class ComponetImport {
// 导入依赖组件
}

上传实现

FileSystemControllerApi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.xuecheng.api.filesystem;

import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import io.swagger.annotations.Api;
import org.springframework.web.multipart.MultipartFile;

@Api(value = "文件管理服务", description = "文件管理,提供文件的上传、下载等操作")
public interface FileSystemControllerApi {

/**
* 文件上传
*
* @param file 文件
* @param filetag 文件标签
* @param businesskey 业务key
* @param metadata 元数据, JSON格式
* @return UploadFileResult 上传结果
*/
UploadFileResult upload(MultipartFile file,
String filetag,
String businesskey,
String metadata);

}

FileSystemController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.xuecheng.filesystem.controller;

import com.xuecheng.api.filesystem.FileSystemControllerApi;
import com.xuecheng.filesystem.service.FileSystemService;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import com.xuecheng.framework.domain.filesystem.response.FileSystemCode;
import com.xuecheng.framework.domain.filesystem.response.UploadFileResult;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.web.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("filesystem")
public class FileSystemController extends BaseController implements FileSystemControllerApi {

@Autowired
private FileSystemService fileSystemService;

/**
* 文件上传
*
* @param file 文件
* @param filetag 文件标签
* @param businesskey 业务key
* @param metadata 元数据, JSON格式
* @return UploadFileResult 上传结果
*/
@Override
@PostMapping("upload")
public UploadFileResult upload(@RequestParam("file") MultipartFile file,
@RequestParam("filetag")String filetag,
@RequestParam(value = "businesskey", required = false)String businesskey,
@RequestParam(value = "metadata", required = false)String metadata) {
FileSystem upload = fileSystemService.upload(file, filetag, businesskey, metadata);
isNullOrEmpty(upload, FileSystemCode.FS_UPLOADFILE_SERVERFAIL);
return new UploadFileResult(CommonCode.SUCCESS, upload);
}
}

FileSystemService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.xuecheng.filesystem.service;

import com.alibaba.fastjson.JSON;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.xuecheng.filesystem.dao.FileSystemRepository;
import com.xuecheng.framework.domain.filesystem.FileSystem;
import com.xuecheng.framework.domain.filesystem.response.FileSystemCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;

@Slf4j
@Service
public class FileSystemService extends BaseService {

@Autowired
private FileSystemRepository fileSystemRepository;

@Autowired
private FastFileStorageClient fastFileStorageClient;

/**
* 文件上传
*
* @param file 文件
* @param filetag 文件标签
* @param businesskey 业务key
* @param metadata 元数据, JSON格式
* @return UploadFileResult 上传结果
*/
public FileSystem upload(MultipartFile file, String filetag, String businesskey, String metadata) {
// 上传文件
String fileId = uploadFile(file);
// 创建文件信息对象
FileSystem fileSystem = new FileSystem();
fileSystem.setFileId(fileId);
fileSystem.setFilePath(fileId);
fileSystem.setBusinesskey(businesskey);
fileSystem.setFiletag(filetag);
if (StringUtils.isNotBlank(metadata)) {
try {
Map metadataMap = JSON.parseObject(metadata, Map.class);
fileSystem.setMetadata(metadataMap);
} catch (Exception e) {
log.error("[文件上传] 从JSON获取文件元数据失败, metadata = {}", metadata);
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_METAERROR);
}
}
fileSystem.setFileName(file.getOriginalFilename());
fileSystem.setFileSize(file.getSize());
fileSystem.setFileType(file.getContentType());
// 保存到文件数据库
FileSystem save = fileSystemRepository.save(fileSystem);

return save;
}

/**
* 文件上传到FastDFS
*
* @param file 文件
* @return 文件ID
*/
private String uploadFile(MultipartFile file) {
try {
if (file.isEmpty()) {
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_FILEISNULL);
}
// 获取文件后缀名
String filename = file.getOriginalFilename();
assert filename != null;
String suffix = filename.substring(filename.indexOf(".") + 1);

// 上传文件
StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), suffix, null);
// 返回文件ID
return storePath.getFullPath();
} catch (IOException e) {
log.error("读取文件内容发生IO异常. e = {}", e);
ExceptionCast.cast(FileSystemCode.FS_UPLOADFILE_SERVERFAIL);
return null;
}
}
}

FileSystemRepository

1
2
3
4
5
6
7
package com.xuecheng.filesystem.dao;

import com.xuecheng.framework.domain.filesystem.FileSystem;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface FileSystemRepository extends MongoRepository<FileSystem, String> {
}

测试

访问图片地址:http://192.168.136.110/group1/M00/00/00/wKiIbl1yH6SARrBnAAB03ZQYayU081.jpg

OK!成功上传和访问到图片。

课程图片管理

完成课程图片的增删改查。

后端

CoursePicControllerApi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.xuecheng.api.course;

import com.xuecheng.framework.domain.course.CoursePic;
import com.xuecheng.framework.model.response.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(value = "课程图片管理接口", description = "课程图片管理接口,提供图片的增删改查")
public interface CoursePicControllerApi {

@ApiOperation("新增课程图片")
CoursePic saveCoursePic(String courseId, String pic);

@ApiOperation("查询课程图片")
CoursePic findById(String courseId);

@ApiOperation("删除课程图片")
ResponseResult deleteById(String courseId);

}

CoursePicController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.xuecheng.manage_course.controller;

import com.xuecheng.api.course.CoursePicControllerApi;
import com.xuecheng.framework.domain.course.CoursePic;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.web.BaseController;
import com.xuecheng.manage_course.service.CoursePicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("course/coursepic")
public class CoursePicController extends BaseController implements CoursePicControllerApi {

@Autowired
private CoursePicService coursePicService;

/**
* 新增/更新课程图片
*
* @param courseId 课程ID
* @param pic 图片ID
* @return CoursePic
*/
@Override
@PostMapping("add")
public CoursePic saveCoursePic(@RequestParam String courseId,
@RequestParam String pic) {
isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR);
isNullOrEmpty(pic, CommonCode.PARAMS_ERROR);
return coursePicService.save(courseId, pic);
}

/**
* 查询课程图片
*
* @param courseId 课程ID
* @return CoursePic
*/
@Override
@GetMapping("list/{courseId}")
public CoursePic findById(@PathVariable String courseId) {
isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR);
return coursePicService.findById(courseId);
}

/**
* 查询课程图片
*
* @param courseId 课程ID
* @return CoursePic
*/
@Override
@DeleteMapping("delete")
public ResponseResult deleteById(@RequestParam String courseId) {
isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR);
coursePicService.deleteById(courseId);
return ResponseResult.SUCCESS();
}
}

CoursePicService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.xuecheng.manage_course.service;

import com.xuecheng.framework.domain.course.CoursePic;
import com.xuecheng.framework.domain.course.response.CourseCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.service.BaseService;
import com.xuecheng.manage_course.dao.CoursePicRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Slf4j
@Service
public class CoursePicService extends BaseService {

@Autowired
private CoursePicRepository coursePicRepository;

/**
* 新增/更新课程图片
*
* @param courseId 课程ID
* @param pic 图片ID
* @return CoursePic
*/
public CoursePic save(String courseId, String pic) {
CoursePic result = null;
// 查询课程图片
Optional<CoursePic> optionalCoursePic = coursePicRepository.findById(courseId);
if (optionalCoursePic.isPresent()) {
// 更新课程图片
result = optionalCoursePic.get();
result.setPic(pic);
} else {
// 新增课程图片
result = new CoursePic();
result.setCourseid(courseId);
result.setPic(pic);
}
return coursePicRepository.save(result);
}

/**
* 查询课程图片
*
* @param courseId 课程ID
* @return CoursePic
*/
public CoursePic findById(String courseId) {
// 查询课程图片
Optional<CoursePic> optionalCoursePic = coursePicRepository.findById(courseId);
if (!optionalCoursePic.isPresent()) {
ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
}
return optionalCoursePic.get();
}

/**
* 删除课程图片
*
* @param courseId 课程ID
* @return
*/
public void deleteById(String courseId) {
coursePicRepository.deleteById(courseId);
}
}

CoursePicRepository

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.xuecheng.framework.domain.course;

import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.io.Serializable;

/**
* Created by admin on 2018/2/10.
*/
@Data
@ToString
@Entity
@Table(name="course_pic")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class CoursePic implements Serializable {
private static final long serialVersionUID = -916357110051689486L;

@Id
@GeneratedValue(generator = "jpa-assigned")
private String courseid;
private String pic;

}

前端

前端没有什么需要修改的地方。

代码获取

代码获取

文章作者: imxushuai
文章链接: https://www.imxushuai.com/2020/06/10/24.学成在线笔记六:分布式文件系统/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 imxushuai
支付宝打赏
微信打赏