mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-19 01:48:47 +08:00
saas gateway
This commit is contained in:
parent
f71aa6316f
commit
345019b4f9
@ -34,6 +34,7 @@
|
|||||||
<module>ruoyi-common-tenant</module>
|
<module>ruoyi-common-tenant</module>
|
||||||
<module>ruoyi-common-websocket</module>
|
<module>ruoyi-common-websocket</module>
|
||||||
<module>ruoyi-common-sse</module>
|
<module>ruoyi-common-sse</module>
|
||||||
|
<module>ruoyi-common-nacos</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
|
|||||||
@ -179,6 +179,13 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SSE模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-nacos</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
43
ruoyi-common/ruoyi-common-nacos/pom.xml
Normal file
43
ruoyi-common/ruoyi-common-nacos/pom.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>ruoyi-common-nacos</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
ruoyi-common-nacos 配置中心
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- SpringCloud Alibaba Nacos -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SpringCloud Alibaba Nacos Config -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>logback-adapter</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba.nacos</groupId>
|
||||||
|
<artifactId>nacos-common</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -24,11 +24,11 @@
|
|||||||
<artifactId>ruoyi-common-redis</artifactId>
|
<artifactId>ruoyi-common-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
<!-- <!– Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ –>-->
|
||||||
<dependency>
|
<!-- <dependency>-->
|
||||||
<groupId>cn.dev33</groupId>
|
<!-- <groupId>cn.dev33</groupId>-->
|
||||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
<!-- <artifactId>sa-token-spring-boot3-starter</artifactId>-->
|
||||||
</dependency>
|
<!-- </dependency>-->
|
||||||
|
|
||||||
<!-- Sa-Token 整合 jwt -->
|
<!-- Sa-Token 整合 jwt -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>ruoyi-sso-server</module>
|
<module>ruoyi-sso-server</module>
|
||||||
<module>ruoyi-h5</module>
|
<module>ruoyi-h5</module>
|
||||||
|
<module>ruoyi-gateway</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<artifactId>ruoyi-site</artifactId>
|
<artifactId>ruoyi-site</artifactId>
|
||||||
|
|||||||
24
ruoyi-site/ruoyi-gateway/Dockerfile
Normal file
24
ruoyi-site/ruoyi-gateway/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
||||||
|
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
|
||||||
|
#FROM bellsoft/liberica-openjdk-debian:21.0.3-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.jar ./app.jar
|
||||||
|
|
||||||
|
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
|
||||||
106
ruoyi-site/ruoyi-gateway/pom.xml
Normal file
106
ruoyi-site/ruoyi-gateway/pom.xml
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-site</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>ruoyi-gateway</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
ruoyi-gateway网关模块
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- SpringCloud Gateway -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-nacos</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SpringBoot Actuator -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SpringCloud Loadbalancer -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-http</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:http://sa-token.dev33.cn/ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
|
||||||
|
<version>${satoken.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-satoken</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- RuoYi Common Redis-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-tenant</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package org.dromara.gateway;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
|
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关启动程序
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
|
||||||
|
public class RuoYiGatewayApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 标记 sentinel 类型为 网关
|
||||||
|
SpringApplication application = new SpringApplication(RuoYiGatewayApplication.class);
|
||||||
|
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
||||||
|
application.run(args);
|
||||||
|
System.out.println("(♥◠‿◠)ノ゙ 网关启动成功 ლ(´ڡ`ლ)゙ ");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package org.dromara.gateway.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关限流配置
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class GatewayConfig {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package org.dromara.gateway.config.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
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<String> whites = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
package org.dromara.gateway.filter;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
|
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.router.SaRouter;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
import org.dromara.common.core.constant.HttpStatus;
|
||||||
|
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.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Sa-Token 权限认证] 拦截器
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class AuthFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册 Sa-Token 全局过滤器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public SaReactorFilter getSaReactorFilter(IgnoreWhiteProperties ignoreWhite) {
|
||||||
|
return new SaReactorFilter()
|
||||||
|
// 拦截地址
|
||||||
|
.addInclude("/**")
|
||||||
|
.addExclude("/favicon.ico", "/actuator", "/actuator/**")
|
||||||
|
// 鉴权方法:每次访问进入
|
||||||
|
.setAuth(obj -> {
|
||||||
|
// 登录校验 -- 拦截所有路由
|
||||||
|
SaRouter.match("/**")
|
||||||
|
.notMatch(ignoreWhite.getWhites())
|
||||||
|
.check(r -> {
|
||||||
|
ServerHttpRequest request = SaReactorSyncHolder.getContext().getRequest();
|
||||||
|
// 检查是否登录 是否有token
|
||||||
|
try {
|
||||||
|
StpUtil.checkLogin();
|
||||||
|
} catch (NotLoginException e) {
|
||||||
|
if (request.getURI().getPath().contains("sse")) {
|
||||||
|
throw new SseException(e.getMessage(), e.getCode());
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有效率影响 用于临时测试
|
||||||
|
// if (log.isDebugEnabled()) {
|
||||||
|
// log.debug("剩余有效时间: {}", StpUtil.getTokenTimeout());
|
||||||
|
// log.debug("临时有效时间: {}", StpUtil.getTokenActivityTimeout());
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
}).setError(e -> {
|
||||||
|
if (e instanceof NotLoginException) {
|
||||||
|
return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
return SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对 actuator 健康检查接口 做账号密码鉴权
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public SaReactorFilter actuatorFilter() {
|
||||||
|
String username = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.username");
|
||||||
|
String password = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.userpassword");
|
||||||
|
return new SaReactorFilter()
|
||||||
|
.addInclude("/actuator", "/actuator/**")
|
||||||
|
.setAuth(obj -> {
|
||||||
|
SaHttpBasicUtil.check(username + ":" + password);
|
||||||
|
})
|
||||||
|
.setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
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<BlackListUrlFilter.Config> {
|
||||||
|
@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<String> blacklistUrl;
|
||||||
|
|
||||||
|
private List<Pattern> blacklistUrlPattern = new ArrayList<>();
|
||||||
|
|
||||||
|
public boolean matchBlacklist(String url) {
|
||||||
|
return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream().anyMatch(p -> p.matcher(url).find());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getBlacklistUrl() {
|
||||||
|
return blacklistUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlacklistUrl(List<String> blacklistUrl) {
|
||||||
|
this.blacklistUrl = blacklistUrl;
|
||||||
|
this.blacklistUrlPattern.clear();
|
||||||
|
this.blacklistUrl.forEach(url -> {
|
||||||
|
this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"), Pattern.CASE_INSENSITIVE));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
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 org.springframework.core.Ordered;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发认证过滤器(内部服务外网隔离)
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ForwardAuthFilter implements GlobalFilter, Ordered {
|
||||||
|
@Override
|
||||||
|
public Mono<Void> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return -100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package org.dromara.gateway.filter;
|
||||||
|
|
||||||
|
import org.dromara.gateway.utils.WebFluxUtils;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局缓存获取body请求数据(解决流不能重复读取问题)
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GlobalCacheRequestFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
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 GlobalCorsFilter 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<Void> 表示异步的过滤器链处理结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Mono<Void> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package org.dromara.gateway.filter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
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 reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局国际化处理
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class GlobalI18nFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
package org.dromara.gateway.filter;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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.springframework.core.Ordered;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局日志过滤器
|
||||||
|
* <p>
|
||||||
|
* 用于打印请求执行参数与响应时间等等
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class GlobalLogFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomGatewayProperties customGatewayProperties;
|
||||||
|
@Autowired
|
||||||
|
private ApiDecryptProperties apiDecryptProperties;
|
||||||
|
|
||||||
|
private static final String START_TIME = "startTime";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> 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;
|
||||||
|
|
||||||
|
// 打印请求参数
|
||||||
|
if (WebFluxUtils.isJsonRequest(exchange)) {
|
||||||
|
if (apiDecryptProperties.getEnabled()
|
||||||
|
&& ObjectUtil.isNotNull(request.getHeaders().getFirst(apiDecryptProperties.getHeaderFlag()))) {
|
||||||
|
log.info("[PLUS]开始请求 => URL[{}],参数类型[encrypt]", url);
|
||||||
|
} else {
|
||||||
|
String jsonParam = WebFluxUtils.resolveBodyFromCacheRequest(exchange);
|
||||||
|
log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MultiValueMap<String, String> parameterMap = request.getQueryParams();
|
||||||
|
if (MapUtil.isNotEmpty(parameterMap)) {
|
||||||
|
String parameters = JSONUtil.toJsonStr(parameterMap);
|
||||||
|
log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
|
||||||
|
} else {
|
||||||
|
log.info("[PLUS]开始请求 => URL[{}],无参数", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
// 日志处理器在负载均衡器之后执行 负载均衡器会导致线程切换 无法获取上下文内容
|
||||||
|
// 如需在日志内操作线程上下文 例如获取登录用户数据等 可以打开下方注释代码
|
||||||
|
// return ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER - 1;
|
||||||
|
return Ordered.LOWEST_PRECEDENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package org.dromara.gateway.filter;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sse 特制异常
|
||||||
|
*
|
||||||
|
* @author LionLi
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public final class SseException extends RuntimeException {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误码
|
||||||
|
*/
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误提示
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误明细,内部调试错误
|
||||||
|
*/
|
||||||
|
private String detailMessage;
|
||||||
|
|
||||||
|
public SseException(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SseException(String message, Integer code) {
|
||||||
|
this.message = message;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SseException setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SseException setDetailMessage(String detailMessage) {
|
||||||
|
this.detailMessage = detailMessage;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package org.dromara.gateway.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.gateway.utils.WebFluxUtils;
|
||||||
|
import org.springframework.boot.web.reactive.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.springframework.web.server.ResponseStatusException;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关统一异常处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Order(-1)
|
||||||
|
@Configuration
|
||||||
|
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||||
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
|
|
||||||
|
if (exchange.getResponse().isCommitted()) {
|
||||||
|
return Mono.error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
String msg;
|
||||||
|
|
||||||
|
if (ex instanceof NotFoundException) {
|
||||||
|
msg = "服务未找到";
|
||||||
|
} else if (ex instanceof ResponseStatusException responseStatusException) {
|
||||||
|
msg = responseStatusException.getMessage();
|
||||||
|
} else {
|
||||||
|
msg = "内部服务器错误";
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
|
||||||
|
|
||||||
|
return WebFluxUtils.webFluxResponseWriter(response, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
package org.dromara.gateway.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import org.dromara.common.core.domain.R;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.gateway.filter.GlobalCacheRequestFilter;
|
||||||
|
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<URI> 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<DataBuffer> body = serverHttpRequest.getBody();
|
||||||
|
AtomicReference<String> 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 GlobalCacheRequestFilter} 全局创建了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()) {
|
||||||
|
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(iterator.next());
|
||||||
|
return charBuffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置webflux模型响应
|
||||||
|
*
|
||||||
|
* @param response ServerHttpResponse
|
||||||
|
* @param value 响应内容
|
||||||
|
* @return Mono<Void>
|
||||||
|
*/
|
||||||
|
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value) {
|
||||||
|
return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置webflux模型响应
|
||||||
|
*
|
||||||
|
* @param response ServerHttpResponse
|
||||||
|
* @param code 响应状态码
|
||||||
|
* @param value 响应内容
|
||||||
|
* @return Mono<Void>
|
||||||
|
*/
|
||||||
|
public static Mono<Void> 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<Void>
|
||||||
|
*/
|
||||||
|
public static Mono<Void> 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<Void>
|
||||||
|
*/
|
||||||
|
public static Mono<Void> 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(JSONUtil.toJsonStr(result).getBytes());
|
||||||
|
return response.writeWith(Mono.just(dataBuffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
33
ruoyi-site/ruoyi-gateway/src/main/resources/application.yml
Normal file
33
ruoyi-site/ruoyi-gateway/src/main/resources/application.yml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Tomcat
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
context-path: /
|
||||||
|
|
||||||
|
# Spring
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
# 应用名称
|
||||||
|
name: ruoyi-gateway
|
||||||
|
profiles:
|
||||||
|
# 环境配置
|
||||||
|
active: @profiles.active@
|
||||||
|
|
||||||
|
--- # nacos 配置
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
# nacos 服务地址
|
||||||
|
server-addr: http://localhost:8848
|
||||||
|
discovery:
|
||||||
|
# 注册组
|
||||||
|
group: dev
|
||||||
|
namespace: c5c00044-93e6-4e66-8060-3f39aa7ab82b
|
||||||
|
config:
|
||||||
|
# 配置组
|
||||||
|
group: dev
|
||||||
|
namespace: c5c00044-93e6-4e66-8060-3f39aa7ab82b
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- optional:nacos:application-common.yml
|
||||||
|
- optional:nacos:${spring.application.name}.yml
|
||||||
10
ruoyi-site/ruoyi-gateway/src/main/resources/banner.txt
Normal file
10
ruoyi-site/ruoyi-gateway/src/main/resources/banner.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Spring Boot Version: ${spring-boot.version}
|
||||||
|
Spring Application Name: ${spring.application.name}
|
||||||
|
_ _
|
||||||
|
(_) | |
|
||||||
|
_ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _
|
||||||
|
| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |
|
||||||
|
| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| |
|
||||||
|
|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, |
|
||||||
|
__/ | __/ | __/ |
|
||||||
|
|___/ |___/ |___/
|
||||||
114
ruoyi-site/ruoyi-gateway/src/main/resources/logback-plus.xml
Normal file
114
ruoyi-site/ruoyi-gateway/src/main/resources/logback-plus.xml
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||||
|
<!-- 日志存放路径 -->
|
||||||
|
<property name="log.path" value="logs/${project.artifactId}"/>
|
||||||
|
<!-- 日志输出格式 -->
|
||||||
|
<property name="console.log.pattern"
|
||||||
|
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
|
||||||
|
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${console.log.pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/console.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件名格式 -->
|
||||||
|
<fileNamePattern>${log.path}/console.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大 1天 -->
|
||||||
|
<maxHistory>1</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<!-- 过滤的级别 -->
|
||||||
|
<level>INFO</level>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 系统日志输出 -->
|
||||||
|
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/info.log</file>
|
||||||
|
<!-- 循环政策:基于时间创建日志文件 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件名格式 -->
|
||||||
|
<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大的历史 60天 -->
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<!-- 过滤的级别 -->
|
||||||
|
<level>INFO</level>
|
||||||
|
<!-- 匹配时的操作:接收(记录) -->
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/error.log</file>
|
||||||
|
<!-- 循环政策:基于时间创建日志文件 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件名格式 -->
|
||||||
|
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大的历史 60天 -->
|
||||||
|
<maxHistory>60</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<!-- 过滤的级别 -->
|
||||||
|
<level>ERROR</level>
|
||||||
|
<!-- 匹配时的操作:接收(记录) -->
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<!-- 不匹配时的操作:拒绝(不记录) -->
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- info异步输出 -->
|
||||||
|
<appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||||
|
<queueSize>512</queueSize>
|
||||||
|
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||||
|
<appender-ref ref="file_info"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- error异步输出 -->
|
||||||
|
<appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||||
|
<queueSize>512</queueSize>
|
||||||
|
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||||
|
<appender-ref ref="file_error"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<include resource="logback-logstash.xml" />
|
||||||
|
|
||||||
|
<!-- 开启 skywalking 日志收集 -->
|
||||||
|
<include resource="logback-skylog.xml" />
|
||||||
|
|
||||||
|
<!--系统操作日志-->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="console"/>
|
||||||
|
<appender-ref ref="async_info"/>
|
||||||
|
<appender-ref ref="async_error"/>
|
||||||
|
<appender-ref ref="file_console"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
Loading…
Reference in New Issue
Block a user