From 1078f78060591ddb9bf6a7cce530e516068a7d65 Mon Sep 17 00:00:00 2001 From: panxuejie <15855548138@163.com> Date: Thu, 8 Jan 2026 17:18:54 +0800 Subject: [PATCH] =?UTF-8?q?gateway=E6=8E=A5=E5=8F=A3=E6=97=A5=E5=BF=97?= =?UTF-8?q?=EF=BC=8CResilience4j=E7=86=94=E6=96=AD=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tacit/common/constant/CommonConstant.java | 3 + .../com/tacit/admin/AdminApplication.java | 2 +- .../tacit/admin/config/SecurityConfig.java | 2 +- .../Resilience4jDemoController.java | 70 ++++++++++ .../service/Resilience4jDemoService.java | 121 ++++++++++++++++++ .../java/com/tacit/app/AppApiApplication.java | 2 +- tacit-gateway/pom.xml | 12 ++ .../com/tacit/gateway/GatewayApplication.java | 2 + .../com/tacit/gateway/config/RedisConfig.java | 45 ------- .../filter/JwtAuthenticationFilter.java | 6 +- .../gateway/filter/RequestLoggingFilter.java | 79 ++++++++++++ .../src/main/resources/tacit-gateway.yaml | 16 ++- 12 files changed, 308 insertions(+), 52 deletions(-) create mode 100644 tacit-admin/src/main/java/com/tacit/admin/controller/Resilience4jDemoController.java create mode 100644 tacit-admin/src/main/java/com/tacit/admin/service/Resilience4jDemoService.java delete mode 100644 tacit-gateway/src/main/java/com/tacit/gateway/config/RedisConfig.java create mode 100644 tacit-gateway/src/main/java/com/tacit/gateway/filter/RequestLoggingFilter.java diff --git a/common/common-model/src/main/java/com/tacit/common/constant/CommonConstant.java b/common/common-model/src/main/java/com/tacit/common/constant/CommonConstant.java index 7c19665..1eb5d89 100644 --- a/common/common-model/src/main/java/com/tacit/common/constant/CommonConstant.java +++ b/common/common-model/src/main/java/com/tacit/common/constant/CommonConstant.java @@ -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:"; } \ No newline at end of file diff --git a/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java b/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java index 5af7df1..b51834c 100644 --- a/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java +++ b/tacit-admin/src/main/java/com/tacit/admin/AdminApplication.java @@ -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); diff --git a/tacit-admin/src/main/java/com/tacit/admin/config/SecurityConfig.java b/tacit-admin/src/main/java/com/tacit/admin/config/SecurityConfig.java index 4d698ef..b570025 100644 --- a/tacit-admin/src/main/java/com/tacit/admin/config/SecurityConfig.java +++ b/tacit-admin/src/main/java/com/tacit/admin/config/SecurityConfig.java @@ -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() ) diff --git a/tacit-admin/src/main/java/com/tacit/admin/controller/Resilience4jDemoController.java b/tacit-admin/src/main/java/com/tacit/admin/controller/Resilience4jDemoController.java new file mode 100644 index 0000000..0220873 --- /dev/null +++ b/tacit-admin/src/main/java/com/tacit/admin/controller/Resilience4jDemoController.java @@ -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 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 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 testCombined(@RequestParam(defaultValue = "0") int failCount) { + log.info("测试断路器和重试组合,failCount: {}", failCount); + String result = resilience4jDemoService.testCombined(failCount); + return ResponseEntity.ok(result); + } + + /** + * 重置断路器状态 + * @return 响应结果 + */ + @GetMapping("/reset") + public ResponseEntity resetCircuitBreaker() { + log.info("重置断路器状态"); + resilience4jDemoService.resetCircuitBreaker(); + return ResponseEntity.ok("断路器状态已重置"); + } +} diff --git a/tacit-admin/src/main/java/com/tacit/admin/service/Resilience4jDemoService.java b/tacit-admin/src/main/java/com/tacit/admin/service/Resilience4jDemoService.java new file mode 100644 index 0000000..0773bff --- /dev/null +++ b/tacit-admin/src/main/java/com/tacit/admin/service/Resilience4jDemoService.java @@ -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("所有计数器已重置"); + } +} diff --git a/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java b/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java index 41c5bdd..cba25a0 100644 --- a/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java +++ b/tacit-app-api/src/main/java/com/tacit/app/AppApiApplication.java @@ -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); diff --git a/tacit-gateway/pom.xml b/tacit-gateway/pom.xml index c60ed13..41bd088 100644 --- a/tacit-gateway/pom.xml +++ b/tacit-gateway/pom.xml @@ -33,6 +33,12 @@ ${project.parent.version} + + com.tacit + common-redis + ${project.parent.version} + + com.tacit common-core @@ -62,6 +68,12 @@ com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery + + com.tacit + common-redis + 1.0.0-SNAPSHOT + compile + diff --git a/tacit-gateway/src/main/java/com/tacit/gateway/GatewayApplication.java b/tacit-gateway/src/main/java/com/tacit/gateway/GatewayApplication.java index 96984be..6c13426 100644 --- a/tacit-gateway/src/main/java/com/tacit/gateway/GatewayApplication.java +++ b/tacit-gateway/src/main/java/com/tacit/gateway/GatewayApplication.java @@ -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); diff --git a/tacit-gateway/src/main/java/com/tacit/gateway/config/RedisConfig.java b/tacit-gateway/src/main/java/com/tacit/gateway/config/RedisConfig.java deleted file mode 100644 index c9c7906..0000000 --- a/tacit-gateway/src/main/java/com/tacit/gateway/config/RedisConfig.java +++ /dev/null @@ -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 redisTemplate(RedisConnectionFactory factory) { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(factory); - - // key 使用 String 序列化 - template.setKeySerializer(new StringRedisSerializer()); - template.setHashKeySerializer(new StringRedisSerializer()); - - // value 使用 JSON 序列化 - Jackson2JsonRedisSerializer 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; - } -} diff --git a/tacit-gateway/src/main/java/com/tacit/gateway/filter/JwtAuthenticationFilter.java b/tacit-gateway/src/main/java/com/tacit/gateway/filter/JwtAuthenticationFilter.java index 62994ed..4c004e0 100644 --- a/tacit-gateway/src/main/java/com/tacit/gateway/filter/JwtAuthenticationFilter.java +++ b/tacit-gateway/src/main/java/com/tacit/gateway/filter/JwtAuthenticationFilter.java @@ -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 { @Autowired - private RedisTemplate redisTemplate; + private RedisUtils redisUtils; // 不需要认证的路径 private static final List WHITE_LIST = List.of( "/auth/login", @@ -72,7 +72,7 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory { + + public RequestLoggingFilter() { + super(Config.class); + } + + @Override + public GatewayFilter apply(Config config) { + return new GatewayFilter() { + @Override + public Mono 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 { + // 可以添加配置属性 + } +} diff --git a/tacit-gateway/src/main/resources/tacit-gateway.yaml b/tacit-gateway/src/main/resources/tacit-gateway.yaml index f3a459b..5a89367 100644 --- a/tacit-gateway/src/main/resources/tacit-gateway.yaml +++ b/tacit-gateway/src/main/resources/tacit-gateway.yaml @@ -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: