#
Junjie
2026-04-12 d89dcf8ba3353f978d7d1dca7f1ad1a8151b1462
#
4个文件已修改
137 ■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/DeviceLogController.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/task/DeviceLogScheduler.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/deviceLogs/deviceLogs.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/deviceLogs/deviceLogs.html 7 ●●●●● 补丁 | 查看 | 原始文档 | 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>