| pom.xml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/basic/utils/FileUtil.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/common/config/FileProperties.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/project/common/CommonController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/resources/application-dev-pro.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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> 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() 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); } } 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; } 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)); } } 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)