9.实现图片上传 rest clinet

参看:乐优商城(六)搭建上传微服务
https://blog.csdn.net/lyj2018gyq/article/details/82227567

补充:
在2.2测试文件上传时
SwitchHosts配置:
在这里插入图片描述

nginx配置:
9.实现图片上传 rest clinet_第1张图片

2.实现图片上传

刚才的新增实现中,我们并没有上传图片,接下来我们一起完成图片上传逻辑。

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

2.1.搭建项目

2.1.1.创建module

9.实现图片上传 rest clinet_第2张图片

9.实现图片上传 rest clinet_第3张图片

9.实现图片上传 rest clinet_第4张图片

9.实现图片上传 rest clinet_第5张图片

2.1.2.依赖

我们需要EurekaClient和web依赖:


<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>leyouartifactId>
        <groupId>com.leyou.parentgroupId>
        <version>1.0.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <groupId>com.leyou.uploadgroupId>
    <artifactId>leyou-uploadartifactId>
    <version>1.0.0-SNAPSHOTversion>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
        dependency>
    dependencies>
project>

2.1.3.编写配置

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:
    lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
    lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期

需要注意的是,我们应该添加了限制文件大小的配置

2.1.4.引导类

@SpringBootApplication
@EnableDiscoveryClient
public class LeyouUploadApplication {

    public static void main(String[] args) {
        SpringApplication.run(LeyouUploadApplication.class, args);
    }
}

结构:
9.实现图片上传 rest clinet_第6张图片

2.2.编写上传功能

文件上传功能,也是自定义组件完成的,参照自定义组件用法指南:
9.实现图片上传 rest clinet_第7张图片

在页面中的使用:
9.实现图片上传 rest clinet_第8张图片

2.2.1.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);
    }
}

2.2.2.service

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

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

文件大小在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("C:\\leyou\\images\\" + originalFilename));

            // 生成url地址,返回
            return "http://image.leyou.com/" + originalFilename;
        } catch (IOException e) {
            LOGGER.info("服务器内部错误:{}", originalFilename);
            e.printStackTrace();
        }
        return null;
    }
}

这里有一个问题:为什么图片地址需要使用另外的url?

  • 图片不能保存在服务器内部,这样会对服务器产生额外的加载负担
  • 一般静态资源都应该使用独立域名,这样访问静态资源时不会携带一些不必要的cookie,减小请求的数据量

2.2.3.测试上传

我们通过RestClient工具来测试:
9.实现图片上传 rest clinet_第9张图片

结果:
9.实现图片上传 rest clinet_第10张图片

去目录下查看:
9.实现图片上传 rest clinet_第11张图片

上传成功!

2.3.绕过网关

图片上传是文件的传输,如果也经过Zuul网关的代理,文件就会经过多次网路传输,造成不必要的网络负担。在高并发时,可能导致网络阻塞,Zuul网关不可用。这样我们的整个系统就瘫痪了。

所以,我们上传文件的请求就不经过网关来处理了。

2.3.1.Zuul的路由过滤

Zuul中提供了一个ignored-patterns属性,用来忽略不希望路由的URL路径,示例:

zuul.ignored-patterns: /upload/**

路径过滤会对一切微服务进行判定。

Zuul还提供了ignored-services属性,进行服务过滤:

zuul.ignored-services: upload-servie

我们这里采用忽略服务:

zuul:
  ignored-services:
    - upload-service # 忽略upload-service服务

上面的配置采用了集合语法,代表可以配置多个。

2.3.2.Nginx的rewrite指令

现在,我们修改页面的访问路径:

<v-upload
      v-model="brand.image" 
      url="/upload/image" 
      :multiple="false" 
      :pic-width="250" :pic-height="90"
      />

查看页面的请求路径:
9.实现图片上传 rest clinet_第12张图片

可以看到这个地址不对,依然是去找Zuul网关,因为我们的系统全局配置了URL地址。怎么办?

有同学会想:修改页面请求地址不就好了。

注意:原则上,我们是不能把除了网关以外的服务对外暴露的,不安全。

既然不能修改页面请求,那么就只能在Nginx反向代理上做文章了。

我们修改nginx配置,将以/api/upload开头的请求拦截下来,转交到真实的服务地址:

location /api/upload {
    proxy_pass http://127.0.0.1:8082;
    proxy_connect_timeout 600;
    proxy_read_timeout 600;
}

这样写大家觉得对不对呢?

显然是不对的,因为ip和端口虽然对了,但是路径没变,依然是:http://127.0.0.1:8002/api/upload/image

前面多了一个/api

Nginx提供了rewrite指令,用于对地址进行重写,语法规则:

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;

    	# 上传路径的映射
		location /api/upload {	
			proxy_pass http://127.0.0.1:8082;
			proxy_connect_timeout 600;
			proxy_read_timeout 600;
			
			rewrite "^/api/(.*)$" /$1 break; 
        }
		
        location / {
			proxy_pass http://127.0.0.1:10010;
			proxy_connect_timeout 600;
			proxy_read_timeout 600;
        }
    }
  • 首先,我们映射路径是/api/upload,而下面一个映射路径是 / ,根据最长路径匹配原则,/api/upload优先级更高。也就是说,凡是以/api/upload开头的路径,都会被第一个配置处理

  • proxy_pass:反向代理,这次我们代理到8082端口,也就是upload-service服务

  • rewrite "^/api/(.*)$" /$1 break,路径重写:

    • "^/api/(.*)$":匹配路径的正则表达式,用了分组语法,把/api/以后的所有部分当做1组

    • /$1:重写的目标路径,这里用$1引用前面正则表达式匹配到的分组(组编号从1开始),即/api/后面的所有。这样新的路径就是除去/api/以外的所有,就达到了去除/api前缀的目的

    • break:指令,常用的有2个,分别是:last、break

      • last:重写路径结束后,将得到的路径重新进行一次路径匹配
      • break:重写路径结束后,不再重新匹配路径。

      我们这里不能选择last,否则以新的路径/upload/image来匹配,就不会被正确的匹配到8082端口了

修改完成,输入nginx -s reload命令重新加载配置。然后再次上传试试。

2.4.跨域问题

重启nginx,再次上传,发现跟上次的状态码已经不一样了,但是依然报错:
9.实现图片上传 rest clinet_第13张图片

不过庆幸的是,这个错误已经不是第一次见了,跨域问题。

我们在upload-service中添加一个CorsFilter即可:
9.实现图片上传 rest clinet_第14张图片

@Configuration
public class LeyouCorsConfiguration {

    @Bean
    public CorsFilter corsFilter() {
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //1) 允许的域,不要写*,否则cookie就无法使用了
        config.addAllowedOrigin("http://manage.leyou.com");
        //3) 允许的请求方式
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("POST");
        // 4)允许的头信息
        config.addAllowedHeader("*");

        //2.添加映射路径,我们拦截一切请求
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }
}

再次测试:
9.实现图片上传 rest clinet_第15张图片

不过,非常遗憾的是,访问图片地址,却没有响应。
9.实现图片上传 rest clinet_第16张图片

这是因为我们并没有任何服务器对应image.leyou.com这个域名。。

这个问题,我们暂时放下,回头再来解决。

2.5.文件上传的缺陷

先思考一下,现在上传的功能,有没有什么问题?

上传本身没有任何问题,问题出在保存文件的方式,我们是保存在服务器机器,就会有下面的问题:

  • 单机器存储,存储能力有限
  • 无法进行水平扩展,因为多台机器的文件无法共享,会出现访问不到的情况
  • 数据没有备份,有单点故障风险
  • 并发能力差

这个时候,最好使用分布式文件存储来代替本地文件存储。

你可能感兴趣的:(乐优商城)