我们在实际开发中,常常用到一些基础参数,比如用户ID、终端版本等,在单体服务中controller层和service层都可以随时取用,基本上是用ThreadLocal实现的,相当方便。但是在微服务中相互调用时,JVM不是一个,甚至不到一台机器上,ThreadLocal肯定不能满足要求,如果都加到参数里传递,和业务参数又不能分离,那我们应该怎么处理呢?如果使用SpringCloud,把参数放到Header里就可以。
首先要有一个UserContext存储基础参数,其内部也是ThreadLocal实现的
package ai.peanut.common.aspects;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class UserContext {
public static final ThreadLocal
其次,请求上要加一个切面,收到请求之后要把前一个服务携带在Header的参数解析出来
package ai.peanut.common.aspects;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
/**
* @author fei
*/
@Aspect
@Order(85)
@Component
public class UserRequestAspect {
private static Logger logger = LoggerFactory.getLogger(UserRequestAspect.class);
@Pointcut("(@target(org.springframework.web.bind.annotation.RestController)) && (execution(public * ai.peanut..*.*(..)))")
public void executionService() {
}
/**
* 方法调用之前调用
*/
@Before("executionService()")
public void doBefore(){
logger.info("开始处理请求头部信息!");
UserContext.init();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
// 系统码
String platform = request.getHeader("x-platform");
if(StringUtils.isNotEmpty(platform)){
UserContext.putPlatform(platform);
}
// 直接调用方,上级服务
String directInvoker = request.getHeader("x-direct-invoker");
if(StringUtils.isNotEmpty(directInvoker)){
UserContext.putDirectInvoker(directInvoker);
}
// 通用用户信息
String userInfo = request.getHeader("x-user");
try {
if (userInfo != null) {
userInfo = URLDecoder.decode(userInfo, "UTF-8");//解码
}
} catch (UnsupportedEncodingException e) {
logger.info("处理通用用户信息时,发生错误");
userInfo = "{}";
}
if(StringUtils.isNotEmpty(userInfo)){
UserContext.putXUser(userInfo, platform);
}
// 客户端版本
String version = request.getHeader("x-version");
if(StringUtils.isNotEmpty(version)){
UserContext.putVersion(version);
}
}
/**
* 方法之后调用
*/
@AfterReturning(pointcut = "executionService()")
public void doAfterReturning(){
UserContext.remove();
}
}
这样收到的参数就能完好地解析并保存起来,但是调用下一个服务的时候怎么传递出去呢,Feign里其实已经有实现了,我们只需要添加一个Feign的配置就可以。
package ai.peanut.common.conf;
import ai.peanut.common.aspects.UserContext;
import feign.Feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(Feign.class)
public class DefaultFeignConfig implements RequestInterceptor {
@Value("${spring.application.name}")
private String appName;
@Override
public void apply(RequestTemplate requestTemplate)
{
String traceId = MDC.get("traceID");
if(StringUtils.isNotEmpty(traceId)){
requestTemplate.header("x-trace-id", traceId);
}
String xUser = UserContext.getXUser();
if(StringUtils.isNotEmpty(xUser)){
requestTemplate.header("x-user", xUser);
}
String platform = UserContext.getPlatform();
if(StringUtils.isNotEmpty(platform)){
requestTemplate.header("x-platform", platform);
}
String version = UserContext.getVersion();
if(StringUtils.isNotEmpty(version)){
requestTemplate.header("x-version", version);
}
if(StringUtils.isNotEmpty(appName)){
requestTemplate.header("x-direct-invoker", appName);
}
}
}
这样参数就会放到Header里传递到下一个服务,并且完成了接收参数--解析参数--存储参数--传输参数的循环,不管一次请求调用多少个服务,都可以用UserContext随时取用了。
如取终端版本,调用 String version = UserContext.getVersion(); 即可。