场景
在单体架构的项目中,可以将用户信息封装在JWT的token中,其他业务在拦截器进行身份认证的同时将token中的用户信息取出并存入ThreadLocal中,由于一条请求从开始到结束始终为同一个线程,因此当需要用到用户信息时,直接从ThreadLocal中获取即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class UserContext { private static final ThreadLocal<Long> tl = new ThreadLocal<>();
public static void setUser(Long userId) { tl.set(userId); }
public static Long getUser() { return tl.get(); }
public static void removeUser(){ tl.remove(); } }
|
而在微服务架构中,不同的业务部署在不同服务上,如果一个请求涉及到了多个业务,当前服务在运行过程中势必要调用其他服务,有两种方案:
- 同步阻塞方案,例如openFeign向其他服务发起请求;
- 异步调用方案,可以采用消息队列的方式发布消息到指定服务,指定服务收到消息后处理。
不同的服务使用不同的Tomcat,因此不可能直接从ThreadLocal中获取信息。
同步调用中的用户信息传递
如果采用openFeign,可以在发起请求前向header中写入希望传递的信息,这样其他服务就能再次通过拦截器获取到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class DefaultFeignConfig { @Bean public RequestInterceptor userInfoInterceptor(){ return new RequestInterceptor() { @Override public void apply(RequestTemplate template) { Long userId = UserContext.getUser(); if(userId != null) { template.header("user-info", userId.toString()); } } }; } }
|
异步调用中的用户信息传递
如果采用消息队列,生产者可以在传递消息的同时传递用户相关信息给消费者。
问题
现在某微服务架构项目需要根据登录用户信息处理业务,而基于MQ的异步调用并不会传递登录用户信息。常规做法比较麻烦,至少要做两件事:
- 消息发送者在消息体中传递登录用户
- 消费者获取消息体中的登录用户,处理业务
应当怎样使得生产者和消费者都无需编写传递信息的相关代码,可以直接从UserContext中获取信息?
解决方案
可以采用静态代理的方式,通过实现 MessageConverter
接口的代理类来代理 Jackson2JsonMessageConverter
消息转换器类,并增强其 toMessage
和 fromMessage
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Configuration @ConditionalOnClass(RabbitTemplate.class) public class MqConfig {
@Bean public MessageConverter messageConverter(){ Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); jackson2JsonMessageConverter.setCreateMessageIds(true); return new AuthMessageConverter(jackson2JsonMessageConverter); }
static class AuthMessageConverter implements MessageConverter { private final MessageConverter delegate;
public AuthMessageConverter(MessageConverter delegate){ this.delegate = delegate; }
@Override public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException { Map<String, Object> headers = messageProperties.getHeaders(); Long user = UserContext.getUser(); if(user != null){ headers.put("user-info", user); } return delegate.toMessage(object, messageProperties); }
@Override public Object fromMessage(Message message) throws MessageConversionException { Map<String, Object> headers = message.getMessageProperties().getHeaders(); if(headers.containsKey("user-info")){ UserContext.setUser((Long) headers.get("user-info")); } return delegate.fromMessage(message); } } }
|
方案优势
- 透明性:生产者和消费者无需手动传递用户信息,直接从
UserContext
中获取。
- 扩展性:通过代理模式增强消息转换器,不影响原有逻辑。
- 安全性:用户信息通过消息头传递,避免了直接暴露用户数据。
__END__