简介这是一套面向Java Web初学者与课程设计需求的网盘系统源码采用JSPServlet技术栈后端以MySQL存储数据适合作为毕业设计、课程作业或自学练手项目。压缩包共123个文件约2.28MB其中22个jsp页面负责前端展示20个java源文件与20个class文件构成核心业务逻辑另有css、js及图片字体等静态资源以及7个jar依赖包和properties、xml等配置文件结构完整、层次清晰。项目围绕用户管理、文件上传下载、关注关系与后台管理等模块展开涵盖登录过滤、下载过滤等常见实现思路可帮助读者理解Servlet请求处理、DAO数据访问与过滤器机制。源码均经本地编译验证按文档配置环境即可运行难度适中已有116人学习。对于需要快速搭建网盘类应用、梳理Java Web分层结构或准备毕设答辩的读者这份资源能提供可运行的参考方案与排错思路。1. JSPServlet 网盘系统从建表到文件上传一套能跑通的 JavaWeb 全链路很多人第一次接触 JavaWeb 课设选题就是网盘系统。原因很直接功能边界清晰能覆盖 JSP、Servlet、MySQL 三件套答辩时也好讲。但真正动手时问题往往不在“写不出来”而在“跑不起来”——Tomcat 版本对不上、JDBC 驱动没放进WEB-INF/lib、上传目录写死在本机路径导致换台机器就 404。这套 JSPServletMySQL 的网盘系统核心链路其实就四条用户注册登录、文件上传落盘、文件列表查询、文件下载与删除。本文按一线实操顺序把建库建表、Servlet 生命周期、文件上传解析、连接池配置、WAR 打包部署这几件事讲透适合正在做 JavaWeb 毕设或课设、需要一套可复现方案的人。读完你能拿到一套能本地跑通、能打成 WAR 丢进 Tomcat 的最小可用骨架而不是一堆散落的代码片段。2. 先把数据库和工程骨架立住网盘系统的表结构与 IDEA 建项2.1 网盘系统需要哪几张表用户表与文件表的字段设计网盘系统的数据模型比想象中简单两张表足够撑起核心功能。用户表存账号密码文件表存文件元信息真实文件内容落在磁盘上数据库只存路径。这样设计的好处是文件读写不经过数据库避免大字段拖慢查询删除文件时先删磁盘再删记录逻辑清晰。常见做法是用户表用user_id做主键自增username加唯一索引密码字段至少 64 位以容纳 SHA-256 哈希。文件表用file_id主键user_id做外键关联file_path存相对路径而非绝对路径——这一点后面避坑章节会重点讲。CREATE DATABASE netdisk DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE netdisk; CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE t_file ( file_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, file_name VARCHAR(255) NOT NULL, file_path VARCHAR(500) NOT NULL, file_size BIGINT DEFAULT 0, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_file_user FOREIGN KEY (user_id) REFERENCES t_user(user_id) ) ENGINEInnoDB;字符集必须用utf8mb4否则中文文件名在部分场景下会变成问号。file_size用BIGINT而不是INT因为INT上限约 2GB网盘场景下单个文件超过这个值并不罕见。外键约束在课设里可以保留生产环境常因性能考虑去掉但这里保留能帮你早发现脏数据。2.2 IDEA 新建 JSP 项目目录结构与依赖放置在 IDEA 里新建 JavaWeb 项目推荐用 Maven 的webapp骨架而不是裸的 Java Enterprise 模板。Maven 骨架的好处是依赖用pom.xml管理换机器不用手动拷 jar。建好后目录结构应该是这样netdisk/ ├── pom.xml └── src/main/ ├── java/com/netdisk/ │ ├── dao/ │ ├── servlet/ │ └── util/ └── webapp/ ├── WEB-INF/ │ ├── web.xml │ └── lib/ ├── index.jsp └── upload/pom.xml里至少要有三个依赖Servlet API、MySQL 驱动、文件上传解析库。Servlet API 的 scope 设为provided因为 Tomcat 自带打 WAR 时不该重复打包。dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcommons-fileupload/groupId artifactIdcommons-fileupload/artifactId version1.5/version /dependency /dependencies版本号这里给的是常见稳定组合实际以你本地仓库能拉到的为准。注意javax.servlet和jakarta.servlet的区别Tomcat 9 及以前用javaxTomcat 10 开始改成jakarta。如果你用 Tomcat 10 却引了javax的包编译能过但运行时报ClassNotFoundException这是新手最常翻的车之一。2.3 数据库连接工具类把 DriverManager 换成连接池直接用DriverManager.getConnection()每次请求都新建连接在网盘这种频繁读写的场景下很快会拖垮数据库。常见做法是引入 Druid 或 HikariCP 连接池。课设里用 Druid 比较多配置直观。public class DBUtil { private static DataSource dataSource; static { try { Properties props new Properties(); props.load(DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(连接池初始化失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭注意顺序rs - stmt - conn } }db.properties放在src/main/resources下内容包含url、username、password、initialSize、maxActive等。url里务必加上useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8否则 MySQL 8 会报时区错误或 SSL 警告。连接池的maxActive课设场景设 10 到 20 足够设太大反而在低配机器上引发连接超时。3. Servlet 生命周期与文件上传网盘系统最核心的两个动作3.1 Servlet 生命周期在网盘系统里到底怎么走Servlet 生命周期分四个阶段加载与实例化、初始化init()、服务service()、销毁destroy()。理解这个顺序对网盘系统很关键因为文件上传的临时目录配置、连接池初始化都该放在init()里而不是每次请求都做一遍。init()只在 Servlet 第一次被访问时调用一次service()每次请求都调用destroy()在 Tomcat 关闭或应用重新部署时调用。网盘系统里UploadServlet的init()可以读取上传根目录配置destroy()可以清理临时文件。如果你把上传目录的创建逻辑写在doPost()里每次上传都判断一次目录是否存在虽然不算错但属于没必要的开销。WebServlet(urlPatterns /upload, loadOnStartup 1) public class UploadServlet extends HttpServlet { private String uploadRoot; Override public void init() throws ServletException { uploadRoot getServletContext().getRealPath(/upload); File dir new File(uploadRoot); if (!dir.exists()) { dir.mkdirs(); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 上传解析逻辑 } }loadOnStartup 1让 Servlet 随容器启动就初始化而不是等第一次请求。这样init()里的目录创建在应用启动时就完成第一次上传不会因为目录不存在而失败。getRealPath(/upload)拿到的是 Web 应用部署后的真实路径注意这个路径在 WAR 解压部署和未解压部署下行为不同后面避坑章节会展开。3.2 用 commons-fileupload 解析上传请求参数与磁盘阈值Servlet 3.0 以后可以用MultipartConfig注解配合req.getPart()处理上传但课设里用commons-fileupload更常见因为它的DiskFileItemFactory能控制内存缓冲和临时文件阈值对网盘这种可能传大文件的场景更友好。protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!ServletFileUpload.isMultipartContent(req)) { resp.sendError(400, 请求不是 multipart 格式); return; } DiskFileItemFactory factory new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); // 超过 1MB 写临时文件 factory.setRepository(new File(System.getProperty(java.io.tmpdir))); ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(100 * 1024 * 1024); // 单文件上限 100MB upload.setSizeMax(200 * 1024 * 1024); // 整个请求上限 200MB try { ListFileItem items upload.parseRequest(req); for (FileItem item : items) { if (item.isFormField()) { continue; // 普通表单字段这里不处理 } String fileName new File(item.getName()).getName(); String savedName UUID.randomUUID() _ fileName; File dest new File(uploadRoot, savedName); item.write(dest); // 后续把 savedName、file_size 写入 t_file 表 } } catch (FileUploadException e) { resp.sendError(500, 上传解析失败: e.getMessage()); } catch (Exception e) { resp.sendError(500, 文件写入失败: e.getMessage()); } }setSizeThreshold控制内存缓冲阈值超过就落临时文件避免大文件把 JVM 内存撑爆。setFileSizeMax和setSizeMax分别限制单文件和整请求大小不设的话恶意用户传个几十 GB 的文件就能把磁盘写满。item.getName()在部分浏览器里返回的是完整路径所以要用new File(...).getName()剥掉路径部分否则存进数据库的文件名会带一堆反斜杠。保存时用 UUID 前缀避免同名文件互相覆盖原始文件名单独存一列用于展示。3.3 文件列表查询与下载从 t_file 表到响应流上传完成后列表页从t_file表按user_id查记录展示文件名、大小、上传时间。下载时根据file_id查出file_path用FileInputStream写进response.getOutputStream()。WebServlet(/download) public class DownloadServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int fileId Integer.parseInt(req.getParameter(fileId)); // 从 t_file 查出 file_name 和 file_path File file new File(uploadRoot, savedName); if (!file.exists()) { resp.sendError(404, 文件不存在); return; } resp.setContentType(application/octet-stream); resp.setHeader(Content-Disposition, attachment; filename URLEncoder.encode(fileName, UTF-8)); try (InputStream in new FileInputStream(file); OutputStream out resp.getOutputStream()) { byte[] buf new byte[8192]; int len; while ((len in.read(buf)) ! -1) { out.write(buf, 0, len); } } } }Content-Disposition里的文件名必须 URL 编码否则中文名在下载时会乱码或截断。缓冲区用 8KB 是常见折中值太小频繁 IO太大占内存。下载前一定要校验file_id对应的记录是否属于当前登录用户否则改个 URL 参数就能下载别人的文件这是网盘系统最典型的安全漏洞。4. 避坑与排查JSPServlet 网盘系统最常见的五类翻车4.1 上传成功但列表不显示路径写死与相对路径的坑现象文件上传返回成功磁盘上也能找到文件但列表页查不到记录或者换台机器部署后文件全部 404。原因file_path存了绝对路径比如D:/tomcat/webapps/netdisk/upload/xxx。换机器后这个路径不存在或者 Tomcat 部署路径变了文件自然找不到。另一种情况是上传时用了getRealPath但 WAR 未解压部署时getRealPath返回 null。解决数据库只存文件名或相对路径读取时用getServletContext().getRealPath(/upload)拼出真实路径。如果getRealPath返回 null说明应用是未解压部署需要在context.xml里配置unpackWARstrue或者改用外部固定目录存文件。4.2 中文文件名乱码从表单到数据库的编码链路现象上传中文名文件后数据库里文件名变成?????.txt下载时文件名也不对。原因编码链路有三处可能出问题——JSP 页面没声明pageEncodingUTF-8、request.setCharacterEncoding(UTF-8)没在读取参数前调用、数据库连接 URL 没加characterEncodingutf8。解决JSP 顶部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里在parseRequest之前调用request.setCharacterEncoding(UTF-8)JDBC URL 加characterEncodingutf8数据库和表都用utf8mb4。四处缺一不可少一处就可能在某个环节丢编码。4.3 Tomcat 启动报 ClassNotFoundException驱动与 Servlet API 冲突现象项目编译通过Tomcat 启动时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或javax.servlet.http.HttpServlet。原因MySQL 驱动没放进WEB-INF/lib或者 Maven 依赖 scope 设成了provided导致没打进 WAR。Servlet API 报错则通常是 Tomcat 10 用了javax包或者反过来。解决MySQL 驱动 scope 用默认的compile确保打进 WAR。Servlet API 用provided因为 Tomcat 自带。Tomcat 版本和javax/jakarta必须匹配Tomcat 9 用javax.servletTomcat 10 用jakarta.servlet。检查pom.xml和 Tomcat 版本是否一致这是排查的第一步。4.4 连接池报错 Connection is not available连接泄漏与超时现象系统跑一段时间后报Connection is not available, request timed out重启 Tomcat 后恢复。原因DAO 里拿了连接没关或者close()写在try块里但异常发生时没执行到。连接池的maxActive被耗尽后后续请求全部等待超时。解决所有数据库操作必须用try-with-resources或在finally里关闭连接。Druid 可以开启removeAbandonedtrue和removeAbandonedTimeout180作为兜底但这只是补救根本还是要保证代码里不泄漏。排查时可以在 Druid 监控页面看活跃连接数持续增长不降就是泄漏。4.5 打包 WAR 部署后 404web.xml 版本与注解扫描现象IDEA 里跑得好好的打成 WAR 丢进 Tomcat 后访问 404。原因web.xml的metadata-completetrue会关闭注解扫描导致WebServlet不生效。或者web.xml的 schema 版本太低不支持 Servlet 3.0 以上的注解。解决web.xml用 4.0 版本的 schema且不要设metadata-completetrue。如果坚持用web.xml配置 Servlet那WebServlet注解可以去掉但两者不要混用导致冲突。打包前在 IDEA 的 Artifacts 里确认WEB-INF/classes和WEB-INF/lib都包含在内。5. 进阶技巧把网盘系统从能跑变成能扛5.1 用存储过程做文件统计减少 Java 层循环文件列表页如果要在 Java 里遍历统计总大小、文件数数据量大时会有性能问题。常见做法是把统计逻辑下沉到 MySQL 存储过程一次查询返回结果。DELIMITER // CREATE PROCEDURE sp_file_stats(IN p_user_id INT) BEGIN SELECT COUNT(*) AS file_count, IFNULL(SUM(file_size), 0) AS total_size FROM t_file WHERE user_id p_user_id; END // DELIMITER ;Java 层用CallableStatement调用拿到结果直接展示。IFNULL处理没有文件时SUM返回 null 的情况避免 Java 层再做空判断。存储过程的参数用IN类型网盘场景下不需要OUT参数因为结果集直接返回。注意存储过程在 MySQL 8 里默认可用但如果你的环境开了log_bin_trust_function_creators相关限制创建时可能需要额外权限。5.2 分页查询与索引文件多了之后列表不能全查t_file表数据量上千后SELECT * FROM t_file WHERE user_id ?全量返回会明显变慢。加LIMIT分页并在user_id上建索引。ALTER TABLE t_file ADD INDEX idx_user_upload (user_id, upload_time DESC); SELECT file_id, file_name, file_size, upload_time FROM t_file WHERE user_id ? ORDER BY upload_time DESC LIMIT ?, ?;联合索引(user_id, upload_time DESC)让按用户查、按时间倒序的查询走索引避免全表扫描。LIMIT的偏移量在深分页时仍然会慢课设场景下用LIMIT offset, size足够生产环境可以考虑游标分页。查询时只取需要的列不要SELECT *减少网络传输和内存占用。5.3 验证清单部署前必须过的五道关在把 WAR 丢给老师或部署到服务器之前按这个清单过一遍能省掉大部分现场翻车检查项验证方式通过标准数据库连接启动后访问列表页不报连接异常中文文件名上传含中文名的文件列表和下载均正常显示大文件上传传一个 50MB 文件不报内存溢出进度正常换机部署换一台机器部署 WAR上传下载功能正常权限校验改 fileId 参数下载他人文件返回 403 或 404这五道关里换机部署和权限校验最容易被忽略。换机部署能暴露路径写死的问题权限校验能暴露越权下载的问题。我自己的习惯是每次改完上传或下载逻辑都手动改一次fileId参数试一下确认拿不到别人的文件才放心。这套 JSPServletMySQL 的网盘系统骨架不复杂但细节决定它能不能在答辩现场稳定跑完。希望帮到你。本文还有配套的精品资源点击获取