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.springframework.stereotype.Component; import java.io.*; import java.math.BigDecimal; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; 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 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; } public String getUtf8String(String str) { if (str != null && str.length() > 0) { String needEncodeCode = "ISO-8859-1"; String neeEncodeCode = "ISO-8859-2"; String gbkEncodeCode = "GBK"; try { if (Charset.forName(needEncodeCode).newEncoder().canEncode(str)) { str = new String(str.getBytes(needEncodeCode), StandardCharsets.UTF_8); } if (Charset.forName(neeEncodeCode).newEncoder().canEncode(str)) { str = new String(str.getBytes(neeEncodeCode), StandardCharsets.UTF_8); } if (Charset.forName(gbkEncodeCode).newEncoder().canEncode(str)) { str = new String(getUTF8BytesFromGBKString(str), StandardCharsets.UTF_8); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return str; } /** * 判断是否是中日韩文字 */ private static boolean isChinese(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { return true; } return false; } public static boolean judge(char c){ if((c >='0' && c<='9')||(c >='a' && c<='z' || c >='A' && c<='Z')){ return true; } return false; } public static boolean isMessyCode(String strName) { //去除字符串中的空格 制表符 换行 回车 Pattern p = Pattern.compile("\\s*|\t*|\r*|\n*"); Matcher m = p.matcher(strName); String after = m.replaceAll(""); //去除字符串中的标点符号 String temp = after.replaceAll("\\p{P}", ""); //处理之后转换成字符数组 char[] ch = temp.trim().toCharArray(); for (int i = 0; i < ch.length; i++) { char c = ch[i]; //判断是否是数字或者英文字符 if (!judge(c)) { //判断是否是中日韩文 if (!isChinese(c)) { //如果不是数字或者英文字符也不是中日韩文则表示是乱码返回true return true; } } } //表示不是乱码 返回false return false; } 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); String childName = originName; boolean directory = header.getDirectory(); if (!directory) { childName = archiveFileName + "_" + originName; headersToBeExtract.add(Collections.singletonMap(childName, header)); } String parentName = getLast2FileName(fullName, 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 { String path = getUtf8String(o.getPath()); if (isMessyCode(path)){ path = new String(o.getPath().getBytes(StandardCharsets.ISO_8859_1), "GBK"); } return new FileHeaderRar(path, o.isFolder()); } catch (SevenZipException e) { e.printStackTrace(); } catch (UnsupportedEncodingException 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; } 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 static String getLast2FileName(String fullName, String rootName) { if (fullName.endsWith(File.separator)) { fullName = fullName.substring(0, fullName.length() - 1); } // 1.获取剩余部分 int endIndex = fullName.lastIndexOf(File.separator); String leftPath = fullName.substring(0, endIndex == -1 ? 0 : endIndex); if (leftPath.length() > 1) { // 2.获取倒数第二个 return getLastFileName(leftPath); } else { return rootName; } } private static String getLastFileName(String fullName) { if (fullName.endsWith(File.separator)) { fullName = fullName.substring(0, fullName.length() - 1); } String newName = fullName; if (fullName.contains(File.separator)) { newName = fullName.substring(fullName.lastIndexOf(File.separator) + 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 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) { } } }