WebSocketConfig
java
@Configuration
public class WebSocketConfig {
private static final Logger logger = LoggerFactory.getLogger(WebSocketConfig.class);
@Bean
public ServerEndpointExporter serverEndpointExporter (){
return new ServerEndpointExporter();
}
}
RedisConfig
java
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("192.168.124.91"); // Redis服务器地址
config.setPort(6379); // Redis服务器端口
return new JedisConnectionFactory(config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 如果需要操作hash结构的数据,还可以设置hash的key和value的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 可以添加多个 messageListener,配置不同的交换机
container.addMessageListener(listenerAdapter, new PatternTopic("showNewestMsg"));// 订阅最新消息频道
return container;
}
@Bean
public MessageListenerAdapter listenerAdapter(RedisReceiver receiver) {
// 消息监听适配器
return new MessageListenerAdapter(receiver, "onMessage");
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
RedisReceiver
java
@Component
public class RedisReceiver implements MessageListener {
private static Logger logger = LoggerFactory.getLogger(RedisReceiver.class);
@Autowired
WebSocketServer webSocketServer;
/**
* 处理接收到的订阅消息
*/
@Override
public void onMessage(Message message, byte[] pattern) {
String channel = new String(message.getChannel());// 订阅的频道名称
String msg = "";
try {
msg = new String(message.getBody(), StandardCharsets.UTF_8);//注意与发布消息编码一致,否则会乱码
if (StringUtils.isNotBlank(msg)) {
if ("showNewestMsg".endsWith(channel))// 最新消息
{
JSONObject json = JSONObject.parseObject(msg);
webSocketServer.sendMessage(json.getString("receive"), json.getString("msg"));
} else {
//TODO 其他订阅的消息处理
}
} else {
logger.info("消息内容为空,不处理。");
}
} catch (Exception e) {
logger.error("处理消息异常:" + e.getMessage());
e.printStackTrace();
}
}
}
WebSocketHandle
java
ServerEndpoint("/websocket/redis/{userId}")
@Component
public class WebSocketHandle {
private static Logger logger = LoggerFactory.getLogger(WebSocketHandle.class);
// 当前在线连接数
private static int onlineCount = 0;
// 存放每个用户对应的WebSocket连接对象,key为userId,确保一个登录用户只建立一个连接
private static Map<String, Session> webSocketSessionMap = new ConcurrentHashMap<String, Session>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
// 接收用户id
private String userId = "";
private static WebSocketHandle webSocketServer;
// 通过@PostConstruct实现初始化bean之前进行的操作
@PostConstruct
public void init() {
// 初使化时将已静态化的webSocketServer实例化
webSocketServer = this;
}
/**
* 连接建立成功调用的方法
*
* @param session 连接会话,由框架创建
* @param userId 用户id, 为处理用户多点登录都能收到消息,需传该格式userId
* @author csf
* @date 2020年8月10日
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
if (!webSocketSessionMap.containsKey(userId)) {
this.session = session;
webSocketSessionMap.put(userId, session);
addOnlineCount(); // 在线数加1
logger.info("有新连接[{}]接入,当前websocket连接数为:{}", userId, getOnlineCount());
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam("userId") String sessionKey) {
if (webSocketSessionMap.containsKey(sessionKey)) {
try {
webSocketSessionMap.get(sessionKey).close();
webSocketSessionMap.remove(sessionKey);
} catch (IOException e) {
logger.error("连接[{}]关闭失败。", sessionKey);
e.printStackTrace();
}
subOnlineCount();
logger.info("连接[{}]关闭,当前websocket连接数:{}", sessionKey, onlineCount);
}
}
/**
* 接收客户端发送的消息
*
* @param message 客户端发送过来的消息
* @param session websocket会话
*/
@OnMessage
public void onMessage(String message, Session session) {
logger.info("收到来自客户端" + userId + "的信息:" + message);
}
/**
* 连接错误时触发
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
try {
session.close();
} catch (IOException e) {
logger.error("发生错误,连接[{}]关闭失败。");
e.printStackTrace();
}
// log.error("websocket发生错误");
// error.printStackTrace();
}
/**
* 给指定的客户端推送消息,可单发和群发
*
* @param sessionKeys 发送消息给目标客户端sessionKey,多个逗号","隔开1234,2345...
* @param message
* @throws IOException
* @author csf
* @date 2020年8月11日
*/
public void sendMessage(String sessionKeys, String message) {
if (StringUtils.isNotBlank(sessionKeys)) {
String[] sessionKeyArr = sessionKeys.split(",");
for (String key : sessionKeyArr) {
try {
// 可能存在一个账号多点登录
List<Session> sessionList = getLikeByMap(webSocketSessionMap, key);
for (Session session : sessionList) {
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
e.printStackTrace();
continue;// 某个客户端发送异常,不影响其他客户端发送
}
}
} else {
logger.info("sessionKeys为空,没有目标客户端");
}
}
/**
* 给当前客户端推送消息,首次建立连接时调用
*/
public void sendMessage(String message)
throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 检查webSocket连接是否在线
*
* @param sesstionKey webSocketMap中维护的key
* @return 是否在线
*/
public static boolean checkOnline(String sesstionKey) {
if (webSocketSessionMap.containsKey(sesstionKey)) {
return true;
} else {
return false;
}
}
/**
* 获取包含key的所有map值
*
* @param map
* @param keyLike
* @return
* @author csf
* @date 2020年8月13日
*/
private List<Session> getLikeByMap(Map<String, Session> map, String keyLike) {
List<Session> list = new ArrayList<>();
for (String key : map.keySet()) {
if (key.contains(keyLike)) {
list.add(map.get(key));
}
}
return list;
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketHandle.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketHandle.onlineCount--;
}
}
RedisTestController
java
@RequestMapping("redis")
@RestController
public class RedisTestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 发布消息测试
*
* @param userId
* @param msg
* @return
*/
@PostMapping("sendMessage")
public String sendMessage(@RequestParam String userId, @RequestParam String msg) {
String newMessge = new String(msg.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
Map<String, String> map = new HashMap<>();
map.put("receive", userId);
map.put("msg", newMessge);
String json = JSONObject.toJSONString(map);//map转String
JSONObject jsonObject = JSONObject.parseObject(json);
stringRedisTemplate.convertAndSend("showNewestMsg", jsonObject.toJSONString());
return "消息发布成功!";
}
}
补充
1.java.lang.NoClassDefFoundError: redis/clients/jedis/util/Pool
xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.1</version>
</dependency>
版本不一致引起的,
jedis版本改为3.6.0
xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.1</version>
</dependency>