package com.zksy.api.utils; import com.alibaba.fastjson.JSONObject; import com.aliyun.auth.credentials.Credential; import com.aliyun.auth.credentials.provider.StaticCredentialProvider; import com.aliyun.sdk.service.dysmsapi20170525.AsyncClient; import com.aliyun.sdk.service.dysmsapi20170525.models.SendBatchSmsRequest; import com.aliyun.sdk.service.dysmsapi20170525.models.SendBatchSmsResponse; import com.google.gson.Gson; import com.zksy.api.config.AliyunSmsConfig; import com.zksy.api.domain.AlarmData; import com.zksy.api.domain.WarningThreshold; import com.zksy.api.service.AlarmDataService; import com.zksy.api.service.WarningThresholdService; import darabonba.core.client.ClientOverrideConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import rk.netDevice.sdk.p2.NodeData; import rk.netDevice.sdk.p2.RealTimeData; import java.math.BigDecimal; import java.time.Duration; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Component @Slf4j public class SmsUtil { private final AliyunSmsConfig aliyunSmsConfig; private final AsyncClient client; private final WarningThresholdService service; private final AlarmDataService alarmDataService; // 报警缓存和冷却时间 // private static final Map ALARM_CACHE = new ConcurrentHashMap<>(); // private static final long ALARM_COOLDOWN_MS = 5 * 60 * 1000; @Autowired public SmsUtil(AliyunSmsConfig aliyunSmsConfig, WarningThresholdService service, AlarmDataService alarmDataService) { this.aliyunSmsConfig = aliyunSmsConfig; this.service = service; this.alarmDataService = alarmDataService; try { StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder() .accessKeyId(aliyunSmsConfig.getAccessKeyId()) .accessKeySecret(aliyunSmsConfig.getAccessKeySecret()) .build()); this.client = AsyncClient.builder() .region(aliyunSmsConfig.getRegionId()) .credentialsProvider(provider) .overrideConfiguration( ClientOverrideConfiguration.create() .setEndpointOverride("dysmsapi.aliyuncs.com") .setConnectTimeout(Duration.ofSeconds(30)) ) .build(); } catch (Exception e) { log.error("阿里云短信客户端初始化失败", e); throw new RuntimeException("短信服务初始化异常", e); // 初始化失败应快速失败 } } /** * 批量发送相同内容的短信给多个手机号 * @param phoneNumbers 接收短信的手机号列表 * @param templateParam 短信模板参数,JSON格式字符串 * @return 发送结果,key为手机号,value为是否成功 */ public Map sendBatchSms(List phoneNumbers, String templateParam) { if (phoneNumbers == null || phoneNumbers.isEmpty()) { log.warn("批量发送短信失败:手机号列表为空"); return Collections.emptyMap(); } try { List signNames = phoneNumbers.stream() .map(phone -> aliyunSmsConfig.getSignName()) .collect(Collectors.toList()); List templateParams = phoneNumbers.stream() .map(phone -> templateParam) .collect(Collectors.toList()); //构建批量发送请求 SendBatchSmsRequest request = SendBatchSmsRequest.builder() .phoneNumberJson(new Gson().toJson(phoneNumbers)) // 手机号列表 .signNameJson(new Gson().toJson(signNames)) // 签名列表 .templateCode(aliyunSmsConfig.getTemplateCode()) .templateParamJson(new Gson().toJson(templateParams)) // 参数列表 .build(); // 发送并处理响应 CompletableFuture response = client.sendBatchSms(request); SendBatchSmsResponse resp = response.get(); log.info("批量短信发送响应: {}",new Gson().toJson(resp.getBody())); return phoneNumbers.stream() .collect(Collectors.toMap( phone -> phone, phone -> "OK".equals(resp.getBody().getCode()) )); } catch (Exception e) { log.error("批量短信发送响应: {}",e.getMessage()); e.printStackTrace(); return phoneNumbers.stream() .collect(Collectors.toMap( phone -> phone, phone -> false )); } } /** * 检查设备数据是否触发报警并发送短信 */ public boolean checkDeviceAlarmAndSend(RealTimeData realTimeData, NodeData nodeData, List alarmPhones) { if (realTimeData == null || nodeData == null) { log.error("预警参数异常:设备数据或节点数据为空"); return false; } if (realTimeData.getDeviceId() <= 0) { log.error("预警参数异常:设备ID无效({})", realTimeData.getDeviceId()); return false; } int nodeId = nodeData.getNodeId(); if (nodeId < 1 || nodeId > 5) { log.error("节点ID无效({}),仅支持1-5的节点", nodeId); return false; } try { //TODO 冷处理待定 // String cacheKey = realTimeData.getDeviceId() + "_" + nodeId; // Long lastAlarmTime = ALARM_CACHE.get(cacheKey); // if (lastAlarmTime != null && System.currentTimeMillis() - lastAlarmTime < ALARM_COOLDOWN_MS) { // log.info("设备{}节点{}在冷却时间内,暂不重复报警", realTimeData.getDeviceId(), nodeId); // return false; // } // 根据node_id获取预警编码(自定义写死,不从枚举获取) String warningCode = getWarningCodeByNodeId(nodeId); if (warningCode == null) { log.warn("设备{}节点{}无对应预警编码,跳过检查", realTimeData.getDeviceId(), nodeId); return false; } // 设备编号从设备数据获取 String deviceCode = String.valueOf(realTimeData.getDeviceId()); // 查询预警阈值表 WarningThreshold threshold = null; try { threshold = service.getWarningThresholdByDeviceAndCode(deviceCode, warningCode); } catch (Exception e) { log.error("查询预警阈值失败", e); } // 获取预警类型,默认使用节点对应的名称 String warningType = getWarningTypeNameByNodeId(nodeId); if (threshold != null && threshold.getWarningType() != null) { warningType = threshold.getWarningType(); } // 获取最小值和最大值 Double minValue = threshold != null ? threshold.getMinValue() : null; Double maxValue = threshold != null ? threshold.getMaxValue() : null; String remark = threshold != null ? threshold.getRemark() : null; // 获取当前节点的指标数值 double currentValue = getCurrentValueByNodeId(nodeData, nodeId); // 判断是否触发报警 boolean isOverThreshold = false; if (minValue != null && currentValue <= minValue) { isOverThreshold = true; } if (maxValue != null && currentValue >= maxValue) { isOverThreshold = true; } log.debug("设备{}节点{} - 预警类型:{},当前值:{},最小值:{},最大值:{},是否超限:{}", realTimeData.getDeviceId(), nodeId, warningType, currentValue, minValue, maxValue, isOverThreshold); // 触发报警 if (isOverThreshold) { log.warn("设备{}节点{}触发预警:{}(当前值:{},最小值:{},最大值:{})", realTimeData.getDeviceId(), nodeId, warningType, currentValue, minValue, maxValue); // 1. 先保存 alarm_data saveAlarmData(deviceCode, warningType, warningCode, minValue != null ? BigDecimal.valueOf(minValue) : null, maxValue != null ? BigDecimal.valueOf(maxValue) : null, BigDecimal.valueOf(currentValue), remark); // 2. 再构造并发送短信 JSONObject params = new JSONObject(); params.put("deviceNo", realTimeData.getDeviceId()); params.put("alarmType", warningType); double lng = nodeData.getLng(); double lat = nodeData.getLat(); params.put("location", String.format("经纬度:%.6f,%.8f", lng, lat)); Map sendResults = this.sendBatchSms(alarmPhones, params.toJSONString()); long successCount = sendResults.values().stream().filter(Boolean::booleanValue).count(); log.info("设备{}报警短信发送完成,成功{}条,失败{}条", realTimeData.getDeviceId(), successCount, sendResults.size() - successCount); // 冷却处理 // ALARM_CACHE.put(cacheKey, System.currentTimeMillis()); return true; } return false; } catch (Exception e) { log.error("设备{}节点{}报警处理失败", realTimeData.getDeviceId(), nodeId, e); return false; } } /** * 保存告警数据到 alarm_data 表 */ private void saveAlarmData(String deviceCode, String warningType, String warningCode, BigDecimal minValue, BigDecimal maxValue, BigDecimal actualValue, String remark) { try { AlarmData alarmData = new AlarmData(); alarmData.setDeviceCode(deviceCode); alarmData.setWarningType(warningType); alarmData.setWarningCode(warningCode); alarmData.setMinValue(minValue); alarmData.setMaxValue(maxValue); alarmData.setActualValue(actualValue); alarmData.setAlarmStatus(0); alarmData.setAlarmTime(LocalDateTime.now()); alarmData.setRemark(remark); alarmData.setCreateTime(LocalDateTime.now()); alarmDataService.saveAlarmData(alarmData); } catch (Exception e) { log.error("保存告警数据失败", e); } } /** * 根据nodeId获取对应的预警编码(自定义写死) */ private String getWarningCodeByNodeId(int nodeId) { switch (nodeId) { case 1: return "WARN_SUSPENDED_SOLIDS"; case 2: return "WARN_COD"; case 3: return "WARN_AMMONIA_NITROGEN"; case 4: return "WARN_CONDUCTIVITY"; case 5: return "WARN_PH"; default: return null; } } /** * 根据nodeId获取对应的预警类型名称 */ private String getWarningTypeNameByNodeId(int nodeId) { switch (nodeId) { case 1: return "悬浮物"; case 2: return "COD"; case 3: return "氨氮"; case 4: return "电导率"; case 5: return "pH值"; default: return "未知预警"; } } /** * 根据nodeId获取对应的指标数值 */ private double getCurrentValueByNodeId(NodeData nodeData, int nodeId) { if (nodeId == 1) { // 节点1需要特殊处理,取悬浮物字段(floatValue字段) return nodeData.getFloatValue(); } else { // 其他节点取hum字段 return nodeData.getHum(); } } }