Commit 54b44882 authored by daniel's avatar daniel

fix(websocket): 使用redis解决多节点问题

parent 10ce27d3
package com.dlbridge.jadp.config.web; package com.dlbridge.jadp.config.web;
import com.dlbridge.jadp.websocket.WebSocketRedisSubscriber;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j; import lombok.extern.log4j.Log4j;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
...@@ -13,14 +13,14 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; ...@@ -13,14 +13,14 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.*; import org.springframework.data.redis.core.*;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPoolConfig;
import javax.annotation.Resource;
import java.util.List;
/** /**
* date : 2019/1/21 * date : 2019/1/21
* author : yangshuxing * author : yangshuxing
...@@ -40,6 +40,27 @@ public class RedisConfig { ...@@ -40,6 +40,27 @@ public class RedisConfig {
return config; return config;
} }
@Bean
public ChannelTopic topic() {
return new ChannelTopic("websocket:message");
}
@Bean
public RedisMessageListenerContainer redisContainer(
RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter,
ChannelTopic topic) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, topic);
return container;
}
@Bean
public MessageListenerAdapter listenerAdapter(WebSocketRedisSubscriber subscriber) {
return new MessageListenerAdapter(subscriber, "onMessage");
}
@Bean @Bean
@Profile("production") @Profile("production")
public RedisClusterConfiguration predisClusterConfiguration() { public RedisClusterConfiguration predisClusterConfiguration() {
...@@ -169,4 +190,6 @@ public class RedisConfig { ...@@ -169,4 +190,6 @@ public class RedisConfig {
return redisTemplate.opsForZSet(); return redisTemplate.opsForZSet();
} }
} }
...@@ -53,7 +53,7 @@ public class WebSocketMessage { ...@@ -53,7 +53,7 @@ public class WebSocketMessage {
return new WebSocketMessage(MessageType.INFO.getValue(), ticketNo, message, data); return new WebSocketMessage(MessageType.INFO.getValue(), ticketNo, message, data);
} }
// 转 JSON 字符串 // ✅ 转 JSON 字符串
public String toJson() { public String toJson() {
JSONObject obj = JSONUtil.createObj() JSONObject obj = JSONUtil.createObj()
.set("type", type) .set("type", type)
...@@ -66,4 +66,19 @@ public class WebSocketMessage { ...@@ -66,4 +66,19 @@ public class WebSocketMessage {
return obj.toString(); return obj.toString();
} }
// ✅ 从 JSON 字符串反序列化(自动修复多重转义问题)
public static WebSocketMessage fromJson(String jsonStr) {
if (jsonStr == null) {
return null;
}
// 处理双重转义的情况:比如 "{\"type\":\"success\"...}"
if (jsonStr.startsWith("\"") && jsonStr.endsWith("\"")) {
jsonStr = jsonStr.substring(1, jsonStr.length() - 1);
jsonStr = jsonStr.replace("\\\"", "\"");
}
return JSONUtil.toBean(jsonStr, WebSocketMessage.class);
}
} }
\ No newline at end of file
package com.dlbridge.jadp.websocket;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class WebSocketRedisSubscriber implements MessageListener {
private final WebSocketSessionService sessionService;
public WebSocketRedisSubscriber(WebSocketSessionService sessionService) {
this.sessionService = sessionService;
}
@Override
public void onMessage(Message message, byte[] pattern) {
try {
String msgStr = new String(message.getBody());
log.info("接收到 Redis 分发的 WebSocket 消息: {}", msgStr);
// ✅ 自动修复双引号问题
WebSocketMessage wsMsg = WebSocketMessage.fromJson(msgStr);
sessionService.sendToUser(wsMsg.getTicketNo(), wsMsg);
} catch (Exception e) {
log.error("Redis 消息处理异常: {}", e.getMessage(), e);
}
}
}
package com.dlbridge.jadp.websocket; package com.dlbridge.jadp.websocket;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.*; import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@Slf4j @Slf4j
@Service @Service
public class WebSocketSessionService { public class WebSocketSessionService {
private final RedisTemplate<String, Object> redisTemplate;
private final ChannelTopic topic;
public WebSocketSessionService(RedisTemplate<String, Object> redisTemplate, ChannelTopic topic) {
this.redisTemplate = redisTemplate;
this.topic = topic;
}
private final Map<String, String> sessionUserMap = new ConcurrentHashMap<>(); private final Map<String, String> sessionUserMap = new ConcurrentHashMap<>();
private final Map<String, Set<String>> userTicketsMap = new ConcurrentHashMap<>(); private final Map<String, Set<String>> userTicketsMap = new ConcurrentHashMap<>();
private final Map<String, String> ticketUserMap = new ConcurrentHashMap<>(); private final Map<String, String> ticketUserMap = new ConcurrentHashMap<>();
...@@ -68,7 +81,7 @@ public class WebSocketSessionService { ...@@ -68,7 +81,7 @@ public class WebSocketSessionService {
}); });
return sessionIds; return sessionIds;
} }
// 发送消息给客户
public void sendToUser(String userId, WebSocketMessage message) { public void sendToUser(String userId, WebSocketMessage message) {
String payload = message.toJson(); String payload = message.toJson();
getSessionIdsByUserId(userId).forEach(sessionId -> { getSessionIdsByUserId(userId).forEach(sessionId -> {
...@@ -77,13 +90,24 @@ public class WebSocketSessionService { ...@@ -77,13 +90,24 @@ public class WebSocketSessionService {
}); });
log.info("用户通知已发送 - 用户: {}, 类型: {}", userId, message.getType()); log.info("用户通知已发送 - 用户: {}, 类型: {}", userId, message.getType());
} }
// 发送核销验证成功的通知
public void sendVerificationSuccess(String ticketNo, String msg) { public void sendVerificationSuccess(String ticketNo, String msg) {
WebSocketMessage message = WebSocketMessage.success(ticketNo, msg);
String userId = ticketUserMap.get(ticketNo);
// 本地推送
sendLocal(ticketNo, message);
// if (userId == null) {
// 发布到 Redis,让其他节点同步
redisTemplate.convertAndSend(topic.getTopic(), message.toJson());
// }
}
// 本地推送消息给客户
private void sendLocal(String ticketNo, WebSocketMessage message) {
String userId = ticketUserMap.get(ticketNo); String userId = ticketUserMap.get(ticketNo);
if (userId != null) sendToUser(userId, WebSocketMessage.success(ticketNo, msg)); if (userId != null) sendToUser(userId, message);
else log.warn("未找到用户,无法发送核销消息 - 票号: {}", ticketNo); else log.warn("未找到用户,无法发送核销消息 - 票号: {}", ticketNo);
} }
// 发送错误消息给客户
public void sendError(String ticketNo, String errorMsg) { public void sendError(String ticketNo, String errorMsg) {
String userId = ticketUserMap.get(ticketNo); String userId = ticketUserMap.get(ticketNo);
if (userId != null) sendToUser(userId, WebSocketMessage.error(ticketNo, errorMsg)); if (userId != null) sendToUser(userId, WebSocketMessage.error(ticketNo, errorMsg));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment