package cn.iocoder.yudao.module.system.service.storage; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO; import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageBlobDO; import cn.iocoder.yudao.module.system.dal.mysql.storage.SystemStorageBlobMapper; import cn.iocoder.yudao.module.system.framework.storage.config.StorageProperties; import cn.iocoder.yudao.module.system.util.storage.StorageFileUtil; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import javax.crypto.SecretKey; import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; /** * 文件主表 Service 实现类 * * @author 芋道源码 */ @Service @RequiredArgsConstructor public class SystemStorageBlobServiceImpl implements SystemStorageBlobService { private final StorageProperties properties; @Resource private SystemStorageBlobMapper storageBlobMapper; private final StorageFileUtil fileUtil; @Override public List upload(List files, Boolean isPublic) { if (files == null || files.isEmpty()) { throw exception(STORAGE_FILE_EMPTY); } List result = new ArrayList<>(); for (MultipartFile file : files) { if (file == null || file.isEmpty()) { throw exception(STORAGE_FILE_EMPTY); } String originalFileName = StringUtils.hasText(file.getOriginalFilename()) ? StringUtils.cleanPath(file.getOriginalFilename()) : UUID.randomUUID().toString(); String fileName = UUID.randomUUID() + "_" + originalFileName; String relativePath = fileUtil.buildRelativePath(); File targetDirectory = new File(properties.getPath(), relativePath); if (!targetDirectory.exists() && !targetDirectory.mkdirs()) { throw exception(STORAGE_BLOB_UPLOAD_FAILED); } File dest = new File(targetDirectory, fileName); SystemStorageBlobRespVO blobRespVO; try { file.transferTo(dest); blobRespVO = createStorageBlob(file, originalFileName, fileName, relativePath, isPublic); if (blobRespVO == null || blobRespVO.getId() == null) { throw exception(STORAGE_BLOB_UPLOAD_FAILED); } } catch (RuntimeException e) { if (dest.exists()) { dest.delete(); } throw e; } catch (IOException e) { throw exception(STORAGE_BLOB_UPLOAD_FAILED); } result.add(blobRespVO); } return result; } @Override public File getFileByToken(String fileName, String token) { if (!StringUtils.hasText(token)) { throw exception(STORAGE_BLOB_TOKEN_INVALID); } String secretStr = properties.getJwtSecret(); SecretKey key = Keys.hmacShaKeyFor(secretStr.getBytes(StandardCharsets.UTF_8)); Claims claims; try { claims = Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(token) .getPayload(); } catch (Exception e) { throw exception(STORAGE_BLOB_TOKEN_INVALID); } if (!fileName.equals(claims.getSubject())) { throw exception(STORAGE_BLOB_TOKEN_MISMATCH); } fileUtil.validateTokenUsage(token); SystemStorageBlobDO blob = storageBlobMapper.selectByUidFilename(fileName); String path = blob == null ? claims.get("path", String.class) : blob.getPath(); if (!StringUtils.hasText(path)) { return new File(properties.getPath(), fileName); } return new File(new File(properties.getPath(), path), fileName); } @Override public File getPublicFile(String fileName, String publicKey) { if (!StringUtils.hasText(fileName)) { throw exception(STORAGE_BLOB_NOT_EXISTS); } if (!StringUtils.hasText(publicKey)) { throw exception(STORAGE_BLOB_PUBLIC_KEY_INVALID); } SystemStorageBlobDO blob = storageBlobMapper.selectByUidFilenameAndResourceKey(fileName, publicKey); if (blob == null) { throw exception(STORAGE_BLOB_PUBLIC_KEY_INVALID); } String path = blob.getPath(); if (!StringUtils.hasText(path)) { return new File(properties.getPath(), fileName); } return new File(new File(properties.getPath(), path), fileName); } @Override public String getDownloadFileName(String fileName) { SystemStorageBlobDO blob = storageBlobMapper.selectByUidFilename(fileName); if (blob == null || !StringUtils.hasText(blob.getOriginalFilename())) { return fileName; } return blob.getOriginalFilename(); } @Override public SystemStorageBlobDO getStorageBlob(Long id) { return storageBlobMapper.selectById(id); } @Override public void deleteStorageBlobs(List ids) { fileUtil.deleteStorageBlobs(ids); } private SystemStorageBlobRespVO createStorageBlob(MultipartFile file, String originalFileName, String fileName, String relativePath, Boolean isPublic) { SystemStorageBlobDO blob = new SystemStorageBlobDO(); blob.setResourceKey(UUID.randomUUID().toString().replace("-", "")); blob.setContentType(file.getContentType()); blob.setOriginalFilename(originalFileName); blob.setUidFilename(fileName); blob.setByteSize(file.getSize()); blob.setPath(relativePath); storageBlobMapper.insert(blob); // 构建返回 VO SystemStorageBlobRespVO respVO = BeanUtils.toBean(blob, SystemStorageBlobRespVO.class); if (Boolean.TRUE.equals(isPublic)) { respVO.setPreviewURL(fileUtil.buildSignedUrl(blob, "/preview/", BigDecimal.valueOf(-1))); respVO.setDownloadURL(fileUtil.buildSignedUrl(blob, "/download/", BigDecimal.valueOf(-1))); } else { respVO.setPreviewURL(fileUtil.buildSignedPreviewUrl(blob)); respVO.setUrl(fileUtil.buildSignedPreviewUrl(blob)); respVO.setName(blob.getOriginalFilename()); respVO.setDownloadURL(fileUtil.buildSignedDownloadUrl(blob)); } return respVO; } }