SpringBoot+Vue古典舞交流平台毕设项目完整实战解析

SpringBoot+Vue古典舞交流平台毕设项目完整实战解析 最近在整理手上的毕设项目正好有几个同学问到古典舞在线交流平台这类题目的实现方式。今天就把这个完整的 SpringBoot Vue 前后端分离项目从头到尾拆开讲一遍包括源码结构、数据库脚本、接口文档、部署上线以及我在实际开发过程中踩过的坑。如果你正在准备 Java Web 方向的毕业设计或者想系统学习前后端分离项目的完整落地流程这篇内容应该能帮你省下不少时间。这个项目本质上是一个面向古典舞爱好者的内容交流社区核心功能包括用户注册登录、舞蹈视频发布与播放、图文帖子发布、评论互动、个人中心、后台管理等等。技术栈选的是 SpringBoot 做后端接口服务Vue 做前端页面展示和交互数据库用 MySQL接口文档统一整理成独立的 Markdown 文件。整体不算复杂但麻雀虽小五脏俱全正好适合作为毕设项目来学习。1. 项目整体设计与技术选型思路1.1 为什么选 SpringBoot Vue 做前后端分离先聊一下选型问题。古典舞交流平台这种偏内容展示和交互类的项目最怕的就是后端模板引擎把页面渲染和业务逻辑揉在一起改个前端样式都得重启服务费时费力。前几年 Java Web 毕设最常见的写法还是 JSP Servlet页面里嵌 Java 代码前端改版简直是灾难。我身边有不少人做过类似的题目后期改来改去最后交上去的代码自己都不想看第二遍。SpringBoot Vue 这种前后端分离架构优势在于后端只负责提供 JSON 数据接口前端专心做页面渲染和用户交互两边可以并行开发。你在答辩的时候也能把前后端分离RESTful API 设计跨域处理这些概念讲得更清楚老师听着也觉得这是个正经项目不是一个 CRUD 堆出来的 demo。选 SpringBoot 而不是 SSMSpring SpringMVC MyBatis也是同样的理由。SpringBoot 内置 Tomcat不用单独配置一堆 XML启动直接mvn spring-boot:run就起来。内置的自动配置机制把数据源、事务、Jackson 序列化这些统统帮你处理好了开发效率提升非常明显。实际上做毕设的时候90% 的时间应该花在业务逻辑和页面上而不是耗在环境配置上。1.2 项目功能模块拆解古典舞在线交流平台这个名字听起来有点宽泛做的时候就必须把它拆成具体功能来落地。我最终设计成下面这几个模块用户模块注册、登录、个人信息维护、密码修改、头像上传。视频模块视频发布、视频列表、视频详情、分类筛选、在线播放。帖子模块图文内容发布、帖子列表、帖子详情、删除帖子。评论模块对视频和帖子发表评论、查看评论列表。分类模块古典舞的舞种分类比如汉唐舞、敦煌舞、昆舞、戏曲古典舞等。后台管理用户管理、内容审核、数据统计看板。每个模块后端对应一组 Controller、Service、Mapper前端对应一个或几个 Vue 页面。这样一来整个项目的结构非常清晰哪里出了问题能快速定位。1.3 目录结构与代码分层先看后端的包结构这个结构基本是 SpringBoot 项目的标准分层方式com.example.dance ├── config // 配置类跨域、拦截器、文件上传 ├── controller // 接口层接收前端请求 ├── service // 业务逻辑层 │ └── impl // 业务实现 ├── mapper // 数据访问层MyBatis 的 Mapper 接口 ├── entity // 数据库实体类 ├── dto // 数据传输对象接收前端参数 ├── vo // 视图对象返回前端数据 ├── common // 统一返回结果、异常处理、常量 └── utils // 工具类JWT、MD5、文件处理等前端 Vue 项目的结构src ├── api // 封装 axios 请求 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // Vuex 状态管理 ├── views // 页面组件 │ ├── Home.vue │ ├── VideoDetail.vue │ ├── PostDetail.vue │ ├── Login.vue │ ├── Register.vue │ ├── Profile.vue │ └── admin // 后台管理页面 ├── App.vue └── main.js这种前后端分离的结构维护起来非常舒服。后端接口要加功能直接在 controller 层加方法就行前端要改页面直接改对应的 vue 文件。代码分层清晰答辩的时候对着项目结构图就能讲十分钟。2. 后端核心模块实现与 SQL 脚本设计2.1 数据库表结构设计古典舞交流平台的核心数据模型其实不复杂关键是表之间的关系要理清楚。我最终设计的数据库叫dance_community一共 6 张核心业务表SQL 脚本拆成建表语句和初始化数据两部分。用户表user是最基础的表字段设计如下CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 用户ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码(MD5加密存储), nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, email varchar(100) DEFAULT NULL COMMENT 邮箱, role tinyint(4) DEFAULT 1 COMMENT 角色 1-普通用户 2-管理员, status tinyint(4) DEFAULT 1 COMMENT 状态 1-正常 0-禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT1000 DEFAULT CHARSETutf8mb4 COMMENT用户表;视频表video是内容核心除了基本信息外我加上了cover_url封面图和video_url视频文件地址另外有play_count播放量和like_count点赞数这两个冗余字段。为什么要冗余这两个数字因为古典舞视频的播放量和点赞量是前台展示的重要指标如果每次页面加载都要去 count 一下关联表数据量大了之后性能会很差。做毕设虽然数据量不大但这个设计思路你可以在答辩时讲出来属于加分项。CREATE TABLE video ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 视频ID, user_id bigint(20) NOT NULL COMMENT 发布者ID, title varchar(100) NOT NULL COMMENT 视频标题, description text COMMENT 视频描述, category_id bigint(20) DEFAULT NULL COMMENT 舞种分类ID, cover_url varchar(255) DEFAULT NULL COMMENT 封面图地址, video_url varchar(255) NOT NULL COMMENT 视频文件地址, play_count int(11) DEFAULT 0 COMMENT 播放量, like_count int(11) DEFAULT 0 COMMENT 点赞数, status tinyint(4) DEFAULT 1 COMMENT 状态 1-已发布 0-下架, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 发布时间, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category_id (category_id) ) ENGINEInnoDB AUTO_INCREMENT2000 DEFAULT CHARSETutf8mb4 COMMENT舞蹈视频表;帖子表和评论表同理评论表通过comment_type字段来区分这条评论属于视频还是帖子用一个字段搞定避免建两张几乎一样的评论表CREATE TABLE comment ( id bigint(20) NOT NULL AUTO_INCREMENT, content_id bigint(20) NOT NULL COMMENT 评论对象ID(视频ID或帖子ID), content_type tinyint(4) NOT NULL COMMENT 评论类型 1-视频 2-帖子, user_id bigint(20) NOT NULL COMMENT 评论人ID, content varchar(500) NOT NULL COMMENT 评论内容, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_content_id (content_id, content_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评论表;2.2 MyBatis 与 SQL 脚本的应用方式项目里持久层选的是 MyBatis配合 XML 文件管理 SQL。这里有个非常关键的经验如果你的毕设项目用了 MyBatis一定要把 SQL 脚本和 XML 里的 SQL 语句都维护好因为评阅老师大概率会打开你的 SQL 脚本去检查。很多人只在代码里写注解 SQL没有单独的 SQL 脚本文件这是个明显的扣分项。我习惯把 Mapper XML 放在src/main/resources/mapper/目录下用MapperScan注解扫描。比如视频列表的查询需要关联查出发布者的昵称和头像如果直接在一个 SQL 里 join效率会比多次查询快代码也整洁。下面这 SQL 就是从视频表关联用户表查列表时把用户信息带出来select idselectVideoList resultMapVideoVOMap SELECT v.*, u.nickname AS author_nickname, u.avatar AS author_avatar FROM video v LEFT JOIN user u ON v.user_id u.id WHERE v.status 1 if testcategoryId ! null AND v.category_id #{categoryId} /if ORDER BY v.create_time DESC LIMIT #{offset}, #{pageSize} /select注意 MyBatis 的if标签是动态 SQL 的核心条件多的时候非常方便。SQL 脚本初始化数据时我预置了一个管理员账号admin和几个分类数据方便前端联调时直接使用。2.3 JWT 登录鉴权与 Spring Security 的选择登录模块是每个毕设项目都会有的但实现方式千差万别。早期项目多用 Session 保存登录状态前后端分离之后 Sesssion 的跨域问题很麻烦所以我选了 JWTJSON Web Token方案。简单解释一下 JWT 的原理用户登录成功后后端把用户 ID、用户名、角色等信息加密生成一个 token 字符串返回给前端前端把 token 存在 localStorage 里之后每次请求都在请求头加上Authorization: Bearer token后端拦截器校验 token 是否有效从而判断用户身份。我为什么不直接引入 Spring Security因为 Spring Security 的 filter 链配置比较复杂对毕设来说太重了。我选择用一个自定义 HandlerInterceptor 拦截器只拦截需要登录的接口路径代码量少而且你能讲清楚拦截器的工作原理比直接引入框架后说不清内部机制要强得多。核心实现如下public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(userRole, claims.get(role)); return true; } catch (Exception e) { // token 解析失败返回 401 } } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write(JSON.toJSONString(Result.error(401, 未登录或登录已过期))); return false; } }要特别注意 token 过期时间的设置。有的人图省事设置成 7 天甚至永久有效这其实很不安全。我建议设置 24 小时过期前端在 axios 拦截器里捕获 401 状态码后自动跳转登录页并清掉本地 token这是比较合理的做法。2.4 统一返回结果与全局异常处理接口是前后端分离项目的前后端契约如果每个接口返回的数据格式都不一样前端处理起来会非常痛苦。我定义了一个统一的Result类public class Result { private Integer code; // 状态码 200成功 500失败 401未登录 private String message; // 提示信息 private Object data; // 业务数据 public static Result success(Object data) { Result r new Result(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static Result error(Integer code, String message) { Result r new Result(); r.setCode(code); r.setMessage(message); return r; } }配合RestControllerAdvice做全局异常处理业务代码里就不用到处写 try-catch 了。Service 层抛出BusinessException(视频不存在)全局异常处理器捕获后转成Result.error(500, 视频不存在)返回。这样Controller 层的代码能干净很多也方便前端统一处理错误弹窗。3. 前端 Vue 项目实现与交互细节3.1 Vue 工程初始化与路由设计前端这块我用 Vue 2 Vue Router Vuex Element UI 的组合这是目前 Vue 毕设项目最主流的搭档。如果你的电脑上还没有 Vue 环境需要先安装 Node.js建议装 14 或 16 LTS 版本部分依赖对高版本 Node 暂时不兼容这也是一个很容易踩的坑。创建项目的命令是官方脚手架这步没有太多技术含量# 安装 vue-cli 脚手架 npm install -g vue/cli # 创建项目 vue create dance-frontend # 进入项目并启动开发服务器 cd dance-frontend npm run serve路由设计上我按照前台-后台两套布局来区分。前台用户看到的是视频列表、帖子列表、个人中心等页面后台则是数据看板和用户管理。路由里的关键操作是给后台页面加一个meta: { requiresAuth: true, requiresAdmin: true }标记然后在全局前置守卫里判断当前用户角色router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) return } if (to.meta.requiresAdmin) { const role localStorage.getItem(userRole) if (role ! 2) { next(/) return } } next() })3.2 核心页面实现视频列表与视频播放首页的视频列表我设计成卡片式布局调用后端/api/video/list接口拿到视频数据用 Element UI 的el-card配合自定义样式渲染。卡片里展示封面图、标题、作者昵称和播放量点击之后跳转到视频详情页。视频播放这个功能有个大坑必须重点讲。很多古典舞视频都是 m3u8 格式的流媒体文件直接用 HTML5 的video标签没法播放。解决方案是用video.js配合videojs-contrib-hls插件或者用市面上成熟的播放器组件。如果你遇到的是 mp4 格式那就简单了直接video标签 src属性就能搞定。但 m3u8 就一定要引入额外依赖。这里给出一段经过验证的 m3u8 播放实现在视频详情页引入 video.jsnpm install video.js然后在组件中引入样式和初始化播放器template div classvideo-player video idmy-player classvideo-js vjs-big-play-centered controls preloadauto/video /div /template script import videojs from video.js import video.js/dist/video-js.css import videojs-contrib-hls export default { name: VideoPlayer, props: { src: { type: String, required: true } }, mounted() { this.player videojs(my-player, { sources: [{ src: this.src, type: this.src.endsWith(.m3u8) ? application/x-mpegURL : video/mp4 }] }) }, beforeDestroy() { if (this.player) { this.player.dispose() } } } /script注意videojs-contrib-hls这个插件在 video.js 7 之后的版本里已经被内置支持了所以如果你的 video.js 版本比较新可能不需要手动装这个插件。我建议直接用 video.js 7 以上版本省掉很多兼容性问题。3.3 Axios 封装与 token 注入前端所有接口请求我都通过 axios 发起统一封装到一个request.js文件里。为什么不直接在组件里调用 axios因为统一封装之后才能做公共的请求头处理、错误拦截和加载状态管理。下面是核心的封装代码import axios from axios import { Message } from element-ui import router from /router const request axios.create({ baseURL: process.env.VUE_APP_BASE_URL, timeout: 15000 }) // 请求拦截器自动携带 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理错误 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) localStorage.removeItem(userInfo) router.push(/login) } else { Message.error(网络异常请稍后重试) } return Promise.reject(error) } ) export default request这里有个细节值得注意我在前端项目根目录创建了.env.development和.env.production两个环境变量文件分别配置不同环境的后端接口地址。开发环境用http://localhost:8080/api生产环境用公网 IP 或域名。这样项目部署到服务器时不需要改任何代码只需要修改环境变量文件就行。3.4 视频上传与文件存储方案前端做视频上传时有现成的 Element UI 上传组件可以复用。但直接走 axios 上传大视频文件会遇到超时和内存占用问题。我的做法是前端先把视频文件传给后端接口后端把文件落盘后返回文件的访问 URL前端再把 URL 作为参数提交到视频信息接口。后端文件上传接口重点是配置 SpringBoot 的文件大小限制。默认情况下 SpringBoot 限制单次请求文件大小为 1MB视频文件动不动几百 MB就必须在application.yml里放开限制spring: servlet: multipart: max-file-size: 500MB max-request-size: 500MB另外还要注意一点本地开发时文件存在项目目录下的upload/文件夹里但 SpringBoot 项目打 jar 包部署后项目内部的文件路径是临时的比较稳妥的做法是把上传文件放到一个固定的磁盘目录然后通过资源映射把这个目录暴露成静态资源访问路径。配置一个静态资源映射让/upload/**路径指向实际的存储目录这样前端就能直接通过 URL 访问到视频和图片Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadDir /); } }这里必须强调upload-dir一定要配成绝对路径比如/home/deploy/dance-upload/不能配相对路径。我一开始就吃了这个亏配成./upload在 IDEA 里跑没问题部署到服务器上 jar 包所在目录一变文件就 404 了。4. 接口文档设计与前后端联调经验4.1 接口文档应该包含什么毕设项目里接口文档的重要性被很多人低估了。说实话代码能跑起来不代表你会写接口文档但答辩的时候老师肯定会翻你的文档目录。我这套项目额外准备了完整的接口文档用 Markdown 格式写的命名很直白接口文档.md放在项目根目录的docs/文件夹下。一篇合格的接口文档每个接口至少要包含以下几个部分接口名称和地址比如POST /api/user/login请求参数说明参数名、类型、是否必填、示例值请求示例JSON 格式响应结果示例JSON 格式状态码说明哪些情况返回什么业务码比如登录接口的文档模板如下### 1. 用户登录 - **接口地址**POST /api/user/login - **请求头**Content-Type: application/json - **请求参数** | 参数名 | 类型 | 必填 | 说明 | | ------- | ------ | ---- | ------ | | username | string | 是 | 用户名 | | password | string | 是 | 密码 | - **请求示例** json { username: admin, password: 123456 }响应示例{ code: 200, message: 登录成功, data: { token: eyJhbGciOiJIUzI1NiJ9..., userInfo: { id: 1000, username: admin, nickname: 管理员, avatar: /upload/avatar/xxx.jpg, role: 2 } } }每个视频播放、文章发布、评论提交的接口我都按这个规范整理了一遍前后端联调时直接看文档就可以对接不需要反复问后端这个接口返回什么字段效率提升很明显。 ### 4.2 统一跨域配置方案 前后端分离项目必须处理跨域问题。如果你的后端端口是 8080前端开发服务器是 8081两个端口不同浏览器的同源策略就会拦截请求。 解决跨域有三种常用方式后端加 CrossOrigin 注解、前端配置 devServer 代理、后端写全局 CORS 配置类。推荐用后端全局配置类的方式因为一次配好后端所有接口都能访问不用每个 Controller 单独处理 java Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意一个细节如果你用allowedOrigins(*)配合allowCredentials(true)在某些 SpringBoot 版本里会报错提示不允许这样配置。换成allowedOriginPatterns(*)就没这个问题这是版本升级带来的行为变化网上很多老教程还在用旧写法照抄容易踩坑。4.3 联调过程中常见的三类问题前后端联调是最容易出 bug 的阶段我总结下来 90% 的问题出在三个方面第一种是字段名对不上。后端实体定义的字段叫createTime前端拿到的是 JSON 里的createTime但组件模板里写的却是create_time。后端返回的 JSON 字段名取决于实体类的字段名你如果用了 Map 返回那 key 就是数据库字段名。解决办法是前后端约定后端统一返回驼峰命名的 JSON 字段。SpringBoot 的 Jackson 默认就把实体字段名序列化成驼峰格式没有问题。要小心的是 MyBatis 查询时如果用了as自定义别名一定要写驼峰命名不然返回到前端就对不上。第二种是日期时间格式不统一。后端返回2025-01-15T10:30:00前端显示不出来要的效果。解决办法是在application.yml里统一 JSON 时间格式化spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8加完这个配置所有接口返回的时间字段都会自动转成常用格式前端拿到手直接显示即可。第三种是接口 404 但代码看起来没问题。这种大概率是路径拼错了。举个例子后端 Controller 的类上面写了RequestMapping(/api/user)方法上是PostMapping(/login)完整路径就是/api/user/login。但如果你在 Controller 里面根本没有加类级RequestMapping那完整路径就变成/login前端调用时就会 404。联调时遇到 404先打开浏览器 F12 的 Network 面板看一下实际请求的 URL再对比接口文档大概率是路径问题。5. 环境配置、部署运行与常见问题排查5.1 后端从源码到运行总共分几步很多同学直接拿到别人的毕设源码之后不知道从哪下手这里给出我实操验证过的步骤第一步确保本机环境里有 JDK 1.8 或以上版本、Maven 3.6、MySQL 5.7 或 8.0。检查命令java -version mvn -v mysql --version第二步创建数据库并导入 SQL 脚本。用 Navicat 或者命令行都行mysql -u root -p dance_community.sql第三步修改后端项目里的数据库连接配置application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/dance_community?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver第四步启动后端项目。cd dance-backend mvn spring-boot:run看到Started Application in x.xx seconds字样且控制台没有报错说明后端启动成功默认端口 8080。如果遇到端口被占用可以在application.yml里改server: port: 80815.2 前端环境安装与依赖版本避坑前端项目拿到手之后第一步不是npm run serve而是先装依赖cd dance-frontend npm installnpm install这一步碰到的坑最多我从实际经验里总结几个高频问题Node 版本太高导致 node-sass 装不上。很多基于 Vue 2 的老项目用的是 node-sass 编译样式它对 Node 版本极其敏感Node 17 以上大概率编译报错。解决办法要么降到 Node 14 或 16要么改用sassdart-sass。如果项目里本来装的是node-sass可以手动卸载后安装 sassnpm uninstall node-sass npm install sass --save-dev依赖安装速度太慢怎么办如果你在国内先把 npm 源切到镜像npm config set registry https://registry.npmmirror.com安装完成后启动开发服务器npm run serve前端默认跑在 8081 端口浏览器访问http://localhost:8081就能看到页面。如果要用 8080 端口可以在vue.config.js里配module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这里如果用代理转发前端请求/api/user/login后端就不需要单独配 CORS 了代理把请求转发到后端同源端口跨域问题直接不存在。但我项目中同时保留了后端 CORS 配置这样即使前端不走代理也能访问两种方案都兼容适合同事合作开发时不同环境的切换。5.3 打包部署本地开发与服务器上线的差异开发阶段用npm run serve就够了但最后提交毕设或者实际部署需要把前端打包成静态文件交给后端或者 Nginx 托管。前端打包命令npm run build打包完成后dist 目录下生成一堆静态文件。你可以直接把 dist 文件夹用 Nginx 托管然后 Nginx 把/api开头的请求反向代理到后端服务。以 Nginx 为例server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /home/deploy/dance-frontend/dist; index index.html; try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 上传文件访问 location /upload/ { alias /home/deploy/dance-upload/; } }这里最大的坑是try_files $uri $uri/ /index.html;这一行。Vue 是单页应用路由切换靠前端 JS 实现如果刷新一个/video/123这样的路由服务器找不到这个文件就会 404。加了这行配置所有请求都会回退到 index.html由 Vue 路由接管继续渲染页面。不写这行刷新页面就白屏这是部署阶段极其常见的问题。后端打 jar 包部署cd dance-backend mvn clean package -DskipTests java -jar target/dance-backend-0.0.1-SNAPSHOT.jar用nohup让服务在后台常驻nohup java -jar dance-backend-0.0.1-SNAPSHOT.jar app.log 21 日志输出到 app.log 文件出问题了可以直接查看日志排查。5.4 上线后必查的经典问题部署之后可能遇到两个非常经典的问题我这里提前帮你排掉。第一个是Invalid bound statement (not found)。这个报错说明 MyBatis 没有扫描到 XML 映射文件。原因通常是mapper目录不在application.yml配置的扫描路径里。解决办法是加配置mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.dance.entity第二个是 CSS/JS 文件加载 404。前端打包后默认资源路径是绝对的/js/xxx.js如果部署在服务器的子路径下比如http://xxx.com/dance/资源就找不到。解决办法是在vue.config.js里设置publicPath: ./让打包出来的资源使用相对路径。5.5 数据库时区与字符集问题跨时区部署或者使用 MySQL 8.0 时数据库连接串里必须加上serverTimezoneAsia/Shanghai否则启动会直接报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这个报错如果你没处理过会觉得莫名其妙但其实只要加上时区参数就好了。字符集方面建议数据库连接串带characterEncodingutf8同时建表时统一用utf8mb4。之前提过utf8mb4支持完整的 Unicode包括 emoji 表情。很多人小程序评论里输入 emoji 导致插入数据库报错就是因为表用的是utf8utf8只是utf8mb4的一个子集存不了 emoji 字符。古典舞交流平台虽然用户主要以文字交流为主但也不能排除用户贴个 emoji 表情的情况统一utf8mb4一劳永逸。6. 毕设答辩重点梳理与项目扩展思路6.1 答辩时老师最爱追问的问题做了这么多毕设项目我大概摸清了评阅老师的提问套路。这个项目答辩时,大概率会从下面几个角度被反复追问为什么选 SpringBoot它和 SpringMVC 有什么区别SpringBoot 的自动配置原理是什么前后端分离的优点和缺点分别是什么JWT 登录认证的完整流程是什么token 存在哪里过期的 token 如何处理MySQL 的索引为什么能提升查询速度你这几个表的索引是怎么设计的视频文件是怎么存储的如果用户量大了之后方案要怎么演进数据库并发量大了怎么优化你们用的连接池是什么这几个问题我建议你提前在文档里准备好答案。特别是 SpringBoot 自动配置原理几乎每次答辩必问。回答的核心思路是SpringBoot 启动类上的SpringBootApplication注解包含EnableAutoConfiguration它通过spring.factories文件加载大量的AutoConfiguration类这些类上有ConditionalOnClass、ConditionalOnMissingBean等条件注解只有当 classpath 下存在对应依赖时才自动装配对应的 Bean。记住这个主线老师再往深问也能接住。6.2 从毕设到真实项目的三个升级方向如果你的时间比较充裕想让这个项目在答辩中脱颖而出可以考虑以下三个扩展方向第一个升级是引入 Redis 做缓存和点赞计数。视频的播放量、点赞数这种高频写入的数据全部打到 MySQL 扛不住高并发。用 Redis 的incr命令做计数定时同步到数据库这个设计讲出来就是亮点。还能把 Token 存到 Redis 里实现真正可控的登录态失效而不是依赖 JWT 的过期时间。第二个升级是视频转码。目前上传统一保存原始文件真正的内容平台需要对视频做转码处理把高清视频转换成多种清晰度。这需要引入 FFmpegSpringBoot 用ProcessBuilder调用 FFmpeg 命令执行转换。虽然复杂度上来了但讲出来技术含量很高。第三个升级是接入云存储。把本地文件存储换成阿里云 OSS 或者腾讯云 COS视频和图片都传到云端通过 CDN 加速分发。项目中只需要替换一个文件上传工具类的实现业务代码完全不用动。6.3 所有源码、脚本和文档应该怎么组织最后一个实用建议整个项目提交前目录结构要整理得清清楚楚。我这套项目的最终组织方式是dance-platform/ ├── backend/ // SpringBoot 后端 │ ├── src/ │ ├── sql/ // SQL 脚本文件 │ ├── docs/ // 接口文档 │ └── pom.xml ├── frontend/ // Vue 前端 │ ├── src/ │ ├── public/ │ └── package.json └── README.md // 项目说明文档README.md 里写清楚项目简介、技术栈、运行步骤、默认账号让人拿到项目后十分钟内能跑起来。这样整个包看起来专业你后续要用它找工作或者交接给别人都方便。最后再分享一点我做完这个项目的体会编程开发最忌讳的是拿着别人的代码跑起来就当学会了。源码依赖要有但更重要的是自己把每一个模块的代码逻辑从头到尾读一遍理解为什么数据表这样设计、为什么接口这样封装、为什么前端要这样组织状态。如果你能在这篇文章提到的每个关键点上停一下对照自己的理解把一个接口的完整链路从头讲到尾那这个毕设你做下来学到的就绝对不止是一份源码而已。