
简介这是一套面向Java Web初学者与课程设计者的完整音乐网站实战项目基于SSMSpringSpringMVCMyBatis技术栈实现核心功能解决在线音乐平台基础开发场景中的播放、下载、分类管理与热度排行等典型需求。资源共881个文件包含222张UI图标png/gif/jpg、80个JSP页面、60个Java业务类、44个依赖jar包、23首示例MP3音频、22个JS交互脚本及配套SQL建表语句整体压缩包达189.75MB结构清晰、模块分明便于理解MVC分层与前后端协作逻辑。已有912人学习下载开箱即用内置可直接部署的WAR包支持EclipseTomcatMySQLJDK环境一键运行源码附带全部依赖与配置文件含数据库初始化脚本、详细注释及典型业务流程如歌曲上传、分类检索、播放计数更新适合教学演示、毕业设计参考或二次开发拓展。1. 项目概述与核心价值最近在整理过去的项目资料翻到了一个自己早期独立完成的、基于JAVAWEB技术栈的小型音乐网站。这个项目虽然体量不大但麻雀虽小五脏俱全从前端页面展示、用户交互到后端业务逻辑、数据库设计再到服务器的部署上线完整地走通了一个Web应用从零到一的全过程。对于正在学习JAVAWEB或者想找一个完整项目来练手、巩固知识体系的朋友来说这类项目具有极高的参考价值。它不像那些庞大的电商或管理系统那样复杂容易让人望而生畏又能覆盖到JSP/Servlet、JDBC、MVC分层、数据库CRUD、文件上传、前端基础等核心知识点。今天我就把这个项目的设计思路、关键技术实现细节以及我在开发过程中踩过的坑和总结的经验系统地梳理一遍希望能为你提供一个清晰、可落地的实战参考。简单来说这个音乐网站的核心功能包括用户注册登录、音乐分类浏览、音乐播放支持在线试听、音乐搜索、用户个人收藏歌单以及后台管理员对音乐、用户信息的管理。技术选型上前端主要使用JSP、HTML、CSS、JavaScript和一点jQuery后端使用Servlet作为控制器JavaBean作为模型采用经典的MVC模式数据库选用MySQL通过JDBC进行连接开发工具是IntelliJ IDEA服务器是Tomcat。整个项目的架构清晰非常适合作为JAVAWEB的入门到进阶的练手项目。2. 项目整体架构与设计思路拆解2.1 为什么选择经典JAVAWEB技术栈在如今Spring Boot大行其道的时代回过头来做纯粹的JSPServlet项目似乎有点“复古”。但我认为这对于初学者理解Web开发的本质至关重要。Spring Boot通过自动配置和约定大于配置的理念极大地简化了开发但同时也封装了许多底层细节。直接从Spring Boot入手你可能会熟练使用Controller和Autowired却不清楚一个HTTP请求是如何被分发、一个数据库连接是如何被管理维护的。基于JSPServlet的技术栈能让你亲手触摸到Web应用的“骨架”Servlet的生命周期init(),service(),destroy()理解单例多线程下的资源管理。请求/响应对象HttpServletRequest/HttpServletResponse如何获取参数、设置属性、进行重定向或转发。会话管理HttpSession用户登录状态的维持购物车、收藏夹等数据的临时存储。JSP的本质它最终会被编译成Servlet理解其中的脚本片段、表达式、声明和指令。JDBC的原生操作手动加载驱动、获取连接、创建Statement/PreparedStatement、处理结果集、管理事务。这能让你深刻理解ORM框架如MyBatis存在的价值。这个音乐网站项目就是基于这些“原始”技术构建的。通过它你能建立起对Web MVC模式最直观的认识JSP负责视图展示Servlet充当控制器处理业务逻辑和路由JavaBean封装数据模型DAO层负责与数据库交互。这种清晰的分层是后续学习任何高级框架的坚实基础。2.2 数据库设计与核心表结构数据库设计是整个项目的基石设计的好坏直接影响到后端代码的复杂度和系统性能。对于小型音乐网站我们主要设计以下几张核心表用户表 (user)这张表存储用户的基本信息。CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名用于登录, password varchar(255) NOT NULL COMMENT 密码务必加密存储, email varchar(100) DEFAULT NULL, avatar varchar(255) DEFAULT NULL COMMENT 头像图片路径, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY username_unique (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意密码字段绝对不允许明文存储。在实际开发中必须使用如BCrypt、MD5加盐等算法进行加密。这里为了演示简化可能在代码中未加密但这是严重的安全漏洞真实项目必须避免。音乐表 (music)这是核心的业务表存储音乐文件的相关元数据。CREATE TABLE music ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 歌曲名, singer varchar(100) NOT NULL COMMENT 歌手, album varchar(200) DEFAULT NULL COMMENT 专辑, duration int(11) DEFAULT NULL COMMENT 时长秒, music_url varchar(500) NOT NULL COMMENT 音乐文件存储路径相对或绝对, cover_url varchar(500) DEFAULT NULL COMMENT 封面图片路径, style_id int(11) DEFAULT NULL COMMENT 风格分类ID外键关联style表, upload_time datetime DEFAULT CURRENT_TIMESTAMP, play_count int(11) DEFAULT 0 COMMENT 播放次数用于热门排序, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;思考点music_url字段的设计。音乐文件是存储在服务器本地磁盘还是云存储如OSS本地存储管理简单但扩展性差云存储成本稍高但易于扩展和CDN加速。小型项目初期可放在本地WebContent下的一个目录如/music/但需在Tomcat配置中设置虚拟路径映射以便通过URL访问。音乐风格表 (style)用于对音乐进行分类如流行、摇滚、古典等。这是一个典型的维度表。CREATE TABLE style ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;收藏表 (favorite)记录用户与音乐之间的多对多关系即用户的歌单。CREATE TABLE favorite ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, music_id int(11) NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY user_music_unique (user_id,music_id) COMMENT 防止重复收藏, KEY fk_fav_music (music_id), CONSTRAINT fk_fav_music FOREIGN KEY (music_id) REFERENCES music (id) ON DELETE CASCADE, CONSTRAINT fk_fav_user FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;实操心得在favorite表上建立(user_id, music_id)的联合唯一约束是一个非常实用的技巧。它可以确保数据库层面不会插入重复的收藏记录避免了在业务代码中先查询再插入的繁琐和并发问题。外键约束的ON DELETE CASCADE选项意味着当用户或音乐被删除时其相关的收藏记录会自动清除保持了数据的一致性。2.3 MVC模式在项目中的具体落地我们采用最经典的三层架构Web层视图控制器、Service层业务逻辑、DAO层数据访问。DAO层 (Data Access Object)职责封装所有对数据库的操作提供增删改查的原子接口。实现为每个实体User, Music, Style, Favorite创建对应的DAO接口和实现类如UserDao,UserDaoImpl。关键技术使用JDBC结合数据库连接池如DBCP, C3P0, HikariCP。绝对不要在每次DAO方法调用中都创建和关闭连接那会耗尽资源。连接池是生产级应用的必备。代码示例UserDaoImpl中的登录验证方法public User findUserByUsernameAndPassword(String username, String password) throws SQLException { String sql SELECT id, username, email, avatar FROM user WHERE username ? AND password ?; // 假设password已在前端或Service层加密 try (Connection conn DataSourceUtil.getConnection(); // 从连接池获取 PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, username); pstmt.setString(2, password); try (ResultSet rs pstmt.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setEmail(rs.getString(email)); user.setAvatar(rs.getString(avatar)); return user; } } } return null; }Service层 (Business Service)职责处理复杂的业务逻辑协调多个DAO的操作管理事务。它是控制器和DAO之间的桥梁。实现创建UserService,MusicService等。关键价值例如“用户收藏音乐”这个操作不仅仅是在favorite表插入一条记录。Service层可能还需要检查音乐是否存在、用户是否已收藏、更新音乐的收藏计数甚至发送消息通知。这些逻辑不应该散落在Servlet中而应由Service统一管理。事务管理对于涉及多个DAO操作如同时更新订单和库存需要在Service层使用Connection的setAutoCommit(false),commit(),rollback()来手动控制事务确保数据一致性。Web层控制器 (Servlet)接收HTTP请求调用相应的Service方法处理业务根据结果将请求转发Forward或重定向Redirect到不同的JSP页面。doGet(): 处理查询请求如展示音乐列表、搜索。doPost(): 处理修改请求如登录、注册、收藏、上传。经验之谈一个常见的良好实践是使用一个BaseServlet在其service()方法中通过请求参数如actionlogin来反射调用本Servlet中对应的login()方法。这样可以避免一个功能一个Servlet导致的类爆炸使结构更清晰。视图 (JSP)负责数据的展示。应尽量只包含显示逻辑避免复杂的Java代码。使用JSTL标签库和EL表达式来替代% ... %脚本片段使页面更简洁、更易维护。3. 核心功能模块实现详解3.1 用户认证与会话管理用户登录是系统的门户。核心流程是用户在表单提交用户名密码 -LoginServlet接收参数 - 调用UserService.login()- Service调用DAO验证 - 验证通过后将用户对象存入Session - 跳转到首页。关键Servlet代码片段 (LoginServlet.doPost)protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); // 1. 对密码进行加密此处演示实际应用更强算法 String encryptedPwd MD5Util.encode(password salt); // 加盐 UserService userService new UserServiceImpl(); User user userService.login(username, encryptedPwd); if (user ! null) { // 2. 登录成功将用户信息存入Session HttpSession session request.getSession(); session.setAttribute(loginUser, user); // 3. 重定向到首页避免表单重复提交 response.sendRedirect(request.getContextPath() /index.jsp); } else { // 4. 登录失败设置错误信息并转发回登录页 request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }重要安全提示密码加密如上所述MD5加盐是基础但MD5已被证明存在碰撞漏洞。生产环境强烈推荐使用BCrypt或Argon2等自适应哈希算法。Session超时在web.xml中配置Session超时时间如30分钟。登录状态验证对于需要登录才能访问的页面如个人中心/user/profile.jsp应使用过滤器Filter进行统一校验。创建一个AuthFilter拦截相关URL检查Session中是否存在loginUser若不存在则跳转到登录页。过滤器 (AuthFilter) 示例WebFilter(/user/*) // 拦截所有/user/下的请求 public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); // 不创建新session if (session null || session.getAttribute(loginUser) null) { // 未登录重定向到登录页并记录来源地址以便登录后返回 resp.sendRedirect(req.getContextPath() /login.jsp?redirect URLEncoder.encode(req.getRequestURI(), UTF-8)); return; } // 已登录放行 chain.doFilter(request, response); } }3.2 音乐播放与文件流输出这是项目的核心体验功能。音乐文件通常较大不能像普通图片一样直接使用img src标签引用。我们需要一个Servlet来读取音乐文件并以二进制流的形式写入HTTP响应。MusicPlayerServlet的实现要点WebServlet(/play) public class MusicPlayerServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String musicId request.getParameter(id); if (musicId null || musicId.isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, Missing music ID); return; } MusicService musicService new MusicServiceImpl(); Music music musicService.getMusicById(Integer.parseInt(musicId)); if (music null) { response.sendError(HttpServletResponse.SC_NOT_FOUND, Music not found); return; } // 假设音乐文件存储在 /WEB-INF/music/ 目录下禁止直接URL访问 String musicFilePath getServletContext().getRealPath(/WEB-INF/music/) music.getMusicUrl(); File musicFile new File(musicFilePath); if (!musicFile.exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND, Music file not found); return; } // 1. 设置响应头告诉浏览器这是音频流 response.setContentType(audio/mpeg); // 根据文件类型调整如 audio/mp3, audio/wav response.setContentLengthLong(musicFile.length()); // 2. 支持范围请求Range Request用于实现播放、跳转、下载续传 response.setHeader(Accept-Ranges, bytes); // 3. 处理范围请求前端播放器发送的 String rangeHeader request.getHeader(Range); try (InputStream in new FileInputStream(musicFile); OutputStream out response.getOutputStream()) { if (rangeHeader ! null rangeHeader.startsWith(bytes)) { // 解析范围请求例如 bytes0-999 String[] range rangeHeader.substring(6).split(-); long start Long.parseLong(range[0]); long end musicFile.length() - 1; if (range.length 1 !range[1].isEmpty()) { end Long.parseLong(range[1]); } long contentLength end - start 1; response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); response.setHeader(Content-Range, bytes start - end / musicFile.length()); response.setContentLengthLong(contentLength); // 跳过起始字节 in.skip(start); copyStream(in, out, contentLength); } else { // 非范围请求发送整个文件 copyStream(in, out, musicFile.length()); } out.flush(); } // 4. 更新播放次数可异步处理避免阻塞响应 new Thread(() - musicService.incrementPlayCount(music.getId())).start(); } private void copyStream(InputStream in, OutputStream out, long length) throws IOException { byte[] buffer new byte[4096]; long remaining length; int read; while (remaining 0 (read in.read(buffer, 0, (int) Math.min(buffer.length, remaining))) ! -1) { out.write(buffer, 0, read); remaining - read; } } }前端HTML5音频播放器调用audio controls source src${pageContext.request.contextPath}/play?id${music.id} typeaudio/mpeg 您的浏览器不支持 audio 元素。 /audio深度解析与避坑指南文件存储安全音乐文件不要放在WebContent根目录或能被直接访问的子目录下。推荐放在/WEB-INF/或服务器文件系统的非Web路径通过Servlet控制访问。WEB-INF是受保护的客户端无法直接请求其中的文件。支持范围请求Range Request这是实现音频视频在线播放、快进、跳转的关键。浏览器和播放器会发送Range头来请求文件的某一部分。我们的Servlet必须能正确解析并返回206 Partial Content状态码和对应的Content-Range头。如果不支持播放器将无法正常工作每次都会加载整个文件。响应头Content-Type必须根据文件格式正确设置如MP3对应audio/mpegWAV对应audio/wav。设置错误可能导致播放器无法解码。性能与并发直接使用FileInputStream在高并发下可能有问题。可以考虑使用NIO的FileChannel或者将静态文件交由Nginx等Web服务器处理效率更高。对于小型项目使用Servlet流处理是可行的。播放统计异步化更新播放次数的数据库操作不应阻塞音频流的输出。可以将其放入一个单独的线程如示例或更优的方案是使用消息队列或异步任务框架。简单项目用线程即可但要注意线程管理。3.3 音乐上传与管理功能管理员需要上传音乐文件。这涉及到文件上传和表单数据混合处理。我们使用Apache Commons FileUpload库来简化这一过程。1. 环境准备将commons-fileupload.jar和commons-io.jar添加到项目的WEB-INF/lib目录。2. UploadMusicServlet关键代码protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 检查是否为多媒体上传 if (!ServletFileUpload.isMultipartContent(request)) { request.setAttribute(msg, 表单必须包含 enctypemultipart/form-data); request.getRequestDispatcher(/admin/upload.jsp).forward(request, response); return; } // 配置上传参数 DiskFileItemFactory factory new DiskFileItemFactory(); // 设置内存缓冲区大小超过后写入临时文件 factory.setSizeThreshold(1024 * 1024); // 1MB // 设置临时文件存储目录 File tempDir (File) getServletContext().getAttribute(javax.servlet.context.tempdir); factory.setRepository(tempDir); ServletFileUpload upload new ServletFileUpload(factory); // 设置单个文件最大大小 (10MB) upload.setFileSizeMax(10 * 1024 * 1024); // 设置整个请求最大大小 (包含文件和表单数据20MB) upload.setSizeMax(20 * 1024 * 1024); try { ListFileItem items upload.parseRequest(request); String title null; String singer null; String styleId null; String album null; String musicFileName null; String coverFileName null; // 遍历表单项 for (FileItem item : items) { if (item.isFormField()) { // 普通表单字段 String fieldName item.getFieldName(); String value item.getString(UTF-8); switch (fieldName) { case title: title value; break; case singer: singer value; break; case styleId: styleId value; break; case album: album value; break; } } else { // 文件字段 String fileName new File(item.getName()).getName(); // 获取客户端原始文件名 if (fileName ! null !fileName.isEmpty()) { // 生成唯一文件名防止覆盖 String fileExt fileName.substring(fileName.lastIndexOf(.)); String savedFileName UUID.randomUUID().toString() fileExt; // 确定文件类型音乐还是封面 String fieldName item.getFieldName(); String uploadDir getServletContext().getRealPath(/WEB-INF/); if (musicFile.equals(fieldName)) { musicFileName savedFileName; uploadDir music/; } else if (coverFile.equals(fieldName)) { coverFileName savedFileName; uploadDir covers/; } else { continue; } // 确保目录存在 File storeDir new File(uploadDir); if (!storeDir.exists()) { storeDir.mkdirs(); } // 保存文件 File storeFile new File(uploadDir savedFileName); item.write(storeFile); } } } // 验证必要字段 if (title null || singer null || musicFileName null) { throw new Exception(歌曲名、歌手和音乐文件为必填项。); } // 组装Music对象调用Service存入数据库 Music music new Music(); music.setTitle(title); music.setSinger(singer); music.setAlbum(album); music.setStyleId(styleId ! null ? Integer.parseInt(styleId) : null); music.setMusicUrl(musicFileName); music.setCoverUrl(coverFileName); MusicService musicService new MusicServiceImpl(); boolean success musicService.addMusic(music); if (success) { response.sendRedirect(request.getContextPath() /admin/music_list.jsp?msg上传成功); } else { throw new Exception(数据库插入失败。); } } catch (Exception e) { e.printStackTrace(); request.setAttribute(msg, 上传失败: e.getMessage()); request.getRequestDispatcher(/admin/upload.jsp).forward(request, response); } }上传功能注意事项表单编码前端表单必须设置enctypemultipart/form-data否则Servlet无法解析文件内容。文件大小限制务必在服务器端设置大小限制防止恶意用户上传超大文件耗尽磁盘和带宽。文件名安全不要直接使用用户上传的文件名保存这可能导致路径遍历攻击如文件名包含../或覆盖系统文件。使用UUID或时间戳生成唯一文件名并保留原文件扩展名。文件类型检查不能仅依赖文件扩展名。应在服务器端检查文件的MIME类型或魔数Magic Number确保上传的是合法的音频和图片文件。存储路径上传的文件应保存在Web应用目录之外如/data/music/或至少是WEB-INF下并通过Servlet提供访问。直接放在Web可访问目录存在安全风险。异常处理妥善处理FileSizeLimitExceededException等异常给用户友好的提示。3.4 音乐搜索与分页展示当音乐数量增多时列表展示和搜索功能必须支持分页。1. 分页查询的SQL与DAO实现核心是使用LIMIT子句。我们需要两个参数pageNum当前页码从1开始和pageSize每页条数。// MusicDaoImpl 中的方法 public ListMusic findMusicByPage(int pageNum, int pageSize, String keyword, Integer styleId) throws SQLException { ListMusic list new ArrayList(); StringBuilder sql new StringBuilder(SELECT * FROM music WHERE 11 ); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append(AND (title LIKE ? OR singer LIKE ? OR album LIKE ?) ); String likeKeyword % keyword %; params.add(likeKeyword); params.add(likeKeyword); params.add(likeKeyword); } if (styleId ! null) { sql.append(AND style_id ? ); params.add(styleId); } sql.append(ORDER BY upload_time DESC LIMIT ?, ?); // 计算偏移量 int offset (pageNum - 1) * pageSize; params.add(offset); params.add(pageSize); try (Connection conn DataSourceUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { pstmt.setObject(i 1, params.get(i)); } try (ResultSet rs pstmt.executeQuery()) { while (rs.next()) { // ... 将结果集映射为Music对象 list.add(music); } } } return list; } // 同时需要一个方法获取总记录数用于计算总页数 public int countMusic(String keyword, Integer styleId) throws SQLException { // ... 构造COUNT(*)的SQL逻辑同上去掉LIMIT }2. Service层封装分页对象通常我们会创建一个PageInfo或Pagination类来封装分页相关的所有数据方便传递给前端。public class PageInfoT { private int pageNum; // 当前页 private int pageSize; // 每页大小 private long total; // 总记录数 private int pages; // 总页数 private ListT list; // 当前页数据列表 // 构造方法、getter/setter省略 }MusicService中的分页查询方法返回PageInfoMusic。3. 前端JSP页面展示分页控件在JSP中使用JSTL和EL表达式循环展示音乐列表并生成分页链接。%-- 音乐列表 --% c:forEach items${pageInfo.list} varmusic div classmusic-item img src${pageContext.request.contextPath}/cover?id${music.id} alt${music.title} h4${music.title}/h4 p${music.singer} - ${music.album}/p audio controls src${pageContext.request.contextPath}/play?id${music.id}/audio a href# onclickaddToFavorite(${music.id})收藏/a /div /c:forEach %-- 分页导航 --% div classpagination c:if test${pageInfo.pageNum 1} a hrefmusic_list?pageNum${pageInfo.pageNum-1}keyword${param.keyword}上一页/a /c:if c:forEach begin1 end${pageInfo.pages} varp c:choose c:when test${p pageInfo.pageNum} span classcurrent${p}/span /c:when c:otherwise a hrefmusic_list?pageNum${p}keyword${param.keyword}${p}/a /c:otherwise /c:choose /c:forEach c:if test${pageInfo.pageNum pageInfo.pages} a hrefmusic_list?pageNum${pageInfo.pageNum1}keyword${param.keyword}下一页/a /c:if /div分页优化技巧数据库性能LIMIT在偏移量很大时如LIMIT 100000, 20性能很差因为MySQL需要扫描前100000条记录。对于深度分页可以考虑使用“上一页/下一页”基于ID查询如WHERE id last_id ORDER BY id LIMIT 20或者使用子查询优化。前后端分离在现代前端框架中分页数据通常通过JSON API提供前端负责渲染分页组件。后端API设计应返回包含list,total,pageNum,pageSize等字段的JSON对象。搜索高亮简单的LIKE搜索在数据量大时效率低且不支持分词。对于音乐网站可以考虑引入轻量级的全文检索引擎如Elasticsearch但这超出了小型项目的范围。初期使用LIKE并做好索引在title,singer字段上是可行的。4. 项目部署、优化与常见问题排查4.1 使用IntelliJ IDEA配置与运行项目创建Web项目在IDEA中创建新项目选择Java Enterprise勾选Web Application。确保已配置好本地Tomcat服务器。导入JAR包将MySQL驱动、数据库连接池如HikariCP、Commons FileUpload等JAR包复制到WEB-INF/lib目录下IDEA会自动将其添加到项目库中。配置数据库连接池在src目录下创建db.properties文件配置连接参数。编写一个DataSourceUtil工具类使用javax.sql.DataSource来管理连接。# db.properties jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/music_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.passwordyourpassword # HikariCP配置 hikari.maximumPoolSize10 hikari.minimumIdle5配置Tomcat在Run/Debug Configurations中添加Tomcat Server将项目部署为Artifact。注意设置Application context如/music这将是你的网站根路径。运行与调试启动TomcatIDEA会自动打开浏览器。你可以通过断点调试Servlet和Java代码这是IDEA相比Eclipse等工具的巨大优势。4.2 性能优化与安全加固建议数据库连接池这是最重要的优化之一。它避免了频繁创建和销毁连接的开销。推荐使用HikariCP它性能优异且配置简单。静态资源分离将图片、CSS、JS等静态文件交给Nginx或Tomcat的默认Servlet处理而不是经过你的应用Servlet可以极大减轻应用服务器压力。可以在Nginx中配置对/music/和/covers/路径的静态文件服务。JSP预编译在生产环境中可以预编译JSP为Servlet避免首次访问时的编译延迟。输出压缩在Tomcat的server.xml中配置HTTP压缩compression减小网络传输量。SQL注入防护全程使用PreparedStatement绝对不要拼接SQL字符串。这是最基本也是最重要的安全准则。XSS防护在JSP页面上使用JSTL的c:out标签或EL表达式默认会对HTML进行转义。如果需要在页面中显示用户输入的内容如评论务必进行转义处理。文件上传安全如前所述检查文件类型、重命名、限制大小、设置存储目录权限不可执行。4.3 常见问题与排查实录问题1启动Tomcat时报“ClassNotFoundException: com.mysql.cj.jdbc.Driver”排查检查WEB-INF/lib下是否有mysql-connector-java-xxx.jar。在IDEA中检查Project Structure - Modules - Dependencies确保该JAR包被正确添加为Library。有时需要手动将JAR包添加到Artifact的输出中。问题2页面显示乱码尤其是中文排查步骤数据库连接确保JDBC URL中包含useUnicodetruecharacterEncodingUTF-8。数据库和表确认MySQL数据库、表和字段的字符集为utf8mb4。Servlet请求/响应在doGet/doPost方法最开始处设置request.setCharacterEncoding(UTF-8);和response.setContentType(text/html;charsetUTF-8);。JSP页面在JSP页面顶部添加% page contentTypetext/html;charsetUTF-8 languagejava %。Tomcat服务器检查Tomcat的server.xml中Connector标签是否配置了URIEncodingUTF-8。问题3文件上传失败提示“临时文件夹不可用”或“权限不足”排查检查DiskFileItemFactory设置的临时目录路径是否存在以及Tomcat进程是否有该目录的读写权限。可以使用getServletContext().getAttribute(javax.servlet.context.tempdir)获取Tomcat为当前应用分配的临时目录。问题4音频播放器可以加载但无法播放或无法跳转排查检查浏览器控制台F12的Network标签查看对/play的请求。确认响应状态码是206 Partial Content范围请求还是200 OK。如果是200说明你的Servlet没有正确处理Range头。检查响应头Content-Type是否正确。检查音乐文件本身是否损坏可以用本地播放器打开试试。确认文件路径是否正确Servlet是否能成功读取到文件。问题5分页查询速度越来越慢排查使用EXPLAIN命令分析你的分页SQL语句。为WHERE条件中用到的字段如title,singer,style_id和ORDER BY字段如upload_time建立索引。对于超大的偏移量考虑使用基于游标的分页WHERE id ? ORDER BY id LIMIT ?。这个基于JAVAWEB的小型音乐网站项目就像一块完整的拼图将散落的知识点串联了起来。从需求分析、数据库设计、到MVC编码、功能实现再到最后的部署调试每一步都充满了挑战和收获。我强烈建议你在理解上述内容的基础上亲手从头到尾实现一遍。过程中你一定会遇到这里没有提到的问题而解决这些问题的过程正是你能力提升最快的时候。当你看到自己搭建的网站成功播放出第一首音乐时那种成就感是无与伦比的。这个项目可以作为你的作品集起点未来你可以在此基础上引入Spring框架、更换前端技术、增加推荐算法让它不断进化。本文还有配套的精品资源点击获取