package cn.iocoder.yudao.module.mes.service.energy; import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.common.pojo.*; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.controller.admin.energy.vo.EnergyAlertRuleSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.energy.*; import cn.iocoder.yudao.module.mes.dal.mysql.energy.*; import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.*; import java.math.BigDecimal; import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @Service @Slf4j public class EnergyAlertService { @Resource private EnergyAlertRuleMapper energyAlertRuleMapper; @Resource private EnergyAlertLogMapper energyAlertLogMapper; @Resource private EnergyRecordMapper energyRecordMapper; @Resource private AdminUserService adminUserService; @Resource private NotifyMessageSendApi notifyMessageSendApi; public PageResult page(PageParam page) { return energyAlertRuleMapper.selectPage(page, new LambdaQueryWrapperX().orderByDesc(EnergyAlertRuleDO::getId)); } public PageResult logs(PageParam page, LocalDate date, String status) { return energyAlertLogMapper.selectPage(page, new LambdaQueryWrapperX() .eqIfPresent(EnergyAlertLogDO::getStatus, status) .and(date != null, w -> w.and(day -> day.eq(EnergyAlertLogDO::getPeriodType, "DAY").eq(EnergyAlertLogDO::getRecordDate, date)) .or(month -> month.eq(EnergyAlertLogDO::getPeriodType, "MONTH").eq(EnergyAlertLogDO::getRecordDate, date.withDayOfMonth(1)))) .orderByDesc(EnergyAlertLogDO::getId)); } public Long save(EnergyAlertRuleSaveReqVO req) { if (req.getId() != null) get(req.getId()); adminUserService.validateUserList(req.getRecipientIds()); EnergyAlertRuleDO rule = BeanUtils.toBean(req, EnergyAlertRuleDO.class); rule.setName(req.getName().trim()); rule.setUsageArea(req.getUsageArea() == null ? "" : req.getUsageArea().trim()); rule.setProductCategory(req.getProductCategory() == null ? "" : req.getProductCategory().trim()); rule.setRecipientIds(req.getRecipientIds().stream().distinct().map(String::valueOf).collect(Collectors.joining(","))); if (rule.getId() == null) energyAlertRuleMapper.insert(rule); else if (energyAlertRuleMapper.updateById(rule) == 0) throw missing(); return rule.getId(); } public void delete(Long id) { get(id); if (energyAlertRuleMapper.deleteById(id) == 0) throw missing(); } private EnergyAlertRuleDO get(Long id) { var rule = energyAlertRuleMapper.selectById(id); if (rule == null) throw missing(); return rule; } private RuntimeException missing() { return exception(new ErrorCode(1_040_951_000, "能耗预警规则不存在")); } /** Check the selected day and its calendar month; successful sends commit with their history. */ @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public Map check(LocalDate date) { int sent = 0, failed = 0; var rules = energyAlertRuleMapper.selectList(new LambdaQueryWrapperX() .eq(EnergyAlertRuleDO::getEnabled, true).orderByAsc(EnergyAlertRuleDO::getId)); for (var candidate : rules) { var rule = energyAlertRuleMapper.lock(candidate.getId()); if (rule == null || !Boolean.TRUE.equals(rule.getEnabled())) continue; boolean monthly = rule.getMetric().startsWith("MONTH_"); LocalDate periodDate = monthly ? date.withDayOfMonth(1) : date; String periodType = monthly ? "MONTH" : "DAY"; boolean amount = rule.getMetric().endsWith("AMOUNT"); BigDecimal actual = energyRecordMapper.selectDailyValue(periodDate, rule); if (!exceeds(actual, rule.getThreshold())) continue; for (String id : rule.getRecipientIds().split(",")) { long recipient = Long.parseLong(id); var log = energyAlertLogMapper.selectOne(new LambdaQueryWrapperX() .eq(EnergyAlertLogDO::getRuleId, rule.getId()).eq(EnergyAlertLogDO::getRecordDate, periodDate) .eq(EnergyAlertLogDO::getPeriodType, periodType) .eq(EnergyAlertLogDO::getRecipientId, recipient)); if (log != null && "SENT".equals(log.getStatus())) continue; if (log == null) log = new EnergyAlertLogDO(); log.setRuleId(rule.getId()); log.setRuleName(rule.getName()); log.setRecordDate(periodDate); log.setPeriodType(periodType); log.setEnergyType(rule.getEnergyType()); log.setMetric(rule.getMetric()); log.setScope((rule.getUsageArea().isEmpty() ? "全部区域" : rule.getUsageArea()) + " / " + (rule.getProductCategory().isEmpty() ? "全部产品" : rule.getProductCategory())); log.setActualValue(actual); log.setThreshold(rule.getThreshold()); log.setRecipientId(recipient); log.setRecipientName(String.valueOf(recipient)); try { adminUserService.validateUserList(List.of(recipient)); log.setRecipientName(adminUserService.getUser(recipient).getNickname()); NotifySendSingleToUserReqDTO message = new NotifySendSingleToUserReqDTO(); message.setUserId(recipient); message.setTemplateCode(monthly ? "energy_monthly_warning" : "energy_daily_warning"); message.setTemplateParams(Map.of("ruleName", rule.getName(), "date", monthly ? java.time.YearMonth.from(date).toString() : date.toString(), "energyName", energyName(rule.getEnergyType()), "scope", log.getScope(), "metricName", amount ? "费用" : "用量", "actual", actual.toPlainString(), "threshold", rule.getThreshold().toPlainString(), "unit", amount ? "元" : unit(rule.getEnergyType()))); Long messageId = notifyMessageSendApi.sendSingleMessageToAdmin(message); if (messageId == null) throw new IllegalStateException("能耗预警通知模板已停用"); log.setMessageId(messageId); log.setStatus("SENT"); log.setErrorMessage(""); sent++; } catch (Exception e) { logFailure(rule.getId(), date, recipient, e); log.setStatus("FAILED"); log.setErrorMessage("发送失败,请检查接收人是否启用以及通知模板 " + (monthly ? "energy_monthly_warning" : "energy_daily_warning") + " 是否可用,然后重新检查该日期。"); failed++; } if (log.getId() == null) energyAlertLogMapper.insert(log); else energyAlertLogMapper.updateById(log); } } return Map.of("sent", sent, "failed", failed); } private void logFailure(Long ruleId, LocalDate date, long recipient, Exception cause) { log.warn("能耗预警站内通知发送失败 rule={} date={} recipient={}", ruleId, date, recipient, cause); } static boolean exceeds(BigDecimal actual, BigDecimal threshold) { return actual != null && actual.compareTo(threshold) > 0; } public static String energyName(String type) { return switch (type) { case "WATER" -> "水"; case "ELECTRICITY" -> "电"; default -> "蒸汽"; }; } public static String unit(String type) { return switch (type) { case "WATER" -> "m³"; case "ELECTRICITY" -> "kWh"; default -> "t"; }; } }