开发者

springboot调用python文件的详细方案

目http://www.devze.com录
  • 介绍
  • 实现思路
    • 1. 解决路径一致性问题
    • 2. 解决文件权限问题
    • 3. 解决资源打包问题
    • 4. 解决 docker 环境隔离
    • 5. 多线程安全写入
    • 6. 资源清理保障
    • 7. 调试与日志追踪
  • 完整代码
    • 注意点

      介绍

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

      实现思路

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

      springboot调用python文件的详细方案

      如何能保证不同环境都能正确读到文件呢

      答案是:使用临时文件复制

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

      1. 解决路径一致性问题

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

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

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

      2. 解决文件权限问题

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

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

      // 显式设置权限(Linux/Unix需要)
      targetPath.toFile().setExecutable(true);
      

      3. 解决资源打包问题

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

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

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

      4. 解决 Docker 环境隔离

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

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

      COPY src/main/resources/python /app/python  # 构建时固化路径
      

      5. 多线程安全写入

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

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

      Path threadSpecificFile = tempDir.resolve("thread_" + Thread.currentThread().getId() + ".dat");
      

      6. 资源清理保障

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

      方案:标准化清理流程

      finally {
          deleteDirectory(tempDir.toFile()); // 确保删除临时文件
      }
      

      7. 调试与日志追踪

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

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

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

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

      完整代码

      @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);
        php          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 IOExceptio编程n {
              // 创建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/reandroidquirements.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部分

      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里设置

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

      python找不到python/raw_data里的文件

       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环境(使用官方镜像+国内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环境
      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"]
      

      以上就是springboot调用python文件的详细方案的详细内容,更多关于springboot调用python的资料请关注编程客栈(www.devze.com)其它相关文章!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜