Spring资源拷贝至系统目录及文件压缩解压处理
文件压缩及解压工具包
package org.zpli.utils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class ZipUtils {
private static final int BUFFER_SIZE = 16 * 1024;
public static final Limit LIMIT_DEFAULT = maxSize(100L * 1024L * 1024L);
public static long MAX_SIZE_ENTRY_WHEN_SIZE_UNKNOWN = 10L * 1024L * 1024L;
public static void unzip(Path zipFile, Path destDir, Limit limit) throws IOException {
unzip(Files.newInputStream(zipFile), destDir, limit);
}
public static void unzip(InputStream inputStream, Path destDir, Limit limit) throws IOException {
if (limit == null) {
limit = LIMIT_DEFAULT;
}
limit = securityFile().and(limit);
if (!(inputStream instanceof BufferedInputStream)) {
inputStream = new BufferedInputStream(inputStream);
}
try (ZipInputStream in = new ZipInputStream(inputStream, StandardCharsets.UTF_8)) {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (!limit.test(entry)) {
entry = in.getNextEntry();
continue;
}
if (entry.isDirectory()) {
Path directory = destDir.resolve(entry.getName());
Files.createDirectories(directory);
} else {
Path f = destDir.resolve(entry.getName());
if (!limit.readEntry(entry, f, in)) {
Files.createDirectories(f.getParent());
Files.copy(in, f, StandardCopyOption.REPLACE_EXISTING);
}
}
entry = in.getNextEntry();
}
}
}
public static void zipFile(Path sourceFile, Path destFile) throws IOException {
try (ZipOutputStream out = new ZipOutputStream(
new BufferedOutputStream(Files.newOutputStream(destFile), BUFFER_SIZE),
StandardCharsets.UTF_8)) {
out.putNextEntry(new ZipEntry(sourceFile.getFileName().toString()));
Files.copy(sourceFile, out);
}
}
public static void zipDirectory(Path sourceDirectory, Path destFile) throws IOException {
try (ZipOutputStream out = new ZipOutputStream(
new BufferedOutputStream(Files.newOutputStream(destFile), BUFFER_SIZE), StandardCharsets.UTF_8)) {
doZip(out, sourceDirectory, "", true);
}
}
private static void doZip(ZipOutputStream out, Path f, String base, boolean root) throws IOException {
if (Files.isDirectory(f)) {
try (DirectoryStream<Path> dir = Files.newDirectoryStream(f)) {
if (root) {
for (Path p : dir) {
doZip(out, p, p.getFileName().toString(), false);
}
} else {
out.putNextEntry(new ZipEntry(base + "/"));
for (Path p : dir) {
doZip(out, p, base + "/" + p.getFileName().toString(), false);
}
}
}
} else {
out.putNextEntry(new ZipEntry(base));
Files.copy(f, out);
}
}
interface Limit {
default boolean readEntry(ZipEntry entry, Path dest, ZipInputStream in) throws IOException {
return false;
}
boolean test(ZipEntry t);
default Limit and(Limit other) {
Limit self = this;
return new Limit() {
@Override
public boolean test(ZipEntry entry) {
return self.test(entry) && other.test(entry);
}
@Override
public boolean readEntry(ZipEntry entry, Path dest, ZipInputStream in) throws IOException {
return self.readEntry(entry, dest, in) || other.readEntry(entry, dest, in);
}
};
}
}
public static Limit maxSize(long size) {
return new Limit() {
long v = 0;
@Override
public boolean readEntry(ZipEntry entry, Path dest, ZipInputStream in) throws IOException {
long s = entry.getSize();
if (s == -1) {
Files.createDirectories(dest.getParent());
int readLen = 0;
byte[] bytes = new byte[BUFFER_SIZE];
int i;
try (OutputStream out = Files.newOutputStream(dest)) {
while ((i = in.read(bytes)) != -1) {
readLen += i;
if (readLen > MAX_SIZE_ENTRY_WHEN_SIZE_UNKNOWN) {
throw new IllegalArgumentException(
"压缩包未确认长度的文件'" + entry.getName() + "'大小超过 "
+ MAX_SIZE_ENTRY_WHEN_SIZE_UNKNOWN);
}
out.write(bytes, 0, i);
}
}
v += readLen;
if (v > size) {
throw new IllegalArgumentException("压缩包大小超过 " + size);
}
return true;
}
return false;
}
@Override
public boolean test(ZipEntry e) {
if (!e.isDirectory()) {
long s = e.getSize();
if (s == -1) {
return true;
}
v += s;
if (v > size) {
throw new IllegalArgumentException("压缩包大小超过 " + size);
}
}
return true;
}
};
}
private static Limit securityFile() {
return e -> {
String name = e.getName();
if (name.contains("../")) {
throw new IllegalArgumentException("文件'" + name + "'为非法文件");
}
return true;
};
}
public static Limit excludeDirectory() {
return e -> {
if (e.isDirectory()) {
return false;
}
return Paths.get(e.getName()).getNameCount() <= 1;
};
}
public static Limit maxDirectoryDepth(int depth) {
return e -> {
if (e.isDirectory()) {
String name = e.getName();
int directoryCount = Paths.get(name).getNameCount();
if (directoryCount > depth) {
throw new IllegalArgumentException("目录'" + name + "'深度超过限制值:" + depth);
}
}
return true;
};
}
public static Limit maxFile(int count) {
return new Limit() {
long v = 0;
@Override
public boolean test(ZipEntry e) {
if (!e.isDirectory()) {
v++;
if (v > count) {
throw new IllegalArgumentException("压缩包文件数超过 " + count);
}
}
return true;
}
};
}
public static Limit maxDirectory(int count) {
return new Limit() {
long v = 0;
@Override
public boolean test(ZipEntry e) {
if (e.isDirectory()) {
v++;
if (v > count) {
throw new IllegalArgumentException("压缩包目录数超过 " + count);
}
}
return true;
}
};
}
}
资源包
提供方
package org.zpli.service;
import com.qq.qidian.frm.event.producer.service.EventPublishService;
import com.qq.qidian.frm.event.producer.service.SystemUserRequestContextInitHelper;
import com.qq.qidian.frm.util.ZipUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.Resource;
import org.springframework.util.FileSystemUtils;
@Component
@Slf4j
public class ResourceConfigUpload implements ApplicationListener<ApplicationReadyEvent> {
private static final String DATA_SOURCE_TOPIC = "upload.pageUrlType";
private static final String PAGES_TEMPLATE_PATH = "pages-template";
private static final String PAGES_TEMPLATE_RESOURCE = "classpath:pages-template";
private static final String DEST_ZIP = "tempZip";
private static final String DOT_ZIP = ".zip";
@Autowired
private ApplicationContext applicationContext;
@Autowired
private EventPublishService eventPublishService;
@Autowired
private SystemUserRequestContextInitHelper systemUserRequestContextInitHelper;
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
try {
systemUserRequestContextInitHelper.initApplicationContext();
uploadConfigZipFile();
} catch (Exception e) {
log.info("handle zip file error: {}", e.getMessage(), e);
} finally {
systemUserRequestContextInitHelper.cleanApplicationContext();
}
}
private void uploadConfigZipFile() throws IOException {
log.info("start uploadConfigZipFile...");
Path dirPath;
Path tempZipFile;
Throwable var3 = null;
InputStream inputStream = null;
try {
dirPath = Files.createTempDirectory(PAGES_TEMPLATE_PATH);
copy(PAGES_TEMPLATE_RESOURCE, dirPath);
tempZipFile = Files.createTempFile(DEST_ZIP, DOT_ZIP);
ZipUtils.zipDirectory(dirPath, tempZipFile);
inputStream = Files.newInputStream(tempZipFile);
byte[] bytes = convertInputStreamToBytes(inputStream);
String data = Base64.getEncoder().encodeToString(bytes);
log.info("published pages template data size: {}", data.length());
FileSystemUtils.deleteRecursively(dirPath);
FileSystemUtils.deleteRecursively(tempZipFile);
eventPublishService.createDoBoMessage(DATA_SOURCE_TOPIC, data);
} catch (Exception e) {
var3 = e;
log.info("handle config zip error: {}", e.getMessage(), e);
} finally {
if (inputStream != null) {
if (var3 != null) {
try {
inputStream.close();
} catch (Throwable var11) {
var3.addSuppressed(var11);
}
} else {
inputStream.close();
}
}
}
log.info("uploadConfigZipFile success!");
}
private void copy(String classpath, Path destDirectory) throws IOException {
val values = applicationContext.getResources(classpath);
Set<String> pathSet = new HashSet<>();
for (val value : values) {
pathSet.add(value.getURL().getPath());
}
String path = classpath + "/**";
val rs = applicationContext.getResources(path);
for (val resource : rs) {
val rPath = resource.getURL().getPath();
B:
for (String parentPath : pathSet) {
if (rPath.startsWith(parentPath)) {
copy(parentPath.length() + 1, resource, destDirectory);
break B;
}
}
}
}
private void copy(int prefixLenPlus1, Resource resource, Path destDir)
throws IOException {
if (!resource.isReadable()) {
return;
}
String relativePath = resource.getURL().getPath().substring(prefixLenPlus1);
val copyPath = destDir.resolve(relativePath);
Files.createDirectories(copyPath.getParent());
Files.copy(resource.getInputStream(), copyPath, StandardCopyOption.REPLACE_EXISTING);
}
private byte[] convertInputStreamToBytes(InputStream inputStream) throws IOException {
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
int available = inputStream.available();
byte[] buff = new byte[available];
int rc;
while ((rc = inputStream.read(buff, 0, available)) > 0) {
swapStream.write(buff, 0, rc);
}
return swapStream.toByteArray();
}
}
使用方
package org.zpli.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Maps;
import com.qq.qidian.cms.plugin.saas.constant.CmsCacheConstants;
import com.qq.qidian.cms.plugin.saas.filter.PageUrlData;
import com.qq.qidian.cms.plugin.saas.filter.PageUrlType;
import com.qq.qidian.cms.plugin.saas.filter.PageUrlTypeRepository;
import com.qq.qidian.frm.event.consumer.kafka.KafkaConsumerConfigurations;
import com.qq.qidian.frm.event.consumer.kafka.KafkaMessageReceiver;
import com.qq.qidian.frm.event.consumer.model.EventSubscribeRecord;
import com.qq.qidian.frm.module.cache.change.util.EntityChangeEventUtils;
import com.qq.qidian.frm.util.JsonUtils;
import com.qq.qidian.frm.util.TenantActionUtils;
import com.qq.qidian.frm.util.ZipUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.collections.CollectionUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileSystemUtils;
@Component
@Slf4j
@ConditionalOnProperty(value = "kafkaEnabled", havingValue = "true")
public class PageUrlTypeListener extends KafkaMessageReceiver {
private static final String UPLOAD = "upload";
private static final String PAGE_URL_JSON = "page-url.json";
private static final String PAGE_FTL = "page.ftl";
private static final String PAGE_CONFIG_JSON = "page-config.json";
private static final String PAGE_LAYOUT_HTML = "page-layout.html";
@Value("${kylin.systemConfigTenantId:-1}")
private Long systemConfigTenantId;
@Autowired
private PageUrlTypeRepository pageUrlTypeRepository;
@Autowired
private EntityManager entityManager;
@KafkaListener(topics = "do.pageUrlType",
id = "cms-reminder-pageUrlType-handler",
groupId = "#{kafkaConsumerConfigurations.getGroupId()}",
containerFactory = KafkaConsumerConfigurations.RETRABLE_MULTI_THREAD_LISTENER)
public void receive(ConsumerRecord<String, String> message) {
processMessage(message);
}
@Override
public List<String> getProcessedActions() {
return Arrays.asList(UPLOAD);
}
@Override
public void processBusinessLogic(EventSubscribeRecord record) {
String data = record.getEventBody();
log.info("PageUrlTypeUploadHandler PageUrlType processBusinessLogic received data is => {}", data);
try {
byte[] dataBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream inputStream = new ByteArrayInputStream(dataBytes);
Path pagesTemplateDir = Files.createTempDirectory("pages-template");
ZipUtils.unzip(inputStream, pagesTemplateDir, null);
Path pageUrlPath = pagesTemplateDir.resolve(PAGE_URL_JSON);
if (!Files.exists(pageUrlPath)) {
log.info("page-url.json is empty, skip logic");
return;
}
byte[] pageUrlBytes = Files.readAllBytes(pageUrlPath);
List<PageUrlType> pageUrlTypes = JsonUtils.getObjectMapper().readValue(pageUrlBytes, new TypeReference<>() {
});
if (CollectionUtils.isEmpty(pageUrlTypes)) {
log.info("pageUrlTypes is empty, skip logic");
return;
}
DirectoryStream<Path> dirPaths = Files.newDirectoryStream(pagesTemplateDir);
Map<String, PageUrlData> pageUrlDataMap = resolve(dirPaths);
for (PageUrlType x : pageUrlTypes) {
log.info("PageUrlType Name: {}", x.getName());
x.setPageData(pageUrlDataMap.get(x.getName()));
}
FileSystemUtils.deleteRecursively(pagesTemplateDir);
handleBusinessLogic(pageUrlTypes);
} catch (Exception e) {
log.error("PageUrlTypeUploadHandler Failed to parse PageUrlType: ", e);
}
}
public Map<String, PageUrlData> resolve(DirectoryStream<Path> dirPaths) throws IOException {
Map<String, PageUrlData> map = Maps.newHashMap();
for (Path path : dirPaths) {
PageUrlData value = new PageUrlData();
fillData(value, path);
map.put(value.getName(), value);
}
return map;
}
private void fillData(PageUrlData value, Path path) throws IOException {
if (!Files.isDirectory(path)) {
return;
}
String fileNameUpperCase = path.getFileName().toString().toUpperCase().replaceAll("-", "_");
value.setName(fileNameUpperCase);
var snapshotTemplateDataPath = path.resolve(PAGE_FTL);
if (Files.exists(snapshotTemplateDataPath)) {
String snapshotTemplateData = Files.readString(snapshotTemplateDataPath, StandardCharsets.UTF_8);
value.setSnapshotTemplateData(snapshotTemplateData);
}
var configPath = path.resolve(PAGE_CONFIG_JSON);
if (Files.exists(configPath)) {
String configData = Files.readString(configPath, StandardCharsets.UTF_8);
value.setPageConfigData(configData);
}
var layoutPath = path.resolve(PAGE_LAYOUT_HTML);
if (Files.exists(layoutPath)) {
String layoutData = Files.readString(layoutPath, StandardCharsets.UTF_8);
value.setPageLayoutData(layoutData);
}
log.info("PageUrlData: {}, {}, {}", value.getName(), value.getPageLayoutData(), value.getPageConfigData());
}
}