前言
关于这个知识点,其实很早之前我就想写一篇文章记录一下遇见的一些问题了,但是由于各种问题,一直没有来得及写,最近时间相对充裕,对我在学习过程中遇见的问题进行记录一下。
关于我为什么要这么来回折腾?如果你看过我以前的文章,可以很明显的看出我是一个纯前端,对于后端语言并不会,因此如果让我使用Java去调用Python,肯定花的时间会更多,因此我通过Node,做了一个调用Python的代码程序。
这个其实并不是很难,只要用过Node开发的,都可以做到。我这篇仅记录我从零接触,在实现过程中遇见的一些问题,并不做深入的讲解。
实现了什么
在Vue项目中写一个按钮,当用户点击按钮的时候,使用JavaScript调用Node的接口,通过新建子进程的方式,调用Python的代码块,如下:
实现过程
前端Web
- 首先我们需要创建一个Vue3+Vite的前端工程;
- 创建页面,支持用户触发事件,以调用Node接口驱动Python;
下面我们先来准备接口请求需要的环境
-
通过
npm install axios
安装插件; -
在/public目录下创建
config.js
,用来存储接口请求路径(避免多个地址导致需要创建多个请求文件);jsconst QUERY_BASE_URL = "http://127.0.0.1:3000"; // 接口请求基础地址 window.config = { baseUrl: QUERY_BASE_URL, };
-
在/public目录下创建
server.js
,用来将配置暴露到引入文件中;jsexport const data = { baseUrl: window.config.baseUrl, };
-
创建
request.js
,方便接口调用;jsimport 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) => { return response.data; }, (error) => { if (error.response) { if (error.request.status == 0) { error.message = "服务器发生错误,请检查服务器。"; } } return Promise.reject(error); } ); export default service;
-
创建接口请求api,下面的接口会在Node部分讲到;
jsimport request from "@/api/request"; import { data } from "~/server"; // /public/server const baseUrl = data.baseUrl + ""; // 添加代理用的 // 测试Get参数使用 export function testNodeGet(param) { return request({ url: baseUrl + "/nodeTestGet", method: "get", params: param, }); } // 测试Post参数使用 export function TestNodePost(data) { return request({ url: baseUrl + "/nodeTestPost", method: "post", data, }); } // 测试Python脚本使用 export function testPyScript(param) { return request({ url: baseUrl + "/py/script", method: "get", params: param, }); }
-
创建前端页面,调用接口;
html<template> <div> <button @click="runCommandGet">测试Get传参</button> <button @click="runCommandPost">测试Post传参</button> <button @click="runCommandPython">Python九九乘法表</button> <pre>{{ output }}</pre> </div> </template> <script> import { testNodeGet, TestNodePost, testPyScript } from "@/api/modules/node.js"; export default { data() { return { output: "", }; }, methods: { async runCommandGet() { try { const params = { name: "张三" }; const response = await testNodeGet(params); this.output = response.result; } catch (error) { this.output = "Error: " + error; } }, async runCommandPost() { try { const params = { name: "张三", age: 28, type: "user" }; const response = await TestNodePost(params); this.output = response.result; } catch (error) { this.output = "Error: " + error; } }, async runCommandPython() { try { const params = {}; const response = await testPyScript(params); this.output = response.result; } catch (error) { this.output = "Error: " + error; } }, }, }; </script> <style scoped lang="scss"> button { margin-right: 12px; } </style>
Node服务
-
创建Node接口程序,我使用
express
来实现,由于我的Node程序与依赖安装在Vue工程里面,因此这里需要注意引用的方式,我这里使用的是import
的方式,并没有使用require
,具体两者有什么区别,可以看我之前写的文章:NodeJS模块化,里面介绍了二者在引用、导出、使用过程中的区别与需要注意的点。下面是使用express
创建的一个简单的接口程序:jsimport 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
进行抓取了,我们需要在上面代码的基础上,添加如下代码:jsapp.use(express.json()); app.use(express.urlencoded({ extended: true }));
通过这段代码,将post的入参进行解码,使能够正常访问,接口的实现如下:
jsapp.post("/api/post", (req, res) => { res.send({ success: true, result: req.body, }); });
-
创建子线程,调用python脚本。
-
使用
child_process
实现创建子线程 -
但是在创建子线程使用
spawn
调用python
的时候,由于我们文件夹结构的原因,无法正常访问到python
文件,因此这里需要对文件夹结构进行格式化,又用到了path
,代码如下:jsimport express from "express"; import { spawn } from "child_process"; import path from "path"; const app = express(); const port = 3000; let urlArr = new URL(".", import.meta.url).pathname.split("/"); const __dirname = urlArr.filter((o) => !!o).join("/"); app.get("/py/script", async (req, res) => { const pythonProcess = await spawn("python", [ path.resolve(__dirname, "../python/test.py"), // python执行脚本路径 "getFunc", // python脚本中的函数名 JSON.stringify(req.query), // 需要传入的参数,因为是数组,保持一致性 ]); try { pythonProcess.stdout.on("data", (data) => { // 接口返回数据 res.send({ success: true, result: data.toString(), }); }); } catch (error) { pythonProcess.stderr.on("data", (data) => { res.status(500).send(data.toString()); }); } });
-
-
Node完整代码如下:
jsimport express from "express"; import { spawn } from "child_process"; import path from "path"; const app = express(); const port = 3000; app.use(express.json()); app.use(express.urlencoded({ extended: true })); let urlArr = new URL(".", import.meta.url).pathname.split("/"); const __dirname = urlArr.filter((o) => !!o).join("/"); app.get("/nodeTestGet", (req, res) => { res.send({ success: true, result: req.query, }); }); app.post("/nodeTestPost", (req, res) => { res.send({ success: true, result: req.body, }); }); app.get("/py/script", async (req, res) => { const pythonProcess = await spawn("python", [ path.resolve(__dirname, "../python/test.py"), // python执行脚本路径 "getFunc", // python脚本中的函数名 JSON.stringify(req.query), // 需要传入的参数,因为是数组,保持一致性 ]); try { pythonProcess.stdout.on("data", (data) => { res.send({ success: true, result: data.toString(), }); }); } catch (error) { pythonProcess.stderr.on("data", (data) => { res.status(500).send(data.toString()); }); } }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); });
Python脚本
这里只需要注意一个点,python
需要返回到接口的数据,直接print
即可,如果print
多个,会拼接返回。
python
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,点击跳转。