ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

JavaWeb云盘项目实战:Servlet+Tomcat+MySQL部署与源码避坑指南

JavaWeb云盘项目实战:Servlet+Tomcat+MySQL部署与源码避坑指南 简介这是一套基于 Java Web 技术仿照百度网盘实现的小型云盘系统源码主要面向 Java Web 初学者以及需要快速搭建个人或小团队在线存储场景的开发者。项目采用经典分层结构将控制层、业务逻辑层、数据访问层、数据模型与视图模板清晰分离完整实现了文件上传、下载、分享、管理以及用户权限控制等核心功能有助于读者理解从 Servlet/JSP 到 Spring MVC 的典型开发流程。压缩包内共包含 204 个文件其中有 65 个编译后的 class 文件、50 个 java 源代码、24 个 js 前端脚本、15 个 jar 依赖库、4 个 jsp 页面及 SQL 数据库脚本整体大小约 4.55MB目录划分明确便于导入 IDE 阅读和实践。资源还附带前端静态图片、样式表、配置文件等辅助材料适合在课程设计或毕业设计中作为基础框架进行二次扩展。当前已有 346 人浏览学习对于希望掌握 Java Web 项目落地、文件流处理、数据库交互与前后端联调的读者而言是一份值得参考的完整样例。1. JavaWeb 云盘到底长什么样先说清这套包的分层与边界想用 Java 做一个能在线存储、下载、分享文件的 Web 系统最适合拿来当课设或毕设的落地方案就是仿照百度网盘做一个小型云盘。这套 zip 包里的 CloudDrive-master 就是一个 javaweb 项目完整案例数据库用的是 MySQL骨架是 Servlet JSP JDBC 的分层 MVC没上 Spring Boot也没有前后端分离。打开源码目录能直接看到 UpLoadServlet、DownLoadServlet、FileListBizImpl、FileDaoImpl、ShareDaoImpl 这一串类名Controller、Biz 业务层、Dao 数据访问层的角色一眼就能认出来。它能解决的是一个个人或小团队最朴素的云存储需求登录后把文件上传到服务器磁盘按目录列出自己的文件支持下载、删除、重命名还能生成一条分享链接丢给别人。相比直接用百度网盘成品这套系统最大的价值不在功能多而在每一层都能拆开讲清楚你既能回答老师问的“Servlet 怎么接收上传文件”也能自己动手加一个“回收站”之类的功能。正在做 JavaWeb 课程设计、找工作想放一个完整项目的人都可以拿它跑通再改。2. 部署到 IDEA Tomcat从 zip 解压到能访问的三个关键动作把 CloudDrive-master 解压后千万别急着在 IDEA 里直接点运行。JavaWeb 项目和 Spring Boot 不一样它没有内置 Tomcat需要你先匹配运行环境、导入数据库再把工程挂到外部 Tomcat 上。这个顺序一旦乱了后面每一步报错都会误导你。解压时如果提示要密码先别急着去找“zip 密码移除”工具很多课设包在传播过程中被压缩软件加上了伪加密标志位用 7-Zip 能直接解开。这种小问题不值得卡住你五分钟。2.1 环境匹配先对着项目形态选 JDK、Tomcat 和 MySQL从类名能判断这是典型的原生 Servlet 工程不是 Maven 管理的 Spring Boot 工程。这种项目的运行环境其实很固定先用一张表把环境锁死后面排错会省很多事。组件推荐版本为什么这么选JDK1.8即 Java 8原生 Servlet JSP 在 JDK 8 下最稳JDK 17 在旧 Tomcat 上容易报unrecognized optionTomcat8.5.x 或 9.0.x代码里用的是javax.servlet.*命名空间Tomcat 10 全改成了jakarta.servlet.*直接跑会崩MySQL5.7 或 8.0两者都能跑用 8.0 时注意驱动要换成com.mysql.cj.jdbc.DriverIDEA2020.x 及以上支持 Artifacts 和 Tomcat Server 集成配置最省事我见过不少人在环境上翻车原因不是不会写代码而是装了个 Tomcat 10 去跑课设包然后满屏NoClassDefFoundError。先记住这句话原生 Servlet 工程就用 Tomcat 8.5 或 9.0别去追新版本。JDK 也建议用 8不是越新越好。2.2 数据库初始化一张用户表加两张业务表这个云盘的核心数据模型可以简化成三张表用户表 users、文件表 file_info、分享表 share_info。文件表里存的是文件元数据不是文件本身真正的文件字节放在服务器磁盘目录里。下面这段 SQL 是常见做法你拿到的包里如果有clouddrive.sql之类的脚本直接执行即可。CREATE DATABASE IF NOT EXISTS clouddrive DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE clouddrive; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE file_info ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, file_name VARCHAR(255) NOT NULL, file_size BIGINT DEFAULT 0, file_path VARCHAR(500) NOT NULL, file_type VARCHAR(50), is_dir TINYINT DEFAULT 0, parent_id INT DEFAULT 0, is_share TINYINT DEFAULT 0, share_code VARCHAR(32), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_parent (user_id, parent_id) ); CREATE TABLE share_info ( id INT PRIMARY KEY AUTO_INCREMENT, file_id INT NOT NULL, user_id INT NOT NULL, share_code VARCHAR(32) NOT NULL, visit_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_share_code (share_code) );三个字段特别说明一下。file_path存的是文件在服务器上的物理路径不是浏览器访问的虚拟路径下载时靠它定位文件parent_id用来撑起目录结构顶层文件的 parent_id 默认 0share_code是分享时生成的短码别人访问/share?codexxx就靠它命中记录。数据库连接配置一般放在src/db.properties或src/jdbc.properties里BaseDao 负责读配置、拿 Connection、关资源。配置内容大概是下面这样jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/clouddrive?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的数据库密码如果你用 MySQL 5.7com.mysql.cj.jdbc.Driver也能用用 MySQL 8.0 时serverTimezoneAsia/Shanghai和allowPublicKeyRetrievaltrue这两个参数基本是必须的不加会直接报时区或公钥检索错误。这是我在部署 JavaWeb 项目时最常见的数据库坑后面避坑章节还会展开。2.3 IDEA 导入与 Tomcat 部署war exploded 别选错环境匹配好了、数据库导入好了接下来把工程挂到 Tomcat 上。我在 IDEA 里跑这类原生 Servlet 项目的固定步骤是这样的。第一步File - Open选择解压出来的 CloudDrive-master 目录等 IDEA 识别成普通 Java 工程。第二步File - Project Structure - Project把 Project SDK 选成 1.8再切到 Modules 的 Dependencies 页签确认 lib 目录下的 jar 已经加进来没有的话手动点 “” 添加。第三步切到 Artifacts 页签点 “” 选择Web Application: ExplodedIDEA 会生成一个 exploded 部署目录。第四步配置 Tomcat ServerDeployment 页签里把这个 exploded 包加进去Application context 建议写成/cloud。第五步以 debug 模式启动看 Tomcat Localhost Log 有没有异常。project 打包这种操作叫 war exploded不是 war 包。war 是把工程打成压缩包再扔给 Tomcat每次改代码都要重新打war exploded 是直接把解压后的目录挂上去改完 JSP 刷新浏览器就能看到效果。用 IDEA 调试课设项目一定要选 exploded这个选择能让你少折腾很多。web.xml里的 Servlet 映射长这样启动前先检查一遍路径有没有对不上servlet servlet-nameLoginServlet/servlet-name servlet-class你的包名.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mappingurl-pattern决定浏览器地址栏输入什么能命中这个 Servlet。比如映射是/login访问地址就是http://localhost:8080/cloud/login。注意前面的/cloud是 context path后面跟着的是url-pattern这两个拼在一起才是完整访问路径。很多人卡在 404就是只记得 Servlet 路径忘了带 context path。3. 源码骨架走读上传、下载、分享、列表分别落在哪些类上项目跑起来之后下一步是搞清楚请求到底走过了哪些类。不要逐行读代码按“登录 - 列表 - 上传下载 - 分享”这条主线走一遍每个类各自干什么就清楚了。3.1 登录与请求流转从 LoginServlet 到 BaseDao登录流程是整个系统的入口。浏览器提交用户名密码LoginServlet接收参数调用 Dao 层查 users 表成功后把用户对象放进 session。常见写法是下面这样的protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { request.getSession().setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /listFiles); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }这段代码有两个细节值得注意。第一setAttribute(loginUser, user)把登录用户放进了 session后续列表、上传、下载都从 session 里拿当前用户 id这是控制“只能操作自己文件”的基础。第二登录成功后用sendRedirect而不是forward因为转发后浏览器地址栏还是/login刷新页面会重复提交表单。BaseDao在这一层的作用是屏蔽 JDBC 样板代码。它提供一个getConnection()方法和一个closeAll(conn, stmt, rs)方法所有 Dao 实现类都继承它。你去看FileDaoImpl的时候如果看到一堆conn.prepareStatement(sql)那就是 BaseDao 在工作。这个类的质量决定后续修改成本建议先看一眼连接有没有正确关闭。3.2 上传与下载UpLoadServlet 和 DownLoadServlet 的分工上传是整个云盘的核心。浏览器端表单必须写enctypemultipart/form-data服务端才能用 Part 接文件。如果项目运行在 Servlet 3.0 以上的 Tomcat上传代码可以这样写protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 从 session 拿当前登录用户 User user (User) request.getSession().getAttribute(loginUser); String uploadRoot /data/clouddrive/ user.getId(); Part part request.getPart(file); String originalFileName part.getSubmittedFileName(); // 时间戳前缀避免重名覆盖 String serverFileName System.currentTimeMillis() _ originalFileName; File dir new File(uploadRoot); if (!dir.exists()) { dir.mkdirs(); } part.write(uploadRoot File.separator serverFileName); // 把元数据写入 file_info供列表和下载查询 saveFileMeta(user.getId(), serverFileName, part.getSize(), uploadRoot File.separator serverFileName); response.sendRedirect(request.getContextPath() /listFiles); }part.getSubmittedFileName()是 Servlet 3.1 才提供的方法如果项目用的 Tomcat 比较老它会返回 null需要从part.getHeader(content-disposition)里手动截取文件名。serverFileName加上时间戳前缀是为了防止两个用户上传同名文件互相覆盖。文件存到磁盘之后马上把文件名、大小、物理路径写进数据库这样列表页才能展示出来。下载正好反过来。DownLoadServlet根据前端传的 file_id 查 file_info拿到物理路径后通过输出流把文件写回浏览器。核心代码是设置响应头File downloadFile new File(fileInfo.getFilePath()); response.setContentType(application/octet-stream); response.setHeader(Content-Disposition, attachment;filename URLEncoder.encode(fileInfo.getFileName(), UTF-8)); try (InputStream in new FileInputStream(downloadFile); OutputStream out response.getOutputStream()) { byte[] buffer new byte[4096]; int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } }Content-Disposition里的attachment告诉浏览器这是下载不是打开文件名那块必须用URLEncoder.encode包一层否则中文文件名会变成乱码甚至直接下载失败。try-with-resources能保证流自动关闭这是 Java 7 以后的语法JDK 8 完全支持。3.3 列表与分享FileListBizImpl、FileManageBizImpl 与 ShareDaoImpl列表页走的是ListFilesServlet - FileListBizImpl - FileDaoImpl这条链。Servlet 只负责取参数、调 Biz、转发 JSP具体的 SQL 逻辑全部收在 FileListBizImpl 里。常见的列表查询是按用户和父目录过滤public ListFileInfo listByParent(int userId, int parentId) { String sql SELECT * FROM file_info WHERE user_id ? AND parent_id ? ORDER BY create_time DESC; // 这里通过 BaseDao 获取连接用 PreparedStatement 执行 }ListFilesServlet 拿到集合后放进 request再forward到list.jsp循环展示。这里必须注意一个权限问题SQL 里一定要带user_id ?只查当前登录用户的文件。如果漏了这个条件用户 A 登录后直接在地址栏改 user_id 参数就能看到用户 B 的文件这是越权漏洞。文件管理交给FileManageBizImpl它负责删除和重命名。删除操作有个顺序讲究先删数据库记录再删磁盘文件。原因是先删磁盘的话如果数据库删除失败就留下一个“数据库里不存在但磁盘上有文件”的孤儿反过来数据库删掉以后磁盘删不删得掉最多是多占点硬盘不会导致数据错乱。分享功能由ShareDaoImpl承担。用户点击分享时服务端生成一个短码更新 file_info 的 share_code并把记录写入 share_info。短码生成我一般用 UUID 截取String shareCode UUID.randomUUID().toString().replace(-, ).substring(0, 8); // 更新文件表分享状态 fileDao.updateShareCode(fileId, shareCode); // 写入分享记录表 shareDao.saveShare(fileId, userId, shareCode); String shareUrl request.getContextPath() /share?code shareCode;分享链接是“短码 查询参数”的结构短码撞车的概率很低但因为只有 8 位严谨一点可以在插入前查一下重。生成链接时一定记得带request.getContextPath()不然部署在/cloud下别人点开会直接 404。4. 避坑跑 JavaWeb 云盘最容易翻车的五个点这一章是血泪经验。下面五个坑每一个我都见过不止一个人在群里问过而且排错时间基本都在半小时以上。按“现象、原因、解决”记下来能少走很多弯路。4.1 数据库与容器的坑第一个坑MySQL 8 连接直接抱错Public Key Retrieval is not allowed或caching_sha2_password相关异常。现象是项目启动正常但一登录就 500控制台提示数据库连接失败。原因有两个一是 MySQL 8.0 默认认证插件是caching_sha2_password老版本的 mysql-connector-java 驱动不认识二是连接串里没开公钥检索允许。解决方法是换com.mysql.cj.jdbc.Driver驱动并在 jdbc.url 末尾加allowPublicKeyRetrievaltrueuseSSLfalse。如果你是用 MySQL 8.0 的 zip 包安装的数据库装完还要注意服务名和密码是否和配置文件里一致这是另一个高频低级错误。第二个坑Tomcat 10 跑旧工程启动后访问任何页面都报NoSuchMethodError或ClassNotFoundException: javax.servlet.*。现象很奇怪因为编译是过的但运行就找不到类。原因是 Tomcat 10 把javax.servlet换成了jakarta.servlet原生 Servlet 工程的代码里全是javax自然找不到。解决方法是铁了心用 Tomcat 8.5 或 9.0这是跑课设包最省心的容器版本。如果你已经在用 Tomcat 10卸载换掉别想着改代码适配那工作量等于重写。第三个坑500 错误里出现JasperException: Unable to compile class for JSP。现象是 JSP 页面编译失败服务器日志一堆 Java 编译错误。原因通常是 JDK 版本和 Tomcat 版本组合不对常见于 JDK 17 Tomcat 9 的组合或者 lib 目录下缺少 JSTL 依赖。解决方法是把环境锁回 JDK 8 Tomcat 8.5并且确认WEB-INF/lib下有jstl.jar和standard.jar。我的习惯是拿到的项目先看一眼 lib 目录没有这两个 jar 就补上。4.2 上传下载文件名的坑第四个坑上传后文件名变成一串乱码或者保存后的名字不对。现象是上传 “工作报告.doc”列表里显示的是 “告.doc” 或者干脆空名字。原因是表单没设置enctypemultipart/form-data或者服务端在解析文件名时按 ISO-8859-1 读了一遍。解决方法是三层一起查前端 form 标签加enctypemultipart/form-datadoPost 最开头写request.setCharacterEncoding(UTF-8)如果是手动从content-disposition截文件名用new String(value.getBytes(ISO-8859-1), UTF-8)做一次转换。这三层缺哪一层都可能出乱码。第五个坑下载文件名乱码浏览器里显示一串%E4%B8%AD%E6%96%87之类的百分号。现象是能下载但文件名不是中文。原因是没有对文件名做 URL 编码或者编码方式不对。解决方式就是上面下载代码里写的那样filename后面拼URLEncoder.encode(fileInfo.getFileName(), UTF-8)不要直接拼原始中文。有一点要注意URLEncoder会把空格编码成多数浏览器能识别如果遇到下载名变成加号的就把替换成%20。4.3 部署与分享的坑第六个坑隐藏得比较深分享链接生成后别人访问 404。现象是自己本机打开分享链接没问题换台电脑或者用手机打开就是 404。原因基本是链接拼接时漏了 context path。比如部署 context path 是/cloud但代码里拼成/share?codeabcTomcat 会把请求发到根路径/share自然找不到 Servlet。解决方法是生成分享链接时统一用request.getContextPath() /share?code shareCode不要裸写/share。这个坑在局域网联调时尤其常见因为本机还能靠 Tomcat 的默认访问绕过去。第七个坑也算一个session 过期后在地址栏直接访问/listFiles会 500 或者空指针。现象是登录后放着不动半小时再去点列表页就报错。原因是 Servlet 从 session 取loginUser时取到 null后端没做空判断就调了user.getId()。解决方法是所有需要登录的 Servlet 开头统一做登录校验User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(login.jsp); return; }。这个坑顺手写一个过滤器能一劳永逸但课设阶段在每个 Servlet 加判断最简单。5. 进阶用 curl、ab 和 general_log 把验证动作固定下来功能跑通只是第一步。我建议你再做两件事用 curl 带登录态去压一下列表接口再打开 MySQL 的 general_log 看慢 SQL。这两个动作都不复杂但能验证这个系统到底能不能扛住几个人同时用、数据库 SQL 写得有没有问题。5.1 用 curl 与 ab 做带登录态的并发请求浏览器里点来点去只能验证功能要模拟并发得用工具。先登录拿到 session再带着 cookie 去压接口。curl -c cookies.txt -d usernameadminpassword123456 http://localhost:8080/cloud/login SESSION_ID$(grep JSESSIONID cookies.txt | awk {print $7}) ab -n 50 -c 5 -C JSESSIONID$SESSION_ID http://localhost:8080/cloud/listFiles?userId1ab 的-C参数负责带 cookie-n 50 -c 5表示总共发 50 个请求、同时 5 个并发。结果里重点看Requests per second和Failed requests。如果失败数不为 0去 Tomcat 日志看是不是内存溢出或数据库连接不够。ab 对 multipart 文件上传支持不好所以压列表接口最省事想压上传就用脚本循环调 curl。for i in $(seq 1 20); do curl -b cookies.txt -F filetest_$i.txt http://localhost:8080/cloud/upload done-b cookies.txt复用登录状态-F模拟 multipart 表单上传。测完看一眼服务器日志有没有超时或堆内存报警。如果上传大文件报 500去conf/server.xml把maxPostSize调大或者改成-1Tomcat 默认对 POST 请求体积有限制。5.2 打开 general_log 定位慢 SQL给 file_info 建索引并发上来了下一步查数据库。先临时打开 general_log它会把所有 SQL 都记下来SET GLOBAL general_log ON; SET GLOBAL log_output TABLE;跑几轮列表和分享操作后查mysql.general_log表就能看到最近执行过的 SQL 和耗时SELECT event_time, command_type, argument FROM mysql.general_log WHERE command_type Query ORDER BY event_time DESC LIMIT 20;云盘这种项目最典型的慢 SQL是按user_id和parent_id查文件列表时全表扫描。数据量从几十条涨到几千条时没索引的查询会明显变慢。可以顺手给 file_info 加上组合索引ALTER TABLE file_info ADD INDEX idx_user_parent (user_id, parent_id);idx_user_parent这个索引能同时支持“查某用户的全部文件”和“查某用户某目录下的文件”这两种高频查询。测完记得把 general_log 关掉否则 MySQL 的日志表会快速膨胀影响性能。从那以后我每次拿到这类 JavaWeb 源码包第一件事都是先跑通再改功能改之前先备份原 zip验证流程固定在 curl ab general_log 这一套上再也不凭感觉说“应该没问题”。这套流程同样适用于你自己的课设和面试前准备。如果你手头还没有这个 zip把它下下来照上面的顺序跑一遍遇到的问题基本都能对症找到答案希望帮到你。本文还有配套的精品资源点击获取
返回列表