ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

拆解JavaWeb小型云盘系统:Servlet分层、数据库设计与上传下载实现

拆解JavaWeb小型云盘系统:Servlet分层、数据库设计与上传下载实现 简介基于Java Web技术实现的小型云盘系统是仿照百度网盘功能设计的在线存储项目主要面向需要完整Web项目案例的Java学习者与有内部文件共享需求的小团队。系统实现了用户登录、文件上传、下载、重命名、删除、分享及权限校验等核心流程覆盖Servlet、JSP、Spring MVC、数据库交互、文件流处理、会话管理等常见Java Web开发知识点。压缩包共204个文件大小约4.55MB主要包含Java源码、编译后的class文件、项目依赖jar包、JSP页面与JS/CSS前端脚本、PNG图标素材以及SQL数据库脚本工程目录按照Controller、Service、Repository、Model等层次组织方便对照模块开展代码阅读和二次开发。当前已有346人学习/下载。资源内提供完整工程源码、数据库初始化脚本、环境配置说明与基础测试代码有助于开发者理解从浏览器请求、业务逻辑处理到数据持久化的完整实现链路也可作为课程设计、毕业设计或小型云盘项目改造的起点。1. 一个 javaweb 小型云盘系统值得拆的地方不在界面在分层手头这套基于 javaweb 仿百度网盘的小型云盘系统解压开是CloudDrive-master目录里头的类名一眼能认全UpLoadServlet、DownLoadServlet、ListFilesServlet、FileManageBizImpl、BaseDao。别急着嫌弃它不带 Spring Boot——正是这种走原生 Servlet JSP 路线的老项目才能让你一次看清 HTTP 请求从web.xml分发、穿过 Servlet、落进 DAO 查库的完整链路。它模仿百度网盘做了文件上传、下载、分享、列表管理四件事数据落在 MySQL用户走网页操作。适合正在交课程设计、想补 Servlet 基本功、或者打算把老 JavaWeb 项目往 Spring Boot 迁移的人。对熟手也有价值拆一遍能快速找回 Tomcat 部署和原生 Servlet 的边界很多改造成 Spring Boot 时踩的坑根子就在这一层。2. 先拆类清单Servlet、Biz、DAO 三层如何接住一次请求拿到一个老 JavaWeb 资源包我一般不会先看 JSP 页面而是先把.java文件按名字摊开因为命名能直接暴露作者的分层习惯。这套代码的分层非常规整适合当成学习样本。2.1 从类名反推架构每个类负责什么把类清单按三层归一下立刻能看出调用关系层次类名职责控制层UpLoadServlet、DownLoadServlet、ListFilesServlet、LoginServlet接收 HTTP 请求解析参数调业务层转发或重定向业务层FileListBizImpl、FileManageBizImpl处理文件列表、重命名、删除、移动等业务规则数据层BaseDao、FileDaoImpl、ShareDaoImpl封装 JDBC 连接与 SQL 操作对应文件表和分享表模型/标记Type文件类型或目录类型的常量定义不是实体类这里有个值得注意的点这套代码没有走 Spring MVC 的Controller注解而是直接用HttpServlet子类 web.xml映射。好处是 Servlet 生命周期、请求分发逻辑完全暴露给你坏处是每个 Servlet 都要自己处理参数解析和编码。你要是毕设被老师问“请求是怎么从 URL 到 DAO 的”照着这个项目的调用链讲一遍比背概念管用得多。调用链大概长这样浏览器发请求 →web.xml里匹配到对应 Servlet 的url-pattern→ Servlet 的doGet/doPost拿到 request → 调用 Biz 层的实现类 → Biz 调 DAO 执行 SQL → 结果写回 request 域 → JSP 渲染。2.2 IDEA 里跑通老项目的完整配置Tomcat、MySQL、部署位老项目跑不起来多数不是代码问题是环境配置问题。我建议在 IDEA 里按下面这套顺序操作能省掉大半翻车时间解压后用 IDEA 打开CloudDrive-master等 Maven 或 lib 目录加载完先看根目录有没有pom.xml。如果有IDEA 会自动识别成 Maven 项目等依赖下载完如果只有lib文件夹就在 Project Structure 里把lib加入 Libraries。确认 JDK 版本。老项目一般按 JDK 8 写的,选 1.8 最稳换更高版本容易遇到UnsupportedClassVersionError或 JSP 编译兼容问题。配置 Tomcat。我用的是 Tomcat 8.5 配 JDK 8对原生 Servlet 项目兼容很好。IDEA 的 Run Configuration 里点加号选 Tomcat Server - Local然后指定 Tomcat 目录。建数据库。按下一章的建表语句执行完改jdbc.properties或db.properties里的连接串。部署在 Tomcat 配置的 Deployment 页签里加 ArtifactApplication context 建议填/cloud这样访问地址就是http://localhost:8080/cloud/避免根路径下静态资源冲突。web.xml是这套项目的总开关核心配置长这样servlet servlet-namelistFilesServlet/servlet-name servlet-classcom.clouddrive.servlet.ListFilesServlet/servlet-class /servlet servlet-mapping servlet-namelistFilesServlet/servlet-name url-pattern/listFiles/url-pattern /servlet-mapping这段配置的价值在于servlet-name只是别名真正决定“哪个 URL 进哪个类”的是url-pattern。表单里actionlistFiles提交后Tomcat 靠这个映射找到对应类。你排错时如果发现请求一直 404优先检查这里写的路径和 JSP 表单里的动作路径是否一致路径少个斜杠或多一层目录都可能找不到。依赖方面老项目的 Maven 坐标一般跑不了这几样按需核对dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependencyprovided作用域意味着 Servlet API 不打包进 war运行期由 Tomcat 提供,避免和 Tomcat 自带的类冲突。如果你用的是 Tomcat 10 及以上javax.servlet会换成jakarta.servlet老项目直接跑会把类找不到所以选 Tomcat 8.5/9 而不是 10是第一道坎。3. 数据库设计是这套系统的命门文件表、用户表、分享表怎么建JavaWeb 云盘这类项目页面能丑一点但数据库设计必须立得住。文件存哪里、路径怎么记、分享状态放哪张表直接决定后面上传下载逻辑的复杂度。3.1 从 Type 类看设计为什么文件目录要用 type 字段标记这个项目里Type类出现在类清单里它不是实体更像一个常量定义。按照这类老项目的常规写法它的作用通常是区分“目录”和“普通文件”public class Type { // 文件类型标记1 表示目录文件夹0 表示普通文件 public static final int DIRECTORY 1; public static final int FILE 0; // 如果需要按文件格式区分还可以在这里加 DOC / IMAGE / VIDEO }这个设计有一个很实在的好处文件夹本身不占磁盘空间它只是文件表里的一条元数据记录靠parent_id把自己挂到父目录下。举个例子你要创建/我的文档/学习资料两层目录不需要真的在磁盘上建目录只需要在file_info表里插两条记录一条type1的“我的文档”一条type1且parent_id指向“我的文档”的“学习资料”。这样在做“列举某个目录下的内容”时只要查parent_id ?就能拿到全部子项目录树天然成立。3.2 建库建表文件表和分享表的 SQL 长什么样给这套系统建表时核心三张表是跑不掉的用户表、文件表、分享表。文件表是灵魂字段设计如下CREATE TABLE file_info ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, user_id INT NOT NULL COMMENT 所属用户ID关联user表, parent_id INT DEFAULT 0 COMMENT 父目录ID0表示根目录, file_name VARCHAR(255) NOT NULL COMMENT 文件或文件夹名, file_path VARCHAR(500) COMMENT 文件在磁盘上的存储路径目录为空, file_size BIGINT DEFAULT 0 COMMENT 文件大小单位字节, file_type TINYINT DEFAULT 0 COMMENT 类型1目录0文件, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间, share_status TINYINT DEFAULT 0 COMMENT 是否已分享0未分享1已分享 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT云盘文件信息表; CREATE TABLE file_share ( id INT PRIMARY KEY AUTO_INCREMENT, file_id INT NOT NULL COMMENT 被分享的文件ID, share_user_id INT NOT NULL COMMENT 发起分享的用户ID, share_code VARCHAR(32) NOT NULL UNIQUE COMMENT 分享提取码, expire_time DATETIME COMMENT 过期时间为空表示永久有效, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT文件分享表;两条 SQL 值得解释的细节有三个。第一file_path只存服务端磁盘路径比如E:/cloudstorage/用户ID/文件名数据库不负责存文件内容这符合云盘系统“数据库记元数据磁盘存实体”的正统做法。第二file_name用 255 字节足够但要留意 MySQL 里的字符集如果表用了utf8而不是utf8mb4中文和 emoji 容易存不进或乱码。第三share_code设置了UNIQUE唯一约束这是为了防止两个分享记录撞码,业务层生成分享码时必须保证随机。3.3 表之间的关联逻辑一次查询完整说明白文件表和分享表通过file_id关联分享表里能查到“哪个文件被谁分享、提取码是什么、是否过期”。经典的查询是用户打开“我的分享”页面要显示他分享过的所有文件。SELECT f.file_name, f.file_size, s.share_code, s.expire_time FROM file_share s LEFT JOIN file_info f ON s.file_id f.id WHERE s.share_user_id ? ORDER BY s.create_time DESC用LEFT JOIN而不是INNER JOIN的原因很直接如果某条分享记录对应的文件被删了用户还是希望能在列表里看到这条分享记录知道哪个链接失效了。做文件管理系统数据表之间尽量少用物理外键、多用逻辑关联删数据时少很多麻烦。file_info表里也可以加一个user_id索引因为云盘最常见的就是按用户查文件列表没有索引表数据一多查询就慢,这是课本不会明说、线上必踩的索引设计。4. 上传与列表UpLoadServlet、ListFilesServlet 的核心实现细节功能和代码最集中的部分就是上传和列表这里把关键写法的逻辑拆透。4.1 上传链路表单怎么进、Servlet 怎么写、文件落盘放哪用原生 Servlet 做上传老项目一般会借助commons-fileupload处理 multipart 请求因为 Servlet 3.1 之前的原生 API 拿不到表单里的文件流。核心代码思路如下// UploadServlet 的 doPost 核心逻辑 DiskFileItemFactory factory new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); // 内存缓冲区 1MB ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(100 * 1024 * 1024); // 单文件最大 100MB FileItemIterator iter upload.getItemIterator(request); while (iter.hasNext()) { FileItemStream item iter.next(); if (!item.isFormField()) { // 不是普通表单字段说明是文件 String filename FilenameUtils.getName(item.getName()); // 按用户ID分目录存储避免所有文件堆在一起 File dir new File(E:/cloudstorage/ user.getId()); if (!dir.exists()) { dir.mkdirs(); } File target new File(dir, filename); try (InputStream in item.openStream(); OutputStream out new FileOutputStream(target)) { IOUtils.copy(in, out); // 流复制 } // 落库file_info 表插入一条 file_type0 的记录 } }这里有两个细节新手容易看漏。第一FilenameUtils.getName(item.getName())不是多余的脱裤子放屁——浏览器上传文件时有些版本的 IE 或 Chrome 会带上完整路径比如C:\Users\name\file.txt不取getName的话存盘时可能把反斜杠当成目录结构轻则路径错乱重则被人利用路径穿越写文件。第二E:/cloudstorage/用户ID/这个按用户分目录的设计很朴素但有个明显的边界问题不同用户上传相同文件名的文件不会冲突可以在后面接时间戳或 UUID 来兜底否则两个用户各传一个a.zip就会互相覆盖。4.2 列表查询分页参数和父目录递归怎么处理文件列表页是网盘的首页也是核心展示页。ListFilesServlet通常接收两个关键参数当前所在目录parentId和页码page。查询语句的骨架是SELECT id, file_name, file_size, file_type, upload_time FROM file_info WHERE user_id ? AND parent_id ? AND file_type ? ORDER BY upload_time DESC LIMIT ?, ?业务层FileListBizImpl要做的就是把用户 ID、目录 ID、类型过滤条件拼进 SQL再算出偏移量。分页参数要留意LIMIT 的第一个问号是偏移量(page - 1) * pageSize第二个问号是每页条数如果前端传来的page是字符串必须做整数转换否则会拼接报错。类型参数这里有点讲究列表页同时展示文件夹和文件SQL 里file_type在查列表时其实可以不加分目录框和文件图标靠前端判断但如果单独做“只看文档”这种筛选file_type条件就很有用了。列表接口返回后后端把结果放进request.setAttribute(fileList, list)JSP 里用c:forEach遍历输出表格。到这里一个“进入根目录 → 看到文件列表 → 点文件夹进入下一层”的流程就闭环了。注意每次进入子目录前端需要把当前parentId传回来作为下次查询的父 ID否则列表会永远停留在根目录——这是新手必犯的错。5. 运行中的五个典型坑404、中文乱码与上传被拒的排查实录这个项目我在三个不同环境里跑过下面这些坑是我和身边人都踩过的按遇到的频率排个序每条都是“现象 → 原因 → 解决”的完整链路。5.1 点击上传后一直 404先查 web.xml 映射现象上传按钮提交后浏览器地址栏变成http://localhost:8080/cloud/servlet/UploadServlet然后一片空白或 404。原因表单里的action路径和web.xml里url-pattern写的路径对不上。最常见的是action/cloud/servlet/UploadServlet而映射写的是/upload两个路径完全不匹配Tomcat 找不到对应的 Servlet。解决统一以web.xml的url-pattern为准。表单里写相对路径比如actionupload不在前面加项目路径Servlet 里重定向用request.getContextPath() /listFiles。这个坑的原因本质是绝对路径和相对路径混用。5.2 上传后文件名在数据库里是一串“%E6%9D%8E”现象文件上传成功但页面上显示文件名是%E6%9D%8E%E5%9B%9B数据库存进去也是这一串。原因请求先经过 Tomcat 的 URI 解码然后才到 ServletTomcat 8 默认 URI 编码是 UTF-8但是表单提交如果是application/x-www-form-urlencodedPOST 体的编码可能被当成 ISO-8859-1 解析中文被转义。这是老项目的“经典乱码”源头是字符编码没对齐。解决在 web.xml 里加一个 CharacterEncodingFilter 或者直接在 Servlet 里request.setCharacterEncoding(UTF-8)并且确认 MySQL 连接串里带useUnicodetruecharacterEncodingutf8。两处都对齐后%E6%9D%8E这种情况基本绝迹。5.3 上传一个 30MB 的文件页面直接 500现象小文件没事一传大文件就抛异常后台日志里有The request was rejected because its size exceeds the configured maximum。原因Tomcat 默认的maxPostSize是 2MB超过直接拒绝请求根本到不了你的 Servlet 代码。代码里配的 100MB 限制没生效因为 Tomcat 的连接器层先拦下来了。解决在conf/server.xml的Connector节点加maxPostSize104857600100MB或者把maxPostSize设为-1表示不限制。如果项目用了 Servlet 3.0 的MultipartConfig注解还要确认maxFileSize已调大这两层限制是叠加的。5.4 数据库连接报Public Key Retrieval is not allowed现象启动后任何需要查库的接口都能通但是登录时抛java.sql.SQLException: Public Key Retrieval is not allowed。原因MySQL 8.0 默认认证插件是caching_sha2_password老版本驱动或者连接串没加允许公钥检索的参数第一次建立连接时拿不到服务端公钥。解决如果继续用 MySQL 8.0在 JDBC 连接串后面加allowPublicKeyRetrievaltrueuseSSLfalse如果你不想折腾干脆配合老项目用 MySQL 5.7com.mysql.jdbc.Driver直接兼容这组坑全绕过。我的习惯是跑老项目一律用 5.7省心。5.5 本地跑得好好的打成 war 部署线上就找不到文件现象IDEA 里直接跑没任何问题导成 war 扔到独立 Tomcat 后上传文件失败日志显示某个目录不存在。原因代码里写的存储路径是E:/cloudstorage本地 Windows 存在线上 Linux 上没有这个路径而且 Tomcat 运行用户可能没有权限自动创建目录。路径硬编码是这类项目的通病。解决把存储路径做成可配置项放到db.properties或专门的config.properties里部署时按机器改。另外在 Servlet 初始化时主动创建目录用file.mkdirs()别依赖用户手建。从那以后我每次拿到 JavaWeb 老项目都强制第一件事就是全局搜盘符路径和绝对路径改造成相对配置文件的方式能省掉后面一大片部署事故。6. 下载与分享把小型网盘的最后一个闭环补上下载功能在DownLoadServlet里实现逻辑不算复杂但有两个细节处理不好会让用户觉得“这网盘是坏的”。设置下载响应头时要让浏览器弹出保存窗口而不是直接在页面上显示文件内容关键在于Content-Disposition// DownLoadServlet 核心片段 response.setContentType(application/octet-stream); String fileName URLEncoder.encode(file.getFileName(), UTF-8); response.setHeader(Content-Disposition, attachment;filename fileName); try (InputStream in new FileInputStream(file.getFilePath()); OutputStream out response.getOutputStream()) { byte[] buffer new byte[4096]; int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } }URLEncoder.encode这一步不能省中文文件名如果不做 URL 编码浏览器收到的响应头里会出现非法字符轻则显示乱码重则下载失败。attachment关键字决定了是“附件下载”如果写成inline浏览器会尝试在页面内预览文件——PDF 和图片没问题压缩包就会直接乱码。缓冲区 4KB 是常规选择想要大文件下载更流畅可以适当调到 8KB 或 16KB但注意byte[]增大会稍微多占内存云盘项目的用户并发一高这个参数需要结合服务器内存掂量。分享功能的闭环保留在ShareDaoImpl层。生成分享码时不要用自增 ID 或者时间戳用户一眼能猜出来的“分享码”没有意义。常见做法是生成一段随机字符串比如 6 位字母数字混排String chars abcdefghijklmnopqrstuvwxyz0123456789; StringBuilder code new StringBuilder(); Random random new Random(); for (int i 0; i 6; i) { code.append(chars.charAt(random.nextInt(chars.length()))); } // 入库前先查是否已存在存在则重新生成插入之前必须做唯一性校验否则撞码之后两个人拿同一个分享码打开是两份完全不同的文件这种事故非常伤信任。入库时顺带记录expire_time查询分享文件时判断当前时间是否超过过期时间超过就直接返回“分享已过期”这样权限边界才算完整。如果你打算拿这套代码继续往深处改我建议按这个顺序动手先把存储路径配置化再把上传文件名加 UUID 前缀防覆盖最后给文件表加一个md5字段用来做秒传——同样的文件上传两次比对 MD5 直接复用已有记录。改完你会发现这套老代码的“原生 Servlet JDBC”结构反而让你对每一步都心里有数不像 Spring Boot 封装太厚出了问题都不知道去哪个类排查。这个工程我自己拆完最大的收获不是学会了百度网盘的平替而是看明白了一个请求从浏览器到数据库的每一步都是可控的。如果你手头也压着一个跑不起来的 JavaWeb 老项目希望这篇拆解能帮到你。本文还有配套的精品资源点击获取
返回列表