package com.ruoyi.energymeter.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.energymeter.constant.EnergyMeterConstants; import com.ruoyi.energymeter.mapper.EnergyMeterDeviceMapper; import com.ruoyi.energymeter.mapper.EnergyMeterMeasurementMapper; import com.ruoyi.energymeter.pojo.EnergyMeterDevice; import com.ruoyi.energymeter.pojo.EnergyMeterMeasurement; import com.ruoyi.energymeter.service.EnergyMeterDemoService; import com.ruoyi.energymeter.util.EnergyMeterSupport; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; /** * 演示数据实现:气表按天生成模拟表码与用量。 * *

模拟值是“设备ID + 日期”的确定性函数,重复生成同一天结果一致,便于核对。

*/ @Slf4j @Service @RequiredArgsConstructor public class EnergyMeterDemoServiceImpl implements EnergyMeterDemoService { private final EnergyMeterDeviceMapper deviceMapper; private final EnergyMeterMeasurementMapper measurementMapper; @Override @Transactional(rollbackFor = Exception.class) public int generateGasDemo(LocalDate date) { if (date == null) { return 0; } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(EnergyMeterDevice::getMeterType, EnergyMeterConstants.TYPE_GAS) .eq(EnergyMeterDevice::getSimulated, 1) .eq(EnergyMeterDevice::getEnabled, 1); List devices = deviceMapper.selectList(wrapper); if (devices.isEmpty()) { return 0; } int generated = 0; LocalDateTime measureTime = date.atStartOfDay(); for (EnergyMeterDevice device : devices) { if (hasUsage(device.getId(), measureTime)) { // 数据已存在时只刷新采集时间,保证“最近采集/在线”反映最近一次自动生成 deviceMapper.markCollectSuccess(device.getId(), EnergyMeterSupport.now()); continue; } BigDecimal usage = simulatedUsage(device.getId(), date); BigDecimal previous = previousReading(device, measureTime); BigDecimal reading = previous.add(usage); EnergyMeterMeasurement usagePoint = new EnergyMeterMeasurement(); usagePoint.setTenantId(device.getTenantId()); usagePoint.setMeterId(device.getId()); usagePoint.setMetricCode(EnergyMeterConstants.METRIC_GAS_USAGE); usagePoint.setGranularity(EnergyMeterConstants.GRANULARITY_DAY); usagePoint.setMeasureTime(measureTime); usagePoint.setMetricValue(usage); usagePoint.setUnit(EnergyMeterConstants.UNIT_GAS); usagePoint.setSourceApi(EnergyMeterConstants.SOURCE_MANUAL); usagePoint.setSourceRecordTime(measureTime); usagePoint.setCollectedAt(EnergyMeterSupport.now()); usagePoint.setRemark("模拟数据:每日用量"); EnergyMeterMeasurement readingPoint = new EnergyMeterMeasurement(); readingPoint.setTenantId(device.getTenantId()); readingPoint.setMeterId(device.getId()); readingPoint.setMetricCode(EnergyMeterConstants.METRIC_GAS_READING); readingPoint.setGranularity(EnergyMeterConstants.GRANULARITY_POINT); readingPoint.setMeasureTime(measureTime); readingPoint.setMetricValue(reading); readingPoint.setUnit(EnergyMeterConstants.UNIT_GAS); readingPoint.setSourceApi(EnergyMeterConstants.SOURCE_MANUAL); readingPoint.setSourceRecordTime(measureTime); readingPoint.setCollectedAt(EnergyMeterSupport.now()); readingPoint.setRemark("模拟数据:每日表码"); measurementMapper.upsertBatch(List.of(usagePoint, readingPoint)); // 采集时间按实际执行时间记录,而不是数据所属日期的 00:00 deviceMapper.markCollectSuccess(device.getId(), EnergyMeterSupport.now()); generated++; } log.info("气表演示数据生成完成 date={} 设备数={}", date, generated); return generated; } private boolean hasUsage(Long meterId, LocalDateTime measureTime) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(EnergyMeterMeasurement::getMeterId, meterId) .eq(EnergyMeterMeasurement::getMetricCode, EnergyMeterConstants.METRIC_GAS_USAGE) .eq(EnergyMeterMeasurement::getMeasureTime, measureTime); return measurementMapper.selectCount(wrapper) > 0; } /** * 上一期表码:取该日期之前的最近一条示值,没有就用设备起始表码。 */ private BigDecimal previousReading(EnergyMeterDevice device, LocalDateTime measureTime) { EnergyMeterMeasurement previous = measurementMapper.selectPreviousPoint(device.getId(), EnergyMeterConstants.METRIC_GAS_READING, measureTime); if (previous != null && previous.getMetricValue() != null) { return previous.getMetricValue(); } return device.getInitialReading() == null ? BigDecimal.ZERO : device.getInitialReading(); } /** * 确定性模拟用量:按食品厂蒸煮/灭菌的实际用气量估算。 * *

估算口径:蒸煮锅按 4~6 m³/h、每天 6~8 小时,单台约 25~35 m³/天; * 灭菌锅、小煮锅约 14~20 m³/天。基础值按设备区分,再叠加季节波动与噪点。

*/ private BigDecimal simulatedUsage(Long meterId, LocalDate date) { long seed = meterId * 131L + date.toEpochDay(); double base = 14 + (meterId % 5) * 5; double season = base * 0.1 * Math.sin(2 * Math.PI * date.getDayOfYear() / 365); double noise = ((seed * 37 % 200) / 200.0 - 0.5) * 3; double usage = Math.max(2, base + season + noise); return BigDecimal.valueOf(usage).setScale(2, RoundingMode.HALF_UP); } }