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.DeviceDataLog;
|
import com.zy.common.web.BaseController;
|
import com.zy.core.enums.SlaveType;
|
import lombok.extern.slf4j.Slf4j;
|
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;
|
import java.nio.file.Paths;
|
import java.util.*;
|
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.stream.Collectors;
|
import java.util.stream.Stream;
|
|
@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", "输送设备");
|
}
|
|
@Value("${deviceLogStorage.loggingPath}")
|
private String loggingPath;
|
|
private static class ProgressInfo {
|
long totalRaw;
|
long processedRaw;
|
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<>();
|
|
@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 = Files.list(dayDir)
|
.filter(p -> !Files.isDirectory(p) && p.getFileName().toString().endsWith(".log"))
|
.collect(Collectors.toList());
|
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) {
|
continue;
|
}
|
String deviceNo = parts[1];
|
String type = parts[0];
|
Map<String, Object> info = deviceMap.computeIfAbsent(deviceNo, k -> {
|
Map<String, Object> map = new HashMap<>();
|
map.put("deviceNo", deviceNo);
|
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);
|
}
|
List<Map<String, Object>> res = deviceMap.values().stream().map(m -> {
|
Map<String, Object> x = new HashMap<>();
|
x.put("deviceNo", m.get("deviceNo"));
|
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<>();
|
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("读取设备日志时间轴失败");
|
}
|
}
|
|
@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 = "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("设备编号错误");
|
}
|
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;
|
}
|
}));
|
|
int from = offset == null || offset < 0 ? 0 : offset;
|
int max = limit == null || limit <= 0 ? 5 : limit; // 默认读取5个文件
|
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);
|
} 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("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("设备编号错误");
|
}
|
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;
|
}
|
}));
|
|
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);
|
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
|
} 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) {
|
try {
|
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;
|
}
|
}
|
|
@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;
|
}
|
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);
|
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 (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(response.getOutputStream())) {
|
for (Path f : files) {
|
java.util.zip.ZipEntry entry = new java.util.zip.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("设备编号错误");
|
}
|
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 ((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/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 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;
|
}
|
}
|
}
|