Skip to content

用户目的地 (User Destinations)

在实时应用中,经常需要向特定用户(而不是所有人)发送消息。Spring 的 STOMP 支持通过 /user/ 前缀识别这类目的地。

客户端订阅

客户端可以订阅以 /user/ 为前缀的目的地。 例如:订阅 /user/queue/position-updates

服务器端的 UserDestinationMessageHandler 会捕获此类订阅,并将其转换为该用户会话特有的唯一目的地(例如 /queue/position-updates-user123)。这避免了不同用户订阅相同名称的主题时发生消息冲突(即每个用户收到的都是属于自己的仓位更新)。

TIP

使用用户目的地时,务必正确配置代理前缀(Broker Prefixes)和应用目的地前缀。否则,如果不匹配,消息可能会错误地直接飞到外部代理而没被 Spring 拦截处理。

服务器端发送

服务器端组件可以向 /user/{username}/queue/position-updates 发送消息。UserDestinationMessageHandler 会将其解析为该用户关联的所有会话(Session)对应的真实目的地。

1. 使用 @SendToUser 注解

在控制器方法中,你可以使用此注解将返回值直接发送给当前用户:

java
@Controller
public class PortfolioController {

	@MessageMapping("/trade")
	@SendToUser("/queue/position-updates")
	public TradeResult executeTrade(Trade trade, Principal principal) {
		// 交易逻辑...
		return tradeResult;
	}
}
  • broadcast 属性: 默认值为 true(如果用户在多个浏览器页签登录,所有页签都会收到)。设为 false 则仅发送给引发当前请求的那个特定会话。

2. 使用 SimpMessagingTemplate

你可以在任何地方(如 Service)编程式地发送:

java
@Service
public class TradeServiceImpl implements TradeService {

	@Autowired
	private SimpMessagingTemplate messagingTemplate;

	public void afterTradeExecuted(Trade trade) {
		this.messagingTemplate.convertAndSendToUser(
				trade.getUserName(), "/queue/position-updates", trade.getResult());
	}
}

补充教学

1. 它是如何区分不同用户的?

它依赖于 java.security.Principal 对象。如果你的应用没有通过 Spring Security 登录,Spring 会为每个会话生成一个匿名的随机 ID 替代。

2. 多节点环境下的挑战

如果用户 A 连在 Server 1,而你的发送指令在 Server 2 执行,Server 2 必须能够知道用户 A 在别处。因此,在分布式环境中,你可能需要配置 userDestinationBroadcast 属性,通过外部代理(如 RabbitMQ)广播这种“找人”请求。

3. 与外部代理协作时的队列清理

在使用 RabbitMQ 时,这种动态生成的目的地(如 /queue/position-updates-user123)可能产生临时队列。你应该利用 RabbitMQ 的 auto-delete 队列特性,确保用户断开后,这些私有队列被自动销毁。

Based on Spring Framework.