package com.lxzn.webservice.controller; import com.lxzn.framework.domain.webservice.request.PlmProgramSource; import com.lxzn.webservice.DncWebService; import com.lxzn.webservice.ext.MesResultModel; import lombok.extern.slf4j.Slf4j; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import com.alibaba.fastjson.JSONObject; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.*; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @RestController @Slf4j @RequestMapping("/plmOuter") public class DncWebController { @Autowired private DncWebService dncWebService; @Value("${ncPdm.file_path}") private String filePath; // 定义关键文件类型 private static final String INNER_ZIP_PREFIX = "【公开】NC程序文件 "; private static final String JSON_EXT = ".json"; @PostMapping(value = "/syncPlmNcLogProgram", consumes = "multipart/form-data") @Transactional public String processNcPackage(MultipartFile file) { try { if (file == null || file.isEmpty()) { log.error("接收到的压缩文件为空"); return MesResultModel.error("接收到的压缩文件为空"); } // 创建临时目录 String timestamp = String.valueOf(System.currentTimeMillis()); Path workDir = Paths.get(filePath, timestamp); Files.createDirectories(workDir); // 保存原始文件 Path originalPath = workDir.resolve( Objects.requireNonNull(file.getOriginalFilename()) ); Files.copy(file.getInputStream(), originalPath, StandardCopyOption.REPLACE_EXISTING); log.info("原始文件已保存: {} (大小: {} 字节)", originalPath, Files.size(originalPath)); // 解析外层压缩包 Map packageContents = extractOuterZipContents(originalPath); log.info("外层压缩包解析成功,包含 {} 个文件", packageContents.size()); // 不再查找单个ZIP,改为查找所有ZIP文件 byte[] jsonData = findFileByExtension(packageContents, JSON_EXT); // 使用扩展名".zip"查找所有ZIP压缩包 List zipFiles = findAllFilesByExtension(packageContents, ".zip"); // 检查是否存在至少一个ZIP文件 if (jsonData == null || zipFiles.isEmpty()) { String message = "未找到必要文件: " + (jsonData == null ? "JSON文件" : "") + (zipFiles.isEmpty() ? (jsonData == null ? " 和 " : "") + "NC程序压缩包" : ""); return MesResultModel.error(message); } // 解析JSON String jsonFileName = getFileNameByExtension(packageContents); PlmProgramSource plmPrograms = parseJsonFile(jsonData); log.info("解析JSON文件成功: {}", jsonFileName); // 创建Map存储所有NC文件(文件名->内容) Map allNcFiles = new HashMap<>(); //遍历并解压所有找到的ZIP文件 for (byte[] zipData : zipFiles) { Map ncFiles = extractInnerZipContents(zipData); // 合并文件并检查重复 for (Map.Entry entry : ncFiles.entrySet()) { String fileName = entry.getKey(); if (allNcFiles.containsKey(fileName)) { // 修改点5:记录重复警告(实际业务中可能需要更严格处理) log.warn("检测到重复NC文件名: {}", fileName); } allNcFiles.put(fileName, entry.getValue()); } } log.info("共解析 {} 个内部压缩包,合并得到 {} 个NC文件", zipFiles.size(), allNcFiles.size()); // 设置NC文件集合 plmPrograms.setNcfiles(allNcFiles); plmPrograms.setFilePath(originalPath.getParent().toString()); plmPrograms.setFileName(originalPath.toString()); // 业务处理 return JSONObject.toJSONString( dncWebService.setTree(plmPrograms) ); } catch (Exception e) { log.error("处理NC文件包失败", e); return MesResultModel.error("文件处理失败: " + e.getMessage()); } } // 优化后的解压方法(使用commons-compress) private Map extractOuterZipContents(Path filePath) throws IOException { Map contents = new HashMap<>(); log.info("开始解析ZIP文件: {}", filePath); try { try (ZipFile zipFile = new ZipFile(filePath.toFile())) { log.info("ZIP文件格式检测: {}", zipFile.getEncoding()); Enumeration entries = zipFile.getEntries(); while (entries.hasMoreElements()) { ZipArchiveEntry entry = entries.nextElement(); if (entry.isDirectory()) continue; try (InputStream is = zipFile.getInputStream(entry)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int len; while ((len = is.read(buffer)) > 0) { baos.write(buffer, 0, len); } // 保留原始文件名 String name = entry.getName(); contents.put(name, baos.toByteArray()); log.debug("提取文件: {} ({} 字节)", name, baos.size()); } } } log.info("Apache Commons解析成功"); } catch (Exception e) { log.warn("Apache Commons解析失败,尝试标准库回退: {}", e.getMessage()); try (InputStream is = Files.newInputStream(filePath); ZipInputStream zis = new ZipInputStream(is, StandardCharsets.ISO_8859_1)) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { if (entry.isDirectory()) continue; ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int len; while ((len = zis.read(buffer)) > 0) { baos.write(buffer, 0, len); } String name = entry.getName(); contents.put(name, baos.toByteArray()); log.debug("回退提取文件: {}", name); } } catch (Exception ex) { throw new IOException("无法解析ZIP文件: " + filePath, ex); } } return contents; } private List findAllFilesByExtension(Map contents, String extension) { String extLower = extension.toLowerCase(); return contents.entrySet().stream() .filter(e -> { String fileName = e.getKey(); // 使用小写比较避免大小写敏感问题 return fileName.toLowerCase().endsWith(extLower); }) .map(Map.Entry::getValue) .collect(Collectors.toList()); } // 内部ZIP解析方法 private Map extractInnerZipContents(byte[] zipData) throws IOException { Map files = new HashMap<>(); try (ByteArrayInputStream bais = new ByteArrayInputStream(zipData); ZipInputStream zis = new ZipInputStream(bais)) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { if (entry.isDirectory()) continue; ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int len; while ((len = zis.read(buffer)) != -1) { baos.write(buffer, 0, len); } files.put(entry.getName(), baos.toByteArray()); } } return files; } /** * 根据扩展名查找文件 */ private byte[] findFileByExtension(Map contents, String extension) { return contents.entrySet().stream() .filter(e -> e.getKey().toLowerCase().endsWith(extension)) .map(Map.Entry::getValue) .findFirst() .orElse(null); } /** * 获取文件名(根据扩展名) */ private String getFileNameByExtension(Map contents) { return contents.keySet().stream() .filter(k -> k.toLowerCase().endsWith(DncWebController.JSON_EXT)) .findFirst() .orElse("Unknown"); } /** * 查找与指定前缀匹配的文件内容 * @param contents 文件内容映射(文件名 -> 文件内容) * @param prefix 要匹配的文件名前缀 * @return 匹配的第一个文件内容,未找到返回 null */ private byte[] findFileByPrefix(Map contents, String prefix) { if (prefix == null || prefix.isEmpty()) { log.warn("文件前缀不能为空"); return null; } return contents.entrySet().stream() .filter(entry -> { String fileName = entry.getKey(); // 路径规范化处理 String normalizedKey = fileName.replace('\\', '/'); // 获取纯文件名 int lastSlash = normalizedKey.lastIndexOf('/'); String pureFileName = (lastSlash >= 0) ? normalizedKey.substring(lastSlash + 1) : normalizedKey; // 调试日志 log.debug("检查文件: [原始: {}], [纯文件名: {}], 前缀: {}", fileName, pureFileName, prefix); return pureFileName.contains("NC"); }) .map(Map.Entry::getValue) .findFirst() .orElse(null); } /** * 解析JSON文件内容 */ private PlmProgramSource parseJsonFile(byte[] jsonData) { String json = new String(jsonData, StandardCharsets.UTF_8); return JSONObject.parseObject(json, PlmProgramSource.class); } }