springcloud入门案例

新建java空项目

创建父工程

配置SDK为1.8

pom.xml内容:

注意pom

	<groupId>com.kkb</groupId>
    <artifactId>bill</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.15.RELEASE</version>
        <relativePath/>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
        <mapper.starter.version>2.1.5</mapper.starter.version>
        <mysql.version>5.1.38</mysql.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 通用Mapper启动器 -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${mapper.starter.version}</version>
            </dependency>
            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

创建服务提供者子工程

用插件生成启动器和配置文件

pom.xml

引入spring-boot-starter-web

<dependencies>
        <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>
        <!--通用mapper启动器-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

    </dependencies>

创建服务调用者

只需要引入web

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

用插件生成启动器和配置文件

启动器代码:

Spring提供了一个RestTemplate模板工具类,对基于http的客户端进行了封装,并且实现了对象json的序列化与反序列化。RestTemplate没有限定http客户端的类型,而是进行了抽象

其中常用的三中支持:HTTPClient、OkHTTP、JDK原生的URLConnection(默认的)

@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

编写实体类

​ 其中只需要保留属性并给类加上@Data注解

controller:

调用restTemplate的方法getForObject

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("")
    public Bill queryById(@RequestParam long id) {
        String url = "http://localhost:9001/bill?id=" + id;
        return restTemplate.getForObject(url, Bill.class);
    }
}

以上操作的问题:

在consumer中,我们把url地址硬编码到了代码中,不方便后期维护 
consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效 
consumer不清楚user-service的状态,服务宕机也不知道 
user-service只有1台服务,不具备高可用性 
即便user-service形成集群,consumer还需自己实现负载均衡

分布式服务要面临的问题:

服务管理
	如何自动注册和发现 
	如何实现状态监管 
	如何实现动态路由 
服务如何实现负载均衡 
服务如何解决容灾问题 
服务如何实现统一配置

编写Eureka

pom.xml:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

编写启动类:

添加@EnableEurekaServer注解

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

编写配置文件:

server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: 
      # eureka服务地址,如果是集群的话,需要指定其他集群eureka地址
      defaultZone: http://127.0.0.1:10086/eureka
    # 不注册自己
    register-with-eureka: false
    # 不拉取服务
    fetch-registry: false

启动服务访问eureka页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWIdw6bz-1636281635441)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210921233411011.png)]

将服务注册到eureka

添加依赖

pom.xml加上

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

启动类加上注解@EnableDiscoveryClient

@SpringBootApplication
@MapperScan("com.mkl.bill.dao")
@EnableDiscoveryClient //开启eureka发现
public class BillApplication {
    public static void main(String[] args) {
        SpringApplication.run(BillApplication.class, args);
    }
}

更新配置文件

加上spring.application.name: xxx以及eureka部分

其中spring.application.name属性指定应用名称而且将来会作为id使用

server:
  port: 9001
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/bill-manager?useSSL=false
    username: root
    password:
  application:
    name: bill-service
mybatis:
  type-aliases-package: com.mkl.bill.entity
  mapper-locations: classpath:/mybatis/*.xml
logging:
  level:
    com.mkl: debug
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

注册成功:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EjUQplQk-1636281750352)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210921234157426.png)]

服务发现

consumer-demo添加依赖

<!-- Eureka客户端 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

启动类添加开启eureka客户端发现的注释

@SpringBootApplication
@EnableDiscoveryClient //开启eureka客户端
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

修改配置文件

spring:
  application:
    name: consumer-demo
eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka

修改controller

discoveryClient.getInstances(服务名)

@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Resource
    private RestTemplate restTemplate;
    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("")
    public Bill queryById(@RequestParam long id) {
        String url = "http://localhost:9001/bill?id=" + id;

        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("bill-service");
        ServiceInstance serviceInstance = serviceInstances.get(0);
        url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/bill?id=" + id;

        return restTemplate.getForObject(url, Bill.class);
    }
}

debug进断点查看url

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vg4xEe5Q-1636281635444)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922091510574.png)]

eureka客户端和服务端配置

修改配置文件

注意这里的配置生效之后修改的不是eureka控制台的内容而是discoveryClient.getInstances(“bill-service”).get(0).getHost()的内容

  instance:
    ip-address: 127.0.0.1
    prefer-ip-address: true # 更倾向于使用ip,而不是host名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ViMIg3Ri-1636281635446)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922091720894.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GlP72wsV-1636281635448)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922092454272.png)]

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉 EurekaServer:“我还活着”。这个我们称为服务的续约(renew)

修改配置文件

  instance:
    prefer-ip-address: true 
    # 服务失效时间 默认 90s
    lease-expiration-duration-in-seconds: 90
    # 服务续约的间隔 默认30s
    lease-renewal-interval-in-seconds: 30

也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心 跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。

当服务消费者启动时,会检测 eureka.client.fetch-registry=true 参数的值,如果为true,则会从Eureka Server服 务的列表拉取只读备份,然后缓存在本地。并且 每隔30秒 会重新拉取并更新数据。可以在 consumer-demo 项目 中通过下面的参数来修改:

eureka:
  client:
    # 每隔30s重新从eureka server服务列表拉取只读备份缓存在本地
    registry-fetch-interval-seconds: 30

生产环境中,我们不需要修改这个值。

但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。

失效剔除和自我保护

服务下线

当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态

失效剔除

有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下 线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。 可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。

自我保护

我们关停一个服务,就会在Eureka面板看到一条警告:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvQdXS16-1636281635450)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922093732279.png)]

这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服 务实例的比例是否超过了85%,当EurekaServer节点在短时间内丢失过多客户端(可能发生了网络分区故障)。在 生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:

eureka:
  server:
    # 关闭自我保护
    enable-self-preservation: false
    # 扫描失效服务的间隔时间 默认60000ms
    eviction-interval-timer-in-ms: 1000

Ribbon

实际的环境中会开启多个service集群

模拟多service

run/debug configurations中复制一个service -Dport=xxxx

修改配置文件server.port: ${port:9001}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U3zvJXFg-1636281635451)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922121131966.png)]

开启负载均衡

其中eureka集成了ribbon不需要加入新的依赖

consumer启动类:@LoadBalanced注解

@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate() {
    return new RestTemplate();
}

修改ConsumerController

不再serviceInstance.getHost() + “:” + serviceInstance.getPort()手动获取,而是直接通过服务名调用

@GetMapping("")
public Bill queryById(@RequestParam long id) {
//        String url = "http://localhost:9001/bill?id=" + id;

//        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("bill-service");
//        ServiceInstance serviceInstance = serviceInstances.get(0);
//        url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/bill?id=" + id;

    String url = "http://bill-service/bill?id=" + id;

    return restTemplate.getForObject(url, Bill.class);
}

负载均衡默认是轮询模式

修改为随机:

	bill-service:
	  ribbon:
	    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Htstrix

Htstrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间

用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理

服务降级

及时返回服务调用失败的结果,让线程不会因为等待服务而阻塞,优先保证核心服务

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息)

服务降级虽然请求会失败但是不会阻塞,对其他服务没有响应

触发hystrix服务降级的情况:线程池已满,请求超时

consumer-demo配置文件:

<!--hystrix-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

启动类加注解 @EnableCircuitBreaker

这三个注解可以用 @SpringCloudApplication代替

//@SpringBootApplication
//@EnableDiscoveryClient //开启eureka客户端
//@EnableCircuitBreaker //开启熔断
@SpringCloudApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Bean
    @LoadBalanced //开启负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

consumerController:

熔断的降级逻辑方法必须跟正常逻辑方法保持相同的参数列表和返回值声明(这里用Bill作为返回值不太合适,应该统一一个封装结果的类)

@HystrixCommand(fallbackMethod = “queryByIdFallback”)声明了一个降级逻辑的方法

@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {

    @Resource
    private RestTemplate restTemplate;
    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("")
    @HystrixCommand(fallbackMethod = "queryByIdFallback")
    public Bill queryById(@RequestParam long id) {

        String url = "http://bill-service/bill?id=" + id;

        return restTemplate.getForObject(url, Bill.class);
    }

    public Bill queryByIdFallback(long id) {
        log.error("查询用户信息失败。id:{}", id);
        Bill bill = new Bill();
        bill.setExplain("对不起 网络太忙了");
        return bill;
    }
}

上面这样针对某个方法专门写fallback如果方法多了就很麻烦,可以写默认的fallback

将默认的fallback注解在类上并提供默认的fallback方法

@RestController
@RequestMapping("/consumer")
@Slf4j
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {

    @Resource
    private RestTemplate restTemplate;
    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("")
//    @HystrixCommand(fallbackMethod = "queryByIdFallback")
    @HystrixCommand
    public Bill queryById(@RequestParam long id) {

        String url = "http://bill-service/bill?id=" + id;

        return restTemplate.getForObject(url, Bill.class);
    }

    public Bill queryByIdFallback(long id) {
        log.error("查询用户信息失败。id:{}", id);
        Bill bill = new Bill();
        bill.setExplain("对不起 网络太忙了");
        return bill;
    }

    public Bill defaultFallback() {
        Bill bill = new Bill();
        bill.setExplain("默认提示:对不起,网络太拥挤了");
        return bill;
    }
}
服务熔断

ConsumerController

短时间内多次访问/bill/10088服务就会熔断

@GetMapping("")
@HystrixCommand
public Bill queryById(@RequestParam long id) {
    if (id == 10088) {
        throw new RuntimeException("太忙了");
    }

    String url = "http://bill-service/bill?id=" + id;

    return restTemplate.getForObject(url, Bill.class);
}

Feign

feign可以把rest请求进行隐藏,伪装成类似springMVC的Controller一样,不用自己再拼接url和参数

consumer-demo pom.xml

<!--feign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

feign的客户端

这是一个接口,feign会通过动态代理生成实现类,有点像mybatis的mapper

@FeignClient声明这是一个feign客户端,value指定服务名称

接口中定义的方法完全采用SpringMVC注解,feign会根据注解帮助生成URL,并访问获得结果

package com.mkl.consumer.client;

import com.mkl.consumer.entity.Bill;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient("bill-service")
public interface BillClient {
    //http://bill-service/bill?id=2
    @GetMapping("/bill")
    Bill queryById(@RequestParam long id);
}

编写新的控制器类ConsumerFeignController,使用BillClient访问

@RestController
@RequestMapping("/cf")
public class ConsumerFeignController {
    
    @Resource
    private BillClient billClient;
    
    @GetMapping("")
    public Bill queryById(@RequestParam long id) {
        return billClient.queryById(id);
    }
}

开启feign功能,在consumerApplication启动类上加上注解@EnableFeignClients

@SpringCloudApplication
@EnableFeignClients
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Bean
    @LoadBalanced //开启负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

启动访问 8080/cf?id=xxxx

gateway

创建子工程

pom.xml:

<dependencies>
    <!--gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--eureka-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

编写启动类

@SpringBootApplication
@EnableDiscoveryClient //开启eureka发现
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

配置文件

server:
  port: 8086
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 路由id 可以随意写
        - id: bill-service-route
          # 代理服务的地址
          uri: http://127.0.0.1:9001
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/bill/**
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true

启动服务测试(代理到8086端口)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rExIKjcW-1636281635451)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922185522826.png)]

面向服务路由

上面写死了代理服务的地址,如果是服务集群这样显然不合理,应该根据服务名称去eureka注册中心找对应的服务,然后进行动态路由

修改配置文件

添加spring.cloud.gateway.routes.uri: lb

路由配置中uri所用的协议为lb时(以uri: lb://bill-service为例),gateway将使用 LoadBalancerClient把 bill-service通过eureka解析为实际的主机和端口,并进行ribbon负载均衡。

server:
  port: 8086
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 路由id 可以随意写
        - id: bill-service-route
          # 代理服务的地址
#          uri: http://127.0.0.1:9001
          uri: lb://bill-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/bill/**
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
路由前缀

添加前缀

修改配置文件

修改spring.cloud.gateway.routes.predicates.path为 /**

添加spring.cloud.gateway.routes.filters.PrefixPath: /bill

server:
  port: 8086
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 路由id 可以随意写
        - id: bill-service-route
          # 代理服务的地址
#          uri: http://127.0.0.1:9001
          uri: lb://bill-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/**
          filter:
            # 添加请求路径的前缀
            - PrefixPath=/bill
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true

去除前缀

server:
  port: 8086
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 路由id 可以随意写
        - id: bill-service-route
          # 代理服务的地址
#          uri: http://127.0.0.1:9001
          uri: lb://bill-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/api/bill/**
          filters:
            # 表示过滤一个路径
            - StripPrefix=1
            # 添加请求路径的前缀
#            - PrefixPath=/bill
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true

过滤器

修改配置文件

server:
  port: 8086
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 路由id 可以随意写
        - id: bill-service-route
          # 代理服务的地址
#          uri: http://127.0.0.1:9001
          uri: lb://bill-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/api/bill/**
          filters:
            # 表示过滤一个路径
            - StripPrefix=1
            # 添加请求路径的前缀
#            - PrefixPath=/bill
      # 默认过滤器,对所有路由都生效
      default-filters:
        - AddResponseHeader=X-Response-Foo, Bar
        - AddResponseHeader=abc-myname,lxs
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true

访问得到

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ebaELYOS-1636281635452)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922195145733.png)]

过滤器分为局部过滤器和全局过滤器

局部过滤器:通过 spring.cloud.gateway.routes.filters 配置在具体路由下,只作用在当前路由上;如果配置spring.cloud.gateway.default-fifilters 上会对所有路由生效也算是全局的过滤器;但是这些过滤器的实现上都是要实现GatewayFilterFactory接口。

全局过滤器:不需要在配置文件中配置,作用在所有的路由上;实现 GlobalFilter 接口即可

自定义局部过滤器

编写过滤器,别忘了注解@Component

package com.mkl.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;

import java.util.Arrays;
import java.util.List;

public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {

    static final String PARAM_NAME = "param";

    public MyParamGatewayFilterFactory() {
        super(Config.class);
    }

    public List<String> shortcutFieldOrder() {
        return Arrays.asList(PARAM_NAME);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            //http://localhost:8086/api/bill?id=3&name=lxs  config.param ==>name
            //获取请求参数中param对应的参数名的参数值
            ServerHttpRequest request = exchange.getRequest();
            if (request.getQueryParams().containsKey(config.param)) {
                request.getQueryParams().get(config.param).forEach(value -> System.out.printf("-----局部过滤器-----%s = %s-----", config.param, value));
            }
            return chain.filter(exchange);
        });
    }

    public static class Config {
        //对应在配置过滤器的时候指定的参数名
        private String param;

        public String getParam() {
            return param;
        }

        public void setParam(String param) {
            this.param = param;
        }
    }
}

修改配置文件

MyParam=name

server:
  port: 8086
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 路由id 可以随意写
        - id: bill-service-route
          # 代理服务的地址
#          uri: http://127.0.0.1:9001
          uri: lb://bill-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/api/bill/**
          filters:
            # 表示过滤一个路径
            - StripPrefix=1
            # 自定义过滤器
            - MyParam=name
            # 添加请求路径的前缀
#            - PrefixPath=/bill
      # 默认过滤器,对所有路由都生效
      default-filters:
        - AddResponseHeader=X-Response-Foo, Bar
        - AddResponseHeader=abc-myname,lxs
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xy6XYb1R-1636281635452)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922225840519.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cm18HLKo-1636281635453)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922225948483.png)]

自定义全局过滤器

编写全局过滤器MyGlobalFilter,别忘了注解@Component

这个token要用postman加在header中

package com.mkl.gateway.filter;

import org.apache.commons.lang.StringUtils;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("-----全局过滤器MyGlobalFilter-----");
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if (StringUtils.isBlank(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        //值越小越先执行
        return 1;
    }
}

gateway对外 feign对内

Git配置管理

gitee建立仓库创建并保存配置文件

创建配置中心微服务工程

配置文件

<dependencies>
    <!--eureka-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--config-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
</dependencies>

创建启动类

@EnableConfigServer注解开启配置服务

@SpringBootApplication
@EnableConfigServer //开启配置服务
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

配置文件

server:
  port: 12000
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/silver1009/bill-config.git
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bHlLMOVT-1636281635454)(C:\Users\Silver\AppData\Roaming\Typora\typora-user-images\image-20210922234753098.png)]

获取配置中心配置

bill-service添加依赖

<!--config-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

删除bill-service的application,yml配置文件

创建bill-service的bootstrap.yml配置文件

spring:
  cloud:
    config:
      # 要与仓库中的配置文件的application保持一致
      name: bill
      # 要与仓库中配置文件的profile保持一致
      profile: dev
      # 要与仓库中的配置文件的所属版本一致
      label: master
      discovery:
        # 使用配置中心
        enabled: true
        # 配置中心服务名
        service-id: config-server
eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka

springcloud bus

修改config-server

pom.xml

<!--spring cloud bus-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>

配置文件

server:
  port: 12000
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/silver1009/bill-config.git
  # 配置rabbitmq信息,如果都是默认值不需要配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
management:
  endpoints:
    web:
      exposure:
        # 暴露触发消息总线的地址
        include: bus-refresh 

改造用户服务

bill-service加入spring cloud bus相关依赖


<!--spring cloud bus-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

修改bootstrap.yml

spring:
  cloud:
    config:
      # 要与仓库中的配置文件的application保持一致
      name: bill
      # 要与仓库中配置文件的profile保持一致
      profile: dev
      # 要与仓库中的配置文件的所属版本一致
      label: master
      discovery:
        # 使用配置中心
        enabled: true
        # 配置中心服务名
        service-id: config-server
  # 配置rabbitmq信息 如果与默认值一致则不需要配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

BillController

注解@RefreshScope

@RestController
@RequestMapping(value = "/bill")
@RefreshScope //刷新配置
public class BillController {

刷新配置需要post请求到http://localhost:12000/actuator/bus-refresh

ure:
# 暴露触发消息总线的地址
include: bus-refresh


改造用户服务

bill-service加入spring cloud bus相关依赖

```xml

<!--spring cloud bus-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

修改bootstrap.yml

spring:
  cloud:
    config:
      # 要与仓库中的配置文件的application保持一致
      name: bill
      # 要与仓库中配置文件的profile保持一致
      profile: dev
      # 要与仓库中的配置文件的所属版本一致
      label: master
      discovery:
        # 使用配置中心
        enabled: true
        # 配置中心服务名
        service-id: config-server
  # 配置rabbitmq信息 如果与默认值一致则不需要配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

BillController

注解@RefreshScope

@RestController
@RequestMapping(value = "/bill")
@RefreshScope //刷新配置
public class BillController {

刷新配置需要post请求到http://localhost:12000/actuator/bus-refresh

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值