项目完成

This commit is contained in:
zyx 2024-04-26 18:54:15 +08:00
commit d44ffceaab
37 changed files with 1913 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@ -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/

15
README.md Normal file
View File

@ -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

116
pom.xml Normal file
View File

@ -0,0 +1,116 @@
<?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>cn.czyx007</groupId>
<artifactId>fileManage</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>fileManage-backend</name>
<description>fileManage-backend</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.6</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.47</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.205</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>cn.czyx007.filemanage.Application</mainClass>
<skip>false</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

53
sql/fileManage.sql Normal file
View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,12 @@
package cn.czyx007.filemanage.common;
public class CustomException extends RuntimeException{
public CustomException() {
super();
}
public CustomException(String message) {
super(message);
}
}

View File

@ -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<String> exceptionHandler(CustomException e){
log.error(e.toString());
return Result.error(e.getMessage());
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<String> list = Arrays.asList("/user/sendVerCode", "/user/captcha", "/user/login", "/user/registry");
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns(list);
}
}

View File

@ -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<IPage<Files>> 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<Files> iPage = new Page<>(page, pageSize);
LambdaQueryWrapper<Files> 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<Files> 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<String> 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<String> 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<Resource> 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<String> 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<String> 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<String> batchUpdateIsDelete(@PathVariable("isDelete") Integer isDelete, @RequestBody List<String> 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<Files> 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<String> batchDelete(@RequestBody List<String> ids){
//清除该用户的文件列表缓存
Set keys = redisTemplate.keys("fileCache::" + BaseContext.getCurrentId() + "*");
if(keys != null)
redisTemplate.delete(keys);
List<Files> 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("文件已被彻底删除");
}
}

View File

@ -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<OSS>> list(){
if(userService.getById(BaseContext.getCurrentId()).getIsAdmin() == 0)
return Result.error("无权限");
List<OSS> list = ossService.list();
Map<String, String> idToName = new HashMap<>();
// 获取所有管理员用户并将其ID与用户名存入idToName映射中
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getIsAdmin, 1);
List<User> 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<String> addConfig(@RequestBody Map<String,String> 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<String> updateConfig(@RequestBody Map<String, String> 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<String> 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<OSS> 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<String> deleteConfig(@PathVariable("id") String id){
if(userService.getById(BaseContext.getCurrentId()).getIsAdmin() == 0)
return Result.error("无权限");
LambdaQueryWrapper<Files> lqw = new LambdaQueryWrapper<>();
lqw.eq(Files::getOssId, id);
if(filesService.exists(lqw)){
return Result.error("该存储策略下存在文件,无法删除");
}
ossService.removeById(id);
return Result.success("存储配置删除成功");
}
}

View File

@ -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<String> sendVerCode(@RequestBody Map<String,String> map){
//生成6位随机数字验证码
String code = ValidateCodeUtils.generateValidateCode(6).toString();
log.info("验证码:{}", code);
//发送短信让用户接受验证码
try {
String email = map.get("email");
SendEmailUtils.sendAuthCodeEmail(email, code);
//把验证码保存到redis5分钟有效
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<String> 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<String, String> 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<String> 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<User> 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<String> registry(@RequestBody UserDto user){
LambdaQueryWrapper<User> 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<String> 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<User> 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<String> updatePassword(@RequestBody Map<String,String> 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<String> updateUsername(@RequestBody User user){
LambdaQueryWrapper<User> 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("用户名修改成功");
}
}

View File

@ -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() + '\'' +
"} ";
}
}

View File

@ -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;
}
}

View File

@ -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<Files> {
}

View File

@ -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<OSS> {
}

View File

@ -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<User> {
}

View File

@ -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<Files> {
/**
* 查询id对应的未被删除的文件
* @param id 文件唯一id
* @return 文件对象Files
*/
Files getFileById(String id);
}

View File

@ -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> {
OSS getActive();
}

View File

@ -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<User> {
/**
* 更新用户已使用存储空间
* @param updateSize 变化的存储空间大小
* @param isAdd 是否增加
* @return true:更新成功;false:更新失败(用户剩余存储空间不足)
*/
boolean updateStorageUsed(long updateSize, boolean isAdd);
}

View File

@ -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<FilesMapper, Files> implements FilesService {
@Override
public Files getFileById(String id){
LambdaQueryWrapper<Files> lqw = new LambdaQueryWrapper<>();
lqw.eq(Files::getId, id).eq(Files::getIsDelete, 0);
return this.getOne(lqw);
}
}

View File

@ -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<OSSMapper, OSS> implements OSSService {
@Override
public OSS getActive(){
return getOne(new LambdaQueryWrapper<OSS>().eq(OSS::getIsActive, true));
}
}

View File

@ -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<UserMapper, User> 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;
}
}

View File

@ -0,0 +1,22 @@
package cn.czyx007.filemanage.utils;
/**
* 基于ThreadLocal封装工具类用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(String id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static String getCurrentId(){
return threadLocal.get();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 <T>
*/
@Data
public class Result<T> implements Serializable {
private Integer code; //编码1成功0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map map = new HashMap(); //动态数据
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
public Result<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}

View File

@ -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.comQQ为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("邮件发送成功");
}
}

View File

@ -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;
}
}

View File

@ -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.comQQ个人邮箱为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

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<logger name="org.springframework.web" level="INFO"/>
<logger name="org.springboot.sample" level="TRACE" />
<!-- 开发、测试环境 -->
<springProfile name="dev,test">
<logger name="org.springframework.web" level="INFO"/>
<logger name="org.springboot.sample" level="INFO" />
<logger name="cn.czyx007" level="DEBUG" />
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<logger name="org.springframework.web" level="ERROR"/>
<logger name="org.springboot.sample" level="ERROR" />
<logger name="cn.czyx007" level="ERROR" />
</springProfile>
</configuration>

View File

@ -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() {
}
}