diff --git a/pom.xml b/pom.xml index 813c843da..9cac58f58 100644 --- a/pom.xml +++ b/pom.xml @@ -403,7 +403,6 @@ ruoyi-auth ruoyi-gateway - ruoyi-gateway-mvc ruoyi-visual ruoyi-modules ruoyi-api diff --git a/ruoyi-gateway-mvc/Dockerfile b/ruoyi-gateway-mvc/Dockerfile deleted file mode 100644 index 3bea2d839..000000000 --- a/ruoyi-gateway-mvc/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/ -FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds -#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds -#FROM findepi/graalvm:java17-native - -LABEL maintainer="Lion Li" - -RUN mkdir -p /ruoyi/gateway/logs \ - /ruoyi/gateway/temp \ - /ruoyi/skywalking/agent - -WORKDIR /ruoyi/gateway - -ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" - -EXPOSE ${SERVER_PORT} - -ADD ./target/ruoyi-gateway-mvc.jar ./app.jar - -SHELL ["/bin/bash", "-c"] - -ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \ - #-Dskywalking.agent.service_name=ruoyi-gateway \ - #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \ - -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \ - -jar app.jar diff --git a/ruoyi-gateway-mvc/README.md b/ruoyi-gateway-mvc/README.md deleted file mode 100644 index c2f27b0d3..000000000 --- a/ruoyi-gateway-mvc/README.md +++ /dev/null @@ -1,7 +0,0 @@ -此服务为 spring-cloud-gateway 的 Servlet MVC 版本 - -建议在JDK21启动虚拟线程的基础上使用 在JDK17下使用性能比原版差很多 - -使用教程 将 ruoyi-gateway-mvc.yml 配置文件上传到 nacos 正常启动服务 与原先版本一样使用 - -注意 原版与此版只能选择一个使用 不允许两个同时使用 diff --git a/ruoyi-gateway-mvc/pom.xml b/ruoyi-gateway-mvc/pom.xml deleted file mode 100644 index bdbe01c84..000000000 --- a/ruoyi-gateway-mvc/pom.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - org.dromara - ruoyi-cloud-plus - ${revision} - - 4.0.0 - - ruoyi-gateway-mvc - - - ruoyi-gateway-mvc网关模块 - - - - - - - org.springframework.cloud - spring-cloud-starter-gateway-server-webmvc - - - - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-tomcat - org.springframework.boot - - - - - - org.springframework.boot - spring-boot-starter-jetty - ${spring-boot.version} - - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - - com.github.ben-manes.caffeine - caffeine - - - - org.dromara - ruoyi-common-nacos - - - - - org.springframework.boot - spring-boot-starter-actuator - - - - cn.hutool - hutool-http - - - - - cn.dev33 - sa-token-spring-boot4-starter - - - - org.dromara - ruoyi-common-satoken - - - - org.dromara - ruoyi-common-json - - - - - org.dromara - ruoyi-common-redis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${project.artifactId} - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - repackage - - - - - - - - diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/RuoYiGatewayMvcApplication.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/RuoYiGatewayMvcApplication.java deleted file mode 100644 index 4e988885f..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/RuoYiGatewayMvcApplication.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.dromara.gateway; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; - -/** - * 网关启动程序 - * - * @author Lion Li - */ -@SpringBootApplication -public class RuoYiGatewayMvcApplication { - - public static void main(String[] args) { - SpringApplication application = new SpringApplication(RuoYiGatewayMvcApplication.class); - application.setApplicationStartup(new BufferingApplicationStartup(2048)); - application.run(args); - System.out.println("(♥◠‿◠)ノ゙ MVC网关启动成功 ლ(´ڡ`ლ)゙ "); - } - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/GatewayConfig.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/GatewayConfig.java deleted file mode 100644 index 0eeaac4f7..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/GatewayConfig.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.dromara.gateway.config; - -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; -import org.springframework.web.servlet.LocaleResolver; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.util.List; -import java.util.Locale; -import org.springframework.core.Ordered; - -/** - * 网关配置 - * - * @author Lion Li - */ -@Configuration -public class GatewayConfig { - - @Bean - public LocaleResolver localeResolver() { - return new LocaleResolver() { - @Override - public Locale resolveLocale(HttpServletRequest request) { - String language = request.getHeader("content-language"); - Locale locale = Locale.getDefault(); - if (language == null || language.isBlank()) { - return locale; - } - String[] split = language.split("_"); - if (split.length == 2) { - return new Locale(split[0], split[1]); - } - return Locale.forLanguageTag(language.replace('_', '-')); - } - - @Override - public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { - // noop - } - }; - } - - @Bean - public FilterRegistrationBean corsFilterRegistrationBean() { - CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOriginPatterns(List.of("*")); - config.setAllowCredentials(true); - config.setAllowedHeaders(List.of( - "X-Requested-With", "Content-Language", "Content-Type", - "Authorization", "clientid", "credential", "X-XSRF-TOKEN", - "isToken", "token", "Admin-Token", "App-Token", "Encrypt-Key", "isEncrypt" - )); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")); - config.setExposedHeaders(List.of("*")); - config.setMaxAge(18000L); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - - FilterRegistrationBean registration = new FilterRegistrationBean<>(new CorsFilter(source)); - registration.setOrder(Ordered.HIGHEST_PRECEDENCE); - return registration; - } - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/UndertowConfig.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/UndertowConfig.java deleted file mode 100644 index e9503b511..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/UndertowConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -//package org.dromara.gateway.config; -// -//import io.undertow.server.DefaultByteBufferPool; -//import io.undertow.server.handlers.DisallowedMethodsHandler; -//import io.undertow.util.HttpString; -//import io.undertow.websockets.jsr.WebSocketDeploymentInfo; -//import org.dromara.common.core.utils.SpringUtils; -//import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; -//import org.springframework.boot.web.server.WebServerFactoryCustomizer; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.core.task.VirtualThreadTaskExecutor; -// -///** -// * Boot 4 迁移后默认改用 Jetty,这里暂时保留 Undertow 配置实现以便后续回切时参考。 -// */ -//@Configuration -//public class UndertowConfig implements WebServerFactoryCustomizer { -// -// @Override -// public void customize(UndertowServletWebServerFactory factory) { -// factory.addDeploymentInfoCustomizers(deploymentInfo -> { -// WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo(); -// webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024)); -// deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo); -// -// if (SpringUtils.isVirtual()) { -// VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-"); -// deploymentInfo.setExecutor(executor); -// deploymentInfo.setAsyncExecutor(executor); -// } -// -// deploymentInfo.addInitialHandlerChainWrapper(handler -> { -// HttpString[] disallowedHttpMethods = { -// HttpString.tryFromString("CONNECT"), -// HttpString.tryFromString("TRACE"), -// HttpString.tryFromString("TRACK") -// }; -// return new DisallowedMethodsHandler(handler, disallowedHttpMethods); -// }); -// }); -// } -// -//} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java deleted file mode 100644 index b44426226..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.dromara.gateway.config.properties; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.stereotype.Component; - -/** - * api解密属性配置类 - * - * @author wdhcr - */ -@Data -@Component -@RefreshScope -@ConfigurationProperties(prefix = "api-decrypt") -public class ApiDecryptProperties { - - /** - * 加密开关 - */ - private Boolean enabled; - - /** - * 头部标识 - */ - private String headerFlag; - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/CustomGatewayProperties.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/CustomGatewayProperties.java deleted file mode 100644 index 599603025..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/CustomGatewayProperties.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.dromara.gateway.config.properties; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.context.annotation.Configuration; - -/** - * 自定义gateway参数配置 - * - * @author Lion Li - */ -@Data -@Configuration -@RefreshScope -@ConfigurationProperties(prefix = "spring.cloud.gateway") -public class CustomGatewayProperties { - - /** - * 请求日志 - */ - private Boolean requestLog; - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/IgnoreWhiteProperties.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/IgnoreWhiteProperties.java deleted file mode 100644 index 50d4799e5..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/config/properties/IgnoreWhiteProperties.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.dromara.gateway.config.properties; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.context.annotation.Configuration; - -import java.util.ArrayList; -import java.util.List; - -/** - * 放行白名单配置 - * - * @author ruoyi - */ -@Data -@NoArgsConstructor -@Configuration -@RefreshScope -@ConfigurationProperties(prefix = "security.ignore") -public class IgnoreWhiteProperties { - - /** - * 放行白名单配置,网关不校验此处的白名单 - */ - private List whites = new ArrayList<>(); - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/AuthFilter.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/AuthFilter.java deleted file mode 100644 index c28f60de8..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/AuthFilter.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.dromara.gateway.filter; - -import cn.dev33.satoken.exception.NotLoginException; -import cn.dev33.satoken.filter.SaServletFilter; -import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil; -import cn.dev33.satoken.interceptor.SaInterceptor; -import cn.dev33.satoken.router.SaRouter; -import cn.dev33.satoken.stp.StpUtil; -import cn.dev33.satoken.util.SaResult; -import cn.dev33.satoken.util.SaTokenConsts; -import org.dromara.common.core.constant.HttpStatus; -import org.dromara.common.core.utils.ServletUtils; -import org.dromara.common.core.utils.SpringUtils; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.gateway.config.properties.IgnoreWhiteProperties; -import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -/** - * [Sa-Token 权限认证] 拦截器配置 - * - * @author Lion Li - */ -@Configuration -public class AuthFilter implements WebMvcConfigurer { - - private final IgnoreWhiteProperties ignoreWhite; - - public AuthFilter(IgnoreWhiteProperties ignoreWhite) { - this.ignoreWhite = ignoreWhite; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new SaInterceptor(handler -> SaRouter.match("/**") - .notMatch(ignoreWhite.getWhites()) - .check(() -> { - HttpServletRequest request = ServletUtils.getRequest(); - HttpServletResponse response = ServletUtils.getResponse(); - if (response != null) { - response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON); - } - - StpUtil.checkLogin(); - - String headerCid = request.getHeader(LoginHelper.CLIENT_KEY); - String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY); - Object extra = StpUtil.getExtra(LoginHelper.CLIENT_KEY); - String clientId = extra == null ? null : extra.toString(); - if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) { - throw NotLoginException.newInstance(StpUtil.getLoginType(), - "-100", "客户端ID与Token不匹配", - StpUtil.getTokenValue()); - } - }))) - .addPathPatterns("/**") - .excludePathPatterns("/favicon.ico", "/actuator", "/actuator/**", "/resource/sse" , "/error"); - } - - /** - * 为 actuator 健康检查接口配置 Basic Auth 鉴权过滤器。 - * - * @return Sa-Token Servlet 过滤器 - */ - @Bean - public SaServletFilter getSaServletFilter() { - String username = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.username"); - String password = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.userpassword"); - return new SaServletFilter() - .addInclude("/actuator", "/actuator/**") - .setAuth(obj -> { - SaHttpBasicUtil.check(username + ":" + password); - }) - .setError(e -> { - HttpServletResponse response = ServletUtils.getResponse(); - response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON); - return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED); - }); - } - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java deleted file mode 100644 index f1ff79c95..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.dromara.gateway.filter; - -import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.same.SaSameUtil; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.dromara.gateway.filter.support.MutableHttpServletRequest; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -/** - * 转发请求头过滤器: - * 1. 动态透传 X-Forwarded-Prefix - * 2. 转发内部 same-token - * - * @author Lion Li - */ -@Component -@Order(Ordered.HIGHEST_PRECEDENCE + 15) -public class ForwardAuthFilter extends OncePerRequestFilter { - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - return request.getRequestURI().startsWith("/actuator"); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - MutableHttpServletRequest newRequest = null; - - String forwardedPrefix = resolveForwardedPrefix(request); - if (forwardedPrefix != null) { - newRequest = getOrCreateMutableRequest(request, newRequest); - newRequest.putHeader("X-Forwarded-Prefix", forwardedPrefix); - } - - if (SaManager.getConfig().getCheckSameToken()) { - newRequest = getOrCreateMutableRequest(request, newRequest); - newRequest.putHeader(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()); - } - - filterChain.doFilter(newRequest == null ? request : newRequest, response); - } - - private String resolveForwardedPrefix(HttpServletRequest request) { - String[] pathSegments = StringUtils.tokenizeToStringArray(request.getRequestURI(), "/"); - if (pathSegments.length == 0) { - return null; - } - return "/" + pathSegments[0]; - } - - private MutableHttpServletRequest getOrCreateMutableRequest(HttpServletRequest request, - MutableHttpServletRequest currentRequest) { - return currentRequest != null ? currentRequest : new MutableHttpServletRequest(request); - } -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java deleted file mode 100644 index af4a01063..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.dromara.gateway.filter; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.constant.SystemConstants; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.json.utils.JsonUtils; -import org.dromara.gateway.config.properties.ApiDecryptProperties; -import org.dromara.gateway.config.properties.CustomGatewayProperties; -import org.dromara.gateway.filter.support.CachedBodyHttpServletRequest; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.UriComponentsBuilder; -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.node.ArrayNode; -import tools.jackson.databind.node.ObjectNode; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -/** - * 全局日志过滤器 - *

- * 用于打印请求执行参数与响应时间等等 - * - * @author Lion Li - */ -@Slf4j -@Component -@Order(Ordered.LOWEST_PRECEDENCE - 10) -public class GlobalLogFilter extends OncePerRequestFilter { - - private final CustomGatewayProperties customGatewayProperties; - private final ApiDecryptProperties apiDecryptProperties; - - public GlobalLogFilter(CustomGatewayProperties customGatewayProperties, ApiDecryptProperties apiDecryptProperties) { - this.customGatewayProperties = customGatewayProperties; - this.apiDecryptProperties = apiDecryptProperties; - } - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - return !Boolean.TRUE.equals(customGatewayProperties.getRequestLog()); - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - HttpServletRequest requestToUse = request; - if (isJsonRequest(request) && !(request instanceof CachedBodyHttpServletRequest)) { - requestToUse = new CachedBodyHttpServletRequest(request); - } - - String url = requestToUse.getMethod() + " " + requestToUse.getRequestURI(); - logRequest(requestToUse, url); - - long startTime = System.currentTimeMillis(); - try { - filterChain.doFilter(requestToUse, response); - } finally { - log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", url, System.currentTimeMillis() - startTime); - } - } - - private void logRequest(HttpServletRequest request, String url) { - if (isJsonRequest(request)) { - if (Boolean.TRUE.equals(apiDecryptProperties.getEnabled()) - && ObjectUtil.isNotNull(request.getHeader(apiDecryptProperties.getHeaderFlag()))) { - log.info("[PLUS]开始请求 => URL[{}],参数类型[encrypt]", url); - return; - } - String jsonParam = resolveBody(request); - if (StringUtils.isNotBlank(jsonParam)) { - jsonParam = removeSensitiveFields(jsonParam); - } - log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam); - return; - } - - MultiValueMap parameterMap = UriComponentsBuilder.newInstance() - .query(request.getQueryString()) - .build() - .getQueryParams(); - if (MapUtil.isNotEmpty(parameterMap)) { - LinkedMultiValueMap map = new LinkedMultiValueMap<>(parameterMap); - MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES); - log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, JsonUtils.toJsonString(map)); - } else { - log.info("[PLUS]开始请求 => URL[{}],无参数", url); - } - } - - private boolean isJsonRequest(HttpServletRequest request) { - return StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE); - } - - private String resolveBody(HttpServletRequest request) { - if (request instanceof CachedBodyHttpServletRequest cachedRequest) { - return cachedRequest.getCachedBodyAsString(); - } - return null; - } - - private String removeSensitiveFields(String jsonParam) { - try { - JsonMapper jsonMapper = JsonUtils.getJsonMapper(); - JsonNode rootNode = jsonMapper.readTree(jsonParam); - removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES); - return rootNode.toString(); - } catch (Exception ex) { - return jsonParam; - } - } - - private void removeSensitiveFields(JsonNode node, String[] excludeProperties) { - if (node == null) { - return; - } - if (node.isObject()) { - ObjectNode objectNode = (ObjectNode) node; - Set fieldsToRemove = new HashSet<>(); - objectNode.propertyNames().forEach(fieldName -> { - if (ArrayUtil.contains(excludeProperties, fieldName)) { - fieldsToRemove.add(fieldName); - } - }); - fieldsToRemove.forEach(objectNode::remove); - objectNode.values().forEach(child -> removeSensitiveFields(child, excludeProperties)); - } else if (node.isArray()) { - ArrayNode arrayNode = (ArrayNode) node; - for (JsonNode child : arrayNode) { - removeSensitiveFields(child, excludeProperties); - } - } - } - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java b/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java deleted file mode 100644 index d5f69514a..000000000 --- a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.dromara.gateway.handler; - -import cn.dev33.satoken.exception.NotLoginException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.constant.HttpStatus; -import org.dromara.common.core.domain.R; -import org.dromara.common.json.utils.JsonUtils; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - * 网关统一异常处理 - * - * @author Lion Li - */ -@Slf4j -@RestControllerAdvice -public class GatewayExceptionHandler { - - @ExceptionHandler(NotLoginException.class) - public void handleNotLogin(HttpServletRequest request, HttpServletResponse response, NotLoginException ex) throws IOException { - log.warn("[网关认证失败]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage()); - writeJson(response, HttpStatus.UNAUTHORIZED, ex.getMessage()); - } - - @ExceptionHandler(Throwable.class) - public void handle(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException { - int code; - String msg; - if ("NotFoundException".equals(ex.getClass().getSimpleName()) - || ex.getMessage().contains("Unable to find instance")) { - code = HttpStatus.NOT_FOUND; - msg = "服务未找到"; - } else if (ex instanceof ResponseStatusException responseStatusException) { - code = responseStatusException.getStatusCode().value(); - msg = responseStatusException.getMessage(); - } else { - code = HttpStatus.ERROR; - msg = "内部服务器错误"; - } - - log.error("[网关异常处理]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage(), ex); - writeJson(response, code, msg); - } - - private void writeJson(HttpServletResponse response, int code, String msg) throws IOException { - response.setStatus(code); - response.setCharacterEncoding(StandardCharsets.UTF_8.name()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.getWriter().write(JsonUtils.toJsonString(R.fail(code, msg))); - } -} diff --git a/ruoyi-gateway-mvc/src/main/resources/application.yml b/ruoyi-gateway-mvc/src/main/resources/application.yml deleted file mode 100644 index b081bfe2c..000000000 --- a/ruoyi-gateway-mvc/src/main/resources/application.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Tomcat -server: - port: 8080 - servlet: - context-path: / - -# Spring -spring: - application: - # 应用名称 - name: ruoyi-gateway-mvc - profiles: - # 环境配置 - active: @profiles.active@ - ---- # nacos 配置 -spring: - cloud: - nacos: - # nacos 服务地址 - server-addr: @nacos.server@ - username: @nacos.username@ - password: @nacos.password@ - discovery: - # 注册组 - group: DEFAULT_GROUP - namespace: public - config: - # 配置组 - group: DEFAULT_GROUP - namespace: public - config: - import: - - optional:nacos:application-common.yml - - optional:nacos:${spring.application.name}.yml diff --git a/ruoyi-gateway-mvc/src/main/resources/banner.txt b/ruoyi-gateway-mvc/src/main/resources/banner.txt deleted file mode 100644 index ceced29f4..000000000 --- a/ruoyi-gateway-mvc/src/main/resources/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -Spring Boot Version: ${spring-boot.version} -Spring Application Name: ${spring.application.name} - _ _ - (_) | | - _ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _ -| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | | -| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| | -|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, | - __/ | __/ | __/ | - |___/ |___/ |___/ \ No newline at end of file diff --git a/ruoyi-gateway-mvc/src/main/resources/logback-plus.xml b/ruoyi-gateway-mvc/src/main/resources/logback-plus.xml deleted file mode 100644 index 59cba6238..000000000 --- a/ruoyi-gateway-mvc/src/main/resources/logback-plus.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - ${console.log.pattern} - utf-8 - - - - - - ${log.path}/console.log - - - ${log.path}/console.%d{yyyy-MM-dd}.log - - 1 - - - ${log.pattern} - utf-8 - - - - INFO - - - - - - ${log.path}/info.log - - - - ${log.path}/info.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - - INFO - - ACCEPT - - DENY - - - - - ${log.path}/error.log - - - - ${log.path}/error.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - - - ERROR - - ACCEPT - - DENY - - - - - - - 0 - - 512 - - - - - - - - 0 - - 512 - - - - - - - - - - - - - - - - - diff --git a/ruoyi-gateway/Dockerfile b/ruoyi-gateway/Dockerfile index 576d9955a..3bea2d839 100644 --- a/ruoyi-gateway/Dockerfile +++ b/ruoyi-gateway/Dockerfile @@ -15,7 +15,7 @@ ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" EXPOSE ${SERVER_PORT} -ADD ./target/ruoyi-gateway.jar ./app.jar +ADD ./target/ruoyi-gateway-mvc.jar ./app.jar SHELL ["/bin/bash", "-c"] diff --git a/ruoyi-gateway/README.md b/ruoyi-gateway/README.md new file mode 100644 index 000000000..728ecc5df --- /dev/null +++ b/ruoyi-gateway/README.md @@ -0,0 +1,4 @@ +此服务为 spring-cloud-gateway 的 Servlet MVC 版本 + +建议在JDK21启动虚拟线程的基础上使用 在JDK17下使用性能比原版差很多 + diff --git a/ruoyi-gateway/pom.xml b/ruoyi-gateway/pom.xml index d1b11d6d2..635514dff 100644 --- a/ruoyi-gateway/pom.xml +++ b/ruoyi-gateway/pom.xml @@ -11,7 +11,7 @@ ruoyi-gateway - ruoyi-gateway网关模块 + ruoyi-gateway 网关模块 @@ -19,7 +19,25 @@ org.springframework.cloud - spring-cloud-starter-gateway-server-webflux + spring-cloud-starter-gateway-server-webmvc + + + + + org.springframework.boot + spring-boot-starter-web + + + spring-boot-starter-tomcat + org.springframework.boot + + + + + + org.springframework.boot + spring-boot-starter-jetty + ${spring-boot.version} @@ -48,10 +66,10 @@ hutool-http - + cn.dev33 - sa-token-reactor-spring-boot3-starter + sa-token-spring-boot4-starter diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java index ff64fd363..f4a381c82 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java @@ -7,14 +7,16 @@ import org.springframework.boot.context.metrics.buffering.BufferingApplicationSt /** * 网关启动程序 * - * @author ruoyi + * @author Lion Li */ @SpringBootApplication public class RuoYiGatewayApplication { + public static void main(String[] args) { SpringApplication application = new SpringApplication(RuoYiGatewayApplication.class); application.setApplicationStartup(new BufferingApplicationStartup(2048)); application.run(args); - System.out.println("(♥◠‿◠)ノ゙ 网关启动成功 ლ(´ڡ`ლ)゙ "); + System.out.println("(♥◠‿◠)ノ゙ MVC网关启动成功 ლ(´ڡ`ლ)゙ "); } + } diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java index 99211d9a2..0eeaac4f7 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java @@ -1,13 +1,71 @@ package org.dromara.gateway.config; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.LocaleResolver; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Locale; +import org.springframework.core.Ordered; /** - * 网关限流配置 + * 网关配置 * - * @author ruoyi + * @author Lion Li */ @Configuration public class GatewayConfig { + @Bean + public LocaleResolver localeResolver() { + return new LocaleResolver() { + @Override + public Locale resolveLocale(HttpServletRequest request) { + String language = request.getHeader("content-language"); + Locale locale = Locale.getDefault(); + if (language == null || language.isBlank()) { + return locale; + } + String[] split = language.split("_"); + if (split.length == 2) { + return new Locale(split[0], split[1]); + } + return Locale.forLanguageTag(language.replace('_', '-')); + } + + @Override + public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { + // noop + } + }; + } + + @Bean + public FilterRegistrationBean corsFilterRegistrationBean() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOriginPatterns(List.of("*")); + config.setAllowCredentials(true); + config.setAllowedHeaders(List.of( + "X-Requested-With", "Content-Language", "Content-Type", + "Authorization", "clientid", "credential", "X-XSRF-TOKEN", + "isToken", "token", "Admin-Token", "App-Token", "Encrypt-Key", "isEncrypt" + )); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")); + config.setExposedHeaders(List.of("*")); + config.setMaxAge(18000L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + + FilterRegistrationBean registration = new FilterRegistrationBean<>(new CorsFilter(source)); + registration.setOrder(Ordered.HIGHEST_PRECEDENCE); + return registration; + } + } diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java index c038bd4f6..b44426226 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Component; /** * api解密属性配置类 + * * @author wdhcr */ @Data diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java index 79bbff59f..c28f60de8 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java @@ -1,92 +1,87 @@ package org.dromara.gateway.filter; import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.filter.SaServletFilter; import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil; -import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; -import cn.dev33.satoken.reactor.filter.SaReactorFilter; +import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.router.SaRouter; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; import cn.dev33.satoken.util.SaTokenConsts; import org.dromara.common.core.constant.HttpStatus; +import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.gateway.config.properties.IgnoreWhiteProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.core.Ordered; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; /** - * [Sa-Token 权限认证] 拦截器 + * [Sa-Token 权限认证] 拦截器配置 * * @author Lion Li */ @Configuration -public class AuthFilter { +public class AuthFilter implements WebMvcConfigurer { - /** - * 注册 Sa-Token 全局过滤器 - */ - @Bean - public SaReactorFilter getSaReactorFilter(IgnoreWhiteProperties ignoreWhite) { - return new SaReactorFilter() - // 拦截地址 - .addInclude("/**") - .addExclude("/favicon.ico", "/actuator", "/actuator/**", "/resource/sse") - // 鉴权方法:每次访问进入 - .setAuth(obj -> { - // 登录校验 -- 拦截所有路由 - SaRouter.match("/**") - .notMatch(ignoreWhite.getWhites()) - .check(r -> { - ServerHttpRequest request = SaReactorSyncHolder.getExchange().getRequest(); - // 检查是否登录 是否有token - StpUtil.checkLogin(); + private final IgnoreWhiteProperties ignoreWhite; - // 检查 header 与 param 里的 clientid 与 token 里的是否一致 - String headerCid = request.getHeaders().getFirst(LoginHelper.CLIENT_KEY); - String paramCid = request.getQueryParams().getFirst(LoginHelper.CLIENT_KEY); - String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString(); - if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) { - // token 无效 - throw NotLoginException.newInstance(StpUtil.getLoginType(), - "-100", "客户端ID与Token不匹配", - StpUtil.getTokenValue()); - } + public AuthFilter(IgnoreWhiteProperties ignoreWhite) { + this.ignoreWhite = ignoreWhite; + } - // 有效率影响 用于临时测试 - // if (log.isDebugEnabled()) { - // log.debug("剩余有效时间: {}", StpUtil.getTokenTimeout()); - // log.debug("临时有效时间: {}", StpUtil.getTokenActivityTimeout()); - // } - }); - }).setError(e -> { - ServerHttpResponse response = SaReactorSyncHolder.getExchange().getResponse(); - response.getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON); - if (e instanceof NotLoginException) { - return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED); + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new SaInterceptor(handler -> SaRouter.match("/**") + .notMatch(ignoreWhite.getWhites()) + .check(() -> { + HttpServletRequest request = ServletUtils.getRequest(); + HttpServletResponse response = ServletUtils.getResponse(); + if (response != null) { + response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON); } - return SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED); - }); + + StpUtil.checkLogin(); + + String headerCid = request.getHeader(LoginHelper.CLIENT_KEY); + String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY); + Object extra = StpUtil.getExtra(LoginHelper.CLIENT_KEY); + String clientId = extra == null ? null : extra.toString(); + if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) { + throw NotLoginException.newInstance(StpUtil.getLoginType(), + "-100", "客户端ID与Token不匹配", + StpUtil.getTokenValue()); + } + }))) + .addPathPatterns("/**") + .excludePathPatterns("/favicon.ico", "/actuator", "/actuator/**", "/resource/sse" , "/error"); } /** - * 对 actuator 健康检查接口 做账号密码鉴权 + * 为 actuator 健康检查接口配置 Basic Auth 鉴权过滤器。 + * + * @return Sa-Token Servlet 过滤器 */ @Bean - public SaReactorFilter actuatorFilter() { + public SaServletFilter getSaServletFilter() { String username = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.username"); String password = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.userpassword"); - return new SaReactorFilter() + return new SaServletFilter() .addInclude("/actuator", "/actuator/**") .setAuth(obj -> { SaHttpBasicUtil.check(username + ":" + password); }) .setError(e -> { - ServerHttpResponse response = SaReactorSyncHolder.getExchange().getResponse(); - response.getHeaders().set(SaTokenConsts.CONTENT_TYPE_KEY, SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON); + HttpServletResponse response = ServletUtils.getResponse(); + response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON); return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED); }); } diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/BlackListUrlFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/BlackListUrlFilter.java deleted file mode 100644 index 177e93cce..000000000 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/BlackListUrlFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.dromara.gateway.filter; - -import org.dromara.gateway.utils.WebFluxUtils; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -/** - * 黑名单过滤器 - * - * @author ruoyi - */ -@Component -public class BlackListUrlFilter extends AbstractGatewayFilterFactory { - @Override - public GatewayFilter apply(Config config) { - return (exchange, chain) -> { - - String url = exchange.getRequest().getURI().getPath(); - if (config.matchBlacklist(url)) { - return WebFluxUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问"); - } - - return chain.filter(exchange); - }; - } - - public BlackListUrlFilter() { - super(Config.class); - } - - public static class Config { - private List blacklistUrl; - - private List blacklistUrlPattern = new ArrayList<>(); - - public boolean matchBlacklist(String url) { - return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream().anyMatch(p -> p.matcher(url).find()); - } - - public List getBlacklistUrl() { - return blacklistUrl; - } - - public void setBlacklistUrl(List blacklistUrl) { - this.blacklistUrl = blacklistUrl; - this.blacklistUrlPattern.clear(); - this.blacklistUrl.forEach(url -> { - this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"), Pattern.CASE_INSENSITIVE)); - }); - } - } - -} diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java index fe0348b82..f1ff79c95 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java @@ -2,40 +2,64 @@ package org.dromara.gateway.filter; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.same.SaSameUtil; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.gateway.filter.support.MutableHttpServletRequest; import org.springframework.core.Ordered; -import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; /** - * 转发认证过滤器(内部服务外网隔离) + * 转发请求头过滤器: + * 1. 动态透传 X-Forwarded-Prefix + * 2. 转发内部 same-token * * @author Lion Li */ @Component -public class ForwardAuthFilter implements GlobalFilter, Ordered { +@Order(Ordered.HIGHEST_PRECEDENCE + 15) +public class ForwardAuthFilter extends OncePerRequestFilter { + @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // 未开启配置则直接跳过 - if (!SaManager.getConfig().getCheckSameToken()) { - return chain.filter(exchange); - } - ServerHttpRequest newRequest = exchange - .getRequest() - .mutate() - // 为请求追加 Same-Token 参数 - .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()) - .build(); - ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); - return chain.filter(newExchange); + protected boolean shouldNotFilter(HttpServletRequest request) { + return request.getRequestURI().startsWith("/actuator"); } @Override - public int getOrder() { - return -100; + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + MutableHttpServletRequest newRequest = null; + + String forwardedPrefix = resolveForwardedPrefix(request); + if (forwardedPrefix != null) { + newRequest = getOrCreateMutableRequest(request, newRequest); + newRequest.putHeader("X-Forwarded-Prefix", forwardedPrefix); + } + + if (SaManager.getConfig().getCheckSameToken()) { + newRequest = getOrCreateMutableRequest(request, newRequest); + newRequest.putHeader(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()); + } + + filterChain.doFilter(newRequest == null ? request : newRequest, response); + } + + private String resolveForwardedPrefix(HttpServletRequest request) { + String[] pathSegments = StringUtils.tokenizeToStringArray(request.getRequestURI(), "/"); + if (pathSegments.length == 0) { + return null; + } + return "/" + pathSegments[0]; + } + + private MutableHttpServletRequest getOrCreateMutableRequest(HttpServletRequest request, + MutableHttpServletRequest currentRequest) { + return currentRequest != null ? currentRequest : new MutableHttpServletRequest(request); } } - diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java index 4bc4dd7eb..af4a01063 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java @@ -3,29 +3,31 @@ package org.dromara.gateway.filter; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; -import lombok.SneakyThrows; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.json.utils.JsonUtils; import org.dromara.gateway.config.properties.ApiDecryptProperties; import org.dromara.gateway.config.properties.CustomGatewayProperties; -import org.dromara.gateway.utils.WebFluxUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.dromara.gateway.filter.support.CachedBodyHttpServletRequest; import org.springframework.core.Ordered; -import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.UriComponentsBuilder; import tools.jackson.databind.JsonNode; import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.ObjectNode; +import java.io.IOException; import java.util.HashSet; import java.util.Set; @@ -38,60 +40,89 @@ import java.util.Set; */ @Slf4j @Component -public class GlobalLogFilter implements GlobalFilter, Ordered { +@Order(Ordered.LOWEST_PRECEDENCE - 10) +public class GlobalLogFilter extends OncePerRequestFilter { - @Autowired - private CustomGatewayProperties customGatewayProperties; - @Autowired - private ApiDecryptProperties apiDecryptProperties; + private final CustomGatewayProperties customGatewayProperties; + private final ApiDecryptProperties apiDecryptProperties; - private static final String START_TIME = "startTime"; + public GlobalLogFilter(CustomGatewayProperties customGatewayProperties, ApiDecryptProperties apiDecryptProperties) { + this.customGatewayProperties = customGatewayProperties; + this.apiDecryptProperties = apiDecryptProperties; + } - @SneakyThrows @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (!customGatewayProperties.getRequestLog()) { - return chain.filter(exchange); - } - ServerHttpRequest request = exchange.getRequest(); - String path = WebFluxUtils.getOriginalRequestUrl(exchange); - String url = request.getMethod().name() + " " + path; + protected boolean shouldNotFilter(HttpServletRequest request) { + return !Boolean.TRUE.equals(customGatewayProperties.getRequestLog()); + } - // 打印请求参数 - if (WebFluxUtils.isJsonRequest(exchange)) { - if (apiDecryptProperties.getEnabled() - && ObjectUtil.isNotNull(request.getHeaders().getFirst(apiDecryptProperties.getHeaderFlag()))) { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + HttpServletRequest requestToUse = request; + if (isJsonRequest(request) && !(request instanceof CachedBodyHttpServletRequest)) { + requestToUse = new CachedBodyHttpServletRequest(request); + } + + String url = requestToUse.getMethod() + " " + requestToUse.getRequestURI(); + logRequest(requestToUse, url); + + long startTime = System.currentTimeMillis(); + try { + filterChain.doFilter(requestToUse, response); + } finally { + log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", url, System.currentTimeMillis() - startTime); + } + } + + private void logRequest(HttpServletRequest request, String url) { + if (isJsonRequest(request)) { + if (Boolean.TRUE.equals(apiDecryptProperties.getEnabled()) + && ObjectUtil.isNotNull(request.getHeader(apiDecryptProperties.getHeaderFlag()))) { log.info("[PLUS]开始请求 => URL[{}],参数类型[encrypt]", url); - } else { - String jsonParam = WebFluxUtils.resolveBodyFromCacheRequest(exchange); - if (StringUtils.isNotBlank(jsonParam)) { - JsonMapper jsonMapper = JsonUtils.getJsonMapper(); - JsonNode rootNode = jsonMapper.readTree(jsonParam); - removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES); - jsonParam = rootNode.toString(); - } - log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam); + return; } - } else { - MultiValueMap parameterMap = request.getQueryParams(); - if (MapUtil.isNotEmpty(parameterMap)) { - LinkedMultiValueMap map = new LinkedMultiValueMap<>(parameterMap); - MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES); - String parameters = JsonUtils.toJsonString(map); - log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters); - } else { - log.info("[PLUS]开始请求 => URL[{}],无参数", url); + String jsonParam = resolveBody(request); + if (StringUtils.isNotBlank(jsonParam)) { + jsonParam = removeSensitiveFields(jsonParam); } + log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam); + return; } - exchange.getAttributes().put(START_TIME, System.currentTimeMillis()); - return chain.filter(exchange).then(Mono.fromRunnable(() -> { - Long startTime = exchange.getAttribute(START_TIME); - if (startTime != null) { - long executeTime = (System.currentTimeMillis() - startTime); - log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", url, executeTime); - } - })); + MultiValueMap parameterMap = UriComponentsBuilder.newInstance() + .query(request.getQueryString()) + .build() + .getQueryParams(); + if (MapUtil.isNotEmpty(parameterMap)) { + LinkedMultiValueMap map = new LinkedMultiValueMap<>(parameterMap); + MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES); + log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, JsonUtils.toJsonString(map)); + } else { + log.info("[PLUS]开始请求 => URL[{}],无参数", url); + } + } + + private boolean isJsonRequest(HttpServletRequest request) { + return StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE); + } + + private String resolveBody(HttpServletRequest request) { + if (request instanceof CachedBodyHttpServletRequest cachedRequest) { + return cachedRequest.getCachedBodyAsString(); + } + return null; + } + + private String removeSensitiveFields(String jsonParam) { + try { + JsonMapper jsonMapper = JsonUtils.getJsonMapper(); + JsonNode rootNode = jsonMapper.readTree(jsonParam); + removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES); + return rootNode.toString(); + } catch (Exception ex) { + return jsonParam; + } } private void removeSensitiveFields(JsonNode node, String[] excludeProperties) { @@ -100,7 +131,6 @@ public class GlobalLogFilter implements GlobalFilter, Ordered { } if (node.isObject()) { ObjectNode objectNode = (ObjectNode) node; - // 收集要删除的字段名(避免 ConcurrentModification) Set fieldsToRemove = new HashSet<>(); objectNode.propertyNames().forEach(fieldName -> { if (ArrayUtil.contains(excludeProperties, fieldName)) { @@ -108,7 +138,6 @@ public class GlobalLogFilter implements GlobalFilter, Ordered { } }); fieldsToRemove.forEach(objectNode::remove); - // 递归处理子节点 objectNode.values().forEach(child -> removeSensitiveFields(child, excludeProperties)); } else if (node.isArray()) { ArrayNode arrayNode = (ArrayNode) node; @@ -118,12 +147,4 @@ public class GlobalLogFilter implements GlobalFilter, Ordered { } } - @Override - public int getOrder() { - // 日志处理器在负载均衡器之后执行 负载均衡器会导致线程切换 无法获取上下文内容 - // 如需在日志内操作线程上下文 例如获取登录用户数据等 可以打开下方注释代码 - // return ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER - 1; - return Ordered.LOWEST_PRECEDENCE; - } - } diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebCacheRequestFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebCacheRequestFilter.java deleted file mode 100644 index 6f2952371..000000000 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebCacheRequestFilter.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.dromara.gateway.filter; - -import org.dromara.gateway.utils.WebFluxUtils; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.Ordered; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - -/** - * 缓存获取body请求数据(解决流不能重复读取问题) - * - * @author Lion Li - */ -@Component -public class WebCacheRequestFilter implements WebFilter, Ordered { - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - // 只缓存json类型请求 - if (!WebFluxUtils.isJsonRequest(exchange)) { - return chain.filter(exchange); - } - return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> { - if (serverHttpRequest == exchange.getRequest()) { - return chain.filter(exchange); - } - return chain.filter(exchange.mutate().request(serverHttpRequest).build()); - }); - } - - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE + 1; - } -} diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebCorsFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebCorsFilter.java deleted file mode 100644 index 417e2bc32..000000000 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebCorsFilter.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.dromara.gateway.filter; - -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -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.cors.reactive.CorsUtils; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - - -/** - * 跨域配置 - * - * @author Lion Li - */ -@Component -public class WebCorsFilter implements WebFilter, Ordered { - - /** - * 这里为支持的请求头,如果有自定义的header字段请自己添加 - */ - private static final String ALLOWED_HEADERS = - "X-Requested-With, Content-Language, Content-Type, " + - "Authorization, clientid, credential, X-XSRF-TOKEN, " + - "isToken, token, Admin-Token, App-Token, Encrypt-Key, isEncrypt"; - - /** - * 允许的请求方法 - */ - private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD"; - - /** - * 允许的请求来源,使用 * 表示允许任何来源 - */ - private static final String ALLOWED_ORIGIN = "*"; - - /** - * 允许前端访问的响应头,使用 * 表示允许任何响应头 - */ - private static final String ALLOWED_EXPOSE = "*"; - - /** - * 预检请求的缓存时间,单位为秒(此处设置为 5 小时) - */ - private static final String MAX_AGE = "18000L"; - - /** - * 实现跨域配置的 Web 过滤器 - * - * @param exchange ServerWebExchange 对象,表示一次 Web 交换 - * @param chain WebFilterChain 对象,表示一组 Web 过滤器链 - * @return Mono 表示异步的过滤器链处理结果 - */ - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - // 判断请求是否为跨域请求 - if (CorsUtils.isCorsRequest(request)) { - ServerHttpResponse response = exchange.getResponse(); - HttpHeaders headers = response.getHeaders(); - headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS); - headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS); - headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN); - headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE); - headers.add("Access-Control-Max-Age", MAX_AGE); - headers.add("Access-Control-Allow-Credentials", "true"); - // 处理预检请求的 OPTIONS 方法,直接返回成功状态码 - if (request.getMethod() == HttpMethod.OPTIONS) { - response.setStatusCode(HttpStatus.OK); - return Mono.empty(); - } - } - return chain.filter(exchange); - } - - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; - } -} diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebI18nFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebI18nFilter.java deleted file mode 100644 index 38a2eedd1..000000000 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/WebI18nFilter.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.dromara.gateway.filter; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.context.i18n.SimpleLocaleContext; -import org.springframework.core.Ordered; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - -import java.util.Locale; - -/** - * 全局国际化处理 - * - * @author Lion Li - */ -@Slf4j -@Component -public class WebI18nFilter implements WebFilter, Ordered { - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - String language = exchange.getRequest().getHeaders().getFirst("content-language"); - Locale locale = Locale.getDefault(); - if (language != null && language.length() > 0) { - String[] split = language.split("_"); - locale = new Locale(split[0], split[1]); - } - LocaleContextHolder.setLocaleContext(new SimpleLocaleContext(locale), true); - return chain.filter(exchange); - } - - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; - } - -} diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/support/CachedBodyHttpServletRequest.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/support/CachedBodyHttpServletRequest.java similarity index 100% rename from ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/support/CachedBodyHttpServletRequest.java rename to ruoyi-gateway/src/main/java/org/dromara/gateway/filter/support/CachedBodyHttpServletRequest.java diff --git a/ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/support/MutableHttpServletRequest.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/support/MutableHttpServletRequest.java similarity index 100% rename from ruoyi-gateway-mvc/src/main/java/org/dromara/gateway/filter/support/MutableHttpServletRequest.java rename to ruoyi-gateway/src/main/java/org/dromara/gateway/filter/support/MutableHttpServletRequest.java diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java index fa6f79278..d5f69514a 100644 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java +++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java @@ -1,46 +1,59 @@ package org.dromara.gateway.handler; -import org.dromara.gateway.utils.WebFluxUtils; +import cn.dev33.satoken.exception.NotLoginException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.webflux.error.ErrorWebExceptionHandler; -import org.springframework.cloud.gateway.support.NotFoundException; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.http.server.reactive.ServerHttpResponse; +import org.dromara.common.core.constant.HttpStatus; +import org.dromara.common.core.domain.R; +import org.dromara.common.json.utils.JsonUtils; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * 网关统一异常处理 * - * @author ruoyi + * @author Lion Li */ @Slf4j -@Order(-1) -@Configuration -public class GatewayExceptionHandler implements ErrorWebExceptionHandler { +@RestControllerAdvice +public class GatewayExceptionHandler { - @Override - public Mono handle(ServerWebExchange exchange, Throwable ex) { - ServerHttpResponse response = exchange.getResponse(); - - if (exchange.getResponse().isCommitted()) { - return Mono.error(ex); - } + @ExceptionHandler(NotLoginException.class) + public void handleNotLogin(HttpServletRequest request, HttpServletResponse response, NotLoginException ex) throws IOException { + log.warn("[网关认证失败]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage()); + writeJson(response, HttpStatus.UNAUTHORIZED, ex.getMessage()); + } + @ExceptionHandler(Throwable.class) + public void handle(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException { + int code; String msg; - - if (ex instanceof NotFoundException) { + if ("NotFoundException".equals(ex.getClass().getSimpleName()) + || ex.getMessage().contains("Unable to find instance")) { + code = HttpStatus.NOT_FOUND; msg = "服务未找到"; } else if (ex instanceof ResponseStatusException responseStatusException) { + code = responseStatusException.getStatusCode().value(); msg = responseStatusException.getMessage(); } else { + code = HttpStatus.ERROR; msg = "内部服务器错误"; } - log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); + log.error("[网关异常处理]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage(), ex); + writeJson(response, code, msg); + } - return WebFluxUtils.webFluxResponseWriter(response, msg); + private void writeJson(HttpServletResponse response, int code, String msg) throws IOException { + response.setStatus(code); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.getWriter().write(JsonUtils.toJsonString(R.fail(code, msg))); } } diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/utils/WebFluxUtils.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/utils/WebFluxUtils.java deleted file mode 100644 index eeeec195f..000000000 --- a/ruoyi-gateway/src/main/java/org/dromara/gateway/utils/WebFluxUtils.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.dromara.gateway.utils; - -import cn.hutool.core.util.ObjectUtil; -import org.dromara.common.core.domain.R; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.json.utils.JsonUtils; -import org.dromara.gateway.filter.WebCacheRequestFilter; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; -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.LinkedHashSet; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR; - -/** - * WebFlux 工具类 - * - * @author Lion Li - */ -public class WebFluxUtils { - - /** - * 获取原请求路径 - */ - public static String getOriginalRequestUrl(ServerWebExchange exchange) { - ServerHttpRequest request = exchange.getRequest(); - LinkedHashSet uris = exchange.getAttributeOrDefault(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, new LinkedHashSet<>()); - URI requestUri = uris.stream().findFirst().orElse(request.getURI()); - return UriComponentsBuilder.fromPath(requestUri.getRawPath()).build().toUriString(); - } - - /** - * 是否是Json请求 - * - * @param exchange HTTP请求 - */ - public static boolean isJsonRequest(ServerWebExchange exchange) { - String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); - return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); - } - - /** - * 读取request内的body - * - * 注意一个request只能读取一次 读取之后需要重新包装 - */ - public static String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) { - // 获取请求体 - Flux body = serverHttpRequest.getBody(); - AtomicReference bodyRef = new AtomicReference<>(); - body.subscribe(buffer -> { - try (DataBuffer.ByteBufferIterator iterator = buffer.readableByteBuffers()) { - CharBuffer charBuffer = StandardCharsets.UTF_8.decode(iterator.next()); - DataBufferUtils.release(buffer); - bodyRef.set(charBuffer.toString()); - } - }); - return bodyRef.get(); - } - - /** - * 从缓存中读取request内的body - * - * 注意要求经过 {@link ServerWebExchangeUtils#cacheRequestBody(ServerWebExchange, Function)} 此方法创建缓存 - * 框架内已经使用 {@link WebCacheRequestFilter} 全局创建了body缓存 - * - * @return body - */ - public static String resolveBodyFromCacheRequest(ServerWebExchange exchange) { - Object obj = exchange.getAttributes().get(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR); - if (ObjectUtil.isNull(obj)) { - return null; - } - DataBuffer buffer = (DataBuffer) obj; - try (DataBuffer.ByteBufferIterator iterator = buffer.readableByteBuffers()) { - StringBuilder sb = new StringBuilder(); - iterator.forEachRemaining(e -> { - sb.append(StandardCharsets.UTF_8.decode(e)); - }); - return sb.toString(); - } - } - - /** - * 设置webflux模型响应 - * - * @param response ServerHttpResponse - * @param value 响应内容 - * @return Mono - */ - public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value) { - return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL); - } - - /** - * 设置webflux模型响应 - * - * @param response ServerHttpResponse - * @param code 响应状态码 - * @param value 响应内容 - * @return Mono - */ - public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value, int code) { - return webFluxResponseWriter(response, HttpStatus.OK, value, code); - } - - /** - * 设置webflux模型响应 - * - * @param response ServerHttpResponse - * @param status http状态码 - * @param code 响应状态码 - * @param value 响应内容 - * @return Mono - */ - public static Mono webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) { - return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code); - } - - /** - * 设置webflux模型响应 - * - * @param response ServerHttpResponse - * @param contentType content-type - * @param status http状态码 - * @param code 响应状态码 - * @param value 响应内容 - * @return Mono - */ - public static Mono webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) { - response.setStatusCode(status); - response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType); - R result = R.fail(code, value.toString()); - DataBuffer dataBuffer = response.bufferFactory().wrap(JsonUtils.toJsonString(result).getBytes()); - return response.writeWith(Mono.just(dataBuffer)); - } -} diff --git a/ruoyi-gateway/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java b/ruoyi-gateway/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java deleted file mode 100644 index 45bf5e016..000000000 --- a/ruoyi-gateway/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.gateway.filter.factory; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -import java.util.Arrays; -import java.util.List; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; - -/** - * This filter removes the first part of the path, known as the prefix, from the request - * before sending it downstream. - * - * @author Ryan Baxter - */ -public class StripPrefixGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Parts key. - */ - public static final String PARTS_KEY = "parts"; - - public StripPrefixGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(PARTS_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - addOriginalRequestUrl(exchange, request.getURI()); - String path = request.getURI().getRawPath(); - String[] originalParts = StringUtils.tokenizeToStringArray(path, "/"); - - // all new paths start with / - StringBuilder newPath = new StringBuilder("/"); - for (int i = 0; i < originalParts.length; i++) { - if (i >= config.getParts()) { - // only append slash if this is the second part or greater - if (newPath.length() > 1) { - newPath.append('/'); - } - newPath.append(originalParts[i]); - } - } - if (newPath.length() > 1 && path.endsWith("/")) { - newPath.append('/'); - } - // 增加doc前缀传递 - String prefix = "/" + originalParts[config.getParts() - 1]; - - ServerHttpRequest newRequest = request.mutate() - .header("X-Forwarded-Prefix", prefix) - .path(newPath.toString()) - .build(); - - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI()); - - return chain.filter(exchange.mutate().request(newRequest).build()); - } - - @Override - public String toString() { - return filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()) - .toString(); - } - }; - } - - public static class Config { - - private int parts = 1; - - public int getParts() { - return parts; - } - - public void setParts(int parts) { - this.parts = parts; - } - - } - -} diff --git a/ruoyi-gateway/src/main/resources/banner.txt b/ruoyi-gateway/src/main/resources/banner.txt index fc8e11848..ceced29f4 100644 --- a/ruoyi-gateway/src/main/resources/banner.txt +++ b/ruoyi-gateway/src/main/resources/banner.txt @@ -1,10 +1,10 @@ -Spring Boot Version: ${spring-boot.version} -Spring Application Name: ${spring.application.name} - _ _ - (_) | | - _ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _ -| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | | -| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| | -|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, | - __/ | __/ | __/ | +Spring Boot Version: ${spring-boot.version} +Spring Application Name: ${spring.application.name} + _ _ + (_) | | + _ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _ +| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | | +| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| | +|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, | + __/ | __/ | __/ | |___/ |___/ |___/ \ No newline at end of file diff --git a/script/config/nacos/ruoyi-gateway-mvc.yml b/script/config/nacos/ruoyi-gateway-mvc.yml deleted file mode 100644 index 3a9bc5f30..000000000 --- a/script/config/nacos/ruoyi-gateway-mvc.yml +++ /dev/null @@ -1,84 +0,0 @@ -# 安全配置 -security: - # 不校验白名单 - ignore: - whites: - - /auth/code - - /auth/logout - - /auth/login - - /auth/binding/* - - /auth/register - - /auth/tenant/list - - /resource/sms/code - - /resource/sse/close - - /*/v3/api-docs - - /*/error - - /csrf - - /warm-flow-ui/** - -spring: - cloud: - gateway: - # 打印请求日志(自定义) - requestLog: true - server: - webmvc: - discovery: - locator: - lowerCaseServiceId: true - enabled: true - routes: - # 认证中心 - - id: ruoyi-auth - uri: lb://ruoyi-auth - predicates: - - Path=/auth/** - filters: - - StripPrefix=1 - # 代码生成 - - id: ruoyi-gen - uri: lb://ruoyi-gen - predicates: - - Path=/tool/** - filters: - - StripPrefix=1 - # 系统模块 - - id: ruoyi-system - uri: lb://ruoyi-system - predicates: - - Path=/system/**,/monitor/** - filters: - - StripPrefix=1 - # 资源服务 - - id: ruoyi-resource - uri: lb://ruoyi-resource - predicates: - - Path=/resource/** - filters: - - StripPrefix=1 - # workflow服务 - - id: ruoyi-workflow - uri: lb://ruoyi-workflow - predicates: - - Path=/workflow/** - filters: - - StripPrefix=1 - # warm-flow服务 - - id: warm-flow - uri: lb://ruoyi-workflow - predicates: - - Path=/warm-flow-ui/**,/warm-flow/** - # 演示服务 - - id: ruoyi-demo - uri: lb://ruoyi-demo - predicates: - - Path=/demo/** - filters: - - StripPrefix=1 - # MQ演示服务 - - id: ruoyi-test-mq - uri: lb://ruoyi-test-mq - predicates: - - Path=/test-mq/** - filters: - - StripPrefix=1 diff --git a/script/config/nacos/ruoyi-gateway.yml b/script/config/nacos/ruoyi-gateway.yml index 3ca5d36b6..3a9bc5f30 100644 --- a/script/config/nacos/ruoyi-gateway.yml +++ b/script/config/nacos/ruoyi-gateway.yml @@ -18,12 +18,11 @@ security: spring: cloud: - # 网关配置 gateway: # 打印请求日志(自定义) requestLog: true server: - webflux: + webmvc: discovery: locator: lowerCaseServiceId: true