|
|
@@ -1,25 +1,23 @@
|
|
1
|
1
|
package com.sundot.airport.ledger.service.impl;
|
|
2
|
2
|
|
|
3
|
|
-import java.util.Collections;
|
|
4
|
|
-import java.util.HashMap;
|
|
5
|
|
-import java.util.List;
|
|
6
|
|
-import java.util.Map;
|
|
7
|
|
-import java.util.Objects;
|
|
8
|
|
-import java.util.Set;
|
|
|
3
|
+import java.util.*;
|
|
9
|
4
|
import java.util.stream.Collectors;
|
|
10
|
5
|
|
|
11
|
6
|
import cn.hutool.core.collection.CollUtil;
|
|
12
|
7
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
8
|
+import com.google.common.collect.Lists;
|
|
13
|
9
|
import com.google.common.collect.Maps;
|
|
14
|
10
|
import com.sundot.airport.common.utils.DateUtils;
|
|
15
|
11
|
import com.sundot.airport.ledger.domain.OperationStationHourlyThroughput;
|
|
16
|
12
|
import com.sundot.airport.ledger.domain.vo.AreaFlowVO;
|
|
|
13
|
+import com.sundot.airport.ledger.domain.vo.LaneThroughputResVO;
|
|
17
|
14
|
import com.sundot.airport.ledger.domain.vo.StationHourlyThroughputGroupResVO;
|
|
18
|
15
|
import com.sundot.airport.ledger.domain.vo.StationHourlyThroughputVO;
|
|
19
|
16
|
import com.sundot.airport.ledger.mapper.OperationStationHourlyThroughputMapper;
|
|
20
|
17
|
import com.sundot.airport.ledger.service.IOperationStationHourlyThroughputService;
|
|
21
|
18
|
import com.sundot.airport.system.domain.BasePosition;
|
|
22
|
19
|
import com.sundot.airport.system.mapper.BasePositionMapper;
|
|
|
20
|
+import lombok.extern.slf4j.Slf4j;
|
|
23
|
21
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
24
|
22
|
import org.springframework.stereotype.Service;
|
|
25
|
23
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
@@ -27,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|
27
|
25
|
/**
|
|
28
|
26
|
* 站级时间段别总过检Service业务层处理
|
|
29
|
27
|
*/
|
|
|
28
|
+@Slf4j
|
|
30
|
29
|
@Service
|
|
31
|
30
|
public class OperationStationHourlyThroughputServiceImpl extends ServiceImpl<OperationStationHourlyThroughputMapper, OperationStationHourlyThroughput> implements IOperationStationHourlyThroughputService {
|
|
32
|
31
|
|
|
|
@@ -107,8 +106,15 @@ public class OperationStationHourlyThroughputServiceImpl extends ServiceImpl<Ope
|
|
107
|
106
|
@Override
|
|
108
|
107
|
public List<StationHourlyThroughputGroupResVO> countStationHourlyThroughput() {
|
|
109
|
108
|
List<StationHourlyThroughputVO> stationHourlyThroughputList = this.baseMapper.countStationHourlyThroughput();
|
|
110
|
|
- if (CollUtil.isEmpty(stationHourlyThroughputList)) {
|
|
111
|
|
- return Collections.emptyList();
|
|
|
109
|
+ stationHourlyThroughputList = CollUtil.emptyIfNull(stationHourlyThroughputList);
|
|
|
110
|
+
|
|
|
111
|
+ log.info("SQL查询返回数据条数: {}", stationHourlyThroughputList.size());
|
|
|
112
|
+ if (!stationHourlyThroughputList.isEmpty()) {
|
|
|
113
|
+ log.info("SQL查询返回的时间点列表: {}",
|
|
|
114
|
+ stationHourlyThroughputList.stream()
|
|
|
115
|
+ .map(StationHourlyThroughputVO::getHourOfDay)
|
|
|
116
|
+ .distinct()
|
|
|
117
|
+ .collect(Collectors.toList()));
|
|
112
|
118
|
}
|
|
113
|
119
|
|
|
114
|
120
|
Map<Long, BasePosition> areaInfoMap = Maps.newHashMap();
|
|
|
@@ -124,33 +130,146 @@ public class OperationStationHourlyThroughputServiceImpl extends ServiceImpl<Ope
|
|
124
|
130
|
areaInfoMap = CollUtil.emptyIfNull(basePositions).stream().collect(Collectors.toMap(BasePosition::getId, v -> v));
|
|
125
|
131
|
}
|
|
126
|
132
|
|
|
127
|
|
- // 2. 核心:按小时分组 → 组装成前端需要的层级结构
|
|
128
|
|
- Map<String, List<StationHourlyThroughputVO>> hourGroupMap = stationHourlyThroughputList.stream().collect(Collectors.groupingBy(StationHourlyThroughputVO::getHourOfDay));
|
|
|
133
|
+ // 2. 按时间点分组
|
|
|
134
|
+ Map<String, List<StationHourlyThroughputVO>> hourGroupMap = stationHourlyThroughputList.stream()
|
|
|
135
|
+ .collect(Collectors.groupingBy(StationHourlyThroughputVO::getHourOfDay));
|
|
|
136
|
+
|
|
|
137
|
+ // 3. 生成近24个具体时间点(从当前时间向前推24小时)
|
|
|
138
|
+ List<String> last24Hours = this.generateLast24Hours();
|
|
|
139
|
+
|
|
|
140
|
+ log.info("需要补齐的24个时间点: {}", last24Hours);
|
|
129
|
141
|
|
|
130
|
142
|
Map<Long, BasePosition> finalAreaInfoMap = areaInfoMap;
|
|
131
|
|
- return hourGroupMap.entrySet().stream().map(entry -> {
|
|
132
|
|
- String hour = entry.getKey();
|
|
133
|
|
- List<StationHourlyThroughputVO> hourDataList = entry.getValue();
|
|
134
|
|
-
|
|
135
|
|
- // 构建区域列表
|
|
136
|
|
- List<AreaFlowVO> areaList = hourDataList.stream().map(stats -> {
|
|
137
|
|
- BasePosition basePosition = finalAreaInfoMap.get(stats.getAreaId());
|
|
138
|
|
-
|
|
139
|
|
- AreaFlowVO areaFlowVO = new AreaFlowVO();
|
|
140
|
|
- areaFlowVO.setAreaId(stats.getAreaId());
|
|
141
|
|
- areaFlowVO.setAreaName(Objects.nonNull(basePosition) ? basePosition.getName() : "");
|
|
142
|
|
- areaFlowVO.setAreaFlow(stats.getAreaFlow());
|
|
143
|
|
- return areaFlowVO;
|
|
144
|
|
- }).collect(Collectors.toList());
|
|
145
|
|
-
|
|
146
|
|
- // 构建小时分组对象
|
|
147
|
|
- StationHourlyThroughputGroupResVO groupVO = new StationHourlyThroughputGroupResVO();
|
|
148
|
|
- groupVO.setHourOfDay(hour);
|
|
149
|
|
- // 总人流量(取第一条即可,同小时数据总流量一致)
|
|
150
|
|
- groupVO.setTotalHourFlow(hourDataList.get(0).getTotalHourFlow());
|
|
151
|
|
- // 区域信息
|
|
152
|
|
- groupVO.setAreaList(areaList);
|
|
153
|
|
- return groupVO;
|
|
154
|
|
- }).collect(Collectors.toList());
|
|
|
143
|
+
|
|
|
144
|
+ // 4. 补齐24个时间点数据
|
|
|
145
|
+ List<StationHourlyThroughputGroupResVO> resultList = Lists.newArrayList();
|
|
|
146
|
+ for (String hourTime : last24Hours) {
|
|
|
147
|
+ // 处理日期 转换hourTime格式的日期,例如:2026-05-21 00:00转成0:00,2026-05-21 01:00转成01:00,2026-05-21 11:00转成11:00
|
|
|
148
|
+ // 提取小时和分钟部分(去掉日期)
|
|
|
149
|
+ String hourOnly = this.extractHourAndMinute(hourTime);
|
|
|
150
|
+
|
|
|
151
|
+ if (hourGroupMap.containsKey(hourTime)) {
|
|
|
152
|
+ // 存在数据的时间点,正常组装
|
|
|
153
|
+ List<StationHourlyThroughputVO> hourDataList = hourGroupMap.get(hourTime);
|
|
|
154
|
+
|
|
|
155
|
+ List<AreaFlowVO> areaList = hourDataList.stream().map(stats -> {
|
|
|
156
|
+ BasePosition basePosition = finalAreaInfoMap.get(stats.getAreaId());
|
|
|
157
|
+ AreaFlowVO areaFlowVO = new AreaFlowVO();
|
|
|
158
|
+ areaFlowVO.setAreaId(stats.getAreaId());
|
|
|
159
|
+ areaFlowVO.setAreaName(Objects.nonNull(basePosition) ? basePosition.getName() : "");
|
|
|
160
|
+ areaFlowVO.setAreaFlow(stats.getAreaFlow());
|
|
|
161
|
+ return areaFlowVO;
|
|
|
162
|
+ }).collect(Collectors.toList());
|
|
|
163
|
+
|
|
|
164
|
+ // 补充缺失的区域信息
|
|
|
165
|
+ if (CollUtil.isEmpty(areaList)){
|
|
|
166
|
+ areaInfoMap.forEach((id, basePosition) -> {
|
|
|
167
|
+ areaList.add(new AreaFlowVO(id, basePosition.getName(), 0));
|
|
|
168
|
+ });
|
|
|
169
|
+ } else {
|
|
|
170
|
+ if (areaList.size() != areaInfoMap.size()) {
|
|
|
171
|
+ areaInfoMap.forEach((id, basePosition) -> {
|
|
|
172
|
+ AreaFlowVO newAreaFlow = areaList.stream().filter(areaFlowVO -> areaFlowVO.getAreaId().equals(id)).findFirst().orElse(null);
|
|
|
173
|
+ if (Objects.isNull(newAreaFlow)) {
|
|
|
174
|
+ areaList.add(new AreaFlowVO(id, basePosition.getName(), 0));
|
|
|
175
|
+ }
|
|
|
176
|
+ });
|
|
|
177
|
+ }
|
|
|
178
|
+ }
|
|
|
179
|
+
|
|
|
180
|
+ // 按区域id升序排序
|
|
|
181
|
+ if (CollUtil.isNotEmpty(areaList)){
|
|
|
182
|
+ areaList.sort(Comparator.comparing(AreaFlowVO::getAreaId));
|
|
|
183
|
+ }
|
|
|
184
|
+
|
|
|
185
|
+ StationHourlyThroughputGroupResVO groupVO = new StationHourlyThroughputGroupResVO();
|
|
|
186
|
+ groupVO.setHourOfDay(hourTime);
|
|
|
187
|
+ groupVO.setHour(hourOnly);
|
|
|
188
|
+ groupVO.setTotalHourFlow(hourDataList.get(0).getTotalHourFlow());
|
|
|
189
|
+ groupVO.setAreaList(areaList);
|
|
|
190
|
+ resultList.add(groupVO);
|
|
|
191
|
+ } else {
|
|
|
192
|
+ List<AreaFlowVO> areaList = Lists.newArrayList();
|
|
|
193
|
+ areaInfoMap.forEach((id, basePosition) -> {
|
|
|
194
|
+ areaList.add(new AreaFlowVO(id, basePosition.getName(), 0));
|
|
|
195
|
+ });
|
|
|
196
|
+
|
|
|
197
|
+ // 按区域id升序排序
|
|
|
198
|
+ if (CollUtil.isNotEmpty(areaList)){
|
|
|
199
|
+ areaList.sort(Comparator.comparing(AreaFlowVO::getAreaId));
|
|
|
200
|
+ }
|
|
|
201
|
+
|
|
|
202
|
+ // 缺失的时间点,补充空数据
|
|
|
203
|
+ StationHourlyThroughputGroupResVO emptyVO = new StationHourlyThroughputGroupResVO();
|
|
|
204
|
+ emptyVO.setHourOfDay(hourTime);
|
|
|
205
|
+ emptyVO.setHour(hourOnly);
|
|
|
206
|
+ emptyVO.setTotalHourFlow(0);
|
|
|
207
|
+ emptyVO.setAreaList(areaList);
|
|
|
208
|
+ resultList.add(emptyVO);
|
|
|
209
|
+ }
|
|
|
210
|
+ }
|
|
|
211
|
+
|
|
|
212
|
+ log.info("补齐后返回的总时间点数: {}", resultList.size());
|
|
|
213
|
+ return resultList;
|
|
|
214
|
+ }
|
|
|
215
|
+
|
|
|
216
|
+ /**
|
|
|
217
|
+ * 生成近24个具体时间点(从当前时间向前推24小时)
|
|
|
218
|
+ * 格式:yyyy-MM-dd HH:00
|
|
|
219
|
+ */
|
|
|
220
|
+ private List<String> generateLast24Hours() {
|
|
|
221
|
+ List<String> hours = new ArrayList<>();
|
|
|
222
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
223
|
+ calendar.set(Calendar.MINUTE, 0);
|
|
|
224
|
+ calendar.set(Calendar.SECOND, 0);
|
|
|
225
|
+ calendar.set(Calendar.MILLISECOND, 0);
|
|
|
226
|
+
|
|
|
227
|
+ // 从当前小时向前推23小时,共24个时间点
|
|
|
228
|
+ for (int i = 23; i >= 0; i--) {
|
|
|
229
|
+ Calendar cal = (Calendar) calendar.clone();
|
|
|
230
|
+ cal.add(Calendar.HOUR_OF_DAY, -i);
|
|
|
231
|
+
|
|
|
232
|
+ String hourTime = String.format("%04d-%02d-%02d %02d:00",
|
|
|
233
|
+ cal.get(Calendar.YEAR),
|
|
|
234
|
+ cal.get(Calendar.MONTH) + 1,
|
|
|
235
|
+ cal.get(Calendar.DAY_OF_MONTH),
|
|
|
236
|
+ cal.get(Calendar.HOUR_OF_DAY));
|
|
|
237
|
+ hours.add(hourTime);
|
|
|
238
|
+ }
|
|
|
239
|
+
|
|
|
240
|
+ return hours;
|
|
|
241
|
+ }
|
|
|
242
|
+
|
|
|
243
|
+ /**
|
|
|
244
|
+ * 从完整日期时间中提取小时和分钟部分
|
|
|
245
|
+ * 例如:2026-05-21 00:00 → 0:00
|
|
|
246
|
+ * 2026-05-21 01:00 → 01:00
|
|
|
247
|
+ * 2026-05-21 11:00 → 11:00
|
|
|
248
|
+ *
|
|
|
249
|
+ * @param hourTime 完整日期时间字符串(格式:yyyy-MM-dd HH:00)
|
|
|
250
|
+ * @return 小时和分钟字符串(格式:H:00 或 HH:00)
|
|
|
251
|
+ */
|
|
|
252
|
+ private String extractHourAndMinute(String hourTime) {
|
|
|
253
|
+ if (hourTime == null || hourTime.trim().isEmpty()) {
|
|
|
254
|
+ return "";
|
|
|
255
|
+ }
|
|
|
256
|
+
|
|
|
257
|
+ // 按空格分割,取时间部分
|
|
|
258
|
+ String[] parts = hourTime.split(" ");
|
|
|
259
|
+ if (parts.length < 2) {
|
|
|
260
|
+ return hourTime;
|
|
|
261
|
+ }
|
|
|
262
|
+
|
|
|
263
|
+ String timePart = parts[1]; // 获取 "00:00" 部分
|
|
|
264
|
+
|
|
|
265
|
+ // 去掉小时的前导0(如果有的话)
|
|
|
266
|
+ String[] timeComponents = timePart.split(":");
|
|
|
267
|
+ if (timeComponents.length >= 2) {
|
|
|
268
|
+ int hour = Integer.parseInt(timeComponents[0]);
|
|
|
269
|
+ String minute = timeComponents[1];
|
|
|
270
|
+ return hour + ":" + minute;
|
|
|
271
|
+ }
|
|
|
272
|
+
|
|
|
273
|
+ return timePart;
|
|
155
|
274
|
}
|
|
156
|
275
|
}
|