# 基于云存储的高校网盘系统-后端 **Repository Path**: singerw/University-Network-Disk ## Basic Information - **Project Name**: 基于云存储的高校网盘系统-后端 - **Description**: 毕设后端, 项目主要为前后端分离项目,后端采用 SpringBoot 作为主体,而为了方便进行增删查改使用了 MybatisPuls 数据库框架,它的优点在于无需编写 sql 语句即可实现种种 sql 操作。因此这样可以省去很多代码量和编码时间。 - **Primary Language**: Java - **License**: AFL-3.0 - **Default Branch**: master - **Homepage**: http://quie.singerw.com - **GVP Project**: No ## Statistics - **Stars**: 26 - **Forks**: 5 - **Created**: 2021-05-13 - **Last Updated**: 2025-12-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于云存储的高校网盘系统基本架构与设计 ​ 项目主要为前后端分离项目,后端采用 SpringBoot 作为主体,而为了方便进行增删查改使用了 MybatisPuls 数据库框架,它的优点在于无需编写 sql 语句即可实现种种 sql 操作。因此这样可以省去很多代码量和编码时间。 ## 一、技术一览 ### 1.1 前端: - 后台框架:Fantastic-admin(**hooray.gitee.io**) - UI 框架:element UI - Axios :进行网络请求 ### 1.2 后端: - 数据库:Mysql - 缓存数据库:Redis - 语言:Java - 后端框架:SpringBoot - 安全框架:SpringSecurity - 数据库框架:MybatisPuls - 验证码生成库:kaptcha - 阿里云 OSS SDK ### 1.3 项目模块 本项目的主要模块可以简单的分为: - 登录模块 - 组织模块 - 日志模块 - 系统管理模块 - 文件操作模块 在这其实可以简单的分为三大块:**权限操作、文件操作、其他操作**。 ## 二、数据库设计与数据库操作 本项目一共有五张表,分别是: - tw_user 记录用户的信息 用户表 - tw_role 记录角色对应信息 角色表 - tw_user_role 记录用户对应的角色 用户权限表 - tw_team 记录组织信息 组织信息表 - tw_options 记录本项目的所有设置信息 - tw_college 记录本项目的学院信息 根据前面的技术一览可以知道,本项目采用的操作数据库的方法是借助数据库框架 MybatisPlus。以下简称为 MP. 那么它在项目中如何展现发挥其功能的呢? 首先我们可以看到,在我们设计数据库表的时候,几乎每张表的 ID 都为 varchar,按理说,在平常我们的数据库表设计中一般都是采用数值自增来设计我们的表。那么为什么这样设计呢? ### 2.1 ID填充 因为本项目采用了 Mp 中的 id填充功能。 可以看到,在实体类之中,我们在 id 的参数上使用了注解。 其含义就是当我们执行插入语句的时候自动附以 UUID 为 id。我们知道,UUID 其实就是一组随机的字符,并且这些字符不可能重复。也就是说,我们以后插入的都是具有唯一性的 ID。 ### 2.2 内容填充 在这之后,我们还是用了内容填充功能。 像我们在表的设计中为了符合 「阿里巴巴 JAVA 开发规范」,我们还在表的其后加上了「create_time」和「update_time」两个列,我们知道这两个值分别代表当前这条数据的创建时间和更新时间。 以往我们为了实现这个功能必然需要手动的去 set 值,此时我们就借用了 Mp 的功能。 我们在这两个值之上写上了「@TableField」注解。并且写上了参数 **fill**。 在第一个参数之上我们使用的「FuekdFill.INSERT」代表在插入的时候我们为其赋值,而后者则是在插入和更新的时候都要重新赋值。这样刚好满足我们对这两个值的需求。 其次,我们就需要创建一个处理器去处理它了。**我们在「com.singerw.handler」包下创建了一个类,名为「MyMetaObjectHandler」它实现了「 MetaObjectHandler」这样一个接口**。 我们实现了它的两个方法: - insertFill() - updateFill() 很明显,它代表了插入和更新时两个事件。 ```Java this.strictInsertFill(metaObject,"createTime",Date.class,new Date()); this.strictInsertFill(metaObject,"updateTime",Date.class,new Date()); ``` 我们在其后就这样完成了参数内容的注入功能。 ### 2.3 功能实现 可以看到,我们的项目之中拥有大量的数据库操作,那么我们是如何设计这些功能的呢? 首先,我们需要创建一些实体类,它们在「com.singerw.pojo」包下,我们需要把参数和数据库的字段相互对应。然后在「com.singerw.mapper」包下做出相对应的映射。因为我们使用的是 Mp,所以我们需要将此 mapper 继承到**「BaseMapper」**之下,Mp 在其中设计了大量的方法,我们可以拿来即用,而不用去编写 sql 语句。 在这之后,我们在「com.singerw.service」包下借助我们上述的 Mapper 实现了功能。 ### 2.4 权限操作 在本项目之中权限相关的设计与应用采用了 SpringSecurity 安全框架作为主体。而主要的鉴权方式,使用了它其中的 JWT(json web token)进行实现。 //此处可以贴上百度了解的 jwt 相关的知识 因此,在本项目中也为用户设计了「角色」表,也就是说每个用户都对应了某个角色。所以,部分功能在用户调用接口的时候是需要它具有相对应角色的,这样可以减少接口被恶意调用的可能性。 回到项目,当用户发起登录之后,如果此时在后台设置之中开启了「验证码模块」(具体后面会讲到),那么会首先经过「VerifyCodeFilter」过滤器,此文件在「com.singerw.filter」包下。它会进行判断,如果当前地址为用户登录请求接口的地址(/auth/login),则会将其拦截,并拿出接口之中的「验证码参数」和「令牌」,然后通过令牌去前往 Redis 之中的验证码数据进行对比,如果接口中的「验证码参数」和 Redis中存储的一致,那么便给与其通过。当然,此处省略了「验证码模块」的具体步骤,后面会进行介绍。 此处便是第一道拦截器,如果此次请求通过了该拦截器,则会进入「登录校验」拦截器,此时如果成功,后端程序便会给与前端程序一条「令牌」,也就是 JWT。在这其中记录了用户的用户名、用户ID、用户角色。 自此,用户得到了一个令牌。在这之后我们就可以以此为标准,来判断用户是否拥有权限了。 ### 2.5 集成注解 值得注意的是,我们在 jwt 中封装了一些数据,像我们在操作用户相关的接口之时,我们经常需要使用这些数据,但为了让代码不是那么臃肿,我们必须要去做一些优化。所以本项目对应「用户名」和「用户Id」集成了两个注解,方便进行获取其中的数据。 分别对应「com.singerw.annotations」路径下的「UserId」和「UserName」。 而他们如何实现的呢? 本项目在「com.singerw.Interceptor」目录下实现了「HandlerMethodArgumentResolver」接口的类(InfoResolver)。此类的作用是进行拦截接口中的参数,如果此参数含有以上所述的注解,那么我们就可以直接本拦截器中对其赋值。 ### 2.6 文件操作 像以往的网盘项目一般都是采用「Linux 文件系统」或「Windows 文件系统」作为基础来进行设计网盘。从技术方面来讲,这两者很容易去实现,可是如果项目如果面临高并发、高 IO的时候该如何解决呢? 很明显,我们需要对服务器进行升级,或者使用「分布式的存储系统」,因此我们不得不对其进行思考,什么才是真正适合「网盘项目」的存储方式。 经过查阅资料得知了「阿里云对象存储服务」 //此处贴上 oss 的介绍。 所以,本项目应用了这项技术,因此我们还封装了一个对其操作的服务类。它是在「com.singerw.service.oss」包下的「OSService」类。 一些常用的文件操作都集成在这里。如:文件上传、下载、移动、重命名、复制,等等。 ### 2.7 基础操作 本项目在对象存储服务中创立了四个目录,分别是 - User_space - User_private - User_team - User_recycle 分别对应:用户空间、用户隐私文件、部门空间、用户回收站。 每个目录相对于来说是独立的,互不干扰。而每一个用户的具体空间是如何设计的呢?举例,当我们现在拥有一个用户id为「123」的用户,那么他的用户空间就对应到:「**user_space/123/**」目录。 通过前述「权限操作」的文档,我们知道,在用户登录之后我们给前端的令牌(JWT)之中传递了用户ID 这样一个数据。 因此我们的接口只需要去获取用户传递它要操作的文件的相对路径,然后我们再通过 jwt 中封装的 id 进行拼接,即可得到用户要操作的对应的文件。 而具体的操作接口封装在「com.singerw.controller」目录下的「FileController」。 具体的操作方式是:使用「@UserID」注解进行获得对应的 id。而此注解按照上述所讲,会自动去获取操作本接口的用户id。但是我们还需要去判断此接口是要访问那个目录里面的资源。 因为我们不止用户空间一个目录,还拥有用户空间、回收站、等目录。所以此处我们在「@UserId」注解的拦截器中做了一些操作。当前端传递的请求头中如果含有以「team」为名的参数时,那么我们在用户 id 的基础上,在之前 加上字符串「user_team/」。而其他操作也是相同。如果这些都没有,那么我们就返回以「user_space/」开头的用户ID,因此我们就可以得到了一个拼接好的目标 路径,并且也**实现了各个空间互不干扰的打算**。 ### 2.8 分享模块 当介绍文件操作之后不得不讲述「分享模块」。首先值的确定的一件事是,一般情况下分享在于两个用户之间互相分享文件,很明显它是**有时限的**。 如果按照以往的思路,我们一般都是将对应的信息存入数据库之中,但由于分享的时限特性,我们不得不采用缓存数据库,也就是 **Redis**。也就是说,我们可以很简单的告诉数据:**我们的数据是拥有局限性的,一天后我需要它消失**。 因此封装「RedisUtils」这样的工具类,以便于我们进行调用。 当然,这只是技术选型,我们如何做到分享中的两种模式呢(即带密码的私有方式分享和不带密码的公开模式)? 根据查阅资料发现,我们可以直接根据数据的 key 进行操作。比如我们此时分享了一个 id 为「abc」的数据,那么根据**Redis命名规范**,我们需要以功能名开头,然后加上冒号。即:「share:abc」。很显然,如果用户通过 「abc」这个key 去那文件的话。我们直接在代码上加入功能名这个前缀(“share:”)即可实现公开共享。 那么私有呢?其实我们直接在 key 上做操作就好了。回到刚才的例子,假设密码为「123」,那么我们的 key 就可以设计为「share:abc:123」。 当用户去试图获得这个 key 的文件之时,我们可以直接去 Redis 中查一下,此时是不带密码的查询,当第一次发现 Redis 中并没有这个数据,那么就只有两种可能:1.此 key 对应的数据是私有的,或者是不存在。 我们可以直接判断它是私有的分享,因此前端显示「用户输入秘钥」,当用户输入完成之后,后端就可以通过这个秘钥组成一个新的 key,去 Redis 中查找。因此,前台就可以拿到 Redis 中存储的数据。就这样实现了分享功能。 ## 三、功能设计 ### 3.1 日志模块 日志模块根据文档,主要目的是记录用户的登录和各个接口功能的被调用。像以往的项目中,往往都是在功能的代码完成后再插入到数据库之中。这种方法会导致代码很是臃肿,而今天我们使用了 springboot,就可以使用它的很强大的特性——AOP,也就是**面向切面**,我们不需要在接口之中插入数据库信息,我们只需要定义一个注解,在这些接口之上写好。然后创立「切面类」把目标对应到使用了我们定义的注解的方法之上就好。 我们的注解就是「com.singerw.annotations」包下的「OperLog」. 它拥有三个参数: - operModul 操作模块 - operType 操作类型 - operDesc 操作说明 因此,我们可以直接在方法之上写上此注解。 举例: 当我们想要记录「分享模块的创建分享接口」的时候,我们只需要在之上加入 **“@OperLog(operModul = "分享模块",operType = "添加",operDesc = "增加一个分享")”** 这样一个注解即可。 在其后,本项目创建了一个切面类进行对使用这个注解的方法做一个切面。 它就是位于「com.singerw.aspect」包下面的「OperLogAspect」类。当它执行的时候,它就会将我们编写的参数将其记录到 Redis 之中保存一天。 ### 3.2 验证码模块 首选,在上诉中我们知道,我们在本项目中使用了一个名字叫做「kaptcha」的一个第三方验证码生成库。 那么我们是如何将其运用到我们的项目之中的呢? 我们是首先在「com.singerw.config」包下编写了一个名为「CaptchaConfig」的配置类,主要用处就是去配置我们的验证码类该以何种方式去生成我们的验证码。因此我们创建了一个名为「captchaProducer」的 bean。它的主要用处就是通过它就可以直接生成验证码了。具体代码请浏览此配置。 然后我们在「com.singerw.controller」包下的 「UserController」中写入了「getKaptchaImage」方法。 在前端项目中,当我们想要申请一个验证码之时,就会随机生成一个 UUID,然后以 get 形式来发往后端,后端通过注入我们之前在配置类中创建的名为「captchaProducer」的 bean,以此来创建我们的验证码。 然后再其次我们将前端传递过来的 UUID作为 key,生成的验证码答案为 value 存入 Redis 之中。再之后此步骤做完之后,将具体的图片信息返回给前端,以此完成工作。 自此,这边是验证码生成的环节。 当前端发起登录请求之后,如果当前开启了验证码,那么当前端用户请求登录接口,首先会通过一个层验证码拦截器「VerifyCodeFilter」。后端会通过用户传递过来的 key 去前往 Redis 之中获取答案,然后再与用户传递过来的答案进行比对,如果一致那么就继续往下进入登录密码是否正常的拦截器。 如果没有通过验证码拦截器,那么就将返回「验证码错误」的信息。 ## 四、运行指导 ### 4.1 项目环境 * jdk1.8 * Node.js * Nginx * MySQL * IntelliJ IDEA * Visual Studio Code ### 4.2 前端运行命令 * 安装依赖:`yarn install` * 运行命令:`yarn run serve` 或 `npm run serve` ### 4.3 后端导入与运行 * IDEA导入`pom.xml`自动安装包 * 导入数据库,修改数据库名、用户名、密码 * 修改前后端端口如:`8080` **先运行后端,后运行前端**