您的位置:首页 > 移动开发

乐优商城--服务(四) : 上传微服务(LyUploadApplication)

2019-05-27 21:37 609 查看

上传微服务:

  • 2. 业务
  • 2.2 service
  • 2.3 忽略路由前缀
  • 2.4 绕过网关缓存
  • 2.5 之前上传的缺陷
  • 3.FastDFS
  • 3.4 改造上传逻辑
  • 3.5 导入图片到虚拟机

  • 图片上传属于文件上传的一种。文件的上传并不只是在品牌管理中有需求,以后的其它服务也可能需要,因此我们创建一个独立的微服务,专门处理各种上传。

    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

    在上传文件过程中,我们需要对上传的内容进行校验:

    1. 校验文件大小
    2. 校验文件的媒体类型
    3. 校验文件的内容

    文件大小在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 上传和下载流程

    上传

    1. Client通过Tracker server查找可用的Storage server。
    2. Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
    3. Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
    4. 上传完成,Storage server返回Client一个文件ID,文件上传结束。

    下载

    1. Client通过Tracker server查找要下载文件所在的的Storage server。
    2. Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号。
    3. Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载文件。
    4. 下载文件成功。

    3.3.3 安装

    Centos下安装FastDFS

    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;
    }
    }
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐