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"; }; }
|
}
|