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 javax.servlet.http.HttpServletResponse;
|
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 {
|
|
@Value("${deviceLogStorage.loggingPath}")
|
private String loggingPath;
|
|
private static class ProgressInfo {
|
long totalRaw;
|
long processedRaw;
|
int totalCount;
|
int processedCount;
|
boolean finished;
|
}
|
|
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}/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 = null;
|
try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) {
|
firstLine = lines.findFirst().orElse(null);
|
}
|
if (firstLine == null) return null;
|
DeviceDataLog firstLog = JSON.parseObject(firstLine, DeviceDataLog.class);
|
return firstLog.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 = 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);
|
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 = 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()) {
|
return R.error("起始序号超出范围");
|
}
|
files = files.subList(from, to);
|
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);
|
}
|
}
|