项目经验之文件搭建文件上传服务
1.新建springboot服务
依赖
<?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>fileupload</artifactId> <groupId>com.test.parent</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.test.upload</groupId> <artifactId>file-upload</artifactId> <version>1.0.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project>
2.编写application.yml配置
server:
port: 8082
spring:
application:
name: upload-service
servlet:
multipart:
max-file-size: 5MB # 限制文件上传的大小
3.引导类
@SpringBootApplication @EnableDiscoveryClient public class FileUploadApplication { public static void main(String[] args) { SpringApplication.run(FileUploadApplication.class, args); } }
4.编写controller
编写controller需要知道4个内容:结合用法指南
-
请求方式:上传肯定是POST
-
请求路径:/upload/image
-
请求参数:文件,参数名是file,SpringMVC会封装为一个接口:MultipartFile
-
返回结果:上传成功后得到的文件的url路径,也就是返回String
如下:
@Controller @RequestMapping("upload") public class UploadController { @Autowired private UploadService uploadService; /** * 图片上传 * @param file * @return */ @PostMapping("image") public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file){ String url = this.uploadService.upload(file); if (StringUtils.isBlank(url)) { return ResponseEntity.badRequest().build(); } return ResponseEntity.status(HttpStatus.CREATED).body(url); } }
5.编写server
在上传文件过程中,我们需要对上传的内容进行校验:
-
校验文件大小
-
校验文件的媒体类型
-
校验文件的内容
文件大小在Spring的配置文件中设置,因此已经会被校验
如下:
@Service public class UploadService { private static final List<String> CONTENT_TYPES = Arrays.asList("image/jpeg", "image/gif"); private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class); public String upload(MultipartFile file) { String originalFilename = file.getOriginalFilename(); // 校验文件的类型 String contentType = file.getContentType(); if (!CONTENT_TYPES.contains(contentType)){ // 文件类型不合法,直接返回null LOGGER.info("文件类型不合法:{}", originalFilename); return null; } try { // 校验文件的内容 BufferedImage bufferedImage = ImageIO.read(file.getInputStream()); if (bufferedImage == null){ LOGGER.info("文件内容不合法:{}", originalFilename); return null; } // 保存到服务器 file.transferTo(new File("D:\\file\\images\\" + originalFilename)); // 生成url地址,返回 return "http://localhost:8888/" + originalFilename; } catch (IOException e) { LOGGER.info("服务器内部错误:{}", originalFilename); e.printStackTrace(); } return null; } }
6.搭建FastDFS分布式文件系统
分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。
FastDFS是由淘宝的余庆先生所开发的一个轻量级、高性能的开源分布式文件系统。用纯C语言开发,功能丰富:
-
文件存储
-
文件同步
-
文件访问(上传、下载)
-
存取负载均衡
-
在线扩容
适合有大容量存储需求的应用或系统。同类的分布式文件系统有谷歌的GFS、HDFS(Hadoop)、TFS(淘宝)等。
FastDFS两个主要的角色:Tracker Server 和 Storage Server 。
-
Tracker Server:跟踪服务器,主要负责调度storage节点与client通信,在访问上起负载均衡的作用,和记录storage节点的运行状态,是连接client和storage节点的枢纽。
-
Storage Server:存储服务器,保存文件和文件的meta data(元数据),每个storage server会启动一个单独的线程主动向Tracker cluster中每个tracker server报告其状态信息,包括磁盘使用情况,文件同步情况及文件上传下载次数统计等信息
-
Group:文件组,多台Storage Server的集群。上传一个文件到同组内的一台机器上后,FastDFS会将该文件即时同步到同组内的其它所有机器上,起到备份的作用。不同组的服务器,保存的数据不同,而且相互独立,不进行通信。
-
Tracker Cluster:跟踪服务器的集群,有一组Tracker Server(跟踪服务器)组成。
-
Storage Cluster :存储集群,有多个Group组成。
6.1文件上传流程
-
-
Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
-
Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
-
6.2文件下载流程
-
-
Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号。
-
Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载文件。
-
6.3在pom中引入依赖
<dependency> <groupId>com.github.tobato</groupId> <artifactId>fastdfs-client</artifactId> <version>1.26.2</version> </dependency>
6.4在config包下引入配置类FastClientImporter
@Configuration @Import(FdfsClientConfig.class) // 解决jmx重复注册bean的问题 @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) public class FastClientImporter { }
6.5编写FastDFS属性
在application.yml配置文件中追加如下内容:
fdfs: so-timeout: 1501 # 超时时间 connect-timeout: 601 # 连接超时时间 thumb-image: # 缩略图 width: 60 height: 60 tracker-list: # tracker地址:你的虚拟机服务器地址+端口(默认是22122) - 192.168.16.191:22122
6.6测试FastDFS
编写测试类FastDFSTest如下:
@SpringBootTest @RunWith(SpringRunner.class) public class FastDFSTest { @Autowired private FastFileStorageClient storageClient; @Autowired private ThumbImageConfig thumbImageConfig; @Test public void testUpload() throws FileNotFoundException { // 要上传的文件 File file = new File("D:\\file\\images\\test.jpg"); // 上传并保存图片,参数:1-上传的文件流 2-文件的大小 3-文件的后缀 4-可以不管他 StorePath storePath = this.storageClient.uploadFile( new FileInputStream(file), file.length(), "jpg", null); // 带分组的路径 System.out.println(storePath.getFullPath()); // 不带分组的路径 System.out.println(storePath.getPath()); } @Test public void testUploadAndCreateThumb() throws FileNotFoundException { File file = new File("D:\\file\\images\\test.jpg"); // 上传并且生成缩略图 StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage( new FileInputStream(file), file.length(), "png", null); // 带分组的路径 System.out.println(storePath.getFullPath()); // 不带分组的路径 System.out.println(storePath.getPath()); // 获取缩略图路径 String path = thumbImageConfig.getThumbImagePath(storePath.getPath()); System.out.println(path); } }
6.7修改之前的上传逻辑
@Service public class UploadService { @Autowired private FastFileStorageClient storageClient; private static final List<String> CONTENT_TYPES = Arrays.asList("image/jpeg", "image/gif"); private static final Logger LOGGER = LoggerFactory.getLogger(UploadService.class); public String upload(MultipartFile file) { String originalFilename = file.getOriginalFilename(); // 校验文件的类型 String contentType = file.getContentType(); if (!CONTENT_TYPES.contains(contentType)){ // 文件类型不合法,直接返回null LOGGER.info("文件类型不合法:{}", originalFilename); return null; } try { // 校验文件的内容 BufferedImage bufferedImage = ImageIO.read(file.getInputStream()); if (bufferedImage == null){ LOGGER.info("文件内容不合法:{}", originalFilename); return null; } // 保存到服务器 // file.transferTo(new File("D:\\file\\images\\" + originalFilename)); String ext = StringUtils.substringAfterLast(originalFilename, "."); StorePath storePath = this.storageClient.uploadFile(file.getInputStream(), file.getSize(), ext, null); // 生成url地址,返回 return "http://localhost:8888/" + storePath.getFullPath(); } catch (IOException e) { LOGGER.info("服务器内部错误:{}", originalFilename); e.printStackTrace(); } return null; } }
修改了原来保存文件的逻辑,把上传到FastDFS就OK。