JSP+MySQL实现的双角色音乐网站:用户在线试听下载 + 管理员后台歌曲管理
本文还有配套的精品资源点击获取简介这个Java Web音乐网站源码基于JSP、Servlet和MySQL构建支持普通用户与管理员两类角色。用户能搜索歌曲、在线播放MP3、下载音频文件、查看歌词.lrc、浏览最新/分类歌曲列表并有分页展示和最近播放记录功能管理员可登录后台完成歌曲的增删改查、上传MP3及歌词文件、编辑歌曲信息如名称、演唱者、分类、路径等、管理个人资料。项目结构完整包含login.jsp、songList.jsp、adm_add.jsp、upFile.jsp等标准页面以及upFile_deal.jsp处理音频上传、upLrcFile_deal.jsp处理歌词上传、checkMusic.jsp播放权限校验、download.jsp下载响应等核心逻辑页面。适配JDK 1.8、Tomcat 7兼容Eclipse和IDEA开发环境数据库含admin管理员、song歌曲、category分类三张主表字段覆盖编号、歌名、演唱者、文件路径、格式、收听数、上传时间等实用信息附带示例音频‘光辉岁月.mp3’和对应歌词文件SQL脚本yinyue_system.sql已提供建表语句。1. 项目概述一个“能跑、能用、能改”的Java Web音乐系统到底长什么样你有没有遇到过这样的情况在学完Servlet和JSP之后脑子里全是request.setAttribute、response.sendRedirect、session.getAttribute这些概念但一想做个完整项目却卡在“登录页怎么连数据库”“文件上传到底要配几个参数”“分页到底是前端算还是后端算”——不是不会写代码而是不知道真实项目里这些模块是怎么咬合在一起的。这个“JSPMySQL双角色音乐网站”就是我当年带学生做课程设计时反复打磨出的一套可落地、可调试、可扩展的Java Web最小可行系统MVP。它不追求炫酷的前端动画或微服务架构而是把“用户能听歌、管理员能管歌”这件事从数据库建表、请求流转、文件存储、权限隔离到异常兜底全部用最朴素、最标准、最符合企业早期Web开发习惯的方式实现出来。核心关键词“JSP音乐系统”“Java Web源码”“MySQL歌曲管理”说白了就是三个锚点界面层用JSP渲染不是Vue也不是React、逻辑层靠Servlet驱动不是Spring Boot自动装配、数据层由MySQL承载不是Redis缓存也不是MongoDB文档。它解决的不是高并发问题而是“如何让一个刚学完JDBC的学生在两天内把一个带文件上传、带权限判断、带分页展示的网站跑起来”。比如普通用户点击“下载”按钮背后是download.jsp读取song表里的file_path字段再用response.getOutputStream()把磁盘上的MP3文件流式吐出去而管理员上传一首新歌upFile.jsp提交表单后upFile_deal.jsp会调用Apache Commons FileUpload解析multipart/form-data校验文件大小和类型生成唯一文件名保存到WebContent/upload/目录下再把路径和元数据插入song表——每一步都对应着教材里的一个知识点但又比教材多出“路径怎么配置才不被外部直接访问”“中文文件名怎么避免乱码”“歌词.lrc怎么同步解析显示”这些真正踩坑后才懂的细节。它适合三类人Java Web初学者用来练手复现毕业设计学生用来快速搭建骨架或者老开发想重温Servlet原生开发时对照着看自己当年漏掉了哪些边界处理。2. 整体架构与设计思路为什么坚持用“古老”技术栈做这件事2.1 技术选型背后的现实考量很多人看到JDK 1.8 Tomcat 7就皱眉觉得“太老了”。但恰恰是这种“老”让它成为教学和快速验证的最优解。我来拆解一下这个组合的底层逻辑JSP作为视图层本质是“HTMLJava脚本”的混合体。它不像Thymeleaf需要额外学习模板语法也不像JSPX那样强制XML规范。login.jsp里直接写% request.getAttribute(msg) ! null ? request.getAttribute(msg) : %学生一眼就能看懂这是从Servlet传过来的错误提示。更重要的是JSP编译后的Servlet源码Tomcat/work/Catalina/localhost/xxx/org/apache/jsp/login_jsp.java可以手动打开看——原来% %里的代码真的变成了_jspx_th_c_005fif_005f0.setTest(((java.lang.Boolean) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate(${empty param.username}, java.lang.Boolean.class, (PageContext)_jspx_page_context, null, false)).booleanValue());这种“所见即所得”的透明性对理解MVC中View如何工作至关重要。Servlet作为控制器强制你直面HTTP协议本身。比如checkMusic.jsp里那句String musicId request.getParameter(id); if (musicId null || musicId.trim().isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST); return; }它逼你思考如果URL里没带id参数是跳转到404页还是返回JSON错误还是重定向回列表而Spring MVC的RequestParam(required false)会帮你兜底但初学者反而容易忽略“参数缺失”这个最基础的异常分支。这个项目里所有Servlet逻辑都藏在JSP的% %块里虽然不推荐但教学上足够清晰或者封装成独立的Java类如SongDao.java目的就是让你看清request怎么来、response怎么走、session怎么存。MySQL作为数据库选择的是“够用就好”的范式设计。admin表只有id、username、password三个字段没加salt、没上bcrypt因为课程设计重点是CRUD流程不是密码安全工程song表的file_path存的是相对路径如upload/ghsy.mp3而不是绝对路径或OSS URL因为本地开发时Tomcat的WebApp目录结构就是天然的文件服务器。这种设计牺牲了生产环境的安全性和扩展性但换来了“导入SQL脚本→启动Tomcat→打开浏览器→输入账号密码→立刻看到歌曲列表”的零障碍体验。你不需要先搭Nginx反向代理也不用配MySQL主从更不用研究Druid连接池的maxActive参数——所有复杂度都被压到了最低。提示不要试图在这个项目里强行加入Spring或MyBatis。就像学骑自行车不能先装ABS系统一样原生Servlet的request.getParameter()、response.getWriter().write()、PreparedStatement.executeUpdate()才是理解Web开发地基的砖块。等你能徒手写出一个带事务回滚的歌曲删除功能删song记录的同时删物理文件再升级框架不迟。2.2 双角色权限模型的极简实现这个系统的权限控制没有用Shiro或Spring Security而是用最原始的session属性标记JSP逻辑判断。流程非常直白用户在login.jsp提交表单LoginServlet查询admin表验证账号密码验证成功后session.setAttribute(admin, admin)其中admin是Admin实体类对象所有后台页面如adm_add.jsp开头都有% if (session.getAttribute(admin) null) { response.sendRedirect(login.jsp?msg请先登录); return; } %普通用户页面如songList.jsp则完全不检查session但播放和下载链接会指向checkMusic.jsp做二次校验防止用户手动拼URL绕过。这种方案的好处是没有配置文件、没有注解、没有拦截器链所有逻辑都在几行JSP脚本里。坏处也很明显——无法细粒度控制比如“管理员只能删自己上传的歌”。但作为教学案例它精准击中了“身份认证”这个核心需求且代码量极少。我试过让学生用这个模型去扩展“会员等级”功能只需在admin表加level字段在songList.jsp里加c:if test${sessionScope.admin.level ge 2}VIP专享/c:if就能立刻理解权限分级的雏形。2.3 文件上传与存储的务实方案音频和歌词文件的处理是这个项目区别于纯CRUD练习的关键。它没用七牛云或阿里OSS而是采用“Web应用目录内建文件夹”的方案所有上传文件存放在WebContent/upload/目录下注意不是src/main/webapp/upload/因为Tomcat部署时会把WebContent整个复制过去upFile_deal.jsp里用String savePath application.getRealPath(/upload/) File.separator fileName;获取真实磁盘路径生成唯一文件名用UUID.randomUUID().toString().replace(-, ) _ originalFileName避免重名覆盖对MP3文件做双重校验一是contentType.equals(audio/mpeg)二是用FileInputStream读取文件头4字节比对是否为ID3MP3标准标识。为什么这么做因为学生最容易卡在“文件上传后找不到文件”。用application.getRealPath()能确保路径指向Tomcat实际运行的目录比如D:\apache-tomcat-9.0.83\webapps\yinyue_system\upload\而不是IDE里的项目路径。而歌词文件.lrc的处理更简单upLrcFile_deal.jsp只校验后缀名然后同样存到upload目录song表里用lrc_path字段记录路径。播放页面songDetail.jsp里用iframe src${song.lrcPath} width100% height200px/iframe直接嵌入利用浏览器对文本文件的原生支持——不用解析时间轴不用写JavaScript滚动同步这就是教学项目的“够用哲学”。3. 核心模块详解与实操要点3.1 数据库设计三张表如何支撑起整个音乐世界数据库脚本yinyue_system.sql定义了admin、song、category三张表表面看很简单但每个字段的选择都藏着实操经验-- 管理员表简化版 CREATE TABLE admin ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(50) NOT NULL COMMENT 密码明文仅教学用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8; -- 歌曲分类表 CREATE TABLE category ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 分类名称如摇滚、流行, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8; -- 歌曲主表核心 CREATE TABLE song ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 歌名, singer varchar(100) DEFAULT NULL COMMENT 演唱者, category_id int(11) DEFAULT NULL COMMENT 外键关联category.id, file_path varchar(255) NOT NULL COMMENT MP3文件相对路径如 upload/ghsy.mp3, lrc_path varchar(255) DEFAULT NULL COMMENT 歌词文件相对路径如 upload/ghsy.lrc, format varchar(10) DEFAULT mp3 COMMENT 音频格式, play_count int(11) DEFAULT 0 COMMENT 收听次数每次checkMusic.jsp访问1, upload_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间, PRIMARY KEY (id), KEY fk_category (category_id), CONSTRAINT fk_category FOREIGN KEY (category_id) REFERENCES category (id) ) ENGINEInnoDB DEFAULT CHARSETutf8;关键细节解析file_path和lrc_path存的是相对路径不是URL。这是为了download.jsp能用getServletContext().getRealPath(filePath)直接定位到文件。如果存成http://localhost:8080/yinyue_system/upload/ghsy.mp3download.jsp就只能重定向302而无法用流式响应200——后者才能触发浏览器下载对话框。play_count字段的更新时机很重要。它不是在songList.jsp里统计而是在checkMusic.jsp里执行UPDATE song SET play_count play_count 1 WHERE id ?。因为只有真正点击“播放”按钮才会触发checkMusic.jsp这样数据才真实反映有效播放量。我试过把更新放到songDetail.jsp结果发现搜索引擎爬虫抓取详情页也会导致计数虚高。外键约束fk_category必须显式声明。很多学生导出SQL时忘了勾选“外键”导致category_id字段变成普通int后续联查时LEFT JOIN category c ON s.category_id c.id就查不到分类名称。在MySQL Workbench里建表时右键“Foreign Key”选项必须手动添加不能只靠字段命名暗示。注意password字段用varchar(50)存明文纯粹为教学简化。真实项目必须用BCrypt加密且字段长度至少60。这里故意留个“安全隐患”就是为了让学生在扩展作业里动手改成BCryptPasswordEncoder.encode(rawPassword)。3.2 用户端核心功能从搜索到播放的全链路普通用户的操作流是登录 → 浏览列表 → 搜索 → 查看详情 → 播放/下载。每个环节都有值得深挖的细节搜索功能songList.jsp搜索框提交到songList.jsp?keyword光辉后端用request.getParameter(keyword)获取关键词SQL拼接为SELECT s.*, c.name as category_name FROM song s LEFT JOIN category c ON s.category_id c.id WHERE s.name LIKE ? OR s.singer LIKE ? ORDER BY s.upload_time DESC LIMIT ?, ?占位符?对应%keyword%。这里有两个易错点一是LIKE查询必须手动加%通配符JDBC不会自动补二是排序用upload_time DESC保证最新上传的歌排前面而不是按id排序id可能不连续。分页实现songList.jsp分页不是前端JS计算而是后端SQL的LIMIT offset, size。offset (currentPage - 1) * pageSizepageSize 10。songList.jsp里用JSTL标签循环输出页码c:forEach begin1 end${totalPages} vari a hrefsongList.jsp?page${i}keyword${param.keyword}${i}/a /c:forEachtotalPages由SELECT COUNT(*) FROM song WHERE ...计算得出。我见过太多学生把分页逻辑写在前端结果数据量一大页面直接卡死——数据库分页才是王道。在线试听checkMusic.jsp这是整个系统的技术亮点。checkMusic.jsp不渲染页面而是做三件事1. 校验song.id是否存在且状态正常比如没被逻辑删除2. 更新play_count字段前面已提3. 用response.setContentType(audio/mpeg)设置响应头再用FileInputStream读取MP3文件流通过response.getOutputStream()输出。关键代码片段String filePath request.getParameter(filePath); // 从song表查出的file_path String realPath application.getRealPath(filePath); File file new File(realPath); if (!file.exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND, 音频文件不存在); return; } response.setContentType(audio/mpeg); response.setHeader(Content-Length, String.valueOf(file.length())); response.setHeader(Content-Disposition, inline; filename\ file.getName() \); FileInputStream fis new FileInputStream(file); ServletOutputStream sos response.getOutputStream(); byte[] buffer new byte[8192]; int len; while ((len fis.read(buffer)) ! -1) { sos.write(buffer, 0, len); } fis.close(); sos.flush();Content-Disposition: inline告诉浏览器“直接播放”而不是下载。如果改成attachment就会触发下载——这正是download.jsp的实现方式。最近播放记录基于Cookie没有用数据库存播放历史太重而是用Cookie// checkMusic.jsp末尾 Cookie historyCookie new Cookie(recent_play, songId , System.currentTimeMillis()); historyCookie.setMaxAge(60 * 60 * 24); // 保存24小时 historyCookie.setPath(/); response.addCookie(historyCookie);songList.jsp里读取% Cookie[] cookies request.getCookies(); String recentPlay ; if (cookies ! null) { for (Cookie cookie : cookies) { if (recent_play.equals(cookie.getName())) { recentPlay cookie.getValue(); break; } } } %然后解析recentPlay字符串用SELECT * FROM song WHERE id IN (...)查出最近播放的5首歌。这种轻量级方案比建一张play_history表更符合教学场景。3.3 管理员后台增删改查之外的文件生命管理管理员功能集中在adm_*.jsp系列页面核心难点不在SQL而在文件与数据库的强一致性。上传新歌upFile.jsp → upFile_deal.jspupFile.jsp是一个标准的enctypemultipart/form-data表单包含歌名、演唱者、分类下拉框、MP3文件选择框。upFile_deal.jsp的处理流程解析multipart请求必须用Apache Commons FileUploadpom.xml里已引入xml dependency groupIdcommons-fileupload/groupId artifactIdcommons-fileupload/artifactId version1.5/version /dependency因为Tomcat原生不支持multipart解析request.getParameter()对文件域永远返回null。校验文件安全性- 文件大小限制if (item.getSize() 50 * 1024 * 1024) { // 50MB }- 文件类型校验不仅看item.getContentType()浏览器可伪造还要读取文件头java byte[] header new byte[4]; item.getInputStream().read(header); String hexHeader bytesToHex(header); // ID3对应494433 if (!hexHeader.startsWith(494433)) { throw new RuntimeException(非MP3文件); }生成唯一文件名并保存java String originalName item.getName(); String ext originalName.substring(originalName.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) ext; String savePath application.getRealPath(/upload/) File.separator newFileName; item.write(new File(savePath));插入数据库记录INSERT INTO song(name, singer, category_id, file_path, format) VALUES (?, ?, ?, ?, ?)其中file_path存的是/upload/abc123.mp3注意开头的斜杠这是相对Web应用根路径的。实操心得application.getRealPath(/upload/)返回的路径末尾没有斜杠所以必须手动加File.separator。我第一次部署到Linux服务器时因为没加这个路径变成/var/lib/tomcat9/webapps/yinyue_system/uploadabc123.mp3少了个/导致文件找不到。这个细节只有真正在不同系统上部署过才会记住。删除歌曲adm_delete.jsp删除是“物理删除文件清理”的原子操作// 先查出要删的song记录 String sql SELECT file_path, lrc_path FROM song WHERE id ?; // 执行DELETE FROM song WHERE id ? // 再用File.delete()删MP3和LRC文件 String realMp3Path application.getRealPath(song.getFilePath()); String realLrcPath application.getRealPath(song.getLrcPath()); new File(realMp3Path).delete(); if (song.getLrcPath() ! null) { new File(realLrcPath).delete(); }必须先查再删不能直接DELETE FROM song后凭空拼路径。因为file_path字段可能被手动修改过直接拼接会有风险。修改歌曲信息adm_edit.jsp → adm_edit_deal.jsp编辑页面要预填充原有数据所以adm_edit.jsp的URL是adm_edit.jsp?id123页面里用input value${song.name}回显。关键点在于如果用户没上传新文件file_path字段要保持原值不能为空。所以adm_edit_deal.jsp里要判断String newFilePath ; // 新上传的文件路径 if (hasNewFile) { // 执行上传逻辑生成newFilePath // 并删除旧文件new File(application.getRealPath(oldFilePath)).delete(); } else { newFilePath oldFilePath; // 复用旧路径 }否则一次不带文件的编辑操作就会让file_path变空导致播放失败。4. 实操过程与环境配置从零开始跑通项目的完整步骤4.1 开发环境搭建Eclipse/IDEA通用第一步导入项目到IDE- 解压资源包找到yinyue_system文件夹不是同名的.zip或master-xxx文件夹- EclipseFile → Import → Existing Projects into Workspace → 选择yinyue_system目录- IDEAFile → Open → 选择yinyue_system目录IDEA会自动识别为Dynamic Web Project- 关键检查Project Facets里必须勾选Dynamic Web Module 3.0对应Servlet 3.0Java版本选1.8。第二步配置Tomcat服务器- EclipseWindow → Preferences → Server → Runtime Environments → Add → Apache Tomcat v7.0 → 指向Tomcat安装目录- IDEAFile → Project Structure → Project Settings → Artifacts → 点击号 → Web Application: Exploded → OK- 启动前右键项目 → Run As → Run on Server → 选择已配置的Tomcat。第三步初始化数据库- 用MySQL客户端如Navicat或命令行执行yinyue_system.sql- 检查字符集SHOW CREATE TABLE song;确认是DEFAULT CHARSETutf8不是latin1- 插入测试数据可选sql INSERT INTO admin(username, password) VALUES (admin, 123456); INSERT INTO category(name) VALUES (经典老歌), (流行音乐); INSERT INTO song(name, singer, category_id, file_path, lrc_path) VALUES (光辉岁月, Beyond, 1, /upload/光辉岁月.mp3, /upload/光辉岁月.lrc);第四步配置数据库连接web.xml或context.xml项目用的是传统JDBC连接信息硬编码在src/com/utils/DBUtil.java里private static final String URL jdbc:mysql://localhost:3306/yinyue_system?useUnicodetruecharacterEncodingUTF-8; private static final String USERNAME root; private static final String PASSWORD your_password;必须修改PASSWORD为你本地MySQL的密码。如果MySQL没设密码留空即可。常见错误忘记改密码导致登录时抛SQLException: Access denied。4.2 关键页面访问路径与调试技巧页面访问URL调试要点登录页http://localhost:8080/yinyue_system/login.jsp输入admin/123456观察response.sendRedirect(songList.jsp)是否生效检查浏览器地址栏是否变成.../songList.jsp歌曲列表http://localhost:8080/yinyue_system/songList.jsp查看页面源码确认c:forEach items${songList} varsong是否渲染出表格用浏览器开发者工具Network标签看songList.jsp返回的HTML里是否有trtd光辉岁月/td.../tr播放校验http://localhost:8080/yinyue_system/checkMusic.jsp?id1直接访问此URL应听到MP3声音若报404检查file_path字段值是否正确如/upload/光辉岁月.mp3再确认WebContent/upload/目录下是否存在该文件下载响应http://localhost:8080/yinyue_system/download.jsp?id1应触发下载对话框若页面空白检查response.setContentType(application/octet-stream)和Content-Disposition头是否设置正确调试利器日志输出在关键JSP里加System.out.println(DEBUG: variable);日志会输出到Tomcat的logs/catalina.out文件。比如在checkMusic.jsp开头加% System.out.println(DEBUG: musicId request.getParameter(id)); %重启Tomcat后访问立刻能看到控制台输出比打断点更快定位参数问题。4.3 文件上传实战处理中文名与路径问题资源包里自带的光辉岁月.mp3和光辉岁月.lrc是UTF-8编码的中文文件名但Windows系统默认用GBK这会导致上传后文件名乱码如光辉岁月.mp3变成???.mp3。解决方案Tomcat配置统一编码在conf/server.xml的Connector节点里加URIEncodingUTF-8xml Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /JSP页面声明编码所有JSP顶部加% page contentTypetext/html;charsetUTF-8 %数据库连接URL加参数jdbc:mysql://...?useUnicodetruecharacterEncodingUTF-8已在DBUtil.java里配置。实测下来三者缺一不可。我曾漏掉server.xml的配置在Linux服务器上一切正常但Windows用户上传中文名文件就失败——因为Tomcat在Windows下默认用系统编码解析URL。5. 常见问题与排查技巧实录5.1 经典报错速查表报错现象可能原因排查步骤解决方案HTTP Status 404 – /yinyue_system/login.jsp项目未部署成功或上下文路径错误1. 查Tomcatwebapps目录下是否有yinyue_system文件夹2. 查浏览器地址栏URL是否多写了/yinyue_system如http://localhost:8080/yinyue_system/yinyue_system/login.jsp重新部署项目确保URL是http://localhost:8080/yinyue_system/login.jspjava.lang.ClassNotFoundException: org.apache.commons.fileupload.FileItem缺少commons-fileupload依赖1. 查WEB-INF/lib目录下是否有commons-fileupload-1.5.jar2. 查pom.xml是否已声明依赖手动拷贝jar包到WEB-INF/lib或用Maven重新buildSQLException: Access denied for user rootlocalhost数据库连接密码错误1. 查DBUtil.java里的PASSWORD常量2. 用MySQL客户端尝试mysql -u root -p登录修改DBUtil.java中的密码或重置MySQL root密码HTTP Status 500 – java.io.FileNotFoundException: .../upload/xxx.mp3 (No such file or directory)文件路径不匹配1. 查song表里的file_path字段值2. 查WebContent/upload/目录下实际文件名3. 查Tomcat日志里application.getRealPath()打印的路径确保file_path以/upload/开头确认文件确实存在检查Linux/Windows路径分隔符/vs\播放页面空白无声音MP3文件损坏或MIME类型错误1. 直接用浏览器访问http://localhost:8080/yinyue_system/upload/xxx.mp32. 查checkMusic.jsp里response.setContentType()的值用Audacity打开MP3确认可播放将contentType改为audio/mpeg5.2 我踩过的坑与独家避坑技巧坑1JSP页面中文乱码表单提交后request.getParameter(name)是乱码- 表象登录页输入用户名“管理员”后端收到的是“管ç†å”- 根因Tomcat对POST请求默认用ISO-8859-1解码而页面是UTF-8- 解决在所有接收POST的JSP或Servlet开头加java request.setCharacterEncoding(UTF-8);或者更彻底在web.xml里配置全局过滤器CharacterEncodingFilter但教学项目里直接加这一行最省事。坑2上传的MP3文件能下载但无法在线播放- 表象checkMusic.jsp?id1返回空白页面Network标签显示200但无音频流- 根因response.setContentType(audio/mpeg)写成了audio/mp3标准MIME类型是audio/mpeg- 解决查W3C标准MP3的合法MIME类型只有audio/mpeg和audio/x-mpegaudio/mp3是无效值。坑3分页时第2页数据显示重复或错乱- 表象songList.jsp第1页显示1-10条第2页还是显示1-10条- 根因LIMIT参数计算错误offset currentPage * pageSize应为(currentPage - 1) * pageSize- 解决在SongDao.java的getSongList()方法里检查SQL的LIMIT子句确保offset从0开始。坑4管理员修改歌曲后旧MP3文件没删磁盘空间越积越多- 表象WebContent/upload/目录下文件数量暴增但song表里只有几十条记录- 根因adm_edit_deal.jsp里只处理了新文件上传没写旧文件删除逻辑- 解决在adm_edit_deal.jsp里先SELECT file_path FROM song WHERE id ?查出旧路径再new File(...).delete()。最后一个小技巧如果想快速验证所有功能不必手动点一遍。写个简单的测试HTMLhtml a hreflogin.jsp登录页/abr a hrefsongList.jsp歌曲列表/abr a hrefcheckMusic.jsp?id1播放ID1/abr a hrefdownload.jsp?id1下载ID1/abr a hrefadm_add.jsp后台添加/a放在WebContent/test.html直接访问就能一键跳转省去反复输URL的时间。6. 项目扩展建议从“能跑”到“能用”的进阶路径这个项目的价值不仅在于它现在能做什么更在于它为你预留了多少可扩展的接口。我带过十几届学生最常见的毕业设计升级方向有三个方向一前端现代化零成本提升体验- 把JSP里的HTML表格换成Bootstrap 5的响应式卡片布局加个搜索框的实时过滤用AJAX songList.jsp?keyword- 用audio controls标签替代checkMusic.jsp的流式播放让播放器控件更友好- 用Chart.js画个简单的“分类歌曲数量饼图”数据从SELECT c.name, COUNT(*) FROM song s JOIN category c ON s.category_id c.id GROUP BY c.name来。方向二后端健壮性补上生产环境短板- 给password字段加BCrypt加密引入spring-boot-starter-web哪怕不用Spring Boot只用spring-security-crypto包在LoginServlet里用BCryptPasswordEncoder.encode()存密码- 增加“软删除”给song表加is_deleted tinyint(1) DEFAULT 0所有查询加WHERE is_deleted 0删除操作改为UPDATE song SET is_deleted 1- 用Druid替换原生JDBC连接池在context.xml里配置Resource namejdbc/yinyue authContainer typejavax.sql.DataSource driverClassNamecom.mysql.jdbc.Driver url... username... password... initialSize5 maxActive20/。方向三功能深化贴近真实产品需求- 加“收藏夹”功能建favorite表user_id,song_id用户登录后可收藏个人中心显示收藏列表- 加“评论区”建comment表song_id,content,create_timesongDetail.jsp里用c:forEach循环输出- 加“歌手页”singer.jsp?nameBeyond查所有演唱者为Beyond的歌曲用WHERE singer LIKE ?实现模糊匹配。这些扩展都不需要推翻现有架构。你只是在songList.jsp里加几行HTML在SongDao.java里加一个getFavoriteSongs(int userId)方法在yinyue_system.sql里加一条CREATE TABLE favorite (...)语句——这就是一个成熟Java Web项目应有的可维护性。它不炫技但每一步都扎实它不完美但每一个缺陷都是你动手改进的理由。当你亲手把password字段从明文改成BCrypt把file_path从相对路径改成OSS URL你就完成了从“会写代码”到“懂系统设计”的跨越。本文还有配套的精品资源点击获取简介这个Java Web音乐网站源码基于JSP、Servlet和MySQL构建支持普通用户与管理员两类角色。用户能搜索歌曲、在线播放MP3、下载音频文件、查看歌词.lrc、浏览最新/分类歌曲列表并有分页展示和最近播放记录功能管理员可登录后台完成歌曲的增删改查、上传MP3及歌词文件、编辑歌曲信息如名称、演唱者、分类、路径等、管理个人资料。项目结构完整包含login.jsp、songList.jsp、adm_add.jsp、upFile.jsp等标准页面以及upFile_deal.jsp处理音频上传、upLrcFile_deal.jsp处理歌词上传、checkMusic.jsp播放权限校验、download.jsp下载响应等核心逻辑页面。适配JDK 1.8、Tomcat 7兼容Eclipse和IDEA开发环境数据库含admin管理员、song歌曲、category分类三张主表字段覆盖编号、歌名、演唱者、文件路径、格式、收听数、上传时间等实用信息附带示例音频‘光辉岁月.mp3’和对应歌词文件SQL脚本yinyue_system.sql已提供建表语句。本文还有配套的精品资源点击获取