Explorar o código

fix(backend): 上传存储路径统一 upload.base-dir - 修复 transferTo 相对路径落盘失败与 /uploads URL 404

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Xiaogang Liao hai 1 mes
pai
achega
2b492ee4fd

+ 5 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/ServiceRoleApplicationController.java

@@ -7,6 +7,7 @@ import com.etotem.cfc.service.ServiceRoleApplicationService;
 import com.etotem.cfc.service.api.WechatServiceInterface;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -30,6 +31,9 @@ public class ServiceRoleApplicationController {
     @Resource
     private WechatServiceInterface wechatService;
 
+    @Value("${upload.base-dir:/data/cfc-uploads}")
+    private String uploadBaseDir;
+
     @PostMapping("/apply")
     @Operation(summary = "提交服务角色申请")
     public Result apply(@RequestAttribute("userId") Long userId,
@@ -64,7 +68,7 @@ public class ServiceRoleApplicationController {
         }
 
         try {
-            String uploadDir = System.getProperty("user.dir") + "/uploads/service-role/" + userId;
+            String uploadDir = uploadBaseDir + "/service-role/" + userId;
             File dirFile = new File(uploadDir);
             if (!dirFile.exists()) {
                 dirFile.mkdirs();

+ 14 - 7
cfc-backend/src/main/java/com/etotem/cfc/service/impl/LocalDiskStorageService.java

@@ -17,15 +17,17 @@ import java.util.UUID;
 @Service
 public class LocalDiskStorageService implements StorageService {
 
-    @Value("${file.upload-dir:./uploads}")
+    @Value("${upload.base-dir:/data/cfc-uploads}")
     private String uploadDir;
 
     @Override
     public String storeFile(MultipartFile file) {
         try {
-            // 生成日期路径:uploads/2026/04/05/
+            // 生成日期路径:2026/04/05/
             String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
-            Path uploadPath = Paths.get(uploadDir, datePath);
+            // 必须使用绝对路径:transferTo 对相对路径会交给 Tomcat 按 multipart 临时目录解析,
+            // 目标父目录不存在导致 FileNotFoundException("文件存储失败")
+            Path uploadPath = Paths.get(uploadDir, datePath).toAbsolutePath().normalize();
 
             // 创建目录
             if (!Files.exists(uploadPath)) {
@@ -44,8 +46,8 @@ public class LocalDiskStorageService implements StorageService {
             Path filePath = uploadPath.resolve(filename);
             file.transferTo(filePath.toFile());
 
-            // 返回相对URL路径
-            return "/" + datePath + "/" + filename;
+            // 返回 /uploads/ 前缀的相对URL(nginx / WebConfig 静态映射 /uploads/** → upload.base-dir)
+            return "/uploads/" + datePath + "/" + filename;
         } catch (IOException e) {
             throw new RuntimeException("文件存储失败", e);
         }
@@ -57,7 +59,7 @@ public class LocalDiskStorageService implements StorageService {
             return;
         }
         try {
-            Path filePath = Paths.get(uploadDir, fileUrl);
+            Path filePath = resolvePath(fileUrl);
             Files.deleteIfExists(filePath);
         } catch (IOException e) {
             // 记录日志但不抛出异常
@@ -67,6 +69,11 @@ public class LocalDiskStorageService implements StorageService {
 
     @Override
     public String getFilePath(String filename) {
-        return Paths.get(uploadDir, filename).toString();
+        return resolvePath(filename).toString();
+    }
+
+    private Path resolvePath(String url) {
+        String relative = url.startsWith("/uploads/") ? url.substring("/uploads/".length()) : url;
+        return Paths.get(uploadDir, relative).toAbsolutePath().normalize();
     }
 }