package com.ruoyi.basic.utils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.basic.dto.StorageAttachmentDTO; import com.ruoyi.basic.dto.StorageAttachmentVO; import com.ruoyi.basic.dto.StorageBlobDTO; import com.ruoyi.basic.dto.StorageBlobVO; import com.ruoyi.basic.enums.ApplicationTypeEnum; import com.ruoyi.basic.enums.RecordTypeEnum; import com.ruoyi.basic.mapper.StorageAttachmentMapper; import com.ruoyi.basic.mapper.StorageBlobMapper; import com.ruoyi.basic.pojo.StorageAttachment; import com.ruoyi.basic.pojo.StorageBlob; import com.ruoyi.common.config.FileProperties; import io.jsonwebtoken.Jwts; 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.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Component @RequiredArgsConstructor public class FileUtil { private final FileProperties properties; private final StorageAttachmentMapper storageAttachmentMapper; private final StringRedisTemplate stringRedisTemplate; private final StorageBlobMapper storageBlobMapper; private static final String TOKEN_USAGE_KEY_PREFIX = "file:token:usage:"; private static final DateTimeFormatter YEAR_PATH_FORMATTER = DateTimeFormatter.ofPattern("yyyy"); private static final DateTimeFormatter MONTH_DAY_PATH_FORMATTER = DateTimeFormatter.ofPattern("MMdd"); /** * 保存附件信息 * * @param application 文件用途 * @param recordType 关联记录类型 * @param recordId 关联记录id * @param storageBlobDTOS 文件信息 */ public void saveStorageAttachment(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, List storageBlobDTOS) { if (application == null) { throw new RuntimeException("文件用途不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } if (recordId == null || recordId <= 0) { throw new RuntimeException("关联记录id不能为空"); } // 删除旧附件信息 deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageBlobDTOS)) { return; } List storageAttachments = new ArrayList<>(); for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) { StorageAttachment storageAttachment = new StorageAttachment(); storageAttachment.setApplication(application.getType()); storageAttachment.setRecordType(recordType.getType()); storageAttachment.setRecordId(recordId); storageAttachment.setStorageBlobId(storageBlobDTO.getId()); storageAttachment.setDeleted(0L); storageAttachments.add(storageAttachment); } storageAttachmentMapper.insert(storageAttachments); } /** * 保存附件信息 * * @param recordType 关联记录类型 * @param recordId 关联记录id * @param storageBlobDTOS 文件信息 */ public void saveStorageAttachmentByRecordTypeAndRecordId(String application,RecordTypeEnum recordType, Long recordId, List storageBlobDTOS) { if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } if (recordId == null) { throw new RuntimeException("关联记录id不能为空"); } // 删除旧附件信息 if (application == null) { for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) { deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.getByType(storageBlobDTO.getApplication()), recordType, recordId); } } else { deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.getByType(application), recordType, recordId); } if (CollectionUtils.isEmpty(storageBlobDTOS)) { deleteStorageAttachmentsByRecordTypeAndRecordId(recordType, recordId); } List storageAttachments = new ArrayList<>(); for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) { StorageAttachment storageAttachment = new StorageAttachment(); storageAttachment.setApplication(Objects.requireNonNullElseGet(application, () -> ApplicationTypeEnum.getByType(storageBlobDTO.getApplication()).getType())); storageAttachment.setRecordType(recordType.getType()); storageAttachment.setRecordId(recordId); storageAttachment.setStorageBlobId(storageBlobDTO.getId()); storageAttachment.setDeleted(0L); storageAttachments.add(storageAttachment); } storageAttachmentMapper.insert(storageAttachments); } /** * 删除文件信息 * * @param storageBlobIds 文件id */ public void deleteStorageBlobs(List storageBlobIds) { storageBlobMapper.deleteByIds(storageBlobIds); } /** * 通过文件关联id删除文件信息 * * @param storageAttachmentIds 文件id */ public void deleteStorageBlobsByStorageAttachmentIds(List storageAttachmentIds) { List storageAttachments = storageAttachmentMapper.selectByIds(storageAttachmentIds); List storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList()); deleteStorageBlobs(storageBlobIds); } /** * 通过文件用途、关联记录类型、关联记录id删除文件信息 * * @param application 文件用途 * @param recordType 关联记录类型 * @param recordIds 关联记录id */ public void deleteStorageBlobsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum application, RecordTypeEnum recordType, List recordIds) { if (recordIds == null || recordIds.isEmpty()) { throw new RuntimeException("关联记录id不能为空"); } if (application == null) { throw new RuntimeException("文件用途不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } List storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, recordType.getType()) .in(StorageAttachment::getRecordId, recordIds) .eq(StorageAttachment::getApplication, application.getType())); if (CollectionUtils.isNotEmpty(storageAttachments)) { List storageAttachmentIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId) .collect(Collectors.toList()); deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds); } } /** * 通过关联记录类型、关联记录id删除文件信息 * * @param recordType 关联记录类型 * @param recordId 关联记录id */ public void deleteStorageBlobsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) { if (recordId == null || recordId <= 0) { throw new RuntimeException("关联记录id不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } List storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, recordType.getType()) .eq(StorageAttachment::getRecordId, recordId)); if (CollectionUtils.isNotEmpty(storageAttachments)) { List storageAttachmentIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId) .collect(Collectors.toList()); deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds); } } /** * 删除文件关联信息 * * @param storageAttachmentIds 文件关联id */ public void deleteStorageAttachmentsByStorageAttachmentIds(List storageAttachmentIds) { deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds); storageAttachmentMapper.deleteByIds(storageAttachmentIds); } /** * 删除文件关联信息 * * @param application 文件用途 * @param recordType 关联记录类型 * @param recordId 关联记录id */ public void deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) { if (recordId == null || recordId <= 0) { throw new RuntimeException("关联记录id不能为空"); } if (application == null) { throw new RuntimeException("文件用途不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } deleteStorageBlobsByApplicationAndRecordTypeAndRecordIds(application, recordType, Arrays.asList(recordId)); storageAttachmentMapper.delete(new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, recordType.getType()) .eq(StorageAttachment::getRecordId, recordId) .eq(StorageAttachment::getApplication, application.getType())); } /** * 删除文件关联信息 * * @param recordType 关联记录类型 * @param recordId 关联记录id */ public void deleteStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) { if (recordId == null || recordId <= 0) { throw new RuntimeException("关联记录id不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } deleteStorageBlobsByRecordTypeAndRecordId(recordType, recordId); storageAttachmentMapper.delete(new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, recordType.getType()) .eq(StorageAttachment::getRecordId, recordId)); } /** * 批量删除文件关联信息 attachment * * @param application 文件用途 * @param recordType 关联记录类型 * @param recordIds 关联记录id */ public void deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum application, RecordTypeEnum recordType, List recordIds) { if (recordIds == null || recordIds.isEmpty()) { throw new RuntimeException("关联记录id不能为空"); } if (application == null) { throw new RuntimeException("文件用途不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } deleteStorageBlobsByApplicationAndRecordTypeAndRecordIds(application, recordType, recordIds); storageAttachmentMapper.delete(new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, recordType.getType()) .in(StorageAttachment::getRecordId, recordIds) .eq(StorageAttachment::getApplication, application.getType())); } /** * 通过文件关联id获取文件信息 attachment * * @param storageAttachmentIds 文件id */ public List getStorageAttachmentsByStorageAttachmentIds(List storageAttachmentIds) { if (CollectionUtils.isEmpty(storageAttachmentIds)) { throw new RuntimeException("文件id不能为空"); } return storageAttachmentMapper.selectByIds(storageAttachmentIds); } /** * 通过记录类型获取文件信息 attachment(分页) * * @param storageAttachmentDTO 关联记录信息 */ public List getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(StorageAttachmentDTO storageAttachmentDTO) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, storageAttachmentDTO.getRecordType()) .eq(StorageAttachment::getRecordId, storageAttachmentDTO.getRecordId()); if (storageAttachmentDTO.getApplication() != null) { queryWrapper.eq(StorageAttachment::getApplication, storageAttachmentDTO.getApplication()); } List storageAttachments = storageAttachmentMapper.selectList(queryWrapper); if (CollectionUtils.isEmpty(storageAttachments)) { return null; } return getStorageBlobVOsByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList())); } /** * 通过文件用途、关联记录类型、关联记录id获取文件关联信息 attachment * * @param application 文件用途 * @param recordType 关联记录类型 * @param recordId 关联记录id */ public List getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) { if (recordId == null || recordId <= 0) { throw new RuntimeException("关联记录id不能为空"); } if (application == null) { throw new RuntimeException("文件用途不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } return storageAttachmentMapper.selectList(new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, recordType.getType()) .eq(StorageAttachment::getRecordId, recordId) .eq(StorageAttachment::getApplication, application.getType())); } /** * 通过文件关联id获取文件信息 blob * * @param storageAttachmentIds 文件id */ public List getStorageBlobVOsByStorageAttachmentIds(List storageAttachmentIds) { List storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds); if (CollectionUtils.isEmpty(storageAttachments)) { return null; } Map blobIdToAttachmentIdMap = storageAttachments.stream() .collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId)); List storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList()); List storageBlobs = storageBlobMapper.selectByIds(storageBlobIds); List storageBlobDTOS = new ArrayList<>(); for (StorageBlob storageBlob : storageBlobs) { StorageBlobVO storageBlobVO = new StorageBlobVO(); BeanUtils.copyProperties(storageBlob, storageBlobVO); storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO)); storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO)); storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId())); storageBlobDTOS.add(storageBlobVO); } return storageBlobDTOS; } /** * 通过文件用途、关联记录类型、关联记录id获取文件关联信息 attachment * * @param recordType 关联记录类型 * @param recordId 关联记录id */ public List getStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) { if (recordId == null || recordId <= 0) { throw new RuntimeException("关联记录id不能为空"); } if (recordType == null) { throw new RuntimeException("关联记录类型不能为空"); } return storageAttachmentMapper.selectList(new LambdaQueryWrapper() .eq(StorageAttachment::getRecordType, recordType.getType()) .eq(StorageAttachment::getRecordId, recordId)); } /** * 通过文件用途、关联记录类型、关联记录id获取文件信息 blob * * @param application 文件用途 * @param recordType 关联记录类型 * @param recordId 关联记录id */ public List getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return null; } // 构建 storageBlobId -> storageAttachmentId 的映射 Map blobIdToAttachmentIdMap = storageAttachments.stream() .collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId)); List storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList()); List storageBlobs = storageBlobMapper.selectByIds(storageBlobIds); List storageBlobDTOS = new ArrayList<>(); for (StorageBlob storageBlob : storageBlobs) { StorageBlobVO storageBlobVO = new StorageBlobVO(); BeanUtils.copyProperties(storageBlob, storageBlobVO); storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO)); storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO)); storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId())); storageBlobDTOS.add(storageBlobVO); } return storageBlobDTOS; } /** * 通过文件用途、关联记录类型、关联记录id获取文件信息 blob * * @param recordType 关联记录类型 * @param recordId 关联记录id */ public List getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) { List storageAttachments = getStorageAttachmentsByRecordTypeAndRecordId(recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return null; } List storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList()); List storageBlobs = storageBlobMapper.selectByIds(storageBlobIds); List storageBlobDTOS = new ArrayList<>(); for (StorageBlob storageBlob : storageBlobs) { StorageBlobVO storageBlobVO = new StorageBlobVO(); BeanUtils.copyProperties(storageBlob, storageBlobVO); storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO)); storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO)); storageBlobDTOS.add(storageBlobVO); } return storageBlobDTOS; } /** * 通过文件用途、关联记录类型、关联记录id获取文件信息 自定义过期时间(分钟) blob * * @param application 文件用途 * @param recordType 关联记录类型 * @param recordId 关联记录id * @param expired 过期时间 */ public List getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return null; } // 构建 storageBlobId -> storageAttachmentId 的映射 Map blobIdToAttachmentIdMap = storageAttachments.stream() .collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId)); List storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList()); List storageBlobs = storageBlobMapper.selectByIds(storageBlobIds); List storageBlobDTOS = new ArrayList<>(); for (StorageBlob storageBlob : storageBlobs) { StorageBlobVO storageBlobVO = new StorageBlobVO(); BeanUtils.copyProperties(storageBlob, storageBlobVO); storageBlobVO.setPreviewURL(buildSignedUrl(storageBlobVO, "/preview/", expired)); storageBlobVO.setDownloadURL(buildSignedUrl(storageBlobVO, "/download/", expired)); storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId())); storageBlobDTOS.add(storageBlobVO); } return storageBlobDTOS; } /** * 通过文件关联id获取文件信息存在过期时间 自定义过期时间(分钟) blob * * @param storageAttachmentIds 文件id * @param expired 过期时间 */ public List getStorageBlobVOsByStorageAttachmentIds(List storageAttachmentIds, BigDecimal expired) { List storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds); if (CollectionUtils.isEmpty(storageAttachments)) { return null; } List storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList()); List storageBlobs = storageBlobMapper.selectByIds(storageBlobIds); List storageBlobDTOS = new ArrayList<>(); for (StorageBlob storageBlob : storageBlobs) { StorageBlobVO storageBlobVO = new StorageBlobVO(); BeanUtils.copyProperties(storageBlob, storageBlobVO); storageBlobVO.setPreviewURL(buildSignedUrl(storageBlobVO, "/preview/", expired)); storageBlobVO.setDownloadURL(buildSignedUrl(storageBlobVO, "/download/", expired)); storageBlobDTOS.add(storageBlobVO); } return storageBlobDTOS; } /** * 通过文件关联id获取文件信息 attachment * * @param storageAttachmentIds 文件id */ public List getStorageAttachmentVOSByStorageAttachmentIds(List storageAttachmentIds) { List storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } List storageAttachmentVOS = new ArrayList<>(); for (StorageAttachment storageAttachment : storageAttachments) { StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO(); BeanUtils.copyProperties(storageAttachment, storageAttachmentVO); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId())); if (CollectionUtils.isEmpty(storageBlobVOS)) { storageAttachmentVO.setStorageBlobVOS(new ArrayList<>()); } else { storageAttachmentVO.setStorageBlobVOS(storageBlobVOS); } storageAttachmentVOS.add(storageAttachmentVO); } return storageAttachmentVOS; } /** * 通过文件关联id获取文件信息存在过期时间 自定义过期时间(分钟) attachment * * @param storageAttachmentIds 文件id * @param expired 过期时间 */ public List getStorageAttachmentVOSByStorageAttachmentIds(List storageAttachmentIds, BigDecimal expired) { List storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } List storageAttachmentVOS = new ArrayList<>(); for (StorageAttachment storageAttachment : storageAttachments) { StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO(); BeanUtils.copyProperties(storageAttachment, storageAttachmentVO); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()), expired); if (CollectionUtils.isEmpty(storageBlobVOS)) { storageAttachmentVO.setStorageBlobVOS(new ArrayList<>()); } else { storageAttachmentVO.setStorageBlobVOS(storageBlobVOS); } storageAttachmentVOS.add(storageAttachmentVO); } return storageAttachmentVOS; } /** * 通过文件关联id获取文件信息 attachment * * @param application 应用 * @param recordType 记录类型 * @param recordId 记录id */ public List getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } List storageAttachmentVOS = new ArrayList<>(); for (StorageAttachment storageAttachment : storageAttachments) { StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO(); BeanUtils.copyProperties(storageAttachment, storageAttachmentVO); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId())); if (CollectionUtils.isEmpty(storageBlobVOS)) { storageAttachmentVO.setStorageBlobVOS(new ArrayList<>()); } else { storageAttachmentVO.setStorageBlobVOS(storageBlobVOS); } storageAttachmentVOS.add(storageAttachmentVO); } return storageAttachmentVOS; } /** * 通过文件关联id获取文件信息存在过期时间 自定义过期时间(分钟) attachment * * @param application 应用 * @param recordType 记录类型 * @param recordId 记录id * @param expired 过期时间 */ public List getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } List storageAttachmentVOS = new ArrayList<>(); for (StorageAttachment storageAttachment : storageAttachments) { StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO(); BeanUtils.copyProperties(storageAttachment, storageAttachmentVO); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()), expired); if (CollectionUtils.isEmpty(storageBlobVOS)) { storageAttachmentVO.setStorageBlobVOS(new ArrayList<>()); } else { storageAttachmentVO.setStorageBlobVOS(storageBlobVOS); } storageAttachmentVOS.add(storageAttachmentVO); } return storageAttachmentVOS; } /** * 通过文件关联id获取文件预览地址 * * @param storageAttachmentIds 文件关联id */ public List getFilePreviewURLByStorageAttachmentIds(List storageAttachmentIds) { List res = new ArrayList<>(); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds); for (StorageBlobVO storageBlobVO : storageBlobVOS) { res.add(buildSignedPreviewUrl(storageBlobVO)); } return res; } /** * 通过文件关联id获取文件预览地址存在过期时间 自定义过期时间(分钟) * * @param storageAttachmentIds 文件关联id * @param expired 过期时间 */ public List getFilePreviewURLByStorageAttachmentIds(List storageAttachmentIds, BigDecimal expired) { List res = new ArrayList<>(); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds); for (StorageBlobVO storageBlobVO : storageBlobVOS) { res.add(buildSignedUrl(storageBlobVO, "/preview/", expired)); } return res; } /** * 通过文件关联id获取文件下载地址 * * @param storageAttachmentIds 文件关联id */ public List getFileDownloadURLByStorageAttachmentIds(List storageAttachmentIds) { List res = new ArrayList<>(); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds); for (StorageBlobVO storageBlobVO : storageBlobVOS) { res.add(buildSignedDownloadUrl(storageBlobVO)); } return res; } /** * 通过文件关联id获取文件下载地址存在过期时间 自定义过期时间(分钟) * * @param storageAttachmentIds 文件关联id * @param expired 过期时间 */ public List getFileDownloadURLByStorageAttachmentIds(List storageAttachmentIds, BigDecimal expired) { List res = new ArrayList<>(); List storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds); for (StorageBlobVO storageBlobVO : storageBlobVOS) { res.add(buildSignedUrl(storageBlobVO, "/download/", expired)); } return res; } /** * 通过文件关联id获取文件预览地址 * * @param application 应用 * @param recordType 记录类型 * @param recordId 记录id */ public List getFilePreviewURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } return getFilePreviewURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList())); } /** * 通过文件关联id获取文件预览地址存在过期时间 * * @param application 应用 * @param recordType 记录类型 * @param recordId 记录id * @param expired 过期时间(分钟) */ public List getFilePreviewURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } return getFilePreviewURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList()), expired); } /** * 通过文件关联id获取文件下载地址 * * @param application 应用 * @param recordType 记录类型 * @param recordId 记录id */ public List getFileDownloadURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } return getFileDownloadURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList())); } /** * 通过文件关联id获取文件下载地址存在过期时间 * * @param application 应用 * @param recordType 记录类型 * @param recordId 记录id * @param expired 过期时间(分钟) */ public List getFileDownloadURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) { List storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId); if (CollectionUtils.isEmpty(storageAttachments)) { return new ArrayList<>(); } return getFileDownloadURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList()), expired); } public String buildSignedPreviewUrl(StorageBlobVO storageBlob) { return buildSignedUrl(storageBlob, "/preview/", properties.getExpired()); } public String buildSignedDownloadUrl(StorageBlobVO storageBlob) { return buildSignedUrl(storageBlob, "/download/", properties.getExpired()); } /** * 构建带签名的URL * * @param storageBlob 文件元数据 * @param actionPath 操作路径 "/preview/" or "/download/" * @param expired 过期时间 如果不配置,不传参,将使用默认值120分钟 * @return 带签名的URL */ public String buildSignedUrl(StorageBlobVO storageBlob, String actionPath, BigDecimal expired) { if (!Arrays.asList("/preview/", "/download/").contains(actionPath)) { throw new IllegalArgumentException("操作路径参数错误"); } if (storageBlob == null || !StringUtils.hasText(storageBlob.getUidFilename())) { throw new IllegalArgumentException("文件信息不完整"); } String domain = StringUtils.trimTrailingCharacter(properties.getDomain(), '/'); String prefix = properties.getUrlPrefix().startsWith("/") ? properties.getUrlPrefix() : "/" + properties.getUrlPrefix(); String normalizedActionPath = StringUtils.hasText(actionPath) ? actionPath : "/preview/"; if (!normalizedActionPath.startsWith("/")) { normalizedActionPath = "/" + normalizedActionPath; } if (!normalizedActionPath.endsWith("/")) { normalizedActionPath = normalizedActionPath + "/"; } String baseUrl = domain + prefix + normalizedActionPath + storageBlob.getUidFilename(); // -1 表示永久有效,不生成 token,改为 publicKey 组合校验 if (expired != null && BigDecimal.valueOf(-1L).compareTo(expired) == 0) { if (!StringUtils.hasText(storageBlob.getResourceKey())) { throw new IllegalArgumentException("公开链接缺少publicKey"); } return baseUrl + "?publicKey=" + storageBlob.getResourceKey(); } long now = System.currentTimeMillis(); BigDecimal expiredValue = expired == null ? new BigDecimal("120") : expired; long expiredMillis = expiredValue.multiply(new BigDecimal("60000")).longValue(); if (expiredMillis <= 0L) { expiredMillis = 2L * 60L * 60L * 1000L; } 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() .subject(storageBlob.getUidFilename()) .issuedAt(issuedAt) // 新版建议直接调用 .issuedAt() .expiration(expiration) // 新版建议直接调用 .expiration() .claim("path", storageBlob.getPath()) .claim("resourceKey", storageBlob.getResourceKey()) .signWith(key) // 重点:传入上面生成的 key 对象,而不是 String .compact(); cacheTokenUsage(token, expiredMillis); return baseUrl + "?token=" + token; } private void cacheTokenUsage(String token, long expiredMillis) { if (!StringUtils.hasText(token)) { return; } long ttl = expiredMillis > 0L ? expiredMillis : 2L * 60L * 60L * 1000L; stringRedisTemplate.opsForValue().set(buildTokenUsageKey(token), "0", ttl, TimeUnit.MILLISECONDS); } private String buildTokenUsageKey(String token) { return TOKEN_USAGE_KEY_PREFIX + token; } public String buildRelativePath() { LocalDate now = LocalDate.now(); return now.format(YEAR_PATH_FORMATTER) + "/" + now.format(MONTH_DAY_PATH_FORMATTER); } public void validateTokenUsage(String token) { String redisKey = buildTokenUsageKey(token); String currentCountValue = stringRedisTemplate.opsForValue().get(redisKey); if (!StringUtils.hasText(currentCountValue)) { throw new IllegalArgumentException("链接已过期或达到使用次数失效"); } long currentCount = Long.parseLong(currentCountValue); int limit = resolveLimit(); if (currentCount >= limit) { stringRedisTemplate.delete(redisKey); throw new IllegalArgumentException("链接达到使用次数失效"); } Long updatedCount = stringRedisTemplate.opsForValue().increment(redisKey); if (updatedCount != null && updatedCount >= limit) { stringRedisTemplate.delete(redisKey); } } private int resolveLimit() { 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); } }