云
2 天以前 657698fd2fb32794932474648dbaee9e284e38c8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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<EnergyAlertRuleDO> page(PageParam page) {
        return energyAlertRuleMapper.selectPage(page, new LambdaQueryWrapperX<EnergyAlertRuleDO>().orderByDesc(EnergyAlertRuleDO::getId));
    }
    public PageResult<EnergyAlertLogDO> logs(PageParam page, LocalDate date, String status) {
        return energyAlertLogMapper.selectPage(page, new LambdaQueryWrapperX<EnergyAlertLogDO>()
                .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<String, Integer> check(LocalDate date) {
        int sent = 0, failed = 0;
        var rules = energyAlertRuleMapper.selectList(new LambdaQueryWrapperX<EnergyAlertRuleDO>()
                .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<EnergyAlertLogDO>()
                        .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"; }; }
}