分布式文件系统
FastDFS相关
FastDFS简介
参考:http://www.xushuai.fun/2018/12/22/FastDFS简介/
FastDFS安装及配置
参考:http://www.xushuai.fun/2018/12/22/FastDFS安装/
文件上传微服务
需求分析
在很多系统都有上传图片/上传文件的需求,比如:上传课程图片、上传课程资料、上传用户头像等,为了提供系统的可重用性专门设立文件系统服务承担图片/文件的管理,文件系统服务实现对文件的上传、删除、查询等功能进行管理。
各各子系统不再开发上传文件的请求,各各子系统通过文件系统服务进行文件的上传、删除等操作。文件系统服务最终会将文件存储到fastDSF文件系统中。
下图是各各子系统与文件系统服务之间的关系:
下图是课程管理中上传图片处理流程:
执行流程如下:
- 管理员进入教学管理前端,点击上传图片。
- 图片上传至文件系统服务,文件系统请求fastDFS上传文件。
- 文件系统将文件入库,存储到文件系统服务数据库中。
- 文件系统服务向前端返回文件上传结果,如果成功则包括文件的Url路径。
- 课程管理前端请求课程管理进行保存课程图片信息到课程数据库。
- 课程管理服务将课程图片保存在课程数据库。
工程导入(省略)
我这里用的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: - 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;
@Configuration @Import(FdfsClientConfig.class)
@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 {
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;
@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;
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; }
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); 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;
@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); }
@Override @GetMapping("list/{courseId}") public CoursePic findById(@PathVariable String courseId) { isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR); return coursePicService.findById(courseId); }
@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;
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); }
public CoursePic findById(String courseId) { Optional<CoursePic> optionalCoursePic = coursePicRepository.findById(courseId); if (!optionalCoursePic.isPresent()) { ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST); } return optionalCoursePic.get(); }
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;
@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;
}
|
前端
前端没有什么需要修改的地方。
代码获取
代码获取