🏆本文收录于《滚雪球学SpringBoot 3.x》,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。
  
该专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】,你想学习的都被收集在内,快速投入学习!!两不误。
  
若还想学习更多,可直接订阅 《Spring Boot实战合集》,一次订阅,持续学习,后续更新内容无需重复付费,适合长期收藏与系统进阶。

演示环境说明:

  • 开发工具:IDEA 2021.3
  • JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
  • Spring Boot版本:3.5.4(于25年7月24日发布)
  • Maven版本:3.8.2 (或更高)
  • Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
  • 操作系统:Windows 11

全文目录:

前言

当 AI 从“试验性能力”变成“企业核心生产力”之后,模型调用不再只是一次简单的 HTTP 请求。企业往往会同时面对多个模型提供商、多个模型版本、多个调用入口、多个业务系统,以及不断变化的安全、合规和成本控制要求。这个时候,最容易失控的地方不是模型本身,而是模型的接入方式。

很多团队在早期会采用“谁需要谁直连”的方式:业务系统直接调用某个大模型厂商的 API,或者在代码里硬编码密钥、模型名和请求参数。这样做在 PoC 阶段很快,但在生产环境里很危险:密钥分散、调用链路不可控、统计口径不统一、费用难以归集、供应商切换成本高、审计困难,甚至还会因为某个业务模块误用高成本模型而产生巨额账单。

因此,AI 能力真正走向平台化时,一个统一的模型接入层就变得非常重要。它像企业内部的“AI 流量中枢”,把鉴权、配额、审计、路由、监控、供应商适配、流式输出等能力统一收口,让上层业务只关心“我要什么能力”,而不是“我要怎么调用某家模型”。

Spring Boot 3.x 在这里非常适合扮演 AI Gateway 的实现底座。它具备成熟的 Web 能力、优雅的依赖注入体系、完善的安全框架、良好的可观测性生态,并且对 Java 17、Jakarta EE 规范、原生镜像等现代化能力支持更好。换句话说,Spring Boot 3.x 不只是一个“写接口的框架”,它完全可以成为企业 AI 平台化的工程基座。

第一章 为什么企业需要统一模型接入层?

1.1 直连模型接口的问题

在单个团队内部,直接调用模型 API 看起来很自然:

  • 前端传入 prompt;
  • 后端拼接请求体;
  • 直接调用厂商接口;
  • 拿回结果返回给用户。

这条链路短,启动快,但它有几个天然缺点。

第一,调用方式碎片化。不同业务线可能分别接入 OpenAI、Azure OpenAI、Claude、通义千问、文心一言、DeepSeek 或者内部自建模型。每个团队都按自己的理解做一套,最后形成多个“半标准”的调用协议。

第二,安全边界模糊。模型密钥散落在不同应用里,一旦某个服务暴露、日志打印失控或者配置仓库管理不当,就可能导致密钥泄露。

第三,成本不可控。模型本身通常按 token 或调用次数计费,如果没有统一的配额与计费能力,业务部门很难知道成本到底花在哪里。

第四,模型切换困难。当某个模型效果下降、价格上涨、接口不稳定、政策变化时,业务系统如果强耦合某一厂商,替换成本会非常高。

第五,审计缺位。企业在上线 AI 能力后,通常需要知道:谁在什么时间调用了什么模型、输入输出大概是什么、是否触发了敏感词、是否越权、是否超额、响应耗时是多少。没有统一入口,这些数据很难完整记录。

因此,统一模型接入层不是“锦上添花”,而是 AI 工程化走向成熟的基础设施。

1.2 统一接入层到底统一什么

统一接入层不是简单地“转发请求”,而是要统一以下几件事:

  • 身份与权限:谁能调用、能调用哪些模型、能调用到什么程度。
  • 配额与限流:每个租户、用户、系统每天能调用多少次,最大 token 消耗是多少。
  • 审计与追踪:每次调用都能留痕,便于合规、排障和统计。
  • 模型路由:按场景、成本、质量、区域、版本自动选择模型。
  • 供应商适配:不同厂商的参数、返回值、流式协议差异都被屏蔽。
  • 观测与告警:吞吐、错误率、延迟、成本、重试次数可监控。

从工程角度看,这些能力共同构成了 AI Gateway。

1.3 AI Gateway 的价值

AI Gateway 的价值可以概括为四句话:

  • 对业务来说,只认统一 API
  • 对平台来说,统一治理、统一统计、统一路由
  • 对安全来说,把风险集中在可控边界
  • 对演进来说,让模型替换像换配置一样简单

这就是平台化的意义:不是让每个业务都去研究模型接入细节,而是让平台把复杂度吞掉。

第二章 统一鉴权、配额、审计、模型路由

2.1 鉴权:先确定“谁能进来”

AI 网关通常有两层身份体系:

  • 平台访问身份:例如某个业务系统、某个前端应用、某个内部服务;
  • 业务调用身份:例如某个租户、某个用户、某个部门、某个项目。

在 Spring Boot 3 中,最常见的做法是配合 Spring Security 完成认证和授权。认证可以使用 JWT、OAuth2 Client Credentials、API Key 等方式;授权则可以根据角色、权限、租户、模型白名单来判断。

企业内部最实用的方式通常是:

  • 外部请求携带 API-KeyBearer Token
  • 网关验证身份;
  • 解析租户信息、应用标识、权限范围;
  • 决定是否允许调用指定模型。

2.2 配额:再决定“能用多少”

配额不只是“调用次数限制”,还可以扩展为:

  • 每分钟请求数限制;
  • 每天请求数限制;
  • 每天 token 消耗上限;
  • 每月费用上限;
  • 每个模型的单独额度;
  • 高峰期的降级阈值。

在平台设计里,配额通常和租户绑定。比如某个部门允许每天调用 10 万 token,其中高价值模型只允许占比 20%。当额度不足时,网关可以拒绝请求,也可以自动路由到成本更低的备用模型。

2.3 审计:知道“发生了什么”?

审计比日志更有治理意义。日志通常是面向排障,审计则是面向合规和追踪。

一个完整的 AI 调用审计记录至少应该包含:

  • 请求 ID;
  • 租户 ID;
  • 调用方应用 ID;
  • 模型名称;
  • 请求时间和响应时间;
  • 输入 token 数;
  • 输出 token 数;
  • 调用结果;
  • 错误信息;
  • 是否命中缓存;
  • 是否触发路由切换。

如果涉及敏感数据,还可以对输入输出进行脱敏后再记录。

2.4 路由:最后决定“调用谁”

AI 模型路由是一项非常重要的能力。路由策略的核心不是“随机挑一个模型”,而是基于规则做决策。例如:

  • 小模型优先:简单问答优先低成本模型;
  • 高价值请求优先:复杂推理请求路由到高能力模型;
  • 区域优先:国内业务优先国内模型;
  • 容灾优先:主模型失败时切到备用模型;
  • 版本优先:灰度期间只让部分租户访问新版本;
  • 价格优先:同类模型中优先选成本更低的供应商。

一个成熟的 AI Gateway,路由逻辑应该是可配置、可观测、可回滚的。

2.5 统一治理流程

一条请求进入网关后,建议按如下顺序处理:

  1. 认证身份;
  2. 校验权限;
  3. 解析租户与业务上下文;
  4. 检查配额与限流;
  5. 记录审计开始日志;
  6. 执行模型路由;
  7. 调用具体模型适配器;
  8. 记录响应与耗时;
  9. 回写成本、token、错误信息;
  10. 返回结果。

这样做的好处是,网关具备了“先治理、后调用”的统一逻辑。

第三章 屏蔽不同模型提供商差异

3.1 为什么要做适配层

不同模型厂商的差异,通常不只体现在接口地址上,还体现在:

  • 请求字段命名不同;
  • 消息结构不同;
  • 流式输出协议不同;
  • 认证方式不同;
  • 错误码不同;
  • 采样参数不同;
  • 上下文窗口不同;
  • 速率限制不同。

如果业务代码直接依赖这些差异,后续每换一家供应商都要改大量代码。最佳实践是让业务只面对统一抽象,比如统一的 ModelRequestModelResponse

3.2 适配器模式是最自然的选择

适配器模式可以把不同厂商的实现包装成一致的接口:

public interface AiModelClient {
    ModelResult generate(ModelRequest request);
}

然后每家供应商实现一个适配器:

  • OpenAiClientAdapter
  • ClaudeClientAdapter
  • DeepSeekClientAdapter
  • InternalModelClientAdapter

网关只和接口交互,不关心底层细节。

3.3 统一请求与统一响应

统一请求对象的设计,决定了平台后续能不能扩展。

建议把请求拆成:

  • 基础上下文:租户、用户、traceId;
  • 模型目标:模型类型、模型版本、温度、最大 token;
  • 业务消息:system、user、assistant、tool;
  • 控制参数:流式、重试、超时、回退策略。

统一响应对象则至少包括:

  • 结果文本;
  • 使用 token;
  • 供应商信息;
  • 模型名称;
  • 是否流式;
  • 是否成功;
  • 错误信息。

3.4 从“硬编码厂商”到“平台配置”

平台化不是把厂商写死在代码里,而是把厂商配置化。比如:

  • 某个租户默认使用 deepseek-chat
  • 某个部门的高价值请求默认使用 gpt-4.1
  • 某个业务只允许访问国内部署的模型;
  • 某个模型只在夜间批处理任务中可用。

这样,运营人员甚至可以在不改代码的情况下切换模型策略。

第四章 Spring Boot 3.x 作为 AI Gateway 的实现思路

4.1 选择 Spring Boot 3.x 的原因

Spring Boot 3.x 带来的价值主要有以下几点:

  • 基于 Java 17,现代语法和性能体验更好;
  • 全面切换到 Jakarta EE 命名空间;
  • 与 Spring Security、Spring WebFlux、Spring Data、Micrometer 等生态结合成熟;
  • 更适合云原生部署;
  • 对观测、指标、健康检查的支持非常自然。

对于 AI Gateway 这种“既要稳定又要灵活”的中台组件,Spring Boot 3.x 是一个非常稳妥的技术底座。

4.2 推荐的模块划分

一个实用的 AI Gateway 项目可以拆成以下模块:

  • ai-gateway-api:对外暴露统一接口;
  • ai-gateway-core:核心业务逻辑;
  • ai-gateway-security:认证与授权;
  • ai-gateway-quota:配额与限流;
  • ai-gateway-audit:审计与日志;
  • ai-gateway-routing:模型路由;
  • ai-gateway-provider-openai:OpenAI 适配器;
  • ai-gateway-provider-deepseek:DeepSeek 适配器;
  • ai-gateway-provider-internal:内部模型适配器。

如果是单体启动,也可以先按包结构拆开,后续再拆成微服务。

4.3 推荐架构图

相关示意图绘制如下,仅供参考:

4.4 请求处理时序图

相关示意图绘制如下,仅供参考:

第五章 从零搭建一个可运行的 AI 网关示例

说明:下面的示例是一个可运行的 Spring Boot 3.x 结构化样例,重点演示“统一请求、统一路由、统一鉴权、统一审计”的实现方式。为了让读者容易理解,示例优先采用 Spring MVC + 内存实现,后续可以自然替换为数据库、Redis、MQ、远程模型 SDK。

5.1 Maven 依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>ai-gateway-demo</artifactId>
    <version>1.0.0</version>
    <name>ai-gateway-demo</name>
    <description>Spring Boot 3.x AI Gateway Demo</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

5.2 统一请求对象

package com.example.aigateway.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.util.List;

/**
 * 统一的 AI 请求对象
 * 这个对象用于屏蔽不同模型厂商的参数差异。
 */
public class ModelRequest {

    /**
     * 租户标识,用于配额、计费和审计。
     */
    @NotBlank(message = "tenantId 不能为空")
    private String tenantId;

    /**
     * 调用方应用标识。
     */
    @NotBlank(message = "appId 不能为空")
    private String appId;

    /**
     * 业务请求 ID,用于链路追踪。
     */
    @NotBlank(message = "requestId 不能为空")
    private String requestId;

    /**
     * 目标模型类型,例如 chat、embedding、rerank。
     */
    @NotBlank(message = "modelType 不能为空")
    private String modelType;

    /**
     * 目标模型名称,可以为空,若为空则由路由器自动选择。
     */
    private String modelName;

    /**
     * 是否开启流式输出。
     */
    @NotNull(message = "stream 不能为空")
    private Boolean stream;

    /**
     * 对话消息列表。
     */
    @NotNull(message = "messages 不能为空")
    private List<ModelMessage> messages;

    /**
     * 最大输出 token。
     */
    private Integer maxTokens;

    /**
     * 温度参数。
     */
    private Double temperature;

    public String getTenantId() { return tenantId; }
    public void setTenantId(String tenantId) { this.tenantId = tenantId; }
    public String getAppId() { return appId; }
    public void setAppId(String appId) { this.appId = appId; }
    public String getRequestId() { return requestId; }
    public void setRequestId(String requestId) { this.requestId = requestId; }
    public String getModelType() { return modelType; }
    public void setModelType(String modelType) { this.modelType = modelType; }
    public String getModelName() { return modelName; }
    public void setModelName(String modelName) { this.modelName = modelName; }
    public Boolean getStream() { return stream; }
    public void setStream(Boolean stream) { this.stream = stream; }
    public List<ModelMessage> getMessages() { return messages; }
    public void setMessages(List<ModelMessage> messages) { this.messages = messages; }
    public Integer getMaxTokens() { return maxTokens; }
    public void setMaxTokens(Integer maxTokens) { this.maxTokens = maxTokens; }
    public Double getTemperature() { return temperature; }
    public void setTemperature(Double temperature) { this.temperature = temperature; }
}

5.3 消息对象

package com.example.aigateway.model;

/**
 * 统一消息结构
 */
public class ModelMessage {

    /**
     * 消息角色,例如 system、user、assistant。
     */
    private String role;

    /**
     * 消息内容。
     */
    private String content;

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

5.4 统一响应对象

package com.example.aigateway.model;

import java.time.LocalDateTime;

/**
 * 统一的 AI 响应对象
 */
public class ModelResponse {

    /**
     * 是否成功。
     */
    private boolean success;

    /**
     * 返回内容。
     */
    private String content;

    /**
     * 使用的模型名称。
     */
    private String modelName;

    /**
     * 供应商名称。
     */
    private String provider;

    /**
     * 请求耗时毫秒。
     */
    private long costMs;

    /**
     * 生成时间。
     */
    private LocalDateTime timestamp;

    /**
     * 错误信息。
     */
    private String errorMessage;

    public static ModelResponse success(String content, String modelName, String provider, long costMs) {
        ModelResponse response = new ModelResponse();
        response.success = true;
        response.content = content;
        response.modelName = modelName;
        response.provider = provider;
        response.costMs = costMs;
        response.timestamp = LocalDateTime.now();
        return response;
    }

    public static ModelResponse failure(String errorMessage) {
        ModelResponse response = new ModelResponse();
        response.success = false;
        response.errorMessage = errorMessage;
        response.timestamp = LocalDateTime.now();
        return response;
    }

    public boolean isSuccess() { return success; }
    public void setSuccess(boolean success) { this.success = success; }
    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
    public String getModelName() { return modelName; }
    public void setModelName(String modelName) { this.modelName = modelName; }
    public String getProvider() { return provider; }
    public void setProvider(String provider) { this.provider = provider; }
    public long getCostMs() { return costMs; }
    public void setCostMs(long costMs) { this.costMs = costMs; }
    public LocalDateTime getTimestamp() { return timestamp; }
    public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
    public String getErrorMessage() { return errorMessage; }
    public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
}

5.5 模型适配器接口

package com.example.aigateway.provider;

import com.example.aigateway.model.ModelRequest;
import com.example.aigateway.model.ModelResponse;

/**
 * 统一的模型调用接口
 */
public interface AiModelClient {

    /**
     * 调用模型并返回统一结果。
     */
    ModelResponse generate(ModelRequest request);

    /**
     * 返回供应商名称。
     */
    String provider();

    /**
     * 返回支持的模型名称。
     */
    String supportModelName();
}

5.6 一个模拟的 OpenAI 适配器

package com.example.aigateway.provider.openai;

import com.example.aigateway.model.ModelRequest;
import com.example.aigateway.model.ModelResponse;
import com.example.aigateway.provider.AiModelClient;
import org.springframework.stereotype.Component;

/**
 * 模拟 OpenAI 适配器
 * 这里为了演示,直接返回模拟内容。
 */
@Component
public class OpenAiAdapter implements AiModelClient {

    @Override
    public ModelResponse generate(ModelRequest request) {
        long start = System.currentTimeMillis();

        // 模拟厂商调用过程
        String content = "[OpenAI模拟结果] 你好,你的问题已经被统一网关处理。";

        long cost = System.currentTimeMillis() - start;
        return ModelResponse.success(content, supportModelName(), provider(), cost);
    }

    @Override
    public String provider() {
        return "openai";
    }

    @Override
    public String supportModelName() {
        return "gpt-4.1-mini";
    }
}

5.7 一个模拟的 DeepSeek 适配器

package com.example.aigateway.provider.deepseek;

import com.example.aigateway.model.ModelRequest;
import com.example.aigateway.model.ModelResponse;
import com.example.aigateway.provider.AiModelClient;
import org.springframework.stereotype.Component;

/**
 * 模拟 DeepSeek 适配器
 */
@Component
public class DeepSeekAdapter implements AiModelClient {

    @Override
    public ModelResponse generate(ModelRequest request) {
        long start = System.currentTimeMillis();

        String content = "[DeepSeek模拟结果] 统一网关已完成模型路由与响应聚合。";

        long cost = System.currentTimeMillis() - start;
        return ModelResponse.success(content, supportModelName(), provider(), cost);
    }

    @Override
    public String provider() {
        return "deepseek";
    }

    @Override
    public String supportModelName() {
        return "deepseek-chat";
    }
}

5.8 路由器实现

package com.example.aigateway.routing;

import com.example.aigateway.model.ModelRequest;
import com.example.aigateway.provider.AiModelClient;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 模型路由器
 * 根据请求参数决定调用哪个适配器。
 */
@Component
public class ModelRouter {

    private final List<AiModelClient> clients;

    public ModelRouter(List<AiModelClient> clients) {
        this.clients = clients;
    }

    /**
     * 根据请求选择模型客户端。
     */
    public AiModelClient route(ModelRequest request) {
        // 1. 若显式指定 modelName,则优先按名称匹配
        if (request.getModelName() != null && !request.getModelName().isBlank()) {
            for (AiModelClient client : clients) {
                if (request.getModelName().equalsIgnoreCase(client.supportModelName())) {
                    return client;
                }
            }
        }

        // 2. 否则按 modelType 做默认路由
        if ("chat".equalsIgnoreCase(request.getModelType())) {
            for (AiModelClient client : clients) {
                if ("deepseek-chat".equalsIgnoreCase(client.supportModelName())) {
                    return client;
                }
            }
        }

        // 3. 兜底返回第一个可用客户端
        if (!clients.isEmpty()) {
            return clients.get(0);
        }

        throw new IllegalStateException("当前没有可用的模型客户端");
    }
}

5.9 配额服务

package com.example.aigateway.quota;

import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 简单的内存配额服务
 * 实际生产应替换为 Redis、数据库或专门的限流组件。
 */
@Service
public class QuotaService {

    private final Map<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();
    private static final int DAILY_LIMIT = 1000;

    /**
     * 检查配额是否允许。
     */
    public boolean allow(String tenantId) {
        AtomicInteger counter = counterMap.computeIfAbsent(tenantId, key -> new AtomicInteger(0));
        return counter.get() < DAILY_LIMIT;
    }

    /**
     * 消耗一次配额。
     */
    public void consume(String tenantId) {
        counterMap.computeIfAbsent(tenantId, key -> new AtomicInteger(0)).incrementAndGet();
    }
}

5.10 审计服务

package com.example.aigateway.audit;

import com.example.aigateway.model.ModelRequest;
import com.example.aigateway.model.ModelResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * 审计服务
 */
@Service
public class AuditService {

    private static final Logger log = LoggerFactory.getLogger(AuditService.class);

    /**
     * 记录请求审计信息。
     */
    public void logRequest(ModelRequest request) {
        log.info("AI请求开始,tenantId={}, appId={}, requestId={}, modelType={}, modelName={}",
                request.getTenantId(), request.getAppId(), request.getRequestId(), request.getModelType(), request.getModelName());
    }

    /**
     * 记录响应审计信息。
     */
    public void logResponse(ModelRequest request, ModelResponse response) {
        log.info("AI请求结束,requestId={}, success={}, provider={}, modelName={}, costMs={}",
                request.getRequestId(), response.isSuccess(), response.getProvider(), response.getModelName(), response.getCostMs());
    }
}

5.11 核心服务

package com.example.aigateway.service;

import com.example.aigateway.audit.AuditService;
import com.example.aigateway.model.ModelRequest;
import com.example.aigateway.model.ModelResponse;
import com.example.aigateway.provider.AiModelClient;
import com.example.aigateway.quota.QuotaService;
import com.example.aigateway.routing.ModelRouter;
import org.springframework.stereotype.Service;

/**
 * 网关核心服务
 */
@Service
public class GatewayService {

    private final QuotaService quotaService;
    private final AuditService auditService;
    private final ModelRouter modelRouter;

    public GatewayService(QuotaService quotaService, AuditService auditService, ModelRouter modelRouter) {
        this.quotaService = quotaService;
        this.auditService = auditService;
        this.modelRouter = modelRouter;
    }

    /**
     * 统一调用入口。
     */
    public ModelResponse call(ModelRequest request) {
        auditService.logRequest(request);

        if (!quotaService.allow(request.getTenantId())) {
            return ModelResponse.failure("当前租户已超过配额限制");
        }

        quotaService.consume(request.getTenantId());

        AiModelClient client = modelRouter.route(request);
        ModelResponse response = client.generate(request);

        auditService.logResponse(request, response);
        return response;
    }
}

5.12 控制器

package com.example.aigateway.controller;

import com.example.aigateway.model.ModelRequest;
import com.example.aigateway.model.ModelResponse;
import com.example.aigateway.service.GatewayService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * AI 网关统一接口
 */
@RestController
@RequestMapping("/api/v1/ai")
public class GatewayController {

    private final GatewayService gatewayService;

    public GatewayController(GatewayService gatewayService) {
        this.gatewayService = gatewayService;
    }

    @PostMapping("/chat")
    public ResponseEntity<ModelResponse> chat(@Valid ModelRequest request) {
        return ResponseEntity.ok(gatewayService.call(request));
    }
}

5.13 启动类

package com.example.aigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * AI Gateway 启动类
 */
@SpringBootApplication
public class AiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(AiGatewayApplication.class, args);
    }
}

5.14 安全配置(基础版)

package com.example.aigateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;

/**
 * Spring Security 基础配置
 */
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/actuator/**").permitAll()
                        .anyRequest().permitAll()
                )
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

5.15 代码解析

这一套示例体现了一个非常重要的设计思路:先统一抽象,再分别实现

ModelRequestModelResponse 是平台和业务之间的稳定边界;AiModelClient 是供应商差异的隔离层;ModelRouter 是策略中心;QuotaService 是治理中心;AuditService 是审计中心;GatewayService 则把所有能力串成一条标准链路。

这种结构最大的优点是可演进。今天先做内存版,明天可以换 Redis;今天先做模拟适配器,明天可以接真实 SDK;今天先做简单路由,后面可以做基于成本、质量、租户等级的智能路由。

第六章 统一鉴权的进一步演进

6.1 API Key 模式

对于企业内部平台,最容易落地的方式往往是 API Key。每个业务系统拿到一个独立的 key,网关根据 key 识别租户与权限。

优点是实现简单,适合内部调用;缺点是权限表达能力有限,且需要良好的密钥管理体系。

6.2 JWT 模式

当平台需要和企业统一身份系统集成时,JWT 是更自然的选择。JWT 中可以携带:

  • tenantId;
  • appId;
  • roles;
  • scopes;
  • modelPermissions。

网关拿到 token 后解析 claims,再决定请求是否可进入后续流程。

6.3 OAuth2 Client Credentials

如果 AI Gateway 面向多个系统服务调用,OAuth2 Client Credentials 很适合用作机器到机器认证。它和 Spring Security 的结合也比较成熟。

6.4 权限粒度建议

推荐至少做到以下粒度:

  • 按租户授权;
  • 按模型类别授权;
  • 按模型名称授权;
  • 按最大 token 数授权;
  • 按是否允许流式输出授权。

这样可以满足大多数企业平台的治理要求。

第七章 限流、重试、熔断与降级

AI 网关不只是“转发”,还必须面对模型调用不稳定的问题。

7.1 为什么需要限流

模型调用的成本高、延迟高、波动大。如果上游业务突然暴涨,网关和模型提供商都可能被打穿。限流的本质是保护系统和预算。

7.2 重试的边界

不是所有错误都适合重试。建议只对网络抖动、超时、偶发 5xx 做有限次重试,不要对参数错误、鉴权失败重复请求。

7.3 熔断与降级

当某个供应商持续失败时,网关可以:

  • 自动切换到备用模型;
  • 返回兜底模板答案;
  • 关闭非核心能力;
  • 延迟队列处理。

在企业生产环境里,降级能力往往比模型本身更重要。

第八章 流式输出与异步调用

很多对话式 AI 场景都需要流式输出,也就是一边生成、一边返回。Spring Boot 3.x 中可以通过 SseEmitterResponseBodyEmitter 或 WebFlux 来实现。

流式输出的价值在于:

  • 用户体验更好;
  • 首字响应时间更短;
  • 大模型长回答时更自然;
  • 可以更快发现问题。

异步调用则适合:

  • 批量摘要;
  • 文档向量化;
  • 离线分类;
  • 长任务编排。

对于平台化系统,建议把同步对话和异步任务分为两套通道治理。

第九章 企业落地时最容易忽略的细节

9.1 日志脱敏

模型输入经常包含用户隐私、业务机密和内部提示词。日志和审计都要考虑脱敏策略。

9.2 提示词版本管理

很多企业把模型能力失败归咎于模型,实际上问题出在提示词没有版本管理。平台最好把 prompt 模板也纳入配置中心和灰度管理。

9.3 结果缓存

重复问题、模板问答、知识库检索结果可以缓存,减少重复 token 消耗。

9.4 统计维度设计

建议按以下维度统计:

  • 租户;
  • 应用;
  • 模型;
  • 供应商;
  • 时间段;
  • 成本;
  • 成功率;
  • 平均响应时间。

9.5 灰度发布

模型升级最好支持灰度。比如先给 5% 的租户切到新模型,观察效果后再逐步扩大。

第十章 结语

AI 网关并不是一个“为了技术而技术”的系统,它是企业 AI 能力规模化之后必然出现的基础设施。只要模型接入不统一,安全、成本、审计、路由、观测就都会失控;而一旦建立统一接入层,AI 能力就会从零散调用变成可治理、可运营、可演进的平台。

Spring Boot 3.x 非常适合作为这套平台的工程基础:它足够成熟、足够稳定,也足够灵活。通过统一请求模型、适配器模式、路由器、配额服务、审计服务和安全体系,我们完全可以在一个简洁的 Spring Boot 项目里,搭出一套具备企业雏形的 AI Gateway。

后续如果继续扩展,还可以把它升级为:

  • 支持多租户的企业 AI 中台;
  • 支持向量检索与知识库问答的 RAG 网关;
  • 支持多模型编排与 Agent 编排的平台;
  • 支持成本计费与账单中心的平台能力。

这正是 AI 平台化的真正方向:让模型成为能力,让网关成为秩序,让平台成为生产力。

ok,同学们,本节课就上到这儿,下课~

🧧 学习福利 · 限时开放 🧧

当然,无论你是计算机专业在读学生,还是对编程充满兴趣的入门者,都强烈建议系统学习SpringBoot全体系专栏:👉 「滚雪球学 Spring Boot」;涵盖SpringBoot所有教学内容。

该专栏以“循序渐进 + 实战驱动”为核心理念,从基础到进阶到就业到架构师逐层展开,帮助你快速建立完整的 Spring Boot 技术体系,带你玩转SpringBoot框架。

📌 学习承诺:
通过该专栏,你将能够:

  • 快速掌握 Spring Boot 核心开发能力
  • 构建完整的后端项目认知体系
  • 实现从“入门”到“独立开发”的跃迁

就像“滚雪球”一样,知识不断积累、能力持续放大,实现指数级成长 🚀

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

同时欢迎大家关注技术号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。

ps:本文涉及所有源代码,均已上传至Gitee开源,供同学们直接对照学习 Gitee传送门,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗

🫵 Who am I?

我是 bug菌,一名深耕 Java 后端领域数十年的一线研发老兵,曾担任独角兽企业后端技术经理、研发架构师等职位,长期专注于 Java 后端、分布式架构、微服务治理、高并发系统、工程效能与研发管理等方向。

目前活跃于多个主流技术社区,包括:

CSDN稀土掘金InfoQ51CTO华为云开发者社区阿里云开发者社区腾讯云开发者社区开源中国博客园墨天轮 等平台。

曾获得:

  • CSDN 博客之星 Top30
  • 华为云多年度十佳博主 & 卓越贡献奖
  • 掘金多年度人气作者 Top40
  • CSDN、掘金、InfoQ、51CTO 等平台签约作者 / 优质作者

截至目前,全网技术内容累计影响读者众多,全网粉丝已超过 30w+

如果你也关注 Java 后端、架构设计、技术成长、职场进阶与研发管理,欢迎关注我的技术内容合集入口:👉 点击查看 👈️

硬核技术号 「猿圈奇妙屋」 期待你的加入。

这里不仅分享技术干货,也记录一线研发人的成长、踩坑、思考与进阶路径。

愿我们一起打怪升级,在技术路上持续进阶。

- End -

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐