ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Spring Boot的音乐播放网站设计与实现全解析

基于Spring Boot的音乐播放网站设计与实现全解析 简介这套毕业设计资源以Spring Boot为核心构建了一个具备歌曲搜索、播放控制、用户登录等功能的音乐播放网站适合Java学习者或本科毕业生作为课程设计/毕业设计的参考项目。资源采用7z压缩包封装整体大小约38.1MB内含完整可运行的Spring Boot工程源码、music.sql数据库初始化脚本、Git属性配置及IDE项目配置等文件搭建后即可对照学习或二次开发。目前已有674人浏览学习说明其在同类毕业设计中具有一定参考价值。借助该源码与数据库可深入理解Spring Boot自动配置、Spring MVC请求处理、Spring Security认证授权、JPA/MyBatis持久化操作等技术同时参考前端界面与后端接口的交互方式结合数据库表设计和播放功能模块能帮助读者快速掌握Web项目从零到一的全流程开发思路。1. Spring Boot音乐播放网站这个毕设选题到底在考什么音乐播放网站是毕业设计里的“熟面孔”看起来无非是歌曲列表、播放按钮、收藏歌单一套CRUD很多人在开题时把它当作保底选项。但真正开始写才发现这个题目的扣分点不在业务复杂度而在几个容易被低估的细节音频文件往哪里存、播放接口怎么响应才能拖进度条、用户登录后静态资源怎么防越权。这些点恰好也是Spring Boot面试里常被追问的地方把这个题目做透比做一个大而全的管理系统更能体现工程能力。这篇博文按“架构设计 → 建表 → 播放主流程 → 联调排错 → 进阶优化”的顺序把一套可以直接落地的方案讲清楚。内容不绑定某个特定的源码包看中的是思路本身——你拿到任何一套基于Spring Boot的音乐网站源码都能用这里的方法拆解和二次改造。2. 四层架构与数据库设计——先把歌单、歌曲、用户这三张表的关系捋顺2.1 用Spring Boot四层架构拆一个音乐网站代码该往哪里放Spring Boot项目的经典分层是Controller、Service、Mapper或Repository、Entity热词里常说的“spring boot四层架构”就是指这一套。放在音乐播放网站里每一层的职责要非常清楚Entity层对应数据库表比如Song、Singer、Playlist、User。字段类型尽量跟表结构一致日期用LocalDateTime别再用java.util.Date硬扛。Mapper层负责SQL和ORM映射MyBatis写XML或者注解SQL都可以简单查询用注解动态拼接用XML。Service层业务规则只在这一层写比如“收藏歌单前先查是否已收藏”“播放量1要放在事务里”。Controller层只做参数接收、调用Service、封装返回结果。返回统一用Result对象包一层前端对接时不需要为每个接口单独处理结构。有一个常犯的错误是Controller里直接写业务逻辑或者Service里拼SQL。毕设答辩时老师问“你觉得你的项目哪里体现了分层思想”很多人答不上来就是因为代码里三层揉成了一层。四个包路径从controller→service→mapper→entity一条线下来谁依赖谁方向明确后面加功能、修bug都轻松很多。除了这四层还有一个容易被忽略的第五块config包。跨域配置、拦截器注册、静态资源映射、文件上传大小限制这些统统放这里。音乐网站一定会有跨域问题因为前端可能跑在8081端口后端在8080没有CORS配置浏览器会把请求拦下来。用Configuration实现WebMvcConfigureraddCorsMappings里把allowCredentials设为trueallowedOriginPatterns用“*”而不是allowedOrigins后者在携带凭证时会失效。2.1.1 建一个音乐网站骨架pom.xml里最少需要什么一个音乐播放网站的maven依赖比一般管理系统要多个处理文件的东西。以下是最小集合dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependencystarter-web自带Tomcat和Spring MVC不用额外配。validation做参数校验比如注册时用户名不能为空。mybatis-plus提供分页插件和代码生成器写毕设时能省掉大量重复的BaseMapper代码。版本号不用刻意追新能稳定跑通最重要。2.2 数据库表设计音乐网站最少需要六张表数据库课程设计里最常见的问题是表拆得太散或者太粗。音乐播放网站建议从六张表起步用户表、歌手表、歌曲表、歌单表、歌曲-歌单关联表、收藏表。前四张是主表后两张是关系表。核心的song表字段设计是整个项目的关键直接影响后面的查询效率CREATE TABLE song ( id bigint(20) NOT NULL AUTO_INCREMENT, song_name varchar(100) NOT NULL COMMENT 歌曲名称, singer_id bigint(20) DEFAULT NULL COMMENT 歌手id, album varchar(100) DEFAULT NULL COMMENT 专辑名称, duration int(11) DEFAULT NULL COMMENT 时长(秒), file_url varchar(255) NOT NULL COMMENT 音频文件相对路径, cover_url varchar(255) DEFAULT NULL COMMENT 封面图相对路径, play_count bigint(20) DEFAULT 0 COMMENT 播放量, status tinyint(1) DEFAULT 1 COMMENT 1上架 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_singer (singer_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;file_url存相对路径而不是完整的http链接这一点特别重要。如果直接存“http://localhost:8080/file/music/xxx.mp3”将来换域名、换端口、换服务器数据库里几百条记录全要改。存相对路径“/file/music/xxx.mp3”或者“music/xxx.mp3”访问时由后端拼前缀或者走静态资源映射灵活得多。duration字段记得存秒数不要存“03:25”这种格式化字符串。前端播放器组件大多接受秒数自行换算存字符串就意味着每次显示还要解析而且排序、比较都没法做。歌曲和歌单的关系表要带一个sort_order字段用来记录同一歌单里歌曲的排序。很多毕设省略了这个字段导致歌单里的歌曲一直按添加时间排没法支持拖动排序和置顶答辩演示时很尴尬。2.3 MyBatis Plus分页和条件查询的配置列表页搜歌、歌单列表分页这两块是CRUD里最有说头的地方。MyBatis Plus提供了分页插件但要让分页生效得先加一个配置类这是新手最容易漏的一步。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }不加这个配置调用selectPage时分页参数不生效SQL里没有LIMIT返回的总条数还是全部记录的数量。这是MyBatis Plus最经典的问题之一答辩前务必检查。条件查询用LambdaQueryWrapper写起来比QueryWrapper更安全字段名经过编译校验不会拼错public PageSong searchSongs(String keyword, int page, int size) { PageSong pageInfo new Page(page, size); LambdaQueryWrapperSong wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), Song::getSongName, keyword) .eq(Song::getStatus, 1) .orderByDesc(Song::getPlayCount); return songMapper.selectPage(pageInfo, wrapper); }这段代码实现的是有条件才拼接LIKE过滤下架歌曲按播放量倒序。like第一个参数是boolean控制这个条件是否加入SQL好处是keyword为空时不会查出全表再过滤。3. 播放主流程从文件上传到前端能拖进度条3.1 音频文件上传本地磁盘和云存储的取舍先解决一个方向问题音频文件放哪。毕设场景下最常见的是本地磁盘存储把文件写到服务器的一个固定目录再通过URL映射暴露出去。这套方案简单直接一个目录加一段配置就搞定。配置文件里加一行自定义属性比如music.upload-path然后写一个上传接口Value(${music.upload-path}) private String uploadPath; PostMapping(/api/upload) public Result upload(RequestParam(file) MultipartFile file, RequestParam(songName) String songName) { if (file.isEmpty()) return Result.error(文件为空); String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replace(-, ) suffix; File dest new File(uploadPath fileName); file.transferTo(dest); return Result.success(fileName); }不建议直接用原始文件名落盘一方面中文文件名在不同操作系统下有编码兼容问题另一方面同名文件会互相覆盖。用UUID重命名原始文件名存入数据库单独字段给前端展示。3.2 播放接口一定要支持HTTP Range不然进度条拖不动这是整个音乐网站技术含量最高的一处。如果用普通的静态资源映射直接把mp3文件喂给前端播放器只能从头播到尾用户一拖进度条就回到起点。原因是浏览器播放音频时靠Range请求头向服务器要数据片段服务器必须返回206 Partial Content。Spring Boot里用ResourceRegion可以比较简洁地实现RestController public class SongPlayController { Value(${music.upload-path}) private String uploadPath; GetMapping(/api/song/play/{songId}) public ResponseEntityResourceRegion play(PathVariable Long songId, RequestHeader HttpHeaders headers) { Song song songService.getById(songId); Resource resource new FileSystemResource(uploadPath song.getFileUrl()); long contentLength resource.contentLength(); ResourceRegion region resourceRegion(resource, headers, contentLength); return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT) .contentType(MediaTypeFactory.getMediaType(resource) .orElse(MediaType.APPLICATION_OCTET_STREAM)) .body(region); } }核心逻辑是把Range请求头里的start和end解析出来构造成ResourceRegion返回。Spring的ResourceRegion会自动处理响应状态码和Content-Range头比手写byte数组拷贝省心很多。代码里contentLength依赖FileSystemResource从文件系统拿长度。如果文件路径配错了这里会直接抛IOException返回500前端播放器表现为“无法播放”。排查时先看这个路径存不存在、有没有读权限属于文件问题而非代码问题。3.2.1 前端播放器对接audio标签的src指对就成功一半前端对接其实最省事。Vue或者原生HTML都用audio标签audio controls :src/api/song/play/ currentSong.id/audio播放器组件自带Range处理能力只要后端返回206和正确的Content-Type拖动进度条和倍速播放都不需要前端写额外逻辑。如果音质不理想微信推流、直播这些前置细节就和你无关毕设里也用不到。本地文件配上Range支持就是完整方案。3.3 拦截器与鉴权登录才能播放的接口怎么做最干净音乐网站通常要求登录后才能收藏、评论但播放行为要不要拦截各人设计不同。这里的坑在于如果用了Spring Security静态资源会被拦截如果自己写拦截器放行规则写错又会把接口暴露。一个常见做法是不引入Spring Security用HandlerInterceptor加token校验。登录接口发放token前端后续请求放在Authorization头里拦截器统一校验public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } String realToken token.substring(7); if (!TokenUtil.verify(realToken)) { response.setStatus(401); return false; } return true; } }注册拦截器时注意addPathPatterns和excludePathPatterns的顺序放行的路径必须写全。比如“/api/song/list”“/api/song/play”播放接口如果不需要登录就放行“/api/user/login”“/api/user/register”必须放行“/api/playlist/**”要拦截。有一个细节很多源码做得不对登录校验通过后后续的Controller里还要拿当前用户id比如收藏操作要记录是谁收藏的。这就要在preHandle里把用户id塞进request属性request.setAttribute(userId, userId)Controller里再用RequestAttribute取出而不是前端传一个userId参数给你。后者几乎等于告诉攻击者可以伪造身份操作别人的数据。4. 联调阶段的高频问题数据库连不上、文件写不进、懒加载报错4.1 数据库连接时区、SSL、驱动版本三座大山第一个问题出现在项目启动时连接MySQL报错“The server time zone value is unrecognized”。这是MySQL 8.0之后时区设置变了JDBC连接串里必须显式指定serverTimezone推荐用Asia/Shanghai。spring: datasource: url: jdbc:mysql://localhost:3306/music_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverallowPublicKeyRetrievaltrue这个参数是针对MySQL 8.0的caching_sha2_password认证插件设计的不开它连接时可能会报“Public Key Retrieval is not allowed”。很多人的代码本身没毛病就是死在这一行连接参数上。driver-class-name用com.mysql.cj.jdbc.Driver这是MySQL 8.0驱动的类名。如果复制了老项目的com.mysql.jdbc.Driver启动时会得到一个弃用警告部分高版本驱动里这个类已经被移除直接报ClassNotFoundException。4.1.1 数据库连接后中文乱码优先查三处第一处是建表时CHARSET是不是utf8mb4第二处是连接串里有没有characterEncodingutf8第三处是页面本身的编码。注意utf8mb4和utf8的区别utf8在MySQL里最多存3个字节而大部分emoji字符要4个字节歌名叫“夏天的风”没问题要是用户昵称里带emojiutf8编码的表直接存不进去。4.2 上传文件写不进磁盘路径、权限、内存大小上传接口报400错误最常见的提示是“the request was rejected because its size exceeds the configured maximum”。Spring Boot默认限制单文件大小为1MB上传一首5MB的mp3直接失败。在配置里调大spring: servlet: multipart: max-file-size: 50MB max-request-size: 100MBmax-file-size是单个文件上限max-request-size是单次请求总大小后者必须大于等于前者否则一次传多张封面图时也会报错。另一种情况是transferTo执行时抛IOException提示No such file or directory。原因多半是上传目录不存在。file.transferTo不会帮你创建父目录必须先手动检查File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); }mkdirs和mkdir的区别是前者可以创建多级目录音乐网站的uploadPath可能是“D:/music-platform/upload/”中间还隔了一层必须用mkdirs。4.3 JSON序列化时报错懒加载和递归引用怎么处理歌曲表里有singer_id查询歌曲时如果连表查出了singer对象而singer对象里又有一个List 属性Jackson序列化时会陷入无限递归栈溢出。处理办法有两种。简单粗暴的是在实体类字段上标注JsonIgnore把它直接排除在序列化之外。但这样会导致前端永远拿不到歌手信息得另外写VO类或者用JsonIgnoreProperties配合。我一般建议查询列表时用VO接收结果VO里只有前端需要的字段。这个方法伤筋动骨一点但最稳。还有一个更隐蔽的问题MyBatis Plus的懒加载和事务。如果在Controller里直接调用song.getSinger()而Mapper配置了懒加载会报LazyInitializationException因为此时Session已关闭。毕设里常见做法是把Mapper的fetchType设为eager但这会拖慢查询性能。更合理的做法是在Service层完成所有关联查询Controller只接收已经组装好的结果。4.4 端口占用8080被占用的快速排查Spring Boot默认跑在8080启动时报“Port 8080 was already in use”。网上教程会叫你去改server.port但8080被占通常意味着另一个Java进程还没退出。最直接的排查命令# Windows netstat -ano | findstr 8080 taskkill /F /PID 进程号 # Linux/macOS lsof -i:8080 kill -9 进程号还有一种情况是同时启动了多个Spring Boot实例。IDEA里旧实例没停新实例又起来也会报端口占用学会看控制台的“Application run failed”日志比盲目改端口更能定位问题。5. 让毕设多走一步播放量计数、分页缓存与Actuator监控5.1 给播放量加缓存别让高频请求打垮数据库播放接口每次都要更新play_count字段如果歌曲热门数据库就要不停执行UPDATE。合理做法是把播放量先累加到Redis里定期刷回MySQL或者至少用本地缓存扛一下。毕设阶段没搭Redis的话可以退而求其次用Spring的缓存注解做查询缓存把热门歌曲列表缓存30秒Cacheable(cacheNames hotSongs, key #size, unless #result null) public ListSong getHotSongs(int size) { return songMapper.selectHotSongs(size); }注意缓存注解在同一个类内部调用时不生效因为Spring AOP基于代理。也就是说getPlaylist方法里调用this.getHotSongs(size)缓存会失效你得把方法拆到两个类里或者直接注入Service自引用。5.2 Actuator监控和未授权访问风险Spring Boot Actuator提供了/actuator/health这类监控端点答辩时用这个展示“项目有运维意识”非常加分。但有个安全细节必须处理老版本Spring Boot的/actuator/env、/actuator/heapdump等端点默认暴露存在信息泄露风险。如果你的毕设引入了Actuator一定要限制端点暴露范围management: endpoints: web: exposure: include: health,infohealth是存活检查info是自定义信息这两个足够演示用。像env、beans、configprops、heapdump这些统统不暴露避免答辩时被问到“你这个actuator安全吗”答不上来。5.3 把歌曲列表做成通用接口方便以后接小程序最后一个实用建议歌曲列表接口不要只想着网页端参数里把“来源”设计好。比如加一个platform参数小程序和Web共用一套后端。代码里对platform不用做什么特殊分支但接口设计时别写死返回HTML片段一律返回JSON将来接什么端都不慌。以上五个小节里缓存和监控这两个改动代码量不大但对评分影响明显。它们证明你不止是写通了CRUD还考虑了高并发访问下的性能表现和系统维护性——这恰恰是“基于Spring Boot的音乐播放网站设计与实现”这个题目最想看到的深度。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进