Browse Source

新版文件上传,解决之前的容器无法移动文件到外部的问题。

skyfffire 2 tháng trước cách đây
mục cha
commit
40a674a523

+ 31 - 31
src/main/java/modules/upload/UploadController.java

@@ -10,6 +10,9 @@ import common.utils.http.MyRet;
 import modules.user.UserController;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.UUID;
@@ -27,56 +30,53 @@ public class UploadController extends MyController {
     @Before(LoginInterceptor.class)
     @RequiredRoleInterface({UserController.ROLE_SUPER_ADMIN})
     public void uploadOneFile() {
-        // JFinal 会自动将上传的文件保存到 getFile() 第一个参数指定的临时目录
-        // 第二个参数是最大上传大小,第三个参数是编码
-        // 如果不指定第一个参数,JFinal 会使用 PathKit.getWebRootPath() + "/upload"
-        // 但为了和 Docker 结合,我们不使用 getFile 的第一个参数来直接指定最终路径。
-        // 因为 getFile 会创建一个与文件名同名的文件,不方便我们重命名。
-        // 我们先让 JFinal 存到临时目录,然后自己移动并重命名。
-        UploadFile uploadFile = getFile("file"); // "file" 是前端 form-data 的字段名
-        // UploadFile uploadFile = getFile(); // 如果只有一个文件,可以不指定字段名
+        UploadFile uploadFile = getFile("file");
 
         if (uploadFile == null) {
             renderJson(MyRet.fail("请上传文件"));
             return;
         }
 
+        File tempFile = uploadFile.getFile(); // 获取 JFinal 上传的临时文件
+        // important: uploadFile instance is useful so we don't delete it directly.
+
         try {
-            // 1. 生成唯一的文件名和按日期组织的子目录
-            String today = new SimpleDateFormat("yyyyMMdd").format(new Date()); // 例如: 20230101
-            String extension = getFileExtension(uploadFile.getOriginalFileName()); // 获取文件扩展名
-            String uniqueFileName = UUID.randomUUID().toString().replace("-", "") + extension; // 生成UUID文件名
+            String today = new SimpleDateFormat("yyyyMMdd").format(new Date());
+            String extension = getFileExtension(uploadFile.getOriginalFileName());
+            String uniqueFileName = UUID.randomUUID().toString().replace("-", "") + extension;
 
-            // 2. 构造最终的物理存储路径 (在容器内)
-            // 最终路径: /app/uploads/20230101/xxxxxxxx.jpg
             File destDir = new File(DEFAULT_UPLOAD_BASE_PATH, today);
             if (!destDir.exists()) {
-                if (!destDir.mkdirs()) { // 创建多级目录
+                if (!destDir.mkdirs()) {
                     throw new RuntimeException("创建目标目录失败: " + destDir.getAbsolutePath());
                 }
             }
             File destFile = new File(destDir, uniqueFileName);
 
-            // 3. 将 JFinal 的临时文件移动到最终位置
-            // getFile() 上传的文件是临时文件,我们需要移动它
-            // 如果不移动,JFinal 在请求结束后可能会删除它
-            if (!uploadFile.getFile().renameTo(destFile)) {
-                // 如果 renameTo 失败(跨文件系统等原因),可以回退到流复制
-                // Files.copy(uploadFile.getFile().toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
-                // uploadFile.getFile().delete(); // 如果流复制成功,删除临时文件
-                throw new RuntimeException("移动文件失败");
-            }
+            // --- 核心修改部分 ---
+            // 使用 Files.move() 进行移动操作
+            // StandardCopyOption.REPLACE_EXISTING: 如果目标文件已存在,则替换掉它
+            // StandardCopyOption.ATOMIC_MOVE: 尝试进行原子性移动,如果不支持原子性则回退到非原子性
+            Files.move(tempFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+            // --- 核心修改部分结束 ---
 
-            // 4. 构造可供前端访问的 URL
-            // URL: http://your-domain.com/uploads/20230101/xxxxxxxx.jpg
             String accessUrl = DEFAULT_URL_BASE + "/" + today + "/" + uniqueFileName;
-
-            // 5. 返回结果给前端
             renderJson(MyRet.ok("上传成功").setData(accessUrl));
-        } catch (Exception e) {
-            // 如果发生异常,删除可能已创建的临时文件
-            uploadFile.getFile().delete();
+
+        } catch (IOException e) { // 捕获 IOException,Files.move 可能会抛出
+            // 移动失败,打印详细错误信息
+            e.printStackTrace();
+            renderJson(MyRet.fail("文件移动失败,请检查服务器日志: " + e.getMessage()));
+        } catch (Exception e) { // 其他异常
+            e.printStackTrace();
             renderJson(MyRet.fail("上传失败: " + e.getMessage()));
+        } finally {
+            // 无论成功失败,确保临时文件被删除
+            // 如果 Files.move 成功,tempFile 会被删除
+            // 如果 Files.move 失败,tempFile 仍然存在,需要手动删除
+            if (tempFile != null && tempFile.exists()) {
+                tempFile.delete();
+            }
         }
     }
 

+ 2 - 2
src/test/rest/UploadControllerTest.http

@@ -4,8 +4,8 @@ Content-Type: multipart/form-data; boundary=WebAppBoundary
 dl-token: {{dl_token_var}}
 
 --WebAppBoundary
-Content-Disposition: form-data; name="file"; filename="RushE.mp3"
+Content-Disposition: form-data; name="file"; filename="PixPin_2025-09-02_15-45-17.png"
  
-< D:/RushE.mp3
+< D:/PixPin_2025-09-02_15-45-17.png
 
 --WebAppBoundary--