ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SpringBoot和MD5去重的校园网盘系统设计

基于SpringBoot和MD5去重的校园网盘系统设计 简介这是一份基于SpringBoot的校园网盘系统毕业设计源码与数据库资源采用B/S架构前端结合HTML、CSS、JavaScript、jQuery与Bootstrap后端使用SpringBoot配合MySQL数据库与Tomcat部署可直接导入运行。系统包含用户、文件、管理员三大模块支持未登录、普通用户和管理员三种角色文件可设置分享、密码、搜索可见性及登录下载等权限并通过存储MD5避免重复文件占用空间适合毕业设计、课程大作业等场景参考学习。资源共430个文件压缩包大小5.86MB主要包含61个Java后端源码、31个HTML页面及CSS/JS前端文件、SQL数据库脚本以及PSD设计稿、XMind思维导图和Visio流程图等资料便于从设计到实现的完整梳理。已有301人浏览学习。下载后可直接获得完整项目源码、数据库初始化脚本、界面设计源文件和项目结构说明经过调试确保可运行能帮助快速理解校园网盘的文件分享、权限控制、MD5去重等核心实现也可作为二次开发的基础框架。1. 校园网盘用 SpringBoot 和 MD5 去重做一个真正能跑的课程设计在高校机房和实验室里“把课件传到另一台电脑”永远是高频需求U 盘容易坏、QQ 传文件有 7 天过期、公网网盘上传下载都要排队而校园网内部几百兆的带宽经常是闲置的。这套基于 SpringBoot 的校园网盘系统就是在这样的场景下做出来的——服务端跑在 Tomcat 上前端是 Bootstrap 加 jQuery数据库用 MySQL核心思路是按文件的 MD5 做去重同一个视频被三个班级重复上传时服务器只保存一份物理文件再叠加“是否分享、访问密码、是否可搜索、需登录才可下载”这一套权限控制管理员还能冻结用户和下架文件。对要做 Java 毕业设计、SpringBoot 课程设计和数据库课程设计的人而言这套源码的完整链路很适合拆开复盘从建表、上传下载到权限过滤每一步都能落成可运行的代码。2. B/S 架构与三角色权限模型为什么单体 SpringBoot 适合校园场景2.1 技术栈选型SpringBoot MySQL Bootstrap 组合逻辑系统采用 B/S 开发模式用户通过浏览器访问不需要安装客户端。这个选择在校园网内部是合理的机房的机器系统各异浏览器兼容性最好服务端部署在腾讯云 Windows Server 上用 Tomcat 提供服务一台低配云主机就能撑起整个课程设计的演示流量。后端框架选 SpringBoot 而不是 SSM主要原因是配置方式不同。SSM 需要维护 spring-mvc.xml、mybatis-config.xml 等多份 XMLSpringBoot 用自动配置把这部分压到最少单人开发一个课程设计时能省下大量排错时间。前端使用 Bootstrap 加 jQueryBootstrap 的栅格系统能快速搭出文件列表的响应式布局jQuery 负责处理上传弹窗、密码输入框这类交互Dreamweaver 里调整静态页面也方便。数据库选 MySQL配合 Navicat 做可视化建表和导数据比在命令行里敲 DDL 直观得多。工程里最核心的依赖只需要两组dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency第一组spring-boot-starter-web自带内嵌 Tomcat所以这个项目可以打成 JAR 包直接运行也可以打 WAR 包丢进已有 Tomcat 的 webapps 目录第二组spring-boot-starter-jdbc提供数据源自动配置配合 MyBatis 的 Mapper 接口完成对 MySQL 的操作。如果要做登录拦截和文件上传大小控制还需要在application.yml里显式声明spring.servlet.multipart.max-file-size和max-request-size默认 1MB 的上传上限在这里不够用。2.2 三类用户与文件六种权限字段系统把用户状态分成三种未登录、普通用户、管理员。未登录只能浏览公开分享且允许搜索的文件普通用户上传自己的文件管理自己文件的重命名、备注和分享设置管理员可以看到全量用户和文件列表执行禁用用户和下架文件的操作。用户权限的差异直接在 Controller 层做拦截而不是在前端隐藏按钮——前端隐藏只是体验优化后端判定才是安全边界。文件表里设计了一组权限字段来控制可见性和下载动作功能项未登录普通用户文件上传者管理员浏览搜索公开文件可可可可上传文件否可可可下载无密码分享文件可可可可下载带密码分享文件需密码需密码可可重命名/改备注否仅自己上传的文件可可禁用用户/下架文件否否否可文件字段中share_flag控制是否进入公共列表share_password为空表示无密码访问searchable决定是否出现在检索结果里need_login控制下载时是否要求登录态file_name是展示名称remark是备注信息。这里容易混淆的一点是“重命名”系统改的是file_info表里的展示名称不是磁盘上的物理文件名因为物理文件是按 MD5 存储的同一个 MD5 可能被多条文件记录引用。2.3 文件列表页的权限过滤与展示规则前端列表页的数据来源于后端接口后端根据当前登录状态返回不同范围的数据。未登录用户请求首页时接口只返回searchable 1 AND share_flag 1的记录普通用户登录后多了一个“我的文件”分类按uploader_id过滤管理员进入管理后台时走独立接口不做这些过滤条件直接分页查全表。这样设计的好处是数据权限集中在服务端前端不需要在表格渲染时频繁判断“当前用户能不能看到这一行”。3. 上传下载与 MD5 去重SpringBoot 后端核心链路实现3.1 上传接口的执行顺序为什么先算 MD5 再落盘上传文件时先计算 MD5再用 MD5 去文件表里查重。如果已存在相同 MD5 的记录说明有人上传过内容完全相同的文件新上传不落盘只新增一条文件记录指向同一份物理文件这就是“秒传”如果不存在才把文件写入存储目录。这个顺序不能反先落盘再查重磁盘写入永远逃不掉去重就失去了意义。PostMapping(/api/file/upload) public Result upload(RequestParam(file) MultipartFile multipartFile, RequestParam(value sharePassword, required false) String sharePassword, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { return Result.error(请先登录); } // 1. 计算 MD5作为文件身份的唯一标识 String md5 MD5Util.getMD5(multipartFile); // 2. 查库确认是否已有相同内容的文件 FileInfo existFile fileInfoMapper.selectByMd5(md5); if (existFile ! null) { // 3. 已存在不重复写磁盘只新增一条记录 FileInfo record new FileInfo(); record.setFileName(multipartFile.getOriginalFilename()); record.setFileMd5(md5); record.setStorePath(existFile.getStorePath()); record.setUploaderId(loginUser.getId()); fileInfoMapper.insertSelective(record); return Result.success(上传成功秒传); } // 4. 不存在保存文件并记录存储路径 String storePath storageService.save(multipartFile, md5); FileInfo newRecord new FileInfo(); newRecord.setFileName(multipartFile.getOriginalFilename()); newRecord.setFileMd5(md5); newRecord.setStorePath(storePath); newRecord.setUploaderId(loginUser.getId()); fileInfoMapper.insertSelective(newRecord); return Result.success(上传成功); }这段代码主干有四个动作从 Session 拿到当前用户、计算 MD5、查库去重、落盘或直接复用路径。MultipartFile是 Spring 对上传文件的封装getOriginalFilename()拿到的是浏览器上传时带的名字可能包含路径前缀入库前最好再截断一次。sharePassword是可选参数表示上传者是否给文件设置访问密码这里通过RequestParam(required false)来兼容“不设密码”的普通上传请求。3.2 大文件 MD5 分块读取与存储目录规划如果直接用multipartFile.getBytes()再丢给MessageDigest计算 MD5几十兆的文档问题不大但课程录像动辄几百 MB一次性读进内存很容易把堆撑爆。常见做法是分块读取每 8KB 作为一个缓冲块循环更新摘要private static String getMD5(MultipartFile file) throws IOException { MessageDigest md MessageDigest.getInstance(MD5); try (InputStream is file.getInputStream()) { byte[] buffer new byte[8192]; int len; while ((len is.read(buffer)) ! -1) { md.update(buffer, 0, len); } } return toHex(md.digest()); }这里的buffer大小可以根据服务器内存调整8KB 是兼顾 I/O 次数和内存占用的常规选择。md.digest()只调用一次在读取完成后输出最终摘要转成 32 位十六进制字符串后作为文件唯一标识。注意try-with-resources写法会自动关闭输入流避免大文件场景下句柄泄漏。物理存储目录采用“MD5 前两位做子目录”的布局例如 MD5 为a1b2c3d4...的文件对应路径是store/a1/a1b2c3d4...store/ ├── a1/ │ └── a1b2c3d4e5f6... # 实体文件 ├── f3/ │ └── f3e5d7c9a1b2... # 实体文件这样目录最多分出 256 个子目录每个子目录里的文件数量只跟哈希分布有关不会出现单目录积压几十万文件的问题而且根据 MD5 能直接推导出存储路径不需要额外维护“路径与 MD5 反向对应”的映射表。3.3 下载接口与权限判定顺序下载是权限控制最密集的接口判定顺序必须固定先确认文件存在再做“需登录”校验再做分享密码校验最后才去读磁盘。如果把文件读取放在权限校验之前攻击者可以用大文件下载把磁盘 I/O 打满。实现上用ResponseEntitybyte[]返回文件内容前端通过window.location.href触发浏览器下载。GetMapping(/api/file/download) public ResponseEntitybyte[] download(RequestParam(fileId) Long fileId, RequestParam(value password, required false) String password, HttpSession session) throws IOException { FileInfo fileInfo fileInfoMapper.selectByPrimaryKey(fileId); if (fileInfo null) { return ResponseEntity.notFound().build(); } // 1. 校验“需登录才可下载”的开关 if (fileInfo.getNeedLogin() 1 session.getAttribute(loginUser) null) { return ResponseEntity.status(401).build(); } // 2. 校验分享密码空密码可以视为不设限 if (fileInfo.getSharePassword() ! null !fileInfo.getSharePassword().equals(password)) { return ResponseEntity.status(403).body(密码错误.getBytes(StandardCharsets.UTF_8)); } Path path Paths.get(fileInfo.getStorePath()); byte[] data Files.readAllBytes(path); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, attachment; filename URLEncoder.encode(fileInfo.getFileName(), UTF-8)) .body(data); }这段代码把两类失败场景区分开了401 表示登录态缺失403 表示密码不对。前端可以根据状态码分别弹“请先登录”和“访问密码错误”而不是笼统地提示下载失败。URLEncoder.encode是对中文文件名做 URL 编码否则浏览器下载时可能把文件名显示成乱码password为空时保持required false兼容无需密码的分享链接。3.4 重命名与备注改库不改文件重命名操作的权限边界是“只能改自己上传的文件”。上传者的uploader_id等于文件表里的uploader_id两者不一致直接拒绝PutMapping(/api/file/rename) public Result rename(RequestParam Long fileId, RequestParam String newName, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); FileInfo fileInfo fileInfoMapper.selectByPrimaryKey(fileId); if (!loginUser.getId().equals(fileInfo.getUploaderId())) { return Result.error(只能修改自己上传的文件); } fileInfo.setFileName(newName); fileInfoMapper.updateByPrimaryKeySelective(fileInfo); return Result.success(); }注意这里没有调用任何文件系统 API只是更新了file_info表的file_name字段。因为物理文件是按 MD5 存储的文件名本来就是展示属性改成什么不影响磁盘上的真实文件名。4. 数据库设计与管理员的文件治理三张表撑起权限控制4.1 用户表与文件表的结构设计数据库侧的核心是user表和file_info表。user表用role字段区分普通用户和管理员status字段控制是否被禁用file_info表用file_md5做去重标识用store_path记录物理文件位置。两张表通过uploader_id关联。CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(64) NOT NULL COMMENT MD5后的密码, role tinyint NOT NULL DEFAULT 0 COMMENT 0普通用户, 1管理员, status tinyint NOT NULL DEFAULT 1 COMMENT 1正常, 0冻结, create_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uni_username (username) ) ENGINEInnoDB COMMENT用户表; CREATE TABLE file_info ( id bigint NOT NULL AUTO_INCREMENT, file_md5 char(32) NOT NULL COMMENT 文件MD5, 去重用, file_name varchar(255) NOT NULL COMMENT 展示名称, file_size bigint DEFAULT NULL COMMENT 文件大小(字节), store_path varchar(255) DEFAULT NULL COMMENT 物理存储路径, share_flag tinyint DEFAULT 0 COMMENT 1分享, 0不分享, share_password varchar(20) DEFAULT NULL COMMENT 分享密码, 空则无密码, searchable tinyint DEFAULT 1 COMMENT 1可被搜索到, 0不可, need_login tinyint DEFAULT 1 COMMENT 1需登录下载, 0不限制, remark varchar(255) DEFAULT NULL COMMENT 备注信息, uploader_id int DEFAULT NULL COMMENT 上传者id, 关联user.id, download_count int DEFAULT 0 COMMENT 累计下载次数, create_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_md5 (file_md5), KEY idx_uploader (uploader_id) ) ENGINEInnoDB COMMENT文件信息表;file_md5设为char(32)而不是varchar(32)因为 MD5 长度固定char 类型在等值查询时不需要计算可变长度配合idx_md5索引秒传判断的selectByMd5查询可以走索引快速命中。uploader_id上建普通索引支撑“我的文件”列表按用户分页查询。4.2 公共文件检索的 SQL 写法与权限字段组合文件检索页面的 SQL 需要把share_flag和searchable组合起来过滤。share_flag 1保证文件没有下架searchable 1保证文件愿意被搜索到两个条件缺一不可。SELECT id, file_name, file_size, remark FROM file_info WHERE searchable 1 AND share_flag 1 AND file_name LIKE CONCAT(%, #{keyword}, %) ORDER BY create_time DESC LIMIT #{offset}, #{pageSize};LIKE CONCAT(%, #{keyword}, %)的写法可以避免在 Java 代码里手动拼接百分号参数通过预处理方式传入不会产生 SQL 注入。如果文件量级上来模糊搜索会扫描全表但对课程设计演示级别的数据量完全够用。注意LIMIT的offset和pageSize来自前端分页参数pageSize建议限制最大值防止有人把分页大小改成一百万直接把数据库拉满。4.3 管理员的治理链路用户冻结、文件下架与 MD5 引用管理员进入后台后接口不走公共检索的过滤条件而是直接查全表。治理操作的关键是“下架”而不是“物理删除”——把share_flag置 0、searchable置 0让分享链接立即失效但保留file_md5记录。因为同一个 MD5 可能被多个用户引用如果管理员看到一条记录就物理删除文件其他引用这个文件的人会突然无法下载。PutMapping(/api/admin/file/{id}/offline) public Result offline(PathVariable Long id, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); if (loginUser null || loginUser.getRole() ! 1) { return Result.error(无管理员权限); } FileInfo fileInfo fileInfoMapper.selectByPrimaryKey(id); fileInfo.setShareFlag(0); fileInfo.setSearchable(0); fileInfoMapper.updateByPrimaryKeySelective(fileInfo); return Result.success(文件已下架); }禁用户以类似方式实现user表的status置 0登录接口里多判断一次状态即可。不同操作对物理文件的影响不完全一样操作场景file_info 表变更物理文件处理用户删除自己的文件删除记录或标记删除若 MD5 无其他引用则删除物理文件管理员下架违规文件share_flag0, searchable0不删除保留秒传引用冻结用户user.status0不处理4.4 Navicat 导入与 application.yml 数据库连接配置拿到项目后先用 Navicat 新建名为cloud_disk的数据库字符集选 utf8mb4然后执行项目里附带的cloud_disk.sql脚本三张表会自动建好。接着改后端配置spring: datasource: url: jdbc:mysql://localhost:3306/cloud_disk?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码 servlet: multipart: max-file-size: 1024MB max-request-size: 1024MBuseUnicodetruecharacterEncodingUTF-8解决中文文件名乱码serverTimezoneAsia/Shanghai解决 MySQL 8.x 的时区报错。上传大小限制在这里放开到 1024MB否则课程录像一传就被拦截Nginx 层如果有client_max_body_size也要同步调大。5. Tomcat 部署验证与 MD5 去重的一个隐蔽坑5.1 在 Windows Server 上用 jar 包方式部署与冒烟验证项目在 MyEclipse 里以 Maven 工程运行时可以直接用自带的 Maven 插件打包mvn clean package -DskipTests打完的 jar 包复制到腾讯云 Windows Server确认 3306 端口 MySQL 连接正常后用命令行启动java -jar cloud-disk-0.0.1-SNAPSHOT.jar --server.port8080冒烟验证不需要打开浏览器用 curl 请求一个简单的列表接口即可curl http://localhost:8080/api/file/list?keyword课件page1pageSize10如果返回 JSON 而不是 404说明工程启动成功、数据库连接配置正确。Windows Server 上注意两个常见问题一是 MySQL 的端口和用户名密码要和application.yml完全一致二是如果 8080 被其他进程占用换端口时记得安全组也要放行对应端口。5.2 MD5 去重隐蔽坑修改内容后重传会命中错误秒传MD5 去重最隐蔽的问题出在“文件名相同、内容不同”的场景。用户 A 上传一份courseware.pptxMD5 是a1b2...用户 B 下载后改了内容另存为courseware_v2.pptx再上传。如果代码里按“用户 ID 文件名”查重而不是按 MD5 查重B 的修改版会被误判为重复文件直接秒传成功下载下来内容还是 A 的旧版本。正确做法是严格以 MD5 作为唯一判重标准文件名只当作展示属性。上传接口对相同 MD5 的处理逻辑是“新增一条记录、复用物理路径”这样多次上传相同文件只会占用一份磁盘空间但有独立的上传记录和下载计数每个人的删除操作也互不影响。这里还有个小细节删除文件时不能只删file_info记录要先按file_md5统计引用次数引用数为 0 时才允许删除物理文件否则会破坏其他用户的下载链路。部署完成后可以连续上传两次同一个大文件验证秒传是否生效然后去store目录确认物理文件只存了一份。改完代码重新打包前记得顺手看一下下载日志里的download_count有没有正常累加这个字段就是衡量 MD5 去重收益最直接的观测点。本文还有配套的精品资源点击获取
返回列表