Vue+Node调用Python并将结果显示到Web页面中

298 阅读4分钟

前言

关于这个知识点,其实很早之前我就想写一篇文章记录一下遇见的一些问题了,但是由于各种问题,一直没有来得及写,最近时间相对充裕,对我在学习过程中遇见的问题进行记录一下。

关于我为什么要这么来回折腾?如果你看过我以前的文章,可以很明显的看出我是一个纯前端,对于后端语言并不会,因此如果让我使用Java去调用Python,肯定花的时间会更多,因此我通过Node,做了一个调用Python的代码程序。

这个其实并不是很难,只要用过Node开发的,都可以做到。我这篇仅记录我从零接触,在实现过程中遇见的一些问题,并不做深入的讲解。

实现了什么

在Vue项目中写一个按钮,当用户点击按钮的时候,使用JavaScript调用Node的接口,通过新建子进程的方式,调用Python的代码块,如下:

image.png

image.png

实现过程

前端Web

  1. 首先我们需要创建一个Vue3+Vite的前端工程;
  2. 创建页面,支持用户触发事件,以调用Node接口驱动Python;

下面我们先来准备接口请求需要的环境

  • 通过npm install axios安装插件;
  • 在/public目录下创建config.js,用来存储接口请求路径(避免多个地址导致需要创建多个请求文件);
    const QUERY_BASE_URL = "http://127.0.0.1:3000"; // 接口请求基础地址
    window.config = {
      baseUrl: QUERY_BASE_URL,
    };
    
  • 在/public目录下创建server.js,用来将配置暴露到引入文件中;
    export const data = {
      baseUrl: window.config.baseUrl,
    };
    
  • 创建request.js,方便接口调用;
    import axios from "axios";
    const service = axios.create({
      timeout: 120000,
      headers: { "Content-Type": "application/json", },
    });
    service.interceptors.request.use(
      (config) => {
        if (sessionStorage.getItem("token")) {
          const token = sessionStorage.getItem("token");
          config.headers["authorization"] = "Bearer " + token;
        }
        return config;
      },
      (error) => Promise.reject(error)
    );
    service.interceptors.response.use(
      (response) => response.data,
      (error) => Promise.reject(error)
    );
    export default service;
    
  • 创建接口请求api,下面列举的接口会在Node部分讲到;
    import request from "@/api/request";
    import { data } from "~/server"; // /public/server
    const baseUrl = data.baseUrl + ""; // 添加代理用的
    // 测试Python脚本使用
    export function testPyScript(param) {
      return request({
        url: baseUrl + "/py/script",
        method: "get",
        params: param,
      });
    }
    
  • 创建前端页面,调用接口;
    <template>
      <div>
        <button @click="runCommandPython">Python九九乘法表</button>
        <pre>{{ output }}</pre>
      </div>
    </template>
    
    <script>
    import { testPyScript } from "@/api/modules/node.js";
    export default {
      data() {
        return { output: "", };
      },
      methods: {
        async runCommandPython() {
          const params = {};
          const response = await testPyScript(params);
          this.output = response.result;
        },
      },
    };
    </script>
    
    <style scoped lang="scss">
    button {
      margin-right: 12px;
    }
    </style>
    

Node服务

  • 创建Node接口程序,我使用express来实现,由于我的Node程序与依赖安装在Vue工程里面,因此这里需要注意引用的方式,我这里使用的是import的方式,并没有使用require,具体两者有什么区别,可以看我之前写的文章:NodeJS模块化,里面介绍了二者在引用、导出、使用过程中的区别与需要注意的点。下面是使用express创建的一个简单的接口程序:
    import express from "express";
    const app = express();
    const port = 3000;
    app.get("/api/get", (req, res) => {
      res.send({ success: true, result: req.query, });
    });
    app.listen(port, () => {
      console.log(`Server is running on http://localhost:${port}`);
    });
    
  • 上面的案例中,使用get方式进行了接口的实现,然而post相比较,需要注意的就是,post通过body进行传参,因此我们在抓取用户传入参数的时候,就不能直接通过req.query进行抓取了,我们需要在上面代码的基础上,添加如下代码:
    app.use(express.json());
    app.use(express.urlencoded({ extended: true }));
    
    通过这段代码,将post的入参进行解码,使能够正常访问,接口的实现如下:
    app.post("/api/post", (req, res) => {
      res.send({ success: true, result: req.body, });
    });
    
  • 创建子线程,调用python脚本。
    • 使用child_process实现创建子线程
    • 但是在创建子线程使用spawn调用python的时候,由于我们文件夹结构的原因,无法正常访问到python文件,因此这里需要对文件夹结构进行格式化,又用到了path,代码如下:
      import { spawn } from "child_process";
      import path from "path";
      
      let urlArr = new URL(".", import.meta.url).pathname.split("/");
      const __dirname = urlArr.filter((o) => !!o).join("/");
      
      const pythonProcess = await spawn("python", [
        path.resolve(__dirname, "../python/test.py"), // python执行脚本路径
        "getFunc", // python脚本中的函数名
        JSON.stringify(req.query), // 需要传入的参数,因为是数组,保持一致性
      ]);
      

Python脚本

这里只需要注意一个点,python需要返回到接口的数据,直接print即可,如果print多个,会拼接返回。

import sys
import json

def getFunc():
    for i in range(1, 10):
        for j in range(1, i+1):
            print(f"{j} x {i} = {i*j}", end="\t")
        print()

def getHello(params): # params是传入的参数,如果要返回给js模块,直接使用print输出即可
    print("Hello, World!", 1+1)

if __name__ == "__main__":
    func_name = sys.argv[1] # Node中调用python的第二个参数
    args = sys.argv[2] # Node中调用python的第三个参数
    if func_name == "getFunc":
        getFunc()
    elif func_name == "getHello":
        params = json.loads(args)  # 解析 JSON 字符串
        getHello(params)

代码仓库

文中的代码片段不完整,只针对相关知识点,摘取了部分,可能无法直接运行,相关完整已上传至gitee,点击跳转