|
|
@@ -0,0 +1,88 @@
|
|
|
+package modules.upload;
|
|
|
+
|
|
|
+import com.jfinal.kit.StrKit;
|
|
|
+import com.jfinal.upload.UploadFile;
|
|
|
+import common.utils.http.MyController;
|
|
|
+import common.utils.http.MyRet;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.UUID;
|
|
|
+
|
|
|
+public class UploadController extends MyController {
|
|
|
+ // 默认上传文件保存的基路径,可以从配置文件中读取
|
|
|
+ private static final String DEFAULT_UPLOAD_BASE_PATH = System.getenv("UPLOAD_BASE_PATH");
|
|
|
+ // 默认的URL访问基路径,可以从配置文件中读取
|
|
|
+ private static final String DEFAULT_URL_BASE = System.getenv("URL_BASE") + "/uploads";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上传一个文件的方法
|
|
|
+ * 前端通过 multipart/form-data 方式,将文件字段命名为 "file" 上传
|
|
|
+ */
|
|
|
+ public void uploadOneFile() {
|
|
|
+ // JFinal 会自动将上传的文件保存到 getFile() 第一个参数指定的临时目录
|
|
|
+ // 第二个参数是最大上传大小,第三个参数是编码
|
|
|
+ // 如果不指定第一个参数,JFinal 会使用 PathKit.getWebRootPath() + "/upload"
|
|
|
+ // 但为了和 Docker 结合,我们不使用 getFile 的第一个参数来直接指定最终路径。
|
|
|
+ // 因为 getFile 会创建一个与文件名同名的文件,不方便我们重命名。
|
|
|
+ // 我们先让 JFinal 存到临时目录,然后自己移动并重命名。
|
|
|
+ UploadFile uploadFile = getFile("file"); // "file" 是前端 form-data 的字段名
|
|
|
+ // UploadFile uploadFile = getFile(); // 如果只有一个文件,可以不指定字段名
|
|
|
+
|
|
|
+ if (uploadFile == null) {
|
|
|
+ renderJson(MyRet.fail("请上传文件"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 生成唯一的文件名和按日期组织的子目录
|
|
|
+ String today = new SimpleDateFormat("yyyyMMdd").format(new Date()); // 例如: 20230101
|
|
|
+ String extension = getFileExtension(uploadFile.getOriginalFileName()); // 获取文件扩展名
|
|
|
+ String uniqueFileName = UUID.randomUUID().toString().replace("-", "") + extension; // 生成UUID文件名
|
|
|
+
|
|
|
+ // 2. 构造最终的物理存储路径 (在容器内)
|
|
|
+ // 最终路径: /app/uploads/20230101/xxxxxxxx.jpg
|
|
|
+ File destDir = new File(DEFAULT_UPLOAD_BASE_PATH, today);
|
|
|
+ if (!destDir.exists()) {
|
|
|
+ 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("移动文件失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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();
|
|
|
+ renderJson(MyRet.fail("上传失败: " + e.getMessage()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取文件扩展名
|
|
|
+ * @param fileName 文件名
|
|
|
+ * @return 扩展名,包含".",例如 ".jpg"
|
|
|
+ */
|
|
|
+ private String getFileExtension(String fileName) {
|
|
|
+ if (StrKit.isBlank(fileName) || fileName.lastIndexOf('.') == -1) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return fileName.substring(fileName.lastIndexOf('.'));
|
|
|
+ }
|
|
|
+}
|