乐优商城--服务(四) : 上传微服务(LyUploadApplication)
上传微服务:
- 3.1 什么是分布式文件系统
- 3.2 什么是FastDFS
- 3.3 FastDFS的架构
- 3.3.1 架构图
- 3.3.2 上传和下载流程
- 3.3.3 安装
- 3.3.4 java客户端
- 3.3.4.1 引入依赖
- 3.3.4.2 引入配置类
- 3.3.4.3 编写FastDFS属性
图片上传属于文件上传的一种。文件的上传并不只是在品牌管理中有需求,以后的其它服务也可能需要,因此我们创建一个独立的微服务,专门处理各种上传。
1. 搭建项目
1.1 引入依赖
我们需要EurekaClient和web依赖:
<?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>leyou</artifactId> <groupId>com.leyou.parent</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.leyou.page.service</groupId> <artifactId>ly-upload</artifactId> <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> <!--引入ly-common后便会用到--> <dependency> <groupId>com.leyou.common</groupId> <artifactId>ly-common</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.github.tobato</groupId> <artifactId>fastdfs-client</artifactId> </dependency></dependencies> </project>
1.2 配置
server: port: 8082 spring: application: name: upload-service servlet: multipart: max-file-size: 5MB # 限制文件上传的大小 # Eureka eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: ip-address: 127.0.0.1 prefer-ip-address: true fdfs: so-timeout: 1501 connect-timeout: 601 thumb-image: # 缩略图 width: 60 height: 60 tracker-list: # tracker地址 - 192.168.184.130:22122 ly: upload: baseUrl: http://image.leyou.com/ allowTypes: - image/png - image/bmp - image/jpeg
1.3 启动类
@SpringBootApplication @EnableDiscoveryClient public class LyUploadService { public static void main(String[] args) { SpringApplication.run(LyUploadService.class, args); } }
2. 业务
2.1 web
2.1.1 页面分析
- 请求方式:上传肯定是POST
- 请求路径:/upload/image
- 请求参数:文件,参数名是file,SpringMVC会封装为一个接口:MultipleFile
- 返回结果:上传成功后得到的文件的url路径
2.1.2 实现业务
@RestController @RequestMapping("upload") public class UploadController { @Autowired private UploadService uploadService; // 图片上传 @PostMapping("image") public ResponseEntity<String> uploadImage(@RequestParam("file")MultipartFile file){ return ResponseEntity.ok(uploadService.uploadImage(file)); } }
2.2 service
在上传文件过程中,我们需要对上传的内容进行校验:
- 校验文件大小
- 校验文件的媒体类型
- 校验文件的内容
文件大小在Spring的配置文件中设置,因此已经会被校验,我们不用管。
@Service public class UploadService { private static final Logger logger = LoggerFactory.getLogger(UploadController.class); // 支持的文件类型 private static final List<String> suffixes = Arrays.asList("image/png", "image/jpeg"); public String upload(MultipartFile file) { try { // 1、图片信息校验 // 1)校验文件类型 String type = file.getContentType(); if (!suffixes.contains(type)) { logger.info("上传失败,文件类型不匹配:{}", type); return null; } // 2)校验图片内容 BufferedImage image = ImageIO.read(file.getInputStream()); if (image == null) { logger.info("上传失败,文件内容不符合要求"); return null; } // 2、保存图片 // 2.1、生成保存目录 File dir = new File("D:\\heima\\upload"); if (!dir.exists()) { dir.mkdirs(); } // 2.2、保存图片 file.transferTo(new File(dir, file.getOriginalFilename())); // 2.3、拼接图片地址 String url = "http://image.leyou.com/upload/" + file.getOriginalFilename(); return url; } catch (Exception e) { return null; } } }
2.3 忽略路由前缀
注:
Zuul的路由功能中,会忽略路由匹配的路径前缀,不过我们的Controller中有 /upload 路径,此时如果通过网关访问,我们的地址应该是http://api.leyou.com/api/upload/upload/image, 这是因为路由匹配的前缀 /upload 在请求转发时会被自动忽略。
这样地址看起来非常臃肿,因此我们可以禁止忽略路由前缀,
在网关中application.yml中配置:
这样,路由前缀也会作为地址一部分转发到微服务,那么我们就可以http://api.leyou.com/api/upload/image 正常访问了
2.4 绕过网关缓存
默认情况下,所有的请求都经过Zuul网关的代理.
Zuul底层就是一个servlet,通常情况下zuul会将请求交给Spring Dispatch去处理,SpringMVC去控制路由。这种情况下,zuul就会去缓存这个请求。如果有直接通过zuul但是不需要缓存:比如大文件的上传服务,这时候就应该跳过SpringDispatch,在地址前加一个 /zuul.
普通请求并不会有什么影响,但是对于图片上传(文件传输),如果也经过Zuul网关的代理,文件就会经过多次网路传输,造成不必要的网络负担。在高并发时,可能导致网络阻塞,Zuul网关不可用,这样我们的整个系统就瘫痪了。
所以,我们上传文件的请求需要绕过请求的缓存,直接通过路由到达目标微服务。
页面请求路径:
由于不能修改页面请求地址,我们需要修改到以 /zuul为前缀(在 /api前加),可以通过Nginx的rewrite指令(用于对地址进行重写)实现这一需求。
server { listen 80; server_name api.leyou.com; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; // 配置 location /api/upload { rewrite "^/(.*)$" /zuul/$1; } location / { proxy_pass http://192.168.1.109:10010; proxy_connect_timeout 600; proxy_read_timeout 600; } }
2.5 之前上传的缺陷
之前,我们把文件保存在服务器机器,就会有下面的问题:
- 单机器存储,存储能力有限
- 无法进行水平扩展,因为多台机器的文件无法共享,会出现访问不到的情况
- 数据没有备份,有单点故障风险
- 并发能力差
这个时候,最好使用分布式文件存储来代替本地文件存储。
3.FastDFS
3.1 什么是分布式文件系统
分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。
通俗来讲:
- 传统文件系统管理的文件就存储在本机。
- 分布式文件系统管理的文件存储在很多机器,这些机器通过网络连接,要被统一管理。无论是上传或者访问文件,都需要通过管理中心来访问
3.2 什么是FastDFS
FastDFS是由淘宝的余庆先生所开发的一个轻量级、高性能的开源分布式文件系统。用纯C语言开发,功能丰富:
- 文件存储
- 文件同步
- 文件访问(上传、下载)
- 存取负载均衡
- 在线扩容
适合有大容量存储需求的应用或系统。同类的分布式文件系统有谷歌的GFS、HDFS(Hadoop)、TFS(淘宝)等。
3.3 FastDFS的架构
3.3.1 架构图
先上图:
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组成。
3.3.2 上传和下载流程
上传
- Client通过Tracker server查找可用的Storage server。
- Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
- Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
- 上传完成,Storage server返回Client一个文件ID,文件上传结束。
下载
- Client通过Tracker server查找要下载文件所在的的Storage server。
- Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号。
- Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载文件。
- 下载文件成功。
3.3.3 安装
3.3.4 java客户端
3.3.4.1 引入依赖
在ly-upload的pom文件中添加:
<dependency> <groupId>com.github.tobato</groupId> <artifactId>fastdfs-client</artifactId> </dependency>
3.3.4.2 引入配置类
@Configuration @Import(FdfsClientConfig.class) // 解决jmx重复注册bean的问题 @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) public class FastClientImporter { }
3.3.4.3 编写FastDFS属性
在ly-upload的 application.yml 文件中添加:
3.4 改造上传逻辑
只需要把原来保存文件的逻辑去掉,然后上传到FastDFS即可。
@Service @Slf4j @EnableConfigurationProperties(UploadProperties.class) public class UploadService { @Autowired private FastFileStorageClient storageClient; @Autowired private UploadProperties prop; public String uploadImage(MultipartFile file) { try { //校验文件类型 String contentType = file.getContentType(); if(!prop.getAllowTypes().contains(contentType)){ throw new LyException(ExceptionEnum.INVALID_FILE_TYPE); } //校验文件内容 BufferedImage image = ImageIO.read(file.getInputStream()); if(image==null) throw new LyException(ExceptionEnum.INVALID_FILE_TYPE); //上传到FastDFS String extension = StringUtils.substringAfterLast(file.getOriginalFilename(),"."); StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), extension, null); //返回路径 return prop.getBaseUrl() + storePath.getFullPath(); } catch (IOException e) { log.error("[文件上传] 上传文件失败",e); throw new LyException(ExceptionEnum.UPLOAD_FILE_ERROR); } } }
在上述中,我们对文件的返回路径进行了抽取,如下:
同时,应当在ly-upload的 application.yml文件中添加:
3.5 导入图片到虚拟机
之前我们已经在数据库导入了数百条品牌及商品数据,不过这些数据中所需要的图片是无法访问的,需要我们把图片放到虚拟机对应的nginx服务器。(上传到Linux的 /leyou/static目录
然后修改linux中的nginx配置:)
vim /opt/nginx/config/nginx.conf
将以image开头的地址代理到本地路径:
server { listen 80; server_name image.taotao.com; # 监听域名中带有group的,交给FastDFS模块处理 location ~/group([0-9])/ { ngx_fastdfs_module; } # 其它图片都走本地目录 location /images/ { root /leyou/static; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
- 乐优商城--服务(七) : 用户中心微服务(LyUserApplication)
- 乐优商城--服务(九) :授权微服务(LyAuthApplication)--前半部分
- 基于 lua-resty-upload 实现简单的文件上传服务
- 上传文件出错:org.apache.commons.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. Stream ended unexpectedly
- 乐优商城--服务--优化搜索微服务和页面微服务
- Apache Cordova 安卓android上传图片或者文件 调用后台服务实现java代码 ft.upload(imageURI, uri, that.uploadSuccess, that.
- SmartUpload文件上传及application内置对象的复习
- IE浏览器AJAX文件上传失败org.apache.commons.fileupload.FileUploadException: Stream ended unexpectedly
- 基于 lua-resty-upload 实现简单的文件上传服务
- 【乐优商城】springcloud微服务-项目搭建
- ajaxfileupload.js ajax上传文件(含application/json)
- 乐优商城--关于微服务的安全问题
- 微信小程序商城后台修改商城配置信息提示“上传目录Data/UploadFiles不存在请手动创建”
- 上传文件出错:org.apache.commons.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. Stream ended unexpectedly
- **从零开始完成微服务项目乐优商城**
- vue使用el-upload上传文件及Feign服务间传递文件的方法
- 乐优商城--服务(八) :短信微服务(LySmsApplication)
- SpringMVC结合ajaxfileupload.js实现文件无刷新上传
- Uploadfile 文件上传控件使用方法
- Jquery和BigFileUpload实现大文件上传及进度条显示