零基础读懂 Spring Boot AI 网关:从场景定义到调用大模型的完整流程
2026/7/21 5:56:12 网站建设 项目流程

前言

随着大语言模型技术的发展,AI 能力逐渐从单一对话工具转变为可嵌入业务系统的智能服务能力。本项目基于 Spring Boot 与 Spring AI 框架,设计并实现了一套轻量化 AI 网关服务,用于统一管理不同业务场景下的大模型调用流程。

传统应用在接入大语言模型时,通常需要在前端或业务代码中直接调用模型接口,不同功能之间容易出现接口管理混乱、提示词难以维护、模型配置分散等问题。因此,本项目通过构建 AI Gateway 层,对 AI 能力进行统一封装,将业务场景识别、Prompt 模板管理、模型调用以及接口返回等流程进行模块化设计。

系统整体采用前后端分离架构,前端仅需要根据具体业务场景调用后端提供的 API 接口,并配置对应的大模型服务参数,即可使用相关 AI 功能。后端通过 Spring AI 提供的 ChatClient 和 ImageModel 实现与大语言模型和图片生成模型的交互,同时通过场景枚举和 Prompt 模板机制,对不同任务进行统一管理。

目前系统主要实现以下 AI 能力:

(1)文本生成能力

基于 ChatClient 调用大语言模型,实现智能问答、内容生成、文本分析等功能。系统根据不同业务场景加载对应提示词模板,将用户输入转换为结构化 Prompt 后发送至模型。

(2)古籍文本语言转换能力

针对中国古籍食谱等文本内容,通过预设 Prompt 模板引导大模型完成古文理解、现代语言转换以及内容解释,使传统文本能够以更加易理解的形式呈现。

(3)文本生成图片能力

基于 Spring AI ImageModel 接入图片生成模型,根据用户输入的描述生成对应图片结果,并支持返回图片 URL 或 Base64 数据,方便前端直接展示。

(4)流式文本输出能力

针对较长文本生成任务,系统提供 SSE 流式接口,使模型生成内容能够实时返回前端,提升用户交互体验。

需要说明的是,当前系统主要关注 AI 能力接入与业务调用流程设计,并未涉及 RAG(Retrieval-Augmented Generation,检索增强生成)相关技术,例如文档切片、向量数据库、知识库检索等模块。系统当前通过 Prompt Engineering(提示词工程)的方式约束模型输出,后续可以根据业务需求进一步扩展知识库检索能力,实现基于私有数据的增强生成。

本文将围绕该 AI 网关系统的核心代码结构展开介绍,按照AI 场景管理 → 配置管理 → Prompt 模板处理 → AI 服务调用 → 前端接口暴露的流程,对各模块功能和实现方式进行分析。


一、先理解:这套代码到底在做什么

可以把这套代码理解成一个“AI 中转站”。

前端不直接调用大模型,而是先向 Spring Boot 后端发送请求。

整体流程如下:

前端发送问题

AiGatewayController 接收请求

判断当前属于哪个 AI 场景

AiGatewayService 处理业务

AiPrivacyService 对敏感内容脱敏

AiPromptTemplateService 加载提示词模板

ChatClient 或 ImageModel 调用 AI 模型

将文字或图片结果返回给前端

例如,当用户发送:

{ "scene": "ancient-text-translation", "prompt": "请翻译:学而时习之,不亦说乎?" }

系统会判断这是“古文翻译”场景,然后找到对应的提示词模板,再调用大模型。

最终发送给大模型的内容可能类似:

你是一名专业的古文翻译老师。 请将下面的古文翻译为通俗易懂的现代汉语: 学而时习之,不亦说乎?

这里的“你是一名专业的古文翻译老师”就来自提示词模板。


二、阅读 Java 代码前需要知道的几个概念

1. package 是什么

代码开头经常出现:

package com.example.springboot.ai;

package表示当前类所在的包。

可以把“包”理解成文件夹,用来对 Java 类进行分类。

例如:

com.example.springboot ├── ai │ ├── AiScene.java │ └── AiPromptTemplateService.java ├── config │ └── AiGatewayProperties.java ├── controller │ └── AiGatewayController.java ├── service │ ├── AiGatewayService.java │ └── AiPrivacyService.java └── utils └── FileUploadUtils.java

不同文件夹负责不同工作:

  • controller:接收前端请求;

  • service:处理具体业务;

  • config:读取配置文件;

  • ai:保存 AI 相关定义;

  • utils:保存通用工具类。


2. import 是什么

例如:

import org.springframework.stereotype.Service;

import表示导入其他类。

因为Service并不是当前文件中定义的,所以需要通过import把它引入进来。


3. 注解是什么

代码中有很多以@开头的内容,例如:

@Service @RestController @PostMapping @ConfigurationProperties

这些都叫作注解。

注解可以理解成给 Spring Boot 的“说明书”。

例如:

@Service public class AiGatewayService { }

@Service是在告诉 Spring:

这个类是一个业务服务类,请帮我创建并管理它。


4. 构造方法和依赖注入

例如:

public AiGatewayService( ChatClient.Builder chatClientBuilder, AiGatewayProperties properties, AiPrivacyService privacyService) { this.properties = properties; this.privacyService = privacyService; }

这段代码是构造方法。

当 Spring 创建AiGatewayService时,会自动把它需要的对象传进来。

这种方式叫作“依赖注入”。

可以简单理解为:

AiGatewayService 需要配置对象和隐私处理对象,Spring 会提前准备好,并自动交给它。


三、AiScene:定义系统支持哪些 AI 场景

代码位置:

package com.example.springboot.ai;

核心代码:

public enum AiScene { ANCIENT_TEXT_TRANSLATION( "ancient-text-translation", false, "translator" ), CONSTITUTION_ANALYSIS( "constitution-analysis", false, "suggester" ), DIET_THERAPY_PLAN( "diet-therapy-plan", true, "projecter" ), CONSTITUTION_CHANGE_ANALYSIS( "constitution-change-analysis", true, "changeanalysis" ), DIET_THERAPY_QA( "diet-therapy-qa", true, "laoji" ), CULINARY_STEP_IMAGE( "culinary-step-image", false, "text-to-image", "image" ); }

1. enum 是什么

enum是枚举类型。

它适合保存一组固定选项。

一个订单状态只能是:未付款、已付款、已发货、已完成,四种之一

这种固定状态就可以使用枚举。

这里的AiScene用来保存系统支持的 AI 场景。

例如:

  • ANCIENT_TEXT_TRANSLATION:古文翻译;

  • CONSTITUTION_ANALYSIS:体质分析;

  • DIET_THERAPY_PLAN:食疗方案;

  • DIET_THERAPY_QA:食疗问答;

  • CULINARY_STEP_IMAGE:烹饪步骤图片生成。


2. 每个参数代表什么

以这段代码为例:

ANCIENT_TEXT_TRANSLATION( "ancient-text-translation", false, "translator" )

它包含三个部分。

第一个参数:templateKey

"ancient-text-translation"

它是提示词模板的名称。

系统默认会寻找:

classpath:/ai/prompts/ancient-text-translation.st

也就是在项目中定义的提示词模板:

src/main/resources/ai/prompts/ancient-text-translation.st

第二个参数:streamingPreferred

这里的 true or false 表示该场景是否更推荐使用流式输出。

流式输出类似聊天机器人逐字显示内容。

普通返回:
等待一段时间后,一次性看到完整答案。

流式返回:
模型生成一点,前端显示一点。

true 表示更推荐流式输出。

需要注意,目前代码只是保存了这个配置,并没有根据它自动决定调用普通接口还是流式接口。


第三个参数:aliases

"translator"

这是场景别名。

因此,下面几种写法都可能识别为古文翻译:

ANCIENT_TEXT_TRANSLATION ancient-text-translation translator

这样做通常是为了兼容旧系统或者前端之前使用的名称。


3. from 方法的作用

public static AiScene from(String value)

这个方法负责把前端传入的字符串转换成AiScene

例如:

AiScene scene = AiScene.from("translator");

最后得到:

AiScene.ANCIENT_TEXT_TRANSLATION

如果用户没有传场景:

if (value == null || value.trim().isEmpty()) { throw new IllegalArgumentException("scene or agent is required"); }

程序会抛出异常:

scene or agent is required

如果传入不存在的场景:

unknown-scene

程序会抛出:

Unknown AI scene: unknown-scene

4. normalize 方法的作用

private static String normalize(String value) { return value.trim() .replace("-", "_") .toUpperCase(Locale.ROOT); }

这个方法用于统一字符串格式。

例如:

ancient-text-translation

经过处理后变成:

ANCIENT_TEXT_TRANSLATION

处理步骤是:

  1. trim():删除前后空格;

  2. replace("-", "_"):将横线替换为下划线;

  3. toUpperCase():转换成大写。

这样可以减少用户输入格式不同造成的问题。


四、AiGatewayProperties:读取配置文件

代码位置:

package com.example.springboot.config;

核心注解:

@Data @Component @ConfigurationProperties(prefix = "ai.gateway") public class AiGatewayProperties { }

1. @Component

@Component

表示这个类由 Spring 管理。

其他类需要使用它时,Spring 可以自动注入。


2. @ConfigurationProperties

@ConfigurationProperties(prefix = "ai.gateway")

表示读取配置文件中以ai.gateway开头的内容。

例如,可以在application.yml中配置:

ai: gateway: scenes: ancient-text-translation: template: classpath:/ai/prompts/ancient-text-translation.st constitution-analysis: template: classpath:/ai/prompts/constitution-analysis.st image: count: 1 size: 1024x1024 response-format: url step-delay-millis: 1000 audio: enabled: false model: "" voice: ""

Spring 会自动把这些配置放入AiGatewayProperties对象。


3. @Data

@Data

这是 Lombok 提供的注解。

它会自动生成:

  • get方法;

  • set方法;

  • toString方法;

  • equals方法;

  • hashCode方法。

例如代码中虽然没有手动写:

public ImageProperties getImage() { return image; }

但因为有@Data,Lombok 会帮助生成。


4. scenes 配置

private Map<String, SceneProperties> scenes = new HashMap<>();

它用于保存不同场景的配置。

例如:

scenes: ancient-text-translation: template: classpath:/ai/prompts/ancient-text-translation.st

读取后可以理解成:

键:ancient-text-translation 值:对应的 SceneProperties 对象

我们要将这个部分与先前的AiScene区分开来:

AiScene只能用于判断前端用的是哪个智能体,而这里则是确定使用模型的类型,运行配置等。


5. 图片配置

private ImageProperties image = new ImageProperties();

默认值为:

private Integer count = 1; private String size = "2K"; private String responseFormat = "url"; private Long stepDelayMillis = 1000L;

它们分别表示:

  • count:每次生成几张图片;

  • size:图片尺寸;

  • responseFormat:返回 URL 还是 Base64;

  • stepDelayMillis:步骤间隔时间。

其中stepDelayMillis在当前展示的代码中还没有实际使用。


6. 音频配置

private AudioProperties audio = new AudioProperties();

其中包括:

private boolean enabled = false; private String model = ""; private String voice = "";

它们表示:

  • 是否开启语音功能;

  • 使用哪个语音模型;

  • 使用哪个声音。


五、AiPromptTemplateService:加载和渲染提示词模板

代码位置:

package com.example.springboot.ai;

这个类负责处理提示词模板。

1. 默认模板地址

private static final String DEFAULT_TEMPLATE_PATTERN = "classpath:/ai/prompts/%s.st";

其中%s是占位符。

假设:

scene.getTemplateKey()

返回:

ancient-text-translation

最终模板路径就是:

classpath:/ai/prompts/ancient-text-translation.st

2. render 方法

public String render( AiScene scene, String prompt, Map<String, Object> meta)

它接收三个参数:

  • scene:当前 AI 场景;

  • prompt:用户输入;

  • meta:其他附加信息。

例如:

{ "scene": "diet-therapy-plan", "prompt": "请制定一份食疗方案", "meta": { "age": 30, "constitution": "湿热体质" } }

其中:

prompt = 请制定一份食疗方案 age = 30 constitution = 湿热体质

3. 模板变量

代码先创建一个变量集合:

Map<String, Object> variables = new HashMap<>();

然后放入用户问题:

variables.put("prompt", prompt == null ? "" : prompt);

如果存在meta

variables.put("meta", meta); variables.putAll(meta);

这意味着模板中既可以通过整体的meta使用数据,也可以直接使用具体字段。

一个模板文件可以写成:

你是一名食疗方案助手。 用户问题: {prompt} 用户年龄: {age} 用户体质: {constitution} 请根据以上信息生成一份清晰、合理的食疗建议。

渲染后会变成:

你是一名食疗方案助手。 用户问题: 请制定一份食疗方案 用户年龄: 30 用户体质: 湿热体质 请根据以上信息生成一份清晰、合理的食疗建议。

4. resolveTemplate 方法

private Resource resolveTemplate(AiScene scene)

这个方法负责确定到底使用哪个模板文件。

它首先检查配置文件中有没有指定模板:

properties.getScenes().get(scene.getTemplateKey());

如果配置文件中有模板路径,就使用配置中的路径。

如果没有,就使用默认路径:

String.format( DEFAULT_TEMPLATE_PATTERN, scene.getTemplateKey() )

因此,这套设计支持两种方式。

第一种是默认约定:

ai/prompts/场景名称.st

第二种是在配置文件中自定义模板路径。


六、AiGatewayService:AI 功能的核心业务层

代码位置:

package com.example.springboot.service;

这个类是整套系统的核心。

它负责:

  • 调用聊天模型;

  • 调用流式聊天;

  • 调用图片模型;

  • 渲染提示词模板;

  • 预留语音生成功能。


1. 主要依赖

private final ChatClient chatClient; private final ImageModel imageModel; private final AiGatewayProperties properties; private final AiPromptTemplateService promptTemplateService;

它们分别负责:

ChatClient

调用聊天模型。

例如:

用户提问 → ChatClient → 大语言模型 → 返回文字

ImageModel

调用图片生成模型。

例如:

图片描述 → ImageModel → 图片模型 → 返回图片

AiGatewayProperties

读取系统配置。

AiPromptTemplateService

将用户问题填入提示词模板。


2. 构造方法

public AiGatewayService( ChatClient.Builder chatClientBuilder, ObjectProvider<ImageModel> imageModelProvider, AiGatewayProperties properties, AiPromptTemplateService promptTemplateService)

Spring 会自动注入这些依赖。

其中:

this.chatClient = chatClientBuilder.build();

表示通过 ChatClient.Builder 创建 ChatClient 对象,
后续用于调用大语言模型。

this.imageModel = imageModelProvider.getIfAvailable();

表示尝试获取图片生成模型。

由于 ImageModel 并不是所有环境都必须配置,因此采用可选注入方式。

如果系统没有配置图片模型,则 imageModel 为 null,
调用图片生成接口时会进行检查。

即使项目暂时没有图片生成功能,文字聊天功能仍然可以正常启动。


3. 普通聊天 chat

public String chat( AiScene scene, String prompt, Map<String, Object> meta)

核心代码:

return chatClient.prompt() .user(buildPrompt(scene, prompt, meta)) .call() .content();

可以分成四步理解。

第一步:创建一次 AI 请求

chatClient.prompt()

第二步:设置用户提示词

.user(buildPrompt(scene, prompt, meta))

buildPrompt会完成模板渲染。

第三步:调用模型

.call()

这是一种普通的同步调用。

程序会等待模型生成完毕。

第四步:获取文字内容

.content()

最终返回模型生成的文字。


4. 流式聊天 stream

public Flux<String> stream( AiScene scene, String prompt, Map<String, Object> meta)

核心代码:

return chatClient.prompt() .user(buildPrompt(scene, prompt, meta)) .stream() .content() .concatWithValues("[DONE]");

它与普通聊天的主要区别是:

普通聊天使用:

.call()

流式聊天使用:

.stream()

流式返回的类型是:

Flux<String>

Flux可以理解成一个不断产生数据的管道。

例如模型正在生成:

春 季 适 合 食 用 ……

后端可以一段一段发送给前端,而不需要等待完整答案生成。

最后添加:

.concatWithValues("[DONE]")

前端看到这个标记,就知道本次回答结束了。


5. 图片生成 generateImages

public List<String> generateImages( AiScene scene, String prompt, Map<String, Object> meta)

首先判断图片模型是否存在:

if (imageModel == null) { throw new IllegalStateException( "Spring AI ImageModel is not configured" ); }

如果没有配置图片模型,就直接报错。

然后创建图片参数:

OpenAiImageOptions options = OpenAiImageOptions.builder() .N(properties.getImage().getCount()) .responseFormat(properties.getImage().getResponseFormat()) .build();

其中:

.N(...)

表示生成图片数量。

.responseFormat(...)

表示图片返回格式。

接着设置尺寸:

options.setSize(properties.getImage().getSize());

然后调用图片模型:

ImageResponse response = imageModel.call( new ImagePrompt( buildPrompt(scene, prompt, meta), options ) );

最后从模型响应中取出图片。

图片可能有两种形式:

图片 URL Base64 图片数据

6. toImageValue 方法

private String toImageValue(Image image)

如果图片模型返回 URL:

if (image.getUrl() != null && !image.getUrl().isBlank()) { return image.getUrl(); }

就直接返回 URL。

如果模型返回 Base64:

return "data:image/png;base64," + image.getB64Json();

就给 Base64 内容加上浏览器可以识别的前缀。

前端可以直接这样显示:

<img src="data:image/png;base64,……">

7. buildPrompt 方法

private String buildPrompt( AiScene scene, String prompt, Map<String, Object> meta)

该方法负责将用户输入转换为完整 Prompt。

return promptTemplateService.render( scene, cleanPrompt, sanitizeMeta(meta) );

然后,将处理后的内容放入提示词模板。


8. 语音生成功能

public byte[] synthesizeSpeech( String text, Map<String, Object> meta)

当前代码只判断语音功能是否开启:

if (!properties.getAudio().isEnabled()) { throw new UnsupportedOperationException( "AI speech is not enabled" ); }

后面直接抛出:

throw new UnsupportedOperationException( "AI speech gateway is not implemented yet" );

说明语音配置已经预留,但真正的调用代码还没有完成。


七、AiGatewayController:接收前端请求

代码位置:

package com.example.springboot.controller;

控制器相当于系统的“入口”。

前端发送的 HTTP 请求,会先进入这个类。


1. 类上的注解

@Tag( name = "AiGatewayController", description = "Spring AI gateway APIs" )

这是 Swagger注解,用于生成接口文档。

@CrossOrigin

表示允许跨域请求。

例如:

前端:http://localhost:5173 后端:http://localhost:8080

两个地址端口不同,浏览器会认为它们来自不同来源。

@CrossOrigin可以允许前端访问后端接口。

@RestController

表示这是一个 REST 接口控制器。

它返回的对象会自动转换为 JSON 或文字。

@RequestMapping("/api")

表示当前控制器中所有接口都以/api开头。


2. 普通聊天接口

@PostMapping({ "/ai/chat", "/ai/chat/structured" }) public String chatStructured( @RequestBody ChatPayload payload)

这个方法支持两个地址:

POST /api/ai/chat POST /api/ai/chat/structured

@RequestBody表示接收前端提交的 JSON。

例如:

{ "scene": "ancient-text-translation", "prompt": "请翻译这段古文", "meta": {} }

然后调用:

aiGatewayService.chat( resolveScene(payload), resolvePrompt(payload), payload.getMeta() );

3. 流式聊天接口

@PostMapping( value = "/ai/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE )

接口地址是:

POST /api/ai/chat/stream

其中:

produces = MediaType.TEXT_EVENT_STREAM_VALUE

表示使用 SSE 流式响应。

SSE 的全称是:

Server-Sent Events

可以理解为服务器不断向浏览器推送文字。


4. 图片生成接口

@PostMapping("/ai/image")

接口地址:

POST /api/ai/image

代码调用:

List<String> images = aiGatewayService.generateImages( scene, resolvePrompt(payload), payload.getMeta() );

然后返回:

result.put("scene", scene.name()); result.put("images", images);

最终 JSON 可能是:

{ "scene": "CULINARY_STEP_IMAGE", "images": [ "https://example.com/image1.png" ] }

5. 配置状态接口

@GetMapping("/ai/config/status")

接口地址:

GET /api/ai/config/status

这个接口不会真正调用 AI。

它用于查看系统配置是否完整。

例如返回:

{ "provider": "spring-ai-openai-compatible", "baseUrlConfigured": true, "apiKeyConfigured": true, "apiKeyLength": 32, "chatModelConfigured": true, "scenes": [] }

需要注意,它只返回 API Key 是否存在和长度,没有返回真正的 API Key。

这比直接输出密钥安全得多。

不过在正式生产环境中,这类配置状态接口仍然建议限制访问权限。


6. resolveScene 方法

private AiScene resolveScene(ChatPayload payload)

它用于获取场景。

首先检查请求是否为空:

if (payload == null)

然后优先读取:

payload.getScene()

如果没有scene,再读取:

payload.getAgent()

这说明代码同时兼容两种字段:

{ "scene": "translator" }

或者:

{ "agent": "translator" }

最后通过:

AiScene.from(scene)

转换成枚举。

如果转换失败,返回 HTTP 400 错误。


7. resolvePrompt 方法

private String resolvePrompt(ChatPayload payload)

它负责检查用户问题是否存在。

如果为空,会返回:

HTTP 400 prompt is required

这样可以防止空问题被发送给大模型。


8. ChatPayload 是什么

提供的代码中没有展示ChatPayload类,但根据调用方式,可以推测它至少包含以下字段:

public class ChatPayload { private String scene; private String agent; private String prompt; private Map<String, Object> meta; // getter 和 setter }

如果使用 Lombok,可以写成:

@Data public class ChatPayload { private String scene; private String agent; private String prompt; private Map<String, Object> meta; }

它的作用就是接收前端提交的数据。


八、FileUploadUtils:文件上传路径工具

代码位置:

package com.example.springboot.utils;

这是一个工具类。

public final class FileUploadUtils

final表示这个类不能被继承。

构造方法:

private FileUploadUtils() { }

被设置为private,表示外部不能创建这个类的对象。

因为里面的方法都是静态方法,所以不需要:

new FileUploadUtils()

可以直接调用:

FileUploadUtils.uploadRoot("uploads");

1. uploadRoot 方法

public static Path uploadRoot(String uploadDir)

它用于获取文件上传根目录。

如果没有配置目录:

uploadDir == null || uploadDir.trim().isEmpty()

就使用默认目录:

uploads

然后:

Paths.get(configured) .toAbsolutePath() .normalize();

分别表示:

  • Paths.get():将字符串转换成路径;

  • toAbsolutePath():转换成绝对路径;

  • normalize():清理路径中的多余部分。

例如:

./uploads/../uploads/images

规范化后可能变为:

项目路径/uploads/images

2. uploadSubdir 方法

public static Path uploadSubdir( String uploadDir, String subdir)

它用于获取上传目录下的子目录。

例如:

FileUploadUtils.uploadSubdir( "uploads", "images" );

结果类似:

项目目录/uploads/images

这里还需要注意一个安全问题。

如果subdir直接来自用户输入,用户可能提交:

../../other-folder

即使调用了normalize(),最终路径仍可能跳出上传根目录。

更安全的写法需要额外检查:

Path root = uploadRoot(uploadDir); Path target = root.resolve(subdir).normalize(); if (!target.startsWith(root)) { throw new IllegalArgumentException( "Invalid upload path" ); } return target;

九、总结

这组代码并不是简单地“调用一次 AI 接口”,而是搭建了一个比较清晰的 AI 网关结构。

它完成了以下职责:

AiScene 统一管理 AI 场景 AiGatewayProperties 统一管理系统配置 AiPromptTemplateService 根据业务场景生成完整提示词 AiGatewayService 统一调用聊天、流式和图片模型 AiGatewayController 向前端提供 HTTP 接口 FileUploadUtils 处理文件上传路径

作为零基础学习者,不需要一开始就理解每一行代码。

更推荐按照下面的顺序理解:

先看请求从哪里进入 ↓ 再看 Controller 调用了谁 ↓ 再看 Service 做了哪些处理 ↓ 再看提示词怎样生成 ↓ 最后理解配置、枚举和工具类

只要记住一条主线,这套代码就会容易很多:

接收请求 → 判断场景 → 检查参数 → 渲染提示词 → 调用 AI → 返回结果

这就是这套 Spring Boot AI 网关代码最核心的执行过程。

当前系统的核心目标是完成 AI 能力的工程化接入,而不是构建知识增强型 AI 系统,因此主要关注模型调用、Prompt 管理和接口封装。


学习使用,内容仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询