news 2026/9/26 11:41:57

SpringBoot+Vue音乐网站实战:数据库设计到前后端联调全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue音乐网站实战:数据库设计到前后端联调全解析

每年这个时候,计算机专业的朋友们就开始为毕业设计发愁了。音乐网站系统算是Java Web方向最经典的题目之一,乍一看到处都是,但真正能跑通、能讲清楚原理、能过答辩的项目其实不多。我前阵子刚帮人完整梳理过一套基于SpringBoot + Vue的音乐网站项目的代码,从数据库设计到前后端联调一步步走了一遍,在这里把整套思路和实操经验完整整理出来,希望对正在做类似毕设或者想学全栈开发的朋友有点实际帮助。

这套系统的核心就是两条线:后端用SpringBoot做接口服务,前端用Vue做页面交互,中间通过RESTful API通信,数据落在MySQL里。用户能注册登录、浏览歌曲、搜索歌手、管理歌单、收藏音乐、在线播放;管理员可以维护歌曲、歌手、封面、歌词等基础数据。功能不算花哨,但麻雀虽小五脏俱全,正好覆盖了一个网站系统该有的核心链路:用户体系、资源管理、关联关系、文件上传、前端路由与状态管理。

1. 项目定位与模块拆解

1.1 这套系统到底在做什么

先说清楚它要解决什么问题。音乐网站本质上是一个内容管理 + 用户互动的系统。你可以把它拆成两个视角来看:普通用户希望快速找到想听的歌、建自己的歌单;管理员希望方便地录入歌曲信息、管理歌手、控制上传的文件。

所以项目的功能边界大概是这样的:

  • 用户端:注册登录、浏览歌手和歌曲、搜索、创建歌单、收藏歌曲、听歌(实际上就是HTML5的audio播放)、写评论。
  • 管理端:歌曲CRUD、歌手CRUD、歌单管理、用户管理(通常只做到禁用或重置密码)。

至于要不要做成后台管理界面,大部分毕设是分开做的,也就是前台展示用Vue,后台管理用另一个Vue页面或同一套系统的不同路由。我见过不少参考项目把管理端做成admin子应用,这样职责清晰,答辩的时候也好讲。

1.2 技术选型背后的逻辑

有人问,为什么这类题目几乎都选SpringBoot + Vue?其实答案很实际。

后端用SpringBoot而不是SSH、SSM,是因为它在极大程度上省掉了繁琐的XML配置。我记得以前用SSM搭一个项目,光配置文件就要写四五份,什么spring-mvc.xml、mybatis-config.xml、web.xml,光应付这些就能耗掉一个新手两周时间。SpringBoot用自动配置和起步依赖把配置量压到了极低,让我们能把精力放在业务代码上。对于毕设周期来说,这是绝对优势。

前端选Vue而不是React,是因为Vue的上手曲线平缓,模板语法直观,还有Element UI这一整套现成的组件库,页面做出来不至于太难看。Vue的生态在面对这种管理系统类型的项目时效率确实高。

数据库必然用MySQL,免费、资料多、各种工具链成熟,遇到问题一搜就有答案。ORM层面我推荐用MyBatis-Plus,不是因为别的,就是因为它自带CRUD方法,不用写一堆重复的Mapper XML,对赶进度的人非常友好。

1.3 角色权限与模块划分

这个系统我建议分成三种角色,虽然很多参考代码只做用户和管理员,但把“普通用户”和“管理员”严格分开会更好:

角色能做什么
游客看首页、看榜单、搜索、听歌(部分项目允许)
注册用户收藏、评论、建歌单、管理自己的歌单
管理员维护歌曲歌手数据、管理用户、上传文件

权限控制这块,后端用拦截器 + JWT,前端用路由守卫,两个地方都要做,不能只靠前端隐藏按钮来“假装”有权限。

2. 数据库设计:先想清楚数据怎么落地

数据库是这类系统最不能糊弄的部分。我见过很多毕设代码表结构混乱,字段含义不清,关联查询也写不明白,答辩的时候被老师一问就露馅。这里把我的表设计思路完整列出来。

2.1 六张核心表结构

一个音乐网站最核心的表,我认为有六张:用户表、歌手表、歌曲表、歌单表、歌单歌曲关联表、收藏表、评论表(如果用心做,评论表也加上)。下面逐个拆解。

用户表:

CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(255) NOT NULL COMMENT '密码(BCrypt加密)', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `role` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1普通用户 2管理员', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里特别提醒几个字段的用意:role用于区分用户和管理员,status用于管理员禁用违规账号,password字段长度留255是因为BCrypt加密后的字符串超过50位,用长度50的字段会存不进去。

歌手表和歌曲表:

CREATE TABLE `singer` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '歌手名', `avatar` varchar(255) DEFAULT NULL COMMENT '歌手头像', `introduction` text COMMENT '歌手简介', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `song` ( `id` int(11) NOT NULL AUTO_INCREMENT, `singer_id` int(11) NOT NULL COMMENT '歌手ID', `name` varchar(100) NOT NULL COMMENT '歌曲名', `album` varchar(100) DEFAULT NULL COMMENT '专辑名', `url` varchar(255) NOT NULL COMMENT '音频文件地址', `cover` varchar(255) DEFAULT NULL COMMENT '封面图地址', `lyric` text COMMENT '歌词', `duration` int(11) DEFAULT '0' COMMENT '时长(秒)', `play_count` int(11) DEFAULT '0' COMMENT '播放次数', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

歌曲表里有一个play_count字段,很多人的代码里根本没用到,但排行榜功能就靠它。虽然真正的大型音乐平台会引入Redis来计数,但毕设用MySQL直接UPDATE就能应付,页面按播放次数倒序排列即可。

歌单、关联、收藏、评论表:

CREATE TABLE `song_list` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '创建者ID', `title` varchar(100) NOT NULL COMMENT '歌单标题', `cover` varchar(255) DEFAULT NULL, `description` varchar(500) DEFAULT NULL, `play_count` int(11) DEFAULT '0', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `song_list_song` ( `id` int(11) NOT NULL AUTO_INCREMENT, `song_list_id` int(11) NOT NULL, `song_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `favorite` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `song_id` int(11) NOT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_song` (`user_id`, `song_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `comment` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `song_id` int(11) NOT NULL, `content` varchar(500) NOT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有两个设计细节值得注意:song_list_song是典型的关联表,用来实现歌单和歌曲的多对多关系,前端传一个歌单ID和歌曲ID数组,后端就是对这个表做批量插入和批量删除;favorite表加了一个联合唯一索引uk_user_song,防止同一个用户重复收藏同一首歌,这个约束在数据库层面做最稳妥,比在Java代码里先查再判断靠谱得多。

2.2 表关系与业务联动

表之间的关系画在脑子里是这样的:user一端连着song_list、favorite、comment;singer是一方,song是多方;song又通过song_list_song关联到song_list。查询的时候,最复杂的SQL也就是两三张表联查。

我一个同事当初在歌单详情接口上卡了两天,其实就是没有想清楚关联表。后来逻辑理清了就很简单:查歌单基本信息一张表,通过song_list_song查出song_id列表,再用IN语句查歌曲信息,外加LEFT JOIN查出歌手的名字。SQL长这样:

SELECT s.id, s.name, s.album, s.url, s.cover, s.duration, sg.name AS singer_name FROM song s LEFT JOIN singer sg ON s.singer_id = sg.id WHERE s.id IN (SELECT song_id FROM song_list_song WHERE song_list_id = #{songListId})

用子查询而不是三表JOIN,可读性高,也方便后面分页。

2.3 初始化数据怎么准备

初始化数据也有讲究。第一,至少准备10个以上歌手、50首以上歌曲,数据太少页面看着空旷;第二,歌曲文件和封面图注意版权和质量,毕设展示尽量用自己录制或版权开放的素材,不要在论文和PPT里放明显有版权争议的商用歌曲截图;第三,默认管理员账号要固定一个,比如admin / 123456,初始化SQL里直接用BCrypt加密后的密码字符串写入,方便后端启动后直接登录。

3. 后端:SpringBoot实现的核心模块

3.1 项目结构与分层策略

这套系统的后端项目结构,我建议严格按照标准Maven结构来组织,包名按业务分层:

com.example.music ├── controller # 接口层 ├── service # 业务层 │ └── impl ├── mapper # MyBatis-Plus持久层 ├── entity # 实体类 ├── dto # 请求/响应对象 ├── config # 配置类,如CORS拦截器 ├── common # 统一返回结果、异常处理 └── utils # JWT、文件上传工具

为什么要分层?因为答辩的时候老师一定会问“你项目的架构是什么样的”。能清楚讲出Controller只接收参数、Service处理业务逻辑、Mapper负责数据库交互,这种基础的三层架构认知,在老师眼里就算合格了。很多C盘代码全堆在Controller里,200行一个方法,虽然能跑,但一被追问就露馅了。

3.2 JWT登录鉴权实现

登录鉴权是每个系统都绕不开的核心。现在基本不用Session了,主流做法是JWT。流程是这样的:用户提交用户名密码 -> 后端校验 -> 生成Token返回前端 -> 前端存到localStorage -> 每次请求带在Header里 -> 后端拦截器解析Token并放行。

JWT工具类里需要三样东西:密钥、过期时间、生成和解析的方法。代码如下:

@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expire}") private Long expire; public String generateToken(Long userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expire)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }

配置文件里我用的密钥是一个足够长的随机字符串,过期时间设为24小时。实际开发中密钥要放在配置里,不要写死在代码中。拦截器里对所有需要登录的接口做校验,注意两个坑:一是放行登录注册接口和静态资源,二是对管理员接口额外校验role字段。

拦截器的写法是在SpringBoot里实现HandlerInterceptor接口,然后在WebMvcConfigurer中注册,并指定拦截路径。核心逻辑就是获取Header里的Authorization,去掉"Bearer "前缀,解析Token,成功就放行并把用户信息存入ThreadLocal或者request的attribute中,方便后续业务取当前用户ID。

3.3 歌曲管理、上传与播放统计

歌曲和歌手的增删改查用MyBatis-Plus的IService接口就顺手完成了。真正要用心的是文件上传。毕设里歌曲文件、封面图都是通过上传接口保存的。上传路径我踩过一个很典型的坑:用绝对路径保存,导致换一台电脑运行就找不到文件。

正确的姿势是在配置文件中定义一个上传根目录,应用启动时确保目录存在,然后把文件保存进去,同时把访问路径映射成静态资源。SpringBoot配置里加一行:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceHandler("file:" + uploadPath + "/"); } }

这样上传后的文件对外访问路径就是http://localhost:8080/files/xxx.mp3,前端直接拼URL就能用。播放次数统计也不复杂,在播放接口里对play_count做自增更新,但这要控制频率。我见过实现比较简单的情况:每次点播放都调一个计数接口。对毕设来说完全够用,但对生产来说肯定不行,答辩时如果有人问,就说可以引入Redis做缓存计数、定时刷入数据库,这个回答可以显示你有进阶思考。

3.4 歌单、收藏与评论的接口设计

这类接口的关键是搞清楚返回结构。我整理一下常见的端点:

方法路径说明
GET/api/song/list歌曲列表,支持关键词搜索、分页
GET/api/song/detail/{id}歌曲详情,含歌手名
POST/api/song新增歌曲(管理员)
PUT/api/song修改歌曲(管理员)
DELETE/api/song/{id}删除歌曲(管理员)
GET/api/songList歌单列表
POST/api/songList创建歌单(登录)
POST/api/songList/{id}/songs歌单添加歌曲
DELETE/api/songList/{id}/songs/{songId}歌单移除歌曲
POST/api/favorite收藏歌曲
DELETE/api/favorite/{songId}取消收藏
POST/api/comment发布评论

接口路径要有语义,统一返回结构。我定义一个Result<T>类,里面放code、message、data三个字段,成功code是200,失败是500,未登录是401,这样前端axios拦截器可以统一处理错误提示,而不是每个请求单独写一遍。

3.5 统一返回、异常处理与跨域配置

写接口时最忌讳每个方法返回类型五花八门,有的返回Map,有的直接返回实体。老老实实统一用Result包装。异常处理用@RestControllerAdvice做全局异常捕获,业务异常抛自定义的BusinessException,系统异常统一返回500并记录日志。

跨域配置是前后端分离项目必配的。用CorsFilter注册一个Bean,允许的前端端口是http://localhost:5173(Vite默认)或http://localhost:8081(Vue CLI默认),允许的Header里必须包含Authorization,否则前端带的Token会被浏览器拦截。

4. 前端:Vue页面与交互实现

4.1 项目搭建与目录划分

前端我建议用Vue CLI或Vite创建项目,UI组件库用Element UI。如果你是Vue 3项目,对应版本是Element Plus;如果是Vue 2项目,就用Element UI老版本。两者写法略有差异,但整体思路一样。我这里的示例以Vue 2 + Element UI为主,因为它资料最多,踩坑也好查。

前端目录结构参考:

src ├── api # 接口封装 ├── assets # 静态资源 ├── components # 通用组件,如播放条 ├── router # 路由配置 ├── store # Vuex状态管理 ├── views # 页面组件 ├── utils # 工具类,如axios封装 └── App.vue

4.2 路由设计与登录守卫

路由配置要预设好页面。常见页面有:首页(推荐位 + 热门歌单)、歌手列表、歌手详情、歌曲搜索、歌单详情、个人信息页、后台管理页。这里我特别说明一个点:播放页到底要不要单独一个路由。我的做法是不单独开页面,而是做一个全局固定的底部播放条组件,全局只有一个audio元素。这样切换页面音乐不会中断,用户体验也舒服。这个组件的逻辑是:点击任何歌曲时,把歌曲信息提交到Vuex,底部播放条监听Vuex状态变化,然后设置audio的src并播放。

路由守卫负责登录校验:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else if (to.meta.role && to.meta.role !== store.state.user.role) { next('/') } else { next() } })

这里要提一个容易忽略的细节:刷新页面时Vuex数据会丢失,所以用户信息要同时存一份到localStorage,页面初始化时再从localStorage恢复。否则用户登录后一刷新,页面就不知道当前用户是谁了,很多人的毕设都有这个bug。

4.3 axios封装与接口对接

axios封装的核心是请求拦截器加Token、响应拦截器处理错误码。登录接口返回的Token存在localStorage里,请求拦截器每次把Token塞进Header。响应拦截器如果拿到401,就清除本地登录信息并跳转登录页。

import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code === 401) { localStorage.clear() router.push('/login') } return res }, error => { Message.error(error.message || '请求失败') return Promise.reject(error) } ) export default service

注意baseURL这里用的是/api,开发环境下需要在Vue CLI的vue.config.js里配代理转发,把/api开头请求转发到8080端口。这是前端遇到跨域问题的最简解法,比后端配CORS还省事。

4.4 播放器组件的核心逻辑

播放条算是这个项目前端最有含金量的组件。核心代码并不复杂,但逻辑要理清楚:audio是HTML元素,Vue通过ref拿到它,然后监听Vuex中的当前歌曲变化,更新audio的src并调用play方法。播放/暂停按钮控制audio的play和pause,进度条更新时监听timeupdate事件,拖动进度条时修改currentTime,音量控制同理。

<template> <div class="player-bar"> <audio ref="audioRef" :src="currentSong.url"></audio> <div class="song-info"> <img :src="currentSong.cover" alt=""> <div> <p>{{ currentSong.name }}</p> <span>{{ currentSong.singerName }}</span> </div> </div> <div class="controls"> <i class="el-icon-video-pause" v-if="isPlaying" @click="pause"></i> <i class="el-icon-video-play" v-else @click="play"></i> <span>{{ formatTime(currentTime) }}</span> <el-slider :max="duration" v-model="currentTime" @change="seek"></el-slider> <span>{{ formatTime(duration) }}</span> </div> </div> </template>

这里有几个常见的坑:audio的src一旦变化,要重新load才能生效;歌曲可能因为CORS问题不能播放,这就需要后端对音频接口也放行;播放失败时要catch住Promise,否则会在控制台刷一堆Uncaught的错误。还有一个体验细节:el-slider在拖动过程中频繁更新currentTime会卡顿,正确的做法是拖动时只更新本地临时值,松手时才写audio的currentTime。

另外歌词展示很多人忽略。其实很简单:歌词格式是LRC,解析成正则匹配时间戳的数组,audio的timeupdate事件里去匹配当前时间对应的歌词行。算是一个加分项,写出来效果很好。

4.5 页面渲染与交互细节

页面渲染用Element UI的列表组件很顺手。首页的推荐位用一个轮播图组件展示专辑封面;歌单卡片用row和col布局实现栅格排列;歌单详情页用表格展示歌曲列表,每行有一个播放按钮,点击调用Vuex的setCurrentSong提交歌曲信息。

搜索功能建议做防抖,用户在输入框打字时不要每敲一个字符都请求一次接口,用setTimeout和clearTimeout把请求频率降下来,或者简单点,用el-input的@keyup.enter触发搜索。虽然毕设不要求高并发性能,但写防抖在答辩中是个加分细节,可以表示你不是完全没考虑过性能。

5. 本地跑通全套代码的完整步骤

拿到这套源码,很多人第一步就卡住。SpringBoot版本不匹配、Node版本太高、Maven依赖下载不下来,这些坑我都见过。下面按顺序走一遍。

5.1 环境准备与版本检查

建议环境版本固定为:

  • JDK 1.8(如果用的是SpringBoot 2.x)或 JDK 17(SpringBoot 3.x)
  • Maven 3.6+
  • Node.js 16.x(Vue 2项目用太新的Node会报OpenSSL错误)
  • MySQL 5.7或8.0

这里最需要注意的就是Node版本。用Vue CLI创建的老项目在Node 17以上版本运行npm run serve时会报ERR_OSSL_EVP_UNSUPPORTED,原因就是Webpack 4依赖的OpenSSL算法在新版Node里被禁用了。解决办法是装Node 16,或者在package.json的scripts里加NODE_OPTIONS=--openssl-legacy-provider,但加参数的方式在Windows上还有兼容问题,不如直接切Node版本省心。

JDK版本同理。SpringBoot 2.x在JDK 17下虽然能跑,但一些旧依赖会有兼容问题,稳妥起见用JDK 8。

5.2 引入数据库初始化脚本

先在MySQL里建一个库,名字可以叫music_db,字符集utf8mb4,排序规则utf8mb4_general_ci。然后执行项目里的sql脚本。如果脚本里没有管理员账号的密码,需要自己手动用BCrypt工具生成一段密文插入。这个工具可以自己写个main方法调SpringSecurity的BCrypt,也可以直接用网上的在线工具,注意生成后核对前缀是$2a$开头即可。

数据导入后,可以在服务器上直接用SQL查一下关键表的行数,确认singer、song、user三张表都有数据,避免启动后端后前端页面一片空白。

5.3 后端启动与常见启动问题

后端启动的步骤,在IntelliJ IDEA中打开pom.xml所在的目录,等待Maven加载依赖(第一次下载依赖耗时较长),然后修改application.yml里的数据库连接配置:

spring: datasource: url: jdbc:mysql://localhost:3306/music_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 servlet: multipart: max-file-size: 100MB max-request-size: 100MB jwt: secret: 一个足够长的随机字符串 expire: 86400000

启动时最常遇到的报错是时区问题,一般表现为serverTimezone相关SQL异常,记得在连接串上追加serverTimezone=Asia/Shanghai。如果提示数据库连接失败,先用Navicat测试本地连接,排除账号密码错误和MySQL服务没启动的问题。如果Maven依赖下载缓慢,可以换阿里云镜像,这个在settings.xml里配置,属于常规操作了。

启动成功后,后端默认端口8080,在浏览器访问http://localhost:8080/api/song/list,如果返回JSON数据,说明后端正常。

5.4 前端启动与代理配置

在前面代码部分提过,前端开发环境配的是代理,所以vue.config.js里这样写:

module.exports = { devServer: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

这样一个代理就解决了跨域问题,前端里所有的请求路径都以/api开头,实际上被转发到了后端。changeOrigin必须设置为true,否则后端可能会因为Host头对不上而拒绝请求。

启动时如果提示node_modules不存在,先执行npm install。这个过程可能遇到警告found 100 vulnerabilities,不用慌,这是依赖版本的安全提示,对本地开发运行没实际影响。安装完成后执行npm run serve,看到Compiled successfully提示后打开浏览器访问即可。

5.5 功能验证清单

系统跑起来后按这个清单快速过一遍:

  • 注册一个新账号,确认能登录、能跳转。
  • 用管理员账号登录,添加一首歌,确认歌曲列表能看到、能播放。
  • 普通用户收藏歌曲,去个人中心确认收藏列表有数据。
  • 创建一个歌单,往歌单里加几首歌,再提交时刷新页面确认数据持久化了。
  • 搜索一个不存在的关键词,确认返回空列表且不报错。

每一步都走通,基本上功能就齐了。后端的SQL日志可以在application.yml里配置mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl,这样控制台会打印SQL,排查问题非常有用。

6. 常见问题与排查技巧实录

6.1 前端请求被拦截,页面报跨域错误

这个是我看到最多的问题。报错特点明显:浏览器控制台出现Access-Control-Allow-Origin相关字样,Network面板里请求显示为CORS error。解决策略分两个方向:一是前端配proxy代理,二是后端配CorsFilter。如果你已经配了proxy还报错,检查是不是直接把axios的baseURL写成了http://localhost:8080,绕过了代理。前端如果配了代理,请求路径必须写相对路径/api/xxx,而不是完整的后端地址。

6.2 请求401或403,Token没带上

Token的传递链路是这样的:登录接口返回Token -> 前端存到localStorage -> 下次请求时在拦截器里取出来放Header。常见问题有两个:一是localStorage的key写错了,存的时候叫token,取的时候写成了Token;二是后端拦截器放行的路径写错,把需要登录的接口也放行了,导致接口拿不到当前用户信息,返回空指针。调试技巧:在浏览器F12的Network里看请求头,确认有没有Authorization字段,没有就是前端问题,有但后端还不认,就是后端解析逻辑问题。

6.3 图片和音频上传成功但访问404

这个问题的根因几乎永远是路径映射没配好。文件保存到了本地磁盘的某个目录,但SpringBoot没有把这个目录映射成静态资源路径,前端访问/files/xxx.jpg自然404。解决办法参考前面WebConfig代码,把uploadPath和/files/**正确对应上。另外一个坑是文件保存时用了绝对路径拼接,Windows下路径分隔符是\,Linux下是/,在网页URL里只能用/。保存数据库的时候一定统一存储为相对路径,例如/files/20240101/xxx.jpg。

6.4 音频跨域导致无法播放

音频地址和后端接口不在同一个域时,audio元素的请求会被浏览器拦截,控制台报跨域错误。如果前端用了proxy,而后端又配了CORS,要注意两边的Header是否冲突。更稳妥的做法是后端对/files/**的静态资源也设置跨域允许,用addCorsMappings在资源映射里同时配置,或者直接用http.cors()配置全局。还有一个容易忽略的点:音频请求有时不经过axios,是audio自己发起的,所以axios里配的Header不会带上去,后端如果对音频接口要求鉴权,就会返回401导致播放失败。我的处理是:音频接口不做强制鉴权,或者用签名URL的方式,但对于毕设,直接放行最省事。

6.5 MyBatis-Plus分页不生效

用了分页插件后如果发现分页没生效,返回的还是全部数据,八成是没配置分页拦截器。MyBatis-Plus在SpringBoot里要手动注册一个PaginationInterceptor(3.4版本后叫MybatisPlusInterceptor),遗漏这步的话Page参数会被当成普通参数忽略。检查一下配置类里有没有写这个Bean,注意版本对应的类名。

写到这里,这套系统的核心链路算是完整走了一遍。我自己的体会是,音乐网站这种题目虽然常见,但想做得扎实并没那么容易。很多同学拿到一个源码就急着跑,跑通了就认为万事大吉,结果答辩时连“你的表结构是怎么设计的”都答不上来。既然这套代码里凝聚了前后端的完整知识链路,与其背代码,不如沉下心把每张表、每个接口、每条数据流都理清楚。这样到了答辩现场,无论老师从哪条线追问,你都能接得住。最后再分享一个小技巧:把项目跑通后,挑一个功能点做一次完整的“数据流讲解”练习,例如从用户点击播放按钮到audio出声,中间经历了哪些方法和接口,把这个讲顺了,整个项目你就真正吃透了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 11:41:37

Unity3D汽车游戏项目资源:车辆物理调参与手感优化实战

简介&#xff1a;这是一款基于Unity3D引擎开发的赛车驾驶类游戏项目&#xff0c;面向想要入门或进阶Unity游戏开发的学习者&#xff0c;可用于研究完整的游戏场景构建、物理模拟与交互逻辑。资源内含464个文件&#xff0c;压缩包约13.93MB&#xff0c;覆盖C#脚本、JavaScript脚…

作者头像 李华
网站建设 2026/9/26 11:41:10

OpenClaw本地部署实战:从模型接入到会话锁排查的完整指南

1. 为什么我在本地办公电脑上跑一个"龙虾"先说句实在话&#xff1a;OpenClaw 这套东西&#xff0c;第一眼看上去很像又一个大而全的 AI Agent 平台&#xff0c;网上铺天盖地的都是"AI 接管电脑""数字员工"这类口号&#xff0c;实际部署的路数却被…

作者头像 李华
网站建设 2026/9/26 11:40:05

Vscode插件推荐:用TaoToken统一Key接入自动检查单词拼写错误

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 11:39:49

AI辅助论文写作全流程实战:从选题到见刊的避坑指南

1. 先说清楚&#xff1a;AI 辅助论文创作的“能”与“不能”过去这一年&#xff0c;我陆陆续续用“虎贲等考 AI”这类工具帮自己、也帮实验室的师弟师妹们处理过十几篇期刊论文。说实话&#xff0c;第一次把它接进工作流的时候&#xff0c;我的心态就是“死马当活马医”——当时…

作者头像 李华
网站建设 2026/9/26 11:38:39

AI 做市场调研能力排行:从资料检索到报告交付,4 类工具怎么选

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华