前言
关于这个知识点,其实很早之前我就想写一篇文章记录一下遇见的一些问题了,但是由于各种问题,一直没有来得及写,最近时间相对充裕,对我在学习过程中遇见的问题进行记录一下。
关于我为什么要这么来回折腾?如果你看过我以前的文章,可以很明显的看出我是一个纯前端,对于后端语言并不会,因此如果让我使用Java去调用Python,肯定花的时间会更多,因此我通过Node,做了一个调用Python的代码程序。
这个其实并不是很难,只要用过Node开发的,都可以做到。我这篇仅记录我从零接触,在实现过程中遇见的一些问题,并不做深入的讲解。
实现了什么
在Vue项目中写一个按钮,当用户点击按钮的时候,使用JavaScript调用Node的接口,通过新建子进程的方式,调用Python的代码块,如下:
实现过程
前端Web
- 首先我们需要创建一个Vue3+Vite的前端工程;
- 创建页面,支持用户触发事件,以调用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
进行抓取了,我们需要在上面代码的基础上,添加如下代码:
通过这段代码,将post的入参进行解码,使能够正常访问,接口的实现如下:app.use(express.json()); app.use(express.urlencoded({ extended: true }));
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,点击跳转。