场景

在单体架构的项目中,可以将用户信息封装在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<>();

/**
* 保存当前登录用户信息到ThreadLocal
* @param userId 用户id
*/
public static void setUser(Long userId) {
tl.set(userId);
}

/**
* 获取当前登录用户信息
* @return 用户id
*/
public static Long getUser() {
return tl.get();
}

/**
* 移除当前登录用户信息
*/
public static void removeUser(){
tl.remove();
}
}

而在微服务架构中,不同的业务部署在不同服务上,如果一个请求涉及到了多个业务,当前服务在运行过程中势必要调用其他服务,有两种方案:

  1. 同步阻塞方案,例如openFeign向其他服务发起请求;
  2. 异步调用方案,可以采用消息队列的方式发布消息到指定服务,指定服务收到消息后处理。

不同的服务使用不同的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 消息转换器类,并增强其 toMessagefromMessage 方法。

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(){
// 1.定义消息转换器
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
// 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
jackson2JsonMessageConverter.setCreateMessageIds(true);
// 3.对jackson消息转换器进行静态代理,扩展其fromMessage和toMessage方法
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);
}
}
}
方案优势
  1. 透明性:生产者和消费者无需手动传递用户信息,直接从 UserContext 中获取。
  2. 扩展性:通过代理模式增强消息转换器,不影响原有逻辑。
  3. 安全性:用户信息通过消息头传递,避免了直接暴露用户数据。

__END__