| | |
| | | |
| | | package org.springblade.mdm.program.service; |
| | | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.codec.digest.DigestUtils; |
| | | import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; |
| | | import org.apache.commons.compress.archivers.zip.ZipFile; |
| | | import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; |
| | | 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.tool.utils.FileUtil; |
| | | 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.core.tool.utils.IoUtil; |
| | | import org.springblade.mdm.flow.service.CureFlowService; |
| | | import org.springblade.mdm.program.entity.NcProgram; |
| | | 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.commons.contants.ZipConstants; |
| | | 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.vo.DncSendBackData; |
| | | import org.springblade.mdm.program.service.programannotation.AnnotationProperties; |
| | | import org.springblade.mdm.program.vo.MdmProgramImportVO; |
| | | import org.springblade.mdm.utils.CustomBinaryReader; |
| | | 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.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.LocalDateTime; |
| | | 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 |
| | | @AllArgsConstructor |
| | | public class MdmProgramImportService extends BizServiceImpl<NcProgramExchangeMapper, NcProgramExchange> { |
| | | private final CureFlowService cureFlowService; |
| | | private final NcProgramService ncProgramService; |
| | | @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导入文件上传 |
| | | * 工控MDM导入文件上传 |
| | | * @param file MDM涉密网导出文件 |
| | | * @return |
| | | */ |
| | | public List<MdmProgramImportVO> mdmImportUpload(MultipartFile file) { |
| | | List<MdmProgramImportVO> list; |
| | | try { |
| | | //String fileName = file.getOriginalFilename(); |
| | | //InputStream zipFileInputStream = FileExchangeUtil.convertFileToZip(file.getInputStream()); |
| | | InputStream zipFileInputStream = file.getInputStream();//test |
| | | 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())); |
| | | } |
| | | |
| | | byte[] bytes = FileUtil.copyToByteArray(zipFileInputStream); |
| | | list = parseDncZipFromByteArray(bytes); |
| | | |
| | | |
| | | //读取文件目录 |
| | | list = readTempDir(extractDir); |
| | | } catch (IOException e) { |
| | | log.error("上传dnc回传文件失败",e); |
| | | list = Collections.emptyList(); |
| | | log.error("导入涉密网摆渡文件失败",e); |
| | | throw new ServiceException("解析DNC回传数据失败"); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | InputStream convertFileToZip(InputStream inputStream) throws IOException { |
| | | |
| | | File tempFile = createTempFile(); |
| | | FileOutputStream fos = new FileOutputStream(tempFile); |
| | | CustomBinaryReader.read(inputStream,fos); |
| | | |
| | | |
| | | FileInputStream dInstream = new FileInputStream(tempFile); |
| | | |
| | | return dInstream; |
| | | } |
| | | |
| | | /** |
| | | * 创建一个临时文件 |
| | | * @return |
| | | * @throws IOException |
| | | * 解压zip包到临时路径 |
| | | * @param zipFilePath zip包文件服务器上的路径 |
| | | * @param extractDir 目标目录 |
| | | * @throws IOException 文件操作异常 |
| | | */ |
| | | File createTempFile() throws IOException { |
| | | Path tempDir = Paths.get(System.getProperty("java.io.tmpdir")); |
| | | // 在临时目录中创建文件 |
| | | String tfilename = "t"+System.currentTimeMillis(); |
| | | Path tempFile = Files.createTempFile(tempDir, tfilename, ".tmp"); |
| | | System.out.println("创建的临时文件: " + tempFile); |
| | | return tempFile.toFile(); |
| | | } |
| | | public static List<MdmProgramImportVO> parseDncZipFromByteArray(byte[] zipData) throws IOException { |
| | | List<MdmProgramImportVO> list = new ArrayList<>(); |
| | | //List<DncSendBackData> datas = ZipFileDirectoryScanner.getFilesInDirectoryRecursive(zipData, ""); |
| | | |
| | | Map<String,String> fileMd5Map = new HashMap<>(); |
| | | Map<String,MdmProgramImportVO> fileDataMap = new HashMap<>(); |
| | | try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(zipData); |
| | | ZipFile zipFile = new ZipFile(channel)) { |
| | | |
| | | ZipArchiveEntry entry; |
| | | Enumeration<ZipArchiveEntry> entries = zipFile.getEntries(); |
| | | while (entries.hasMoreElements()) { |
| | | //while ((entry = zis.getNextZipEntry()) != null) { |
| | | entry = entries.nextElement(); |
| | | DncSendBackData prog = new DncSendBackData(); |
| | | String entryName = entry.getName(); |
| | | |
| | | if (!entry.isDirectory()) { |
| | | //直接解析程序的json文件 |
| | | if(entryName.equals(NcProgramExportDNCService.PROGRAM_JSON_FILE)){ |
| | | |
| | | try (InputStream inputStream = zipFile.getInputStream(entry)) { |
| | | String jsonStr = IoUtil.readToString(inputStream); |
| | | |
| | | JSONArray jsonArray = JSONArray.parseArray(jsonStr); |
| | | for(int i=0;i<jsonArray.size();i++){ |
| | | JSONObject jsonObject = jsonArray.getJSONObject(i); |
| | | MdmProgramImportVO d = new MdmProgramImportVO(); |
| | | d.setName(jsonObject.getString("name")); |
| | | d.setId(jsonObject.getLong("id")); |
| | | d.setCode(jsonObject.getString("code")); |
| | | //d.setFileBackTime(LocalDateTime.now());//到达时间 |
| | | |
| | | fileDataMap.put(d.getName(),d); |
| | | list.add(d); |
| | | } |
| | | |
| | | } |
| | | }else{ |
| | | try (InputStream inputStream = zipFile.getInputStream(entry)) { |
| | | fileMd5Map.put(entryName,DigestUtils.md5Hex(inputStream));//获取文件MD5 |
| | | } |
| | | |
| | | } |
| | | System.out.println("文件名: " + entry.getName()); |
| | | System.out.println("大小: " + entry.getSize()); |
| | | |
| | | // 读取文件内容到字节数组 |
| | | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| | | |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | |
| | | } |
| | | //设置md5值 |
| | | fileDataMap.forEach((k,v)->{ |
| | | if(fileMd5Map.containsKey(k)){ |
| | | v.setMd5(fileMd5Map.get(k)); |
| | | } |
| | | 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<MdmProgramImportVO> readTempDir(Path extractDir) throws IOException { |
| | | List<MdmProgramImportVO> list = new ArrayList<>(); |
| | | try (Stream<Path> paths = Files.walk(extractDir)) { |
| | | List<Path> filePathList = paths |
| | | .filter(Files::isRegularFile).toList(); // 只保留普通文件,排除目录 |
| | | |
| | | for(Path path : filePathList){ |
| | | list.add(readFileToVO(path)); |
| | | } |
| | | } |
| | | /* |
| | | //读取所有文件夹 |
| | | try (DirectoryStream<Path> stream = Files.newDirectoryStream(extractDir)) { |
| | | for (Path path : stream) { |
| | | if (Files.isDirectory(path)) { |
| | | // 如果是子目录,读取其中的文件 |
| | | try (DirectoryStream<Path> 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 pnmameVO = tryParseProgramName(vo.getFilename()); |
| | | vo.setDrawingNo(pnmameVO.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(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列表逗号分隔 |
| | | * @return |
| | | */ |
| | | public void mdmFileAccept(String ids) { |
| | | /* |
| | | List<Long> idList = Func.toLongList(ids); |
| | | List<NcProgram> progList = ncProgramService.listByIds(idList); |
| | | NcProgramExchange exchange; |
| | | //NcProgram program; |
| | | //NcNode programNode; |
| | | public void mdmFileAccept(String ids) throws IOException { |
| | | |
| | | for(NcProgram prog:progList){ |
| | | exchange = new NcProgramExchange(); |
| | | exchange.setName(prog.getName()); |
| | | exchange.setExchangeType(2);//回传 |
| | | exchange.setNcProgramId(prog.getId()); |
| | | List<FileSendRecord> importedRecords = new ArrayList<>(); |
| | | List<String> idList = Func.toStrList(ids); |
| | | |
| | | this.save(exchange); |
| | | |
| | | String dictStr = bladeRedis.get(getFileKey()); |
| | | if(dictStr == null){ |
| | | throw new ServiceException("文件缓存已过期,请重新上传文件。"); |
| | | } |
| | | //直接入库,无流程 |
| | | */ |
| | | Path extractDir = Paths.get(dictStr); |
| | | List<MdmProgramImportVO> list = readTempDir(extractDir); |
| | | |
| | | String destFileFull; |
| | | for(String str : idList){ |
| | | Optional<MdmProgramImportVO> 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); |
| | | |
| | | importedRecords.add(record); |
| | | } |
| | | |
| | | } |
| | | } |