package cn.keking.service; import cn.keking.config.ConfigConstants; import cn.keking.model.FileType; import cn.keking.utils.FileHeaderRar; import cn.keking.utils.KkFileUtils; import cn.keking.web.filter.BaseUrlFilter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.junrar.Archive; import com.github.junrar.exception.RarException; import com.github.junrar.rarfile.FileHeader; import net.sf.sevenzipjbinding.*; import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; import net.sf.sevenzipjbinding.simple.ISimpleInArchive; import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; import org.apache.commons.compress.archivers.sevenz.SevenZFile; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.io.FileUtils; import org.springframework.stereotype.Component; import java.io.*; import java.math.BigDecimal; import java.text.CollationKey; import java.text.Collator; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @author yudian-it * create 2017/11/27 */ @Component public class CompressFileReader { private static final Pattern pattern = Pattern.compile("^\\d+"); private final FileHandlerService fileHandlerService; private final String fileDir = ConfigConstants.getFileDir(); private final ExecutorService executors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public CompressFileReader(FileHandlerService fileHandlerService) { this.fileHandlerService = fileHandlerService; } public String readZipFile(String filePath, String fileKey) { String archiveSeparator = "/"; Map appender = new HashMap<>(); List imgUrls = new LinkedList<>(); String baseUrl = BaseUrlFilter.getBaseUrl(); String archiveFileName = fileHandlerService.getFileNameFromPath(filePath); try { ZipFile zipFile = new ZipFile(filePath, KkFileUtils.getFileEncode(filePath)); Enumeration entries = zipFile.getEntries(); // 排序 entries = sortZipEntries(entries); List> entriesToBeExtracted = new LinkedList<>(); while (entries.hasMoreElements()) { ZipArchiveEntry entry = entries.nextElement(); String fullName = entry.getName().replaceAll("//", "").replaceAll("\\\\", ""); int level = fullName.split(archiveSeparator).length; // 展示名 String originName = getLastFileName(fullName, archiveSeparator); String childName = level + "_" + originName; boolean directory = entry.isDirectory(); if (!directory) { childName = archiveFileName + "_" + originName; entriesToBeExtracted.add(Collections.singletonMap(childName, entry)); } String parentName = getLast2FileName(fullName, archiveSeparator, archiveFileName); parentName = (level - 1) + "_" + parentName; FileType type = FileType.typeFromUrl(childName); if (type.equals(FileType.PICTURE)) {//添加图片文件到图片列表 imgUrls.add(baseUrl + childName); } FileNode node = new FileNode(originName, childName, parentName, new ArrayList<>(), directory, fileKey); addNodes(appender, parentName, node); appender.put(childName, node); } // 开启新的线程处理文件解压 executors.submit(new ZipExtractorWorker(entriesToBeExtracted, zipFile, filePath)); fileHandlerService.putImgCache(fileKey, imgUrls); return new ObjectMapper().writeValueAsString(appender.get("")); } catch (IOException e) { e.printStackTrace(); return null; } } private Enumeration sortZipEntries(Enumeration entries) { List sortedEntries = new LinkedList<>(); while (entries.hasMoreElements()) { sortedEntries.add(entries.nextElement()); } sortedEntries.sort(Comparator.comparingInt(o -> o.getName().length())); return Collections.enumeration(sortedEntries); } public String unRar(String filePath, String fileKey) { Map appender = new HashMap<>(); List imgUrls = new ArrayList<>(); String baseUrl = BaseUrlFilter.getBaseUrl(); try { List items = getRar4Paths(filePath); String archiveFileName = fileHandlerService.getFileNameFromPath(filePath); List> headersToBeExtract = new ArrayList<>(); for (FileHeaderRar header : items) { String fullName = header.getFileNameW(); String originName = getLastFileName(fullName, File.separator); String childName = originName; boolean directory = header.getDirectory(); if (!directory) { childName = archiveFileName + "_" + originName; headersToBeExtract.add(Collections.singletonMap(childName, header)); } String parentName = getLast2FileName(fullName, File.separator, archiveFileName); FileType type = FileType.typeFromUrl(childName); if (type.equals(FileType.PICTURE)) { imgUrls.add(baseUrl + childName); } FileNode node = new FileNode(originName, childName, parentName, new ArrayList<>(), directory, fileKey); addNodes(appender, parentName, node); appender.put(childName, node); } fileHandlerService.putImgCache(fileKey, imgUrls); executors.submit(new RarExtractorWorker(headersToBeExtract, filePath)); return new ObjectMapper().writeValueAsString(appender.get("")); } catch (IOException e) { e.printStackTrace(); } return null; } public List getRar4Paths(String paths) { RandomAccessFile randomAccessFile = null; IInArchive inArchive = null; List itemPath = null; try { randomAccessFile = new RandomAccessFile(paths, "r"); inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile)); String folderName = paths.substring(paths.lastIndexOf(File.separator) + 1); String extractPath = paths.substring(0, paths.lastIndexOf(folderName)); inArchive.extract(null, false, new ExtractCallback(inArchive, extractPath, folderName + "_")); ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface(); itemPath = Arrays.stream(simpleInArchive.getArchiveItems()) .map( o -> { try { return new FileHeaderRar(o.getPath(), o.isFolder()); } catch (SevenZipException e) { e.printStackTrace(); } return null; }) .collect(Collectors.toList()) .stream() .sorted(Comparator.comparing(FileHeaderRar::getFileNameW)) .collect(Collectors.toList()); } catch (Exception e) { System.err.println("Error occurs: " + e); } finally { if (inArchive != null) { try { inArchive.close(); } catch (SevenZipException e) { System.err.println("Error closing archive: " + e); } } if (randomAccessFile != null) { try { randomAccessFile.close(); } catch (IOException e) { System.err.println("Error closing file: " + e); } } } return itemPath; } public String read7zFile(String filePath, String fileKey) { String archiveSeparator = "/"; Map appender = new HashMap<>(); List imgUrls = new ArrayList<>(); String baseUrl = BaseUrlFilter.getBaseUrl(); String archiveFileName = fileHandlerService.getFileNameFromPath(filePath); try { SevenZFile zipFile = new SevenZFile(new File(filePath)); Iterable entries = zipFile.getEntries(); // 排序 Enumeration newEntries = sortSevenZEntries(entries); List> entriesToBeExtracted = new ArrayList<>(); while (newEntries.hasMoreElements()) { SevenZArchiveEntry entry = newEntries.nextElement(); String fullName = entry.getName().replaceAll("//", "").replaceAll("\\\\", ""); int level = fullName.split(archiveSeparator).length; // 展示名 String originName = getLastFileName(fullName, archiveSeparator); String childName = level + "_" + originName; boolean directory = entry.isDirectory(); if (!directory) { childName = archiveFileName + "_" + originName; entriesToBeExtracted.add(Collections.singletonMap(childName, entry)); } String parentName = getLast2FileName(fullName, archiveSeparator, archiveFileName); parentName = (level - 1) + "_" + parentName; FileType type = FileType.typeFromUrl(childName); if (type.equals(FileType.PICTURE)) {//添加图片文件到图片列表 imgUrls.add(baseUrl + childName); } FileNode node = new FileNode(originName, childName, parentName, new ArrayList<>(), directory, fileKey); addNodes(appender, parentName, node); appender.put(childName, node); } // 开启新的线程处理文件解压 executors.submit(new SevenZExtractorWorker(entriesToBeExtracted, filePath)); fileHandlerService.putImgCache(fileKey, imgUrls); return new ObjectMapper().writeValueAsString(appender.get("")); } catch (IOException e) { e.printStackTrace(); return null; } } private Enumeration sortSevenZEntries(Iterable entries) { List sortedEntries = new ArrayList<>(); for (SevenZArchiveEntry entry : entries) { sortedEntries.add(entry); } return Collections.enumeration(sortedEntries); } private void addNodes(Map appender, String parentName, FileNode node) { if (appender.containsKey(parentName)) { appender.get(parentName).getChildList().add(node); appender.get(parentName).getChildList().sort(sortComparator); } else { // 根节点 FileNode nodeRoot = new FileNode(parentName, parentName, "", new ArrayList<>(), true); nodeRoot.getChildList().add(node); appender.put("", nodeRoot); appender.put(parentName, nodeRoot); } } private List sortedHeaders(List headers) { List sortedHeaders = new ArrayList<>(); Map mapHeaders = new TreeMap<>(); headers.forEach( header -> mapHeaders.put( new Integer(0).equals(header.getFileNameW().length()) ? header.getFileNameString().length() : header.getFileNameW().length(), header)); for (Map.Entry entry : mapHeaders.entrySet()) { for (FileHeader header : headers) { if (entry .getKey() .equals( new Integer(0).equals(header.getFileNameW().length()) ? header.getFileNameString().length() : header.getFileNameW().length())) { sortedHeaders.add(header); } } } return sortedHeaders; } private static String getLast2FileName(String fullName, String seperator, String rootName) { if (fullName.endsWith(seperator)) { fullName = fullName.substring(0, fullName.length() - 1); } // 1.获取剩余部分 int endIndex = fullName.lastIndexOf(seperator); String leftPath = fullName.substring(0, endIndex == -1 ? 0 : endIndex); if (leftPath.length() > 1) { // 2.获取倒数第二个 return getLastFileName(leftPath, seperator); } else { return rootName; } } private static String getLastFileName(String fullName, String seperator) { if (fullName.endsWith(seperator)) { fullName = fullName.substring(0, fullName.length() - 1); } String newName = fullName; if (fullName.contains(seperator)) { newName = fullName.substring(fullName.lastIndexOf(seperator) + 1); } return newName; } public static Comparator sortComparator = new Comparator() { final Collator cmp = Collator.getInstance(Locale.US); @Override public int compare(FileNode o1, FileNode o2) { // 判断两个对比对象是否是开头包含数字,如果包含数字则获取数字并按数字真正大小进行排序 BigDecimal num1, num2; if (null != (num1 = isStartNumber(o1)) && null != (num2 = isStartNumber(o2))) { return num1.subtract(num2).intValue(); } CollationKey c1 = cmp.getCollationKey(o1.getOriginName()); CollationKey c2 = cmp.getCollationKey(o2.getOriginName()); return cmp.compare(c1.getSourceString(), c2.getSourceString()); } }; private static BigDecimal isStartNumber(FileNode src) { Matcher matcher = pattern.matcher(src.getOriginName()); if (matcher.find()) { return new BigDecimal(matcher.group()); } return null; } public static class FileNode { private String originName; private String fileName; private String parentFileName; private boolean directory; //用于图片预览时寻址 private String fileKey; private List childList; public FileNode(String originName, String fileName, String parentFileName, List childList, boolean directory) { this.originName = originName; this.fileName = fileName; this.parentFileName = parentFileName; this.childList = childList; this.directory = directory; } public FileNode(String originName, String fileName, String parentFileName, List childList, boolean directory, String fileKey) { this.originName = originName; this.fileName = fileName; this.parentFileName = parentFileName; this.childList = childList; this.directory = directory; this.fileKey = fileKey; } public String getFileKey() { return fileKey; } public void setFileKey(String fileKey) { this.fileKey = fileKey; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getParentFileName() { return parentFileName; } public void setParentFileName(String parentFileName) { this.parentFileName = parentFileName; } public List getChildList() { return childList; } public void setChildList(List childList) { this.childList = childList; } @Override public String toString() { try { return new ObjectMapper().writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return ""; } } public String getOriginName() { return originName; } public void setOriginName(String originName) { this.originName = originName; } public boolean isDirectory() { return directory; } public void setDirectory(boolean directory) { this.directory = directory; } } class ZipExtractorWorker implements Runnable { private final List> entriesToBeExtracted; private final ZipFile zipFile; private final String filePath; public ZipExtractorWorker(List> entriesToBeExtracted, ZipFile zipFile, String filePath) { this.entriesToBeExtracted = entriesToBeExtracted; this.zipFile = zipFile; this.filePath = filePath; } @Override public void run() { for (Map entryMap : entriesToBeExtracted) { String childName = entryMap.keySet().iterator().next(); ZipArchiveEntry entry = entryMap.values().iterator().next(); try { extractZipFile(childName, zipFile.getInputStream(entry)); } catch (IOException e) { e.printStackTrace(); } } try { zipFile.close(); } catch (IOException e) { e.printStackTrace(); } KkFileUtils.deleteFileByPath(filePath); } private void extractZipFile(String childName, InputStream zipFile) { String outPath = fileDir + childName; try (OutputStream ot = new FileOutputStream(outPath)) { byte[] inByte = new byte[1024]; int len; while ((-1 != (len = zipFile.read(inByte)))) { ot.write(inByte, 0, len); } } catch (IOException e) { e.printStackTrace(); } } } class SevenZExtractorWorker implements Runnable { private final List> entriesToBeExtracted; private final String filePath; public SevenZExtractorWorker(List> entriesToBeExtracted, String filePath) { this.entriesToBeExtracted = entriesToBeExtracted; this.filePath = filePath; } @Override public void run() { try { SevenZFile sevenZFile = new SevenZFile(new File(filePath)); SevenZArchiveEntry entry = sevenZFile.getNextEntry(); while (entry != null) { if (entry.isDirectory()) { entry = sevenZFile.getNextEntry(); continue; } String childName = "default_file"; SevenZArchiveEntry entry1; for (Map entryMap : entriesToBeExtracted) { childName = entryMap.keySet().iterator().next(); entry1 = entryMap.values().iterator().next(); if (entry.getName().equals(entry1.getName())) { break; } } FileOutputStream out = new FileOutputStream(fileDir + childName); byte[] content = new byte[(int) entry.getSize()]; sevenZFile.read(content, 0, content.length); out.write(content); out.close(); entry = sevenZFile.getNextEntry(); } sevenZFile.close(); } catch (IOException e) { e.printStackTrace(); } KkFileUtils.deleteFileByPath(filePath); } } class RarExtractorWorker implements Runnable { private final List> headersToBeExtracted; private final List> headersToBeExtract; private final Archive archive; /** * 用以删除源文件 */ private final String filePath; public RarExtractorWorker( List> headersToBeExtracted, Archive archive, String filePath) { this.headersToBeExtracted = headersToBeExtracted; this.archive = archive; this.filePath = filePath; headersToBeExtract = null; } public RarExtractorWorker( List> headersToBeExtract, String filePath) { this.headersToBeExtract = headersToBeExtract; this.filePath = filePath; archive = null; headersToBeExtracted = null; } @Override public void run() { for (Map entryMap : headersToBeExtracted) { String childName = entryMap.keySet().iterator().next(); extractRarFile(childName, entryMap.values().iterator().next(), archive); } try { archive.close(); } catch (IOException e) { e.printStackTrace(); } KkFileUtils.deleteFileByPath(filePath); } private void extractRarFile(String childName, FileHeader header, Archive archive) { String outPath = fileDir + childName; try (OutputStream ot = new FileOutputStream(outPath)) { archive.extractFile(header, ot); } catch (IOException | RarException e) { e.printStackTrace(); } } } private static class ExtractCallback implements IArchiveExtractCallback { private final IInArchive inArchive; private final String extractPath; private final String folderName; public ExtractCallback(IInArchive inArchive, String extractPath, String folderName) { this.inArchive = inArchive; if (!extractPath.endsWith("/") && !extractPath.endsWith("\\")) { extractPath += File.separator; } this.extractPath = extractPath; this.folderName = folderName; } @Override public void setTotal(long total) { } @Override public void setCompleted(long complete) { } @Override public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) throws SevenZipException { String filePath = inArchive.getStringProperty(index, PropID.PATH); String real = folderName + filePath.substring(filePath.lastIndexOf(File.separator) + 1); File f = new File(extractPath + real); f.delete(); return data -> { FileOutputStream fos = null; try { File path = new File(extractPath + real); if (!path.getParentFile().exists()) { path.getParentFile().mkdirs(); } if (!path.exists()) { path.createNewFile(); } fos = new FileOutputStream(path, true); fos.write(data); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fos != null) { fos.flush(); fos.close(); } } catch (IOException e) { e.printStackTrace(); } } return data.length; }; } @Override public void prepareOperation(ExtractAskMode extractAskMode) { } @Override public void setOperationResult(ExtractOperationResult extractOperationResult) { } } }