很多做Java毕设的同学,一到选题就头疼。管理系统太老套,电商项目又烂大街,最后答辩时老师看一眼标题就没兴趣了。今天分享这个古典舞在线交流平台,算是一条比较巧的思路——用Spring Boot做后端,把视频展示、课程管理、社区交流这些功能串起来,既贴合文化类热点,又覆盖了Spring Boot的核心知识点。我花了两周左右把完整项目跑通,代码、数据库脚本、部署文档都已整理完毕,免费分享出来,希望能给正在纠结毕设题目的同学一个参考。
1. 项目背景与核心需求拆解
1.1 为什么选“古典舞”这个垂直领域
做毕设选题,第一原则是“让老师觉得你有想法”。泛泛的“视频网站”或“在线教育平台”很难出彩,但加上“古典舞”这个垂直标签之后,整个项目的定位就变了:它是一个带有文化属性的垂直社区。这个定位带来的直接好处有三个:
- 功能上天然需要视频播放、分类检索、用户互动、课程管理等多个模块,适合展示Spring Boot的综合能力。
- 数据模型比普通博客或商城更有层次感,比如舞蹈视频需要按舞种、难度、适用人群分别归类。
- 后续做扩展(比如直播教学、AI动作评分)时,叙事空间很大,答辩时有话可说。
当然,选什么垂直领域其实都可以,重点是要通过这个领域,把“为什么需要这个平台”讲清楚。古典舞在线交流平台的核心用户画像是舞蹈爱好者、培训班学员和独立舞蹈老师。他们的需求是:找优质教学视频、按水平筛选内容、互相交流练习心得、获取系统化课程。
1.2 核心功能模块梳理
从需求倒推,平台必须解决以下问题:
- 用户体系:注册、登录、个人信息维护、角色区分(普通用户/管理员)。
- 内容展示:古典舞视频的分类浏览、关键词搜索、热度排序。
- 互动交流:视频评论、点赞、收藏。
- 课程组织:按主题或难度组织的系列课程,用户可以查看课程详情和章节课时。
- 后台管理:视频、课程、用户、评论的运营管理页面。
一句话概括:这是一个“内容 + 社区 + 管理后台”三位一体的典型业务系统。用Spring Boot实现这样的系统,技术路径成熟、参考资料丰富,非常适合作为毕设。
2. 技术选型与架构设计思路
2.1 后端技术栈:Spring Boot 2.x为核心
后端选择Spring Boot 2.7.x,搭配Spring MVC、Spring Data JPA、Spring Security。很多人纠结用MyBatis还是JPA,我的建议很直接:毕设项目,优先用Spring Data JPA。原因是实体关系映射写起来快,尤其在用户、视频、评论、课程这些表之间有关联查询时,JPA的@OneToMany、@ManyToOne能显著减少代码量。如果你对SQL更熟悉,用MyBatis-Plus也完全没问题,但要注意分页插件和LambdaQueryWrapper的版本兼容。
数据库选MySQL 8.0,ORM层用Hibernate自动建表策略(ddl-auto: update)开发期很方便,但最终交付时要记得改成validate或none,并附上完整的init.sql脚本,避免评审时被问“数据库表结构从哪来的”。
2.2 前端与交互方案
前端用Vue 2 + Element UI这套组合。为什么不是Vue 3?因为对毕设而言,Vue 2的生态最稳定,Element UI组件齐全,网上现成的后台管理模板也多,可以节省大量调样式的时间。页面结构如下:
- 门户端(面向普通用户):首页视频流、视频详情页、课程列表页、个人中心。
- 管理端(面向管理员):数据概览、视频管理、用户管理、评论审核。
如果你不想自己写前端,也可以直接使用开源的后台模板,比如vue-element-admin,只保留需要用的页面。但建议至少手写首页和视频详情页,这两个页面最能体现你对项目的理解。
2.3 整体架构与请求流转
项目采用前后端分离架构,通过RESTful API通信,JWT(JSON Web Token)做身份认证。整个请求流转过程是:
- 前端发起请求,携带token(存储在localStorage中)。
- 后端Spring Security过滤器链校验token,解析出用户ID和角色。
- Controller接收参数,调用Service层处理业务逻辑。
- Service层通过Repository与MySQL交互,返回数据。
- 统一封装Result对象返回给前端,格式为
{ code, message, data }。
这个架构清晰、扩展性好,而且答辩时可以顺势讲出“为什么用JWT而不是Session”——因为前后端分离后,Session跨域处理麻烦,JWT无状态、天然适合分布式部署。虽然毕设不要求分布式,但这个回答显得有深度。
3. 数据库设计与核心表结构
3.1 表设计概览
数据库是整个项目的地基。这里共设计6张核心表,表结构如下:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| user | 用户表 | id, username, password, nickname, avatar, role, create_time |
| video | 视频表 | id, title, cover_url, video_url, category, difficulty, description, play_count, like_count, status, create_time |
| comment | 评论表 | id, video_id, user_id, content, parent_id, create_time |
| course | 课程表 | id, title, description, cover_url, teacher, difficulty, status |
| course_section | 课节表 | id, course_id, title, video_url, duration |
| user_favorite | 收藏表 | id, user_id, video_id, create_time |
这里有个设计细节容易被忽略:comment表中加parent_id字段,是为了支持“回复某条评论”的二级评论功能。相比把所有评论平铺,这种设计更接近真实社区产品。
视频表中加status字段(0下架 1上架)也很有必要。后台审核后上架,前台只展示上架内容,既符合内容管理需求,又能在答辩时体现你考虑过“内容安全”的问题。
3.2 索引与关联关系设计
单表数据量大了之后,索引就很重要了。video表的category和difficulty字段要建普通索引,因为前台会频繁按这两个条件筛选;create_time字段建议建索引,用于按最新排序。comment表的video_id也一定要建索引,否则详情页加载评论时会全表扫描。
实体关联关系上,用户和视频是@OneToMany(一个用户可上传多个视频);视频和评论是@OneToMany(一个视频有多条评论);课程和课节是@OneToMany(一个课程包含多个课时)。在JPA中,这些关系用注解声明后,查询时通过join fetch或实体导航方法就能拿到关联数据。
3.3 用户密码安全处理
用户表的password字段存储的必须是加密后的密文,绝对不能明文保存。使用BCrypt加密,Spring Security内置支持。注册时调用BCryptPasswordEncoder.encode(),登录时用matches()校验。
// 注册时加密 String encryptedPwd = passwordEncoder.encode(user.getPassword()); user.setPassword(encryptedPwd); // 登录时校验 boolean isMatched = passwordEncoder.matches(rawPassword, user.getPassword());这里有一个常见错误:很多同学把password字段长度设置为50,但BCrypt加密后的字符串长度是60,导致插入数据库时报错。字段长度建议直接设为varchar(100),留足余量。
4. 核心功能模块实现详解
4.1 用户注册登录与JWT认证
用户模块的代码结构并不复杂,但它是整个项目的入口。核心流程是:
- 注册接口:接收用户名、密码、昵称等参数,检查用户名是否唯一,加密密码,保存用户。
- 登录接口:校验用户名密码,成功后生成JWT返回前端。
- 获取当前用户接口:前端携带token访问,后端解析出用户信息。
JWT工具类通常包含三个方法:生成token、解析token、校验token。token中存放用户ID和用户名,设置7天有效期。
public String generateToken(Long userId, String username) { Date now = new Date(); Date expireDate = new Date(now.getTime() + 7 * 24 * 3600 * 1000); return Jwts.builder() .setSubject(username) .claim("userId", userId) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }Spring Security配置类中,需要放行/api/auth/login和/api/auth/register接口,其余接口都要认证。前后端联调时,前端需要在Axios请求拦截器中统一加上Authorization: Bearer <token>头。
4.2 视频模块与分页搜索
视频列表页是用户进入平台后看到的第一个页面,性能直接关系到体验。使用Spring Data JPA的分页查询,接收page、size、category、difficulty、keyword参数。
public Page<Video> getVideoList(int page, int size, String category, String difficulty, String keyword) { Pageable pageable = PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createTime")); // 动态拼接查询条件 Specification<Video> spec = (root, query, cb) -> { List<Predicate> predicates = new ArrayList<>(); if (StringUtils.hasText(category)) { predicates.add(cb.equal(root.get("category"), category)); } if (StringUtils.hasText(difficulty)) { predicates.add(cb.equal(root.get("difficulty"), difficulty)); } if (StringUtils.hasText(keyword)) { predicates.add(cb.like(root.get("title"), "%" + keyword + "%")); } return cb.and(predicates.toArray(new Predicate[0])); }; return videoRepository.findAll(spec, pageable); }注意一个细节:PageRequest.of的页码是从0开始的,而前端传过来的page通常从1开始,所以后端要page - 1。这个偏移量问题非常经典,调试接口时很容易忽略,一不注意就会导致前端第一页数据为空。
视频详情页的逻辑是:查询视频基本信息 + 查询该视频下的评论列表 + 播放数加1。播放数加1是典型的“写操作”,直接用UPDATE video SET play_count = play_count + 1 WHERE id = ?,不要先查出来再加再保存,避免并发丢失更新。
4.3 评论与点赞功能
评论功能是“交流平台”的核心。评论列表要按时间倒序,展示评论者昵称、头像、评论内容、评论时间。前端传入videoId和content,后端从token中解析出userId,组装评论实体保存。
二级评论的处理:如果parentId不为空,说明这是一条回复。在DTO中加一个replyCount字段,统计每条一级评论下的回复数量。这部分实现用的是@OneToMany映射,评论实体中增加@ManyToOne关联用户,查询时用join fetch一次性查出评论者信息,避免N+1查询问题。
点赞和收藏的功能逻辑类似:先判断是否存在记录,存在则取消,不存在则新增。通过user_favorite表中user_id和video_id的唯一约束保证数据一致性。点赞数变化时,同步更新video表中的like_count字段。
4.4 课程模块与课时组织
课程模块体现的是“系统化学习”的场景。课程实体包含标题、简介、封面、讲师、难度,课程下挂多个课时。前端课程详情页展示课时列表,每个课时是一个视频。
课程列表页的筛选条件通常包含难度和分类,课时数可以冗余存储在course表中的一个section_count字段,避免每次都要count子查询。这种“冗余字段”的设计在真实项目中很常见,答辩时提一句“用空间换查询效率”,会显得你理解业务。
后台的课程管理就是简单的CRUD,使用Element UI的表格组件展示课程列表,支持新增、编辑、上下架、删除操作。删除课程时要同时删除关联的课节记录,所以Service层要标注@Transactional,保证事务一致性。
5. 关键问题与踩坑记录
5.1 跨域问题导致前端请求失败
前后端分离项目最常见的坑就是跨域。Vue前端跑在8080端口,Spring Boot跑在8081端口,浏览器会拦截非同源的Ajax请求。解决方法是配置CORS(跨域资源共享)过滤器。
@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); } }注意,allowCredentials(true)和allowedOriginPatterns("*")必须配套使用。如果用旧版的allowedOrigins("*"),会出现“origin cannot be null”的报错。
5.2 JWT token失效后前端无感知
token有效期为7天,但用户登录后如果一直不操作,7天后token过期,前端请求会返回401。最直接的处理方式是在前端Axios响应拦截器中判断HTTP状态码,遇到401就清除本地存储的token,跳转登录页。
如果想让体验更好,可以引入Refresh Token机制:在登录时返回两个token(访问token短时效、刷新token长时效),访问token过期后用刷新token去换取新token。毕设做到这个程度,属于加分项。
5.3 数据库方言问题导致分页SQL异常
切换MySQL版本后,有时Hibernate生成的SQL会带上limit ?,但方言配置不正确时,分页查询会报错。确保application.yml中配置的dialect为org.hibernate.dialect.MySQL8Dialect。如果你用的是MySQL 5.7,则改为MySQL5InnoDBDialect。
5.4 上传视频文件到服务器时的配置问题
课程管理或视频管理中,需要上传视频文件。Spring Boot默认单次请求最大文件大小是1MB,显然不够用。需要在配置文件中放大限制:
spring: servlet: multipart: max-file-size: 1024MB max-request-size: 1024MB同时建议把视频文件存储到本地磁盘某个固定目录(如/data/upload/),数据库中保存访问URL,通过自定义静态资源映射暴露出去。
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:/data/upload/"); }很多同学在答辩时演示上传功能,视频一大就报错,基本都是这个配置没改。
5.5 前后端时间格式不一致
后端返回的LocalDateTime默认格式是2025-01-15T10:30:00,前端显示时很丑。在application.yml中统一配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8实体中的日期字段用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")也可以,但建议以全局配置为主,注解只处理特殊字段。
5.6 懒加载异常LazyInitializationException
使用JPA的@ManyToOne关联查询时,如果在Controller层直接返回实体,Session已经关闭,关联对象为null,就会报LazyInitializationException。三种解决方案:
- 在Service层事务内完成关联对象的初始化,把实体转为DTO再返回。
- 查询时使用
@EntityGraph或join fetch,预加载关联对象。 - 配置
spring.jpa.open-in-view: false,并在Service中封装好VO。
最推荐第一种,因为DTO封装还能隐藏掉密码等敏感字段。很多初学者偷懒直接把实体返回前端,虽然能跑,但密码字段也会被序列化出去,这是严重的低级错误。
6. 项目部署与演示要点
6.1 本地开发环境启动流程
整个项目的启动流程,需要按顺序操作:
- 启动MySQL,导入
init.sql脚本,创建数据库和表。 - 修改
application.yml中的数据库用户名密码。 - 启动Redis(如果项目中用到了缓存),这里没有依赖Redis,可以跳过。
- 启动Spring Boot应用,检查控制台日志,确认端口启动成功。
- 启动Vue前端,执行
npm install安装依赖,再执行npm run serve。 - 浏览器访问前端地址,完成注册登录,浏览功能。
前端依赖安装失败是很常见的坑。如果npm install特别慢或报错,可以切换npm镜像源:
npm config set registry https://registry.npmmirror.com6.2 云服务器部署的简易方案
如果想让老师通过外网访问你的项目,最简单的方式是买一台轻量云服务器,部署流程如下:
- 安装JDK 1.8和MySQL。
- 上传后端jar包,使用
nohup java -jar app.jar &后台运行。 - 安装Nginx,配置前端静态文件,并反向代理后端接口。
- 配置安全组规则,开放80端口。
Nginx配置中,核心是处理前端路由的history模式和API代理:
server { listen 80; server_name your_server_ip; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }后端接口路径统一以/api开头,这样反向代理的规则写起来非常顺手。接口前缀统一这件事,在项目开发第一天就要约定好,不然后面改起来很麻烦。
6.3 答辩演示的技巧性准备
答辩演示时,不要光点页面,要一边操作一边解说。建议按以下顺序演示:
- 从注册登录讲起,展示JWT认证的效果(比如未登录时访问个人中心会跳转)。
- 进入首页,演示按分类筛选和关键词搜索。
- 打开视频详情页,展示评论和点赞交互,强调数据实时刷新。
- 进入后台管理,演示添加一个新视频或课程,再回前台刷新确认展示。
- 打开数据库工具,展示数据变化情况,印证前后端联调的真实性。
老师通常会问“你这个项目最大的难点是什么”或“有哪些地方自己思考得比较多”。这时候可以重点讲两个点:一是视频播放数在多用户并发下的更新策略(用SQL自增而非读改写);二是JWT无状态认证相比Session的优势。只要能把这两个点讲清楚,项目的技术深度评价就不会低。
7. 经验总结与扩展方向
开发这个项目踩了不少坑,有几条经验非常值得分享:
- 规划阶段先梳理核心流程再写代码。代码写了一半再改表结构,代价是巨大的。我先画出模块图和数据表关系,再动手写代码,整体推进非常顺。
- 统一返回结构
Result一定要从第一天就定好。如果一开始直接返回实体或Map,后面所有方法都要改,改动量会指数级上升。 - 前端联调时要学会看Network面板和接口返回。很多问题不在代码而在网络请求,学会用浏览器开发者工具排查问题,是每个Java开发者必备的基本功。
- 多写注释和多用日志。毕业设计的代码是可以被翻阅的,注释清晰是一个隐性加分项。
如果学有余力,可以在现有基础上扩展以下方向:
- 引入ElasticSearch实现全文搜索,替代MySQL的like模糊查询,搜索体验会有明显提升。
- 引入Redis缓存热门视频列表,降低数据库压力。这个点答辩时往“高并发优化”方向讲,很有说服力。
- 引入WebSocket实现在线聊天,把“交流平台”的属性再强化一步。
- 集成阿里云OSS保存视频文件,避免本地存储的磁盘空间限制。
最后回到实用层面:这个古典舞在线交流平台的完整源码、数据库脚本以及部署说明文档,我已经整理打包好了。代码可以直接导入IDEA运行,前端npm install后也能直接启动。有需要的同学可以关注获取下载方式。项目中还有不完善的地方,比如视频转码、评论敏感词过滤等,这些留给你自己发挥。写完这个项目,你不仅能交出一份合格的毕设,对Spring Boot的整体掌握也会上一个台阶。