spring-cloud學習之2.搭建請求網關spring-cloud-getway


一:准備

請求網關,顧名思義,所有請求都有網關統一處理,路由至各個服務,getway是spring最新網關,有取代zuul的趨勢,具體請百度。

1.導包

getway包:

<!--gateway 網關依賴,內置webflux 依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

getway熔斷:

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

getway自帶限流功能,內部使用的是redis

   <!--        redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

eureka實例:

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

2:配置:

server:
  port: 2001
spring:
  application:
    name: dandelion-getway
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服務注冊與發現組件結合,設置為 true 后可以直接使用應用名稱調用服務
          enabled: true

      # 路由配置中心
      routes:
        # 服務中心
        - id: dandelion-api #id 以-開頭 唯一即可
          uri: lb://dandelion-api  #lb://代表服務內轉發,后跟服務名稱
          #斷言 和過濾差不多意思,其中有配置具體百度
          predicates:
            - Method=GET  #只接受get方法
            - Path=/system/** #只接收/system開頭的路徑
            # 過濾器配置  getway有兩種過濾方式,GatewayFilter和GlobalFilter
            # GatewayFilter : 需要通過spring.cloud.routes.filters 配置在具體路由下,只作用在當前路由上或通過spring.cloud.default-filters配置在全局,作用在所有路由上
            #GlobalFilter : 全局過濾器,不需要在配置文件中配置,作用在所有的路由上。
          filters:
            # 驗證碼處理
            - ImgCodeFilter
            #設置StripPrefix=1表示從二級url路徑轉發,即http://localhost:2001/auth/demo將會轉發到http://localhost:2002/demo
            - StripPrefix=1
            - name: RequestRateLimiter  #固定名稱
              args:
                #配置限流鍵的解析器
                key-resolver: '#{@ipRequestLimiter}'
                #令牌桶每秒填充速率,1s/1次
                redis-rate-limiter.replenishRate: 1
                # 令牌桶總數量
                redis-rate-limiter.burstCapacity: 1
            # 降級配置
            - name: Hystrix #固定名稱
              args:
                name: fallbackcmd
                fallbackUri: 'forward:/fallback'

  redis:
    host: 192.168.211.128
    jedis:
      pool:
        max-wait: 300ms
    timeout: 1 #單位秒

eureka:
  client:
    service-url:
      defaultZone: http://localhost:1001/eureka
  instance:
    instance-id:  ${spring.application.name}:${server.port}
    prefer-ip-address: true     #訪問路徑可以顯示IP地址

hystrix:
  command:
    default:  #default全局有效,service id指定應用有效
      execution:
        timeout:
          enabled: true  #是否啟用超時 默認啟用
        isolation:
          thread:
            timeoutInMilliseconds: 1000 # 命令執行超時時間,默認1000ms

3.過濾器:

package club.dandelion.cloud.getway.filter;

import club.dandelion.cloud.common.R;
import club.dandelion.cloud.common.cons.Constants;
import club.dandelion.cloud.common.exception.ValidateCodeException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 驗證碼處理
 *
 * @author jiang
 */
@Component
public class ImgCodeFilter extends AbstractGatewayFilterFactory<ImgCodeFilter.Config> {
    private final static String AUTH_URL = "/auth/login";

    @Autowired
    private StringRedisTemplate redisTemplate;

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

    @Override
    public GatewayFilter apply(Config config) {

        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            URI uri = request.getURI();
            // 不是登錄請求,直接向下執行
            if (!StringUtils.containsIgnoreCase(uri.getPath(), AUTH_URL)) {
                return chain.filter(exchange);
            }
            try {
                String bodyStr = resolveBodyFromRequest(request);
                JSONObject bodyJson = JSONObject.parseObject(bodyStr);
                String code = (String) bodyJson.get("captcha");
                String randomStr = (String) bodyJson.get("randomStr");
                // 校驗驗證碼
                checkCode(code, randomStr);
            } catch (Exception e) {
                e.printStackTrace();
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
                response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                String msg = JSON.toJSONString(R.error(e.getMessage()));
                DataBuffer bodyDataBuffer = response.bufferFactory().wrap(msg.getBytes());
                return response.writeWith(Mono.just(bodyDataBuffer));
            }
            return chain.filter(exchange);
        };
    }

    /**
     * 獲取請求體
     *
     * @param serverHttpRequest
     * @return
     */
    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        // 獲取請求體
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });

        return bodyRef.get();
    }

    /**
     * 檢查code
     *
     * @param code
     * @param randomStr
     */
    @SneakyThrows
    private void checkCode(String code, String randomStr) {
        if (StringUtils.isBlank(code)) {
            throw new ValidateCodeException("驗證碼不能為空");
        }
        if (StringUtils.isBlank(randomStr)) {
            throw new ValidateCodeException("驗證碼不合法");
        }
        String key = Constants.DEFAULT_CODE_KEY + randomStr;
        String saveCode = redisTemplate.opsForValue().get(key);
        redisTemplate.delete(key);
        if (!code.equalsIgnoreCase(saveCode)) {
            throw new ValidateCodeException("驗證碼不合法");
        }
    }

    /**
     * 必須要有
     */
    public static class Config {
    }
}
View Code
package club.dandelion.cloud.getway.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

/**
 * getway限流配置
 *
 * @author jiang
 */
@Configuration
public class HttpRequestLimiter {

    @Bean
    public KeyResolver ipRequestLimiter() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }

}
View Code

4.熔斷反饋:

import club.dandelion.cloud.common.R;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.util.Optional;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;

/**
 * 錯誤信息
 *
 * @author jiang
 */
@Slf4j
@Component
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse> {
    @Override
    public Mono<ServerResponse> handle(ServerRequest serverRequest) {
        Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        originalUris.ifPresent(originalUri -> log.error("網關執行請求:{}失敗,hystrix服務降級處理", originalUri));
        return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(BodyInserters.fromValue(JSON.toJSONString(R.error("服務已被降級熔斷"))));
    }
}

 

 

5:路徑路由 相當於controller

 
         
import club.dandelion.cloud.getway.handler.HystrixFallbackHandler;
import club.dandelion.cloud.getway.handler.ImgCodeHandler;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;

/**
* 沒有controller所以配置路由信息
* <p>
* RouterFunction使用RequestPredicate將傳入請求映射到HandlerFunction。
* <p>
* AllArgsConstructor lombok注解,代表有所有的構造參數 所以下面兩個屬性是根據構造參數注入的
*
* @author jiang
*/
@Configuration
@AllArgsConstructor
public class RouterFunctionConfiguration {

private HystrixFallbackHandler hystrixFallbackHandler;
private ImgCodeHandler imgCodeHandler;

@Bean
public RouterFunction<?> routerFunction() {
return RouterFunctions
.route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
hystrixFallbackHandler)
.andRoute(RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
imgCodeHandler);
}
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM