【SpringBoot 3.x 第216节】AI 网关与模型调用平台化,一文带你了解!
🏆本文收录于《滚雪球学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-Key或Bearer Token; - 网关验证身份;
- 解析租户信息、应用标识、权限范围;
- 决定是否允许调用指定模型。
2.2 配额:再决定“能用多少”
配额不只是“调用次数限制”,还可以扩展为:
- 每分钟请求数限制;
- 每天请求数限制;
- 每天 token 消耗上限;
- 每月费用上限;
- 每个模型的单独额度;
- 高峰期的降级阈值。
在平台设计里,配额通常和租户绑定。比如某个部门允许每天调用 10 万 token,其中高价值模型只允许占比 20%。当额度不足时,网关可以拒绝请求,也可以自动路由到成本更低的备用模型。
2.3 审计:知道“发生了什么”?
审计比日志更有治理意义。日志通常是面向排障,审计则是面向合规和追踪。
一个完整的 AI 调用审计记录至少应该包含:
- 请求 ID;
- 租户 ID;
- 调用方应用 ID;
- 模型名称;
- 请求时间和响应时间;
- 输入 token 数;
- 输出 token 数;
- 调用结果;
- 错误信息;
- 是否命中缓存;
- 是否触发路由切换。
如果涉及敏感数据,还可以对输入输出进行脱敏后再记录。
2.4 路由:最后决定“调用谁”
AI 模型路由是一项非常重要的能力。路由策略的核心不是“随机挑一个模型”,而是基于规则做决策。例如:
- 小模型优先:简单问答优先低成本模型;
- 高价值请求优先:复杂推理请求路由到高能力模型;
- 区域优先:国内业务优先国内模型;
- 容灾优先:主模型失败时切到备用模型;
- 版本优先:灰度期间只让部分租户访问新版本;
- 价格优先:同类模型中优先选成本更低的供应商。
一个成熟的 AI Gateway,路由逻辑应该是可配置、可观测、可回滚的。
2.5 统一治理流程
一条请求进入网关后,建议按如下顺序处理:
- 认证身份;
- 校验权限;
- 解析租户与业务上下文;
- 检查配额与限流;
- 记录审计开始日志;
- 执行模型路由;
- 调用具体模型适配器;
- 记录响应与耗时;
- 回写成本、token、错误信息;
- 返回结果。
这样做的好处是,网关具备了“先治理、后调用”的统一逻辑。
第三章 屏蔽不同模型提供商差异
3.1 为什么要做适配层
不同模型厂商的差异,通常不只体现在接口地址上,还体现在:
- 请求字段命名不同;
- 消息结构不同;
- 流式输出协议不同;
- 认证方式不同;
- 错误码不同;
- 采样参数不同;
- 上下文窗口不同;
- 速率限制不同。
如果业务代码直接依赖这些差异,后续每换一家供应商都要改大量代码。最佳实践是让业务只面对统一抽象,比如统一的 ModelRequest 和 ModelResponse。
3.2 适配器模式是最自然的选择
适配器模式可以把不同厂商的实现包装成一致的接口:
public interface AiModelClient {
ModelResult generate(ModelRequest request);
}
然后每家供应商实现一个适配器:
OpenAiClientAdapterClaudeClientAdapterDeepSeekClientAdapterInternalModelClientAdapter
网关只和接口交互,不关心底层细节。
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 代码解析
这一套示例体现了一个非常重要的设计思路:先统一抽象,再分别实现。
ModelRequest 和 ModelResponse 是平台和业务之间的稳定边界;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 中可以通过 SseEmitter、ResponseBodyEmitter 或 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|稀土掘金|InfoQ|51CTO|华为云开发者社区|阿里云开发者社区|腾讯云开发者社区|开源中国|博客园|墨天轮 等平台。
曾获得:
- CSDN 博客之星 Top30
- 华为云多年度十佳博主 & 卓越贡献奖
- 掘金多年度人气作者 Top40
- CSDN、掘金、InfoQ、51CTO 等平台签约作者 / 优质作者
截至目前,全网技术内容累计影响读者众多,全网粉丝已超过 30w+。
如果你也关注 Java 后端、架构设计、技术成长、职场进阶与研发管理,欢迎关注我的技术内容合集入口:👉 点击查看 👈️
硬核技术号 「猿圈奇妙屋」 期待你的加入。
这里不仅分享技术干货,也记录一线研发人的成长、踩坑、思考与进阶路径。
愿我们一起打怪升级,在技术路上持续进阶。
- End -
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)