1.前期设计
一般看来,网盘系统主要实体包括:用户、存储信息、用户文件、文件、回收文件等基本功能点。
各实体对应的表结构如下所示:
表名:user(用户表)
字段名 | 属性 | 说明 |
---|---|---|
userId | bigint | 主键 |
username | varchar | 用户名 |
password | varchar | 密码 |
telephone | varchar | 手机号 |
salt | varchar | 盐值 |
registerTime | varchar | 注册时间 |
表名:userfile (用户文件表)
字段名 | 属性 | 说明 |
---|---|---|
userFileId | bigint | 用户文件 id |
userId | bigint | 用户 id |
fileId | bigint | 文件 id |
fileName | varchar | 文件名 |
filePath | varchar | 文件路径 |
extendName | varchar | 扩展名 |
isDir | int | 是否时目录 |
uploadTime | varchar | 上传时间 |
deleteFlag | int | 删除标志 |
deleteTime | varchar | 删除时间 |
deleteBatchNum | varchar | 删除批次号 |
表名:file (文件表)
字段名 | 属性 | 说明 |
---|---|---|
fileId | bigint | 文件 id |
timeStampName | varchar | 时间戳名 |
fileUrl | varchar | 文件 url |
fileSize | bigint | 文件大小 |
storageType | int | 存储类型 |
identifier | varchar | md5 唯一标识 |
pointCount | int | 引用数量 |
表名:storage (存储信息表)
字段名 | 属性 | 说明 |
---|---|---|
storageId | bigint | 存储信息 id |
userId | bigint | 用户 id |
storageSize | bigint | 存储大小 |
表名:recoveryfile (回收文件表)
字段名 | 属性 | 说明 |
---|---|---|
recoveryFileId | bigint | 回收文件 id |
userFileId | bigint | 用户文件 id |
deleteTime | varchar | 删除时间 |
deleteBatchNum | varchar | 删除批次号 |
2.创建项目
2.1 文件与用户
网盘系统主要功能就是对文件进行管理,因此这里就需要对网盘文件进行分类。
- 文件分为普通文件和目录文件。
- 普通文件是真实存在的,保存在磁盘上,因此它具有真实的文件路径和大小。
- 目录文件是虚拟的,它存在的目的是对普通文件进行分类归档。
在一个网盘系统中,首先实现用户登录,登录的用户就可以对文件进行管理,最主要的功能为:
- 文件的上传,删除,修改文件名,文件列表展示。
- 文件的移动,文件的复制等。
在一个网盘系统,一个用户可以拥有多个文件,一个文件被多个用户所拥有。
2.2 JPA建表
JPA(Java Persistence API),中文名 Java 持久层 API,是 Java 持久化规范,这里可以使用这个工具,建立一个 Java 数据模型与数据库表结构之间的一种关系,并将 Java 的数据模型映射到数据库并建立数据库表结构。
2.2.1 添加依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shiyanlou.file</groupId>
<artifactId>qiwen-file</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
2.2.2 添加配置文件
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/springboot-file
spring.datasource.username=root
spring.datasource.password=12345678
# 配置日志保存路径
logging.file.name=/Users/xuzhi/java-projects/java-WangPan/log
# 配置日志级别
logging.level.root=info
#jpa 配置
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
2.2.3 创建实体类
创建entity包,在该包下创建对应的实体类。
File.java
import lombok.Data;
import javax.persistence.*;
@Data
@Table(name = "file")
@Entity
public class File {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(columnDefinition="bigint(20) comment '文件id'")
private Long fileId;
@Column(columnDefinition="varchar(500) comment '时间戳名称'")
private String timeStampName;
@Column(columnDefinition="varchar(500) comment '文件url'")
private String fileUrl;
@Column(columnDefinition="bigint(10) comment '文件大小'")
private Long fileSize;
}
User.java
import lombok.Data;
import javax.persistence.*;
@Data
@Table(name = "user")
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(columnDefinition = "bigint(20) comment '用户id'")
private Long userId;
@Column(columnDefinition = "varchar(30) comment '用户名'")
private String username;
@Column(columnDefinition = "varchar(35) comment '密码'")
private String password;
@Column(columnDefinition = "varchar(15) comment '手机号码'")
private String telephone;
@Column(columnDefinition = "varchar(20) comment '盐值'")
private String salt;
@Column(columnDefinition = "varchar(30) comment '注册时间'")
private String registerTime;
}
UserFile.java
import lombok.Data;
import javax.persistence.*;
@Data
@Table(name = "userfile", uniqueConstraints = {
@UniqueConstraint(name = "fileindex", columnNames = {"fileName", "filePath", "extendName"})})
@Entity
public class UserFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(columnDefinition = "bigint(20) comment '用户文件id'")
private Long userFileId;
@Column(columnDefinition = "bigint(20) comment '用户id'")
private Long userId;
@Column(columnDefinition="bigint(20) comment '文件id'")
private Long fileId;
@Column(columnDefinition="varchar(100) comment '文件名'")
private String fileName;
@Column(columnDefinition="varchar(500) comment '文件路径'")
private String filePath;
@Column(columnDefinition="varchar(100) comment '扩展名'")
private String extendName;
@Column(columnDefinition="int(1) comment '是否是目录 0-否, 1-是'")
private Integer isDir;
@Column(columnDefinition="varchar(25) comment '上传时间'")
private String uploadTime;
}
在JPA中,主要的注解作用如下:
- @Entity 表明该类是一个实体类,添加了该注解后,才能被 jpa 扫描到
- @Table 可以自定义表名,让实体类的类名和数据库中表名进行对应
- @Id 用来声明主键
- @GeneratedValue 设置主键生成方式,主要有四种类型,这里将 strategy 属性设置为 GenerationType.IDENTITY,表明主键由数据库生成,为自动增长型
- @Column 可以自定义列名或者定义其他的数据类型
接着启动项目就会在数据库中创建对应的表。
3.整合MyBatis与MyBatis plus
在完整的项目中,一般会使用成熟的持久层框架来操作数据库,这里就选用mybatis和mybatis plus进行持久层操作。
3.1 添加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
3.2 添加配置
# mybatis配置
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis-plus.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis-plus.configuration.map-underscore-to-camel-case=false
在启动类上添加@MapperScan注解,用于指定扫描的mapper接口。
3.3 创建mapper接口
创建mapper包,在该包下创建实体类对应的mapper接口。
FileMapper
public interface FileMapper {
}
UserfileMapper
public interface UserfileMapper {
}
UserMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.picacho.entity.User;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
void insertUser(User user);
List<User> selectUser();
}
在 resources 文件夹下创建mybatis文件夹,并在该文件夹下创建配置文件 mybatis-config.xml ,该配置文件主要用来指定 MyBatis 基础配置文件和实体类映射文件的地址。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>
3.4 创建mapper对应的xml文件
在mybatis文件夹下创建mapper文件夹,在该文件夹下创建UserMapper.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.picacho.mapper.UserMapper">
<insert id="insertUser" parameterType="com.picacho.entity.User">
insert into user (username, password, telephone)
value (#{username}, #{password}, #{telephone})
</insert>
<select id="selectUser" resultType="com.picacho.entity.User">
select * from user
</select>
</mapper>
3.5 进行测试
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() {
User user = new User();
user.setUsername("picacho1");
user.setPassword("123456");
user.setTelephone("12345566789");
userMapper.insertUser(user);
System.out.println("数据库字段查询结果显示");
List<User> list = userMapper.selectUser();
list.forEach(System.out::println);
}
3.6 使用MyBatis Plus功能
在之前已经创建的三个实体类中,需要添加 MyBatis Plus相关注解,这里需要用到的注解有两个,分别是 @TableName 和 @TableId。
- @TableName(“表名”) 实体类添加,如果不添加,会按照默认规则进行表明的映射,比如 UserTable->user_table
- @TableId(type = IdType.AUTO) 用来标注实体类主键
使用mybatis plus自带的方法实现测试。
@Test
public void testInsert2() {
User user = new User();
user.setUsername("picacho2");
user.setPassword("123456");
user.setTelephone("123456678");
userMapper.insert(user);
List<User> list = userMapper.selectList(null);
System.out.println("数据库字段查询结果显示");
list.forEach(System.out::println);
}
测试结果:
4.统一返回结果封装
在前后端分离的项目中,一般需要统一封装响应的结果。
首先就需要定义响应码对应的枚举类。创建common包,在该包下创建状态码枚举类ResultCodeEnum.java。
import lombok.Getter;
/**
* 结果类枚举
*/
@Getter
public enum ResultCodeEnum {
SUCCESS(true,200,"成功"),
UNKNOWN_ERROR(false,201,"未知错误"),
PARAM_ERROR(false,202,"参数错误"),
NULL_POINT(false, 203, "空指针异常"),
INDEX_OUT_OF_BOUNDS(false, 204, "下标越界异常"),
;
// 响应是否成功
private Boolean success;
// 响应状态码
private Integer code;
// 响应信息
private String message;
ResultCodeEnum(boolean success, Integer code, String message) {
this.success = success;
this.code = code;
this.message = message;
}
}
接着需要创建统一结果返回类RestResult.java。
import lombok.Data;
/**
* 统一结果返回
*
* @param <T>
*/
@Data
public class RestResult<T> {
private Boolean success = true;
private Integer code;
private String message;
private T data;
// 通用返回成功
public static RestResult success() {
RestResult r = new RestResult();
r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
r.setCode(ResultCodeEnum.SUCCESS.getCode());
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
return r;
}
// 通用返回失败,未知错误
public static RestResult fail() {
RestResult r = new RestResult();
r.setSuccess(ResultCodeEnum.UNKNOWN_ERROR.getSuccess());
r.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode());
r.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage());
return r;
}
}
4.1 测试效果
添加控制器,测试通用统一返回结果的效果。创建controller包,在该包下创建控制器UserController.java。
import com.picacho.common.RestResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 成功响应测试
*/
@GetMapping(value="/testSuccess")
@ResponseBody
public RestResult testSuccess(){
return RestResult.success();
}
/**
* 失败响应测试
*/
@GetMapping(value="/testFail")
@ResponseBody
public RestResult testFail(){
return RestResult.fail();
}
}
启动项目测试其效果:
这里返回通用的情况,但是有时候会有一种情况,我们需要给前端返回自定义消息的响应结果。因此需要在RestResult.java添加自定义的返回方法。
// 自定义返回数据
public RestResult data(T param) {
this.setData(param);
return this;
}
// 自定义状态信息
public RestResult message(String message) {
this.setMessage(message);
return this;
}
// 自定义状态码
public RestResult code(Integer code) {
this.setCode(code);
return this;
}
// 设置结果,形参为结果枚举
public static RestResult setResult(ResultCodeEnum result) {
RestResult r = new RestResult();
r.setSuccess(result.getSuccess());
r.setCode(result.getCode());
r.setMessage(result.getMessage());
return r;
}
5.统一异常处理
对控制层进行切面拦截,当发生异常时就会执行异常处理类,在Spring Boot项目中创建统一异常处理类。
创建advice包,在该包下创建GlobalExceptionHandlerAdvice.java类。
import com.picacho.common.RestResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandlerAdvice {
/**-------- 通用异常处理方法 --------**/
@ExceptionHandler(Exception.class)
@ResponseBody
public RestResult error(Exception e) {
e.printStackTrace();
log.error("全局异常捕获:" + e);
return RestResult.fail(); // 通用异常结果
}
}
在全局异常处理中,主要使用以下三个注解。
- @ControllerAdvice 这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:1、全局异常处理, 2、全局数据绑定, 3、全局数据预处理
- @ExceptionHandler 该注解用来指明异常的处理类型
- @ResponseBody 该注解为 Spring Boot 响应体注解,用在这里的目的就是当出现异常时,直接将错误返回给前台
这里除了可以处理Exception异常,也可以处理其他异常或者自定义异常。
/**
* 处理空指针异常
* @param e
* @return
*/
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public RestResult error(NullPointerException e) {
e.printStackTrace();
log.error("全局异常捕获:" + e);
return RestResult.setResult(ResultCodeEnum.NULL_POINT);
}
/**
* 越界异常
* @param e
* @return
*/
@ExceptionHandler(IndexOutOfBoundsException.class)
@ResponseBody
public RestResult error(IndexOutOfBoundsException e) {
e.printStackTrace();
log.error("全局异常捕获:" + e);
return RestResult.setResult(ResultCodeEnum.INDEX_OUT_OF_BOUNDS);
}
在UserController.java中添加测试方法,测试异常处理效果:
/**
* 空指针异常响应测试
*/
@GetMapping(value="/testNullException")
@ResponseBody
public RestResult testNullException(){
String s = null;
int index = s.length();
return RestResult.success();
}
项目demo源码下载地址:源码下载