
简介基于JavaWeb的小型音乐网站完整项目适合JavaWeb初学者、课程设计或毕业设计人群参考功能覆盖音乐在线播放、下载、分类浏览与排行榜展示。资源包共881个文件压缩包约189.75MB除jsp/java源文件、class编译文件、jar依赖库和sql数据库脚本外还包含png/jpg/gif等页面素材、css/js/html前端样式、mp3示例音乐war包可直接放置到Tomcat下运行测试开发环境为Eclipse、Tomcat、MySQL和JDK。目前已有915人学习下载。对于希望理解原生JavaWeb开发流程的读者该项目展示了从前端页面到Servlet/DAO层再到MySQL数据库的完整调用链路既可直接部署体验也可以按需抽取音乐上传、播放、分类排行等模块配合完整jar包快速迁移到自己的课设或练手项目中降低重复搭建成本。1. 基于JavaWeb的小型音乐网站课设级项目为什么值得自己敲一遍期末前两周你刚把JavaSE的语法忘掉一半JavaWeb课程设计却要求交一个能跑、能演示、能答辩的Web项目——音乐网站几乎是这类需求里最经典的选题因为它功能边界清晰用户注册登录、歌曲搜索、歌单管理、在线播放每一个模块都对应着JavaWeb里最核心的知识点。这个标题背后就是用Servlet JSP MySQL Tomcat这套原生技术栈从零手写一个小型在线音乐站不依赖Spring Boot不引入前后端分离把请求从哪里来、数据存到哪里去这条链路彻底走一遍。相比那些抄来的SSM商城项目JavaWeb原生项目反而更容易讲清楚也更容易在答辩时扛住追问它对新手非常友好对想补项目经验的人则是一块扎实的垫脚石。我自己带过的几个人都是从这类项目开始才真正分得清Servlet、Filter、Listener到底在Web容器里各自扮演什么角色。2. 技术选型与工程骨架ServletJSPMySQL这套组合现在依然能打2.1 为什么不用Spring BootJavaWeb原生栈的边界在哪很多人一上来就问现在都2025年了为什么还要用JavaWeb原生的Servlet写项目答案要分两头说。如果你去公司实习或做正式产品Spring Boot确实是绝对主流它把Tomcat内嵌、依赖管理、自动配置全包了你写一个controller就能跑起来。但课程设计和面试造轮子的场景恰恰相反老师要考察的是你知不知道HTTP请求是怎么被处理的、Session是怎么维持的、JDBC连接是怎么打开的——这些东西在Spring Boot里全被框架遮住了你打开一个Spring Boot项目甚至找不到web.xml在哪。用JavaWeb原生栈做一个音乐网站你会被迫面对每一个细节Servlet映射要自己配、数据库连接要自己管、JSP里的EL表达式要自己写。这种“不舒服”恰恰是学习价值所在。这台小型音乐网站的边界也很明确不追求高并发、不引入Redis缓存、不做分布式存储所有数据落在本地MySQL里Tomcat单节点部署。它的核心目标是把一个完整的业务闭环跑通——注册、登录、搜索、建歌单、播放歌曲——让你看清楚一条请求从浏览器地址栏出发穿过Servlet、Service、DAO三个层最后回到JSP页面的完整路径。如果你已经能独立把这个流程讲给同学听再去学Spring Boot会发现很多概念其实你已经会了只是换个写法而已。2.2 IDEA里搭出JavaWeb项目结构Artifact与lib目录是第一个坎在IDEA里新建JavaWeb项目的步骤不算多但每一步都有容易踩坑的地方。常见做法是选择Jakarta EE或老版本里的Java Enterprise下的Web Application模板创建项目然后手动补上src/main/java、src/main/resources、src/main/webapp这三个目录。注意IDEA里其实有两种“JavaWeb项目”一种是Maven骨架生成的另一种是普通Java项目手动加Web支持我建议直接用Maven骨架因为后续需要哪些依赖在pom.xml里声明一次就够了不用满世界找jar包。接下来是Artifact配置。在Project Structure快捷键CtrlAltShiftS里找到Artifacts确保Web Application Exploded这个产物存在且Output Layout里已经把webapp目录作为Web资源根目录。很多人在这里翻车项目能编译但Tomcat一启动就404原因十有八九是Artifact里没有把编译后的class文件输出到WEB-INF/classes下也没有把依赖的jar包输出到WEB-INF/lib下。IDEA虽然会自动帮你处理大部分情况但如果你手动改过输出路径这两个目录就很容易丢。每次改完结构最好在Output Layout里点一下“Available Elements”里右键放进左边把依赖完整打进去。2.3 三层架构的调用链从浏览器请求到数据库回写的完整路径一个典型的音乐网站请求长这样用户在登录页输入账号密码点击登录浏览器向/login发送POST请求Tomcat根据web.xml或注解里的映射关系找到LoginServletLoginServlet调用UserService的login方法UserService里再调用UserDao的findByUsernameAndPassword方法UserDao通过JDBC执行SQL把结果一层层返回最后Servlet把用户信息写进Session重定向到首页。这条链路就是JavaWeb的三层架构表示层JSP/Servlet、业务层Service、数据访问层DAO。这个项目里我一般把包结构固定下来entity包里放User、Song、SongList三个实体类dao包里放对应的数据访问类service包里放业务逻辑web包或controller包放Servletfilter包放编码过滤器和登录校验过滤器util包里放JDBC连接工具类。这样做的好处是代码找起来快答辩时也好讲每个包对一层架构。有一点需要注意小型项目不必为了“架构完美”强行把Service再拆接口和实现类直接写成UserService类就够用写两层反而让新手看不懂调用关系。3. 数据库设计四张表撑起一个音乐站3.1 表结构与DDL脚本用户、歌曲、歌单、关联表小型音乐网站不需要复杂的数据库设计四张表就够用户表、歌曲表、歌单表、歌单歌曲关联表。歌单和歌曲是多对多关系一首歌可以出现在多个歌单里一个歌单包含多首歌所以需要一张中间表。以下是完整的建表脚本按顺序在MySQL里执行即可。-- 用户表存储注册用户的基本信息 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名唯一约束, password VARCHAR(100) NOT NULL COMMENT 密码建议存MD5摘要, nickname VARCHAR(50) DEFAULT COMMENT 昵称允许为空, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 歌曲表存储歌曲元数据音频文件放服务器磁盘数据库只存路径 CREATE TABLE t_song ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 歌曲ID, title VARCHAR(100) NOT NULL COMMENT 歌曲名, singer VARCHAR(50) NOT NULL COMMENT 歌手名, duration VARCHAR(10) DEFAULT 00:00 COMMENT 时长存储为字符串如03:45, album VARCHAR(100) DEFAULT COMMENT 专辑名, url VARCHAR(255) NOT NULL COMMENT 音频文件路径如/static/music/song1.mp3 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT歌曲表; -- 歌单表属于某个用户 CREATE TABLE t_songlist ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 歌单ID, user_id INT NOT NULL COMMENT 所属用户ID, name VARCHAR(50) NOT NULL COMMENT 歌单名称, description VARCHAR(200) DEFAULT COMMENT 歌单描述, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_songlist_user FOREIGN KEY (user_id) REFERENCES t_user(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT歌单表; -- 歌单歌曲关联表一张歌单对应多首歌一首歌可出现在多个歌单 CREATE TABLE t_songlist_song ( id INT PRIMARY KEY AUTO_INCREMENT, songlist_id INT NOT NULL, song_id INT NOT NULL, added_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_songlist_song (songlist_id, song_id), CONSTRAINT fk_sls_songlist FOREIGN KEY (songlist_id) REFERENCES t_songlist(id) ON DELETE CASCADE, CONSTRAINT fk_sls_song FOREIGN KEY (song_id) REFERENCES t_song(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT歌单歌曲关联表;这段DDL里有几个设计决策值得说明。第一t_user.username加了UNIQUE约束这是注册时判断“用户名是否存在”的数据库层面的最后一道防线靠代码判断总会存在并发间隙唯一索引能把重复拦死。第二duration字段用VARCHAR而不是INT因为页面上要显示“03:45”这样的格式存秒数的话每层都得做转换小型项目没必要为难自己。第三外键约束在这个规模下可以加InnoDB引擎能保证数据一致性删用户的时候级联删掉他的歌单省去手动清理。3.2 字段设计里的三个细节索引、软删除和音频文件存储第一个细节是索引。t_song表最频繁的查询是按歌名或歌手模糊搜索所以应该给title和singer分别加上普通索引。虽然LIKE %关键词%这种前置模糊匹配用不上索引但搜索框里用户输入完整歌名的概率并不低有索引总是多一层保障。index这块很多新手会忽略数据量小时没感觉等插了几千首歌再搜就会明显变慢先养成设计索引的习惯没坏处。第二个细节是软删除。小型音乐网站不做回收站功能所以直接物理删除就好不需要在表里加deleted字段。但如果你后期想加“最近播放”“收藏数量”这类功能软删除反而会拖累统计逻辑。项目规模决定设计复杂度不要在四张表的阶段就引入冗余字段这是很多人容易犯的过度设计。第三个细节是音频文件存储。数据库里只存/static/music/xxx.mp3这样的相对路径文件本身放在webapp目录下的static/music文件夹里。有些同学喜欢把音频BASE64编码后塞进数据库这会让数据库体积爆炸、查询变慢是典型的反模式。Tomcat默认会处理webapp下的静态文件所以audio标签能直接访问到这些MP3不需要写专门的下载Servlet。3.3 用命令行导入初始数据给歌曲表塞几十首歌项目演示时需要数据支撑空荡荡的页面没法答辩。可以手动INSERT也可以在MySQL命令行里写一个小脚本循环插入。我一般会准备20到50首常见歌曲的数据这样搜索时命中率高一些。-- 插入测试用户密码是123456的MD5摘要 INSERT INTO t_user (username, password, nickname) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 管理员); -- 批量插入歌曲 INSERT INTO t_song (title, singer, duration, album, url) VALUES (晴天, 周杰伦, 04:29, 叶惠美, /static/music/qingtian.mp3), (海阔天空, Beyond, 05:24, 乐与怒, /static/music/haikuotiankong.mp3), (成都, 赵雷, 05:28, 无法长大, /static/music/chengdu.mp3);密码用MD5存储是JavaWeb项目里的常规操作但注意MD5本身已经不够安全进阶做法是加盐后再哈希后面第6章会讲。插入歌曲时URL对应真实的MP3文件名如果你没有现成的音频文件可以用FFmpeg生成几秒的静音测试文件或者从自己的本地音乐库里挑几首常用的拷进来。数据库建议使用utf8mb4字符集只比utf8多一点存储开销却能完整支持表情符号现在这已经是建库默认选择了。4. 核心功能实现注册登录、搜索播放、歌单管理4.1 登录与SessionLoginServlet这段代码值得背下来登录功能是每个JavaWeb项目的地基它涉及请求接收、参数校验、业务查询、Session写入、重定向跳转这几个环节。下面是登录Servlet的核心代码路径映射通过WebServlet注解完成。package com.example.web; import com.example.entity.User; import com.example.service.UserService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 设置编码注意要在读取参数之前执行 req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); // 2. 调用service层做校验 User user userService.login(username, password); if (user ! null) { // 3. 登录成功把用户信息放进Session页面就能用${sessionScope.user}取值 HttpSession session req.getSession(); session.setAttribute(user, user); resp.sendRedirect(req.getContextPath() /index); } else { // 4. 失败则回到登录页并携带错误提示 req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }这段代码有两个地方需要解释。第一req.setCharacterEncoding(UTF-8)必须在第一次读取参数之前调用否则POST请求体里的中文会按ISO-8859-1解析后面再设置就来不及了。第二登录成功后用sendRedirect而不用forward是为了防止用户按F5刷新导致表单重复提交这是PRG模式Post/Redirect/Get的核心思想课设答辩时能讲出这一点是加分项。UserService和UserDao的部分不复杂但DAO里有一个必须坚持的写法使用PreparedStatement而不是Statement。用户名和密码来自用户输入直接用字符串拼接SQL会引发SQL注入 OR 11这种字符串可以直接绕过登录。PreparedStatement先把SQL骨架发给数据库预编译再用参数填充注入代码就只是普通字符串没有执行机会。4.2 歌曲搜索与PreparedStatement模糊查询的写法搜索是音乐网站的核心体验搜索框输入一个歌名列表实时展示匹配结果。这里用最简单的表单提交即可GET请求传关键词Servlet解析后调用DAO查询。下面是搜索Servlet和DAO关键方法的代码。WebServlet(/search) public class SearchServlet extends HttpServlet { private SongDao songDao new SongDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String keyword req.getParameter(keyword); if (keyword null || keyword.trim().isEmpty()) { // 空关键词返回热门歌曲列表避免页面空白 keyword ; } // 调用DAO查询把结果放进request域转发到搜索页 req.setAttribute(songList, songDao.search(keyword.trim())); req.getRequestDispatcher(/search.jsp).forward(req, resp); } }// SongDao中的模糊查询方法 public ListSong search(String keyword) { String sql SELECT id, title, singer, duration, album, url FROM t_song WHERE title LIKE ? OR singer LIKE ? ORDER BY id DESC; ListSong list new ArrayList(); // Connection和PreparedStatement通过JDBC工具类获取try-with-resources释放 try (Connection conn JdbcUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String like % keyword %; ps.setString(1, like); ps.setString(2, like); ResultSet rs ps.executeQuery(); while (rs.next()) { Song song new Song(); song.setId(rs.getInt(id)); song.setTitle(rs.getString(title)); song.setSinger(rs.getString(singer)); // 省略其他字段set方法 list.add(song); } } catch (SQLException e) { e.printStackTrace(); } return list; }模糊查询有两个参数细节要注意。LIKE ?配合ps.setString(1, % keyword %)才能实现包含匹配如果直接把%写进SQL里也一样但参数化写法更统一。另外OR连接的两个条件都不加括号时如果将来再拼接其他条件ORDER BY的优先级容易出问题我习惯在SQL里给每个LIKE条件都加上显式括号降低后续维护时的理解成本。4.3 播放不写流代码Tomcat静态资源映射就够了在线播放音乐的功能在JavaWeb项目里其实比大多数人想的简单——不需要写任何流处理代码。把MP3文件放进src/main/webapp/static/music/目录数据库里存相对路径前端JSP页面直接用HTML5的audio标签播放。!-- 歌曲列表里的播放控件song.url来自后台渲染 -- audio controls preloadnone src${song.url}/audio这个方案能跑通的先决条件是Tomcat的默认静态资源映射没有被覆盖。Tomcat会把webapp目录作为站点根目录用户访问http://localhost:8080/static/music/qingtian.mp3时容器直接在磁盘上找对应的物理文件返回。如果你的web.xml里配置了servlet-mapping把根路径/拦截了静态资源就会404这就是很多项目出现了“列表能显示、点击播放没声音”的根源。解决方法是确认项目里没有全局拦截的Servlet或者在web.xml里追加一个DefaultServlet的映射配置。4.4 统一编码过滤器CharacterEncodingFilter的位置很关键中文乱码是JavaWeb新手最容易碰到的问题根因在于HTTP请求和响应默认使用ISO-8859-1编码。逐个Servlet写req.setCharacterEncoding太繁琐用一个Filter统一处理才是正解。以下是过滤器的完整实现。package com.example.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebFilter(/*) public class CharacterEncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; // 统一设置请求和响应的编码 req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); // 请求继续向下传递直到被对应的Servlet处理 chain.doFilter(request, response); } }WebFilter(/*)表示拦截所有请求包括静态资源这样每个Servlet里都不用再写编码相关代码。这个Filter必须放在登录、搜索等Servlet之前执行因为FilterChain是链式顺序执行的如果映射路径覆盖不到编码设置就不生效。在实际的IDEA/Tomcat环境中这个过滤器解决的是POST请求的乱码GET请求的乱码来自Tomcat内部对URL的解析需要改Tomcat的conf/server.xml里Connector的URIEncodingUTF-8属性或者用new String(value.getBytes(ISO-8859-1), UTF-8)做手动转码这一点在第5章展开细讲。5. JavaWeb项目避坑IDEA运行配置与五个高频报错5.1 404怪圈Web源目录和Artifact没配对现象代码完全照着教程写的Tomcat也能启动但访问http://localhost:8080/就是404连Tomcat默认首页都看不到。原因你访问的根本不是你的webapp。IDEA里部署项目时Deployment选项卡里Application context配置的是/还是/musicArtifact是否已经正确关联到Tomcat如果context配置成了/music访问路径就要完整带上。另外如果Artifact的Output Layout里没有把webapp下的静态资源和JSP页面复制进去Tomcat跑的是一个空壳。解决打开Run/Debug Configurations选中Tomcat Server实例在Deployment标签页点加号选择Artifact。注意IDEA里会出现两个同名Artifact一个带:war后缀一个带:war exploded本地开发用exploded即可它会直接把webapp目录映射到Tomcat改完代码刷新页面就能看到效果。如果还是404把Artifact删掉重新构建一次绝大多数情况是旧的输出缓存没清干净。5.2 中文乱码GET和POST是两条完全不同的路现象登录时用户名是“张三”跳转后页面显示“å¼ ä¸‰”或一串问号。原因POST请求的表单数据在请求体里过滤器统一设置req.setCharacterEncoding(UTF-8)就能解决但GET请求的参数在URL里Tomcat 8以上默认已经是UTF-8如果你用的是Tomcat 7或更老版本则默认ISO-8859-1就必乱。解决分两步走POST乱码用第4.4节的FilterGET乱码在Tomcat的conf/server.xml里找到Connector节点加上URIEncodingUTF-8。改完必须重启Tomcat才生效。课后演示时经常出现“本地是好的一上演示机器就乱码”的玄学基本就是两个Tomcat版本默认编码不一致导致的。5.3 JDBC驱动ClassNotFoundExceptionjar包放错位置的经典翻车现象项目在IDEA里编译通过启动Tomcat后访问任何涉及数据库的页面控制台报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因你只把mysql-connector-java的jar包添加到了Project Structure的Libraries里。这个设置只对编译期有效Tomcat运行时加载的是WEB-INF/lib目录下的jar包两回事。解决把MySQL驱动jar包拷贝到src/main/webapp/WEB-INF/lib/目录下IDEA会自动识别并把它们打进Artifact。用Maven的话在pom.xml里声明依赖并设置scopeprovided/scope之外的默认scope即可Maven会把jar包放进最终的lib目录。回头看这个报错本质是对JavaWeb应用运行时类加载机制不熟悉容器只会从WEB-INF/lib和WEB-INF/classes里加载应用类。5.4 Tomcat端口占用与数据库连接时区报错现象一上次关闭Tomcat没等进程完全退出或者别的程序占了8080端口启动时报Port 8080 was already in use。解决Mac/Linux执行lsof -i:8080找到PID然后killWindows用netstat -ano | findstr 8080或者直接把Tomcat端口改成8081。这里有个小技巧一个IDEA实例里可以通过Add Configuration添加多个Tomcat但务必保证端口互不相同否则会互相顶掉。现象二MySQL 8.0连接时报Could not create connection to database server后面跟The server time zone value Öйú±ê׼ʱ¼ä的乱码提示。这是MySQL 8.0的时区问题需要在JDBC连接URL里加上serverTimezoneAsia/Shanghai。// JdbcUtil中的连接字符串MySQL 8.0必须带时区参数 String url jdbc:mysql://localhost:3306/music_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8;这两个问题看着不相关但都是课设答辩前最容易临时冒出来的端口问题影响演示时区问题影响初始化建议提前把server.xml端口确认好把连接URL的参数一次性写对避免现场改配置的窘迫。6. 验证清单与三个进阶改造把课设项目拉到能用的程度6.1 功能验证清单从头到尾跑一遍项目做完后别急着交按下面这张清单过一遍能过滤掉90%的常规问题。每项都点一遍、操作一遍再勾掉。序号验证点操作方式预期结果1注册新用户名注册然后重复注册首次成功入库重复时提示用户已存在2登录正确密码与错误密码各试一次正确跳首页错误留在登录页并显示提示3搜索输入歌名关键词与歌手名关键词列表命中对应歌曲无关键词时展示默认列表4播放点击歌曲所在行的播放按钮音频正常出声进度条可拖动5建歌单登录后创建歌单再往歌单收藏歌曲新建歌单出现在个人列表收藏后数量16编码用中文歌名搜索、播放中文歌曲页面无乱码控制台无编码报错7会话登录后手动关掉浏览器再打开重新访问需要登录Session失效6.2 进阶一连接池与密码散列四张表、几十条数据的小项目每次查询都新建Connection其实也能跑但答辩时“性能优化”这道送分题别空着。最轻量的改造是引入Druid连接池替换掉手写的DriverManager连接逻辑核心只是把连接方式从DriverManager.getConnection换成从池子里取。另一个必做改造是密码脱敏MD5摘要存储比明文强但用彩虹表几秒就能破解进阶做法是为每个用户生成随机的盐值存储MD5(password salt)的结果。这两处改动加起来不超过一天工作量但能直接在答辩时展示你考虑到了真实项目关注的可靠性和安全性。6.3 进阶二分页与最近播放如果想让歌曲列表真正“可用”可以给搜索/列表接口加分页常见做法是SQL里加LIMIT offset, size页面底部渲染上一页/下一页按钮。还有一个加分项是把用户点击播放的歌曲写入一张t_play_history表个人中心展示最近播放列表。这个功能能让你的项目从“课程作业”变成“产品原型”因为录像里能看到真实的使用痕迹答辩时讲起来也更自然。6.4 一个过来人的教训我最早写这类项目时急着把代码堆上去登录注册Search都写完了才回头整理数据库字段结果用户表和歌单表的外键对不上数据导了三遍才干净。后来养成习惯无论项目大小先把DDL表和接口清单写在README里再动手写代码。这个习惯后来帮我在真实工作中省了很多返工的时间。JavaWeb音乐网站这个方向不难但它把Web开发的骨架完整立住了——搞清楚Servlet、JSP、Filter怎么配合比你背十套Spring Boot模板都有用。希望帮到你。本文还有配套的精品资源点击获取