first commit

This commit is contained in:
abcv7
2026-02-25 15:08:40 +08:00
commit 7f1d83ada7
2003 changed files with 144362 additions and 0 deletions
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>sfbx-framework</artifactId>
<groupId>com.itheima.sfbx</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--基础模块-gateway支持-->
<artifactId>framework-gateway</artifactId>
<name>framework-gateway</name>
<dependencies>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-rabbitmq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,102 @@
package com.itheima.sfbx.framework.gateway.authorization;
import com.itheima.sfbx.framework.commons.constant.security.OauthCacheConstant;
import com.itheima.sfbx.framework.commons.constant.security.OauthConstant;
import com.itheima.sfbx.framework.commons.constant.security.SecurityConstant;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* @ClassName AuthorizationManager.java
* @Description 鉴权管理器
*/
@Slf4j
@Component
public class JwtReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
RedissonClient redissonClient;
@Autowired
ReactiveJwtDecoder reactiveJwtDecoder;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication,
AuthorizationContext authorizationContext) {
//目标路径:获得url路径
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
String path = request.getURI().getPath();
path = path.substring(path.indexOf("/",1));
String methodValue = request.getMethodValue();
String tagetResource = methodValue+path;
log.info("===============进入鉴权tagetResource路径:{}==========",tagetResource);
//校验userToken:如果当前的tooken获得为空,则认为鉴权失败
String userToken = request.getHeaders().getFirst(SecurityConstant.USER_TOKEN);
if (EmptyUtil.isNullOrEmpty(userToken)){
return Mono.justOrEmpty(new AuthorizationDecision(false));
}
//兑换userVO:使用当前usertoken获得缓存中userVO信息
RBucket<UserVO> userVOBucket = redissonClient.getBucket(OauthCacheConstant.USER_TOKEN + userToken);
UserVO userVO = userVOBucket.get();
if (EmptyUtil.isNullOrEmpty(userVO)){
return Mono.justOrEmpty(new AuthorizationDecision(false));
}
//剔除处理:如果页面传入usetToken不等于缓存中的usetToken,则表示当前usetToken被剔除
RBucket<String> userTokenBindRBucket = redissonClient.getBucket(OauthCacheConstant.USER_TOKEN_BIND + userVO.getUsername());
String userTokenBind = userTokenBindRBucket.get();
if (!userToken.equals(userTokenBind)){
return Mono.justOrEmpty(new AuthorizationDecision(false));
}
//兑换accessToken:使用userToken兑换去缓存中兑换accessToken
RBucket<String> accessTokenBucket = redissonClient.getBucket(OauthCacheConstant.ACCESS_TOKEN + userTokenBind);
String accessToken = accessTokenBucket.get();
//校验accessToken:令牌过期获得为空,校验失败
if (EmptyUtil.isNullOrEmpty(accessToken)){
return Mono.justOrEmpty(new AuthorizationDecision(false));
}
//解析accessToken:调用rsa接口进行验证签名
Mono<Jwt> decode = reactiveJwtDecoder.decode(accessToken);
Jwt jwt = null;
try {
jwt = decode.toFuture().get();
} catch (Exception e) {
return Mono.justOrEmpty(new AuthorizationDecision(false));
}
if (EmptyUtil.isNullOrEmpty(jwt)){
return Mono.justOrEmpty(new AuthorizationDecision(false));
}
//权限校验:当前用户不为非只认证类型,我们会从jwt中获得载荷,然后判断载荷中的权限是否包含访问路径权限
if (!userVO.getOnlyAuthenticate()){
List<String> authorities = (List<String>)jwt.getClaims().get(OauthConstant.AUTHORITIES_KEY);
for (String authority : authorities) {
boolean isMatch = antPathMatcher.match(authority, tagetResource);
if (isMatch){
log.info("用户:{}拥有tagetResource权限:{}==========",userVO.getUsername(),tagetResource);
return Mono.just(new AuthorizationDecision(true));
}
}
log.info("用户:{}不拥有tagetResource权限:{}==========",userVO.getUsername(),tagetResource);
return Mono.just(new AuthorizationDecision(false));
}
return Mono.just(new AuthorizationDecision(true));
}
}
@@ -0,0 +1,12 @@
package com.itheima.sfbx.framework.gateway.binding;
import com.itheima.sfbx.framework.rabbitmq.source.LogSource;
import org.springframework.cloud.stream.annotation.EnableBinding;
/**
* @ClassName Binding.java
* @Description 绑定声明
*/
@EnableBinding({LogSource.class})
public class SourceBinding {
}
@@ -0,0 +1,107 @@
package com.itheima.sfbx.framework.gateway.config;
import com.itheima.sfbx.framework.commons.enums.security.AuthEnum;
import com.itheima.sfbx.framework.commons.properties.SecurityConfigProperties;
import com.itheima.sfbx.framework.gateway.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import reactor.core.publisher.Mono;
/**
* @ClassName ResourceServerConfig.java
* @Description 资源服务器配置
*/
@Configuration
@EnableWebFluxSecurity
@EnableConfigurationProperties(SecurityConfigProperties.class)
public class ResourceServerConfig {
@Autowired
private ReactiveAuthorizationManager jwtReactiveAuthorizationManager;
@Autowired
private SecurityConfigProperties securityConfigProperties;
/***
* @description 跨域处理
*/
@Bean
public CorsConfigurationSource corsConfigurationSource(){
return httpServletRequest -> {
//初始化跨域配置
CorsConfiguration cfg = new CorsConfiguration();
//请求头中可以包含任意的参数
cfg.addAllowedHeader("*");
//请求方式可以是任意方式:post get patch pu delete opertions
cfg.addAllowedMethod("*");
//指定请求源的目标地址
securityConfigProperties.getOrigins().forEach(origin->{
cfg.addAllowedOrigin(origin);
});
cfg.setAllowCredentials(true);
return cfg;
};
}
/***
* @description 鉴权过滤器链
* @param http 服务器鉴权请求
* @return 过滤器链
*/
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
//jwt鉴权转换器
http.oauth2ResourceServer().jwt();
http.authorizeExchange()
//匿名资源放行
.pathMatchers(securityConfigProperties.getIgnoreUrl()
.toArray(new String[securityConfigProperties.getIgnoreUrl().size()])).permitAll()
// 访问权限控制
.anyExchange().access(jwtReactiveAuthorizationManager)
.and()
.exceptionHandling()
//处理未授权
.accessDeniedHandler(accessDeniedHandler())
//处理未认证
.authenticationEntryPoint(authenticationEntryPoint())
.and().csrf().disable();
return http.build();
}
/***
* @description 未授权处理
* @return 过滤器链
*/
@Bean
ServerAccessDeniedHandler accessDeniedHandler() {
return (exchange, denied) -> {
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
.flatMap(response -> WebUtils.writeFailedToResponse(response, AuthEnum.AUTH_FAIL));
return mono;
};
}
/**
* token无效或者已过期自定义响应
*/
@Bean
ServerAuthenticationEntryPoint authenticationEntryPoint() {
return (exchange, e) -> {
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
.flatMap(response -> WebUtils.writeFailedToResponse(response,AuthEnum.NEED_LOGIN));
return mono;
};
}
}
@@ -0,0 +1,27 @@
package com.itheima.sfbx.framework.gateway.config;
import com.itheima.sfbx.framework.commons.utils.SnowflakeIdWorker;
import com.itheima.sfbx.framework.gateway.properties.SnowflakeIdWorkerProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName SnowflakeIdWorkerConfig.java
* @Description 唯一键
*/
@Configuration
@EnableConfigurationProperties(SnowflakeIdWorkerProperties.class)
public class SnowflakeIdWorkerConfig {
@Autowired
SnowflakeIdWorkerProperties snowflakeIdWorkerProperties;
@Bean
public SnowflakeIdWorker snowflakeIdWorker(){
return new SnowflakeIdWorker(
snowflakeIdWorkerProperties.getWorkerId(),
snowflakeIdWorkerProperties.getDatacenterId());
}
}
@@ -0,0 +1,60 @@
package com.itheima.sfbx.framework.gateway.decorator;
import com.itheima.sfbx.framework.gateway.util.RequestHelper;
import io.netty.buffer.UnpooledByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
/**
* @ClassName RecorderServerHttpRequestDecorator.java
* @Description 对ServerHttpRequest进行二次封装,解决requestBody只能读取一次的问题
*/
@Slf4j
public class CacheServerHttpRequestDecorator extends ServerHttpRequestDecorator {
byte[] bytes;
public CacheServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
}
/***
* @description 改下body的获得方式,传递给下游业务系统使用
* @return
* @return: reactor.core.publisher.Flux<org.springframework.core.io.buffer.DataBuffer>
*/
@Override
public Flux<DataBuffer> getBody() {
return super.getBody() //获得父类获得请求体信息
.publishOn(Schedulers.single()) //切换单线程防止阻塞
.map(this::cache) //重写body可多次读取
.doOnComplete(() -> addHeaders(getDelegate(),bytes));//读取请求体放入当前请求头便于后续处理
}
private DataBuffer cache(DataBuffer dataBuffer) {
//先构建一个与dataBuffer长度相同的byty数组
bytes = new byte[dataBuffer.readableByteCount()];
//从dataBuffer中读出数据存入当前bytes中
dataBuffer.read(bytes);
//释放掉内存
DataBufferUtils.release(dataBuffer);
//放回到当前的NettyDataBufferFactory,用于下游系统的使用
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
return nettyDataBufferFactory.wrap(bytes);
}
private void addHeaders(ServerHttpRequest request,byte[] bytes) {
//请求体信息
String requesBody = RequestHelper.readRequestBody(request,bytes);
String replaceString = requesBody.replaceAll("\t|\n|\r", "").replaceAll(" ","");
request.mutate().header("requesBody", replaceString).build();
}
}
@@ -0,0 +1,169 @@
package com.itheima.sfbx.framework.gateway.decorator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.dto.log.LogBusinessVO;
import com.itheima.sfbx.framework.commons.dto.security.UserVO;
import com.itheima.sfbx.framework.commons.enums.basic.BaseEnum;
import com.itheima.sfbx.framework.commons.utils.CityUtil;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.utils.SubjectContent;
import com.itheima.sfbx.framework.rabbitmq.pojo.MqMessage;
import com.itheima.sfbx.framework.rabbitmq.source.LogSource;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicReference;
/**
* @ClassName CacheServerHttpResponseDecorator.java
* @Description 缓存应答装饰器
*/
@Slf4j
public class CacheServerHttpResponseDecorator extends ServerHttpResponseDecorator {
LogSource logSource;
ServerWebExchange exchange;
private Long messageId;
private String sender;
private byte[] bytes;
public CacheServerHttpResponseDecorator(ServerWebExchange exchange,
LogSource logSource,
Long messageId,
String sender) {
super(exchange.getResponse());
this.logSource = logSource;
this.exchange = exchange;
this.messageId = messageId;
this.sender =sender;
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
AtomicReference<String> bodyRef = new AtomicReference<>();
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffer);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// 释放掉内存
DataBufferUtils.release(join);
String resultString = new String(content, Charset.forName("UTF-8"));
//返回结果处理
ResponseResult responseResult = JSON.parseObject(resultString, ResponseResult.class);
// 服务访问记录-ResponseResult类型
this.trace(exchange.getRequest(),responseResult);
// 返回响应数据
byte[] uppedContent = resultString.getBytes();
return getDelegate().bufferFactory().wrap(uppedContent);
}));
}
return super.writeWith(body);
}
/***
* @description 服务访问记录-ResponseResult类型
*/
private void trace(ServerHttpRequest request, ResponseResult responseResult) {
//创建请求日志记录
String logJsonString = createLogJsonString(request, responseResult);
//发送队列延迟信息
sendLogJsonString(logJsonString);
}
/**
* 发送日志报文到mq
* @param logBusinessVOJsonString
*/
private void sendLogJsonString(String logBusinessVOJsonString) {
//发送队列信息
MqMessage mqMessage = MqMessage.builder()
.id(messageId)
.title("log-message")
.content(logBusinessVOJsonString)
.messageType("log-request")
.produceTime(Timestamp.valueOf(LocalDateTime.now()))
.sender(sender)
.build();
Message<MqMessage> message = MessageBuilder.withPayload(mqMessage).setHeader("type", "log-key").build();
boolean flag = logSource.logOutput().send(message);
log.info("发送:{}结果:{}",mqMessage.toString(),flag);
}
/**
* 构建日志记录
* @param request ServerHttpRequest-请求对象
* @param responseResult 返回的响应结果
* @return
*/
private String createLogJsonString(ServerHttpRequest request,ResponseResult responseResult ) {
//请求IP
String hostAddress = request.getRemoteAddress().getAddress().getHostAddress();
//请求host
String host = request.getURI().getHost();
//请求路径
String requestUri = request.getURI().getPath();
//请求方式
String method = request.getMethodValue().toUpperCase();
//请求id
String requestId = request.getId();
//请求体
String requestBody = request.getHeaders().getFirst("requestBody");
//业务类型
String businessType = request.getHeaders().getFirst("businessType");
//设备号
String deviceNumber = request.getHeaders().getFirst("deviceNumber");
//省份数据
String province = request.getHeaders().getFirst("province");
//市区数据
String city = request.getHeaders().getFirst("city");
//日志对象封装
LogBusinessVO logBusinessVO = LogBusinessVO.builder()
.requestId(requestId)
.host(host)
.hostAddress(hostAddress)
.requestUri(requestUri)
.requestBody(requestBody)
.requestMethod(method)
.responseBody(JSONObject.toJSONString(responseResult.getData()))
.responseCode(responseResult.getCode())
.responseMsg(responseResult.getMsg())
.businessType(businessType)
.deviceNumber(deviceNumber)
.province(province)
.userId(responseResult.getOperatorId())
.userName(responseResult.getOperatorName())
.sex(responseResult.getOperatorSex())
.city(city)
.build();
CityUtil.handlerCity(logBusinessVO);
String logBusinessVOJsonString = JSONObject.toJSONString(logBusinessVO);
log.info("================logBusinessVOJsonString:{}",logBusinessVOJsonString);
return logBusinessVOJsonString;
}
}
@@ -0,0 +1,61 @@
package com.itheima.sfbx.framework.gateway.filter;
import com.itheima.sfbx.framework.gateway.decorator.CacheServerHttpRequestDecorator;
import com.itheima.sfbx.framework.gateway.properties.LogProperties;
import com.itheima.sfbx.framework.gateway.util.RequestHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* @ClassName RequestRecordFilter.java
* @Description 请求日志拦截
*/
@Component
@EnableConfigurationProperties(LogProperties.class)
public class RequestRecordFilter implements GlobalFilter,Ordered {
@Autowired
LogProperties logProperties;
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//处理文件上传:如果是文件上传则不记录日志
MediaType mediaType =exchange.getRequest().getHeaders().getContentType();
boolean flag = RequestHelper.isUploadFile(mediaType);
//忽略路径处理:获得请求路径然后与logProperties的路进行匹配,匹配上则不记录日志
String path = exchange.getRequest().getURI().getPath();
List<String> ignoreTestUrl = logProperties.getIgnoreUrl();
for (String testUrl : ignoreTestUrl) {
if (antPathMatcher.match(testUrl, path)){
flag = true;
break;
}
}
//无需记录日志:直接放过请求
if (flag){
return chain.filter(exchange);
}
//需记录日志:对ServerHttpRequest进行二次封装,解决requestBody只能读取一次的问题
CacheServerHttpRequestDecorator decorator = new CacheServerHttpRequestDecorator(exchange.getRequest());
//把当前的请求体进行改变,用于传递新放入的body
return chain.filter(exchange.mutate().request(decorator).build());
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
@@ -0,0 +1,81 @@
package com.itheima.sfbx.framework.gateway.filter;
import com.itheima.sfbx.framework.commons.utils.SnowflakeIdWorker;
import com.itheima.sfbx.framework.gateway.decorator.CacheServerHttpResponseDecorator;
import com.itheima.sfbx.framework.gateway.properties.LogProperties;
import com.itheima.sfbx.framework.gateway.util.RequestHelper;
import com.itheima.sfbx.framework.rabbitmq.source.LogSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* @ClassName ResponseRecordFilter.java
* @Description Response记录过滤
*/
@Slf4j
@Component
public class ResponseRecordFilter implements GlobalFilter, Ordered {
@Autowired
private LogSource logSource;
@Autowired
private SnowflakeIdWorker snowflakeIdWorker;
@Autowired
LogProperties logProperties;
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Value("${spring.application.name}")
private String applicationName;
@Value("${server.port}")
private String port;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//处理文件上传:如果是文件上传则不记录日志
MediaType mediaType =exchange.getRequest().getHeaders().getContentType();
boolean flag = RequestHelper.isUploadFile(mediaType);
//忽略路径处理:获得请求路径然后与logProperties的路进行匹配,匹配上则不记录日志
String path = exchange.getRequest().getURI().getPath();
List<String> ignoreTestUrl = logProperties.getIgnoreUrl();
for (String testUrl : ignoreTestUrl) {
if (antPathMatcher.match(testUrl, path)){
flag = true;
break;
}
}
//无需记录日志:直接放过请求
if (flag){
return chain.filter(exchange);
}
//需记录日志:对ServerHttpResponse进行二次封装
CacheServerHttpResponseDecorator serverHttpResponseDecorator =
new CacheServerHttpResponseDecorator(
exchange,
logSource,
snowflakeIdWorker.nextId(),
applicationName+":"+port);
//把当前的应答体进行改变,用于传递新放入的response中
return chain.filter(exchange.mutate().response(serverHttpResponseDecorator).build());
}
@Override
public int getOrder() {
return -2;
}
}
@@ -0,0 +1,39 @@
package com.itheima.sfbx.framework.gateway.filter;
import com.itheima.sfbx.framework.commons.constant.security.SecurityConstant;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @ClassName TenantFilter.java
* @Description 多租户过滤器
*/
@Slf4j
@Component
@Order(-99)
public class UserTokenGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//放入下游请求头中
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().build();
//获得请求头中的userToken信息
String userToken = exchange.getRequest().getHeaders().getFirst(SecurityConstant.USER_TOKEN);
//已登录从头部中拿到userToken
if (!EmptyUtil.isNullOrEmpty(userToken)){
serverHttpRequest = serverHttpRequest.mutate().header(SecurityConstant.USER_TOKEN, userToken).build();
}
//把新的exchange放回到过滤链
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
}
}
@@ -0,0 +1,22 @@
package com.itheima.sfbx.framework.gateway.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName TenantProperties.java
* @Description MyBaits-plus多租户属性
*/
@Data
@NoArgsConstructor
@ConfigurationProperties(prefix = "itheima.framework.log")
public class LogProperties {
public List<String> ignoreUrl = new ArrayList<>();
}
@@ -0,0 +1,24 @@
package com.itheima.sfbx.framework.gateway.properties;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @ClassName SnowflakeIdWorkerProperties.java
* @Description 雪花算法配置
*/
@Setter
@Getter
@NoArgsConstructor
@ToString
@ConfigurationProperties(prefix = "itheima.framework.snowflake")
public class SnowflakeIdWorkerProperties {
private Long workerId;
private Long datacenterId;
}
@@ -0,0 +1,67 @@
package com.itheima.sfbx.framework.gateway.util;
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.lang.Nullable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @ClassName LogHelper.java
* @Description 请求处理类
*/
public class RequestHelper {
/**
* @Description 根据MediaType获取字符集,如果获取不到,则默认返回<tt>UTF_8</tt>
* @param mediaType MediaType
* @return Charset
*/
public static Charset getMediaTypeCharset(@Nullable MediaType mediaType) {
if (Objects.nonNull(mediaType) && mediaType.getCharset() != null) {
return mediaType.getCharset();
} else {
return StandardCharsets.UTF_8;
}
}
/**
* @Description 读取请求体内容
* @param request ServerHttpRequest
* @return 请求体
*/
public static String readRequestBody(ServerHttpRequest request,byte[] bytes) {
HttpHeaders headers = request.getHeaders();
MediaType mediaType = headers.getContentType();
String method = request.getMethodValue().toUpperCase();
if (Objects.nonNull(mediaType) && mediaType.equals(MediaType.MULTIPART_FORM_DATA)) {
return "upload-file";
}else if (method.equals(HttpMethod.GET)&&!request.getQueryParams().isEmpty()) {
return request.getQueryParams().toString();
}else if (headers.getContentLength() > 0) {
return new String(bytes, getMediaTypeCharset(mediaType));
}else {
return "no-have-body";
}
}
/**
* @Description 判断是否是上传文件
* @param mediaType MediaType
* @return Boolean
*/
public static boolean isUploadFile(@Nullable MediaType mediaType) {
if (Objects.isNull(mediaType)) {
return false;
}
return mediaType.includes(MediaType.MULTIPART_FORM_DATA)
|| mediaType.includes(MediaType.IMAGE_GIF)
|| mediaType.includes(MediaType.IMAGE_JPEG)
|| mediaType.includes(MediaType.IMAGE_PNG)
|| mediaType.equals(MediaType.MULTIPART_FORM_DATA_VALUE);
}
}
@@ -0,0 +1,36 @@
package com.itheima.sfbx.framework.gateway.util;
import com.alibaba.fastjson.JSONObject;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.enums.basic.IBaseEnum;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import io.netty.util.CharsetUtil;
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.ServerHttpResponse;
import reactor.core.publisher.Mono;
/***
* @description web工具类
*/
public class WebUtils {
public static Mono writeFailedToResponse(ServerHttpResponse response, IBaseEnum basicEnum){
//应答状态
response.setStatusCode(HttpStatus.OK);
//响应格式
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
//返回结果封装
ResponseResult<Boolean> responseWrap = ResponseResultBuild.build(basicEnum, false);
String result = JSONObject.toJSONString(responseWrap);
DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8));
//写入响应结果
return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer));
}
}