fix: store uploads under relative file directory

main
chenyuan 4 weeks ago
parent c956d06d49
commit 4c15249264

@ -87,7 +87,8 @@ public class WebConfigurerAdapter implements WebMvcConfigurer {
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { 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); registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
} }

@ -1,6 +1,7 @@
package com.sztzjy.linkCommerce.controller.common; package com.sztzjy.linkCommerce.controller.common;
import com.sztzjy.linkCommerce.util.file.IFileUtil; import com.sztzjy.linkCommerce.util.file.IFileUtil;
import com.sztzjy.linkCommerce.util.file.LocalFileUtil;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -41,11 +42,6 @@ public class CommonUploadController {
} }
private String publicFileUrl(String filePath) { private String publicFileUrl(String filePath) {
String baseUrl = fileUrl == null ? "" : fileUrl.trim(); return LocalFileUtil.publicFileUrl(fileUrl, filePath);
while (baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
}
String relativePath = filePath == null ? "" : filePath.trim();
return baseUrl + (relativePath.startsWith("/") ? relativePath : "/" + relativePath);
} }
} }

@ -11,6 +11,7 @@ import com.sztzjy.linkCommerce.util.CompareListsUtil;
import com.sztzjy.linkCommerce.util.FileHashUtil; import com.sztzjy.linkCommerce.util.FileHashUtil;
import com.sztzjy.linkCommerce.util.ResultEntity; import com.sztzjy.linkCommerce.util.ResultEntity;
import com.sztzjy.linkCommerce.util.file.IFileUtil; import com.sztzjy.linkCommerce.util.file.IFileUtil;
import com.sztzjy.linkCommerce.util.file.LocalFileUtil;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
@ -401,14 +402,14 @@ public class ProductPlanningController {
uploadExperment.setModule(module); uploadExperment.setModule(module);
uploadExperment.setUserId(userId); uploadExperment.setUserId(userId);
stuUploadExpermentMapper.insertSelective(uploadExperment); stuUploadExpermentMapper.insertSelective(uploadExperment);
return new ResultEntity<>(HttpStatus.OK, "上传成功!", url + filePath); return new ResultEntity<>(HttpStatus.OK, "上传成功!", LocalFileUtil.publicFileUrl(url, filePath));
} else { } else {
StuUploadExperment uploadExperment = stuUploadExpermentList.get(0); StuUploadExperment uploadExperment = stuUploadExpermentList.get(0);
uploadExperment.setUpdateTime(new Date()); uploadExperment.setUpdateTime(new Date());
uploadExperment.setFilePath(filePath); uploadExperment.setFilePath(filePath);
stuUploadExpermentMapper.updateByPrimaryKeySelective(uploadExperment); stuUploadExpermentMapper.updateByPrimaryKeySelective(uploadExperment);
return new ResultEntity<>(HttpStatus.OK, "上传成功!", url + filePath); return new ResultEntity<>(HttpStatus.OK, "上传成功!", LocalFileUtil.publicFileUrl(url, filePath));
} }

@ -10,6 +10,8 @@ import javax.servlet.http.HttpServletResponse;
import java.io.*; import java.io.*;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -18,11 +20,17 @@ import java.util.List;
public class LocalFileUtil implements IFileUtil{ public class LocalFileUtil implements IFileUtil{
private final static List<String> excludeSp = Arrays.asList("exe", "bin", "sh"); private final static List<String> excludeSp = Arrays.asList("exe", "bin", "sh");
private static final String FILE_PREFIX = "file/";
private final String localPath; private final String localPath;
public LocalFileUtil(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 @Override
@ -109,10 +117,28 @@ public class LocalFileUtil implements IFileUtil{
@Override @Override
public String getFullPath(String relativePath) { public String getFullPath(String relativePath) {
if (!relativePath.startsWith("/")) { Path root = Paths.get(localPath);
relativePath = "/" + relativePath; 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 @Override
@ -137,8 +163,8 @@ public class LocalFileUtil implements IFileUtil{
String year = Integer.toString(c.get(Calendar.YEAR)); String year = Integer.toString(c.get(Calendar.YEAR));
String month = Integer.toString(c.get(Calendar.MONTH) + 1); String month = Integer.toString(c.get(Calendar.MONTH) + 1);
String day = Integer.toString(c.get(Calendar.DATE)); String day = Integer.toString(c.get(Calendar.DATE));
StringBuilder path = new StringBuilder(); StringBuilder path = new StringBuilder(FILE_PREFIX);
path.append("/").append(year) path.append(year)
.append("/").append(month) .append("/").append(month)
.append("/").append(day) .append("/").append(day)
.append("/").append(fileName); .append("/").append(fileName);
@ -173,4 +199,17 @@ public class LocalFileUtil implements IFileUtil{
throw new IllegalArgumentException("上传文件失败,IO错误"); 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;
}
} }

@ -20,7 +20,7 @@ spring:
# 文件存储 # 文件存储
file: file:
type: local type: local
path: E:/workspace/dianshang/link_commerce/uploadFile path: file
url: ${FILE_URL:http://localhost:7548/file} url: ${FILE_URL:http://localhost:7548/file}
#path: D:/tianzeProject/linkCommerce/ #path: D:/tianzeProject/linkCommerce/
#url: "http://192.168.2.8:147/file" #url: "http://192.168.2.8:147/file"

@ -19,7 +19,7 @@ class CommonUploadControllerTest {
@Test @Test
void uploadReturnsAbsoluteConfiguredFileUrl() { void uploadReturnsAbsoluteConfiguredFileUrl() {
IFileUtil fileUtil = mock(IFileUtil.class); 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(); CommonUploadController controller = new CommonUploadController();
ReflectionTestUtils.setField(controller, "fileUtil", fileUtil); ReflectionTestUtils.setField(controller, "fileUtil", fileUtil);
ReflectionTestUtils.setField(controller, "fileUrl", "https://files.example.com/file/"); ReflectionTestUtils.setField(controller, "fileUrl", "https://files.example.com/file/");

@ -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<Path> files = Files.walk(relativeRoot)) {
files.sorted(java.util.Comparator.reverseOrder()).forEach(path -> {
try {
Files.deleteIfExists(path);
} catch (IOException exception) {
throw new IllegalStateException(exception);
}
});
}
}
}
}
}
Loading…
Cancel
Save