你随意给我一个用openapi定义的接口文档,我不动代码就能调用你的接口

技术博客 (105) 2023-11-15 09:01:01

最近在思考构建一个服务编排(Service Orchestration)系统,考虑这个系统至少需要具备以下特征:

  1. 使用统一的方法定义服务功能单元
  2. 使用一种通用的方式将一个或多个服务的输出映射到下游服务的输入,映射时支持基础的数据转换与处理
  3. 支持以搭积木的方式将低层服务功能单元组织成更高层抽象的服务功能,直至一个完整的服务
  4. 用户编排服务时,具备较大的灵活性定制业务

1 OpenAPI介绍

系统与系统之间、系统内各组件之间通过http API进行通信的情况非常普遍,于是有人专门制定了相应的规范OpenAPI SpecificationOAS),一般简称为OpenAPI,以前被称为Swagger。这个规范规定了RESTful API从设计、构建、文档到使用的方方面面,而且是人类和计算机方便理解的,还是与开发语音无关的,当前已然成为定义与描述http API的标准。围绕这个规范,大家开发了很多相关工具,比如用于OpenAPI文档开发的Swagger Editor,用于生成、可视化和测试OpenAPI的Swagger UI。另外还有SwaggerHubSpringDocApiFoxKnife4jpostman等等开源或商业产品。对于支持OpenAPI的java语言工具来说,都会使用到swagger-parser这个底层组件,下面简单的介绍一下这个工具的基本使用。

2 基于OpenAPI文档调用http接口

假设某个服务提供了下面这个接口:

public class HelloRequest {
    private String content;
}

public class HelloResponse {
    private String content;
}

@RestController
@RequestMapping(value = "/")
public class Controller {

    @PostMapping(value = "/test")
    public HelloResponse test(@RequestBody HelloRequest request) {
        return new HelloResponse("echo: " + request.getContent());
    }
}

那么我们就可以基于OpenAPI规范使用以下json文档来描述这个接口:

{
  "openapi": "3.1.0",
  "info": {
    "title": "test openapi",
    "version": "1.0.0"
  },
  "paths": {
    # 接口路径
    "/test": {
      # 接口方法
      "post": {
        "operationId": "openapi-test",
        # 接口请求body
        "requestBody": {
          "content": {
            # 接口请求body格式
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  # request body有一个类型为string,名字为content的字段
                  "content": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          # 接口请求成功时的返回内容
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "content": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

也可以用yaml文档进行等价描述,它看上去更简洁些:

openapi: 3.1.0
info:
  title: test openapi
  version: 1.0.0
servers:
- url: /
paths:
  /test:
    post:
      operationId: openapi-test
      requestBody:
        content:
          application/json:
            schema:
              properties:
                content: 
                  type: string
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                properties:
                  content: 
                    type: string

上述接口的OpenAPI定义中,我们看到一个叫做operationId的字段,我们权且把它作为接口的唯一标识。

从上面的OpenAPI文档可知,它描述了调用接口所需的所有信息,所以我们可以写一个通用的方法来调用接口:

pom文件添加依赖:

    <dependency>
        <groupId>io.swagger.parser.v3</groupId>
        <artifactId>swagger-parser</artifactId>
        <version>2.1.16</version>
    </dependency>

接口实现原型大概如下所示:

/** * * @param openApiFile OpenAPI定义文件 * @param operationId 接口的operationId * @param requestBody 请求body内容 */
public String callOpenAPI(String openApiFile, String operationId, String requestBody) {
    // 解析OpenAPI文档为java内部表示:OpenAPI
    SwaggerParseResult parseResult = new OpenAPIV3Parser().readLocation(openApiFile, null, null);
    OpenAPI openAPI = parseResult.getOpenAPI();
    
    // 遍历文档中定义的所有path
    for (Map.Entry<String, PathItem> entry : openAPI.getPaths().entrySet()) {
        // 当前path
        String path = entry.getKey();
        PathItem pathItem = entry.getValue();
        // 当前path定义了Post方法
        if (pathItem.getPost() != null) {
            Operation postOperation = pathItem.getPost();
            // 当前方法的operationId,是我们的目标OperationId,也就是需要调用的方法
            String operationId = postOperation.getOperationId();
            if (Objects.equals(operationId, operationId)) {
                
                // 根据OpenAPI参数构造RestTemplate
                RestTemplate restTemplate = new RestTemplate();
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);
                String response = restTemplate.postForObject("http://localhost:8102" + path, requestEntity, String.class);

                return response;
            }
        }
    }
    
    throw new IllegalArgumentException("not exist");
}


THE END

发表回复