Spring Boot动态api执行python脚本

一、背景

现状:

1、每次客户有需求,我们都需要在系统中新增接口,然后再将系统重新进行发布。

2、系统中存在很多的基本接口,大部分数据都能通过这些基本接口进行调用拼接。

因此,基于以上两点现状,领导提出以下要求:

1、系统能够动态新增接口,接口返回数据可以由系统已有基本接口进行调用拼接处理,而且系统不能够重新发布。

2、调用基本接口及拼接数据的流程在python脚本中完成,也就是一个接口对应一个脚本文件,能够将python脚本文件执行的结果返回给用户。(至于使用python脚本的原因主要是考虑外部团队使用python居多,既然未来动态api系统可能开发给他们,那么选择使用python最好不过,实际情况根据大家各自情况进行选择)。

3、能够向脚本文件中传入外部参数。

解决方案:

1、动态新增api接口时(并非实际在系统中新增一个接口,而是通过路径进行动态匹配),绑定python脚本文件,新增成功返回调用接口码。

2、根据固定调用地址+接口码进行接口调用,如果接口存在,则调用,不存在则结束。

3、用户调用接口之后,将找到对应的脚本文件进行调用,将调用结果返回给用户。

二、实现过程

1、创建一个实体类,用于接收用户提交的创建接口相关参数

/**
 * api实体类(页面提交)
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class ApiVO {

    private String name;

    private String file;
}

2、创建一个Map存储工具类,用于模拟数据库插入获取数据(懒得创建表和写SQL之类的了)

@Component
public class AccessUtil {

    private final Map<String, String> urls = new ConcurrentHashMap<String, String>();

    public void putUrls(String code, String filePath){
        urls.put(code, filePath);
    }

    public String getValue(String code){
        return urls.get(code);
    }

}

3、创建一个api新增接口,接口创建成功将返回唯一的一串字符码

/**
 * api管理类
 */
@RequestMapping("/dynamics-api")
@RestController
public class ApiController {

    @Resource
    private AccessUtil accessUtil;

    /**
     * 动态新增一个api接口, 并非实际创建出一个接口,而是根据路径上的{xxx}进行接口匹配
     * @param apiVO 新增接口对象
     */
    @PostMapping("/one")
    public String addApi(@RequestBody ApiVO apiVO){
        String apiName = apiVO.getName();
        // 这里实际需要上传一个脚本文件,为了省事,我先准备好了文件,这里通过上传脚本名称替代上传文件
        String file= apiVO.getFile();
        // code为生成的接口调用地址的一部分,唯一
        String code = DigestUtils.md5DigestAsHex((apiName + file).getBytes());

        // 模拟生成了一个接口
        accessUtil.putUrls(code, file);
        return code;
    }

}

4、创建一个通过接口调用适配接口,所有新增的接口,将通过以下适配接口进行匹配调用

@Slf4j
@RequestMapping("/adapter")
@RestController
public class AdapterController {

    @Resource
    private AccessUtil accessUtil;

    @GetMapping("/execute/{code}")
    public String executeApi(@PathVariable("code") String code) throws Exception{
        String value = accessUtil.getValue(code);
        if(value == null){
            log.error("接口不存在");
            return null;
        }

        // 拿到python脚本文件
        String scriptPath = "D:\\test\\" + value;
        File file = new File(scriptPath);

        if(!file.exists() || !file.isFile()){
            log.error("脚本文件不存在");
            return null;
        }

        // 随便制造点参数,模拟向脚本中传递参数
        Map<String, String> paramsMap = new HashMap<>();
        paramsMap.put("param1", "value1");
        paramsMap.put("params2", "value2");

        // 拼接参数,以key1@@#@@value1##@##key2@@#@@value2的方式
        String lineSplit = "##@##";
        String valueSplit = "@@#@@";
        StringJoiner paramsStr = new StringJoiner(lineSplit);
        for(String key : paramsMap.keySet()) {
            paramsStr.add(key + valueSplit + paramsMap.get(key));
        }

        // 执行脚本
        ProcessBuilder processBuilder;
        // 没有配置virtualenv环境,直接运行,前提是本地安装了python
        processBuilder = new ProcessBuilder("python", file.getPath(), paramsStr.toString());
        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();

        // 处理结果,每行以#@#@#进行分割
        InputStreamReader ir = new InputStreamReader(process.getInputStream());
        BufferedReader br = new BufferedReader(ir);
        String line;
        StringJoiner result = new StringJoiner("#@#@#");
        while ((line = br.readLine()) != null) {
            log.warn(line);
            result.add(line);
        }
        br.close();
        ir.close();
        process.waitFor();

        // 打印结果
        return result.toString();
    }


}

5、python脚本内容如下

# coding:utf-8
import sys
import json
import ssl
import base64
import requests
import time

# 命令参数=========================================================================
COMMAND_PARAMS = {}

def getData():
    return [
        {
            "name": "zhangsan",
            "phone" "12345678910",
            "address": "x-y-z-santi"
        }
    ]

def getCommandParams():
    return COMMAND_PARAMS

def main():
    # 用户的主要编辑区域,返回变量data为最终想要的结果
    data = getData()
    commandParams = getCommandParams()
    return data

#获取传入参数集合,参数是以如下形式传入: key1@@#@@value1##@##key2@@#@@value2
paramList = sys.argv[1].split("##@##")
for num in paramList:
    COMMAND_PARAMS[num.split("@@#@@")[0]] = num.split("@@#@@")[1]

# 执行函数
data = main()
# 接口获取数据通过print()得到
print(COMMAND_PARAMS)
print(data)

请根据实际情况写脚本,我这里随便写的

6、测试效果

调用新增结果,返回code

返回唯一码:调用脚本返回结果如下:

将code拼接在适配接口上进行调用

调用脚本返回结果如下:

三、拓展

以上是通过在本地的python环境中执行的,还有一种就是在沙箱环境中去执行,能够更加的安全,请参考以下博客:

https://blog.csdn.net/lmchhh/article/details/128021914

https://blog.csdn.net/lmchhh/article/details/128806208?spm=1001.2014.3001.5502

四、总结

以上就是新增api接口执行脚本具体实现流程,如果觉得对你有所有帮助,请点个赞或者收藏支持一下吧!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值