From fd82105a3dfe347c4c9acb0410c117d8d67c9339 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 18 三月 2026 10:40:16 +0800
Subject: [PATCH] #
---
src/main/java/com/zy/asrs/controller/DeviceLogController.java | 436 ++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 386 insertions(+), 50 deletions(-)
diff --git a/src/main/java/com/zy/asrs/controller/DeviceLogController.java b/src/main/java/com/zy/asrs/controller/DeviceLogController.java
index a498bd5..d5804a2 100644
--- a/src/main/java/com/zy/asrs/controller/DeviceLogController.java
+++ b/src/main/java/com/zy/asrs/controller/DeviceLogController.java
@@ -14,6 +14,8 @@
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;
@@ -27,6 +29,16 @@
@RestController
public class DeviceLogController extends BaseController {
+ private static final List<String> DEVICE_TYPE_ORDER = Arrays.asList("Crn", "DualCrn", "Rgv", "Devp");
+ private static final Map<String, String> DEVICE_TYPE_LABELS = new LinkedHashMap<>();
+
+ static {
+ DEVICE_TYPE_LABELS.put("Crn", "鍫嗗灈鏈�");
+ DEVICE_TYPE_LABELS.put("DualCrn", "鍙屽伐浣嶅爢鍨涙満");
+ DEVICE_TYPE_LABELS.put("Rgv", "RGV");
+ DEVICE_TYPE_LABELS.put("Devp", "杈撻�佽澶�");
+ }
+
@Value("${deviceLogStorage.loggingPath}")
private String loggingPath;
@@ -36,6 +48,31 @@
int totalCount;
int processedCount;
boolean finished;
+ }
+
+ private static class FileNameInfo {
+ String type;
+ String deviceNo;
+ String day;
+ int index;
+ }
+
+ private static class FileTimeRange {
+ Long startTime;
+ Long endTime;
+ }
+
+ private static class DeviceAggregate {
+ String type;
+ String typeLabel;
+ String deviceNo;
+ int fileCount;
+ Long firstTime;
+ Long lastTime;
+ Integer firstIndex;
+ Integer lastIndex;
+ Path firstFile;
+ Path lastFile;
}
private static final Map<String, ProgressInfo> DOWNLOAD_PROGRESS = new ConcurrentHashMap<>();
@@ -135,6 +172,142 @@
return R.ok(res);
} catch (Exception e) {
return R.error("璇诲彇璁惧鍒楄〃澶辫触");
+ }
+ }
+
+ @RequestMapping(value = "/deviceLog/day/{day}/summary/auth")
+ @ManagerAuth
+ public R summary(@PathVariable("day") String day) {
+ try {
+ String dayClean = normalizeDay(day);
+ if (dayClean == null) {
+ return R.error("鏃ユ湡鏍煎紡閿欒");
+ }
+ Path dayDir = Paths.get(loggingPath, dayClean);
+ if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
+ return R.ok(buildEmptySummary());
+ }
+
+ 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;
+ }
+ }
+ }
+ for (DeviceAggregate aggregate : aggregateMap.values()) {
+ if (aggregate.firstFile != null) {
+ FileTimeRange firstRange = readFileTimeRange(aggregate.firstFile);
+ aggregate.firstTime = firstRange.startTime != null ? firstRange.startTime : firstRange.endTime;
+ }
+ if (aggregate.lastFile != null) {
+ FileTimeRange lastRange = readFileTimeRange(aggregate.lastFile);
+ aggregate.lastTime = lastRange.endTime != null ? lastRange.endTime : lastRange.startTime;
+ }
+ }
+ return R.ok(buildSummaryResponse(aggregateMap.values()));
+ } catch (Exception e) {
+ log.error("璇诲彇璁惧鏃ュ織鎽樿澶辫触", e);
+ return R.error("璇诲彇璁惧鏃ュ織鎽樿澶辫触");
+ }
+ }
+
+ @RequestMapping(value = "/deviceLog/day/{day}/timeline/auth")
+ @ManagerAuth
+ public R timeline(@PathVariable("day") String day,
+ @RequestParam("type") String type,
+ @RequestParam("deviceNo") String deviceNo) {
+ try {
+ String dayClean = normalizeDay(day);
+ if (dayClean == null) {
+ return R.error("鏃ユ湡鏍煎紡閿欒");
+ }
+ if (type == null || SlaveType.findInstance(type) == null) {
+ return R.error("璁惧绫诲瀷閿欒");
+ }
+ if (deviceNo == null || !deviceNo.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);
+ if (files.isEmpty()) {
+ return R.error("鏈壘鍒版棩蹇楁枃浠�");
+ }
+
+ List<Map<String, Object>> segments = new ArrayList<>();
+ Long startTime = null;
+ for (int i = 0; i < files.size(); i++) {
+ Long segmentStart = getFileStartTime(files.get(i));
+ if (segmentStart != null && (startTime == null || segmentStart < startTime)) {
+ startTime = segmentStart;
+ }
+ Map<String, Object> segment = new LinkedHashMap<>();
+ segment.put("offset", i);
+ segment.put("startTime", segmentStart);
+ segment.put("endTime", null);
+ segments.add(segment);
+ }
+ Long endTime = getFileEndTime(files.get(files.size() - 1));
+ if (endTime == null) {
+ for (int i = segments.size() - 1; i >= 0; i--) {
+ Long segmentStart = (Long) segments.get(i).get("startTime");
+ if (segmentStart != null) {
+ endTime = segmentStart;
+ break;
+ }
+ }
+ }
+ for (int i = 0; i < segments.size(); i++) {
+ Long segmentEnd = null;
+ if (i < segments.size() - 1) {
+ segmentEnd = (Long) segments.get(i + 1).get("startTime");
+ }
+ if (segmentEnd == null && i == segments.size() - 1) {
+ segmentEnd = endTime;
+ }
+ if (segmentEnd == null) {
+ segmentEnd = (Long) segments.get(i).get("startTime");
+ }
+ segments.get(i).put("endTime", segmentEnd);
+ }
+
+ Map<String, Object> result = new HashMap<>();
+ result.put("type", type);
+ result.put("typeLabel", DEVICE_TYPE_LABELS.getOrDefault(type, type));
+ result.put("deviceNo", deviceNo);
+ result.put("startTime", startTime);
+ result.put("endTime", endTime);
+ result.put("totalFiles", files.size());
+ result.put("segments", segments);
+ return R.ok(result);
+ } catch (Exception e) {
+ log.error("璇诲彇璁惧鏃ュ織鏃堕棿杞村け璐�", e);
+ return R.error("璇诲彇璁惧鏃ュ織鏃堕棿杞村け璐�");
}
}
@@ -306,13 +479,21 @@
private Long getFileStartTime(Path file) {
try {
- String firstLine = null;
- try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) {
- firstLine = lines.findFirst().orElse(null);
- }
+ String firstLine = readFirstNonBlankLine(file);
if (firstLine == null) return null;
DeviceDataLog firstLog = JSON.parseObject(firstLine, DeviceDataLog.class);
return firstLog.getCreateTime().getTime();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private Long getFileEndTime(Path file) {
+ try {
+ String lastLine = readLastNonBlankLine(file);
+ if (lastLine == null) return null;
+ DeviceDataLog lastLog = JSON.parseObject(lastLine, DeviceDataLog.class);
+ return lastLog.getCreateTime().getTime();
} catch (Exception e) {
return null;
}
@@ -346,31 +527,8 @@
response.setStatus(404);
return;
}
- List<Path> files = Files.list(dayDir)
- .filter(p -> {
- String name = p.getFileName().toString();
- String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
- return name.endsWith(".log") && name.startsWith(prefix);
- }).collect(Collectors.toList());
- // 鎺掑簭锛堟寜鏂囦欢涓殑绱㈠紩鍙烽�掑锛�
- String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
- 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;
- }
- }));
- int from = offset == null || offset < 0 ? 0 : offset;
- int max = limit == null || limit <= 0 ? 200 : limit;
- int to = Math.min(files.size(), from + max);
- if (from >= files.size()) {
- response.setStatus(404);
- return;
- }
- files = files.subList(from, to);
+ List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo);
+ files = sliceDownloadFiles(files, offset, limit);
if (files.isEmpty()) {
response.setStatus(404);
return;
@@ -447,29 +605,14 @@
if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
return R.error("褰撴棩鐩綍涓嶅瓨鍦�");
}
- List<Path> files = Files.list(dayDir)
- .filter(p -> {
- String name = p.getFileName().toString();
- String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
- return name.endsWith(".log") && name.startsWith(prefix);
- }).collect(Collectors.toList());
- String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
- 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;
- }
- }));
- int from = offset == null || offset < 0 ? 0 : offset;
- int max = limit == null || limit <= 0 ? 200 : limit;
- int to = Math.min(files.size(), from + max);
- if (from >= files.size()) {
+ List<Path> files = findDeviceFiles(dayDir, dayClean, type, deviceNo);
+ if ((offset != null && offset >= files.size())) {
return R.error("璧峰搴忓彿瓒呭嚭鑼冨洿");
}
- files = files.subList(from, to);
+ files = sliceDownloadFiles(files, offset, limit);
+ if (files.isEmpty()) {
+ return R.error("鏈壘鍒版棩蹇楁枃浠�");
+ }
String id = UUID.randomUUID().toString();
ProgressInfo info = new ProgressInfo();
info.totalCount = files.size();
@@ -552,4 +695,197 @@
return R.ok(enums);
}
+
+ private Map<String, Object> buildEmptySummary() {
+ return buildSummaryResponse(Collections.emptyList());
+ }
+
+ private Map<String, Object> buildSummaryResponse(Collection<DeviceAggregate> aggregates) {
+ List<DeviceAggregate> aggregateList = new ArrayList<>(aggregates);
+ Map<String, Object> stats = new LinkedHashMap<>();
+ Map<String, Object> typeCounts = new LinkedHashMap<>();
+ int totalFiles = 0;
+ for (String type : DEVICE_TYPE_ORDER) {
+ int count = (int) aggregateList.stream().filter(item -> type.equals(item.type)).count();
+ typeCounts.put(type, count);
+ }
+ for (DeviceAggregate aggregate : aggregateList) {
+ totalFiles += aggregate.fileCount;
+ }
+ stats.put("totalDevices", aggregateList.size());
+ stats.put("totalFiles", totalFiles);
+ stats.put("typeCounts", typeCounts);
+
+ List<Map<String, Object>> groups = new ArrayList<>();
+ for (String type : DEVICE_TYPE_ORDER) {
+ List<DeviceAggregate> devices = aggregateList.stream()
+ .filter(item -> type.equals(item.type))
+ .sorted(Comparator.comparingInt(item -> parseDeviceNo(item.deviceNo)))
+ .collect(Collectors.toList());
+ Map<String, Object> group = new LinkedHashMap<>();
+ group.put("type", type);
+ group.put("typeLabel", DEVICE_TYPE_LABELS.get(type));
+ group.put("deviceCount", devices.size());
+ group.put("totalFiles", devices.stream().mapToInt(item -> item.fileCount).sum());
+ group.put("devices", devices.stream().map(item -> {
+ Map<String, Object> x = new LinkedHashMap<>();
+ x.put("type", item.type);
+ x.put("typeLabel", item.typeLabel);
+ x.put("deviceNo", item.deviceNo);
+ x.put("fileCount", item.fileCount);
+ x.put("firstTime", item.firstTime);
+ x.put("lastTime", item.lastTime);
+ return x;
+ }).collect(Collectors.toList()));
+ groups.add(group);
+ }
+
+ Map<String, Object> result = new LinkedHashMap<>();
+ result.put("stats", stats);
+ result.put("groups", groups);
+ return result;
+ }
+
+ private String normalizeDay(String day) {
+ String dayClean = day == null ? null : day.replaceAll("\\D", "");
+ if (dayClean == null || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
+ return null;
+ }
+ return dayClean;
+ }
+
+ private List<Path> findDeviceFiles(Path dayDir, String dayClean, String type, String deviceNo) throws Exception {
+ String prefix = type + "_" + deviceNo + "_" + dayClean + "_";
+ List<Path> files;
+ try (Stream<Path> stream = Files.list(dayDir)) {
+ files = stream
+ .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;
+ }
+ }));
+ return files;
+ }
+
+ private List<Path> sliceDownloadFiles(List<Path> files, Integer offset, Integer limit) {
+ if (files == null || files.isEmpty()) {
+ return Collections.emptyList();
+ }
+ int from = offset == null || offset < 0 ? 0 : offset;
+ if (from >= files.size()) {
+ return Collections.emptyList();
+ }
+ if (offset == null && limit == null) {
+ return new ArrayList<>(files);
+ }
+ int to;
+ if (limit == null || limit <= 0) {
+ to = files.size();
+ } else {
+ to = Math.min(files.size(), from + limit);
+ }
+ return new ArrayList<>(files.subList(from, to));
+ }
+
+ private FileNameInfo parseFileName(String fileName) {
+ if (fileName == null || !fileName.endsWith(".log")) {
+ return null;
+ }
+ String[] parts = fileName.split("_", 4);
+ if (parts.length < 4) {
+ return null;
+ }
+ FileNameInfo info = new FileNameInfo();
+ info.type = parts[0];
+ info.deviceNo = parts[1];
+ info.day = parts[2];
+ try {
+ info.index = Integer.parseInt(parts[3].replace(".log", ""));
+ } catch (Exception e) {
+ return null;
+ }
+ return info;
+ }
+
+ private int parseDeviceNo(String deviceNo) {
+ try {
+ return Integer.parseInt(String.valueOf(deviceNo));
+ } catch (Exception e) {
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ private FileTimeRange readFileTimeRange(Path file) {
+ FileTimeRange range = new FileTimeRange();
+ try {
+ String firstLine = readFirstNonBlankLine(file);
+ String lastLine = readLastNonBlankLine(file);
+ range.startTime = parseLogTime(firstLine);
+ range.endTime = parseLogTime(lastLine);
+ return range;
+ } catch (Exception e) {
+ return range;
+ }
+ }
+
+ private Long parseLogTime(String line) {
+ try {
+ if (line == null || line.trim().isEmpty()) {
+ return null;
+ }
+ DeviceDataLog logItem = JSON.parseObject(line, DeviceDataLog.class);
+ return logItem != null && logItem.getCreateTime() != null ? logItem.getCreateTime().getTime() : null;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private String readFirstNonBlankLine(Path file) {
+ try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) {
+ return lines.filter(line -> line != null && !line.trim().isEmpty()).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) {
+ 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);
+ }
+ 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;
+ } catch (Exception e) {
+ return null;
+ }
+ }
}
--
Gitblit v1.9.1