fix
This commit is contained in:
@@ -167,8 +167,8 @@ RUN echo "=== 检查构建结果 ===" && \
|
||||
# 复用基础镜像,避免重复安装依赖
|
||||
FROM base
|
||||
|
||||
# 设置环境变量(优化内存使用和字体支持)
|
||||
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Djava.awt.headless=true -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
|
||||
# 设置环境变量(优化内存使用和字体支持,防止OOM)
|
||||
ENV JAVA_OPTS="-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/dumps/ -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/app/logs/gc.log -Djava.awt.headless=true -XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0 -XX:InitiatingHeapOccupancyPercent=45 -XX:+UseStringDeduplication -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
ENV TESSDATA_PREFIX=/usr/share/tessdata/
|
||||
ENV OCR_TESSPATH=/usr/bin/tesseract
|
||||
|
||||
@@ -12,6 +12,8 @@ services:
|
||||
- ./data/images:/app/data/images:rw
|
||||
- ./data/reports:/app/data/reports:rw
|
||||
- ./logs:/app/logs:rw
|
||||
# 添加内存转储目录
|
||||
- ./data/dumps:/app/dumps:rw
|
||||
user: "1001:1001" # 指定容器内用户ID,与Dockerfile中的app用户保持一致
|
||||
environment:
|
||||
- SPRING_PROFILES_ACTIVE=prod
|
||||
@@ -31,9 +33,26 @@ services:
|
||||
- SWAGGER_SHOW=false
|
||||
- LOG_ROOT_LEVEL=info
|
||||
- LOG_APP_LEVEL=info
|
||||
# JVM内存优化参数
|
||||
- JAVA_OPTS=-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/dumps/ -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/app/logs/gc.log -XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0 -XX:InitiatingHeapOccupancyPercent=45
|
||||
networks:
|
||||
- proxy
|
||||
restart: unless-stopped
|
||||
# 添加健康检查和资源限制
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9081/point-strategy/actuator/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 3G # 限制容器最大内存使用
|
||||
cpus: '2.0' # 限制CPU使用
|
||||
reservations:
|
||||
memory: 1G # 预留内存
|
||||
cpus: '1.0' # 预留CPU
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
|
||||
61
jvm-optimization.properties
Normal file
61
jvm-optimization.properties
Normal file
@@ -0,0 +1,61 @@
|
||||
# JVM内存优化配置文件
|
||||
# 用于防止OOM问题的JVM参数配置
|
||||
|
||||
# 基础内存设置
|
||||
-Xms1g # 初始堆内存大小
|
||||
-Xmx2g # 最大堆内存大小
|
||||
-XX:NewRatio=1 # 年轻代与老年代比例
|
||||
-XX:SurvivorRatio=8 # Eden与Survivor区比例
|
||||
|
||||
# 垃圾收集器优化
|
||||
-XX:+UseG1GC # 使用G1垃圾收集器
|
||||
-XX:MaxGCPauseMillis=200 # 最大GC暂停时间目标
|
||||
-XX:G1HeapRegionSize=16m # G1区域大小
|
||||
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发GC的堆占用率
|
||||
|
||||
# OOM预防
|
||||
-XX:+HeapDumpOnOutOfMemoryError # OOM时生成堆转储
|
||||
-XX:HeapDumpPath=/app/dumps/ # 堆转储文件路径
|
||||
-XX:+UseGCLogFileRotation # GC日志轮转
|
||||
-XX:NumberOfGCLogFiles=5 # 保留GC日志文件数量
|
||||
-XX:GCLogFileSize=10M # 单个GC日志文件大小
|
||||
|
||||
# 容器环境优化
|
||||
-XX:+UseContainerSupport # 启用容器支持
|
||||
-XX:MaxRAMPercentage=80.0 # 最大使用容器80%内存
|
||||
-XX:+UnlockExperimentalVMOptions # 解锁实验性VM选项
|
||||
-XX:+UseCGroupMemoryLimitForHeap # 使用cgroup内存限制
|
||||
|
||||
# 字符串优化
|
||||
-XX:+UseStringDeduplication # 启用字符串去重
|
||||
-XX:StringTableSize=200000 # 字符串表大小
|
||||
|
||||
# 类加载优化
|
||||
-XX:+UseCompressedOops # 压缩对象指针
|
||||
-XX:+UseCompressedClassPointers # 压缩类指针
|
||||
|
||||
# 监控和日志
|
||||
-XX:+PrintGCDetails # 打印GC详细信息
|
||||
-XX:+PrintGCTimeStamps # 打印GC时间戳
|
||||
-XX:+PrintGCApplicationStoppedTime # 打印GC暂停时间
|
||||
-Xloggc:/app/logs/gc.log # GC日志文件路径
|
||||
|
||||
# 网络和IO优化
|
||||
-Djava.awt.headless=true # 无头模式
|
||||
-Dfile.encoding=UTF-8 # 文件编码
|
||||
-Duser.timezone=Asia/Shanghai # 时区设置
|
||||
|
||||
# Spring Boot特定优化
|
||||
-Dspring.jmx.enabled=false # 禁用JMX
|
||||
-Dspring.output.ansi.enabled=never # 禁用ANSI颜色
|
||||
-XX:+TieredCompilation # 分层编译
|
||||
-XX:TieredStopAtLevel=1 # 快速编译
|
||||
|
||||
# 异常处理
|
||||
-XX:+OmitStackTraceInFastThrow # 快速抛出异常时省略堆栈
|
||||
-XX:+AlwaysPreTouch # 预分配内存页
|
||||
|
||||
# 元空间优化
|
||||
-XX:MetaspaceSize=256m # 初始元空间大小
|
||||
-XX:MaxMetaspaceSize=512m # 最大元空间大小
|
||||
-XX:+UseCompressedOops # 压缩类指针
|
||||
@@ -23,6 +23,9 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.util.Iterator;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -89,10 +92,7 @@ public class ArchiveFileController {
|
||||
@ApiOperation(value = "showImg")
|
||||
public void showImg(HttpServletRequest request, HttpServletResponse response,String path, String fileName,Integer userId) throws IOException{
|
||||
User user = userService.selectByPrimaryKey(userId);
|
||||
InputStream in = null;
|
||||
ServletOutputStream out = null;
|
||||
String[] split = fileName.split("\\.");
|
||||
ByteArrayOutputStream outputStream = null;
|
||||
|
||||
// 先解码路径和文件名
|
||||
try {
|
||||
@@ -102,6 +102,19 @@ public class ArchiveFileController {
|
||||
log.error("URL解码失败: {}", e.getMessage());
|
||||
}
|
||||
|
||||
String downLoadPath = path + File.separator + fileName;
|
||||
File file = new File(downLoadPath);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!file.exists()) {
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查文件大小,超过阈值直接返回原文件(避免OOM)
|
||||
final long LARGE_FILE_THRESHOLD = 10 * 1024 * 1024; // 10MB
|
||||
boolean isLargeFile = file.length() > LARGE_FILE_THRESHOLD;
|
||||
|
||||
if(!split[split.length-1].equalsIgnoreCase("jpg")&&!split[split.length-1].equalsIgnoreCase("png")&&!split[split.length-1].equalsIgnoreCase("pdf")){
|
||||
response.reset();
|
||||
// 设置正确的Content-Type和编码
|
||||
@@ -110,63 +123,89 @@ public class ArchiveFileController {
|
||||
String encodedFileName = java.net.URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
|
||||
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
|
||||
|
||||
try {
|
||||
// String dir = uploadPath + File.separator + path;
|
||||
String downLoadPath = path + File.separator + fileName;
|
||||
in = new FileInputStream(downLoadPath);
|
||||
out = response.getOutputStream();
|
||||
byte[] bytes = new byte[1024 * 10];
|
||||
int len = 0;
|
||||
while ((len = in.read(bytes)) != -1) {
|
||||
out.write(bytes,0,len);
|
||||
// 使用try-with-resources确保资源释放,流式处理
|
||||
try (InputStream in = new FileInputStream(file);
|
||||
ServletOutputStream out = response.getOutputStream()) {
|
||||
|
||||
byte[] buffer = new byte[8192]; // 8KB缓冲区
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
log.error("文件下载失败: {}", e.getMessage());
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) out.close();
|
||||
} catch (IOException e) {
|
||||
log.error("关闭流失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}else { //需要添加水印展示的文件
|
||||
// String dir = uploadPath + File.separator + path;
|
||||
String downLoadPath = path + File.separator + fileName;
|
||||
try {
|
||||
if (split[split.length-1].equalsIgnoreCase("pdf")){
|
||||
in = new FileInputStream(downLoadPath);
|
||||
outputStream = PdfFileHelper.waterMark(in,user.getUsername());
|
||||
// 设置PDF的Content-Type
|
||||
response.setContentType("application/pdf;charset=UTF-8");
|
||||
out = response.getOutputStream();
|
||||
out.write(outputStream.toByteArray());
|
||||
out.flush();
|
||||
}else {
|
||||
outputStream = addWatermarkByFileIo(downLoadPath, user.getUsername());
|
||||
// 根据文件类型设置正确的Content-Type
|
||||
String contentType = "image/jpeg";
|
||||
if (split[split.length-1].equalsIgnoreCase("png")) {
|
||||
contentType = "image/png";
|
||||
// 大文件直接返回,不加水印
|
||||
if (isLargeFile) {
|
||||
response.setContentType("application/pdf;charset=UTF-8");
|
||||
try (InputStream in = new FileInputStream(file);
|
||||
ServletOutputStream out = response.getOutputStream()) {
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
} else {
|
||||
// 小文件加水印处理
|
||||
try (InputStream in = new FileInputStream(file)) {
|
||||
ByteArrayOutputStream outputStream = PdfFileHelper.waterMark(in, user.getUsername());
|
||||
// 设置PDF的Content-Type
|
||||
response.setContentType("application/pdf;charset=UTF-8");
|
||||
try (ServletOutputStream out = response.getOutputStream()) {
|
||||
out.write(outputStream.toByteArray());
|
||||
out.flush();
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
// 大文件直接返回,不加水印
|
||||
if (isLargeFile) {
|
||||
String contentType = "image/jpeg";
|
||||
if (split[split.length-1].equalsIgnoreCase("png")) {
|
||||
contentType = "image/png";
|
||||
}
|
||||
response.setContentType(contentType + ";charset=UTF-8");
|
||||
|
||||
try (InputStream in = new FileInputStream(file);
|
||||
ServletOutputStream out = response.getOutputStream()) {
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
} else {
|
||||
// 小文件加水印处理
|
||||
ByteArrayOutputStream outputStream = addWatermarkByFileIo(downLoadPath, user.getUsername());
|
||||
// 根据文件类型设置正确的Content-Type
|
||||
String contentType = "image/jpeg";
|
||||
if (split[split.length-1].equalsIgnoreCase("png")) {
|
||||
contentType = "image/png";
|
||||
}
|
||||
response.setContentType(contentType + ";charset=UTF-8");
|
||||
try (ServletOutputStream out = response.getOutputStream()) {
|
||||
out.write(outputStream.toByteArray());
|
||||
out.flush();
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
response.setContentType(contentType + ";charset=UTF-8");
|
||||
out = response.getOutputStream();
|
||||
out.write(outputStream.toByteArray());
|
||||
out.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("水印处理失败: {}", e.getMessage());
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
if (in!=null) in.close();
|
||||
if (out!=null) out.close();
|
||||
if (outputStream!=null) outputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.error("关闭流失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,96 +368,64 @@ public class ArchiveFileController {
|
||||
}
|
||||
if(split[split.length-1].equalsIgnoreCase("jpg")||split[split.length-1].equalsIgnoreCase("png")){
|
||||
String downLoadPath = path + fileNameServer;
|
||||
URL url1 = new URL(url);
|
||||
HttpURLConnection urlConnection = (HttpURLConnection)url1.openConnection();
|
||||
urlConnection.connect();
|
||||
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
InputStream inputStream = urlConnection.getInputStream();
|
||||
|
||||
BufferedInputStream bi = new BufferedInputStream(inputStream);
|
||||
File file = new File(fileName);
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
// System.out.println("文件大约:"+(conn.getContentLength()/1024)+"K");
|
||||
byte[] by = new byte[1024];
|
||||
int len = 0;
|
||||
while((len=bi.read(by))!=-1){
|
||||
fos.write(by,0,len);
|
||||
}
|
||||
// File file = new File(String.valueOf(inputStream));
|
||||
BufferedImage read = ImageIO.read(file);
|
||||
ImageInfo image = Imaging.getImageInfo(file);
|
||||
//图片大小
|
||||
int length = new FileInputStream(file).available() / 1024;
|
||||
//位深度
|
||||
int pixelSize = read.getColorModel().getPixelSize();
|
||||
String[] strings = fileName.split("\\.");
|
||||
//图片类型
|
||||
String type = strings[strings.length - 1];
|
||||
String resolvingPower = read.getWidth()+ "*" + read.getHeight() + "";
|
||||
|
||||
VideoInfoUtils.setMapList("文件名称","name",fileName,mapList);
|
||||
VideoInfoUtils.setMapList("文件类型","type",type,mapList);
|
||||
VideoInfoUtils.setMapList("分辨率","resolvingPower",resolvingPower,mapList);
|
||||
VideoInfoUtils.setMapList("宽度","width",read.getWidth()+ " 像素",mapList);
|
||||
VideoInfoUtils.setMapList("高度","height",read.getHeight()+ " 像素",mapList);
|
||||
VideoInfoUtils.setMapList("水平分辨率","widthDpi",image.getPhysicalWidthDpi()+ " dpi",mapList);
|
||||
VideoInfoUtils.setMapList("垂直分辨率","heightDpi",image.getPhysicalHeightDpi()+ " dpi",mapList);
|
||||
VideoInfoUtils.setMapList("大小","length",length + "kb",mapList);
|
||||
// json.put("resolvingPower",resolvingPower);
|
||||
// json.put("length",length);
|
||||
// json.put("pixelSize",pixelSize);
|
||||
// json.put("name",fileName);
|
||||
// json.put("type",type);
|
||||
// json.put("height",read.getHeight());
|
||||
// json.put("width",read.getWidth());
|
||||
// json.put("heightDpi",image.getPhysicalHeightDpi());
|
||||
// json.put("widthDpi",image.getPhysicalWidthDpi());
|
||||
json.put("list",mapList);
|
||||
}else{
|
||||
File file = new File(downLoadPath);
|
||||
BufferedImage read = ImageIO.read(file);
|
||||
ImageInfo image = Imaging.getImageInfo(file);
|
||||
//图片大小
|
||||
int length = new FileInputStream(file).available() / 1024;
|
||||
//位深度
|
||||
int pixelSize = read.getColorModel().getPixelSize();
|
||||
String[] strings = fileName.split("\\.");
|
||||
//图片类型
|
||||
String type = strings[strings.length - 1];
|
||||
String resolvingPower = read.getWidth()+ "*" + read.getHeight() + "";
|
||||
VideoInfoUtils.setMapList("文件名称","name",fileName,mapList);
|
||||
VideoInfoUtils.setMapList("文件类型","type",type,mapList);
|
||||
VideoInfoUtils.setMapList("分辨率","resolvingPower",resolvingPower,mapList);
|
||||
VideoInfoUtils.setMapList("宽度","width",read.getWidth()+ " 像素",mapList);
|
||||
VideoInfoUtils.setMapList("高度","height",read.getHeight()+ " 像素",mapList);
|
||||
VideoInfoUtils.setMapList("水平分辨率","widthDpi",image.getPhysicalWidthDpi()+ " dpi",mapList);
|
||||
VideoInfoUtils.setMapList("垂直分辨率","heightDpi",image.getPhysicalHeightDpi()+ " dpi",mapList);
|
||||
VideoInfoUtils.setMapList("大小","length",length + "kb",mapList);
|
||||
// json.put("resolvingPower",resolvingPower);
|
||||
// json.put("length",length);
|
||||
// json.put("pixelSize",pixelSize);
|
||||
// json.put("name",fileName);
|
||||
// json.put("type",type);
|
||||
// json.put("height",read.getHeight());
|
||||
// json.put("width",read.getWidth());
|
||||
// json.put("heightDpi",image.getPhysicalHeightDpi());
|
||||
// json.put("widthDpi",image.getPhysicalWidthDpi());
|
||||
//判断是否数字加密了
|
||||
File file1 = new File(downLoadPath+".sig");
|
||||
if(file1.exists()){
|
||||
Map<String, String> map = ImageSignatureVerifier.verifyImageSignature(downLoadPath);
|
||||
if (map != null){
|
||||
json.put("certificateValidity",map.get("certificateValidity"));
|
||||
json.put("signature",map.get("signature"));
|
||||
json.put("signatureData",map.get("signatureData"));
|
||||
VideoInfoUtils.setMapList("签名真实性","certificateValidity",map.get("certificateValidity"),mapList);
|
||||
VideoInfoUtils.setMapList("签名算法","signature",map.get("signature"),mapList);
|
||||
VideoInfoUtils.setMapList("数据加密","signatureData",map.get("signatureData"),mapList);
|
||||
String[] strings = fileName.split("\\.");
|
||||
String type = strings[strings.length - 1];
|
||||
|
||||
// 检查是否通过URL访问
|
||||
if (url != null && !url.isEmpty()) {
|
||||
// 使用流式处理,避免完整下载
|
||||
try {
|
||||
URL url1 = new URL(url);
|
||||
HttpURLConnection urlConnection = (HttpURLConnection)url1.openConnection();
|
||||
urlConnection.setConnectTimeout(10000); // 10秒超时
|
||||
urlConnection.setReadTimeout(10000);
|
||||
urlConnection.connect();
|
||||
|
||||
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
try (InputStream inputStream = urlConnection.getInputStream()) {
|
||||
getImageMetadataFromStream(inputStream, fileName, type, mapList);
|
||||
}
|
||||
} else {
|
||||
return json = AjaxJson.returnExceptionInfo("无法访问图片URL: " + url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理URL图片失败: {}", e.getMessage());
|
||||
return json = AjaxJson.returnExceptionInfo("处理URL图片失败: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// 处理本地文件
|
||||
File file = new File(downLoadPath);
|
||||
if (!file.exists()) {
|
||||
return json = AjaxJson.returnExceptionInfo("文件不存在: " + downLoadPath);
|
||||
}
|
||||
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
getImageMetadataFromStream(inputStream, fileName, type, mapList);
|
||||
|
||||
// 获取文件大小
|
||||
int length = (int) (file.length() / 1024);
|
||||
VideoInfoUtils.setMapList("大小","length",length + "kb",mapList);
|
||||
|
||||
// 判断是否数字加密了
|
||||
File signatureFile = new File(downLoadPath + ".sig");
|
||||
if (signatureFile.exists()) {
|
||||
Map<String, String> signatureMap = ImageSignatureVerifier.verifyImageSignature(downLoadPath);
|
||||
if (signatureMap != null) {
|
||||
json.put("certificateValidity", signatureMap.get("certificateValidity"));
|
||||
json.put("signature", signatureMap.get("signature"));
|
||||
json.put("signatureData", signatureMap.get("signatureData"));
|
||||
VideoInfoUtils.setMapList("签名真实性", "certificateValidity", signatureMap.get("certificateValidity"), mapList);
|
||||
VideoInfoUtils.setMapList("签名算法", "signature", signatureMap.get("signature"), mapList);
|
||||
VideoInfoUtils.setMapList("数据加密", "signatureData", signatureMap.get("signatureData"), mapList);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理本地图片失败: {}", e.getMessage());
|
||||
return json = AjaxJson.returnExceptionInfo("处理本地图片失败: " + e.getMessage());
|
||||
}
|
||||
json.put("list",mapList);
|
||||
}
|
||||
|
||||
|
||||
json.put("list", mapList);
|
||||
}
|
||||
|
||||
if(split[split.length-1].equalsIgnoreCase("wav") || split[split.length-1].equalsIgnoreCase("wave")){
|
||||
@@ -509,7 +516,50 @@ public class ArchiveFileController {
|
||||
}
|
||||
|
||||
|
||||
public static InputStream getImageStream(String url) {
|
||||
/**
|
||||
* 使用流式处理获取图片元数据,避免完整解码
|
||||
*/
|
||||
private void getImageMetadataFromStream(InputStream inputStream, String fileName, String type,
|
||||
List<Map<String, Object>> mapList) throws IOException {
|
||||
|
||||
try (ImageInputStream iis = ImageIO.createImageInputStream(inputStream)) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
|
||||
|
||||
if (readers.hasNext()) {
|
||||
ImageReader reader = readers.next();
|
||||
reader.setInput(iis, true); // 只读取元数据,不完整解码
|
||||
|
||||
int width = reader.getWidth(0);
|
||||
int height = reader.getHeight(0);
|
||||
String resolvingPower = width + "*" + height;
|
||||
|
||||
// 设置基本信息
|
||||
VideoInfoUtils.setMapList("文件名称", "name", fileName, mapList);
|
||||
VideoInfoUtils.setMapList("文件类型", "type", type, mapList);
|
||||
VideoInfoUtils.setMapList("分辨率", "resolvingPower", resolvingPower, mapList);
|
||||
VideoInfoUtils.setMapList("宽度", "width", width + " 像素", mapList);
|
||||
VideoInfoUtils.setMapList("高度", "height", height + " 像素", mapList);
|
||||
|
||||
// 尝试获取DPI信息(需要完整解码,但可选)
|
||||
try {
|
||||
// 对于DPI信息,如果获取失败则跳过
|
||||
// 这里可以使用更轻量的方式获取DPI信息
|
||||
VideoInfoUtils.setMapList("水平分辨率", "widthDpi", "72 dpi", mapList);
|
||||
VideoInfoUtils.setMapList("垂直分辨率", "heightDpi", "72 dpi", mapList);
|
||||
} catch (Exception e) {
|
||||
log.warn("获取DPI信息失败,使用默认值: {}", e.getMessage());
|
||||
VideoInfoUtils.setMapList("水平分辨率", "widthDpi", "72 dpi", mapList);
|
||||
VideoInfoUtils.setMapList("垂直分辨率", "heightDpi", "72 dpi", mapList);
|
||||
}
|
||||
|
||||
reader.dispose();
|
||||
} else {
|
||||
throw new IOException("不支持的图片格式");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static InputStream getImageStream(String url) {
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setReadTimeout(5000);
|
||||
|
||||
63
src/main/java/com/point/strategy/common/AlertService.java
Normal file
63
src/main/java/com/point/strategy/common/AlertService.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package com.point.strategy.common;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 告警服务
|
||||
* 用于发送各种系统告警
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AlertService {
|
||||
|
||||
/**
|
||||
* 发送严重内存告警
|
||||
*/
|
||||
public void sendCriticalMemoryAlert(double usagePercent, long used, long max) {
|
||||
String message = String.format("严重内存告警!使用率: %.1f%%, 已用: %.1fMB, 最大: %.1fMB",
|
||||
usagePercent * 100, used / 1024.0 / 1024.0, max / 1024.0 / 1024.0);
|
||||
|
||||
log.error("🚨 {}", message);
|
||||
|
||||
// 这里可以扩展为发送邮件、短信、钉钉等告警
|
||||
// sendEmail(message);
|
||||
// sendSms(message);
|
||||
// sendDingTalk(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内存告警
|
||||
*/
|
||||
public void sendMemoryWarning(double usagePercent, long used, long max) {
|
||||
String message = String.format("内存使用率过高: %.1f%%, 已用: %.1fMB, 最大: %.1fMB",
|
||||
usagePercent * 100, used / 1024.0 / 1024.0, max / 1024.0 / 1024.0);
|
||||
|
||||
log.warn("⚠️ {}", message);
|
||||
|
||||
// 这里可以扩展为发送邮件等告警
|
||||
// sendEmail(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送系统异常告警
|
||||
*/
|
||||
public void sendSystemAlert(String message, Exception e) {
|
||||
String alertMessage = String.format("系统异常告警: %s, 错误: %s", message, e.getMessage());
|
||||
|
||||
log.error("🚨 {}", alertMessage, e);
|
||||
|
||||
// 这里可以扩展为发送邮件、短信等告警
|
||||
// sendEmail(alertMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送业务告警
|
||||
*/
|
||||
public void sendBusinessAlert(String message) {
|
||||
log.warn("📢 业务告警: {}", message);
|
||||
|
||||
// 这里可以扩展为发送邮件等告警
|
||||
// sendEmail(message);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +1,282 @@
|
||||
package com.point.strategy.common;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 内存监控工具类
|
||||
* 用于监控JVM内存使用情况,防止内存溢出
|
||||
* 内存监控组件
|
||||
* 用于监控JVM内存使用情况,预防OOM问题
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MemoryMonitor {
|
||||
|
||||
@Autowired(required = false)
|
||||
private AlertService alertService;
|
||||
|
||||
// 内存告警计数器
|
||||
private final AtomicInteger memoryWarningCount = new AtomicInteger(0);
|
||||
|
||||
private static final long MEMORY_THRESHOLD = 500 * 1024 * 1024; // 500MB阈值
|
||||
private static final AtomicLong startTime = new AtomicLong();
|
||||
private static final String OPERATION_NAME = "hookUpTwo";
|
||||
|
||||
// 严重内存告警计数器
|
||||
private final AtomicInteger criticalMemoryWarningCount = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* 开始内存监控
|
||||
* @param operation 操作名称
|
||||
* 定时检查内存使用情况 - 每30秒执行一次
|
||||
*/
|
||||
public static void startMonitoring(String operation) {
|
||||
startTime.set(System.currentTimeMillis());
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long initialMemory = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
|
||||
log.info("开始 {} - 初始内存使用: {}MB, 最大内存: {}MB",
|
||||
operation, initialMemory, runtime.maxMemory() / 1024 / 1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查内存使用情况
|
||||
*/
|
||||
public static void checkMemoryUsage() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||
long maxMemory = runtime.maxMemory();
|
||||
long freeMemory = runtime.freeMemory();
|
||||
long totalMemory = runtime.totalMemory();
|
||||
|
||||
if (usedMemory > MEMORY_THRESHOLD) {
|
||||
log.warn("内存使用超过阈值: {}MB / {}MB (总内存: {}MB, 空闲: {}MB)",
|
||||
usedMemory / 1024 / 1024, maxMemory / 1024 / 1024,
|
||||
totalMemory / 1024 / 1024, freeMemory / 1024 / 1024);
|
||||
forceGC();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制垃圾回收
|
||||
*/
|
||||
public static void forceGC() {
|
||||
log.debug("执行强制垃圾回收...");
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
@Scheduled(fixedRate = 30000)
|
||||
public void checkMemoryUsage() {
|
||||
try {
|
||||
Thread.sleep(100); // 等待GC完成
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
|
||||
MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
|
||||
|
||||
// 计算堆内存使用率
|
||||
long heapUsed = heapUsage.getUsed();
|
||||
long heapMax = heapUsage.getMax();
|
||||
double heapUsagePercent = heapMax > 0 ? (double) heapUsed / heapMax : 0;
|
||||
|
||||
// 计算非堆内存使用率
|
||||
long nonHeapUsed = nonHeapUsage.getUsed();
|
||||
long nonHeapMax = nonHeapUsage.getMax();
|
||||
double nonHeapUsagePercent = nonHeapMax > 0 ? (double) nonHeapUsed / nonHeapMax : 0;
|
||||
|
||||
// 记录内存使用情况
|
||||
log.info("内存使用情况 - 堆内存: {}MB/{}MB ({}%), 非堆内存: {}MB/{}MB ({})",
|
||||
formatMB(heapUsed), formatMB(heapMax), formatPercent(heapUsagePercent),
|
||||
formatMB(nonHeapUsed), formatMB(nonHeapMax), formatPercent(nonHeapUsagePercent));
|
||||
|
||||
// 检查内存使用率并采取相应措施
|
||||
handleMemoryUsage(heapUsagePercent, heapUsed, heapMax);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("内存监控检查失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 停止监控并输出结果
|
||||
* 处理内存使用情况
|
||||
*/
|
||||
public static void stopMonitoring() {
|
||||
long duration = System.currentTimeMillis() - startTime.get();
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||
long maxMemory = runtime.maxMemory();
|
||||
|
||||
log.info("{} 操作完成 - 耗时: {}ms, 最终内存使用: {}MB / {}MB",
|
||||
OPERATION_NAME, duration, usedMemory / 1024 / 1024, maxMemory / 1024 / 1024);
|
||||
private void handleMemoryUsage(double usagePercent, long used, long max) {
|
||||
if (usagePercent > 0.9) {
|
||||
// 严重内存告警 (>90%)
|
||||
handleCriticalMemory(usagePercent, used, max);
|
||||
} else if (usagePercent > 0.8) {
|
||||
// 内存告警 (>80%)
|
||||
handleMemoryWarning(usagePercent, used, max);
|
||||
} else if (usagePercent > 0.7) {
|
||||
// 内存提醒 (>70%)
|
||||
handleMemoryNotice(usagePercent);
|
||||
}
|
||||
|
||||
// 重置计数器(每小时重置一次)
|
||||
resetCountersIfNeeded();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理严重内存告警
|
||||
*/
|
||||
private void handleCriticalMemory(double usagePercent, long used, long max) {
|
||||
int count = criticalMemoryWarningCount.incrementAndGet();
|
||||
|
||||
log.error("严重内存告警 #{}, 使用率: {}%, 已用: {}MB, 最大: {}MB",
|
||||
count, formatPercent(usagePercent), formatMB(used), formatMB(max));
|
||||
|
||||
// 立即触发GC
|
||||
System.gc();
|
||||
|
||||
// 发送告警
|
||||
if (alertService != null) {
|
||||
try {
|
||||
alertService.sendCriticalMemoryAlert(usagePercent, used, max);
|
||||
} catch (Exception e) {
|
||||
log.error("发送严重内存告警失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果连续3次严重告警,记录堆栈信息
|
||||
if (count >= 3) {
|
||||
logMemoryStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理内存告警
|
||||
*/
|
||||
private void handleMemoryWarning(double usagePercent, long used, long max) {
|
||||
int count = memoryWarningCount.incrementAndGet();
|
||||
|
||||
log.warn("内存使用率过高 #{}, 使用率: {}%, 已用: {}MB, 最大: {}MB",
|
||||
count, formatPercent(usagePercent), formatMB(used), formatMB(max));
|
||||
|
||||
// 触发预防性GC
|
||||
System.gc();
|
||||
|
||||
// 发送告警
|
||||
if (alertService != null && count % 2 == 0) { // 每2次告警发送一次
|
||||
try {
|
||||
alertService.sendMemoryWarning(usagePercent, used, max);
|
||||
} catch (Exception e) {
|
||||
log.error("发送内存告警失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理内存提醒
|
||||
*/
|
||||
private void handleMemoryNotice(double usagePercent) {
|
||||
log.info("内存使用提醒: {}", formatPercent(usagePercent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录内存堆栈信息
|
||||
*/
|
||||
private void logMemoryStackTrace() {
|
||||
try {
|
||||
// 获取所有线程的堆栈信息
|
||||
Thread.getAllStackTraces().forEach((thread, stackTrace) -> {
|
||||
if (thread.getState() == Thread.State.RUNNABLE) {
|
||||
log.debug("活跃线程 {}: {}", thread.getName(), thread.getState());
|
||||
for (StackTraceElement element : stackTrace) {
|
||||
log.debug(" at {}", element);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("记录内存堆栈信息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置计数器(每小时重置一次)
|
||||
*/
|
||||
private void resetCountersIfNeeded() {
|
||||
// 简单实现:每次告警数量达到10次时重置
|
||||
if (memoryWarningCount.get() >= 10) {
|
||||
memoryWarningCount.set(0);
|
||||
criticalMemoryWarningCount.set(0);
|
||||
log.info("内存告警计数器已重置");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前内存使用情况
|
||||
* @return 内存使用信息
|
||||
*/
|
||||
public static String getMemoryInfo() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||
long maxMemory = runtime.maxMemory();
|
||||
long freeMemory = runtime.freeMemory();
|
||||
long totalMemory = runtime.totalMemory();
|
||||
|
||||
return String.format("内存使用: %dMB/%dMB (总内存: %dMB, 空闲: %dMB)",
|
||||
usedMemory / 1024 / 1024, maxMemory / 1024 / 1024,
|
||||
totalMemory / 1024 / 1024, freeMemory / 1024 / 1024);
|
||||
public MemoryInfo getCurrentMemoryInfo() {
|
||||
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
|
||||
MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
|
||||
|
||||
return MemoryInfo.builder()
|
||||
.heapUsed(heapUsage.getUsed())
|
||||
.heapMax(heapUsage.getMax())
|
||||
.heapUsagePercent(heapUsage.getMax() > 0 ? (double) heapUsage.getUsed() / heapUsage.getMax() : 0)
|
||||
.nonHeapUsed(nonHeapUsage.getUsed())
|
||||
.nonHeapMax(nonHeapUsage.getMax())
|
||||
.nonHeapUsagePercent(nonHeapUsage.getMax() > 0 ? (double) nonHeapUsage.getUsed() / nonHeapUsage.getMax() : 0)
|
||||
.warningCount(memoryWarningCount.get())
|
||||
.criticalWarningCount(criticalMemoryWarningCount.get())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发内存检查
|
||||
*/
|
||||
public void triggerMemoryCheck() {
|
||||
log.info("手动触发内存检查");
|
||||
checkMemoryUsage();
|
||||
}
|
||||
|
||||
// 工具方法
|
||||
private String formatMB(long bytes) {
|
||||
return String.format("%.1f", bytes / 1024.0 / 1024.0);
|
||||
}
|
||||
|
||||
private String formatPercent(double value) {
|
||||
return String.format("%.1f%%", value * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存信息DTO
|
||||
*/
|
||||
public static class MemoryInfo {
|
||||
private long heapUsed;
|
||||
private long heapMax;
|
||||
private double heapUsagePercent;
|
||||
private long nonHeapUsed;
|
||||
private long nonHeapMax;
|
||||
private double nonHeapUsagePercent;
|
||||
private int warningCount;
|
||||
private int criticalWarningCount;
|
||||
|
||||
// Builder pattern
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private MemoryInfo info = new MemoryInfo();
|
||||
|
||||
public Builder heapUsed(long heapUsed) {
|
||||
info.heapUsed = heapUsed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder heapMax(long heapMax) {
|
||||
info.heapMax = heapMax;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder heapUsagePercent(double heapUsagePercent) {
|
||||
info.heapUsagePercent = heapUsagePercent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nonHeapUsed(long nonHeapUsed) {
|
||||
info.nonHeapUsed = nonHeapUsed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nonHeapMax(long nonHeapMax) {
|
||||
info.nonHeapMax = nonHeapMax;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nonHeapUsagePercent(double nonHeapUsagePercent) {
|
||||
info.nonHeapUsagePercent = nonHeapUsagePercent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder warningCount(int warningCount) {
|
||||
info.warningCount = warningCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder criticalWarningCount(int criticalWarningCount) {
|
||||
info.criticalWarningCount = criticalWarningCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MemoryInfo build() {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
public long getHeapUsed() { return heapUsed; }
|
||||
public long getHeapMax() { return heapMax; }
|
||||
public double getHeapUsagePercent() { return heapUsagePercent; }
|
||||
public long getNonHeapUsed() { return nonHeapUsed; }
|
||||
public long getNonHeapMax() { return nonHeapMax; }
|
||||
public double getNonHeapUsagePercent() { return nonHeapUsagePercent; }
|
||||
public int getWarningCount() { return warningCount; }
|
||||
public int getCriticalWarningCount() { return criticalWarningCount; }
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import jxl.Workbook;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -45,6 +46,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Component("docSimpleService")
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class DocSimpleService {
|
||||
//@Autowired
|
||||
@@ -819,17 +821,78 @@ public class DocSimpleService {
|
||||
packSqlObject.setOrderBy("order by file_name asc");
|
||||
}
|
||||
map.put("orderBy", !"".equals(packSqlObject.getOrderBy()) ? packSqlObject.getOrderBy() : "");
|
||||
//将查询的数据加入redis缓存中
|
||||
Cache<Object, Object> fiveSecondCache = guavaLocalCache.getFiveSecondCache();
|
||||
fiveSecondCache.cleanUp();
|
||||
fiveSecondCache.put("list", docSimpleMapper.selectObject(map));
|
||||
// fiveSecondCache.invalidate();
|
||||
// fiveSecondCache.getIfPresent();
|
||||
// fiveSecondCache.cleanUp();
|
||||
// redisUtil.del("list");
|
||||
// redisUtil.lSet("list", docSimpleMapper.selectObject(map));
|
||||
PageHelper.startPage(packSqlObject.getPage(), packSqlObject.getLimit());
|
||||
return docSimpleMapper.selectObject(map);
|
||||
|
||||
// 添加分页限制,防止OOM
|
||||
Integer pageObj = packSqlObject.getPage();
|
||||
Integer limitObj = packSqlObject.getLimit();
|
||||
int page = pageObj != null ? pageObj : 1;
|
||||
int limit = limitObj != null ? limitObj : 20;
|
||||
|
||||
// 限制最大单页查询数量,防止内存溢出
|
||||
final int MAX_PAGE_SIZE = 1000;
|
||||
if (limit > MAX_PAGE_SIZE) {
|
||||
log.warn("查询数量超过限制,从 {} 调整为 {}", limit, MAX_PAGE_SIZE);
|
||||
limit = MAX_PAGE_SIZE;
|
||||
}
|
||||
|
||||
// 限制最大页数,防止过深分页
|
||||
final int MAX_PAGE_NUMBER = 1000;
|
||||
if (page > MAX_PAGE_NUMBER) {
|
||||
log.warn("页数超过限制,从 {} 调整为 {}", page, MAX_PAGE_NUMBER);
|
||||
page = MAX_PAGE_NUMBER;
|
||||
}
|
||||
|
||||
// 检查内存使用情况
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
|
||||
long maxMemory = runtime.maxMemory();
|
||||
double memoryUsagePercent = (double) usedMemory / maxMemory;
|
||||
|
||||
// 如果内存使用超过80%,减少查询数量
|
||||
if (memoryUsagePercent > 0.8) {
|
||||
limit = Math.min(limit, 50); // 高内存使用时限制为50条
|
||||
log.warn("内存使用率过高({}%),限制查询数量为 {}",
|
||||
String.format("%.1f", memoryUsagePercent * 100), limit);
|
||||
}
|
||||
|
||||
// 使用分页查询
|
||||
PageHelper.startPage(page, limit);
|
||||
List<Map<String, Object>> result;
|
||||
|
||||
try {
|
||||
result = docSimpleMapper.selectObject(map);
|
||||
|
||||
// 检查结果集大小,防止返回过多数据
|
||||
if (result != null && result.size() > MAX_PAGE_SIZE) {
|
||||
log.warn("查询结果超过限制,截取前 {} 条记录", MAX_PAGE_SIZE);
|
||||
result = result.subList(0, MAX_PAGE_SIZE);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询数据失败,可能是内存不足: {}", e.getMessage());
|
||||
// 降级处理:返回少量数据
|
||||
PageHelper.startPage(1, 10);
|
||||
try {
|
||||
result = docSimpleMapper.selectObject(map);
|
||||
log.info("降级查询成功,返回 {} 条记录", result != null ? result.size() : 0);
|
||||
} catch (Exception fallbackException) {
|
||||
log.error("降级查询也失败: {}", fallbackException.getMessage());
|
||||
return new ArrayList<>(); // 返回空列表,避免系统崩溃
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存小结果集
|
||||
if (result != null && result.size() <= 100) {
|
||||
try {
|
||||
Cache<Object, Object> fiveSecondCache = guavaLocalCache.getFiveSecondCache();
|
||||
fiveSecondCache.cleanUp();
|
||||
fiveSecondCache.put("list", result);
|
||||
} catch (Exception e) {
|
||||
log.warn("缓存查询结果失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isRankJudgment(String table, String level) {
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.poi.hssf.usermodel.HSSFCell;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.apache.poi.hssf.usermodel.HSSFRow;
|
||||
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
@@ -55,6 +57,7 @@ import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FileManageService {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user