From f0566a59699a919cc217816b99fdd6b5b814e638 Mon Sep 17 00:00:00 2001
From: chendaofei <857448963@qq.com>
Date: Tue, 24 Jun 2025 08:50:54 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
event_smart/pom.xml | 25 +++
.../controller/EventGateController.java | 44 ++++
.../LargeScreenCountController.java | 12 +
.../transientes/filter/JwtRequestFilter.java | 68 ++++++
.../transientes/security/AuthController.java | 63 ++++++
.../security/MyUserDetailsService.java | 36 +++
.../transientes/security/SecurityConfig.java | 56 +++++
.../event/transientes/utils/JwtUtil.java | 66 ++++++
.../websocket/WebSocketServer.java | 211 +++++++-----------
9 files changed, 452 insertions(+), 129 deletions(-)
create mode 100644 event_smart/src/main/java/com/njcn/gather/event/transientes/controller/EventGateController.java
create mode 100644 event_smart/src/main/java/com/njcn/gather/event/transientes/filter/JwtRequestFilter.java
create mode 100644 event_smart/src/main/java/com/njcn/gather/event/transientes/security/AuthController.java
create mode 100644 event_smart/src/main/java/com/njcn/gather/event/transientes/security/MyUserDetailsService.java
create mode 100644 event_smart/src/main/java/com/njcn/gather/event/transientes/security/SecurityConfig.java
create mode 100644 event_smart/src/main/java/com/njcn/gather/event/transientes/utils/JwtUtil.java
diff --git a/event_smart/pom.xml b/event_smart/pom.xml
index 20e192b6..b62b5466 100644
--- a/event_smart/pom.xml
+++ b/event_smart/pom.xml
@@ -64,6 +64,31 @@
21.6.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
+ runtime
+
+
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/controller/EventGateController.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/controller/EventGateController.java
new file mode 100644
index 00000000..45df1e8b
--- /dev/null
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/controller/EventGateController.java
@@ -0,0 +1,44 @@
+package com.njcn.gather.event.transientes.controller;
+
+import cn.hutool.json.JSONObject;
+import com.njcn.common.pojo.annotation.OperateInfo;
+import com.njcn.common.pojo.enums.response.CommonResponseEnum;
+import com.njcn.common.pojo.response.HttpResult;
+import com.njcn.gather.event.transientes.websocket.WebSocketServer;
+import com.njcn.web.controller.BaseController;
+import com.njcn.web.utils.HttpResultUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @Author: cdf
+ * @CreateTime: 2025-06-23
+ * @Description:
+ */
+@Api(tags = "暂降接收")
+@RequestMapping("accept")
+@RestController
+@RequiredArgsConstructor
+public class EventGateController extends BaseController {
+
+ private final WebSocketServer webSocketServer;
+
+
+ @OperateInfo
+ @GetMapping("/eventMsg")
+ @ApiOperation("接收远程推送的暂态事件")
+ @ApiImplicitParam(name = "eventMsg", value = "暂态事件json字符", required = true)
+ public HttpResult eventMsg(@RequestParam("msg") String msg) {
+ String methodDescribe = getMethodDescribe("eventMsg");
+ System.out.println(msg);
+
+ JSONObject jsonObject = new JSONObject(msg);
+ webSocketServer.sendMessageToUser("bbb",jsonObject.toString());
+ return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, jsonObject, methodDescribe);
+ }
+
+
+}
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/controller/LargeScreenCountController.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/controller/LargeScreenCountController.java
index b68d53ed..c53b0ebe 100644
--- a/event_smart/src/main/java/com/njcn/gather/event/transientes/controller/LargeScreenCountController.java
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/controller/LargeScreenCountController.java
@@ -78,4 +78,16 @@ public class LargeScreenCountController extends BaseController {
}
+
+
+
+
+
+
+
+
+
+
+
+
}
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/filter/JwtRequestFilter.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/filter/JwtRequestFilter.java
new file mode 100644
index 00000000..5e823eb6
--- /dev/null
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/filter/JwtRequestFilter.java
@@ -0,0 +1,68 @@
+package com.njcn.gather.event.transientes.filter;
+
+import com.njcn.gather.event.transientes.utils.JwtUtil;
+import io.jsonwebtoken.ExpiredJwtException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+@Slf4j
+public class JwtRequestFilter extends OncePerRequestFilter {
+
+ private final UserDetailsService userDetailsService;
+ private final JwtUtil jwtUtil;
+
+ public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
+ this.userDetailsService = userDetailsService;
+ this.jwtUtil = jwtUtil;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+ throws ServletException, IOException {
+
+ final String authorizationHeader = request.getHeader("Authorization");
+
+ String username = null;
+ String jwt = null;
+
+ if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
+ jwt = authorizationHeader.substring(7);
+ try {
+ username = jwtUtil.extractUsername(jwt);
+ } catch (Exception e) {
+ // 可以在这里处理令牌过期的情况
+ log.info("JWT Token 校验异常,可能过期了");
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ response.getWriter().write("Token expired");
+ return; // 终止请求处理
+ }
+ }
+
+ if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
+
+ if (jwtUtil.validateToken(jwt, userDetails)) {
+ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
+ new UsernamePasswordAuthenticationToken(
+ userDetails, null, userDetails.getAuthorities());
+ usernamePasswordAuthenticationToken
+ .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
+ }
+ }
+ chain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/security/AuthController.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/security/AuthController.java
new file mode 100644
index 00000000..6b93ee72
--- /dev/null
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/security/AuthController.java
@@ -0,0 +1,63 @@
+package com.njcn.gather.event.transientes.security;
+
+import com.njcn.common.pojo.exception.BusinessException;
+import com.njcn.gather.event.transientes.utils.JwtUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class AuthController {
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ @Autowired
+ private JwtUtil jwtUtil;
+
+ @PostMapping("/cn_authenticate")
+ public String createAuthenticationToken(@RequestBody AuthRequest authRequest) throws Exception {
+ try {
+ authenticationManager.authenticate(
+ new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
+ );
+ } catch (BadCredentialsException e) {
+ e.printStackTrace();
+ throw new BusinessException("Incorrect username or password");
+ }
+ final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername());
+ final String jwt = jwtUtil.generateToken(userDetails);
+ return jwt;
+ }
+}
+
+// 认证请求类
+class AuthRequest {
+ private String username;
+ private String password;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+}
\ No newline at end of file
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/security/MyUserDetailsService.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/security/MyUserDetailsService.java
new file mode 100644
index 00000000..3b5a9172
--- /dev/null
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/security/MyUserDetailsService.java
@@ -0,0 +1,36 @@
+package com.njcn.gather.event.transientes.security;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+
+@Service
+@RequiredArgsConstructor
+public class MyUserDetailsService implements UserDetailsService {
+
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ // 这里应该从数据库中获取用户信息,本示例使用硬编码用户
+ if ("cdf".equals(username)) {
+ PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+ String encodedPassword = passwordEncoder.encode("@#001njcnpqs");
+ return new User("cdf", encodedPassword,
+ new ArrayList<>());
+ }else if("screen".equals(username)){
+ PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+ String encodedPassword = passwordEncoder.encode("@#001njcnpqs");
+ return new User("screen", encodedPassword,
+ new ArrayList<>());
+ } else {
+ throw new UsernameNotFoundException("User not found with username: " + username);
+ }
+ }
+}
\ No newline at end of file
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/security/SecurityConfig.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/security/SecurityConfig.java
new file mode 100644
index 00000000..7e85b470
--- /dev/null
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/security/SecurityConfig.java
@@ -0,0 +1,56 @@
+package com.njcn.gather.event.transientes.security;
+
+import com.njcn.gather.event.transientes.filter.JwtRequestFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ @Autowired
+ private JwtRequestFilter jwtRequestFilter;
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.csrf().disable()
+ .authorizeRequests()
+ .antMatchers("/cn_authenticate").permitAll() // 允许访问认证接口
+ .anyRequest().authenticated()
+ .and()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 使用无状态会话
+
+ http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
+ }
+
+ @Bean
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
\ No newline at end of file
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/utils/JwtUtil.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/utils/JwtUtil.java
new file mode 100644
index 00000000..47941473
--- /dev/null
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/utils/JwtUtil.java
@@ -0,0 +1,66 @@
+package com.njcn.gather.event.transientes.utils;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import java.security.Key;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+@Component
+public class JwtUtil {
+
+ private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
+ private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 100000L; // 10小时
+
+ // 生成JWT令牌
+ public String generateToken(UserDetails userDetails) {
+ Map claims = new HashMap<>();
+ return createToken(claims, userDetails.getUsername());
+ }
+
+ private String createToken(Map claims, String subject) {
+ return Jwts.builder()
+ .setClaims(claims)
+ .setSubject(subject)
+ .setIssuedAt(new Date(System.currentTimeMillis()))
+ .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
+ .signWith(SECRET_KEY, SignatureAlgorithm.HS256)
+ .compact();
+ }
+
+ // 验证令牌
+ public Boolean validateToken(String token, UserDetails userDetails) {
+ final String username = extractUsername(token);
+ return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
+ }
+
+ // 提取用户名
+ public String extractUsername(String token) {
+ return extractClaim(token, Claims::getSubject);
+ }
+
+ // 提取过期时间
+ public Date extractExpiration(String token) {
+ return extractClaim(token, Claims::getExpiration);
+ }
+
+ private T extractClaim(String token, Function claimsResolver) {
+ final Claims claims = extractAllClaims(token);
+ return claimsResolver.apply(claims);
+ }
+
+ private Claims extractAllClaims(String token) {
+ return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
+ }
+
+ private Boolean isTokenExpired(String token) {
+ return extractExpiration(token).before(new Date());
+ }
+}
\ No newline at end of file
diff --git a/event_smart/src/main/java/com/njcn/gather/event/transientes/websocket/WebSocketServer.java b/event_smart/src/main/java/com/njcn/gather/event/transientes/websocket/WebSocketServer.java
index d81f3c8c..969f3528 100644
--- a/event_smart/src/main/java/com/njcn/gather/event/transientes/websocket/WebSocketServer.java
+++ b/event_smart/src/main/java/com/njcn/gather/event/transientes/websocket/WebSocketServer.java
@@ -1,6 +1,7 @@
package com.njcn.gather.event.transientes.websocket;
+import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@@ -12,6 +13,9 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
/**
* Description:
@@ -22,156 +26,105 @@ import java.util.concurrent.ConcurrentHashMap;
*/
@Slf4j
@Component
-@ServerEndpoint(value ="/api/pushMessage/{userIdAndLineIdAndDevId}")
+@ServerEndpoint(value = "/ws/{userId}")
public class WebSocketServer {
- /**
- * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
- */
- private static int onlineCount = 0;
- /**
- * concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
- */
- private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap<>();
- /**
- * 与某个客户端的连接会话,需要通过它来给客户端发送数据
- */
- private Session session;
- /**
- * 接收userId
- */
- private String userIdAndLineIdAndDevId = "";
+ private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>();
+ private final ScheduledExecutorService heartbeatExecutor = Executors.newScheduledThreadPool(1);
- /**
- * 连接建立成
- * 功调用的方法
- */
@OnOpen
- public void onOpen(Session session, @PathParam("userIdAndLineIdAndDevId") String userIdAndLineIdAndDevId) {
- this.session = session;
- this.userIdAndLineIdAndDevId = userIdAndLineIdAndDevId;
- if (webSocketMap.containsKey(userIdAndLineIdAndDevId)) {
- webSocketMap.remove(userIdAndLineIdAndDevId);
- //加入set中
- webSocketMap.put(userIdAndLineIdAndDevId, this);
+ public void onOpen(Session session, @PathParam("userId") String userId) {
+ if (StrUtil.isNotBlank(userId)) {
+ sessions.put(userId, session);
+ sendMessage(session, "连接成功");
+ System.out.println("用户 " + userId + " 已连接");
+
+ // 启动心跳检测
+ startHeartbeat(session, userId);
} else {
- //加入set中
- webSocketMap.put(userIdAndLineIdAndDevId, this);
- //在线数加1
- addOnlineCount();
+ try {
+ session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "用户ID不能为空"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
- sendMessage("连接成功");
}
- /**
- * 连接关闭
- * 调用的方法
- */
- @OnClose
- public void onClose() {
- if (webSocketMap.containsKey(userIdAndLineIdAndDevId)) {
- webSocketMap.remove(userIdAndLineIdAndDevId);
- //从set中删除
- subOnlineCount();
- }
- log.info("监测点退出:" + userIdAndLineIdAndDevId + ",当前在线监测点数为:" + getOnlineCount());
- }
-
- /**
- * 收到客户端消
- * 息后调用的方法
- *
- * @param message 客户端发送过来的消息
- **/
@OnMessage
- public void onMessage(String message, Session session) {
- //会每30s发送请求1次
- log.info("监测点消息:" + userIdAndLineIdAndDevId + ",报文:" + message);
- log.info("监测点连接:" + userIdAndLineIdAndDevId + ",当前在线监测点数为:" + getOnlineCount());
-
+ public void onMessage(String message, Session session, @PathParam("userId") String userId) {
+ if ("ping".equalsIgnoreCase(message)) {
+ // 处理心跳请求
+ sendMessage(session, "pong");
+ } else {
+ // 处理业务消息
+ System.out.println("收到用户 " + userId + " 的消息: " + message);
+ // TODO: 处理业务逻辑
+ }
}
+ @OnClose
+ public void onClose(Session session, CloseReason closeReason, @PathParam("userId") String userId) {
+ sessions.remove(userId);
+ System.out.println("用户 " + userId + " 已断开连接,状态码: " + closeReason.getReasonPhrase());
+ }
- /**
- * @param session
- * @param error
- */
@OnError
- public void onError(Session session, Throwable error) {
-
- log.error("监测点错误:" + this.userIdAndLineIdAndDevId + ",原因:" + error.getMessage());
- error.printStackTrace();
- }
-
- /**
- * 实现服务
- * 器主动推送
- */
- public void sendMessage(String message) {
+ public void onError(Session session, Throwable throwable, @PathParam("userId") String userId) {
+ System.out.println("用户 " + userId + " 发生错误: " + throwable.getMessage());
try {
- this.session.getBasicRemote().sendText(message);
+ session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "发生错误"));
} catch (IOException e) {
e.printStackTrace();
}
}
- /**
- * 发送自定
- * 义消息
- **/
- public static void sendInfo(String message, String lineId) {
- log.info("发送消息到:" + lineId + ",报文:" + message);
- if (StringUtils.isNotBlank(lineId)) {
- Map stringStringMap = WebSocketServer.filterMapByKey(webSocketMap, lineId);
- stringStringMap.forEach((k,v)->{
- webSocketMap.get(k).sendMessage(message);
-
- });
- } else {
- log.error("监测点" + lineId + ",不在线!");
- }
- }
-
- /**
- * 获得此时的
- * 在线监测点
- *
- * @return
- */
- public static synchronized int getOnlineCount() {
- return onlineCount;
- }
-
- /**
- * 在线监测点
- * 数加1
- */
- public static synchronized void addOnlineCount() {
- WebSocketServer.onlineCount++;
- }
-
- /**
- * 在线监测点
- * 数减1
- */
- public static synchronized void subOnlineCount() {
- WebSocketServer.onlineCount--;
- }
-
- /**
- * 过滤所有键包含指定字符串的条目
- * @param map 原始的Map
- * @param substring 要检查的子字符串
- * @return 过滤的Map
- */
- public static Map filterMapByKey(ConcurrentHashMap map, String substring) {
- Map result = new HashMap<>();
- for (Map.Entry entry : map.entrySet()) {
- if (entry.getKey().contains(substring)) {
- result.put(entry.getKey(), entry.getValue().toString());
+ public void sendMessageToUser(String userId, String message) {
+ Session session = sessions.get(userId);
+ if (session != null && session.isOpen()) {
+ try {
+ session.getBasicRemote().sendText(message);
+ } catch (IOException e) {
+ System.out.println("发送消息给用户 " + userId + " 失败: " + e.getMessage());
}
+ } else {
+ System.out.println("用户 " + userId + " 不在线或会话已关闭");
}
- return result;
}
+ public void sendMessageToAll(String message) {
+ sessions.forEach((userId, session) -> {
+ if (session.isOpen()) {
+ try {
+ session.getBasicRemote().sendText(message);
+ } catch (IOException e) {
+ System.out.println("发送消息给用户 " + userId + " 失败: " + e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void sendMessage(Session session, String message) {
+ try {
+ session.getBasicRemote().sendText(message);
+ } catch (IOException e) {
+ System.out.println("发送消息失败: " + e.getMessage());
+ }
+ }
+
+ private void startHeartbeat(Session session, String userId) {
+ heartbeatExecutor.scheduleAtFixedRate(() -> {
+ if (session.isOpen()) {
+ try {
+ session.getBasicRemote().sendText("ping");
+ } catch (IOException e) {
+ System.out.println("发送心跳给用户 " + userId + " 失败: " + e.getMessage());
+ try {
+ session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "心跳失败"));
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+ }, 10, 10, TimeUnit.SECONDS); // 每10秒发送一次心跳
+ }
}
\ No newline at end of file