package com.zy.asrs.controller; import com.core.annotations.ManagerAuth; import com.core.common.Cools; import com.core.common.R; import com.zy.common.web.BaseController; import com.zy.core.enums.SlaveType; 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.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; @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 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 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>> 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> tree = new ArrayList<>(); for (Map.Entry>> yEntry : grouped.entrySet()) { Map yNode = new HashMap<>(); yNode.put("title", yEntry.getKey()); yNode.put("id", yEntry.getKey()); List> mChildren = new ArrayList<>(); for (Map.Entry> mEntry : yEntry.getValue().entrySet()) { Map mNode = new HashMap<>(); mNode.put("title", mEntry.getKey()); mNode.put("id", yEntry.getKey() + "-" + mEntry.getKey()); List> dChildren = new ArrayList<>(); for (String d : mEntry.getValue()) { Map 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 files = Files.list(dayDir) .filter(p -> !Files.isDirectory(p) && p.getFileName().toString().endsWith(".log")) .collect(Collectors.toList()); Map> 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 info = deviceMap.computeIfAbsent(deviceNo, k -> { Map map = new HashMap<>(); map.put("deviceNo", deviceNo); map.put("types", new HashSet()); map.put("fileCount", 0); return map; }); ((Set) info.get("types")).add(type); info.put("fileCount", ((Integer) info.get("fileCount")) + 1); } List> res = deviceMap.values().stream().map(m -> { Map x = new HashMap<>(); x.put("deviceNo", m.get("deviceNo")); x.put("types", ((Set) 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}/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 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 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 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 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 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); } }