From 719cfb797e1e4ba9a48517c2ce1e2c95628a44c6 Mon Sep 17 00:00:00 2001 From: liutao <790864623@qq.com> Date: Sat, 9 May 2026 11:01:05 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E8=BF=90=E8=A1=8C=E6=80=BB?= =?UTF-8?q?=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeviceOperationRecordController.java | 91 +++++++------ .../vo/DeviceOperationOverviewRespVO.java | 127 ++++++++++++++++++ .../vo/DeviceTotalTimeRecordReqVO.java | 23 ++-- .../iot/service/device/TDengineService.java | 53 +++++++- .../DeviceOperationRecordServiceImpl.java | 104 ++++++++++++++ 5 files changed, 345 insertions(+), 53 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceOperationOverviewRespVO.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/DeviceOperationRecordController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/DeviceOperationRecordController.java index 277a8888c..4d5eb7385 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/DeviceOperationRecordController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/DeviceOperationRecordController.java @@ -1,37 +1,46 @@ package cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord; -import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.security.access.prepost.PreAuthorize; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceOperationOverviewRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceOperationRateTrendRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceOperationRecordPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceOperationRecordRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceOperationRecordSaveReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceTotalTimeRecordReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.DeviceTotalTimeRecordRespVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.deviceoperationrecord.DeviceOperationRecordDO; +import cn.iocoder.yudao.module.iot.service.deviceoperationrecord.DeviceOperationRecordService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; -import javax.validation.constraints.*; -import javax.validation.*; -import javax.servlet.http.*; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; import java.net.URLEncoder; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; -import java.io.IOException; +import java.util.List; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; - -import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; -import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; - -import cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo.*; -import cn.iocoder.yudao.module.iot.dal.dataobject.deviceoperationrecord.DeviceOperationRecordDO; -import cn.iocoder.yudao.module.iot.service.deviceoperationrecord.DeviceOperationRecordService; - @Tag(name = "管理后台 - 运行记录") @RestController @RequestMapping("/iot/device-operation-record") @@ -87,12 +96,11 @@ public class DeviceOperationRecordController { @PreAuthorize("@ss.hasPermission('iot:device-operation-record:export')") @ApiAccessLog(operateType = EXPORT) public void exportDeviceOperationRecordExcel(@Valid DeviceOperationRecordPageReqVO pageReqVO, - HttpServletResponse response) throws IOException { + HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List list = deviceOperationRecordService.getDeviceOperationRecordPage(pageReqVO).getList(); - // 导出 Excel ExcelUtils.write(response, "运行记录.xls", "数据", DeviceOperationRecordRespVO.class, - BeanUtils.toBean(list, DeviceOperationRecordRespVO.class)); + BeanUtils.toBean(list, DeviceOperationRecordRespVO.class)); } @GetMapping("/deviceOperationPage") @@ -111,45 +119,44 @@ public class DeviceOperationRecordController { } @GetMapping("/deviceRateTrendByDeviceId") - @Operation(summary = "根据设备ID查询某个设备开机率和稼动率") + @Operation(summary = "根据设备 ID 查询单设备开机率和利用率趋势") @PreAuthorize("@ss.hasPermission('iot:device-operation-record:query')") - public CommonResult> getDeviceRateTrendByDeviceId(@RequestParam("deviceId") Long deviceId, - @RequestParam(value = "period", required = false) String period) { + public CommonResult> getDeviceRateTrendByDeviceId( + @RequestParam("deviceId") Long deviceId, + @RequestParam(value = "period", required = false) String period) { return success(deviceOperationRecordService.getDeviceRateTrendByDeviceId(deviceId, period)); } - @GetMapping("/deviceOperationList") - @Operation(summary = "产线设备运行开机率/稼动率-大屏") + @Operation(summary = "产线设备运行开机率/利用率大屏") @PreAuthorize("@ss.hasPermission('iot:device-operation-record:query')") public CommonResult> deviceOperationList(@Valid DeviceTotalTimeRecordReqVO pageReqVO) { - List deviceTotalTimeRecordRespVOList = deviceOperationRecordService.deviceOperationList(pageReqVO); - return success(deviceTotalTimeRecordRespVOList); + return success(deviceOperationRecordService.deviceOperationList(pageReqVO)); } @GetMapping("/runOverview") - @Operation(summary = "设备运行总览") + @Operation( + summary = "设备运行总览", + description = "根据设备 ID 集合、统计时间范围和时间轴分页参数,返回运行总览指标卡、按小时状态分布、状态汇总以及设备时间轴数据" + ) @PreAuthorize("@ss.hasPermission('iot:device-operation-record:query')") public CommonResult runOverview(@Valid DeviceTotalTimeRecordReqVO pageReqVO) { return success(deviceOperationRecordService.runOverview(pageReqVO)); } - @GetMapping("/export-device-operation-report") @Operation(summary = "导出设备运行报表记录 Excel") @PreAuthorize("@ss.hasPermission('iot:device-operation-record:export')") @ApiAccessLog(operateType = EXPORT) public void exportDeviceOperationReport(@Valid DeviceTotalTimeRecordReqVO pageReqVO, - HttpServletResponse response) throws IOException { + HttpServletResponse response) throws IOException { PageResult pageResult = deviceOperationRecordService.deviceOperationPage(pageReqVO); - // 设置响应头 response.setContentType("application/vnd.ms-excel;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("设备运行报表记录.xls", "UTF-8")); response.setHeader("Content-Encoding", "identity"); - // 导出Excel - String fileName = String.format("设备运行报表记录_%s.xls", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); - // 导出 Excel - ExcelUtils.write(response, fileName, "数据", DeviceTotalTimeRecordRespVO.class,pageResult.getList()); + String fileName = String.format("设备运行报表记录_%s.xls", + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); + ExcelUtils.write(response, fileName, "数据", DeviceTotalTimeRecordRespVO.class, pageResult.getList()); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceOperationOverviewRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceOperationOverviewRespVO.java new file mode 100644 index 000000000..c17bd5b35 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceOperationOverviewRespVO.java @@ -0,0 +1,127 @@ +package cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Schema(description = "管理后台 - 设备运行总览 Response VO") +@Data +public class DeviceOperationOverviewRespVO { + + @Schema(description = "指标卡数据") + private List metrics; + + @Schema(description = "按小时统计的状态分布,单位为百分比") + private List hourlyStatus; + + @Schema(description = "状态汇总数据") + private List summary; + + @Schema(description = "汇总总时长,单位小时") + private Double summaryTotalHours; + + @Schema(description = "设备时间轴数据") + private List timelineRows; + + @Schema(description = "设备总数") + private Integer totalDevices; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "指标卡项") + public static class MetricItem { + @Schema(description = "指标 key,例如 utilizationRate、powerOnRate") + private String key; + + @Schema(description = "前端图标标识") + private String icon; + + @Schema(description = "指标值") + private Double value; + + @Schema(description = "指标单位") + private String unit; + + @Schema(description = "对比变化值,当前默认返回 0") + private Double change; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "小时状态分布项") + public static class HourlyStatusItem { + @Schema(description = "小时刻度,例如 08:00") + private String hour; + + @Schema(description = "运行占比,百分比") + private Double running; + + @Schema(description = "待机占比,百分比") + private Double standby; + + @Schema(description = "故障占比,百分比") + private Double fault; + + @Schema(description = "离线占比,百分比") + private Double offline; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "状态汇总项") + public static class SummaryItem { + @Schema(description = "状态编码,running/standby/fault/offline") + private String status; + + @Schema(description = "状态占比,百分比") + private Double percent; + + @Schema(description = "状态累计时长,单位小时") + private Double hours; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "设备时间轴行") + public static class TimelineRowItem { + @Schema(description = "设备 ID") + private String id; + + @Schema(description = "设备名称") + private String name; + + @Schema(description = "设备利用率,百分比") + private Double utilizationRate; + + @Schema(description = "时间轴状态段") + private List segments; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "时间轴状态段") + public static class TimelineSegmentItem { + @Schema(description = "状态编码,running/standby/fault/offline") + private String status; + + @Schema(description = "段开始位置,按 24 小时刻度折算") + private Double startHour; + + @Schema(description = "段结束位置,按 24 小时刻度折算") + private Double endHour; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceTotalTimeRecordReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceTotalTimeRecordReqVO.java index ab33724cd..d03d8452d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceTotalTimeRecordReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/deviceoperationrecord/vo/DeviceTotalTimeRecordReqVO.java @@ -1,35 +1,38 @@ package cn.iocoder.yudao.module.iot.controller.admin.deviceoperationrecord.vo; - import cn.iocoder.yudao.framework.common.pojo.PageParam; -import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -@Schema(description = "管理后台 - 运行记录分页 Request VO") +@Schema(description = "管理后台 - 设备运行统计请求 VO") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class DeviceTotalTimeRecordReqVO extends PageParam{ +public class DeviceTotalTimeRecordReqVO extends PageParam { - @Schema(description = "时间区间:LAST_WEEK/THIS_WEEK/LAST_7_DAYS/LAST_MONTH/THIS_MONTH") + @Schema(description = "快捷时间范围,支持 LAST_WEEK、THIS_WEEK、LAST_7_DAYS、LAST_MONTH、THIS_MONTH") private String period; - @Schema(description = "设备编码") + @Schema(description = "设备编码,模糊匹配") private String deviceCode; - @Schema(description = "设备名称") + @Schema(description = "设备名称,模糊匹配") private String deviceName; - @Schema(description = "开始时间") + @Schema(description = "统计开始时间,格式 yyyy-MM-dd HH:mm:ss") private String startTime; - @Schema(description = "结束时间") + @Schema(description = "统计结束时间,格式 yyyy-MM-dd HH:mm:ss") private String endTime; - @Schema(description = "ids导出集合用") + @Schema(description = "设备 ID 集合,使用英文逗号分隔,例如 1,2,3") private String ids; + @Schema(description = "时间轴页码,默认 1") + private Integer timelinePageNo; + + @Schema(description = "时间轴每页条数,默认 10") + private Integer timelinePageSize; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index d9c58da51..be992ded2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java @@ -2748,7 +2748,7 @@ public class TDengineService { List params = new ArrayList<>(); sql.append("SELECT ") - .append("HOUR(_wstart) AS hourValue, ") + .append("TO_CHAR(_wstart, 'HH24') AS hourValue, ") .append("SUM(CASE WHEN rule = '0' THEN 1 ELSE 0 END) * 60 AS totalOfflineTime, ") .append("SUM(CASE WHEN rule = '1' THEN 1 ELSE 0 END) * 60 AS totalRunningTime, ") .append("SUM(CASE WHEN rule = '2' THEN 1 ELSE 0 END) * 60 AS totalStandbyTime, ") @@ -2786,6 +2786,57 @@ public class TDengineService { } } + @DS("tdengine") + public List queryDeviceOperationTimeline(List deviceIds, String startTime, String endTime) { + if (deviceIds == null || deviceIds.isEmpty()) { + return Collections.emptyList(); + } + + StringBuilder sql = new StringBuilder(); + List params = new ArrayList<>(); + + sql.append("SELECT device_id, rule, create_time ") + .append("FROM besure_server.iot_device_operation_record ") + .append("WHERE deleted = 0 "); + + sql.append("AND device_id IN ("); + for (int i = 0; i < deviceIds.size(); i++) { + if (i > 0) { + sql.append(", "); + } + sql.append("?"); + params.add(deviceIds.get(i)); + } + sql.append(") "); + + if (startTime != null && !startTime.trim().isEmpty()) { + sql.append("AND create_time >= ? "); + params.add(startTime); + } + if (endTime != null && !endTime.trim().isEmpty()) { + sql.append("AND create_time <= ? "); + params.add(endTime); + } + + sql.append("ORDER BY device_id ASC, create_time ASC"); + + try { + return jdbcTemplate.query(sql.toString(), params.toArray(), (rs, rowNum) -> { + DeviceOperationRecordDO record = new DeviceOperationRecordDO(); + record.setDeviceId(rs.getLong("device_id")); + record.setRule(rs.getString("rule")); + Timestamp createTime = rs.getTimestamp("create_time"); + if (createTime != null) { + record.setCreateTime(createTime.toLocalDateTime()); + } + return record; + }); + } catch (Exception e) { + log.error("TDengine 查询设备时间轴失败,sql={}, params={}", sql, params, e); + return Collections.emptyList(); + } + } + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/deviceoperationrecord/DeviceOperationRecordServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/deviceoperationrecord/DeviceOperationRecordServiceImpl.java index 4f31f99a1..cb2b5d1f7 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/deviceoperationrecord/DeviceOperationRecordServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/deviceoperationrecord/DeviceOperationRecordServiceImpl.java @@ -452,6 +452,7 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe @Override public DeviceOperationOverviewRespVO runOverview(DeviceTotalTimeRecordReqVO pageReqVO) { + //参数构造 applyPeriodIfNecessary(pageReqVO); List records = deviceOperationPageList(pageReqVO); @@ -487,6 +488,11 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe List deviceIds = parseIds(pageReqVO.getIds()); List> hourlyRows = tDengineService.queryDeviceHourTrend(deviceIds, pageReqVO.getStartTime(), pageReqVO.getEndTime()); respVO.setHourlyStatus(buildHourlyStatus(hourlyRows)); + //设备运行轨迹 + List timelineRows = + buildTimelineRows(records, deviceIds, pageReqVO.getStartTime(), pageReqVO.getEndTime()); + respVO.setTotalDevices(timelineRows.size()); + respVO.setTimelineRows(paginateTimelineRows(timelineRows, pageReqVO.getTimelinePageNo(), pageReqVO.getTimelinePageSize())); return respVO; } @@ -552,6 +558,104 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe return Math.round(value * 100D) / 100D; } + private List buildTimelineRows(List records, + List deviceIds, + String startTime, + String endTime) { + if (records == null || records.isEmpty()) { + return Collections.emptyList(); + } + + Map> recordMap = tDengineService.queryDeviceOperationTimeline(deviceIds, startTime, endTime) + .stream() + .filter(item -> item.getDeviceId() != null) + .collect(Collectors.groupingBy(DeviceOperationRecordDO::getDeviceId, LinkedHashMap::new, Collectors.toList())); + + LocalDateTime start = LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern(DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)); + LocalDateTime end = LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern(DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)); + double totalSeconds = Math.max(1D, Duration.between(start, end).getSeconds()); + + return records.stream().map(record -> { + List segments = buildTimelineSegments( + recordMap.getOrDefault(record.getId(), Collections.emptyList()), + start, + end, + totalSeconds + ); + return DeviceOperationOverviewRespVO.TimelineRowItem.builder() + .id(String.valueOf(record.getId())) + .name(record.getDeviceName()) + .utilizationRate(parsePercentValue(record)) + .segments(segments) + .build(); + }).collect(Collectors.toList()); + } + + private List paginateTimelineRows(List rows, + Integer pageNo, + Integer pageSize) { + if (rows == null || rows.isEmpty()) { + return Collections.emptyList(); + } + int actualPageNo = pageNo == null || pageNo < 1 ? 1 : pageNo; + int actualPageSize = pageSize == null || pageSize < 1 ? 10 : pageSize; + int fromIndex = Math.min((actualPageNo - 1) * actualPageSize, rows.size()); + int toIndex = Math.min(fromIndex + actualPageSize, rows.size()); + return rows.subList(fromIndex, toIndex); + } + + private List buildTimelineSegments(List timelineRecords, + LocalDateTime start, + LocalDateTime end, + double totalSeconds) { + if (timelineRecords == null || timelineRecords.isEmpty()) { + return Collections.emptyList(); + } + + List segments = new ArrayList<>(); + for (int i = 0; i < timelineRecords.size(); i++) { + DeviceOperationRecordDO current = timelineRecords.get(i); + LocalDateTime currentTime = current.getCreateTime(); + if (currentTime == null) { + continue; + } + LocalDateTime segmentStart = currentTime.isBefore(start) ? start : currentTime; + LocalDateTime segmentEnd = i + 1 < timelineRecords.size() && timelineRecords.get(i + 1).getCreateTime() != null + ? timelineRecords.get(i + 1).getCreateTime() + : end; + + if (segmentEnd.isAfter(end)) { + segmentEnd = end; + } + if (!segmentEnd.isAfter(segmentStart)) { + continue; + } + + double startHour = Duration.between(start, segmentStart).getSeconds() / totalSeconds * 24D; + double endHour = Duration.between(start, segmentEnd).getSeconds() / totalSeconds * 24D; + + segments.add(DeviceOperationOverviewRespVO.TimelineSegmentItem.builder() + .status(mapRuleToStatus(current.getRule())) + .startHour(round2(startHour)) + .endHour(round2(endHour)) + .build()); + } + return segments; + } + + private String mapRuleToStatus(String rule) { + if ("1".equals(rule)) { + return "running"; + } + if ("2".equals(rule)) { + return "standby"; + } + if ("3".equals(rule)) { + return "fault"; + } + return "offline"; + } + private double getDoubleValue(Map map, String key) { Object value = map.get(key); if (value == null) {