|
|
@@ -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();
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|