mirror of
https://github.com/abcv7/sfbx-cloud.git
synced 2026-08-16 19:49:49 +00:00
first commit
This commit is contained in:
@@ -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>
|
||||
+102
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -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 {
|
||||
}
|
||||
+107
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+27
@@ -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());
|
||||
}
|
||||
}
|
||||
+60
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+169
@@ -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;
|
||||
}
|
||||
}
|
||||
+61
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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());
|
||||
}
|
||||
}
|
||||
+22
@@ -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<>();
|
||||
|
||||
}
|
||||
+24
@@ -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;
|
||||
|
||||
}
|
||||
+67
@@ -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);
|
||||
}
|
||||
}
|
||||
+36
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user