news 2026/9/11 16:27:03

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于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 等多份 XML,SpringBoot 用自动配置把这部分压到最少,单人开发一个课程设计时能省下大量排错时间。前端使用 Bootstrap 加 jQuery,Bootstrap 的栅格系统能快速搭出文件列表的响应式布局,jQuery 负责处理上传弹窗、密码输入框这类交互,Dreamweaver 里调整静态页面也方便。数据库选 MySQL,配合 Navicat 做可视化建表和导数据,比在命令行里敲 DDL 直观得多。

工程里最核心的依赖只需要两组:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-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-sizemax-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 打满。实现上用ResponseEntity<byte[]>返回文件内容,前端通过window.location.href触发浏览器下载。

@GetMapping("/api/file/download") public ResponseEntity<byte[]> 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`) ) ENGINE=InnoDB 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`) ) ENGINE=InnoDB COMMENT='文件信息表';

file_md5设为char(32)而不是varchar(32),因为 MD5 长度固定,char 类型在等值查询时不需要计算可变长度,配合idx_md5索引,秒传判断的selectByMd5查询可以走索引快速命中。uploader_id上建普通索引,支撑“我的文件”列表按用户分页查询。

4.2 公共文件检索的 SQL 写法与权限字段组合

文件检索页面的 SQL 需要把share_flagsearchable组合起来过滤。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 注入。如果文件量级上来,模糊搜索会扫描全表,但对课程设计演示级别的数据量完全够用。注意LIMIToffsetpageSize来自前端分页参数,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_flag=0, searchable=0不删除,保留秒传引用
冻结用户user.status=0不处理

4.4 Navicat 导入与 application.yml 数据库连接配置

拿到项目后,先用 Navicat 新建名为cloud_disk的数据库,字符集选 utf8mb4,然后执行项目里附带的cloud_disk.sql脚本,三张表会自动建好。接着改后端配置:

spring: datasource: url: jdbc:mysql://localhost:3306/cloud_disk?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 你的数据库密码 servlet: multipart: max-file-size: 1024MB max-request-size: 1024MB

useUnicode=true&characterEncoding=UTF-8解决中文文件名乱码,serverTimezone=Asia/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.port=8080

冒烟验证不需要打开浏览器,用 curl 请求一个简单的列表接口即可:

curl "http://localhost:8080/api/file/list?keyword=课件&page=1&pageSize=10"

如果返回 JSON 而不是 404,说明工程启动成功、数据库连接配置正确。Windows Server 上注意两个常见问题:一是 MySQL 的端口和用户名密码要和application.yml完全一致,二是如果 8080 被其他进程占用,换端口时记得安全组也要放行对应端口。

5.2 MD5 去重隐蔽坑:修改内容后重传会命中错误秒传

MD5 去重最隐蔽的问题出在“文件名相同、内容不同”的场景。用户 A 上传一份courseware.pptx,MD5 是a1b2...;用户 B 下载后改了内容,另存为courseware_v2.pptx再上传。如果代码里按“用户 ID + 文件名”查重,而不是按 MD5 查重,B 的修改版会被误判为重复文件,直接秒传成功,下载下来内容还是 A 的旧版本。

正确做法是严格以 MD5 作为唯一判重标准,文件名只当作展示属性。上传接口对相同 MD5 的处理逻辑是“新增一条记录、复用物理路径”,这样多次上传相同文件只会占用一份磁盘空间,但有独立的上传记录和下载计数,每个人的删除操作也互不影响。这里还有个小细节:删除文件时不能只删file_info记录,要先按file_md5统计引用次数,引用数为 0 时才允许删除物理文件,否则会破坏其他用户的下载链路。

部署完成后,可以连续上传两次同一个大文件验证秒传是否生效,然后去store目录确认物理文件只存了一份。改完代码重新打包前,记得顺手看一下下载日志里的download_count有没有正常累加,这个字段就是衡量 MD5 去重收益最直接的观测点。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 16:25:13

你写的是“论文”,审稿人读的是“指纹”

毕夏AI官网 www.bixiaai.com 毕夏AI写作官网 www.bixiaai.com 毕夏官网 www.bixiaai.com 毕夏智能写作官网 www.bixiaai.com 你好&#xff0c;我是那个专门教人写论文、也专门拆穿工具神话的教育博主。 今天想跟你聊一个你可能从来没想过的问题&#xff1a;审稿人在读你…

作者头像 李华
网站建设 2026/9/11 16:24:40

如何 3 分钟拉完 dify-plugin-daemon:DaoCloud 镜像加速实战

如何 3 分钟拉完 dify-plugin-daemon&#xff1a;DaoCloud 镜像加速实战 【免费下载链接】public-image-mirror 很多镜像都在国外。比如 gcr 。国内下载很慢&#xff0c;需要加速。致力于提供连接全世界的稳定可靠安全的容器镜像服务。 项目地址: https://gitcode.com/GitHub…

作者头像 李华
网站建设 2026/9/11 16:24:38

COMSOL多物理场仿真:从原理到工程实践

1. COMSOL多物理场仿真&#xff1a;工程师的虚拟实验室第一次接触COMSOL Multiphysics时&#xff0c;我正为一个复杂的耦合场问题头疼不已——需要同时分析电磁热三场相互作用对设备性能的影响。传统单物理场仿真软件根本无法满足需求&#xff0c;直到发现COMSOL这个"虚拟…

作者头像 李华
网站建设 2026/9/11 16:19:58

学术写作AI工具的核心竞争力与虎贲等考AI实测分析

1. 学术写作AI工具的核心竞争力解析当我们需要完成一篇学术论文时&#xff0c;从选题到最终成稿往往需要耗费大量时间精力。近年来&#xff0c;各类AI写作工具如雨后春笋般涌现&#xff0c;但真正能胜任学术写作的却凤毛麟角。通过实测市面上十余款主流AI写作工具后&#xff0c…

作者头像 李华
网站建设 2026/9/11 16:18:38

私域管理软件选型实战指南:三年踩坑经验全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华