每年到了这个节点,总能在各种群里看到同一个问题:“计算机毕业设计到底选什么题好?”今天想拆的这个题目——计算机毕业设计之springboot基于vue技术的电影音乐网站设计与实现,恰好是我一直觉得很适合拿来当参照系的题。它技术栈主流、功能边界清晰、演示效果够直观,工作量又不会失控,用来梳理一整套前后端分离项目怎么做,是很合适的标本。
这篇文章不会只讲“建表、写接口、写页面”这种流水账,更多是想把背后的选型逻辑、模块怎么拆、视频播放这类硬骨头怎么啃,以及打包部署时踩过的坑一次性讲清楚。不管是本科毕设、课程设计,还是想练手独立开发一个完整站点,都可以照着这个思路搭骨架,再往里面填自己的东西。
1. 项目整体设计与技术栈选型思路
1.1 这个题目为什么值得选
先说一个很现实的问题:毕设选题最怕什么?怕题目大到一个人根本做不完,比如“基于微服务的高并发视频平台”,一听就得崩溃;也怕题目小到没内容,比如“用户管理系统的增删改查”,写出来自己都觉得没脸答辩。电影音乐网站刚好卡在中间。
这个题目天然自带两个核心业务域:电影(视频点播)和音乐(音频播放),再加上用户、评论、收藏、分类、后台管理等通用模块。视频和音频在实现上既有共性,又各有关键点,比如视频需要处理清晰度、播放器兼容性,音频需要考虑列表循环、全局播放不中断。这些点足够支撑一篇毕业设计的创新性描述,比如“实现视频在线点播”“基于HLS协议的流媒体播放”之类,工作量又完全控制在一个学期内能完成的范围内。
更关键的是,SpringBoot加Vue这个组合并不冷门。资料多、轮子全,遇到问题基本都能查到对应的解决方案。作为毕业设计,这意味着你能把大部分精力放在理解和实现上,而不是花大量时间跟冷门技术死磕。用最主流的技术栈,把最扎实的功能做完整,在答辩环节远比堆一个“看起来很高级但说不清楚”的框架要稳妥。
1.2 技术栈选型与版本搭配
后端这侧我强烈建议用Spring Boot 2.7.x,不要一上来就追新用3.x。原因很实际:很多网上现成的教程、博客、开源项目都是基于2.x写的,Spring Boot 3把javax包换成了jakarta,一些老代码直接粘过来会报错。对毕设来说,稳定、资料多比新版本更重要。JDK用1.8版本就够了,很多人在这点上有误解,觉得版本越高越好,其实2.7配JDK 8是非常成熟的组合,兼容性几乎不会出问题。
持久层框架选MyBatis-Plus,而不是原生的MyBatis。原生MyBatis写单表增删改查太啰嗦,要自己写一堆XML。MyBatis-Plus能帮我们把单表CRUD和分页查询在Service层直接搞定,省下的时间可以投入到业务逻辑和前端页面上。数据库用MySQL 8.0,缓存可以引入Redis,用来存首页推荐数据、热门影片榜单这类高频读取的数据,顺带给论文里加点缓存设计的篇幅。
前端这侧选Vue 2.7加Element UI,这个组合对毕设最友好。Element UI的表格、表单、弹窗、分页组件非常完善,后台管理页面一个小时就能拼完。Vue 3配Element Plus当然也可以,但如果你是刚开始看Vue没多久,Vue 2的教程和案例要丰富得多。状态管理用Vuex,路由用Vue Router,HTTP请求统一走axios。这里插一句,Vue 2.7是官方最后一个大版本,还持续维护,安心用没问题。
关于版本这里多提醒一句:千万不要把前端node_modules的版本问题拖到最后再解决。创建一个新Vue项目时,很多报错都来自Node.js版本和脚手架版本不匹配,建议Node 14到16之间,装依赖前先把npm镜像源切换成国内镜像,否则等待时间非常痛苦。
1.3 功能模块怎么划分
功能模块划分直接影响编码效率,我的建议是分成前台和后台两条线来想。前台面向游客和注册用户,核心是内容浏览与播放:首页要有轮播图、热门电影、推荐音乐,电影模块支持分类浏览、关键词搜索、进入详情页获取简介、演员、评分、播放地址,详情页下方要有评论和收藏入口。音乐模块除了列表和搜索,还需要一个整站统一的播放器,用户切换页面时音乐不能断,这是很多初学Vue的人容易忽略的功能点。
后台这侧面向管理员,主要管理内容数据:电影管理包括新增、编辑、上下架、封面图与视频文件上传,音乐管理类似,分类管理维护电影的题材类型和音乐的曲风流派。评论管理用来删除违规内容,用户管理可以查看注册用户列表以及禁用账号。轮播图管理也要做,首页头图换了直接后台操作就行。
不要为了方便省掉后台,哪怕你用数据库脚本直接录数据,也建议把后台做出来。答辩时演示“在后台添加一部电影,前台马上就能看到”是很有说服力的闭环场景,比干讲一堆接口设计更能让老师直观理解你的系统。
2. 后端核心设计与接口实现
2.1 数据库表结构与设计要点
数据表设计不用追求太多表,但每张表都要合理。我用过的核心表大概是这些:用户表(用户ID、用户名、密码、昵称、头像、角色、状态、创建时间)、电影表(电影ID、标题、简介、导演、主演、封面图、视频地址、分类ID、评分、上映年份、播放量、状态、创建时间)、音乐表(音乐ID、歌名、歌手、专辑、封面、音频地址、歌词、分类ID、播放量、状态、创建时间)、分类表、评论表、收藏表、轮播图表。
设计时有三点容易被忽略。第一,密码不要明文存,用Spring Security自带的BCryptPasswordEncoder或者Hutool的MD5加盐都行。我会推荐BCrypt,它是不可逆加密,安全性比MD5好很多。第二,时间字段统一用datetime,不要用时间戳数字,后续前端格式化会头疼。第三,视频和音频地址字段长度至少给到255,很多人用默认的255然后塞长URL就出问题,建议直接设成varchar(500)。
以下是电影表的建表SQL片段,可以直接参考:
CREATE TABLE `t_movie` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '电影ID', `title` varchar(100) NOT NULL COMMENT '电影标题', `cover_url` varchar(500) DEFAULT NULL COMMENT '封面图片地址', `video_url` varchar(500) DEFAULT NULL COMMENT '视频播放地址', `director` varchar(100) DEFAULT NULL COMMENT '导演', `actors` varchar(255) DEFAULT NULL COMMENT '演员', `summary` text COMMENT '电影简介', `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID', `rating` decimal(3,1) DEFAULT NULL COMMENT '评分', `play_count` bigint(20) DEFAULT 0 COMMENT '播放量', `status` tinyint(1) DEFAULT 1 COMMENT '1上架 0下架', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;这个表基本满足前台展示和后台管理所需字段。需要提一下utf8mb4,在MySQL 8里不用它会遇到中文正常但特殊符号入库报错的情况,尽量一步到位。
2.2 统一响应、异常与JWT权限
后端接口设计我习惯定义统一的响应体,前端拿到什么格式都是固定的,不用每个接口单独判断。最常见的做法是定义一个Result类,包含code、msg、data三个字段,code为200表示成功,401表示未认证,500表示服务异常。Controller里所有方法都返回Result,前端axios拦截器里统一判断code再处理。
登录状态用JWT令牌来维护。用户登录成功后服务端生成一个Token返回给前端,前端放到请求头里,服务端通过拦截器校验。这个方案的好处是后端不需要记录会话状态,接口天然支持跨域,理解起来也不难,毕业论文里还能单独写一节“基于Token的认证机制”。拦截器核心逻辑就是放行登录、注册、首页数据等公开接口,其余接口从请求头里解析Token,解析成功就放行,失败就返回401。管理员接口还要额外判断角色字段。
下面是一个拦截器实现的骨架:
public class AuthInterceptor 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 (StrUtil.isBlank(token)) { response.setStatus(401); return false; } // 解析token,这里用JWT工具类 Long userId = JwtUtil.getUserId(token); if (userId == null) { response.setStatus(401); return false; } request.setAttribute("userId", userId); return true; } }权限认证做完之后,后端还需要统一处理异常。做一个全局异常处理器,用@RestControllerAdvice注解,捕获业务异常、参数校验异常和兜底异常,分别返回不同的code和提示信息。这样前端不用每个接口都try-catch,响应拦截器里统一弹提示就行,代码会干净很多。
2.3 接口开发示例与文件上传
接口风格我统一用RESTful风格。比如电影模块的接口可以是:
- GET /api/movie/list 分页查询电影列表
- GET /api/movie/{id} 获取电影详情
- POST /api/movie 新增电影(管理员)
- PUT /api/movie 修改电影
- DELETE /api/movie/{id} 删除电影
分页查询这里用MyBatis-Plus的分页插件。很多人第一次配分页插件会忘记那几行关键配置,直接在Service里用Page方法就查不出分页效果。需要在配置类里注册PaginationInnerInterceptor:
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置好后,Service层写分页查询非常快:
public Result getMovieList(int page, int size, String keyword, Long categoryId) { Page<Movie> p = new Page<>(page, size); LambdaQueryWrapper<Movie> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StrUtil.isNotBlank(keyword), Movie::getTitle, keyword) .eq(categoryId != null, Movie::getCategoryId, categoryId) .orderByDesc(Movie::getCreateTime); movieMapper.selectPage(p, wrapper); return Result.success(p); }文件上传也是网站必备模块。本地上传比较适合毕设项目,就是接收MultipartFile,重命名后保存到指定目录,然后返回可访问的URL。重命名我用UUID加原始文件后缀。这里最容易出事的是上传目录的权限和静态资源映射,Linux上目录权限不对会写不进去;URL访问不到大概率是没有配置资源映射。Spring Boot里需要在配置文件里加一个自定义路径映射,把虚拟路径/upload/**指到真实的磁盘目录。
一个细节是文件大小限制。Spring Boot默认有1MB上传上限,放视频肯定不够,需要在application.yml里设置spring.servlet.multipart.max-file-size和max-request-size,通常设成1024MB或更高。不然上传到一半直接报错,而且报错信息还很隐晦。
3. 前端Vue实现与关键模块拆解
3.1 项目初始化和依赖安装
前端项目用Vue脚手架创建,最省事的是vue create命令。创建时勾选Router、Vuex,CSS预处理器选Less或Sass,后面写样式会舒服很多。项目初始化完成后第一步是把基础依赖装上:axios用来发请求,element-ui负责组件库,nprogress做页面顶部加载进度条,最好再装一个dayjs用来格式化时间。
npm装依赖是个容易出问题的节点。一方面网络问题常见,切换成淘宝镜像源能解决一大半;另一方面是依赖版本冲突,特别是node-sass这种需要编译的包,一不小心就报错。我现在都用sass替代node-sass,不需要编译,兼容性好很多。在Vue 2项目里,装了node-sass后再换sass,只需要安装sass和sass-loader就行,代码基本不用改。
目录结构我习惯这样组织:views放页面组件,router放路由配置,store放Vuex模块,api目录按业务域拆成movie.js、music.js、user.js这类文件,每个文件里统一导出接口调用函数。utils目录放axios实例和token操作工具。组件目录放公共组件,比如音乐播放器、分页、上传组件这些会被多个页面复用的部分。
3.2 路由、状态管理与登录守卫
路由配置的核心是路由懒加载,按需加载页面组件,不然首屏加载会很慢。写法就是component写成箭头函数动态import,比如:
component: () => import('@/views/Home.vue')路由传参是新手最容易搞混的地方。详情页接口需要电影ID,首页跳详情页通常这样写:
this.$router.push({ path: '/movie/detail', query: { id: row.id } });接收用query方式,组件里读取this.$route.query.id。为什么不建议用params?因为params传参刷新页面后会丢失,除非配合动态路由path里显式定义/:id。query传参会把参数暴露在URL上,但就是直观且不丢参。毕设场景下用query够用了。
登录守卫用Vue Router的全局前置守卫实现。思路是定义一个白名单数组,把登录、注册、首页、列表页放在里面,其余路径都要判断token是否存在,不存在就跳转到登录页并带上redirect参数,登录成功后跳回来源页。Vuex里维护用户信息,每次页面刷新时再调用一次获取用户信息的接口,保证刷新后登录态不丢。
Vuex的状态管理除了用户信息外,还要管理播放器全局状态。这个设计是音乐模块的关键,建议单独建一个player模块,state里存当前播放列表、当前歌曲索引、播放状态、播放进度。这样任何页面都可以通过dispatch去切换歌曲、上一曲、下一曲,而播放器组件本身放在App.vue的全局位置,始终渲染在页面右下角。这是很多毕设会忽略的点,很多同学把播放器写在音乐页里,一跳到其他页面音乐就停了,整体感觉立刻掉了一个档次。
3.3 视频播放模块与m3u8处理
视频播放是这个项目的核心技术点。最简单的实现是直接放一个video标签,src指向MP4文件的地址。但实际中你会发现MP4文件如果很大,拖动进度条经常需要缓冲很久;而且当你需要把功能写得更有亮点时,HLS协议是一个很自然的补充点。HLS把视频切成很多个ts小片段,用m3u8文件做索引,播放时可以做到视频一边下载一边播放,切片大小的控制也影响流畅度。
前端播放m3u8,我自己常用的是hls.js这个库,它会把m3u8转成浏览器能播放的媒体流。用起来也不复杂,在你的组件里这样处理:
import Hls from 'hls.js'; initPlayer() { const video = this.$refs.video; if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(this.videoUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play(); }); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = this.videoUrl; } }这里要注意,hls.js的版本不同,部分API会有细微变化,装依赖时最好锁定一个大版本,别直接装latest。视频源的获取路径也很容易踩坑,后端返回的videoUrl如果是“localhost”或内网IP,在别的设备上访问时就废了,所以存储时最好用相对路径,前端再根据当前域名拼完整地址。
另外视频组件销毁时要记得释放hls实例,调用hls.destroy(),否则页面切换多了容易出现内存堆积。
3.4 音乐播放器与全局播放状态
音乐播放器比视频播放器难在状态管理上,而不是播放本身。用audio标签播放MP3非常容易,关键是做到全局不中断、进度可控、列表可切换。我建议把audio对象包在一个全局组件里,放在App.vue模板中,这样组件不会因为路由变化被销毁。
全局播放器的核心逻辑大致是:Vuex里的player模块维护播放状态,任何页面对播放列表发起变更时把歌曲列表和当前索引一起提交;播放器组件watch当前歌曲变化,调用audio.play()和audio.src赋值。播放进度用timeupdate事件更新到Vuex,当前时间下的进度条拖拽则反过来重新设置audio.currentTime。
播放模式这里实现三种:列表循环、单曲循环、随机播放。列表循环就是播放结束后index加一,到末尾回到0;单曲循环是播放结束后倒回0并继续播放;随机播放则用随机数生成索引。模式状态也放到Vuex里,作用于播放结束事件的回调逻辑中。这块不要偷懒不做,答辩时老师很爱问播放器是怎么控制连续播放的,能把自己放在后端存储还是前端队列讲清楚,就是一个小亮点。
自定义v-model在音乐搜索组件里也可以用到。比如做一个搜索框组件,内部管理输入状态,对外通过v-model绑定搜索关键字。这样父组件只要写v-model="keyword",而组件内部用model选项自定义model价值对应的prop和event,实现上很简单,但能体现你对Vue高级用法的掌握,论文里也值得写一小段。
3.5 axios封装与跨域联调
前端请求后端的统一封装是必做的一件事。新建utils/request.js,创建axios实例,设置baseURL为“/api”,然后添加请求拦截器,从localStorage拿token放到headers里。响应拦截器里,如果返回的code是200就resolve数据;code是401就清空登录信息并跳转登录页;其他错误码和网络异常统一弹出Message提示。这套封装做完,所有页面调用接口都简洁很多,也不用反复写错误处理。
开发环境联调,跨域问题怎么解决?最省事的方法是利用Vue CLI的devServer代理。在vue.config.js里配置:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }这样前端发起“/api/movie/list”时,开发服务器会把请求转发到本机8080后端服务。注意后端接口如果本身就是“/api”开头,那前端代理就不需要改写路径;如果后端没有/api前缀,则需要配置pathRewrite把/api去掉再转发。这也是一个常见联调坑,很多人配完代理接口返回404,多半就是路径重写没处理好。
4. 项目打包部署和联调
4.1 后端打包与启动细节
后端打包用Maven即可。在IDEA右侧Maven面板里执行clean然后package,或者用命令行:
mvn clean package -DskipTestsDskipTests是跳过测试,不然每次打包都会跑单元测试,稍微慢几秒钟。打包成功后target目录下会生成一个jar包。启动jar前先在application.yml里检查数据库地址、Redis地址是否改成服务器环境对应的地址,尤其是数据库密码,很多人打包时写的是本地密码,部署到服务器后又不记得改,折腾好久。
启动可以用java -jar命令,但为了后面方便管理,推荐用nohup放到后台执行:
nohup java -jar movie-server.jar --server.port=8080 > app.log 2>&1 &这里有个小坑,服务器上如果8080被占用了,启动会报端口冲突。可以用--server.port临时指定一个新端口,或者用lsof -i:8080查看是谁占用。先把端口搞定再启动,后面排查问题会轻松很多。日志要养成看的习惯,启动卡住时重点看最后几行错误,大多数情况会在日志里直接给出线索。
4.2 前端构建与Nginx部署配置
前端打包执行npm run build,生成dist目录,这个目录里的文件就是所有静态资源。把dist整个目录传到服务器上,配置Nginx指向它。
Nginx配置是所有部署环节里错误发生率最高的地方。我自己用的一个标准配置:
server { listen 80; server_name your-domain.com; root /var/www/movie-frontend/dist; index index.html; location / { 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 /data/movie-project/upload/; } }这里边的try_files那行是关键。如果不写这行,Vue Router用了history模式后,刷新某个非首页路径会瞬间变成404。try_files的意思是先尝试按URL找文件,找不到就回退到index.html,由前端路由接管。这个配置能解决绝大多数刷新白屏问题。
api前缀反向代理到本机8080,和开发代理是一致的。上传目录单独映射,也是为了让视频和图片能通过URL直接访问。三个location配置好,重启Nginx后基本就通了。还有一点,服务器防火墙要放行80端口,否则外网访问不到,很多学生部署完发现打不开页面,查了半天代码才发现是防火墙没开。
4.3 本地开发环境常用调试技巧
开发阶段调试效率非常重要。后端IDEA里直接运行启动类就行,建议加Spring Boot DevTools依赖,这样改完代码会自动重启,不用自己手动停掉再启动。前端用Vue DevTools插件调试组件状态,Vuex的变更记录看得清清楚楚,排查状态问题时强烈建议开着它。
前后端联调时最想吐槽的就是控制台报错太泛,比如axios请求报“Network Error”,第一反应往往是跨域或后端没启动。这时候要按步骤排查:先看后端日志有没有收到请求,收到就说明网络链路通,问题大概率在后端异常;后端没收到就看Nginx或前端代理的配置。这种分层次排查的思路,比看报错瞎猜要高效得多。
数据库问题也很常见,特别是连接超时和时区报错。URL里加上serverTimezone=Asia/Shanghai和useSSL=false能解决多数MySQL连接异常。如果连接被拒,检查MySQL服务是否启动,以及配置文件里的密码是否匹配。Redis连不上也类似,先确认Redis进程在跑,再用redis-cli ping验证连通性。
5. 常见问题与避坑心得
5.1 高频问题排查速查表
项目过程中遇到的问题,我把它整理成一张速查表,给后面做类似项目的人一些参考。
| 问题现象 | 排查方向 | 常见解决方式 |
|---|---|---|
| npm install 长时间卡住或报错 | 网络源、Node版本、依赖冲突 | 切换淘宝镜像、锁定Node版本、用sass替代node-sass |
| 前端接口访问404 | 代理路径写错、后端接口前缀不匹配 | 检查devServer.proxy的pathRewrite是否符合实际 |
| 后端启动报端口被占用 | 端口冲突 | 找出占用进程并kill,或换端口启动 |
| MySQL连接失败 | 驱动版本、URL参数、密码错误 | 确认URL加serverTimezone,驱动与MySQL8匹配 |
| 上传文件超过大小限制 | 未配置multipart上限 | 配置文件里调整max-file-size与max-request-size |
| 视频无法播放 | 文件编码、m3u8源不可达、MIME类型缺失 | 确认地址可访问、检查后缀对应的Content-Type |
| Vue页面刷新404 | 路由模式与Nginx配合问题 | Nginx中配置try_files $uri $uri/ /index.html |
| Token失效后仍能访问 | 拦截器未覆盖或请求头没带 | 检查请求拦截器是否从localStorage读取并设置Token |
| 中文乱码 | 数据库字符集、连接参数 | 库表统一utf8mb4,连接URL设置characterEncoding=UTF-8 |
这张表里很多问题我实际都踩过,而且往往是在答辩前一周集中爆发。所以强烈建议把开发周期往前压,留出充裕时间做部署和全流程演示,不要压到最后几天。
5.2 答辩展示与后续扩展思路
答辩的时候不需要把每个接口都背下来,但要能清楚讲出系统分层:前端Vue负责渲染和交互,后端SpringBoot负责业务和数据,二者通过JSON格式的REST接口通信,登录认证用Token,媒体资源通过静态映射对外提供访问。按照这个逻辑走一遍演示,主线就清楚了。
讲到技术亮点时,可以重点讲这几点:第一,JWT无状态认证是怎么实现的;第二,MyBatis-Plus分页插件解决了什么问题;第三,hls.js让浏览器具备了m3u8点播能力;第四,全局播放器如何借助Vuex实现路由切换不中断。这几点既有技术深度又有业务场景,比笼统说“我用了SpringBoot和Vue”要扎实得多。
如果时间有余力,还可以做几个不影响主流程的小扩展,比如用Redis缓存热门电影和首页轮播数据、用WebSocket做评论实时通知、接一个简单的基于标签的推荐逻辑、把项目加一个Dockerfile然后用docker-compose一键启动。这些扩展每加一个,论文的“系统特色”章节就能多写一段,答辩被问“你这个项目有什么难点”时也更有底气。
最后再多说一句实践感受:这个项目最值得你花时间的不是把页面写得有多炫,而是把每一个接口从数据库到前端页面的完整链路亲手打通一遍。我见过很多同学代码能跑起来,但在这个链路上哪个环节都说不透,这种状态在答辩时非常容易被追问到死角。把链路中的“为什么这样设计”想清楚,比多抄几个功能模块管用得多。做毕设这件事,本质上不是为了应付评审,而是借一个可控的题目,把做事的方法论过一遍,这套方法论以后工作了也一样用得上。