
最近刚把一套毕业设计项目完整整理出来——基于SpringBoot、Vue和小程序的旅游攻略平台还接入了AI能力。这套东西说实话不是那种纯玩具Demo攻略管理、线路规划、AI问答、小程序端、管理后台全都有前后端代码都整理好了项目名就叫“基于SpringBoot AI和Vue的旅游攻略小程序源码”。当时做这个项目的原因其实很现实很多人的毕设选题是旅游类的Web系统但实际场景里大家更多用手机而且现在AI那么火如果所谓的“基于AI”只是挂个名号答辩的时候一问就露馅。所以我的思路是小程序端做成真正的用户入口SpringBoot作为统一的业务后端Vue做管理后台再在AI能力上做几个真实落地场景——AI行程生成、AI攻略内容生成、AI问答助手。整套做完无论你是用来当毕设还是想自己练手学全栈都有很直接的参考价值。这篇文章我打算把整个项目的设计思路、核心实现、踩过的坑都捋一遍尤其那些网上教程里不怎么写的细节——SpringBoot版本坑、Vue打包后塞进SpringBoot的静态目录、小程序的列表加载更多、AI请求的超时与频控处理、联调时的抓包和日志排查。都是实操经验跟着走能少走很多弯路。1. 项目整体设计与技术选型思路1.1 三方端怎么分工这个项目是典型的三端架构微信小程序、SpringBoot后端、Vue管理后台。小程序是C端用户的入口主要完成攻略浏览、搜索、路线收藏、AI生成行程、AI问答这些高频操作。管理后台是给运营或管理员用的负责录入景点数据、审核攻略内容、配置AI参数、看用户反馈。SpringBoot后端则是中间枢纽统一接数据库、Redis、AI模型API把业务逻辑收敛到一处。为什么小程序而不是H5最重要的原因是旅游场景的“随手打开”属性用户到了某个城市想在微信里搜一下景点、问一句“某某区适合玩几天”小程序从入口到使用的最短路径比H5要短很多。而且小程序里有原生地图组件和相关API做景区位置展示、周边搜索比在浏览器里舒服得多。Vue这边我用了Vue 3 Vite Element Plus。Vue 3的Composition API写业务逻辑更紧凑Vite的启动速度实测比Webpack快几倍开发体验好不少。管理后台不追求太花哨重点是把数据的增删改查做清晰配合富文本编辑器录入攻略内容用ECharts展示一些统计信息。1.2 AI能力应该落在哪些点上AI不能只是一个聊天框旅游攻略小程序里最值得做的AI场景我很早就想清楚了第一是AI行程生成用户输入“想去成都4天3晚喜欢美食和人文”后端把问题交给大语言模型让它按照指定JSON结构返回一份日程安排程序解析后渲染成行程卡片和地图路点这个价值比单纯聊天高很多。第二是AI攻略润色与生成管理员在后台录入资料后可以直接调用AI生成一段适合发布的攻略文案再人工微调。第三是AI问答助手用户在浏览景点时随时提问比如“这个景区适合推婴儿车吗”后端把该景点的介绍和用户问题拼装成提示词让模型结合资料回答。这里有一个关键设计AI能力必须做一层封装。不要在前端直接调模型API也不要在多个业务代码里各自拼Prompt。我单独抽象了一个AiService接口底下根据实际需要可以切换不同实现比如OpenAI兼容协议、各家国内模型网关等。业务侧只关心“输入一个结构返回一个结构”具体模型怎么选不影响业务流程。这样以后要换模型、加模型策略都只改一个地方。1.3 技术选型里容易被忽视的版本一致性SpringBoot和Vue这两个生态版本迭代很快选型时如果版本漂移很痛苦。我这套项目比较稳的组合是SpringBoot 2.7.x JDK 8 MyBatis-Plus 3.5.x Redis MySQL 8前端是Vue 3.4 Vite 5 Element Plus。SpringBoot 3虽然新但对JDK版本和部分库的兼容要求更高学生机和大部分云服务器上还是JDK 8最保险所以我特意选了2.7.x而不是追新。Vue这边Vite插件生态跟Vue版本基本绑定尽量用官方维护的vitejs/plugin-vue少用那些个人维护的魔改插件省得升级之后跑不起来。注意SpringBoot版本太高会遇到一些兼容问题比如2.7升级到3.x后javax包变jakarta很多老教程代码直接报错。我建议做毕设或学习项目就用2.7.x配JDK 8跑在阿里云或腾讯云的轻量服务器上都很稳。2. 核心功能模块拆解与数据库设计2.1 功能模块地图整个项目的功能可以分成几个模块用户模块负责小程序的微信登录、个人中心、收藏与足迹内容模块负责景点资料的维护、图文攻略的编辑与发布线路模块负责路线的创建、AI行程的生成与保存互动模块负责问答与评论AI模块负责所有模型调用、Prompt配置、结构化结果解析。每个模块都不是孤立存在的比如AI生成行程之后会先落到一个草稿状态用户确认后再转成正式路线这样避免模型返回的脏数据直接污染主表。2.2 数据库表设计里的一些取舍数据库我用MySQL核心表大概有这些user用户表、spot景点表、guide攻略内容表、route行程路线表、route_item路线详情表、question问答表、user_favorite收藏表。这里我特别想强调两点第一景点和攻略之间我用了比较简单的设计景点表只存基础信息名称、简介、经纬度、封面图、城市编码、门票信息等攻略内容表里存富文本正文并通过spot_id关联景点。这样一条攻略可以核心关联一个景点但又不需要搞复杂的多对多映射。如果你的项目要做多景点的专题攻略可以再加一张关联表但作为毕设来说一对多已经足够说明数据关系了。第二AI生成内容要能追溯。我在路线表里加了source_type字段标记数据来源是“人工创建”还是“AI生成”还加了raw_ai_response字段存模型返回的原始JSON。好处很明显一方面答辩的时候你能现场演示“这条路线是AI生成的这是它的原始返回”另一方面出问题排查时能快速定位是模型乱答还是前端解析错了。CREATE TABLE route ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) DEFAULT NULL COMMENT 创建人, title varchar(100) NOT NULL COMMENT 行程标题, city_code varchar(10) DEFAULT NULL COMMENT 城市编码, days int(11) DEFAULT NULL COMMENT 天数, source_type varchar(10) DEFAULT manual COMMENT manual/AI, raw_ai_response text COMMENT AI原始返回JSON, status tinyint(4) DEFAULT 0 COMMENT 草稿/发布, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT行程路线表;第三所有表的id都用bigint自增时间字段用datetime字符集统一utf8mb4。utf8mb4比utf8更省心因为老版的utf8在MySQL里存不了emoji和部分生僻字用户在前端打一个表情符号就能让存储报错这个坑不想再踩第二次。2.3 AI返回结果的结构化解析大模型返回的内容是文本但计算机程序需要的是结构。所以我在设计Prompt时强制要求模型返回指定JSON格式比如{ title: 成都4日美食人文之旅, days: 4, schedule: [ {day: 1, title: 武侯祠-锦里-宽窄巷子, spots: [武侯祠, 锦里], notes: 建议下午去武侯祠晚上逛锦里}, {day: 2, title: 大熊猫基地-东郊记忆, spots: [成都大熊猫繁育研究基地], notes: 早点出发上午熊猫最活跃} ] }后端拿到这个字符串后用Jackson解析成RouteDTO再转成实体写入数据库。这里有个细节模型偶尔会多输出一些文字比如“好的这是您的行程”这类前后缀所以解析时不能直接JSON.parse(整段)要先做提取把json代码块标记或最外层的花括号部分截出来再解析。我的做法是写一个JsonExtractor工具类优先匹配代码块如果没有代码块就截取第一个{到最后一个}之间的内容解析失败时兜底重试一次换一个更严格的Prompt再调。3. 后端SpringBoot实操要点3.1 项目初始化的一个稳妥方案后端我用Spring Initializr初始化的工程依赖只选了Spring Web、MySQL Driver、LombokMyBatis-Plus和Redis自己手动加依赖。很多人这里会纠结要不要用Spring Data JPA还是MyBatis-Plus我的建议是MyBatis-Plus因为单表操作不需要写SQL分页查询有现成的Page对象对毕业设计的代码量来说体验非常友好。如果一个新工程启动就报错最常见的原因是SpringBoot自动配置尝试连接数据库失败了。所以我会在application.yml里先把数据源配置好而且一定要把ddl-auto这类配置弄清楚。MyBatis-Plus这边把Mapper接口都继承了BaseMapperT再写一个统一的MybatisPlusConfig配置分页插件PaginationInnerInterceptor这样后面的列表查询直接传Page参数就行。3.2 统一返回结构和异常处理前后端联调最烦的就是每个人返回的数据格式不一样。我在项目里定义了一个ResultT类统一code、message、data三个字段成功返回200业务失败返回自定义错误码比如40001表示参数错误40002表示数据不存在50001表示AI服务超时。配合GlobalExceptionHandler用RestControllerAdvice捕获异常统一转换成Result输出。这样小程序端和Vue端就只用处理一种结构省掉大量兼容代码。3.3 AI接口整合的完整流程AI服务是整个项目的亮点实现上我做了一个比较清晰的抽象AiProperties读取配置中心或yml里的模型网关地址、API Key、模型名称、超时时间。AiClient底层HTTP客户端用OkHttp封装对模型网关的调用支持流式和非流式两种模式。实时问答我用流式让效果“一个字一个字出来”体验更好行程生成这类长任务我用非流式配合前端loading状态。AiService上层业务门面负责构造Prompt把返回的文本解析成结构化对象并做异常兜底。我在做AI行程生成的时候发现很多模型对中文地理信息掌握不精准生成的路线里会出现“上午去东郊记忆下午去春熙路然后去酆都鬼城”这种完全不合理的方案。所以我的Prompt里明确写了“只推荐用户指定城市范围内的景点不得自行增加其他城市”并且把城市编码对应的城市名称拼进Prompt。这个限制非常有效。3.4 接口层面的频控与降级模型API不是内部接口有并发限制和费用成本所以必须做频控和降级。我的方案是拦截器加Redis用户在调用AI接口前先根据userId查Redis里的计数比如每分钟最多3次每天最多30次超了就返回提示“AI生成次数已用完”。这样不仅保护了后端钱包也防止接口被脚本刷爆。降级策略我也做了当模型接口超时或返回错误时后端先直接返回一个预先准备好的基础行程模板不经过模型提示“AI服务繁忙已为您生成基础路线”。对小程序用户来说至少还能得到结果不至于页面白屏。这个逻辑在答辩时特别加分因为评委能看到你不是只会调用一个API而是考虑了异常场景。4. 前端Vue管理后台与小程序端实现细节4.1 Vue环境配置与工程化要点Vue端开发环境其实最耗时间的是Node环境。Node版本太新或太旧都可能导致依赖编译不过Vite要求Node 18但Node 22在某些老插件下也会有兼容警告。我的建议是直接用Node 18.20 LTS版本配合pnpm安装依赖pnpm install比npm快且磁盘占用小。生成项目用npm create vuelatest选上Router、Pinia、ESLint然后手动加Element Plus。管理后台我做了几个页面登录页、数据看板、景点管理、攻略管理、路线管理、AI配置。接口请求我用axios统一封装拦截器里自动把后端的code处理了非200时直接Message提示。路由守卫里判断token没登录就跳到登录页。这些都属于常规操作但能看出项目是否规范。4.2 Vue打包后如何塞进SpringBoot这是一个非常容易被忽略但特别实用的点。如果前端和后端分开部署需要两台服务器或者一个Nginx反向代理很多学生的云服务器就一台配置Nginx又要填一堆东西。其实最简单的办法是把Vue打包后的dist目录直接复制到SpringBoot的src/main/resources/static下这样SpringBoot启动后用8080端口既能访问后端接口也能访问管理后台页面。但直接复制会有一个大坑前端路由如果用了history模式用户在管理后台刷新页面时会404因为SpringBoot处理不了前端的虚拟路径。解决办法有两个一是Vite路由用hash模式URL里带#刷新不会有问题最简单二是后端写一个WebMvcConfigurer把非接口路径都转发到index.html。我的项目里两个都做了但如果你想少折腾直接用hash模式就够了。4.3 小程序端关键页面与列表加载更多小程序端基于微信原生框架没有用uni-app。为什么不用uni-app因为这个项目只有微信端用原生框架少一层编译转换调试更直接而且原生组件和小程序地图组件配合更顺畅。小程序里最核心的页面是首页、目的地列表页、攻略详情页、AI行程生成页、个人中心页。列表加载更多是几乎每个项目都会遇到的功能很多人的代码是一页一页地onReachBottom然后setData拼数组但容易出现重复请求或者loading状态错乱。我建议做一个统一的请求工具函数async function loadList({ page, pageSize, resets }) { if (resets) { pageData.value []; page 1; loading.value true; } const res await request({ url: /list, data: { page, pageSize } }); const rows res.data.records || []; const newList page 1 ? rows : pageData.value.concat(rows); hasMore.value rows.length pageSize; pageData.value newList; page 1; loading.value false; }关键是hasMore的判定条件如果当前页的返回条数不足pageSize说明没有更多了这时候再把onReachBottom里的请求直接拦掉。配合一个全局的loading标志位防止用户快速滚动时并发请求导致的数据混乱。4.4 地图与AI页面交互路线图页面我用小程序的map组件动态把行程中的景点坐标用markers渲染出来点击marker弹窗显示景点名和当天安排。这里要注意map组件的markers是一个数组但每次setData全量更新可能卡建议只更新变化的markers或使用include-points把视野缩放到全部标记。AI行程生成页面是从输入到结果的交互我设计成三步用户填目的地、天数、偏好标签提交后进入生成中动画轮询后端任务状态生成完成后渲染行程卡片。轮询我用的是setInterval定时请求后端状态接口因为模型生成一次可能需要十几秒用一个长HTTP请求客户端容易超时轮询反而可靠。5. 常见问题、联调技巧与排错实录5.1 联调抓包的实用方法小程序端的网络请求排查比Web要麻烦一些我通常用抓包工具来看小程序发出的HTTPS请求内容。具体做法电脑和手机连同一个局域网电脑上开启抓包代理并安装根证书手机WiFi设置里配置代理指向电脑IP然后在小程序开发工具关掉“校验合法域名”选项就能看到完整的请求参数、返回数据、Cookies等信息。这里有一个注意点只抓包的代理端口是科研和调试的正常需要但涉及线上生产环境的数据安全要谨慎授权范围内调试。另外生产环境的小程序必须配置request合法域名而且要HTTPS不能直接写IP。5.2 高频报错与排查速查表我把这个项目开发过程中实际遇到的高频问题整理成了一张表方便你对照排查现象可能原因解决办法小程序请求后端一直超时域名没配HTTPS或不在合法域名列表开发工具勾选“不校验合法域名”上线前配置合法域名和HTTPS证书Vue打包后刷新页面404Vite路由用了history模式改用hash模式或后端配置转发到index.htmlAI返回的内容里出现乱码服务器JDK或MySQL字符集不是UTF-8数据库连接串加characterEncodingutf8表统一utf8mb4分页数据重复未判空即拼接或page重复自增统一列表加载工具重置时将page置为1SpringBoot启动内存溢出云服务器内存太小启动命令加-Xmx256m或换轻量配置小程序地图markers不显示经纬度字段类型不对或坐标为0打印坐标确认后端转成字符串确保经纬度有效AI偶发超时模型响应太慢HTTP连接超时太短OkHttp连接超时设60秒读超时设90秒加降级模板Jackson解析AI结果失败模型返回了多余文字增加JsonExtractor提取校验后再解析Vue后台登录后刷新丢失Token只存内存存到localStoragePinia初始化时读取连不上MySQL云服务器安全组没放行3306安全组和防火墙都放行生产环境建议用内网连接部署后端后静态文件不更新打包的dist被缓存Nginx加协商缓存SpringBoot加版本号或时间戳参数5.3 现场答辩演示的几个加分细节如果你的目的是用这个项目毕业答辩有几个细节我强烈建议注意。第一提前关上小程序开发工具的缓存清空后台日志现场演示时从一个稳定的页面开始操作别一上来就点AI生成万一模型接口临时故障就尴尬了先演示数据库里已有的攻略和路线再把AI生成作为压轴。第二准备好一份简单的演示脚本每做一个操作就讲一句对应的技术点比如“大家现在看到的是列表的加载更多它是通过触底事件配合分页参数实现的”。第三把数据库表结构、核心接口清单、项目目录结构各截一张图放PPT里答辩时不用现场翻代码往那一放就很有条理。5.4 关于扩展方向与后续维护这个项目其实是留了扩展空间的。热点里提到的SpringBoot整合Flink可以理解为如果你想做用户行为分析比如统计用户浏览了哪些景点、在某篇攻略上停留多久可以把日志打到消息队列再用Flink做实时统计。不过毕设的话不太建议往里加因为Flink本身部署和编程门槛高除非你有充足时间。更推荐的两个轻量扩展是一是把攻略内容接入全文检索增加搜索的候选词推荐二是做一个分享海报生成接口后端用Java生成带二维码的海报图用户分享到朋友圈后扫码能直接进入对应攻略这个小功能还挺招人喜欢。维护方面AI模型调用一定要在后台留一个开关如果模型因为账户欠费或政策原因不能用了管理员能一键切回“纯数据库检索”模式保证系统核心功能不瘫痪。这个开关我在后台里做了本质是AiProperties.enabled配置动态可刷新很实用。6. 源码使用与运行部署指北6.1 拿到源码后怎么快速跑起来整个工程结构一般是四个部分travel-backendSpringBoot后端、travel-adminVue管理后台、travel-miniapp微信小程序、doc或sql数据库脚本和说明文档。第一步先把sql目录下的脚本导入MySQL把application.yml里的数据库名、用户名、密码改成自己的。第二步在travel-admin目录执行pnpm install pnpm build把打包后的dist复制到后端resources/static目录。第三步启动SpringBoot试试后端接口通不通。第四步用微信开发者工具导入travel-miniapp目录把app.js里的baseUrl改成你电脑局域网IP加端口比如http://192.168.1.100:8080。这里有个顺序问题一定要先启动后端再把小程序连上去不然小程序请求会直接失败。另外小程序端不支持HTTP明文开发工具里需要勾选“不校验合法域名、web-view、TLS版本及HTTPS证书”否则请求会被拦截。6.2 部署到云服务器的简化方案云服务器部署我用的是最省事的方案安装JDK 8和MySQL把SpringBoot打成jar包用nohup java -jar后台运行。小程序端如果要真机预览需要HTTPS地址这里有两种做法一是买一台有公网的服务器用Nginx加SSL证书做HTTPS反向代理把后端接口代理出去二是在小程序管理后台配置request合法域名。如果只是毕业答辩用开发者工具的模拟器演示就足够了不需要真机调试。6.3 源码里我觉得写得最值的部分如果让我挑一个最值得读的文件我选AI模块的调用与解析封装。很多初学SpringBoot的同学调用模型API都是直接在Controller里写一堆HTTP代码参数解析和异常处理全都散落着。我把整个流程收敛成了配置读取出Key、客户端统一发送、Prompt模板管理、结构化解析、降级兜底每个文件职责很单一。比如新增一个“攻略润色”功能只需要在Prompt模板里加一条然后在业务层调AiService.generateGuideContent(spotInfo)就可以代码非常干净。除了AI模块路线模块也值得一看。路线和路线明细是用事务控制的一个行程包含多个日期、多个景点和备注我用了Transactional保证要么全部写入成功要么全部回滚不会出现“路线创建了但里面的景点是空的”这种脏数据。写在最后的小体会整套项目从设计到编码其实每一步都踩了不少教科书没写过的坑。SpringBoot版本、Vite打包、小程序地图、AI解析这些细节单独看都不难但组合在一起的时候如果前期没有一个清晰的分工和技术选型很容易陷入“每步都在填坑”的循环。我个人最大的体会是做全栈项目前期把数据结构和接口边界定义清楚比写代码多花点时间都是值得的。数据库表字段想清楚了前后端联调就不会天天改接口AI返回的格式约定好了解析代码一次写完不用反复调技术版本锁定在稳定组合就不会被兼容性问题反复折腾。这套旅游攻略小程序的项目源码虽然定位是毕业设计但如果你认真把它读透、跑通其实等于把一个完整的商业闭环项目拆开看了一遍。后续你想加什么功能比如预订酒店、拼团出行、语音导览这个骨架都能接得住。