| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- 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<String, Long> 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<String, Boolean> sendBatchSms(List<String> phoneNumbers, String templateParam) {
- if (phoneNumbers == null || phoneNumbers.isEmpty()) {
- log.warn("批量发送短信失败:手机号列表为空");
- return Collections.emptyMap();
- }
- try {
- List<String> signNames = phoneNumbers.stream()
- .map(phone -> aliyunSmsConfig.getSignName())
- .collect(Collectors.toList());
- List<String> 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<SendBatchSmsResponse> 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<String> 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<String, Boolean> 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();
- }
- }
- }
|