1. 古典舞交流平台,为什么要用这一套技术栈
先把这个项目的定位说清楚。它不是一个简单的展示型网站,而是一个面向古典舞爱好者、舞蹈教师、机构运营者的在线交流与管理系统。除了基础的内容展示,还要承载用户注册登录、视频课程发布、在线播放、评论互动、后台管理、数据统计这一整条业务链路。说白了,它既要有面向普通用户的“前台”,也要有面向管理员的“后台”,属于典型的Web全栈项目。
技术选型上,SpringBoot + Vue + MyBatis + MySQL 这套组合,放在今天依然是Java后端开发里最主流、最稳妥的搭配。我见过的课程设计、毕业设计、企业级原型项目里,十有八九都是这套架构。它的好处非常直接:SpringBoot负责把后端服务快速跑起来,不用像传统SSM那样堆一大堆XML配置;Vue负责前端页面的交互和渲染,组件化开发让页面维护起来不痛苦;MyBatis把数据库操作和Java代码解耦,SQL自己掌控,复杂查询也好调优;MySQL则是开源数据库里最普及的选择,学习成本低,资料多,部署也方便。
这个项目适合谁来参考?如果你是Java后端方向的学生,想做一套拿得出手的课程设计或毕业设计;如果你是企业里需要快速搭建一个内容型管理系统的开发人员;或者你只是想系统性地看看SpringBoot + Vue前后端分离项目到底怎么落地,这套源码都值得仔细过一遍。它把一套真实业务系统的完整链路串了起来,而不是那种只写几个CRUD接口的“玩具项目”。
下文我会从项目架构拆解、核心功能实现、部署实操、踩坑记录、二次开发建议这几个维度,把整个项目翻个底朝天。所有内容都基于我实际搭建和调试这类项目的经验,尽量把关键细节讲透。
2. 项目整体设计与功能模块拆解
2.1 前后端分离架构,到底解决了什么问题
这个项目采用的是前后端分离模式,前端和后端是两个独立的工程,通过RESTful API进行数据交互。前端跑在Node服务或者直接打包成静态文件扔到Nginx里,后端跑在SpringBoot内置的Tomcat里,两边互不干扰。
这个设计有一个非常实际的好处:前后端可以并行开发。前端同学只关心页面长什么样、接口返回什么数据,不用管后端SQL怎么写;后端同学只关心接口返回的结构和数据正确性,不用操心页面样式。哪怕你是一个人做整套项目,分离架构也能让代码更清晰——改前端不会碰到后端,改后端不会影响前端,排查问题的时候边界非常明确。
数据交互的格式统一走JSON,前端拿到之后直接渲染。这个项目里还做了统一响应体设计,比如返回结构是{ code, message, data }这种格式,前端能根据code判断请求是否成功,而不是每次都要自己拼接判断逻辑。这一点对于多人协作或者后期维护特别重要,接口风格统一了,沟通成本会低很多。
2.2 功能模块地图:一个内容型平台需要哪些能力
先梳理一下这个古典舞交流平台的核心功能。它不只是发帖、看视频那么简单,按角色划分,至少要有这样几类能力。
用户端:
- 注册登录:邮箱或手机号注册,密码加密存储,登录后签发Token。
- 舞蹈视频浏览与播放:视频列表、分类筛选、视频详情页、在线播放。
- 课程/文章内容展示:古典舞知识文章、课程介绍、舞者风采展示。
- 评论互动:对视频或文章进行评论、回复、点赞。
- 个人中心:修改资料、查看浏览记录、收藏管理。
管理端:
- 用户管理:查看用户列表、禁用/启用账号、重置密码。
- 内容管理:视频上传、文章发布、分类维护、内容审核。
- 评论管理:删除违规评论、按关键字过滤。
- 数据统计:用户增长曲线、视频播放量排行、评论数量统计。
这些模块听起来多,但落到技术实现上,本质上就是一套围绕“用户—内容—互动”的CRUD加业务逻辑处理。难点不在于单个功能,而在于如何把这一大堆功能组织得有条理,让代码不失控。
我当时接手这类项目的第一件事,不是急着写代码,而是先把表结构设计好。表设计一旦确定,后面的接口开发基本就是体力活。这个项目的表结构大概包括:用户表、角色表、视频表、文章表、分类表、评论表、收藏表、操作日志表。每张表的主键用自增ID,关键字段加索引,时间字段统一用datetime,状态字段用tinyint。这种设计中规中矩,但足够应对当前业务场景,而且扩展起来也容易。
2.3 为什么用MyBatis而不是JPA
选MyBatis一个很重要的原因是SQL可控。古典舞这个领域的内容查询往往会涉及到多表关联、条件拼接、分页统计,用JPA这类ORM框架虽然写起来省事,但一旦遇到复杂查询,要么疯狂拼Specification,要么直接写原生SQL。而MyBatis本质上就是在帮你管理SQL,你把SQL写在Mapper的XML文件里,格式清晰,调优也方便。
另一个原因是国内Java项目的生态惯性。大多数公司的Java技术栈里,MyBatis还是主流,面试也会重点问。你做了这个项目,等于把MyBatis的常用操作都过了一遍,后面找工作聊项目经历的时候,这些点都能拿出来说。比如缓存机制、分页插件、动态SQL、一对多映射,这个项目都会涉及到。
3. 核心功能实现细节与关键代码解析
3.1 用户登录与Token鉴权机制
用户登录是一个系统的地基。这个项目采用的是基于Token的无状态认证方式,流程是这样的:用户提交账号密码,后端校验通过后,生成一个Token返回给前端,前端把它存在本地(一般是localStorage),之后每次请求都在Header里带上这个Token,后端通过拦截器校验Token是否有效。
Token的生成方式用的是JWT(JSON Web Token)。JWT的优势在于服务端不需要保存会话状态,Token本身携带了用户身份信息和过期时间,后端只需要验签即可。用JWT的时候有个要注意的点:密钥要单独配置,不要写死在代码里;过期时间不能设置太长,一般两小时左右比较合理;如果用户密码修改或者账号被禁用,Token是没法立即失效的,所以在拦截器里最好再查一次用户状态。
登录接口的大致逻辑:
@Service public class UserService { @Autowired private UserMapper userMapper; public String login(String username, String password) { User user = userMapper.selectByUsername(username); if (user == null) { throw new BusinessException("用户不存在"); } // 这里注意,密码存储用的是BCrypt加密,不是MD5 if (!BCrypt.checkpw(password, user.getPassword())) { throw new BusinessException("密码错误"); } if (user.getStatus() == 0) { throw new BusinessException("账号已被禁用,请联系管理员"); } return JwtUtil.generateToken(user.getId(), user.getUsername()); } }密码加密这里我要多说一句。很多学生项目还在用MD5加盐,但MD5本身是不安全的,现在GPU暴力碰撞MD5的速度非常快。推荐用BCrypt,它是自适应哈希算法,可以通过增加计算成本来抵御暴力破解,Spring Security里默认也支持。如果项目里没集成Spring Security,单独引入jbcrypt这个库也能用。
3.2 视频上传与播放的实现思路
古典舞交流平台里最核心的内容就是视频。视频上传要考虑文件大小、格式校验、存储位置、访问权限这几个问题。
文件上传:后端接口接收MultipartFile,校验文件类型和后缀名,然后存储到配置的目录下。存储路径建议放在服务器磁盘的某个固定目录,而不是直接放在项目编译目录里,不然重启服务文件就丢了。数据库里只存文件的相对路径,比如/upload/video/2025/06/01/xxx.mp4,这样后面迁移存储位置只需要改配置文件。
在线播放:这个项目里视频播放采用的方式是HTML5的Video标签直接播放MP4文件。但这里有个非常常见的坑:MP4文件的moov元数据如果放在文件末尾,会导致视频无法拖动进度条,必须等整个文件下载完才能播放。解决办法是在上传完成后用FFmpeg做一次转码,把moov元数据移动到文件头部。FFmpeg命令大致如下:
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4对于播放体验要求更高的场景,可以考虑用HLS协议,把视频切成ts分片,通过m3u8索引文件播放。这样视频加载快,拖动进度条也流畅。不过会让系统复杂度上升不少,需要额外的切片和分发机制,小型项目可以先不做。
3.3 评论模块与敏感词过滤
评论是交流平台的核心互动功能。它的实现逻辑不复杂:评论表关联用户ID、视频ID或文章ID、评论内容、父评论ID。父评论ID是为了支持楼中楼回复,没有父评论ID的就是一级评论。
这里有个设计细节值得注意:查询评论列表的时候,不要一次性把某个视频的所有评论都查出来,用户量大之后会非常慢。应该先查一级评论,然后用一级评论的ID批量去查对应的二级回复。这也就是MyBatis里常见的先主后子、分批查询的方式。当然为了减少查询次数,也可以冗余一层——在评论表里加一个root_id字段,表示这条评论属于哪条一级评论,查询的时候直接where root_id in (...)就完事了。
敏感词过滤做的是DFA算法,也就是确定有穷自动机。简单理解就是把敏感词构建成一颗Trie树,遍历文本的时候同步走树节点,能匹配到就替换成*。这个算法在文本长度不大时性能非常可观,几千个敏感词跑一遍也就几毫秒。实现的时候要注意从文本里抠出词时别把英文单词的一部分给误判了,最好对中文按字符处理,对英文按单词处理。
3.4 后台统计报表的数据查询优化
管理端的数据统计模块,涉及用户增长趋势、视频播放排行、评论活跃度这些报表。如果直接对业务表做 count 和 group by,在数据量不大的时候问题不大,但一旦数据量上来,查询会越来越慢。
实际项目里,我倾向于单拆一张统计表,每天通过定时任务把前一天的数据汇总好,报表页面只查汇总表,而不是每次实时去扫业务表。比如用户增长趋势,可以用一个简单的Spring定时任务,每天统计一次总用户数和当日新增用户数,插入统计表:
@Component public class StatisticTask { @Autowired private StatisticMapper statisticMapper; // 每天凌晨1点执行 @Scheduled(cron = "0 0 1 * * ?") public void dailyStatistic() { int totalUserCount = statisticMapper.countTotalUser(); int todayNewUserCount = statisticMapper.countTodayNewUser(); Statistic statistic = new Statistic(); statistic.setStatDate(new Date()); statistic.setTotalUserCount(totalUserCount); statistic.setTodayNewUserCount(todayNewUserCount); statisticMapper.insert(statistic); } }这样做有两个好处:一是报表查询响应快,二是避免统计查询影响线上业务的性能。属于典型的时间换空间思路,项目中遇到类似场景都可以套用。
4. 数据库设计与MyBatis实战要点
4.1 核心表结构设计思路
数据库设计是整个系统的地基。我把这个项目的核心表大概画出来,你可以对照着理解:
用户表(user):id、username、password、nickname、avatar、phone、email、status(0禁用1正常)、create_time、update_time。
视频表(video):id、title、cover_url、video_url、category_id、description、play_count、status(0待审核1已发布2下架)、create_time、update_time。
文章表(article):id、title、content、cover_url、category_id、author_id、view_count、status、create_time。
分类表(category):id、name、parent_id、sort、create_time。
评论表(comment):id、content、user_id、video_id、article_id、parent_id、root_id、like_count、create_time。
收藏表(favorite):id、user_id、target_type(1视频2文章)、target_id、create_time,唯一索引uk_user_target防止重复收藏。
这里有几个设计上的小讲究:
- 所有表都带
create_time和update_time,方便排查问题和做统计。 - 状态字段用
tinyint而不是varchar,存储更省,查询更快,程序里用常量类统一维护状态值。 - 评论表同时关联视频和文章,用
target_type区分,避免拆两张评论表带来重复代码。
4.2 分页查询的三种写法,从最笨到最优
列表页基本逃不掉分页查询。我见过很多项目分页写得非常随意,数据量一大就卡。这里我按从最笨到最优的顺序,把三种写法都列出来。
第一种,最笨但最直观的——手动limit。先查count,再查当前页数据,自己去算偏移量。缺点很明显:每写一个分页接口,就要写两遍SQL,代码冗余,而且count和数据查询之间可能因为数据变动导致总数对不上。
第二种,用PageHelper分页插件。这个是国内MyBatis项目里用烂了的方案,核心用法是:
PageHelper.startPage(pageNum, pageSize); List<Video> list = videoMapper.selectVideoList(categoryId, keyword); PageInfo<Video> pageInfo = new PageInfo<>(list);PageHelper的原理是对MyBatis的Executor做拦截,在SQL执行前自动拼接limit。好处是开发效率高,不用维护count查询。但用的时候要小心一个坑:PageHelper.startPage()只对下一条SQL生效,如果在这中间执行了其他查询,分页就错乱了。还有,分页插件和复杂SQL(比如包含多个嵌套子查询)一起用时,count语句偶尔会拼出问题,需要手动指定countSQL。
第三种,对性能要求高的场景,用游标分页。不传页码,传上一页最后一条记录的ID,用where id < lastId order by id desc limit size这种方式查下一页。它的优势很明显:不管翻到第几页,查询都是走主键索引,数据量大之后性能依然稳定,而且不会出现用户翻到一半,前面新增了数据导致重复的问题。缺点是只能一页一页翻,不能直接跳页。
如果这个项目要考虑高并发场景,我建议把用户端那种Feed流的接口改成游标分页,管理端那种固定页码的表格则继续用PageHelper,各取所长。
4.3 MyBatis缓存机制的正确打开方式
MyBatis的缓存分为一级缓存和二级缓存。一级缓存是SqlSession级别的,默认开启;二级缓存是Mapper级别的,默认关闭,需要手动配置。
一级缓存有个经典坑:在同一个SqlSession里,如果先查了数据,然后执行了任何更新操作(insert/update/delete),一级缓存会被清空,这是正常的。但如果你用Spring管理事务,一个事务里多次查询同一个对象,MyBatis会直接返回缓存里的同一个实例,如果你偷偷改了它的某个字段,后续查询拿到的就是被改过的数据,排查起来很酸爽。解决办法也很简单:查询返回的对象不要直接改它的属性,需要修改就new一个对象再set进去。
二级缓存我一般建议开启,但要注意别把敏感数据也缓存了,比如用户密码就是绝对不能用缓存,否则别人分页查用户列表,某个用户的信息就会一直停留在内存里。另外,多表关联查询的结果如果被二级缓存命中,一旦其中一张表更新了,另外一张表对应的缓存是不会自动失效的。所以二级缓存更适合那些基本不变的数据,比如分类列表,或者一条视频的播放量之外的基础信息。
4.4 动态SQL,让条件查询不再写死
古典舞视频列表页,基本上会有分类筛选、关键词搜索、时间范围筛选这几个条件。如果用传统方式,得在Java代码里拼SQL字符串,容易出SQL注入问题,代码也丑。MyBatis的动态SQL就是专门解决这个问题的。
<select id="selectVideoList" resultType="com.example.entity.Video"> select * from video <where> <if test="categoryId != null"> and category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> and (title like concat('%', #{keyword}, '%') or description like concat('%', #{keyword}, '%')) </if> <if test="status != null"> and status = #{status} </if> </where> order by create_time desc </select>这里的<where>标签会自动去掉开头的and,不用自己写where 1=1这种丑陋的兜底。<if>就是条件判断,标签里的条件为真才会拼进去。这套机制掌握好,几乎所有查询场景都能应付。如果你看到有人用where 1=1,那多半是在用老版本的MyBatis或者不太熟悉动态SQL,遇到这种代码可以直接优化掉。
5. 前端Vue实现与前后端联调细节
5.1 项目初始化与Vue工程结构
前端部分基于Vue全家桶,路由用的Vue Router,状态管理用的Vuex(如果版本是Vue 3也可以用Pinia,但很多现有项目还是Vue 2的栈)。如果这是你接手的源码,第一步先看package.json,确认Vue版本、Element UI或Element Plus版本、Axios版本,再决定怎么启动。
一个合理的Vue工程目录大概是这样的:
src/ ├── api/ # 所有接口请求 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── views/ # 页面组件 │ ├── home/ # 首页 │ ├── video/ # 视频列表/详情 │ ├── article/ # 文章 │ ├── user/ # 个人中心 │ └── admin/ # 管理后台 ├── router/ # 路由配置 ├── store/ # 状态管理 ├── utils/ # 工具函数 └── App.vue这个结构的好处是约定大于配置,新成员拿到项目后扫一眼目录就明白该去哪里改代码。接口统一放在api目录下,而不是在组件里直接写axios.get,这样接口变动时只需要改一个文件。
5.2 路由守卫与权限控制
前端权限控制的核心代码在路由守卫里。用户未登录时,访问需要登录的页面,要跳转到登录页。管理员访问后台管理页面时,要校验角色。
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('role') if (role !== 'admin') { next('/403') return } } next() })这里要强调一点:前端路由守卫只是用户体验层面上的控制,真正的权限校验必须由后端接口来做。前端能拦截的都拦了,但用户直接通过Postman之类工具去调后端接口,后端没有校验的话一样会暴露数据。所以后端接口每个涉及数据操作的请求,都必须在拦截器里校验Token和角色权限。
5.3 Axios封装与错误处理
在实际项目中,Axios请求不会直接在每个页面里写。一般会封装成一个统一的请求模块,把BaseURL、超时时间、请求头设置、响应拦截逻辑都放在一起。
// utils/request.js import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 15000 }) // 请求拦截器:自动携带Token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) // 响应拦截器:统一处理错误码 service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message || '请求失败') if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { Message.error('网络异常,请稍后重试') return Promise.reject(error) } ) export default service这里有几个细节值得留意。超时时间要根据网络环境设置,15秒是一个比较合理的默认值;响应拦截器里只要是code 401就强制跳登录页,这是全项目登出逻辑的统一出口;错误提示用统一组件处理,避免每个页面里散落着各种风格不一的报错弹窗。
5.4 视频播放组件与m3u8支持
如果视频采用的方案是HLS(m3u8),那么HTML5原生Video是播不了的,需要引入hls.js这个库。使用方式如下:
import Hls from 'hls.js' if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(videoUrl) hls.attachMedia(videoElement) }一个值得注意的坑是:跨域访问m3u8和ts分片时,后端要做跨域配置(CORS),不然前端会一直报跨域错误。另外,如果视频是在本地开发环境调试,路径要注意代理配置,不要让前端直接访问后端服务器上的绝对路径,否则生产环境IP一换就全挂了,务必用相对路径拼接或环境变量管理API地址。
6. 实战部署:从本地启动到服务器上线
6.1 本地开发环境的搭建步骤
拿到源码后的第一步,不是急着看代码,而是先把环境跑起来。我梳理一下完整流程,照着做基本不会卡壳。
后端环境:
- 安装JDK 1.8或更高版本,配置好
JAVA_HOME环境变量。 - 安装Maven 3.6+,配置好
MAVEN_HOME,并设置国内镜像仓库,不然依赖下载可能慢到怀疑人生。 - 安装MySQL 5.7或8.0版本,新建数据库,导入项目附带的
sql文件。 - 修改
application.yml里的数据库连接地址、用户名、密码。 - 启动项目,观察控制台日志,看到
Started Application in xx seconds就是启动成功了。
前端环境:
- 安装Node.js 14或16版本,npm会随Node一起安装。
- 进入前端工程目录,运行
npm install安装依赖。 - 修改前端接口代理配置,本地开发时把
/api代理到后端地址。 - 运行
npm run dev,浏览器访问本地端口。
这里有个新手容易踩的坑:npm install报错的时候,不要盲目去改代码,大部分情况是Node版本和项目依赖不兼容。比如老的Vue 2项目配Node 18+,经常会出现node-sass编译报错,解决办法是换成Node 14,或者把node-sass换成sass(dart-sass)。安装依赖之前先看下项目里的engines字段声明。
6.2 前后端联调时的接口代理配置
本地开发时,前后端是两套服务,不同端口,跨域是必然的。处理方式有两种:一种是后端开启CORS,前端直接请求全路径;另一种是更好的做法——前端用Webpack或Vite的代理能力,把/api开头的请求转发到后端地址。
以Vue CLI为例,在vue.config.js中配置:
module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true } } } }这样前端代码里请求/api/video/list,开发环境下会被代理到http://localhost:8081/api/video/list。生产环境就把后端接口部署为http://你的域名/api的路径,配合Nginx反向代理转发到后端服务。这样前端代码里的请求路径完全不用改,环境切换零成本。
6.3 服务器部署的核心流程
服务器部署我推荐用这种思路:后端打成Jar包,用systemd或Supervisor守护进程;前端打包成静态文件,用Nginx托管;MySQL和Redis(如果用到)直接装到服务器上,或者用云数据库。
后端打包之前,记得先执行mvn clean package -DskipTests,跳过测试能节省大量时间。打出来的jar包放到服务器的指定目录,然后用如下方式启动:
nohup java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &这里我推荐用--spring.profiles.active=prod指定生产环境配置。项目里维护application-dev.yml和application-prod.yml两套配置,开发环境和生产环境用不同的数据库账号、文件上传路径、日志级别。数据库密码不要明文存在配置文件里,可以用环境变量或者配置中心管理。
前端打包执行npm run build,生成的dist目录里就是所有静态文件,复制到服务器上,Nginx配置好根目录指向它就行。Nginx还有一个重要的配置是反向代理/api请求到后端服务:
server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 前端路由刷新404问题的关键配置 location / { try_files $uri $uri/ /index.html; } }最后那个try_files配置非常重要。Vue Router开启history模式后,刷新非首页页面会404,就是因为Nginx找不到对应的静态文件。加上这行,所有请求都回退到index.html,由前端路由自己接管。
7. 高频问题与坑位复盘
7.1 数据库连接相关
问题1:启动项目时数据库连不上。
先看application.yml里的数据库地址、端口、用户名、密码是否正确;再确认MySQL服务是否启动,本机能不能用命令行连上;最后看防火墙有没有拦3306端口。如果数据库连接方式有SSL报错,在URL后面加上useSSL=false&serverTimezone=Asia/Shanghai就可以解决。
问题2:MySQL 8.0连接驱动是com.mysql.cj.jdbc.Driver,不是老的com.mysql.jdbc.Driver。
用老驱动连接MySQL 8.0会直接报驱动类找不到。项目里如果用的MySQL版本不同,记得同步调整pom.xml里的驱动依赖版本和配置。
问题3:中文乱码。
确保数据库、表、字段的字符集都是utf8mb4,后端连接URL里加上characterEncoding=utf8,前端页面HTML头部有charset=UTF-8。这三处都对了,乱码基本不会出现。
7.2 MyBatis常见问题
问题1:Invalid bound statement (not found)。
这个报错很常见。原因一般是Mapper接口和XML文件没有匹配上。检查点有三个:Mapper接口的全限定名和XML文件里的namespace是否一致;接口方法名和XML里SQL标签的id是否一致;XML文件是否被Maven打包到了classes目录(如果XML放在src/main/java下,需要在pom里配置resources包含xml文件)。
问题2:分页插件不生效。
确认PageHelper版本和MyBatis版本兼容;确认startPage后面紧跟的确实是你要分页的那条查询SQL;项目中如果用Spring Boot,要确保分页插件被Spring管理且没有被重复注册,重复注册在框架升级后经常会出莫名其妙的问题。
问题3:查询结果明明有数据,映射到Java对象却全是null。
大概率是数据库字段和下划线转驼峰映射没有开启。在application.yml里配置:
mybatis: configuration: map-underscore-to-camel-case: true这样create_time就能自动映射到Java对象里的createTime字段。
7.3 前端排查经验
问题1:前端页面白屏,F12报错。
Vue项目最常见的是组件引入路径写错,或者某个依赖没安装。另外,Node版本过高导致编译失败也会白屏。排查思路是先看控制台有没有编译错误,再看Network里请求是否正常返回。
问题2:接口请求返回跨域错误。
本地开发优先用代理解决,不要直接在后端开allowCredentials+ 指定域名的复杂跨域配置。如果业务场景确实需要开放跨域,后端可以用CorsFilter统一处理。生产环境用Nginx做同源代理,一劳永逸。
7.4 部署上线相关
问题1:配置了Nginx后,刷新页面404。
按上文配置里加location / { try_files $uri $uri/ /index.html; }这行即可。
问题2:上传的视频或图片,前端访问不到。
这是因为上传的文件保存在后端服务器的磁盘路径,Nginx没托管这个目录。在Nginx配置里加上对应目录的静态映射:
location /upload/ { alias /data/app/upload/; }这样才能保证上传的文件可以直接通过URL访问。
问题3:服务器内存不够,项目启动后被杀掉。
Java应用默认JVM会申请物理内存的1/4作为堆内存,云服务器如果只有1G内存,应用很容易OOM被杀。启动时限制一下内存:
java -Xms256m -Xmx512m -jar demo.jar业务量不大时,512M堆内存足够支撑这套系统。
8. 源码的二次开发方向与个人建议
如果你拿到了这套源码,我强烈建议不要只满足于“能跑起来”。源码最大的价值在于可以成为你进一步学习的跳板。我个人给几个明确的二次开发方向。
方向一:接入Redis做缓存和会话管理。视频播放量、热门排行这些数据可以用Redis缓存,减轻数据库压力。用户的Token也可以从JWT换成Redis会话,这样响应速度和系统吞吐量都会有一个明显改善。
方向二:引入消息队列处理视频转码。现在的视频上传是直接存储,如果视频文件很大,上传体验会很差。可以改成异步方式:上传后立即返回成功,后台用RabbitMQ或RocketMQ通知转码服务,转码完成后回写视频地址,前端轮询转码状态。这个架构一上,整个系统立马就带上了生产级色彩。
方向三:补充一些运营层面的功能。比如首页Banner轮播管理、推送通知、站内信、积分体系、学习打卡、直播预告。这些功能单独看都不复杂,但组合起来会让平台更像一个真正运营中的产品,也让你在写简历的时候有更多可描述的亮点。
方向四:前端体验打磨。视频播放页增加“猜你喜欢”推荐、“学习进度记录”、评论区的排序和点赞、移动端适配。很多古典舞爱好者会用手机浏览,一套响应式前端能极大提升实际使用体验。
这个项目最打动我的地方在于,它的技术栈非常标准,结构非常清晰。你把这个项目吃透,基本上就掌握了SpringBoot + Vue全栈开发的主干技能。后面无论做嵌入式方向、大数据方向还是别的业务系统的开发,这套思维方式都可以平移过去:先梳理业务流程,再设计数据库,然后后端接口,最后前端联调,部署上线后持续收集问题迭代。
把这份源码里每一个功能都自己动手敲一遍、改一遍、坏一遍再修好一遍,比看十遍教程管用得多。中间踩过的每一个坑,都会变成你后面面试和工作里的底气。