springcloud(二)

Nacos配置管理

统一配置管理

  1. 在Nacos中新增配置信息
    在这里插入图片描述
  2. 配置相关信息
    在这里插入图片描述
    获取统一配置的管理中的内容
  3. 引入Nacos的配置管理客户端依赖:
<!--nacos配置管理依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
  1. 在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev # 运行环境
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
      config: 
        file-extension: yaml # 文件后缀
  1. 删除application.yml中重复的配置代码
server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
    username: root
    password: root
mybatis:
  configuration:
    map-underscore-to-camel-case: true # true开启驼峰命名法
logging:
  level:
    com.zfc: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
  1. 我们在user-service中将pattern.dateformat这个属性注入到UserController中做测试:看有没有使用到统一配置管理中的配置
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    //从nacos中获取配置属性
    @Value("${pattern.dateformat}")
    private String dateformat;

    //编写controller,通过日期格式化器来格式化现在的时间并返回
    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat, Locale.CHINA));
    }

如果输出的格式和 统一配置管理 中的一样,则代表使用成功,可以更改几次观察。。。

总结,将配置交给Nacos管理的步骤:

  1. 在Nacos中添加配置文件
  2. 在微服务中引入nacos的config依赖
    3.在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件
  3. 清除application.yml 中nacos 相关的代码

配置热更新

如果你有测试上面的获取统一配置管理会发现一个问题,那就是更改了统一配置管理中的内容后需要在idea中重启服务后才能及时同步统一配置管理中的内容,因此就需要配置热更新了。

方式一: 在@Value注入的变量所在类上添加注解@RefreshScope
在这里插入图片描述
方式二: 使用@ConfigurationProperties注解
新增配置类

package com.zfc.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * ConfigurationProperties
 * 约定大于配置,只要你的前缀名(prefix = "pattern")和属性名(dateformat) 拼接跟配置文件一致,就能完成属性的自动注入
 */
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat;
}

使用

    @Autowired
    private PatternProperties patternProperties;

    //编写controller,通过日期格式化器来格式化现在的时间并返回
    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat(), Locale.CHINA));
    }

完成后即可进行测试。

总结: Nacos配置更改后,微服务可以实现热更新,方式:

  1. 通过@Value注解注入,结合@RefreshScope来刷新
  2. 通过@ConfigurationProperties注入,自动刷新

注意事项:

  • 不是所有的配置都适合放到配置中心,维护起来比较麻烦
  • 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置

配置共享

? 当有很多个环境时,如果环境中有些配置是相同的,那是不是就只需要配置一次呢?这个时候就用到了配置共享了。

微服务启动时会从nacos读取多个配置文件

  • [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
  • [spring.application.name].yaml,例如:userservice.yaml
  1. 新增配置
    在这里插入图片描述在这里插入图片描述
  2. 在配置文件中新增相对于的属性
    在这里插入图片描述
  3. 测试
    在这里插入图片描述
    如果想看到明显的区别,可以复制一个服务,然后更改它的项目环境,比如test

多种配置的优先级: [服务名]-[环境].yaml >[服务名].yaml > 本地配置

无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件

搭建Nacos集群

略。黑马资料中有相对于的md笔记,这里就不过多记录

Feign远程调用

Feign 介绍 Feign是一个声明式的HTTP客户端,它的目的就是让HTTP调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。

Feign替代RestTemplate

  1. 导入依赖
    <!--feign客户端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 启动类上添加注解 @EnableFeignClients
  2. 编写feign客户端
package cn.itcast.order.clients;

import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("userservice") //服务名称
public interface UserClient {

    @GetMapping("/user/{id}")// 请求方式  请求路径
    User findById(@PathVariable("id") Long id);// 返回值类型  请求参数
}

  1. 使用Feign替换restTemplate
package cn.itcast.order.service;

import cn.itcast.order.clients.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired(required = false)
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        // 2.使用feign客户端替代resettemplate
        User user = userClient.findById(order.getUserId());
        order.setUser(user);
        // 4.返回
        return order;
    }
}

Feign自定义配置,如下:

feign支持自定义配置:

类型作用说明
feign.Logger.Level修改日志级别包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析json字符串为java对象
feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送
feign. Contract支持的注解格式默认是SpringMVC的注解
feign. Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

一般情况下默认值就能满足我的需求,如果要自定义时,只需要创建自定义的@Bean覆盖默认的Bean几个。

使用 修改日志级别 案例
  1. 使用配置yml的方式
    (1)、修改ymal配置文件
    # 全局生效
    feign:
      client:
        config:
          default: # defaul 是默认使用全局配置,如果写服务名称,就只争对某个微服务的配置
            logger-level: FULL # 日志级别
    
    #局部生效
    feign:
      client:
        config:
          userservice: # 写服务名称,就只争对某个微服务的配置
            logger-level: FULL # 日志级别
    
  2. 使用java代码配置的方式
    (1)、声明一个@Bean
        package cn.itcast.order.configuration;
    
        import feign.Logger;
        import org.springframework.context.annotation.Bean;
        
        public class FeignClientConfiguration {
        
            @Bean
            public Logger.Level feignLogLevel(){
                return Logger.Level.BASIC;
            }
        }
    
    (2)、如果是使用全局配置,就放在@EnableFeignClients 这个注解中
    @EnableFeignClients(defaultConfiguration = cn.itcast.order.configuration.FeignClientConfiguration.class)
    
    (2)、如果是想局部配置,就放在 @FeignClient 这个注解中
    @FeignClient(value = "userservice",configuration = cn.itcast.order.configuration.FeignClientConfiguration.class)
    

日志的级别分为四种:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign使用优化

  1. 日志级别尽量用basic
  2. 使用HttpClient或OkHttp代替URLConnection
    (1)、引入对应依赖
        <!--feign客户端依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    
    (2)、yml配置文件中配置, 开启httpclient功能,设置连接参数
    feign:
      client:
        config:
          default: # defaul 是默认使用全局配置,如果写服务名称,就只争对某个微服务的配置
            logger-level: NONE # 日志级别
      httpclient:
        enabled: true #使用httpclient的开关
        max-connections: 200 # 最大连接数
        max-connections-per-route: 50 # 每个路径的最大连接数
    

最佳实践

?简单的理解就睡将重复使用的代码,抽取出来,提高代码的复用性…
将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。简单的说就是把重复的代码,公共化

  1. 在cloud-demo 项目的基础上 再新增一个模块 feign-api
    在这里插入图片描述

  2. 引入feign客户端依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 把order-service 中的 client … 相关的(需要提取出来作为公关部分的),复制到 feign-api
    在这里插入图片描述

  2. 在order-service 中引入 feign-api

        <dependency>
            <groupId>cn.itcast.demo</groupId>
            <artifactId>feign-api</artifactId>
            <version>1.0</version>
        </dependency>
  1. 修改order-service中与公共部分相关的import部分,导入 feign-api 中的
  2. 重启测试

注意: 如果你的两个包结构不一样,会出现启动报错的情况,大概意思就是自动装配失败
在这里插入图片描述

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:

方法一:在启动类声明的@EnableFeignClients()注解中,指定FeignClient所在包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")

方法二:在启动类声明的@EnableFeignClients()注解中,指定FeignClient字节码(就是用哪个类就声明哪个类,{},表示可以方多个,用, 隔开)
@EnableFeignClients(clients = {UserClient.class})

Gateway服务网关

为什么需要网关

  1. 不是谁都可以访问服务的(不安全),这个时候就需要网关来做身份认证、权限校验
  2. 网关不能处理业务查询的业务,这个时候网关就需要根据你的请求去使用哪个服务,这个就是服务路由,同时一个服务可能有多个实例,这个时候就通过负载均衡实现
  3. 如果我的微服务允许的用户请求数量是520个,那么超过了这个额度就只能等待520中请求结束后才能请求,否则只能等待或者结束请求

网关功能:

  • 身份认证和权限校验
  • 服务路由、负载均衡
  • 请求限流
    在这里插入图片描述
网关的技术实现

在SpringCloud中网关的实现包括两种:

  • gateway
  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程。
SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

gateway快速入门

  1. 在cloud-demo的基础上,新建gateway module
  2. 引入所需要的依赖坐标
        <!-- nacos客户端依赖包 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--网关依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
  1. application.yml 中,编写nacos地址、路由配置
server:
  port: 10010 # 网管端口
spring:
  application:
    name: geteway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:80 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id:路由标识,自己定义,必须唯一
          uri: lb://userservice # 路由目标地址 ,lb代表负载均衡(LoadBalanced)://服务名称
          predicates: # 路由断言,判断请求是否符合路由规则的条件( 如果为真就执行,为假就不执行)
            - Path=/user/** # 按照路匹配,只要是/user/开头的请求就符合格式
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
  1. 编写启动类启动
    在这里插入图片描述

  2. 访问测试
    http://localhost:10010/user/1
    http://localhost:10010/order/101

网关路由可以配置的内容包括:

  • 路由id:路由唯一标示
  • uri:路由目的地,支持lb和http两种
  • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
  • filters:路由过滤器,处理请求或响应

断言工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

例如Path=/user/**是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个:

名称说明示例
After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie请求必须包含某些cookie- Cookie=chocolate, ch.p
Header请求必须包含某些header- Header=X-Request-Id, \d+
Host请求必须是访问某个host(域名)- Host=.somehost.org,.anotherhost.org
Method请求方式必须是指定方式- Method=GET,POST
Path请求路径必须符合指定规则- Path=/red/{segment},/blue/**
Query请求参数必须包含指定参数- Query=name, Jack或者- Query=name
RemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24
Weight权重处理

根据需要使用

过滤器工厂

Spring提供了31种不同的路由过滤器工厂。例如:

名称说明
AddRequestHeader给当前请求添加一个请求头
RemoveRequestHeader移除请求中的一个请求头
AddResponseHeader给响应结果中添加一个响应头
RemoveResponseHeader从响应结果中移除有一个响应头
RequestRateLimiter限制请求的流量
请求头过滤器
  1. 只需要修改gateway服务的application.yml文件,添加路由过滤即可:
server:
  port: 10010 # 网管端口
spring:
  application:
    name: geteway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:80 # nacos地址
    gateway:
      routes:
        - id: user-service
          uri: lb://userservice
          predicates:
            - Path=/user/**
          filters: # 过滤
            - AddRequestHeader=hello,hello zfc # 添加请求头 hello:key,hello zfc:value
  1. 在对应服务的请求方法中获取请求头
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id,
                          @RequestHeader(value = "hello",required = false)String hlo) {
        System.out.println("hlo = " + hlo);
        return userService.queryById(id);
    }
默认过滤器

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/**
      default-filters: # 默认过滤项
      - AddRequestHeader=hello,hello zfc # 添加请求头 hello:key,hello
  1. 在对应服务的请求方法中获取请求头

总结:
过滤器的作用是什么?

  1. 对路由的请求或响应做加工处理,比如添加请求头

  2. 配置在路由下的过滤器只对当前路由的请求生效

defaultFilters的作用是什么?

  1. 对所有路由都生效的过滤器

全局过滤器

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

  • 参数中是否有authorization,

  • authorization参数值是否为admin

如果同时满足则放行,否则拦截

实现代码:

package cn.itcast.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 全局过滤器
 */
//@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter , Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        //2.获取authorization参数
        MultiValueMap<String, String> params = request.getQueryParams();
        String authorization = params.getFirst("authorization");

        //3.判断请求参数
        if ("admin".equals(authorization)){
            //4 是 放行
            return chain.filter(exchange);
        }

        //5.否 拦截
        //5.1 禁止访问,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        //5.2 结束处理
        return exchange.getResponse().setComplete();
    }

    /**
     * 执行顺序,越小优先级越高,
     * 跟上面注解@Order(-1)一个效果
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }
}

全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑

实现全局过滤器的步骤?

  • 实现GlobalFilter接口
  • 添加@Order注解或实现Ordered接口
  • 编写处理逻辑
过滤器执行顺序

路由过滤器、defaultFilter、全局过滤器的执行顺序?

  • order值越小,优先级越高
  • 当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器

跨域问题

  1. 在gateway服务的application.yml文件中,添加下面的配置:然后重启项目
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期
  1. 编写html测试
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<pre>
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期
</pre>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
  axios.get("http://localhost:10010/user/1?authorization=admin")
  .then(resp => console.log(resp.data))
  .catch(err => console.log(err))
</script>
</html>
  1. 打开。注意 这里不要直接使用浏览器打开
  • 打开cmd 输入指令:npm install live-server -g
  • 再输入 live-server --port=8090
  • 就在浏览器中启动成功了
  • 把地址栏地址127.0.0.1:8090修改为localhost:8090
  1. 打开 开发者工具 查看
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值