Spring AI与PDF生成在旅游推荐系统中的实践
1. 项目背景与核心价值去年在开发一个旅游推荐系统时我遇到了一个典型的技术痛点用户需要将复杂的行程规划导出为可打印的PDF文档同时还要整合多个AI服务的处理结果。传统做法需要分别对接不同API再手动拼接数据整个过程既繁琐又容易出错。这正是Spring AI能完美解决的场景。Spring AI作为Spring生态中面向AI应用开发的新成员本质上是一个智能化的胶水层。它通过标准化接口封装了主流AI服务如OpenAI、Azure AI等的差异化细节让开发者能用熟悉的Spring风格代码调用多种AI能力。更妙的是它原生支持上下文管理、提示词模板等AI开发中的高频需求。这个项目要实现的旅游计划PDF生成器正是Spring AI的典型应用场景。我们需要完成三个核心目标通过统一接口调用多个AI服务完成行程规划、景点推荐等任务将不同服务的返回结果自动整合为结构化数据动态生成包含图文排版的精美PDF文档2. 技术架构设计2.1 整体架构拆解系统采用分层设计从上到下分为表现层REST API Thymeleaf模板用于管理后台业务层Spring AI服务编排 业务逻辑数据层MongoDB存储行程模板 Redis缓存AI响应graph TD A[用户请求] -- B[API网关] B -- C{路由判断} C --|新建行程| D[Spring AI编排] C --|导出PDF| E[PDF生成服务] D -- F[调用OpenAI] D -- G[调用Stable Diffusion] E -- H[iText渲染]2.2 关键组件选型Spring AI 0.8.1基础AI编排框架支持ChatClient统一接口调用不同厂商API内置PromptTemplate实现动态提示词实验性支持多模态处理OpenAI GPT-4核心行程规划引擎处理自然语言需求输入生成结构化行程方案输出Markdown格式内容Stable Diffusion XL景点图像生成根据景点描述生成特色图片固定风格参数保证视觉统一iText 7PDF生成工具支持中文排版动态插入AI生成内容自动分页与目录生成3. 核心实现细节3.1 多AI服务集成Spring AI通过ChatClient抽象实现多服务切换配置示例Configuration public class AIConfig { Bean Primary public ChatClient openAIClient( Value(${spring.ai.openai.api-key}) String apiKey) { return new OpenAiChatClient(new OpenAiApi(apiKey)); } Bean Qualifier(azureClient) public ChatClient azureClient( Value(${spring.ai.azure.api-key}) String apiKey) { return new AzureOpenAiChatClient(new AzureOpenAiApi(apiKey)); } }服务调用时通过Qualifier指定实现实现故障自动转移public String generatePlan(String request, boolean fallback) { try { return openAIClient.call(buildPrompt(request)); } catch (ServiceException e) { if(fallback) return azureClient.call(buildPrompt(request)); throw e; } }3.2 智能PDF生成流程内容结构化处理# AI返回的Markdown示例 ## 三日巴黎行程 ### 第一天卢浮宫区 - 上午卢浮宫参观建议3小时 - 午餐Le Fumoir传统法餐 - 下午杜乐丽花园散步 动态模板渲染// Thymeleaf模板片段 div th:eachday : ${itinerary.days} h2 th:text${day.title}/h2 div th:eachitem : ${day.items} pb th:text${item.time}/b: span th:text${item.content}/span/p /div /divPDF样式控制PdfFont chineseFont PdfFontFactory.createFont(STSong-Light, UniGB-UCS2-H); Paragraph p new Paragraph(text) .setFont(chineseFont) .setFontSize(12) .setFixedLeading(20);4. 性能优化实践4.1 异步处理流水线Async public CompletableFutureItinerary createAsync(String request) { CompletableFutureString textFuture CompletableFuture.supplyAsync( () - chatClient.call(buildPrompt(request))); CompletableFutureListString imagesFuture textFuture.thenCompose( text - generateImagesAsync(extractKeywords(text))); return textFuture.thenCombine(imagesFuture, (text, images) - new Itinerary(text, images)); }4.2 缓存策略设计缓存层级存储内容过期时间实现方式L1原始AI响应30分钟Caffeine本地缓存L2渲染后的PDF24小时Redis集群L3用户历史行程永久MongoDB归档5. 踩坑经验与解决方案5.1 中文排版常见问题问题现象PDF中中文显示为方框自动换行位置错误解决方案明确指定中文字体PdfFont font PdfFontFactory.createFont( src/main/resources/fonts/NotoSansSC-Regular.ttf, PdfEncodings.IDENTITY_H);设置正确的编码ConverterProperties props new ConverterProperties(); props.setCharset(UTF-8); HtmlConverter.convertToPdf(html, new FileOutputStream(pdfFile), props);5.2 AI响应稳定性保障我们实现了三级降级策略首次调用首选GPT-4超时重试3秒后尝试Azure OpenAI完全降级返回预置模板行程监控指标配置示例management.metrics.export.prometheus: step: 1m descriptions: true spring.ai.openai.metrics: enabled: true percentiles: 0.5,0.9,0.996. 扩展应用场景这套架构稍作修改即可应用于教育领域智能教案生成电商领域个性化产品手册医疗领域患者康复计划关键调整点替换领域特定的Prompt模板定制PDF样式模板增加领域知识库检索项目完整代码已开源在GitHub示例仓库名spring-ai-itinerary-demo包含Docker Compose一键部署配置。实际生产部署时建议为PDF服务单独设置内存限制最少2GB启用Redis持久化防止缓存丢失对AI服务调用实施熔断机制