From 4c15249264509c7001f6a1fd865bb80b8d030bda Mon Sep 17 00:00:00 2001 From: chenyuan Date: Mon, 3 Aug 2026 17:27:51 +0800 Subject: [PATCH] fix: store uploads under relative file directory --- .../config/security/WebConfigurerAdapter.java | 3 +- .../common/CommonUploadController.java | 8 +-- .../stu/ProductPlanningController.java | 5 +- .../linkCommerce/util/file/LocalFileUtil.java | 51 ++++++++++++++++--- src/main/resources/application-dev.yml | 2 +- .../common/CommonUploadControllerTest.java | 2 +- .../util/file/LocalFileUtilTest.java | 44 ++++++++++++++++ 7 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 src/test/java/com/sztzjy/linkCommerce/util/file/LocalFileUtilTest.java diff --git a/src/main/java/com/sztzjy/linkCommerce/config/security/WebConfigurerAdapter.java b/src/main/java/com/sztzjy/linkCommerce/config/security/WebConfigurerAdapter.java index 2d737a1..7aa5040 100644 --- a/src/main/java/com/sztzjy/linkCommerce/config/security/WebConfigurerAdapter.java +++ b/src/main/java/com/sztzjy/linkCommerce/config/security/WebConfigurerAdapter.java @@ -87,7 +87,8 @@ public class WebConfigurerAdapter implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/file/**").addResourceLocations("file:" + normalizeFileLocation(filePath)).setCachePeriod(0); + String storageRoot = LocalFileUtil.resolveStorageRoot(filePath).toString(); + registry.addResourceHandler("/file/**").addResourceLocations("file:" + normalizeFileLocation(storageRoot)).setCachePeriod(0); registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0); } diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/common/CommonUploadController.java b/src/main/java/com/sztzjy/linkCommerce/controller/common/CommonUploadController.java index 2c6580d..19d01ef 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/common/CommonUploadController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/common/CommonUploadController.java @@ -1,6 +1,7 @@ package com.sztzjy.linkCommerce.controller.common; import com.sztzjy.linkCommerce.util.file.IFileUtil; +import com.sztzjy.linkCommerce.util.file.LocalFileUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -41,11 +42,6 @@ public class CommonUploadController { } private String publicFileUrl(String filePath) { - String baseUrl = fileUrl == null ? "" : fileUrl.trim(); - while (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.length() - 1); - } - String relativePath = filePath == null ? "" : filePath.trim(); - return baseUrl + (relativePath.startsWith("/") ? relativePath : "/" + relativePath); + return LocalFileUtil.publicFileUrl(fileUrl, filePath); } } diff --git a/src/main/java/com/sztzjy/linkCommerce/controller/stu/ProductPlanningController.java b/src/main/java/com/sztzjy/linkCommerce/controller/stu/ProductPlanningController.java index 548d9ca..5063297 100644 --- a/src/main/java/com/sztzjy/linkCommerce/controller/stu/ProductPlanningController.java +++ b/src/main/java/com/sztzjy/linkCommerce/controller/stu/ProductPlanningController.java @@ -11,6 +11,7 @@ import com.sztzjy.linkCommerce.util.CompareListsUtil; import com.sztzjy.linkCommerce.util.FileHashUtil; import com.sztzjy.linkCommerce.util.ResultEntity; import com.sztzjy.linkCommerce.util.file.IFileUtil; +import com.sztzjy.linkCommerce.util.file.LocalFileUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -401,14 +402,14 @@ public class ProductPlanningController { uploadExperment.setModule(module); uploadExperment.setUserId(userId); stuUploadExpermentMapper.insertSelective(uploadExperment); - return new ResultEntity<>(HttpStatus.OK, "上传成功!", url + filePath); + return new ResultEntity<>(HttpStatus.OK, "上传成功!", LocalFileUtil.publicFileUrl(url, filePath)); } else { StuUploadExperment uploadExperment = stuUploadExpermentList.get(0); uploadExperment.setUpdateTime(new Date()); uploadExperment.setFilePath(filePath); stuUploadExpermentMapper.updateByPrimaryKeySelective(uploadExperment); - return new ResultEntity<>(HttpStatus.OK, "上传成功!", url + filePath); + return new ResultEntity<>(HttpStatus.OK, "上传成功!", LocalFileUtil.publicFileUrl(url, filePath)); } diff --git a/src/main/java/com/sztzjy/linkCommerce/util/file/LocalFileUtil.java b/src/main/java/com/sztzjy/linkCommerce/util/file/LocalFileUtil.java index b7a3c55..fe2dfc1 100644 --- a/src/main/java/com/sztzjy/linkCommerce/util/file/LocalFileUtil.java +++ b/src/main/java/com/sztzjy/linkCommerce/util/file/LocalFileUtil.java @@ -10,6 +10,8 @@ import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -18,11 +20,17 @@ import java.util.List; public class LocalFileUtil implements IFileUtil{ private final static List excludeSp = Arrays.asList("exe", "bin", "sh"); + private static final String FILE_PREFIX = "file/"; private final String localPath; public LocalFileUtil(String localPath) { - this.localPath = localPath; + this.localPath = resolveStorageRoot(localPath).toString(); + try { + Files.createDirectories(Paths.get(this.localPath)); + } catch (IOException exception) { + throw new IllegalArgumentException("创建文件存储目录失败", exception); + } } @Override @@ -109,10 +117,28 @@ public class LocalFileUtil implements IFileUtil{ @Override public String getFullPath(String relativePath) { - if (!relativePath.startsWith("/")) { - relativePath = "/" + relativePath; + Path root = Paths.get(localPath); + Path target = root.resolve(normalizeStorageRelativePath(relativePath)).normalize(); + Assert.isTrue(target.startsWith(root), "文件路径错误"); + return target.toString(); + } + + public static Path resolveStorageRoot(String configuredPath) { + Assert.hasText(configuredPath, "文件存储路径不能为空"); + Path configured = Paths.get(configuredPath.trim()); + if (!configured.isAbsolute()) { + configured = Paths.get(System.getProperty("user.dir")).resolve(configured); } - return localPath + relativePath; + return configured.toAbsolutePath().normalize(); + } + + public static String publicFileUrl(String fileUrl, String storedPath) { + Assert.hasText(fileUrl, "文件访问地址不能为空"); + String baseUrl = fileUrl.trim(); + while (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + return baseUrl + "/" + normalizeStorageRelativePath(storedPath); } @Override @@ -137,8 +163,8 @@ public class LocalFileUtil implements IFileUtil{ String year = Integer.toString(c.get(Calendar.YEAR)); String month = Integer.toString(c.get(Calendar.MONTH) + 1); String day = Integer.toString(c.get(Calendar.DATE)); - StringBuilder path = new StringBuilder(); - path.append("/").append(year) + StringBuilder path = new StringBuilder(FILE_PREFIX); + path.append(year) .append("/").append(month) .append("/").append(day) .append("/").append(fileName); @@ -173,4 +199,17 @@ public class LocalFileUtil implements IFileUtil{ throw new IllegalArgumentException("上传文件失败,IO错误"); } } + + private static String normalizeStorageRelativePath(String relativePath) { + Assert.hasText(relativePath, "文件路径不能为空"); + String normalized = relativePath.trim().replace("\\", "/"); + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + if (normalized.startsWith(FILE_PREFIX)) { + normalized = normalized.substring(FILE_PREFIX.length()); + } + Assert.hasText(normalized, "文件路径不能为空"); + return normalized; + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index c7f826b..171a2f7 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -20,7 +20,7 @@ spring: # 文件存储 file: type: local - path: E:/workspace/dianshang/link_commerce/uploadFile + path: file url: ${FILE_URL:http://localhost:7548/file} #path: D:/tianzeProject/linkCommerce/ #url: "http://192.168.2.8:147/file" diff --git a/src/test/java/com/sztzjy/linkCommerce/controller/common/CommonUploadControllerTest.java b/src/test/java/com/sztzjy/linkCommerce/controller/common/CommonUploadControllerTest.java index 64b82eb..b355ffd 100644 --- a/src/test/java/com/sztzjy/linkCommerce/controller/common/CommonUploadControllerTest.java +++ b/src/test/java/com/sztzjy/linkCommerce/controller/common/CommonUploadControllerTest.java @@ -19,7 +19,7 @@ class CommonUploadControllerTest { @Test void uploadReturnsAbsoluteConfiguredFileUrl() { IFileUtil fileUtil = mock(IFileUtil.class); - when(fileUtil.upload(any(MultipartFile.class))).thenReturn("/2026/8/1/example.docx"); + when(fileUtil.upload(any(MultipartFile.class))).thenReturn("file/2026/8/1/example.docx"); CommonUploadController controller = new CommonUploadController(); ReflectionTestUtils.setField(controller, "fileUtil", fileUtil); ReflectionTestUtils.setField(controller, "fileUrl", "https://files.example.com/file/"); diff --git a/src/test/java/com/sztzjy/linkCommerce/util/file/LocalFileUtilTest.java b/src/test/java/com/sztzjy/linkCommerce/util/file/LocalFileUtilTest.java new file mode 100644 index 0000000..6d73958 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/util/file/LocalFileUtilTest.java @@ -0,0 +1,44 @@ +package com.sztzjy.linkCommerce.util.file; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class LocalFileUtilTest { + + @Test + void createsRelativeFileRootAndUsesFilePrefixedDatePaths() throws IOException { + Path relativeRoot = Paths.get("target", "upload-root-" + UUID.randomUUID()); + try { + LocalFileUtil fileUtil = new LocalFileUtil(relativeRoot.toString()); + + assertTrue(Files.isDirectory(relativeRoot)); + String path = fileUtil.getDiskRelativePath("example", "pdf"); + assertTrue(path.startsWith("file/")); + assertTrue(path.endsWith("/example.pdf")); + assertEquals(relativeRoot.toAbsolutePath().normalize().resolve(path.substring("file/".length())).toString(), + fileUtil.getFullPath(path)); + assertEquals(relativeRoot.toAbsolutePath().normalize().resolve("2024/1/2/legacy.pdf").toString(), + fileUtil.getFullPath("/2024/1/2/legacy.pdf")); + } finally { + if (Files.exists(relativeRoot)) { + try (java.util.stream.Stream files = Files.walk(relativeRoot)) { + files.sorted(java.util.Comparator.reverseOrder()).forEach(path -> { + try { + Files.deleteIfExists(path); + } catch (IOException exception) { + throw new IllegalStateException(exception); + } + }); + } + } + } + } +}