From 89433d782a834ae4ab1835a0b70fa16340bbbd49 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 13 四月 2026 19:41:20 +0800
Subject: [PATCH] #算法耗时优化

---
 src/main/java/com/zy/asrs/controller/DeviceLogController.java |  458 ++++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 294 insertions(+), 164 deletions(-)

diff --git a/src/main/java/com/zy/asrs/controller/DeviceLogController.java b/src/main/java/com/zy/asrs/controller/DeviceLogController.java
index d5804a2..79cd046 100644
--- a/src/main/java/com/zy/asrs/controller/DeviceLogController.java
+++ b/src/main/java/com/zy/asrs/controller/DeviceLogController.java
@@ -4,18 +4,20 @@
 import com.core.annotations.ManagerAuth;
 import com.core.common.Cools;
 import com.core.common.R;
+import com.zy.asrs.entity.BasDevp;
 import com.zy.asrs.entity.DeviceDataLog;
+import com.zy.asrs.service.BasDevpService;
 import com.zy.common.web.BaseController;
 import com.zy.core.enums.SlaveType;
+import com.zy.core.model.StationObjModel;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 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;
@@ -24,6 +26,8 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 @Slf4j
 @RestController
@@ -39,6 +43,9 @@
         DEVICE_TYPE_LABELS.put("Devp", "杈撻�佽澶�");
     }
 
+    @Autowired
+    private BasDevpService basDevpService;
+
     @Value("${deviceLogStorage.loggingPath}")
     private String loggingPath;
 
@@ -53,6 +60,7 @@
     private static class FileNameInfo {
         String type;
         String deviceNo;
+        String stationId;
         String day;
         int index;
     }
@@ -66,6 +74,8 @@
         String type;
         String typeLabel;
         String deviceNo;
+        String stationId;
+        List<String> stationIds;
         int fileCount;
         Long firstTime;
         Long lastTime;
@@ -140,31 +150,29 @@
             if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                 return R.ok(new ArrayList<>());
             }
-            List<Path> files = Files.list(dayDir)
-                    .filter(p -> !Files.isDirectory(p) && p.getFileName().toString().endsWith(".log"))
-                    .collect(Collectors.toList());
+            List<Path> files = listDayLogFiles(dayDir);
             Map<String, Map<String, Object>> deviceMap = new HashMap<>();
             for (Path p : files) {
-                String name = p.getFileName().toString();
-                String[] parts = name.split("_");
-                if (parts.length < 4) {
+                FileNameInfo info = parseFileName(p.getFileName().toString());
+                if (info == null || !day.equals(info.day)) {
                     continue;
                 }
-                String deviceNo = parts[1];
-                String type = parts[0];
-                Map<String, Object> info = deviceMap.computeIfAbsent(deviceNo, k -> {
+                String deviceKey = buildDeviceKey(info.type, info.deviceNo, info.stationId);
+                Map<String, Object> infoMap = deviceMap.computeIfAbsent(deviceKey, k -> {
                     Map<String, Object> map = new HashMap<>();
-                    map.put("deviceNo", deviceNo);
+                    map.put("deviceNo", info.deviceNo);
+                    map.put("stationId", info.stationId);
                     map.put("types", new HashSet<String>());
                     map.put("fileCount", 0);
                     return map;
                 });
-                ((Set<String>) info.get("types")).add(type);
-                info.put("fileCount", ((Integer) info.get("fileCount")) + 1);
+                ((Set<String>) infoMap.get("types")).add(info.type);
+                infoMap.put("fileCount", ((Integer) infoMap.get("fileCount")) + 1);
             }
             List<Map<String, Object>> res = deviceMap.values().stream().map(m -> {
                 Map<String, Object> x = new HashMap<>();
                 x.put("deviceNo", m.get("deviceNo"));
+                x.put("stationId", m.get("stationId"));
                 x.put("types", ((Set<String>) m.get("types")).stream().collect(Collectors.toList()));
                 x.put("fileCount", m.get("fileCount"));
                 return x;
@@ -189,44 +197,42 @@
             }
 
             Map<String, DeviceAggregate> aggregateMap = new LinkedHashMap<>();
-            try (Stream<Path> stream = Files.list(dayDir)) {
-                List<Path> files = stream
-                        .filter(p -> !Files.isDirectory(p) && p.getFileName().toString().endsWith(".log"))
-                        .collect(Collectors.toList());
-                for (Path file : files) {
-                    FileNameInfo info = parseFileName(file.getFileName().toString());
-                    if (info == null || !dayClean.equals(info.day) || !DEVICE_TYPE_LABELS.containsKey(info.type)) {
-                        continue;
-                    }
-                    String key = info.type + ":" + info.deviceNo;
-                    DeviceAggregate aggregate = aggregateMap.computeIfAbsent(key, k -> {
-                        DeviceAggregate x = new DeviceAggregate();
-                        x.type = info.type;
-                        x.typeLabel = DEVICE_TYPE_LABELS.get(info.type);
-                        x.deviceNo = info.deviceNo;
-                        return x;
-                    });
-                    aggregate.fileCount += 1;
-                    if (aggregate.firstIndex == null || info.index < aggregate.firstIndex) {
-                        aggregate.firstIndex = info.index;
-                        aggregate.firstFile = file;
-                    }
-                    if (aggregate.lastIndex == null || info.index > aggregate.lastIndex) {
-                        aggregate.lastIndex = info.index;
-                        aggregate.lastFile = file;
-                    }
+            List<Path> files = listDayLogFiles(dayDir);
+            for (Path file : files) {
+                FileNameInfo info = parseFileName(file.getFileName().toString());
+                if (info == null || !dayClean.equals(info.day) || !DEVICE_TYPE_LABELS.containsKey(info.type)) {
+                    continue;
+                }
+                String key = buildDeviceKey(info.type, info.deviceNo, info.stationId);
+                DeviceAggregate aggregate = aggregateMap.computeIfAbsent(key, k -> {
+                    DeviceAggregate x = new DeviceAggregate();
+                    x.type = info.type;
+                    x.typeLabel = DEVICE_TYPE_LABELS.get(info.type);
+                    x.deviceNo = info.deviceNo;
+                    x.stationId = info.stationId;
+                    return x;
+                });
+                aggregate.fileCount += 1;
+                if (aggregate.firstIndex == null || info.index < aggregate.firstIndex) {
+                    aggregate.firstIndex = info.index;
+                    aggregate.firstFile = file;
+                }
+                if (aggregate.lastIndex == null || info.index > aggregate.lastIndex) {
+                    aggregate.lastIndex = info.index;
+                    aggregate.lastFile = file;
                 }
             }
             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;
                 }
             }
+            enrichDevpStationIds(aggregateMap.values());
             return R.ok(buildSummaryResponse(aggregateMap.values()));
         } catch (Exception e) {
             log.error("璇诲彇璁惧鏃ュ織鎽樿澶辫触", e);
@@ -238,7 +244,8 @@
     @ManagerAuth
     public R timeline(@PathVariable("day") String day,
                       @RequestParam("type") String type,
-                      @RequestParam("deviceNo") String deviceNo) {
+                      @RequestParam("deviceNo") String deviceNo,
+                      @RequestParam(value = "stationId", required = false) String stationId) {
         try {
             String dayClean = normalizeDay(day);
             if (dayClean == null) {
@@ -250,11 +257,14 @@
             if (deviceNo == null || !deviceNo.chars().allMatch(Character::isDigit)) {
                 return R.error("璁惧缂栧彿閿欒");
             }
+            if (isDevpType(type) && (stationId == null || !stationId.chars().allMatch(Character::isDigit))) {
+                return R.error("绔欑偣缂栧彿閿欒");
+            }
             Path dayDir = Paths.get(loggingPath, dayClean);
             if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                 return R.error("鏈壘鍒版棩蹇楁枃浠�");
             }
-            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo);
+            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo, stationId);
             if (files.isEmpty()) {
                 return R.error("鏈壘鍒版棩蹇楁枃浠�");
             }
@@ -262,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;
                 }
@@ -272,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");
@@ -300,6 +310,7 @@
             result.put("type", type);
             result.put("typeLabel", DEVICE_TYPE_LABELS.getOrDefault(type, type));
             result.put("deviceNo", deviceNo);
+            result.put("stationId", stationId);
             result.put("startTime", startTime);
             result.put("endTime", endTime);
             result.put("totalFiles", files.size());
@@ -316,6 +327,7 @@
     public R preview(@PathVariable("day") String day,
                      @RequestParam("type") String type,
                      @RequestParam("deviceNo") String deviceNo,
+                     @RequestParam(value = "stationId", required = false) String stationId,
                      @RequestParam(value = "offset", required = false) Integer offset,
                      @RequestParam(value = "limit", required = false) Integer limit) {
         try {
@@ -329,48 +341,37 @@
             if (deviceNo == null || !deviceNo.chars().allMatch(Character::isDigit)) {
                 return R.error("璁惧缂栧彿閿欒");
             }
+            if (isDevpType(type) && (stationId == null || !stationId.chars().allMatch(Character::isDigit))) {
+                return R.error("绔欑偣缂栧彿閿欒");
+            }
             Path dayDir = Paths.get(loggingPath, dayClean);
             if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                 return R.ok(new ArrayList<>());
             }
-            String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
-            List<Path> files = Files.list(dayDir)
-                    .filter(p -> {
-                        String name = p.getFileName().toString();
-                        return name.endsWith(".log") && name.startsWith(prefix);
-                    }).collect(Collectors.toList());
-            
-            files.sort(Comparator.comparingInt(p -> {
-                String n = p.getFileName().toString();
-                try {
-                    String suf = n.substring(prefix.length(), n.length() - 4);
-                    return Integer.parseInt(suf);
-                } catch (Exception e) {
-                    return Integer.MAX_VALUE;
-                }
-            }));
-            
+            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo, stationId);
+
             int from = offset == null || offset < 0 ? 0 : offset;
-            int max = limit == null || limit <= 0 ? 5 : limit; // 榛樿璇诲彇5涓枃浠�
-            if (max > 10) max = 10; // 闄愬埗鏈�澶ф枃浠舵暟锛岄槻姝㈣秴鏃�
+            int max = limit == null || limit <= 0 ? 5 : limit;
+            if (max > 10) max = 10;
             int to = Math.min(files.size(), from + max);
-            
+
             if (from >= files.size()) {
                 return R.ok(new ArrayList<>());
             }
-            
+
             List<Path> targetFiles = files.subList(from, to);
             List<DeviceDataLog> resultLogs = new ArrayList<>();
-            
+
             for (Path f : targetFiles) {
                 try (Stream<String> lines = Files.lines(f, StandardCharsets.UTF_8)) {
                     lines.forEach(line -> {
                         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) {
-                                // 蹇界暐瑙f瀽閿欒
                             }
                         }
                     });
@@ -378,9 +379,8 @@
                     log.error("璇诲彇鏃ュ織鏂囦欢澶辫触: " + f, e);
                 }
             }
-            // 鎸夋椂闂存帓搴�
             resultLogs.sort(Comparator.comparing(DeviceDataLog::getCreateTime, Comparator.nullsLast(Date::compareTo)));
-            
+
             return R.ok(resultLogs);
         } catch (Exception e) {
             log.error("棰勮鏃ュ織澶辫触", e);
@@ -393,6 +393,7 @@
     public R seek(@PathVariable("day") String day,
                   @RequestParam("type") String type,
                   @RequestParam("deviceNo") String deviceNo,
+                  @RequestParam(value = "stationId", required = false) String stationId,
                   @RequestParam("timestamp") Long timestamp) {
         try {
             String dayClean = day == null ? null : day.replaceAll("\\D", "");
@@ -405,81 +406,58 @@
             if (deviceNo == null || !deviceNo.chars().allMatch(Character::isDigit)) {
                 return R.error("璁惧缂栧彿閿欒");
             }
+            if (isDevpType(type) && (stationId == null || !stationId.chars().allMatch(Character::isDigit))) {
+                return R.error("绔欑偣缂栧彿閿欒");
+            }
             Path dayDir = Paths.get(loggingPath, dayClean);
             if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                 return R.error("鏈壘鍒版棩蹇楁枃浠�");
             }
-            
-            String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
-            List<Path> files = Files.list(dayDir)
-                    .filter(p -> {
-                        String name = p.getFileName().toString();
-                        return name.endsWith(".log") && name.startsWith(prefix);
-                    }).collect(Collectors.toList());
-            
-            files.sort(Comparator.comparingInt(p -> {
-                String n = p.getFileName().toString();
-                try {
-                    String suf = n.substring(prefix.length(), n.length() - 4);
-                    return Integer.parseInt(suf);
-                } catch (Exception e) {
-                    return Integer.MAX_VALUE;
-                }
-            }));
-            
+
+            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo, stationId);
+
             if (files.isEmpty()) {
                 return R.error("鏈壘鍒版棩蹇楁枃浠�");
             }
-            
-            // Binary search for the file containing the timestamp
-            // We want to find the LAST file that has startTime <= targetTime.
-            // Because files are sequential: [t0, t1), [t1, t2), ...
-            // If we find file[i].startTime <= target < file[i+1].startTime, then target is in file[i].
-            
+
             int low = 0;
             int high = files.size() - 1;
             int foundIndex = -1;
-            
+
             while (low <= high) {
                 int mid = (low + high) >>> 1;
                 Path midFile = files.get(mid);
-                
-                // Read start time of this file
-                Long midStart = getFileStartTime(midFile);
+                Long midStart = getFileStartTime(midFile, stationId);
                 if (midStart == null) {
                     low = mid + 1;
                     continue;
                 }
-                
+
                 if (midStart <= timestamp) {
-                    // This file starts before or at target. It COULD be the one.
-                    // But maybe a later file also starts before target?
                     foundIndex = mid;
-                    low = mid + 1; // Try to find a later start time
+                    low = mid + 1;
                 } else {
-                    // This file starts AFTER target. So target must be in an earlier file.
                     high = mid - 1;
                 }
             }
-            
+
             if (foundIndex == -1) {
                 foundIndex = 0;
             }
-            
-            // Return the file index (offset)
+
             Map<String, Object> result = new HashMap<>();
             result.put("offset", foundIndex);
             return R.ok(result);
-            
+
         } catch (Exception e) {
             log.error("瀵诲潃澶辫触", e);
             return R.error("瀵诲潃澶辫触");
         }
     }
     
-    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();
@@ -488,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();
@@ -522,12 +500,17 @@
                 response.setStatus(400);
                 return;
             }
+            String stationId = request.getParameter("stationId");
+            if (isDevpType(type) && (stationId == null || !stationId.chars().allMatch(Character::isDigit))) {
+                response.setStatus(400);
+                return;
+            }
             Path dayDir = Paths.get(loggingPath, dayClean);
             if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                 response.setStatus(404);
                 return;
             }
-            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo);
+            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo, stationId);
             files = sliceDownloadFiles(files, offset, limit);
             if (files.isEmpty()) {
                 response.setStatus(404);
@@ -563,9 +546,9 @@
             response.setHeader("X-Total-Size", String.valueOf(totalRawSize));
             response.setHeader("X-File-Count", String.valueOf(files.size()));
             response.setHeader("X-Progress-Id", id);
-            try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(response.getOutputStream())) {
+            try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
                 for (Path f : files) {
-                    java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry(f.getFileName().toString());
+                    ZipEntry entry = new ZipEntry(f.getFileName().toString());
                     zos.putNextEntry(entry);
                     Files.copy(f, zos);
                     zos.closeEntry();
@@ -601,11 +584,15 @@
             if (Cools.isEmpty(deviceNo) || !deviceNo.chars().allMatch(Character::isDigit)) {
                 return R.error("璁惧缂栧彿閿欒");
             }
+            String stationId = param.getString("stationId");
+            if (isDevpType(type) && (Cools.isEmpty(stationId) || !stationId.chars().allMatch(Character::isDigit))) {
+                return R.error("绔欑偣缂栧彿閿欒");
+            }
             Path dayDir = Paths.get(loggingPath, dayClean);
             if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
                 return R.error("褰撴棩鐩綍涓嶅瓨鍦�");
             }
-            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo);
+            List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo, stationId);
             if ((offset != null && offset >= files.size())) {
                 return R.error("璧峰搴忓彿瓒呭嚭鑼冨洿");
             }
@@ -732,9 +719,11 @@
                 x.put("type", item.type);
                 x.put("typeLabel", item.typeLabel);
                 x.put("deviceNo", item.deviceNo);
+                x.put("stationId", item.stationId);
                 x.put("fileCount", item.fileCount);
                 x.put("firstTime", item.firstTime);
                 x.put("lastTime", item.lastTime);
+                x.put("stationIds", item.stationIds == null ? Collections.emptyList() : item.stationIds);
                 return x;
             }).collect(Collectors.toList()));
             groups.add(group);
@@ -746,6 +735,56 @@
         return result;
     }
 
+    private void enrichDevpStationIds(Collection<DeviceAggregate> aggregates) {
+        if (aggregates == null || aggregates.isEmpty() || basDevpService == null) {
+            return;
+        }
+        List<DeviceAggregate> devpAggregates = aggregates.stream()
+                .filter(item -> item != null && isDevpType(item.type) && Cools.isEmpty(item.stationId) && !Cools.isEmpty(item.deviceNo))
+                .collect(Collectors.toList());
+        if (devpAggregates.isEmpty()) {
+            return;
+        }
+        List<Integer> devpNos = devpAggregates.stream()
+                .map(item -> parseInteger(item.deviceNo))
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (devpNos.isEmpty()) {
+            return;
+        }
+        Map<Integer, List<String>> stationIdsByDevpNo = basDevpService.listByIds(devpNos).stream()
+                .filter(Objects::nonNull)
+                .collect(Collectors.toMap(BasDevp::getDevpNo, this::extractStationIds, (left, right) -> left));
+        for (DeviceAggregate aggregate : devpAggregates) {
+            Integer devpNo = parseInteger(aggregate.deviceNo);
+            if (devpNo != null) {
+                aggregate.stationIds = stationIdsByDevpNo.getOrDefault(devpNo, Collections.emptyList());
+            }
+        }
+    }
+
+    private List<String> extractStationIds(BasDevp basDevp) {
+        if (basDevp == null) {
+            return Collections.emptyList();
+        }
+        return basDevp.getStationList$().stream()
+                .map(StationObjModel::getStationId)
+                .filter(Objects::nonNull)
+                .map(String::valueOf)
+                .distinct()
+                .sorted(Comparator.comparingInt(this::parseDeviceNo))
+                .collect(Collectors.toList());
+    }
+
+    private Integer parseInteger(String value) {
+        try {
+            return Integer.parseInt(String.valueOf(value));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
     private String normalizeDay(String day) {
         String dayClean = day == null ? null : day.replaceAll("\\D", "");
         if (dayClean == null || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
@@ -754,27 +793,57 @@
         return dayClean;
     }
 
-    private List<Path> findDeviceFiles(Path dayDir, String dayClean, String type, String deviceNo) throws Exception {
-        String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
+    private List<Path> findDeviceFiles(Path dayDir, String dayClean, String type, String deviceNo, String stationId) throws Exception {
+        FileNameInfo target = new FileNameInfo();
+        target.type = type;
+        target.deviceNo = deviceNo;
+        target.stationId = stationId;
+        target.day = dayClean;
+        Path deviceDir = resolveDeviceDir(dayDir, type, deviceNo);
+        if (deviceDir == null || !Files.exists(deviceDir) || !Files.isDirectory(deviceDir)) {
+            return Collections.emptyList();
+        }
         List<Path> files;
-        try (Stream<Path> stream = Files.list(dayDir)) {
+        try (Stream<Path> stream = Files.list(deviceDir)) {
             files = stream
-                    .filter(p -> {
-                        String name = p.getFileName().toString();
-                        return name.endsWith(".log") && name.startsWith(prefix);
-                    })
+                    .filter(p -> !Files.isDirectory(p) && matchesFileInfo(parseFileName(p.getFileName().toString()), target))
                     .collect(Collectors.toList());
         }
         files.sort(Comparator.comparingInt(p -> {
-            String n = p.getFileName().toString();
-            try {
-                String suf = n.substring(prefix.length(), n.length() - 4);
-                return Integer.parseInt(suf);
-            } catch (Exception e) {
-                return Integer.MAX_VALUE;
-            }
+            FileNameInfo info = parseFileName(p.getFileName().toString());
+            return info == null ? Integer.MAX_VALUE : info.index;
         }));
         return files;
+    }
+
+    private List<Path> listDayLogFiles(Path dayDir) throws Exception {
+        if (dayDir == null || !Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
+            return Collections.emptyList();
+        }
+        List<Path> files = new ArrayList<>();
+        try (Stream<Path> typeStream = Files.list(dayDir)) {
+            List<Path> typeDirs = typeStream.filter(Files::isDirectory).collect(Collectors.toList());
+            for (Path typeDir : typeDirs) {
+                try (Stream<Path> deviceStream = Files.list(typeDir)) {
+                    List<Path> deviceDirs = deviceStream.filter(Files::isDirectory).collect(Collectors.toList());
+                    for (Path deviceDir : deviceDirs) {
+                        try (Stream<Path> fileStream = Files.list(deviceDir)) {
+                            fileStream
+                                    .filter(p -> !Files.isDirectory(p) && p.getFileName().toString().endsWith(".log"))
+                                    .forEach(files::add);
+                        }
+                    }
+                }
+            }
+        }
+        return files;
+    }
+
+    private Path resolveDeviceDir(Path dayDir, String type, String deviceNo) {
+        if (dayDir == null || Cools.isEmpty(type) || Cools.isEmpty(deviceNo)) {
+            return null;
+        }
+        return dayDir.resolve(type).resolve(deviceNo);
     }
 
     private List<Path> sliceDownloadFiles(List<Path> files, Integer offset, Integer limit) {
@@ -801,20 +870,69 @@
         if (fileName == null || !fileName.endsWith(".log")) {
             return null;
         }
-        String[] parts = fileName.split("_", 4);
+        String fileNameNoExt = fileName.substring(0, fileName.length() - 4);
+        String[] parts = fileNameNoExt.split("_");
         if (parts.length < 4) {
             return null;
         }
         FileNameInfo info = new FileNameInfo();
         info.type = parts[0];
         info.deviceNo = parts[1];
+        if (isDevpType(info.type)) {
+            if (parts.length != 6 || !"station".equals(parts[2])) {
+                return null;
+            }
+            info.stationId = parts[3];
+            info.day = parts[4];
+            try {
+                info.index = Integer.parseInt(parts[5]);
+            } catch (Exception e) {
+                return null;
+            }
+            return info;
+        }
+        if (parts.length != 4) {
+            return null;
+        }
         info.day = parts[2];
         try {
-            info.index = Integer.parseInt(parts[3].replace(".log", ""));
+            info.index = Integer.parseInt(parts[3]);
         } catch (Exception e) {
             return null;
         }
         return info;
+    }
+
+    private String buildDeviceKey(String type, String deviceNo, String stationId) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(String.valueOf(type)).append(":").append(String.valueOf(deviceNo));
+        if (isDevpType(type)) {
+            builder.append(":").append(String.valueOf(stationId));
+        }
+        return builder.toString();
+    }
+
+    private boolean matchesFileInfo(FileNameInfo actual, FileNameInfo target) {
+        if (actual == null || target == null) {
+            return false;
+        }
+        if (!Objects.equals(actual.type, target.type)) {
+            return false;
+        }
+        if (!Objects.equals(actual.deviceNo, target.deviceNo)) {
+            return false;
+        }
+        if (!Objects.equals(actual.day, target.day)) {
+            return false;
+        }
+        if (isDevpType(actual.type)) {
+            return Objects.equals(actual.stationId, target.stationId);
+        }
+        return true;
+    }
+
+    private boolean isDevpType(String type) {
+        return SlaveType.Devp.name().equals(type);
     }
 
     private int parseDeviceNo(String deviceNo) {
@@ -825,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;
         }

--
Gitblit v1.9.1