longz
2026-08-04 3a6462da1c0582ebc9ba21b78e79e7a0ac46018e
refactor: aggregate storage dashboard data in java
4个文件已修改
297 ■■■■■ 已修改文件
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/mapper/EmindStorageDashboardMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindStorageDashboardService.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindStorageDashboardMapper.xml 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/emind/service/EmindStorageDashboardServiceTest.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/mapper/EmindStorageDashboardMapper.java
@@ -20,8 +20,10 @@
    int upsertDaily(@Param("stationId") Long stationId, @Param("rows") List<EmindIncomeHistoryDto> rows,
                    @Param("syncedAt") LocalDateTime syncedAt);
    List<Map<String, Object>> selectStations();
    Map<String, Object> selectOverview(@Param("stationId") Long stationId, @Param("day") LocalDate day);
    List<Map<String, Object>> selectHistory(@Param("stationId") Long stationId, @Param("period") String period,
                                            @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
    List<Map<String, Object>> selectCurve(@Param("stationId") Long stationId);
    List<Map<String, Object>> selectSnapshotRows(@Param("stationId") Long stationId);
    List<Map<String, Object>> selectDailyRows(@Param("stationId") Long stationId,
                                               @Param("startDate") LocalDate startDate,
                                               @Param("endDate") LocalDate endDate);
    List<Map<String, Object>> selectCurveRows(@Param("stationId") Long stationId);
    List<Map<String, Object>> selectEnergyBlockRows(@Param("stationId") Long stationId);
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindStorageDashboardService.java
@@ -3,7 +3,9 @@
import com.ruoyi.other.emind.dto.EmindDashboardQuery;
import com.ruoyi.other.emind.mapper.EmindStorageDashboardMapper;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -15,48 +17,144 @@
@Service
public class EmindStorageDashboardService {
    private final EmindStorageDashboardMapper mapper;
    public EmindStorageDashboardService(EmindStorageDashboardMapper mapper) { this.mapper=mapper; }
    public List<Map<String,Object>> stations() { return mapper.selectStations(); }
    public Map<String,Object> overview(EmindDashboardQuery query) {
        LocalDate end=query.getEndDate()==null?LocalDate.now():query.getEndDate();
        String period=query.getPeriod()==null?"DAY":query.getPeriod().toUpperCase();
        LocalDate start=query.getStartDate();
        if (start==null) start="YEAR".equals(period)?end.withDayOfYear(1):("MONTH".equals(period)?end.withDayOfMonth(1):end.minusDays(6));
        Map<String,Object> result=new LinkedHashMap<>();
        result.put("summary", mapper.selectOverview(query.getStationId(), LocalDate.now().minusDays(1)));
        result.put("history", completeHistory(mapper.selectHistory(query.getStationId(), period, start, end), period, start, end));
        result.put("curve", mapper.selectCurve(query.getStationId()));
    public EmindStorageDashboardService(EmindStorageDashboardMapper mapper) { this.mapper = mapper; }
    public List<Map<String, Object>> stations() { return mapper.selectStations(); }
    public Map<String, Object> overview(EmindDashboardQuery query) {
        LocalDate end = query.getEndDate() == null ? LocalDate.now() : query.getEndDate();
        String period = query.getPeriod() == null ? "DAY" : query.getPeriod().toUpperCase();
        LocalDate start = query.getStartDate();
        if (start == null) start = "YEAR".equals(period) ? end.withDayOfYear(1)
                : ("MONTH".equals(period) ? end.withDayOfMonth(1) : end.minusDays(6));
        List<Map<String, Object>> dailyRows = mapper.selectDailyRows(query.getStationId(), start, end);
        LocalDate yesterday = LocalDate.now().minusDays(1);
        List<Map<String, Object>> yesterdayRows = start.equals(yesterday) && end.equals(yesterday)
                ? dailyRows : mapper.selectDailyRows(query.getStationId(), yesterday, yesterday);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("summary", summarize(mapper.selectSnapshotRows(query.getStationId()), yesterdayRows, yesterday,
                mapper.selectEnergyBlockRows(query.getStationId())));
        result.put("history", completeHistory(dailyRows, period, start, end));
        result.put("curve", summarizeCurve(mapper.selectCurveRows(query.getStationId())));
        return result;
    }
    private List<Map<String, Object>> completeHistory(List<Map<String, Object>> history, String period,
                                                      LocalDate start, LocalDate end) {
    private Map<String, Object> summarize(List<Map<String, Object>> snapshots, List<Map<String, Object>> dailyRows,
                                           LocalDate yesterday, List<Map<String, Object>> blocks) {
        BigDecimal incomeSum = BigDecimal.ZERO;
        BigDecimal chargedEnergySum = BigDecimal.ZERO;
        BigDecimal dischargedEnergySum = BigDecimal.ZERO;
        BigDecimal realTimePower = BigDecimal.ZERO;
        BigDecimal surplusEnergy = BigDecimal.ZERO;
        BigDecimal capacity = BigDecimal.ZERO;
        BigDecimal totalSoc = BigDecimal.ZERO;
        BigDecimal elinkTotal = BigDecimal.ZERO;
        BigDecimal elinkOnline = BigDecimal.ZERO;
        Object syncedAt = null;
        for (Map<String, Object> row : snapshots) {
            incomeSum = incomeSum.add(decimal(row.get("incomeSum")));
            chargedEnergySum = chargedEnergySum.add(decimal(row.get("chargedEnergySum")));
            dischargedEnergySum = dischargedEnergySum.add(decimal(row.get("dischargedEnergySum")));
            realTimePower = realTimePower.add(decimal(row.get("realTimePower")));
            surplusEnergy = surplusEnergy.add(decimal(row.get("surplusEnergy")));
            capacity = capacity.add(decimal(row.get("capacity")));
            totalSoc = totalSoc.add(decimal(row.get("soc")));
            elinkTotal = elinkTotal.add(decimal(row.get("elinkTotal")));
            elinkOnline = elinkOnline.add(decimal(row.get("elinkOnline")));
            if (row.get("syncedAt") != null) syncedAt = row.get("syncedAt");
        }
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("incomeSum", incomeSum);
        summary.put("chargedEnergySum", chargedEnergySum);
        summary.put("dischargedEnergySum", dischargedEnergySum);
        summary.put("realTimePower", realTimePower);
        summary.put("surplusEnergy", surplusEnergy);
        summary.put("capacity", capacity);
        summary.put("soc", capacity.signum() > 0 ? surplusEnergy.multiply(BigDecimal.valueOf(100)).divide(capacity, 2, RoundingMode.HALF_UP)
                : (snapshots.isEmpty() ? BigDecimal.ZERO : totalSoc.divide(BigDecimal.valueOf(snapshots.size()), 2, RoundingMode.HALF_UP)));
        summary.put("elinkTotal", elinkTotal);
        summary.put("elinkOnline", elinkOnline);
        summary.put("eblockTotal", blocks.size());
        summary.put("eblockOnline", blocks.stream().filter(this::isOnlineBlock).count());
        summary.put("incomeYesterday", sumDaily(dailyRows, yesterday, "income"));
        summary.put("chargedEnergyYesterday", sumDaily(dailyRows, yesterday, "chargedEnergy"));
        summary.put("dischargedEnergyYesterday", sumDaily(dailyRows, yesterday, "dischargedEnergy"));
        summary.put("syncedAt", syncedAt);
        return summary;
    }
    private boolean isOnlineBlock(Map<String, Object> row) {
        int status = decimal(row.get("runStatus")).intValue();
        return status >= 1 && status <= 4 && row.get("recordTime") != null && !String.valueOf(row.get("recordTime")).isEmpty();
    }
    private BigDecimal sumDaily(List<Map<String, Object>> rows, LocalDate day, String field) {
        return rows.stream().filter(row -> day.equals(asDate(row.get("statDate"))))
                .map(row -> decimal(row.get(field))).reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    private List<Map<String, Object>> completeHistory(List<Map<String, Object>> dailyRows, String period,
                                                       LocalDate start, LocalDate end) {
        DateTimeFormatter formatter = "YEAR".equals(period) ? DateTimeFormatter.ofPattern("yyyy-MM") : DateTimeFormatter.ISO_LOCAL_DATE;
        Map<String, Map<String, Object>> historyByLabel = new HashMap<>();
        if (history != null) {
            for (Map<String, Object> row : history) {
                if (row != null && row.get("label") != null) {
                    historyByLabel.put(String.valueOf(row.get("label")), row);
                }
            }
        for (Map<String, Object> row : dailyRows) {
            LocalDate statDate = asDate(row.get("statDate"));
            if (statDate == null) continue;
            String label = statDate.format(formatter);
            Map<String, Object> total = historyByLabel.computeIfAbsent(label, ignored -> zeroHistory(label));
            total.put("income", decimal(total.get("income")).add(decimal(row.get("income"))));
            total.put("chargedEnergy", decimal(total.get("chargedEnergy")).add(decimal(row.get("chargedEnergy"))));
            total.put("dischargedEnergy", decimal(total.get("dischargedEnergy")).add(decimal(row.get("dischargedEnergy"))));
        }
        List<Map<String, Object>> completed = new ArrayList<>();
        DateTimeFormatter formatter = "YEAR".equals(period)
                ? DateTimeFormatter.ofPattern("yyyy-MM") : DateTimeFormatter.ISO_LOCAL_DATE;
        LocalDate cursor = "YEAR".equals(period) ? start.withDayOfMonth(1) : start;
        LocalDate last = "YEAR".equals(period) ? end.withDayOfMonth(1) : end;
        while (!cursor.isAfter(last)) {
            String label = cursor.format(formatter);
            Map<String, Object> row = historyByLabel.get(label);
            if (row == null) {
                row = new LinkedHashMap<>();
                row.put("label", label);
                row.put("income", BigDecimal.ZERO);
                row.put("chargedEnergy", BigDecimal.ZERO);
                row.put("dischargedEnergy", BigDecimal.ZERO);
            }
            completed.add(row);
            completed.add(historyByLabel.getOrDefault(label, zeroHistory(label)));
            cursor = "YEAR".equals(period) ? cursor.plusMonths(1) : cursor.plusDays(1);
        }
        return completed;
    }
    private List<Map<String, Object>> summarizeCurve(List<Map<String, Object>> rows) {
        Map<String, Map<String, Object>> totals = new LinkedHashMap<>();
        for (Map<String, Object> row : rows) {
            String label = String.valueOf(row.get("recordTime"));
            Map<String, Object> total = totals.computeIfAbsent(label, ignored -> new LinkedHashMap<>());
            total.put("label", label);
            total.put("power", decimal(total.get("power")).add(decimal(row.get("power"))));
            total.put("socSum", decimal(total.get("socSum")).add(decimal(row.get("soc"))));
            total.put("count", decimal(total.get("count")).add(BigDecimal.ONE));
        }
        List<Map<String, Object>> result = new ArrayList<>();
        for (Map<String, Object> total : totals.values()) {
            BigDecimal count = decimal(total.remove("count"));
            BigDecimal soc = decimal(total.remove("socSum"));
            total.put("soc", count.signum() == 0 ? BigDecimal.ZERO : soc.divide(count, 2, RoundingMode.HALF_UP));
            result.add(total);
        }
        return result;
    }
    private Map<String, Object> zeroHistory(String label) {
        Map<String, Object> row = new LinkedHashMap<>();
        row.put("label", label);
        row.put("income", BigDecimal.ZERO);
        row.put("chargedEnergy", BigDecimal.ZERO);
        row.put("dischargedEnergy", BigDecimal.ZERO);
        return row;
    }
    private LocalDate asDate(Object value) {
        if (value instanceof LocalDate) return (LocalDate) value;
        return value == null ? null : LocalDate.parse(String.valueOf(value));
    }
    private BigDecimal decimal(Object value) {
        if (value == null) return BigDecimal.ZERO;
        if (value instanceof BigDecimal) return (BigDecimal) value;
        return new BigDecimal(String.valueOf(value));
    }
}
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindStorageDashboardMapper.xml
@@ -49,41 +49,27 @@
        SELECT station_id stationId, station_name stationName, platform_name platformName
        FROM t_emind_dashboard_snapshot ORDER BY station_name
    </select>
    <select id="selectOverview" resultType="map">
        SELECT COALESCE(SUM(income_sum),0) incomeSum, COALESCE(SUM(charged_energy_sum),0) chargedEnergySum,
          COALESCE(SUM(discharged_energy_sum),0) dischargedEnergySum,
          COALESCE((SELECT SUM(daily.charged_energy) FROM t_emind_dashboard_daily daily
            WHERE daily.stat_date=#{day}
            <if test="stationId != null">AND daily.station_id=#{stationId}</if>),0) chargedEnergyYesterday,
          COALESCE((SELECT SUM(daily.discharged_energy) FROM t_emind_dashboard_daily daily
            WHERE daily.stat_date=#{day}
            <if test="stationId != null">AND daily.station_id=#{stationId}</if>),0) dischargedEnergyYesterday,
          COALESCE(SUM(real_time_power),0) realTimePower, COALESCE(SUM(surplus_energy),0) surplusEnergy,
          COALESCE(SUM(capacity),0) capacity,
          CASE WHEN SUM(capacity)&gt;0 THEN ROUND(SUM(surplus_energy)/SUM(capacity)*100,2) ELSE COALESCE(AVG(soc),0) END soc,
          COALESCE(SUM(elink_total),0) elinkTotal, COALESCE(SUM(elink_online),0) elinkOnline,
          COALESCE((SELECT COUNT(*) FROM t_emind_energy_block block WHERE block.del_flag=0
            <if test="stationId != null">AND block.station_id=#{stationId}</if>),0) eblockTotal,
          COALESCE((SELECT COUNT(*) FROM t_emind_energy_block block WHERE block.del_flag=0
            AND block.run_status BETWEEN 1 AND 4
            AND block.record_time IS NOT NULL AND block.record_time!=''
            <if test="stationId != null">AND block.station_id=#{stationId}</if>),0) eblockOnline,
          COALESCE((SELECT SUM(daily.income) FROM t_emind_dashboard_daily daily
            WHERE daily.stat_date=#{day}
            <if test="stationId != null">AND daily.station_id=#{stationId}</if>),0) incomeYesterday,
          MAX(synced_at) syncedAt
        FROM t_emind_dashboard_snapshot WHERE 1=1 <if test="stationId != null">AND station_id=#{stationId}</if>
    <select id="selectSnapshotRows" resultType="map">
        SELECT income_sum incomeSum, charged_energy_sum chargedEnergySum,
          discharged_energy_sum dischargedEnergySum, real_time_power realTimePower,
          surplus_energy surplusEnergy, soc, capacity, elink_total elinkTotal,
          elink_online elinkOnline, synced_at syncedAt
        FROM t_emind_dashboard_snapshot WHERE 1=1
        <if test="stationId != null">AND station_id=#{stationId}</if>
    </select>
    <select id="selectHistory" resultType="map">
        SELECT <choose><when test="period == 'YEAR'">DATE_FORMAT(stat_date,'%Y-%m')</when><otherwise>DATE_FORMAT(stat_date,'%Y-%m-%d')</otherwise></choose> label,
          SUM(income) income, SUM(charged_energy) chargedEnergy, SUM(discharged_energy) dischargedEnergy
    <select id="selectDailyRows" resultType="map">
        SELECT stat_date statDate, income, charged_energy chargedEnergy, discharged_energy dischargedEnergy
        FROM t_emind_dashboard_daily WHERE stat_date BETWEEN #{startDate} AND #{endDate}
        <if test="stationId != null">AND station_id=#{stationId}</if>
        GROUP BY label ORDER BY label
        ORDER BY stat_date
    </select>
    <select id="selectCurve" resultType="map">
        SELECT record_time label, SUM(power) power, AVG(soc) soc FROM t_emind_dashboard_curve WHERE 1=1
    <select id="selectCurveRows" resultType="map">
        SELECT record_time recordTime, power, soc FROM t_emind_dashboard_curve WHERE 1=1
        <if test="stationId != null">AND station_id=#{stationId}</if>
        GROUP BY record_time ORDER BY record_time
        ORDER BY record_time
    </select>
    <select id="selectEnergyBlockRows" resultType="map">
        SELECT run_status runStatus, record_time recordTime FROM t_emind_energy_block WHERE del_flag=0
        <if test="stationId != null">AND station_id=#{stationId}</if>
    </select>
</mapper>
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/emind/service/EmindStorageDashboardServiceTest.java
@@ -3,57 +3,72 @@
import com.ruoyi.other.emind.dto.EmindDashboardQuery;
import com.ruoyi.other.emind.mapper.EmindStorageDashboardMapper;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class EmindStorageDashboardServiceTest {
    @Test
    void overviewUsesOnlyLocalMapperAndDefaultsToRecentDays() {
        EmindStorageDashboardMapper mapper=mock(EmindStorageDashboardMapper.class);
        when(mapper.selectOverview(isNull(), any())).thenReturn(Collections.emptyMap());
        when(mapper.selectHistory(isNull(), eq("DAY"), any(), any())).thenReturn(Collections.emptyList());
        when(mapper.selectCurve(isNull())).thenReturn(Collections.emptyList());
        EmindDashboardQuery query=new EmindDashboardQuery();
        assertEquals(3, new EmindStorageDashboardService(mapper).overview(query).size());
        verify(mapper).selectOverview(isNull(), eq(LocalDate.now().minusDays(1)));
        verify(mapper).selectHistory(isNull(), eq("DAY"), eq(LocalDate.now().minusDays(6)), eq(LocalDate.now()));
    void overviewReadsRowsAndAggregatesThemInJava() {
        EmindStorageDashboardMapper mapper = mock(EmindStorageDashboardMapper.class);
        LocalDate yesterday = LocalDate.now().minusDays(1);
        when(mapper.selectSnapshotRows(isNull())).thenReturn(Arrays.asList(
                row("incomeSum", "10", "capacity", "100", "surplusEnergy", "40", "elinkTotal", 2, "elinkOnline", 1),
                row("incomeSum", "20", "capacity", "100", "surplusEnergy", "60", "elinkTotal", 3, "elinkOnline", 2)));
        when(mapper.selectDailyRows(isNull(), any(), any())).thenReturn(Collections.emptyList());
        when(mapper.selectDailyRows(isNull(), eq(yesterday), eq(yesterday))).thenReturn(Arrays.asList(
                row("statDate", yesterday, "income", "12.5", "chargedEnergy", "5", "dischargedEnergy", "3"),
                row("statDate", yesterday, "income", "7.5", "chargedEnergy", "2", "dischargedEnergy", "1")));
        when(mapper.selectCurveRows(isNull())).thenReturn(Collections.emptyList());
        when(mapper.selectEnergyBlockRows(isNull())).thenReturn(Arrays.asList(
                row("runStatus", 1, "recordTime", "10:00"), row("runStatus", 5, "recordTime", "10:00")));
        Map<String, Object> summary = (Map<String, Object>) new EmindStorageDashboardService(mapper)
                .overview(new EmindDashboardQuery()).get("summary");
        assertEquals(new BigDecimal("30"), summary.get("incomeSum"));
        assertEquals(new BigDecimal("50"), summary.get("soc"));
        assertEquals(2, summary.get("eblockTotal"));
        assertEquals(1L, summary.get("eblockOnline"));
        assertEquals(new BigDecimal("20.0"), summary.get("incomeYesterday"));
        assertEquals(new BigDecimal("7"), summary.get("chargedEnergyYesterday"));
        verify(mapper).selectDailyRows(isNull(), eq(yesterday), eq(yesterday));
    }
    @Test
    void overviewFillsMissingDailyHistoryWithZeroValues() {
    void overviewGroupsDailyRowsByMonthInJava() {
        EmindStorageDashboardMapper mapper = mock(EmindStorageDashboardMapper.class);
        LocalDate start = LocalDate.of(2026, 7, 20);
        LocalDate end = LocalDate.of(2026, 7, 22);
        Map<String, Object> recordedDay = new HashMap<>();
        recordedDay.put("label", "2026-07-21");
        recordedDay.put("income", 12);
        recordedDay.put("chargedEnergy", 5);
        recordedDay.put("dischargedEnergy", 3);
        when(mapper.selectOverview(isNull(), any())).thenReturn(Collections.emptyMap());
        when(mapper.selectHistory(isNull(), eq("DAY"), eq(start), eq(end))).thenReturn(Collections.singletonList(recordedDay));
        when(mapper.selectCurve(isNull())).thenReturn(Collections.emptyList());
        LocalDate start = LocalDate.of(2026, 7, 1);
        LocalDate end = LocalDate.of(2026, 8, 31);
        when(mapper.selectSnapshotRows(isNull())).thenReturn(Collections.emptyList());
        when(mapper.selectDailyRows(isNull(), any(), any())).thenReturn(Collections.emptyList());
        when(mapper.selectDailyRows(isNull(), eq(start), eq(end))).thenReturn(Arrays.asList(
                row("statDate", LocalDate.of(2026, 7, 1), "income", "2"),
                row("statDate", LocalDate.of(2026, 7, 2), "income", "3")));
        when(mapper.selectCurveRows(isNull())).thenReturn(Collections.emptyList());
        when(mapper.selectEnergyBlockRows(isNull())).thenReturn(Collections.emptyList());
        EmindDashboardQuery query = new EmindDashboardQuery();
        query.setPeriod("DAY");
        query.setStartDate(start);
        query.setEndDate(end);
        query.setPeriod("YEAR"); query.setStartDate(start); query.setEndDate(end);
        Map<String, Object> result = new EmindStorageDashboardService(mapper).overview(query);
        List<Map<String, Object>> history = (List<Map<String, Object>>) result.get("history");
        List<Map<String, Object>> history = (List<Map<String, Object>>) new EmindStorageDashboardService(mapper)
                .overview(query).get("history");
        List<Object> labels = new ArrayList<>();
        for (Map<String, Object> row : history) labels.add(row.get("label"));
        assertEquals(Arrays.asList("2026-07-20", "2026-07-21", "2026-07-22"), labels);
        assertEquals(BigDecimal.ZERO, history.get(0).get("income"));
        assertEquals(12, history.get(1).get("income"));
        assertEquals(BigDecimal.ZERO, history.get(2).get("chargedEnergy"));
        assertEquals("2026-07", history.get(0).get("label"));
        assertEquals(new BigDecimal("5"), history.get(0).get("income"));
        assertEquals("2026-08", history.get(1).get("label"));
    }
    private Map<String, Object> row(Object... values) {
        Map<String, Object> row = new java.util.HashMap<>();
        for (int i = 0; i < values.length; i += 2) row.put((String) values[i], values[i + 1]);
        return row;
    }
}