first commit

This commit is contained in:
陈现府
2025-11-20 10:45:16 +08:00
commit 8355431361
2643 changed files with 291314 additions and 0 deletions
@@ -0,0 +1,29 @@
package com.mhd.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* 网关启动程序
*
* @author mhd
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class MhdGatewayApplication
{
public static void main(String[] args)
{
SpringApplication.run(MhdGatewayApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 若依网关启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}
@@ -0,0 +1,83 @@
package com.mhd.gateway.config;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import static com.google.code.kaptcha.Constants.*;
/**
* 验证码配置
*
* @author mhd
*/
@Configuration
public class CaptchaConfig
{
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean()
{
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath()
{
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.mhd.gateway.config.KaptchaTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
@@ -0,0 +1,23 @@
package com.mhd.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.mhd.gateway.handler.SentinelFallbackHandler;
/**
* 网关限流配置
*
* @author mhd
*/
@Configuration
public class GatewayConfig
{
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelFallbackHandler sentinelGatewayExceptionHandler()
{
return new SentinelFallbackHandler();
}
}
@@ -0,0 +1,75 @@
package com.mhd.gateway.config;
import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/**
* 验证码文本生成器
*
* @author mhd
*/
public class KaptchaTextCreator extends DefaultTextCreator
{
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
@Override
public String getText()
{
Integer result = 0;
Random random = new Random();
int x = random.nextInt(10);
int y = random.nextInt(10);
StringBuilder suChinese = new StringBuilder();
int randomoperands = random.nextInt(3);
if (randomoperands == 0)
{
result = x * y;
suChinese.append(CNUMBERS[x]);
suChinese.append("*");
suChinese.append(CNUMBERS[y]);
}
else if (randomoperands == 1)
{
if ((x != 0) && y % x == 0)
{
result = y / x;
suChinese.append(CNUMBERS[y]);
suChinese.append("/");
suChinese.append(CNUMBERS[x]);
}
else
{
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
}
else if (randomoperands == 2)
{
if (x >= y)
{
result = x - y;
suChinese.append(CNUMBERS[x]);
suChinese.append("-");
suChinese.append(CNUMBERS[y]);
}
else
{
result = y - x;
suChinese.append(CNUMBERS[y]);
suChinese.append("-");
suChinese.append(CNUMBERS[x]);
}
}
else
{
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
suChinese.append("=?@" + result);
return suChinese.toString();
}
}
@@ -0,0 +1,31 @@
package com.mhd.gateway.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import com.mhd.gateway.handler.ValidateCodeHandler;
/**
* 路由配置信息
*
* @author mhd
*/
@Configuration
public class RouterFunctionConfiguration
{
@Autowired
private ValidateCodeHandler validateCodeHandler;
@SuppressWarnings("rawtypes")
@Bean
public RouterFunction routerFunction()
{
return RouterFunctions.route(
RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
validateCodeHandler);
}
}
@@ -0,0 +1,79 @@
package com.mhd.gateway.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
/**
* 聚合系统接口
*
* @author mhd
*/
@Component
public class SwaggerProvider implements SwaggerResourcesProvider, WebFluxConfigurer
{
/**
* Swagger2默认的url后缀
*/
public static final String SWAGGER2URL = "/v2/api-docs";
/**
* 网关路由
*/
@Lazy
@Autowired
private RouteLocator routeLocator;
@Autowired
private GatewayProperties gatewayProperties;
/**
* 聚合其他服务接口
*
* @return
*/
@Override
public List<SwaggerResource> get()
{
List<SwaggerResource> resourceList = new ArrayList<>();
List<String> routes = new ArrayList<>();
// 获取网关中配置的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
gatewayProperties.getRoutes().stream()
.filter(routeDefinition -> routes
.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))
.filter(predicateDefinition -> !"mhd-auth".equalsIgnoreCase(routeDefinition.getId()))
.forEach(predicateDefinition -> resourceList
.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs()
.get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL)))));
return resourceList;
}
private SwaggerResource swaggerResource(String name, String location)
{
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
/** swagger-ui 地址 */
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
}
}
@@ -0,0 +1,46 @@
package com.mhd.gateway.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 验证码配置
*
* @author mhd
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties
{
/**
* 验证码开关
*/
private Boolean enabled;
/**
* 验证码类型(math 数组计算 char 字符)
*/
private String type;
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
}
@@ -0,0 +1,33 @@
package com.mhd.gateway.config.properties;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 放行白名单配置
*
* @author mhd
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties
{
/**
* 放行白名单配置,网关不校验此处的白名单
*/
private List<String> whites = new ArrayList<>();
public List<String> getWhites()
{
return whites;
}
public void setWhites(List<String> whites)
{
this.whites = whites;
}
}
@@ -0,0 +1,44 @@
package com.mhd.gateway.config.properties;
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 mhd
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.world")
public class WorldWhiteProperties
{
private String symbol;
/**
* 放行白名单配置,网关不校验此处的白名单
*/
private List<String> whites = new ArrayList<>();
public List<String> getWhites()
{
return whites;
}
public void setWhites(List<String> whites)
{
this.whites = whites;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
}
@@ -0,0 +1,48 @@
package com.mhd.gateway.config.properties;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* XSS跨站脚本配置
*
* @author mhd
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.xss")
public class XssProperties
{
/**
* Xss开关
*/
private Boolean enabled;
/**
* 排除路径
*/
private List<String> excludeUrls = new ArrayList<>();
public Boolean getEnabled()
{
return enabled;
}
public void setEnabled(Boolean enabled)
{
this.enabled = enabled;
}
public List<String> getExcludeUrls()
{
return excludeUrls;
}
public void setExcludeUrls(List<String> excludeUrls)
{
this.excludeUrls = excludeUrls;
}
}
@@ -0,0 +1,263 @@
package com.mhd.gateway.filter;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.mhd.common.core.domain.po.UserConfigSwitchPo;
import com.mhd.gateway.config.properties.IgnoreWhiteProperties;
import com.mhd.gateway.config.properties.WorldWhiteProperties;
import com.mhd.gateway.uilts.SensitiveWordLibrary;
import com.mhd.gateway.uilts.SensitiveWordLoader;
import com.mhd.gateway.uilts.TextSegmentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
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 com.mhd.common.core.constant.CacheConstants;
import com.mhd.common.core.constant.HttpStatus;
import com.mhd.common.core.constant.SecurityConstants;
import com.mhd.common.core.constant.TokenConstants;
import com.mhd.common.core.utils.JwtUtils;
import com.mhd.common.core.utils.ServletUtils;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.common.redis.service.RedisService;
import io.jsonwebtoken.Claims;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 网关鉴权
*
* @author mhd
*/
@Component
public class AuthFilter implements GlobalFilter, Ordered
{
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
// 排除过滤的 uri 地址,nacos自行添加
@Autowired
private IgnoreWhiteProperties ignoreWhite;
@Autowired
private RedisService redisService;
//敏感词汇过滤 uri地址,nacos配置
@Autowired
private WorldWhiteProperties worldWhiteProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder mutate = request.mutate();
String url = request.getURI().getPath();
// 跳过不需要验证的路径
if (StringUtils.matches(url, ignoreWhite.getWhites()))
{
return chain.filter(exchange);
}
String token = getToken(request);
if (StringUtils.isEmpty(token))
{
return unauthorizedResponse(exchange, "令牌不能为空");
}
Claims claims = JwtUtils.parseToken(token);
if (claims == null)
{
return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
}
String userkey = JwtUtils.getUserKey(claims);
boolean islogin = redisService.hasKey(getTokenKey(userkey));
if (!islogin)
{
return unauthorizedResponse(exchange, "登录状态已过期");
}
String userid = JwtUtils.getUserId(claims);
String username = JwtUtils.getUserName(claims);
if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))
{
return unauthorizedResponse(exchange, "令牌验证失败");
}
//校验单点登录 0-否,1-是
String topOrganizationId = JwtUtils.getTopOrganizationId(claims);
UserConfigSwitchPo userConfigSwitchPo = redisService.getCacheObject(CacheConstants.USER_COMFIG_SWITCH_KEY + topOrganizationId);
if (ObjUtil.isNotNull(userConfigSwitchPo)){
Integer isSingleSign = userConfigSwitchPo.getIsSingleSign();
if (ObjUtil.isNotNull(isSingleSign) && isSingleSign == 1) {
String key = CacheConstants.USER_LOGIN_TOKENS + userid;
String oldToken = CacheConstants.LOGIN_TOKEN_KEY + userkey;
String currentToken = redisService.getCacheObject(key);
if (StrUtil.isEmpty(currentToken)) {
//重新登录 存储单点登录 用户-token
redisService.deleteObject(oldToken);
return unauthorizedResponse(exchange, "登录状态已过期,请重新登录");
}
//判断是否是单点登录保存的token,不一致则下线
if (!StrUtil.equals(currentToken, oldToken)){
redisService.deleteObject(oldToken);
return unauthorizedResponse(exchange, "您已在其他设备登录,请重新登录");
}
}
}
//是post请求的走获取body的方法
String method = request.getMethodValue();
if ("POST".equals(method)){
// 校验路径,如果路径需要过滤,则进入脱敏方法
if (StringUtils.matches(url, worldWhiteProperties.getWhites()))
{
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
//进入脱敏方法
if(StringUtils.isNotBlank(bodyString)){
bodyString = worldFilter(bodyString);
log.info("替换完成后字符串" + bodyString);
}
exchange.getAttributes().put("POST_BODY",bodyString);
DataBufferUtils.release(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest)
.build());
});
}
}
// 设置用户信息到请求
addHeader(mutate, SecurityConstants.USER_KEY, userkey);
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
// 内部请求来源参数清除
removeHeader(mutate, SecurityConstants.FROM_SOURCE);
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value)
{
if (value == null)
{
return;
}
String valueStr = value.toString();
String valueEncode = ServletUtils.urlEncode(valueStr);
mutate.header(name, valueEncode);
}
private void removeHeader(ServerHttpRequest.Builder mutate, String name)
{
mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
}
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg)
{
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
}
/**
* 获取缓存key
*/
private String getTokenKey(String token)
{
return CacheConstants.LOGIN_TOKEN_KEY + token;
}
/**
* 获取请求token
*/
private String getToken(ServerHttpRequest request)
{
String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
// 如果前端设置了令牌前缀,则裁剪掉前缀
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
{
token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
}
return token;
}
@Override
public int getOrder()
{
return -200;
}
/**
* 文字脱敏方法
* @param bodyString 获取的请求参数jsonString
*/
private String worldFilter(String bodyString) {
log.info("替换符:" + worldWhiteProperties.getSymbol());
log.info(bodyString);
//创建敏感词库
SensitiveWordLibrary sensitiveWordLibrary = new SensitiveWordLibrary();
//加载敏感词库
try {
SensitiveWordLoader.load(sensitiveWordLibrary);
} catch (IOException e) {
e.printStackTrace();
log.error("加载敏感词库失败,过滤失效:" + e.getMessage());
}
//分词
String[] words = TextSegmentation.segment(bodyString);
//轮训替换整个json串
return exchangeWord(bodyString,sensitiveWordLibrary,words);
}
/**
* 替换敏感词
* @param bodyString 获取的请求参数jsonString
* @param sensitiveWordLibrary 敏感词库
* @return
*/
private String exchangeWord(String bodyString,SensitiveWordLibrary sensitiveWordLibrary,String[] words) {
for (int i = 0; i < words.length; i++) {
String word = words[i];
if (sensitiveWordLibrary.isSensitiveWord(word + ",")) {
//如果含有敏感词汇,则进行全局替换
String symbol = worldWhiteProperties.getSymbol();
StringBuilder symbolBuilder = new StringBuilder();
int length = word.length();
for (int i1 = 0; i1 < length; i1++) {
symbolBuilder.append(symbol);
}
bodyString = bodyString.replaceAll(word, symbolBuilder.toString());
words[i] = worldWhiteProperties.getSymbol();
//替换完成一个后,在继续进行轮训,直到满足全部过滤
exchangeWord(bodyString,sensitiveWordLibrary,words);
}
}
return bodyString;
}
}
@@ -0,0 +1,65 @@
package com.mhd.gateway.filter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import com.mhd.common.core.utils.ServletUtils;
/**
* 黑名单过滤器
*
* @author mhd
*/
@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 ServletUtils.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,87 @@
package com.mhd.gateway.filter;
import java.util.Collections;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 获取body请求数据(解决流不能重复读取问题)
*
* @author mhd
*/
@Component
public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
{
public CacheRequestFilter()
{
super(Config.class);
}
@Override
public String name()
{
return "CacheRequestFilter";
}
@Override
public GatewayFilter apply(Config config)
{
CacheRequestGatewayFilter cacheRequestGatewayFilter = new CacheRequestGatewayFilter();
Integer order = config.getOrder();
if (order == null)
{
return cacheRequestGatewayFilter;
}
return new OrderedGatewayFilter(cacheRequestGatewayFilter, order);
}
public static class CacheRequestGatewayFilter implements GatewayFilter
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
// GET DELETE 不过滤
HttpMethod method = exchange.getRequest().getMethod();
if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE)
{
return chain.filter(exchange);
}
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
if (serverHttpRequest == exchange.getRequest())
{
return chain.filter(exchange);
}
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
});
}
}
@Override
public List<String> shortcutFieldOrder()
{
return Collections.singletonList("order");
}
static class Config
{
private Integer order;
public Integer getOrder()
{
return order;
}
public void setOrder(Integer order)
{
this.order = order;
}
}
}
@@ -0,0 +1,89 @@
package com.mhd.gateway.filter;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import cn.hutool.core.util.StrUtil;
import com.mhd.gateway.config.properties.CaptchaProperties;
import com.mhd.gateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.mhd.common.core.utils.ServletUtils;
import com.mhd.common.core.utils.StringUtils;
import reactor.core.publisher.Flux;
/**
* 验证码过滤器
*
* @author mhd
*/
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
{
private final static String[] VALIDATE_URL = new String[] { "/auth/login","/auth/loginApi","/login","/tokenApi/login","/auth/register","/basicApi/findAgreement","/approvalDocumentApi/dingdingHuiDiao" };
@Autowired
private ValidateCodeService validateCodeService;
@Autowired
private CaptchaProperties captchaProperties;
private static final String CODE = "code";
private static final String UUID = "uuid";
private static final String VERIFICATION_CODE = "verificationCode";
@Override
public GatewayFilter apply(Object config)
{
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
{
return chain.filter(exchange);
}
try
{
String rspStr = resolveBodyFromRequest(request);
JSONObject obj = JSON.parseObject(rspStr);
if (StrUtil.equals(request.getURI().getPath(), "/auth/loginApi") && StrUtil.isNotEmpty(obj.getString(VERIFICATION_CODE))) {
return chain.filter(exchange);
}
// if (StrUtil.equals(request.getURI().getPath(), "/auth/dingloginApi") && StrUtil.isNotEmpty(obj.getString(VERIFICATION_CODE))) {
// return chain.filter(exchange);
// }
validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
}
catch (Exception e)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
{
// 获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
}
@@ -0,0 +1,125 @@
package com.mhd.gateway.filter;
import java.nio.charset.StandardCharsets;
import com.mhd.gateway.config.properties.XssProperties;
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.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
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 com.mhd.common.core.utils.StringUtils;
import com.mhd.common.core.utils.html.EscapeUtil;
import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 跨站脚本过滤器
*
* @author mhd
*/
@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 (!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 = EscapeUtil.clean(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;
}
/**
* 是否是Json请求
*
* @param exchange HTTP请求
*/
public boolean isJsonRequest(ServerWebExchange exchange)
{
String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
}
@Override
public int getOrder()
{
return -100;
}
}
@@ -0,0 +1,56 @@
package com.mhd.gateway.handler;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
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 com.mhd.common.core.utils.ServletUtils;
import reactor.core.publisher.Mono;
/**
* 网关统一异常处理
*
* @author mhd
*/
@Order(-1)
@Configuration
public class GatewayExceptionHandler implements ErrorWebExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
@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 responseStatusException = (ResponseStatusException) ex;
msg = responseStatusException.getMessage();
}
else
{
msg = "内部服务器错误";
}
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
return ServletUtils.webFluxResponseWriter(response, msg);
}
}
@@ -0,0 +1,41 @@
package com.mhd.gateway.handler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.mhd.common.core.utils.ServletUtils;
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 mhd
*/
public class SentinelFallbackHandler implements WebExceptionHandler
{
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试");
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
{
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);
}
}
@@ -0,0 +1,56 @@
package com.mhd.gateway.handler;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler
{
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources)
{
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration()
{
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()),
HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration()
{
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@SuppressWarnings("rawtypes")
@GetMapping("")
public Mono<ResponseEntity> swaggerResources()
{
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
@@ -0,0 +1,42 @@
package com.mhd.gateway.handler;
import java.io.IOException;
import com.mhd.gateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.mhd.common.core.exception.CaptchaException;
import com.mhd.common.core.web.domain.AjaxResult;
import reactor.core.publisher.Mono;
/**
* 验证码获取
*
* @author mhd
*/
@Component
public class ValidateCodeHandler implements HandlerFunction<ServerResponse>
{
@Autowired
private ValidateCodeService validateCodeService;
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest)
{
AjaxResult ajax;
try
{
ajax = validateCodeService.createCaptcha();
}
catch (CaptchaException | IOException e)
{
return Mono.error(e);
}
return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax));
}
}
@@ -0,0 +1,23 @@
package com.mhd.gateway.service;
import java.io.IOException;
import com.mhd.common.core.exception.CaptchaException;
import com.mhd.common.core.web.domain.AjaxResult;
/**
* 验证码处理
*
* @author mhd
*/
public interface ValidateCodeService
{
/**
* 生成验证码
*/
public AjaxResult createCaptcha() throws IOException, CaptchaException;
/**
* 校验验证码
*/
public void checkCaptcha(String key, String value) throws CaptchaException;
}
@@ -0,0 +1,119 @@
package com.mhd.gateway.service.impl;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import com.mhd.gateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;
import com.google.code.kaptcha.Producer;
import com.mhd.common.core.constant.Constants;
import com.mhd.common.core.exception.CaptchaException;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.common.core.utils.sign.Base64;
import com.mhd.common.core.utils.uuid.IdUtils;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.redis.service.RedisService;
import com.mhd.gateway.config.properties.CaptchaProperties;
/**
* 验证码实现处理
*
* @author mhd
*/
@Service
public class ValidateCodeServiceImpl implements ValidateCodeService
{
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;
@Autowired
private RedisService redisService;
@Autowired
private CaptchaProperties captchaProperties;
/**
* 生成验证码
*/
@Override
public AjaxResult createCaptcha() throws IOException, CaptchaException
{
AjaxResult ajax = AjaxResult.success();
boolean captchaEnabled = captchaProperties.getEnabled();
ajax.put("captchaEnabled", captchaEnabled);
if (!captchaEnabled)
{
return ajax;
}
// 保存验证码信息
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
String captchaType = captchaProperties.getType();
// 生成验证码
if ("math".equals(captchaType))
{
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(captchaType))
{
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
/**
* 校验验证码
*/
@Override
public void checkCaptcha(String code, String uuid) throws CaptchaException
{
if (StringUtils.isEmpty(code))
{
throw new CaptchaException("验证码不能为空");
}
if (StringUtils.isEmpty(uuid))
{
throw new CaptchaException("验证码已失效");
}
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String captcha = redisService.getCacheObject(verifyKey);
redisService.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha))
{
throw new CaptchaException("验证码错误");
}
}
}
@@ -0,0 +1,25 @@
package com.mhd.gateway.uilts;
import java.util.HashSet;
import java.util.Set;
/**
* 构建敏感词库
*/
public class SensitiveWordLibrary {
private Set<String> sensitiveWords;
public SensitiveWordLibrary() {
sensitiveWords = new HashSet<>();
}
public void addSensitiveWord(String word) {
sensitiveWords.add(word);
}
public boolean isSensitiveWord(String word) {
return sensitiveWords.contains(word);
}
}
@@ -0,0 +1,29 @@
package com.mhd.gateway.uilts;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
/**
* 加载敏感词库
*/
public class SensitiveWordLoader {
public static void load(SensitiveWordLibrary library) throws IOException {
URL resource = SensitiveWordLoader.class.getClassLoader().getResource("disabledWord/politics.txt");
if(null == resource){
throw new IOException("加载敏感词库失败");
}
String path = resource.getPath();
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
library.addSensitiveWord(line);
}
}
}
}
@@ -0,0 +1,19 @@
package com.mhd.gateway.uilts;
import org.ansj.domain.Result;
import org.ansj.splitWord.analysis.NlpAnalysis;
/**
* 待检测文本分词
*/
public class TextSegmentation {
public static String[] segment(String text) {
Result result = NlpAnalysis.parse(text);
return result.getTerms().stream()
.map(term -> term.getName())
.toArray(String[]::new);
}
}
+10
View File
@@ -0,0 +1,10 @@
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
_ _
(_) | |
_ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _
| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |
| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| |
|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, |
__/ | __/ | __/ |
|___/ |___/ |___/
@@ -0,0 +1,35 @@
# Tomcat
server:
port: 8088
# Spring
spring:
application:
# 应用名称
name: mhd-gateway
profiles:
# 环境配置
active: dev
cloud:
nacos:
discovery:
#server-addr: 192.168.0.69:8848
server-addr: 127.0.0.1:8848
#server-addr: 116.204.23.92:8848
namespace: 4dd3256a-22c0-42db-9af1-de513a05c6a9
config:
# 配置中心地址
#server-addr: 192.168.0.69:8848
server-addr: 127.0.0.1:8848
#server-addr: 116.204.23.92:8848
# 锁定server端的配置文件(读取它的配置项)
namespace: 4dd3256a-22c0-42db-9af1-de513a05c6a9
group: DEFAULT_GROUP # 默认分组就是DEFAULT_GROUP,如果使用默认分组可以不配置
file-extension: yml #默认properties
zipkin:
base-url: http://192.168.0.69:9411 # zipkin server的请求地址
sender:
# web 客户端将踪迹日志数据通过网络请求的方式传送到服务端,另外还有配置
# kafka/rabbit 客户端将踪迹日志数据传递到mq进行中转
type: web
@@ -0,0 +1,631 @@
习近平,
平近习,
xjp,
习太子,
习明泽,
老习,
温家宝,
温加宝,
温x,
温jia宝,
温宝宝,
温加饱,
温加保,
张培莉,
温云松,
温如春,
温jb,
胡温,
胡x,
胡jt,
胡boss,
胡总,
胡王八,
hujintao,
胡jintao,
胡j涛,
胡惊涛,
胡景涛,
胡紧掏,
湖紧掏,
胡紧套,
锦涛,
hjt,
胡派,
胡主席,
刘永清,
胡海峰,
胡海清,
江泽民,
民泽江,
江胡,
江哥,
江主席,
江书记,
江浙闽,
江沢民,
江浙民,
择民,
则民,
茳泽民,
zemin,
ze民,
老江,
老j,
江core,
江x,
江派,
江zm,
jzm,
江戏子,
江蛤蟆,
江某某,
江贼,
江猪,
江氏集团,
江绵恒,
江绵康,
王冶坪,
江泽慧,
邓小平,
平小邓,
xiao平,
邓xp,
邓晓平,
邓朴方,
邓榕,
邓质方,
毛泽东,
猫泽东,
猫则东,
猫贼洞,
毛zd,
毛zx,
z东,
ze东,
泽d,
zedong,
毛太祖,
毛相,
主席画像,
改革历程,
朱镕基,
朱容基,
朱镕鸡,
朱容鸡,
朱云来,
李鹏,
李peng,
里鹏,
李月月鸟,
李小鹏,
李小琳,
华主席,
华国,
国锋,
国峰,
锋同志,
白春礼,
薄熙来,
薄一波,
蔡赴朝,
蔡武,
曹刚川,
常万全,
陈炳德,
陈德铭,
陈建国,
陈良宇,
陈绍基,
陈同海,
陈至立,
戴秉国,
丁一平,
董建华,
杜德印,
杜世成,
傅锐,
郭伯雄,
郭金龙,
贺国强,
胡春华,
耀邦,
华建敏,
黄华华,
黄丽满,
黄兴国,
回良玉,
贾庆林,
贾廷安,
靖志远,
李长春,
李春城,
李建国,
李克强,
李岚清,
李沛瑶,
李荣融,
李瑞环,
李铁映,
李先念,
李学举,
李源潮,
栗智,
梁光烈,
廖锡龙,
林树森,
林炎志,
林左鸣,
令计划,
柳斌杰,
刘奇葆,
刘少奇,
刘延东,
刘云山,
刘志军,
龙新民,
路甬祥,
罗箭,
吕祖善,
马飚,
马恺,
孟建柱,
欧广源,
强卫,
沈跃跃,
宋平顺,
粟戎生,
苏树林,
孙家正,
铁凝,
屠光绍,
王东明,
汪东兴,
王鸿举,
王沪宁,
王乐泉,
王洛林,
王岐山,
王胜俊,
王太华,
王学军,
王兆国,
王振华,
吴邦国,
吴定富,
吴官正,
无官正,
吴胜利,
吴仪,
奚国华,
习仲勋,
徐才厚,
许其亮,
徐绍史,
杨洁篪,
叶剑英,
由喜贵,
于幼军,
俞正声,
袁纯清,
曾培炎,
曾庆红,
曾宪梓,
曾荫权,
张德江,
张定发,
张高丽,
张立昌,
张荣坤,
张志国,
赵洪祝,
紫阳,
周生贤,
周永康,
朱海仑,
中南海,
大陆当局,
中国当局,
北京当局,
共产党,
党产共,
共贪党,
阿共,
产党共,
公产党,
工产党,
共c党,
共x党,
共铲,
供产,
共惨,
供铲党,
供铲谠,
供铲裆,
共残党,
共残主义,
共产主义的幽灵,
拱铲,
老共,
中共,
中珙,
中gong,
gc党,
贡挡,
gong党,
g产,
狗产蛋,
共残裆,
恶党,
邪党,
共产专制,
共产王朝,
裆中央,
土共,
土g,
共狗,
g匪,
共匪,
仇共,
政府,
症腐,
政腐,
政付,
正府,
政俯,
政f,
zhengfu,
政zhi,
挡中央,
档中央,
中央领导,
中国zf,
中央zf,
国wu院,
中华帝国,
gong和,
大陆官方,
北京政权,
江泽民,
胡锦涛,
温家宝,
习近平,
习仲勋,
贺国强,
贺子珍,
周永康,
李长春,
李德生,
王岐山,
姚依林,
回良玉,
李源潮,
李干成,
戴秉国,
黄镇,
刘延东,
刘瑞龙,
俞正声,
黄敬,
薄熙,
薄一波,
周小川,
周建南,
温云松,
徐明,
江泽慧,
江绵恒,
江绵康,
李小鹏,
李鹏,
李小琳,
朱云来,
朱容基,
法轮功,
李洪志,
新疆骚乱,
爱液,
按摩棒,
拔出来,
爆草,
包二奶,
暴干,
暴奸,
暴乳,
爆乳,
暴淫,
被操,
被插,
被干,
逼奸,
仓井空,
插暴,
操逼,
操黑,
操烂,
肏你,
肏死,
操死,
操我,
厕奴,
插比,
插b,
插逼,
插进,
插你,
插我,
插阴,
潮吹,
潮喷,
成人电影,
成人论坛,
成人色情,
成人网站,
成人文学,
成人小说,
艳情小说,
成人游戏,
吃精,
抽插,
春药,
大波,
大力抽送,
大乳,
荡妇,
荡女,
盗撮,
发浪,
放尿,
肥逼,
粉穴,
风月大陆,
干死你,
干穴,
肛交,
肛门,
龟头,
裹本,
国产av,
好嫩,
豪乳,
黑逼,
后庭,
后穴,
虎骑,
换妻俱乐部,
黄片,
几吧,
鸡吧,
鸡巴,
鸡奸,
妓女,
奸情,
叫床,
脚交,
精液,
就去日,
巨屌,
菊花洞,
菊门,
巨奶,
巨乳,
菊穴,
开苞,
口爆,
口活,
口交,
口射,
口淫,
裤袜,
狂操,
狂插,
浪逼,
浪妇,
浪叫,
浪女,
狼友,
聊性,
凌辱,
漏乳,
露b,
乱交,
乱伦,
轮暴,
轮操,
轮奸,
裸陪,
买春,
美逼,
美少妇,
美乳,
美腿,
美穴,
美幼,
秘唇,
迷奸,
密穴,
蜜穴,
蜜液,
摸奶,
摸胸,
母奸,
奈美,
奶子,
男奴,
内射,
嫩逼,
嫩女,
嫩穴,
捏弄,
女优,
炮友,
砲友,
喷精,
屁眼,
前凸后翘,
强jian,
强暴,
强奸处女,
情趣用品,
情色,
拳交,
全裸,
群交,
人妻,
人兽,
日逼,
日烂,
肉棒,
肉逼,
肉唇,
肉洞,
肉缝,
肉棍,
肉茎,
肉具,
揉乳,
肉穴,
肉欲,
乳爆,
乳房,
乳沟,
乳交,
乳头,
骚逼,
骚比,
骚女,
骚水,
骚穴,
色逼,
色界,
色猫,
色盟,
色情网站,
色区,
色色,
色诱,
色欲,
色b,
少年阿宾,
射爽,
射颜,
食精,
释欲,
兽奸,
兽交,
手淫,
兽欲,
熟妇,
熟母,
熟女,
爽片,
双臀,
死逼,
丝袜,
丝诱,
松岛枫,
酥痒,
汤加丽,
套弄,
体奸,
体位,
舔脚,
舔阴,
调教,
偷欢,
推油,
脱内裤,
文做,
舞女,
无修正,
吸精,
夏川纯,
相奸,
小逼,
校鸡,
小穴,
小xue,
性感妖娆,
性感诱惑,
性虎,
性饥渴,
性技巧,
性交,
性奴,
性虐,
性息,
性欲,
胸推,
穴口,
穴图,
亚情,
颜射,
阳具,
杨思敏,
要射了,
夜勤病栋,
一本道,
一夜欢,
一夜情,
一ye情,
阴部,
淫虫,
阴唇,
淫荡,
阴道,
淫电影,
阴阜,
淫妇,
淫河,
阴核,
阴户,
淫贱,
淫叫,
淫教师,
阴茎,
阴精,
淫浪,
淫媚,
淫糜,
淫魔,
淫母,
淫女,
淫虐,
淫妻,
淫情,
淫色,
淫声浪语,
淫兽学园,
淫书,
淫术炼金士,
淫水,
淫娃,
淫威,
淫亵,
淫样,
淫液,
淫照,
阴b,
应召,
幼交,
欲火,
欲女,
玉乳,
玉穴,
援交,
原味内衣,
援助交际,
招鸡,
招妓,
抓胸,
自慰,
作爱,
a片,
fuck,
gay片,
g点,
h动画,
h动漫,
失身粉,
淫荡自慰器,
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="logs/mhd-gateway" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</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>
<!-- 系统模块日志级别控制 -->
<logger name="com.mhd" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>