| src/main/java/com/ruoyi/basic/service/StorageBlobService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | 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/project/common/CommonController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/ruoyi/basic/service/StorageBlobService.java
@@ -23,9 +23,11 @@ * @param files 文件列表 * @return 上传结果 */ List<StorageBlobVO> upload(List<MultipartFile> files); List<StorageBlobVO> upload(List<MultipartFile> files, Boolean isPublic); File getFileByToken(String fileName, String token); public String getDownloadFileName(String fileName); File getPublicFile(String fileName, String publicKey); String getDownloadFileName(String fileName); } src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -25,7 +25,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.util.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
@@ -20,6 +20,7 @@ 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; @@ -39,7 +40,7 @@ private final FileUtil fileUtil; @Override public List<StorageBlobVO> upload(List<MultipartFile> files) { public List<StorageBlobVO> upload(List<MultipartFile> files, Boolean isPublic) { if (CollectionUtils.isEmpty(files)) { throw new IllegalArgumentException("文件不能为空"); } @@ -65,7 +66,7 @@ StorageBlobVO storageBlob; try { file.transferTo(dest); storageBlob = getStorageBlob(file, originalFileName, fileName, relativePath); storageBlob = getStorageBlob(file, originalFileName, fileName, relativePath, isPublic); if (storageBlob == null || storageBlob.getId() == null) { throw new RuntimeException("文件元数据保存失败"); } @@ -109,13 +110,35 @@ return new File(new File(properties.getPath(), path), fileName); } @Override public File getPublicFile(String fileName, String publicKey) { if (!StringUtils.hasText(fileName)) { throw new IllegalArgumentException("文件名不能为空"); } if (!StringUtils.hasText(publicKey)) { throw new IllegalArgumentException("publicKey不能为空"); } StorageBlob storageBlob = storageBlobMapper.selectOne(new LambdaQueryWrapper<StorageBlob>() .eq(StorageBlob::getUidFilename, fileName) .eq(StorageBlob::getResourceKey, publicKey) .last("limit 1")); if (storageBlob == null) { throw new IllegalArgumentException("公开文件不存在或publicKey不匹配"); } String path = storageBlob.getPath(); if (!StringUtils.hasText(path)) { return new File(properties.getPath(), fileName); } return new File(new File(properties.getPath(), path), fileName); } private StorageBlob findStorageBlob(String fileName) { return storageBlobMapper.selectOne(new LambdaQueryWrapper<StorageBlob>() .eq(StorageBlob::getUidFilename, fileName) .last("limit 1")); } private StorageBlobVO getStorageBlob(MultipartFile file, String originalFileName, String fileName, String relativePath) { private StorageBlobVO getStorageBlob(MultipartFile file, String originalFileName, String fileName, String relativePath, Boolean isPublic) { StorageBlobVO storageBlob = new StorageBlobVO(); storageBlob.setResourceKey(UUID.randomUUID().toString().replace("-", "")); storageBlob.setContentType(file.getContentType()); @@ -123,8 +146,13 @@ storageBlob.setUidFilename(fileName); storageBlob.setByteSize(file.getSize()); storageBlob.setPath(relativePath); storageBlob.setPreviewURL(fileUtil.buildSignedPreviewUrl(storageBlob)); storageBlob.setDownloadURL(fileUtil.buildSignedDownloadUrl(storageBlob)); if (isPublic) { storageBlob.setPreviewURL(fileUtil.buildSignedUrl(storageBlob, "/preview/", BigDecimal.valueOf(-1))); storageBlob.setDownloadURL(fileUtil.buildSignedUrl(storageBlob, "/download/", BigDecimal.valueOf(-1))); } else { storageBlob.setPreviewURL(fileUtil.buildSignedPreviewUrl(storageBlob)); storageBlob.setDownloadURL(fileUtil.buildSignedDownloadUrl(storageBlob)); } int affectedRows = storageBlobMapper.insert(storageBlob); if (affectedRows <= 0) { throw new RuntimeException("文件元数据保存失败"); src/main/java/com/ruoyi/basic/utils/FileUtil.java
@@ -565,8 +565,28 @@ 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(); long expiredMillis = expired.multiply(new BigDecimal("60000")).longValue(); BigDecimal expiredValue = expired == null ? new BigDecimal("120") : expired; long expiredMillis = expiredValue.multiply(new BigDecimal("60000")).longValue(); if (expiredMillis <= 0L) { expiredMillis = 2L * 60L * 60L * 1000L; } @@ -583,16 +603,7 @@ .signWith(key) // 重点:传入上面生成的 key 对象,而不是 String .compact(); cacheTokenUsage(token, expiredMillis); 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 + "/"; } return domain + prefix + normalizedActionPath + storageBlob.getUidFilename() + "?token=" + token; return baseUrl + "?token=" + token; } private void cacheTokenUsage(String token, long expiredMillis) { src/main/java/com/ruoyi/project/common/CommonController.java
@@ -128,15 +128,32 @@ // } @PostMapping({"/upload"}) @Operation(summary = "文件上传") public R upload(@RequestParam("files") List<MultipartFile> files) throws Exception { return R.ok(storageBlobService.upload(files)); public R upload(@RequestParam("files") List<MultipartFile> files) { return R.ok(storageBlobService.upload(files, false)); } /** * 公共文件上传 * 此接口上传的文件永久有效,慎用 */ @PostMapping({"/public/upload"}) @Operation(summary = "文件上传") public R publicUpload(@RequestParam("files") List<MultipartFile> files) { return R.ok(storageBlobService.upload(files, true)); } @GetMapping("/download/{fileName}") @Anonymous public void download(@PathVariable String fileName, @RequestParam("token") String token, HttpServletResponse response) throws Exception { File file = storageBlobService.getFileByToken(fileName, token); public void download(@PathVariable String fileName, @RequestParam(value = "token", required = false) String token, @RequestParam(value = "publicKey", required = false) String publicKey, HttpServletResponse response) throws Exception { File file; if (publicKey != null) { file = fileUtil.compressFile(storageBlobService.getPublicFile(fileName, publicKey)); } else { file = fileUtil.compressFile(storageBlobService.getFileByToken(fileName, token)); } 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); @@ -147,8 +164,14 @@ @GetMapping("/preview/{fileName}") @Anonymous public ResponseEntity<FileSystemResource> preview(@PathVariable String fileName, @RequestParam("token") String token) throws Exception { File file = fileUtil.compressFile(storageBlobService.getFileByToken(fileName, token)); @RequestParam(value = "token", required = false) String token, @RequestParam(value = "publicKey", required = false) String publicKey) throws Exception { File file; if (publicKey != null) { file = fileUtil.compressFile(storageBlobService.getPublicFile(fileName, publicKey)); } else { file = fileUtil.compressFile(storageBlobService.getFileByToken(fileName, token)); } String contentType = Files.probeContentType(file.toPath()); ContentDisposition contentDisposition = ContentDisposition.inline()