组态新增压缩包文件上传下载功能
This commit is contained in:
@@ -5,7 +5,7 @@ import lombok.Data;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import javax.validation.constraints.NotNull;
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类的介绍:
|
* 类的介绍:
|
||||||
@@ -35,11 +35,13 @@ public class ElementParam {
|
|||||||
private String elementMark;
|
private String elementMark;
|
||||||
|
|
||||||
@ApiModelProperty(value = "图元文件")
|
@ApiModelProperty(value = "图元文件")
|
||||||
@NotNull(message="图元文件不能为空!")
|
|
||||||
private MultipartFile multipartFile;
|
private MultipartFile multipartFile;
|
||||||
|
|
||||||
@ApiModelProperty(value = "图元类型")
|
@ApiModelProperty(value = "图元类型")
|
||||||
@NotBlank(message="图元类型不能为空!")
|
@NotBlank(message="图元类型不能为空!")
|
||||||
private String elementForm;
|
private String elementForm;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "压缩包文件")
|
||||||
|
private MultipartFile zipFile;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,566 @@
|
|||||||
|
package com.njcn.cssystem.utils;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVG压缩包处理器
|
||||||
|
* 专门处理ZIP压缩包中的SVG文件
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SvgZipProcessor {
|
||||||
|
|
||||||
|
// SVG文件头检查
|
||||||
|
private static final String SVG_HEADER = "<?xml";
|
||||||
|
private static final String SVG_START_TAG = "<svg";
|
||||||
|
private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(
|
||||||
|
Arrays.asList(".svg", ".svgz")
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法1:验证并提取所有SVG文件
|
||||||
|
* 返回格式:Map<文件名, InputStream>
|
||||||
|
*/
|
||||||
|
public static Map<String, InputStream> extractValidSvgFiles(MultipartFile zipFile) throws IOException, InvalidSvgException {
|
||||||
|
Map<String, InputStream> svgFiles = new LinkedHashMap<>();
|
||||||
|
ZipArchiveInputStream zis = null;
|
||||||
|
ZipArchiveEntry entry = null;
|
||||||
|
try {
|
||||||
|
// 使用支持编码检测的ZipArchiveInputStream
|
||||||
|
zis = new ZipArchiveInputStream(zipFile.getInputStream(), "UTF-8", true);
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
while ((entry = zis.getNextZipEntry()) != null) {
|
||||||
|
if (!entry.isDirectory() && !isSvgFile(entry.getName())) {
|
||||||
|
throw new BusinessException("文件格式错误: " + entry.getName());
|
||||||
|
}
|
||||||
|
if (!entry.isDirectory() && isSvgFile(entry.getName())) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
int len;
|
||||||
|
while ((len = zis.read(buffer)) > 0) {
|
||||||
|
baos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
byte[] fileData = baos.toByteArray();
|
||||||
|
String fileName = entry.getName();
|
||||||
|
// 验证SVG文件
|
||||||
|
if (validateSvgFile(fileName, fileData)) {
|
||||||
|
ByteArrayInputStream svgStream = new ByteArrayInputStream(fileData);
|
||||||
|
svgFiles.put(fileName, svgStream);
|
||||||
|
} else {
|
||||||
|
throw new InvalidSvgException("无效的SVG文件: " + fileName);
|
||||||
|
}
|
||||||
|
baos.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
throw new BusinessException("文件格式错误: " + entry.getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("处理ZIP文件时出错", e);
|
||||||
|
} finally {
|
||||||
|
// 确保流被关闭
|
||||||
|
if (zis != null) {
|
||||||
|
try {
|
||||||
|
zis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// 记录日志但不影响主要逻辑
|
||||||
|
System.err.println("关闭ZipArchiveInputStream时出错: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (svgFiles.isEmpty()) {
|
||||||
|
throw new InvalidSvgException("压缩包中没有找到有效的SVG文件");
|
||||||
|
}
|
||||||
|
return svgFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法2:流式处理SVG文件
|
||||||
|
*/
|
||||||
|
public void processSvgFilesStreaming(MultipartFile zipFile,
|
||||||
|
BiConsumer<String, InputStream> processor)
|
||||||
|
throws IOException, InvalidSvgException {
|
||||||
|
|
||||||
|
List<String> invalidFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) {
|
||||||
|
ZipEntry entry;
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
if (!entry.isDirectory() && isSvgFile(entry.getName())) {
|
||||||
|
|
||||||
|
// 读取并验证
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
int len;
|
||||||
|
while ((len = zis.read(buffer)) > 0) {
|
||||||
|
baos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fileData = baos.toByteArray();
|
||||||
|
|
||||||
|
if (validateSvgFile(entry.getName(), fileData)) {
|
||||||
|
ByteArrayInputStream svgStream = new ByteArrayInputStream(fileData);
|
||||||
|
processor.accept(entry.getName(), svgStream);
|
||||||
|
svgStream.close();
|
||||||
|
} else {
|
||||||
|
invalidFiles.add(entry.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
baos.close();
|
||||||
|
}
|
||||||
|
zis.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有无效文件,抛出异常
|
||||||
|
if (!invalidFiles.isEmpty()) {
|
||||||
|
throw new InvalidSvgException("发现无效的SVG文件: " + invalidFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法3:严格的SVG验证和处理
|
||||||
|
*/
|
||||||
|
public List<SvgFileInfo> processAndValidateSvgFiles(MultipartFile zipFile,
|
||||||
|
ValidationOptions options)
|
||||||
|
throws IOException, InvalidSvgException {
|
||||||
|
|
||||||
|
List<SvgFileInfo> svgFiles = new ArrayList<>();
|
||||||
|
List<String> validationErrors = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) {
|
||||||
|
ZipEntry entry;
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
String fileName = entry.getName();
|
||||||
|
|
||||||
|
// 跳过目录和非SVG文件
|
||||||
|
if (entry.isDirectory() || !isSvgFile(fileName)) {
|
||||||
|
if (!entry.isDirectory() && options.isStrictMode()) {
|
||||||
|
validationErrors.add("非SVG文件: " + fileName);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取文件内容
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
int len;
|
||||||
|
while ((len = zis.read(buffer)) > 0) {
|
||||||
|
baos.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fileData = baos.toByteArray();
|
||||||
|
|
||||||
|
// 执行验证
|
||||||
|
ValidationResult result = validateSvgWithDetails(fileName, fileData, options);
|
||||||
|
|
||||||
|
if (result.isValid()) {
|
||||||
|
SvgFileInfo svgInfo = new SvgFileInfo(
|
||||||
|
fileName,
|
||||||
|
new ByteArrayInputStream(fileData),
|
||||||
|
fileData.length,
|
||||||
|
entry.getTime(),
|
||||||
|
result.getSvgAttributes()
|
||||||
|
);
|
||||||
|
svgFiles.add(svgInfo);
|
||||||
|
} else {
|
||||||
|
validationErrors.add(fileName + ": " + result.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
baos.close();
|
||||||
|
zis.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查验证结果
|
||||||
|
if (!validationErrors.isEmpty()) {
|
||||||
|
throw new InvalidSvgException("SVG验证失败:\n" +
|
||||||
|
String.join("\n", validationErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svgFiles.isEmpty()) {
|
||||||
|
throw new InvalidSvgException("压缩包中没有有效的SVG文件");
|
||||||
|
}
|
||||||
|
|
||||||
|
return svgFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法4:仅获取SVG文件名
|
||||||
|
*/
|
||||||
|
public List<String> getSvgFileNames(MultipartFile zipFile) throws IOException {
|
||||||
|
List<String> svgNames = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) {
|
||||||
|
ZipEntry entry;
|
||||||
|
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
if (!entry.isDirectory() && isSvgFile(entry.getName())) {
|
||||||
|
svgNames.add(entry.getName());
|
||||||
|
}
|
||||||
|
zis.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return svgNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证是否为SVG文件(基于扩展名)
|
||||||
|
*/
|
||||||
|
public static boolean isSvgFile(String fileName) {
|
||||||
|
if (fileName == null) return false;
|
||||||
|
|
||||||
|
String lowerName = fileName.toLowerCase();
|
||||||
|
return ALLOWED_EXTENSIONS.stream()
|
||||||
|
.anyMatch(lowerName::endsWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基本的SVG文件验证
|
||||||
|
*/
|
||||||
|
private static boolean validateSvgFile(String fileName, byte[] fileData) {
|
||||||
|
try {
|
||||||
|
// 转换为字符串进行检查
|
||||||
|
String content = new String(fileData, StandardCharsets.UTF_8);
|
||||||
|
content = content.trim();
|
||||||
|
|
||||||
|
// 检查文件大小
|
||||||
|
if (fileData.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含XML声明和SVG标签
|
||||||
|
boolean hasXmlHeader = content.startsWith(SVG_HEADER);
|
||||||
|
boolean hasSvgTag = content.contains(SVG_START_TAG);
|
||||||
|
|
||||||
|
// 简单验证:要么有XML声明+SVG标签,要么直接以<svg开头
|
||||||
|
if ((hasXmlHeader && hasSvgTag) || content.startsWith(SVG_START_TAG)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于压缩的SVGZ文件,尝试解压验证
|
||||||
|
if (fileName.toLowerCase().endsWith(".svgz")) {
|
||||||
|
// 这里可以添加SVGZ解压验证逻辑
|
||||||
|
return true; // 暂时返回true,实际使用时需要解压验证
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详细的SVG验证
|
||||||
|
*/
|
||||||
|
private ValidationResult validateSvgWithDetails(String fileName, byte[] fileData,
|
||||||
|
ValidationOptions options) {
|
||||||
|
ValidationResult result = new ValidationResult(fileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String content = new String(fileData, StandardCharsets.UTF_8).trim();
|
||||||
|
|
||||||
|
// 1. 检查文件大小
|
||||||
|
if (fileData.length > options.getMaxFileSize()) {
|
||||||
|
result.setError("文件大小超过限制: " + fileData.length + " bytes");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileData.length == 0) {
|
||||||
|
result.setError("文件为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查基本结构
|
||||||
|
boolean hasXmlHeader = content.startsWith(SVG_HEADER);
|
||||||
|
boolean hasSvgTag = content.contains(SVG_START_TAG);
|
||||||
|
|
||||||
|
if (!hasSvgTag) {
|
||||||
|
result.setError("缺少SVG标签");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 如果开启了XML验证,进行严格的XML解析
|
||||||
|
if (options.isValidateXml()) {
|
||||||
|
if (!isValidXml(content)) {
|
||||||
|
result.setError("无效的XML结构");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取SVG属性
|
||||||
|
Map<String, String> attributes = extractSvgAttributes(content);
|
||||||
|
result.setSvgAttributes(attributes);
|
||||||
|
|
||||||
|
// 检查SVG尺寸
|
||||||
|
if (options.isCheckDimensions()) {
|
||||||
|
String width = attributes.get("width");
|
||||||
|
String height = attributes.get("height");
|
||||||
|
|
||||||
|
if (width != null && height != null) {
|
||||||
|
try {
|
||||||
|
// 移除单位(px, em, %等)
|
||||||
|
width = width.replaceAll("[^0-9.]", "");
|
||||||
|
height = height.replaceAll("[^0-9.]", "");
|
||||||
|
|
||||||
|
double w = Double.parseDouble(width);
|
||||||
|
double h = Double.parseDouble(height);
|
||||||
|
|
||||||
|
if (w > options.getMaxWidth() || h > options.getMaxHeight()) {
|
||||||
|
result.setError(String.format("尺寸过大: %.0fx%.0f", w, h));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w < options.getMinWidth() || h < options.getMinHeight()) {
|
||||||
|
result.setError(String.format("尺寸过小: %.0fx%.0f", w, h));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 如果无法解析尺寸,可能是使用百分比等单位
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 检查是否有潜在危险元素(安全考虑)
|
||||||
|
if (options.isCheckSecurity()) {
|
||||||
|
if (containsDangerousElements(content)) {
|
||||||
|
result.setError("包含潜在危险元素");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setValid(true);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.setError("验证异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为有效的XML
|
||||||
|
*/
|
||||||
|
private boolean isValidXml(String content) {
|
||||||
|
try {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
|
||||||
|
// 安全配置,防止XXE攻击
|
||||||
|
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
|
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||||
|
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||||
|
factory.setXIncludeAware(false);
|
||||||
|
factory.setExpandEntityReferences(false);
|
||||||
|
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
builder.parse(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
return true;
|
||||||
|
} catch (ParserConfigurationException | SAXException | IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取SVG属性
|
||||||
|
*/
|
||||||
|
private Map<String, String> extractSvgAttributes(String content) {
|
||||||
|
Map<String, String> attributes = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int startIndex = content.indexOf("<svg");
|
||||||
|
if (startIndex == -1) return attributes;
|
||||||
|
|
||||||
|
int endIndex = content.indexOf('>', startIndex);
|
||||||
|
if (endIndex == -1) return attributes;
|
||||||
|
|
||||||
|
String svgTag = content.substring(startIndex, endIndex);
|
||||||
|
|
||||||
|
// 简单解析属性
|
||||||
|
String[] parts = svgTag.split("\\s+");
|
||||||
|
for (String part : parts) {
|
||||||
|
if (part.contains("=")) {
|
||||||
|
String[] keyValue = part.split("=", 2);
|
||||||
|
if (keyValue.length == 2) {
|
||||||
|
String key = keyValue[0].trim();
|
||||||
|
String value = keyValue[1].trim().replaceAll("[\"']", "");
|
||||||
|
attributes.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 解析失败,返回空map
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否包含危险元素
|
||||||
|
*/
|
||||||
|
private boolean containsDangerousElements(String content) {
|
||||||
|
String lowerContent = content.toLowerCase();
|
||||||
|
|
||||||
|
// 检查潜在的脚本标签
|
||||||
|
if (lowerContent.contains("<script") ||
|
||||||
|
lowerContent.contains("javascript:") ||
|
||||||
|
lowerContent.contains("onload=") ||
|
||||||
|
lowerContent.contains("onclick=") ||
|
||||||
|
lowerContent.contains("xlink:href=")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件扩展名
|
||||||
|
*/
|
||||||
|
public String getFileExtension(String fileName) {
|
||||||
|
int dotIndex = fileName.lastIndexOf('.');
|
||||||
|
return dotIndex > 0 ? fileName.substring(dotIndex + 1).toLowerCase() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============== 内部类和异常 ===============
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SVG文件信息类
|
||||||
|
*/
|
||||||
|
public class SvgFileInfo {
|
||||||
|
private final String fileName;
|
||||||
|
private final InputStream inputStream;
|
||||||
|
private final long fileSize;
|
||||||
|
private final long lastModified;
|
||||||
|
private final Map<String, String> svgAttributes;
|
||||||
|
|
||||||
|
public SvgFileInfo(String fileName, InputStream inputStream, long fileSize,
|
||||||
|
long lastModified, Map<String, String> svgAttributes) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.fileSize = fileSize;
|
||||||
|
this.lastModified = lastModified;
|
||||||
|
this.svgAttributes = svgAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
public String getFileName() { return fileName; }
|
||||||
|
public InputStream getInputStream() { return inputStream; }
|
||||||
|
public long getFileSize() { return fileSize; }
|
||||||
|
public long getLastModified() { return lastModified; }
|
||||||
|
public Map<String, String> getSvgAttributes() { return svgAttributes; }
|
||||||
|
|
||||||
|
public String getBaseName() {
|
||||||
|
int dotIndex = fileName.lastIndexOf('.');
|
||||||
|
return dotIndex > 0 ? fileName.substring(0, dotIndex) : fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtension() {
|
||||||
|
return getFileExtension(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证选项类
|
||||||
|
*/
|
||||||
|
public class ValidationOptions {
|
||||||
|
private boolean strictMode = true; // 严格模式,不允许非SVG文件
|
||||||
|
private boolean validateXml = true; // 验证XML结构
|
||||||
|
private boolean checkDimensions = true; // 检查尺寸
|
||||||
|
private boolean checkSecurity = true; // 安全检查
|
||||||
|
private long maxFileSize = 10 * 1024 * 1024; // 最大文件大小(10MB)
|
||||||
|
private double maxWidth = 5000; // 最大宽度
|
||||||
|
private double maxHeight = 5000; // 最大高度
|
||||||
|
private double minWidth = 10; // 最小宽度
|
||||||
|
private double minHeight = 10; // 最小高度
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public boolean isStrictMode() { return strictMode; }
|
||||||
|
public void setStrictMode(boolean strictMode) { this.strictMode = strictMode; }
|
||||||
|
|
||||||
|
public boolean isValidateXml() { return validateXml; }
|
||||||
|
public void setValidateXml(boolean validateXml) { this.validateXml = validateXml; }
|
||||||
|
|
||||||
|
public boolean isCheckDimensions() { return checkDimensions; }
|
||||||
|
public void setCheckDimensions(boolean checkDimensions) { this.checkDimensions = checkDimensions; }
|
||||||
|
|
||||||
|
public boolean isCheckSecurity() { return checkSecurity; }
|
||||||
|
public void setCheckSecurity(boolean checkSecurity) { this.checkSecurity = checkSecurity; }
|
||||||
|
|
||||||
|
public long getMaxFileSize() { return maxFileSize; }
|
||||||
|
public void setMaxFileSize(long maxFileSize) { this.maxFileSize = maxFileSize; }
|
||||||
|
|
||||||
|
public double getMaxWidth() { return maxWidth; }
|
||||||
|
public void setMaxWidth(double maxWidth) { this.maxWidth = maxWidth; }
|
||||||
|
|
||||||
|
public double getMaxHeight() { return maxHeight; }
|
||||||
|
public void setMaxHeight(double maxHeight) { this.maxHeight = maxHeight; }
|
||||||
|
|
||||||
|
public double getMinWidth() { return minWidth; }
|
||||||
|
public void setMinWidth(double minWidth) { this.minWidth = minWidth; }
|
||||||
|
|
||||||
|
public double getMinHeight() { return minHeight; }
|
||||||
|
public void setMinHeight(double minHeight) { this.minHeight = minHeight; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证结果类
|
||||||
|
*/
|
||||||
|
public class ValidationResult {
|
||||||
|
private final String fileName;
|
||||||
|
private boolean valid;
|
||||||
|
private String errorMessage;
|
||||||
|
private Map<String, String> svgAttributes = new HashMap<>();
|
||||||
|
|
||||||
|
public ValidationResult(String fileName) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public String getFileName() { return fileName; }
|
||||||
|
public boolean isValid() { return valid; }
|
||||||
|
public void setValid(boolean valid) { this.valid = valid; }
|
||||||
|
|
||||||
|
public String getErrorMessage() { return errorMessage; }
|
||||||
|
public void setError(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
this.valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getSvgAttributes() { return svgAttributes; }
|
||||||
|
public void setSvgAttributes(Map<String, String> svgAttributes) {
|
||||||
|
this.svgAttributes = svgAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义异常类
|
||||||
|
*/
|
||||||
|
public static class InvalidSvgException extends Exception {
|
||||||
|
public InvalidSvgException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidSvgException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,4 +66,23 @@ public class ElementController extends BaseController {
|
|||||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@PostMapping("/addByZip")
|
||||||
|
@ApiOperation("压缩包新增图元")
|
||||||
|
public HttpResult<List<CsElement>> addByZip(@Validated ElementParam param){
|
||||||
|
String methodDescribe = getMethodDescribe("addByZip");
|
||||||
|
List<CsElement> list = csElementService.addByZip(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@PostMapping("/downloadByZip")
|
||||||
|
@ApiOperation("压缩包下载图元")
|
||||||
|
@ApiImplicitParam(name = "name", value = "组件分类名称", required = true)
|
||||||
|
public HttpResult downloadByZip(@RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response){
|
||||||
|
String methodDescribe = getMethodDescribe("downloadByZip");
|
||||||
|
HttpServletResponse resp = csElementService.downloadZip(name, request, response);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, resp, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
|||||||
import com.njcn.cssystem.pojo.param.ElementParam;
|
import com.njcn.cssystem.pojo.param.ElementParam;
|
||||||
import com.njcn.cssystem.pojo.po.CsElement;
|
import com.njcn.cssystem.pojo.po.CsElement;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,4 +34,11 @@ public interface IElementService extends IService<CsElement> {
|
|||||||
*/
|
*/
|
||||||
void deleteById(String id);
|
void deleteById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
List<CsElement> addByZip(ElementParam param);
|
||||||
|
|
||||||
|
HttpServletResponse downloadZip(String name, HttpServletRequest request, HttpServletResponse response);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,35 @@
|
|||||||
package com.njcn.cssystem.service.impl;
|
package com.njcn.cssystem.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.njcn.common.pojo.exception.BusinessException;
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
import com.njcn.cssystem.enums.CsSystemResponseEnum;
|
|
||||||
import com.njcn.cssystem.mapper.CsElementMapper;
|
import com.njcn.cssystem.mapper.CsElementMapper;
|
||||||
import com.njcn.cssystem.pojo.param.ElementParam;
|
import com.njcn.cssystem.pojo.param.ElementParam;
|
||||||
import com.njcn.cssystem.pojo.po.CsElement;
|
import com.njcn.cssystem.pojo.po.CsElement;
|
||||||
import com.njcn.cssystem.service.IElementService;
|
import com.njcn.cssystem.service.IElementService;
|
||||||
|
import com.njcn.cssystem.utils.SvgZipProcessor;
|
||||||
import com.njcn.oss.constant.OssPath;
|
import com.njcn.oss.constant.OssPath;
|
||||||
import com.njcn.oss.utils.FileStorageUtil;
|
import com.njcn.oss.utils.FileStorageUtil;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.Adler32;
|
||||||
|
import java.util.zip.CheckedOutputStream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -24,21 +39,23 @@ import java.util.Objects;
|
|||||||
* @author xuyang
|
* @author xuyang
|
||||||
* @since 2023-07-12
|
* @since 2023-07-12
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CsElementServiceImpl extends ServiceImpl<CsElementMapper, CsElement> implements IElementService {
|
public class CsElementServiceImpl extends ServiceImpl<CsElementMapper, CsElement> implements IElementService {
|
||||||
|
|
||||||
private final FileStorageUtil fileStorageUtil;
|
private final FileStorageUtil fileStorageUtil;
|
||||||
|
private final SvgZipProcessor svgZipProcessor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CsElement addElement(ElementParam param) {
|
public CsElement addElement(ElementParam param) {
|
||||||
CsElement po = this.lambdaQuery().eq(CsElement::getStatus,1)
|
// CsElement po = this.lambdaQuery().eq(CsElement::getStatus,1)
|
||||||
.eq(CsElement::getElementCode,param.getElementCode())
|
// .eq(CsElement::getElementCode,param.getElementCode())
|
||||||
.eq(CsElement::getElementName,param.getElementName())
|
// .eq(CsElement::getElementName,param.getElementName())
|
||||||
.eq(CsElement::getElementMark,param.getElementMark()).one();
|
// .eq(CsElement::getElementMark,param.getElementMark()).one();
|
||||||
if (!Objects.isNull(po)){
|
// if (!Objects.isNull(po)){
|
||||||
throw new BusinessException(CsSystemResponseEnum.SAME_DATA_ERROR);
|
// throw new BusinessException(CsSystemResponseEnum.SAME_DATA_ERROR);
|
||||||
}
|
// }
|
||||||
CsElement csElement = new CsElement();
|
CsElement csElement = new CsElement();
|
||||||
BeanUtils.copyProperties(param,csElement);
|
BeanUtils.copyProperties(param,csElement);
|
||||||
String path = fileStorageUtil.uploadMultipart(param.getMultipartFile(), OssPath.ELEMENT);
|
String path = fileStorageUtil.uploadMultipart(param.getMultipartFile(), OssPath.ELEMENT);
|
||||||
@@ -61,4 +78,85 @@ public class CsElementServiceImpl extends ServiceImpl<CsElementMapper, CsElement
|
|||||||
csElement.setStatus(0);
|
csElement.setStatus(0);
|
||||||
this.updateById(csElement);
|
this.updateById(csElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CsElement> addByZip(ElementParam param) {
|
||||||
|
List<CsElement> list = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
// 验证并提取SVG文件
|
||||||
|
Map<String, InputStream> svgFiles = SvgZipProcessor.extractValidSvgFiles(param.getZipFile());
|
||||||
|
log.info("提取到 {} 个SVG文件:", svgFiles.size());
|
||||||
|
svgFiles.forEach((name, stream) -> {
|
||||||
|
//获取文件名称
|
||||||
|
String[] arr = name.split("/");
|
||||||
|
//存储数据库、上传文件服务器
|
||||||
|
CsElement csElement = new CsElement();
|
||||||
|
BeanUtils.copyProperties(param,csElement);
|
||||||
|
String path = fileStorageUtil.uploadStreamSpecifyName(stream, OssPath.ELEMENT, arr[arr.length - 1]);
|
||||||
|
csElement.setPath(path);
|
||||||
|
csElement.setStatus(1);
|
||||||
|
csElement.setElementName(arr[arr.length - 1].split("\\.")[0]);
|
||||||
|
this.save(csElement);
|
||||||
|
csElement.setPath(path);
|
||||||
|
list.add(csElement);
|
||||||
|
});
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
throw new BusinessException(e.getMessage());
|
||||||
|
} catch (IOException | SvgZipProcessor.InvalidSvgException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpServletResponse downloadZip(String name, HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
LambdaQueryWrapper<CsElement> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(CsElement::getElementSonType,name).eq(CsElement::getStatus,1);
|
||||||
|
List<CsElement> list = this.list(wrapper);
|
||||||
|
if(CollectionUtils.isNotEmpty(list)) {
|
||||||
|
try {
|
||||||
|
Map<String, String> filePathMap = list.stream().filter(r -> StringUtils.isNotBlank(r.getPath())).collect(Collectors.toMap(CsElement::getId, CsElement::getPath));
|
||||||
|
// 创建临时文件
|
||||||
|
File zipFile = File.createTempFile(name, ".zip");
|
||||||
|
FileOutputStream f = new FileOutputStream(zipFile);
|
||||||
|
CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
|
||||||
|
// 用于将数据压缩成Zip文件格式
|
||||||
|
ZipOutputStream zos = new ZipOutputStream(csum);
|
||||||
|
for (Map.Entry<String, String> entry : filePathMap.entrySet()) {
|
||||||
|
CsElement csElement = this.getById(entry.getKey());
|
||||||
|
InputStream inputStream = fileStorageUtil.getFileStream(entry.getValue());
|
||||||
|
// 对于每一个要被存放到压缩包的文件,都必须调用ZipOutputStream对象的putNextEntry()方法,确保压缩包里面文件不同名
|
||||||
|
String path = csElement.getPath();
|
||||||
|
zos.putNextEntry(new ZipEntry(csElement.getElementName() + ".svg"));
|
||||||
|
int bytesRead = 0;
|
||||||
|
// 向压缩文件中输出数据
|
||||||
|
while ((bytesRead = inputStream.read()) != -1) {
|
||||||
|
zos.write(bytesRead);
|
||||||
|
}
|
||||||
|
inputStream.close();
|
||||||
|
zos.closeEntry(); // 当前文件写完,定位为写入下一条项目
|
||||||
|
}
|
||||||
|
zos.close();
|
||||||
|
InputStream fis = new BufferedInputStream(Files.newInputStream(zipFile.toPath()));
|
||||||
|
byte[] buffer = new byte[fis.available()];
|
||||||
|
fis.read(buffer);
|
||||||
|
fis.close();
|
||||||
|
// 清空response
|
||||||
|
response.reset();
|
||||||
|
// 设置response的Header
|
||||||
|
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(name + ".zip", "utf-8"));
|
||||||
|
response.addHeader("Content-Length", "" + zipFile.length());
|
||||||
|
OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
|
||||||
|
response.setContentType("application/octet-stream");
|
||||||
|
toClient.write(buffer);
|
||||||
|
toClient.flush();
|
||||||
|
toClient.close();
|
||||||
|
zipFile.delete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user