From 55a16a2b0144c30277c7ac01712f347eedfaab41 Mon Sep 17 00:00:00 2001
From: chenhj <1263187585@qq.com>
Date: 星期三, 22 四月 2026 17:49:46 +0800
Subject: [PATCH] feat(common): 优化文件预览接口实现及文件压缩功能

---
 src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java |    4 -
 src/main/java/com/ruoyi/basic/utils/FileUtil.java                      |   56 ++++++++++++++++++++++++---
 src/main/java/com/ruoyi/common/config/FileProperties.java              |    6 ++
 src/main/resources/application-dev-pro.yml                             |    6 +++
 pom.xml                                                                |    7 +++
 src/main/java/com/ruoyi/project/common/CommonController.java           |   29 ++++++++++----
 6 files changed, 89 insertions(+), 19 deletions(-)

diff --git a/pom.xml b/pom.xml
index b96426c..e646950 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,7 @@
         <mybatis-plus.version>3.5.16</mybatis-plus.version>
         <getui-sdk.version>1.0.7.0</getui-sdk.version>
         <jsqlparser.version>4.9</jsqlparser.version>
+        <thumbnailator.version>0.4.20</thumbnailator.version>
     </properties>
 
     <dependencies>
@@ -360,6 +361,12 @@
             <artifactId>jackson-datatype-jsr310</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>net.coobird</groupId>
+            <artifactId>thumbnailator</artifactId>
+            <version>${thumbnailator.version}</version>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
index b576d23..4fb85e2 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
@@ -89,9 +89,7 @@
             throw new IllegalArgumentException("token涓嶈兘涓虹┖");
         }
 
-        String secretStr = StringUtils.hasText(properties.getJwtSecret())
-                ? properties.getJwtSecret()
-                : "local-file-jwt-secret";
+        String secretStr = properties.getJwtSecret();
 
         SecretKey key = Keys.hmacShaKeyFor(secretStr.getBytes(StandardCharsets.UTF_8));
         Claims claims = Jwts.parser()
diff --git a/src/main/java/com/ruoyi/basic/utils/FileUtil.java b/src/main/java/com/ruoyi/basic/utils/FileUtil.java
index 606228c..1e629cf 100644
--- a/src/main/java/com/ruoyi/basic/utils/FileUtil.java
+++ b/src/main/java/com/ruoyi/basic/utils/FileUtil.java
@@ -7,18 +7,21 @@
 import com.ruoyi.basic.dto.StorageAttachmentDTO;
 import com.ruoyi.basic.dto.StorageBlobVO;
 import com.ruoyi.basic.mapper.StorageAttachmentMapper;
-import com.ruoyi.basic.mapper.StorageBlobMapper;
 import com.ruoyi.basic.pojo.StorageAttachment;
 import com.ruoyi.common.config.FileProperties;
 import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
 import lombok.RequiredArgsConstructor;
+import net.coobird.thumbnailator.Thumbnails;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
+import javax.crypto.SecretKey;
+import java.io.File;
 import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -30,7 +33,6 @@
 public class FileUtil {
     private final FileProperties properties;
     private final StorageAttachmentMapper storageAttachmentMapper;
-    private final StorageBlobMapper storageBlobMapper;
     private final StringRedisTemplate stringRedisTemplate;
 
     private static final String TOKEN_USAGE_KEY_PREFIX = "file:token:usage:";
@@ -431,13 +433,15 @@
         }
         Date issuedAt = new Date(now);
         Date expiration = new Date(now + expiredMillis);
+        SecretKey key = Keys.hmacShaKeyFor(properties.getJwtSecret().getBytes(StandardCharsets.UTF_8));
+
         String token = Jwts.builder()
-                .setSubject(storageBlob.getUidFilename())
-                .setIssuedAt(issuedAt)
-                .setExpiration(expiration)
+                .subject(storageBlob.getUidFilename())
+                .issuedAt(issuedAt)       // 鏂扮増寤鸿鐩存帴璋冪敤 .issuedAt()
+                .expiration(expiration)   // 鏂扮増寤鸿鐩存帴璋冪敤 .expiration()
                 .claim("path", storageBlob.getPath())
                 .claim("resourceKey", storageBlob.getResourceKey())
-                .signWith(SignatureAlgorithm.HS256, properties.getJwtSecret())
+                .signWith(key)            // 閲嶇偣锛氫紶鍏ヤ笂闈㈢敓鎴愮殑 key 瀵硅薄锛岃�屼笉鏄� String
                 .compact();
         cacheTokenUsage(token, expiredMillis);
         String domain = StringUtils.trimTrailingCharacter(properties.getDomain(), '/');
@@ -491,4 +495,42 @@
         return properties.getUseLimit() == null || properties.getUseLimit() <= 0 ? 10 : properties.getUseLimit();
     }
 
+    /**
+     * 鍘嬬缉鏂囦欢
+     *
+     * @param file 鏂囦欢
+     * @return 鍘嬬缉鍚庣殑鏂囦欢
+     */
+    public File compressFile(File file) {
+        if (properties.getCompress() && isImage(file.getName()) && (file.length() > properties.getNeedCompressSize().toBytes())) {
+            try {
+                // 鍒涘缓涓�涓复鏃舵枃浠跺瓨鏀惧帇缂╁悗鐨勫浘鐗囷紝閬垮厤鐮村潖鍘熷浘
+                File compressedFile = new File(file.getParent(), "thumb_" + file.getName());
+
+                // 1. 濡傛灉宸茬粡瀛樺湪鍘嬬缉杩囩殑鏂囦欢锛岀洿鎺ヨ繑鍥烇紝涓嶅啀娑堣�� CPU 鍘嬬缉
+                if (compressedFile.exists()) {
+                    return compressedFile;
+                }
+
+                // 浣跨敤 Thumbnailator 杩涜鍘嬬缉
+                Thumbnails.of(file)
+                        .scale(1.0f)                // 淇濇寔鍘熷昂瀵�
+                        .outputQuality(properties.getCompressQuality())        // 鏍稿績锛氳缃敾璐� (0.0~1.0)
+                        .toFile(compressedFile);
+
+                return compressedFile;
+            } catch (Exception e) {
+                // 濡傛灉鍘嬬缉澶辫触锛岄檷绾у鐞嗭細杩斿洖鍘熷浘
+                return file;
+            }
+        }
+        return file;
+    }
+    // 绠�鍗曠殑鍚庣紑鍒ゆ柇
+    private boolean isImage(String fileName) {
+        String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
+        return "jpg".equals(ext) || "jpeg".equals(ext) || "png".equals(ext);
+    }
+
+
 }
diff --git a/src/main/java/com/ruoyi/common/config/FileProperties.java b/src/main/java/com/ruoyi/common/config/FileProperties.java
index 2687212..f7ef613 100644
--- a/src/main/java/com/ruoyi/common/config/FileProperties.java
+++ b/src/main/java/com/ruoyi/common/config/FileProperties.java
@@ -5,12 +5,13 @@
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.stereotype.Component;
+import org.springframework.util.unit.DataSize;
 
 import java.math.BigDecimal;
 
 @Configuration
 @Component
-@ConfigurationProperties(prefix = "file")
+@ConfigurationProperties(prefix = "file", ignoreUnknownFields = true)
 @Data
 public class FileProperties {
     private String path = "D:/upload";
@@ -21,4 +22,7 @@
     // 浠ょ墝绉橀挜
     @Value("${token.secret}")
     private String jwtSecret;
+    private Boolean compress;
+    private DataSize needCompressSize;
+    private float compressQuality;
 }
diff --git a/src/main/java/com/ruoyi/project/common/CommonController.java b/src/main/java/com/ruoyi/project/common/CommonController.java
index f4ca6ca..ca64819 100644
--- a/src/main/java/com/ruoyi/project/common/CommonController.java
+++ b/src/main/java/com/ruoyi/project/common/CommonController.java
@@ -1,18 +1,23 @@
 package com.ruoyi.project.common;
 
 import com.ruoyi.basic.service.StorageBlobService;
+import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.framework.aspectj.lang.annotation.Anonymous;
 import com.ruoyi.framework.config.ServerConfig;
 import com.ruoyi.framework.web.domain.R;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.AllArgsConstructor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.ContentDisposition;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import jakarta.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
@@ -36,6 +41,7 @@
     private static final String FILE_DELIMETER = ",";
 
     private final StorageBlobService storageBlobService;
+    private final FileUtil fileUtil;
 
 
 //    /**
@@ -171,7 +177,6 @@
 //            log.error("涓嬭浇鏂囦欢澶辫触", e);
 //        }
 //    }
-
     @PostMapping({"/upload"})
     @ApiOperation(value = "鏂囦欢涓婁紶")
     public R upload(@RequestParam("files") List<MultipartFile> files) throws Exception {
@@ -186,17 +191,25 @@
         String originalFileName = storageBlobService.getDownloadFileName(fileName);
         String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
         response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encodedFileName);
+        response.setContentLengthLong(file.length());
         Files.copy(file.toPath(), response.getOutputStream());
     }
 
     @GetMapping("/preview/{fileName}")
     @Anonymous
-    public void preview(@PathVariable String fileName, @RequestParam("token") String token, HttpServletResponse response) throws Exception {
-        File file = storageBlobService.getFileByToken(fileName, token);
+    public ResponseEntity<FileSystemResource> preview(@PathVariable String fileName,
+                                                      @RequestParam("token") String token) throws Exception {
+        File file = fileUtil.compressFile(storageBlobService.getFileByToken(fileName, token));
         String contentType = Files.probeContentType(file.toPath());
-        response.setContentType(contentType == null ? "application/octet-stream" : contentType);
-        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
-        response.setHeader("Content-Disposition", "inline;filename*=UTF-8''" + encodedFileName);
-        Files.copy(file.toPath(), response.getOutputStream());
+
+        ContentDisposition contentDisposition = ContentDisposition.inline()
+                .filename(fileName, StandardCharsets.UTF_8)
+                .build();
+
+        return ResponseEntity.ok()
+                .contentType(MediaType.parseMediaType(contentType != null ? contentType : "application/octet-stream"))
+                .contentLength(file.length())
+                .header("Content-Disposition", contentDisposition.toString())
+                .body(new FileSystemResource(file));
     }
 }
diff --git a/src/main/resources/application-dev-pro.yml b/src/main/resources/application-dev-pro.yml
index 4ebc169..01be012 100644
--- a/src/main/resources/application-dev-pro.yml
+++ b/src/main/resources/application-dev-pro.yml
@@ -37,6 +37,9 @@
     uri-encoding: UTF-8
     # 杩炴帴鏁版弧鍚庣殑鎺掗槦鏁帮紝榛樿涓�100
     accept-count: 1000
+    max-swallow-size: -1          # -1 琛ㄧず涓嶉檺鍒�
+    max-http-form-post-size: -1   # POST 璇锋眰浣撲笉闄愬埗
+    connection-timeout: 60000     # 杩炴帴瓒呮椂鏃堕棿(姣)
     threads:
       # tomcat鏈�澶х嚎绋嬫暟锛岄粯璁や负200
       max: 800
@@ -261,3 +264,6 @@
   domain: http://127.0.0.1:7003 # 鍩熷悕鍓嶇紑
   expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
   useLimit: 10 # 浣跨敤娆℃暟
+  compress: true # 鏄惁鍘嬬缉
+  needCompressSize: 10MB # 鍘嬬缉闃堝��
+  compressQuality: 0.1 # 鍘嬬缉璐ㄩ噺(0.0-1.0)

--
Gitblit v1.9.3