package com.zy.asrs.controller;
|
|
import com.alibaba.fastjson.JSON;
|
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.BufferedWriter;
|
import java.io.OutputStreamWriter;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeParseException;
|
import java.util.*;
|
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
import java.util.stream.Collectors;
|
import java.util.stream.Stream;
|
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipOutputStream;
|
|
@Slf4j
|
@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", "输送设备");
|
}
|
|
@Autowired
|
private BasDevpService basDevpService;
|
|
@Value("${deviceLogStorage.loggingPath}")
|
private String loggingPath;
|
|
@Value("${logging.file.path:./stock/out/@pom.build.finalName@/logs}")
|
private String systemLoggingPath;
|
|
private static final DateTimeFormatter SYSTEM_LOG_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
private static final DateTimeFormatter SYSTEM_LOG_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
private static final DateTimeFormatter SYSTEM_LOG_EXPORT_TIME = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
private static final Pattern SYSTEM_LOG_TIMESTAMP = Pattern.compile("^(\\d{2}:\\d{2}:\\d{2}\\.\\d{3})");
|
private static final Pattern SYSTEM_LOG_ROLLED_FILE = Pattern.compile("^(info|error)_(\\d{4}-\\d{2}-\\d{2})\\.(\\d+)\\.log$");
|
|
private static class ProgressInfo {
|
long totalRaw;
|
long processedRaw;
|
int totalCount;
|
int processedCount;
|
boolean finished;
|
}
|
|
private static class FileNameInfo {
|
String type;
|
String deviceNo;
|
String stationId;
|
String day;
|
int index;
|
}
|
|
private static class FileTimeRange {
|
Long startTime;
|
Long endTime;
|
}
|
|
private static class DeviceAggregate {
|
String type;
|
String typeLabel;
|
String deviceNo;
|
String stationId;
|
List<String> stationIds;
|
int fileCount;
|
Long firstTime;
|
Long lastTime;
|
Integer firstIndex;
|
Integer lastIndex;
|
Path firstFile;
|
Path lastFile;
|
}
|
|
private static class SystemLogFileInfo {
|
String logType;
|
LocalDate day;
|
Integer index;
|
boolean active;
|
Path path;
|
}
|
|
private static class SystemLogDownloadRequest {
|
String logType;
|
LocalDateTime startTime;
|
LocalDateTime endTime;
|
List<Path> files;
|
}
|
|
private static final Map<String, ProgressInfo> DOWNLOAD_PROGRESS = new ConcurrentHashMap<>();
|
private static final int SYSTEM_LOG_MAX_RANGE_DAYS = 10;
|
private static final int SYSTEM_LOG_MATCH_BUFFER_LINES = 200000;
|
private static final String SYSTEM_LOG_ENTRY_NAME = "system.log";
|
|
private static final Map<String, SystemLogDownloadRequest> SYSTEM_LOG_DOWNLOAD_REQUESTS = new ConcurrentHashMap<>();
|
|
@RequestMapping(value = "/deviceLog/dates/auth")
|
@ManagerAuth
|
public R dates() {
|
try {
|
Path baseDir = Paths.get(loggingPath);
|
if (!Files.exists(baseDir)) {
|
return R.ok(new ArrayList<>());
|
}
|
List<String> days = Files.list(baseDir)
|
.filter(Files::isDirectory)
|
.map(p -> p.getFileName().toString())
|
.filter(name -> name.length() == 8 && name.chars().allMatch(Character::isDigit))
|
.sorted()
|
.collect(Collectors.toList());
|
Map<String, Map<String, List<String>>> grouped = new LinkedHashMap<>();
|
for (String day : days) {
|
String year = day.substring(0, 4);
|
String month = day.substring(4, 6);
|
grouped.computeIfAbsent(year, k -> new LinkedHashMap<>())
|
.computeIfAbsent(month, k -> new ArrayList<>())
|
.add(day);
|
}
|
List<Map<String, Object>> tree = new ArrayList<>();
|
for (Map.Entry<String, Map<String, List<String>>> yEntry : grouped.entrySet()) {
|
Map<String, Object> yNode = new HashMap<>();
|
yNode.put("title", yEntry.getKey());
|
yNode.put("id", yEntry.getKey());
|
List<Map<String, Object>> mChildren = new ArrayList<>();
|
for (Map.Entry<String, List<String>> mEntry : yEntry.getValue().entrySet()) {
|
Map<String, Object> mNode = new HashMap<>();
|
mNode.put("title", mEntry.getKey());
|
mNode.put("id", yEntry.getKey() + "-" + mEntry.getKey());
|
List<Map<String, Object>> dChildren = new ArrayList<>();
|
for (String d : mEntry.getValue()) {
|
Map<String, Object> dNode = new HashMap<>();
|
dNode.put("title", d.substring(6, 8));
|
dNode.put("id", d);
|
dNode.put("day", d);
|
dChildren.add(dNode);
|
}
|
mNode.put("children", dChildren);
|
mChildren.add(mNode);
|
}
|
yNode.put("children", mChildren);
|
tree.add(yNode);
|
}
|
return R.ok(tree);
|
} catch (Exception e) {
|
return R.error("读取日期失败");
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/day/{day}/devices/auth")
|
@ManagerAuth
|
public R devices(@PathVariable("day") String day) {
|
try {
|
if (day == null || day.length() != 8 || !day.chars().allMatch(Character::isDigit)) {
|
return R.error("日期格式错误");
|
}
|
Path dayDir = Paths.get(loggingPath, day);
|
if (!Files.exists(dayDir) || !Files.isDirectory(dayDir)) {
|
return R.ok(new ArrayList<>());
|
}
|
List<Path> files = listDayLogFiles(dayDir);
|
Map<String, Map<String, Object>> deviceMap = new HashMap<>();
|
for (Path p : files) {
|
FileNameInfo info = parseFileName(p.getFileName().toString());
|
if (info == null || !day.equals(info.day)) {
|
continue;
|
}
|
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", info.deviceNo);
|
map.put("stationId", info.stationId);
|
map.put("types", new HashSet<String>());
|
map.put("fileCount", 0);
|
return map;
|
});
|
((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;
|
}).collect(Collectors.toList());
|
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<>();
|
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, aggregate.stationId);
|
aggregate.firstTime = firstRange.startTime != null ? firstRange.startTime : firstRange.endTime;
|
}
|
if (aggregate.lastFile != null) {
|
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);
|
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,
|
@RequestParam(value = "stationId", required = false) String stationId) {
|
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("设备编号错误");
|
}
|
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, stationId);
|
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), stationId);
|
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), stationId);
|
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("stationId", stationId);
|
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("读取设备日志时间轴失败");
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/day/{day}/preview/auth")
|
@ManagerAuth
|
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 {
|
String dayClean = day == null ? null : day.replaceAll("\\D", "");
|
if (dayClean == null || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
|
return R.error("日期格式错误");
|
}
|
if (type == null || SlaveType.findInstance(type) == null) {
|
return R.error("设备类型错误");
|
}
|
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<>());
|
}
|
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;
|
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);
|
if (matchesRequestedStation(logItem, stationId)) {
|
resultLogs.add(logItem);
|
}
|
} catch (Exception e) {
|
}
|
}
|
});
|
} catch (Exception e) {
|
log.error("读取日志文件失败: " + f, e);
|
}
|
}
|
resultLogs.sort(Comparator.comparing(DeviceDataLog::getCreateTime, Comparator.nullsLast(Date::compareTo)));
|
|
return R.ok(resultLogs);
|
} catch (Exception e) {
|
log.error("预览日志失败", e);
|
return R.error("预览日志失败");
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/day/{day}/seek/auth")
|
@ManagerAuth
|
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", "");
|
if (dayClean == null || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
|
return R.error("日期格式错误");
|
}
|
if (type == null || SlaveType.findInstance(type) == null) {
|
return R.error("设备类型错误");
|
}
|
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, stationId);
|
|
if (files.isEmpty()) {
|
return R.error("未找到日志文件");
|
}
|
|
int low = 0;
|
int high = files.size() - 1;
|
int foundIndex = -1;
|
|
while (low <= high) {
|
int mid = (low + high) >>> 1;
|
Path midFile = files.get(mid);
|
Long midStart = getFileStartTime(midFile, stationId);
|
if (midStart == null) {
|
low = mid + 1;
|
continue;
|
}
|
|
if (midStart <= timestamp) {
|
foundIndex = mid;
|
low = mid + 1;
|
} else {
|
high = mid - 1;
|
}
|
}
|
|
if (foundIndex == -1) {
|
foundIndex = 0;
|
}
|
|
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, String stationId) {
|
try {
|
String firstLine = readFirstMatchingLine(file, stationId);
|
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, String stationId) {
|
try {
|
String lastLine = readLastMatchingLine(file, stationId);
|
if (lastLine == null) return null;
|
DeviceDataLog lastLog = JSON.parseObject(lastLine, DeviceDataLog.class);
|
return lastLog.getCreateTime().getTime();
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/day/{day}/download/auth")
|
@ManagerAuth
|
public void download(@PathVariable("day") String day,
|
@RequestParam("type") String type,
|
@RequestParam("deviceNo") String deviceNo,
|
@RequestParam(value = "offset", required = false) Integer offset,
|
@RequestParam(value = "limit", required = false) Integer limit,
|
@RequestParam(value = "progressId", required = false) String progressId,
|
HttpServletResponse response) {
|
try {
|
String dayClean = day == null ? null : day.replaceAll("\\D", "");
|
if (dayClean == null || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
|
response.setStatus(400);
|
return;
|
}
|
if (type == null || SlaveType.findInstance(type) == null) {
|
response.setStatus(400);
|
return;
|
}
|
if (deviceNo == null || !deviceNo.chars().allMatch(Character::isDigit)) {
|
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, stationId);
|
files = sliceDownloadFiles(files, offset, limit);
|
if (files.isEmpty()) {
|
response.setStatus(404);
|
return;
|
}
|
ProgressInfo info;
|
String id = progressId;
|
if (Cools.isEmpty(id)) {
|
id = UUID.randomUUID().toString();
|
}
|
List<Path> finalFiles = files;
|
info = DOWNLOAD_PROGRESS.computeIfAbsent(id, k -> {
|
ProgressInfo x = new ProgressInfo();
|
x.totalCount = finalFiles.size();
|
long sum = 0L;
|
for (Path f : finalFiles) {
|
try { sum += Files.size(f); } catch (Exception ignored) {}
|
}
|
x.totalRaw = sum;
|
x.processedRaw = 0L;
|
x.processedCount = 0;
|
x.finished = false;
|
return x;
|
});
|
response.reset();
|
response.setContentType("application/zip");
|
String filename = type + "_" + deviceNo + "_" + dayClean + ".zip";
|
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
|
long totalRawSize = 0L;
|
for (Path f : files) {
|
try { totalRawSize += Files.size(f); } catch (Exception ignored) {}
|
}
|
response.setHeader("X-Total-Size", String.valueOf(totalRawSize));
|
response.setHeader("X-File-Count", String.valueOf(files.size()));
|
response.setHeader("X-Progress-Id", id);
|
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
for (Path f : files) {
|
ZipEntry entry = new ZipEntry(f.getFileName().toString());
|
zos.putNextEntry(entry);
|
Files.copy(f, zos);
|
zos.closeEntry();
|
try {
|
info.processedRaw += Files.size(f);
|
} catch (Exception ignored) {}
|
info.processedCount += 1;
|
}
|
zos.finish();
|
info.finished = true;
|
}
|
} catch (Exception e) {
|
try { response.setStatus(500); } catch (Exception ignore) {}
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/download/init/auth")
|
@ManagerAuth
|
public R init(@org.springframework.web.bind.annotation.RequestBody com.alibaba.fastjson.JSONObject param) {
|
try {
|
String day = param.getString("day");
|
String type = param.getString("type");
|
String deviceNo = param.getString("deviceNo");
|
Integer offset = param.getInteger("offset");
|
Integer limit = param.getInteger("limit");
|
String dayClean = Cools.isEmpty(day) ? null : day.replaceAll("\\D", "");
|
if (Cools.isEmpty(dayClean) || dayClean.length() != 8 || !dayClean.chars().allMatch(Character::isDigit)) {
|
return R.error("日期格式错误");
|
}
|
if (Cools.isEmpty(type) || SlaveType.findInstance(type) == null) {
|
return R.error("设备类型错误");
|
}
|
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, stationId);
|
if ((offset != null && offset >= files.size())) {
|
return R.error("起始序号超出范围");
|
}
|
files = sliceDownloadFiles(files, offset, limit);
|
if (files.isEmpty()) {
|
return R.error("未找到日志文件");
|
}
|
String id = UUID.randomUUID().toString();
|
ProgressInfo info = new ProgressInfo();
|
info.totalCount = files.size();
|
long sum = 0L;
|
for (Path f : files) {
|
try { sum += Files.size(f); } catch (Exception ignored) {}
|
}
|
info.totalRaw = sum;
|
info.processedRaw = 0L;
|
info.processedCount = 0;
|
info.finished = false;
|
DOWNLOAD_PROGRESS.put(id, info);
|
Map<String, Object> res = new HashMap<>();
|
res.put("progressId", id);
|
res.put("totalSize", info.totalRaw);
|
res.put("fileCount", info.totalCount);
|
return R.ok(res);
|
} catch (Exception e) {
|
return R.error("初始化失败");
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/download/progress/auth")
|
@ManagerAuth
|
public R progress(String id) {
|
ProgressInfo info = DOWNLOAD_PROGRESS.get(id);
|
if (info == null) {
|
return R.error("无效进度");
|
}
|
long total = info.totalRaw;
|
long done = info.processedRaw;
|
int percent;
|
if (info.finished) {
|
percent = 100;
|
} else if (total > 0) {
|
percent = (int) Math.min(99, (done * 100L) / total);
|
} else if (info.totalCount > 0) {
|
percent = (int) Math.min(99, (info.processedCount * 100L) / info.totalCount);
|
} else {
|
percent = 0;
|
}
|
Map<String, Object> res = new HashMap<>();
|
res.put("percent", percent);
|
res.put("processedSize", done);
|
res.put("totalSize", total);
|
res.put("processedCount", info.processedCount);
|
res.put("totalCount", info.totalCount);
|
res.put("finished", info.finished);
|
return R.ok(res);
|
}
|
|
@RequestMapping(value = "/deviceLog/system/download/init/auth")
|
@ManagerAuth
|
public R initSystemDownload(@org.springframework.web.bind.annotation.RequestBody com.alibaba.fastjson.JSONObject param) {
|
try {
|
String logType = normalizeSystemLogType(param.getString("logType"));
|
if (logType == null) {
|
return R.error("日志类型错误");
|
}
|
LocalDateTime startTime = parseSystemLogDateTime(param.getString("startTime"));
|
LocalDateTime endTime = parseSystemLogDateTime(param.getString("endTime"));
|
if (startTime == null || endTime == null) {
|
return R.error("时间格式错误");
|
}
|
if (startTime.isAfter(endTime)) {
|
return R.error("开始时间不能晚于结束时间");
|
}
|
long daySpan = java.time.temporal.ChronoUnit.DAYS.between(startTime.toLocalDate(), endTime.toLocalDate());
|
if (daySpan > SYSTEM_LOG_MAX_RANGE_DAYS) {
|
return R.error("时间范围不能超过" + SYSTEM_LOG_MAX_RANGE_DAYS + "天");
|
}
|
List<Path> files = findSystemLogFiles(logType, startTime, endTime);
|
if (files.isEmpty()) {
|
return R.error("未找到日志文件");
|
}
|
String id = UUID.randomUUID().toString();
|
ProgressInfo info = new ProgressInfo();
|
info.totalCount = files.size();
|
long sum = 0L;
|
for (Path f : files) {
|
try { sum += Files.size(f); } catch (Exception ignored) {}
|
}
|
info.totalRaw = sum;
|
info.processedRaw = 0L;
|
info.processedCount = 0;
|
info.finished = false;
|
DOWNLOAD_PROGRESS.put(id, info);
|
SystemLogDownloadRequest request = new SystemLogDownloadRequest();
|
request.logType = logType;
|
request.startTime = startTime;
|
request.endTime = endTime;
|
request.files = files;
|
SYSTEM_LOG_DOWNLOAD_REQUESTS.put(id, request);
|
Map<String, Object> res = new HashMap<>();
|
res.put("progressId", id);
|
res.put("totalSize", info.totalRaw);
|
res.put("fileCount", info.totalCount);
|
return R.ok(res);
|
} catch (Exception e) {
|
log.error("初始化系统日志下载失败", e);
|
return R.error("初始化失败");
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/system/download/auth")
|
@ManagerAuth
|
public void downloadSystemLog(@RequestParam("logType") String logTypeParam,
|
@RequestParam("startTime") String startTimeParam,
|
@RequestParam("endTime") String endTimeParam,
|
@RequestParam(value = "progressId", required = false) String progressId,
|
HttpServletResponse response) {
|
String progressKey = null;
|
try {
|
String logType = normalizeSystemLogType(logTypeParam);
|
LocalDateTime startTime = parseSystemLogDateTime(startTimeParam);
|
LocalDateTime endTime = parseSystemLogDateTime(endTimeParam);
|
if (logType == null || startTime == null || endTime == null || startTime.isAfter(endTime)) {
|
response.setStatus(400);
|
return;
|
}
|
SystemLogDownloadRequest requestInfo = null;
|
if (!Cools.isEmpty(progressId)) {
|
requestInfo = SYSTEM_LOG_DOWNLOAD_REQUESTS.get(progressId);
|
progressKey = progressId;
|
}
|
List<Path> files;
|
if (requestInfo != null
|
&& Objects.equals(requestInfo.logType, logType)
|
&& Objects.equals(requestInfo.startTime, startTime)
|
&& Objects.equals(requestInfo.endTime, endTime)) {
|
files = requestInfo.files == null ? Collections.emptyList() : requestInfo.files;
|
} else {
|
files = findSystemLogFiles(logType, startTime, endTime);
|
}
|
if (files.isEmpty()) {
|
response.setStatus(404);
|
return;
|
}
|
if (Cools.isEmpty(progressKey)) {
|
progressKey = UUID.randomUUID().toString();
|
}
|
ProgressInfo info = prepareProgress(progressKey, files);
|
response.reset();
|
response.setContentType("application/zip");
|
String filename = logType + "_" + formatSystemExportTime(startTime) + "_" + formatSystemExportTime(endTime) + ".zip";
|
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
|
response.setHeader("X-Progress-Id", progressKey);
|
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
zos.putNextEntry(new ZipEntry(SYSTEM_LOG_ENTRY_NAME));
|
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zos, StandardCharsets.UTF_8))) {
|
boolean written = writeSystemLogContent(files, logType, startTime, endTime, writer, info);
|
writer.flush();
|
if (!written) {
|
response.reset();
|
response.setStatus(404);
|
return;
|
}
|
}
|
zos.closeEntry();
|
zos.finish();
|
info.finished = true;
|
}
|
} catch (Exception e) {
|
log.error("下载系统日志失败", e);
|
try {
|
response.reset();
|
response.setStatus(500);
|
} catch (Exception ignore) {
|
}
|
} finally {
|
if (!Cools.isEmpty(progressId)) {
|
SYSTEM_LOG_DOWNLOAD_REQUESTS.remove(progressId);
|
}
|
}
|
}
|
|
@RequestMapping(value = "/deviceLog/enums/auth")
|
@ManagerAuth
|
public R getEnums() {
|
Map<String, Map<String, String>> enums = new HashMap<>();
|
|
enums.put("CrnModeType", Arrays.stream(com.zy.core.enums.CrnModeType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
enums.put("CrnStatusType", Arrays.stream(com.zy.core.enums.CrnStatusType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
enums.put("CrnForkPosType", Arrays.stream(com.zy.core.enums.CrnForkPosType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
enums.put("CrnLiftPosType", Arrays.stream(com.zy.core.enums.CrnLiftPosType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
enums.put("DualCrnForkPosType", Arrays.stream(com.zy.core.enums.DualCrnForkPosType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
enums.put("DualCrnLiftPosType", Arrays.stream(com.zy.core.enums.DualCrnLiftPosType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
enums.put("RgvModeType", Arrays.stream(com.zy.core.enums.RgvModeType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
enums.put("RgvStatusType", Arrays.stream(com.zy.core.enums.RgvStatusType.values())
|
.collect(Collectors.toMap(e -> String.valueOf(e.id), e -> e.desc)));
|
|
return R.ok(enums);
|
}
|
|
private ProgressInfo prepareProgress(String progressId, List<Path> files) {
|
return DOWNLOAD_PROGRESS.compute(progressId, (key, existing) -> {
|
ProgressInfo next = existing == null ? new ProgressInfo() : existing;
|
next.totalCount = files == null ? 0 : files.size();
|
long total = 0L;
|
if (files != null) {
|
for (Path f : files) {
|
try { total += Files.size(f); } catch (Exception ignored) {}
|
}
|
}
|
next.totalRaw = total;
|
next.processedRaw = 0L;
|
next.processedCount = 0;
|
next.finished = false;
|
return next;
|
});
|
}
|
|
private boolean writeSystemLogContent(List<Path> files,
|
String logType,
|
LocalDateTime startTime,
|
LocalDateTime endTime,
|
BufferedWriter writer,
|
ProgressInfo progressInfo) throws Exception {
|
boolean wroteAny = false;
|
for (Path file : files) {
|
SystemLogFileInfo fileInfo = parseSystemLogFile(file);
|
if (fileInfo == null) {
|
updateProgress(progressInfo, file);
|
continue;
|
}
|
LocalDate baseDate = fileInfo.day != null ? fileInfo.day : startTime.toLocalDate();
|
try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) {
|
List<String> pending = new ArrayList<>();
|
boolean pendingMatched = false;
|
for (Iterator<String> iterator = lines.iterator(); iterator.hasNext(); ) {
|
String line = iterator.next();
|
Matcher matcher = SYSTEM_LOG_TIMESTAMP.matcher(line == null ? "" : line);
|
if (matcher.find()) {
|
if (pendingMatched && !pending.isEmpty()) {
|
for (String pendingLine : pending) {
|
writer.write(pendingLine == null ? "" : pendingLine);
|
writer.newLine();
|
}
|
wroteAny = true;
|
}
|
pending.clear();
|
pendingMatched = isSystemLogLineInRange(baseDate, matcher.group(1), startTime, endTime);
|
}
|
if (pendingMatched) {
|
pending.add(line);
|
if (pending.size() > SYSTEM_LOG_MATCH_BUFFER_LINES) {
|
for (String pendingLine : pending) {
|
writer.write(pendingLine == null ? "" : pendingLine);
|
writer.newLine();
|
}
|
wroteAny = true;
|
pending.clear();
|
}
|
} else if (!pending.isEmpty()) {
|
pending.clear();
|
}
|
}
|
if (pendingMatched && !pending.isEmpty()) {
|
for (String pendingLine : pending) {
|
writer.write(pendingLine == null ? "" : pendingLine);
|
writer.newLine();
|
}
|
wroteAny = true;
|
}
|
}
|
updateProgress(progressInfo, file);
|
}
|
if (progressInfo != null) {
|
progressInfo.finished = true;
|
}
|
return wroteAny;
|
}
|
|
private void updateProgress(ProgressInfo progressInfo, Path file) {
|
if (progressInfo == null) {
|
return;
|
}
|
try {
|
progressInfo.processedRaw += Files.size(file);
|
} catch (Exception ignored) {
|
}
|
progressInfo.processedCount += 1;
|
}
|
|
private List<Path> findSystemLogFiles(String logType, LocalDateTime startTime, LocalDateTime endTime) throws Exception {
|
Path baseDir = Paths.get(systemLoggingPath);
|
if (!Files.exists(baseDir) || !Files.isDirectory(baseDir)) {
|
return Collections.emptyList();
|
}
|
LocalDate startDate = startTime.toLocalDate();
|
LocalDate endDate = endTime.toLocalDate();
|
List<SystemLogFileInfo> matched = new ArrayList<>();
|
try (Stream<Path> stream = Files.list(baseDir)) {
|
stream.filter(path -> !Files.isDirectory(path)).forEach(path -> {
|
SystemLogFileInfo info = parseSystemLogFile(path);
|
if (info == null || !Objects.equals(info.logType, logType)) {
|
return;
|
}
|
if (info.active || info.day == null || (!info.day.isBefore(startDate) && !info.day.isAfter(endDate))) {
|
matched.add(info);
|
}
|
});
|
}
|
matched.sort((left, right) -> {
|
LocalDate leftDay = left.day == null ? LocalDate.MAX : left.day;
|
LocalDate rightDay = right.day == null ? LocalDate.MAX : right.day;
|
int cmp = leftDay.compareTo(rightDay);
|
if (cmp != 0) {
|
return cmp;
|
}
|
int leftIndex = left.index == null ? Integer.MAX_VALUE : left.index;
|
int rightIndex = right.index == null ? Integer.MAX_VALUE : right.index;
|
if (left.active != right.active) {
|
return left.active ? 1 : -1;
|
}
|
return Integer.compare(leftIndex, rightIndex);
|
});
|
return matched.stream().map(item -> item.path).collect(Collectors.toList());
|
}
|
|
private SystemLogFileInfo parseSystemLogFile(Path path) {
|
if (path == null) {
|
return null;
|
}
|
String name = path.getFileName().toString();
|
if ("info.log".equals(name) || "error.log".equals(name)) {
|
SystemLogFileInfo info = new SystemLogFileInfo();
|
info.logType = name.startsWith("info") ? "info" : "error";
|
info.active = true;
|
info.path = path;
|
return info;
|
}
|
Matcher matcher = SYSTEM_LOG_ROLLED_FILE.matcher(name);
|
if (!matcher.matches()) {
|
return null;
|
}
|
try {
|
SystemLogFileInfo info = new SystemLogFileInfo();
|
info.logType = matcher.group(1);
|
info.day = LocalDate.parse(matcher.group(2), SYSTEM_LOG_DATE);
|
info.index = Integer.parseInt(matcher.group(3));
|
info.active = false;
|
info.path = path;
|
return info;
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
|
private String normalizeSystemLogType(String logType) {
|
if (Cools.isEmpty(logType)) {
|
return null;
|
}
|
String value = logType.trim().toLowerCase(Locale.ROOT);
|
if ("info".equals(value) || "error".equals(value)) {
|
return value;
|
}
|
return null;
|
}
|
|
private LocalDateTime parseSystemLogDateTime(String value) {
|
if (Cools.isEmpty(value)) {
|
return null;
|
}
|
try {
|
return LocalDateTime.parse(value.trim(), SYSTEM_LOG_DATE_TIME);
|
} catch (DateTimeParseException e) {
|
return null;
|
}
|
}
|
|
private boolean isSystemLogLineInRange(LocalDate baseDate,
|
String timePart,
|
LocalDateTime startTime,
|
LocalDateTime endTime) {
|
if (baseDate == null || Cools.isEmpty(timePart) || startTime == null || endTime == null) {
|
return false;
|
}
|
try {
|
LocalTime time = LocalTime.parse(timePart);
|
LocalDateTime timestamp = LocalDateTime.of(baseDate, time);
|
return !timestamp.isBefore(startTime) && !timestamp.isAfter(endTime);
|
} catch (DateTimeParseException e) {
|
return false;
|
}
|
}
|
|
private String formatSystemExportTime(LocalDateTime dateTime) {
|
if (dateTime == null) {
|
return "unknown";
|
}
|
return SYSTEM_LOG_EXPORT_TIME.format(dateTime);
|
}
|
|
|
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("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);
|
}
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("stats", stats);
|
result.put("groups", groups);
|
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)) {
|
return null;
|
}
|
return 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(deviceDir)) {
|
files = stream
|
.filter(p -> !Files.isDirectory(p) && matchesFileInfo(parseFileName(p.getFileName().toString()), target))
|
.collect(Collectors.toList());
|
}
|
files.sort(Comparator.comparingInt(p -> {
|
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) {
|
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 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]);
|
} 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) {
|
try {
|
return Integer.parseInt(String.valueOf(deviceNo));
|
} catch (Exception e) {
|
return Integer.MAX_VALUE;
|
}
|
}
|
|
private FileTimeRange readFileTimeRange(Path file, String stationId) {
|
FileTimeRange range = new FileTimeRange();
|
try {
|
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, 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 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())
|
.filter(line -> matchesRequestedStation(parseLogLine(line), stationId))
|
.findFirst()
|
.orElse(null);
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
|
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;
|
}
|
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;
|
}
|
return JSON.parseObject(line, DeviceDataLog.class);
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
}
|