ZipReader.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. package cn.keking.utils;
  2. import cn.keking.config.ConfigConstants;
  3. import cn.keking.model.FileType;
  4. import com.fasterxml.jackson.core.JsonProcessingException;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import com.github.junrar.Archive;
  7. import com.github.junrar.exception.RarException;
  8. import com.github.junrar.rarfile.FileHeader;
  9. import com.google.common.collect.Lists;
  10. import com.google.common.collect.Maps;
  11. import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
  12. import org.apache.commons.compress.archivers.zip.ZipFile;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.stereotype.Component;
  15. import org.springframework.web.context.request.RequestContextHolder;
  16. import java.io.*;
  17. import java.math.BigDecimal;
  18. import java.text.CollationKey;
  19. import java.text.Collator;
  20. import java.util.*;
  21. import java.util.concurrent.ExecutorService;
  22. import java.util.concurrent.Executors;
  23. import java.util.regex.Matcher;
  24. import java.util.regex.Pattern;
  25. /**
  26. *
  27. * @author yudian-it
  28. * @date 2017/11/27
  29. */
  30. @Component
  31. public class ZipReader {
  32. static Pattern pattern = Pattern.compile("^\\d+");
  33. @Autowired
  34. FileUtils fileUtils;
  35. String fileDir = ConfigConstants.getFileDir();
  36. ExecutorService executors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
  37. /**
  38. * 读取压缩文件
  39. * 文件压缩到统一目录fileDir下,并且命名使用压缩文件名+文件名因为文件名
  40. * 可能会重复(在系统中对于同一种类型的材料压缩文件内的文件是一样的,如果文件名
  41. * 重复,那么这里会被覆盖[同一个压缩文件中的不同目录中的相同文件名暂时不考虑])
  42. * <b>注:</b>
  43. * <p>
  44. * 文件名命名中的参数的说明:
  45. * 1.archiveName,为避免解压的文件中有重名的文件会彼此覆盖,所以加上了archiveName,因为在ufile中archiveName
  46. * 是不会重复的。
  47. * 2.level,这里层级结构的列表我是通过一个map来构造的,map的key是文件的名字,值是对应的文件,这样每次向map中
  48. * 加入节点的时候都会获取父节点是否存在,存在则会获取父节点的value并将当前节点加入到父节点的childList中(这里利用
  49. * 的是java语言的引用的特性)。
  50. * </p>
  51. * @param filePath
  52. */
  53. public String readZipFile(String filePath,String fileKey) {
  54. String archiveSeparator = "/";
  55. Map<String, FileNode> appender = Maps.newHashMap();
  56. List imgUrls=Lists.newArrayList();
  57. String baseUrl= (String) RequestContextHolder.currentRequestAttributes().getAttribute("baseUrl",0);
  58. String archiveFileName = fileUtils.getFileNameFromPath(filePath);
  59. try {
  60. ZipFile zipFile = new ZipFile(filePath, fileUtils.getFileEncodeUTFGBK(filePath));
  61. Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
  62. // 排序
  63. entries = sortZipEntries(entries);
  64. List<Map<String, ZipArchiveEntry>> entriesToBeExtracted = Lists.newArrayList();
  65. while (entries.hasMoreElements()){
  66. ZipArchiveEntry entry = entries.nextElement();
  67. String fullName = entry.getName();
  68. int level = fullName.split(archiveSeparator).length;
  69. // 展示名
  70. String originName = getLastFileName(fullName, archiveSeparator);
  71. String childName = level + "_" + originName;
  72. boolean directory = entry.isDirectory();
  73. if (!directory) {
  74. childName = archiveFileName + "_" + originName;
  75. entriesToBeExtracted.add(Collections.singletonMap(childName, entry));
  76. }
  77. String parentName = getLast2FileName(fullName, archiveSeparator, archiveFileName);
  78. parentName = (level-1) + "_" + parentName;
  79. FileType type=fileUtils.typeFromUrl(childName);
  80. if (type.equals(FileType.picture)){//添加图片文件到图片列表
  81. imgUrls.add(baseUrl+childName);
  82. }
  83. FileNode node = new FileNode(originName, childName, parentName, new ArrayList<>(), directory,fileKey);
  84. addNodes(appender, parentName, node);
  85. appender.put(childName, node);
  86. }
  87. // 开启新的线程处理文件解压
  88. executors.submit(new ZipExtractorWorker(entriesToBeExtracted, zipFile, filePath));
  89. fileUtils.setRedisImgUrls(fileKey,imgUrls);
  90. return new ObjectMapper().writeValueAsString(appender.get(""));
  91. } catch (IOException e) {
  92. e.printStackTrace();
  93. return null;
  94. }
  95. }
  96. /**
  97. * 排序zipEntries(对原来列表倒序)
  98. * @param entries
  99. */
  100. private Enumeration<ZipArchiveEntry> sortZipEntries(Enumeration<ZipArchiveEntry> entries) {
  101. List<ZipArchiveEntry> sortedEntries = Lists.newArrayList();
  102. while(entries.hasMoreElements()){
  103. sortedEntries.add(entries.nextElement());
  104. }
  105. Collections.sort(sortedEntries, Comparator.comparingInt(o -> o.getName().length()));
  106. return Collections.enumeration(sortedEntries);
  107. }
  108. public String unRar(String filePath,String fileKey){
  109. Map<String, FileNode> appender = Maps.newHashMap();
  110. List imgUrls=Lists.newArrayList();
  111. String baseUrl= (String) RequestContextHolder.currentRequestAttributes().getAttribute("baseUrl",0);
  112. try {
  113. Archive archive = new Archive(new File(filePath));
  114. List<FileHeader> headers = archive.getFileHeaders();
  115. headers = sortedHeaders(headers);
  116. String archiveFileName = fileUtils.getFileNameFromPath(filePath);
  117. List<Map<String, FileHeader>> headersToBeExtracted = Lists.newArrayList();
  118. for (FileHeader header : headers) {
  119. String fullName;
  120. if (header.isUnicode()) {
  121. fullName = header.getFileNameW();
  122. }else {
  123. fullName = header.getFileNameString();
  124. }
  125. // 展示名
  126. String originName = getLastFileName(fullName, "\\");
  127. String childName = originName;
  128. boolean directory = header.isDirectory();
  129. if (!directory) {
  130. childName = archiveFileName + "_" + originName;
  131. headersToBeExtracted.add(Collections.singletonMap(childName, header));
  132. }
  133. String parentName = getLast2FileName(fullName, "\\", archiveFileName);
  134. FileType type=fileUtils.typeFromUrl(childName);
  135. if (type.equals(FileType.picture)){//添加图片文件到图片列表
  136. imgUrls.add(baseUrl+childName);
  137. }
  138. FileNode node = new FileNode(originName, childName, parentName, new ArrayList<>(), directory,fileKey);
  139. addNodes(appender, parentName, node);
  140. appender.put(childName, node);
  141. }
  142. executors.submit(new RarExtractorWorker(headersToBeExtracted, archive, filePath));
  143. fileUtils.setRedisImgUrls(fileKey,imgUrls);
  144. return new ObjectMapper().writeValueAsString(appender.get(""));
  145. } catch (RarException e) {
  146. e.printStackTrace();
  147. } catch (IOException e) {
  148. e.printStackTrace();
  149. }
  150. return null;
  151. }
  152. private void addNodes(Map<String, FileNode> appender, String parentName, FileNode node) {
  153. if (appender.containsKey(parentName)) {
  154. appender.get(parentName).getChildList().add(node);
  155. Collections.sort(appender.get(parentName).getChildList(), sortComparator);
  156. // appender.get(parentName).getChildList().sort((final FileNode h1, final FileNode h2) -> h1.getOriginName().compareTo(h2.getOriginName()));//排序
  157. }else { // 根节点
  158. FileNode nodeRoot = new FileNode(parentName, parentName, "", new ArrayList<>(), true);
  159. nodeRoot.getChildList().add(node);
  160. appender.put("", nodeRoot);
  161. appender.put(parentName, nodeRoot);
  162. }
  163. }
  164. private List<FileHeader> sortedHeaders(List<FileHeader> headers) {
  165. List<FileHeader> sortedHeaders = new ArrayList<>();
  166. Map<Integer, FileHeader> mapHeaders = new TreeMap<>();
  167. headers.forEach(header -> mapHeaders.put(header.getFileNameW().length(), header));
  168. for (Map.Entry<Integer, FileHeader> entry : mapHeaders.entrySet()){
  169. for (FileHeader header : headers) {
  170. if (entry.getKey().intValue() == header.getFileNameW().length()) {
  171. sortedHeaders.add(header);
  172. }
  173. }
  174. }
  175. return sortedHeaders;
  176. }
  177. /**
  178. * 获取倒数第二个文件(夹)名
  179. * @param fullName
  180. * @param seperator
  181. * 压缩文件解压后,不同的压缩格式分隔符不一样zip是/,而rar是\
  182. * @param rootName
  183. * 根目录名:如果倒数第二个路径为空,那么赋值为rootName
  184. * @return
  185. */
  186. private static String getLast2FileName(String fullName, String seperator, String rootName) {
  187. if (fullName.endsWith(seperator)) {
  188. fullName = fullName.substring(0, fullName.length()-1);
  189. }
  190. // 1.获取剩余部分
  191. int endIndex = fullName.lastIndexOf(seperator);
  192. String leftPath = fullName.substring(0, endIndex == -1 ? 0 : endIndex);
  193. if (null != leftPath && leftPath.length() > 1) {
  194. // 2.获取倒数第二个
  195. return getLastFileName(leftPath, seperator);
  196. }else {
  197. return rootName;
  198. }
  199. }
  200. /**
  201. * 获取最后一个文件(夹)的名字
  202. * @param fullName
  203. * @param seperator
  204. * 压缩文件解压后,不同的压缩格式分隔符不一样zip是/,而rar是\
  205. * @return
  206. */
  207. private static String getLastFileName(String fullName, String seperator) {
  208. if (fullName.endsWith(seperator)) {
  209. fullName = fullName.substring(0, fullName.length()-1);
  210. }
  211. String newName = fullName;
  212. if (null != fullName && fullName.contains(seperator)) {
  213. newName = fullName.substring(fullName.lastIndexOf(seperator) + 1);
  214. }
  215. return newName;
  216. }
  217. public static Comparator<FileNode> sortComparator = new Comparator<FileNode>() {
  218. Collator cmp = Collator.getInstance(Locale.US);
  219. @Override
  220. public int compare(FileNode o1, FileNode o2) {
  221. // 判断两个对比对象是否是开头包含数字,如果包含数字则获取数字并按数字真正大小进行排序
  222. BigDecimal num1,num2;
  223. if (null != (num1 = isStartNumber(o1))
  224. && null != (num2 = isStartNumber(o2))) {
  225. return num1.subtract(num2).intValue();
  226. }
  227. CollationKey c1 = cmp.getCollationKey(o1.getOriginName());
  228. CollationKey c2 = cmp.getCollationKey(o2.getOriginName());
  229. return cmp.compare(c1.getSourceString(), c2.getSourceString());
  230. }
  231. };
  232. private static BigDecimal isStartNumber(FileNode src) {
  233. Matcher matcher = pattern.matcher(src.getOriginName());
  234. if (matcher.find()) {
  235. return new BigDecimal(matcher.group());
  236. }
  237. return null;
  238. }
  239. /**
  240. * 文件节点(区分文件上下级)
  241. */
  242. public class FileNode{
  243. private String originName;
  244. private String fileName;
  245. private String parentFileName;
  246. private boolean directory;
  247. private String fileKey;//用于图片预览时寻址
  248. private List<FileNode> childList;
  249. public FileNode() {
  250. }
  251. public FileNode(String originName, String fileName, String parentFileName, List<FileNode> childList, boolean directory) {
  252. this.originName = originName;
  253. this.fileName = fileName;
  254. this.parentFileName = parentFileName;
  255. this.childList = childList;
  256. this.directory = directory;
  257. }
  258. public FileNode(String originName, String fileName, String parentFileName, List<FileNode> childList, boolean directory,String fileKey) {
  259. this.originName = originName;
  260. this.fileName = fileName;
  261. this.parentFileName = parentFileName;
  262. this.childList = childList;
  263. this.directory = directory;
  264. this.fileKey=fileKey;
  265. }
  266. public String getFileKey() {
  267. return fileKey;
  268. }
  269. public void setFileKey(String fileKey) {
  270. this.fileKey = fileKey;
  271. }
  272. public String getFileName() {
  273. return fileName;
  274. }
  275. public void setFileName(String fileName) {
  276. this.fileName = fileName;
  277. }
  278. public String getParentFileName() {
  279. return parentFileName;
  280. }
  281. public void setParentFileName(String parentFileName) {
  282. this.parentFileName = parentFileName;
  283. }
  284. public List<FileNode> getChildList() {
  285. return childList;
  286. }
  287. public void setChildList(List<FileNode> childList) {
  288. this.childList = childList;
  289. }
  290. @Override
  291. public String toString() {
  292. try {
  293. return new ObjectMapper().writeValueAsString(this);
  294. } catch (JsonProcessingException e) {
  295. e.printStackTrace();
  296. return "";
  297. }
  298. }
  299. public String getOriginName() {
  300. return originName;
  301. }
  302. public void setOriginName(String originName) {
  303. this.originName = originName;
  304. }
  305. public boolean isDirectory() {
  306. return directory;
  307. }
  308. public void setDirectory(boolean directory) {
  309. this.directory = directory;
  310. }
  311. }
  312. /**
  313. * Zip文件抽取线程
  314. */
  315. class ZipExtractorWorker implements Runnable {
  316. private List<Map<String, ZipArchiveEntry>> entriesToBeExtracted;
  317. private ZipFile zipFile;
  318. private String filePath;
  319. public ZipExtractorWorker(List<Map<String, ZipArchiveEntry>> entriesToBeExtracted, ZipFile zipFile, String filePath) {
  320. this.entriesToBeExtracted = entriesToBeExtracted;
  321. this.zipFile = zipFile;
  322. this.filePath = filePath;
  323. }
  324. @Override
  325. public void run() {
  326. System.out.println("解析压缩文件开始《《《《《《《《《《《《《《《《《《《《《《《");
  327. for (Map<String, ZipArchiveEntry> entryMap : entriesToBeExtracted) {
  328. String childName = entryMap.keySet().iterator().next();
  329. ZipArchiveEntry entry = entryMap.values().iterator().next();
  330. try {
  331. extractZipFile(childName, zipFile.getInputStream(entry));
  332. } catch (IOException e) {
  333. e.printStackTrace();
  334. }
  335. }
  336. try {
  337. zipFile.close();
  338. } catch (IOException e) {
  339. e.printStackTrace();
  340. }
  341. if (new File(filePath).exists()) {
  342. new File(filePath).delete();
  343. }
  344. System.out.println("解析压缩文件结束《《《《《《《《《《《《《《《《《《《《《《《");
  345. }
  346. /**
  347. * 读取压缩文件并写入到fileDir文件夹下
  348. * @param childName
  349. * @param zipFile
  350. */
  351. private void extractZipFile(String childName, InputStream zipFile) {
  352. String outPath = fileDir + childName;
  353. try (OutputStream ot = new FileOutputStream(outPath)){
  354. byte[] inByte = new byte[1024];
  355. int len;
  356. while ((-1 != (len = zipFile.read(inByte)))){
  357. ot.write(inByte, 0, len);
  358. }
  359. } catch (FileNotFoundException e) {
  360. e.printStackTrace();
  361. } catch (IOException e) {
  362. e.printStackTrace();
  363. }
  364. }
  365. }
  366. /**
  367. * Rar文件抽取
  368. */
  369. class RarExtractorWorker implements Runnable {
  370. private List<Map<String, FileHeader>> headersToBeExtracted;
  371. private Archive archive;
  372. /**
  373. * 用以删除源文件
  374. */
  375. private String filePath;
  376. public RarExtractorWorker(List<Map<String, FileHeader>> headersToBeExtracted, Archive archive, String filePath) {
  377. this.headersToBeExtracted = headersToBeExtracted;
  378. this.archive = archive;
  379. this.filePath = filePath;
  380. }
  381. @Override
  382. public void run() {
  383. System.out.println("解析压缩文件开始《《《《《《《《《《《《《《《《《《《《《《《");
  384. for (Map<String, FileHeader> entryMap : headersToBeExtracted) {
  385. String childName = entryMap.keySet().iterator().next();
  386. extractRarFile(childName, entryMap.values().iterator().next(), archive);
  387. }
  388. try {
  389. archive.close();
  390. } catch (IOException e) {
  391. e.printStackTrace();
  392. }
  393. if (new File(filePath).exists()) {
  394. new File(filePath).delete();
  395. }
  396. System.out.println("解析压缩文件结束《《《《《《《《《《《《《《《《《《《《《《《");
  397. }
  398. /**
  399. * 抽取rar文件到指定目录下
  400. * @param childName
  401. * @param header
  402. * @param archive
  403. */
  404. private void extractRarFile(String childName, FileHeader header, Archive archive) {
  405. String outPath = fileDir + childName;
  406. try(OutputStream ot = new FileOutputStream(outPath)) {
  407. archive.extractFile(header, ot);
  408. } catch (FileNotFoundException e) {
  409. e.printStackTrace();
  410. } catch (IOException e) {
  411. e.printStackTrace();
  412. } catch (RarException e) {
  413. e.printStackTrace();
  414. }
  415. }
  416. }
  417. }