1. 毕业设计选题为什么我推荐“旅游分享商品交易”这个组合带毕业设计这些年我一直觉得选题这东西最考验人。太简单的没含金量答辩时评委两眼一翻太难的学生做不出来最后三天两头找我哭。而“基于SpringBoot的旅游信息交流网站”这种题目恰好踩在了一个很舒服的点上功能链路完整技术栈主流并且能自然地把“内容分享”和“商品交易”两条业务线揉在一起做出一个像模像样的全域旅游互动门户。先说说这类题目为什么值得选。很多学生一听到“旅游网站”第一反应就是携程、马蜂窝那种巨无霸然后心里发怵。但毕业设计根本不要求你做那种体量它考察的是你对一个完整业务系统的理解能力和工程落地能力。就拿这个“行走圈”平台来说表面上是发游记、看攻略实际拆开来看它把用户系统、内容发布、图片存储、评论互动、商品管理、订单流程、后台管理这一条完整的业务链路全串起来了。做完这个项目你相当于把后端开发里最常碰到的几个场景全部走了一遍面试的时候也能拿出一个完整作品来聊。另外说一下技术路线。SpringBootVue这套组合在近几年的毕业设计里几乎是统治级的选项不是因为它花哨而是因为它“稳”。SpringBoot把以前Spring那套繁琐的XML配置全干掉用自动装配和约定优于配置的方式让你快速起步Vue则凭借组件化开发、响应式数据绑定和成熟生态让前端开发效率和维护性都上了一个台阶。这两个东西组合在一起就是当前中小型Web应用最主流的开发范式之一。你学它、用它、拿它做毕设完全没有脱离实际生产环境答辩时轻轻松松就能把技术亮点讲清楚。那具体到“行走圈”这个项目又该从哪里开始拆我建议第一步别急着写代码先把需求边界划清楚。作为一个旅游分享与商品交易平台核心用户是谁是准备出门旅行、想找攻略的人和已经旅行回来、想分享经验顺便买点地方特产的人。所以系统必须有两条主业务线一条是内容的“发布-浏览-互动”另一条是商品的“浏览-下单-支付”再往上叠一个管理员的后台负责审核内容和处理订单。三条线各有各的难点合在一起就是一个完整度很高的毕业设计。2. 系统设计与技术选型从整体架构到数据库表结构2.1 前后端分离架构怎么搭才不虚先说整体架构。我用的是前后端完全分离的模式前端Vue跑在Node环境的开发服务器上通过Axios请求后端的RESTful接口后端SpringBoot打包成独立服务负责业务逻辑、数据持久化和文件存储数据层用MySQL存储结构化数据Redis做缓存热点内容文件这块我接了Minio来做对象存储。有的学生会问为什么不用FastDFS或者直接把图片存本地FastDFS太重了配置麻烦而且现在正逐步被Minio这类轻量级对象存储取代直接存本地在开发阶段看着省事但项目一部署、一迁移就会出一堆路径问题。所以Minio是一个很好的折中它在单机模式下部署非常轻量接口又兼容S3协议说白了就是以后上了云也能平滑迁移。前端这边如果是从零开始我推荐Vue 3搭配Vite而不是Vue 2加Webpack。原因很简单Vite冷启动速度是真的快几秒钟起来一个开发环境Vue 3的Composition API写起来逻辑更聚合一个功能的响应式数据、计算属性、方法都可以放在一起后期维护比Vue 2的Options API好太多。项目结构上我会把components、views、router、store、api这五块单独分出来保证每个页面组件只关注渲染业务请求全部收敛到api目录下的模块文件里。后端部分SpringBoot版本我建议直接上2.7.x如果你是新学期的新项目也可以选3.x。但这里有个坑3.x是基于Jakarta命名空间某些老教程里的javax包导进来会直接报错如果学生基础一般我建议稳妥点用2.7.x。依赖管理用MavenMyBatis-Plus做ORMShiro或者Spring Security做权限都可以我个人更推荐Spring Security配合JWT因为它的过滤器链逻辑更清晰答辩时能讲出东西。2.2 功能模块划分别让“互动门户”变成“静态展示”很多学生把网站做成了展示墙页面好看但没什么东西能点能用。这个题目既然叫“互动门户”那就必须有真互动的功能。我划分了这几个核心模块用户模块注册、登录、个人信息维护、忘记密码、头像上传。这里要注意密码不能明文存储用BCrypt加密。旅游分享模块游记发布富文本编辑、图片上传、游记列表分页展示、关键词搜索、按热度/时间排序。互动模块点赞、评论、收藏。这三个是轻量级操作接口设计上要能承受住并发点击所以考虑用Redis做计数缓存。商品模块旅游周边商品展示、商品分类、商品详情、购物车、生成订单。订单模块下单、订单列表、订单状态变更待支付/已支付/已取消/已完成因为是毕设支付功能接到模拟支付即可。后台管理内容审核、商品上下架、订单管理、用户禁用。看上去模块不少但每个模块的粒度都不大适合一步一步实现。我见过不少人想把所有功能一次性堆上去结果数据库表搞得乱七八糟最后哪个都没做好。正确的做法是先做主线注册登录→发布游记→浏览列表→评论点赞跑通之后再叠加商品和订单最后补后台。2.3 数据库设计的几个关键点数据库表的设计直接决定你这项目能走多远。我给一个我常用的表结构清单照着这个思路去扩展基本不会错user表id、username、password、nickname、avatar、phone、email、role、status、create_timetravel_note表id、user_id、title、content、cover_image、location、views、likes、comments、status、create_timecomment表id、note_id、user_id、content、create_timelike_record表id、note_id、user_id、create_timefavorite表id、note_id、user_id、create_timeproduct表id、name、description、price、stock、cover_image、category、status、create_timecart_item表id、user_id、product_id、quantity、create_timeorder表id、order_no、user_id、total_amount、status、create_timeorder_item表id、order_id、product_id、product_name、price、quantity有两个表对比表设计的重点你可能想不到。第一个like_record必须做成单独的表。如果只给travel_note加一个like_count字段用户重复点赞没法控制点一下加一下点两下又加一下后面想取消点赞还得写一堆判断逻辑非常难受。做成独立表之后用(user_id, note_id)做唯一索引就能保证一个用户只能点一次赞。第二个comment表必须冗余一个note_id和user_id因为前端展示评论时大概率是要连表查用户昵称和头像的不要图省事只存一个user_id就完事那样你会在查询时被迫用一堆子查询或者连表性能反而难看。索引方面有几个务必去建travel_note表的user_id和create_time、comment表的note_id、order表的user_id。这些是高频查询字段不建索引的话数据量稍微一上去接口响应时间就会明显变慢。见过太多学生建好表不建索引然后答辩演示时说“系统卡住了”那场面真的尴尬。3. 核心实现从SpringBoot后端到Vue前端的实操拆解3.1 SpringBoot项目初始化和依赖清单后端项目我一般用Spring Initializr生成你打开start.spring.io选好Maven、Java版本然后勾选这几个依赖Spring Web、Spring Validation、MyBatis-Plus这个不在Initializr里得自己在pom.xml手动加、MySQL Driver、Lombok、Redis。这里有个小技巧不用一开始就把所有依赖都勾全比如文件上传用的Minio依赖、JWT用的jjwt后面用到再加不然新手根本分不清哪些依赖是干嘛的。pom.xml里有个很关键的配置是Maven的编译插件。SpringBoot官方默认的spring-boot-maven-plugin会把项目打成可直接运行的Jar包但有的学生照着网上的教程把版本号写错或者把插件放在不该放的位置导致打出来的包一直起不来。我建议统一用这个build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build这个插件能帮你把依赖和项目本身一起打包成可执行Jar部署的时候一句话就能跑起来。项目结构这块我按controller/service/mapper/entity/common四层走。controller只做参数接收和响应封装service里写业务逻辑mapper是MyBatis-Plus的接口。看起来多了一层但后期加功能、改需求每一块都在它该在的位置不会出现一个类两三千行的情况。3.2 用户登录与JWT权限控制的实现思路用户认证是这个项目绕不开的第一关。我这边用的是JWT令牌的方式用户提交用户名密码后端校验通过后生成一个包含用户ID和角色的Token返回给前端前端每次请求都把Token放在请求头里后端通过拦截器或过滤器统一校验。和Session相比JWT天然适合前后端分离的场景因为后端不需要维护会话状态扩展起来也容易。具体的实现要点是这样的// JWT生成部分 String token Jwts.builder() .setSubject(user.getId().toString()) .claim(username, user.getUsername()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();这里必须提醒一点JWT密钥一定不能写死在代码里。虽然这是毕业设计但把密钥放到application.yml配置文件里答辩的时候老师一问你也好交代。另外一个容易被忽略的地方是JWT过期时间的处理2小时太短用户看几篇游记就过期了体验很差72小时太长安全性下降。我一般设24小时前端在Axios拦截器里检测到401状态码就自动跳转到登录页。登录接口的密码校验一定要用加密后的密文比对千万不能把明文密码存到数据库里。用Spring Security自带的BCryptPasswordEncoder注册的时候加密登录的时候比对几行代码的事却是一个很加分的安全点。3.3 文章发布与Minio图片上传落地旅游分享的门面就是游记而游记里最重要的又是图片。所以我单独把图片上传模块拎出来讲。我用Minio来做对象存储部署很简单Docker一条命令就能启动docker run -p 9000:9000 -p 9001:9001 \ -e MINIO_ROOT_USERadmin \ -e MINIO_ROOT_PASSWORDadmin123456 \ minio/minio server /data --console-address :9001启动之后在代码里配置MinioClient然后封装一个上传方法。上传时注意Bucket的创建逻辑项目启动时检查Bucket是否存在如果不存在就自动创建防止新人首次运行时报BucketNotFound。文件命名上别用原始文件名用UUID重命名不然不同用户上传同名的图片会互相覆盖这个问题我见得太多了。String fileName UUID.randomUUID().toString().replace(-, ) . StringUtils.getFilenameExtension(originalFilename);返回给前端的URL需要拼接Minio的访问地址这个URL要能直接在浏览器里打开。配置好Minio的公开读取策略不然图片显示不出来或者每次都要生成临时链接。毕设项目建议直接设为公开读省事且够用。3.4 商品交易流程的状态机设计商品交易这部分是“行走圈”和普通分享类网站拉开差距的地方。实现时要注意订单状态的流转不要乱跳。我建了一个状态机待支付 → 已支付 → 已完成 待支付 → 已取消用户下单的同时扣减库存如果支付超时或者主动取消就回补库存。毕设里不需要做定时取消但你可以做一个简单的逻辑用户点取消按钮时修改状态并回补库存。另一个细节下单时要生成一个唯一的订单号可以用时间戳加用户ID加随机数拼不要用数据库自增ID当订单号对外展示那样容易暴露你的订单量。3.5 Vue前端路由和Axios实战前端这块我拿Vite创建完项目后的第一件事就是配路由和请求封装。路由配置采用动态路由的思路把需要登录才能访问的页面统一挂在一个带守卫的父路由下const router createRouter({ history: createWebHistory(), routes: [ { path: /, component: () import(/views/Home.vue) }, { path: /login, component: () import(/views/Login.vue) }, { path: /user, component: () import(/layout/UserLayout.vue), meta: { requiresAuth: true }, children: [ { path: profile, component: () import(/views/user/Profile.vue) }, { path: notes, component: () import(/views/user/MyNotes.vue) }, { path: orders, component: () import(/views/user/MyOrders.vue) } ] } ] }) router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })Axios封装是前端工程质量的关键。我习惯创建一个统一实例设置baseURL、超时时间、请求拦截器自动带上Token响应拦截器统一处理错误码。这样每个业务页面请求的时候只关心数据本身不用每次都重复处理401和500。3.6 富文本编辑器和图片插入的联动游记发布页需要用富文本编辑器我选的是wangEditor或者Quill。这里有个非常容易踩的坑在富文本里插入图片的时候编辑器会先把图片转成Base64直接塞进正文结果一篇文章发出来光图片编码可能就有几MB后端接口都撑不住。正确做法是给富文本编辑器配一个自定义图片上传处理选中图片后先调用Minio上传接口拿回图片URL再把URL插入到编辑器内容中。这样数据库里存的是一串图片链接页面加载时按需加载性能和体验都好很多。4. 前后端联调、部署与答辩准备经验4.1 跨域配置与联调中的接口规范前后端分离开发跨域问题基本躲不掉。后端的解决办法很直接写一个CORS配置类允许前端的开发地址访问Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这里注意一点如果你用了JWT前后端联调时所有自定义请求头都要放行。上面的写法已经把allowedHeader设为*了所以问题不大。另外联调阶段接口返回值格式最好统一。我习惯用{ code: 200, message: success, data: {...} }这种结构前端Axios响应拦截器只需要判断code就能决定是正常处理还是弹错误提示非常省心。4.2 项目打包与部署流程后端打包直接执行mvn clean package然后把生成的jar包放到服务器上运行。如果是阿里云学生机这类环境可以简单一点直接java -jar跑起来。前端打包用npm run build产物是一个dist目录我们需要处理dist目录里的静态资源怎么被访问。两种方式一是把dist丢到Nginx下并配置反向代理到后端接口二是把前端编译产物放到SpringBoot的resources/static目录下这样前后端变成一个包部署极其简单。对于毕设演示我推荐第二种方式省掉Nginx配置的环节减少答辩时的变量。如果你愿意多花一点时间用Nginx方式更专业server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://localhost:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }这里try_files那行是前端路由刷新404的解法很重要。因为Vue是单页应用路由模式为history时刷新一个深层路径Nginx会找不到对应的真实文件必须让它回退到index.html。4.3 答辩演示的数据准备和话术答辩的时候最怕什么怕现场数据不好看。所以演示之前一定要准备一批逼真的测试数据十来篇内容详实的游记、配好图片、有不同时间的评论和点赞、商品列表起码8个、用户角色有普通用户和管理员。不要整数据全是“测试1”“测试2”看着就像应付任务。话术上每个模块要能讲清楚三件事这个功能是给谁用的、解决什么问题、技术上用了什么方案。比如游记推荐列表你可以说“首页根据浏览量倒序展示热门游记这是为了提升用户停留时长同时展示平台内容运营成果。技术实现上使用MyBatis-Plus的分页查询并且把热度数据缓存到Redis降低数据库压力”。能讲到这一层评委基本不会再为难你。5. 常见问题与排错技巧实录5.1 前端请求跨域失败症状浏览器控制台报CORS错误数据拿不到。排查路径先确认后端有没有加CorsFilter、前端Axios的baseURL是否指向了后端地址、两个服务的端口是否冲突。最常见的坑是前端用了localhost:5173后端接口是localhost:8080但请求路径写成了绝对路径导致协议或域名不匹配。5.2 图片上传报错症状上传图片时提示413或者得到502。多数情况下是SpringBoot默认的单次请求大小限制在1MB而你上传的旅游照片动辄几MB。解决办法是在application.yml里把限制调大同时前端也要配合调整别上传10MB以上的原图前端压缩后再传是更优方案。spring: servlet: multipart: max-file-size: 20MB max-request-size: 20MB5.3 Vue打包后页面空白IntelliJ IDEA里面运行没问题但打包部署后打开是白屏。这个一般是静态资源路径不对。Vite构建时默认资源路径是绝对路径/在很多子路径部署环境下会找不到文件。解决办法是在vite.config.js里设置base: ./让打包产物使用相对路径。5.4 启动报端口被占用本地开发经常遇到8080端口被占尤其是Windows机器。换个端口跑起来是最快的server: port: 8081但这只是权宜之计排查一下是什么程序占用了8080端口更靠谱。6. 写在最后的个人体会做毕设这件事本质上是一次小型的工程化训练它逼着你去面对真实项目里才会出现的各种琐碎问题。我带了这么多届学生发现那些做得好的项目往往不是技术用得最炫的而是边界清晰、逻辑自洽、每一步都打实的。SpringBoot加Vue这个组合刚好让你以较低的折腾成本完整走一遍从需求分析、表结构设计、接口开发到前端联调、打包部署的流程。把“行走圈”这个项目做完你在简历上写“独立开发并部署了一个包含内容分享与商品交易功能的旅游互动平台”面试官大概率会眼前一亮。最后分享一个小技巧整个项目做完之后花半天时间做一次全流程自测用管理员账号从前台发一条带图带的游记再到后台审核通过再用买家账号下单一个商品最后到个人中心看订单状态。这条路径只要走通答辩就成功了一大半。祝你的毕设顺利也期待你做出一个自己真正满意的作品。