Prechádzať zdrojové kódy

fix(backend): 京东云存储下 LangGraph 改读共享卷缓存路径

- JdCloudStorageService.getFilePath: 下载到 upload.base-dir/.jdcloud-cache 共享卷(供 cfc-langgraph 容器按本地路径读取),确定性命名+缓存复用+可读权限
- HealthReportController: 去掉 2 处 startsWith(http) 直传 OSS URL 的捷径,统一经 getFilePath 下载
- 代上传(proxy-parse): 改用 storageService 存 OSS 再经 getFilePath 传给 LangGraph
Xiaogang Liao 1 deň pred
rodič
commit
8d96d1728c

+ 7 - 25
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java

@@ -899,10 +899,9 @@ public class HealthReportController {
         String originalFilename = draft.getOriginalFilename();
 
         try {
-            // LangGraph 优先:远程 OSS URL 直接传 URL(LangGraph 自行下载),本地路径传共享卷路径
+            // LangGraph 需要本地路径:经 getFilePath 下载到共享卷(LangGraph 容器可读)
             try {
-                String lgPath = (fileUrl != null && fileUrl.startsWith("http"))
-                        ? fileUrl : storageService.getFilePath(fileUrl);
+                String lgPath = storageService.getFilePath(fileUrl);
                 Map<String, Object> lgRequest = new HashMap<>();
                 lgRequest.put("file_path", lgPath);
                 lgRequest.put("user_id", userId);
@@ -1081,12 +1080,11 @@ public class HealthReportController {
 
             // 保存文件(通过 StorageService 支持本地磁盘或京东云 OSS)
             String fileUrl = storageService.storeFile(file, "health-reports");
-            boolean isRemote = fileUrl != null && fileUrl.startsWith("http");
 
-            // 尝试 LangGraph 远程解析(远程 OSS URL 直接传 URL,LangGraph 自行下载)
+            // 尝试 LangGraph 解析(LangGraph 只认本地路径,必须经 getFilePath 下载到共享卷)
             try {
                 Map<String, Object> lgRequest = new HashMap<>();
-                lgRequest.put("file_path", isRemote ? fileUrl : storageService.getFilePath(fileUrl));
+                lgRequest.put("file_path", storageService.getFilePath(fileUrl));
                 lgRequest.put("user_id", userId);
                 if (familyId != null) {
                     lgRequest.put("family_id", familyId);
@@ -2439,7 +2437,8 @@ public class HealthReportController {
         }
 
         try {
-            String absolutePath = saveUploadFileForProxy(file, userId);
+            String fileUrl = storageService.storeFile(file, "health-reports");
+            String absolutePath = storageService.getFilePath(fileUrl);
 
             // 调用 LangGraph 解析
             Map<String, Object> result = callLangGraphParse(absolutePath, userId, familyId, subjectId);
@@ -2448,6 +2447,7 @@ public class HealthReportController {
                 return Result.error("报告解析失败");
             }
 
+            result.put("fileUrl", fileUrl);
             // 保存草稿
             Long draftId = (Long) result.get("draftId");
             if (draftId != null && note != null) {
@@ -2519,24 +2519,6 @@ public class HealthReportController {
         }
     }
 
-    /**
-     * 代上传场景:保存文件(用代上传人目录)
-     */
-    private String saveUploadFileForProxy(MultipartFile file, Long proxyUserId) throws IOException {
-        String uploadDir = "uploads" + File.separator + "health-reports-proxy" + File.separator + proxyUserId;
-        File dir = new File(uploadDir);
-        if (!dir.exists()) {
-            dir.mkdirs();
-        }
-        String originalFilename = file.getOriginalFilename();
-        if (originalFilename == null) originalFilename = "report.pdf";
-        String extension = originalFilename.substring(originalFilename.lastIndexOf('.'));
-        String savedFilename = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + extension;
-        Path filePath = Paths.get(uploadDir, savedFilename);
-        Files.copy(file.getInputStream(), filePath);
-        return uploadDir.replace("\\", "/") + "/" + savedFilename;
-    }
-
     /**
      * 调用 LangGraph 解析报告
      */

+ 28 - 9
cfc-backend/src/main/java/com/etotem/cfc/service/impl/JdCloudStorageService.java

@@ -25,9 +25,11 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.time.LocalDate;
@@ -61,6 +63,13 @@ public class JdCloudStorageService implements StorageService {
     @Value("${jdcloud.region:cn-north-1}")
     private String region;
 
+    /**
+     * 本地共享目录。京东云对象在供本地进程(如 cfc-langgraph 容器)按路径读取时,
+     * 会被下载到该目录下的缓存子目录,确保 LangGraph 能读到文件。
+     */
+    @Value("${upload.base-dir:/data/cfc-uploads}")
+    private String uploadBaseDir;
+
     @Resource
     private FileRecordMapper fileRecordMapper;
 
@@ -153,25 +162,35 @@ public class JdCloudStorageService implements StorageService {
 
     @Override
     public String getFilePath(String filename) {
-        // 京东云文件不在本地磁盘,下载到临时目录供本地解析使用
+        // 京东云文件不在本地磁盘:下载到共享卷 upload.base-dir 下的缓存目录,
+        // 供本地进程(含 cfc-langgraph 容器)按本地路径读取;缓存目录需可被其它用户遍历/读取。
         try {
             String key = extractKey(filename);
-            S3Object object = s3Client.getObject(bucket, key);
-
-            Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"), "cfc-jdcloud");
-            Files.createDirectories(tempDir);
-            String localName = key.substring(key.lastIndexOf('/') + 1);
-            Path tempFile = tempDir.resolve(UUID.randomUUID().toString() + "-" + localName);
+            Path cacheDir = Paths.get(uploadBaseDir, ".jdcloud-cache").toAbsolutePath().normalize();
+            Files.createDirectories(cacheDir);
+            cacheDir.toFile().setReadable(true, false);
+            cacheDir.toFile().setExecutable(true, false);
+
+            String localName = sha256(key.getBytes(StandardCharsets.UTF_8))
+                    + "-" + key.substring(key.lastIndexOf('/') + 1);
+            Path cached = cacheDir.resolve(localName);
+            if (Files.exists(cached) && Files.size(cached) > 0) {
+                return cached.toString();
+            }
 
+            S3Object object = s3Client.getObject(bucket, key);
+            Path tmp = cacheDir.resolve(localName + ".part");
             try (InputStream in = object.getObjectContent();
-                 FileOutputStream out = new FileOutputStream(tempFile.toFile())) {
+                 FileOutputStream out = new FileOutputStream(tmp.toFile())) {
                 byte[] buffer = new byte[8192];
                 int len;
                 while ((len = in.read(buffer)) != -1) {
                     out.write(buffer, 0, len);
                 }
             }
-            return tempFile.toString();
+            Files.move(tmp, cached, StandardCopyOption.REPLACE_EXISTING);
+            cached.toFile().setReadable(true, false);
+            return cached.toString();
         } catch (IOException e) {
             throw new RuntimeException("下载京东云文件失败: " + filename, e);
         }