1
|
1
|
package org.thingsboard.server.dao.yunteng.impl;
|
2
|
2
|
|
|
3
|
+import static org.thingsboard.server.dao.timeseries.AbstractCassandraBaseTimeseriesDao.DESC_ORDER;
|
|
4
|
+
|
3
|
5
|
import com.google.common.util.concurrent.*;
|
|
6
|
+import java.time.*;
|
|
7
|
+import java.time.format.DateTimeFormatter;
|
|
8
|
+import java.time.temporal.TemporalAdjusters;
|
|
9
|
+import java.util.*;
|
|
10
|
+import java.util.concurrent.CompletableFuture;
|
|
11
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
12
|
+import java.util.concurrent.ConcurrentMap;
|
|
13
|
+import java.util.concurrent.ExecutionException;
|
|
14
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
15
|
+import java.util.concurrent.atomic.AtomicReference;
|
|
16
|
+import java.util.stream.Collectors;
|
4
|
17
|
import lombok.RequiredArgsConstructor;
|
5
|
18
|
import org.apache.commons.lang3.StringUtils;
|
6
|
19
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
...
|
...
|
@@ -10,6 +23,7 @@ import org.springframework.web.context.request.async.DeferredResult; |
10
|
23
|
import org.thingsboard.server.common.data.ApiUsageState;
|
11
|
24
|
import org.thingsboard.server.common.data.alarm.AlarmStatus;
|
12
|
25
|
import org.thingsboard.server.common.data.id.CustomerId;
|
|
26
|
+import org.thingsboard.server.common.data.id.DeviceId;
|
13
|
27
|
import org.thingsboard.server.common.data.id.EntityId;
|
14
|
28
|
import org.thingsboard.server.common.data.id.TenantId;
|
15
|
29
|
import org.thingsboard.server.common.data.kv.Aggregation;
|
...
|
...
|
@@ -26,25 +40,16 @@ import org.thingsboard.server.common.data.yunteng.enums.TrendType; |
26
|
40
|
import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
|
27
|
41
|
import org.thingsboard.server.dao.entity.EntityService;
|
28
|
42
|
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
|
43
|
+import org.thingsboard.server.dao.usagerecord.ApiUsageStateDao;
|
29
|
44
|
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
|
30
|
45
|
import org.thingsboard.server.dao.yunteng.mapper.DeviceMapper;
|
31
|
|
-import org.thingsboard.server.dao.yunteng.mapper.UserMapper;
|
32
|
46
|
import org.thingsboard.server.dao.yunteng.mapper.TkAlarmMapper;
|
|
47
|
+import org.thingsboard.server.dao.yunteng.mapper.UserMapper;
|
33
|
48
|
import org.thingsboard.server.dao.yunteng.service.HomePageService;
|
34
|
49
|
import org.thingsboard.server.dao.yunteng.service.TkDeviceProfileService;
|
35
|
50
|
import org.thingsboard.server.dao.yunteng.service.TkTenantService;
|
36
|
51
|
import org.thingsboard.server.dao.yunteng.service.TkUserService;
|
37
|
52
|
|
38
|
|
-import java.time.*;
|
39
|
|
-import java.time.format.DateTimeFormatter;
|
40
|
|
-import java.time.temporal.TemporalAdjusters;
|
41
|
|
-import java.util.*;
|
42
|
|
-import java.util.concurrent.CompletableFuture;
|
43
|
|
-import java.util.concurrent.ExecutionException;
|
44
|
|
-import java.util.concurrent.atomic.AtomicInteger;
|
45
|
|
-import java.util.concurrent.atomic.AtomicReference;
|
46
|
|
-import java.util.stream.Collectors;
|
47
|
|
-
|
48
|
53
|
@Service
|
49
|
54
|
@RequiredArgsConstructor
|
50
|
55
|
public class TkHomePageServiceImpl implements HomePageService {
|
...
|
...
|
@@ -58,7 +63,9 @@ public class TkHomePageServiceImpl implements HomePageService { |
58
|
63
|
private final EntityService entityService;
|
59
|
64
|
|
60
|
65
|
private final ApiUsageStateService apiUsageStateService;
|
|
66
|
+ private final ApiUsageStateDao apiUsageStateDao;
|
61
|
67
|
|
|
68
|
+ /** 查询遥测数据,兼容多种数据库 */
|
62
|
69
|
private final TimeseriesService timeseriesService;
|
63
|
70
|
|
64
|
71
|
private final TkUserService tkUserService;
|
...
|
...
|
@@ -81,41 +88,47 @@ public class TkHomePageServiceImpl implements HomePageService { |
81
|
88
|
HomePageLeftTopDTO homePageLeftTopDTO = new HomePageLeftTopDTO();
|
82
|
89
|
HomeDeviceInfoDTO homeDeviceInfo;
|
83
|
90
|
LocalDateTime nowTime = LocalDateTime.now();
|
84
|
|
- LocalDateTime startTime = LocalDateTime.of(nowTime.toLocalDate(), LocalTime.MIN);
|
85
|
|
- LocalDateTime endTime = LocalDateTime.of(nowTime.toLocalDate(), LocalTime.MAX);
|
|
91
|
+ LocalDateTime todayBegin = LocalDateTime.of(nowTime.toLocalDate(), LocalTime.MIN);
|
|
92
|
+ LocalDateTime todayEnd = LocalDateTime.of(nowTime.toLocalDate(), LocalTime.MAX);
|
86
|
93
|
int zero = FastIotConstants.MagicNumber.ZERO;
|
87
|
94
|
HomePageTopMessage messageInfo = new HomePageTopMessage(zero);
|
88
|
95
|
BaseHomePageTop alarm = new BaseHomePageTop(zero);
|
89
|
96
|
BaseHomePageTop product = new BaseHomePageTop(zero);
|
90
|
97
|
Map<String, Object> queryMap = new HashMap<>();
|
|
98
|
+ TenantId currentTenantId = TenantId.fromUUID(UUID.fromString(tenantId));
|
91
|
99
|
String customerId = null;
|
92
|
100
|
List<DeviceDTO> deviceList;
|
93
|
101
|
if (isPtSysAdmin || isPtAdmin) {
|
94
|
|
- setTenantInfoData(homePageLeftTopDTO, startTime, endTime);
|
95
|
|
- setCustomerInfoData(homePageLeftTopDTO, startTime, endTime);
|
|
102
|
+ setTenantInfoData(homePageLeftTopDTO, todayBegin, todayEnd);
|
|
103
|
+ setCustomerInfoData(homePageLeftTopDTO, todayBegin, todayEnd);
|
96
|
104
|
} else if (isTenantAdmin) {
|
97
|
105
|
queryMap.put("tenantId", tenantId);
|
98
|
|
- setAlarmAndMessageInfo(tenantId, messageInfo, alarm);
|
|
106
|
+
|
|
107
|
+ ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(currentTenantId);
|
|
108
|
+ setAllAlarmAndMessageInfo(currentTenantId, apiUsageState.getId(), messageInfo, alarm);
|
|
109
|
+
|
|
110
|
+ // 查询今日数据
|
|
111
|
+ setTodayAlarmAndMessageInfo(
|
|
112
|
+ todayBegin, todayEnd, currentTenantId, apiUsageState.getId(), messageInfo, alarm);
|
99
|
113
|
|
100
|
114
|
} else {
|
101
|
115
|
customerId = userMapper.findCustomerIdByUserId(currentUserId);
|
102
|
116
|
if (StringUtils.isNotEmpty(customerId)) {
|
103
|
117
|
// 查询customerId
|
104
|
118
|
queryMap.put("customerId", customerId);
|
105
|
|
- long startTs = startTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
106
|
|
- List<BaseHomePageTop> baseHomePageTopList =
|
107
|
|
- deviceMapper.findDeviceMessageInfo(startTs, customerId);
|
108
|
|
- if (baseHomePageTopList.size() > zero) {
|
109
|
|
- BaseHomePageTop baseHomePageTop = baseHomePageTopList.get(0);
|
110
|
|
- messageInfo.setMessageCount(baseHomePageTop.getSumCount());
|
111
|
|
- messageInfo.setTodayMessageAdd(baseHomePageTop.getTodayAdd() ==null ? zero:baseHomePageTop.getTodayAdd());
|
|
119
|
+ List<String> tbDevices = deviceMapper.findDeviceIdsByCustomerId(customerId);
|
|
120
|
+ for (String devId : tbDevices) {
|
|
121
|
+ setCustomerMessageInfo(
|
|
122
|
+ todayBegin, todayEnd, currentTenantId, DeviceId.fromString(devId), messageInfo);
|
112
|
123
|
}
|
|
124
|
+ long startTs = todayBegin.toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
113
|
125
|
List<BaseHomePageTop> alarmList =
|
114
|
126
|
deviceMapper.findDeviceAlarmInfoByCustomer(startTs, customerId);
|
115
|
127
|
if (alarmList.size() > zero) {
|
116
|
128
|
BaseHomePageTop baseHomePageTop = alarmList.get(0);
|
117
|
129
|
alarm.setSumCount(baseHomePageTop.getSumCount());
|
118
|
|
- alarm.setTodayAdd(baseHomePageTop.getTodayAdd() == null ? zero :baseHomePageTop.getTodayAdd());
|
|
130
|
+ alarm.setTodayAdd(
|
|
131
|
+ baseHomePageTop.getTodayAdd() == null ? zero : baseHomePageTop.getTodayAdd());
|
119
|
132
|
}
|
120
|
133
|
}
|
121
|
134
|
}
|
...
|
...
|
@@ -133,21 +146,21 @@ public class TkHomePageServiceImpl implements HomePageService { |
133
|
146
|
if (StringUtils.isNotEmpty(customerId)) {
|
134
|
147
|
deviceProfileDTOList =
|
135
|
148
|
tkDeviceProfileService.findCustomerDeviceProfiles(
|
136
|
|
- tenantId, new CustomerId(UUID.fromString(customerId)), null,null);
|
|
149
|
+ tenantId, new CustomerId(UUID.fromString(customerId)), null, null);
|
137
|
150
|
}
|
138
|
151
|
}
|
139
|
152
|
int todayAdd = zero;
|
140
|
|
- if (null !=deviceProfileDTOList && !deviceProfileDTOList.isEmpty()) {
|
|
153
|
+ if (null != deviceProfileDTOList && !deviceProfileDTOList.isEmpty()) {
|
141
|
154
|
todayAdd =
|
142
|
155
|
(int)
|
143
|
156
|
deviceProfileDTOList.stream()
|
144
|
157
|
.filter(
|
145
|
158
|
item ->
|
146
|
|
- item.getCreateTime().isAfter(startTime)
|
147
|
|
- && item.getCreateTime().isBefore(endTime))
|
|
159
|
+ item.getCreateTime().isAfter(todayBegin)
|
|
160
|
+ && item.getCreateTime().isBefore(todayEnd))
|
148
|
161
|
.count();
|
149
|
162
|
}
|
150
|
|
- product.setSumCount(null !=deviceProfileDTOList?deviceProfileDTOList.size():zero);
|
|
163
|
+ product.setSumCount(null != deviceProfileDTOList ? deviceProfileDTOList.size() : zero);
|
151
|
164
|
product.setTodayAdd(todayAdd);
|
152
|
165
|
homePageLeftTopDTO.setProductInfo(product);
|
153
|
166
|
}
|
...
|
...
|
@@ -334,10 +347,12 @@ public class TkHomePageServiceImpl implements HomePageService { |
334
|
347
|
|
335
|
348
|
return filter;
|
336
|
349
|
}
|
|
350
|
+
|
337
|
351
|
/** 获取当前用户权限下的客户信息 */
|
338
|
352
|
private List<UserDetailsDTO> getCustomerInfo(Map<String, Object> filter) {
|
339
|
353
|
return userMapper.findCustomers(filter);
|
340
|
354
|
}
|
|
355
|
+
|
341
|
356
|
/**
|
342
|
357
|
* 查询实体ID的设备
|
343
|
358
|
*
|
...
|
...
|
@@ -380,7 +395,6 @@ public class TkHomePageServiceImpl implements HomePageService { |
380
|
395
|
return statistics;
|
381
|
396
|
}
|
382
|
397
|
|
383
|
|
-
|
384
|
398
|
@Override
|
385
|
399
|
public TkPageData<TenantDTO> getHomePageRightInfo(Map<String, Object> queryMap) {
|
386
|
400
|
return tenantService.getCurrentMonthExpireTenantPage(queryMap);
|
...
|
...
|
@@ -388,52 +402,45 @@ public class TkHomePageServiceImpl implements HomePageService { |
388
|
402
|
|
389
|
403
|
@Override
|
390
|
404
|
public DeferredResult<List<TkTsValue>> getHomePageLeftBottomInfo(
|
391
|
|
- String customerId,
|
|
405
|
+ TenantId tenantId,
|
|
406
|
+ CustomerId customerId,
|
392
|
407
|
long startTs,
|
393
|
408
|
long endTs,
|
394
|
409
|
long interval,
|
395
|
|
- TrendType trend,
|
396
|
|
- boolean isCustomer) {
|
|
410
|
+ TrendType trend) {
|
397
|
411
|
List<CompletableFuture<TkTsValue>> futures = new ArrayList<>();
|
398
|
412
|
long stepTs = startTs;
|
399
|
413
|
boolean isYearQuery = false;
|
400
|
|
- if((endTs - startTs)/86400000 >= 365){
|
401
|
|
- isYearQuery =true;
|
|
414
|
+ if ((endTs - startTs) / FastIotConstants.Unit.ONE_DAY_MILLISECONDS >= 365) {
|
|
415
|
+ isYearQuery = true;
|
402
|
416
|
}
|
403
|
417
|
while (stepTs < endTs) {
|
404
|
|
- Long tempStartTs = stepTs;
|
|
418
|
+ long tempStartTs = stepTs;
|
405
|
419
|
Long tempEndTs = stepTs + interval;
|
406
|
420
|
LocalDateTime startTime;
|
407
|
421
|
LocalDateTime endTime;
|
408
|
|
- if(isYearQuery){
|
409
|
|
- //按月份过滤
|
|
422
|
+ if (isYearQuery) {
|
|
423
|
+ // 按月份过滤
|
410
|
424
|
Instant instant = Instant.ofEpochMilli(tempStartTs);
|
411
|
425
|
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
412
|
426
|
startTime = getMonthStartTime(dateTime);
|
413
|
427
|
endTime = getNextMonthStartTime(dateTime);
|
414
|
|
- }else{
|
415
|
|
- //按聚合条件过滤
|
416
|
|
- startTime =
|
417
|
|
- LocalDateTime.ofEpochSecond(tempStartTs / 1000, 0, ZoneOffset.ofHours(8));
|
418
|
|
- endTime =
|
419
|
|
- LocalDateTime.ofEpochSecond(tempEndTs / 1000, 0, ZoneOffset.ofHours(8));
|
|
428
|
+ } else {
|
|
429
|
+ // 按聚合条件过滤
|
|
430
|
+ startTime = LocalDateTime.ofEpochSecond(tempStartTs / 1000, 0, ZoneOffset.ofHours(8));
|
|
431
|
+ endTime = LocalDateTime.ofEpochSecond(tempEndTs / 1000, 0, ZoneOffset.ofHours(8));
|
420
|
432
|
}
|
421
|
433
|
CompletableFuture<TkTsValue> tsValueCompletableFuture = null;
|
422
|
|
- // 客户查询的是告警统计 消息统计
|
423
|
|
- if (isCustomer) {
|
424
|
|
- String date = getDateByLocalTime(startTime,isYearQuery,true);
|
425
|
|
- if (trend == TrendType.CUSTOMER_ALARM_STATISTICAL) {
|
426
|
|
- tsValueCompletableFuture =
|
427
|
|
- findDeviceInfoByTs(
|
428
|
|
- customerId, tempStartTs, tempEndTs, date, TrendType.CUSTOMER_ALARM_STATISTICAL);
|
429
|
|
- }
|
430
|
|
- if (trend == TrendType.CUSTOMER_MESSAGE_STATISTICAL) {
|
431
|
|
- tsValueCompletableFuture =
|
432
|
|
- findDeviceInfoByTs(
|
433
|
|
- customerId, tempStartTs, tempEndTs, date, TrendType.CUSTOMER_MESSAGE_STATISTICAL);
|
434
|
|
- }
|
|
434
|
+
|
|
435
|
+ String date = getDateByLocalTime(startTime, isYearQuery, true);
|
|
436
|
+ if (trend == TrendType.CUSTOMER_MESSAGE_STATISTICAL) {
|
|
437
|
+ tsValueCompletableFuture =
|
|
438
|
+ findCusterMessageHistory(tenantId, customerId, startTime, endTime, date);
|
|
439
|
+ } else if (trend == TrendType.CUSTOMER_ALARM_STATISTICAL) {
|
|
440
|
+ tsValueCompletableFuture =
|
|
441
|
+ findCusterAlarmHistory(customerId.getId().toString(), tempStartTs, tempEndTs, date);
|
435
|
442
|
} else {
|
436
|
|
- String date = getDateByLocalTime(startTime,isYearQuery,false);
|
|
443
|
+ date = getDateByLocalTime(startTime, isYearQuery, false);
|
437
|
444
|
if (trend == TrendType.TENANT_TREND) {
|
438
|
445
|
tsValueCompletableFuture = tenantService.findTenantsByTs(startTime, endTime, date);
|
439
|
446
|
}
|
...
|
...
|
@@ -445,7 +452,7 @@ public class TkHomePageServiceImpl implements HomePageService { |
445
|
452
|
stepTs = tempEndTs;
|
446
|
453
|
}
|
447
|
454
|
final DeferredResult<List<TkTsValue>> deferredResult = new DeferredResult<>();
|
448
|
|
- if (futures.size() > FastIotConstants.MagicNumber.ZERO) {
|
|
455
|
+ if (!futures.isEmpty()) {
|
449
|
456
|
ListenableFuture<List<TkTsValue>> listenableFuture =
|
450
|
457
|
Futures.transform(
|
451
|
458
|
setFutures(futures),
|
...
|
...
|
@@ -473,7 +480,7 @@ public class TkHomePageServiceImpl implements HomePageService { |
473
|
480
|
futures.add(getTransportMessageByTenantId(tenantId, tenant.getName()));
|
474
|
481
|
});
|
475
|
482
|
final DeferredResult<List<TenantTransportMessageDTO>> deferredResult = new DeferredResult<>();
|
476
|
|
- if (futures.size() > FastIotConstants.MagicNumber.ZERO) {
|
|
483
|
+ if (!futures.isEmpty()) {
|
477
|
484
|
ListenableFuture<List<TenantTransportMessageDTO>> listenableFuture =
|
478
|
485
|
Futures.transform(
|
479
|
486
|
settableFuture(futures),
|
...
|
...
|
@@ -500,36 +507,32 @@ public class TkHomePageServiceImpl implements HomePageService { |
500
|
507
|
@Async
|
501
|
508
|
public CompletableFuture<TenantTransportMessageDTO> getTransportMessageByTenantId(
|
502
|
509
|
TenantId tenantId, String tenantName) {
|
503
|
|
- List<EntityKey> latestValues = new ArrayList<>();
|
504
|
|
- latestValues.add(new EntityKey(EntityKeyType.TIME_SERIES, "transportMsgCount"));
|
505
|
510
|
ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId);
|
506
|
|
- PageData<EntityData> pageData =
|
507
|
|
- queryEntityData(apiUsageState.getId(), tenantId, null, latestValues);
|
508
|
|
- Map<EntityKeyType, Map<String, TsValue>> latest = pageData.getData().get(0).getLatest();
|
|
511
|
+ List<String> allKeys = new ArrayList<>();
|
|
512
|
+ allKeys.add(FastIotConstants.Statistics.TRANSPORT_MSG_COUNT);
|
|
513
|
+ ListenableFuture<List<TsKvEntry>> tsKvFuture =
|
|
514
|
+ queryAllTimescalDatas(apiUsageState.getId(), tenantId, allKeys, Aggregation.SUM);
|
|
515
|
+ ListenableFuture<Long> dataFuture = Futures.transform(
|
|
516
|
+ tsKvFuture,
|
|
517
|
+ result -> {
|
|
518
|
+ AtomicReference<Long> value = new AtomicReference<>(0L);
|
|
519
|
+ result.forEach(
|
|
520
|
+ tsKvEntry -> {
|
|
521
|
+ if (tsKvEntry.getLongValue().isPresent()) {
|
|
522
|
+ value.updateAndGet(v -> v + tsKvEntry.getLongValue().get());
|
|
523
|
+ }
|
|
524
|
+ });
|
|
525
|
+ return value.get();
|
|
526
|
+ },
|
|
527
|
+ MoreExecutors.directExecutor());
|
509
|
528
|
TenantTransportMessageDTO transportMessage = new TenantTransportMessageDTO();
|
510
|
|
- latest
|
511
|
|
- .keySet()
|
512
|
|
- .forEach(
|
513
|
|
- item -> {
|
514
|
|
- if (item.equals(EntityKeyType.TIME_SERIES)) {
|
515
|
|
- Map<String, TsValue> tsValueMap = latest.get(item);
|
516
|
|
- tsValueMap
|
517
|
|
- .keySet()
|
518
|
|
- .forEach(
|
519
|
|
- mapKey -> {
|
520
|
|
- Long value =
|
521
|
|
- Long.valueOf(
|
522
|
|
- tsValueMap.get(mapKey).getValue().isEmpty()
|
523
|
|
- ? FastIotConstants.MagicNumber.ZERO + ""
|
524
|
|
- : tsValueMap.get(mapKey).getValue());
|
525
|
|
- if (mapKey.equals("transportMsgCount")) {
|
526
|
|
- transportMessage.setName(tenantName);
|
527
|
|
- transportMessage.setCount(value);
|
528
|
|
- }
|
529
|
|
- });
|
530
|
|
- }
|
531
|
|
- });
|
532
|
|
- return CompletableFuture.supplyAsync(() -> transportMessage);
|
|
529
|
+ transportMessage.setName(tenantName);
|
|
530
|
+ try {
|
|
531
|
+ transportMessage.setCount(dataFuture.get());
|
|
532
|
+ } catch (InterruptedException | ExecutionException e) {
|
|
533
|
+ throw new RuntimeException(e);
|
|
534
|
+ }
|
|
535
|
+ return CompletableFuture.supplyAsync(() -> transportMessage);
|
533
|
536
|
}
|
534
|
537
|
|
535
|
538
|
/**
|
...
|
...
|
@@ -609,46 +612,6 @@ public class TkHomePageServiceImpl implements HomePageService { |
609
|
612
|
}
|
610
|
613
|
|
611
|
614
|
/**
|
612
|
|
- * 设置首页的告警和消息
|
613
|
|
- *
|
614
|
|
- * @param tenantId 租户ID
|
615
|
|
- * @param messageInfo 消息
|
616
|
|
- * @param alarm 告警
|
617
|
|
- */
|
618
|
|
- private void setAlarmAndMessageInfo(
|
619
|
|
- String tenantId, HomePageTopMessage messageInfo, BaseHomePageTop alarm)
|
620
|
|
- throws ExecutionException, InterruptedException {
|
621
|
|
- List<String> dictionaries = new ArrayList<>();
|
622
|
|
- String key= "transportMsgCount";
|
623
|
|
- String key1= "transportDataPointsCount";
|
624
|
|
- String key2= "createdAlarmsCount";
|
625
|
|
- dictionaries.add(key);
|
626
|
|
- dictionaries.add(key1);
|
627
|
|
- dictionaries.add(key2);
|
628
|
|
- //查询所有数据
|
629
|
|
- List<KvDictionaryValueDTO> sumCount = deviceMapper.getMsgSumByTenantIdAndDictionary(tenantId,dictionaries);
|
630
|
|
- if(!sumCount.isEmpty()){
|
631
|
|
- for (KvDictionaryValueDTO kvDictionary : sumCount) {
|
632
|
|
- if(Objects.equals(key,kvDictionary.getKey())){
|
633
|
|
- messageInfo.setMessageCount(kvDictionary.getValue());
|
634
|
|
- continue;
|
635
|
|
- }
|
636
|
|
- if(Objects.equals(key1,kvDictionary.getKey())){
|
637
|
|
- messageInfo.setDataPointsCount(kvDictionary.getValue());
|
638
|
|
- continue;
|
639
|
|
- }
|
640
|
|
- if(Objects.equals(key2,kvDictionary.getKey())){
|
641
|
|
- alarm.setSumCount(kvDictionary.getValue());
|
642
|
|
- }
|
643
|
|
- }
|
644
|
|
- }
|
645
|
|
- TenantId currentTenantId = TenantId.fromUUID(UUID.fromString(tenantId));
|
646
|
|
- ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(currentTenantId);
|
647
|
|
- // 查询今日数据
|
648
|
|
- setTodayAlarmAndMessageInfo(currentTenantId, apiUsageState.getId(), messageInfo, alarm);
|
649
|
|
- }
|
650
|
|
-
|
651
|
|
- /**
|
652
|
615
|
* 设置今日告警和今日消息
|
653
|
616
|
*
|
654
|
617
|
* @param currentTenantId 当前租户
|
...
|
...
|
@@ -659,29 +622,33 @@ public class TkHomePageServiceImpl implements HomePageService { |
659
|
622
|
* @throws InterruptedException 异常
|
660
|
623
|
*/
|
661
|
624
|
private void setTodayAlarmAndMessageInfo(
|
|
625
|
+ LocalDateTime todayBegin,
|
|
626
|
+ LocalDateTime todayend,
|
662
|
627
|
TenantId currentTenantId,
|
663
|
628
|
EntityId apiUsageState,
|
664
|
629
|
HomePageTopMessage messageInfo,
|
665
|
630
|
BaseHomePageTop alarm)
|
666
|
631
|
throws ExecutionException, InterruptedException {
|
667
|
|
- Map<String, Aggregation> queries = new HashMap<>();
|
668
|
|
- queries.put("transportMsgCountHourly", Aggregation.SUM);
|
669
|
|
- queries.put("transportDataPointsCountHourly", Aggregation.SUM);
|
670
|
|
- queries.put("createdAlarmsCountHourly", Aggregation.SUM);
|
671
|
|
-
|
672
|
|
- List<TsKvEntry> tsKv = queryEntityTimeseries(apiUsageState, currentTenantId, queries);
|
|
632
|
+ List<String> allKeys = new ArrayList<>();
|
|
633
|
+ allKeys.add(FastIotConstants.Statistics.TRANSPORT_MSG_COUNT_HOURLY);
|
|
634
|
+ allKeys.add(FastIotConstants.Statistics.TRANSPORT_DATAPOINTS_COUNT_HOURLY);
|
|
635
|
+ allKeys.add(FastIotConstants.Statistics.CREATED_ALARMS_COUNT_HOURLY);
|
|
636
|
+ List<TsKvEntry> tsKv =
|
|
637
|
+ queryTodayTimescalDatas(
|
|
638
|
+ todayBegin, todayend, apiUsageState, currentTenantId, allKeys, Aggregation.SUM)
|
|
639
|
+ .get();
|
673
|
640
|
tsKv.forEach(
|
674
|
641
|
tsKvEntry -> {
|
675
|
642
|
if (tsKvEntry.getLongValue().isPresent()) {
|
676
|
643
|
int count = tsKvEntry.getLongValue().get().intValue();
|
677
|
644
|
switch (tsKvEntry.getKey()) {
|
678
|
|
- case "transportMsgCountHourly":
|
|
645
|
+ case FastIotConstants.Statistics.TRANSPORT_MSG_COUNT_HOURLY:
|
679
|
646
|
messageInfo.setTodayMessageAdd(count + messageInfo.getTodayMessageAdd());
|
680
|
647
|
break;
|
681
|
|
- case "transportDataPointsCountHourly":
|
|
648
|
+ case FastIotConstants.Statistics.TRANSPORT_DATAPOINTS_COUNT_HOURLY:
|
682
|
649
|
messageInfo.setTodayDataPointsAdd(count + messageInfo.getTodayDataPointsAdd());
|
683
|
650
|
break;
|
684
|
|
- case "createdAlarmsCountHourly":
|
|
651
|
+ case FastIotConstants.Statistics.CREATED_ALARMS_COUNT_HOURLY:
|
685
|
652
|
alarm.setTodayAdd(count + alarm.getTodayAdd());
|
686
|
653
|
break;
|
687
|
654
|
default:
|
...
|
...
|
@@ -690,6 +657,116 @@ public class TkHomePageServiceImpl implements HomePageService { |
690
|
657
|
}
|
691
|
658
|
});
|
692
|
659
|
}
|
|
660
|
+ /**
|
|
661
|
+ * 设置今日告警和今日消息
|
|
662
|
+ *
|
|
663
|
+ * @param currentTenantId 当前租户
|
|
664
|
+ * @param messageInfo 消息
|
|
665
|
+ * @param alarm 告警
|
|
666
|
+ * @throws ExecutionException 异常
|
|
667
|
+ * @throws InterruptedException 异常
|
|
668
|
+ */
|
|
669
|
+
|
|
670
|
+ private void setAllAlarmAndMessageInfo(
|
|
671
|
+ TenantId currentTenantId,
|
|
672
|
+ EntityId apiUsageId,
|
|
673
|
+ HomePageTopMessage messageInfo,
|
|
674
|
+ BaseHomePageTop alarm) {
|
|
675
|
+ List<String> allKeys = new ArrayList<>();
|
|
676
|
+ allKeys.add(FastIotConstants.Statistics.TRANSPORT_MSG_COUNT);
|
|
677
|
+ allKeys.add(FastIotConstants.Statistics.TRANSPORT_DATAPOINTS_COUNT);
|
|
678
|
+ allKeys.add(FastIotConstants.Statistics.CREATED_ALARMS_COUNT);
|
|
679
|
+ ListenableFuture<List<TsKvEntry>> tsKvFuture =
|
|
680
|
+ queryAllTimescalDatas(apiUsageId, currentTenantId, allKeys, Aggregation.SUM);
|
|
681
|
+ Futures.transform(
|
|
682
|
+ tsKvFuture,
|
|
683
|
+ result -> {
|
|
684
|
+ result.forEach(
|
|
685
|
+ tsKvEntry -> {
|
|
686
|
+ if (tsKvEntry.getLongValue().isPresent()) {
|
|
687
|
+ int count = tsKvEntry.getLongValue().get().intValue();
|
|
688
|
+ switch (tsKvEntry.getKey()) {
|
|
689
|
+ case FastIotConstants.Statistics.TRANSPORT_MSG_COUNT:
|
|
690
|
+ messageInfo.setMessageCount(count + messageInfo.getMessageCount());
|
|
691
|
+ break;
|
|
692
|
+ case FastIotConstants.Statistics.TRANSPORT_DATAPOINTS_COUNT:
|
|
693
|
+ messageInfo.setDataPointsCount(count + messageInfo.getDataPointsCount());
|
|
694
|
+ break;
|
|
695
|
+ case FastIotConstants.Statistics.CREATED_ALARMS_COUNT:
|
|
696
|
+ alarm.setSumCount(count + alarm.getSumCount());
|
|
697
|
+ break;
|
|
698
|
+ default:
|
|
699
|
+ break;
|
|
700
|
+ }
|
|
701
|
+ }
|
|
702
|
+ });
|
|
703
|
+ return null;
|
|
704
|
+ },
|
|
705
|
+ MoreExecutors.directExecutor());
|
|
706
|
+ }
|
|
707
|
+
|
|
708
|
+ private void setCustomerMessageInfo(
|
|
709
|
+ LocalDateTime todayBegin,
|
|
710
|
+ LocalDateTime todayend,
|
|
711
|
+ TenantId currentTenantId,
|
|
712
|
+ EntityId devId,
|
|
713
|
+ HomePageTopMessage messageInfo)
|
|
714
|
+ throws ExecutionException, InterruptedException {
|
|
715
|
+
|
|
716
|
+ List<String> allKeys = deviceKeys(currentTenantId, devId).get();
|
|
717
|
+ if(allKeys == null || allKeys.isEmpty()){
|
|
718
|
+ return;
|
|
719
|
+ }
|
|
720
|
+ List<TsKvEntry> allTsKv =
|
|
721
|
+ queryAllTimescalDatas(devId, currentTenantId, allKeys, Aggregation.COUNT).get();
|
|
722
|
+ if(allTsKv != null){
|
|
723
|
+ allTsKv.forEach(
|
|
724
|
+ tsKvEntry -> {
|
|
725
|
+ if (tsKvEntry.getLongValue().isPresent()) {
|
|
726
|
+ int count = tsKvEntry.getLongValue().get().intValue();
|
|
727
|
+ messageInfo.setMessageCount(count + messageInfo.getMessageCount());
|
|
728
|
+ }
|
|
729
|
+ });
|
|
730
|
+ }
|
|
731
|
+ List<TsKvEntry> todayTsKv =
|
|
732
|
+ queryTodayTimescalDatas(
|
|
733
|
+ todayBegin, todayend, devId, currentTenantId, allKeys, Aggregation.COUNT)
|
|
734
|
+ .get();
|
|
735
|
+ if(todayTsKv != null){
|
|
736
|
+ todayTsKv.forEach(
|
|
737
|
+ tsKvEntry -> {
|
|
738
|
+ if (tsKvEntry.getLongValue().isPresent()) {
|
|
739
|
+ int count = tsKvEntry.getLongValue().get().intValue();
|
|
740
|
+ messageInfo.setTodayMessageAdd(count + messageInfo.getTodayMessageAdd());
|
|
741
|
+ }
|
|
742
|
+ });
|
|
743
|
+ }
|
|
744
|
+ }
|
|
745
|
+
|
|
746
|
+ /**
|
|
747
|
+ * 获取设备的遥测指标
|
|
748
|
+ *
|
|
749
|
+ * @param tenantId 租户ID
|
|
750
|
+ * @param devId 设备ID
|
|
751
|
+ * @return
|
|
752
|
+ */
|
|
753
|
+ private ListenableFuture<List<String>> deviceKeys(TenantId tenantId, EntityId devId) {
|
|
754
|
+ List<EntityId> keyFilters = new ArrayList<>();
|
|
755
|
+ keyFilters.add(devId);
|
|
756
|
+ List<String> allKeys = timeseriesService.findAllKeysByEntityIds(tenantId, keyFilters);
|
|
757
|
+
|
|
758
|
+ ListenableFuture<List<String>> keysFuture;
|
|
759
|
+ if (allKeys == null || allKeys.isEmpty()) {
|
|
760
|
+ keysFuture =
|
|
761
|
+ Futures.transform(
|
|
762
|
+ timeseriesService.findAllLatest(tenantId, devId),
|
|
763
|
+ latest -> latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()),
|
|
764
|
+ MoreExecutors.directExecutor());
|
|
765
|
+ } else {
|
|
766
|
+ keysFuture = Futures.immediateFuture(allKeys);
|
|
767
|
+ }
|
|
768
|
+ return keysFuture;
|
|
769
|
+ }
|
693
|
770
|
|
694
|
771
|
/**
|
695
|
772
|
* 查询实体的运行数据
|
...
|
...
|
@@ -725,32 +802,53 @@ public class TkHomePageServiceImpl implements HomePageService { |
725
|
802
|
}
|
726
|
803
|
|
727
|
804
|
/**
|
728
|
|
- * 查询实体当天遥测数据的统计信息
|
|
805
|
+ * 查询实体当天遥测数据的统计信息 统计流量信息
|
729
|
806
|
*
|
730
|
807
|
* @param entityId 实体ID,例如:设备、用户、流量统计等
|
731
|
808
|
* @param currentTenantId 租户ID
|
732
|
|
- * @param statics 统计内容,包括需要统计的指标名、数据聚合的算法 统计的时间区间、数据聚合的时间窗口、
|
733
|
809
|
* @return
|
734
|
810
|
* @throws ExecutionException
|
735
|
811
|
* @throws InterruptedException
|
736
|
812
|
*/
|
737
|
|
- private List<TsKvEntry> queryEntityTimeseries(
|
738
|
|
- EntityId entityId, TenantId currentTenantId, Map<String, Aggregation> statics)
|
|
813
|
+ private ListenableFuture<List<TsKvEntry>> queryTodayTimescalDatas(
|
|
814
|
+ LocalDateTime todayBegin,
|
|
815
|
+ LocalDateTime todayend,
|
|
816
|
+ EntityId entityId,
|
|
817
|
+ TenantId currentTenantId,
|
|
818
|
+ List<String> keys,
|
|
819
|
+ Aggregation aggregation)
|
739
|
820
|
throws ExecutionException, InterruptedException {
|
740
|
|
- long startTs =
|
741
|
|
- LocalDateTime.of(LocalDateTime.now().toLocalDate(), LocalTime.MIN)
|
742
|
|
- .toInstant(ZoneOffset.of("+8"))
|
743
|
|
- .toEpochMilli();
|
744
|
|
- long endTs = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
745
|
|
- int interval = 7200000;
|
746
|
|
- int limit = (int) ((endTs - startTs) / interval) + FastIotConstants.MagicNumber.ONE;
|
|
821
|
+ long startTs = todayBegin.toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
|
822
|
+ long endTs = todayend.toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
|
823
|
+ int interval = (int) (endTs - startTs);
|
|
824
|
+ List<ReadTsKvQuery> queries = new ArrayList<>();
|
|
825
|
+ for (String key : keys) {
|
|
826
|
+ queries.add(
|
|
827
|
+ new BaseReadTsKvQuery(
|
|
828
|
+ key, startTs, endTs, interval, FastIotConstants.MagicNumber.ONE, aggregation));
|
|
829
|
+ }
|
|
830
|
+ return timeseriesService.findAll(currentTenantId, entityId, queries);
|
|
831
|
+ }
|
747
|
832
|
|
|
833
|
+ /**
|
|
834
|
+ * 查询实体累积的遥测数据的统计信息 统计流量信息
|
|
835
|
+ *
|
|
836
|
+ * @param apiUsageId 实体ID,例如:设备、用户、流量统计等
|
|
837
|
+ * @param currentTenantId 租户ID
|
|
838
|
+ * @return
|
|
839
|
+ * @throws ExecutionException
|
|
840
|
+ * @throws InterruptedException
|
|
841
|
+ */
|
|
842
|
+ private ListenableFuture<List<TsKvEntry>> queryAllTimescalDatas(
|
|
843
|
+ EntityId apiUsageId, TenantId currentTenantId, List<String> keys, Aggregation aggregation) {
|
|
844
|
+ /** 浏览信息统计 1、先从ApiUsageState获取entityId 2、查询流量统计 */
|
|
845
|
+ long startTs = 0;
|
|
846
|
+ long endTs = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
748
|
847
|
List<ReadTsKvQuery> queries = new ArrayList<>();
|
749
|
|
- for (String key : statics.keySet()) {
|
750
|
|
- queries.add(new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, statics.get(key)));
|
|
848
|
+ for (String key : keys) {
|
|
849
|
+ queries.add(new BaseReadTsKvQuery(key, startTs, endTs, endTs, 1, aggregation, DESC_ORDER));
|
751
|
850
|
}
|
752
|
|
- List<TsKvEntry> tsKv = timeseriesService.findAll(currentTenantId, entityId, queries).get();
|
753
|
|
- return tsKv;
|
|
851
|
+ return timeseriesService.findAll(currentTenantId, apiUsageId, queries);
|
754
|
852
|
}
|
755
|
853
|
|
756
|
854
|
/**
|
...
|
...
|
@@ -766,14 +864,14 @@ public class TkHomePageServiceImpl implements HomePageService { |
766
|
864
|
int sumCount = userList != null ? userList.size() : FastIotConstants.MagicNumber.ZERO;
|
767
|
865
|
|
768
|
866
|
int todayAdd = FastIotConstants.MagicNumber.ZERO;
|
769
|
|
- if (userList != null && userList.size() > FastIotConstants.MagicNumber.ZERO) {
|
|
867
|
+ if (userList != null && !userList.isEmpty()) {
|
770
|
868
|
todayAdd +=
|
771
|
|
- userList.stream()
|
772
|
|
- .filter(
|
773
|
|
- userDTO ->
|
774
|
|
- userDTO.getCreateTime().isAfter(startTime)
|
775
|
|
- && userDTO.getCreateTime().isBefore(endTime))
|
776
|
|
- .count();
|
|
869
|
+ (int) userList.stream()
|
|
870
|
+ .filter(
|
|
871
|
+ userDTO ->
|
|
872
|
+ userDTO.getCreateTime().isAfter(startTime)
|
|
873
|
+ && userDTO.getCreateTime().isBefore(endTime))
|
|
874
|
+ .count();
|
777
|
875
|
}
|
778
|
876
|
BaseHomePageTop baseHomePageTop = new BaseHomePageTop(sumCount, todayAdd);
|
779
|
877
|
homePageLeftTopDTO.setCustomerInfo(baseHomePageTop);
|
...
|
...
|
@@ -835,7 +933,7 @@ public class TkHomePageServiceImpl implements HomePageService { |
835
|
933
|
@Override
|
836
|
934
|
public void onSuccess(@Nullable List<TenantTransportMessageDTO> values) {
|
837
|
935
|
// sort
|
838
|
|
- if (null != values && values.size() > FastIotConstants.MagicNumber.ZERO) {
|
|
936
|
+ if (null != values && !values.isEmpty()) {
|
839
|
937
|
int length = values.size() - FastIotConstants.MagicNumber.ONE;
|
840
|
938
|
for (int i = FastIotConstants.MagicNumber.ZERO; i < length; i++) {
|
841
|
939
|
for (int j = FastIotConstants.MagicNumber.ZERO; j < length - i; j++) {
|
...
|
...
|
@@ -866,42 +964,90 @@ public class TkHomePageServiceImpl implements HomePageService { |
866
|
964
|
};
|
867
|
965
|
}
|
868
|
966
|
|
869
|
|
- private CompletableFuture<TkTsValue> findDeviceInfoByTs(
|
870
|
|
- String customerId, Long startTs, Long endTs, String date, TrendType trend) {
|
871
|
|
- Integer value;
|
872
|
|
- if (trend == TrendType.CUSTOMER_MESSAGE_STATISTICAL) {
|
873
|
|
- value = deviceMapper.findDeviceMessageInfoByTs(customerId, startTs, endTs);
|
874
|
|
- } else {
|
875
|
|
- value = deviceMapper.findDeviceAlarmInfoByCreatedTime(customerId, startTs, endTs);
|
|
967
|
+ private final ConcurrentMap<String, DeviceId> customer = new ConcurrentHashMap<>();
|
|
968
|
+
|
|
969
|
+ private CompletableFuture<TkTsValue> findCusterMessageHistory(
|
|
970
|
+ TenantId tenantId,
|
|
971
|
+ CustomerId customerId,
|
|
972
|
+ LocalDateTime todayBegin,
|
|
973
|
+ LocalDateTime todayend,
|
|
974
|
+ String dataKey) {
|
|
975
|
+ List<String> tbDeviceIds =
|
|
976
|
+ deviceMapper.findDeviceIdsByCustomerId(customerId.getId().toString());
|
|
977
|
+ AtomicReference<Integer> value = new AtomicReference<>(0);
|
|
978
|
+ if (tbDeviceIds == null || tbDeviceIds.isEmpty()){
|
|
979
|
+ return CompletableFuture.failedFuture(new NullPointerException());
|
876
|
980
|
}
|
|
981
|
+ tbDeviceIds.forEach(
|
|
982
|
+ id -> {
|
|
983
|
+ DeviceId deviceId = DeviceId.fromString(id);
|
|
984
|
+ try {
|
|
985
|
+ List<String> allKeys = deviceKeys(tenantId, deviceId).get();
|
|
986
|
+ if(allKeys == null || allKeys.isEmpty()){
|
|
987
|
+ return;
|
|
988
|
+ }
|
|
989
|
+ List<TsKvEntry> tempDatas =
|
|
990
|
+ queryTodayTimescalDatas(
|
|
991
|
+ todayBegin, todayend, deviceId, tenantId, allKeys, Aggregation.COUNT)
|
|
992
|
+ .get();
|
|
993
|
+ if(tempDatas == null){
|
|
994
|
+ return;
|
|
995
|
+ }
|
|
996
|
+ tempDatas.forEach(
|
|
997
|
+ item -> {
|
|
998
|
+ item.getLongValue()
|
|
999
|
+ .ifPresent(lv -> value.updateAndGet(v -> Math.toIntExact(v + lv)));
|
|
1000
|
+ });
|
|
1001
|
+ } catch (ExecutionException | InterruptedException e) {
|
|
1002
|
+ throw new RuntimeException(e);
|
|
1003
|
+ }
|
|
1004
|
+ });
|
|
1005
|
+
|
|
1006
|
+ return CompletableFuture.supplyAsync(() -> new TkTsValue(dataKey, String.valueOf(value.get())));
|
|
1007
|
+ }
|
|
1008
|
+
|
|
1009
|
+ private CompletableFuture<TkTsValue> findCusterAlarmHistory(
|
|
1010
|
+ String customerId, Long startTs, Long endTs, String date) {
|
|
1011
|
+ Integer value = deviceMapper.findDeviceAlarmInfoByCreatedTime(customerId, startTs, endTs);
|
877
|
1012
|
return CompletableFuture.supplyAsync(() -> new TkTsValue(date, String.valueOf(value)));
|
878
|
1013
|
}
|
879
|
1014
|
|
880
|
1015
|
/**
|
881
|
1016
|
* 获取本月的开始时间
|
|
1017
|
+ *
|
882
|
1018
|
* @param dateTime 本月时间
|
883
|
1019
|
* @return 本月第一天时间
|
884
|
1020
|
*/
|
885
|
|
- public LocalDateTime getMonthStartTime(LocalDateTime dateTime){
|
|
1021
|
+ public LocalDateTime getMonthStartTime(LocalDateTime dateTime) {
|
886
|
1022
|
// 获取本月的第一天0点的时间戳
|
887
|
|
- return dateTime.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
|
1023
|
+ return dateTime
|
|
1024
|
+ .with(TemporalAdjusters.firstDayOfMonth())
|
|
1025
|
+ .withHour(0)
|
|
1026
|
+ .withMinute(0)
|
|
1027
|
+ .withSecond(0)
|
|
1028
|
+ .withNano(0);
|
888
|
1029
|
}
|
889
|
1030
|
|
890
|
1031
|
/**
|
891
|
1032
|
* 获取下个月的开始时间
|
|
1033
|
+ *
|
892
|
1034
|
* @param dateTime 本月时间
|
893
|
1035
|
* @return 下个月第一天时间
|
894
|
1036
|
*/
|
895
|
|
- public LocalDateTime getNextMonthStartTime(LocalDateTime dateTime){
|
|
1037
|
+ public LocalDateTime getNextMonthStartTime(LocalDateTime dateTime) {
|
896
|
1038
|
// 获取下个月的第一天
|
897
|
|
- LocalDateTime firstDayOfNextMonth = dateTime.with(TemporalAdjusters.firstDayOfNextMonth()).with(TemporalAdjusters.firstDayOfMonth());
|
|
1039
|
+ LocalDateTime firstDayOfNextMonth =
|
|
1040
|
+ dateTime
|
|
1041
|
+ .with(TemporalAdjusters.firstDayOfNextMonth())
|
|
1042
|
+ .with(TemporalAdjusters.firstDayOfMonth());
|
898
|
1043
|
// 将时间设置为0点
|
899
|
1044
|
return firstDayOfNextMonth.withHour(0).withMinute(0).withSecond(0).withNano(0);
|
900
|
1045
|
}
|
901
|
1046
|
|
902
|
|
- public String getDateByLocalTime(LocalDateTime startTime,boolean isYearQuery,boolean isCustomer){
|
903
|
|
- String format = isYearQuery ?"yyyy-MM":"yyyy-MM-dd";
|
904
|
|
- format = isCustomer?"yyyy-MM-dd HH:mm:ss":format;
|
|
1047
|
+ public String getDateByLocalTime(
|
|
1048
|
+ LocalDateTime startTime, boolean isYearQuery, boolean isCustomer) {
|
|
1049
|
+ String format = isYearQuery ? "yyyy-MM" : "yyyy-MM-dd";
|
|
1050
|
+ format = isCustomer ? "yyyy-MM-dd HH:mm:ss" : format;
|
905
|
1051
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
|
906
|
1052
|
return startTime.format(formatter);
|
907
|
1053
|
}
|
...
|
...
|
|