diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/IotMqttRuntimeCache.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/IotMqttRuntimeCache.java index 1fd3f241a..8818c17bb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/IotMqttRuntimeCache.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/IotMqttRuntimeCache.java @@ -122,7 +122,7 @@ public class IotMqttRuntimeCache { } List parsedRules = new ArrayList<>(); for (DevicePointRulesDO rule : rules) { - if (StringUtils.isBlank(rule.getFieldRule())) { + if (StringUtils.isBlank(rule.getAlarmLevel()) || StringUtils.isBlank(rule.getFieldRule())) { continue; } try { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java index 048f301f3..f3ba88628 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/mqtt/consumer/MqttDataHandler.java @@ -398,7 +398,6 @@ public class MqttDataHandler extends SuperConsumer { attributeCode, processedValue, JSON.toJSONString(pointRulesRespVO)); handleMatchedSuccessRule(cachedRule.getRule(), pointRulesRespVO, processedValue, device, attributeCode, point); - break; } else { log.debug("规则不匹配: modelId={}, value={}, rule={}", attributeCode, processedValue, diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceProductionDailyReportServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceProductionDailyReportServiceImpl.java index 6d82107bd..7d5795e95 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceProductionDailyReportServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceProductionDailyReportServiceImpl.java @@ -12,24 +12,31 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceProductionDa import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceProductionDailyReportRespVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.devicewarinningrecord.DeviceWarinningRecordDO; import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper; import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactModelMapper; import cn.iocoder.yudao.module.mes.api.workteam.WorkTeamApi; import cn.iocoder.yudao.module.mes.api.workteam.dto.WorkTeamRespDTO; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; +import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.math.BigDecimal; @@ -57,8 +64,10 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD private static final String TEMPLATE_PATH = "excel/templates/fully-automatic-molding-production-daily-report-template.xlsx"; private static final String REPORT_PATH_PREFIX = "iot/device/production-daily-report"; + private static final String ALARM_LEVEL_DICT_TYPE = "alarm_registration"; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); @Resource private DeviceMapper deviceMapper; @@ -69,6 +78,8 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD @Resource private WorkTeamApi workTeamApi; @Resource + private DictDataApi dictDataApi; + @Resource private FileService fileService; @Resource private FileMapper fileMapper; @@ -96,15 +107,19 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD if (!endTime.isAfter(startTime)) { endTime = endTime.plusDays(1); } + log.info("[production-daily-report] generate request: reportDate={}, workTeamId={}, workTeamName={}, startTime={}, endTime={}, deviceIds={}", + reqVO.getReportDate(), reqVO.getWorkTeamId(), workTeam.getTeamName(), startTime, endTime, deviceIds); List items = buildCapacityItems(deviceIds, deviceMap, startTime, endTime); - byte[] excelContent = buildExcel(reqVO, workTeam, startTime, endTime, items); + byte[] excelContent = buildExcel(reqVO, workTeam, startTime, endTime, items, deviceIds); String fileName = String.format("全自动成型机生产日报表_%s_%s_%s.xlsx", reqVO.getReportDate().format(DateTimeFormatter.BASIC_ISO_DATE), reqVO.getWorkTeamId(), System.currentTimeMillis()); String filePath = String.format("%s/date=%s/workTeamId=%s/deviceIds=%s/deviceTags=%s/%s", REPORT_PATH_PREFIX, reqVO.getReportDate(), reqVO.getWorkTeamId(), joinDeviceIds(deviceIds), buildDeviceTags(deviceIds), fileName); Map file = fileService.createFile(fileName, filePath, excelContent); + log.info("[production-daily-report] file created: fileName={}, filePath={}, fileUrl={}, contentLength={}", + file.get("fileName"), filePath, file.get("fileUrl"), excelContent.length); DeviceProductionDailyReportRespVO respVO = buildBaseResp(reqVO.getReportDate(), workTeam, startTime, endTime, deviceIds); respVO.setFileName(file.get("fileName")); @@ -141,7 +156,7 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD Map deviceMap, LocalDateTime startTime, LocalDateTime endTime) { - Map> capacityPointMap = queryCapacityPoints(deviceIds); + Map capacityPointMap = queryCapacityPoints(deviceIds); Map> firstRows = tDengineService.queryDevicesEarliestRow(deviceIds, startTime.format(DATE_TIME_FORMATTER), endTime.format(DATE_TIME_FORMATTER)); Map> lastRows = tDengineService.queryDevicesLatestRow(deviceIds, @@ -150,31 +165,28 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD List items = new ArrayList<>(); for (Long deviceId : deviceIds) { DeviceDO device = deviceMap.get(deviceId); - List points = capacityPointMap.getOrDefault(deviceId, Collections.emptyList()); - if (CollUtil.isEmpty(points)) { + DeviceContactModelDO point = capacityPointMap.get(deviceId); + if (point == null) { items.add(buildEmptyCapacityItem(device)); continue; } Map firstRow = firstRows.get(deviceId); Map lastRow = lastRows.get(deviceId); - for (DeviceContactModelDO point : points) { - items.add(buildCapacityItem(device, point, firstRow, lastRow)); - } + items.add(buildCapacityItem(device, point, firstRow, lastRow)); } return items; } - private Map> queryCapacityPoints(List deviceIds) { + private Map queryCapacityPoints(List deviceIds) { List points = deviceContactModelMapper.selectList( Wrappers.lambdaQuery() .in(DeviceContactModelDO::getDeviceId, deviceIds) - .and(wrapper -> wrapper.like(DeviceContactModelDO::getTypeName, "产能") - .or().like(DeviceContactModelDO::getAttributeType, "产能") - .or().like(DeviceContactModelDO::getAttributeName, "产能")) + .eq(DeviceContactModelDO::getTypeName, "产能") .orderByAsc(DeviceContactModelDO::getDeviceId) - .orderByAsc(DeviceContactModelDO::getSort) - .orderByAsc(DeviceContactModelDO::getId)); - return points.stream().collect(Collectors.groupingBy(DeviceContactModelDO::getDeviceId, LinkedHashMap::new, Collectors.toList())); + .orderByDesc(DeviceContactModelDO::getCreateTime) + .orderByDesc(DeviceContactModelDO::getId)); + return points.stream().collect(Collectors.toMap(DeviceContactModelDO::getDeviceId, item -> item, (first, second) -> first, + LinkedHashMap::new)); } private DeviceProductionDailyReportRespVO.DeviceCapacityItem buildEmptyCapacityItem(DeviceDO device) { @@ -212,36 +224,190 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD @SneakyThrows private byte[] buildExcel(DeviceProductionDailyReportGenerateReqVO reqVO, WorkTeamRespDTO workTeam, LocalDateTime startTime, LocalDateTime endTime, - List items) { + List items, List deviceIds) { try (InputStream inputStream = new ClassPathResource(TEMPLATE_PATH).getInputStream(); - Workbook workbook = WorkbookFactory.create(inputStream); + Workbook workbook = WorkbookFactory.create(inputStream); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { Sheet sheet = workbook.getSheetAt(0); - setCellValue(sheet, 2, 0, "日期:" + LocalDate.now().format(DATE_FORMATTER)); - setCellValue(sheet, 2, 4, "班次:" + StrUtil.blankToDefault(workTeam.getTeamName(), "")); - setCellValue(sheet, 19, 9, "审核:"); - - int rowIndex = 5; + log.info("[production-daily-report] template loaded: path={}, sheetName={}, mergedRegions={}", + TEMPLATE_PATH, sheet.getSheetName(), sheet.getMergedRegions()); + String dateText = "日期:" + reqVO.getReportDate().format(DATE_FORMATTER); + String shiftText = "班次:" + StrUtil.blankToDefault(workTeam.getTeamName(), ""); + setHeaderValue(sheet, "日期:", dateText, 2, 0); + setHeaderValue(sheet, "班次:", shiftText, 2, 4); + log.info("[production-daily-report] header cells after write: A3={}, C3={}, E3={}", + getCellValue(sheet, 2, 0), getCellValue(sheet, 2, 2), getCellValue(sheet, 2, 4)); + + int rowIndex = 4; + int exceptionStartRowIndex = 5; + Row templateRow = sheet.getRow(rowIndex); + if (items.size() > 1) { + sheet.shiftRows(exceptionStartRowIndex, sheet.getLastRowNum(), items.size() - 1, true, false); + } for (int index = 0; index < items.size(); index++) { - if (rowIndex + index > 9) { - sheet.shiftRows(10, sheet.getLastRowNum(), 1, true, false); - } Row row = sheet.getRow(rowIndex + index); if (row == null) { row = sheet.createRow(rowIndex + index); + copyRowStyle(templateRow, row, 5); } DeviceProductionDailyReportRespVO.DeviceCapacityItem item = items.get(index); setCellValue(row, 0, index + 1); setCellValue(row, 1, StrUtil.blankToDefault(item.getDeviceCode(), item.getDeviceName())); - setCellValue(row, 4, item.getCapacityName()); - setCellValue(row, 20, item.getCapacityDiff()); - setCellValue(row, 21, toHour(startTime, endTime)); - setCellValue(row, 22, startTime.toLocalTime() + "-" + endTime.toLocalTime()); - setCellValue(row, 24, workTeam.getTeamName()); + setCellValue(row, 2, buildCapacityPointText(item)); + setCellValue(row, 3, item.getCapacityDiff()); + setCellValue(row, 4, startTime.toLocalTime() + "-" + endTime.toLocalTime()); } + fillWarningDescription(sheet, rowIndex + items.size() + 2, deviceIds, startTime, endTime); workbook.write(outputStream); - return outputStream.toByteArray(); + byte[] bytes = outputStream.toByteArray(); + logGeneratedHeader(bytes); + return bytes; + } + } + + private void fillWarningDescription(Sheet sheet, int warningRowIndex, List deviceIds, + LocalDateTime startTime, LocalDateTime endTime) { + List warningRecords = tDengineService.selectDeviceWarningList(deviceIds, startTime, endTime); + Map> warningMap = warningRecords.stream() + .collect(Collectors.groupingBy(DeviceWarinningRecordDO::getDeviceId, LinkedHashMap::new, Collectors.toList())); + Row templateRow = sheet.getRow(warningRowIndex); + for (int index = 0; index < deviceIds.size(); index++) { + Row row = sheet.getRow(warningRowIndex + index); + if (row == null) { + row = sheet.createRow(warningRowIndex + index); + copyRowStyle(templateRow, row, 5); + mergeCellsIfAbsent(sheet, warningRowIndex + index, 2, 4); + } + Long deviceId = deviceIds.get(index); + setCellValue(row, 0, index + 1); + setCellValue(row, 1, getDeviceText(deviceId)); + String warningDescription = buildWarningDescription(warningMap.getOrDefault(deviceId, Collections.emptyList())); + setCellValue(row, 2, warningDescription); + setWrapText(row, 2); + row.setHeightInPoints(Math.max(row.getHeightInPoints(), 18F * Math.max(1, warningDescription.split("\n", -1).length))); + } + } + + private String buildCapacityPointText(DeviceProductionDailyReportRespVO.DeviceCapacityItem item) { + String capacityName = item.getCapacityName(); + String capacityCode = item.getCapacityCode(); + if (StringUtils.isBlank(capacityName)) { + return StrUtil.blankToDefault(capacityCode, ""); + } + if (StringUtils.isBlank(capacityCode) || StringUtils.equals(capacityName, capacityCode)) { + return capacityName; + } + return capacityName + "(" + capacityCode + ")"; + } + + private String getDeviceText(Long deviceId) { + DeviceDO device = deviceMapper.selectById(deviceId); + if (device == null) { + return ""; + } + return StrUtil.blankToDefault(device.getDeviceCode(), device.getDeviceName()); + } + + private String buildWarningDescription(List warningRecords) { + if (CollUtil.isEmpty(warningRecords)) { + return ""; } + return warningRecords.stream() + .collect(Collectors.groupingBy(this::getWarningPointName, LinkedHashMap::new, Collectors.toList())) + .entrySet().stream() + .map(entry -> entry.getKey() + ":" + buildWarningPointSummary(entry.getValue())) + .collect(Collectors.joining("\n")); + } + + private String buildWarningPointSummary(List warningRecords) { + return warningRecords.stream() + .collect(Collectors.groupingBy(this::getWarningRuleKey, LinkedHashMap::new, Collectors.toList())) + .values().stream() + .map(this::buildWarningRuleSummary) + .collect(Collectors.joining(";")); + } + + private String getWarningRuleKey(DeviceWarinningRecordDO record) { + return getWarningName(record) + "|" + StrUtil.blankToDefault(record.getAlarmLevel(), ""); + } + + private String buildWarningRuleSummary(List warningRecords) { + DeviceWarinningRecordDO firstRecord = warningRecords.get(0); + String levelText = buildAlarmLevelText(firstRecord.getAlarmLevel()); + return getWarningName(firstRecord) + warningRecords.size() + "次" + + buildWarningValueRangeText(warningRecords) + levelText + buildWarningTimeRangeText(warningRecords); + } + + private String buildAlarmLevelText(String alarmLevel) { + if (StringUtils.isBlank(alarmLevel)) { + return ""; + } + DictDataRespDTO dictData = dictDataApi.getDictData(ALARM_LEVEL_DICT_TYPE, alarmLevel); + return ",等级=" + (dictData != null && StringUtils.isNotBlank(dictData.getLabel()) ? dictData.getLabel() : alarmLevel); + } + + private String getWarningPointName(DeviceWarinningRecordDO record) { + return StrUtil.blankToDefault(record.getModelName(), + StrUtil.blankToDefault(record.getRuleName(), StrUtil.blankToDefault(record.getRule(), "未知点位"))); + } + + private String getWarningName(DeviceWarinningRecordDO record) { + return StrUtil.blankToDefault(record.getRuleName(), StrUtil.blankToDefault(record.getRule(), "告警")); + } + + private String buildWarningValueRangeText(List warningRecords) { + List values = warningRecords.stream() + .map(this::parseWarningValue) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (CollUtil.isEmpty(values)) { + return ""; + } + double minValue = values.stream().min(Double::compareTo).orElse(0D); + double maxValue = values.stream().max(Double::compareTo).orElse(0D); + if (BigDecimal.valueOf(minValue).compareTo(BigDecimal.valueOf(maxValue)) == 0) { + return ",值=" + formatNumber(minValue); + } + return ",值=" + formatNumber(minValue) + "~" + formatNumber(maxValue); + } + + private Double parseWarningValue(DeviceWarinningRecordDO record) { + if (StringUtils.isBlank(record.getAddressValue())) { + return null; + } + try { + return Double.valueOf(record.getAddressValue()); + } catch (NumberFormatException e) { + return null; + } + } + + private String buildWarningTimeRangeText(List warningRecords) { + List times = warningRecords.stream() + .map(DeviceWarinningRecordDO::getCreateTime) + .filter(Objects::nonNull) + .sorted() + .collect(Collectors.toList()); + if (CollUtil.isEmpty(times)) { + return ""; + } + LocalDateTime startTime = times.get(0); + LocalDateTime endTime = times.get(times.size() - 1); + if (Objects.equals(startTime, endTime)) { + return ",时间=" + formatWarningTime(startTime, startTime); + } + return ",时间=" + formatWarningTime(startTime, endTime) + "~" + formatWarningTime(endTime, startTime); + } + + private String formatWarningTime(LocalDateTime time, LocalDateTime compareTime) { + if (Objects.equals(time.toLocalDate(), compareTime.toLocalDate())) { + return time.format(TIME_FORMATTER); + } + return time.format(DATE_TIME_FORMATTER); + } + + private String formatNumber(Double value) { + return BigDecimal.valueOf(value).stripTrailingZeros().toPlainString(); } private DeviceProductionDailyReportRespVO convertFileToReport(FileDO file) { @@ -343,6 +509,11 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD } private void setCellValue(Sheet sheet, int rowIndex, int columnIndex, Object value) { + CellRangeAddress mergedRegion = getMergedRegion(sheet, rowIndex, columnIndex); + if (mergedRegion != null) { + rowIndex = mergedRegion.getFirstRow(); + columnIndex = mergedRegion.getFirstColumn(); + } Row row = sheet.getRow(rowIndex); if (row == null) { row = sheet.createRow(rowIndex); @@ -361,5 +532,148 @@ public class DeviceProductionDailyReportServiceImpl implements DeviceProductionD cell.setCellValue(value == null ? "" : value.toString()); } } + + private void setWrapText(Row row, int columnIndex) { + Cell cell = row.getCell(columnIndex); + if (cell == null) { + return; + } + CellStyle cellStyle = row.getSheet().getWorkbook().createCellStyle(); + cellStyle.cloneStyleFrom(cell.getCellStyle()); + cellStyle.setWrapText(true); + cell.setCellStyle(cellStyle); + } + + private void copyRowStyle(Row sourceRow, Row targetRow, int columnCount) { + if (sourceRow == null || targetRow == null) { + return; + } + targetRow.setHeight(sourceRow.getHeight()); + for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { + Cell sourceCell = sourceRow.getCell(columnIndex); + if (sourceCell == null) { + continue; + } + Cell targetCell = targetRow.getCell(columnIndex); + if (targetCell == null) { + targetCell = targetRow.createCell(columnIndex); + } + targetCell.setCellStyle(sourceCell.getCellStyle()); + } + } + + private void mergeCellsIfAbsent(Sheet sheet, int rowIndex, int firstColumn, int lastColumn) { + CellRangeAddress newRegion = new CellRangeAddress(rowIndex, rowIndex, firstColumn, lastColumn); + for (CellRangeAddress mergedRegion : sheet.getMergedRegions()) { + if (mergedRegion.intersects(newRegion)) { + return; + } + } + sheet.addMergedRegion(newRegion); + } + + private void setHeaderValue(Sheet sheet, String label, String value, int fallbackRowIndex, int fallbackColumnIndex) { + Cell labelCell = findCellByPrefix(sheet, label); + if (labelCell != null) { + CellRangeAddress mergedRegion = getMergedRegion(sheet, labelCell.getRowIndex(), labelCell.getColumnIndex()); + log.info("[production-daily-report] set header by label: label={}, foundCell={}, rowIndex={}, columnIndex={}, mergedRegion={}, value={}", + label, labelCell.getAddress().formatAsString(), labelCell.getRowIndex(), labelCell.getColumnIndex(), mergedRegion, value); + setStringCellValueForce(sheet, labelCell.getRowIndex(), labelCell.getColumnIndex(), value); + return; + } + CellRangeAddress mergedRegion = getMergedRegion(sheet, fallbackRowIndex, fallbackColumnIndex); + log.info("[production-daily-report] set header by fallback: label={}, fallbackRowIndex={}, fallbackColumnIndex={}, mergedRegion={}, value={}", + label, fallbackRowIndex, fallbackColumnIndex, mergedRegion, value); + setStringCellValueForce(sheet, fallbackRowIndex, fallbackColumnIndex, value); + } + + private void setStringCellValueForce(Sheet sheet, int rowIndex, int columnIndex, String value) { + CellRangeAddress mergedRegion = getMergedRegion(sheet, rowIndex, columnIndex); + int firstRowIndex = mergedRegion != null ? mergedRegion.getFirstRow() : rowIndex; + int firstColumnIndex = mergedRegion != null ? mergedRegion.getFirstColumn() : columnIndex; + Row row = sheet.getRow(firstRowIndex); + if (row == null) { + row = sheet.createRow(firstRowIndex); + } + + Cell oldCell = row.getCell(firstColumnIndex); + org.apache.poi.ss.usermodel.CellStyle cellStyle = oldCell != null ? oldCell.getCellStyle() : null; + if (oldCell != null) { + row.removeCell(oldCell); + } + Cell newCell = row.createCell(firstColumnIndex, CellType.STRING); + if (cellStyle != null) { + newCell.setCellStyle(cellStyle); + } + newCell.setCellValue(value); + + if (mergedRegion != null) { + for (int column = mergedRegion.getFirstColumn() + 1; column <= mergedRegion.getLastColumn(); column++) { + Cell mergedCell = row.getCell(column); + if (mergedCell != null) { + mergedCell.setCellValue(""); + } + } + } + log.info("[production-daily-report] force set header cell: targetRowIndex={}, targetColumnIndex={}, mergedRegion={}, value={}, actualValue={}", + firstRowIndex, firstColumnIndex, mergedRegion, value, getCellValue(sheet, firstRowIndex, firstColumnIndex)); + } + + @SneakyThrows + private void logGeneratedHeader(byte[] bytes) { + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(bytes))) { + Sheet sheet = workbook.getSheetAt(0); + log.info("[production-daily-report] generated bytes header check: A3={}, C3={}, E3={}, mergedRegions={}", + getCellValue(sheet, 2, 0), getCellValue(sheet, 2, 2), getCellValue(sheet, 2, 4), sheet.getMergedRegions()); + } + } + + private String getCellValue(Sheet sheet, int rowIndex, int columnIndex) { + Row row = sheet.getRow(rowIndex); + Cell cell = row != null ? row.getCell(columnIndex) : null; + if (cell == null) { + CellRangeAddress mergedRegion = getMergedRegion(sheet, rowIndex, columnIndex); + if (mergedRegion != null) { + row = sheet.getRow(mergedRegion.getFirstRow()); + cell = row != null ? row.getCell(mergedRegion.getFirstColumn()) : null; + } + } + if (row == null) { + return null; + } + if (cell == null) { + return null; + } + if (cell.getCellType() == CellType.STRING) { + return cell.getStringCellValue(); + } + if (cell.getCellType() == CellType.NUMERIC) { + return String.valueOf(cell.getNumericCellValue()); + } + if (cell.getCellType() == CellType.BOOLEAN) { + return String.valueOf(cell.getBooleanCellValue()); + } + return cell.toString(); + } + + private Cell findCellByPrefix(Sheet sheet, String label) { + for (Row row : sheet) { + for (Cell cell : row) { + if (cell.getCellType() == CellType.STRING && StringUtils.startsWith(cell.getStringCellValue(), label)) { + return cell; + } + } + } + return null; + } + + private CellRangeAddress getMergedRegion(Sheet sheet, int rowIndex, int columnIndex) { + for (CellRangeAddress mergedRegion : sheet.getMergedRegions()) { + if (mergedRegion.isInRange(rowIndex, columnIndex)) { + return mergedRegion; + } + } + return null; + } } 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 8c5409b1f..5c579f585 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 @@ -2662,6 +2662,40 @@ public class TDengineService { } } + /** + * 查询设备在指定时间段内的告警记录 + */ + @DS("tdengine") + public List selectDeviceWarningList(List deviceIds, LocalDateTime startTime, LocalDateTime endTime) { + if (CollectionUtils.isEmpty(deviceIds)) { + return Collections.emptyList(); + } + + String placeholders = deviceIds.stream().map(item -> "?").collect(Collectors.joining(",")); + StringBuilder sql = new StringBuilder(); + List params = new ArrayList<>(); + sql.append("SELECT * FROM ").append(tdengineTable("iot_device_warning_record")).append(" ") + .append("WHERE deleted = 0 ") + .append("AND device_id IN (").append(placeholders).append(") "); + params.addAll(deviceIds); + if (startTime != null) { + sql.append("AND create_time >= ? "); + params.add(Timestamp.valueOf(startTime)); + } + if (endTime != null) { + sql.append("AND create_time <= ? "); + params.add(Timestamp.valueOf(endTime)); + } + sql.append("ORDER BY device_id ASC, create_time ASC"); + + try { + return jdbcTemplate.query(sql.toString(), params.toArray(), (rs, rowNum) -> mapRowToWarningRecordDO(rs)); + } catch (Exception e) { + log.error("查询设备时间段告警记录失败: deviceIds={}, startTime={}, endTime={}", deviceIds, startTime, endTime, e); + return Collections.emptyList(); + } + } + /** * 映射查询结果 */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/excel/templates/fully-automatic-molding-production-daily-report-template.xlsx b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/excel/templates/fully-automatic-molding-production-daily-report-template.xlsx index ab81f204d..551e70a49 100644 Binary files a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/excel/templates/fully-automatic-molding-production-daily-report-template.xlsx and b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/excel/templates/fully-automatic-molding-production-daily-report-template.xlsx differ