10.28websocket模块修改

dev
xiaohuo 8 months ago
parent 0950044a88
commit 7aa07df754
  1. 4
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/config/SecurityConfig.java
  2. 23
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/config/WebSocketConfig.java
  3. 65
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/config/WebSocketHandshakeInterceptor.java
  4. 43
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/hander/WebSocketServer.java
  5. 98
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/web/service/TokenService.java
  6. 5
      ALOps_sys_backend/alops-system/pom.xml
  7. 153
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/hander/WebSocketServer.java
  8. 11
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertSenderServiceImpl.java
  9. 63
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/WebSocketService.java

@ -115,7 +115,9 @@ public class SecurityConfig
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**","/swagger-ui/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.antMatchers("/ws/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter

@ -1,20 +1,27 @@
package com.alops.system.config;
package com.alops.framework.config;
import com.alops.system.hander.WebSocketServer;
import com.alops.framework.hander.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
//理解为WebSocket的controller
//注册端点,允许跨域
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private WebSocketServer webSocketServer;
@Autowired
private WebSocketHandshakeInterceptor handshakeInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketServer(), "/ws/onlineMessage")
registry.addHandler(webSocketServer, "/ws/onlineMessage")
.addInterceptors(handshakeInterceptor)
.setAllowedOrigins("*");
}
}
}

@ -0,0 +1,65 @@
package com.alops.framework.config;
import com.alops.common.core.domain.model.LoginUser;
import com.alops.framework.web.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.List;
import java.util.Map;
//拦截器设置,首先在springscrity放行
@Slf4j
@Component
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
@Autowired
private TokenService tokenService;
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes) {
try {
List<String> protocols = request.getHeaders().get("Sec-WebSocket-Protocol");
if (protocols == null || protocols.isEmpty()) {
log.warn("【WS握手失败】未携带Token");
return false;
}
String token = protocols.get(0);
LoginUser loginUser = tokenService.getLoginUser(token);
if (loginUser == null) {
log.warn("【WS握手失败】无效Token");
return false;
}
// 验证并刷新 Token
tokenService.verifyToken(loginUser);
// 设置返回协议头(必须,否则浏览器报错)
response.getHeaders().add("Sec-WebSocket-Protocol", token);
// 把用户信息写入 session attributes
attributes.put("loginUser", loginUser);
log.info("【WS握手成功】用户:{}", loginUser.getUsername());
return true;
} catch (Exception e) {
log.error("【WS握手异常】", e);
return false;
}
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
}
}

@ -0,0 +1,43 @@
package com.alops.framework.hander;
import com.alops.common.core.domain.model.LoginUser;
import com.alops.system.service.impl.WebSocketService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.slf4j.LoggerFactory;
@Component
public class WebSocketServer extends TextWebSocketHandler {
@Autowired
private WebSocketService webSocketService;
private final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
LoginUser loginUser = (LoginUser) session.getAttributes().get("loginUser");
if (loginUser != null && loginUser.getUser() != null) {
Long userId = loginUser.getUser().getUserId();
webSocketService.addOnlineUser(userId, session);
logger.info("WebSocket 连接成功,用户上线: {}", userId);
} else {
logger.warn("无效连接,关闭 session: {}", session.getId());
session.close();
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
LoginUser loginUser = (LoginUser) session.getAttributes().get("loginUser");
if (loginUser != null && loginUser.getUser() != null) {
Long userId = loginUser.getUser().getUserId();
webSocketService.removeOnlineUser(userId);
logger.info("WebSocket 连接关闭,用户下线: {}, 状态: {}", userId, status);
}
}
}

@ -31,8 +31,7 @@ import io.jsonwebtoken.SignatureAlgorithm;
* @author ruoyi
*/
@Component
public class TokenService
{
public class TokenService {
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
// 令牌自定义标识
@ -58,17 +57,14 @@ public class TokenService
/**
* 获取用户身份信息
*
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
if (StringUtils.isNotEmpty(token)) {
try {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
@ -78,22 +74,42 @@ public class TokenService
LoginUser user = JSON.parseObject(userJson, LoginUser.class);
return user;
}
catch (Exception e)
{
} catch (Exception e) {
log.error("获取用户信息异常'{}'", e.getMessage());
}
}
return null;
}
public LoginUser getLoginUser(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
// 去掉可能的 "token-" 前缀
if (token.startsWith("token-")) {
token = token.substring(6);
}
try {
Claims claims = parseToken(token);
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
Object userObj = redisCache.getCacheObject(userKey);
String userJson = JSON.toJSONString(userObj);
return JSON.parseObject(userJson, LoginUser.class);
} catch (Exception e) {
log.error("获取用户信息异常'{}'", e.getMessage());
return null;
}
}
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser)
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
public void setLoginUser(LoginUser loginUser) {
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
refreshToken(loginUser);
}
}
@ -101,10 +117,8 @@ public class TokenService
/**
* 删除用户身份信息
*/
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
public void delLoginUser(String token) {
if (StringUtils.isNotEmpty(token)) {
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
@ -112,12 +126,11 @@ public class TokenService
/**
* 创建令牌
*
*
* @param loginUser 用户信息
* @return 令牌
*/
public String createToken(LoginUser loginUser)
{
public String createToken(LoginUser loginUser) {
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
@ -131,27 +144,24 @@ public class TokenService
/**
* 验证令牌有效期相差不足20分钟自动刷新缓存
*
*
* @param loginUser 登录信息
* @return 令牌
*/
public void verifyToken(LoginUser loginUser)
{
public void verifyToken(LoginUser loginUser) {
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY)
{
if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) {
refreshToken(loginUser);
}
}
/**
* 刷新令牌有效期
*
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
public void refreshToken(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
@ -161,11 +171,10 @@ public class TokenService
/**
* 设置用户代理信息
*
*
* @param loginUser 登录信息
*/
public void setUserAgent(LoginUser loginUser)
{
public void setUserAgent(LoginUser loginUser) {
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip);
@ -180,8 +189,7 @@ public class TokenService
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims)
{
private String createToken(Map<String, Object> claims) {
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
@ -194,8 +202,7 @@ public class TokenService
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
private Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
@ -208,8 +215,7 @@ public class TokenService
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
{
public String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.getSubject();
}
@ -220,18 +226,16 @@ public class TokenService
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
private String getToken(HttpServletRequest request) {
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private String getTokenKey(String uuid)
{
private String getTokenKey(String uuid) {
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
}
}
}

@ -92,10 +92,7 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>

@ -1,153 +0,0 @@
package com.alops.system.hander;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class WebSocketServer extends TextWebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
// 存储在线用户 (Key: 用户ID, Value: WebSocketSession)
public static final ConcurrentHashMap<Long, WebSocketSession> onlineUsers = new ConcurrentHashMap<>();
private static ObjectMapper objectMapper;
@Autowired
public void setObjectMapper(ObjectMapper mapper) {
WebSocketServer.objectMapper = mapper;
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
Long userId = getUserIdFromSession(session);
if (userId != null) {
logger.info("WebSocket 连接成功,用户上线: " + userId);
onlineUsers.put(userId, session);
} else {
logger.warn("无效的用户连接,关闭连接: " + session.getId());
session.close(); // 没有 userId 的连接直接关闭
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
Long userId = getUserIdFromSession(session);
if (userId != null) {
logger.info("WebSocket 连接关闭,用户下线: " + userId + ", 关闭状态: " + status);
onlineUsers.remove(userId);
}
}
// 获取 WebSocketSession 中的 userId,并转换为 Long 类型
public static Long getUserIdFromSession(WebSocketSession session) {
String query = session.getUri().getQuery();
logger.info("WebSocket连接查询参数: {}", query);
if (query != null) {
MultiValueMap<String, String> params = UriComponentsBuilder.newInstance()
.query(query)
.build()
.getQueryParams();
String userIdStr = params.getFirst("userId");
if (userIdStr != null) {
try {
return Long.parseLong(userIdStr);
} catch (NumberFormatException e) {
logger.error("WebSocket连接无效的用户ID: {}", userIdStr, e);
}
} else {
logger.warn("WebSocket连接没有 userId 参数");
}
} else {
logger.warn("WebSocket连接没有查询参数");
}
return null;
}
//
// // 发送消息给指定用户
// public void sendMessageToUser(Long userId, String content, String title) throws Exception {
// // 获取对应用户的 WebSocket session
// WebSocketSession session = onlineUsers.get(userId);
//
// // 检查 session 是否有效且连接未关闭
// if (session != null && session.isOpen()) {
// // 创建一个 Map 来构建消息内容
// Map<String, Object> messageMap = new HashMap<>();
// messageMap.put("from UserName", fromUserName); // 发送者姓名
// messageMap.put("content", content); // 消息内容
//
// // 使用 Jackson ObjectMapper 将 Map 转换为 JSON 字符串
// ObjectMapper objectMapper = new ObjectMapper();
// String jsonMessage = objectMapper.writeValueAsString(messageMap);
//
// // 发送 JSON 格式的消息
// session.sendMessage(new TextMessage(jsonMessage));
// }
// }
/**
* 发送通知到指定用户
* @param messageId 数据库中站内信的ID
* @param title 通知标题例如 "设备异常预警"
* @param content 通知内容
*/
public static void sendAlertMessage(Long userId, Long messageId, String title, String content) {
WebSocketSession session = onlineUsers.get(userId);
try {
if (session != null && session.isOpen()) {
// 构建消息内容
Map<String, Object> messageMap = new HashMap<>();
messageMap.put("messageId", messageId); // 对应数据库ID
messageMap.put("title", title); // 弹窗标题
messageMap.put("content", content); // 弹窗内容
messageMap.put("timestamp", new Date()); // 发送时间
// 转换为 JSON
String jsonMessage = objectMapper.writeValueAsString(messageMap);
// 发送
session.sendMessage(new TextMessage(jsonMessage));
logger.info("成功向用户 [{}] 发送通知: {}", session.getId(), jsonMessage);
}
} catch (Exception e) {
logger.error("发送通知失败: messageId=" + messageId, e);
}
}
public void sendMessageToUser1(Long userId, String message) throws Exception {
WebSocketSession session = onlineUsers.get(userId);
if (session != null && session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
}
// 获取所有在线用户 ID (Long 类型)
public Set<Long> getOnlineUserIds() {
return onlineUsers.keySet();
}
}

@ -1,13 +1,16 @@
package com.alops.system.service.impl;
import com.alops.common.core.domain.entity.SysUser;
import com.alops.system.domain.AlertMessage;
import com.alops.system.domain.InternalMessage;
import com.alops.system.hander.WebSocketServer;
import com.alops.system.mapper.InternalMessageMapper;
import com.alops.system.mapper.SysUserMapper;
import com.alops.system.service.IAlertSenderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -31,6 +34,8 @@ public class AlertSenderServiceImpl implements IAlertSenderService {
@Autowired
private InternalMessageMapper messageMapper; // 站内信 Mapper
@Autowired
private WebSocketService webSocketService;
@Value("${spring.mail.username}")
private String from;
@ -127,7 +132,7 @@ public class AlertSenderServiceImpl implements IAlertSenderService {
Long messageId = (Long) item.get("id");
// 调用 WebSocket 工具发送
WebSocketServer.sendAlertMessage(userId, messageId, title, content);
webSocketService.sendAlertMessage(userId, messageId, title, content);
}
log.info("共发送站内信 {} 条(角色ID={})", result.size(), roleId);

@ -0,0 +1,63 @@
package com.alops.system.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
//负责在线用户
@Service
public class WebSocketService {
private final Map<Long, WebSocketSession> onlineUsers = new ConcurrentHashMap<>();
@Autowired
private ObjectMapper objectMapper;
// 添加在线用户
public void addOnlineUser(Long userId, WebSocketSession session) {
onlineUsers.put(userId, session);
}
// 移除在线用户
public void removeOnlineUser(Long userId) {
onlineUsers.remove(userId);
}
// 获取在线用户
public Set<Long> getOnlineUserIds() {
return onlineUsers.keySet();
}
// 给指定用户发送通知
public void sendAlertMessage(Long userId, Long messageId, String title, String content) {
WebSocketSession session = onlineUsers.get(userId);
try {
if (session != null && session.isOpen()) {
Map<String, Object> messageMap = new HashMap<>();
messageMap.put("messageId", messageId);
messageMap.put("title", title);
messageMap.put("content", content);
messageMap.put("timestamp", new Date());
String jsonMessage = objectMapper.writeValueAsString(messageMap);
session.sendMessage(new TextMessage(jsonMessage));
LoggerFactory.getLogger(WebSocketService.class)
.info("成功向用户 [{}] 发送通知: {}", userId, jsonMessage);
}
} catch (Exception e) {
LoggerFactory.getLogger(WebSocketService.class)
.error("发送通知失败: messageId=" + messageId, e);
}
}
}
Loading…
Cancel
Save