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.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.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.AnnotationProperties; import org.springblade.mdm.program.vo.MdmProgramImportVO; import org.springblade.mdm.utils.FileContentUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.*; 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.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; private String getFileKey(){ return "mdmgkwimpfile-"+ AuthUtil.getUserId(); } /** * 工控MDM导入文件上传 * @param file MDM涉密网导出文件 * @return */ public List mdmImportUpload(MultipartFile file) { List list = new ArrayList<>(); 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 tempZipFile = Files.createTempFile("mdmimpfile-"+System.currentTimeMillis(), ".zip"); file.transferTo(tempZipFile); // 创建解压目标目录(在临时目录下创建一个唯一子目录) Path extractDir = Files.createTempDirectory(tempPath, "unzip_"); bladeRedis.setEx(getFileKey(),extractDir.toString(), Duration.ofHours(2)); extractZipToTempDir(tempZipFile,extractDir); //读取文件目录 list = readTempDir(extractDir); } catch (IOException e) { log.error("导入涉密网摆渡文件失败",e); throw new ServiceException("解析DNC回传数据失败"); } return list; } public void extractZipToTempDir(Path zipFilePath,Path extractDir) throws IOException { // 获取系统临时目录 String tempDir = System.getProperty("java.io.tmpdir"); Path tempPath = Paths.get(tempDir); // 创建解压目标目录(在临时目录下创建一个唯一子目录) try (InputStream fis = Files.newInputStream(zipFilePath); ZipInputStream zis = new ZipInputStream(fis)) { 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 * @param targetDir * @return * @throws IOException */ 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; } public List readTempDir(Path extractDir) throws IOException { List list = new ArrayList<>(); //List machines = machineService.getEnableMachines(); //读取所有文件夹 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); //这里 找到的文件不是 MdmProgramImportVO vo = new MdmProgramImportVO(); vo.setFilename(path.getFileName().toString()); vo.setDrawingNo(parseDrawingNo(vo.getFilename())); 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.mark(0); bas.reset(); String statusLine = FileContentUtil.readLineAt(bas,defAnnoProperties.getStatusLineIndex()); log.info("sendPathLine={}", sendPathLine); Machine matchedMachine = machineService.getMachineBySendPathAnnotation(sendPathLine); /* for (Machine machine : machines) { if(Func.isNotBlank(machine.getProgSendDir()) && sendPathLine.contains(machine.getProgSendDir())){ matchedMachine = machine; break; } } */ if (matchedMachine != null) { vo.setName(parseProgramName(vo.getFilename())); vo.setMachineCode(matchedMachine.getCode()); vo.setFullPath(path.toString());//文件地址 vo.setSendPath(matchedMachine.getProgSendDir()); vo.setId(vo.getFullPath()); vo.setProgramStatus(programAnnotationService.removeAnnotation(matchedMachine.getControlSystem(),statusLine)); list.add(vo); } } catch (IOException e) { log.error("读取文件md5失败",e); } } } } return list; } /** * 解析出零组件好 * @param filename * @return */ String parseDrawingNo(String filename){ String drawingNo = ""; int idx = filename.lastIndexOf("-"); String temp; if(idx != -1){ temp = filename.substring(0,idx); idx = temp.lastIndexOf("-"); if(idx != -1){ temp = temp.substring(0,idx); //去掉工序版次 idx = temp.lastIndexOf("-"); if(idx != -1){ temp = temp.substring(0,idx); //去掉工序号 idx = temp.lastIndexOf("-"); if(idx != -1){ drawingNo = temp.substring(0,idx); } } } //以上去掉了最后2段段数和段号 } return drawingNo; } String parseProgramName(String filename){ String programName = ""; int idx = filename.lastIndexOf("-"); String temp; if(idx != -1){ temp = filename.substring(0,idx); idx = temp.lastIndexOf("-"); if(idx != -1){ temp = temp.substring(0,idx); //去掉工序版次 idx = temp.lastIndexOf("-"); if(idx != -1){ programName = temp.substring(0,idx); } } //以上去掉了最后2段段数和段号 } return programName; } /** * 入库mdm涉密网文件 * @param ids id列表逗号分隔 * @return */ public void mdmFileAccept(String ids) throws IOException { 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); String destFileFull; for(String str : idList){ for(MdmProgramImportVO vo : list){ if(StringUtils.equals(vo.getFullPath(),str)){ 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)); fileSendRecordService.save(record); break; } } } } }