# Conflicts:
#	ALOps_sys_backend/alops-system/pom.xml
dev
YYD-YY 8 months ago
commit 5d69726a64
  1. 46
      ALOps_sys_backend/alops-admin/src/main/java/com/alops/web/controller/exceptionController/AlertMessageController.java
  2. 4
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/config/SecurityConfig.java
  3. 21
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/config/WebSocketConfig.java
  4. 65
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/config/WebSocketHandshakeInterceptor.java
  5. 43
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/hander/WebSocketServer.java
  6. 86
      ALOps_sys_backend/alops-framework/src/main/java/com/alops/framework/web/service/TokenService.java
  7. 1
      ALOps_sys_backend/alops-system/pom.xml
  8. 4
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/AlertMessage.java
  9. 3
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/EquBase.java
  10. 6
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/vo/homeEquAlertMessageVo.java
  11. 3
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/domain/vo/queryEquBaseVO.java
  12. 153
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/hander/WebSocketServer.java
  13. 1
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertLaunchServiceImpl.java
  14. 11
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/AlertSenderServiceImpl.java
  15. 79
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/ScheduleRulesServiceImpl.java
  16. 63
      ALOps_sys_backend/alops-system/src/main/java/com/alops/system/service/impl/WebSocketService.java
  17. 36
      ALOps_sys_backend/alops-system/src/main/resources/mapper/system/AlertMessageMapper.xml
  18. 7
      ALOps_sys_backend/alops-system/src/main/resources/mapper/system/EquBaseMapper.xml

@ -44,26 +44,26 @@ public class AlertMessageController extends BaseController
/**
* 立即执行预警带规则 ID 列表
*/
// @PreAuthorize("@ss.hasPermi('equipment:rules:execute')")
// @ApiOperation("立即执行预警")
// @Log(title = "【立即执行预警】", businessType = BusinessType.DELETE)
// @PostMapping("/executeImmediate")
// public AjaxResult executeImmediate(@RequestBody Map<String, List<Long>> paramMap) {
// try {
// List<Long> ruleIds = paramMap.get("ruleIds");
// if (ruleIds == null || ruleIds.isEmpty()) {
// return AjaxResult.error("请选择至少一条规则");
// }
//
// iAlertLaunchService.executeImmediateAlerts(); // 调用 Service 执行
// return AjaxResult.success("立即预警任务已触发,存入成功,请查看");
//
// } catch (Exception e) {
// e.printStackTrace();
// return AjaxResult.error("执行立即预警失败: " + e.getMessage());
// }
//
// }
@PreAuthorize("@ss.hasPermi('equipment:rules:execute')")
@ApiOperation("立即执行预警")
@Log(title = "【立即执行预警】", businessType = BusinessType.DELETE)
@PostMapping("/executeImmediate")
public AjaxResult executeImmediate(@RequestBody Map<String, List<Long>> paramMap) {
try {
List<Long> ruleIds = paramMap.get("ruleIds");
if (ruleIds == null || ruleIds.isEmpty()) {
return AjaxResult.error("请选择至少一条规则");
}
iAlertLaunchService.executeImmediateAlerts(); // 调用 Service 执行
return AjaxResult.success("立即预警任务已触发,存入成功,请查看");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("执行立即预警失败: " + e.getMessage());
}
}
/**
* 修改预警信息
@ -78,11 +78,11 @@ public class AlertMessageController extends BaseController
}
/**
* 查询预警信息统计列表
* 首页查询预警信息统计列表
*/
@PreAuthorize("@ss.hasPermi('inMonitoring:alertMessage:statistic:list')")
@GetMapping ("/statistic/list")
@ApiOperation("查询预警信息")
@ApiOperation("首页查询预警信息")
@Log(title = "查询预警信息", businessType = BusinessType.UPDATE)
public TableDataInfo homeList()
{
@ -93,7 +93,7 @@ public class AlertMessageController extends BaseController
/**
* 首页查询预警信息统计列表
* 查询预警信息统计列表
*/
@PreAuthorize("@ss.hasPermi('inMonitoring:alertMessage:list')")
@GetMapping ("/list")

@ -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);
// 令牌自定义标识
@ -61,14 +60,11 @@ 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);
}
@ -116,8 +130,7 @@ 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);
@ -135,12 +148,10 @@ public class TokenService
* @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);
}
}
@ -150,8 +161,7 @@ public class TokenService
*
* @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缓存
@ -164,8 +174,7 @@ 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;
}
}

@ -94,6 +94,7 @@
</dependency>
</dependencies>
</project>

@ -5,6 +5,7 @@ import java.util.Date;
import com.alops.common.annotation.Excel;
import com.alops.common.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -73,6 +74,9 @@ public class AlertMessage
@Excel(name ="预警信息")
private String message;
@JsonIgnore
@Excel(name ="预警规则id")
private Long alertRuleId;

@ -56,7 +56,8 @@ public class EquBase
@Excel(name = "设备类型 id")
private String equTypeId;
@Excel(name = "楼层位置")
private String floor;
public EquType getEquTypeVO() {
return equTypeVO;

@ -26,12 +26,14 @@ public class homeEquAlertMessageVo {
@Excel(name = "处理状态")
private String status;
@Excel(name = "处理状态")
@Excel(name = "设备名称")
private String name;
@Excel(name = "处理状态")
@Excel(name = "设备位置")
private String location;
@Excel(name = "预警规则名称")
private String ruleName;
}

@ -99,6 +99,9 @@ public class queryEquBaseVO {
@Excel(name = "设备编号")
private String equNumber;
@Excel(name = "楼层位置")
private String floor;
public void setId(String id)

@ -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();
}
}

@ -154,6 +154,7 @@ public class AlertLaunchServiceImpl implements IAlertLaunchService {
msg.setBatch(currentBatch);
msg.setMessage(msgdes);
msg.setAlertRuleId(rule.getId());
messages.add(msg);
msgs.add(msgdes);

@ -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);

@ -360,25 +360,74 @@ public class ScheduleRulesServiceImpl implements IScheduleRulesService {
executor.shutdown();
// ✅ 完整性校验:找出所有子表为空的设备
List<String> invalidDevices = successList.stream()
.filter(g -> g.getSubEntities() == null
|| g.getSubEntities().isEmpty()
|| g.getSubEntities().values().stream().anyMatch(list -> list == null || list.isEmpty()))
.map(g -> g.getEquGather().getEquId())
.collect(Collectors.toList());
// // ✅ 完整性校验:找出所有子表为空的设备
// List<String> invalidDevices = successList.stream()
// .filter(g -> g.getSubEntities() == null
// || g.getSubEntities().isEmpty()
// || g.getSubEntities().values().stream().anyMatch(list -> list == null || list.isEmpty()))
// .map(g -> g.getEquGather().getEquId())
// .collect(Collectors.toList());
//
// // 如果全部失败或者发现不完整设备,抛异常
// if (successList.isEmpty() || !invalidDevices.isEmpty()) {
// String msg = "本次采集失败,保持旧数据完好。失败设备:" +
// String.join(", ", failed) +
// (invalidDevices.isEmpty() ? "" : ",采集数据不完整设备:" + String.join(", ", invalidDevices));
// log.error(msg);
// throw new RuntimeException(msg); // 异常抛出去,外层可捕获发送邮件
// }
// ✅ 完整性校验:找出所有子表为空的设备及对应子表
Map<String, List<String>> invalidDeviceMap = successList.stream()
.map(g -> {
String equId = g.getEquGather().getEquId();
// 找出这个设备下不完整的子表
List<String> emptyTables = new ArrayList<>();
// subEntities 为空或没有数据
if (g.getSubEntities() == null || g.getSubEntities().isEmpty()) {
emptyTables.add("所有子表为空");
} else {
// 检查每个子表的值
g.getSubEntities().forEach((tableName, list) -> {
if (list == null || list.isEmpty()) {
emptyTables.add(tableName);
}
});
}
// 如果有任何空子表,返回 (设备ID, 空表列表)
return emptyTables.isEmpty() ? null : Map.entry(equId, emptyTables);
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// ✅ 提取不完整设备ID列表
List<String> invalidDevices = new ArrayList<>(invalidDeviceMap.keySet());
// 如果全部失败或者发现不完整设备,抛异常
// ✅ 如果全部失败或有不完整设备,抛异常
if (successList.isEmpty() || !invalidDevices.isEmpty()) {
String msg = "本次采集失败,保持旧数据完好。失败设备:" +
String.join(", ", failed) +
(invalidDevices.isEmpty() ? "" : ",采集数据不完整设备:" + String.join(", ", invalidDevices));
log.error(msg);
throw new RuntimeException(msg); // 异常抛出去,外层可捕获发送邮件
StringBuilder msg = new StringBuilder("本次采集失败,保持旧数据完好。");
if (!failed.isEmpty()) {
msg.append("失败设备:").append(String.join(", ", failed));
}
if (!invalidDeviceMap.isEmpty()) {
msg.append(";采集数据不完整设备如下:");
invalidDeviceMap.forEach((equId, tables) ->
msg.append("\n设备[").append(equId).append("] 缺少表:")
.append(String.join(", ", tables)));
}
log.error(msg.toString());
throw new RuntimeException(msg.toString());
}
// 事务内:删除旧表 + 插入成功设备数据
// 事务内:删除旧表 + 插入成功设备数据+执行预警
deleteAllEquipmentData(); // 删除依附表,不含 equ_gather
int inserted = persistAndUpdate(successList); // 插入成功设备数据
int inserted = persistAndUpdate(successList);
if (inserted <= 0) {
throw new RuntimeException("成功采集设备写入失败,回滚事务");

@ -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);
}
}
}

@ -20,6 +20,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="status" column="status" />
<result property="batch" column="batch" />
<result property="message" column="message" />
<result property="alertRuleId" column="alert_rule_id" />
</resultMap>
<select id="selectMaxBatch" resultType="integer">
@ -30,12 +32,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO alert_message
(equ_id, type, equ_type, solution, level, occur_time, handler, handle_time, status,batch,message)
(equ_id, type, equ_type, solution, level, occur_time, handler, handle_time, status,batch,message,alert_rule_id)
VALUES
<foreach collection="list" item="msg" separator=",">
(#{msg.equId},#{msg.type}, #{msg.equType},
#{msg.solution}, #{msg.level}, #{msg.occurTime},
#{msg.handler}, #{msg.handleTime}, #{msg.status}, #{msg.batch}, #{msg.message})
#{msg.handler}, #{msg.handleTime}, #{msg.status}, #{msg.batch}, #{msg.message}, #{msg.alertRuleId})
</foreach>
</insert>
@ -86,21 +88,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<!-- 查询最新预警信息(按时间排序,取前N条) -->
<!-- <select id="selectStaticAlertMessageList" resultType="homeEquAlertMessageVo">-->
<!-- SELECT-->
<!-- a.level,-->
<!-- a.occur_time AS occurTime,-->
<!-- a.status,-->
<!-- a.message,-->
<!-- e.name,-->
<!-- e.location-->
<!-- FROM alert_message a-->
<!-- LEFT JOIN equ_base e ON a.equ_id = e.id-->
<!-- ORDER BY a.occur_time DESC-->
<!-- </select>-->
<!-- 查询最新预警信息(按时间排序,取前N条),关联规则名称 -->
<select id="selectStaticAlertMessageList" resultType="homeEquAlertMessageVo">
SELECT
a.level,
a.occur_time AS occurTime,
a.status,
a.message,
e.name,
e.location
a.level,
a.occur_time AS occurTime,
a.status,
a.message,
e.name,
e.location,
r.rule_name AS ruleName <!-- 新增字段 -->
FROM alert_message a
LEFT JOIN equ_base e ON a.equ_id = e.id
LEFT JOIN equ_base e ON a.equ_id = e.id
LEFT JOIN alert_rule r ON a.alert_rule_id = r.id <!-- 新增关联 -->
ORDER BY a.occur_time DESC
</select>
<select id="selectAlertMessageById" parameterType="Long" resultMap="AlertMessageResult">
<include refid="selectAlertMessageVo"/>
where am.id = #{id}

@ -25,6 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="gatherFrequency" column="gather_frequency" />
<result property="equImage" column="equ_image" />
<result property="equNumber" column="equ_number" />
<result property="floor" column="floor" />
<association property="manufactureVO" columnPrefix="equ_manu_" autoMapping="true" />
<association property="equTypeVO" columnPrefix="equ_type_" autoMapping="true" />
@ -159,7 +160,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
b.status AS status,
b.device_level AS deviceLevel,
g.health AS health,
b.equ_image AS equImage
b.equ_image AS equImage,
b.floor AS floor
</sql>
<select id="selectEquBaseList" parameterType="equBaseQueryDto" resultType="com.alops.system.domain.vo.queryEquBaseVO">
@ -452,6 +454,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createBy != null">create_by,</if>
<if test="createDate != null">create_date,</if>
<if test="equNumber != null">equ_number,</if>
<if test="floor != null">floor,</if>
status,
<if test="equImage != null">equ_image,</if>
@ -475,6 +478,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createBy != null">#{createBy},</if>
<if test="createDate != null">#{createDate},</if>
<if test="equNumber != null">#{equNumber},</if>
<if test="floor != null">#{floor},</if>
COALESCE(#{status,jdbcType=INTEGER}, 0),
<if test="equImage != null">#{equImage},</if>
COALESCE(#{failureFrequency,jdbcType=BIGINT}, 0),
@ -503,6 +507,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="supplierId != null">supplier_id = #{supplierId},</if>
<if test="equImage != null">equ_image = #{equImage},</if>
<if test="equNumber != null">equ_number = #{equNumber},</if>
<if test="floor != null">floor = #{floor},</if>
</trim>
where id = #{id}
</update>

Loading…
Cancel
Save