8 小时以前 b29d86d9ed34fd890eb14cb7a75f6ae4f7148865
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package cn.iocoder.yudao.module.mes.service.trace.consumer;
 
import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.trace.consumer.MesTraceConsumerScanEventDO;
import cn.iocoder.yudao.module.mes.dal.mysql.trace.consumer.MesTraceConsumerScanStatisticsMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
 
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
 
/**
 * 消费者扫码访问统计 Service 实现
 * <p>
 * 采用查明细 + Java 侧聚合:避免统计 SQL 与 MySQL/H2 方言差异,同时不暴露动态列 SQL。
 *
 * @author 超级管理员
 */
@Service
public class MesTraceConsumerScanStatisticsServiceImpl implements MesTraceConsumerScanStatisticsService {
 
    /**
     * 分组维度白名单:仅允许按固定字段聚合,非法维度直接拒绝
     */
    private static final Map<String, Function<MesTraceConsumerScanEventDO, String>> GROUP_COLUMNS = Map.of(
            "sourceType", e -> e.getSourceType(),
            "success", e -> Boolean.TRUE.equals(e.getSuccess()) ? "1" : "0",
            "channel", MesTraceConsumerScanEventDO::getChannel,
            "province", MesTraceConsumerScanEventDO::getProvince,
            "city", MesTraceConsumerScanEventDO::getCity,
            "batchId", e -> e.getBatchId() == null ? null : String.valueOf(e.getBatchId()));
 
    /**
     * 空分组键占位符:Collectors.groupingBy 不接受 null key,先统一替换再在展示时映射为"未知"
     */
    private static final String UNKNOWN_KEY = "__unknown__";
 
    @Resource
    private MesTraceConsumerScanStatisticsMapper mapper;
 
    @Override
    public MesTraceConsumerScanSummaryRespVO getSummary(LocalDateTime startTime, LocalDateTime endTime) {
        List<MesTraceConsumerScanEventDO> events = mapper.selectListByTime(startTime, endTime);
        MesTraceConsumerScanSummaryRespVO vo = new MesTraceConsumerScanSummaryRespVO();
        vo.setTotal((long) events.size());
        vo.setSuccess(events.stream().filter(e -> Boolean.TRUE.equals(e.getSuccess())).count());
        vo.setFailed(events.stream().filter(e -> !Boolean.TRUE.equals(e.getSuccess())).count());
        vo.setUniqueBatchCount(events.stream().map(MesTraceConsumerScanEventDO::getBatchId)
                .filter(java.util.Objects::nonNull).distinct().count());
        return vo;
    }
 
    @Override
    public List<MesTraceConsumerScanTrendRespVO> getTrend(LocalDateTime startTime, LocalDateTime endTime) {
        List<MesTraceConsumerScanEventDO> events = mapper.selectListByTime(startTime, endTime);
        // 按日期聚合
        Map<LocalDate, MesTraceConsumerScanTrendRespVO> data = new TreeMap<>();
        LocalDate min = null;
        LocalDate max = null;
        for (MesTraceConsumerScanEventDO event : events) {
            LocalDate date = event.getEventTime().toLocalDate();
            MesTraceConsumerScanTrendRespVO vo = data.computeIfAbsent(date, d -> {
                MesTraceConsumerScanTrendRespVO v = new MesTraceConsumerScanTrendRespVO();
                v.setDate(d.toString());
                v.setTotal(0L);
                v.setSuccess(0L);
                v.setFailed(0L);
                return v;
            });
            vo.setTotal(vo.getTotal() + 1);
            if (Boolean.TRUE.equals(event.getSuccess())) {
                vo.setSuccess(vo.getSuccess() + 1);
            } else {
                vo.setFailed(vo.getFailed() + 1);
            }
            if (min == null || date.isBefore(min)) {
                min = date;
            }
            if (max == null || date.isAfter(max)) {
                max = date;
            }
        }
        // 补齐区间内无数据的日期(时间参数为空时退回已有数据的最小/最大日期区间)
        LocalDate start = startTime != null ? startTime.toLocalDate() : min;
        LocalDate end = endTime != null
                ? (endTime.toLocalTime().equals(LocalTime.MIDNIGHT) ? endTime.toLocalDate().minusDays(1) : endTime.toLocalDate())
                : max;
        if (start != null && end != null && !start.isAfter(end)) {
            for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) {
                data.computeIfAbsent(day, d -> {
                    MesTraceConsumerScanTrendRespVO v = new MesTraceConsumerScanTrendRespVO();
                    v.setDate(d.toString());
                    v.setTotal(0L);
                    v.setSuccess(0L);
                    v.setFailed(0L);
                    return v;
                });
            }
        }
        return new ArrayList<>(data.values());
    }
 
    @Override
    public List<MesTraceConsumerScanDistributionRespVO> getDistribution(MesTraceConsumerScanDistributionReqVO reqVO) {
        Function<MesTraceConsumerScanEventDO, String> extractor = GROUP_COLUMNS.get(reqVO.getGroupBy());
        if (extractor == null) {
            throw new IllegalArgumentException("不支持的分组维度");
        }
        List<MesTraceConsumerScanEventDO> events = mapper.selectListByTime(reqVO.getStartTime(), reqVO.getEndTime());
        // 按数量倒序分组;groupingBy 不接受 null key,先统一替换为占位符再聚合
        Map<String, Long> counts = events.stream().collect(Collectors.groupingBy(e -> {
            String key = extractor.apply(e);
            return key == null ? UNKNOWN_KEY : key;
        }, Collectors.counting()));
        return counts.entrySet().stream()
                .sorted(Comparator.comparing(Map.Entry<String, Long>::getValue).reversed()
                        .thenComparing(Map.Entry.comparingByKey()))
                .map(entry -> {
                    MesTraceConsumerScanDistributionRespVO vo = new MesTraceConsumerScanDistributionRespVO();
                    String name = entry.getKey();
                    vo.setName(UNKNOWN_KEY.equals(name) ? "未知" : displayName(reqVO.getGroupBy(), name));
                    vo.setCount(entry.getValue());
                    return vo;
                })
                .collect(Collectors.toCollection(ArrayList::new));
    }
 
    /**
     * 布尔分区做可读映射,保证统计口径与前端展示一致
     */
    private String displayName(String groupBy, String name) {
        if ("success".equals(groupBy)) {
            return "1".equals(name) ? "成功" : "失败";
        }
        return name;
    }
 
}