架构师学习第22周——Bus,Gateaway

Bus简介

在这里插入图片描述
在这里插入图片描述
这里我们最重要的作用就是利用Bus总线去同时刷新我们多台服务器的配置文件。

总线式架构的配置中心

Demo使用
Config-bus-server
bootstrap.yml

encrypt:
  key: 20051001

application.yml

spring:
  application:
    name: config-bus-server
  rabbitmq:
    host: 192.168.31.183
    port: 5672
    username: admin
    password: admin123
  cloud:
    config:
      server:
        git:
          uri: https://github.com/yangzhao-feng/config-repo/
          # 强制拉取资源文件
          #          search-paths:
          #          username:
          #          password:
          # 可以配置多个注册中心,根据pattern匹配
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:20000/eureka/

server:
  port: 60002

management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

启动类

package com.imooc.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;

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

config-bus-client模块
bootstrap.yml

spring:
  application:
    name: config-bus-client
  rabbitmq:
    host: 192.168.31.183
    port: 5672
    username: admin
    password: admin123
  cloud:
    stream:
      default-binder: rabbit
    config:
#      uri: http://localhost:60000
      discovery:
        enabled: true
        service-id: config-bus-server
      profile: prod
      label: master
      name: config-consumer


server:
  port: 61001

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:20000/eureka/

##//从github中拉取属性文件,再从本地的属性文件通过@Value(..)拉取到我们的属性中
#myWords: ${words}

management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

启动类

package com.imooc.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;


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

Controller

package com.imooc.springcloud;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {

    @Value("${name}")
    private String name;

    @Value("${food}")
    private String food;

    //@Value("${myWords}")
    @Value("${words}")
    private String words;

    @GetMapping("/name")
    public String getName()
    {
        return name;
    }

    @GetMapping("/words")
    public String getWords()
    {
        return words;
    }

    @GetMapping("/dinner")
    public String dinner(){
        return "May I have one " + food;
    }

}

RefreshController

package com.imooc.springcloud;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/refresh")
@RefreshScope
public class RefreshController {

    @Value("${words}")
    private String words;

    @GetMapping("/words")
    public String getWords()
    {
        return words;
    }

//http://localhost:60002/actuator/bus-refresh
}

更改client的端口号作为配置中心的多个子节点使,用postman发送http://localhost:60002/actuator/bus-refresh,刷新后发现多个子节点的属性刷新成功,而且只要我们刷新某个子节点其他的子节点也会一起刷新。

Gateway

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Gateway使用Demo

创建gateway-sample模块
application.yml

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:20000/eureka/

##//从github中拉取属性文件,再从本地的属性文件通过@Value(..)拉取到我们的属性中
#myWords: ${words}

management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

server:
  port: 65000

启动类

package com.imooc.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

启动我们的Eureka服务中心以及两个Feign-Client,端口号位40002和40003,gateway端口号位65000,使用postman访问http://localhost:65000/feign-client/sayHi访问Feign-Client中的接口,第一次访问返回端口40002,第二次返回40003,即默认轮询规则。

添加路由:
用postman发送post请求http://localhost:65000/actuator/gateway/routes/dynamic
最后的dynamic位自定义的id名
请求体

{
	"predicates": [
		{
			"name": "Path",
			"args": {
				"_genkey_0": "/dynamic/**"
			}
		}],
	"filters": [
		{
			"name": "StripPrefix",
			"args": {
				"_genkey_0": "1"
			}
		}],
	"uri": "lb://FEIGN-CLIENT",
	"order": 0
}

返回201创建成功
在这里插入图片描述
http://localhost:65000/actuator/gateway/routes查看,名为dynamic的路由规则已经成功添加。

验证路由规则:
利用postman发送Get请求http://localhost:65000/dynamic/sayHi,成功返回,规则验证成功。
在这里插入图片描述

删除路由:
利用postman发送Delete请求http://localhost:65000/actuator/gateway/routes/dynamic,其中最后一个式创建路由规则名,返回200,删除成功。

Path断言

使用yml配置路由规则

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
      - id: feignclient
        uri: lb://FEIGN-CLIENT
        predicates:
        - Path=/yml/**
        filters:
        #localhost:port/yml/......——>localhost:port/.......
        - StripPrefix=1

java配置(推荐使用)路由:

package com.imooc.springcloud;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
@Configuration
public class GatewayConfiguration {

    @Bean
    @Order
    public RouteLocator customizedRoutes(RouteLocatorBuilder builder)
    {
        return builder.routes()
        		//请求路径满足/java/**
                .route(r -> r.path("/java/**")
                        //请求必须是Get请求
                        .and().method(HttpMethod.GET)
                        //请求头里必须有name属性
                        .and().header("name")
                        .filters(f -> f.stripPrefix(1)
                                    //Respoonse中添加属性位java-param,值为gateway-config
                                    .addResponseHeader("java-param","gateway-config"))
                        .uri("lb://FEIGN-CLIENT")
                )
                .build();
    }
}

After断言实现简易秒杀

在Feign-Client中添加一个Controller

package com.imooc.springcloud;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@Slf4j
@RequestMapping("gateway")
public class GatewayController {

    public static final Map<Long,Product> items = new ConcurrentHashMap<>();

	//查看或添加商品接口
    @ResponseBody
    @RequestMapping(value = "details",method = RequestMethod.GET)
    public Product get(@RequestParam("pid") Long pid)
    {
        if(!items.containsKey(pid))
        {
            Product prod = Product.builder()
                    .productId(pid)
                    .description("好吃不贵")
                    .stock(100L)
                    .build();
            items.putIfAbsent(pid,prod);
        }
        return items.get(pid);

    }
	//下单接口
    @RequestMapping(value = "placeOrder",method = RequestMethod.POST)
    public String buy(@RequestParam("pid") Long pid){
        Product prod = items.get(pid);

        if(prod == null)
        {
            return "Product not found";
        }else if(prod.getStock() <= 0){
            return "Sold out";
        }

        synchronized (prod)
        {
            if(prod.getStock() <= 0L)
            {
                return "Sold out";
            }
            prod.setStock(prod.getStock() - 1);
        }

        return "Order Placed";
    }

}

Product类

package com.imooc.springcloud;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class Product {

    private Long productId;
    private String description;
    private Long stock;

}

在gateway-config模块中GatewayConfiguration添加路由规则

package com.imooc.springcloud;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;

import java.time.ZonedDateTime;

@Configuration
public class GatewayConfiguration {

    @Bean
    @Order
    public RouteLocator customizedRoutes(RouteLocatorBuilder builder)
    {
        return builder.routes()
                .route(r -> r.path("/seckill/**")
                        //当前这个路由规则只在某个时间之后生效
                        .and().after(ZonedDateTime.now().plusMinutes(1))
                        //当前这个路由规则只在某个时间之前生效
                        //.and().before()
                        //在两个时间点之间
                        //.and().between()
                        .filters(f -> f.stripPrefix(1))
                            .uri("lb://FEIGN-CLIENT")
                )
                .build();
    }
}

再模块启动的一分钟后规则启动,在该时间范围之外的默认返回404。

自定义过滤器

在gateway-config模块中定义一个TimerFilter
这个过滤器的作用就是打印接口的调用时间及相关信息。

package com.imooc.springcloud;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Component
//GlobalFilter 全局过滤器
public class TimerFilter implements GatewayFilter, Ordered {


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        StopWatch timer = new StopWatch();
        timer.start(exchange.getRequest().getURI().getRawPath());

//        exchange.getAttributes().put("requestTimeBegin",System.currentTimeMillis());
        //Zull - Filter before / after
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    timer.stop();
                    log.info(timer.prettyPrint());
                })
        );
    }


    @Override
    public int getOrder() {
        return 0;
    }
}

GatewayConfiguration
在之前的基础上的filters中添加我们自定义的filter。

package com.imooc.springcloud;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;

import java.time.ZonedDateTime;

@Configuration
public class GatewayConfiguration {

    @Autowired
    private TimerFilter timerFilter;

    @Bean
    @Order
    public RouteLocator customizedRoutes(RouteLocatorBuilder builder)
    {
        return builder.routes()
                .route(r -> r.path("/java/**")
                        //请求必须是Get请求
                        .and().method(HttpMethod.GET)
                        //请求头里必须有name属性
                        .and().header("name")
                        .filters(f -> f.stripPrefix(1)
                                    //Respoonse中添加属性位java-param,值为gateway-config
                                    .addResponseHeader("java-param","gateway-config")
                                    .filters(timerFilter)
                        )
                        .uri("lb://FEIGN-CLIENT")
                )
                .route(r -> r.path("/seckill/**")
                        //当前这个路由规则只在某个时间之后生效
                        .and().after(ZonedDateTime.now().plusMinutes(1))
                        //当前这个路由规则只在某个时间之前生效
                        //.and().before()
                        //在两个时间点之间
                        //.and().between()
                        .filters(f -> f.stripPrefix(1))
                            .uri("lb://FEIGN-CLIENT")
                )
                .build();
    }
}

我们还可以使用全局Filter,只需将过滤器继承GatewayFilter换成GlobalFilter,即可并且不需要再任何一个匹配类中再添加自定义的过滤器。作用是过滤所有的路由,并将执行时间打印出来。
除了执行时间我们还可以做我们想做的其他操作。

package com.imooc.springcloud;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Component
//GlobalFilter 全局过滤器
public class TimerFilter implements GlobalFilter , Ordered {


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        StopWatch timer = new StopWatch();
        timer.start(exchange.getRequest().getURI().getRawPath());

//        exchange.getAttributes().put("requestTimeBegin",System.currentTimeMillis());
        //Zull - Filter before / after
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    timer.stop();
                    log.info(timer.prettyPrint());
                })
        );
    }


    @Override
    public int getOrder() {
        return 0;
    }
}

实现JWT鉴权

这里只贴出狠心代码
负责鉴权的Controller
JwtService

package com.imooc.test;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.imooc.springcloud.Account;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Date;

@Slf4j
@Service
public class JwtService {
    //生产环境不能这么用
    private static final  String KEY = "changeIt";
    private static final String ISSUER = "yao";
    private static final long TOKEN_EXPTIME_TIME = 60000;
    private static final String USER_NAME = "username";

    /**
     * 生成Token
     * @param acct
     * @return
     */
    public String token(Account acct)
    {
        Date now = new Date();
		//生成算法
        Algorithm algorithm = Algorithm.HMAC256(KEY);
		//创建token
        String token = JWT.create()
                        .withIssuer(ISSUER)
                        .withIssuedAt(now)
                        .withExpiresAt(new Date(now.getTime() + TOKEN_EXPTIME_TIME))
                        .withClaim(USER_NAME,acct.getUsername())
                        .sign(algorithm);

        log.info("jwt generated user={}",acct.getUsername());

        return token;
    }

    /**
     * 校验Token
     * @param token
     * @param username
     * @return
     */
    public boolean verify(String token,String username)
    {
        log.info("verifying jwt - username={}",username);

        try {
        	//生成对应的算法
            Algorithm algorithm = Algorithm.HMAC256(KEY);
            //放入对应的信息进行鉴权withIssuer(ISSUER),withClaim(USER_NAME,username)与上面对应
            JWTVerifier verifier = JWT.require(algorithm)
                                    .withIssuer(ISSUER)
                                    .withClaim(USER_NAME,username)
                                    .build();
            //开始鉴权,如果鉴权不成功,将自动报错。
            verifier.verify(token);

        }catch (Exception e) {
            log.error("auth failed",e);
            return false;
        }


        return true;
    }

}

声明一个过滤器AuthFilter,并在Configuration类中需要此过滤器的路由添加此路由。

package com.imooc.springcloud;


import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component("authFilter")
@Slf4j
public class AuthFilter implements GatewayFilter, Ordered {

    private static final String AUTH = "Authorization";
    private static final String USERNAME = "imooc-user-name";

    @Autowired
    private AuthService authService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("Auth start");
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders header = request.getHeaders();
        //获取header中的Authorization信息
        String token = header.getFirst(AUTH);
        //获取header中的imooc-user-name信息
        String username = header.getFirst(USERNAME);

        ServerHttpResponse response = exchange.getResponse();
        //如果Authorization为空则,返回错误
        if (StringUtils.isBlank(token)) {
            log.error("token not found");
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
		
		//如果传入的imooc-user-name信息和Authorization信息校验不成功,返回错误。
        AuthResponse resp = authService.verify(token, username);
        if (resp.getCode() != 1L) {
            log.error("invalid token");
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }

        // TODO 将用户信息存放在请求header中传递给下游业务
        ServerHttpRequest.Builder mutate = request.mutate();
        mutate.header("imooc-user-name", username);
        ServerHttpRequest buildReuqest = mutate.build();

        //todo 如果响应中需要放数据,也可以放在response的header中
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("imooc-username",username);
        return chain.filter(exchange.mutate()
                .request(buildReuqest)
                .response(response)
                .build());
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

其他网管技术&选型

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值