package org.springblade.mdm.program.service; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BizServiceImpl; import org.springblade.core.oss.OssTemplate; import org.springblade.core.oss.model.BladeFile; import org.springblade.core.redis.cache.BladeRedis; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.FileUtil; import org.springblade.core.tool.utils.Func; import org.springblade.mdm.basesetting.machine.entity.Machine; import org.springblade.mdm.basesetting.machine.service.MachineService; import org.springblade.mdm.basesetting.producedivision.entity.QinzheFgb; import org.springblade.mdm.basesetting.producedivision.service.QinzheFgbService; import org.springblade.mdm.commons.contants.RegExpConstants; import org.springblade.mdm.commons.service.ParamService; import org.springblade.mdm.flow.entity.FlowProgramFile; import org.springblade.mdm.flow.service.CureFlowService; import org.springblade.mdm.flow.service.FlowCommonService; import org.springblade.mdm.program.entity.DncBackFile; import org.springblade.mdm.program.entity.NcNode; import org.springblade.mdm.program.entity.NcProgramExchange; import org.springblade.mdm.program.mapper.NcProgramExchangeMapper; import org.springblade.mdm.program.service.programannotation.*; import org.springblade.mdm.program.vo.DncSendBackData; import org.springblade.mdm.program.vo.DncSendBackFile; import org.springblade.mdm.program.vo.ProgramAnnotation; import org.springblade.mdm.utils.FileContentUtil; import org.springblade.mdm.utils.ProgramFileNameParser; import org.springblade.mdm.utils.ZipTextFileContentUtil; import org.springblade.system.pojo.entity.DictBiz; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.time.Duration; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * DNC回传文件处理服务 * * @author yangys */ @Slf4j @Service @AllArgsConstructor public class DNCSendBackService extends BizServiceImpl { private final CureFlowService cureFlowService; private final QinzheFgbService qinzheFbgService; private final NcNodeService ncNodeService; private final OssTemplate ossTemplate; private final BladeRedis bladeRedis; private final FlowCommonService flowCommonService; private final DncBackFileService dncBackFileService; private final MachineService machineService; private final ParamService paramService; /** * 偏离单文件末尾的模式:P+数字 */ private static final String P_NUMBER_PATTERN = "(?i)P\\d+"; private String getFileKey(){ return "dncimpfile-"+ AuthUtil.getUserId(); } /** * dnc回传文件上传 * @param file DNC回传文件 * @return 压缩包内程序包名的列表 */ public List dncSendBackUpload(MultipartFile file) { List list; if(file == null || file.isEmpty()){ throw new ServiceException("文件为空"); } if(!StringUtils.endsWith(file.getOriginalFilename(),".zip")){ throw new ServiceException("文件必须为zip包"); } try { BladeFile bfile = ossTemplate.putFile(file);//上传,供后续入库使用 //设置一个缓存,2小时过期 bladeRedis.setEx(getFileKey(),bfile.getName(), Duration.ofHours(2)); try(InputStream zipFileInputStream = ossTemplate.statFileStream(bfile.getName());) { list = parseProgramListFromZip(zipFileInputStream); } } catch (IOException e) { log.error("上传dnc回传文件失败",e); throw new ServiceException("解析DNC回传数据失败"); } return list; } /** * 从压缩包 解析回传程序列表,这里解析目录即可,目录就是程序包名 * @param inputStream 压缩包输入流 * @return 回传程序列表 * @throws IOException 文件操作异常 */ List parseProgramListFromZip(InputStream inputStream) throws IOException { List list = new ArrayList<>(); Path tempZipFile = createTempFile(inputStream); List fileEntryNameList = new ArrayList<>(); List dirEntryNameList = new ArrayList<>();//程序包名+工序版次 作为文件夹名 try (ZipFile zipFile = new ZipFile(tempZipFile.toFile())) { Enumeration zipEntries = zipFile.entries(); ZipEntry entry; //获取所有的entry名称 while (zipEntries.hasMoreElements()) { entry = zipEntries.nextElement(); if(entry.isDirectory()){ dirEntryNameList.add(entry.getName()); }else{ fileEntryNameList.add(entry.getName()); } } NcNode programPackageNode; //目录列表,即程序包列表 for(String entryName : dirEntryNameList){ DncSendBackData progData = new DncSendBackData(); String packageName = StringUtils.removeEnd(entryName,"/"); Optional optFilename = fileEntryNameList.stream().filter(n -> n.startsWith(entryName)).findFirst(); if(optFilename.isPresent()){ entry = zipFile.getEntry(optFilename.get()); InputStream ins = zipFile.getInputStream(entry); ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(IOUtils.toByteArray(ins)); //解析机床 progData.setFileBackTime(DateUtil.fromInstant(entry.getLastModifiedTime().toInstant())); AnnotationProperties defAnnoProperties =AnnotationProperties.getDefault(); String statusLine = FileContentUtil.readLineAt(byteArrayIns,defAnnoProperties.getStatusLineIndex());//状态注释行 byteArrayIns.reset(); String sendPathLine = FileContentUtil.readLineAt(byteArrayIns,defAnnoProperties.getSendPathLineIndex());//状态注释行 byteArrayIns.reset(); if(statusLine.contains(AnnotationUtil.GH)){ //固化,不应回传,忽略 log.warn("状态{},不应回传,忽略",statusLine); continue; } Machine machine = this.machineService.getMachineBySendPathAnnotation(sendPathLine); if(machine == null){ throw new ServiceException("根据下发路径未找到程序对应的机床:"+sendPathLine); } progData.setProgramName(packageName); if(statusLine.contains(AnnotationUtil.SQ)){ //试切 programPackageNode = ncNodeService.getLastEditionTryingProgramPackage(packageName);//TODO 还需根据机床组(如何获取?根据下发路径获取机床,进而获取),,processEdition }else if(statusLine.contains(AnnotationUtil.LG)){ //临时更改单 programPackageNode =ncNodeService.getLastEditionDeviationProgramPackage(packageName); }else{ throw new ServiceException("状态注释不在范围内:"+statusLine+",仅试切、临时更改单可以回传"); } if(programPackageNode != null) { progData.setId(programPackageNode.getId()); progData.setProgramNo(programPackageNode.getProgramNo()); List fileEntryNames = fileEntryNameList.stream().filter(n -> n.startsWith(packageName)).toList(); List programFiles = new ArrayList<>(); fileEntryNames.forEach( filePath ->{ DncSendBackFile backFile = new DncSendBackFile(); backFile.setEntryName(filePath); backFile.setName(StringUtils.removeStart(filePath,entryName)); programFiles.add(backFile); }); progData.setFiles(programFiles); list.add(progData); }else{ throw new ServiceException("找不到程序包名:"+packageName); } }else{ throw new ServiceException(entryName+"包下未找到文件"); } } } return list; } /** * 修复程序包名,fanuc不识别下划线,下发时转换为了-,这里需要确认。应该是从导出记录中查找修改后的包名,但是现场编制的没有咋办? * @param packageNameInZip * @return */ /* private String fixProgramPackageName(String packageNameInZip) { Matcher matcher = RegExpConstants.PROGRAM_PACKAGE_PATTERN.matcher(packageNameInZip); String drawingNo = null; if(matcher.find()) { drawingNo = matcher.group(1); } String processNo = null; if(matcher.find()) { processNo = matcher.group(2); } String processEdition = null; if(matcher.find()) { processEdition = matcher.group(3); } if(drawingNo != null && processNo != null && processEdition != null) { if(drawingNo.contains("_")) { } } return packageNameInZip; } */ /** * 入库回传文件,并启动固化流程 * @param ids id列表逗号分隔,程序包名 节点的id */ @Transactional public void dncFileAccept(String ids) throws IOException { List acceptIdList = Func.toLongList(ids); // NcProgramExchange exchange; String filekey = getFileKey(); String zipFileName = bladeRedis.get(filekey); log.info("filekey={},文件名={}",filekey,zipFileName); Map> pkgIdFileMap = dealWithBackFile(zipFileName,acceptIdList); cureFlowService.startCureNew(pkgIdFileMap); } /** * 处理回传文件 * @param ossFileName * @param acceptIdList * @return * @throws IOException */ private Map> dealWithBackFile(String ossFileName, List acceptIdList) throws IOException{ Map> pkgIdFileMap = new HashMap<>(); InputStream inputStream = this.ossTemplate.statFileStream(ossFileName); Path tempZipFile = createTempFile(inputStream); List entryNameList = new ArrayList<>(); ZipEntry entry; try (java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(tempZipFile.toFile())) { Enumeration entries = zipFile.entries(); while(entries.hasMoreElements()) { entry = entries.nextElement(); entryNameList.add(entry.getName()); } log.info("allentrynames:{}",entryNameList); List allAcceptPackages = this.ncNodeService.lambdaQuery().in(NcNode::getId,acceptIdList).list(); //根据内部文件,读取和分析程序包和程序文件数据 List dirList = entryNameList.stream().filter(s -> s.endsWith("/")).toList(); for(String dir : dirList){ String programPackageName = StringUtils.removeEnd(dir,"/"); Optional optPackageNode = allAcceptPackages.stream().filter(node -> StringUtils.equals(node.getName(),programPackageName)).findFirst(); if(optPackageNode.isEmpty()){ throw new ServiceException("找不到程序"+programPackageName); } NcNode packageNode = optPackageNode.get(); if(packageNode.hasCured()) { throw new ServiceException(programPackageName + "已经固化,请勿重复入库。"); } //偏离程序判断是否重复回传 if(packageNode.isDeviationProgram() && packageNode.hasLocked()) { throw new ServiceException(programPackageName + "已锁定的程序不可以再次回传。"); } //检查是否在审批过程中 boolean active = flowCommonService.isProcessInstanceActive(packageNode.getProcessInstanceId()); if(active){ throw new ServiceException(programPackageName+"正在审批中,请勿等待审批完成。"); } //验证都过了,保存dncbackFile DncBackFile backFile = new DncBackFile(); backFile.setNcNodeId(packageNode.getId()); backFile.setOssName(ossFileName); dncBackFileService.save(backFile); List flowFiles = new ArrayList<>(); //查找包下的文件数据, entryNameList.stream().filter(s -> s.startsWith(dir)).forEach(entryName -> { log.info("{}下的文件:{}",dir,entryName); if(!entryName.endsWith("/")){ //实际的文件 String fileName = StringUtils.removeStart(entryName,dir);//去除文件名路径部分 fileName = removeDeviationPart(fileName); try { FlowProgramFile newFlowFile = new FlowProgramFile(); newFlowFile.setProgramName(packageNode.getName()); newFlowFile.setProcessInstanceId(null);//先置为空,启动流程后设置该值 newFlowFile.setFileType(FlowProgramFile.TYPE_PROGRAM); newFlowFile.setName(fileName); InputStream ins = zipFile.getInputStream(zipFile.getEntry(entryName)); BladeFile newOssFile = ossTemplate.putFile("mdm",fileName,ins); newFlowFile.setOssName(newOssFile.getName()); flowFiles.add(newFlowFile); } catch (IOException e) { throw new RuntimeException(e); } } }); pkgIdFileMap.put(packageNode.getId(),flowFiles); } } return pkgIdFileMap; } String removeDeviationPart(String filename){ String finalFilename = filename; //去掉文件名中可能带有的偏离单部分:-P[序号] String ext = FilenameUtils.getExtension(filename); String dotExt = StringUtils.isNotBlank(ext)?"."+ext:ext;//带点的扩展名 String notExtName = StringUtils.removeEnd(filename,dotExt); int idx = notExtName.lastIndexOf("-"); if(idx != -1){ String endPart = notExtName.substring(idx+1); //Pattern.CASE_INSENSITIVE boolean containsPld = endPart.matches(P_NUMBER_PATTERN); if(containsPld){ finalFilename = notExtName.substring(0, idx)+dotExt; } } return finalFilename; } /** * 创建一个临时zip文件 * @param inputStream 文件的输入流 * @return path 文件 * @throws IOException */ Path createTempFile(InputStream inputStream) throws IOException { byte[] zipData = FileUtil.copyToByteArray(inputStream); Path tempFile = Files.createTempFile("tempzip"+System.currentTimeMillis(), ".zip"); // 写入字节数据到临时文件 Files.write(tempFile, zipData, StandardOpenOption.WRITE); return tempFile; } /** * 获取回传文件的内容 * @param entryName 文件在压缩包内的路径 * @return 文件内容文本 */ public String getEntryFileContent(String entryName) throws IOException { String result = ""; String zipFileName = bladeRedis.get(getFileKey()); return ZipTextFileContentUtil.getTextContent(this.ossTemplate.statFileStream(zipFileName),entryName); } } @Data class PackageAndProcessEdition{ private String programPackageName; private String processEdition; public String programName(){ return programPackageName+"-"+processEdition; } }