|
package org.springblade.mdm.program.service;
|
|
import jodd.util.annotation.AnnotationParser;
|
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.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.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<NcProgramExchangeMapper, NcProgramExchange> {
|
@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<MdmProgramImportVO> mdmImportUpload(MultipartFile file) {
|
List<MdmProgramImportVO> 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<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列表逗号分隔
|
*/
|
public void mdmFileAccept(String ids) throws IOException {
|
|
List<FileSendRecord> importedRecords = new ArrayList<>();
|
List<String> idList = Func.toStrList(ids);
|
|
String dictStr = bladeRedis.get(getFileKey());
|
if(dictStr == null){
|
throw new ServiceException("文件缓存已过期,请重新上传文件。");
|
}
|
Path extractDir = Paths.get(dictStr);
|
List<MdmProgramImportVO> list = readTempDir(extractDir);
|
|
List<DictBiz> annoDictList = programAnnotationService.getAnnotionDictList();
|
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());
|
}
|
|
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);
|
/*
|
ProgramNameVO nameVO = ProgramFileNameParser.parseProgramName(record.getName());
|
if(StringUtils.isNotBlank(nameVO.getLgPart())) {
|
record.setDeviation(annotationData.getDeviation());
|
}*/
|
record.setProgramStatus(annotationData.getProgramStatus());
|
}
|
fileSendRecordService.save(record);
|
|
importedRecords.add(record);
|
}
|
|
machineBackTaskService.saveBatch(parseMachineBackTask(importedRecords));
|
}
|
|
/**
|
* 创建机床回传任务
|
* @param importedRecords
|
*/
|
List<MachineBackTask> parseMachineBackTask(List<FileSendRecord> importedRecords){
|
List<FileSendRecord> recList = importedRecords.stream().filter(r ->{
|
return AnnotationUtil.SQ.equals(r.getProgramStatus())
|
|| AnnotationUtil.LG.equals(r.getProgramStatus());}).toList();
|
|
Map<String,FileSendRecord> map = new HashMap<>();
|
for(FileSendRecord record : recList){
|
ProgramNameVO nameVO = ProgramFileNameParser.parseProgramName(record.getName());
|
String key = record.getMachineCode()+","+nameVO.logicProgramName();
|
map.put(key,record);
|
}
|
|
List<MachineBackTask> 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.setProgramName(nameVO.logicProgramName());
|
task.setSegCount(nameVO.getSegmentCount());
|
backTasks.add(task);
|
}
|
//提取程序包名,机床编码集合
|
return backTasks;
|
}
|
}
|