news 2026/10/3 2:53:26

SpringBoot+Vue旅游信息交流网站毕业设计:从数据库到部署全流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue旅游信息交流网站毕业设计:从数据库到部署全流程实战

很多读者最近都在问我计算机毕业设计选旅游方向到底该怎么做。我前前后后帮人改过好几版基于SpringBoot的旅游信息交流网站,印象最深的还是“行走圈”这个题目:它把旅游分享和商品交易揉在一起,前端用Vue做互动门户,后端用SpringBoot管业务,复杂度刚好撑得起一篇像样的毕业设计,也不至于做到一半想换题。这篇文章就按我实际写过的方案,从需求拆解、数据库设计、后端接口、前端页面,到部署上线和答辩准备,一步步还原整个落地的过程。适合正在纠结选题方向、或者已经选定了SpringBoot+Vue但是还不知道怎么开工的同学来看。

1. “行走圈”到底要做成一个什么系统:毕业设计需求再拆解

1.1 题目里隐藏的三层功能域

先别看“全域旅游互动门户”这种词就觉得虚。拆开这个题目,“旅游信息交流网站”是皮,“旅游分享与商品交易”才是里子。一个完整的“行走圈”,至少包含三层功能域:

  • 信息交流层:用户注册登录、发布旅游攻略/游记/动态,浏览他人的分享,评论、点赞、收藏。这一层解决的是“内容从哪来、用户怎么互动”的问题,也是答辩时最容易讲清楚的部分。
  • 商城交易层:商品(景区门票、特色手信、旅行周边)的展示、购物车、生成订单、模拟支付、订单状态管理。这一层是整个平台区别于普通论坛的关键,也是把“交易系统”写进论文的重要素材。
  • 管理门户层:后台管理,包括用户管理、帖子审核/置顶、商品上下架、订单处理、统计看板。毕设有没有后台,在答辩时是两种印象——只有前台叫“页面”不叫“系统”。

如果把“全域旅游互动门户”理解为整个系统的对外门面,信息交流层和商城交易层就是门面底下的承重结构:一个负责沉淀内容,一个负责交易转化,再加上后台管理做运营支撑,“行走圈”才算是一个能自圆其说的平台。我建议第一版就把这三层做齐,哪怕某些功能做简单一点。因为毕业设计的评分逻辑通常是“功能广度 + 技术亮点 + 完成度”三者取平衡,少一层都得在答辩时解释半天。

1.2 功能边界与论文大纲如何互相倒推

很多同学拿到题目第一步就急着写代码,我反而不建议。先把论文大纲列出来,再回来约束功能,效率高得多。

典型论文结构大概是六章:绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试。其中“系统设计”这一章,必然要有用例图、架构图、数据库E-R图;“系统实现”这一章,要把核心模块逐张截图配文字讲。这意味着你倒推回来,代码里必须存在这些可画、可截图的实体:

  • 有用户角色区分(普通用户、管理员),才有机会画角色用例图。
  • 有帖子、商品、订单、评论四类核心实体,数据库E-R图才不会太空。
  • 有登录、发帖、下单三个主要业务流程,流程图才有内容可画。
  • 有后台列表页、发布页、详情页、个人中心页,截图素材才够撑满实现章节。

按这个规则反推,“行走圈”的功能清单其实可以收敛得非常明确,我列一下第一版我的原型清单:

模块功能点说明
用户模块注册、登录、个人资料编辑、头像上传密码加密,JWT鉴权
内容模块发布旅游攻略、浏览信息流、关键词搜索、分类筛选支持多图片
互动模块点赞、收藏、评论前台做计数,后台做记录
商城模块商品列表、商品详情、购物车、下单、订单列表订单状态流转
后台模块用户管理、内容审核、商品管理、订单管理独立管理路由

1.3 技术栈的取舍原则

技术栈这块,题目清清楚楚写了SpringBoot+Vue,剩下的是你自己选。我给学弟的建议一般是这套:

  • 后端:SpringBoot 2.7.x + MyBatis-Plus + MySQL 5.7/8.0 + Redis(可选)
  • 认证:JWT(jjwt库)+ Spring拦截器
  • 前端:Vue3 + Vite + Vue Router + Pinia + Element Plus + Axios
  • 接口文档:SpringDoc/knife4j(Swagger)
  • 部署:后端打成jar包,前端打包后放入静态目录或独立Nginx

为什么要强调版本?“springboot版本太高”是这几年踩坑重灾区。SpringBoot 3.x要求JDK 17,某些学校的机房装的是JDK 8,而且很多教程里的写法在3.x里已经变了。除非你确定本机环境支持,否则毕业设计老老实实选2.7.x,它兼容JDK8,生态环境也成熟。前端同理,Vue3没问题,但如果你只看过Vue2的课,硬上Composition API反而会拖节奏,这时选Vue2也不丢人。工具是给别人用的,不是折磨自己的。

另外一个构建层面的经验:项目构建工具用Maven,parent就认准spring-boot-starter-parent,版本号控制在2.7.x,依赖的版本尽量交给parent统一管理,别自己手动引入一堆不清楚的版本号,否则依赖冲突会占用你大量时间。

2. 整表建模:旅游内容与商品交易的双域数据结构

2.1 用户表设计与扩展字段

用户表是所有模块的地基。设计成什么样,直接决定后面功能好不好写。我用的基础结构如下:

CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL', `gender` tinyint(1) DEFAULT '0' COMMENT '0未知 1男 2女', `phone` varchar(20) DEFAULT NULL, `email` varchar(100) DEFAULT NULL, `intro` varchar(500) DEFAULT NULL COMMENT '个人签名', `role` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0普通用户 1管理员', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常 0禁用', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有几个值得注意的点:

  • username和nickname分开,登录名和展示名解耦,后面做“改昵称”功能不用动账号。
  • password一定存哈希后的结果,用Spring Security的BCryptPasswordEncoder或jBCrypt都行,千万不要自己写MD5拼接盐。
  • role字段放用户表里,简单场景够用,不需要单独拆RBAC表;如果后面想加权限粒度再加角色表。

2.2 旅游分享内容模型

帖子表的设计,我见过很多同学把内容全部塞进一个text字段,其他什么信息都没有,这会导致前端信息流非常难看。做旅游分享,至少要知道这个分享发生在哪里、封面图是什么、属于哪个分类:

CREATE TABLE `post` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `category_id` bigint(20) DEFAULT NULL COMMENT '分类:攻略/游记/问答', `title` varchar(100) NOT NULL, `content` text NOT NULL COMMENT '富文本或纯文本', `location` varchar(100) DEFAULT NULL COMMENT '地点/目的地', `cover` varchar(255) DEFAULT NULL COMMENT '封面图地址', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常 0待审 2隐藏', `like_count` int(11) NOT NULL DEFAULT '0', `comment_count` int(11) NOT NULL DEFAULT '0', `view_count` int(11) NOT NULL DEFAULT '0', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关于帖子配图,很多人纠结是存JSON还是拆表。我的答案是:图片列表单独建一张表或者用逗号分隔都行,但主帖表里一定要有cover封面字段,因为信息流卡片必须靠封面来撑排面,查封面比查全部图片效率高得多。评论区在互动模块一起讲。

2.3 商品与订单表的设计取舍

商城部分的核心是商品表、订单表、订单明细表。商品表相对常规:id、name、cover、description、price、stock、sales、category_id、status。真正麻烦的是订单。

先看订单表:

CREATE TABLE `order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(64) NOT NULL COMMENT '业务订单号', `user_id` bigint(20) NOT NULL, `total_amount` decimal(10,2) NOT NULL, `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待支付 1已支付 2已发货 3已完成 4已取消', `receiver_name` varchar(50) DEFAULT NULL, `receiver_phone` varchar(20) DEFAULT NULL, `receiver_address` varchar(200) DEFAULT NULL, `pay_time` datetime DEFAULT NULL, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

订单明细表记录下单时商品维度的快照,至少要包含商品ID、名称、单价、数量、小计金额。一个需要注意的设计问题:不要在订单表里记一个“商品名称字符串”就完事,明细表是必须的。原因是真实商城一笔订单可以包含多个商品,而且毕业设计答辩时老师很可能问“订单怎么保证属于同一笔交易”“下单之后商品改价了怎么办”。有了明细表,快照问题就解决了——下单时把当时的名称和单价写进明细,商品后续改价不影响这笔订单。

下单是一段典型的事务逻辑:校验库存 -> 冻结/扣减库存 -> 生成主订单 -> 生成明细。库存扣减放在哪里很关键,最稳妥的做法是在下单SQL里加条件,例如“update product set stock = stock - 1 where id = ? and stock >= 1”,然后判断受影响行数,而不是先查一遍再更新,避免并发超卖。这种细节写进论文里,是加分的。

2.4 收藏、点赞、评论的表结构细节

这三类互动,虽然看起来简单,但有一个容易出现的问题:唯一约束和逻辑删除的冲突。

点赞表:

CREATE TABLE `like_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `post_id` bigint(20) NOT NULL, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_post` (`user_id`, `post_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

收藏表结构类似。它的作用不是存“数据”,而是存“关系”:谁对什么内容点了赞。前端只显示数字,后端靠这张表去重。唯一索引(user_id, post_id)是防止同一个用户重复点赞的数据库防线。

但这里有个坑:如果给like_record加deleted逻辑删除字段,再保留唯一索引,同一个用户取消点赞再点赞会插入新记录,然后旧记录还占着唯一索引里的坑位,直接导致第二次点赞失败。这就是“逻辑删除和唯一索引冲突”。解决方式有三种:一是干脆物理删除,毕业设计场景完全够用;二是把唯一索引改成(user_id, post_id, deleted)并把deleted设计成tinyint区分;三是改用状态字段,0取消1点赞,唯一索引覆盖(user_id, post_id)去更新状态。我一般推荐第一种或第三种,别在这种地方给自己挖坑。

另外,帖子的点赞数不要每次回复都去count整张表,可以像我的post表那样在帖子表里冗余一个like_count字段,点赞时做+1,这样列表页拿数字非常快。删除帖子的时候顺手把这个帖子的关联互动记录清掉即可。

3. SpringBoot后端实现:从接口分层到业务闭环

3.1 工程结构、统一返回体与全局异常处理

先承认一个事实:SpringBoot的好处就是约定大于配置,一个空的工程跑起来只需要几分钟。但你一定不要把全部代码写在一个XXXApplication里面。给个实用的目录结构:

com.walkingcircle ├── controller │ ├── UserController.java │ ├── PostController.java │ ├── ProductController.java │ ├── OrderController.java │ └── AdminController.java ├── service │ ├── impl ├── mapper ├── entity ├── dto ├── vo ├── common │ ├── Result.java │ └── GlobalExceptionHandler.java ├── config │ ├── WebConfig.java │ └── MybatisPlusConfig.java └── WalkCircleApplication.java

统一返回体这一条,很多课上不讲,但实际项目里没有它你会想哭。所有接口都返回一个Result对象:

public class Result<T> { private Integer code; private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "success"; r.data = data; return r; } public static <T> Result<T> error(Integer code, String msg) { Result<T> r = new Result<>(); r.code = code; r.msg = msg; return r; } }

再加上全局异常处理器@RestControllerAdvice,把参数校验异常、业务异常、未知异常都收敛起来返回给前端。这样前端axios拦截器只需要判断code是不是200,可以省掉大量重复的if/else。

3.2 JWT登录与会话安全方案

毕业设计最常见的登录方案是Session,但我更推荐JWT。理由很简单:Vue前端和后端分离之后,跨域场景下Session要么配CORS开credentials,要么cookie容易被浏览器拦,而JWT不存在这个问题;而且“无状态认证”写在论文里是一个现成的技术亮点。

实现起来不复杂:

  1. 登录接口校验用户名密码,成功后用jjwt生成token。
  2. token里放userId和role两个claim,过期时间设为24小时。
  3. 写一个LoginInterceptor,从请求头Authorization里解析token,解析成功放行。
  4. 将拦截器注册到WebConfig里,排除login、register、静态资源路径。

具体拦截器大体逻辑:

@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); try { Claims claims = JwtUtil.parseToken(token); request.setAttribute("userId", claims.get("userId")); return true; } catch (Exception e) { // 解析失败,抛业务异常 } } throw new BusinessException(401, "请先登录"); } }

做完之后记得留一个获取当前登录用户的工具方法,因为发帖、评论、下单都要拿到当前用户的ID。常用的做法是把用户ID放request attribute,然后在Controller里用@RequestAttribute取,或者用ThreadLocal的UserContext工具类。两者都行,我倾向于后者,写的时候更干净,不用每个接口方法都加一个额外参数。

3.3 发帖与图片上传的完整链路

发帖这个功能看起来简单,真正做的时候涉及两个链路:上传图片和提交内容。很多同学把这两个混成一个接口,结果图片出问题的时候内容也存不进去。我建议拆成两个接口:

  • POST /api/upload/image:上传图片,返回图片URL。
  • POST /api/post:提交帖子内容,内容是纯文本,里面引用图片URL。

图片上传落地实际注意点:

@PostMapping("/upload/image") public Result<String> upload(@RequestParam("file") MultipartFile file) { // 1. 校验文件类型和大小 // 2. 生成唯一文件名:UUID + 后缀 // 3. 保存到本地磁盘 upload/ 目录 // 4. 返回可访问的URL }

生成唯一文件名防止重名覆盖;限制大小例如单张不得超过5MB;注意把文件后缀转小写,否则上传.jpg和.JPG会生成不同后缀。保存路径不要写死成绝对路径,用配置项管理。还有一个容易忽略的点:图片URL最终返回的时候,如果开发环境用localhost,生产环境要换成域名或者服务器IP。最好的办法是约定前端所有图片URL都存相对路径,由前端拼接完整地址,或者后端配置一个base-url参数拼接。否则你本地图片正常,打包上线后全裂了。

发帖service的核心,我给一个要点清单:

  • 写入post表时必须带上当前登录用户ID。
  • content不要直接相信前端,做一下长度和基础内容校验,至少长度不能为空、不能超上限。
  • 事务注解@Transactional要加在service方法上,虽然单表插入看起来不需要,但后续要同步更新帖子分类计数时,事务能避免中间状态。

3.4 商品下单与库存扣减的事务控制

商城业务的后端,核心难点在下单。我之前帮学弟改过的代码,一半问题出在库存和订单状态。

下单的service方法可以整理成四步:

  1. 参数校验:检查收货地址、商品ID、购买数量。
  2. 查询商品并校验在架状态。
  3. 扣减库存,使用库存字段加条件更新的写法。
  4. 生成订单和明细,返回orderNo。

关键代码示意:

@Transactional public OrderVO createOrder(OrderCreateDTO dto) { Product product = productMapper.selectById(dto.getProductId()); if (product == null || product.getStatus() != 1) { throw new BusinessException("商品不存在或已下架"); } // 条件更新扣库存,返回受影响行数 int rows = productMapper.deductStock(dto.getProductId(), dto.getQuantity()); if (rows == 0) { throw new BusinessException("库存不足"); } // 生成订单和明细... }

失败立刻抛异常,事务回滚,库存不会变成负数。订单状态字段我用0/1/2/3/4五个值,前端根据值渲染对应的按钮:待支付显示“去支付”,已支付显示“待发货”,已发货显示“确认收货”,已完成和已取消都只是展示状态。这里不要把状态文案直接写死在前端,最好从后端字典里取,或者至少在常量类里统一管理,否则后面加一个状态你得改三个页面。

4. Vue端页面落地:信息流、商品详情与状态管理

4.1 Vue工程初始化与前端目录结构

前端这块,我推荐Vite,理由就一条:启动快,喝茶的时间都省了。创建工程一句话:

npm create vite@latest walk-front -- --template vue npm install npm install vue-router pinia axios element-plus

目录结构可以这么拆:

src ├── api │ ├── post.js │ ├── product.js │ ├── order.js │ └── user.js ├── router │ └── index.js ├── stores │ └── user.js ├── views │ ├── Home.vue │ ├── PostDetail.vue │ ├── ProductList.vue │ ├── ProductDetail.vue │ ├── Cart.vue │ ├── OrderList.vue │ ├── UserCenter.vue │ └── admin │ ├── AdminUser.vue │ ├── AdminPost.vue │ └── AdminOrder.vue ├── components │ ├── PostCard.vue │ └── Pagination.vue └── utils └── request.js

按api目录集中封装接口,不要在每一个页面里直接写axios.get,后面联调改baseURL你才知道什么叫痛苦。views目录按路由页面组织,admin单独建子目录,方便在路由做懒加载和守卫。

4.2 路由守卫与axios拦截器

路由守卫解决的是“未登录不能访问个人中心/商城下单”的问题。核心逻辑就一段:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next('/login'); } else { next(); } });

注意meta里给admin页面设置requiresAdmin,管理员路由除了要有token,还要从Pinia里取用户role,role不是1就重定向到404或首页。这里最容易漏的是:后端接口权限一定要做,前端路由守卫只是改善体验,不能作为安全手段——直接curl你的后端接口就能绕过前端。

axios拦截器是每次请求的必经之路,配置成复用逻辑:

service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); service.interceptors.response.use( res => { if (res.data.code === 200) return res.data.data; return Promise.reject(res.data.msg); }, err => { if (err.response && err.response.status === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(err); } );

这样所有业务页面拿到的就是接口真正的data,不需要每次都写res.data.data。

4.3 旅游动态信息流组件的实现

信息流是“行走圈”的门面。我的做法是首页用PostCard组件渲染帖子列表,每个PostCard包含封面图、标题、地点、作者头像、点赞数、评论数。拉到页面底部加载更多。

列表接口建议做分页:?pageNum=1&pageSize=10,返回total。前端拿到total之后决定是否显示“加载更多/没有更多了”。如果不做分页,数据一多页面直接卡顿,答辩老师一旦往上多滑几下,观感差别很大。

PostCard的骨架大概是这样:

<template> <div class="post-card" @click="goDetail"> <el-image :src="post.cover" fit="cover" /> <div class="post-info"> <h3>{{ post.title }}</h3> <span>{{ post.location }}</span> <div class="footer"> <span>{{ post.author.nickname }}</span> <span>赞 {{ post.likeCount }} 评论 {{ post.commentCount }}</span> </div> </div> </div> </template>

注意前端在v-for渲染时要加:key,列表数据里最好有唯一id;封面加载失败要兜底,Element Plus的el-image自带error插槽,随便放个占位图,否则大面积红x很难看。

4.4 商品交易与个人中心的关键联动

商品详情页到订单页的链路,往往需要跨路由传参数。常见做法有三种:query传id、动态路由、本地变量。前端商品详情页点击“立即购买”时,你要把商品id带到确认订单页,同时把数量、总价在确认页展示。

因为确认订单页需要根据商品id重新查商品再计算价格,所以最稳的是把商品id放在路由query里,确认页面onMounted时重新请求接口取最新价格。绝对不要只靠上一页传过来的price字符串,以防用户在前端篡改支付金额。毕业设计也许没人黑你,但答辩老师很可能问这个设计漏洞,提前准备好答案会很加分。

个人中心要展示的信息包括:我的资料、我的帖子数、我的收藏数、我的订单列表。这些接口尽量按用户维度统一规划,例如后端提供GET /api/user/center/{userId}一次性返回基础信息和统计值,前端一个页面只调一次接口。否则个人中心打开要发五六个请求,Loading转半天,看着就很业余。

5. 联调、跨域与部署的一揽子工程

5.1 跨域配置:开发代理与生产CORS

前后端分离,跨域问题是绕不开的。开发阶段最优雅的不是在后端开CORS,而是用Vite的代理,前端中间件把 /api 转给后端:

// vite.config.js server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端请求 /api/xxx,Vite开发服务器会转发到后端,浏览器眼里请求的是同一个origin,不存在跨域。后端不需要对开发环境开放CORS。

但生产环境要分情况。如果前端和后端不同域名部署,就必须在后端启用CORS。SpringBoot里加一个配置类:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(false) .maxAge(3600); } }

如果你用JWT,CORS的allowCredentials设false问题不大,token在header里不带cookie,安全性反而更好控制。

5.2 图片文件存储选型(本地还是对象存储)

图片存储,第一个选项是本地磁盘,第二个是MinIO或云OSS。毕业设计预算有限,我建议直接本地磁盘。做法不复杂:后端把上传的图片写成UUID.png放到upload目录,然后配置一个静态资源映射,让SpringBoot能把 /upload/** 映射到物理目录。SpringBoot 2.7里这样配:

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:D:/walkingcircle/upload/"); }

如果你身边有对象存储环境,比如学校给的服务器上有MinIO,那就可以顺带做个练习,把图片传到MinIO再返回访问地址。MinIO的好处是图床和后端分离,将来部署到生产环境不会被本地磁盘空间限死。MinIO和SpringBoot整合也是一些人常问的点,我的经验是前端配置不多:安装MinIO客户端、配置endpoint/accessKey/secretKey/bucket,然后在Spring Boot里集成一个StorageService封装putObject就行,代码量很小,但写在简历和论文里能多出一个“对象存储”的关键词。

5.3 前后端打包上线的两种常规路线

上线部署有两种常见方案,我两种都跑通过:

方案一:前端后端合体部署。把Vue打包出来的dist目录,直接复制到SpringBoot的src/main/resources/static下面,然后重新打包jar。这样访问同一个端口就能同时打开前端页面和后端接口,连CORS都省了。缺点是不方便单独更新前端,但毕业设计毫无压力。

方案二:Nginx反向代理。前端dist单独部署到Nginx,后端jar单独启动,Nginx配置把 /api 转发给后端端口。结构更正规,但如果服务器上还没装Nginx,搞起来多花半天。

不管哪种方案,有一个点特别容易翻车:打包前端的时候,baseURL。如果是方案一,请求地址可以用相对路径’/api’,由Nginx或SpringBoot转发;如果是方案二,你要把axios的baseURL写成一个完整的后端地址,或者同样用Nginx代理。我见过太多同学本地好好的,打包上线后所有接口401,原因就是baseURL写死localhost:8080,服务器上根本不存在。

6. 从开发到答辩,我的实战踩坑与讲解要点

6.1 几个真正浪费过我时间的Bug

第一个坑是Long类型主键传到前端精度丢失。前端的JS Number能安全表示的最大整数是2的53次方,而MyBatis-Plus默认主键策略生成的是雪花ID那种19位Long,传到前端后最后几位全变成了0,详情页id对不上。解决方式很简单:在SpringBoot里统一给Long字段配置ToStringSerializer,或者给VO里的主键字段加@JsonSerialize(using = ToStringSerializer.class)。这个坑发生率很高,早处理早舒服。

第二个坑是MyBatis-Plus的分页插件没有配置,导致Page对象返回的records总是空或者total永远是0。你需要注册一个MybatisPlusInterceptor:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }

同时注意Page的current从1开始,前端分页组件当前页别从0传。另外时间字段序列化,默认Jackson会把LocalDateTime输出成一大串数组,前端根本看不懂。统一配置日期格式或者给字段加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),两个都要会。

第三个坑是上传图片返回的URL在本地能访问,上线后图片全部404。原因一般就两个:一是静态资源映射没配到生产环境目录,二是URL里没带服务器实际端口或域名。排查的时候先curl一下图片URL看看响应码,再检查映射路径,别急着改代码。

6.2 演示准备与基础性能优化

演示是毕设答辩的命门。再好的系统,演示卡顿都会扣印象分。这里给你几条实在建议:

  • 准备一个“演示账号”,里面预先发布10篇左右的攻略、上传好图片、下过几笔订单,不要现场临时注册再发帖。
  • 把Redis加速这个功能想清楚再决定做不做。如果为了加分做了,至少要在论文里讲清“热帖缓存、登录token缓存”两个应用场景,否则老师说一句“你这个Redis就只是存了个token吧”会非常尴尬。
  • 基础性能优化只要做两件事就够:首页帖子列表用分页,后端SQL关键字字段加索引。这两条足够答辩时回答“系统有没有做性能优化”这种问题。

6.3 答辩现场如何讲清楚项目亮点

最后一个实际经验:答辩时不要照念论文,讲项目重点抓三条主线。

第一条,业务主线:这是“旅游信息交流+商品交易”双系统,用户发布内容、互动、交易、后台管理全都有,证明完整度。老师听完会知道这不是只写了几个页面。

第二条,技术主线:说明前后端分离、JWT无状态认证、MyBatis-Plus的使用、事务实现下单、对象存储或者本地静态映射,证明你的技术不是空吹。

第三条,成长主线:讲一个你踩过的坑,比如Long精度丢失或者分页插件,然后讲你如何一步步排查解决。这个环节最有说服力,比任何自夸都有效。


我个人是常年帮人把关全栈毕业设计项目、改bug时积累的这些经验。做完“行走圈”这套,最大的感受是毕设选型真的不用贪多求全,把SpringBoot+Vue这条线彻底走通,数据库设计做到不冲突、事务不丢、鉴权不裸奔,就已经超过大多数同学了。最后再分享一个小建议:不要直接去网上找现成源码照着粘贴,代码可以借鉴结构,但每一段都要能自己讲明白。答辩老师并不怕你做得简单,怕的是你在台上讲不清楚自己写了什么。希望这篇文章能帮你把“行走圈”从题目变成真正可以演示、可以答辩的系统。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 2:52:15

毕业设计可用的知识图谱问答系统:Neo4j+规则NLQ实战

简介&#xff1a;这是一份面向计算机专业本科生的毕业设计级实战项目&#xff0c;聚焦知识图谱与推荐系统交叉应用&#xff0c;为正在完成大作业、毕业设计或寻求深度学习图谱融合实践的学习者提供可直接复现的完整方案。资源包含44个文件&#xff0c;以7个核心Python脚本&…

作者头像 李华
网站建设 2026/10/3 2:52:00

用Python实现储备池计算预测数据:原理、代码与避坑指南

简介&#xff1a;面向时间序列预测与混沌系统研究的Python储备池计算&#xff08;RC&#xff09;实现资源&#xff0c;基于Echo State Network算法&#xff0c;适合机器学习初学者与科研人员复现非线性动态系统预测实验。压缩包为RAR格式&#xff08;705KB&#xff09;&#xf…

作者头像 李华
网站建设 2026/10/3 2:51:46

Cocos Creator节点截图倒图?一文讲透Y轴翻转与图片保存流程

需求一句话&#xff1a;用户在游戏里点“分享”&#xff0c;我们把某个节点渲染成一张图&#xff0c;保存到相册或者上传到服务器。听起来特别简单&#xff0c;但我第一次交付这个功能的时候就被测试打回&#xff1a;保存下来的图片整个是倒立的。从 Cocos 的节点截图到最终保存…

作者头像 李华
网站建设 2026/10/3 2:51:40

Java微服务+Python OpenCV人脸识别门禁系统毕设源码与实战

简介&#xff1a;这是一套面向高校计算机相关专业毕业设计的人脸识别门禁系统完整项目源码&#xff0c;采用Java前后端分离与Python OpenCV人脸识别相结合的方案&#xff0c;适合需要完成门禁类课题或学习微服务架构的学生与开发者参考。项目以Spring Cloud微服务划分后端&…

作者头像 李华
网站建设 2026/10/3 2:51:36

RAID磁盘阵列从原理到实战:级别选型、软硬件配置与故障恢复

1. 内容整体设计与思路拆解1.1 为什么偏偏要聊 RAID如果你管过几台像样的服务器&#xff0c;或者折腾过 NAS&#xff0c;那 RAID 大概率是你绕不开的一个词。我最早接触 RAID 的时候还是一头雾水&#xff0c;总觉得这玩意儿是玄学&#xff1a;明明是好几块硬盘&#xff0c;怎么…

作者头像 李华
网站建设 2026/10/3 2:51:15

VMware报错“客户机操作系统已禁用CPU”排查:从VMX配置到宿主环境

装好Ubuntu&#xff0c;按下启动按钮&#xff0c;VMware状态栏突然跳出一行红字&#xff1a;客户机操作系统已禁用CPU。屏幕上的虚拟机停在开机画面&#xff0c;按键没反应&#xff0c;仿佛整个系统被按了暂停键。我第一次碰到这个报错时&#xff0c;第一反应是物理CPU烧了&…

作者头像 李华