这两年看过的毕业设计项目里,音乐电影网站系统算是最常见也最容易被做砸的选题之一。很多同学一听到"springboot基于Java的音乐电影网站系统",第一反应是"不就是几个CRUD页面拼在一起",结果代码写得一团乱,文件上传路径写死、播放器对接不上、数据库表设计得一塌糊涂,答辩时被老师一问就露馅。这个项目表面上是做个网站,实际上考察的是完整项目的组织能力:业务建模、数据表设计、文件处理、前后端交互、异常处理、部署上线,每一环都藏着一堆可以深挖的点。这篇文章会把这套系统的架构选型、模块设计、数据库、核心代码、部署坑、答辩高频问题从头到尾摊开讲一遍,给正在做这个题目的同学一份能直接对着干活的参考。
1. 为什么音乐电影网站是Java毕设里的"高性价比"选题
先聊点实际的。音乐电影网站这个选题能在毕业设计里常年不衰,不是因为老师对这个题目情有独钟,而是它天然涵盖了一个完整业务系统的全部要素。它既有面向用户的展示和交互,又有面向管理员的内容管理,还牵扯到文件存储和播放这类稍有不慎就翻车的硬骨头。换句话说,它比单纯的学生管理系统多了一层"媒体内容"的复杂度,又没有电商系统那么庞大的订单和支付逻辑,恰好卡在一个"能做完、又能讲出东西"的位置。
1.1 这个项目真正的难点不在增删改查
我看过不少学生写的音乐网站,页面做得花里胡哨,但数据库里就一张表,把歌曲、歌手、专辑、分类全塞在一个字段里,这属于典型的"用Excel思维做系统"。真正的音乐电影网站,核心矛盾是内容数据与用户数据之间的多对多关系:一个用户可以收藏多首歌,一首歌可以被多个用户收藏,评论挂在歌曲上还是挂在用户上,收藏之后用户取消收藏会不会产生脏数据,这些关系如果不在一开始设计清楚,后面每一个功能都会纠缠在一起。
还有一个容易被忽视的点是文件的处理方式。音乐和电影都是资源文件,不是简单存个URL就完事。上传的封面图怎么压缩,音频文件存本地还是走对象存储,视频太大的时候前端怎么播放,这些都是这个项目区别于普通管理系统的地方。把这些想通的人,和只会把文件存到本地磁盘的人,代码质量完全是两个层级。
1.2 能力覆盖面决定了它的答辩上限
音乐电影网站适合当毕设的另一个原因,是它给了学生足够多的"讲点"。技术选型可以说为什么用Spring Boot不用SSH;性能优化可以说列表页接口怎么避免N+1查询;安全性可以说密码怎么加密、JWT怎么验证;部署可以说jar包怎么打、服务器怎么配。哪怕只做到中等完成度,可以展示的内容也比一个简单的记账本系统多得多。
当然,这也意味着如果你只是把别人的源码跑起来、看着能点,却讲不出任何设计理由,答辩时就会非常难看。老师一年要看几十个Spring Boot项目,代码是不是自己写的、有没有真正理解业务模型,问两个问题就能试出来。那接下来这篇内容,我不会去贴一份完整源码,那没有意义。我重点拆解的是这个项目从零到落地过程中,每一个关键节点你该怎么做、为什么这么做、以及做的时候容易踩哪些坑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:别一上来就问"用哪个版本"这类问题
技术选型不是一个填空题,而是一道权衡题。同一个音乐电影网站,可以用Spring Boot 2.7配JDK 8,也可以用Spring Boot 3.2配JDK 17,还可以把前端换成Vue做前后端完全分离。不同的选择没有绝对的对错,但每一步选择都会影响后面开发的顺畅程度和答辩时的解释逻辑。
2.1 Spring Boot版本和JDK版本怎么搭配才不打架
我接触过的毕设项目里,Spring Boot 2.7.x和3.x是最常见的两个阵营。如果你打算用JDK 8开发,那就老老实实用Spring Boot 2.7.x,因为Spring Boot 3.0开始最低要求JDK 17,这个约束是硬性的,装不上就是装不上。反过来,如果你电脑上装的是JDK 17或更高版本,再用Spring Boot 2.x有时会遇到一些依赖兼容问题,倒不如直接上3.x,还能在文档里写一句"使用了最新的Jakarta EE规范"。
就这个课题而言,我给大多数人的建议是:Spring Boot 2.7.x + JDK 8 + MyBatis Plus。原因很简单,网上参考资料最多,遇到问题搜得到答案,而且绝大多数高校的毕业设计环境还停留在JDK 8。如果你的目标是求稳,不要在这个节骨眼上尝试"最新版本",稳定跑通比版本新更重要。当然,如果你已经装了IDEA 2023以上版本且默认配置JDK 17,也可以用Spring Boot 3.x,只是注意把MyBatis Plus换成支持3.x的版本(3.5.3以上),避免踩到驱动类名变更的坑。
2.2 ORM选型:MyBatis Plus是当前性价比最高的选择
音乐电影网站的业务逻辑不算复杂,但涉及多表联查的地方很多,比如"查询某个分类下所有电影并带上评分"、"查询某个用户收藏的所有歌曲并关联出歌手信息"。用MyBatis Plus写单表操作几乎是零成本,自带的BaseMapper把增删改查全封装好了,分页插件PaginationInnerInterceptor一行配置搞定,不需要自己写一堆重复的XML。遇到多表联查时再手写SQL放在Mapper的XML文件里,这就叫"符合直觉的开发方式"。
有些教程会推荐Spring Data JPA,它的思想是"用面向对象的方式操作数据库",设计得好确实很优雅,但对于绝大多数没接触过JPA的毕设学生来说,学习曲线太陡了,而且在做联表查询、动态SQL、复杂排序时反而更费劲。相比之下,MyBatis Plus在国内的社区资料和毕设现成代码量都是最大的,遇到报错十有八九能直接搜到一样的问题。
2.3 前端方案:Thymeleaf还是前后端分离
这是这个项目里最需要你自己拿主意的分叉路口。用Thymeleaf做服务端渲染,最大的好处是项目结构简单,Controller返回页面名称,内置模板引擎把数据填充进HTML,一个Spring Boot应用就能把前端后端全部跑起来,部署时只需要打一个jar包。这种方案适合想快速做出完整效果、又不想额外折腾Node环境和Vue项目的同学。
前后端分离则是用Vue(通常配合Element UI或Vue Element Admin)做管理端,用Vite或Webpack打包,再通过Nginx或Spring Boot的静态资源映射来访问。这套方案的优点在于页面交互更流畅、调试时前后端可以分开进行,也更贴近企业中的实际开发模式;代价是你至少要搭建一套Vue工程,还要解决跨域问题,整体工作量会多出三分之一左右。
我的建议是:如果你对自己Java后端的掌控力一般,优先选择Thymeleaf,把精力放到后端业务和数据库上,保证核心逻辑稳稳当当;如果你已经会Vue,或者想借着毕设学一下前后端分离,那就直接上Vue,这个技能在简历上比Thymeleaf值钱得多。关键还是那句话,不要两头都想抓,最后两头都没做好。
| 对比维度 | Thymeleaf服务端渲染 | Vue前后端分离 |
|---|---|---|
| 项目结构 | 单工程,结构简单 | 前后端两个工程,部署稍复杂 |
| 跨域问题 | 不存在 | 需要处理CORS或配置代理 |
| 交互体验 | 刷新页面为主 | 局部刷新,交互流畅 |
| 学习成本 | 低,几天能上手 | 较高,需要理解Vue生命周期和组件思想 |
| 简历价值 | 一般 | 更好,贴近企业常见工作流 |
3. 核心功能模块拆解:从登录到播放,每个模块的边界在哪
把技术框架定下来之后,接下来就是要设计功能模块的边界。很多人的项目代码混乱,根源就是模块边界没想清楚,业务逻辑散落在Controller里,Service写成一层皮,导致后期改一个需求要动五六处代码。音乐电影网站按照角色和业务动作,拆成下面几个模块是最清晰的。
3.1 用户模块:注册、登录、JWT与权限拦截
用户模块是整个系统的地基。注册时对密码的处理有一个很容易被答辩老师盯上的点:密码到底该不该加密存储?直接明文存数据库是绝对不可接受的,至少要用MD5加盐或者BCrypt做哈希。Spring Security自带的BCryptPasswordEncoder用起来很省心,encode方法生成带随机盐的哈希串,每次比对时直接matches就行,不用自己琢磨盐怎么存。
登录成功之后,最常用的方案是签发一个JWT,把用户ID、用户名、过期时间放进去,前端拿到Token后存在本地,每次请求在请求头里带上Authorization: Bearer <token>。然后后端写一个拦截器(HandlerInterceptor),在preHandle方法里校验Token的合法性,如果校验失败直接返回401。这里最容易踩的坑是拦截器会连登录接口也拦掉,需要在WebMvcConfigurer里把/user/login和/user/register一次性放行,同时把播放相关的公开接口也放行,不然前端拿不到数据就白调了。
3.2 音乐模块:歌曲上传、文件存储与播放器对接
音乐模块是这个项目的第一个"媒体类"功能。后台管理员上传歌曲时,前端传一个MultipartFile文件,后端通过transferTo方法保存到指定目录。这里有一个我反复强调的坑:千万不要把文件路径写死在代码里,比如D:/music/upload/这种。正确做法是放在配置文件里,然后通过@Value注解读进来,部署到服务器时只需要改配置不需要改代码。
文件保存之后需要把可访问的URL返回给前端,这才是前端真正用来请求的地址。如果用的是Spring Boot内置的静态资源映射,需要重写addResourceHandlers方法,把文件系统里的目录映射成一个以/upload/**开头的URL路径。播放器对接倒不需要多复杂的操作,前端用<audio>标签塞入音频URL就能实现播放,重点是后端要确保这个URL不需要登录也能访问,否则播放器请求的时候会因为没有Token被拦截器拦下来。
3.3 电影模块:分类、检索与详情页的关联数据
电影模块表面上看和音乐模块差不多,但它有更多的元数据需要管理:导演、主演、地区、年份、评分、简介、海报地址。这些字段如果直接全部堆在电影表里,表会变得越来越宽,而且如果之后想给电影加一个"标签"功能,又要改表结构。正确做法是把分类单独拆一张表,电影表里只存category_id,查询时通过JOIN把表关联起来。
详情页的数据组装是另一个考察点。一个电影详情往往需要展示基本信息、相关评论、同一分类下的其他推荐,这三个数据如果分别写在三个接口里,前端要发起三次请求,体验差而且代码冗余。更好的做法是在Service层写一个getMovieDetail方法,一次查出电影基本信息,再查评论列表,再查同类推荐,封装成一个VO对象返回给前端,这叫"接口按业务维度聚合",答辩时提到这个点会加分。
3.4 评论模块与收藏模块:不要忘记外键、索引和用户关联
评论和收藏看起来简单,实际上最容易写出性能问题和脏数据。评论表必须同时存user_id和movie_id(或song_id),一说到底应该用哪个字段去关联,取决于这个评论挂在音乐还是电影上。如果音乐和电影都要评论,可以考虑设计成一张多态关联的评论表,用target_type字段区分是歌曲还是电影,target_id存具体的主键ID,这种设计更能体现数据库建模能力。
收藏表的逻辑同理,也要存user_id和target_type、target_id,并且要设置联合唯一索引,防止用户对同一首歌收藏两次。之前我看到一个项目代码里,用户反复点击收藏按钮就会反复插入记录,显示"收藏数"直接按count数出来,含了大量重复数据。要是你在设计时加了联合唯一索引,再配合插入时捕获DuplicateKeyException做提示,这个细节讲出来连答辩老师都会点头。
4. 数据库表设计:五张核心表的字段取舍与会踩的坑
数据库设计是整个项目的地基,地基歪了,后面所有查询都是歪的。音乐电影网站通常涉及用户、歌手/演员、歌曲、电影、分类、评论、收藏这些实体,但核心的、绕不开的其实就是五张表:用户表、歌曲表、电影表、评论表、收藏表。分类表可以并入歌曲和电影表中作为外键,也可以单独提出来,看分类的数据量而定。
4.1 用户表和权限字段的设计思路
用户表user的常规字段是id、username、password、nickname、avatar、email、role、create_time。这里的role字段非常关键,它直接决定了一个用户能否访问管理后台的接口。最简单的做法是用一个小整数,0表示普通用户,1表示管理员,然后写一个注解配合拦截器做权限校验,只有role=1的请求才允许访问管理端接口。
如果想让项目看着更"专业"一点,可以引入status字段做禁用/启用状态的区分,以及last_login_time记录最近登录时间,方便管理员做用户管理列表的排序。但我不建议在这个项目里去追求细节太多的RBAC权限模型,像Spring Security那样搞权限继承和角色树,对毕设来说过度设计了,徒增答辩时被追问的风险。
4.2 歌曲表和电影表的核心字段对比
歌曲表song至少要包含:id、song_name、singer、album、duration、cover_url、music_url、lyric、play_count、category_id、create_time。其中music_url存的是音频文件的访问路径,play_count可以用来做热门歌曲排行。这里有一个小细节,很多项目会在歌曲表里把歌手直接存成一个字符串,这样在后台按歌手搜索时就只能like模糊匹配,效率差。如果要做得更规范,歌手可以单独拆一张表,歌曲表存singer_id,这样前端就可以按歌手筛选歌单,也算是一个不错的加分点。
电影表movie字段大体相似:id、title、director、actors、region、release_year、rating、cover_url、video_url、description、category_id、create_time。rating字段建议用decimal(2,1),可以存8.5这样的评分,而不用去纠结浮点数的精度问题。description建议用text类型,因为电影简介往往比较长,varchar(255)很可能放不下。
4.3 评论表和收藏表的多态关联设计
评论表和收藏表如果分开设计成"音乐评论"和"电影评论",代码数量会翻倍,而且以后维护任何一个公共逻辑都要改两处。更推荐的方式是设计成多态关联:表中同时保留target_type和target_id两个字段,target_type=1表示歌曲,target_type=2表示电影,查询时根据类型分别联表。
这种设计并不是银弹,它的缺点是查询评论时没法直接用一条JOIN同时查出歌名和电影名,需要先按类型分组,再分别查歌曲表和电影表。但考虑到毕设的数据量,这个缺点完全可以忽略,而且这种设计能体现出你对数据库模型的思考,而不是只会照着表结构机械建表。收藏表同理,加上target_type和target_id,再加一个user_id,三个字段组成联合唯一索引。
4.4 冗余字段的取舍:播放量和收藏数字段怎么处理
在音乐和电影网站上,"播放量"是一个几乎一定会展示的数据。如果每次展示列表页都去统计一次播放量,数据量小的时候看起来没事,数据量大了之后性能会非常难看。所以我建议在歌曲表和电影表上都直接冗余一个play_count字段,播放的时候用UPDATE song SET play_count = play_count + 1 WHERE id = ?去做自增,查询的时候直接读这个字段排序。
同理,歌曲和电影的收藏数、评论数也可以在各自表上冗余一个favorite_count和comment_count字段,在新增评论或新增收藏时同时更新这个冗余字段。这种"以空间换时间"的冗余思路,在答辩时是很好的加分回答,因为它表明你考虑过读多写少的真实场景,而不是一个只会照本宣科的调用库。
sql复制-- 歌曲表核心建表片段
CREATE TABLE `song` (
`id` bigint NOT NULL AUTO_INCREMENT,
`song_name` varchar(100) NOT NULL COMMENT '歌曲名称',
`singer_name` varchar(50) DEFAULT NULL COMMENT '歌手名称',
`cover_url` varchar(255) DEFAULT NULL COMMENT '封面图路径',
`music_url` varchar(255) NOT NULL COMMENT '音频文件路径',
`play_count` int DEFAULT '0' COMMENT '播放次数',
`category_id` bigint DEFAULT NULL COMMENT '分类ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='歌曲表';
5. 从零开始搭项目:初始化、配置、登录与上传的关键代码
前面铺垫了这么多设计思路,接下来把核心代码逐个落地。这部分的重点不是让你复制粘贴,而是让你明白每一个文件是干什么的、为什么这么写。项目初始化以Spring Boot + MyBatis Plus + Thymeleaf(或Vue静态资源)为例,JDK用8,Spring Boot用2.7.x。
5.1 用IDEA创建项目并配置application.yml
创建一个Spring Boot项目,Group填com.example,Artifact填musicmovie,依赖勾选Spring Web、Thymeleaf、MySQL Driver、Lombok。项目生成后先不要急着写代码,把application.yml配置好:
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/music_movie_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
servlet:
multipart:
max-file-size: 200MB
max-request-size: 200MB
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
serverTimezone=Asia/Shanghai这个参数别漏了,漏掉的话连接MySQL 8时会报时区错误。max-file-size要根据你的资源文件实际大小设置,默认为1MB,不调大的话上传一首稍微大点的音乐文件就直接报异常。
5.2 整合MyBatis Plus并完成第一个Mapper
引入MyBatis Plus依赖时,老版本和新版本的依赖坐标有差异。Spring Boot 2.7.x对应的是:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
实体类上加上@TableName("song")注解,主键字段加上@TableId(type = IdType.AUTO),这样MyBatis Plus就能自动完成基本CRUD。写一个SongMapper接口继承BaseMapper<Song>,什么都不用写,就已经有selectById、insert、updateById这些方法了。
5.3 编写用户登录接口:Controller只做参数接收,业务放Service
很多初学者的通病是把所有逻辑都写进Controller,一个方法几百行,看着很吓人,后期改起来也痛苦。这里我刻意把登录接口按标准三层结构拆开,Controller、Service、Mapper各司其职。
java复制@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO) {
// Controller 层只负责接收参数和返回结果
return userService.login(loginDTO);
}
}
ServiceImpl里写主要的登录逻辑,需要注意判断用户是否存在、密码是否正确、状态是否被禁用三个分支。
java复制@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public Result login(LoginDTO loginDTO) {
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
.eq(User::getUsername, loginDTO.getUsername()));
if (user == null) {
return Result.error("用户名不存在");
}
if (!BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) {
return Result.error("密码错误");
}
if (user.getStatus() != null && user.getStatus() == 0) {
return Result.error("账号已被禁用");
}
// 生成Token,这里用HashMap模拟,实际建议用JWT工具类
Map<String, Object> data = new HashMap<>();
data.put("token", JwtUtil.generateToken(user.getId(), user.getUsername()));
data.put("nickname", user.getNickname());
data.put("role", user.getRole());
return Result.success(data);
}
}
5.4 统一返回结果Result和全局异常处理
前后端交互的时候,最忌讳的就是每个接口返回结构都不一样。前端判断成功的逻辑五花八门,后端改起来也费劲。所以从第一个接口开始就要定义统一的返回类,最简单的结构是code、message、data三个字段,code=200表示成功,其他值表示失败。写成泛型类:
java复制@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("操作成功");
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
全局异常处理使用@RestControllerAdvice注解,捕获全局的Exception和自定义的业务异常,统一返回Result格式。这样即使代码里某个地方抛了空指针,前端拿到的接口响应也依然是固定结构,而不是一堆看不懂的堆栈信息。
5.5 文件上传接口:处理好路径映射,前端才能访问到文件
音频和视频上传是音乐电影网站绕不开的功能。写一个FileController,接收MultipartFile,保存到一个配置好的上传目录,然后把可访问的URL返回出来。
java复制@RestController
@RequestMapping("/file")
public class FileController {
@Value("${upload.path}")
private String uploadPath;
@PostMapping("/upload")
public Result upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
// 用时间戳加随机数生成文件名,避免中文名和重名问题
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename != null ? originalFilename.substring(originalFilename.lastIndexOf(".")) : "";
String newFileName = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().replace("-", "") + suffix;
File dest = new File(uploadPath + "/" + newFileName);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
file.transferTo(dest);
} catch (IOException e) {
return Result.error("文件上传失败");
}
// 返回给前端的URL路径
return Result.success("/upload/" + newFileName);
}
}
同时配置静态资源映射:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${upload.path}")
private String uploadPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:" + uploadPath + "/");
}
}
这样一个文件上传并返回可访问URL的流程就闭环了。前端拿到返回的URL直接塞给<audio>或<video>标签即可播放。
6. 部署运行时的五个高频坑:每一个都是我见过真实翻车的
代码写完之后,接下来的"跑起来"阶段才是重灾区。每年都有学生在我面前演示项目,说"明明在我电脑上是好的",结果换一台电脑、换一个环境就全崩了。下面几个问题是我在帮人排查时遇到次数最多的,提前规避掉会省下大量时间。
6.1 端口被占用和数据库连接失败
Spring Boot默认端口是8080,但如果你本地开了其他服务占用了8080,启动就直接报错。排查方法很简单,看到Port 8080 was already in use的日志,用netstat -ano | findstr 8080(Windows)或lsof -i :8080(Mac/Linux)查一下占用进程,找到PID之后杀掉,或者干脆在application.yml里换个端口比如8081。
数据库连接失败的概率更高,常见原因有三个:MySQL服务没启动、用户名密码配错、serverTimezone缺失导致时区报错。我的建议是配置文件里所有值都用你最熟悉的那一套,不要为了"跟教程一致"去改一个不存在的密码,否则排查起来特别浪费时间。
6.2 文件上传大小超限
Spring Boot默认单文件上传大小是1MB,很多人在上传音乐时遇到MaxUploadSizeExceededException,第一反应是去找代码bug,实际上只要在application.yml里调大max-file-size和max-request-size就好。但要提醒一句,如果你做的项目包含视频上传功能,也别把上限设置成无限大,200MB是一个相对合理的值,对于演示环境来说足够了。
6.3 跨域问题:前后端分离项目的经典拦路虎
如果你采用的是Vue前后端分离,必然要面对跨域。后端解决跨域最直接的方式是写一个配置类实现WebMvcConfigurer,重写addCorsMappings方法:
java复制@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
allowedOriginPatterns("*")比allowedOrigins("*")更灵活,允许携带Cookie和认证信息,这在需要Token鉴权的接口中很关键。
6.4 拦截器把静态资源也拦截了
如果你配置了登录拦截器,而且没有放行静态资源,会发现页面样式全没了、图片加载不出来、音频播放报错。这是因为拦截器默认拦截了所有请求,包括/upload/**下的静态资源和Thymeleaf模板里的静态文件(/css/**、/js/**、/images/**)。在WebMvcConfigurer的addInterceptors方法里,一定记得把静态资源路径一并excludePathPatterns放行掉。
6.5 打包部署:先弄清你在哪运行
本地IDEA直接点main方法运行是最简单的模式,但如果你需要把项目放到云服务器上演示,建议用Maven的package指令打包成jar包,在后端运行。打包前需要注意application.yml里的数据库连接地址和文件上传路径要改成服务器上的实际路径,不要把本地路径带上去。部署命令也很简单:
bash复制mvn clean package -DskipTests
java -jar target/musicmovie-0.0.1-SNAPSHOT.jar
如果想后台运行,用nohup java -jar xxx.jar > log.txt 2>&1 &,日志会输出到log.txt里,排查问题时直接看这个文件。
7. 答辩环节:老师最常追问的20个问题
代码都跑通了,不等于答辩就稳了。老师看一个项目,首先看完成度,其次看设计逻辑,最后看你能否把你做的东西讲清楚。下面这些问题是从往年答辩真实场景中总结出来的,挑几个你觉得最心虚的提前准备一轮,会有明显的效果。
7.1 Spring Boot原理类
- 为什么选择Spring Boot而不是传统的SSM框架?可以围绕"自动配置、起步依赖、内嵌服务器、不需要外部Tomcat"四个关键词展开。
- Spring Boot的自动配置原理是什么?核心指向
@SpringBootApplication里的@EnableAutoConfiguration,通过spring.factories或AutoConfiguration.imports文件加载大量的XxxAutoConfiguration类,再配合@ConditionalOnClass等条件注解按需生效。 @Component、@Service、@Repository、@Controller这些注解有什么区别?平时都是怎么使用的?
7.2 项目实现细节类
- JWT的Token被偷了怎么办?这个问题的稳妥答案是:Token有效期设置得短一点,配合Redis做黑名单,同时在密码修改后强制Token失效。
- 密码是怎么加密的?为什么不用MD5?答案要提BCrypt加盐机制,说明MD5虽然快但容易被彩虹表反查,而BCrypt每次生成的哈希值都不同,安全性高很多。
- 文件上传有哪些限制?怎么处理文件名重复和中文文件名?要提到时间戳加UUID重命名、限制文件大小、校验文件后缀,甚至可以做文件类型白名单。
- 数据库有没有做过索引优化?评论表、收藏表、播放量查询的索引分别怎么建?这道题是加分项,能答出联合索引就是记住了关键。
- 分页查询是怎么实现的?MyBatis Plus的
Page对象和PaginationInnerInterceptor配合使用,前端传页码和每页大小,后端返回总条数和列表数据。
7.3 扩展性与优化类
- 如果用户量大了,当前项目哪些地方要改?这道题可以往缓存方向答:热点歌曲和电影列表用Redis做缓存;数据库做读写分离;文件上传从本地磁盘换成云对象存储(OSS),视频转码走队列异步处理。
- 为什么把音乐和电影的评论收藏设计成多态关联?可以解释为"减少表数量、统一用户的动态流查询",再补充说明这种设计的适用范围和查询时的取舍。
- 如果要做推荐功能,可以从哪里下手?不需要提复杂算法,只需要说按分类、播放量、收藏行为做协同过滤的冷启动版本,老师对这个回答一般都会比较满意。
每一次技术选择都要能讲出理由,这是答辩能否拿高分的分水岭。不要背概念,要把项目代码和概念对应起来。
写在最后的一点个人经验
带过的毕业设计项目里,真正能做出高质量效果的同学,往往有一个共同点:他们不是追求"某个功能做得多炫",而是先把整个系统的数据流和模块边界想清楚再动手。音乐电影网站系统的难点从来不是某个代码片段写不出来,而是如何把用户、资源、行为三类数据组织成一个完整闭环。你在设计数据库时多花一天想清楚表结构,后期写代码和改bug的时间就能省下三天;你在写第一个接口时多花半小时定义好统一的返回结构和全局异常,后面整整几十个接口都会受益。花费在思考和设计上的时间是永远值得的,在这个项目里尤其如此。希望这篇拆解能帮你少绕一点弯路,踏踏实实把这个系统做明白。
