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.io.FilenameUtils; 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.RegExpConstants; import org.springblade.mdm.gkw.programnode.vo.ProgramNameVO; 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.springblade.mdm.utils.ProgramFileNameParser; 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.regex.Matcher; 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; 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文件夹 String basename = FilenameUtils.getBaseName(file.getOriginalFilename()); String ext = FilenameUtils.getExtension(file.getOriginalFilename()); //Path filepath = Files.createTempFile(extractDir,basename,StringUtils.isEmpty(ext) ? "": "."+ext); //Path filepath = Files.createFile(); 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 { // 获取系统临时目录 String tempDir = System.getProperty("java.io.tmpdir"); // 创建解压目标目录(在临时目录下创建一个唯一子目录) 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 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(); // 只保留普通文件,排除目录 //.collect(Collectors.toList()); //System.out.println("所有文件"+filePathList); 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; } /** * 将文件组织成VO * @param path 文件path * @return vo */ MdmProgramImportVO readFileToVO(Path path){ MdmProgramImportVO vo = new MdmProgramImportVO(); vo.setFilename(path.getFileName().toString()); ProgramNameVO pnmameVO = ProgramFileNameParser.parseProgramName(vo.getFilename()); vo.setDrawingNo(pnmameVO.getDrawingNo()); //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); if (matchedMachine != null) { //vo.setName(parseProgramName(vo.getFilename())); vo.setName(pnmameVO.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; } /** * 解析出零组件好 * @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列表逗号分隔 */ 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){ 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()); } fileSendRecordService.save(record); } } }