From 5ce5332891c93b432ce7197270b636b6b7dcde30 Mon Sep 17 00:00:00 2001 From: wzp Date: Fri, 1 Nov 2024 11:12:29 +0800 Subject: [PATCH] feat: adding controllers and base --- build.gradle.kts | 3 +- .../filemanager/FileManagerApplication.java | 13 ++ .../annotation/AuthorizationRequired.java | 12 ++ .../commands/AuthorizationCommands.java | 19 ++ .../AuthorizationConfiguration.java | 34 ++++ .../configuration/RedisConfiguration.java | 31 ++++ .../controller/FileController.java | 40 +++++ .../controller/GlobalExceptionHandler.java | 16 ++ .../controller/UserController.java | 31 +++- .../cn/wzpmc/filemanager/entities/Page.java | 13 ++ .../cn/wzpmc/filemanager/entities/Result.java | 105 +++++++++++ .../entities/files/CheckChunkResponse.java | 18 ++ .../entities/files/ChunkChecked.java | 14 ++ .../entities/files/ChunkReady.java | 11 ++ .../entities/files/DoneUploadRequest.java | 8 + .../entities/files/PrepareUploadRequest.java | 12 ++ .../entities/user/UserAuthResponse.java | 8 - .../entities/user/UserLoginRequest.java | 9 + .../entities/user/UserRegisterRequest.java | 12 ++ .../filemanager/entities/user/enums/Auth.java | 9 +- .../filemanager/entities/vo/ChunkVo.java | 14 +- .../filemanager/entities/vo/FileChunkVo.java | 12 +- .../wzpmc/filemanager/entities/vo/FileVo.java | 24 ++- .../filemanager/entities/vo/FolderVo.java | 18 +- .../filemanager/entities/vo/StatisticsVo.java | 13 +- .../wzpmc/filemanager/entities/vo/UserVo.java | 23 ++- .../exceptions/AuthorizationException.java | 13 ++ .../wzpmc/filemanager/mapper/ChunkMapper.java | 7 + .../filemanager/mapper/FileChunksMapper.java | 7 + .../wzpmc/filemanager/mapper/FileMapper.java | 7 + .../filemanager/mapper/FolderMapper.java | 7 + .../wzpmc/filemanager/mapper/UserMapper.java | 7 + .../filemanager/service/FileService.java | 166 ++++++++++++++++++ .../filemanager/service/UserService.java | 96 ++++++++++ .../utils/AuthorizationArgumentResolver.java | 35 ++++ .../AuthorizationHandlerInterceptor.java | 33 ++++ .../filemanager/utils/AuthorizationUtils.java | 62 +++++++ .../cn/wzpmc/filemanager/utils/JwtUtils.java | 56 ++++++ .../wzpmc/filemanager/utils/RandomUtils.java | 19 ++ src/main/resources/application.yaml | 7 +- 40 files changed, 1022 insertions(+), 22 deletions(-) create mode 100644 src/main/java/cn/wzpmc/filemanager/annotation/AuthorizationRequired.java create mode 100644 src/main/java/cn/wzpmc/filemanager/commands/AuthorizationCommands.java create mode 100644 src/main/java/cn/wzpmc/filemanager/configuration/AuthorizationConfiguration.java create mode 100644 src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java create mode 100644 src/main/java/cn/wzpmc/filemanager/controller/FileController.java create mode 100644 src/main/java/cn/wzpmc/filemanager/controller/GlobalExceptionHandler.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/Page.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/Result.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/ChunkChecked.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/ChunkReady.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/PrepareUploadRequest.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/user/UserAuthResponse.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/user/UserLoginRequest.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/user/UserRegisterRequest.java create mode 100644 src/main/java/cn/wzpmc/filemanager/exceptions/AuthorizationException.java create mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java create mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/FileChunksMapper.java create mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/FileMapper.java create mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/FolderMapper.java create mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/UserMapper.java create mode 100644 src/main/java/cn/wzpmc/filemanager/service/FileService.java create mode 100644 src/main/java/cn/wzpmc/filemanager/service/UserService.java create mode 100644 src/main/java/cn/wzpmc/filemanager/utils/AuthorizationArgumentResolver.java create mode 100644 src/main/java/cn/wzpmc/filemanager/utils/AuthorizationHandlerInterceptor.java create mode 100644 src/main/java/cn/wzpmc/filemanager/utils/AuthorizationUtils.java create mode 100644 src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java create mode 100644 src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java diff --git a/build.gradle.kts b/build.gradle.kts index d718094..3a7ef08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation("org.springframework.shell:spring-shell-starter") // https://mvnrepository.com/artifact/com.mybatis-flex/mybatis-flex-spring-boot3-starter implementation("com.mybatis-flex:mybatis-flex-spring-boot3-starter:1.9.7") + annotationProcessor("com.mybatis-flex:mybatis-flex-processor:1.9.7") // https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 implementation("com.alibaba.fastjson2:fastjson2:2.0.53") // https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2-extension @@ -55,7 +56,7 @@ dependencies { // https://mvnrepository.com/artifact/com.mysql/mysql-connector-j implementation("com.mysql:mysql-connector-j:9.0.0") compileOnly("org.projectlombok:lombok") - developmentOnly("org.springframework.boot:spring-boot-devtools") + /*developmentOnly("org.springframework.boot:spring-boot-devtools")*/ annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc") diff --git a/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java b/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java index 42ae3c3..4b13d8f 100644 --- a/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java +++ b/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java @@ -1,14 +1,27 @@ package cn.wzpmc.filemanager; +import com.mybatisflex.core.audit.AuditManager; +import com.mybatisflex.core.audit.ConsoleMessageCollector; +import com.mybatisflex.core.audit.MessageCollector; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.shell.command.annotation.EnableCommand; +import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication @ConfigurationPropertiesScan(basePackages = {"cn.wzpmc.filemanager.config"}) +@MapperScan("cn.wzpmc.filemanager.mapper") +@EnableTransactionManagement +@EnableCommand public class FileManagerApplication { public static void main(String[] args) { + //开启审计功能 + AuditManager.setAuditEnable(true); + MessageCollector collector = new ConsoleMessageCollector(); + AuditManager.setMessageCollector(collector); SpringApplication.run(FileManagerApplication.class, args); } diff --git a/src/main/java/cn/wzpmc/filemanager/annotation/AuthorizationRequired.java b/src/main/java/cn/wzpmc/filemanager/annotation/AuthorizationRequired.java new file mode 100644 index 0000000..bbee749 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/annotation/AuthorizationRequired.java @@ -0,0 +1,12 @@ +package cn.wzpmc.filemanager.annotation; + +import cn.wzpmc.filemanager.entities.user.enums.Auth; + +import java.lang.annotation.*; + +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorizationRequired { + Auth level() default Auth.user; + boolean force() default false; +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/commands/AuthorizationCommands.java b/src/main/java/cn/wzpmc/filemanager/commands/AuthorizationCommands.java new file mode 100644 index 0000000..5947f63 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/commands/AuthorizationCommands.java @@ -0,0 +1,19 @@ +package cn.wzpmc.filemanager.commands; + +import cn.wzpmc.filemanager.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.shell.standard.ShellComponent; +import org.springframework.shell.standard.ShellMethod; + +@ShellComponent +public class AuthorizationCommands { + private final UserService userService; + @Autowired + public AuthorizationCommands(UserService userService) { + this.userService = userService; + } + @ShellMethod("创建一个密钥") + public void key(){ + this.userService.genInviteCode(); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/configuration/AuthorizationConfiguration.java b/src/main/java/cn/wzpmc/filemanager/configuration/AuthorizationConfiguration.java new file mode 100644 index 0000000..88a91ba --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/configuration/AuthorizationConfiguration.java @@ -0,0 +1,34 @@ +package cn.wzpmc.filemanager.configuration; + +import cn.wzpmc.filemanager.utils.AuthorizationArgumentResolver; +import cn.wzpmc.filemanager.utils.AuthorizationHandlerInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class AuthorizationConfiguration implements WebMvcConfigurer { + private final AuthorizationArgumentResolver authorizationArgumentResolver; + private final AuthorizationHandlerInterceptor authorizationHandlerInterceptor; + @Autowired + public AuthorizationConfiguration(AuthorizationArgumentResolver authorizationArgumentResolver, AuthorizationHandlerInterceptor authorizationHandlerInterceptor) { + this.authorizationArgumentResolver = authorizationArgumentResolver; + this.authorizationHandlerInterceptor = authorizationHandlerInterceptor; + } + @Override + public void addArgumentResolvers(@NonNull List resolvers) { + WebMvcConfigurer.super.addArgumentResolvers(resolvers); + resolvers.add(authorizationArgumentResolver); + } + + @Override + public void addInterceptors(@NonNull InterceptorRegistry registry) { + WebMvcConfigurer.super.addInterceptors(registry); + registry.addInterceptor(authorizationHandlerInterceptor); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java b/src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java new file mode 100644 index 0000000..0928a54 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java @@ -0,0 +1,31 @@ +package cn.wzpmc.filemanager.configuration; + +import cn.wzpmc.filemanager.entities.files.ChunkChecked; +import cn.wzpmc.filemanager.entities.files.ChunkReady; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +public class RedisConfiguration { + private final RedisConnectionFactory redisConnectionFactory; + + @Autowired + public RedisConfiguration(RedisConnectionFactory redisConnectionFactory) { + this.redisConnectionFactory = redisConnectionFactory; + } + @Bean + public RedisTemplate uploadMapper() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + @Bean + public RedisTemplate chunkUploadMapper() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + return template; + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/controller/FileController.java b/src/main/java/cn/wzpmc/filemanager/controller/FileController.java new file mode 100644 index 0000000..11bdbac --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/controller/FileController.java @@ -0,0 +1,40 @@ +package cn.wzpmc.filemanager.controller; + +import cn.wzpmc.filemanager.annotation.AuthorizationRequired; +import cn.wzpmc.filemanager.entities.Result; +import cn.wzpmc.filemanager.entities.files.CheckChunkResponse; +import cn.wzpmc.filemanager.entities.files.DoneUploadRequest; +import cn.wzpmc.filemanager.entities.files.FileObject; +import cn.wzpmc.filemanager.entities.files.PrepareUploadRequest; +import cn.wzpmc.filemanager.entities.vo.UserVo; +import cn.wzpmc.filemanager.service.FileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/file") +public class FileController { + private final FileService fileService; + + @Autowired + public FileController(FileService fileService) { + this.fileService = fileService; + } + @PostMapping("/prepare") + public Result prepareUploadChunks(@RequestBody PrepareUploadRequest prepareUploadRequest, @AuthorizationRequired UserVo user){ + return fileService.prepareUploadChunks(prepareUploadRequest, user); + } + @PutMapping("/chunk/upload") + public Result uploadChunk(MultipartFile file,@RequestParam String id){ + return fileService.uploadChunk(file, id); + } + @GetMapping("/chunk/check") + public Result checkChunk(@RequestParam String hash, @RequestParam String id, @RequestParam long index){ + return fileService.checkChunk(hash, id, index); + } + @PostMapping("/done") + public Result doneUpload(@RequestBody DoneUploadRequest data){ + return fileService.doneUpload(data.getId()); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/controller/GlobalExceptionHandler.java b/src/main/java/cn/wzpmc/filemanager/controller/GlobalExceptionHandler.java new file mode 100644 index 0000000..4c35a14 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/controller/GlobalExceptionHandler.java @@ -0,0 +1,16 @@ +package cn.wzpmc.filemanager.controller; + +import cn.wzpmc.filemanager.entities.Result; +import cn.wzpmc.filemanager.exceptions.AuthorizationException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(AuthorizationException.class) + public ResponseEntity> handleAuthorizationException(AuthorizationException e) { + Result result = e.getResult(); + return ResponseEntity.ok(result); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/controller/UserController.java b/src/main/java/cn/wzpmc/filemanager/controller/UserController.java index becaee6..a06678a 100644 --- a/src/main/java/cn/wzpmc/filemanager/controller/UserController.java +++ b/src/main/java/cn/wzpmc/filemanager/controller/UserController.java @@ -1,12 +1,35 @@ package cn.wzpmc.filemanager.controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import cn.wzpmc.filemanager.annotation.AuthorizationRequired; +import cn.wzpmc.filemanager.entities.Result; +import cn.wzpmc.filemanager.entities.user.UserLoginRequest; +import cn.wzpmc.filemanager.entities.user.UserRegisterRequest; +import cn.wzpmc.filemanager.entities.user.enums.Auth; +import cn.wzpmc.filemanager.entities.vo.UserVo; +import cn.wzpmc.filemanager.service.UserService; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/user") public class UserController { + private final UserService userService; + @Autowired + public UserController(UserService userService) { + this.userService= userService; + } @PostMapping("/login") - public + public void login(@RequestBody UserLoginRequest loginRequest, HttpServletResponse response) { + userService.login(loginRequest, response); + } + @PutMapping("/register") + public void register(@RequestBody UserRegisterRequest registerRequest, HttpServletResponse response) { + userService.register(registerRequest, response); + } + @GetMapping("/invite") + @AuthorizationRequired(level = Auth.admin) + public Result invite(){ + return userService.invite(); + } } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/Page.java b/src/main/java/cn/wzpmc/filemanager/entities/Page.java new file mode 100644 index 0000000..2b7d71b --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/Page.java @@ -0,0 +1,13 @@ +package cn.wzpmc.filemanager.entities; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +@Data +@AllArgsConstructor +public class Page { + private int total; + private List data; +} diff --git a/src/main/java/cn/wzpmc/filemanager/entities/Result.java b/src/main/java/cn/wzpmc/filemanager/entities/Result.java new file mode 100644 index 0000000..b55c04c --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/Result.java @@ -0,0 +1,105 @@ +package cn.wzpmc.filemanager.entities; + +import com.alibaba.fastjson2.JSON; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +@Slf4j +@Data +public class Result { + private int status; + private String msg; + private T data; + private long timestamp; + protected Result() { + this.timestamp = System.currentTimeMillis(); + } + protected Result(int status, String msg, T data) { + this(); + this.status = status; + this.msg = msg; + this.data = data; + } + protected Result(HttpStatus status) { + this(); + this.status = status.value(); + this.msg = status.getReasonPhrase(); + } + protected Result(HttpStatus status, String msg) { + this(status); + this.msg = msg; + } + protected Result(HttpStatus status, T data) { + this(status); + this.data = data; + } + protected Result(HttpStatus status, String msg, T data) { + this(status, msg); + this.data = data; + } + public static Result success() { + return new Result<>(HttpStatus.OK); + } + public static Result success(String msg) { + return new Result<>(HttpStatus.OK, msg); + } + public static Result success(T data) { + return new Result<>(HttpStatus.OK, data); + } + public static Result success(String msg, T data) { + return new Result<>(HttpStatus.OK, msg, data); + } + public static Result failed() { + return new Result<>(HttpStatus.FORBIDDEN); + } + public static Result failed(String msg) { + return new Result<>(HttpStatus.FORBIDDEN, msg); + } + public static Result failed(HttpStatus status) { + return new Result<>(status); + } + public static Result failed(HttpStatus status, String msg) { + return new Result<>(status, msg); + } + public static Result create() { + return new Result<>(); + } + public Result status(int status) { + this.status = status; + return this; + } + public Result status(HttpStatus status) { + this.status = status.value(); + this.msg = status.getReasonPhrase(); + return this; + } + public Result msg(String msg) { + this.msg = msg; + return this; + } + public Result data(T data) { + this.data = data; + return this; + } + + public void writeToResponse(HttpServletResponse response){ + try(ServletOutputStream outputStream = response.getOutputStream()){ + writeToOutputStream(outputStream); + } catch (IOException e) { + log.trace("写出到流失败,", e); + } + } + public void writeToOutputStream(OutputStream stream) throws IOException { + stream.write(JSON.toJSONString(this).getBytes(StandardCharsets.UTF_8)); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java b/src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java new file mode 100644 index 0000000..2c2a7a8 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java @@ -0,0 +1,18 @@ +package cn.wzpmc.filemanager.entities.files; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class CheckChunkResponse { + private boolean has; + private String uploadCode; + public static CheckChunkResponse has() { + return new CheckChunkResponse(true, null); + } + public static CheckChunkResponse shouldUpload(String uploadCode) { + return new CheckChunkResponse(false, uploadCode); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkChecked.java b/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkChecked.java new file mode 100644 index 0000000..0c2059d --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkChecked.java @@ -0,0 +1,14 @@ +package cn.wzpmc.filemanager.entities.files; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +public class ChunkChecked implements Serializable { + private String fileId; + private String hash; + private long index; +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkReady.java b/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkReady.java new file mode 100644 index 0000000..4a3c205 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkReady.java @@ -0,0 +1,11 @@ +package cn.wzpmc.filemanager.entities.files; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class ChunkReady implements Serializable { + private long fileId; + private long length; +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java b/src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java new file mode 100644 index 0000000..f0021b4 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java @@ -0,0 +1,8 @@ +package cn.wzpmc.filemanager.entities.files; + +import lombok.Data; + +@Data +public class DoneUploadRequest { + private String id; +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/PrepareUploadRequest.java b/src/main/java/cn/wzpmc/filemanager/entities/files/PrepareUploadRequest.java new file mode 100644 index 0000000..22a7cd2 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/PrepareUploadRequest.java @@ -0,0 +1,12 @@ +package cn.wzpmc.filemanager.entities.files; + +import lombok.Data; + +@Data +public class PrepareUploadRequest { + private String name; + private String ext; + private Long size; + private int folder; + private String fullSha1; +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/user/UserAuthResponse.java b/src/main/java/cn/wzpmc/filemanager/entities/user/UserAuthResponse.java deleted file mode 100644 index 38a06a6..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/user/UserAuthResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.wzpmc.filemanager.entities.user; - -import lombok.Data; - -@Data -public class UserAuthResponse { - private String -} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/user/UserLoginRequest.java b/src/main/java/cn/wzpmc/filemanager/entities/user/UserLoginRequest.java new file mode 100644 index 0000000..0ba5bdc --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/user/UserLoginRequest.java @@ -0,0 +1,9 @@ +package cn.wzpmc.filemanager.entities.user; + +import lombok.Data; + +@Data +public class UserLoginRequest { + private String username; + private String password; +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/user/UserRegisterRequest.java b/src/main/java/cn/wzpmc/filemanager/entities/user/UserRegisterRequest.java new file mode 100644 index 0000000..a8b9cc4 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/user/UserRegisterRequest.java @@ -0,0 +1,12 @@ +package cn.wzpmc.filemanager.entities.user; + +import cn.wzpmc.filemanager.entities.user.enums.Auth; +import lombok.Data; + +@Data +public class UserRegisterRequest { + private String username; + private String password; + private Auth auth; + private String inviteCode; +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/user/enums/Auth.java b/src/main/java/cn/wzpmc/filemanager/entities/user/enums/Auth.java index 16b5034..54a0c22 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/user/enums/Auth.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/user/enums/Auth.java @@ -1,5 +1,12 @@ package cn.wzpmc.filemanager.entities.user.enums; +import com.mybatisflex.annotation.EnumValue; +import lombok.AllArgsConstructor; + +@AllArgsConstructor public enum Auth { - ADMIN, USER + admin(1, "admin"), user(0, "user"); + public final int value; + @EnumValue + public final String name; } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/vo/ChunkVo.java b/src/main/java/cn/wzpmc/filemanager/entities/vo/ChunkVo.java index 080f56e..4f46481 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/ChunkVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/ChunkVo.java @@ -1,4 +1,16 @@ package cn.wzpmc.filemanager.entities.vo; -public record ChunkVo(int id, String sha1, long size) { +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import lombok.Data; + +@Table("chunk") +@Data +public class ChunkVo { + @Id(keyType = KeyType.Auto) + private int id; + private String sha1; + private long size; + } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/vo/FileChunkVo.java b/src/main/java/cn/wzpmc/filemanager/entities/vo/FileChunkVo.java index 1355468..6b4fbd1 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/FileChunkVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/FileChunkVo.java @@ -1,4 +1,14 @@ package cn.wzpmc.filemanager.entities.vo; -public record FileChunkVo(int file, int chunk, int index) { +import com.mybatisflex.annotation.Table; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Table("file_chunks") +@Data +@AllArgsConstructor +public class FileChunkVo { + private long file; + private long chunk; + private long index; } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/vo/FileVo.java b/src/main/java/cn/wzpmc/filemanager/entities/vo/FileVo.java index 27aa3e6..787bcf7 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/FileVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/FileVo.java @@ -1,6 +1,26 @@ package cn.wzpmc.filemanager.entities.vo; -import java.util.Date; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import lombok.Data; + +import java.util.Date; +import java.util.Objects; + +@Table("file") +@Data +public class FileVo { + @Id(keyType = KeyType.Auto) + private int id; + private String name; + private String ext; + private String mime; + private String sha1; + private int uploader; + private int folder; + @Column(onInsertValue = "now()") + private Date uploadTime; -public record FileVo(int id, String name, String ext, String mime, String sha1, int uploader, int folder, Date uploadTime) { } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/vo/FolderVo.java b/src/main/java/cn/wzpmc/filemanager/entities/vo/FolderVo.java index cdc59db..1e39141 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/FolderVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/FolderVo.java @@ -1,6 +1,22 @@ package cn.wzpmc.filemanager.entities.vo; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import lombok.Data; + import java.util.Date; -public record FolderVo(int id, String name, int parent, int creator, Date createTime) { +@Table("folder") +@Data +public class FolderVo { + @Id(keyType = KeyType.Auto) + private int id; + private String name; + private int parent; + private int creator; + @Column(onInsertValue = "now()") + private Date createTime; + } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/vo/StatisticsVo.java b/src/main/java/cn/wzpmc/filemanager/entities/vo/StatisticsVo.java index 21a8af5..eaff214 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/StatisticsVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/StatisticsVo.java @@ -1,8 +1,19 @@ package cn.wzpmc.filemanager.entities.vo; import cn.wzpmc.filemanager.entities.statistics.enums.Actions; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Table; +import lombok.Data; import java.util.Date; -public record StatisticsVo(int actor, Actions action, String params, Date time) { +@Table("statistics") +@Data +public class StatisticsVo { + private int actor; + private Actions action; + private String params; + @Column(onInsertValue = "now()") + private Date time; + } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/vo/UserVo.java b/src/main/java/cn/wzpmc/filemanager/entities/vo/UserVo.java index 1273225..e94c778 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/UserVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/UserVo.java @@ -1,6 +1,27 @@ package cn.wzpmc.filemanager.entities.vo; import cn.wzpmc.filemanager.entities.user.enums.Auth; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.KeyType; +import com.mybatisflex.annotation.Table; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Table("user") +@Data +public class UserVo { + @Id(keyType = KeyType.Auto) + private int id; + private String name; + private String password; + private Auth auth; + @Column(isLogicDelete = true, onInsertValue = "0") + private boolean banned; + public UserVo(String name, String password, Auth auth) { + this.name = name; + this.password = password; + this.auth = auth; + } -public record UserVo(int id, String name, String password, Auth auth, boolean banned) { } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/exceptions/AuthorizationException.java b/src/main/java/cn/wzpmc/filemanager/exceptions/AuthorizationException.java new file mode 100644 index 0000000..300a505 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/exceptions/AuthorizationException.java @@ -0,0 +1,13 @@ +package cn.wzpmc.filemanager.exceptions; + +import cn.wzpmc.filemanager.entities.Result; +import lombok.Getter; + +@Getter +public class AuthorizationException extends RuntimeException { + private final Result result; + public AuthorizationException(Result result) { + super(result.getMsg()); + this.result = result; + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java b/src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java new file mode 100644 index 0000000..efa646a --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java @@ -0,0 +1,7 @@ +package cn.wzpmc.filemanager.mapper; + +import cn.wzpmc.filemanager.entities.vo.ChunkVo; +import com.mybatisflex.core.BaseMapper; + +public interface ChunkMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/mapper/FileChunksMapper.java b/src/main/java/cn/wzpmc/filemanager/mapper/FileChunksMapper.java new file mode 100644 index 0000000..9fc4a60 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/mapper/FileChunksMapper.java @@ -0,0 +1,7 @@ +package cn.wzpmc.filemanager.mapper; + +import cn.wzpmc.filemanager.entities.vo.FileChunkVo; +import com.mybatisflex.core.BaseMapper; + +public interface FileChunksMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/mapper/FileMapper.java b/src/main/java/cn/wzpmc/filemanager/mapper/FileMapper.java new file mode 100644 index 0000000..e8eed0a --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/mapper/FileMapper.java @@ -0,0 +1,7 @@ +package cn.wzpmc.filemanager.mapper; + +import cn.wzpmc.filemanager.entities.vo.FileVo; +import com.mybatisflex.core.BaseMapper; + +public interface FileMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/mapper/FolderMapper.java b/src/main/java/cn/wzpmc/filemanager/mapper/FolderMapper.java new file mode 100644 index 0000000..d922d85 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/mapper/FolderMapper.java @@ -0,0 +1,7 @@ +package cn.wzpmc.filemanager.mapper; + +import cn.wzpmc.filemanager.entities.vo.FolderVo; +import com.mybatisflex.core.BaseMapper; + +public interface FolderMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/mapper/UserMapper.java b/src/main/java/cn/wzpmc/filemanager/mapper/UserMapper.java new file mode 100644 index 0000000..e988e00 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/mapper/UserMapper.java @@ -0,0 +1,7 @@ +package cn.wzpmc.filemanager.mapper; + +import cn.wzpmc.filemanager.entities.vo.UserVo; +import com.mybatisflex.core.BaseMapper; + +public interface UserMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/service/FileService.java b/src/main/java/cn/wzpmc/filemanager/service/FileService.java new file mode 100644 index 0000000..fc9949a --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/service/FileService.java @@ -0,0 +1,166 @@ +package cn.wzpmc.filemanager.service; + +import cn.wzpmc.filemanager.config.FileManagerProperties; +import cn.wzpmc.filemanager.entities.Result; +import cn.wzpmc.filemanager.entities.files.*; +import cn.wzpmc.filemanager.entities.vo.ChunkVo; +import cn.wzpmc.filemanager.entities.vo.FileChunkVo; +import cn.wzpmc.filemanager.entities.vo.FileVo; +import cn.wzpmc.filemanager.entities.vo.UserVo; +import cn.wzpmc.filemanager.mapper.ChunkMapper; +import cn.wzpmc.filemanager.mapper.FileChunksMapper; +import cn.wzpmc.filemanager.mapper.FileMapper; +import cn.wzpmc.filemanager.mapper.FolderMapper; +import cn.wzpmc.filemanager.utils.RandomUtils; +import com.mybatisflex.core.query.QueryWrapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StreamUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.List; + +import static cn.wzpmc.filemanager.entities.vo.table.ChunkVoTableDef.CHUNK_VO; +import static cn.wzpmc.filemanager.entities.vo.table.FileChunkVoTableDef.FILE_CHUNK_VO; +import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO; + +@Service +@RequiredArgsConstructor +public class FileService { + private final RedisTemplate uploadMapper; + private final RedisTemplate chunkUploadMapper; + private final FileMapper fileMapper; + private final ChunkMapper chunkMapper; + private final FileChunksMapper fileChunksMapper; + private final FolderMapper folderMapper; + private final RandomUtils randomUtils; + private final FileManagerProperties properties; + private static final String UPLOAD_FILE_PREPARE_HEAD = "UPLOAD_"; + private static final String CHUNK_PREPARE_HEAD = "UPLOAD_CHUNK_"; + public Result prepareUploadChunks(PrepareUploadRequest prepareUploadRequest, UserVo user) { + String name = prepareUploadRequest.getName(); + String ext = prepareUploadRequest.getExt(); + int folder = prepareUploadRequest.getFolder(); + if (this.fileMapper.selectCountByCondition(FILE_VO.NAME.eq(name).and(FILE_VO.EXT.eq(ext)).and(FILE_VO.FOLDER.eq(folder))) > 0) { + return Result.failed(HttpStatus.CONFLICT, "文件已存在!"); + } + String fullSha1 = prepareUploadRequest.getFullSha1(); + FileVo otherSameFile = this.fileMapper.selectOneByCondition(FILE_VO.SHA1.eq(fullSha1)); + ChunkReady chunkReady = new ChunkReady(); + FileVo fileVo = new FileVo(); + fileVo.setUploader(user.getId()); + fileVo.setName(name); + fileVo.setExt(ext); + fileVo.setFolder(folder); + fileVo.setSha1(fullSha1); + this.fileMapper.insert(fileVo); + int fid = fileVo.getId(); + if (otherSameFile != null) { + int id = otherSameFile.getId(); + List fileChunkVos = this.fileChunksMapper.selectListByCondition(FILE_CHUNK_VO.FILE.eq(id)); + for (FileChunkVo fileChunkVo : fileChunkVos) { + fileChunkVo.setFile(fid); + this.fileChunksMapper.insert(fileChunkVo); + } + return Result.failed(HttpStatus.FOUND, "后台存在相同文件,无需上传!"); + } + String uploadId = this.randomUtils.generatorRandomString(40); + chunkReady.setFileId(fid); + chunkReady.setLength(prepareUploadRequest.getSize()); + uploadMapper.opsForValue().set(UPLOAD_FILE_PREPARE_HEAD + uploadId, chunkReady); + return Result.success("成功", uploadId); + } + + @SneakyThrows + @Transactional + public Result uploadChunk(MultipartFile file, String id) { + ValueOperations chunkOps = chunkUploadMapper.opsForValue(); + ChunkChecked chunkData = chunkOps.getAndDelete(CHUNK_PREPARE_HEAD + id); + if (chunkData == null) { + return Result.failed(HttpStatus.NOT_FOUND, "未知的文件块"); + } + long size = file.getSize(); + if (size > 64 * 1024 * 1024) { + return Result.failed(HttpStatus.PAYLOAD_TOO_LARGE, "文件块不应大于64MB"); + } + byte[] bytes = file.getBytes(); + String s = DigestUtils.sha1Hex(bytes); + if (!s.equals(chunkData.getHash())) { + return Result.failed(HttpStatus.CONFLICT, "文件块内容错误!"); + } + ChunkVo chunkVo = new ChunkVo(); + chunkVo.setSize(size); + chunkVo.setSha1(s); + String hashHead = s.substring(0, 2); + File hashHeadFolder = new File(properties.getSavePath(), hashHead); + if (!hashHeadFolder.exists()) { + if (!hashHeadFolder.mkdirs()) { + return Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, "写入文件块出现错误,创建文件夹失败"); + } + } + File chunkFile = new File(hashHeadFolder, s); + if (!chunkFile.createNewFile()) { + return Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, "写入文件块出现错误,无法写入块文件"); + } + try(FileOutputStream fos = new FileOutputStream(chunkFile)) { + fos.write(bytes); + } + String fileId = chunkData.getFileId(); + ChunkReady chunkReady = uploadMapper.opsForValue().get(UPLOAD_FILE_PREPARE_HEAD + fileId); + assert chunkReady != null; + long fileTableId = chunkReady.getFileId(); + this.chunkMapper.insert(chunkVo); + int chunkId = chunkVo.getId(); + FileChunkVo fileChunkVo = new FileChunkVo(fileTableId, chunkId, chunkData.getIndex()); + this.fileChunksMapper.insert(fileChunkVo); + return Result.success("成功"); + } + + public Result checkChunk(String hash, String id, long index) { + ChunkReady chunkReady = uploadMapper.opsForValue().get(UPLOAD_FILE_PREPARE_HEAD + id); + if (chunkReady == null) { + return Result.failed(HttpStatus.NOT_FOUND, "未知的文件ID!"); + } + long fileId = chunkReady.getFileId(); + ChunkVo chunkVo = chunkMapper.selectOneByCondition(CHUNK_VO.SHA1.eq(hash)); + if (chunkVo != null) { + FileChunkVo fileChunkVo = new FileChunkVo(fileId, chunkVo.getId(), index); + this.fileChunksMapper.insert(fileChunkVo); + return Result.success(CheckChunkResponse.has()); + } + ValueOperations chunkOps = chunkUploadMapper.opsForValue(); + String chunkId = randomUtils.generatorRandomString(40); + chunkOps.set(CHUNK_PREPARE_HEAD + chunkId, new ChunkChecked(id, hash, index)); + return Result.success(CheckChunkResponse.shouldUpload(chunkId)); + } + + public Result doneUpload(String id) { + ChunkReady andDelete = uploadMapper.opsForValue().getAndDelete(UPLOAD_FILE_PREPARE_HEAD + id); + if (andDelete == null) { + return Result.failed(HttpStatus.NOT_FOUND, "未知的文件ID"); + } + long totalLength = andDelete.getLength(); + long l = calcFileSize(andDelete.getFileId()); + if (l != totalLength) { + fileMapper.deleteById(andDelete.getFileId()); + return Result.failed(HttpStatus.LENGTH_REQUIRED, "应收到" + totalLength + "字节,但只收到" + l + "字节!"); + } + return Result.success(); + } + private long calcFileSize(long id) { + List longs = this.fileChunksMapper.selectListByQueryAs(new QueryWrapper().select(CHUNK_VO.SIZE).from(FILE_CHUNK_VO).where(FILE_CHUNK_VO.FILE.eq(id)).leftJoin(CHUNK_VO).on(CHUNK_VO.ID.eq(FILE_CHUNK_VO.CHUNK)), Long.class); + long sum = 0L; + for (Long aLong : longs) { + sum += aLong; + } + return sum; + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/service/UserService.java b/src/main/java/cn/wzpmc/filemanager/service/UserService.java new file mode 100644 index 0000000..d10bd6f --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/service/UserService.java @@ -0,0 +1,96 @@ +package cn.wzpmc.filemanager.service; + +import cn.wzpmc.filemanager.entities.Result; +import cn.wzpmc.filemanager.entities.user.UserLoginRequest; +import cn.wzpmc.filemanager.entities.user.UserRegisterRequest; +import cn.wzpmc.filemanager.entities.user.enums.Auth; +import cn.wzpmc.filemanager.entities.vo.UserVo; +import cn.wzpmc.filemanager.mapper.UserMapper; +import cn.wzpmc.filemanager.utils.JwtUtils; +import cn.wzpmc.filemanager.utils.RandomUtils; +import com.mybatisflex.core.query.QueryCondition; +import com.mybatisflex.core.query.QueryWrapper; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +import static cn.wzpmc.filemanager.entities.vo.table.UserVoTableDef.USER_VO; + +@Slf4j +@Service +public class UserService { + private final UserMapper userMapper; + private final JwtUtils jwtUtils; + private final StringRedisTemplate authTemplate; + private final RandomUtils randomUtils; + @Autowired + public UserService(UserMapper userMapper, JwtUtils jwtUtils, StringRedisTemplate authTemplate, RandomUtils randomUtils) { + this.userMapper = userMapper; + this.jwtUtils = jwtUtils; + this.authTemplate = authTemplate; + this.randomUtils = randomUtils; + long count = this.userMapper.selectCountByQuery(new QueryWrapper()); + if (count == 0) { + String s = genInviteCode(); + log.info("生成了管理员密钥:{},有效期15分钟,若失效请使用控制台命令/key或重启后端重新生成!", s); + } + } + public void login(UserLoginRequest request, HttpServletResponse response) { + String username = request.getUsername(); + String password = request.getPassword(); + String sha1edPassword = DigestUtils.sha1Hex(password); + QueryCondition findUserCondition = USER_VO.NAME.eq(username).and(USER_VO.PASSWORD.eq(sha1edPassword)); + long count = this.userMapper.selectCountByCondition(findUserCondition); + if (count < 0) { + Result.failed(HttpStatus.UNAUTHORIZED, "账号或密码错误").writeToResponse(response); + return; + } + UserVo userVo = this.userMapper.selectOneByCondition(findUserCondition); + String token = this.jwtUtils.createToken(userVo.getId()); + response.addHeader("Add-Authorization", token); + Result.success("登录成功").writeToResponse(response); + } + public void register(UserRegisterRequest request, HttpServletResponse response) { + String username = request.getUsername(); + String password = request.getPassword(); + Auth auth = request.getAuth(); + if (this.userMapper.selectCountByCondition(USER_VO.NAME.eq(username)) > 0) { + Result.failed(HttpStatus.CONFLICT).msg("用户名已存在,若需要修改密码,请联系网站管理员处理").writeToResponse(response); + return; + } + if (auth.equals(Auth.admin)) { + String inviteCode = request.getInviteCode(); + ValueOperations ops = authTemplate.opsForValue(); + String andDelete = ops.getAndDelete(inviteCode); + if (andDelete == null) { + Result.failed(HttpStatus.NOT_FOUND, "过期或无效的邀请码").writeToResponse(response); + return; + } + } + UserVo userVo = new UserVo(username, password, auth); + this.userMapper.insert(userVo); + int id = userVo.getId(); + String token = this.jwtUtils.createToken(id); + response.addHeader("Add-Authorization", token); + Result.success("注册成功!").writeToResponse(response); + } + + public Result invite() { + String s = genInviteCode(); + return Result.success("生成了一个有效期15分钟的邀请码", s); + } + public String genInviteCode() { + ValueOperations ops = authTemplate.opsForValue(); + String s = this.randomUtils.generatorRandomString(8); + log.info("生成了新的邀请码:{}", s); + ops.set(s, "", 15, TimeUnit.MINUTES); + return s; + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationArgumentResolver.java b/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationArgumentResolver.java new file mode 100644 index 0000000..80f8873 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationArgumentResolver.java @@ -0,0 +1,35 @@ +package cn.wzpmc.filemanager.utils; + +import cn.wzpmc.filemanager.annotation.AuthorizationRequired; +import cn.wzpmc.filemanager.exceptions.AuthorizationException; +import jakarta.annotation.Nullable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class AuthorizationArgumentResolver implements HandlerMethodArgumentResolver { + private final AuthorizationUtils authorizationUtils; + @Autowired + public AuthorizationArgumentResolver(AuthorizationUtils authorizationUtils){ + this.authorizationUtils = authorizationUtils; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(AuthorizationRequired.class); + } + + @Override + @Nullable + public Object resolveArgument(@NonNull MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, @NonNull NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws AuthorizationException { + AuthorizationRequired parameterAnnotation = parameter.getParameterAnnotation(AuthorizationRequired.class); + assert parameterAnnotation != null; + return this.authorizationUtils.auth(webRequest, parameterAnnotation); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationHandlerInterceptor.java b/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationHandlerInterceptor.java new file mode 100644 index 0000000..e8880ab --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationHandlerInterceptor.java @@ -0,0 +1,33 @@ +package cn.wzpmc.filemanager.utils; + +import cn.wzpmc.filemanager.annotation.AuthorizationRequired; +import cn.wzpmc.filemanager.exceptions.AuthorizationException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.lang.reflect.Method; + +@Component +public class AuthorizationHandlerInterceptor implements HandlerInterceptor { + private final AuthorizationUtils authorizationUtils; + @Autowired + public AuthorizationHandlerInterceptor(AuthorizationUtils authorizationUtils) { + this.authorizationUtils = authorizationUtils; + } + @Override + public boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws AuthorizationException { + if (handler instanceof HandlerMethod method) { + if (!method.hasMethodAnnotation(AuthorizationRequired.class)) { + return true; + } + AuthorizationRequired annotation = method.getMethodAnnotation(AuthorizationRequired.class); + return authorizationUtils.auth(request, annotation); + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationUtils.java b/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationUtils.java new file mode 100644 index 0000000..ebb4159 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/utils/AuthorizationUtils.java @@ -0,0 +1,62 @@ +package cn.wzpmc.filemanager.utils; + +import cn.wzpmc.filemanager.annotation.AuthorizationRequired; +import cn.wzpmc.filemanager.entities.Result; +import cn.wzpmc.filemanager.entities.user.enums.Auth; +import cn.wzpmc.filemanager.entities.vo.UserVo; +import cn.wzpmc.filemanager.exceptions.AuthorizationException; +import cn.wzpmc.filemanager.mapper.UserMapper; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.WebRequest; + +import java.util.Optional; + +@Slf4j +@Component +public class AuthorizationUtils { + private final JwtUtils jwtUtils; + private final UserMapper userMapper; + @Autowired + public AuthorizationUtils(JwtUtils jwtUtils, UserMapper userMapper) { + this.jwtUtils = jwtUtils; + this.userMapper = userMapper; + } + private UserVo auth(String header, AuthorizationRequired authorizationRequired) throws AuthorizationException { + log.info("auth {} with token {}", authorizationRequired, header); + if (header == null) { + throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "未找到token")); + } + Auth level = authorizationRequired.level(); + Optional user = this.jwtUtils.getUser(header); + if (user.isEmpty()) { + throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "token错误或已过期")); + } + Integer i = user.get(); + UserVo userVo = this.userMapper.selectOneById(i); + if (userVo == null) { + throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "用户不存在")); + } + Auth auth = userVo.getAuth(); + if (authorizationRequired.force()) { + if (auth.value == level.value) { + return userVo; + } + }else { + if (auth.value >= level.value) { + return userVo; + } + } + throw new AuthorizationException(Result.failed(HttpStatus.UNAUTHORIZED, "权限不足")); + } + public UserVo auth(WebRequest request, AuthorizationRequired authorizationRequired) throws AuthorizationException { + return auth(request.getHeader("Authorization"), authorizationRequired); + } + public boolean auth(HttpServletRequest request, AuthorizationRequired authorizationRequired) throws AuthorizationException { + auth(request.getHeader("Authorization"), authorizationRequired); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java b/src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java new file mode 100644 index 0000000..524c916 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java @@ -0,0 +1,56 @@ +package cn.wzpmc.filemanager.utils; + +import cn.wzpmc.filemanager.config.FileManagerProperties; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Calendar; +import java.util.Optional; +import java.util.Random; + +@Component +@Log4j2 +public class JwtUtils { + private final Algorithm hmacKey; + private final RandomUtils randomUtils; + private String generatorHmacKey(){ + return this.randomUtils.generatorRandomString(16); + } + @Autowired + public JwtUtils(FileManagerProperties properties, RandomUtils randomUtils){ + this.randomUtils = randomUtils; + String hmacKey = properties.getHmacKey(); + String key; + if ("RANDOM".equalsIgnoreCase(hmacKey)){ + key = this.generatorHmacKey(); + log.info("Using Random Hmac Key: {}", key); + }else{ + key = hmacKey; + } + this.hmacKey = Algorithm.HMAC512(key); + } + public String createToken(int uid){ + Calendar instance = Calendar.getInstance(); + instance.add(Calendar.HOUR,24 * 5); + JWTCreator.Builder builder = JWT.create(); + builder.withClaim("uid", uid); + builder.withExpiresAt(instance.getTime()); + return builder.sign(this.hmacKey); + } + public Optional getUser(String token){ + DecodedJWT verify; + try { + verify = JWT.require(this.hmacKey).build().verify(token); + }catch (Exception e){ + return Optional.empty(); + } + Claim uid = verify.getClaim("uid"); + return Optional.of(uid.asInt()); + } +} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java b/src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java new file mode 100644 index 0000000..84766d8 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java @@ -0,0 +1,19 @@ +package cn.wzpmc.filemanager.utils; + +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Random; + +@Component +@NoArgsConstructor +public class RandomUtils { + public String generatorRandomString(int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + int c = new Random().nextInt(33, 126); + builder.append((char) c); + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b75f88a..59fdeb8 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -13,6 +13,11 @@ spring: host: "server.wzpmc.cn" database: 3 password: "MyCraftAdmin123" + servlet: + multipart: + max-file-size: 100GB + max-request-size: 100GB wzp: filemanager: - save-path: "./file" \ No newline at end of file + save-path: "./file" + hmac-key: "V(LWJ6D5*4%,Hk{1" \ No newline at end of file