Spring Boot在线音乐系统搭建:MyBatis-Plus数据库设计与REST接口实战

发布时间:2026/8/30 22:49:16
Spring Boot在线音乐系统搭建:MyBatis-Plus数据库设计与REST接口实战 毕业设计和课程设计项目中在线音乐系统是一个经常出现的题目。很多同学最开始想到的是用 Servlet JSP 实现但功能一多登录、歌曲管理、收藏、歌单、评论都堆在一起代码会越来越难维护。换成 Spring Boot 之后内嵌容器、自动配置、Starter 依赖和 REST 接口让整个过程变得直接很多。这篇内容就以一个可运行的 Java 网易云音乐系统为例说明如何用 IntelliJ IDEA 打开 Spring Boot 源码从数据库设计到核心接口把一个能演示的后端项目跑起来。完成之后你既能用于毕业设计演示也能理解 Spring Boot MyBatis-Plus MySQL 的实际用法。整个实现按“概念、环境、数据库、接口、验证、排错、扩展”的顺序推进。代码示例以 Spring Boot 2.7 JDK 8 为主因为这是目前校园项目里兼容性最好的组合。如果你手里已经有一套源码也可以对照这个思路确认自己的项目结构而不是只看代码跑起来。1. 先想清楚在线音乐系统到底要做什么1.1 用最小业务闭环拆解功能模块一个完整的在线音乐系统往往包含推荐、排行榜、歌单、评论、收藏、搜索、个人中心等模块。作为毕业设计或者课程设计不建议一开始就追求大而全先做一个能讲清楚业务闭环的版本要更重要。所谓最小业务闭环可以这样定义用户能够注册登录登录后能够浏览歌曲能够收藏歌曲能够创建歌单能够把歌曲加入歌单能够在个人中心看到收藏列表和歌单列表。这套链路覆盖了用户、歌曲、收藏、歌单四类核心数据也覆盖了注册、登录、分页查询、新增、删除、多对多关联等常见开发场景。在这个闭环里可以拆出以下几张核心表用户表保存用户名、加密后的密码、昵称、头像等。歌曲表保存歌名、歌手、专辑、时长、封面、歌词、播放地址。歌单表保存歌单名称、描述、创建人。歌单歌曲表歌单和歌曲的多对多关系表。收藏表记录用户收藏了哪些歌曲。为什么不把收藏直接做成“在歌曲表里加一个收藏字段”因为一个用户收藏的歌曲属于个人数据和多用户共享的歌曲数据并不在同一个维度。收藏行为必须有一个独立的表来记录“谁收藏了哪首歌”否则同一首歌被多个用户收藏时字段会互相覆盖。1.2 Spring Boot 在这个系统里的位置Spring Boot 解决的是“如何快速把 Java Web 服务跑起来”的问题。它把 Spring 框架中重复的 Bean 配置、组件扫描、数据源配置、内嵌容器启动等步骤自动化了。对于音乐系统这类以 CRUD 和业务流程为主的项目Spring Boot 的价值体现在几个方面内嵌 Tomcat不需要单独部署 War 包。Starter 依赖统一管理版本减少 Maven 坐标冲突。自动配置数据源、MyBatis、Jackson 等常用组件。REST 接口开发简单前端和后端可以分离。社区资料多遇到问题容易搜索到解决方案。如果使用传统的 SSM 框架需要手工配置web.xml、Spring 容器、MyBatis 的 SqlSessionFactory很多东西在课程设计阶段会消耗大量时间。Spring Boot 把这些约定成了默认行为所以更适合作为快速出成果的基础框架。下面用一张表对比设计思路的差异对比点Servlet/JSP 传统做法Spring Boot 做法页面渲染JSP 在服务端渲染前后端分离返回 JSON启动方式部署到外部 Tomcat内嵌 Tomcat直接运行 main数据库访问JDBC 或 MyBatis 手写配置MyBatis-Plus 自动 CRUD接口规范方法名和返回类型不统一Controller Result 统一包装适合场景小规模老课程设计可扩展、易维护的毕设项目2. 环境准备IDEA、JDK、Maven、MySQL2.1 建议的版本组合在开始写代码之前先确认环境版本。很多项目启动失败并不是代码问题而是 JDK、Spring Boot 和 MyBatis-Plus 版本不兼容。推荐按下面的组合选择场景JDKSpring BootMaven数据库学习/毕设常用组合JDK 8Spring Boot 2.7.xMaven 3.6MySQL 5.7/8.0新版组合JDK 17Spring Boot 3.2.xMaven 3.8MySQL 8.0不想安装 MySQL 时JDK 8Spring Boot 2.7.xMaven 3.6H2 内置数据库如果没有特别原因优先选择 Spring Boot 2.7.x JDK 8。Spring Boot 3.x 包名和部分依赖有变化MyBatis-Plus 需要引入mybatis-plus-spring-boot3-starter对新手来说多一点兼容成本。如果使用的是 IDEA Community 版也可以在 start.spring.io 上生成项目压缩包再通过 IDEA 的 Open 导入。不会影响后续开发只是没有 Spring Initializr 的图形化快捷入口。社区版对学习 Spring Boot 完全够用。2.2 创建项目并引入所需依赖在 IDEA 中创建项目时可以在 Spring Initializr 页面选择 Java 8、Spring Boot 2.7.x然后添加以下依赖Spring WebMySQL DriverLombokValidation还需要在pom.xml中手动添加 MyBatis-Plus 依赖因为它不在 Spring Initializr 默认选项中。完整依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies这里需要注意Spring Boot 2.7.x 使用的 MySQL 驱动坐标是mysql:mysql-connector-java不同版本可能坐标名称不同。上面的mysql-connector-j是 MySQL 官方新坐标在 Spring Boot 2.7 中也能被管理版本。如果发现驱动类找不到可以改成dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency关键点在于Spring Boot 父 POM 会管理这些依赖的版本一般不需要手动指定版本。MyBatis-Plus 不在 Spring Boot 管理范围内必须写版本号。2.3 配置文件数据源和 MyBatis-Plus 基础配置项目创建后在src/main/resources目录下找到application.yml写入以下配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/music_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case的作用是把数据库的create_time自动映射成 Java 实体的createTime避免每一列都写映射关系。logic-delete-field表示逻辑删除字段。用户、歌曲这类数据不适合直接物理删除删除后还需要保留历史记录所以使用逻辑删除字段。查询时 MyBatis-Plus 会自动追加WHERE deleted 0。配置完成后启动一次项目确认能够连接数据库。如果还没有建库会看到数据库连接失败的错误这是正常的下一步先建库建表。3. 数据库设计表和关系一次建好3.1 表结构和关系设计核心关系如下用户表user和歌单表playlist是一对多一个用户可以创建多个歌单。歌单表playlist和歌曲表song是多对多一个歌单可以包含多首歌曲一首歌曲可以出现在多个歌单中。用户表user和歌曲表song通过收藏表favorite建立多对多关系一个用户可以收藏多首歌曲一首歌曲可以被多个用户收藏。设计表时不推荐大量使用物理外键。外键约束会影响插入、删除性能也会让删除逻辑变得复杂。实际项目中经常保留逻辑关系由 Service 层保证一致性。但在表设计阶段仍然可以通过索引来保证唯一性。3.2 建表 SQL进入 MySQL执行下面的 SQLCREATE DATABASE IF NOT EXISTS music_db DEFAULT CHARACTER SET utf8mb4; USE music_db; CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除0未删除1已删除, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE song ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 歌曲名, singer varchar(50) DEFAULT NULL COMMENT 歌手, album varchar(100) DEFAULT NULL COMMENT 专辑, duration int DEFAULT NULL COMMENT 时长单位秒, cover_url varchar(255) DEFAULT NULL COMMENT 封面地址, play_url varchar(255) DEFAULT NULL COMMENT 播放地址, lyric text COMMENT 歌词, create_time datetime DEFAULT CURRENT_TIMESTAMP, deleted tinyint NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT歌曲表; CREATE TABLE playlist ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 创建人id, name varchar(100) NOT NULL COMMENT 歌单名称, description varchar(255) DEFAULT NULL COMMENT 描述, cover_url varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, deleted tinyint NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT歌单表; CREATE TABLE playlist_song ( id bigint NOT NULL AUTO_INCREMENT, playlist_id bigint NOT NULL, song_id bigint NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_playlist_song (playlist_id, song_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT歌单歌曲关联表; CREATE TABLE favorite ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, song_id bigint NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_song (user_id, song_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表; INSERT INTO song (name, singer, album, duration, play_url) VALUES (晴天, 周杰伦, 叶惠美, 269, /audio/qingtian.mp3), (起风了, 买辣椒也用券, 起风了, 326, /audio/qifengle.mp3), (平凡之路, 朴树, 猎户星座, 302, /audio/pingfanzhilu.mp3);这段 SQL 用utf8mb4字符集原因是要兼容中文和 emoji 字符。歌曲表的初始数据用于分页和收藏测试没有真实音频文件时播放地址可以先写成相对路径。唯一索引非常重要。收藏表里的uk_user_song确保同一个用户不会重复收藏同一首歌曲歌单歌曲表的uk_playlist_song确保同一首歌不会重复加入同一个歌单。如果没有唯一索引代码里即使做了判断并发请求下仍然可能插入重复数据。3.3 实体类和 MyBatis-Plus 映射创建User.javapackage com.example.music.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import java.time.LocalDateTime; Data TableName(user) public class User { TableId(type IdType.AUTO) private Long id; private String username; JsonIgnore private String password; private String nickname; private String avatar; private LocalDateTime createTime; TableLogic JsonIgnore private Integer deleted; }这里有两个关键点TableName(user)使用反引号因为user是 MySQL 的保留字不加反引号会报语法错误。JsonIgnore加在密码字段上防止用户对象返回给前端时把密码也带出去。创建Song.javapackage com.example.music.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; Data TableName(song) public class Song { TableId(type IdType.AUTO) private Long id; private String name; private String singer; private String album; private Integer duration; private String coverUrl; private String playUrl; private String lyric; private LocalDateTime createTime; TableLogic private Integer deleted; }创建Playlist.java和Favorite.java时同理。Favorite对应favorite表包含userId和songId。MyBatis-Plus 的驼峰映射会把userId对应到user_id列不需要额外配置。创建实体类时常见错误是漏了TableLogic导致删除时变成真正物理删除。如果使用了逻辑删除配置实体类里必须写上对应字段。4. 核心接口实现从注册登录到收藏歌单4.1 注册与登录密码一定要加密如果直接把明文密码存进数据库演示时一旦出现安全相关提问这会成为明显减分项。登录注册模块要做到两件事注册时把密码用 BCrypt 加密后存入数据库登录时用 BCrypt 校验密码不比较明文。创建一个配置类提供PasswordEncoderpackage com.example.music.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; Configuration public class PasswordConfig { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }这里没有引入 Spring Security只使用了 Spring Security 中的加密类。依赖spring-security-crypto会通过项目中的其他依赖传递进来。如果项目中没有这个类可以在pom.xml中添加dependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId /dependency用户注册和登录的 Controllerpackage com.example.music.controller; import com.example.music.common.Result; import com.example.music.entity.User; import com.example.music.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import javax.validation.constraints.NotBlank; RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/register) public Result register(RequestBody Valid RegisterDTO dto) { return userService.register(dto.getUsername(), dto.getPassword()); } PostMapping(/login) public Result login(RequestBody Valid LoginDTO dto) { return userService.login(dto.getUsername(), dto.getPassword()); } public static class RegisterDTO { NotBlank(message 用户名不能为空) private String username; NotBlank(message 密码不能为空) private String password; // getter/setter 省略 } public static class LoginDTO { NotBlank(message 用户名不能为空) private String username; NotBlank(message 密码不能为空) private String password; // getter/setter 省略 } }Service 层代码package com.example.music.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.music.common.Result; import com.example.music.entity.User; import com.example.music.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; Service public class UserService { Autowired private UserMapper userMapper; Autowired private PasswordEncoder passwordEncoder; public Result register(String username, String password) { Long count userMapper.selectCount( new LambdaQueryWrapperUser().eq(User::getUsername, username)); if (count 0) { return Result.error(用户名已存在); } User user new User(); user.setUsername(username); user.setPassword(passwordEncoder.encode(password)); userMapper.insert(user); return Result.success(注册成功); } public Result login(String username, String password) { User user userMapper.selectOne( new LambdaQueryWrapperUser().eq(User::getUsername, username)); if (user null || !passwordEncoder.matches(password, user.getPassword())) { return Result.error(用户名或密码错误); } user.setPassword(null); return Result.success(user); } }推荐用Result统一返回结构例如code、message、data三个字段。前后端联调时不用一会儿返回true一会儿返回1。4.2 歌曲分页查询分页插件要注册歌曲列表推荐使用分页查询。先配置 MyBatis-Plus 分页插件package com.example.music.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setMaxLimit(100L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }setMaxLimit(100L)是防止一次查询过多数据学习项目不用太大。歌曲查询接口RestController RequestMapping(/api/song) public class SongController { Autowired private SongService songService; GetMapping(/page) public Result page(RequestParam(defaultValue 1) long pageNum, RequestParam(defaultValue 10) long pageSize, RequestParam(required false) String keyword) { return songService.page(pageNum, pageSize, keyword); } }Service 实现public Result page(long pageNum, long pageSize, String keyword) { LambdaQueryWrapperSong wrapper new LambdaQueryWrapper(); if (keyword ! null !keyword.isBlank()) { wrapper.like(Song::getName, keyword).or().like(Song::getSinger, keyword); } wrapper.orderByDesc(Song::getCreateTime); PageSong page songMapper.selectPage(new Page(pageNum, pageSize), wrapper); return Result.success(page); }这里有个常见坑如果忘记注册PaginationInnerInterceptorselectPage不会报错但返回的records是全部数据total也有问题。注册分页插件后再验证总数和每页数量。4.3 收藏与取消收藏唯一索引和事务收藏接口需要三个方法收藏、取消收藏、查询收藏列表。收藏接口PostMapping(/favorite/add) public Result add(RequestParam Long userId, RequestParam Long songId) { return favoriteService.add(userId, songId); } PostMapping(/favorite/cancel) public Result cancel(RequestParam Long userId, RequestParam Long songId) { return favoriteService.cancel(userId, songId); } GetMapping(/favorite/list) public Result list(RequestParam Long userId) { return favoriteService.listByUser(userId); }Service 层使用TransactionalTransactional(rollbackFor Exception.class) public Result add(Long userId, Long songId) { Long count favoriteMapper.selectCount( new LambdaQueryWrapperFavorite() .eq(Favorite::getUserId, userId) .eq(Favorite::getSongId, songId)); if (count 0) { return Result.error(已经收藏过了); } Favorite favorite new Favorite(); favorite.setUserId(userId); favorite.setSongId(songId); favoriteMapper.insert(favorite); return Result.success(收藏成功); }取消收藏时可以直接按userId和songId删除Transactional(rollbackFor Exception.class) public Result cancel(Long userId, Long songId) { favoriteMapper.delete( new LambdaQueryWrapperFavorite() .eq(Favorite::getUserId, userId) .eq(Favorite::getSongId, songId)); return Result.success(取消收藏成功); }收藏表不需要逻辑删除因为取消收藏后记录可以直接删除。这里使用事务是为了后续扩展时间更复杂时多个表操作可以保持一致。查询收藏列表时如果只需要歌曲列表可以分两步先查收藏表中的songId再根据songId批量查询歌曲。也可以直接写一个联表 SQL把收藏表和歌曲表 join 起来。用 MyBatis-Plus 的话推荐先查 ID 列表再查询歌曲逻辑更清晰。4.4 歌单创建与添加歌曲多对多关联歌单和歌曲是多对多关系操作时涉及三张表歌单表、歌曲表、关联表。创建歌单PostMapping(/playlist/create) public Result create(RequestParam Long userId, RequestParam String name, RequestParam(required false) String description) { Playlist playlist new Playlist(); playlist.setUserId(userId); playlist.setName(name); playlist.setDescription(description); playlistMapper.insert(playlist); return Result.success(playlist); }向歌单添加歌曲PostMapping(/playlist/song/add) Transactional(rollbackFor Exception.class) public Result addSongToPlaylist(RequestParam Long playlistId, RequestParam Long songId) { // 1. 判断歌单是否存在 Playlist playlist playlistMapper.selectById(playlistId); if (playlist null) { return Result.error(歌单不存在); } // 2. 判断歌曲是否存在 Song song songMapper.selectById(songId); if (song null) { return Result.error(歌曲不存在); } // 3. 判断是否已经加入 Long count playlistSongMapper.selectCount( new LambdaQueryWrapperPlaylistSong() .eq(PlaylistSong::getPlaylistId, playlistId) .eq(PlaylistSong::getSongId, songId)); if (count 0) { return Result.error(该歌曲已在歌单中); } // 4. 插入关联数据 PlaylistSong playlistSong new PlaylistSong(); playlistSong.setPlaylistId(playlistId); playlistSong.setSongId(songId); playlistSongMapper.insert(playlistSong); return Result.success(添加成功); }在 Service 层先判断歌单和歌曲是否存在再操作关联表可以避免产生脏数据。虽然关联表已经有唯一索引但业务层的提示信息会更友好不会直接抛出数据库异常。同时这里要注意所有数据库写操作类方法都建议加上Transactional(rollbackFor Exception.class)避免第一步插入了歌单、第二步插入关联数据失败时留下不完整的数据。5. 在 IDEA 里运行和验证5.1 启动项目和确认日志找到项目主类例如MusicApplication.java点击 main 方法前的运行按钮。启动成功后控制台会看到类似下面的日志Tomcat started on port(s): 8080 (http) Started MusicApplication in 3.5 seconds如果项目启动失败优先看启动日志的第一段异常。不要只看最下面的提示很多错误原因在堆栈顶部。启动成功不代表接口可用。还要检查数据库表中的初始数据是否已经插入。如果歌曲表有 3 条数据就可以开始接口验证。5.2 使用 curl 验证核心接口在 IDEA 终端或系统终端中执行下面的请求。注册用户curl -X POST http://localhost:8080/api/user/register \ -H Content-Type: application/json \ -d {username:zhangsan,password:123456}预期返回{ code: 200, message: 注册成功, data: null }登录curl -X POST http://localhost:8080/api/user/login \ -H Content-Type: application/json \ -d {username:zhangsan,password:123456}预期返回用户信息其中密码字段不会出现因为已经加了JsonIgnore。分页查询歌曲curl http://localhost:8080/api/song/page?pageNum1pageSize2预期返回一页两条歌曲记录同时包含total总数。收藏歌曲curl -X POST http://localhost:8080/api/favorite/add?userId1songId1再次执行同样的请求会返回“已经收藏过了”。取消收藏后再执行又可以收藏。创建歌单并加入歌曲curl -X POST http://localhost:8080/api/playlist/create?userId1name我的最爱description测试歌单 curl -X POST http://localhost:8080/api/playlist/song/add?playlistId1songId1如果使用 Postman注意参数类型和请求方式。使用RequestParam接收的参数在 Postman 中放到 Params 里使用RequestBody接收的参数放到 Body 的 raw JSON 里。5.3 常见运行错误排查用表格整理几个容易遇到的问题问题现象常见原因检查方式处理建议Tomcat 启动失败提示端口被占用8080 端口已被其他程序使用执行 netstat -anofindstr 8080数据库连接失败Access denied用户名或密码错误检查application.yml确认 MySQL 账号密码并确认库已创建找不到驱动类ClassNotFoundExceptionmysql 驱动坐标不对检查pom.xmlSpring Boot 2.7 使用mysql-connector-java3.x 使用mysql-connector-j分页返回全部数据没有注册分页插件检查MybatisPlusConfig添加PaginationInnerInterceptor插入的中文变成问号字符集设置不对检查连接 URL 和表字符集URL 加characterEncodingutf8表使用utf8mb4Lombok 报错getter/setter 找不到IDEA 没有启用注解处理检查 Settings Build Compiler Annotation Processors勾选 Enable annotation processing搜索这个问题时很多同学会到处改代码但没有检查 IDEA 的注解处理器配置。Lombok 如果配置不对代码在编译阶段就找不到getter和setter但项目本身不需要改任何代码。6. 从“跑通”到“能答辩”常见坑与优化方向6.1 最值得避开的几个坑第一个坑是返回密码字段。就算数据库里存的是 BCrypt 加密结果也不应该返回给前端。解决方案是实体类字段上加JsonIgnore或者在返回 DTO 时不复制密码字段。第二个坑是 MyBatis-Plus 分页不生效。很多同学发现selectPage返回的records是全部记录以为代码写错了实际上是没有注册MybatisPlusInterceptor。分页插件是拦截器机制不注入拦截器Page对象虽然创建了但分页 SQL 不会生成。第三个坑是关联表重复插入。用户重复点击收藏按钮、重复添加歌曲到歌单时没有唯一索引会插入多条相同记录。解决方案是表上建立联合唯一索引同时 Service 层做存在性判断两个手段结合使用。第四个坑是删除操作误用逻辑删除。TableLogic会让deleteById变成update deleted 1但对关联表来说可能不是期望行为。收藏表和歌单歌曲表更适合物理删除关联记录删掉不影响主数据。主数据表如用户、歌曲、歌单可以使用逻辑删除。6.2 学习环境到生产环境的差异课程设计和真实生产环境差别很大可以在文档或答辩中主动说出来会成为加分项。学习环境通常会把数据库配置写在application.yml生产环境应该使用环境变量或配置中心学习环境的 token 放在内存 Map生产环境应该用 Redis 或 JWT学习环境的日志打印到控制台生产环境要把日志接入文件或集中日志系统。可以用下面这张表总结关注点学习项目做法生产项目建议配置硬编码在 yml配置中心或环境变量密码BCrypt 加密BCrypt 加密并增加登录失败限制身份认证登录直接返回用户信息JWT Redis 会话管理日志控制台输出按天滚动文件分级别归档异常处理Controller 内部分散处理全局异常处理器 统一错误码文件存储本地路径对象存储 OSS 或自建文件服务数据库单表 CRUD主从分离、读写分离、慢查询监控实际项目里上线前还要考虑数据备份、回滚方案和资源监控。比如歌曲播放地址如果失效怎么补偿歌单被删除后关联的歌单歌曲怎么清理这些不写进需求里但答辩时被问到是常见的加分点。6.3 功能扩展方向如果基础功能已经跑通可以按下面顺序扩展评论功能增加comment表用户可以对歌曲发表评论。搜索功能在歌曲分页里增加关键词模糊搜索已经实现了基础版本。排行榜统计歌曲收藏数量按收藏数倒序返回榜单。歌单详情返回歌单基本信息连同歌单内歌曲列表一起返回。播放功能前端播放音频需要提供真实音频资源和播放地址。前端页面用 Vue 3 Element Plus 对接后端接口形成前后端分离完整项目。如果时间紧张评论功能是性价比比较高的扩展。它涉及一张新表和简单的 CRUD又能体现你对业务关系的理解。7. 可复用检查清单7.1 开发过程检查清单确认 JDK、Spring Boot、MyBatis-Plus 版本组合。确认数据库连接 URL 包含characterEncodingutf8和serverTimezone。确认表名是 MySQL 保留字时使用反引号。确认用户密码使用 BCrypt 加密存储。确认用户对象不返回密码字段。确认 MyBatis-Plus 分页插件已经注册。确认逻辑删除字段已经加上TableLogic。确认收藏表和歌单歌曲表有唯一索引。确认写操作涉及多张表时使用Transactional。确认前端请求参数与 Controller 接收方式一致。7.2 演示和答辩前检查清单提前准备好测试账号并确认账号可以登录。歌曲表中插入至少 3 到 5 条背景音乐相关数据让演示看起来真实。演示顺序建议注册 - 登录 - 歌曲分页 - 收藏 - 取消收藏 - 创建歌单 - 添加歌曲 - 查询歌单。准备一张简单的表结构图用于解释用户、歌曲、收藏、歌单之间的关系。准备 2 到 3 个你在开发中踩过的坑说明现象、原因和解决方式。如果使用了网络图片资源提前确认访问地址可用。一个能跑的 Spring Boot 音乐系统后端本质上并不复杂。真正要花时间的是把表关系设计清楚、把接口返回结构统一、把用户密码和事务处理做好。把这几个点做到位课程设计演示、毕业设计答辩或者简历上的项目描述都会更有底气。接下来可以继续往前端页面、评论模块和部署方向扩展整个系统的能力会越来越完整。