springboot调用python文件,python文件使用其他dat文件,适配windows和linux,以及docker环境的方案

介绍

后台是用springboot技术,其他同事做的算法是python,现在的需求是springboot调用python,python又需要调用其他的数据文件,比如dat文件,这个文件是app通过蓝牙获取智能戒指数据以后,保存到后台,后台需要将数据写入到dat文件里,供python调用,本身难点并不大,主要本地环境死windows,服务器环境是ubuntu,并且是通过docker部署的,这样文件的路径就会产生问题,需要适配

代码部分

先处理一下文件路径问题,路径如下

如何能保证不同环境都能正确读到文件呢
答案是:使用临时文件复制

在 Spring Boot 调用 Python 脚本的场景中使用临时文件的方式来实现跨平台和 Docker 兼容,主要基于以下几个关键原因:

1. 解决路径一致性问题

问题:不同操作系统路径格式不同(Windows: C:\path,Linux: /path),且 Docker 容器内路径与宿主机不同

方案:临时目录提供统一的绝对路径基准

java 复制代码
Path tempDir = Files.createTempDirectory("prefix"); // 自动适应各平台路径格式

2. 解决文件权限问题

问题:直接操作项目资源文件可能因权限不足失败(尤其是 Docker 默认以非 root 用户运行)

方案:临时目录确保可写权限

java 复制代码
// 显式设置权限(Linux/Unix需要)
targetPath.toFile().setExecutable(true);

3. 解决资源打包问题

问题:Spring Boot 打包后,resources 下的文件存在于 JAR 内,无法直接通过文件系统访问

方案:运行时复制到临时目录解压

java 复制代码
ClassPathResource resource = new ClassPathResource("python/script.py");
Files.copy(resource.getInputStream(), tempPath); // 从JAR内解压到文件系统

4. 解决 Docker 环境隔离

问题:Docker 容器有独立文件系统,无法直接访问宿主机的项目资源

方案:构建镜像时复制资源,运行时使用临时目录

java 复制代码
COPY src/main/resources/python /app/python  # 构建时固化路径

5. 多线程安全写入

问题:多线程并发写入同一文件会导致冲突

方案:每个线程使用独立临时文件

java 复制代码
Path threadSpecificFile = tempDir.resolve("thread_" + Thread.currentThread().getId() + ".dat");

6. 资源清理保障

问题:运行后残留文件可能积累

方案:标准化清理流程

java 复制代码
finally {
    deleteDirectory(tempDir.toFile()); // 确保删除临时文件
}

7. 调试与日志追踪

问题:直接操作原始文件难以追踪运行时状态

方案:临时文件提供独立运行环境

java 复制代码
System.out.println("临时目录: " + tempDir); // 明确显示运行时文件位置

代码部分的思路大致是,先将文件复制到临时路径,然后往临时路径的文件里写内容,这个临时路径是可变的,所以不能写死,需要将路径当入参传给python文件

代码

java 复制代码
@Service
public class PythonService {

    public String executePythonScript(String waveData) {
        Path tempDir = null;
        try {
            System.out.println("1:"+ DateUtils.dateTimeNow());
            // 1. 创建临时目录(使用NIO API确保跨平台兼容)
            tempDir = Files.createTempDirectory("python_workspace");
            System.out.println("2:"+ DateUtils.dateTimeNow());
            // 2. 复制资源
            copyPythonResourcesToTemp(tempDir);
            System.out.println("3:"+ DateUtils.dateTimeNow());
            //写入数据
            Path tempFile = tempDir.resolve("python/raw_data/bp_106_63.dat");

            try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile.toFile()))) {
                writer.write("");//先清空数据
                writer.write(waveData);
            }

            // 3. 构建命令(使用绝对路径)
            String pythonScriptPath = tempDir.resolve("python/realTime_predict.py").toString();
            String[] command = {
                    "python3",
                    pythonScriptPath
            };
            String pythonPath = "C:\\xxxx\\Python\\Python311\\python.exe";  // 替换为你的实际路径
            boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");
            if (isWindows) {
                command = new String[]{pythonPath, pythonScriptPath,tempFile.toString()};
            } else {
                command = new String[]{"python3",pythonScriptPath,tempFile.toString()};
            }
            System.out.println("4:"+ DateUtils.dateTimeNow());
            // 4. 执行命令
            ProcessBuilder pb = new ProcessBuilder(command);
            pb.directory(tempDir.toFile());
            pb.redirectErrorStream(true);
            System.out.println("5:"+ DateUtils.dateTimeNow());
            Process process = pb.start();
            String output = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))
                    .lines().collect(Collectors.joining("\n"));
            System.out.println("6:"+ DateUtils.dateTimeNow());
            int exitCode = process.waitFor();
            System.out.println("output:"+ output);
            if (exitCode != 0) {
                return "计算出错";
            }
            System.out.println("7:"+ DateUtils.dateTimeNow());
            System.out.println("output:"+ output);
            String[] split = output.split("\n");
            return split[split.length-1];
        } catch (Exception e) {
            e.printStackTrace();
            //throw new RuntimeException("执行Python脚本出错: " + e.getMessage(), e);
        } finally {
            // 生产环境建议保留日志,开发时可清理
            if (tempDir != null) {
                deleteDirectory(tempDir.toFile());
            }
        }
        return "";
    }

    private void copyPythonResourcesToTemp(Path tempDir) throws IOException {
        // 创建python子目录
        Files.createDirectories(tempDir.resolve("python"));
        // 创建必要的子目录结构
        Files.createDirectories(tempDir.resolve("python/raw_data"));
        // 使用Spring的ResourceUtils复制所有文件
        copyResourceToTemp("python/realTime_predict.py", tempDir.resolve("python/realTime_predict.py"));
        copyResourceToTemp("python/best_model.pth", tempDir.resolve("python/best_model.pth"));
        copyResourceToTemp("python/model.py", tempDir.resolve("python/model.py"));
        copyResourceToTemp("python/readData.py", tempDir.resolve("python/readData.py"));
        // 确保先创建raw_data目录再复制数据文件
        copyResourceToTemp("python/raw_data/bp_106_63.dat", tempDir.resolve("python/raw_data/bp_106_63.dat"));

        // 复制requirements.txt(如果存在)
        copyResourceIfExists("python/requirements.txt", tempDir.resolve("python/requirements.txt"));

        // 可选:复制requirements.txt
        ClassPathResource requirements = new ClassPathResource("python/requirements.txt");
        if (requirements.exists()) {
            Files.copy(requirements.getInputStream(),
                    tempDir.resolve("python/requirements.txt"),
                    StandardCopyOption.REPLACE_EXISTING);
        }
    }
    // 新增方法:安全复制资源(仅当资源存在时)
    private void copyResourceIfExists(String resourcePath, Path targetPath) throws IOException {
        ClassPathResource resource = new ClassPathResource(resourcePath);
        if (resource.exists()) {
            try (InputStream in = resource.getInputStream()) {
                Files.copy(in, targetPath, StandardCopyOption.REPLACE_EXISTING);
            }
        }
    }
    // 专用资源复制方法(处理JAR内资源)
    private void copyResourceToTemp(String resourcePath, Path targetPath) throws IOException {
        ClassPathResource resource = new ClassPathResource(resourcePath);
        try (InputStream in = resource.getInputStream()) {
            Files.copy(in, targetPath, StandardCopyOption.REPLACE_EXISTING);
        }
        // 设置可执行权限(Linux/Unix需要)
        targetPath.toFile().setExecutable(true);
    }

    // 辅助方法:递归删除目录
    private void deleteDirectory(File directory) {
        if (directory.exists()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        deleteDirectory(file);
                    } else {
                        file.delete();
                    }
                }
            }
            directory.delete();
        }
    }

}

python部分

python 复制代码
if __name__ == "__main__":
    # 测试预测
    if len(sys.argv) < 2:
        print("请提供数据文件路径作为参数")
        sys.exit(1)

    input_file = sys.argv[1]  # 获取Java传递的文件路径参数
    try:
        sbp, dbp = predict_bp(input_file)
        print(f"{sbp}/{dbp} mmHg")
    except Exception as e:
        print(f"错误: {str(e)}", file=sys.stderr)
        sys.exit(1)

input_file就是

command = new String[]{"python3",pythonScriptPath,tempFile.toString()};

里的tempFile.toString()

注意点

java调用python输出中文是乱码,在python里设置

python 复制代码
from pathlib import Path
# 强制设置标准输出编码为UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

python找不到python/raw_data里的文件

python 复制代码
 def get_data_file_path(self, filename):
        """获取数据文件的绝对路径"""
        # 1. 尝试在同目录下的raw_data文件夹查找
        script_dir = os.path.dirname(os.path.abspath(__file__))
        data_path = os.path.join(script_dir, "raw_data", filename)

        # 2. 如果在开发环境找不到,尝试在上级目录的raw_data查找
        if not os.path.exists(data_path):
            parent_dir = os.path.dirname(script_dir)
            data_path = os.path.join(parent_dir, "raw_data", filename)

        # 3. 如果还是找不到,尝试在Docker环境路径查找
        if not os.path.exists(data_path):
            data_path = os.path.join("/app/python/raw_data", filename)

        if not os.path.exists(data_path):
            raise FileNotFoundError(f"Data file not found at: {data_path}")

        return data_path

dockerFile注意点

dockerfile这个打包,困扰了我一天,主要遇到了库拉取不下来,下载离线库,又遇到了缺少其他依赖的问题

1.docker build报错

python 复制代码
# 第一阶段:构建Python环境(使用官方镜像+国内pip源)
FROM python:3.9-slim AS python-builder

这个就遇到了

ERROR: failed to solve: python:3.9-slim: failed to resolve source metadata for docker.io/library/python:3.9-slim: failed commit on ref "unknown-sha256:b1fd1b5f83b18a7a7377874e3791c8104d5cf26c52677291a31d8805a9a3e5b0": "unknown-sha256:b1fd1b5f83b18a7a7377874e3791c8104d5cf26c52677291a31d8805a9a3e5b0" failed size validation: 7630 != 7317: failed precondition

还有另一个库openjdk:11-jre-slim,也报错

ERROR: failed to solve: adoptopenjdk:11-jre-hotspot: failed to resolve source metadata for docker.io/library/adoptopenjdk:11-jre-hotspot: failed commit on ref "unknown-sha256:09a07bc840c63d79cfcc70a8960e0cead643b14cfdf6bdbca14a22bd6a9d3991": "unknown-sha256:09a07bc840c63d79cfcc70a8960e0cead643b14cfdf6bdbca14a22bd6a9d3991" failed size validation: 7634 != 7377: failed precondition

解决的办法是,先docker pull一下这些库,因为比较大,docker build的时候可能会中断

docker pull python:3.9

docker pull openjdk:11-jdk-slim

2.离线包的问题

构建镜像(禁用网络访问)

docker build --no-cache --pull=false --network=none -t ring_1.0.0 .

因为上边的问题,我本来想着都用离线包,把requirements.txt里的依赖库全下载到本地,然后copy到docker里,结果

6.811 ERROR: Could not find a version that satisfies the requirement nvidia-cuda-cupti-cu1212.1.105; platform_system == "Linux" and platform_machine == "x86_64" (from torch) (from versions: none)
6.812 ERROR: No matching distribution found for nvidia-cuda-cupti-cu1212.1.105; platform_system == "Linux" and platform_machine == "x86_64"

如果一个一个去找,非常麻烦,还不一定匹配,所以还是需要用在线打包

3.在线打包的注意点

一定一定注意,名称是否正确

FROM python:3.9-slim as python-builder

我本来是这个,结果还是一直出错,后来发现是名称错了,可以用

docker images查看一下镜像名称

结果我本地的python镜像名是3.9不是3.9-slim,同样的,FROM openjdk:11-jre-slim 这个也要确认名称

FROM python:3.9 as python-builder

Dockerfile文件内容

python 复制代码
# 第一阶段:构建Python环境
FROM python:3.9 as python-builder

WORKDIR /app
COPY ruoyi-admin/src/main/resources/python/requirements.txt .
RUN pip install --user -r requirements.txt && \
    mkdir -p /app/python/raw_data

# 第二阶段:构建Java应用
FROM openjdk:11-jdk-slim

# 安装基础Python环境
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    python3 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

# 从python阶段复制依赖
COPY --from=python-builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH

# 设置工作目录
WORKDIR /app

# 复制应用文件
COPY ruoyi-admin/target/ring.jar /ring.jar
COPY ruoyi-admin/src/main/resources/python /app/python

# 设置权限
RUN chmod -R 755 /app/python && \
    find /app/python -name "*.py" -exec chmod +x {} \; && \
    chmod -R 777 /app/python/raw_data  # 确保数据目录可写

# 环境变量
ENV PYTHON_SCRIPT_PATH=/app/python
ENV PYTHONUNBUFFERED=1

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/ring.jar"]
相关推荐
databook21 小时前
Manim实现脉冲闪烁特效
后端·python·动效
程序设计实验室21 小时前
2025年了,在 Django 之外,Python Web 框架还能怎么选?
python
倔强青铜三1 天前
苦练Python第46天:文件写入与上下文管理器
人工智能·python·面试
考虑考虑1 天前
Jpa使用union all
java·spring boot·后端
用户2519162427111 天前
Python之语言特点
python
刘立军1 天前
使用pyHugeGraph查询HugeGraph图数据
python·graphql
数据智能老司机1 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
c8i1 天前
django中的FBV 和 CBV
python·django
c8i1 天前
python中的闭包和装饰器
python