ZipReader.java 15 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
package com.hotent.file.util;

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 com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hotent.file.model.FileType;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;

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;

/**
 * 压缩文件读取
 *
 * @company 广州宏天软件股份有限公司
 * @author heyifan
 * @email heyf@jee-soft.cn
 * @date 2018年9月30日
 */
@SuppressWarnings(value = "unchecked")
@Component
public class ZipReader {
	static Pattern pattern = Pattern.compile("^\\d+");

	@Autowired
	FileUtils fileUtils;
	@Value("${file.file.dir}")
	String fileDir;
	ExecutorService executors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

	/**
	 * 读取压缩文件
	 * 文件压缩到统一目录fileDir下,并且命名使用压缩文件名+文件名因为文件名
	 * 可能会重复(在系统中对于同一种类型的材料压缩文件内的文件是一样的,如果文件名
	 * 重复,那么这里会被覆盖[同一个压缩文件中的不同目录中的相同文件名暂时不考虑])
	 * <b>注:</b>
	 * <p>
	 *     文件名命名中的参数的说明:
	 *     1.archiveName,为避免解压的文件中有重名的文件会彼此覆盖,所以加上了archiveName,因为在ufile中archiveName
	 *     是不会重复的。
	 *     2.level,这里层级结构的列表我是通过一个map来构造的,map的key是文件的名字,值是对应的文件,这样每次向map中
	 *     加入节点的时候都会获取父节点是否存在,存在则会获取父节点的value并将当前节点加入到父节点的childList中(这里利用
	 *     的是java语言的引用的特性)。
	 * </p>
	 * @param filePath
	 */
	public String readZipFile(String filePath,String fileKey) {
		fileDir=filePath.substring(0,filePath.lastIndexOf(File.separator)+1);
		String archiveSeparator = "/";
		Map<String, FileNode> appender = Maps.newHashMap();
		List<String> imgUrls=new ArrayList<String>();
		String archiveFileName = fileUtils.getFileNameFromPath(filePath);
		try (ZipFile zipFile = new ZipFile(filePath, fileUtils.getFileEncodeUTFGBK(filePath));){
			Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
			// 排序
			entries = sortZipEntries(entries);
			List<Map<String, ZipArchiveEntry>> entriesToBeExtracted = Lists.newArrayList();
			while (entries.hasMoreElements()){
				ZipArchiveEntry entry = entries.nextElement();
				String fullName = entry.getName();
				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=fileUtils.typeFromUrl(childName);
				if (type.equals(FileType.picture)){//添加图片文件到图片列表
					imgUrls.add(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));
			return new ObjectMapper().writeValueAsString(appender.get(""));
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 排序zipEntries(对原来列表倒序)
	 * @param entries
	 */
	private Enumeration<ZipArchiveEntry> sortZipEntries(Enumeration<ZipArchiveEntry> entries) {
		List<ZipArchiveEntry> sortedEntries = Lists.newArrayList();
		while(entries.hasMoreElements()){
			sortedEntries.add(entries.nextElement());
		}
		Collections.sort(sortedEntries, Comparator.comparingInt(o -> o.getName().length()));
		return Collections.enumeration(sortedEntries);
	}

	@SuppressWarnings("rawtypes")
	public String unRar(String filePath,String fileKey){
		Map<String, FileNode> appender = Maps.newHashMap();
		List imgUrls=Lists.newArrayList();
		String baseUrl= (String) RequestContextHolder.currentRequestAttributes().getAttribute("baseUrl",0);
		try (Archive archive = new Archive(new File(filePath));){
			List<FileHeader> headers = archive.getFileHeaders();
			headers = sortedHeaders(headers);
			String archiveFileName = fileUtils.getFileNameFromPath(filePath);
			List<Map<String, FileHeader>> headersToBeExtracted = Lists.newArrayList();
			for (FileHeader header : headers) {
				String fullName;
				if (header.isUnicode()) {
					fullName = header.getFileNameW();
				}else {
					fullName = header.getFileNameString();
				}
				// 展示名
				String originName = getLastFileName(fullName, "\\");
				String childName = originName;
				boolean directory = header.isDirectory();
				if (!directory) {
					childName = archiveFileName + "_" + originName;
					headersToBeExtracted.add(Collections.singletonMap(childName, header));
				}
				String parentName = getLast2FileName(fullName, "\\", archiveFileName);
				FileType type=fileUtils.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 RarExtractorWorker(headersToBeExtracted, archive, filePath));
			return new ObjectMapper().writeValueAsString(appender.get(""));
		} catch (RarException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private void addNodes(Map<String, FileNode> appender, String parentName, FileNode node) {
		if (appender.containsKey(parentName)) {
			appender.get(parentName).getChildList().add(node);
			Collections.sort(appender.get(parentName).getChildList(), sortComparator);
		}else { 
			// 根节点
			FileNode nodeRoot = new FileNode(parentName, parentName, "", new ArrayList<>(), true);
			nodeRoot.getChildList().add(node);
			appender.put("", nodeRoot);
			appender.put(parentName, nodeRoot);
		}
	}

	private List<FileHeader> sortedHeaders(List<FileHeader> headers) {
		List<FileHeader> sortedHeaders = new ArrayList<>();
		Map<Integer, FileHeader> mapHeaders = new TreeMap<>();
		headers.forEach(header -> mapHeaders.put(header.getFileNameW().length(), header));
		for (Map.Entry<Integer, FileHeader> entry : mapHeaders.entrySet()){
			for (FileHeader header : headers) {
				if (entry.getKey().intValue() == header.getFileNameW().length()) {
					sortedHeaders.add(header);
				}
			}
		}
		return sortedHeaders;
	}

	/**
	 * 获取倒数第二个文件(夹)名
	 * @param fullName
	 * @param seperator
	 *       压缩文件解压后,不同的压缩格式分隔符不一样zip是/,而rar是\
	 * @param rootName
	 *      根目录名:如果倒数第二个路径为空,那么赋值为rootName
	 * @return
	 */
	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 (null != leftPath && leftPath.length() > 1) {
			// 2.获取倒数第二个
			return getLastFileName(leftPath, seperator);
		}else {
			return rootName;
		}
	}

	/**
	 * 获取最后一个文件(夹)的名字
	 * @param fullName
	 * @param seperator
	 *       压缩文件解压后,不同的压缩格式分隔符不一样zip是/,而rar是\
	 * @return
	 */
	private static String getLastFileName(String fullName, String seperator) {
		if (fullName.endsWith(seperator)) {
			fullName = fullName.substring(0, fullName.length()-1);
		}
		String newName = fullName;
		if (null != fullName && fullName.contains(seperator)) {
			newName = fullName.substring(fullName.lastIndexOf(seperator) + 1);
		}
		return newName;
	}

	public static Comparator<FileNode> sortComparator = new Comparator<FileNode>() {
		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 class FileNode{

		private String originName;
		private String fileName;
		private String parentFileName;
		private boolean directory;
		private String fileKey;//用于图片预览时寻址
		private List<FileNode> childList;

		public FileNode() {
		}

		public FileNode(String originName, String fileName, String parentFileName, List<FileNode> 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<FileNode> 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<FileNode> getChildList() {
			return childList;
		}

		public void setChildList(List<FileNode> 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;
		}
	}

	/**
	 * Zip文件抽取线程
	 */
	class ZipExtractorWorker implements Runnable {

		private List<Map<String, ZipArchiveEntry>> entriesToBeExtracted;
		private ZipFile zipFile;
		private String filePath;

		public ZipExtractorWorker(List<Map<String, ZipArchiveEntry>> entriesToBeExtracted, ZipFile zipFile, String filePath) {
			this.entriesToBeExtracted = entriesToBeExtracted;
			this.zipFile = zipFile;
			this.filePath = filePath;
		}

		@Override
		public void run() {
			System.out.println("解析压缩文件开始《《《《《《《《《《《《《《《《《《《《《《《");
			for (Map<String, ZipArchiveEntry> 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();
			}
			if (new File(filePath).exists()) {
				new File(filePath).delete();
			}
			System.out.println("解析压缩文件结束《《《《《《《《《《《《《《《《《《《《《《《");
		}


		/**
		 * 读取压缩文件并写入到fileDir文件夹下
		 * @param childName
		 * @param zipFile
		 */
		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 (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Rar文件抽取
	 */
	class RarExtractorWorker implements Runnable {
		private List<Map<String, FileHeader>> headersToBeExtracted;
		private Archive archive;
		/**
		 * 用以删除源文件
		 */
		private String filePath;

		public RarExtractorWorker(List<Map<String, FileHeader>> headersToBeExtracted, Archive archive, String filePath) {
			this.headersToBeExtracted = headersToBeExtracted;
			this.archive = archive;
			this.filePath = filePath;
		}

		@Override
		public void run() {
			System.out.println("解析压缩文件开始《《《《《《《《《《《《《《《《《《《《《《《");
			for (Map<String, FileHeader> entryMap : headersToBeExtracted) {
				String childName = entryMap.keySet().iterator().next();
				extractRarFile(childName, entryMap.values().iterator().next(), archive);
			}
			try {
				archive.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (new File(filePath).exists()) {
				new File(filePath).delete();
			}
			System.out.println("解析压缩文件结束《《《《《《《《《《《《《《《《《《《《《《《");
		}

		/**
		 * 抽取rar文件到指定目录下
		 * @param childName
		 * @param header
		 * @param archive
		 */
		private void extractRarFile(String childName, FileHeader header, Archive archive) {
			String outPath = fileDir + childName;
			try(OutputStream ot = new FileOutputStream(outPath)) {
				archive.extractFile(header, ot);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (RarException e) {
				e.printStackTrace();
			}
		}
	}
}