易君召
易君召
发布于 2026-07-12 / 12 阅读
0
0

多个HTTP接口封装一个接口对外服务的技术方案选型

近期有个项目需求,因为原有项目的接口限制问题,需要将数十个 HTTP协议的Get、Post接口,通过 type字段 区分不同的接口,统一封装为对外服务的一个 HTTP接口。

一、技术方案对比

这个需求本质是API 门面模式 + 按业务类型路由分发,核心是用一个 HTTP 入口接收请求,根据type字段匹配后端目标接口,完成参数适配、请求转发、结果统一返回。根据技术栈、定制化程度和运维成本,有三类主流方案,从轻量代码封装到专业网关各有优劣。

方案一:轻量代码门面服务(最通用)

单独开发一个聚合服务,对外暴露一个 HTTP 接口,内部根据type分发到对应后端接口,是数十个接口量级下性价比最高的方案。

核心实现逻辑

  1. 统一入口约定 对外仅暴露 1 个接口(推荐统一 POST,路径如/api/gateway),入参采用标准化结构:

    {
      "type": "user_info",
      "data": {}
    }
    

    type为接口唯一标识,data为对应业务参数。

  2. 路由配置映射 维护type -> 后端接口的配置映射(放在配置文件即可,无需数据库),每条配置包含:目标 URL、请求方法(GET/POST)、参数类型(form/json)、超时时间等。

  3. 参数自动适配 根据后端接口类型自动转换参数:

    • 原接口为 GET:将data内字段转为 URL 查询参数

    • 原接口为 POST:将data转为请求体(JSON/Form)

  4. 转发与统一返回 使用带连接池的 HTTP 客户端(Java 的 WebClient/Feign、Go 的 net/http、Node 的 axios)发起调用,最终将各后端返回统一封装为code/message/data标准格式。

优缺点

  • ✅ 优势:灵活度极高,支持参数加解密、数据聚合、业务校验、异常兜底等定制逻辑;与现有技术栈对齐,学习成本低。

  • ❌ 劣势:鉴权、限流、监控等治理能力需自行集成;有一定开发工作量(数十个接口量级下工作量很小)。

适用场景

接口存在业务定制逻辑、参数需要转换、团队有开发能力。

方案二:API 网关 / 反向代理(纯透传场景)

利用 OpenResty、APISIX、Kong、Spring Cloud Gateway 等网关产品,通过配置实现「按 type 参数路由」,纯透传转发,无需开发业务代码。

典型实现

  1. 对外暴露统一路由,接收所有请求。

  2. 通过 Lua 脚本 / 网关插件读取请求中的type参数(query 或 body 中)。

  3. 根据type匹配对应的上游服务和路径,执行反向代理。

  4. 网关原生提供鉴权、限流、熔断、日志、监控等治理能力。

优缺点

  • ✅ 优势:无需开发业务代码,转发性能高;开箱即用的 API 治理能力;运维统一管理。

  • ❌ 劣势:复杂参数映射、请求体改写、业务逻辑定制成本很高;需要额外部署和维护网关组件。

适用场景

后端接口格式统一、仅需纯透传转发;希望统一入口同时做全量 API 治理。

方案三:API 编排低代码平台

使用可视化 API 编排产品,通过配置化完成接口聚合和路由,零代码上线。

代表产品

  • 开源:APISIX Dashboard、Spring Cloud Gateway + 配置中心

  • 商业:MuleSoft、Postman Flows、国内 Eolink/ApiPost 等

优缺点

  • ✅ 优势:零代码 / 低代码,接口增删改通过界面配置即可,上线快;非技术人员可维护。

  • ❌ 劣势:定制化能力弱;商业产品成本高;自部署开源版仍有运维成本。

适用场景

接口频繁变动、需要快速迭代;非研发团队主导接口管理。

核心设计要点(所有方案通用)

  1. type 传递方式:优先放在 POST 请求体中,兼容性最好、无长度限制;如需兼容 GET 调用,可放在 URL 查询参数中。

  2. 请求方法统一:对外建议统一使用 POST,内部根据配置自动转换为后端接口的 GET/POST,减少前端适配成本。

  3. 异常与降级:统一入口做全局异常处理、超时熔断、降级返回,避免单个后端接口故障拖垮整个服务。

  4. 鉴权与审计:统一入口一次性完成身份校验、限流、日志审计,无需每个后端接口重复实现。

  5. 特殊接口处理:文件上传、下载、流式响应类接口,需确保转发层支持流式透传,避免内存溢出。

选型建议

  • 绝大多数业务场景首选方案一。数十个接口的开发量很小,Spring Boot 等框架下 1-2 天即可完成,灵活度最高,与业务系统无缝集成。

  • 纯基础设施视角、无业务定制选方案二。适合中台 / 平台团队做统一 API 网关,同时治理所有对外接口。

  • 非研发团队、快速上线选方案三。适合运营 / 测试团队快速搭建接口聚合层。

二、方案落地实现

下面按照方案一提供一套 Spring Boot 3.x 最简可落地实现,采用「配置化路由 + 统一入口 + 自动参数适配」设计,新增接口只需改配置、无需动代码。

(一)核心依赖(pom.xml)

仅需 Web 基础包,使用 Spring 6 自带的 RestClient 做 HTTP 转发,轻量无额外依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

(二)路由配置(application.yml)

所有后端接口的映射关系全部配置化,type 为唯一标识,支持 GET/POST 自由切换:

server:
  port: 8000

# 接口路由配置:新增/修改接口只需改这里
api:
  routes:
    - type: user_info        # 接口类型标识
      url: http://127.0.0.1:8081/api/user/detail  # 后端真实接口地址
      method: GET            # 后端接口请求方法
      timeout: 3000          # 超时时间(毫秒)
    - type: order_create
      url: http://127.0.0.1:8082/api/order/create
      method: POST
      timeout: 5000

(三)配置映射类

读取配置并封装,启动时自动加载:

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;

@Data
@Component
@ConfigurationProperties(prefix = "api")
public class ApiRouteProperties {
    private List<RouteItem> routes;

    @Data
    public static class RouteItem {
        private String type;
        private String url;
        private String method;
        private Integer timeout;
    }
}

(四)统一请求 / 响应结构

1. 统一入参

import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.Map;

@Data
public class ApiRequest {
    @NotBlank(message = "type 不能为空")
    private String type;       // 接口类型
    private Map<String, Object> data; // 业务参数
}

2. 统一出参

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;

    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data);
    }

    public static <T> ApiResponse<T> fail(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

(五)核心分发服务

根据 type 路由到目标接口,自动适配 GET/POST 参数格式:

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class ApiGatewayService {

    private final RestClient restClient;
    private final Map<String, ApiRouteProperties.RouteItem> routeMap;

    // 构造注入:初始化路由映射 + HTTP客户端
    public ApiGatewayService(ApiRouteProperties properties, RestClient.Builder restClientBuilder) {
        this.restClient = restClientBuilder.build();
        // List 转 Map,O(1) 快速查找路由
        this.routeMap = properties.getRoutes().stream()
                .collect(Collectors.toMap(ApiRouteProperties.RouteItem::getType, Function.identity()));
    }

    /**
     * 统一分发入口
     */
    public Object dispatch(ApiRequest request) {
        ApiRouteProperties.RouteItem route = routeMap.get(request.getType());
        if (route == null) {
            throw new IllegalArgumentException("不支持的接口类型: " + request.getType());
        }

        Map<String, Object> params = request.getData() != null ? request.getData() : Collections.emptyMap();
        String method = route.getMethod().toUpperCase();

        return switch (method) {
            case "GET" -> doGet(route, params);
            case "POST" -> doPost(route, params);
            default -> throw new IllegalArgumentException("不支持的请求方法: " + method);
        };
    }

    /**
     * GET 请求:data 自动转为 URL 查询参数
     */
    private Object doGet(ApiRouteProperties.RouteItem route, Map<String, Object> params) {
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        params.forEach((k, v) -> queryParams.add(k, String.valueOf(v)));

        URI uri = UriComponentsBuilder.fromHttpUrl(route.getUrl())
                .queryParams(queryParams)
                .build()
                .toUri();

        return restClient.get()
                .uri(uri)
                .retrieve()
                .body(Object.class);
    }

    /**
     * POST 请求:data 自动转为 JSON 请求体
     */
    private Object doPost(ApiRouteProperties.RouteItem route, Map<String, Object> params) {
        return restClient.post()
                .uri(route.getUrl())
                .contentType(MediaType.APPLICATION_JSON)
                .body(params)
                .retrieve()
                .body(Object.class);
    }
}

(六)统一入口 Controller

对外只暴露一个 POST 接口:

import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class ApiGatewayController {

    private final ApiGatewayService gatewayService;

    public ApiGatewayController(ApiGatewayService gatewayService) {
        this.gatewayService = gatewayService;
    }

    @PostMapping("/gateway")
    public ApiResponse<Object> gateway(@RequestBody @Valid ApiRequest request) {
        Object result = gatewayService.dispatch(request);
        return ApiResponse.success(result);
    }
}

(七)全局异常处理

统一异常返回格式,避免后端异常透传:

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ApiResponse<Object> handleParamError(IllegalArgumentException e) {
        return ApiResponse.fail(400, e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse<Object> handleSystemError(Exception e) {
        return ApiResponse.fail(500, "接口调用失败:" + e.getMessage());
    }
}

(八)调用示例

前端 / 调用方统一请求 POST http://localhost:8000/api/gateway,通过 type 区分业务:

// 查询用户信息(自动转 GET)
{
  "type": "user_info",
  "data": {
    "userId": 1001
  }
}

// 创建订单(自动转 POST JSON)
{
  "type": "order_create",
  "data": {
    "goodsId": 2001,
    "amount": 99.9
  }
}

生产环境优化建议

  1. HTTP 连接池:配置 RestClient 连接池、全局超时、重试策略,避免每次新建连接

  2. 熔断降级:集成 Resilience4j / Sentinel,单个后端接口故障不影响整体服务

  3. 统一鉴权:在网关层一次性做 JWT 校验、签名验签、权限控制,后端接口无需重复实现

  4. 限流风控:按 IP / 用户维度做接口限流,防止恶意调用

  5. 日志审计:统一记录请求入参、响应结果、耗时、异常栈,便于排查问题

  6. 配置热更新:接入 Nacos / Apollo 配置中心,新增接口无需重启服务

  7. 扩展支持:可按需增加 Form 表单、文件上传下载、请求加解密等适配逻辑


原文链接 https://www.yijunzhao.cn/archives/api-gateway-aggregation-multiple-http-endpoints-into-single-service-comparison

欢迎访问 小易撩挨踢

https://www.yijunzhao.cn/


评论