坦白说刚接到这个项目需求的时候我对“动漫社区AI”这个组合的期待值是偏低的。动漫圈子是一个重度情感连接、轻工具理性的用户群体大家既希望平台能懂自己喜欢的番剧和角色又对千篇一律的机器推荐非常敏感。但需求摆在那里一个动漫交流社区想做成带AI味的互动平台要智能推荐、AI助手、评论情感分析还要有可视化数据后台技术栈指定用Spring Boot Vue.js这种前后端分离方案并且强调SpringbootAI——也就是把Spring AI整合进现有Spring Boot项目里来承接大模型能力。整个项目从需求拆解到上线前后花了大概两个多月。回头复盘真正难的其实不是某一个AI接口怎么调而是怎么把AI能力自然地嵌进一个社区产品里让推荐不突兀、助手不说教、情感分析不误伤。这篇文章把我这次开发过程中的设计思路、核心实现、踩坑记录和排障心得都整理出来适合正在做类似AI社区、内容推荐平台或者毕业设计选型的朋友参考尤其是对“Spring Boot Spring AI Vue.js”这套组合感兴趣的人。1. 整体功能规划与技术选型思路1.1 需求拆解先把“AI味”落到具体功能上项目最初的需求描述非常模糊核心就一句话“做一个有AI功能的动漫社区”。这种需求最大的坑在于“AI功能”是一个筐什么都能往里装但如果什么都做结果往往是四不像。所以第一步我做的是把“AI功能”拆成用户可感知、技术可实现、运营可验证的五个模块。最终整理出来的功能清单是这样的模块核心功能用户价值技术要点智能推荐首页信息流、猜你喜欢、相关番剧推荐让用户更快找到感兴趣的内容多路召回加权排序LLM推荐解释AI智能助手动漫百科问答、剧情咨询、推荐理由解释24小时在线的“动漫百事通”RAG知识库 Prompt模板 流式输出评论情感分析对用户评论做情感判定识别负面情绪和争议话题帮助运营及时发现舆情风险大模型批量分类 情感标签持久化可视化数据分析用户增长、内容热度、评论情感分布的可视化看板让运营看懂平台数据ECharts WebSocket实时推送数据采集爬虫补充番剧信息、角色资料、评分数据解决冷启动期内容不足问题HttpClient Jsoup 定时任务拆完之后整个项目的颗粒度就清楚很多了。每个模块都有独立的数据流、独立的实现路径也有明确的验收标准。比如情感分析模块验收标准就是“运营后台能按负面情感排序查看评论”这比“分析用户情绪”这种描述要落地一百倍。1.2 技术选型为什么是Spring Boot Spring AI Vue.js这套组合技术选型其实没有太多悬念因为需求里已经圈定了大方向Spring Boot做后端、Vue.js做前端。但真正让我下定决心用这套组合的原因是Spring AI这个官方抽象层。在Spring AI出现之前Java后端接大模型通常要自己封装HTTP调用、处理流式响应、维护Prompt模板代码一旦多了就很零散。Spring AI做的事情简单说就是把“跟大模型对话”这件事抽象成了ChatClient把“让模型按格式输出”抽象成了OutputParser把“把文本变成向量”抽象成了EmbeddingClient并统一支持多种模型来源。这意味着你写的业务代码跟具体模型厂商解耦后续从A模型切到B模型改一个配置就行。数据库选型上我没有上太重的组件。核心业务数据用MySQL热点数据和高频查询用Redis搜索和向量检索用Elasticsearch。有人可能会问为什么向量检索不单独用Milvus或者Chroma我的考虑是项目体量有限ES本身的向量检索能力已经够用而且它还能同时承担关键词搜索的职责少维护一个组件就是省一类运维事故。前端技术栈选了Vue 3 Vite Element Plus Pinia ECharts没有用更重的antd或umi体系。原因很简单这个项目的前端主要工作是列表页、详情页、聊天气泡和图表展示Vue 3的组合式API写起来效率很高Element Plus在后台管理界面上的成熟度也足够ECharts在可视化这块几乎没有替代成本。1.3 工程结构设计一个能让前后端并行开发的目录划分前后端分离开发最容易出问题的不是技术本身而是接口语义不统一。所以项目一开始我就把工程分成了三个部分后端服务、前端应用、爬虫模块。爬虫模块严格来说也属于后端但考虑到它的数据流相对独立我把它单独拆了一个目录。后端按业务域划分包结构没有按技术层划分。这一点我特别想强调一下很多团队喜欢按controller、service、dao这样分层建包小项目还行一旦业务模块多起来找代码就变成捉迷藏。我采用的是按模块划分recommend、assistant、sentiment、stats、crawler每个模块内部再自己分层。这样做的直接好处是改推荐逻辑的时候不需要去别的包翻文件。前端对应地按页面和组件组织views下放页面api下按后端模块放请求封装components放通用组件。另外专门建了一个utils目录放格式化、鉴权、WebSocket连接之类的工具函数。这种“后端按业务域、前端按页面域”的对应关系让前后端联调时的沟通成本明显降低——说“推荐模块的接口”两边都知道去哪儿找。2. 三个AI核心模块的设计思路与实现要点2.1 智能推荐多路召回加权排序让AI给推荐理由推荐系统听起来高大上但在这个项目里我没有一上来就上机器学习模型。原因很现实社区初期用户量就几千行为数据稀疏得很你就算把TensorFlow搬过来它也学不到啥东西。所以我的方案是业界经典的“多路召回 加权排序”再加上一个AI解释层把推荐结果包装得“有人味”。召回阶段我设计了三条通道一是基于用户最近浏览和点赞过的番剧按标签类型、题材、年代做相似内容召回二是按全局热门榜召回保证内容多样性三是按用户主动订阅的“追番”列表召回。三条通道各取Top N然后合并去重。排序阶段用加权公式。比如最终得分 0.4 × 用户标签匹配度 0.3 × 内容热度分 0.2 × 协同过滤相似度 0.1 × 新鲜度。这里没有用太复杂的算法但效果不错——因为对“冷启动期”的内容平台来说简单加权排序的可解释性远胜于黑盒模型运营也更容易接受和干预。这个推荐模块最出彩的地方其实是我用Spring AI加了一个“推荐理由”功能。每次返回推荐列表时后端把推荐出的番剧标题和用户的兴趣标签发给大模型让大模型为每条推荐生成一句话解释比如“因为你喜欢燃系战斗番所以推荐这部近期评分上升的机甲新作”。这样一个看似微小的设计直接让用户对推荐的信任度高了一个量级。实现上也不复杂就是调一次ChatClient加上一个稳定的Prompt模板。2.2 AI智能助手RAG知识库比“大模型什么都会”靠谱得多AI助手这块如果直接拿通用大模型来做动漫问答效果是达不到预期的。一是通用模型对冷门番剧、小众角色的了解往往停留在几年前甚至直接编造二是社区用户问的问题很多时候是高度私域的比如“这部番第几集开始好看”这些问题是模型训练数据里不可能有的。所以我采用的是RAG检索增强生成架构。提前把爬取的番剧资料、角色介绍、用户FAQ以及社区精华帖分块、向量化之后存入Elasticsearch。用户发问时先把问题向量化在知识库中检索出最相关的3到5个片段再把这些片段作为上下文连同用户问题一起发给大模型让它“基于给定的资料去回答”。Spring AI在这个流程里帮了大忙。EmbeddingClient负责把文本转成向量向量检索我直接用ES的knn查询然后把检索结果拼成Prompt上下文。Prompt模板我用的是Spring AI的PromptTemplate模板里明确要求模型“只能根据提供的资料回答资料中没有的信息要明确说不知道”这个方法直接抑制了大模型的幻觉问题实测下来回答准确率比裸调用提升非常明显。前端交互上我做的是类聊天界面用SseEmitter实现了流式输出。用户提问后回答是一个字一个字“蹦”出来的而不是转圈等半天再一次性吐出一大段。体验上的差别真的很大特别是对动漫社区这种重氛围的用户群流式输出的“对话感”比什么都重要。2.3 评论情感分析用大模型替代传统分类模型的真实原因评论情感分析这个模块最初我打算用传统的方式比如训练一个简单的文本分类模型。后来发现这条路走不通。动漫社区的评论语言实在太“花”了大量黑话、玩梗、反讽、缩写还有各种表情符号。“这番真神”和“这番真神了”可能一个是夸一个是贬传统的词向量加分类器根本扛不住这种语境变化。所以我把方案改成了大模型批量情感标注。在Spring Boot里写一个定时任务定时拉取新增评论调用大模型对评论进行分类正面、负面、中性并提取一个简短的情感摘要。这里有个关键操作为了让模型输出稳定的结构化数据我通过Prompt要求模型只输出JSON格式并且严格限定情感标签只能从给定枚举里选。相情感分析结果用上了很多地方。运营后台可以按“负面情感优先”来查看评论第一时间发现口碑崩坏的苗头首页的番剧评分卡片上会显示“近期口碑好评居多”这样的动态标签甚至内容运营还可以根据负面评论的高频话题知道用户究竟在骂什么。这个模块可以说是投入产出比最高的一个功能。2.4 可视化数据分析从数据到决策还差一个“看得见”可视化这块并没有太复杂的技术但我认为它是整个平台运营闭环里最重要的一环。没有看板数据就是躺在数据库里的死数字有了看板数据才会被运营真正用起来。我用ECharts做的可视化看板主要分成三个区块用户分析区新增用户、活跃用户、留存、内容分析区番剧热度排行、评论量趋势、推荐点击率、情感分析区评论情感分布饼图、负面话题词云。其中情感分析区是和评论情感分析模块联动的数据实时更新。实时性我用WebSocket来做。后端在评论情感分析任务跑完后通过WebSocket向前端推送一个“数据已更新”的事件前端收到事件后再拉取最新统计数据并刷新图表。没有用轮询因为轮询既不实时又白白浪费服务器资源。关于大屏布局我用的是flex加CSS Grid做的自适应排版分三栏展示运营同事在普通办公屏上也能看得舒服。3. 爬虫与数据管线建设从原始网页到知识库的全链路3.1 合规采集只拿公开数据频率控制是底线爬虫模块在这个项目里承担的角色是解决“内容冷启动”问题。社区刚上线库里空空如也用户进来发现啥都没有那肯定是留不住的。我用爬虫去公开的动漫资料站采集了番剧基本信息、角色介绍、评分数据为知识库和推荐系统提供初始数据源。技术上其实不复杂。我用的是Java自带的HttpClient发请求配合Jsoup解析HTML。这里有个细节一定不要裸奔爬取要设置一个常规的User-Agent并且控制请求频率每抓一个页面必须sleep几秒。一方面是给目标网站减负另一方面也是避免被反爬策略封IP。代码大致是这样一个思路HttpClient client HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) .build(); String html client.send( HttpRequest.newBuilder(URI.create(pageUrl)) .header(User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64)) .build(), HttpResponse.BodyHandlers.ofString() ).body(); Document doc Jsoup.parse(html); Elements items doc.select(对应的列表选择器);关于采集的合规性我反反复复和团队强调了几件事第一只采集公开页面上的公开信息绝不触碰任何需要登录后才能看到的用户数据第二严格遵守目标网站的robots规则如果网站明确禁止抓取就立刻换源第三不对目标网站造成任何访问压力所有请求都限速。做爬虫第一课学的不是技术而是边界在哪。3.2 清洗与去重网上爬的数据不洗真的没法用从网页上直接扒下来的数据是非常脏的基本不能直接入库。比如番剧简介里可能混着大量换行符和广告文本评分字段可能存的是“8.1分12345人评价”这种混着文字的长字符串还有大量内容完全重复的页面。清洗这块我做了几件事字段格式化、空值兜底、内容去重。字段格式化就是把评分、播放量、追番数这些数值型字段从混着的字符串里用正则提取出来转成统一的数值类型空值兜底是给那些缺失简介、缺失封面的记录生成默认值内容去重是用标题的MD5哈希做唯一键Crc32冲突率也够用重复数据直接丢弃。增量更新策略也值得提一下。我建了一个crawl_record表每条已抓取的页面URL对应一条记录。每次定时任务启动前先查这个表里的记录只抓取本地还没有的URL或者超过一周未更新的URL。这样一来爬虫不会每次都全量跑一遍效率高了很多也给目标网站省了不少流量。3.3 向量化与知识库入库让AI助手“有话可说”清洗完的番剧和角色资料一部分直接存进MySQL用于业务展示另一部分要走向量化管线最终进入AI助手的情感感知库。这个环节是RAG效果好不好的关键我的经验是切分策略比模型选择更影响结果。切分的时候我把每部番剧的资料按章节切块比如剧情简介、角色介绍、动画制作、评价口碑每个块控制在300到500字左右。太长了检索召回时语义不聚焦问题问得很细就容易把不相关的上下文也拼进去太短了上下文碎片化大模型能参考的信息量有限。切分完后用EmbeddingClient对每个块做向量化连同原文和元数据番剧ID、章节名一起存入ES的向量索引。这里我特别注意了一个问题ES的向量检索虽然好用但索引设计是有讲究的。建索引时必须显式声明向量字段的维度和相似度算法如果忘了声明默认设置跑出来的检索结果会非常离谱。我当时就在这个坑里卡了一天调出来的相似度分数几乎全是乱的后来排查到是索引映射里少了dense_vector字段定义。4. 前后端分离开发中的联调与避坑4.1 接口规范先行统一返回体救了整个团队前后端分离项目最怕的是接口各说各话。前端说“我只要这个字段”后端说“这个是内部结构”两边各执一词最后联调阶段就是互相折磨。所以我在项目一开始就定死了接口规范前后端都按同一个约定来。统一返回体我设计的是R.ok(data)和R.fail(code,msg)所有接口都是这个壳BFF层都过这个壳再出去。分页请求统一用PageResultT里面固定是total、records、current、size这四个字段。时间格式统一为yyyy-MM-dd HH:mm:ss的字符串因为前端用JavaScript处理时间戳容易出时区问题。枚举值也做了统一比如情感标签在接口层固定返回POSITIVE、NEGATIVE、NEUTRAL这几种字符串前后端各保留一份常量表。接口文档我用的OpenAPI后端启动后直接访问/swagger-ui.html能看到所有接口定义。前端开发的时候不需要来问后端“这个字段啥意思”自己看文档就行。这个看似老生常谈的规范实际上让我们的联调周期缩短了至少三分之一。4.2 AI接口的异步与流式处理不能让用户等“思考完毕”AI接口和普通CRUD接口最大的不同在于——它的响应时间是不可预测的。普通查询接口几十毫秒就返回了但大模型接口慢的时候可能要十几秒甚至更久。如果我用同步调用的方式用户点击提问后前端转圈半天体验基本就废了。所以我在实现AI相关接口时定了两条原则耗时操作永不阻塞请求线程、长文本输出永远走流式。具体实现上对于“批量情感分析”这种离线任务直接丢给线程池异步执行任务跑完再通过WebSocket通知前端刷新数据。对于“AI助手问答”这种在线交互接口返回类型用SseEmitter后端边生成边推送GetMapping(value /assistant/chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter chat(RequestParam String question) { SseEmitter emitter new SseEmitter(60_000L); assistantExecutor.execute(() - { try { String answer chatClient.prompt().user(question).call().content(); emitter.send(SseEmitter.event().data(answer)); emitter.complete(); } catch (Exception e) { emitter.completeWithError(e); } }); return emitter; }这块踩过最大的坑是网关超时。最开始后端API通过网关转发网关层默认超时时间是60秒但大模型冷启动加上检索时长很容易超过这个阈值结果用户刚看到答案的开头连接就被网关切断了。排查好半天才发现问题不在应用本身而在于网关超时配置。4.3 联调阶段最常遇见的三个问题及排查思路第一个是跨域问题。前端Vue项目在开发模式下跑在5173端口后端跑在8080端口两者不同源直接请求就会被浏览器拦截。我的解决方案是后端配置CorsFilter允许指定来源的跨域请求同时允许携带Authorization请求头。生产环境直接用Nginx反向代理把/api路径转发到后端服务前端始终访问同源路径跨域问题就彻底消失了。第二个是鉴权方案。用户登录后签发JWT令牌前端存储在localStorage里每次请求通过axios拦截器把令牌放进Authorization头。后端写了一个过滤器统一校验校验通过就把用户信息放进ThreadLocal业务代码里直接取当前用户不用每个接口自己解析令牌。这里有个细节容易忽略就是上传头像这类请求是multipart/form-data格式的axios拦截器要判断Content-Type不能无脑加JSON头。第三个是AI接口的流式解析。前端用EventSource直接接SSE流是最省事的但EventSource有个限制它只能通过GET请求建立连接没办法自定义请求头。如果聊天接口需要带JWT令牌直接原生使用EventSource就不行了。我的解法是用fetch配合ReadableStream去读流把克制化解析的代码封装成一个独立工具函数所有AI聊天页面都复用这一个方法。5. 部署上线与性能优化实践5.1 前后端分离的部署架构上线部署这块我采用的是最简单也最稳定的方案前端打包成静态文件交给Nginx托管后端打成jar包独立运行。Nginx不仅承担静态文件服务还负责把/api路径的请求反向代理到后端服务地址。构建过程用Maven和npm配合搞定。后端执行mvn clean package打出可执行jar包前端执行npm run build生成dist目录。然后写一个部署脚本实现“拉代码、构建、停服务、替换文件、启动”的全流程自动化。这个脚本在项目迭代期真的能救命每次发布只需要执行一行命令不用再手动复制文件、杀进程、重启省下的时间都够多写两个功能模块。数据库初始化我用的Flyway管理版本脚本。每个版本的Schema变更都记录在db/migration目录下应用启动时自动检查并执行未应用的脚本。这种方式在多人协作开发时特别重要避免出现“我本地能跑你本地也能跑但部署环境起不来”的窘境。5.2 性能优化缓存优先、批量优于循环、索引要建对性能优化我最先做的是Redis缓存。热点数据比如首页推荐列表、番剧详情页、公告信息在Redis里缓存一份设置合理的过期时间。推荐结果我甚至缓存了30分钟因为用户刷首页时推荐内容如果频繁变化反而容易让人困惑。AI接口的批量调用也做了优化。比如情感分析任务最初实现是逐条循环调用大模型跑几百条评论要等很久。后来改成了一次性把20条评论打包在一条Prompt里让模型输出JSON数组虽然在Prompt设计上需要多花心思但整体耗时直接降到了原来的五分之一。表格结构化了Prompt之后模型输出稳定性也好了很多。数据库层面我给三个最频繁查询的表都建了联合索引评论表的(work_id, status)、用户行为表的(user_id, behavior_type, create_time)、番剧表的(status, publish_date)。没有索引之前运营后台筛选负面评论要扫全表页面转圈半天加完索引之后查询时间从秒级降到了毫秒级这个性价比高到离谱。5.3 成本控制大模型调用不是免费的每一分token都要花在刀刃上最后说一个很多人会忽视的点大模型接口的调用成本。情感分析任务每天要处理几千条评论如果全部用大模型做算下来一个月也是一笔不小的开销。我的策略是分级处理先用成本极低的关键词规则把明显的中性评论过滤掉比如无意义的灌水评论、全是表情包的评论这些根本不需要大模型介入。只有关键词规则判定为“可能带情绪”的评论才进入大模型分析环节。这个策略实施下来直接省掉了大约40%的大模型调用量而且对最终的准确率几乎没有负面影响。AI助手场景也是一样对于高频的、简单的百科类问题我把标准答案在Redis里缓存起来命中缓存就直接返回不重复调用大模型。关于模型选择我在性价比和效果之间做了平衡。对话助手用的是效果更强的模型因为用户能明显感知到对话质量情感分析这种内部任务用轻量模型就够结构化输出同样稳定但价格只有前者的一小部分。这个“不同场景配不同模型”的思路是大模型应用上线后控制成本的关键。这个项目做完之后我最大的感悟是在动漫社区这类重情感连接的产品里AI最合适的身份不是一个“无所不知的专家”而是一个懂氛围的伙伴。推荐算法不需要算出什么精妙的最优解用户更在意的是推荐理由是否说到了自己心里AI助手不需要掌握所有知识它只要能诚实地告诉你“这个我没查到”用户反而更信任它情感分析更是如此误伤一句玩梗的话比漏判一句真正的恶评更容易让用户寒心。如果让我给后来者一个具体的建议那就是架构设计和模型选型都别追求“猛”先把产品体验做“柔”。Spring AI这套体系足够平滑不会成为项目的瓶颈真正决定项目成败的永远是那些流程细节是Prompt里的一句话是缓存命中的一次优化是接口超时的那几秒。