| src/main/java/com/zy/asrs/controller/DeviceLogController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/zy/core/task/DeviceLogScheduler.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/webapp/static/js/deviceLogs/deviceLogs.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/webapp/views/deviceLogs/deviceLogs.html | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/zy/asrs/controller/DeviceLogController.java
@@ -18,8 +18,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -226,11 +224,11 @@ } for (DeviceAggregate aggregate : aggregateMap.values()) { if (aggregate.firstFile != null) { FileTimeRange firstRange = readFileTimeRange(aggregate.firstFile); FileTimeRange firstRange = readFileTimeRange(aggregate.firstFile, aggregate.stationId); aggregate.firstTime = firstRange.startTime != null ? firstRange.startTime : firstRange.endTime; } if (aggregate.lastFile != null) { FileTimeRange lastRange = readFileTimeRange(aggregate.lastFile); FileTimeRange lastRange = readFileTimeRange(aggregate.lastFile, aggregate.stationId); aggregate.lastTime = lastRange.endTime != null ? lastRange.endTime : lastRange.startTime; } } @@ -274,7 +272,7 @@ List<Map<String, Object>> segments = new ArrayList<>(); Long startTime = null; for (int i = 0; i < files.size(); i++) { Long segmentStart = getFileStartTime(files.get(i)); Long segmentStart = getFileStartTime(files.get(i), stationId); if (segmentStart != null && (startTime == null || segmentStart < startTime)) { startTime = segmentStart; } @@ -284,7 +282,7 @@ segment.put("endTime", null); segments.add(segment); } Long endTime = getFileEndTime(files.get(files.size() - 1)); Long endTime = getFileEndTime(files.get(files.size() - 1), stationId); if (endTime == null) { for (int i = segments.size() - 1; i >= 0; i--) { Long segmentStart = (Long) segments.get(i).get("startTime"); @@ -370,7 +368,9 @@ if (line != null && !line.trim().isEmpty()) { try { DeviceDataLog logItem = JSON.parseObject(line, DeviceDataLog.class); resultLogs.add(logItem); if (matchesRequestedStation(logItem, stationId)) { resultLogs.add(logItem); } } catch (Exception e) { } } @@ -427,7 +427,7 @@ while (low <= high) { int mid = (low + high) >>> 1; Path midFile = files.get(mid); Long midStart = getFileStartTime(midFile); Long midStart = getFileStartTime(midFile, stationId); if (midStart == null) { low = mid + 1; continue; @@ -455,9 +455,9 @@ } } private Long getFileStartTime(Path file) { private Long getFileStartTime(Path file, String stationId) { try { String firstLine = readFirstNonBlankLine(file); String firstLine = readFirstMatchingLine(file, stationId); if (firstLine == null) return null; DeviceDataLog firstLog = JSON.parseObject(firstLine, DeviceDataLog.class); return firstLog.getCreateTime().getTime(); @@ -466,9 +466,9 @@ } } private Long getFileEndTime(Path file) { private Long getFileEndTime(Path file, String stationId) { try { String lastLine = readLastNonBlankLine(file); String lastLine = readLastMatchingLine(file, stationId); if (lastLine == null) return null; DeviceDataLog lastLog = JSON.parseObject(lastLine, DeviceDataLog.class); return lastLog.getCreateTime().getTime(); @@ -943,65 +943,77 @@ } } private FileTimeRange readFileTimeRange(Path file) { private FileTimeRange readFileTimeRange(Path file, String stationId) { FileTimeRange range = new FileTimeRange(); try { String firstLine = readFirstNonBlankLine(file); String lastLine = readLastNonBlankLine(file); range.startTime = parseLogTime(firstLine); range.endTime = parseLogTime(lastLine); String firstLine = readFirstMatchingLine(file, stationId); String lastLine = readLastMatchingLine(file, stationId); range.startTime = parseLogTime(firstLine, stationId); range.endTime = parseLogTime(lastLine, stationId); return range; } catch (Exception e) { return range; } } private Long parseLogTime(String line) { private Long parseLogTime(String line, String stationId) { try { if (line == null || line.trim().isEmpty()) { return null; } DeviceDataLog logItem = JSON.parseObject(line, DeviceDataLog.class); if (!matchesRequestedStation(logItem, stationId)) { return null; } return logItem != null && logItem.getCreateTime() != null ? logItem.getCreateTime().getTime() : null; } catch (Exception e) { return null; } } private String readFirstNonBlankLine(Path file) { private boolean matchesRequestedStation(DeviceDataLog logItem, String stationId) { if (Cools.isEmpty(stationId)) { return true; } if (logItem == null || logItem.getStationId() == null) { return false; } return Objects.equals(String.valueOf(logItem.getStationId()), stationId); } private String readFirstMatchingLine(Path file, String stationId) { try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) { return lines.filter(line -> line != null && !line.trim().isEmpty()).findFirst().orElse(null); return lines .filter(line -> line != null && !line.trim().isEmpty()) .filter(line -> matchesRequestedStation(parseLogLine(line), stationId)) .findFirst() .orElse(null); } catch (Exception e) { return null; } } private String readLastNonBlankLine(Path file) { try (RandomAccessFile randomAccessFile = new RandomAccessFile(file.toFile(), "r")) { long length = randomAccessFile.length(); if (length <= 0) { private String readLastMatchingLine(Path file, String stationId) { try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) { List<String> matched = lines .filter(line -> line != null && !line.trim().isEmpty()) .filter(line -> matchesRequestedStation(parseLogLine(line), stationId)) .collect(Collectors.toList()); if (matched.isEmpty()) { return null; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (long pointer = length - 1; pointer >= 0; pointer--) { randomAccessFile.seek(pointer); int read = randomAccessFile.read(); if (read == '\n' || read == '\r') { if (baos.size() > 0) { break; } continue; } baos.write(read); return matched.get(matched.size() - 1); } catch (Exception e) { return null; } } private DeviceDataLog parseLogLine(String line) { try { if (line == null || line.trim().isEmpty()) { return null; } byte[] bytes = baos.toByteArray(); for (int i = 0, j = bytes.length - 1; i < j; i++, j--) { byte tmp = bytes[i]; bytes[i] = bytes[j]; bytes[j] = tmp; } String line = new String(bytes, StandardCharsets.UTF_8).trim(); return line.isEmpty() ? null : line; return JSON.parseObject(line, DeviceDataLog.class); } catch (Exception e) { return null; } src/main/java/com/zy/core/task/DeviceLogScheduler.java
@@ -149,12 +149,8 @@ if (prefix == null) { continue; } String deviceFolderKey = buildDeviceFolderKey(logItem); if (deviceFolderKey == null) { continue; } group.computeIfAbsent(datePart, k -> new HashMap<>()) .computeIfAbsent(deviceFolderKey, k -> new ArrayList<>()) .computeIfAbsent(prefix, k -> new ArrayList<>()) .add(logItem); } for (Map.Entry<String, Map<String, List<DeviceDataLog>>> dateEntry : group.entrySet()) { @@ -217,13 +213,6 @@ return logItem.getType() + "_" + logItem.getDeviceNo() + "_station_" + logItem.getStationId() + "_" + datePart + "_"; } return logItem.getType() + "_" + logItem.getDeviceNo() + "_" + datePart + "_"; } private String buildDeviceFolderKey(DeviceDataLog logItem) { if (logItem == null || logItem.getType() == null || logItem.getDeviceNo() == null) { return null; } return logItem.getType() + ":" + logItem.getDeviceNo(); } private Path resolveDeviceDir(Path dayDir, DeviceDataLog logItem) { src/main/webapp/static/js/deviceLogs/deviceLogs.js
@@ -33,6 +33,7 @@ selectedType: '', selectedDeviceNo: '', selectedStationId: '', selectedStationLabel: '', visualFocusStationId: '', activeDeviceKey: '', @@ -129,7 +130,7 @@ var deviceKeyword = String(this.searchDeviceNo || '').trim(); var stationKeyword = String(this.searchStationId || '').trim(); return devices.map(function (item) { var matchedStationId = ''; var matchedStationIds = []; var matchesDeviceNo = !deviceKeyword || String(item.deviceNo).indexOf(deviceKeyword) >= 0; var matchesStation = true; if (stationKeyword) { @@ -137,14 +138,13 @@ if (item.type === 'Devp') { if (item.stationId && String(item.stationId).indexOf(stationKeyword) >= 0) { matchesStation = true; matchedStationId = String(item.stationId); matchedStationIds = [String(item.stationId)]; } else { var stationIds = Array.isArray(item.stationIds) ? item.stationIds : []; for (var i = 0; i < stationIds.length; i++) { if (String(stationIds[i]).indexOf(stationKeyword) >= 0) { matchesStation = true; matchedStationId = String(stationIds[i]); break; matchedStationIds.push(String(stationIds[i])); } } } @@ -154,7 +154,8 @@ return null; } return Object.assign({}, item, { matchedStationId: matchedStationId matchedStationIds: matchedStationIds, matchedStationId: matchedStationIds.length === 1 ? matchedStationIds[0] : '' }); }).filter(function (item) { return !!item; @@ -506,6 +507,7 @@ deviceNo: String(device.deviceNo), stationId: device.stationId == null ? '' : String(device.stationId), stationIds: (device.stationIds || []).map(function (stationId) { return String(stationId); }), matchedStationIds: [], matchedStationId: '', fileCount: device.fileCount || 0, firstTime: device.firstTime || 0, @@ -573,6 +575,7 @@ this.selectedType = ''; this.selectedDeviceNo = ''; this.selectedStationId = ''; this.selectedStationLabel = ''; this.visualFocusStationId = ''; this.activeDeviceKey = ''; this.detailTab = 'logs'; @@ -601,9 +604,10 @@ this.selectedType = device.type; this.selectedDeviceNo = String(device.deviceNo); this.selectedStationId = device.stationId == null ? '' : String(device.stationId); this.visualFocusStationId = device.stationId == null || String(device.stationId) === '' this.selectedStationLabel = device.stationId == null || String(device.stationId) === '' ? (device.matchedStationId == null ? '' : String(device.matchedStationId)) : String(device.stationId); this.visualFocusStationId = this.selectedStationLabel; this.activeDeviceKey = nextKey; this.detailTab = 'raw'; this.rawTab = 'wcs'; @@ -613,6 +617,9 @@ this.loadingOffsets = {}; this.selectedTimestamp = 0; this.logLoadError = ''; if (this.selectedType === 'Devp' && !this.selectedStationId) { return; } this.loadTimeline(); }, resolveVisualStationId: function (logItem) { src/main/webapp/views/deviceLogs/deviceLogs.html
@@ -1180,6 +1180,7 @@ <span>末条: {{ formatTimestamp(device.lastTime, false) }}</span> <span v-if="device.type === 'Devp' && device.stationIds && device.stationIds.length">站点数: {{ device.stationIds.length }}</span> <span v-if="device.type === 'Devp' && device.matchedStationId">命中站点: {{ device.matchedStationId }}</span> <span v-else-if="device.type === 'Devp' && device.matchedStationIds && device.matchedStationIds.length > 1">命中 {{ device.matchedStationIds.length }} 个站点</span> </div> </button> </div> @@ -1194,11 +1195,11 @@ <div class="dl-viewer-header-main"> <button type="button" class="dl-btn is-ghost" @click="returnToSelector">返回筛选</button> <div class="dl-viewer-copy"> <div class="dl-panel-title">{{ selectedDeviceSummary ? (selectedDeviceSummary.typeLabel + ' ' + selectedDeviceSummary.deviceNo + '号' + (selectedDeviceSummary.type === 'Devp' && selectedDeviceSummary.stationId ? (' · 站点 ' + selectedDeviceSummary.stationId) : '')) : '设备状态查看' }}</div> <div class="dl-panel-desc">{{ selectedDay ? ('日志日期 ' + formatDayText(selectedDay)) : '请选择日期和设备' }}</div> <div class="dl-panel-title">{{ selectedDeviceSummary ? (selectedDeviceSummary.typeLabel + ' ' + selectedDeviceSummary.deviceNo + '号' + ((selectedDeviceSummary.type === 'Devp' && selectedStationLabel) ? (' · 站点 ' + selectedStationLabel) : '')) : '设备状态查看' }}</div> <div class="dl-panel-desc">{{ selectedDay ? ('日志日期 ' + formatDayText(selectedDay)) : '请选择日期和设备' }}<template v-if="selectedType === 'Devp' && selectedStationId === '' && selectedStationLabel"> · 当前仅按站点定位展示设备实时状态,未绑定单站点日志文件</template></div> <div v-if="selectedDeviceSummary" class="dl-viewer-meta"> <span class="dl-viewer-meta-item">类型 <strong>{{ selectedDeviceSummary.typeLabel }}</strong></span> <span v-if="selectedDeviceSummary.type === 'Devp' && selectedDeviceSummary.stationId" class="dl-viewer-meta-item">站点 <strong>{{ selectedDeviceSummary.stationId }}</strong></span> <span v-if="selectedDeviceSummary.type === 'Devp' && selectedStationLabel" class="dl-viewer-meta-item">站点 <strong>{{ selectedStationLabel }}</strong></span> <span class="dl-viewer-meta-item">文件 <strong>{{ selectedDeviceSummary.fileCount }}</strong></span> <span class="dl-viewer-meta-item">已载 <strong>{{ loadedSegmentCount }}</strong></span> <span class="dl-viewer-meta-item">范围 <strong>{{ timelineRangeText }}</strong></span>