2026-04-23 4cb12d21253d754152b7591fe49fa131fed1b3c8
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);
    }
}