commit d44ffceaabd83659b80923126c877c34406b0494
Author: zyx <1029606625@qq.com>
Date: Fri Apr 26 18:54:15 2024 +0800
项目完成
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f7c53eb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+files
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..32cd998
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# 多模态文件管理系统后端工程 filemanage-backend
+
+## 创建数据库表
+运行sql/fileManage.sql(基于MySQL,版本不低于5.5,推荐8)
+
+## 自定义项目配置
+修改application.yml下的配置信息(或部署后创建生产环境配置文件)
+
+## 开发环境下启动
+启动src/main/java/cn/czyx007/filemanage/Application.java
+
+## 打包工程到生产环境部署
+1.mvn clean
+
+2.mvn package
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1e3498e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,116 @@
+
+
+ 4.0.0
+ cn.czyx007
+ fileManage
+ 0.0.1-SNAPSHOT
+ fileManage-backend
+ fileManage-backend
+
+ 17
+ UTF-8
+ UTF-8
+ 2.7.6
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.5.5
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.21
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.47
+
+
+ com.mysql
+ mysql-connector-j
+
+
+ com.github.whvcse
+ easy-captcha
+ 1.6.2
+
+
+ org.apache.commons
+ commons-email
+ 1.5
+
+
+ com.qcloud
+ cos_api
+ 5.6.205
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ 1.8
+ UTF-8
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring-boot.version}
+
+ cn.czyx007.filemanage.Application
+ false
+
+
+
+ repackage
+
+ repackage
+
+
+
+
+
+
+
+
diff --git a/sql/fileManage.sql b/sql/fileManage.sql
new file mode 100644
index 0000000..3b70c03
--- /dev/null
+++ b/sql/fileManage.sql
@@ -0,0 +1,53 @@
+create database if not exists fileManage charset=utf8mb4;
+
+use fileManage;
+
+/**
+ * 文件表
+ */
+create table if not exists files(
+ id bigint primary key auto_increment comment '唯一ID',
+ name varchar(255) comment '文件名',
+ store_name varchar(64) comment '文件存储名-uuid',
+ path varchar(255) comment '文件存储路径',
+ size bigint comment '文件大小',
+ type tinyint unsigned comment '文件类型-0:图片 1:视频 2:音频 3:文档 4:其他',
+ create_time datetime comment '文件创建时间',
+ update_time datetime comment '文件更新时间',
+ is_delete boolean default false comment '是否逻辑删除',
+ user_id bigint comment '所属用户ID',
+ oss_id bigint comment '存储策略ID'
+) DEFAULT CHARSET=utf8mb4;
+
+/**
+ * 用户表
+ */
+create table if not exists user(
+ id bigint primary key auto_increment comment '唯一ID',
+ email varchar(255) comment '邮箱',
+ username varchar(255) comment '用户名',
+ password char(40) comment '密码',
+ create_time datetime comment '注册时间',
+ update_time datetime comment '个人信息更新时间',
+ is_admin boolean default false comment '是否为管理员',
+ storage_used bigint default 0 comment '已使用存储空间(B)',
+ # 默认1024GB
+ storage_total bigint default (1024*1024*1024*1024) comment '总存储空间(B)'
+)DEFAULT CHARSET=utf8mb4;
+
+/**
+ * 存储策略表
+ */
+create table if not exists oss(
+ id bigint primary key auto_increment comment '唯一ID',
+ name varchar(64) comment '存储策略名',
+ type tinyint unsigned comment '存储策略类型-0:阿里云 1:腾讯云 2:七牛云 3:本地',
+ config json comment '存储策略配置信息',
+ create_time datetime comment '存储策略创建时间',
+ update_time datetime comment '存储策略更新时间',
+ creator bigint comment '存储策略创建者ID',
+ updater bigint comment '存储策略更新者ID',
+ is_active boolean default false comment '是否启用'
+)DEFAULT CHARSET=utf8mb4;
+
+insert into oss values (1, 'default', 3, null, now(), now(), -1, -1, true);
\ No newline at end of file
diff --git a/src/main/java/cn/czyx007/filemanage/Application.java b/src/main/java/cn/czyx007/filemanage/Application.java
new file mode 100644
index 0000000..746985a
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/Application.java
@@ -0,0 +1,17 @@
+package cn.czyx007.filemanage;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@SpringBootApplication
+@EnableTransactionManagement
+@EnableCaching
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/src/main/java/cn/czyx007/filemanage/bean/Files.java b/src/main/java/cn/czyx007/filemanage/bean/Files.java
new file mode 100644
index 0000000..b69171c
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/bean/Files.java
@@ -0,0 +1,74 @@
+package cn.czyx007.filemanage.bean;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+public class Files implements Serializable {
+ /**
+ * 唯一ID
+ */
+ private String id;
+
+ /**
+ * 文件名
+ */
+ private String name;
+
+ /*
+ * 文件存储名(UUID形式)
+ */
+ private String storeName;
+
+ /**
+ * 文件存储路径
+ */
+ private String path;
+
+ /**
+ * 文件大小
+ */
+ private Long size;
+
+ /**
+ * 文件类型-0:图片 1:视频 2:音频 3:文档 4:其他
+ */
+ private Integer type;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createTime;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime updateTime;
+
+ /**
+ * 是否删除
+ */
+ private Integer isDelete;
+
+ /**
+ * 所属用户ID
+ */
+ private String userId;
+
+ /**
+ * 存储策略ID
+ */
+ private String ossId;
+}
diff --git a/src/main/java/cn/czyx007/filemanage/bean/OSS.java b/src/main/java/cn/czyx007/filemanage/bean/OSS.java
new file mode 100644
index 0000000..0170170
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/bean/OSS.java
@@ -0,0 +1,66 @@
+package cn.czyx007.filemanage.bean;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+@TableName("oss")
+public class OSS implements Serializable {
+ /**
+ * 唯一ID
+ */
+ private String id;
+
+ /**
+ * 存储策略名
+ */
+ private String name;
+
+ /**
+ * 存储策略类型-0:阿里云 1:腾讯云 2:七牛云 3:本地
+ */
+ private Integer type;
+
+ /**
+ * 存储策略配置信息
+ */
+ private String config;
+
+ /**
+ * 存储策略创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createTime;
+
+ /**
+ * 存储策略更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime updateTime;
+
+ /**
+ * 存储策略创建者ID
+ */
+ private String creator;
+
+ /**
+ * 存储策略更新者ID
+ */
+ private String updater;
+
+ /**
+ * 是否启用
+ */
+ private Integer isActive;
+}
diff --git a/src/main/java/cn/czyx007/filemanage/bean/User.java b/src/main/java/cn/czyx007/filemanage/bean/User.java
new file mode 100644
index 0000000..c25c741
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/bean/User.java
@@ -0,0 +1,65 @@
+package cn.czyx007.filemanage.bean;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+public class User implements Serializable {
+ /**
+ * 唯一ID
+ */
+ private String id;
+
+ /**
+ * 邮箱
+ */
+ private String email;
+
+ /**
+ * 用户名
+ */
+ private String username;
+
+ /**
+ * 密码
+ */
+ private String password;
+
+ /**
+ * 注册时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createTime;
+
+ /**
+ * 个人信息更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime updateTime;
+
+ /**
+ * 是否为管理员
+ */
+ private Integer isAdmin;
+
+ /**
+ * 已使用存储空间(B)
+ */
+ private Long storageUsed;
+
+ /**
+ * 总存储空间(B)
+ * 默认1024GB
+ */
+ private Long storageTotal;
+}
diff --git a/src/main/java/cn/czyx007/filemanage/common/CustomException.java b/src/main/java/cn/czyx007/filemanage/common/CustomException.java
new file mode 100644
index 0000000..a6d9ba3
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/common/CustomException.java
@@ -0,0 +1,12 @@
+package cn.czyx007.filemanage.common;
+
+
+public class CustomException extends RuntimeException{
+ public CustomException() {
+ super();
+ }
+
+ public CustomException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/common/GlobalExceptionHandler.java b/src/main/java/cn/czyx007/filemanage/common/GlobalExceptionHandler.java
new file mode 100644
index 0000000..2c11a64
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/common/GlobalExceptionHandler.java
@@ -0,0 +1,18 @@
+package cn.czyx007.filemanage.common;
+
+import cn.czyx007.filemanage.utils.Result;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+//全局异常处理
+@RestControllerAdvice(annotations = RestController.class)
+@Slf4j
+public class GlobalExceptionHandler {
+ @ExceptionHandler(Exception.class)
+ public Result exceptionHandler(CustomException e){
+ log.error(e.toString());
+ return Result.error(e.getMessage());
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/common/MyMetaObjectHandler.java b/src/main/java/cn/czyx007/filemanage/common/MyMetaObjectHandler.java
new file mode 100644
index 0000000..559b152
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/common/MyMetaObjectHandler.java
@@ -0,0 +1,26 @@
+package cn.czyx007.filemanage.common;
+
+import cn.czyx007.filemanage.utils.BaseContext;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+@Component
+@Slf4j
+public class MyMetaObjectHandler implements MetaObjectHandler {
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ log.info("insertFil: {}", metaObject.toString());
+ metaObject.setValue("createTime", LocalDateTime.now());
+ metaObject.setValue("updateTime", LocalDateTime.now());
+ }
+
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ log.info("updateFill: {}", metaObject.toString());
+ metaObject.setValue("updateTime", LocalDateTime.now());
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/config/CorsConfig.java b/src/main/java/cn/czyx007/filemanage/config/CorsConfig.java
new file mode 100644
index 0000000..7832095
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/config/CorsConfig.java
@@ -0,0 +1,26 @@
+package cn.czyx007.filemanage.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+@Configuration
+public class CorsConfig {
+
+ @Bean
+ public CorsFilter corsFilter() {
+ CorsConfiguration config = new CorsConfiguration();
+
+ config.addAllowedOrigin("http://localhost:8088"); // 允许域名跨域访问
+ config.addAllowedMethod("*"); // 允许所有请求方法跨域访问
+ config.addAllowedHeader("*"); // 允许所有请求头跨域访问
+ config.setAllowCredentials(true);
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", config);
+
+ return new CorsFilter(source);
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/config/MybatisPlusConfig.java b/src/main/java/cn/czyx007/filemanage/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..b7345c4
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/config/MybatisPlusConfig.java
@@ -0,0 +1,18 @@
+package cn.czyx007.filemanage.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+//MybatisPlus的配置类
+@Configuration
+public class MybatisPlusConfig {
+
+ @Bean //该注解加在方法上,表示该方法返回的实例交给spring管理
+ public MybatisPlusInterceptor mybatisPlusInterceptor(){
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+ return interceptor;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cn/czyx007/filemanage/config/WebMvcConfig.java b/src/main/java/cn/czyx007/filemanage/config/WebMvcConfig.java
new file mode 100644
index 0000000..28d16ab
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/config/WebMvcConfig.java
@@ -0,0 +1,18 @@
+package cn.czyx007.filemanage.config;
+
+import cn.czyx007.filemanage.interceptor.LoginInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ List list = Arrays.asList("/user/sendVerCode", "/user/captcha", "/user/login", "/user/registry");
+ registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns(list);
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/controller/FileController.java b/src/main/java/cn/czyx007/filemanage/controller/FileController.java
new file mode 100644
index 0000000..f4395e5
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/controller/FileController.java
@@ -0,0 +1,376 @@
+package cn.czyx007.filemanage.controller;
+
+import cn.czyx007.filemanage.bean.Files;
+import cn.czyx007.filemanage.bean.OSS;
+import cn.czyx007.filemanage.service.FilesService;
+import cn.czyx007.filemanage.service.OSSService;
+import cn.czyx007.filemanage.service.UserService;
+import cn.czyx007.filemanage.utils.BaseContext;
+import cn.czyx007.filemanage.utils.COSUtil;
+import cn.czyx007.filemanage.utils.Result;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+
+@RestController
+@RequestMapping("/file")
+@Slf4j
+public class FileController {
+ private static String uploadPath;
+ @Autowired
+ private FilesService filesService;
+ @Autowired
+ private OSSService ossService;
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private StringRedisTemplate redisTemplate;
+
+ @Value("${oss.uploadPath}")
+ public void setUploadPath(String uploadPath) {
+ FileController.uploadPath = uploadPath;
+ }
+
+ @GetMapping("/page/{page}/{pageSize}/{isDelete}")
+ public Result> list(@PathVariable("page") Integer page, @PathVariable("pageSize") Integer pageSize,
+ @PathVariable("isDelete") Integer isDelete, @RequestParam("name") String name) {
+ String key = "fileCache::" + BaseContext.getCurrentId() + "_" + page + "_" + pageSize + "_" + isDelete + "_" + name;
+ String res = redisTemplate.opsForValue().get(key);
+ if (res != null){
+ IPage resPage = JSON.parseObject(res, IPage.class);
+ return Result.success(resPage);
+ }
+
+ IPage iPage = new Page<>(page, pageSize);
+ LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
+ lqw.eq(Files::getUserId, BaseContext.getCurrentId()).eq(Files::getIsDelete, isDelete);
+ if(name != null && !name.isEmpty())
+ lqw.like(Files::getName, name);
+ filesService.page(iPage, lqw);
+
+ redisTemplate.opsForValue().set(key, JSON.toJSONString(iPage));
+ return Result.success(iPage);
+ }
+
+// @GetMapping("/{id}")
+// public Result getFile(@PathVariable("id") String id){
+// Files file = filesService.getFileById(id);
+// if(file == null)
+// return Result.error("文件不存在或被删除");
+// return Result.success(file);
+// }
+
+ @GetMapping("/preview/{id}")
+ public Result getPreviewFile(@PathVariable("id") String id){
+ String key = "previewFile::"+BaseContext.getCurrentId()+"_"+id;
+ Files file = filesService.getById(id);
+ if(file == null) {
+ redisTemplate.delete(key);
+ return Result.error("文件不存在或被删除");
+ } else {
+ String res = redisTemplate.opsForValue().get(key);
+ if (res != null)
+ return Result.success(res);
+ }
+
+ //云存储,直接返回url
+ if(file.getPath().startsWith("http")) {
+ redisTemplate.opsForValue().set(key, file.getPath());
+ return Result.success(file.getPath());
+ }
+ // 本地存储,读取图片文件内容
+ Path imagePath = Paths.get(file.getPath());
+ byte[] imageBytes;
+ try {
+ imageBytes = java.nio.file.Files.readAllBytes(imagePath);
+ } catch (IOException e) {
+ log.error("读取图片文件内容失败", e);
+ return Result.error(e.getMessage());
+ }
+
+ // 将图片的内容转换为 base64 格式
+ String suffix = file.getName().substring(file.getName().lastIndexOf(".")+1);
+ String base64Image = "data:image/"+suffix+";base64,"+Base64.getEncoder().encodeToString(imageBytes);
+ redisTemplate.opsForValue().set(key, base64Image);
+ return Result.success(base64Image);
+ }
+
+ @PostMapping("/upload")
+ public Result upload(@RequestParam("file") MultipartFile multipartFile){
+ if (multipartFile == null || multipartFile.isEmpty()) {
+ return Result.error("未上传文件");
+ }
+ OSS ossConfig = ossService.getActive();
+ if (ossConfig == null) {
+ return Result.error("没有有效的存储策略");
+ }
+
+ //更新用户已用空间
+ if(!userService.updateStorageUsed(multipartFile.getSize(), true))
+ return Result.error("空间不足");
+
+ try {
+ String fileUploadPath = System.getProperty("user.dir") + uploadPath;
+
+ // 处理文件上传逻辑,根据存储策略来保存文件
+ Files file = new Files();
+
+ // 设置文件名
+ file.setName(multipartFile.getOriginalFilename());
+
+ // 设置存储名(UUID 形式,保证唯一性)
+ String uuid = UUID.randomUUID().toString();
+ String originName = multipartFile.getOriginalFilename();
+ String suffix = originName.substring(originName.lastIndexOf("."));
+ String storeName = uuid+suffix;
+ file.setStoreName(storeName);
+ log.info(file.toString());
+
+ // 设置文件存储路径
+ switch (ossConfig.getType()){
+ case 0://阿里云
+
+ break;
+ case 1://腾讯云
+ COSUtil.init(ossConfig);
+ file.setPath(COSUtil.customUrl + BaseContext.getCurrentId() + "/" + storeName);
+ break;
+ case 2://七牛云
+
+ break;
+ case 3://本地
+ String filePath = fileUploadPath + "/" + storeName;
+ file.setPath(filePath);
+ break;
+ }
+ log.info("上传文件保存路径:" + file.getPath());
+
+ // 设置文件大小
+ file.setSize(multipartFile.getSize());
+
+ // 设置文件类型
+ int fileType;
+ String mimeType = java.nio.file.Files.probeContentType(Paths.get(storeName));
+ if (mimeType != null) {
+ if (mimeType.startsWith("image/")) {
+ fileType = 0; // 图片类型
+ } else if (mimeType.startsWith("audio/")) {
+ fileType = 1; // 音频类型
+ } else if (mimeType.startsWith("video/")) {
+ fileType = 2; // 视频类型
+ } else if (mimeType.startsWith("text/") || mimeType.endsWith("pdf") ||
+ mimeType.startsWith("application/vnd.openxmlformats-officedocument") ||
+ mimeType.equals("application/pdf") || mimeType.endsWith("json")) {
+ fileType = 3; // 文档类型
+ } else {
+ fileType = 4; // 其他类型
+ }
+ } else {
+ fileType = 4; // 无法确定类型,设置为其他类型
+ }
+ file.setType(fileType);
+
+ // 设置所属用户ID
+ String userId = BaseContext.getCurrentId();
+ file.setUserId(userId);
+
+ //设置存储策略id
+ file.setOssId(ossConfig.getId());
+
+ // 保存文件到指定目录
+ switch (ossConfig.getType()){
+ case 0://阿里云
+ break;
+ case 1://腾讯云
+ COSUtil.uploadFile(multipartFile, file);
+ break;
+ case 2://七牛云
+ break;
+ case 3://本地
+ File tmpFile = new File(fileUploadPath, storeName);
+ // 检查目录是否存在,如果不存在则创建目录
+ if (!tmpFile.getParentFile().exists()) {
+ tmpFile.getParentFile().mkdirs(); // 创建目录及其父目录
+ }
+ multipartFile.transferTo(tmpFile);
+ break;
+ default:
+ break;
+ }
+
+ // 调用文件服务保存文件信息到数据库
+ filesService.save(file);
+ //清除该用户的文件列表缓存
+ Set keys = redisTemplate.keys("fileCache::" + BaseContext.getCurrentId() + "*");
+ if (keys != null) {
+ redisTemplate.delete(keys);
+ }
+
+ return Result.success("文件上传成功");
+ } catch (Exception e) {
+ log.error("文件上传失败,", e);
+ return Result.error(e.getMessage());
+ }
+ }
+
+ @GetMapping("/download/{id}")
+ public ResponseEntity download(@PathVariable("id") String id) {
+ Files file = filesService.getFileById(id);
+ if (file == null) {
+ return ResponseEntity.status(404).body(null);
+ }
+ // 设置响应头信息
+ HttpHeaders headers = new HttpHeaders();
+ headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());
+
+ // 创建文件资源对象并返回
+ Resource resource = null;
+ if(file.getPath().startsWith("http")){
+ try {
+ resource = new UrlResource(file.getPath());
+ } catch (MalformedURLException e) {
+ log.error(e.toString());
+ }
+ } else {
+ resource = new FileSystemResource(new File(file.getPath()));
+ }
+ return ResponseEntity.ok().headers(headers).body(resource);
+ }
+
+ @PutMapping("/{id}/{isDelete}")
+ public Result updateIsDelete(@PathVariable("id") String id, @PathVariable("isDelete") Integer isDelete){
+ //清除该用户的文件列表缓存
+ Set keys = redisTemplate.keys("fileCache::" + BaseContext.getCurrentId() + "*");
+ if (keys != null) {
+ redisTemplate.delete(keys);
+ }
+ //清除该用户的该文件预览缓存
+ redisTemplate.delete("previewFile::"+BaseContext.getCurrentId()+"_"+id);
+
+ Files file = filesService.getById(id);
+ if(file == null) {
+ return Result.error("文件不存在或被删除");
+ }
+ if(isDelete == 1){
+ file.setIsDelete(1);
+ filesService.updateById(file);
+ return Result.success("文件删除成功");
+ } else if(isDelete == 0){
+ file.setIsDelete(0);
+ filesService.updateById(file);
+ return Result.success("文件恢复成功");
+ } else return Result.error("参数错误");
+ }
+
+ @DeleteMapping("/{id}")
+ public Result delete(@PathVariable("id") String id){
+ //清除该用户的文件列表缓存
+ Set keys = redisTemplate.keys("fileCache::" + BaseContext.getCurrentId() + "*");
+ if(keys != null)
+ redisTemplate.delete(keys);
+
+ Files file = filesService.getById(id);
+ if(file == null)
+ return Result.error("文件不存在或被删除");
+ if(file.getIsDelete() == 0) {
+ return Result.error("你不能永久删除未在回收站的文件");
+ }
+ //物理删除
+ filesService.removeById(id);
+ if(file.getPath().startsWith("http")){
+ COSUtil.init(ossService.getById(file.getOssId()));
+ COSUtil.createCOSClient().deleteObject(COSUtil.bucketName, BaseContext.getCurrentId()+"/"+file.getStoreName());
+ } else {
+ new File(file.getPath()).delete();
+ }
+ //更新用户已用存储空间
+ userService.updateStorageUsed(file.getSize(), false);
+
+ return Result.success("文件已被彻底删除");
+ }
+
+ @PutMapping("/{isDelete}")
+ public Result batchUpdateIsDelete(@PathVariable("isDelete") Integer isDelete, @RequestBody List ids){
+ //清除该用户的文件列表缓存
+ Set keys = redisTemplate.keys("fileCache::" + BaseContext.getCurrentId() + "*");
+ if (keys != null) {
+ redisTemplate.delete(keys);
+ }
+ //清除该用户的该文件预览缓存
+ keys = redisTemplate.keys("previewFile::" + BaseContext.getCurrentId() + "*");
+ if (keys != null) {
+ redisTemplate.delete(keys);
+ }
+
+ List list = filesService.listByIds(ids);
+ if(list == null) {
+ return Result.error("文件不存在或被删除");
+ }
+
+ if(isDelete == 1){
+ list.forEach(file -> file.setIsDelete(1));
+ filesService.updateBatchById(list);
+ return Result.success("文件批量删除成功");
+ } else if(isDelete == 0){
+ list.forEach(file -> file.setIsDelete(0));
+ filesService.updateBatchById(list);
+ return Result.success("文件批量恢复成功");
+ } else return Result.error("参数错误");
+ }
+
+ @DeleteMapping
+ public Result batchDelete(@RequestBody List ids){
+ //清除该用户的文件列表缓存
+ Set keys = redisTemplate.keys("fileCache::" + BaseContext.getCurrentId() + "*");
+ if(keys != null)
+ redisTemplate.delete(keys);
+
+ List list = filesService.listByIds(ids);
+ if(list == null)
+ return Result.error("文件不存在或被删除");
+
+ long updateSize = 0;
+ for(Files file : list) {
+ if (file.getIsDelete() == 0) {
+ return Result.error("你不能永久删除未在回收站的文件");
+ }
+ updateSize += file.getSize();
+ }
+ //物理删除
+ filesService.removeBatchByIds(ids);
+
+ for (Files file : list) {
+ if(file.getPath().startsWith("http")){
+ COSUtil.init(ossService.getById(file.getOssId()));
+ COSUtil.createCOSClient().deleteObject(COSUtil.bucketName, BaseContext.getCurrentId()+"/"+file.getStoreName());
+ } else {
+ new File(file.getPath()).delete();
+ }
+ }
+ //更新用户已用存储空间
+ userService.updateStorageUsed(updateSize, false);
+
+ return Result.success("文件已被彻底删除");
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/controller/OSSController.java b/src/main/java/cn/czyx007/filemanage/controller/OSSController.java
new file mode 100644
index 0000000..39ef2df
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/controller/OSSController.java
@@ -0,0 +1,147 @@
+package cn.czyx007.filemanage.controller;
+
+import cn.czyx007.filemanage.bean.Files;
+import cn.czyx007.filemanage.bean.OSS;
+import cn.czyx007.filemanage.bean.User;
+import cn.czyx007.filemanage.service.FilesService;
+import cn.czyx007.filemanage.service.OSSService;
+import cn.czyx007.filemanage.service.UserService;
+import cn.czyx007.filemanage.utils.BaseContext;
+import cn.czyx007.filemanage.utils.Result;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/oss")
+public class OSSController {
+ @Autowired
+ private UserService userService;
+ @Autowired
+ private OSSService ossService;
+ @Autowired
+ private FilesService filesService;
+
+ @GetMapping
+ public Result> list(){
+ if(userService.getById(BaseContext.getCurrentId()).getIsAdmin() == 0)
+ return Result.error("无权限");
+
+ List list = ossService.list();
+ Map idToName = new HashMap<>();
+
+ // 获取所有管理员用户并将其ID与用户名存入idToName映射中
+ LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
+ lqw.eq(User::getIsAdmin, 1);
+ List adminUsers = userService.list(lqw);
+ for (User adminUser : adminUsers) {
+ idToName.put(adminUser.getId(), adminUser.getUsername());
+ }
+
+ // 更新OSS对象中的创建者和更新者信息
+ for (OSS oss : list) {
+ String creator = oss.getCreator();
+ if (!"-1".equals(creator)) {
+ String username = idToName.get(creator);
+ if (username != null) {
+ oss.setCreator(username);
+ }
+ }
+
+ String updater = oss.getUpdater();
+ if (!"-1".equals(updater)) {
+ String username = idToName.get(updater);
+ if (username != null) {
+ oss.setUpdater(username);
+ }
+ }
+ }
+
+ return Result.success(list);
+ }
+
+ @PostMapping
+ public Result addConfig(@RequestBody Map config){
+ if(userService.getById(BaseContext.getCurrentId()).getIsAdmin() == 0)
+ return Result.error("无权限");
+ OSS oss = new OSS();
+ oss.setName(config.get("name"));
+ oss.setType(Integer.valueOf(config.get("type")));
+
+ config.remove("name");
+ config.remove("type");
+ oss.setConfig(JSON.toJSONString(config));
+
+ oss.setCreator(BaseContext.getCurrentId());
+ oss.setUpdater(BaseContext.getCurrentId());
+ ossService.save(oss);
+ return Result.success("存储配置保存成功");
+ }
+
+ @PutMapping("/{id}")
+ public Result updateConfig(@RequestBody Map config, @PathVariable("id") String id){
+ if(userService.getById(BaseContext.getCurrentId()).getIsAdmin() == 0)
+ return Result.error("无权限");
+ OSS oss = ossService.getById(id);
+ oss.setName(config.get("name"));
+ oss.setUpdater(BaseContext.getCurrentId());
+
+ config.remove("name");
+ config.remove("type");
+ JSONObject storageConfig = JSON.parseObject(oss.getConfig());
+ if(storageConfig != null) {
+ storageConfig.put("customUrl", config.get("customUrl"));
+ oss.setConfig(JSON.toJSONString(storageConfig));
+ } else {
+ oss.setConfig(JSON.toJSONString(config));
+ }
+
+ ossService.updateById(oss);
+ return Result.success("存储配置修改成功");
+ }
+
+ @PutMapping("/{id}/{isActive}")
+ public Result updateConfigStatus(@PathVariable("id") String id, @PathVariable("isActive") Integer isActive){
+ if(userService.getById(BaseContext.getCurrentId()).getIsAdmin() == 0)
+ return Result.error("无权限");
+ OSS oss = ossService.getById(id);
+ if(oss == null)
+ return Result.error("存储配置不存在");
+
+ //启用指定存储策略,将其他策略禁用
+ if(isActive == 0) {
+ oss.setIsActive(1);
+ oss.setUpdater(BaseContext.getCurrentId());
+ ossService.updateById(oss);
+
+ LambdaUpdateWrapper luw = new LambdaUpdateWrapper<>();
+ luw.ne(OSS::getId, id).set(OSS::getIsActive, 0);
+ ossService.update(luw);
+ return Result.success("存储策略启用成功");
+ } else {
+ //试图仅仅禁用某一个策略,未知要启用的存储策略
+ //避免误操作导致没有有效的存储策略
+ return Result.error("不允许直接禁用某项存储策略");
+ }
+ }
+
+ @DeleteMapping("/{id}")
+ public Result deleteConfig(@PathVariable("id") String id){
+ if(userService.getById(BaseContext.getCurrentId()).getIsAdmin() == 0)
+ return Result.error("无权限");
+ LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
+ lqw.eq(Files::getOssId, id);
+ if(filesService.exists(lqw)){
+ return Result.error("该存储策略下存在文件,无法删除");
+ }
+ ossService.removeById(id);
+ return Result.success("存储配置删除成功");
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/controller/UserController.java b/src/main/java/cn/czyx007/filemanage/controller/UserController.java
new file mode 100644
index 0000000..30e98d5
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/controller/UserController.java
@@ -0,0 +1,198 @@
+package cn.czyx007.filemanage.controller;
+
+import cn.czyx007.filemanage.bean.User;
+import cn.czyx007.filemanage.dto.UserDto;
+import cn.czyx007.filemanage.service.UserService;
+import cn.czyx007.filemanage.utils.*;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.wf.captcha.SpecCaptcha;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.mail.EmailException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@RestController
+@RequestMapping("/user")
+@Slf4j
+public class UserController {
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private StringRedisTemplate redisTemplate;
+
+ //发送注册验证码
+ @PostMapping("/sendVerCode")
+ public Result sendVerCode(@RequestBody Map map){
+ //生成6位随机数字验证码
+ String code = ValidateCodeUtils.generateValidateCode(6).toString();
+ log.info("验证码:{}", code);
+ //发送短信,让用户接受验证码
+ try {
+ String email = map.get("email");
+ SendEmailUtils.sendAuthCodeEmail(email, code);
+ //把验证码保存到redis,5分钟有效
+ redisTemplate.opsForValue().set(email + ":code", code, 5, TimeUnit.MINUTES);
+ return Result.success("验证码发送成功,请查看邮箱");
+ } catch (EmailException e) {
+ log.error(e.toString());
+ return Result.error("验证码发送失败,请稍后重试");
+ }
+ }
+
+ @GetMapping("/captcha")
+ public Result sendCaptcha() {
+ SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
+ String verCode = specCaptcha.text().toLowerCase();
+ String key = UUID.randomUUID().toString();
+ // 存入redis并设置过期时间为1分钟
+ redisTemplate.opsForValue().set(key, verCode, 1, TimeUnit.MINUTES);
+ // 将key和base64返回给前端
+ HashMap map = new HashMap<>();
+ map.put("key", key);
+ map.put("image", specCaptcha.toBase64());
+ log.info("验证码:{}, key:{}", specCaptcha.text(), key);
+ return Result.success(JSON.toJSONString(map));
+ }
+
+ @PostMapping("/login")
+ public Result login(@RequestBody UserDto userDto, HttpServletResponse response){
+ log.info("UserDto: {}", userDto);
+ // 获取redis中的验证码
+ String redisCode = redisTemplate.opsForValue().get(userDto.getVerKey());
+ if(redisCode==null){
+ return Result.error("验证码已过期,请刷新");
+ }
+ // 校验验证码
+ String captcha = userDto.getCaptcha();
+ if (captcha==null || !captcha.toLowerCase().equalsIgnoreCase(redisCode)) {
+ return Result.error("验证码错误");
+ }
+
+ LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
+ lqw.eq(User::getEmail, userDto.getEmail())
+ .eq(User::getPassword, EncryptUtils.hashPassword(userDto.getPassword()))
+ .or(i -> i.eq(User::getUsername, userDto.getEmail())
+ .eq(User::getPassword, EncryptUtils.hashPassword(userDto.getPassword())));
+
+ User user = userService.getOne(lqw);
+ if (userService.getOne(lqw)==null){
+ return Result.error("账号不存在或密码错误");
+ }
+
+ Cookie cookie = new Cookie("user", String.valueOf(user.getId()));
+ cookie.setPath("/");
+ cookie.setDomain("localhost");
+// cookie.setHttpOnly(true);
+
+ response.addCookie(cookie);
+ BaseContext.setCurrentId(user.getId());
+ log.info("用户登录成功,用户id:{}", user.getId());
+ log.info("cookie:{}",cookie.getValue());
+
+ //验证码使用之后,从redis中删除
+ redisTemplate.delete(userDto.getVerKey());
+ return Result.success("登录成功");
+ }
+
+ @PostMapping("/registry")
+ public Result registry(@RequestBody UserDto user){
+ LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
+ lqw.eq(User::getEmail, user.getEmail());
+ if (userService.getOne(lqw)!=null){
+ return Result.error("该邮箱已被注册");
+ }
+ // 获取redis中的验证码
+ String redisCode = redisTemplate.opsForValue().get(user.getEmail()+":code");
+ if(redisCode==null){
+ return Result.error("验证码已过期,请重新获取");
+ }
+ // 校验验证码
+ String verCode = user.getVerificationCode();
+ if (verCode==null || !verCode.equals(redisCode)) {
+ return Result.error("验证码错误");
+ }
+
+ user.setUsername(user.getEmail());
+ user.setPassword(EncryptUtils.hashPassword(user.getPassword()));
+ userService.save(user);
+ log.info("用户注册成功:{}", user);
+
+ //验证码使用之后,从redis中删除
+ redisTemplate.delete(user.getEmail() + ":code");
+ return Result.success("注册成功");
+ }
+
+ @PostMapping("/logout")
+ public Result logout(HttpServletResponse response){
+ BaseContext.setCurrentId(null);
+ Cookie cookie = new Cookie("user", null);
+ cookie.setPath("/");
+ cookie.setDomain("localhost");
+// cookie.setHttpOnly(true);
+ cookie.setMaxAge(0);
+
+ response.addCookie(cookie);
+ return Result.success("退出成功");
+ }
+
+ @GetMapping
+ public Result getUser(){
+ String userId = BaseContext.getCurrentId();
+ log.info("当前用户id:{}", userId);
+ User user = userService.getById(userId);
+ if(user==null){
+ return Result.error("用户不存在");
+ }
+ user.setPassword(null);
+ return Result.success(user);
+ }
+
+ @PutMapping("/updatePassword")
+ public Result updatePassword(@RequestBody Map map, HttpServletResponse response){
+ String oldPassword = map.get("oldPassword");
+ String newPassword = map.get("newPassword");
+ User user = userService.getById(BaseContext.getCurrentId());
+ if(user==null){
+ return Result.error("用户不存在");
+ }
+ if(!user.getPassword().equals(EncryptUtils.hashPassword(oldPassword))){
+ return Result.error("旧密码错误");
+ }
+ user.setPassword(EncryptUtils.hashPassword(newPassword));
+ userService.updateById(user);
+
+ //密码修改成功,删除cookie,将用户踢下线
+ BaseContext.setCurrentId(null);
+ Cookie cookie = new Cookie("user", null);
+ cookie.setPath("/");
+ cookie.setDomain("localhost");
+ cookie.setMaxAge(0);
+ response.addCookie(cookie);
+
+ log.info("用户密码修改成功,用户id:{}", user.getId());
+ return Result.success("密码修改成功");
+ }
+
+ @PutMapping("/updateUsername")
+ public Result updateUsername(@RequestBody User user){
+ LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
+ lqw.eq(User::getUsername, user.getUsername());
+ if (userService.getOne(lqw)!=null){
+ return Result.error("该用户名已被使用");
+ }
+ user.setId(BaseContext.getCurrentId());
+ userService.updateById(user);
+ log.info("用户修改用户名成功,用户id:{}", user.getId());
+ return Result.success("用户名修改成功");
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/dto/UserDto.java b/src/main/java/cn/czyx007/filemanage/dto/UserDto.java
new file mode 100644
index 0000000..1cf9616
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/dto/UserDto.java
@@ -0,0 +1,40 @@
+package cn.czyx007.filemanage.dto;
+
+import cn.czyx007.filemanage.bean.User;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class UserDto extends User {
+ /**
+ * 确认密码
+ */
+ private String confirmPassword;
+
+ /**
+ * 邮箱验证码
+ */
+ private String verificationCode;
+
+ /**
+ * 图片验证码
+ */
+ private String captcha;
+
+ /**
+ * 图片验证码对应key
+ */
+ private String verKey;
+
+ @Override
+ public String toString() {
+ return "UserDto{" +
+ "confirmPassword='" + confirmPassword + '\'' +
+ ", verificationCode='" + verificationCode + '\'' +
+ ", captcha='" + captcha + '\'' +
+ ", verKey='" + verKey + '\'' +
+ ", " + super.toString() + '\'' +
+ "} ";
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/interceptor/LoginInterceptor.java b/src/main/java/cn/czyx007/filemanage/interceptor/LoginInterceptor.java
new file mode 100644
index 0000000..6f72f41
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/interceptor/LoginInterceptor.java
@@ -0,0 +1,41 @@
+package cn.czyx007.filemanage.interceptor;
+
+import cn.czyx007.filemanage.utils.BaseContext;
+import cn.czyx007.filemanage.utils.Result;
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Arrays;
+
+@Slf4j
+public class LoginInterceptor implements HandlerInterceptor {
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ //获取cookie['user']
+ String userId = null;
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null) {
+ for (Cookie cookie : cookies) {
+ if ("user".equals(cookie.getName())) {
+ userId = cookie.getValue();
+ break;
+ }
+ }
+ }
+ log.info("LoginInterceptor >> "+request.getRequestURI()+" >> cookie['user'] = " + userId);
+ //判断是否为空
+ if (userId != null) {
+ log.info("当前用户已经登录,用户id为:{}", userId);
+ BaseContext.setCurrentId(userId);
+ return true;
+ }
+ //未登录,响应数据
+ response.getWriter().write(JSON.toJSONString(Result.error("NOT-LOGIN")));
+ return false;
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/mapper/FilesMapper.java b/src/main/java/cn/czyx007/filemanage/mapper/FilesMapper.java
new file mode 100644
index 0000000..94053b9
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/mapper/FilesMapper.java
@@ -0,0 +1,12 @@
+package cn.czyx007.filemanage.mapper;
+
+import cn.czyx007.filemanage.bean.Files;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 文件信息(File)表数据库访问层
+ */
+@Mapper
+public interface FilesMapper extends BaseMapper {
+}
diff --git a/src/main/java/cn/czyx007/filemanage/mapper/OSSMapper.java b/src/main/java/cn/czyx007/filemanage/mapper/OSSMapper.java
new file mode 100644
index 0000000..7fa3ffd
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/mapper/OSSMapper.java
@@ -0,0 +1,9 @@
+package cn.czyx007.filemanage.mapper;
+
+import cn.czyx007.filemanage.bean.OSS;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface OSSMapper extends BaseMapper {
+}
diff --git a/src/main/java/cn/czyx007/filemanage/mapper/UserMapper.java b/src/main/java/cn/czyx007/filemanage/mapper/UserMapper.java
new file mode 100644
index 0000000..088ea14
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/mapper/UserMapper.java
@@ -0,0 +1,15 @@
+package cn.czyx007.filemanage.mapper;
+
+import cn.czyx007.filemanage.bean.User;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+
+/**
+ * 用户信息(User)表数据库访问层
+ */
+@Mapper
+public interface UserMapper extends BaseMapper {
+}
diff --git a/src/main/java/cn/czyx007/filemanage/service/FilesService.java b/src/main/java/cn/czyx007/filemanage/service/FilesService.java
new file mode 100644
index 0000000..1973508
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/service/FilesService.java
@@ -0,0 +1,16 @@
+package cn.czyx007.filemanage.service;
+
+import cn.czyx007.filemanage.bean.Files;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * 文件信息(File)表服务接口
+ */
+public interface FilesService extends IService {
+ /**
+ * 查询id对应的未被删除的文件
+ * @param id 文件唯一id
+ * @return 文件对象Files
+ */
+ Files getFileById(String id);
+}
diff --git a/src/main/java/cn/czyx007/filemanage/service/OSSService.java b/src/main/java/cn/czyx007/filemanage/service/OSSService.java
new file mode 100644
index 0000000..1ac4c57
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/service/OSSService.java
@@ -0,0 +1,8 @@
+package cn.czyx007.filemanage.service;
+
+import cn.czyx007.filemanage.bean.OSS;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+public interface OSSService extends IService {
+ OSS getActive();
+}
diff --git a/src/main/java/cn/czyx007/filemanage/service/UserService.java b/src/main/java/cn/czyx007/filemanage/service/UserService.java
new file mode 100644
index 0000000..e9ec4db
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/service/UserService.java
@@ -0,0 +1,17 @@
+package cn.czyx007.filemanage.service;
+
+import cn.czyx007.filemanage.bean.User;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * 用户信息(User)表服务接口
+ */
+public interface UserService extends IService {
+ /**
+ * 更新用户已使用存储空间
+ * @param updateSize 变化的存储空间大小
+ * @param isAdd 是否增加
+ * @return true:更新成功;false:更新失败(用户剩余存储空间不足)
+ */
+ boolean updateStorageUsed(long updateSize, boolean isAdd);
+}
diff --git a/src/main/java/cn/czyx007/filemanage/service/impl/FilesServiceImpl.java b/src/main/java/cn/czyx007/filemanage/service/impl/FilesServiceImpl.java
new file mode 100644
index 0000000..a8eb32a
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/service/impl/FilesServiceImpl.java
@@ -0,0 +1,21 @@
+package cn.czyx007.filemanage.service.impl;
+
+import cn.czyx007.filemanage.bean.Files;
+import cn.czyx007.filemanage.mapper.FilesMapper;
+import cn.czyx007.filemanage.service.FilesService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * 文件信息(File)表服务实现类
+ */
+@Service
+public class FilesServiceImpl extends ServiceImpl implements FilesService {
+ @Override
+ public Files getFileById(String id){
+ LambdaQueryWrapper lqw = new LambdaQueryWrapper<>();
+ lqw.eq(Files::getId, id).eq(Files::getIsDelete, 0);
+ return this.getOne(lqw);
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/service/impl/OSSServiceImpl.java b/src/main/java/cn/czyx007/filemanage/service/impl/OSSServiceImpl.java
new file mode 100644
index 0000000..4915e17
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/service/impl/OSSServiceImpl.java
@@ -0,0 +1,16 @@
+package cn.czyx007.filemanage.service.impl;
+
+import cn.czyx007.filemanage.bean.OSS;
+import cn.czyx007.filemanage.mapper.OSSMapper;
+import cn.czyx007.filemanage.service.OSSService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service
+public class OSSServiceImpl extends ServiceImpl implements OSSService {
+ @Override
+ public OSS getActive(){
+ return getOne(new LambdaQueryWrapper().eq(OSS::getIsActive, true));
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/service/impl/UserServiceImpl.java b/src/main/java/cn/czyx007/filemanage/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..5ce3bbc
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/service/impl/UserServiceImpl.java
@@ -0,0 +1,31 @@
+package cn.czyx007.filemanage.service.impl;
+
+import cn.czyx007.filemanage.bean.User;
+import cn.czyx007.filemanage.mapper.UserMapper;
+import cn.czyx007.filemanage.service.UserService;
+import cn.czyx007.filemanage.utils.BaseContext;
+import cn.czyx007.filemanage.utils.Result;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * 用户信息(User)表服务实现类
+ */
+@Service
+public class UserServiceImpl extends ServiceImpl implements UserService {
+ @Override
+ public boolean updateStorageUsed(long updateSize, boolean isAdd) {
+ User user = getById(BaseContext.getCurrentId());
+ if(isAdd) {
+ long newSize = updateSize + user.getStorageUsed();
+ //检查是否有足够空间用于存储
+ if(newSize > user.getStorageTotal())
+ return false;
+ user.setStorageUsed(newSize);
+ }
+ else
+ user.setStorageUsed(user.getStorageUsed() - updateSize);
+ updateById(user);
+ return true;
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/utils/BaseContext.java b/src/main/java/cn/czyx007/filemanage/utils/BaseContext.java
new file mode 100644
index 0000000..8160a63
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/utils/BaseContext.java
@@ -0,0 +1,22 @@
+package cn.czyx007.filemanage.utils;
+
+/**
+ * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
+ */
+public class BaseContext {
+ private static ThreadLocal threadLocal = new ThreadLocal<>();
+ /**
+ * 设置值
+ * @param id
+ */
+ public static void setCurrentId(String id){
+ threadLocal.set(id);
+ }
+ /**
+ * 获取值
+ * @return
+ */
+ public static String getCurrentId(){
+ return threadLocal.get();
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/utils/COSUtil.java b/src/main/java/cn/czyx007/filemanage/utils/COSUtil.java
new file mode 100644
index 0000000..35d9511
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/utils/COSUtil.java
@@ -0,0 +1,121 @@
+package cn.czyx007.filemanage.utils;
+
+import cn.czyx007.filemanage.bean.Files;
+import cn.czyx007.filemanage.bean.OSS;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.ClientConfig;
+import com.qcloud.cos.auth.BasicCOSCredentials;
+import com.qcloud.cos.auth.COSCredentials;
+import com.qcloud.cos.http.HttpProtocol;
+import com.qcloud.cos.model.*;
+import com.qcloud.cos.region.Region;
+import com.qcloud.cos.transfer.TransferManager;
+import com.qcloud.cos.transfer.TransferManagerConfiguration;
+import com.qcloud.cos.transfer.Upload;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@Slf4j
+public class COSUtil {
+// private static COSClient cosClient = null;
+// private static TransferManager transferManager = null;
+ private static String secretId;
+ private static String secretKey;
+ public static String regionName;
+ public static String bucketName;
+ public static String customUrl;
+
+ public static void init(OSS oss) {
+ JSONObject config = JSON.parseObject(oss.getConfig());
+ secretId = (String) config.get("secretId");
+ secretKey = (String) config.get("secretKey");
+ regionName = (String) config.get("regionName");
+ bucketName = (String) config.get("bucketName");
+ if(config.get("customUrl") == null)
+ customUrl = "https://"+COSUtil.bucketName + ".cos." + COSUtil.regionName + ".myqcloud.com/";
+ else customUrl = "https://"+config.get("customUrl")+"/";
+ }
+
+ // 创建 COSClient 实例,这个实例用来后续调用请求
+ public static COSClient createCOSClient() {
+ // 设置用户身份信息。
+ COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
+ // ClientConfig 中包含了后续请求 COS 的客户端设置:
+ ClientConfig clientConfig = new ClientConfig();
+
+ // 设置 bucket 的地域
+ // COS_REGION 请参见 https://cloud.tencent.com/document/product/436/6224
+ clientConfig.setRegion(new Region(regionName));
+
+ // 设置请求协议, http 或者 https
+ // 5.6.53 及更低的版本,建议设置使用 https 协议
+ // 5.6.54 及更高版本,默认使用了 https
+ clientConfig.setHttpProtocol(HttpProtocol.https);
+
+ // 生成 cos 客户端。
+ return new COSClient(cred, clientConfig);
+ }
+
+ // 创建 TransferManager 实例,这个实例用来后续调用高级接口
+ public static TransferManager createTransferManager() {
+ // 创建一个 COSClient 实例,这是访问 COS 服务的基础实例。
+ // 详细代码参见本页: 简单操作 -> 创建 COSClient
+ COSClient cosClient = createCOSClient();
+
+ // 自定义线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM,同地域上传 COS)的情况下,设置成16或32即可,可较充分的利用网络资源
+ // 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。
+ ExecutorService threadPool = Executors.newFixedThreadPool(16);
+
+ // 传入一个 threadpool, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。
+ TransferManager transferManager = new TransferManager(cosClient, threadPool);
+
+ // 设置高级接口的配置项
+ // 分块上传阈值和分块大小分别为 5MB 和 1MB
+ TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
+ transferManagerConfiguration.setMultipartUploadThreshold(5*1024*1024);
+ transferManagerConfiguration.setMinimumUploadPartSize(1*1024*1024);
+ transferManager.setConfiguration(transferManagerConfiguration);
+
+ return transferManager;
+ }
+
+ public static void uploadFile(MultipartFile multipartFile, Files file) throws Exception {
+ // 使用高级接口必须先保证本进程存在一个 TransferManager 实例,如果没有则创建
+ // 详细代码参见本页:高级接口 -> 创建 TransferManager
+ TransferManager transferManager = createTransferManager();
+
+ ObjectMetadata objectMetadata = new ObjectMetadata();
+ // 上传的流如果能够获取准确的流长度,则推荐一定填写 content-length
+ // 如果确实没办法获取到,则下面这行可以省略,但同时高级接口也没办法使用分块上传了
+ objectMetadata.setContentLength(multipartFile.getSize());
+
+ // 对象键(Key)是对象在存储桶中的唯一标识。
+ String key = BaseContext.getCurrentId()+"/"+file.getStoreName();
+
+ try(InputStream inputStream = multipartFile.getInputStream()) {
+ PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, inputStream, objectMetadata);
+
+ // 设置存储类型(如有需要,不需要请忽略此行代码), 默认是标准(Standard), 低频(standard_ia)
+ // 更多存储类型请参见 https://cloud.tencent.com/document/product/436/33417
+ putObjectRequest.setStorageClass(StorageClass.Standard);
+ // 高级接口会返回一个异步结果Upload
+ // 可同步地调用 waitForUploadResult 方法等待上传完成,成功返回 UploadResult, 失败抛出异常
+ Upload upload = transferManager.upload(putObjectRequest);
+ upload.waitForUploadResult();
+ }
+ shutdownTransferManager(transferManager);
+ }
+
+ public static void shutdownTransferManager(TransferManager transferManager) {
+ if(transferManager != null) {
+ // 指定参数为 false, 则不会关闭 transferManager 内部的 COSClient 实例。
+ transferManager.shutdownNow(true);
+ }
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/utils/EncryptUtils.java b/src/main/java/cn/czyx007/filemanage/utils/EncryptUtils.java
new file mode 100644
index 0000000..b8014ad
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/utils/EncryptUtils.java
@@ -0,0 +1,31 @@
+package cn.czyx007.filemanage.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+@Slf4j
+public class EncryptUtils {
+ public static String hashPassword(String password) {
+ try {
+ // 创建 MessageDigest 实例并指定算法为 SHA-1
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ // 将密码转换为字节数组
+ byte[] passwordBytes = password.getBytes();
+ // 使用 MessageDigest 更新字节数组
+ byte[] hashedBytes = md.digest(passwordBytes);
+
+ // 将字节数组转换为十六进制字符串
+ StringBuilder sb = new StringBuilder();
+ for (byte b : hashedBytes) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException e) {
+ // 处理算法不存在异常
+ log.error("密码加密失败:", e);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/utils/Result.java b/src/main/java/cn/czyx007/filemanage/utils/Result.java
new file mode 100644
index 0000000..68d80c8
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/utils/Result.java
@@ -0,0 +1,36 @@
+package cn.czyx007.filemanage.utils;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 通用返回结果,服务端响应的数据最终都会封装成此对象
+ * @param
+ */
+@Data
+public class Result implements Serializable {
+ private Integer code; //编码:1成功,0和其它数字为失败
+ private String msg; //错误信息
+ private T data; //数据
+ private Map map = new HashMap(); //动态数据
+
+ public static Result success(T object) {
+ Result result = new Result();
+ result.data = object;
+ result.code = 1;
+ return result;
+ }
+ public static Result error(String msg) {
+ Result result = new Result();
+ result.msg = msg;
+ result.code = 0;
+ return result;
+ }
+ public Result add(String key, Object value) {
+ this.map.put(key, value);
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cn/czyx007/filemanage/utils/SendEmailUtils.java b/src/main/java/cn/czyx007/filemanage/utils/SendEmailUtils.java
new file mode 100644
index 0000000..7c10613
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/utils/SendEmailUtils.java
@@ -0,0 +1,67 @@
+package cn.czyx007.filemanage.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author : 张宇轩
+ * @createTime : 2023/1/19 - 15:43
+ */
+@Component
+@Slf4j
+public class SendEmailUtils {
+ private static String hostName;
+ private static String userName;
+ private static String password;
+
+ @Value("${email.hostName}")
+ public void setHostName(String hostName) {
+ SendEmailUtils.hostName = hostName;
+ }
+
+ @Value("${email.userName}")
+ public void setUserName(String userName) {
+ SendEmailUtils.userName = userName;
+ }
+
+ @Value("${email.password}")
+ public void setPassword(String password) {
+ SendEmailUtils.password = password;
+ }
+
+
+ /**
+ * 发送验证码
+ * @param email 接收邮箱
+ * @param code 验证码
+ */
+ public static void sendAuthCodeEmail(String email,String code) throws EmailException {
+ HtmlEmail mail = new HtmlEmail();
+ /*发送邮件的服务器 126邮箱为smtp.126.com,163邮箱为163.smtp.com,QQ为smtp.qq.com*/
+ mail.setHostName(hostName);
+ mail.setSmtpPort(465);
+
+ // 使用 SSL 连接
+ mail.setSSLOnConnect(true);
+
+ /*不设置发送的消息有可能是乱码*/
+ mail.setCharset("UTF-8");
+ /*IMAP/SMTP服务的密码 username为你开启发送验证码功能的邮箱号 password为你在qq邮箱获取到的一串字符串*/
+ mail.setAuthentication(userName, password);
+ /*发送邮件的邮箱和发件人*/
+ mail.setFrom(userName, "文件管理系统");
+ /*使用安全链接*/
+ mail.setSSLOnConnect(true);
+ /*接收的邮箱*/
+ mail.addTo(email);
+ /*设置邮件的主题*/
+ mail.setSubject("登录验证码");
+ /*设置邮件的内容*/
+ mail.setMsg("尊敬的用户:你好! 登录验证码为:" + code + "(有效期为5分钟)");
+ mail.send();//发送
+ log.info("邮件发送成功");
+ }
+}
diff --git a/src/main/java/cn/czyx007/filemanage/utils/ValidateCodeUtils.java b/src/main/java/cn/czyx007/filemanage/utils/ValidateCodeUtils.java
new file mode 100644
index 0000000..1932045
--- /dev/null
+++ b/src/main/java/cn/czyx007/filemanage/utils/ValidateCodeUtils.java
@@ -0,0 +1,43 @@
+package cn.czyx007.filemanage.utils;
+
+import java.util.Random;
+
+/**
+ * 随机生成验证码工具类
+ */
+public class ValidateCodeUtils {
+ /**
+ * 随机生成验证码
+ * @param length 长度为4位或者6位
+ * @return
+ */
+ public static Integer generateValidateCode(int length){
+ Integer code =null;
+ if(length == 4){
+ code = new Random().nextInt(9999);//生成随机数,最大为9999
+ if(code < 1000){
+ code = code + 1000;//保证随机数为4位数字
+ }
+ }else if(length == 6){
+ code = new Random().nextInt(999999);//生成随机数,最大为999999
+ if(code < 100000){
+ code = code + 100000;//保证随机数为6位数字
+ }
+ }else{
+ throw new RuntimeException("只能生成4位或6位数字验证码");
+ }
+ return code;
+ }
+
+ /**
+ * 随机生成指定长度字符串验证码
+ * @param length 长度
+ * @return
+ */
+ public static String generateValidateCode4String(int length){
+ Random rdm = new Random();
+ String hash1 = Integer.toHexString(rdm.nextInt());
+ String capstr = hash1.substring(0, length);
+ return capstr;
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..6fe0a7b
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,55 @@
+server:
+ port: 8080
+
+mybatis-plus:
+ mapper-locations: classpath*:/mapper/**/*.xml
+ global-config:
+ db-config:
+ id-type: ASSIGN_ID
+ configuration:
+ map-underscore-to-camel-case: true
+# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+oss:
+ uploadPath: '/files'
+
+#用于发送邮箱验证码的账户和密码
+email:
+ #发送邮件的服务器域名
+ #126邮箱为smtp.126.com,163邮箱为163.smtp.com,QQ个人邮箱为smtp.qq.com,腾讯企业邮为smtp.exmail.qq.com
+ hostName: smtp.qq.com
+ userName: xxx@qq.com
+ password: xxx
+
+spring:
+ jackson:
+ serialization:
+ fail-on-empty-beans: false
+ servlet:
+ multipart:
+ max-file-size: -1
+ max-request-size: -1
+ redis:
+ host: localhost
+ port: 6379
+ database: 0
+ datasource:
+ druid:
+ #MySQL
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/fileManage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
+ username: xxx
+ password: xxx
+ #初始化连接数
+ initial-size: 1
+ #最小空闲连接
+ min-idle: 1
+ #最大活动连接
+ max-active: 20
+ #获取连接时测试是否可用
+ test-on-borrow: true
+ #监控页面启动
+ filter:
+ wall:
+ config:
+ start-transaction-allow: true
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000..747fa10
--- /dev/null
+++ b/src/main/resources/logback-spring.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/cn/czyx007/filemanage/ApplicationTests.java b/src/test/java/cn/czyx007/filemanage/ApplicationTests.java
new file mode 100644
index 0000000..eb9d623
--- /dev/null
+++ b/src/test/java/cn/czyx007/filemanage/ApplicationTests.java
@@ -0,0 +1,12 @@
+package cn.czyx007.filemanage;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ApplicationTests {
+ @Test
+ void contextLoads() {
+ }
+
+}