feat:完成app统计报表

ck
HuangHuiKang 1 month ago
parent 29567d00af
commit 6923984f8f

@ -52,6 +52,7 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_ATTRIBUTE_NOT_EXISTS = new ErrorCode(1_003_000_006, "设备属性不存在");
ErrorCode DEVICE_ATTRIBUTE_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_007, "采集点分类不存在");
ErrorCode DEVICE_CODE_EXISTS = new ErrorCode(1_003_000_000, "采集点编码已存在");
ErrorCode DEVICE_RATE_TREND_PERIOD_INVALID = new ErrorCode(1_003_000_100, "设备趋势查询时间区间参数不正确");
ErrorCode DEVICE_ATTRIBUTE_TYPE_REFERENCES_EXISTS = new ErrorCode(1_003_000_008, "存在采集点类型已被引用,请先删除对应引用");
ErrorCode ENDPOINT_DOES_NOT_EXIS = new ErrorCode(1_003_000_009, "暂未设置设备端点");
ErrorCode DEVICE_DOES_NOT_EXIST= new ErrorCode(1_003_000_010, "该采集设备不存在");

@ -256,6 +256,12 @@ public class DeviceController {
return success(deviceOperationalStatus);
}
@GetMapping("/deviceRateTrend")
@Operation(summary = "按天查询设备整体开机率和稼动率趋势")
public CommonResult<List<DeviceRateTrendPointRespVO>> getDeviceRateTrend(@Valid DeviceRateTrendReqVO reqVO) {
return success(deviceService.getDeviceRateTrend(reqVO));
}
@GetMapping("/getDeviceOverview")
@Operation(summary = "获取设备概况")
public CommonResult<DeviceOverviewRespVO> getDeviceOverview() {

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_RATE_TREND_PERIOD_INVALID;
@Getter
@AllArgsConstructor
public enum DeviceRateTrendPeriodEnum {
LAST_WEEK("LAST_WEEK", "上周") {
@Override
public DateRange resolve(LocalDate today) {
LocalDate start = today.minusWeeks(1).with(java.time.DayOfWeek.MONDAY);
return new DateRange(start, start.plusDays(6));
}
},
THIS_WEEK("THIS_WEEK", "本周") {
@Override
public DateRange resolve(LocalDate today) {
LocalDate start = today.with(java.time.DayOfWeek.MONDAY);
return new DateRange(start, today.minusDays(1));
}
},
LAST_7_DAYS("LAST_7_DAYS", "近7日") {
@Override
public DateRange resolve(LocalDate today) {
return new DateRange(today.minusDays(7), today.minusDays(1));
}
},
LAST_MONTH("LAST_MONTH", "上月") {
@Override
public DateRange resolve(LocalDate today) {
LocalDate start = today.minusMonths(1).withDayOfMonth(1);
return new DateRange(start, start.with(TemporalAdjusters.lastDayOfMonth()));
}
},
THIS_MONTH("THIS_MONTH", "本月") {
@Override
public DateRange resolve(LocalDate today) {
LocalDate start = today.withDayOfMonth(1);
return new DateRange(start, today.minusDays(1));
}
};
private final String code;
private final String name;
public abstract DateRange resolve(LocalDate today);
public static DeviceRateTrendPeriodEnum valueOfCode(String code) {
for (DeviceRateTrendPeriodEnum value : values()) {
if (value.code.equalsIgnoreCase(code)) {
return value;
}
}
throw exception(DEVICE_RATE_TREND_PERIOD_INVALID);
}
@Getter
@AllArgsConstructor
public static class DateRange {
private final LocalDate start;
private final LocalDate end;
}
}

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "管理后台 - 设备开机率/稼动率趋势点 Response VO")
public class DeviceRateTrendPointRespVO {
@Schema(description = "日期")
private String day;
@Schema(description = "当天整体开机率")
private String powerOnRate;
@Schema(description = "当天整体稼动率")
private String utilizationRate;
@Schema(description = "当天参与统计设备数")
private Integer deviceCount;
}

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "管理后台 - 设备开机率/稼动率趋势 Request VO")
public class DeviceRateTrendReqVO {
@Schema(description = "设备编码")
private String deviceCode;
@Schema(description = "设备名称")
private String deviceName;
@Schema(description = "设备ID集合逗号分隔")
private String ids;
@Schema(description = "时间区间LAST_WEEK/THIS_WEEK/LAST_7_DAYS/LAST_MONTH/THIS_MONTH")
private String period;
@Schema(description = "是否只统计排产设备,默认 true")
private Boolean onlyScheduled;
@Schema(description = "是否跳过节假日,默认 false")
private Boolean skipHoliday;
}

@ -103,10 +103,24 @@ public class DeviceOperationRecordController {
return success(pageResult);
}
@GetMapping("/deviceOperationPageList")
@Operation(summary = "设备运行报表列表")
@PreAuthorize("@ss.hasPermission('iot:device-operation-record:query')")
public CommonResult<List<DeviceTotalTimeRecordRespVO>> deviceOperationPageList(@Valid DeviceTotalTimeRecordReqVO pageReqVO) {
return success(deviceOperationRecordService.deviceOperationPageList(pageReqVO));
}
@GetMapping("/deviceRateTrendByDeviceId")
@Operation(summary = "根据设备ID查询某个设备近7日开机率和稼动率")
@PreAuthorize("@ss.hasPermission('iot:device-operation-record:query')")
public CommonResult<List<DeviceOperationRateTrendRespVO>> getDeviceRateTrendByDeviceId(@RequestParam("deviceId") Long deviceId) {
return success(deviceOperationRecordService.getDeviceRateTrendByDeviceId(deviceId));
}
@GetMapping("/deviceOperationList")
@Operation(summary = "产线设备运行开机率/稼动率-大屏")
// @PreAuthorize("@ss.hasPermission('iot:device-operation-record:query')")
@PreAuthorize("@ss.hasPermission('iot:device-operation-record:query')")
public CommonResult<List<DeviceTotalTimeRecordRespVO>> deviceOperationList(@Valid DeviceTotalTimeRecordReqVO pageReqVO) {
List<DeviceTotalTimeRecordRespVO> deviceTotalTimeRecordRespVOList = deviceOperationRecordService.deviceOperationList(pageReqVO);
return success(deviceTotalTimeRecordRespVOList);

@ -15,6 +15,7 @@ import org.apache.ibatis.annotations.Select;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -135,6 +136,10 @@ public interface DeviceMapper extends BaseMapperX<DeviceDO> {
List<DeviceDO> selectListIncludeDeletedByIds(@Param("ids") Collection<Long> ids);
List<Long> selectScheduledDvIds(@Param("requestIds") Collection<Long> requestIds);
List<Date> selectHolidayDays(@Param("startDay") Date startDay, @Param("endDay") Date endDay);
}

@ -138,6 +138,8 @@ public interface DeviceService {
DeviceOverviewRespVO getDeviceOverview();
List<DeviceRateTrendPointRespVO> getDeviceRateTrend(DeviceRateTrendReqVO reqVO);
List<Map<String, Object>> getMultiDeviceAttributes(Long goviewId);
List<DeviceContactModelDO> getDeviceAttributeList(Long deviceId);

@ -5,14 +5,18 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.DeviceConnectionStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.opc.OpcUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceRateTrendPeriodEnum;
import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceStatusEnum;
import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.utils.CronExpressionUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.scheduler.TaskSchedulerManager;
import cn.iocoder.yudao.module.iot.controller.admin.device.utils.DataTypeParseUtil;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
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.controller.admin.mqttdatarecord.vo.MqttDataRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattributetype.DeviceAttributeTypeDO;
@ -31,6 +35,8 @@ import cn.iocoder.yudao.module.iot.dal.mysql.devicemodel.DeviceModelMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.devicemodelattribute.DeviceModelAttributeMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.devicemodelrules.DeviceModelRulesMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.deviceoperationrecord.DeviceOperationRecordMapper;
import cn.iocoder.yudao.module.iot.service.deviceoperationrecord.DeviceOperationRecordService;
import cn.iocoder.yudao.module.iot.dal.mysql.devicepointrules.DevicePointRulesMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.gateway.GatewayMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.mqttdatarecord.MqttDataRecordMapper;
@ -57,7 +63,10 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
@ -114,6 +123,8 @@ public class DeviceServiceImpl implements DeviceService {
@Resource
private DeviceOperationRecordMapper deviceOperationRecordMapper;
@Resource
private DeviceOperationRecordService deviceOperationRecordService;
@Resource
private IMqttservice mqttService;
@ -1279,7 +1290,7 @@ public class DeviceServiceImpl implements DeviceService {
standbyCount++;
} else if ("3".equals(status)) {
faultCount++;
} else if ("0".equals(status)){
} else {
offlineCount++;
}
}
@ -1725,6 +1736,124 @@ public class DeviceServiceImpl implements DeviceService {
));
}
/**
*
* deviceOperationPage
*/
@Override
public List<DeviceRateTrendPointRespVO> getDeviceRateTrend(DeviceRateTrendReqVO reqVO) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
// 根据 period 解析查询日期范围,不包含当天
DeviceRateTrendPeriodEnum.DateRange dateRange = DeviceRateTrendPeriodEnum.valueOfCode(reqVO.getPeriod()).resolve(LocalDate.now());
LocalDateTime startDateTime = dateRange.getStart().atStartOfDay();
LocalDateTime endDateTime = dateRange.getEnd().atTime(LocalTime.MAX).withNano(0);
// 默认只统计排产设备,这里先得到最终参与统计的设备集合
String filteredIds = buildFilteredDeviceIds(reqVO);
List<DeviceRateTrendPointRespVO> result = new ArrayList<>();
for (LocalDate day = startDateTime.toLocalDate(); !day.isAfter(endDateTime.toLocalDate()); day = day.plusDays(1)) {
// 跳过节假日时,这一天不查询,也不返回
if (Boolean.TRUE.equals(reqVO.getSkipHoliday()) && isHoliday(day)) {
continue;
}
LocalDateTime dayStart = day.atStartOfDay();
LocalDateTime dayEnd = day.atTime(LocalTime.MAX).withNano(0);
// 复用现有 deviceOperationPage查出当天每台设备的开机率、稼动率基础数据
List<DeviceTotalTimeRecordRespVO> deviceDayList = queryDeviceDayList(reqVO, filteredIds, dayStart.format(formatter), dayEnd.format(formatter));
double powerOnRateSum = 0D;
double utilizationRateSum = 0D;
int deviceCount = deviceDayList.size();
for (DeviceTotalTimeRecordRespVO record : deviceDayList) {
powerOnRateSum += parsePercentValue(record.getPowerOnRate());
utilizationRateSum += parsePercentValue(record.getUtilizationRate());
}
DeviceRateTrendPointRespVO point = new DeviceRateTrendPointRespVO();
point.setDay(day.toString());
point.setDeviceCount(deviceCount);
point.setPowerOnRate(formatPercentValue(deviceCount > 0 ? powerOnRateSum / deviceCount : 0D));
point.setUtilizationRate(formatPercentValue(deviceCount > 0 ? utilizationRateSum / deviceCount : 0D));
result.add(point);
}
return result;
}
private double parsePercentValue(String percent) {
if (StringUtils.isBlank(percent)) {
return 0D;
}
String value = percent.trim();
if (value.endsWith("%")) {
value = value.substring(0, value.length() - 1);
}
if (StringUtils.isBlank(value)) {
return 0D;
}
return Double.parseDouble(value);
}
private String formatPercentValue(double value) {
return String.format("%.2f%%", value);
}
private List<DeviceTotalTimeRecordRespVO> queryDeviceDayList(DeviceRateTrendReqVO reqVO, String ids, String startTime, String endTime) {
DeviceTotalTimeRecordReqVO pageReqVO =
new DeviceTotalTimeRecordReqVO();
pageReqVO.setDeviceCode(reqVO.getDeviceCode());
pageReqVO.setDeviceName(reqVO.getDeviceName());
pageReqVO.setIds(ids);
pageReqVO.setStartTime(startTime);
pageReqVO.setEndTime(endTime);
return deviceOperationRecordService.deviceOperationPageList(pageReqVO);
}
private String buildFilteredDeviceIds(DeviceRateTrendReqVO reqVO) {
boolean onlyScheduled = reqVO.getOnlyScheduled() == null || reqVO.getOnlyScheduled();
if (!onlyScheduled) {
return reqVO.getIds();
}
Collection<Long> requestIds = parseIds(reqVO.getIds());
List<Long> filteredDvIds = deviceMapper.selectScheduledDvIds(requestIds);
if (StringUtils.isNotBlank(reqVO.getIds())) {
Set<Long> requestIdSet = new HashSet<>(requestIds);
filteredDvIds = filteredDvIds.stream().filter(requestIdSet::contains).collect(Collectors.toList());
}
if (filteredDvIds.isEmpty()) {
return "-1";
}
return filteredDvIds.stream().map(String::valueOf).collect(Collectors.joining(","));
}
private boolean isHoliday(LocalDate day) {
List<Date> holidays = deviceMapper.selectHolidayDays(java.sql.Date.valueOf(day), java.sql.Date.valueOf(day.plusDays(1)));
return !CollectionUtils.isEmpty(holidays);
}
private String toPercent(double value) {
return String.format("%.2f%%", value * 100);
}
private Collection<Long> parseIds(String ids) {
if (StringUtils.isBlank(ids)) {
return Collections.emptyList();
}
return Arrays.stream(ids.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.map(Long::valueOf)
.collect(Collectors.toList());
}
}

@ -54,5 +54,9 @@ public interface DeviceOperationRecordService {
PageResult<DeviceTotalTimeRecordRespVO> deviceOperationPage(@Valid DeviceTotalTimeRecordReqVO pageReqVO);
List<DeviceTotalTimeRecordRespVO> deviceOperationPageList(@Valid DeviceTotalTimeRecordReqVO pageReqVO);
List<DeviceOperationRateTrendRespVO> getDeviceRateTrendByDeviceId(Long deviceId);
List<DeviceTotalTimeRecordRespVO> deviceOperationList(@Valid DeviceTotalTimeRecordReqVO pageReqVO);
}

@ -13,6 +13,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@ -144,6 +145,79 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe
}
@Override
public List<DeviceTotalTimeRecordRespVO> deviceOperationPageList(DeviceTotalTimeRecordReqVO pageReqVO) {
List<DeviceTotalTimeRecordRespVO> deviceList =
deviceOperationRecordMapper.selectDeviceListFromMySQL(pageReqVO);
if (CollectionUtils.isEmpty(deviceList)) {
return Collections.emptyList();
}
List<Long> deviceIds = deviceList.stream()
.map(DeviceTotalTimeRecordRespVO::getId)
.collect(Collectors.toList());
List<Map<String, Object>> statsList =
deviceOperationRecordMapper.selectDeviceStatsFromTD(
deviceIds,
pageReqVO.getStartTime(),
pageReqVO.getEndTime()
);
Map<Long, DeviceTotalTimeRecordRespVO> tdStatsMap = convertStatsListToMap(statsList);
List<DeviceTotalTimeRecordRespVO> result = new ArrayList<>();
for (DeviceTotalTimeRecordRespVO device : deviceList) {
DeviceTotalTimeRecordRespVO stats = tdStatsMap.get(device.getId());
if (stats != null) {
device.setTotalOfflineTime(stats.getTotalOfflineTime());
device.setTotalRunningTime(stats.getTotalRunningTime());
device.setTotalStandbyTime(stats.getTotalStandbyTime());
device.setTotalFaultTime(stats.getTotalFaultTime());
} else {
device.setTotalOfflineTime(0);
device.setTotalRunningTime(0);
device.setTotalStandbyTime(0);
device.setTotalFaultTime(0);
}
result.add(device);
}
calculateAndSetConvertedValues(result, pageReqVO);
result.sort(Comparator.comparingDouble(this::parsePercentValue).reversed());
return result;
}
@Override
public List<DeviceOperationRateTrendRespVO> getDeviceRateTrendByDeviceId(Long deviceId) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
List<DeviceOperationRateTrendRespVO> result = new ArrayList<>();
for (int offset = 6; offset >= 0; offset--) {
LocalDate day = LocalDate.now().minusDays(offset);
LocalDateTime dayStart = day.atStartOfDay();
LocalDateTime dayEnd = day.atTime(LocalTime.MAX).withNano(0);
DeviceTotalTimeRecordReqVO reqVO = new DeviceTotalTimeRecordReqVO();
reqVO.setIds(String.valueOf(deviceId));
reqVO.setStartTime(dayStart.format(formatter));
reqVO.setEndTime(dayEnd.format(formatter));
List<DeviceTotalTimeRecordRespVO> dayList = deviceOperationPageList(reqVO);
DeviceTotalTimeRecordRespVO record = CollectionUtils.isNotEmpty(dayList) ? dayList.get(0) : null;
DeviceOperationRateTrendRespVO trend = new DeviceOperationRateTrendRespVO();
trend.setDay(day.toString());
trend.setPowerOnRate(record != null ? record.getPowerOnRate() : "0%");
trend.setUtilizationRate(record != null ? record.getUtilizationRate() : "0%");
result.add(trend);
}
return result;
}
@Override
public List<DeviceTotalTimeRecordRespVO> deviceOperationList(DeviceTotalTimeRecordReqVO deviceTotalTimeRecordReqVO) {
@ -242,6 +316,13 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe
return lineList;
}
private double parsePercentValue(DeviceTotalTimeRecordRespVO record) {
if (record == null || StringUtils.isBlank(record.getUtilizationRate())) {
return 0D;
}
return Double.parseDouble(record.getUtilizationRate().replace("%", "").trim());
}
private void calculateAndSetConvertedValues(
List<DeviceTotalTimeRecordRespVO> records,
DeviceTotalTimeRecordReqVO reqVO) {
@ -269,19 +350,19 @@ public class DeviceOperationRecordServiceImpl implements DeviceOperationRecordSe
// 在线时间 = 运行 + 待机 + 故障
double onlineSec = runningSec + standbySec + faultSec;
double totalSec = offlineSec + onlineSec;
// 计算总时间(根据筛选时间)
double totalSec = 0;
if (StringUtils.isNotBlank(startTimeStr) && StringUtils.isNotBlank(endTimeStr)) {
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime start = LocalDateTime.parse(startTimeStr, formatter);
LocalDateTime end = LocalDateTime.parse(endTimeStr, formatter);
totalSec = Duration.between(start, end).getSeconds();
}
// double totalSec = 0;
// if (StringUtils.isNotBlank(startTimeStr) && StringUtils.isNotBlank(endTimeStr)) {
//
// DateTimeFormatter formatter =
// DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//
// LocalDateTime start = LocalDateTime.parse(startTimeStr, formatter);
// LocalDateTime end = LocalDateTime.parse(endTimeStr, formatter);
//
// totalSec = Duration.between(start, end).getSeconds();
// }
// 防止负数或异常
if (totalSec < 0) {

@ -246,5 +246,31 @@
</foreach>
</select>
<select id="selectScheduledDvIds" resultType="java.lang.Long">
SELECT DISTINCT mo.dv_id
FROM mes_organization mo
INNER JOIN mes_device_ledger mdl ON mdl.id = mo.machine_id
WHERE mo.deleted = 0
AND mo.machine_id IS NOT NULL
AND mo.dv_id IS NOT NULL
AND mdl.deleted = 0
AND mdl.is_scheduled = 1
<if test="requestIds != null and requestIds.size() > 0">
AND mo.dv_id IN
<foreach collection="requestIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
<select id="selectHolidayDays" resultType="java.util.Date">
SELECT the_day
FROM mes_cal_holiday
WHERE deleted = 0
AND holiday_type = 'HOLIDAY'
AND the_day &gt;= #{startDay}
AND the_day &lt; #{endDay}
</select>
</mapper>

Loading…
Cancel
Save