gateway接口日志,Resilience4j熔断测试类
This commit is contained in:
parent
47e8f1ea5f
commit
1078f78060
|
|
@ -24,4 +24,7 @@ public class CommonConstant {
|
|||
// 角色常量
|
||||
public static final String ROLE_ADMIN = "admin";
|
||||
public static final String ROLE_USER = "user";
|
||||
|
||||
// Redis key
|
||||
public static final String REDIS_KEY_USER_TOKEN = "user:token:";
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import org.springframework.context.annotation.ComponentScan;
|
|||
@MapperScan("com.tacit.admin.mapper")
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients(basePackages = "com.tacit.common.feign")
|
||||
@ComponentScan(basePackages = {"com.tacit.admin", "com.tacit.common"})
|
||||
@ComponentScan(basePackages = {"com.tacit.admin", "com.tacit.common.redis"})
|
||||
public class AdminApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AdminApplication.class, args);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class SecurityConfig {
|
|||
.cors(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/auth/**", "/test/hello", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||
.requestMatchers("/auth/**","test/**", "/test/hello", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||
.requestMatchers("/test/feign/**").authenticated()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tacit.admin.controller;
|
||||
|
||||
import com.tacit.admin.service.Resilience4jDemoService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Resilience4j演示控制器
|
||||
* 用于测试断路器和重试功能
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/resilience4j")
|
||||
@Slf4j
|
||||
public class Resilience4jDemoController {
|
||||
|
||||
@Autowired
|
||||
private Resilience4jDemoService resilience4jDemoService;
|
||||
|
||||
/**
|
||||
* 测试断路器功能
|
||||
* @param failCount 失败次数,用于模拟服务失败
|
||||
* @return 响应结果
|
||||
*/
|
||||
@GetMapping("/circuit-breaker")
|
||||
public ResponseEntity<String> testCircuitBreaker(@RequestParam(defaultValue = "0") int failCount) {
|
||||
log.info("测试断路器,failCount: {}", failCount);
|
||||
String result = resilience4jDemoService.testCircuitBreaker(failCount);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试重试功能
|
||||
* @param failCount 失败次数,用于模拟服务失败
|
||||
* @return 响应结果
|
||||
*/
|
||||
@GetMapping("/retry")
|
||||
public ResponseEntity<String> testRetry(@RequestParam(defaultValue = "0") int failCount) {
|
||||
log.info("测试重试,failCount: {}", failCount);
|
||||
String result = resilience4jDemoService.testRetry(failCount);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试断路器和重试组合功能
|
||||
* @param failCount 失败次数,用于模拟服务失败
|
||||
* @return 响应结果
|
||||
*/
|
||||
@GetMapping("/combined")
|
||||
public ResponseEntity<String> testCombined(@RequestParam(defaultValue = "0") int failCount) {
|
||||
log.info("测试断路器和重试组合,failCount: {}", failCount);
|
||||
String result = resilience4jDemoService.testCombined(failCount);
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置断路器状态
|
||||
* @return 响应结果
|
||||
*/
|
||||
@GetMapping("/reset")
|
||||
public ResponseEntity<String> resetCircuitBreaker() {
|
||||
log.info("重置断路器状态");
|
||||
resilience4jDemoService.resetCircuitBreaker();
|
||||
return ResponseEntity.ok("断路器状态已重置");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package com.tacit.admin.service;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
|
||||
import io.github.resilience4j.retry.annotation.Retry;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Resilience4j演示服务
|
||||
* 用于测试断路器和重试功能
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class Resilience4jDemoService {
|
||||
|
||||
// 用于记录调用次数的计数器
|
||||
private int circuitBreakerCallCount = 0;
|
||||
private int retryCallCount = 0;
|
||||
private int combinedCallCount = 0;
|
||||
|
||||
/**
|
||||
* 测试断路器功能
|
||||
* @param failCount 失败次数,用于模拟服务失败
|
||||
* @return 响应结果
|
||||
*/
|
||||
@CircuitBreaker(name = "demoCircuitBreaker", fallbackMethod = "circuitBreakerFallback")
|
||||
public String testCircuitBreaker(int failCount) {
|
||||
circuitBreakerCallCount++;
|
||||
log.info("断路器测试调用,第{}次,failCount: {}", circuitBreakerCallCount, failCount);
|
||||
|
||||
// 模拟服务失败
|
||||
if (circuitBreakerCallCount <= failCount) {
|
||||
log.error("断路器测试调用失败,第{}次", circuitBreakerCallCount);
|
||||
throw new RuntimeException("服务调用失败");
|
||||
}
|
||||
|
||||
return "断路器测试调用成功,第" + circuitBreakerCallCount + "次调用";
|
||||
}
|
||||
|
||||
/**
|
||||
* 断路器的回退方法
|
||||
* @param failCount 失败次数
|
||||
* @param ex 异常信息
|
||||
* @return 回退结果
|
||||
*/
|
||||
public String circuitBreakerFallback(int failCount, Exception ex) {
|
||||
log.warn("断路器回退被调用,failCount: {}, 异常: {}", failCount, ex.getMessage());
|
||||
return "断路器回退结果: " + ex.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试重试功能
|
||||
* @param failCount 失败次数,用于模拟服务失败
|
||||
* @return 响应结果
|
||||
*/
|
||||
@Retry(name = "demoRetry", fallbackMethod = "retryFallback")
|
||||
public String testRetry(int failCount) {
|
||||
retryCallCount++;
|
||||
log.info("重试测试调用,第{}次,failCount: {}", retryCallCount, failCount);
|
||||
|
||||
// 模拟服务失败
|
||||
if (retryCallCount <= failCount) {
|
||||
log.error("重试测试调用失败,第{}次", retryCallCount);
|
||||
throw new RuntimeException("服务调用失败");
|
||||
}
|
||||
|
||||
return "重试测试调用成功,第" + retryCallCount + "次调用";
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试的回退方法
|
||||
* @param failCount 失败次数
|
||||
* @param ex 异常信息
|
||||
* @return 回退结果
|
||||
*/
|
||||
public String retryFallback(int failCount, Exception ex) {
|
||||
log.warn("重试回退被调用,failCount: {}, 异常: {}", failCount, ex.getMessage());
|
||||
return "重试回退结果: " + ex.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试断路器和重试组合功能
|
||||
* @param failCount 失败次数,用于模拟服务失败
|
||||
* @return 响应结果
|
||||
*/
|
||||
@CircuitBreaker(name = "demoCircuitBreaker", fallbackMethod = "combinedFallback")
|
||||
@Retry(name = "demoRetry")
|
||||
public String testCombined(int failCount) {
|
||||
combinedCallCount++;
|
||||
log.info("组合测试调用,第{}次,failCount: {}", combinedCallCount, failCount);
|
||||
|
||||
// 模拟服务失败
|
||||
if (combinedCallCount <= failCount) {
|
||||
log.error("组合测试调用失败,第{}次", combinedCallCount);
|
||||
throw new RuntimeException("服务调用失败");
|
||||
}
|
||||
|
||||
return "组合测试调用成功,第" + combinedCallCount + "次调用";
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合功能的回退方法
|
||||
* @param failCount 失败次数
|
||||
* @param ex 异常信息
|
||||
* @return 回退结果
|
||||
*/
|
||||
public String combinedFallback(int failCount, Exception ex) {
|
||||
log.warn("组合回退被调用,failCount: {}, 异常: {}", failCount, ex.getMessage());
|
||||
return "组合回退结果: " + ex.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置断路器状态
|
||||
*/
|
||||
public void resetCircuitBreaker() {
|
||||
circuitBreakerCallCount = 0;
|
||||
retryCallCount = 0;
|
||||
combinedCallCount = 0;
|
||||
log.info("所有计数器已重置");
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import org.springframework.context.annotation.ComponentScan;
|
|||
@SpringBootApplication
|
||||
@MapperScan("com.tacit.app.mapper")
|
||||
@EnableFeignClients(basePackages = "com.tacit.common.feign")
|
||||
@ComponentScan(basePackages = {"com.tacit.app", "com.tacit.common"})
|
||||
@ComponentScan(basePackages = {"com.tacit.app", "com.tacit.common.redis"})
|
||||
public class AppApiApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AppApiApplication.class, args);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@
|
|||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tacit</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tacit</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
|
|
@ -62,6 +68,12 @@
|
|||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tacit</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package com.tacit.gateway;
|
|||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@ComponentScan(basePackages = {"com.tacit.gateway", "com.tacit.common.redis"})
|
||||
public class GatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
package com.tacit.gateway.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// key 使用 String 序列化
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// value 使用 JSON 序列化
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// 设置字段可见性
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
|
||||
// 新版 Jackson 安全的多态类型配置
|
||||
mapper.activateDefaultTyping(
|
||||
LaissezFaireSubTypeValidator.instance,
|
||||
ObjectMapper.DefaultTyping.NON_FINAL
|
||||
);
|
||||
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tacit.gateway.filter;
|
||||
|
||||
import com.tacit.common.constant.CommonConstant;
|
||||
import com.tacit.common.redis.utils.RedisUtils;
|
||||
import com.tacit.common.utils.JwtUtils;
|
||||
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.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
|
@ -25,7 +25,7 @@ import java.util.List;
|
|||
@Slf4j
|
||||
public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory<JwtAuthenticationFilter.Config> {
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
private RedisUtils redisUtils;
|
||||
// 不需要认证的路径
|
||||
private static final List<String> WHITE_LIST = List.of(
|
||||
"/auth/login",
|
||||
|
|
@ -72,7 +72,7 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory<JwtAut
|
|||
|
||||
// 验证JWT令牌
|
||||
try {
|
||||
Boolean isTokenValid = redisTemplate.hasKey(token);
|
||||
boolean isTokenValid = redisUtils.hasKey(token);
|
||||
if (!isTokenValid) {
|
||||
return unauthorizedResponse(exchange, "Token已被注销");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
package com.tacit.gateway.filter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 请求日志记录过滤器
|
||||
* 用于记录请求的详细信息,包括请求路径、方法、参数、响应状态码、响应时间等
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RequestLoggingFilter extends AbstractGatewayFilterFactory<RequestLoggingFilter.Config> {
|
||||
|
||||
public RequestLoggingFilter() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
return new GatewayFilter() {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// 记录请求开始时间
|
||||
long startTime = System.currentTimeMillis();
|
||||
String requestId = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
|
||||
|
||||
// 获取请求信息
|
||||
String method = exchange.getRequest().getMethod().name();
|
||||
String path = exchange.getRequest().getURI().getPath();
|
||||
String query = exchange.getRequest().getURI().getQuery();
|
||||
|
||||
// 安全获取客户端IP,添加空检查
|
||||
String clientIp = "unknown";
|
||||
if (exchange.getRequest().getRemoteAddress() != null &&
|
||||
exchange.getRequest().getRemoteAddress().getAddress() != null) {
|
||||
clientIp = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
// 添加请求ID到请求头
|
||||
exchange.getRequest().mutate().header("X-Request-Id", requestId).build();
|
||||
|
||||
// 记录请求开始日志
|
||||
log.info("[{}] Request received: {} {}?{}", requestId, method, path, query);
|
||||
log.info("[{}] Client IP: {}", requestId, clientIp);
|
||||
log.info("[{}] Headers: {}", requestId, exchange.getRequest().getHeaders());
|
||||
|
||||
// 处理响应
|
||||
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
|
||||
// 记录响应时间
|
||||
long endTime = System.currentTimeMillis();
|
||||
long responseTime = endTime - startTime;
|
||||
|
||||
// 安全获取响应状态码,添加空检查
|
||||
int statusCode = 0;
|
||||
if (exchange.getResponse().getStatusCode() != null) {
|
||||
statusCode = exchange.getResponse().getStatusCode().value();
|
||||
}
|
||||
|
||||
// 记录响应日志
|
||||
log.info("[{}] Response sent: {} {}", requestId, statusCode, path);
|
||||
log.info("[{}] Response time: {}ms", requestId, responseTime);
|
||||
log.info("[{}] Response headers: {}", requestId, exchange.getResponse().getHeaders());
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
// 可以添加配置属性
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ spring:
|
|||
locator:
|
||||
enabled: true
|
||||
lower-case-service-id: true
|
||||
default-filters:
|
||||
- name: RequestLoggingFilter
|
||||
routes:
|
||||
# Admin Service Route
|
||||
- id: tacit-admin
|
||||
|
|
@ -17,7 +19,19 @@ spring:
|
|||
filters:
|
||||
- StripPrefix=1
|
||||
|
||||
# App API Service Route
|
||||
# 接口级路由重写
|
||||
- id: tacit-app-api-login
|
||||
uri: lb://tacit-app-api
|
||||
predicates:
|
||||
- Path=/api/user/login/v1
|
||||
- Method=POST
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- RewritePath=/user/login/v1, /user/login/v2
|
||||
- AddResponseHeader=X-Special-Route, login
|
||||
- AddResponseHeader=X-Rewritten-Path, /user/login/v2
|
||||
|
||||
# App API Service Route - Other APIs (With Auth)
|
||||
- id: tacit-app-api
|
||||
uri: lb://tacit-app-api
|
||||
predicates:
|
||||
|
|
|
|||
Loading…
Reference in New Issue