Jelajahi Sumber

文件上传demo

skyfffire 2 bulan lalu
induk
melakukan
8f18115ec8

+ 3 - 2
README.MD

@@ -50,7 +50,7 @@
 > 添加一个映射,将服务器的端口(Host port)映射到容器的端口(Container port),例如 8080:8080。<br>
 > 
 > Modify - Run Options
-> 在这个新的输入框中输入:--network dalian-nft-network
+> 在这个新的输入框中输入:--network dalian-nft-network -v /app/jfinal_uploads:/app/uploads
 
 
 
@@ -60,4 +60,5 @@
 虽然宿主机的端口是33061,但是使用容器内部网络访问时应该写3306
 docker run -d --name mysql-container -p 33061:3306 -v /app/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=xxxxxx -e MYSQL_ROOT_HOST=% mysql:8.0
 
-## 使用IDEA并在本地生成代码时,可以在运行里配置数据库相关环境变量
+## 使用IDEA并在本地生成代码时,可以在运行里配置数据库相关环境变量
+参考:DB_PASSWORD=123456; DB_URL=jdbc:mysql://mysql-container:3306/dalian-nft-db; DB_USER=root; DEV_MODE=0; UPLOAD_BASE_PATH=/app/uploads; URL_BASE=http://117.72.208.239

+ 6 - 0
pom.xml

@@ -52,6 +52,12 @@
             <artifactId>validation-api</artifactId>
             <version>1.0.0.GA</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.jfinal</groupId>
+            <artifactId>cos</artifactId>
+            <version>2022.2</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 1 - 1
src/main/java/common/handler/AllCorsHandler.java

@@ -15,7 +15,7 @@ public class AllCorsHandler extends Handler {
     @Override
     public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
         // *** 关键修改:将 '*' 替换为具体的调用方域名 ***
-        String ALLOWED_ORIGIN = "http://117.72.208.239";
+        String ALLOWED_ORIGIN = System.getenv("URL_BASE");
         response.setHeader("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
 
         response.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");

+ 4 - 0
src/main/java/common/jfinal/AppConfig.java

@@ -11,6 +11,7 @@ import common.interceptor.ExceptionInterceptor;
 import common.interceptor.empty.EmptyInterceptor;
 import common.model._MappingKit;
 import modules.hello.HelloController;
+import modules.upload.UploadController;
 import modules.user.UserController;
 
 public class AppConfig extends JFinalConfig {
@@ -29,6 +30,8 @@ public class AppConfig extends JFinalConfig {
 
     @Override
     public void configConstant(Constants constants) {
+        constants.setMaxPostSize(1024 * 1024 * 10);
+        
         constants.setDevMode(DEV_MODE.equals("1"));
         constants.setInjectDependency(true);
     }
@@ -38,6 +41,7 @@ public class AppConfig extends JFinalConfig {
         routes.add("/", HelloController.class);
         
         routes.add("/user", UserController.class);
+        routes.add("/upload", UploadController.class);
     }
 
     @Override

+ 88 - 0
src/main/java/modules/upload/UploadController.java

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

+ 11 - 0
src/test/rest/UploadControllerTest.http

@@ -0,0 +1,11 @@
+### 上传文件
+POST {{ baseUrl }}/upload/uploadOneFile
+Content-Type: multipart/form-data; boundary=WebAppBoundary
+dl-token: 3814944677224469098
+
+--WebAppBoundary
+Content-Disposition: form-data; name="file"; filename="RushE.mp3"
+ 
+< D:/RushE.mp3
+
+--WebAppBoundary--