| | |
| | | package com.lxzn.webservice.controller; |
| | | |
| | | import com.lxzn.framework.domain.webservice.request.ThirdDeProgramSource; |
| | | 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.apache.commons.io.FilenameUtils; |
| | | import org.apache.commons.io.IOUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | 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; |
| | | |
| | |
| | | @Autowired |
| | | private DncWebService dncWebService; |
| | | |
| | | @Value("${ncPdm.file_path}") |
| | | @Value("${fileHomePath}") |
| | | private String filePath; |
| | | |
| | | // 定义关键文件类型 |
| | | private static final String INNER_ZIP_PREFIX = "【公开】NC程序文件 "; |
| | | private static final String JSON_EXT = ".json"; |
| | | private static final String TXT_EXT = ".txt"; |
| | | |
| | | /** |
| | | * 集成plm数据,压缩包(里面是NC压缩包与JSON文件) |
| | | * @param file |
| | | * @return |
| | | */ |
| | | @PostMapping(value = "/syncPlmNcLogProgram", consumes = "multipart/form-data") |
| | | @Transactional |
| | | public String processNcPackage(MultipartFile file) { |
| | | Path workDir = null; |
| | | try { |
| | | if (file == null || file.isEmpty()) { |
| | | log.error("接收到的压缩文件为空"); |
| | |
| | | |
| | | // 创建临时目录 |
| | | String timestamp = String.valueOf(System.currentTimeMillis()); |
| | | Path workDir = Paths.get(filePath, timestamp); |
| | | workDir = Paths.get(filePath, "plm_" + timestamp); |
| | | Files.createDirectories(workDir); |
| | | |
| | | // 保存原始文件 |
| | |
| | | for (Map.Entry<String, byte[]> entry : ncFiles.entrySet()) { |
| | | String fileName = entry.getKey(); |
| | | if (allNcFiles.containsKey(fileName)) { |
| | | // 修改点5:记录重复警告(实际业务中可能需要更严格处理) |
| | | log.warn("检测到重复NC文件名: {}", fileName); |
| | | } |
| | | allNcFiles.put(fileName, entry.getValue()); |
| | |
| | | |
| | | // 业务处理 |
| | | return JSONObject.toJSONString( |
| | | dncWebService.setTree(plmPrograms) |
| | | dncWebService.setPlmTree(plmPrograms) |
| | | ); |
| | | } catch (Exception e) { |
| | | log.error("处理NC文件包失败", e); |
| | | return MesResultModel.error("文件处理失败: " + e.getMessage()); |
| | | } finally { |
| | | // 清理临时文件 |
| | | if (workDir != null) { |
| | | deleteDirectory(workDir.toFile()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 集成3DE数据,压缩包(单个压缩包包含多个NC文件、一个工艺数据.TXT) |
| | | * @param file 上传的压缩包文件 |
| | | * @return 处理结果 |
| | | */ |
| | | @PostMapping(value = "/integration3DEData", consumes = "multipart/form-data") |
| | | @Transactional |
| | | public String integration3DEData(MultipartFile file) { |
| | | Path workDir = null; |
| | | try { |
| | | if (file == null || file.isEmpty()) { |
| | | log.error("接收到的3DE压缩文件为空"); |
| | | return MesResultModel.error("接收到的3DE压缩文件为空"); |
| | | } |
| | | // 创建临时工作目录 |
| | | String timestamp = String.valueOf(System.currentTimeMillis()); |
| | | workDir = Paths.get(filePath, "3de_" + timestamp); |
| | | Files.createDirectories(workDir); |
| | | // 保存原始文件 |
| | | Path originalPath = workDir.resolve( |
| | | Objects.requireNonNull(file.getOriginalFilename()) |
| | | ); |
| | | Files.copy(file.getInputStream(), originalPath, StandardCopyOption.REPLACE_EXISTING); |
| | | log.info("3DE原始文件已保存: {} (大小: {} 字节)", |
| | | originalPath, Files.size(originalPath)); |
| | | // 解压ZIP包获取所有文件 |
| | | Map<String, byte[]> packageContents = extractOuterZipContents(originalPath); |
| | | log.info("3DE压缩包解析成功,包含 {} 个文件", packageContents.size()); |
| | | |
| | | // 查找工艺数据TXT文件 |
| | | byte[] processData = findFileByExtension(packageContents, TXT_EXT); |
| | | if (processData == null) { |
| | | return MesResultModel.error("未找到工艺数据TXT文件"); |
| | | } |
| | | |
| | | // 解析工艺数据TXT文件 |
| | | ThirdDeProgramSource thirdDeProgramSource = parseProcessTxtFile(processData); |
| | | if (thirdDeProgramSource == null) { |
| | | return MesResultModel.error("解析工艺数据TXT文件失败"); |
| | | } |
| | | |
| | | // 提取所有NC文件 |
| | | Map<String, byte[]> ncFiles = extractNcFiles(packageContents); |
| | | if (ncFiles.isEmpty()) { |
| | | return MesResultModel.error("未找到NC程序文件"); |
| | | } |
| | | log.info("找到 {} 个NC程序文件", ncFiles.size()); |
| | | |
| | | // 设置NC文件集合和文件路径信息 |
| | | thirdDeProgramSource.setNcFiles(ncFiles); |
| | | thirdDeProgramSource.setFilePath(workDir.toString()); |
| | | thirdDeProgramSource.setFileName(originalPath.getFileName().toString()); |
| | | |
| | | // 调用服务处理3DE数据 |
| | | return JSONObject.toJSONString( |
| | | dncWebService.processThirdDEProgram(thirdDeProgramSource) |
| | | ); |
| | | } catch (Exception e) { |
| | | log.error("处理3DE数据包失败", e); |
| | | return MesResultModel.error("3DE文件处理失败: " + e.getMessage()); |
| | | } finally { |
| | | // 清理临时文件 |
| | | if (workDir != null) { |
| | | deleteDirectory(workDir.toFile()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析工艺数据TXT文件 |
| | | * @param txtData TXT文件内容 |
| | | * @return ThirdDeProgramSource对象 |
| | | */ |
| | | private ThirdDeProgramSource parseProcessTxtFile(byte[] txtData) { |
| | | try { |
| | | String content = new String(txtData, StandardCharsets.UTF_8); |
| | | log.info("工艺数据TXT内容:\n{}", content); |
| | | |
| | | // 使用FastJSON解析JSON格式的TXT |
| | | JSONObject json = JSONObject.parseObject(content); |
| | | if (json == null) { |
| | | log.error("解析工艺数据失败: 不是有效的JSON格式"); |
| | | return null; |
| | | } |
| | | |
| | | // 创建对象并设置属性 |
| | | ThirdDeProgramSource programSource = new ThirdDeProgramSource(); |
| | | programSource.setPartName(json.getString("part_name")); |
| | | programSource.setPartNo(json.getString("part_no")); |
| | | programSource.setRevisionNo(json.getString("revision_no")); |
| | | programSource.setSkgxId(json.getString("skgx_id")); |
| | | programSource.setSkgxName(json.getString("skgx_name")); |
| | | programSource.setNcJcid(json.getString("nc_jcid")); |
| | | programSource.setNcOs(json.getString("nc_os")); |
| | | programSource.setEquipmentId(json.getString("equipment_id")); |
| | | programSource.setNcFileName(json.getString("nc_file_name")); |
| | | programSource.setNcPlantNo(json.getString("nc_plant_no")); |
| | | programSource.setNcPlantName(json.getString("nc_plant_name")); |
| | | log.debug("解析的工艺数据: partName={}, partNo={}, revisionNo={}, skgxId={}, skgxName={}, " + |
| | | "ncJcid={}, ncOs={}, equipmentId={}, ncFileName={}, ncPlantNo={}, ncPlantName={}", |
| | | programSource.getPartName(), programSource.getPartNo(), |
| | | programSource.getRevisionNo(), programSource.getSkgxId(), |
| | | programSource.getSkgxName(), programSource.getNcJcid(), |
| | | programSource.getNcOs(), programSource.getEquipmentId(), |
| | | programSource.getNcFileName(), programSource.getNcPlantNo(), |
| | | programSource.getNcPlantName()); |
| | | |
| | | return programSource; |
| | | } catch (Exception e) { |
| | | log.error("解析工艺数据TXT文件失败", e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析键值对格式的TXT文件 |
| | | * @param content TXT文件内容 |
| | | * @return 键值对映射 |
| | | */ |
| | | private Map<String, String> parseKeyValueTxt(String content) { |
| | | Map<String, String> result = new HashMap<>(); |
| | | String[] lines = content.split("\\r?\\n"); |
| | | |
| | | for (String line : lines) { |
| | | line = line.trim(); |
| | | if (line.isEmpty() || line.startsWith("#") || line.startsWith("//")) { |
| | | continue; // 跳过空行和注释 |
| | | } |
| | | |
| | | int separatorIndex = line.indexOf('='); |
| | | if (separatorIndex > 0) { |
| | | String key = line.substring(0, separatorIndex).trim(); |
| | | String value = line.substring(separatorIndex + 1).trim(); |
| | | result.put(key, value); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 从文件内容中提取NC文件 |
| | | * @param packageContents 包内容映射 |
| | | * @return NC文件映射 |
| | | */ |
| | | private Map<String, byte[]> extractNcFiles(Map<String, byte[]> packageContents) { |
| | | Map<String, byte[]> ncFiles = new HashMap<>(); |
| | | |
| | | for (Map.Entry<String, byte[]> entry : packageContents.entrySet()) { |
| | | String fileName = entry.getKey(); |
| | | String fileExt = FilenameUtils.getExtension(fileName).toLowerCase(); |
| | | if (fileExt.equals("nc")) { |
| | | ncFiles.put(fileName, entry.getValue()); |
| | | log.debug("找到NC文件: {}", fileName); |
| | | } |
| | | } |
| | | |
| | | return ncFiles; |
| | | } |
| | | |
| | | // 优化后的解压方法(使用commons-compress) |
| | | private Map<String, byte[]> extractOuterZipContents(Path filePath) throws IOException { |
| | | Map<String, byte[]> contents = new HashMap<>(); |
| | | log.info("开始解析ZIP文件: {}", filePath); |
| | | |
| | | try { |
| | | try (ZipFile zipFile = new ZipFile(filePath.toFile())) { |
| | | log.info("ZIP文件格式检测: {}", zipFile.getEncoding()); |
| | |
| | | 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()); |
| | | byte[] data = IOUtils.toByteArray(is); |
| | | contents.put(entry.getName(), data); |
| | | log.debug("提取文件: {} ({} 字节)", entry.getName(), data.length); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | private List<byte[]> findAllFilesByExtension(Map<String, byte[]> 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()); |
| | | List<byte[]> result = new ArrayList<>(); |
| | | |
| | | for (Map.Entry<String, byte[]> entry : contents.entrySet()) { |
| | | String fileName = entry.getKey().toLowerCase(); |
| | | if (fileName.endsWith(extLower)) { |
| | | result.add(entry.getValue()); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | // 内部ZIP解析方法 |
| | |
| | | * 根据扩展名查找文件 |
| | | */ |
| | | private byte[] findFileByExtension(Map<String, byte[]> contents, String extension) { |
| | | return contents.entrySet().stream() |
| | | .filter(e -> e.getKey().toLowerCase().endsWith(extension)) |
| | | .map(Map.Entry::getValue) |
| | | .findFirst() |
| | | .orElse(null); |
| | | String extLower = extension.toLowerCase(); |
| | | for (Map.Entry<String, byte[]> entry : contents.entrySet()) { |
| | | if (entry.getKey().toLowerCase().endsWith(extLower)) { |
| | | return entry.getValue(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 获取文件名(根据扩展名) |
| | | */ |
| | | private String getFileNameByExtension(Map<String, byte[]> 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<String, byte[]> contents, String prefix) { |
| | | if (prefix == null || prefix.isEmpty()) { |
| | | log.warn("文件前缀不能为空"); |
| | | return null; |
| | | for (String key : contents.keySet()) { |
| | | if (key.toLowerCase().endsWith(JSON_EXT)) { |
| | | return key; |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | return "Unknown"; |
| | | } |
| | | |
| | | /** |
| | |
| | | return JSONObject.parseObject(json, PlmProgramSource.class); |
| | | } |
| | | |
| | | /** |
| | | * 递归删除目录 |
| | | */ |
| | | private void deleteDirectory(File directory) { |
| | | if (!directory.exists()) return; |
| | | |
| | | File[] files = directory.listFiles(); |
| | | if (files != null) { |
| | | for (File file : files) { |
| | | if (file.isDirectory()) { |
| | | deleteDirectory(file); |
| | | } else { |
| | | if (!file.delete()) { |
| | | log.warn("无法删除文件: {}", file.getAbsolutePath()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (!directory.delete()) { |
| | | log.warn("无法删除目录: {}", directory.getAbsolutePath()); |
| | | } |
| | | } |
| | | } |