New file |
| | |
| | | #FROM findepi/graalvm:java17-native |
| | | FROM openjdk:17.0.2-oraclelinux8 |
| | | |
| | | 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 \ |
| | | -jar app.jar \ |
| | | -XX:+HeapDumpOnOutOfMemoryError -Xlog:gc*,:time,tags,level -XX:+UseZGC ${JAVA_OPTS} |
New file |
| | |
| | | <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-cloud-plus</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> |
| | | |
| | | <!-- 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> |
| | | |
| | | <!-- SpringCloud Alibaba Sentinel Gateway --> |
| | | <dependency> |
| | | <groupId>com.alibaba.cloud</groupId> |
| | | <artifactId>spring-cloud-alibaba-sentinel-gateway</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-sentinel</artifactId> |
| | | <exclusions> |
| | | <exclusion> |
| | | <groupId>com.alibaba.csp</groupId> |
| | | <artifactId>sentinel-apache-dubbo3-adapter</artifactId> |
| | | </exclusion> |
| | | </exclusions> |
| | | </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> |
| | | |
| | | <!-- 自定义负载均衡(多团队开发使用) --> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.dromara</groupId>--> |
| | | <!-- <artifactId>ruoyi-common-loadbalancer</artifactId>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- ELK 日志收集 --> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.dromara</groupId>--> |
| | | <!-- <artifactId>ruoyi-common-logstash</artifactId>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- skywalking 日志收集 --> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.dromara</groupId>--> |
| | | <!-- <artifactId>ruoyi-common-skylog</artifactId>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!-- prometheus 监控 --> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.dromara</groupId>--> |
| | | <!-- <artifactId>ruoyi-common-prometheus</artifactId>--> |
| | | <!-- </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> |
New file |
| | |
| | | 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 类型为 网关 |
| | | System.setProperty("csp.sentinel.app.type", "1"); |
| | | SpringApplication application = new SpringApplication(RuoYiGatewayApplication.class); |
| | | application.setApplicationStartup(new BufferingApplicationStartup(2048)); |
| | | application.run(args); |
| | | System.out.println("(♥◠‿◠)ノ゙ 网关启动成功 ლ(´ڡ`ლ)゙ "); |
| | | } |
| | | } |
New file |
| | |
| | | package org.dromara.gateway.config; |
| | | |
| | | import org.dromara.gateway.handler.SentinelFallbackHandler; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.core.Ordered; |
| | | import org.springframework.core.annotation.Order; |
| | | |
| | | /** |
| | | * 网关限流配置 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @Configuration |
| | | public class GatewayConfig { |
| | | @Bean |
| | | @Order(Ordered.HIGHEST_PRECEDENCE) |
| | | public SentinelFallbackHandler sentinelGatewayExceptionHandler() { |
| | | return new SentinelFallbackHandler(); |
| | | } |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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<String> whites = new ArrayList<>(); |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * XSS跨站脚本配置 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @Data |
| | | @Configuration |
| | | @RefreshScope |
| | | @ConfigurationProperties(prefix = "security.xss") |
| | | public class XssProperties { |
| | | /** |
| | | * Xss开关 |
| | | */ |
| | | private Boolean enabled; |
| | | |
| | | /** |
| | | * 排除路径 |
| | | */ |
| | | private List<String> excludeUrls = new ArrayList<>(); |
| | | |
| | | } |
New file |
| | |
| | | package org.dromara.gateway.filter; |
| | | |
| | | import cn.dev33.satoken.exception.NotLoginException; |
| | | 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.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/**") |
| | | // 鉴权方法:每次访问进入 |
| | | .setAuth(obj -> { |
| | | // 登录校验 -- 拦截所有路由 |
| | | SaRouter.match("/**") |
| | | .notMatch(ignoreWhite.getWhites()) |
| | | .check(r -> { |
| | | // 检查是否登录 是否有token |
| | | StpUtil.checkLogin(); |
| | | |
| | | // 检查 header 与 param 里的 clientid 与 token 里的是否一致 |
| | | ServerHttpRequest request = SaReactorSyncHolder.getContext().getRequest(); |
| | | 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); |
| | | }); |
| | | } |
| | | } |
New file |
| | |
| | | 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)); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | 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; |
| | | } |
| | | } |
New file |
| | |
| | | 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"; |
| | | private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD"; |
| | | private static final String ALLOWED_ORIGIN = "*"; |
| | | private static final String ALLOWED_EXPOSE = "*"; |
| | | private static final String MAX_AGE = "18000L"; |
| | | |
| | | @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"); |
| | | if (request.getMethod() == HttpMethod.OPTIONS) { |
| | | response.setStatusCode(HttpStatus.OK); |
| | | return Mono.empty(); |
| | | } |
| | | } |
| | | return chain.filter(exchange); |
| | | } |
| | | |
| | | @Override |
| | | public int getOrder() { |
| | | return Ordered.HIGHEST_PRECEDENCE; |
| | | } |
| | | } |
New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package org.dromara.gateway.filter; |
| | | |
| | | import cn.hutool.core.map.MapUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | 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 lombok.extern.slf4j.Slf4j; |
| | | 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 = JsonUtils.toJsonString(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 Ordered.LOWEST_PRECEDENCE; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package org.dromara.gateway.filter; |
| | | |
| | | import cn.hutool.http.HtmlUtil; |
| | | import org.dromara.common.core.utils.StringUtils; |
| | | import org.dromara.gateway.config.properties.XssProperties; |
| | | import org.dromara.gateway.utils.WebFluxUtils; |
| | | import io.netty.buffer.ByteBufAllocator; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| | | import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
| | | import org.springframework.cloud.gateway.filter.GlobalFilter; |
| | | import org.springframework.core.Ordered; |
| | | import org.springframework.core.io.buffer.*; |
| | | import org.springframework.http.HttpHeaders; |
| | | import org.springframework.http.HttpMethod; |
| | | import org.springframework.http.server.reactive.ServerHttpRequest; |
| | | import org.springframework.http.server.reactive.ServerHttpRequestDecorator; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.web.server.ServerWebExchange; |
| | | import reactor.core.publisher.Flux; |
| | | import reactor.core.publisher.Mono; |
| | | |
| | | import java.nio.charset.StandardCharsets; |
| | | |
| | | /** |
| | | * 跨站脚本过滤器 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | @Component |
| | | @ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true") |
| | | public class XssFilter implements GlobalFilter, Ordered { |
| | | // 跨站脚本的 xss 配置,nacos自行添加 |
| | | @Autowired |
| | | private XssProperties xss; |
| | | |
| | | @Override |
| | | public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
| | | ServerHttpRequest request = exchange.getRequest(); |
| | | // GET DELETE 不过滤 |
| | | HttpMethod method = request.getMethod(); |
| | | if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) { |
| | | return chain.filter(exchange); |
| | | } |
| | | // 非json类型,不过滤 |
| | | if (!WebFluxUtils.isJsonRequest(exchange)) { |
| | | return chain.filter(exchange); |
| | | } |
| | | // excludeUrls 不过滤 |
| | | String url = request.getURI().getPath(); |
| | | if (StringUtils.matches(url, xss.getExcludeUrls())) { |
| | | return chain.filter(exchange); |
| | | } |
| | | ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange); |
| | | return chain.filter(exchange.mutate().request(httpRequestDecorator).build()); |
| | | |
| | | } |
| | | |
| | | private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) { |
| | | ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) { |
| | | @Override |
| | | public Flux<DataBuffer> getBody() { |
| | | Flux<DataBuffer> body = super.getBody(); |
| | | return body.buffer().map(dataBuffers -> { |
| | | DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); |
| | | DataBuffer join = dataBufferFactory.join(dataBuffers); |
| | | byte[] content = new byte[join.readableByteCount()]; |
| | | join.read(content); |
| | | DataBufferUtils.release(join); |
| | | String bodyStr = new String(content, StandardCharsets.UTF_8); |
| | | // 防xss攻击过滤 |
| | | bodyStr = HtmlUtil.cleanHtmlTag(bodyStr); |
| | | // 转成字节 |
| | | byte[] bytes = bodyStr.getBytes(); |
| | | NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); |
| | | DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); |
| | | buffer.write(bytes); |
| | | return buffer; |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | public HttpHeaders getHeaders() { |
| | | HttpHeaders httpHeaders = new HttpHeaders(); |
| | | httpHeaders.putAll(super.getHeaders()); |
| | | // 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length |
| | | httpHeaders.remove(HttpHeaders.CONTENT_LENGTH); |
| | | httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
| | | return httpHeaders; |
| | | } |
| | | |
| | | }; |
| | | return serverHttpRequestDecorator; |
| | | } |
| | | |
| | | @Override |
| | | public int getOrder() { |
| | | return Ordered.HIGHEST_PRECEDENCE; |
| | | } |
| | | } |
New file |
| | |
| | | package org.dromara.gateway.handler; |
| | | |
| | | import org.dromara.gateway.utils.WebFluxUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | 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); |
| | | } |
| | | } |
New file |
| | |
| | | package org.dromara.gateway.handler; |
| | | |
| | | import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; |
| | | import com.alibaba.csp.sentinel.slots.block.BlockException; |
| | | import org.dromara.gateway.utils.WebFluxUtils; |
| | | import org.springframework.web.reactive.function.server.ServerResponse; |
| | | import org.springframework.web.server.ServerWebExchange; |
| | | import org.springframework.web.server.WebExceptionHandler; |
| | | import reactor.core.publisher.Mono; |
| | | |
| | | /** |
| | | * 自定义限流异常处理 |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | public class SentinelFallbackHandler implements WebExceptionHandler { |
| | | private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) { |
| | | return WebFluxUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试"); |
| | | } |
| | | |
| | | @Override |
| | | public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { |
| | | ex.printStackTrace(); |
| | | if (exchange.getResponse().isCommitted()) { |
| | | return Mono.error(ex); |
| | | } |
| | | if (!BlockException.isBlockException(ex)) { |
| | | return Mono.error(ex); |
| | | } |
| | | return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange)); |
| | | } |
| | | |
| | | private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) { |
| | | return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); |
| | | } |
| | | } |
New file |
| | |
| | | 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.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.ByteBuffer; |
| | | 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(JsonUtils.toJsonString(result).getBytes()); |
| | | return response.writeWith(Mono.just(dataBuffer)); |
| | | } |
| | | } |
New file |
| | |
| | | # Tomcat |
| | | server: |
| | | port: 8080 |
| | | servlet: |
| | | context-path: / |
| | | |
| | | # Spring |
| | | spring: |
| | | application: |
| | | # 应用名称 |
| | | name: ruoyi-gateway |
| | | profiles: |
| | | # 环境配置 |
| | | active: @profiles.active@ |
| | | |
| | | --- # nacos 配置 |
| | | spring: |
| | | cloud: |
| | | nacos: |
| | | # nacos 服务地址 |
| | | server-addr: @nacos.server@ |
| | | discovery: |
| | | # 注册组 |
| | | group: @nacos.discovery.group@ |
| | | namespace: ${spring.profiles.active} |
| | | config: |
| | | # 配置组 |
| | | group: @nacos.config.group@ |
| | | namespace: ${spring.profiles.active} |
| | | config: |
| | | import: |
| | | - optional:nacos:application-common.yml |
| | | - optional:nacos:${spring.application.name}.yml |
New file |
| | |
| | | Spring Boot Version: ${spring-boot.version} |
| | | Spring Application Name: ${spring.application.name} |
| | | _ _ |
| | | (_) | | |
| | | _ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _ |
| | | | '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | | |
| | | | | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| | |
| | | |_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, | |
| | | __/ | __/ | __/ | |
| | | |___/ |___/ |___/ |
New file |
| | |
| | | <?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> |