| | |
| | | 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 { |
| | | |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | | } |