Spring Gateway Java反应式框架Reactor中的Mono和Flux - 码农小胖哥 - 博客园 (cnblogs.com)
业务 Spring Gateway
除此之外,Spring Gateway还可以通过整合其他组件来实现更为完善的功能。比如:
整合Spring Security:Spring Security是Spring生态圈中的一款强大的身份验证和授权框架,可以提供基于角色的访问控制和OAuth2认证等功能,可以与Spring Gateway集成,增强API网关的安全性。
整合OAuth2协议:OAuth2是一种流行的授权协议,可以用于客户端和API网关的交互,同时也可以为后端服务提供安全保障。Spring Gateway可以通过整合Spring Security和Spring Cloud Security等组件来实现OAuth2认证的功能。
整合服务发现和注册中心:Spring Gateway可以使用Eureka、Consul等注册中心来实现服务的发现和负载均衡,从而使API网关可以自动发现和调用服务。
整合开发框架:Spring Gateway可以通过整合Spring Boot、Spring Cloud等框架来简化开发过程,提高开发效率和代码可维护性。
总之,通过Spring Gateway和其他组件的整合,可以实现更为全面和强大的API网关功能,为企业应用架构的构建提供更多的可能性。
鉴权 + 熔断 这里提供一个实现Spring Gateway、Sentinel和Spring Security的示例代码,实现了接口访问次数统计、熔断降级、权限验证和OAuth2认证等功能。
首先是pom.xml文件的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-webflux</artifactId > </dependency > <dependency > <> org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-gateway</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > </dependency > <dependency > <> org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactIdspring-cloud-starter-security-oauth2</artifactId > </dependency >
接下来是application.yml文件的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 server: port: 8080 spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/api/user/** - id: product-service uri: lb://product-service predicates: - Path=/api/product/** application: name: api-gateway security: oauth2: client: registration: oauth-client: provider: my-oauth2-server client-id: client client-secret: secret authorization-grant-type: authorization_code redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' scope: email, profile, openid provider: my-oauth2-server: authorize-uri: http://localhost:8081/oauth/authorize token-uri: http://localhost:8081/oauth/token user-info-uri: http://localhost:8081/userinfo jwk-set-uri: http://localhost:8081/.well-known/jwks.json user-name-attribute: sub
其中,OAuth2相关配置需要根据具体的情况进行修改。
接下来是启动类的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @SpringBootApplication @EnableDiscoveryClient @EnableWebFluxSecurity @EnableReactiveMethodSecurity @EnableOAuth2Client public class ApiGatewayApplication { public static void main (String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } @Bean public SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) { http.authorizeExchange() .pathMatchers("/actuator/**" ).permitAll() .pathMatchers("/login/oauth2/**" ).permitAll() .anyExchange().authenticated() .and() .oauth2Login() .and() .oauth2ResourceServer().jwt(); return http.build(); } @Bean public RouteLocator customRouteLocator (RouteLocatorBuilder builder) { return builder.routes() .route("user-service" , r -> r.path("/api/user/**" ).filters(f -> f.circuitBreaker(c -> c.setName("user-service-cb" ).setFallbackUri("forward:/user-service-fallback" ))).uri("lb://user-service" )) .route("product-service" , r -> r.path("/api/product/**" ).filters(f -> f.circuitBreaker(c -> c.setName("product-service-cb" ).setFallbackUri("forward:/product-service-fallback" ))).uri("lb://product-service" )) .build(); } @Bean public CustomFallbackHandler userServiceFallbackHandler () { return new CustomFallbackHandler ("user-service is unavailable, please try again later." ); } @Bean public CustomFallbackHandler productServiceFallbackHandler () { return new CustomFallbackHandler ("product-service is unavailable, please try again later." ); } @Bean public SentinelGatewayFilterFactory sentinelGatewayFilterFactory () { return new SentinelGatewayFilterFactory (); } @Bean public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler () { return new SentinelGatewayBlockExceptionHandler (new DefaultBlockExceptionHandler (), new CustomBlockPageHandler ()); } @Bean public BlockRequestHandler blockRequestHandler () { return new CustomBlockRequestHandler (); } }
其中,SecurityWebFilterChain负责OAuth2认证的配置,customRouteLocator负责路由规则的配置,CustomFallbackHandler负责熔断降级的处理,SentinelGatewayFilterFactory、SentinelGatewayBlockExceptionHandler和BlockRequestHandler负责Sentinel的集成和异常处理。
路由规则中,添加了熔断器的配置,当访问/user-service和/product-service服务时出现异常时,将跳转到/user-service-fallback和/product-service-fallback路径,展示友好的提示页面。
最后是熔断降级的处理类CustomFallbackHandler的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class CustomFallbackHandler implements HandlerFunction <Response> { private final String message; public CustomFallbackHandler (String message) { this .message = message; } @Override public Mono<Response> handle (ServerRequest serverRequest) { return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(Collections.singletonMap("message" , message))); } }
这里定义了一个简单的处理逻辑,返回一个包含错误信息的json消息。可以根据具体需求进行定制。
至此,一个集成Spring Gateway、Sentinel和Spring Security的API网关就搭建完成了。实现了统计接口访问次数、接口隔离、权限验证和熔断降级等功能,为企业应用架构的构建提供了有力保障。
接口调用次数 是的,可以通过Redis和MySQL实现统计每个接口的调用次数。具体实现方式如下:
定义一个拦截器,用于统计每个接口的访问次数。在每个接口执行之前,从Redis中读取该接口的访问次数,并将其累加1;在接口执行完成之后,将访问次数写回Redis中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Component public class CountInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate<String, Integer> redisTemplate; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); Integer count = redisTemplate.opsForValue().get(uri); if (count == null ) { redisTemplate.opsForValue().set(uri, 1 ); } else { redisTemplate.opsForValue().increment(uri); } return true ; } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { String uri = request.getRequestURI(); Integer count = redisTemplate.opsForValue().get(uri); if (count != null ) { count = redisTemplate.opsForValue().decrement(uri); if (count <= 0 ) { redisTemplate.delete(uri); } } } }
将拦截器注册到Spring的MVC配置中。在Spring MVC配置中添加一个interceptor bean,然后将该拦截器添加到interceptor列表中即可。
1 2 3 4 5 6 7 8 9 10 11 @Configuration public class MvcConfig implements WebMvcConfigurer { @Autowired private CountInterceptor countInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(countInterceptor).addPathPatterns("/**" ); } }
创建一个定时任务,在任务中将Redis中的接口访问次数数据写入到MySQL中。定时任务可以使用Spring的Scheduled注解实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class DataSyncTask { @Autowired private RedisTemplate<String, Integer> redisTemplate; @Autowired private ApiCallCountRepo apiCallCountRepo; @Scheduled(fixedRate = 60 * 1000) public void syncRedisToDb () { Set<String> keys = redisTemplate.keys("*" ); List<ApiCallCount> apiCallCounts = new ArrayList <>(); for (String key : keys) { Integer count = redisTemplate.opsForValue().get(key); ApiCallCount apiCallCount = new ApiCallCount (); apiCallCount.setUri(key); apiCallCount.setCount(count); apiCallCount.setCreateTime(new Date ()); apiCallCounts.add(apiCallCount); } apiCallCountRepo.saveAll(apiCallCounts); } }
创建一个MySQL表,用于保存每个接口的访问次数信息。该表可以使用以下的SQL命令创建:
1 2 3 4 5 6 CREATE TABLE api_call_count ( id BIGINT PRIMARY KEY AUTO_INCREMENT, uri VARCHAR (255 ) NOT NULL , count INT NOT NULL , create_time TIMESTAMP NOT NULL );
创建一个JPA实体类,用于映射MySQL表中每个接口的访问次数信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Entity @Table(name = "api_call_count") public class ApiCallCount { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "uri") private String uri; @Column(name = "count") private Integer count; @Column(name = "create_time") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; }
至此,可以通过Redis+MySQL的方式统计每个接口的实际访问次数,并且实时保存和更新,同时还可以将数据进行持
源码解读