零基础读懂 Spring Boot AI 网关:从场景定义到调用大模型的完整流程
前言随着大语言模型技术的发展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 能力接入与业务调用流程设计并未涉及 RAGRetrieval-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 )它包含三个部分。第一个参数templateKeyancient-text-translation它是提示词模板的名称。系统默认会寻找classpath:/ai/prompts/ancient-text-translation.st也就是在项目中定义的提示词模板src/main/resources/ai/prompts/ancient-text-translation.st第二个参数streamingPreferred这里的 true or false 表示该场景是否更推荐使用流式输出。流式输出类似聊天机器人逐字显示内容。普通返回等待一段时间后一次性看到完整答案。流式返回模型生成一点前端显示一点。true 表示更推荐流式输出。需要注意目前代码只是保存了这个配置并没有根据它自动决定调用普通接口还是流式接口。第三个参数aliasestranslator这是场景别名。因此下面几种写法都可能识别为古文翻译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-scene4. normalize 方法的作用private static String normalize(String value) { return value.trim() .replace(-, _) .toUpperCase(Locale.ROOT); }这个方法用于统一字符串格式。例如ancient-text-translation经过处理后变成ANCIENT_TEXT_TRANSLATION处理步骤是trim()删除前后空格replace(-, _)将横线替换为下划线toUpperCase()转换成大写。这样可以减少用户输入格式不同造成的问题。四、AiGatewayProperties读取配置文件代码位置package com.example.springboot.config;核心注解Data Component ConfigurationProperties(prefix ai.gateway) public class AiGatewayProperties { }1. ComponentComponent表示这个类由 Spring 管理。其他类需要使用它时Spring 可以自动注入。2. ConfigurationPropertiesConfigurationProperties(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. DataData这是 Lombok 提供的注解。它会自动生成get方法set方法toString方法equals方法hashCode方法。例如代码中虽然没有手动写public ImageProperties getImage() { return image; }但因为有DataLombok 会帮助生成。4. scenes 配置private MapString, 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 还是 Base64stepDelayMillis步骤间隔时间。其中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.st2. render 方法public String render( AiScene scene, String prompt, MapString, Object meta)它接收三个参数scene当前 AI 场景prompt用户输入meta其他附加信息。例如{ scene: diet-therapy-plan, prompt: 请制定一份食疗方案, meta: { age: 30, constitution: 湿热体质 } }其中prompt 请制定一份食疗方案 age 30 constitution 湿热体质3. 模板变量代码先创建一个变量集合MapString, Object variables new HashMap();然后放入用户问题variables.put(prompt, prompt null ? : prompt);如果存在metavariables.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第二种是在配置文件中自定义模板路径。六、AiGatewayServiceAI 功能的核心业务层代码位置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, ObjectProviderImageModel imageModelProvider, AiGatewayProperties properties, AiPromptTemplateService promptTemplateService)Spring 会自动注入这些依赖。其中this.chatClient chatClientBuilder.build();表示通过 ChatClient.Builder 创建 ChatClient 对象后续用于调用大语言模型。this.imageModel imageModelProvider.getIfAvailable();表示尝试获取图片生成模型。由于 ImageModel 并不是所有环境都必须配置因此采用可选注入方式。如果系统没有配置图片模型则 imageModel 为 null调用图片生成接口时会进行检查。即使项目暂时没有图片生成功能文字聊天功能仍然可以正常启动。3. 普通聊天 chatpublic String chat( AiScene scene, String prompt, MapString, Object meta)核心代码return chatClient.prompt() .user(buildPrompt(scene, prompt, meta)) .call() .content();可以分成四步理解。第一步创建一次 AI 请求chatClient.prompt()第二步设置用户提示词.user(buildPrompt(scene, prompt, meta))buildPrompt会完成模板渲染。第三步调用模型.call()这是一种普通的同步调用。程序会等待模型生成完毕。第四步获取文字内容.content()最终返回模型生成的文字。4. 流式聊天 streampublic FluxString stream( AiScene scene, String prompt, MapString, Object meta)核心代码return chatClient.prompt() .user(buildPrompt(scene, prompt, meta)) .stream() .content() .concatWithValues([DONE]);它与普通聊天的主要区别是普通聊天使用.call()流式聊天使用.stream()流式返回的类型是FluxStringFlux可以理解成一个不断产生数据的管道。例如模型正在生成春 季 适 合 食 用 ……后端可以一段一段发送给前端而不需要等待完整答案生成。最后添加.concatWithValues([DONE])前端看到这个标记就知道本次回答结束了。5. 图片生成 generateImagespublic ListString generateImages( AiScene scene, String prompt, MapString, 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)如果图片模型返回 URLif (image.getUrl() ! null !image.getUrl().isBlank()) { return image.getUrl(); }就直接返回 URL。如果模型返回 Base64return data:image/png;base64, image.getB64Json();就给 Base64 内容加上浏览器可以识别的前缀。前端可以直接这样显示img srcdata:image/png;base64,……7. buildPrompt 方法private String buildPrompt( AiScene scene, String prompt, MapString, Object meta)该方法负责将用户输入转换为完整 Prompt。return promptTemplateService.render( scene, cleanPrompt, sanitizeMeta(meta) );然后将处理后的内容放入提示词模板。8. 语音生成功能public byte[] synthesizeSpeech( String text, MapString, 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/structuredRequestBody表示接收前端提交的 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代码调用ListString 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 MapString, Object meta; // getter 和 setter }如果使用 Lombok可以写成Data public class ChatPayload { private String scene; private String agent; private String prompt; private MapString, Object meta; }它的作用就是接收前端提交的数据。八、FileUploadUtils文件上传路径工具代码位置package com.example.springboot.utils;这是一个工具类。public final class FileUploadUtilsfinal表示这个类不能被继承。构造方法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/images2. 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 管理和接口封装。学习使用内容仅供参考

相关新闻