iothome
This commit is contained in:
parent
d83af87f9f
commit
5832600d0b
|
@ -16,6 +16,7 @@ import java.util.Arrays;
|
|||
@AllArgsConstructor
|
||||
public enum DateIntervalEnum implements ArrayValuable<Integer> {
|
||||
|
||||
HOUR(0, "小时"),
|
||||
DAY(1, "天"),
|
||||
WEEK(2, "周"),
|
||||
MONTH(3, "月"),
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package cn.iocoder.yudao.module.iot.controller.admin.statistics;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryRespVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsReqVO;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsSummaryRespVO;
|
||||
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
|
@ -10,18 +11,24 @@ import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
|
|||
import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Tag(name = "管理后台 - IoT 数据统计")
|
||||
@RestController
|
||||
|
@ -49,7 +56,7 @@ public class IotStatisticsController {
|
|||
respVO.setDeviceMessageCount(deviceLogService.getDeviceLogCount(null));
|
||||
// 1.2 获取今日新增数量
|
||||
// TODO @super:使用 LocalDateTimeUtils.getToday()
|
||||
LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
|
||||
LocalDateTime todayStart = LocalDateTimeUtils.getToday();
|
||||
respVO.setProductCategoryTodayCount(productCategoryService.getProductCategoryCount(todayStart));
|
||||
respVO.setProductTodayCount(productService.getProductCount(todayStart));
|
||||
respVO.setDeviceTodayCount(deviceService.getDeviceCount(todayStart));
|
||||
|
@ -70,10 +77,16 @@ public class IotStatisticsController {
|
|||
@GetMapping("/get-log-summary")
|
||||
@Operation(summary = "获取 IoT 设备上下行消息数据统计")
|
||||
public CommonResult<IotStatisticsDeviceMessageSummaryRespVO> getIotStatisticsDeviceMessageSummary(
|
||||
@Valid IotStatisticsReqVO reqVO) {
|
||||
@Parameter(description = "查询起始时间戳(毫秒)", required = true, example = "1658460600000") @RequestParam(required = true) Long startTime,
|
||||
@Parameter(description = "查询结束时间戳(毫秒)", required = true, example = "1754888399000") @RequestParam(required = true) Long endTime) {
|
||||
// 当时间范围过大 前端图表组件会产生问题 所以不以小时返回 以天返回 判断时间跨度是否大于30天
|
||||
long thirtyDaysInMillis = TimeUnit.DAYS.toMillis(30);
|
||||
boolean isLongTimeSpan = (endTime - startTime) > thirtyDaysInMillis;
|
||||
|
||||
return success(new IotStatisticsDeviceMessageSummaryRespVO()
|
||||
.setDownstreamCounts(deviceLogService.getDeviceLogUpCountByHour(null, reqVO.getStartTime(), reqVO.getEndTime()))
|
||||
.setDownstreamCounts((deviceLogService.getDeviceLogDownCountByHour(null, reqVO.getStartTime(), reqVO.getEndTime()))));
|
||||
.setStatType(isLongTimeSpan ? DateIntervalEnum.DAY.getInterval() : DateIntervalEnum.HOUR.getInterval())
|
||||
.setUpstreamCounts(deviceLogService.getDeviceLogCountByHour(true, null, startTime, endTime))
|
||||
.setDownstreamCounts(deviceLogService.getDeviceLogCountByHour(false, null, startTime, endTime)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.module.iot.controller.admin.statistics.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
@ -10,10 +12,14 @@ import java.util.Map;
|
|||
@Data
|
||||
public class IotStatisticsDeviceMessageSummaryRespVO {
|
||||
|
||||
@Schema(description = "每小时上行数据数量统计")
|
||||
@Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@InEnum(value = DateIntervalEnum.class, message = "时间间隔类型")
|
||||
private Integer statType;
|
||||
|
||||
@Schema(description = "上行数据数量统计")
|
||||
private List<Map<Long, Integer>> upstreamCounts;
|
||||
|
||||
@Schema(description = "每小时下行数据数量统计")
|
||||
@Schema(description = "下行数据数量统计")
|
||||
private List<Map<Long, Integer>> downstreamCounts;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.controller.admin.statistics.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - IoT 统计 Request VO")
|
||||
@Data
|
||||
public class IotStatisticsReqVO {
|
||||
|
||||
// TODO @super:前端传递的时候,还是通过 startTime 和 endTime 传递。后端转成 Long
|
||||
|
||||
@Schema(description = "查询起始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1658486600000")
|
||||
@NotNull(message = "查询起始时间不能为空")
|
||||
private Long startTime;
|
||||
|
||||
@Schema(description = "查询结束时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1758486600000")
|
||||
@NotNull(message = "查询结束时间不能为空")
|
||||
private Long endTime;
|
||||
|
||||
}
|
|
@ -90,6 +90,7 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
|
|||
* @return 设备数量统计列表
|
||||
*/
|
||||
// TODO @super:通过 mybatis-plus 来写哈,然后返回 Map 貌似就行了?!
|
||||
// TODO 讨论:试了一下 mybatis-plus 如:select("state as `key`", "count(1) as `value`") 有点啰嗦感觉不如放在 xml 里,但是放 xml 里又会增加冗余
|
||||
List<Map<String, Object>> selectDeviceCountMapByProductId();
|
||||
|
||||
// TODO @super:通过 mybatis-plus 来写哈,然后返回 Map 貌似就行了?!
|
||||
|
|
|
@ -59,6 +59,7 @@ public interface IotDeviceLogMapper {
|
|||
|
||||
// TODO @super:1)上行、下行,不写在 mapper 里,而是通过参数传递,这样,selectDeviceLogUpCountByHour、selectDeviceLogDownCountByHour 可以合并;
|
||||
// TODO @super:2)不能只基于 identifier 来计算,而是要 type + identifier 成对
|
||||
// TODO 感觉 type + identifier 这块目前还没固定 这里打算等后续等大体不变了再参照艿哥的建议方式修改
|
||||
/**
|
||||
* 查询每个小时设备上行消息数量
|
||||
*/
|
||||
|
|
|
@ -48,28 +48,30 @@ public interface IotDeviceLogService {
|
|||
Long getDeviceLogCount(@Nullable LocalDateTime createTime);
|
||||
|
||||
// TODO @super:deviceKey 是不是用不上哈?
|
||||
// TODO 讨论:这个 deviceKey 是打算用来查看每个设备上下行数据时的预留参数
|
||||
/**
|
||||
* 获得每个小时设备上行消息数量统计
|
||||
* 获得每个小时设备消息数量统计
|
||||
*
|
||||
* @param upstream 消息上下行标识
|
||||
* @param deviceKey 设备标识
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return key: 时间戳, value: 消息数量
|
||||
*/
|
||||
List<Map<Long, Integer>> getDeviceLogUpCountByHour(@Nullable String deviceKey,
|
||||
List<Map<Long, Integer>> getDeviceLogCountByHour(@Nullable Boolean upstream, @Nullable String deviceKey,
|
||||
@Nullable Long startTime,
|
||||
@Nullable Long endTime);
|
||||
|
||||
/**
|
||||
* 获得每个小时设备下行消息数量统计
|
||||
*
|
||||
* @param deviceKey 设备标识
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return key: 时间戳, value: 消息数量
|
||||
*/
|
||||
List<Map<Long, Integer>> getDeviceLogDownCountByHour(@Nullable String deviceKey,
|
||||
@Nullable Long startTime,
|
||||
@Nullable Long endTime);
|
||||
// /**
|
||||
// * 获得每个小时设备下行消息数量统计
|
||||
// *
|
||||
// * @param deviceKey 设备标识
|
||||
// * @param startTime 开始时间
|
||||
// * @param endTime 结束时间
|
||||
// * @return key: 时间戳, value: 消息数量
|
||||
// */
|
||||
// List<Map<Long, Integer>> getDeviceLogDownCountByHour(@Nullable String deviceKey,
|
||||
// @Nullable Long startTime,
|
||||
// @Nullable Long endTime);
|
||||
|
||||
}
|
||||
|
|
|
@ -18,9 +18,12 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
@ -75,37 +78,150 @@ public class IotDeviceLogServiceImpl implements IotDeviceLogService {
|
|||
return deviceLogMapper.selectCountByCreateTime(createTime != null ? LocalDateTimeUtil.toEpochMilli(createTime) : null);
|
||||
}
|
||||
|
||||
// TODO:这俩方法看看后续要不要抽到 utils 里
|
||||
/**
|
||||
* 根据起始和结束时间戳,生成每小时时间戳的列表
|
||||
*
|
||||
* @param startTimestamp 开始时间戳(毫秒)
|
||||
* @param endTimestamp 结束时间戳(毫秒)
|
||||
* @return 每小时时间戳的列表
|
||||
*/
|
||||
public List<Long> generateHourlyTimestamps(Long startTimestamp, Long endTimestamp) {
|
||||
// 转换为Instant
|
||||
Instant startInstant = Instant.ofEpochMilli(startTimestamp);
|
||||
Instant endInstant = Instant.ofEpochMilli(endTimestamp);
|
||||
|
||||
// 将起始时间调整为整点小时
|
||||
Instant alignedStart = startInstant.truncatedTo(ChronoUnit.HOURS);
|
||||
|
||||
// 计算需要多少个小时
|
||||
long hoursBetween = ChronoUnit.HOURS.between(alignedStart, endInstant);
|
||||
// 如果结束时间有分钟秒的部分,需要多加一个小时
|
||||
if (!endInstant.equals(endInstant.truncatedTo(ChronoUnit.HOURS))) {
|
||||
hoursBetween++;
|
||||
}
|
||||
|
||||
// 生成每小时时间戳列表
|
||||
List<Long> hourlyTimestamps = new ArrayList<>();
|
||||
for (int i = 0; i <= hoursBetween; i++) {
|
||||
Instant currentHour = alignedStart.plus(i, ChronoUnit.HOURS);
|
||||
hourlyTimestamps.add(currentHour.toEpochMilli());
|
||||
}
|
||||
|
||||
return hourlyTimestamps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据起始和结束时间戳,生成每天时间戳的列表
|
||||
*
|
||||
* @param startTimestamp 开始时间戳(毫秒)
|
||||
* @param endTimestamp 结束时间戳(毫秒)
|
||||
* @return 每天时间戳的列表
|
||||
*/
|
||||
public List<Long> generateDailyTimestamps(Long startTimestamp, Long endTimestamp) {
|
||||
// 转换为Instant
|
||||
Instant startInstant = Instant.ofEpochMilli(startTimestamp);
|
||||
Instant endInstant = Instant.ofEpochMilli(endTimestamp);
|
||||
|
||||
// 将起始时间调整为一天的开始
|
||||
Instant alignedStart = startInstant.truncatedTo(ChronoUnit.DAYS);
|
||||
|
||||
// 计算需要多少天
|
||||
long daysBetween = ChronoUnit.DAYS.between(alignedStart, endInstant);
|
||||
// 如果结束时间不是在一天的开始,需要多加一天
|
||||
if (!endInstant.equals(endInstant.truncatedTo(ChronoUnit.DAYS))) {
|
||||
daysBetween++;
|
||||
}
|
||||
|
||||
// 生成每天时间戳列表
|
||||
List<Long> dailyTimestamps = new ArrayList<>();
|
||||
for (int i = 0; i <= daysBetween; i++) {
|
||||
Instant currentDay = alignedStart.plus(i, ChronoUnit.DAYS);
|
||||
dailyTimestamps.add(currentDay.toEpochMilli());
|
||||
}
|
||||
|
||||
return dailyTimestamps;
|
||||
}
|
||||
|
||||
// TODO @super:加一个参数,Boolean upstream:true 上行,false 下行,null 不过滤
|
||||
// TODO 这块后续和 mapper 方法一起再做进一步改进
|
||||
@Override
|
||||
public List<Map<Long, Integer>> getDeviceLogUpCountByHour(String deviceKey, Long startTime, Long endTime) {
|
||||
public List<Map<Long, Integer>> getDeviceLogCountByHour(Boolean upstream, String deviceKey, Long startTime, Long endTime) {
|
||||
// TODO @super:不能只基于数据库统计。因为有一些小时,可能出现没数据的情况,导致前端展示的图是不全的。可以参考 CrmStatisticsCustomerService 来实现
|
||||
List<Map<String, Object>> list = deviceLogMapper.selectDeviceLogUpCountByHour(deviceKey, startTime, endTime);
|
||||
return list.stream()
|
||||
.map(map -> {
|
||||
// 从Timestamp获取时间戳
|
||||
Timestamp timestamp = (Timestamp) map.get("time");
|
||||
Long timeMillis = timestamp.getTime();
|
||||
// 消息数量转换
|
||||
Integer count = ((Number) map.get("data")).intValue();
|
||||
return Map.of(timeMillis, count);
|
||||
})
|
||||
// TODO 讨论:因为 tdengine 的时间数据要转为时间戳,所有没有复用 CrmStatisticsCustomerService 中调用的 utils 方法 还有目前时间戳与 LocalDateTime 的转换还是比较混乱,艿哥这里要不要统一全转成 LocalDateTime 输出,现在是将时间戳交给前端去处理了
|
||||
|
||||
// 获取数据库中的统计数据
|
||||
List<Map<String, Object>> list = null;
|
||||
if(upstream == true){
|
||||
list = deviceLogMapper.selectDeviceLogUpCountByHour(deviceKey, startTime, endTime);
|
||||
}
|
||||
else {
|
||||
list = deviceLogMapper.selectDeviceLogDownCountByHour(deviceKey, startTime, endTime);
|
||||
}
|
||||
|
||||
// 将数据库返回的结果转换成Map结构
|
||||
Map<Long, Integer> hourlyCountMap = list.stream()
|
||||
.collect(Collectors.toMap(
|
||||
map -> ((Timestamp) map.get("time")).getTime(),
|
||||
map -> ((Number) map.get("data")).intValue()
|
||||
));
|
||||
|
||||
// 当时间范围过大 前端图表组件会产生问题 所以以天返回 判断时间跨度是否大于30天(30天 * 24小时 * 60分钟 * 60秒 * 1000毫秒)
|
||||
long thirtyDaysInMillis = TimeUnit.DAYS.toMillis(30);
|
||||
boolean isLongTimeSpan = (endTime - startTime) > thirtyDaysInMillis;
|
||||
|
||||
if (isLongTimeSpan) {
|
||||
//TODO: 这里的 SQL 等后续一块优化时 改成分查小时还是查天
|
||||
// 按天统计 - 先生成每天的时间戳列表
|
||||
List<Long> allDailyTimestamps = generateDailyTimestamps(startTime, endTime);
|
||||
|
||||
// 将小时数据按天进行合并
|
||||
Map<Long, Integer> dailyCountMap = new HashMap<>();
|
||||
|
||||
// 遍历每个小时的数据,将其归入对应的天
|
||||
hourlyCountMap.forEach((hourTimestamp, count) -> {
|
||||
// 将小时时间戳转为当天开始的时间戳
|
||||
Instant hourInstant = Instant.ofEpochMilli(hourTimestamp);
|
||||
Instant dayInstant = hourInstant.truncatedTo(ChronoUnit.DAYS);
|
||||
Long dayTimestamp = dayInstant.toEpochMilli();
|
||||
|
||||
// 累加到对应的天
|
||||
dailyCountMap.merge(dayTimestamp, count, Integer::sum);
|
||||
});
|
||||
|
||||
// 确保每天都有数据,没有的填充0
|
||||
return allDailyTimestamps.stream()
|
||||
.map(timestamp -> Map.of(timestamp, dailyCountMap.getOrDefault(timestamp, 0)))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
// 按小时统计 - 生成所有小时的时间戳列表
|
||||
List<Long> allHourlyTimestamps = generateHourlyTimestamps(startTime, endTime);
|
||||
|
||||
// 确保每个小时都有数据,没有的填充0
|
||||
return allHourlyTimestamps.stream()
|
||||
.map(timestamp -> Map.of(timestamp, hourlyCountMap.getOrDefault(timestamp, 0)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @super:getDeviceLogDownCountByHour 融合到 getDeviceLogUpCountByHour
|
||||
@Override
|
||||
public List<Map<Long, Integer>> getDeviceLogDownCountByHour(String deviceKey, Long startTime, Long endTime) {
|
||||
List<Map<String, Object>> list = deviceLogMapper.selectDeviceLogDownCountByHour(deviceKey, startTime, endTime);
|
||||
return list.stream()
|
||||
.map(map -> {
|
||||
// 从Timestamp获取时间戳
|
||||
Timestamp timestamp = (Timestamp) map.get("time");
|
||||
Long timeMillis = timestamp.getTime();
|
||||
// 消息数量转换
|
||||
Integer count = ((Number) map.get("data")).intValue();
|
||||
return Map.of(timeMillis, count);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
// @Override
|
||||
// public List<Map<Long, Integer>> getDeviceLogDownCountByHour(String deviceKey, Long startTime, Long endTime) {
|
||||
// // 获取数据库中的统计数据
|
||||
// List<Map<String, Object>> list = deviceLogMapper.selectDeviceLogDownCountByHour(deviceKey, startTime, endTime);
|
||||
// Map<Long, Integer> hourlyCountMap = list.stream()
|
||||
// .collect(Collectors.toMap(
|
||||
// map -> ((Timestamp) map.get("time")).getTime(),
|
||||
// map -> ((Number) map.get("data")).intValue()
|
||||
// ));
|
||||
//
|
||||
// // 生成所有小时的时间戳列表
|
||||
// List<Long> allHourlyTimestamps = generateHourlyTimestamps(startTime, endTime);
|
||||
//
|
||||
// // 确保每个小时都有数据,没有的填充0
|
||||
// return allHourlyTimestamps.stream()
|
||||
// .map(timestamp -> Map.of(timestamp, hourlyCountMap.getOrDefault(timestamp, 0)))
|
||||
// .collect(Collectors.toList());
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue