From f380b9fc10f4ed0ff818400a97f6e90485ff8916 Mon Sep 17 00:00:00 2001 From: liu Date: Tue, 5 Dec 2023 13:45:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=95=E7=82=B9=E7=99=BB=E5=BD=95=E8=B8=A2?= =?UTF-8?q?=E4=B8=8B=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/supervision/config/WebConfig.java | 1 + .../constant/UserTokenConstant.java | 2 + virtual-patient-web/pom.xml | 5 ++ .../VirtualPatientApplication.java | 2 + .../supervision/config/WebSocketConfig.java | 17 ++++ .../controller/WebSocketServer.java | 82 +++++++++++++++++++ .../usermanage/KickUserListener.java | 25 ++++++ .../supervision/usermanage/RedisConfig.java | 24 ++++++ 8 files changed, 158 insertions(+) create mode 100644 virtual-patient-web/src/main/java/com/supervision/config/WebSocketConfig.java create mode 100644 virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java create mode 100644 virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java create mode 100644 virtual-patient-web/src/main/java/com/supervision/usermanage/RedisConfig.java diff --git a/virtual-patient-common/src/main/java/com/supervision/config/WebConfig.java b/virtual-patient-common/src/main/java/com/supervision/config/WebConfig.java index 80e72538..fa9bd682 100644 --- a/virtual-patient-common/src/main/java/com/supervision/config/WebConfig.java +++ b/virtual-patient-common/src/main/java/com/supervision/config/WebConfig.java @@ -41,6 +41,7 @@ public class WebConfig implements WebMvcConfigurer { paths.add("/error"); paths.add("/favicon.ico"); paths.add("/user/login"); + paths.add("/webSocket/**"); // 开发环境,放开不校验token.每次修改这里需要重启(热部署不行) // paths.add("/**"); return paths; diff --git a/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java b/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java index a6e5d2fb..e3576264 100644 --- a/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java +++ b/virtual-patient-common/src/main/java/com/supervision/constant/UserTokenConstant.java @@ -3,4 +3,6 @@ package com.supervision.constant; public interface UserTokenConstant { String TOKEN_CACHE = "USER:LOGIN:TOKEN:"; + + String KICK_CHANNEL = "USER:KICK:CHANNEL"; } diff --git a/virtual-patient-web/pom.xml b/virtual-patient-web/pom.xml index 0d3bfcd5..34155a28 100644 --- a/virtual-patient-web/pom.xml +++ b/virtual-patient-web/pom.xml @@ -53,6 +53,11 @@ provided + + org.springframework.boot + spring-boot-starter-websocket + + diff --git a/virtual-patient-web/src/main/java/com/supervision/VirtualPatientApplication.java b/virtual-patient-web/src/main/java/com/supervision/VirtualPatientApplication.java index 2332487e..918f89d8 100644 --- a/virtual-patient-web/src/main/java/com/supervision/VirtualPatientApplication.java +++ b/virtual-patient-web/src/main/java/com/supervision/VirtualPatientApplication.java @@ -3,9 +3,11 @@ package com.supervision; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.socket.config.annotation.EnableWebSocket; @SpringBootApplication @MapperScan(basePackages = {"com.supervision.**.mapper"}) +@EnableWebSocket public class VirtualPatientApplication { public static void main(String[] args) { diff --git a/virtual-patient-web/src/main/java/com/supervision/config/WebSocketConfig.java b/virtual-patient-web/src/main/java/com/supervision/config/WebSocketConfig.java new file mode 100644 index 00000000..317b7d45 --- /dev/null +++ b/virtual-patient-web/src/main/java/com/supervision/config/WebSocketConfig.java @@ -0,0 +1,17 @@ +package com.supervision.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +public class WebSocketConfig { + + /** + * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint + */ + @Bean + public ServerEndpointExporter serverEndpointExporter(){ + return new ServerEndpointExporter(); + } +} diff --git a/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java b/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java new file mode 100644 index 00000000..d8ee4dfc --- /dev/null +++ b/virtual-patient-web/src/main/java/com/supervision/controller/WebSocketServer.java @@ -0,0 +1,82 @@ +package com.supervision.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicInteger; + +@Component +@Slf4j +@ServerEndpoint("/webSocket/{uid}") +public class WebSocketServer { + + //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 + private static final AtomicInteger onlineNum = new AtomicInteger(0); + + //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。 + private static final CopyOnWriteArraySet SESSION_POOL = new CopyOnWriteArraySet(); + + /** + * 有客户端连接成功 + */ + @OnOpen + public void onOpen(Session session, @PathParam(value = "uid") String uid) { + SESSION_POOL.add(session); + onlineNum.incrementAndGet(); + log.info(uid + "加入webSocket!当前人数为" + onlineNum); + } + + /** + * 连接关闭调用的方法 + */ + @OnClose + public void onClose(Session session) { + SESSION_POOL.remove(session); + int cnt = onlineNum.decrementAndGet(); + log.info("有连接关闭,当前连接数为:{}", cnt); + } + + /** + * 发送消息 + */ + public void sendMessage(Session session, String message) throws IOException { + if (session != null) { + synchronized (session) { + session.getBasicRemote().sendText(message); + } + } + } + + /** + * 群发消息 + */ + public void broadCastInfo(String message) throws IOException { + for (Session session : SESSION_POOL) { + if (session.isOpen()) { + sendMessage(session, message); + } + } + } + + /** + * 发生错误 + */ + @OnError + public void onError(Session session, Throwable throwable) { + log.error("发生错误"); + throwable.printStackTrace(); + } + + // 实现一个方法用于踢下线用户 + public void kickUser(String userId) { + SESSION_POOL.removeIf(session -> session.getUserPrincipal().getName().equals(userId)); + } +} diff --git a/virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java b/virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java new file mode 100644 index 00000000..29c8fa3f --- /dev/null +++ b/virtual-patient-web/src/main/java/com/supervision/usermanage/KickUserListener.java @@ -0,0 +1,25 @@ +package com.supervision.usermanage; + +import com.supervision.constant.UserTokenConstant; +import com.supervision.controller.WebSocketServer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + + +@Slf4j +@Component +public class KickUserListener implements MessageListener { + + @Autowired + private WebSocketServer webSocketServer; + + @Override + public void onMessage(Message message, byte[] pattern) { + String userId = message.toString(); + log.info("Redis的Channel:{}收到踢用户下线消息:{}", UserTokenConstant.KICK_CHANNEL, userId); + webSocketServer.kickUser(userId); + } +} diff --git a/virtual-patient-web/src/main/java/com/supervision/usermanage/RedisConfig.java b/virtual-patient-web/src/main/java/com/supervision/usermanage/RedisConfig.java new file mode 100644 index 00000000..68e38764 --- /dev/null +++ b/virtual-patient-web/src/main/java/com/supervision/usermanage/RedisConfig.java @@ -0,0 +1,24 @@ +package com.supervision.usermanage; + +import com.supervision.constant.UserTokenConstant; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; + +@Configuration +public class RedisConfig { + + @Autowired + private KickUserListener kickUserListener; + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(redisConnectionFactory); + container.addMessageListener(kickUserListener, new ChannelTopic(UserTokenConstant.KICK_CHANNEL)); + return container; + } +}