Przeglądaj źródła

Merge remote-tracking branch 'origin/master'

ligao 4 miesięcy temu
rodzic
commit
75bf86da54

+ 15 - 0
emoon-file-downloader/.dockerignore

@@ -0,0 +1,15 @@
+target/classes/
+target/generated-sources/
+target/maven-archiver/
+target/maven-status/
+target/test-classes/
+*.jar.original
+.git
+.gitignore
+README.md
+.DS_Store
+.idea/
+*.iml
+logs/
+*.log
+node_modules/

+ 40 - 0
emoon-file-downloader/Dockerfile

@@ -0,0 +1,40 @@
+# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
+#FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
+# 使用贝尔实验室的 OpenJDK 21 镜像
+FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
+
+LABEL maintainer="wangkang"
+
+# 创建必要的目录
+RUN mkdir -p /emoon/server/logs \
+    /emoon/server/temp \
+    /emoon/server/downloads \
+    /home/gansu/gansu/shetou_picture
+
+WORKDIR /emoon/server
+
+# 设置环境变量
+ENV SERVER_PORT=8083 \
+    LANG=C.UTF-8 \
+    LC_ALL=C.UTF-8 \
+    JAVA_OPTS="" \
+    FILE_DOWNLOAD_PATH=/home/gansu/gansu/shetou_picture
+
+EXPOSE ${SERVER_PORT}
+# 暴露 snail job 客户端端口 用于定时任务调度中心通信
+EXPOSE ${SNAIL_PORT}
+
+# 添加应用jar包
+ADD ./target/emoon-file-downloader.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+# 使用环境变量来配置文件下载路径
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
+           -Dserver.port=${SERVER_PORT} \
+           -Dfile.download.path=${FILE_DOWNLOAD_PATH} \
+           # 应用名称 如果想区分集群节点监控 改成不同的名称即可
+           #-Dskywalking.agent.service_name=ruoyi-server \
+           #-javaagent:/emoon/skywalking/agent/skywalking-agent.jar \
+           -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+           -jar app.jar

+ 5 - 0
emoon-file-downloader/README.md

@@ -0,0 +1,5 @@
+# 文件下载服务
+用于部署在目标服务器,接受并下载到指定位置
+
+当前用途:
+舌诊专精模型仅支持从本地磁盘读取文件,因此需要先将图片下载至磁盘

+ 78 - 0
emoon-file-downloader/pom.xml

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.emoon</groupId>
+        <artifactId>emoon-backend</artifactId>
+        <version>5.4.1</version>
+    </parent>
+
+    <artifactId>emoon-file-downloader</artifactId>
+
+    <properties>
+        <maven.compiler.source>21</maven.compiler.source>
+        <maven.compiler.target>21</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.emoon</groupId>
+            <artifactId>emoon-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.emoon</groupId>
+            <artifactId>emoon-common-log</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>emoon-common-redis</artifactId>
+                    <groupId>com.emoon</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.emoon</groupId>
+            <artifactId>emoon-common-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>emoon-common-redis</artifactId>
+                    <groupId>com.emoon</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>${maven-jar-plugin.version}</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>${maven-war-plugin.version}</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 18 - 0
emoon-file-downloader/src/main/java/com/emoon/filedownloader/FileDownloaderApplication.java

@@ -0,0 +1,18 @@
+package com.emoon.filedownloader;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author destiny
+ * @description:
+ * @date 2025/12/20 21:27
+ */
+@SpringBootApplication
+public class FileDownloaderApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(FileDownloaderApplication.class, args);
+    }
+
+}

+ 212 - 0
emoon-file-downloader/src/main/java/com/emoon/filedownloader/controller/DownloadController.java

@@ -0,0 +1,212 @@
+package com.emoon.filedownloader.controller;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import com.emoon.common.core.domain.R;
+import com.emoon.common.core.utils.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 文件下载控制器
+ *
+ * @author destiny
+ * @description: 提供文件base64编码下载到服务器和删除文件的功能
+ * @date 2025/12/20 21:29
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/file")
+public class DownloadController {
+
+    /**
+     * 文件存储根目录,配置在application.yml中
+     */
+    @Value("${file.download.path:/tmp/downloads}")
+    private String basePath;
+
+    /**
+     * 下载文件到服务器
+     * 接收文件的base64编码,保存到指定目录并返回文件路径
+     *
+     * @param requestData 请求数据,包含base64编码的文件内容和文件名
+     * @return 文件在服务器上的完整路径
+     */
+    @PostMapping("/download")
+    public R<Map<String, String>> downloadFile(@RequestBody Map<String, String> requestData) {
+        try {
+            String base64Content = requestData.get("content");
+            String fileName = requestData.get("fileName");
+
+            // 参数校验
+            if (StringUtils.isEmpty(base64Content)) {
+                return R.fail("文件内容不能为空");
+            }
+            if (StringUtils.isEmpty(fileName)) {
+                return R.fail("文件名不能为空");
+            }
+
+            // 安全性检查:防止路径遍历攻击
+            if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
+                return R.fail("文件名包含非法字符");
+            }
+
+            // 创建存储目录
+            File baseDir = new File(basePath);
+            if (!baseDir.exists()) {
+                baseDir.mkdirs();
+            }
+
+            // 生成唯一文件名(防止文件名冲突)
+            String fileExtension = FileUtil.extName(fileName);
+            String baseFileName = FileUtil.mainName(fileName);
+            String uniqueFileName = baseFileName + "_" + IdUtil.simpleUUID() + "." + fileExtension;
+
+            // 构建完整的文件路径
+            Path filePath = Paths.get(basePath, uniqueFileName);
+
+            // 解码base64并写入文件
+            String cleanBase64 = base64Content.replaceAll("^data:[^;]+;base64,", "");
+            byte[] fileBytes = Base64.getDecoder().decode(cleanBase64);
+            Files.write(filePath, fileBytes);
+
+            // 计算文件MD5(用于后续校验)
+            String fileMd5 = DigestUtil.md5Hex(fileBytes);
+
+            // 构建返回结果
+            Map<String, String> result = new HashMap<>();
+            result.put("fileName", uniqueFileName);
+            result.put("filePath", filePath.toString());
+            result.put("originalFileName", fileName);
+            result.put("fileSize", String.valueOf(fileBytes.length));
+            result.put("md5", fileMd5);
+
+            log.info("文件下载成功: 原文件名={}, 存储路径={}, 大小={}bytes", fileName, filePath, fileBytes.length);
+
+            return R.ok("文件下载成功", result);
+
+        } catch (IOException e) {
+            log.error("文件下载失败", e);
+            return R.fail("文件下载失败:" + e.getMessage());
+        } catch (IllegalArgumentException e) {
+            log.error("base64解码失败", e);
+            return R.fail("文件内容格式错误,请检查base64编码");
+        } catch (Exception e) {
+            log.error("系统异常", e);
+            return R.fail("系统异常,请稍后重试");
+        }
+    }
+
+    /**
+     * 删除服务器上的文件
+     * 只能删除配置的basePath下的文件,防止误删
+     *
+     * @param requestData 请求数据,包含要删除的文件名
+     * @return 删除结果
+     */
+    @DeleteMapping("/delete")
+    public R<String> deleteFile(@RequestBody Map<String, String> requestData) {
+        try {
+            String fileName = requestData.get("fileName");
+
+            // 参数校验
+            if (StringUtils.isEmpty(fileName)) {
+                return R.fail("文件名不能为空");
+            }
+
+            // 安全性检查:防止路径遍历攻击
+            if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
+                return R.fail("文件名包含非法字符");
+            }
+
+            // 构建文件路径(确保只能删除basePath下的文件)
+            Path filePath = Paths.get(basePath, fileName);
+            File file = filePath.toFile();
+
+            // 检查文件是否存在
+            if (!file.exists()) {
+                return R.fail("文件不存在");
+            }
+
+            // 检查文件是否在允许的目录下(双重安全保障)
+            String canonicalBasePath = new File(basePath).getCanonicalPath();
+            String canonicalFilePath = file.getCanonicalPath();
+            if (!canonicalFilePath.startsWith(canonicalBasePath)) {
+                log.warn("尝试删除不在允许目录下的文件: {}", canonicalFilePath);
+                return R.fail("无权限删除该文件");
+            }
+
+            // 删除文件
+            boolean deleted = FileUtil.del(file);
+            if (deleted) {
+                log.info("文件删除成功: {}", filePath);
+                return R.ok("文件删除成功");
+            } else {
+                log.error("文件删除失败: {}", filePath);
+                return R.fail("文件删除失败");
+            }
+
+        } catch (IOException e) {
+            log.error("删除文件时发生IO异常", e);
+            return R.fail("删除文件失败:" + e.getMessage());
+        } catch (Exception e) {
+            log.error("系统异常", e);
+            return R.fail("系统异常,请稍后重试");
+        }
+    }
+
+    /**
+     * 获取文件信息(可选功能,用于检查文件是否存在)
+     *
+     * @param fileName 文件名
+     * @return 文件信息
+     */
+    @GetMapping("/info/{fileName}")
+    public R<Map<String, Object>> getFileInfo(@PathVariable String fileName) {
+        try {
+            // 安全性检查
+            if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
+                return R.fail("文件名包含非法字符");
+            }
+
+            Path filePath = Paths.get(basePath, fileName);
+            File file = filePath.toFile();
+
+            if (!file.exists()) {
+                return R.fail("文件不存在");
+            }
+
+            // 检查文件是否在允许的目录下
+            String canonicalBasePath = new File(basePath).getCanonicalPath();
+            String canonicalFilePath = file.getCanonicalPath();
+            if (!canonicalFilePath.startsWith(canonicalBasePath)) {
+                return R.fail("无权限访问该文件");
+            }
+
+            // 构建文件信息
+            Map<String, Object> fileInfo = new HashMap<>();
+            fileInfo.put("fileName", fileName);
+            fileInfo.put("filePath", filePath.toString());
+            fileInfo.put("fileSize", file.length());
+            fileInfo.put("lastModified", file.lastModified());
+            fileInfo.put("exists", file.exists());
+
+            return R.ok("获取文件信息成功", fileInfo);
+
+        } catch (IOException e) {
+            log.error("获取文件信息失败", e);
+            return R.fail("获取文件信息失败:" + e.getMessage());
+        }
+    }
+}

+ 6 - 0
emoon-file-downloader/src/main/resources/application.yml

@@ -0,0 +1,6 @@
+server:
+  port: 8082
+
+file:
+  download:
+    path: /tmp/downloads

+ 1 - 0
pom.xml

@@ -478,6 +478,7 @@
         <module>emoon-openplatform</module>
         <module>emoon-openplatform</module>
         <module>emoon-modules-api</module>
         <module>emoon-modules-api</module>
         <module>emoon-tongue</module>
         <module>emoon-tongue</module>
+        <module>emoon-file-downloader</module>
     </modules>
     </modules>
     <packaging>pom</packaging>
     <packaging>pom</packaging>