package com.taylor.gateway.config.context;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
/**
*
* reactor does not contain a {@link RequestContextHolder} object that has the same effect as mvc.
* If you need to get the current request from the context, you need to manually configure it.
*
* Bind ServerWebExchange to {@link Context} via {@link ReactiveRequestContextHolder} after intercepting with filter.
*
* from {https://gitee.com/596392912/mica/tree/master/mica-boot/src/main/java/net/dreamlu/mica/reactive/context}
* 使用 {@link Context} 的方式不能解决此问题 存入成功之后使用 {@link Mono#subscriberContext} 进行获取是无法成功的
*
* 改为ThreadLocal的方式
*
*/
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveRequestContextFilter implements WebFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ReactiveRequestContextHolder.put(request);
return chain.filter(exchange)
//Clear request save in ThreadLocal to prevent memory overflow
.doFinally(s -> ReactiveRequestContextHolder.reset());
//Invalid way
// return chain.filter(exchange)
// .subscriberContext(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
}
@Override
public int getOrder() {
return -20;
}
}
package com.taylor.gateway.config.context;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.server.reactive.ServerHttpRequest;
import java.util.Objects;
/**
*
* Used to bind the current request and context and provide a way to get the current request
*
*/
public class ReactiveRequestContextHolder {
// private static final Class CONTEXT_KEY = ServerWebExchange.class;
//
// /**
// * Gets the {@code Mono} from Reactor {@link Context}
// *
// * @return the {@code Mono}
// */
// public static Mono getExchange() {
// return Mono.subscriberContext()
// .map(ctx -> ctx.get(CONTEXT_KEY));
// }
//
// /**
// * Gets the {@code Mono} from Reactor {@link Context}
// *
// * @return the {@code Mono}
// */
// public static Mono getRequest() {
// return ReactiveRequestContextHolder.getExchange()
// .map(ServerWebExchange::getRequest);
// }
//
// /**
// * Put the {@code ServerWebExchange} to Reactor {@link Context}
// *
// * @param context Context
// * @param exchange ServerWebExchange
// * @return the Reactor {@link Context}
// */
// public static Context put(Context context, ServerWebExchange exchange) {
// return context.put(CONTEXT_KEY, exchange);
// }
private static final ThreadLocal<ServerHttpRequest> requests = new NamedThreadLocal<>("Thread ServerHttpRequest");
/**
* Store {@link ServerHttpRequest} to {@link ThreadLocal} in the current thread
*
* @param request {@link ServerHttpRequest}
*/
public static void put(ServerHttpRequest request){
//When the request time out, the reset will be invalid
//because the timeout thread is the thread of hystrix, not the thread of HTTP
if(Objects.nonNull(get())) {
reset();
}
if(request != null){
requests.set(request);
}
}
/**
* Get the current thread {@link ServerHttpRequest} from {@link ThreadLocal}
*
* @return {@link ServerHttpRequest}
*/
public static ServerHttpRequest get(){
ServerHttpRequest request = requests.get();
return request;
}
/**
* Clear the current thread {@link ServerHttpRequest} from {@link ThreadLocal}
*/
public static void reset(){
requests.remove();
}
}
package com.taylor.gateway.config.annotation;
import lombok.Getter;
/**
*
* 参数名
*/
@Getter
public enum Param {
/**
* 来源系统
*/
PLATFORM("Platform"),
/**
* token
*/
ACCESSTOKEN("Authentication"),
/**
* 身份ID
*/
IDENTITYID("IdentityID"),
/**
* 账号ID
*/
USERID("userId"),
/**
* 账号编号
*/
USERNO("userNo"),
/**
* 用户名
*/
USERNAME("userName");
String name;
Param(String name) {
this.name = name;
}
}
package com.taylor.gateway.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* 指定要注入的参数
*
*/
@Target({
ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionParam {
Param name();
boolean required() default true;
}
package com.taylor.gateway.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* 注解切入点
*
*/
@Target({
ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionParamAutowired {
}
package com.taylor.gateway.config.annotation;
import com.taylor.gateway.config.context.ReactiveRequestContextHolder;
import com.taylor.gateway.config.exception.MissingServerRequestParameterException;
import com.taylor.gateway.security.token.JwtAccessTokenInfo;
import com.taylor.gateway.security.token.JwtClientUserInfo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import java.lang.reflect.Parameter;
import java.util.Objects;
/**
*
* 对接口进行切面进行参数的注入,使其能在接口中正常获取到需要的常用参数
* 目前仅对常规参数类型添加了支持
*
*/
@Aspect
@Component
public class SessionParamWrapper {
@Autowired
private RedisTemplate redisTemplate;
@Pointcut("@within(com.taylor.gateway.config.annotation.SessionParamAutowired) || @annotation(com.taylor.gateway.config.annotation.SessionParamAutowired)")
public void annotationWrapper(){
}
@Around("annotationWrapper()")
public Object annotationHandler(ProceedingJoinPoint joinPoint) throws Throwable {
ServerHttpRequest request = ReactiveRequestContextHolder.get();
Objects.requireNonNull(request, "Cannot retrieve the current request from the context");
Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < parameters.length; i++) {
SessionParam sessionParam = parameters[i].getAnnotation(SessionParam.class);
if (Objects.nonNull(sessionParam)) {
Param name = sessionParam.name();
Object value = this.getValue(name, sessionParam.required(), request);
args[i] = value;
}
}
return joinPoint.proceed(args);
}
private Object getValue(Param name, boolean required, ServerHttpRequest request) {
Object value = null;
if (Param.PLATFORM == name) {
value = request.getHeaders().getFirst(Param.PLATFORM.getName());
} else if (Param.IDENTITYID == name) {
value = request.getHeaders().getFirst(Param.IDENTITYID.getName());
} else if (Param.ACCESSTOKEN == name) {
value = request.getHeaders().getFirst(Param.ACCESSTOKEN.getName());
} else {
//获取用户相关信息
//这里要改一下 不应该每次获取值都去序列化一次 如果需要用户信息的地方比较多 就提前进行获取
String jti = request.getHeaders().getFirst(Param.IDENTITYID.getName());
Objects.requireNonNull(jti, "Unable to obtain identity id from request information");
Object obj = redisTemplate.opsForValue().get(jti);
Objects.requireNonNull(obj, "Unable to read information based on identity id, probably because id has expired");
JwtAccessTokenInfo tokenInfo = (JwtAccessTokenInfo) obj;
JwtClientUserInfo userInfo = tokenInfo.getUserInfo();
switch (name) {
case USERID : value = userInfo.getUserId(); break;
case USERNO : value = userInfo.getUserNo(); break;
case USERNAME : value = userInfo.getUsername(); break;
default:
break;
}
}
if (value == null && required) {
throw new MissingServerRequestParameterException
("Cannot get the required value from the request information, -> " + name.getName());
}
return value;
}
}
这里注解的功能较为简单,后续将设计更多功能,实现map以及pojo对象属性的注入.