package org.springblade.mdm.program.service; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; 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.Func; import org.springblade.mdm.basesetting.machine.service.MachineService; import org.springblade.mdm.basesetting.machine.entity.Machine; import org.springblade.mdm.commons.contants.ZipConstants; import org.springblade.mdm.gkw.programnode.vo.ProgramNameVO; import org.springblade.mdm.gkw.task.entity.MachineBackTask; import org.springblade.mdm.gkw.task.service.MachineBackTaskService; import org.springblade.mdm.machinefile.entity.FileSendRecord; import org.springblade.mdm.machinefile.service.FileSendRecordService; 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.MdmProgramImportVO; import org.springblade.mdm.utils.FileContentUtil; import org.springblade.mdm.utils.ProgramFileNameParser; import org.springblade.system.pojo.entity.DictBiz; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.*; import java.nio.file.*; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * MDM程序导入(工控网功能) * 目前工控网展现形式未定,暂时实现暂停 * @author yangys */ @Slf4j @Service public class MdmProgramImportService extends BizServiceImpl { @Autowired private BladeRedis bladeRedis; @Autowired private ProgramAnnotationService programAnnotationService; @Autowired private MachineService machineService; @Autowired private FileSendRecordService fileSendRecordService; @Autowired private OssTemplate ossTemplate; @Autowired private AnnotationProcessorHelper annotationProcessorHelper; @Autowired private MachineBackTaskService machineBackTaskService; private String getFileKey(){ return "mdmgkwimpfile-"+ AuthUtil.getUserId(); } /** * 工控MDM导入文件上传 * @param file MDM涉密网导出文件 * @return */ public List mdmImportUpload(MultipartFile file) { List list; try { if(file == null || file.isEmpty()){ throw new ServiceException("文件为空"); } /* if(!StringUtils.endsWith(file.getOriginalFilename(),".zip")){ throw new ServiceException("文件必须为zip包"); }*/ String tempDir = System.getProperty("java.io.tmpdir"); Path tempPath = Paths.get(tempDir); Path extractDir = Files.createTempDirectory(tempPath, "unzip_"+System.currentTimeMillis()); bladeRedis.setEx(getFileKey(),extractDir.toString(), Duration.ofHours(2)); if(StringUtils.endsWithIgnoreCase(file.getOriginalFilename(),".zip")){ // 创建解压目标目录(在临时目录下创建一个唯一子目录) Path tempZipFile = Files.createTempFile("mdmimpfile-"+System.currentTimeMillis(), ".zip"); file.transferTo(tempZipFile); extractZipToTempDir(tempZipFile,extractDir); }else{ //普通文件,直接放入extract文件夹 file.transferTo(Paths.get(extractDir.toString()+File.separator+file.getOriginalFilename())); } //读取文件目录 list = readTempDir(extractDir); } catch (IOException e) { log.error("导入涉密网摆渡文件失败",e); throw new ServiceException("解析DNC回传数据失败"); } return list; } /** * 解压zip包到临时路径 * @param zipFilePath zip包文件服务器上的路径 * @param extractDir 目标目录 * @throws IOException 文件操作异常 */ public void extractZipToTempDir(Path zipFilePath,Path extractDir) throws IOException{ for (String encoding : ZipConstants.TRY_ENCODINGS) { try { extractZipToTempDirWithCharset(zipFilePath,extractDir,Charset.forName(encoding)); log.error("使用编码 {} 解析成功 ",encoding); break; } catch (Exception e) { log.error("使用编码 {} 解析失败: ",encoding,e); } } } public void extractZipToTempDirWithCharset(Path zipFilePath, Path extractDir, Charset charset) throws IOException { // 获取系统临时目录 String tempDir = System.getProperty("java.io.tmpdir"); // 创建解压目标目录(在临时目录下创建一个唯一子目录) try (InputStream fis = Files.newInputStream(zipFilePath); ZipInputStream zis = new ZipInputStream(fis,charset)) { ZipEntry zipEntry = zis.getNextEntry(); while (zipEntry != null) { Path newPath = zipSlipProtect(zipEntry, extractDir); if (zipEntry.isDirectory()) { Files.createDirectories(newPath); } else { // 确保父目录存在 if (newPath.getParent() != null) { Files.createDirectories(newPath.getParent()); } // 写入文件 try (OutputStream fos = Files.newOutputStream(newPath)) { byte[] buffer = new byte[1024]; int len; while ((len = zis.read(buffer)) > 0) { fos.write(buffer, 0, len); } } } zipEntry = zis.getNextEntry(); } zis.closeEntry(); } } /** * 防止ZIP Slip攻击 * @param zipEntry zip内部文件路径 * @param targetDir 目标文件夹 * @return 文件路径 * @throws IOException 操作文件IO异常 */ Path zipSlipProtect(ZipEntry zipEntry, Path targetDir) throws IOException { Path targetDirResolved = targetDir.resolve(zipEntry.getName()); // 规范化路径 Path normalizePath = targetDirResolved.normalize(); if (!normalizePath.startsWith(targetDir)) { throw new IOException("恶意ZIP条目: " + zipEntry.getName()); } return normalizePath; } /** * 读取解压文件夹下所有文件 解析成vo列表 * @param extractDir 解压文件夹 * @return vo列表 * @throws IOException 解析文件的异常 */ public List readTempDir(Path extractDir) throws IOException { List list = new ArrayList<>(); try (Stream paths = Files.walk(extractDir)) { List filePathList = paths .filter(Files::isRegularFile).toList(); // 只保留普通文件,排除目录 for(Path path : filePathList){ list.add(readFileToVO(path)); } } /* //读取所有文件夹 try (DirectoryStream stream = Files.newDirectoryStream(extractDir)) { for (Path path : stream) { if (Files.isDirectory(path)) { // 如果是子目录,读取其中的文件 try (DirectoryStream subStream = Files.newDirectoryStream(path)) { for (Path subPath : subStream) { if (Files.isRegularFile(subPath)) { System.out.println("找到文件: " + subPath); } } } } else if (Files.isRegularFile(path)) { System.out.println("找到文件2: " + path); //这里 找到的文件不是 list.add(readFileToVO(path)); } } }*/ return list; } public static byte[] getUTF8BytesFromGBKString(String gbkStr) { int n = gbkStr.length(); byte[] utfBytes = new byte[3 * n]; int k = 0; for (int i = 0; i < n; i++) { int m = gbkStr.charAt(i); if (m < 128 && m >= 0) { utfBytes[k++] = (byte) m; continue; } utfBytes[k++] = (byte) (0xe0 | (m >> 12)); utfBytes[k++] = (byte) (0x80 | ((m >> 6) & 0x3f)); utfBytes[k++] = (byte) (0x80 | (m & 0x3f)); } if (k < utfBytes.length) { byte[] tmp = new byte[k]; System.arraycopy(utfBytes, 0, tmp, 0, k); return tmp; } return utfBytes; } ProgramNameVO tryParseProgramName(String fiilename){ ProgramNameVO pnmameVO = ProgramFileNameParser.parseProgramName(fiilename);//标准utf8编码 if(pnmameVO.getDrawingNo() == null) { //使用GBK编码解析 pnmameVO = ProgramFileNameParser.parseProgramName(new String(getUTF8BytesFromGBKString(fiilename), StandardCharsets.UTF_8)); } return pnmameVO; } /** * 将文件组织成VO * @param path 文件path * @return vo */ MdmProgramImportVO readFileToVO(Path path) throws UnsupportedEncodingException { MdmProgramImportVO vo = new MdmProgramImportVO(); vo.setFilename(path.getFileName().toString()); ProgramNameVO progNmameVO = tryParseProgramName(vo.getFilename()); vo.setDrawingNo(progNmameVO.getDrawingNo()); try (InputStream inputStream = Files.newInputStream(path)) { // 使用输入流读取文件内容 byte[] buffer = new byte[2000]; inputStream.read(buffer); vo.setMd5(DigestUtils.md5Hex(buffer)); } catch (IOException e) { log.error("读取文件md5失败",e); } try (InputStream inputStream = Files.newInputStream(path, StandardOpenOption.READ)) { // 使用输入流读取文件内容 ByteArrayInputStream bas = new ByteArrayInputStream(inputStream.readAllBytes()); AnnotationProperties defAnnoProperties = AnnotationProperties.getDefault(); String sendPathLine = FileContentUtil.readLineAt(bas,defAnnoProperties.getSendPathLineIndex()); bas.reset(); String statusLine = FileContentUtil.readLineAt(bas,defAnnoProperties.getStatusLineIndex()); log.info("sendPathLine={}", sendPathLine); Machine matchedMachine = machineService.getMachineBySendPathAnnotation(sendPathLine); if (matchedMachine != null) { vo.setName(progNmameVO.logicProgramName()); vo.setMachineCode(matchedMachine.getCode()); vo.setFullPath(path.toString());//文件地址 vo.setSendPath(matchedMachine.getProgSendDir()); vo.setId(vo.getFullPath()); vo.setProgramStatus(programAnnotationService.removeAnnotation(matchedMachine.getControlSystem(),statusLine)); } } catch (IOException e) { log.error("读取文件失败",e); throw new ServiceException("导入程序失败"+path.getFileName().toString()+","+e.getMessage()); } return vo; } /** * 入库mdm涉密网文件 * @param ids id列表逗号分隔 */ public void mdmFileAccept(String ids) throws IOException { List importedRecords = new ArrayList<>(); List idList = Func.toStrList(ids); String dictStr = bladeRedis.get(getFileKey()); if(dictStr == null){ throw new ServiceException("文件缓存已过期,请重新上传文件。"); } Path extractDir = Paths.get(dictStr); List list = readTempDir(extractDir); //List annoDictList = programAnnotationService.getAnnotionDictList(); String destFileFull; for(String str : idList){ Optional optVO = list.stream().filter(vo -> vo.getId().equals(str)).findFirst(); if(optVO.isEmpty()){ continue; } MdmProgramImportVO vo = optVO.get(); destFileFull = vo.getSendPath()+File.separator+vo.getFilename(); File destFile = new File(destFileFull); FileUtils.forceMkdirParent(destFile); FileUtils.copyFile(new File(str),destFile); FileSendRecord record = new FileSendRecord(); record.setName(destFile.getName()); Path destPath = Paths.get(destFileFull); record.setMachineCode(vo.getMachineCode()); record.setFileSize(Files.size(destPath)); try(InputStream inputStream = new FileInputStream(destFile)){ BladeFile bFile = ossTemplate.putFile(record.getName(), inputStream); record.setOssName(bFile.getName()); } try(InputStream inputStream = ossTemplate.statFileStream(record.getOssName())){ //读取程序状态 Machine machine = this.machineService.getByCode(record.getMachineCode()); AnnotationProcessor processor = annotationProcessorHelper.getProcessor(machine.getControlSystem()); AnnotationData annotationData = processor.readAnnotationData(inputStream); record.setDeviation(annotationData.getDeviation()); ProgramNameVO nameVO = ProgramFileNameParser.parseProgramName(record.getName()); record.setDeviationSerial(nameVO.lgSerial()); record.setProgramStatus(annotationData.getProgramStatus()); } fileSendRecordService.save(record); importedRecords.add(record); } machineBackTaskService.saveBatch(parseMachineBackTask(importedRecords)); } /** * 创建机床回传任务 * @param importedRecords 导入记录列表 */ List parseMachineBackTask(List importedRecords){ List recList = importedRecords.stream().filter(r -> AnnotationUtil.SQ.equals(r.getProgramStatus()) || AnnotationUtil.LG.equals(r.getProgramStatus())).toList(); Map map = new HashMap<>(); for(FileSendRecord record : recList){ ProgramNameVO nameVO = ProgramFileNameParser.parseProgramName(record.getName()); String key = record.getMachineCode()+","+nameVO.logicProgramName(); map.put(key,record); } List backTasks = new ArrayList<>(); for(String key : map.keySet()){ MachineBackTask task = new MachineBackTask(); task.setTaskType(MachineBackTask.TASK_TYPE_PROGRAM); FileSendRecord record = map.get(key); task.setMachineCode(record.getMachineCode()); ProgramNameVO nameVO = ProgramFileNameParser.parseProgramName(record.getName()); task.setDeviation(record.getDeviation()); task.setDeviationSerial(record.getDeviationSerial()); task.setProgramName(nameVO.logicProgramName()); task.setSegCount(nameVO.getSegmentCount()); backTasks.add(task); } //提取程序包名,机床编码集合 return backTasks; } }