让数据流通在Html Java Python

让数据流通在Html Java Python

一、介绍

首先说一下为什么需要让数据在Html Java Python之间流通。

前端Html使用的 thymeleaf [1] 模板,Java用 Spring Boot [2] ,Python采用 FastAPI [3] 框架。

目前有一个数据分析的功能,要求从html前端接受数据,后台进行处理。但是Java直接做数据分析是不太方便的,所以这里想接入Python来做。

接入Python调查到有两种方式,但是都有其局限性, [4] [5]

  1. Java调用Python脚本 。直接写好Python脚本,然后用Java执行该脚本。参数以args的形式传给python脚本,用python用print来返回数据。 但是该方法对参数的传输不太友好,功能很局限。无法传输复杂的数据类型。
  2. 通过 Jython 。Jython是Python用Java的实现,所以可以很自然的用Java调Jython。 但是Jython对第三方模块支持很少,无法满足这里需要用到的数据分析第三方模块。

由于这些局限性,所以上面这两种方式都被否定。采用了以下方式:

  1. 前端Html传一个 form 表单数据,发送请求到Java。
  2. Spring框架的Controller对该数据进行封装,整理成 json ,发送 请求 到Python服务。
  3. Python中的服务接受该 json 数据,对json进行解析得到结果。
  4. 结果再交由数据分析的功能模块进行处理。
  5. 处理后将最终结果组装成json返回给Spring。
  6. Spring再将结果放到Model中返回给Html。

后面就从代码上描述如何完成以上步骤。

二、Html提交form

定义了两个input框,用来输入字符串数据。

为form设定post请求,发送给 Spring的 /data 服务。

代码和结果图如下

<form th:action="@{/data}"  method="post">
        <div class="form-group">
            <label for="coordinate1" class="col-sm-2 control-label">coordinate1</label>
            <input type="text" class="form-control" name="coordinate"  id="coordinate1" placeholder="coordinate1"/>
        </div>
        <div class="form-group">
            <label for="coordinate2" class="col-sm-2 control-label" >coordinate2</label>
            <input type="text" class="form-control" name="coordinate" id="coordinate2" placeholder="coordinate2"/>
        </div>
        <div class="form-group">
            <input type="submit" value="Submit" class="btn btn-info" />
            <input type="reset" value="Reset" class="btn btn-info" />
        </div>
    </form>
html界面图

当点击了Submit按钮,两个input框中的数据就会传输到Controller。

F12 Network查看数据传输

三、Spring Controller接受并转发给Python

1. Controller接受数据

Controller设定好RequestMapping("/data"),直接在形参列表中加入 List<String> coordinates即可,数据会自动封装到coordinates中,接收该数据很轻松。

2. 传递Json

已经获取到了List<String> coordinates对象,要把该对象封装好,以合适的形式发送给Python。

显然,不同服务之间传输数据的最好方式是将数据转成Json传输。

这里采用Jackon工具类将对象转换成json,使用writeValueAsString方法将对象转成String,极其简单。

ObjectMapper objectMapper = new ObjectMapper();
String paramString = objectMapper.writeValueAsString(任意对象);

这里先将coordinates装载到Map中,然后转换成Json字符串。

MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("coordinates",coordinates);
System.out.println(paramMap);
String paramString = objectMapper.writeValueAsString(paramMap);

这里有一个MultiValueMap,我理解的是,HashMap的key value,本来是一一对应的,但是这里使用MultiValue,就使得一个key可以对应多个value,多个value组成一个数组(Java)或者列表(Python)。网上有提到一个错误,我暂时没有遇到,但还是记录下来。 [6]

3. 使用RestTemplate发送请求

RestTemplate是Spring提供的一个可以访问其他服务(url)的一个类,更专业的解释在官方文档 [7] 。简而言之就是,通过该类,可以给其他服务发送请求。

单独使用起来也非常简单,如下代码:

RestTemplate restTemplate = new RestTemplate();
String content = restTemplate.getForObject("https://www.baidu.com/",String.class);
System.out.println(content);

运行该代码,可以得到如下输出

该类提供了很多方法,其中就包括传递参数的post方法。

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException

只需使用该方法,就可以传递参数给python服务器。

String content = restTemplate.postForObject(pythonServerUrl+"/data",paramString,String.class);

在content中,就是python返回的结果。

使用Model将结果保存,就可以返回给前端。

4. 完整代码

    @RequestMapping("/data")
    public String dataToPython(@RequestParam("coordinate") List<String> coordinates) throws JsonProcessingException {
        System.out.println(coordinates.get(0));
        System.out.println(coordinates.get(0).length());
//        HashMap<String,Object> paramMap = new HashMap<>();
//        paramMap.put("coordinates",coordinates);
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
        paramMap.add("coordinates",coordinates);
        System.out.println(paramMap);
        String paramString = objectMapper.writeValueAsString(paramMap);
        String content = restTemplate.postForObject(pythonServerUrl+"/data",paramString,String.class);
        System.out.println("返回值为: "+content);
        return "redirect:/";
    }

四、python接受数据并返回结果

Python部分,使用FastAPI搭建一个微服务非常简单,参考这个文章 [8]

贴出我的代码,再给出解释。

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
import uvicorn
class PointList(BaseModel):
    coordinates:List
app = FastAPI()
@app.post("/data")
def data(x:PointList):
    print(x)
    return "ooooook"