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<StorageBlobDTO> 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<StorageAttachment> 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<StorageBlobDTO> 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<StorageAttachment> 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<Long> storageBlobIds) {
|
storageBlobMapper.deleteByIds(storageBlobIds);
|
}
|
|
/**
|
* 通过文件关联id删除文件信息
|
*
|
* @param storageAttachmentIds 文件id
|
*/
|
public void deleteStorageBlobsByStorageAttachmentIds(List<Long> storageAttachmentIds) {
|
List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectByIds(storageAttachmentIds);
|
List<Long> 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<Long> recordIds) {
|
if (recordIds == null || recordIds.isEmpty()) {
|
throw new RuntimeException("关联记录id不能为空");
|
}
|
if (application == null) {
|
throw new RuntimeException("文件用途不能为空");
|
}
|
if (recordType == null) {
|
throw new RuntimeException("关联记录类型不能为空");
|
}
|
List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
|
.eq(StorageAttachment::getRecordType, recordType.getType())
|
.in(StorageAttachment::getRecordId, recordIds)
|
.eq(StorageAttachment::getApplication, application.getType()));
|
if (CollectionUtils.isNotEmpty(storageAttachments)) {
|
List<Long> 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<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
|
.eq(StorageAttachment::getRecordType, recordType.getType())
|
.eq(StorageAttachment::getRecordId, recordId));
|
if (CollectionUtils.isNotEmpty(storageAttachments)) {
|
List<Long> storageAttachmentIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId)
|
.collect(Collectors.toList());
|
deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds);
|
}
|
}
|
|
/**
|
* 删除文件关联信息
|
*
|
* @param storageAttachmentIds 文件关联id
|
*/
|
public void deleteStorageAttachmentsByStorageAttachmentIds(List<Long> 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<StorageAttachment>()
|
.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<StorageAttachment>()
|
.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<Long> 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<StorageAttachment>()
|
.eq(StorageAttachment::getRecordType, recordType.getType())
|
.in(StorageAttachment::getRecordId, recordIds)
|
.eq(StorageAttachment::getApplication, application.getType()));
|
}
|
|
/**
|
* 通过文件关联id获取文件信息 attachment
|
*
|
* @param storageAttachmentIds 文件id
|
*/
|
public List<StorageAttachment> getStorageAttachmentsByStorageAttachmentIds(List<Long> storageAttachmentIds) {
|
if (CollectionUtils.isEmpty(storageAttachmentIds)) {
|
throw new RuntimeException("文件id不能为空");
|
}
|
return storageAttachmentMapper.selectByIds(storageAttachmentIds);
|
}
|
|
/**
|
* 通过记录类型获取文件信息 attachment(分页)
|
*
|
* @param storageAttachmentDTO 关联记录信息
|
*/
|
public List<StorageBlobVO> getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(StorageAttachmentDTO storageAttachmentDTO) {
|
LambdaQueryWrapper<StorageAttachment> queryWrapper = new LambdaQueryWrapper<StorageAttachment>()
|
.eq(StorageAttachment::getRecordType, storageAttachmentDTO.getRecordType())
|
.eq(StorageAttachment::getRecordId, storageAttachmentDTO.getRecordId());
|
if (storageAttachmentDTO.getApplication() != null) {
|
queryWrapper.eq(StorageAttachment::getApplication, storageAttachmentDTO.getApplication());
|
}
|
List<StorageAttachment> 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<StorageAttachment> 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<StorageAttachment>()
|
.eq(StorageAttachment::getRecordType, recordType.getType())
|
.eq(StorageAttachment::getRecordId, recordId)
|
.eq(StorageAttachment::getApplication, application.getType()));
|
}
|
|
/**
|
* 通过文件关联id获取文件信息 blob
|
*
|
* @param storageAttachmentIds 文件id
|
*/
|
public List<StorageBlobVO> getStorageBlobVOsByStorageAttachmentIds(List<Long> storageAttachmentIds) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return null;
|
}
|
Map<Long, Long> blobIdToAttachmentIdMap = storageAttachments.stream()
|
.collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId));
|
|
List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
|
List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
|
List<StorageBlobVO> 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<StorageAttachment> 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<StorageAttachment>()
|
.eq(StorageAttachment::getRecordType, recordType.getType())
|
.eq(StorageAttachment::getRecordId, recordId));
|
}
|
|
/**
|
* 通过文件用途、关联记录类型、关联记录id获取文件信息 blob
|
*
|
* @param application 文件用途
|
* @param recordType 关联记录类型
|
* @param recordId 关联记录id
|
*/
|
public List<StorageBlobVO> getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return null;
|
}
|
// 构建 storageBlobId -> storageAttachmentId 的映射
|
Map<Long, Long> blobIdToAttachmentIdMap = storageAttachments.stream()
|
.collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId));
|
|
List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
|
List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
|
List<StorageBlobVO> 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<StorageBlobVO> getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByRecordTypeAndRecordId(recordType, recordId);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return null;
|
}
|
List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
|
List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
|
List<StorageBlobVO> 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<StorageBlobVO> getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return null;
|
}
|
// 构建 storageBlobId -> storageAttachmentId 的映射
|
Map<Long, Long> blobIdToAttachmentIdMap = storageAttachments.stream()
|
.collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId));
|
|
List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
|
List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
|
List<StorageBlobVO> 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<StorageBlobVO> getStorageBlobVOsByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return null;
|
}
|
List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
|
List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
|
List<StorageBlobVO> 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<StorageAttachmentVO> getStorageAttachmentVOSByStorageAttachmentIds(List<Long> storageAttachmentIds) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return new ArrayList<>();
|
}
|
List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
|
for (StorageAttachment storageAttachment : storageAttachments) {
|
StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
|
BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
|
List<StorageBlobVO> 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<StorageAttachmentVO> getStorageAttachmentVOSByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return new ArrayList<>();
|
}
|
List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
|
for (StorageAttachment storageAttachment : storageAttachments) {
|
StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
|
BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
|
List<StorageBlobVO> 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<StorageAttachmentVO> getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return new ArrayList<>();
|
}
|
List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
|
for (StorageAttachment storageAttachment : storageAttachments) {
|
StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
|
BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
|
List<StorageBlobVO> 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<StorageAttachmentVO> getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
|
List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
|
if (CollectionUtils.isEmpty(storageAttachments)) {
|
return new ArrayList<>();
|
}
|
List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
|
for (StorageAttachment storageAttachment : storageAttachments) {
|
StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
|
BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
|
List<StorageBlobVO> 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<String> getFilePreviewURLByStorageAttachmentIds(List<Long> storageAttachmentIds) {
|
List<String> res = new ArrayList<>();
|
List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds);
|
for (StorageBlobVO storageBlobVO : storageBlobVOS) {
|
res.add(buildSignedPreviewUrl(storageBlobVO));
|
}
|
return res;
|
}
|
|
/**
|
* 通过文件关联id获取文件预览地址存在过期时间 自定义过期时间(分钟)
|
*
|
* @param storageAttachmentIds 文件关联id
|
* @param expired 过期时间
|
*/
|
public List<String> getFilePreviewURLByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
|
List<String> res = new ArrayList<>();
|
List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds);
|
for (StorageBlobVO storageBlobVO : storageBlobVOS) {
|
res.add(buildSignedUrl(storageBlobVO, "/preview/", expired));
|
}
|
return res;
|
}
|
|
/**
|
* 通过文件关联id获取文件下载地址
|
*
|
* @param storageAttachmentIds 文件关联id
|
*/
|
public List<String> getFileDownloadURLByStorageAttachmentIds(List<Long> storageAttachmentIds) {
|
List<String> res = new ArrayList<>();
|
List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds);
|
for (StorageBlobVO storageBlobVO : storageBlobVOS) {
|
res.add(buildSignedDownloadUrl(storageBlobVO));
|
}
|
return res;
|
}
|
|
/**
|
* 通过文件关联id获取文件下载地址存在过期时间 自定义过期时间(分钟)
|
*
|
* @param storageAttachmentIds 文件关联id
|
* @param expired 过期时间
|
*/
|
public List<String> getFileDownloadURLByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
|
List<String> res = new ArrayList<>();
|
List<StorageBlobVO> 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<String> getFilePreviewURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
|
List<StorageAttachment> 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<String> getFilePreviewURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
|
List<StorageAttachment> 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<String> getFileDownloadURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
|
List<StorageAttachment> 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<String> getFileDownloadURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
|
List<StorageAttachment> 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);
|
}
|
|
|
}
|