From 5b01a663710c2faf0fde55ff3d783dd7da1a14fa Mon Sep 17 00:00:00 2001 From: Wzp-2008 Date: Tue, 19 Nov 2024 11:02:20 +0800 Subject: [PATCH] feat: adding path system --- settings.gradle.kts | 2 +- .../filemanager/FileManagerApplication.java | 2 + .../wzpmc/filemanager/annotation/Address.java | 11 + .../commands/AuthorizationCommands.java | 3 +- .../config/FileManagerProperties.java | 6 +- .../configuration/AddressConfiguration.java | 21 + .../configuration/RedisConfiguration.java | 15 +- .../controller/FileController.java | 80 +++- .../controller/UserController.java | 22 +- .../entities/{Page.java => PageResult.java} | 4 +- .../cn/wzpmc/filemanager/entities/Result.java | 4 +- .../entities/files/CheckChunkResponse.java | 18 - .../entities/files/ChunkChecked.java | 14 - .../entities/files/ChunkReady.java | 11 - .../entities/files/DeleteRequest.java | 10 + .../entities/files/DoneUploadRequest.java | 8 - .../filemanager/entities/files/FileData.java | 11 - .../entities/files/FileObject.java | 12 - .../entities/files/FolderCreateRequest.java | 9 + .../entities/files/FolderObject.java | 12 - .../entities/files/PrepareUploadRequest.java | 12 - .../entities/files/RawFileObject.java | 29 +- .../entities/files/enums/FileType.java | 2 +- .../entities/statistics/enums/Actions.java | 2 +- .../filemanager/entities/vo/ChunkVo.java | 16 - .../filemanager/entities/vo/FileChunkVo.java | 14 - .../wzpmc/filemanager/entities/vo/FileVo.java | 13 +- .../filemanager/entities/vo/FolderVo.java | 6 +- .../filemanager/entities/vo/StatisticsVo.java | 9 +- .../wzpmc/filemanager/entities/vo/UserVo.java | 13 +- .../interfaces/FilePathService.java | 13 + .../interfaces/impl/ComplexResolver.java | 60 +++ .../interfaces/impl/ReverseResolver.java | 72 +++ .../interfaces/impl/SimplePathResolver.java | 38 ++ .../interfaces/impl/SimpleResolver.java | 53 +++ .../wzpmc/filemanager/mapper/ChunkMapper.java | 7 - .../filemanager/mapper/FileChunksMapper.java | 7 - .../filemanager/mapper/StatisticsMapper.java | 7 + .../filemanager/service/FileService.java | 440 +++++++++++++----- .../service/StatisticsService.java | 31 ++ .../filemanager/service/UserService.java | 33 +- .../utils/AddressArgumentResolver.java | 31 ++ .../cn/wzpmc/filemanager/utils/JwtUtils.java | 3 +- .../wzpmc/filemanager/utils/RandomUtils.java | 14 + .../SizeStatisticsDigestInputStream.java | 30 ++ src/main/resources/application.yaml | 5 +- 46 files changed, 894 insertions(+), 341 deletions(-) create mode 100644 src/main/java/cn/wzpmc/filemanager/annotation/Address.java create mode 100644 src/main/java/cn/wzpmc/filemanager/configuration/AddressConfiguration.java rename src/main/java/cn/wzpmc/filemanager/entities/{Page.java => PageResult.java} (76%) delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/ChunkChecked.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/ChunkReady.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/DeleteRequest.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/FileData.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/FileObject.java create mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/FolderCreateRequest.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/FolderObject.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/files/PrepareUploadRequest.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/vo/ChunkVo.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/entities/vo/FileChunkVo.java create mode 100644 src/main/java/cn/wzpmc/filemanager/interfaces/FilePathService.java create mode 100644 src/main/java/cn/wzpmc/filemanager/interfaces/impl/ComplexResolver.java create mode 100644 src/main/java/cn/wzpmc/filemanager/interfaces/impl/ReverseResolver.java create mode 100644 src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimplePathResolver.java create mode 100644 src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimpleResolver.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java delete mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/FileChunksMapper.java create mode 100644 src/main/java/cn/wzpmc/filemanager/mapper/StatisticsMapper.java create mode 100644 src/main/java/cn/wzpmc/filemanager/service/StatisticsService.java create mode 100644 src/main/java/cn/wzpmc/filemanager/utils/AddressArgumentResolver.java create mode 100644 src/main/java/cn/wzpmc/filemanager/utils/SizeStatisticsDigestInputStream.java diff --git a/settings.gradle.kts b/settings.gradle.kts index 86b7f9d..9905375 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "FileManager" +rootProject.name = "FileManager" \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java b/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java index 4b13d8f..81ee918 100644 --- a/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java +++ b/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java @@ -7,6 +7,7 @@ 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.scheduling.annotation.EnableAsync; import org.springframework.shell.command.annotation.EnableCommand; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @MapperScan("cn.wzpmc.filemanager.mapper") @EnableTransactionManagement @EnableCommand +@EnableAsync public class FileManagerApplication { public static void main(String[] args) { diff --git a/src/main/java/cn/wzpmc/filemanager/annotation/Address.java b/src/main/java/cn/wzpmc/filemanager/annotation/Address.java new file mode 100644 index 0000000..818b4e0 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/annotation/Address.java @@ -0,0 +1,11 @@ +package cn.wzpmc.filemanager.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Address { +} \ 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 index 5947f63..a2b979e 100644 --- a/src/main/java/cn/wzpmc/filemanager/commands/AuthorizationCommands.java +++ b/src/main/java/cn/wzpmc/filemanager/commands/AuthorizationCommands.java @@ -1,5 +1,6 @@ package cn.wzpmc.filemanager.commands; +import cn.wzpmc.filemanager.entities.vo.UserVo; import cn.wzpmc.filemanager.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.shell.standard.ShellComponent; @@ -14,6 +15,6 @@ public class AuthorizationCommands { } @ShellMethod("创建一个密钥") public void key(){ - this.userService.genInviteCode(); + this.userService.genInviteCode(UserVo.CONSOLE, "0.0.0.0"); } } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/config/FileManagerProperties.java b/src/main/java/cn/wzpmc/filemanager/config/FileManagerProperties.java index f6db4e6..8ecc3dc 100644 --- a/src/main/java/cn/wzpmc/filemanager/config/FileManagerProperties.java +++ b/src/main/java/cn/wzpmc/filemanager/config/FileManagerProperties.java @@ -1,6 +1,7 @@ package cn.wzpmc.filemanager.config; import lombok.Data; +import lombok.Getter; import org.springframework.boot.context.properties.ConfigurationProperties; import java.io.File; @@ -8,11 +9,8 @@ import java.io.File; @ConfigurationProperties(prefix = "wzp.filemanager") @Data public class FileManagerProperties { + @Getter private File savePath; private String hmacKey = "RANDOM"; private FFmpegConfiguration ffmpeg; - public File getSavePath() { - System.out.println(this.savePath); - return this.savePath; - } } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/configuration/AddressConfiguration.java b/src/main/java/cn/wzpmc/filemanager/configuration/AddressConfiguration.java new file mode 100644 index 0000000..3e27a6c --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/configuration/AddressConfiguration.java @@ -0,0 +1,21 @@ +package cn.wzpmc.filemanager.configuration; + +import cn.wzpmc.filemanager.utils.AddressArgumentResolver; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +@RequiredArgsConstructor +public class AddressConfiguration implements WebMvcConfigurer { + private final AddressArgumentResolver addressArgumentResolver; + @Override + public void addArgumentResolvers(@NonNull List resolvers) { + WebMvcConfigurer.super.addArgumentResolvers(resolvers); + resolvers.add(addressArgumentResolver); + } +} diff --git a/src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java b/src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java index 0928a54..cfd5c35 100644 --- a/src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java +++ b/src/main/java/cn/wzpmc/filemanager/configuration/RedisConfiguration.java @@ -1,7 +1,7 @@ package cn.wzpmc.filemanager.configuration; -import cn.wzpmc.filemanager.entities.files.ChunkChecked; -import cn.wzpmc.filemanager.entities.files.ChunkReady; +import cn.wzpmc.filemanager.entities.vo.FileVo; +import lombok.Getter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,6 +9,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration +@Getter public class RedisConfiguration { private final RedisConnectionFactory redisConnectionFactory; @@ -17,14 +18,8 @@ public class RedisConfiguration { this.redisConnectionFactory = redisConnectionFactory; } @Bean - public RedisTemplate uploadMapper() { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(redisConnectionFactory); - return template; - } - @Bean - public RedisTemplate chunkUploadMapper() { - RedisTemplate template = new RedisTemplate<>(); + public RedisTemplate linkMapper() { + RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } diff --git a/src/main/java/cn/wzpmc/filemanager/controller/FileController.java b/src/main/java/cn/wzpmc/filemanager/controller/FileController.java index 11bdbac..4d46d78 100644 --- a/src/main/java/cn/wzpmc/filemanager/controller/FileController.java +++ b/src/main/java/cn/wzpmc/filemanager/controller/FileController.java @@ -1,40 +1,78 @@ package cn.wzpmc.filemanager.controller; +import cn.wzpmc.filemanager.annotation.Address; import cn.wzpmc.filemanager.annotation.AuthorizationRequired; +import cn.wzpmc.filemanager.entities.PageResult; 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.files.DeleteRequest; +import cn.wzpmc.filemanager.entities.files.FolderCreateRequest; +import cn.wzpmc.filemanager.entities.files.RawFileObject; +import cn.wzpmc.filemanager.entities.files.enums.FileType; +import cn.wzpmc.filemanager.entities.vo.FileVo; +import cn.wzpmc.filemanager.entities.vo.FolderVo; import cn.wzpmc.filemanager.entities.vo.UserVo; import cn.wzpmc.filemanager.service.FileService; -import org.springframework.beans.factory.annotation.Autowired; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import java.util.Date; @RestController @RequestMapping("/api/file") +@RequiredArgsConstructor public class FileController { private final FileService fileService; - @Autowired - public FileController(FileService fileService) { - this.fileService = fileService; + @PutMapping("/upload") + public Result simpleUpload(MultipartHttpServletRequest file, @AuthorizationRequired UserVo user, @Address String address) { + return fileService.simpleUpload(file, user, address); } - @PostMapping("/prepare") - public Result prepareUploadChunks(@RequestBody PrepareUploadRequest prepareUploadRequest, @AuthorizationRequired UserVo user){ - return fileService.prepareUploadChunks(prepareUploadRequest, user); + + @GetMapping("/get") + public Result> getFilePager(@RequestParam long page, @RequestParam int num, @RequestParam long folder, @Address String address) { + return fileService.getFilePager(page, num, folder, address); } - @PutMapping("/chunk/upload") - public Result uploadChunk(MultipartFile file,@RequestParam String id){ - return fileService.uploadChunk(file, id); + + @PostMapping("/mkdir") + public Result mkdir(@RequestBody FolderCreateRequest request, @AuthorizationRequired UserVo user, @Address String address) { + return fileService.mkdir(request, user, address); } - @GetMapping("/chunk/check") - public Result checkChunk(@RequestParam String hash, @RequestParam String id, @RequestParam long index){ - return fileService.checkChunk(hash, id, index); + + @GetMapping("/detail") + public Result getFileDetail(@RequestParam long id) { + return fileService.getFileDetail(id); } - @PostMapping("/done") - public Result doneUpload(@RequestBody DoneUploadRequest data){ - return fileService.doneUpload(data.getId()); + + @DeleteMapping("/rm") + public Result delete(@RequestBody DeleteRequest request, @AuthorizationRequired UserVo user, @Address String address) { + return fileService.delete(request, user, address); + } + + @GetMapping("/link") + public Result getFileLink(@RequestParam long id, @Address String address, HttpServletRequest request) { + return fileService.getFileLink(id, address, request); + } + + @GetMapping("/download/{id}") + public void downloadFile(@PathVariable String id, @RequestHeader(value = "Range", defaultValue = "null") String range, HttpServletResponse response) { + fileService.downloadFile(id, range, response); + } + + @GetMapping("/share") + public Result shareFile(@RequestParam Long id, @RequestParam(defaultValue = "9999-12-31") Date lastCouldDownloadTime, @RequestParam(defaultValue = "-1") int maxDownloadCount) { + return fileService.shareFile(id, lastCouldDownloadTime, maxDownloadCount); + } + + @GetMapping("/path/resolve") + public Result resolveFileDetail(@RequestParam String path) { + return fileService.resolveFileDetail(path); + } + + @GetMapping("/path/{id}") + public Result findFilePathById(@PathVariable("id") long id, @RequestParam(value = "type", defaultValue = "FILE") FileType type) { + return type.equals(FileType.FILE) ? fileService.findFilePathById(id) : fileService.findFolderPathById(id); } } \ 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 a06678a..e4571df 100644 --- a/src/main/java/cn/wzpmc/filemanager/controller/UserController.java +++ b/src/main/java/cn/wzpmc/filemanager/controller/UserController.java @@ -1,5 +1,6 @@ package cn.wzpmc.filemanager.controller; +import cn.wzpmc.filemanager.annotation.Address; import cn.wzpmc.filemanager.annotation.AuthorizationRequired; import cn.wzpmc.filemanager.entities.Result; import cn.wzpmc.filemanager.entities.user.UserLoginRequest; @@ -8,28 +9,33 @@ 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 lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +@Slf4j @RestController @RequestMapping("/api/user") public class UserController { private final UserService userService; + @Autowired public UserController(UserService userService) { - this.userService= userService; + this.userService = userService; } + @PostMapping("/login") - public void login(@RequestBody UserLoginRequest loginRequest, HttpServletResponse response) { - userService.login(loginRequest, response); + public void login(@RequestBody UserLoginRequest loginRequest, HttpServletResponse response, @Address String address) { + userService.login(loginRequest, response, address); } + @PutMapping("/register") - public void register(@RequestBody UserRegisterRequest registerRequest, HttpServletResponse response) { - userService.register(registerRequest, response); + public void register(@RequestBody UserRegisterRequest registerRequest, HttpServletResponse response, @Address String address) { + userService.register(registerRequest, response, address); } + @GetMapping("/invite") - @AuthorizationRequired(level = Auth.admin) - public Result invite(){ - return userService.invite(); + public Result invite(@AuthorizationRequired(level = Auth.admin) UserVo userVo, @Address String address) { + return userService.invite(userVo, address); } } \ 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/PageResult.java similarity index 76% rename from src/main/java/cn/wzpmc/filemanager/entities/Page.java rename to src/main/java/cn/wzpmc/filemanager/entities/PageResult.java index 2b7d71b..1685d01 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/Page.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/PageResult.java @@ -7,7 +7,7 @@ import java.util.List; @Data @AllArgsConstructor -public class Page { - private int total; +public class PageResult { + private long 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 index b55c04c..0b273fb 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/Result.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/Result.java @@ -3,10 +3,7 @@ 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; @@ -93,6 +90,7 @@ public class Result { } public void writeToResponse(HttpServletResponse response){ + response.addHeader("Content-Type", "application/json; charset=utf-8"); try(ServletOutputStream outputStream = response.getOutputStream()){ writeToOutputStream(outputStream); } catch (IOException e) { diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java b/src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java deleted file mode 100644 index 2c2a7a8..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/CheckChunkResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 0c2059d..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkChecked.java +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 4a3c205..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/ChunkReady.java +++ /dev/null @@ -1,11 +0,0 @@ -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/DeleteRequest.java b/src/main/java/cn/wzpmc/filemanager/entities/files/DeleteRequest.java new file mode 100644 index 0000000..369936e --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/DeleteRequest.java @@ -0,0 +1,10 @@ +package cn.wzpmc.filemanager.entities.files; + +import cn.wzpmc.filemanager.entities.files.enums.FileType; +import lombok.Data; + +@Data +public class DeleteRequest { + private FileType type; + private long id; +} diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java b/src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java deleted file mode 100644 index f0021b4..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/DoneUploadRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -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/FileData.java b/src/main/java/cn/wzpmc/filemanager/entities/files/FileData.java deleted file mode 100644 index 2eaac2a..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/FileData.java +++ /dev/null @@ -1,11 +0,0 @@ -package cn.wzpmc.filemanager.entities.files; - -import cn.wzpmc.filemanager.entities.vo.ChunkVo; -import lombok.Data; - -import java.util.List; - -@Data -public class FileData { - private List chunks; -} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/FileObject.java b/src/main/java/cn/wzpmc/filemanager/entities/files/FileObject.java deleted file mode 100644 index f082374..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/FileObject.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.wzpmc.filemanager.entities.files; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode(callSuper = true) -@Data -public class FileObject extends RawFileObject { - private String ext; - private String mime; - private String sha1; -} \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/FolderCreateRequest.java b/src/main/java/cn/wzpmc/filemanager/entities/files/FolderCreateRequest.java new file mode 100644 index 0000000..bd86371 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/FolderCreateRequest.java @@ -0,0 +1,9 @@ +package cn.wzpmc.filemanager.entities.files; + +import lombok.Data; + +@Data +public class FolderCreateRequest { + private long parent; + private String name; +} diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/FolderObject.java b/src/main/java/cn/wzpmc/filemanager/entities/files/FolderObject.java deleted file mode 100644 index 63a49d3..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/FolderObject.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.wzpmc.filemanager.entities.files; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; - -@EqualsAndHashCode(callSuper = true) -@Data -public class FolderObject extends RawFileObject { - private List children; -} \ 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 deleted file mode 100644 index 22a7cd2..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/PrepareUploadRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -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/files/RawFileObject.java b/src/main/java/cn/wzpmc/filemanager/entities/files/RawFileObject.java index e3c9bcf..d15729c 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/RawFileObject.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/RawFileObject.java @@ -1,14 +1,35 @@ package cn.wzpmc.filemanager.entities.files; import cn.wzpmc.filemanager.entities.files.enums.FileType; +import cn.wzpmc.filemanager.entities.vo.FileVo; +import cn.wzpmc.filemanager.entities.vo.FolderVo; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.Date; + @Data -public abstract class RawFileObject { +@AllArgsConstructor +@NoArgsConstructor +public class RawFileObject { + private long id; private String name; - private Integer id; - private String uploader; - private Date createTime; + private String ext; + private long size; + private long owner; + private long parent; + private Date time; private FileType type; + + public static RawFileObject of(FileVo file) { + return new RawFileObject(file.getId(), file.getName(), file.getExt(), file.getSize(), file.getUploader(), file.getFolder(), file.getUploadTime(), FileType.FILE); + } + + public static RawFileObject of(FolderVo folder) { + return new RawFileObject(folder.getId(), folder.getName(), null, -1, folder.getCreator(), folder.getParent(), folder.getCreateTime(), FileType.FOLDER); + } + public static String getRawFileName(RawFileObject object){ + return object.type.equals(FileType.FILE) ? object.getName() + '.' + object.getExt() : object.getName(); + } } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/files/enums/FileType.java b/src/main/java/cn/wzpmc/filemanager/entities/files/enums/FileType.java index 5ab91fd..e972874 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/files/enums/FileType.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/files/enums/FileType.java @@ -1,5 +1,5 @@ package cn.wzpmc.filemanager.entities.files.enums; public enum FileType { - FILE, FOLDER; + FILE, FOLDER } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/entities/statistics/enums/Actions.java b/src/main/java/cn/wzpmc/filemanager/entities/statistics/enums/Actions.java index 4d256c1..6f9c8a3 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/statistics/enums/Actions.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/statistics/enums/Actions.java @@ -1,5 +1,5 @@ package cn.wzpmc.filemanager.entities.statistics.enums; public enum Actions { - UPLOAD, DELETE, ACCESS, DOWNLOAD, SEARCH + UPLOAD, DELETE, ACCESS, DOWNLOAD, SEARCH, LOGIN, INVITE, REGISTER } \ 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 deleted file mode 100644 index 4f46481..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/ChunkVo.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.wzpmc.filemanager.entities.vo; - -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 deleted file mode 100644 index 6b4fbd1..0000000 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/FileChunkVo.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.wzpmc.filemanager.entities.vo; - -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 787bcf7..0ad85f5 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/FileVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/FileVo.java @@ -6,20 +6,21 @@ import com.mybatisflex.annotation.KeyType; import com.mybatisflex.annotation.Table; import lombok.Data; +import java.io.Serializable; import java.util.Date; -import java.util.Objects; @Table("file") @Data -public class FileVo { +public class FileVo implements Serializable { @Id(keyType = KeyType.Auto) - private int id; + private long id; private String name; private String ext; private String mime; - private String sha1; - private int uploader; - private int folder; + private String hash; + private long uploader; + private long folder; + private long size; @Column(onInsertValue = "now()") private Date uploadTime; 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 1e39141..76cb936 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/FolderVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/FolderVo.java @@ -12,10 +12,10 @@ import java.util.Date; @Data public class FolderVo { @Id(keyType = KeyType.Auto) - private int id; + private long id; private String name; - private int parent; - private int creator; + private long parent; + private long creator; @Column(onInsertValue = "now()") private Date createTime; 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 eaff214..eacf2c8 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/StatisticsVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/StatisticsVo.java @@ -4,16 +4,23 @@ import cn.wzpmc.filemanager.entities.statistics.enums.Actions; import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Table; import lombok.Data; +import lombok.NoArgsConstructor; import java.util.Date; @Table("statistics") @Data +@NoArgsConstructor public class StatisticsVo { - private int actor; + private Long actor; private Actions action; private String params; @Column(onInsertValue = "now()") private Date time; + public StatisticsVo(Long actor, Actions action, String params) { + this.actor = actor; + this.action = action; + this.params = params; + } } \ 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 e94c778..6a34707 100644 --- a/src/main/java/cn/wzpmc/filemanager/entities/vo/UserVo.java +++ b/src/main/java/cn/wzpmc/filemanager/entities/vo/UserVo.java @@ -10,9 +10,10 @@ import lombok.Data; @Table("user") @Data +@AllArgsConstructor public class UserVo { @Id(keyType = KeyType.Auto) - private int id; + private long id; private String name; private String password; private Auth auth; @@ -23,5 +24,13 @@ public class UserVo { this.password = password; this.auth = auth; } - + private UserVo(long id, String name, Auth auth){ + this.id = id; + this.name = name; + this.auth = auth; + } + public UserVo(long id) { + this.id = id; + } + public static final UserVo CONSOLE = new UserVo(0L, "CONSOLE", Auth.admin); } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/interfaces/FilePathService.java b/src/main/java/cn/wzpmc/filemanager/interfaces/FilePathService.java new file mode 100644 index 0000000..7b624a1 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/interfaces/FilePathService.java @@ -0,0 +1,13 @@ +package cn.wzpmc.filemanager.interfaces; + +import cn.wzpmc.filemanager.entities.files.RawFileObject; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +public interface FilePathService { + @NonNull + String getFilePath(@NonNull RawFileObject file); + + @Nullable + RawFileObject resolveFile(@NonNull String[] path); +} diff --git a/src/main/java/cn/wzpmc/filemanager/interfaces/impl/ComplexResolver.java b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/ComplexResolver.java new file mode 100644 index 0000000..922bd60 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/ComplexResolver.java @@ -0,0 +1,60 @@ +package cn.wzpmc.filemanager.interfaces.impl; + +import cn.wzpmc.filemanager.entities.files.RawFileObject; +import cn.wzpmc.filemanager.mapper.FileMapper; +import cn.wzpmc.filemanager.mapper.FolderMapper; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.Date; + +import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO; +import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO; + +@Component +@Primary +@Log4j2 +public class ComplexResolver extends SimplePathResolver { + private final ReverseResolver reverseResolver; + private final SimpleResolver simpleResolver; + @Autowired + public ComplexResolver(FileMapper fileMapper, FolderMapper folderMapper, ReverseResolver reverseResolver, SimpleResolver simpleResolver) { + super(fileMapper, folderMapper); + this.reverseResolver = reverseResolver; + this.simpleResolver = simpleResolver; + } + + @Override + @Nullable + public RawFileObject resolveFile(@NonNull String[] path) { + String strPath = Arrays.toString(path); + String targetFileName = path[path.length - 1]; + int lastDotIndex = targetFileName.lastIndexOf('.'); + String name = targetFileName; + String ext = ""; + if (lastDotIndex != -1) { + name = targetFileName.substring(0, lastDotIndex); + ext = targetFileName.substring(lastDotIndex + 1); + } + long start = new Date().getTime(); + long totalRawFileCount = this.fileMapper.selectCountByCondition(FILE_VO.NAME.eq(name).and(FILE_VO.EXT.eq(ext))) + this.folderMapper.selectCountByCondition(FOLDER_VO.NAME.eq(name)); + if (totalRawFileCount == 0) return null; + if (totalRawFileCount > path.length) { + log.info("use simple resolver to solve path with {}", strPath); + RawFileObject rawFileObject = simpleResolver.resolveFile(path); + long end = new Date().getTime(); + log.info("solve path {} cost {}ms", strPath, end - start); + return rawFileObject; + } + log.info("use reverse resolver to solve path with {}", strPath); + RawFileObject rawFileObject = reverseResolver.resolveFile(path); + long end = new Date().getTime(); + log.info("solve path {} cost {}ms", strPath, end - start); + return rawFileObject; + } +} diff --git a/src/main/java/cn/wzpmc/filemanager/interfaces/impl/ReverseResolver.java b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/ReverseResolver.java new file mode 100644 index 0000000..b413ce6 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/ReverseResolver.java @@ -0,0 +1,72 @@ +package cn.wzpmc.filemanager.interfaces.impl; + +import cn.wzpmc.filemanager.entities.files.RawFileObject; +import cn.wzpmc.filemanager.entities.vo.FolderVo; +import cn.wzpmc.filemanager.mapper.FileMapper; +import cn.wzpmc.filemanager.mapper.FolderMapper; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +import java.util.*; + +import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO; +import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO; + +@Component +public class ReverseResolver extends SimplePathResolver { + public ReverseResolver(FileMapper fileMapper, FolderMapper folderMapper) { + super(fileMapper, folderMapper); + } + + @Nullable + @Override + public RawFileObject resolveFile(@NonNull String[] path) { + List pathList = removeEmptyPath(path); + String targetFileName = pathList.get(pathList.size() - 1); + int lastDotIndex = targetFileName.lastIndexOf('.'); + String name = targetFileName; + String ext = ""; + if (lastDotIndex != -1) { + name = targetFileName.substring(0, lastDotIndex); + ext = targetFileName.substring(lastDotIndex + 1); + } + List rawFileObjects = new ArrayList<>(); + fileMapper.selectListByCondition(FILE_VO.NAME.eq(name).and(FILE_VO.EXT.eq(ext))).stream().map(RawFileObject::of).forEach(rawFileObjects::add); + folderMapper.selectListByCondition(FOLDER_VO.NAME.eq(name)).stream().map(RawFileObject::of).forEach(rawFileObjects::add); + if (rawFileObjects.isEmpty()) return null; + if (rawFileObjects.size() == 1) return rawFileObjects.get(0); + List possibleParents = rawFileObjects.stream().map(RawFileObject::getParent).toList(); + Optional inRoot = rawFileObjects.stream().filter(e -> e.getParent() == -1).findFirst(); + if (inRoot.isPresent()) { + if (pathList.size() <= 1) { + return inRoot.get(); + } + } + List folderVos = folderMapper.selectListByIds(possibleParents); + FolderVo parent = reverseFindFileParent(folderVos, pathList.subList(0, pathList.size() - 1)); + if (parent == null) return null; + Optional first = rawFileObjects.stream().filter(e -> e.getParent() == parent.getId()).findFirst(); + return first.orElse(null); + } + + private FolderVo reverseFindFileParent(List possibleParent, List path) { + if (path.isEmpty()) return null; + if (possibleParent.size() == 1) return possibleParent.get(0); + String currentLayerName = path.get(path.size() - 1); + List folderVoStream = possibleParent.stream().filter(e -> e.getName().equals(currentLayerName)).toList(); + Optional inRoot = folderVoStream.stream().filter(e -> e.getParent() == -1).findFirst(); + if (inRoot.isPresent()) { + if (path.size() <= 1) { + return inRoot.get(); + } + } + List list = folderVoStream.stream().map(FolderVo::getParent).toList(); + if (list.isEmpty()) return null; + List parents = folderMapper.selectListByIds(list); + FolderVo parent = reverseFindFileParent(parents, path.subList(0, path.size() - 1)); + if (parent == null) return null; + Optional first = folderVoStream.stream().filter(e -> e.getParent() == parent.getId()).findFirst(); + return first.orElse(null); + } +} diff --git a/src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimplePathResolver.java b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimplePathResolver.java new file mode 100644 index 0000000..78c5ea5 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimplePathResolver.java @@ -0,0 +1,38 @@ +package cn.wzpmc.filemanager.interfaces.impl; + +import cn.wzpmc.filemanager.entities.files.RawFileObject; +import cn.wzpmc.filemanager.entities.vo.FolderVo; +import cn.wzpmc.filemanager.interfaces.FilePathService; +import cn.wzpmc.filemanager.mapper.FileMapper; +import cn.wzpmc.filemanager.mapper.FolderMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; + +import java.util.Arrays; +import java.util.List; + +@RequiredArgsConstructor +public abstract class SimplePathResolver implements FilePathService { + protected final FileMapper fileMapper; + protected final FolderMapper folderMapper; + + @NonNull + @Override + public String getFilePath(@NonNull RawFileObject file) { + return resolvePath(file.getParent()) + RawFileObject.getRawFileName(file); + } + + private String resolvePath(long id) { + if (id == -1) { + return "/"; + } + FolderVo folderVo = folderMapper.selectOneById(id); + long parent = folderVo.getParent(); + String name = folderVo.getName(); + return resolvePath(parent) + name + "/"; + } + + protected List removeEmptyPath(String[] path) { + return Arrays.stream(path).filter(e -> !e.isEmpty()).toList(); + } +} diff --git a/src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimpleResolver.java b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimpleResolver.java new file mode 100644 index 0000000..552617a --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/interfaces/impl/SimpleResolver.java @@ -0,0 +1,53 @@ +package cn.wzpmc.filemanager.interfaces.impl; + +import cn.wzpmc.filemanager.entities.files.RawFileObject; +import cn.wzpmc.filemanager.entities.vo.FileVo; +import cn.wzpmc.filemanager.entities.vo.FolderVo; +import cn.wzpmc.filemanager.mapper.FileMapper; +import cn.wzpmc.filemanager.mapper.FolderMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO; +import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO; + +@Component +public class SimpleResolver extends SimplePathResolver { + @Autowired + public SimpleResolver(FileMapper fileMapper, FolderMapper folderMapper) { + super(fileMapper, folderMapper); + } + + @Nullable + @Override + public RawFileObject resolveFile(@NonNull String[] path) { + return resolveFile(removeEmptyPath(path), -1); + } + + private RawFileObject resolveFile(List path, long parentId) { + String currentLayerName = path.get(0); + if (path.size() == 1) { + int lastDotIndex = currentLayerName.lastIndexOf('.'); + String name = currentLayerName; + String ext = ""; + if (lastDotIndex != -1) { + name = currentLayerName.substring(0, lastDotIndex); + ext = currentLayerName.substring(lastDotIndex + 1); + } + FileVo file = fileMapper.selectOneByCondition(FILE_VO.NAME.eq(name).and(FILE_VO.EXT.eq(ext)).and(FILE_VO.FOLDER.eq(parentId))); + if (file != null) { + return RawFileObject.of(file); + } + return RawFileObject.of(folderMapper.selectOneById(FOLDER_VO.NAME.eq(name).and(FOLDER_VO.PARENT.eq(parentId)))); + } + FolderVo folderVo = folderMapper.selectOneByCondition(FOLDER_VO.NAME.eq(currentLayerName).and(FOLDER_VO.PARENT.eq(parentId))); + if (folderVo == null) { + return null; + } + return resolveFile(path.subList(1, path.size()), folderVo.getId()); + } +} diff --git a/src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java b/src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java deleted file mode 100644 index efa646a..0000000 --- a/src/main/java/cn/wzpmc/filemanager/mapper/ChunkMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 9fc4a60..0000000 --- a/src/main/java/cn/wzpmc/filemanager/mapper/FileChunksMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -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/StatisticsMapper.java b/src/main/java/cn/wzpmc/filemanager/mapper/StatisticsMapper.java new file mode 100644 index 0000000..dd431c3 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/mapper/StatisticsMapper.java @@ -0,0 +1,7 @@ +package cn.wzpmc.filemanager.mapper; + +import cn.wzpmc.filemanager.entities.vo.StatisticsVo; +import com.mybatisflex.core.BaseMapper; + +public interface StatisticsMapper extends BaseMapper { +} diff --git a/src/main/java/cn/wzpmc/filemanager/service/FileService.java b/src/main/java/cn/wzpmc/filemanager/service/FileService.java index fc9949a..9935be9 100644 --- a/src/main/java/cn/wzpmc/filemanager/service/FileService.java +++ b/src/main/java/cn/wzpmc/filemanager/service/FileService.java @@ -1,166 +1,372 @@ package cn.wzpmc.filemanager.service; import cn.wzpmc.filemanager.config.FileManagerProperties; +import cn.wzpmc.filemanager.entities.PageResult; 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.files.DeleteRequest; +import cn.wzpmc.filemanager.entities.files.FolderCreateRequest; +import cn.wzpmc.filemanager.entities.files.RawFileObject; +import cn.wzpmc.filemanager.entities.files.enums.FileType; +import cn.wzpmc.filemanager.entities.statistics.enums.Actions; +import cn.wzpmc.filemanager.entities.user.enums.Auth; import cn.wzpmc.filemanager.entities.vo.FileVo; +import cn.wzpmc.filemanager.entities.vo.FolderVo; import cn.wzpmc.filemanager.entities.vo.UserVo; -import cn.wzpmc.filemanager.mapper.ChunkMapper; -import cn.wzpmc.filemanager.mapper.FileChunksMapper; +import cn.wzpmc.filemanager.interfaces.FilePathService; import cn.wzpmc.filemanager.mapper.FileMapper; import cn.wzpmc.filemanager.mapper.FolderMapper; +import cn.wzpmc.filemanager.utils.JwtUtils; import cn.wzpmc.filemanager.utils.RandomUtils; +import cn.wzpmc.filemanager.utils.SizeStatisticsDigestInputStream; +import com.alibaba.fastjson2.JSONObject; +import com.mybatisflex.core.audit.http.HashUtil; +import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.query.QueryWrapper; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.tika.Tika; +import org.apache.tomcat.util.http.fileupload.FileItemStream; +import org.apache.tomcat.util.http.fileupload.FileUpload; +import org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl; +import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.core.StringRedisTemplate; 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 org.springframework.web.multipart.MultipartHttpServletRequest; -import java.io.File; -import java.io.FileOutputStream; -import java.util.List; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.concurrent.TimeUnit; -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; +import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO; +import static com.mybatisflex.core.query.QueryMethods.*; +@Slf4j @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, "文件已存在!"); + private final StatisticsService statisticsService; + private final RedisTemplate linkMapper; + /*private final RedisTemplate linkCountMapper;*/ + private final StringRedisTemplate idAddrLinkMapper; + private final JwtUtils jwtUtils; + private final FilePathService pathService; + public static final String ID_ADDR_PREFIX = "ID_ADDR_"; + public static final String SHARE_PREFIX = "SHARE_"; + public static final char PATH_SEPARATOR_CHAR = '/'; + public static final String PATH_SEPARATOR = "" + PATH_SEPARATOR_CHAR; + + protected void tryDeleteOrDeleteOnExit(File tmpFile) { + if (!tmpFile.delete()) { + log.error("delete tmp file error"); + tmpFile.deleteOnExit(); } - 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, "写入文件块出现错误,创建文件夹失败"); + public Result simpleUpload(MultipartHttpServletRequest request, UserVo user, String address) { + long folderParams = getFolderParams(request); + ServletRequestContext servletRequestContext = new ServletRequestContext(request); + FileUpload upload = new FileUpload(); + FileItemIteratorImpl fileItemIterator = new FileItemIteratorImpl(upload, servletRequestContext); + FileVo lastUploadFile = null; + while (fileItemIterator.hasNext()) { + FileItemStream next = fileItemIterator.next(); + String fieldName = next.getFieldName(); + if (fieldName.equals("file")) { + String name = next.getName(); + int i = name.lastIndexOf("."); + String extName = null; + String start = name; + if (i != -1) { + start = name.substring(0, i); + } + if (!(i == -1 || i == name.length() - 1)) { + extName = name.substring(i + 1); + } + if (fileMapper.selectCountByCondition(FILE_VO.NAME.eq(start).and(FILE_VO.EXT.eq(extName)).and(FILE_VO.FOLDER.eq(folderParams))) > 0) { + return Result.failed(HttpStatus.CONFLICT, "存在同名文件,请改名或删除后重试!"); + } + InputStream inputStream = next.openStream(); + SizeStatisticsDigestInputStream digestInputStream = new SizeStatisticsDigestInputStream(inputStream, DigestUtils.getSha512Digest()); + File savePath = properties.getSavePath(); + File tmpFile = new File(savePath, "cache-" + randomUtils.generatorRandomFileName(20)); + try (FileOutputStream fileOutputStream = new FileOutputStream(tmpFile)) { + StreamUtils.copy(digestInputStream, fileOutputStream); + } + digestInputStream.close(); + String hex = HashUtil.toHex(digestInputStream.getMessageDigest().digest()); + long size = digestInputStream.getSize(); + if (size == 0) { + tryDeleteOrDeleteOnExit(tmpFile); + return Result.failed(HttpStatus.LENGTH_REQUIRED, "请勿上传空文件!"); + } + Tika tika = new Tika(); + String detect = tika.detect(tmpFile); + FileVo fileVo = new FileVo(); + fileVo.setUploader(user.getId()); + fileVo.setMime(detect); + fileVo.setSize(size); + fileVo.setName(start); + fileVo.setExt(extName); + fileVo.setHash(hex); + fileVo.setFolder(folderParams); + fileMapper.insert(fileVo); + statisticsService.insertAction(user, Actions.UPLOAD, JSONObject.of("id", fileVo.getId(), "hex", hex, "address", address)); + File targetFile = new File(savePath, hex); + lastUploadFile = fileVo; + if (targetFile.isFile()) { + tryDeleteOrDeleteOnExit(tmpFile); + continue; + } + if (!tmpFile.renameTo(targetFile)) { + throw new RuntimeException("error while moving file"); + } } } - File chunkFile = new File(hashHeadFolder, s); - if (!chunkFile.createNewFile()) { - return Result.failed(HttpStatus.INTERNAL_SERVER_ERROR, "写入文件块出现错误,无法写入块文件"); + if (lastUploadFile == null) { + return Result.failed(HttpStatus.BAD_REQUEST, "未找到文件参数"); } - 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("成功"); + return Result.success("上传成功!", lastUploadFile); } - 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!"); + private static long getFolderParams(MultipartHttpServletRequest request) { + String params = request.getQueryString(); + long folderParams = -1L; + if (params != null && !params.isEmpty()) { + String[] param = params.split("&"); + for (String s : param) { + String[] keyValue = s.split("="); + if (keyValue.length == 2) { + String key = keyValue[0]; + String value = keyValue[1]; + if (key.equals("folder")) { + folderParams = Long.parseLong(value); + } + } + } } - 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)); + return folderParams; } - 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"); + public Result> getFilePager(long page, int num, long folder, String address) { + QueryWrapper queryWrapper = select( + FILE_VO.ID.as("id"), + FILE_VO.NAME.as("name"), + FILE_VO.EXT.as("ext"), + FILE_VO.SIZE.as("size"), + FILE_VO.FOLDER.as("parent"), + FILE_VO.UPLOADER.as("owner"), + FILE_VO.UPLOAD_TIME.as("time"), + string("FILE").as("type") + ).from(FILE_VO). + where(FILE_VO.FOLDER.eq(folder)). + unionAll( + select( + FOLDER_VO.ID.as("id"), + FOLDER_VO.NAME.as("name"), + null_().as("ext"), + number(-1).as("size"), + FOLDER_VO.PARENT.as("parent"), + FOLDER_VO.CREATOR.as("owner"), + FOLDER_VO.CREATE_TIME.as("time"), + string("FOLDER").as("type") + ).from(FOLDER_VO). + where(FOLDER_VO.PARENT.eq(folder)) + ). + orderBy( + column("time"). + asc(), + column("id"). + asc() + ); + Page paginate = fileMapper.paginateAs(page, num, queryWrapper, RawFileObject.class); + PageResult result = new PageResult<>(paginate.getTotalRow(), paginate.getRecords()); + return Result.success(result); + } + + public Result mkdir(FolderCreateRequest request, UserVo user, String address) { + String name = request.getName(); + long parent = request.getParent(); + if (fileMapper.selectCountByCondition(FILE_VO.EXT.eq(null_()).and(FILE_VO.NAME.eq(name)).and(FILE_VO.FOLDER.eq(parent))) > 0) { + return Result.failed(HttpStatus.CONFLICT, "创建文件夹失败,同名文件已存在!"); } - long totalLength = andDelete.getLength(); - long l = calcFileSize(andDelete.getFileId()); - if (l != totalLength) { - fileMapper.deleteById(andDelete.getFileId()); - return Result.failed(HttpStatus.LENGTH_REQUIRED, "应收到" + totalLength + "字节,但只收到" + l + "字节!"); + if (folderMapper.selectCountByCondition(FOLDER_VO.NAME.eq(name).and(FOLDER_VO.PARENT.eq(parent))) > 0) { + return Result.failed(HttpStatus.CONFLICT, "创建文件夹失败,同名文件已存在!"); } + FolderVo folderVo = new FolderVo(); + folderVo.setCreator(user.getId()); + folderVo.setName(name); + folderVo.setParent(parent); + folderMapper.insert(folderVo); + statisticsService.insertAction(user, Actions.UPLOAD, JSONObject.of("type", "folder", "id", folderVo.getId(), "address", address)); + return Result.success("创建成功", folderVo); + } + + protected void deleteFile(FileVo fileVo) { + if (fileMapper.selectCountByCondition(FILE_VO.HASH.eq(fileVo.getHash())) <= 0) { + String hash = fileVo.getHash(); + File file = new File(properties.getSavePath(), hash); + if (file.exists()) { + if (!file.delete()) { + log.error("删除文件 {} 失败!", file); + } + } else { + log.error("存储目录可能损坏,在删除文件 {} 时未发现文件", file); + } + } + } + + @Transactional + public Result delete(DeleteRequest request, UserVo user, String address) { + long id = request.getId(); + FileType type = request.getType(); + long actorId = user.getId(); + if (type.equals(FileType.FILE)) { + FileVo fileVo = fileMapper.selectOneById(id); + if (fileVo == null) { + return Result.failed(HttpStatus.NOT_FOUND, "文件不存在!"); + } + if (user.getAuth().equals(Auth.user)) { + if (fileMapper.selectCountByCondition(FILE_VO.ID.eq(id).and(FILE_VO.UPLOADER.eq(actorId))) <= 0) { + return Result.failed(HttpStatus.UNAUTHORIZED, "权限不足!"); + } + } + fileMapper.deleteById(fileVo.getId()); + deleteFile(fileVo); + } else { + FolderVo folder = folderMapper.selectOneById(id); + if (folder == null) { + return Result.failed(HttpStatus.NOT_FOUND, "文件不存在!"); + } + if (user.getAuth().equals(Auth.user)) { + if (folderMapper.selectCountByCondition(FOLDER_VO.ID.eq(id).and(FOLDER_VO.CREATOR.eq(actorId))) <= 0) { + return Result.failed(HttpStatus.UNAUTHORIZED, "权限不足!"); + } + } + fileMapper.deleteByCondition(FILE_VO.FOLDER.eq(id)); + for (FileVo fileVo : fileMapper.selectListByCondition(FILE_VO.FOLDER.eq(id))) { + deleteFile(fileVo); + } + } + statisticsService.insertAction(user, Actions.DELETE, JSONObject.of("id", id, "type", type, "address", address)); 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; + + public Result getFileDetail(long id) { + FileVo fileVo = this.fileMapper.selectOneById(id); + if (fileVo == null) { + return Result.failed(HttpStatus.NOT_FOUND, "未知文件"); } - return sum; + return Result.success(fileVo); + } + + @SneakyThrows + public void downloadFile(String id, String range, HttpServletResponse response) { + FileVo fileVo = linkMapper.opsForValue().get(id); + if (fileVo == null) { + Result.failed(HttpStatus.NOT_FOUND, "未知文件!").writeToResponse(response); + return; + } + long size = fileVo.getSize(); + long min = 0; + long max = size; + if (!range.equals("null")) { + String[] unitRanges = range.split("="); + String[] minMax = unitRanges[1].split("-"); + if (minMax.length > 0) { + min = Long.parseLong(minMax[0]); + } + if (minMax.length > 1) { + max = Long.parseLong(minMax[1]); + } + } + String hash = fileVo.getHash(); + File file = new File(properties.getSavePath(), hash); + String fullName = fileVo.getName(); + String ext = fileVo.getExt(); + if (ext != null) { + fullName += '.' + ext; + } + response.setStatus(206); + response.addHeader("Content-Length", String.valueOf(max - min)); + response.addHeader("Content-Range", "bytes " + min + "-" + max + "/" + size); + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fullName, StandardCharsets.UTF_8)); + ServletOutputStream outputStream = response.getOutputStream(); + try (FileInputStream fis = new FileInputStream(file)) { + StreamUtils.copyRange(fis, outputStream, min, max); + } + outputStream.flush(); + } + + public Result getFileLink(long id, String address, HttpServletRequest request) { + FileVo fileVo = fileMapper.selectOneById(id); + long fileId = fileVo.getId(); + String identify = ID_ADDR_PREFIX + fileId + address; + String link = idAddrLinkMapper.opsForValue().get(identify); + if (link == null) { + link = randomUtils.generatorRandomFileName(8); + String authorization = request.getHeader("Authorization"); + if (authorization != null) { + jwtUtils.getUser(authorization).ifPresent(uid -> statisticsService.insertAction(new UserVo(uid), Actions.DOWNLOAD, JSONObject.of("id", fileId, "address", address))); + } else { + statisticsService.insertAction(Actions.DOWNLOAD, JSONObject.of("id", fileId, "address", address)); + } + linkMapper.opsForValue().set(link, fileVo, 30, TimeUnit.MINUTES); + idAddrLinkMapper.opsForValue().set(identify, link, 30, TimeUnit.MINUTES); + } + return Result.success("成功", link); + } + + public Result shareFile(Long id, Date lastCouldDownloadTime, int maxDownloadCount) { + FileVo fileVo = this.fileMapper.selectOneById(id); + if (fileVo == null) { + return Result.failed(HttpStatus.NOT_FOUND, "未知文件"); + } + String linkName = randomUtils.generatorRandomFileName(10); + String innerId = randomUtils.generatorRandomString(30); + long expireAfterMs = lastCouldDownloadTime.getTime() - new Date().getTime(); + return Result.success(linkName); + } + + public Result resolveFileDetail(String path) { + String[] split = path.split(PATH_SEPARATOR); + RawFileObject fileObj = pathService.resolveFile(split); + if (fileObj == null) { + return Result.failed(HttpStatus.NOT_FOUND, "文件不存在!"); + } + return Result.success(fileObj); + } + + public Result findFilePathById(long id) { + FileVo fileVo = fileMapper.selectOneById(id); + if (fileVo == null) { + return Result.failed(HttpStatus.NOT_FOUND, "文件不存在!"); + } + return Result.success("成功", pathService.getFilePath(RawFileObject.of(fileVo))); + } + + public Result findFolderPathById(long id) { + FolderVo folderVo = folderMapper.selectOneById(id); + if (folderVo == null) { + return Result.failed(HttpStatus.NOT_FOUND, "文件夹不存在"); + } + return Result.success("成功", pathService.getFilePath(RawFileObject.of(folderVo))); } } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/service/StatisticsService.java b/src/main/java/cn/wzpmc/filemanager/service/StatisticsService.java new file mode 100644 index 0000000..6ee2e00 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/service/StatisticsService.java @@ -0,0 +1,31 @@ +package cn.wzpmc.filemanager.service; + +import cn.wzpmc.filemanager.entities.statistics.enums.Actions; +import cn.wzpmc.filemanager.entities.vo.StatisticsVo; +import cn.wzpmc.filemanager.entities.vo.UserVo; +import cn.wzpmc.filemanager.mapper.StatisticsMapper; +import jakarta.annotation.Nullable; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class StatisticsService { + private final StatisticsMapper statisticsMapper; + + public void insertAction(@Nullable UserVo actor, Actions actions, @Nullable Object params) { + statisticsMapper.insert(new StatisticsVo(actor != null ? actor.getId() : null, actions, params != null ? params.toString() : null)); + } + + public void insertAction(@Nullable UserVo actor, Actions actions) { + this.insertAction(actor, actions, null); + } + + public void insertAction(Actions actions, @Nullable Object params) { + this.insertAction(null, actions, params); + } + + public void insertAction(Actions actions) { + this.insertAction(actions, null); + } +} diff --git a/src/main/java/cn/wzpmc/filemanager/service/UserService.java b/src/main/java/cn/wzpmc/filemanager/service/UserService.java index d10bd6f..c7a44bb 100644 --- a/src/main/java/cn/wzpmc/filemanager/service/UserService.java +++ b/src/main/java/cn/wzpmc/filemanager/service/UserService.java @@ -1,6 +1,7 @@ package cn.wzpmc.filemanager.service; import cn.wzpmc.filemanager.entities.Result; +import cn.wzpmc.filemanager.entities.statistics.enums.Actions; import cn.wzpmc.filemanager.entities.user.UserLoginRequest; import cn.wzpmc.filemanager.entities.user.UserRegisterRequest; import cn.wzpmc.filemanager.entities.user.enums.Auth; @@ -8,6 +9,7 @@ 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.alibaba.fastjson2.JSONObject; import com.mybatisflex.core.query.QueryCondition; import com.mybatisflex.core.query.QueryWrapper; import jakarta.servlet.http.HttpServletResponse; @@ -30,66 +32,77 @@ public class UserService { private final JwtUtils jwtUtils; private final StringRedisTemplate authTemplate; private final RandomUtils randomUtils; + private final StatisticsService statisticsService; @Autowired - public UserService(UserMapper userMapper, JwtUtils jwtUtils, StringRedisTemplate authTemplate, RandomUtils randomUtils) { + public UserService(UserMapper userMapper, JwtUtils jwtUtils, StringRedisTemplate authTemplate, RandomUtils randomUtils, StatisticsService statisticsService) { this.userMapper = userMapper; this.jwtUtils = jwtUtils; this.authTemplate = authTemplate; this.randomUtils = randomUtils; + this.statisticsService = statisticsService; long count = this.userMapper.selectCountByQuery(new QueryWrapper()); if (count == 0) { - String s = genInviteCode(); + String s = genInviteCode(UserVo.CONSOLE, "0.0.0.0"); log.info("生成了管理员密钥:{},有效期15分钟,若失效请使用控制台命令/key或重启后端重新生成!", s); } } - public void login(UserLoginRequest request, HttpServletResponse response) { + public void login(UserLoginRequest request, HttpServletResponse response, String address) { 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) { + if (count <= 0) { + this.statisticsService.insertAction(Actions.LOGIN, JSONObject.of("status", "error", "msg", "账号或密码错误", "address", address)); Result.failed(HttpStatus.UNAUTHORIZED, "账号或密码错误").writeToResponse(response); return; } UserVo userVo = this.userMapper.selectOneByCondition(findUserCondition); - String token = this.jwtUtils.createToken(userVo.getId()); + long id = userVo.getId(); + String token = this.jwtUtils.createToken(id); response.addHeader("Add-Authorization", token); + this.statisticsService.insertAction(userVo, Actions.LOGIN, JSONObject.of("status", "success", "address", address)); Result.success("登录成功").writeToResponse(response); } - public void register(UserRegisterRequest request, HttpServletResponse response) { + public void register(UserRegisterRequest request, HttpServletResponse response, String address) { String username = request.getUsername(); String password = request.getPassword(); Auth auth = request.getAuth(); if (this.userMapper.selectCountByCondition(USER_VO.NAME.eq(username)) > 0) { + this.statisticsService.insertAction(Actions.REGISTER, JSONObject.of("status", "error", "auth", auth, "msg", "用户名已存在", "address", address)); Result.failed(HttpStatus.CONFLICT).msg("用户名已存在,若需要修改密码,请联系网站管理员处理").writeToResponse(response); return; } + JSONObject statisticsData = JSONObject.of("status", "success", "auth", auth, "address", address); if (auth.equals(Auth.admin)) { String inviteCode = request.getInviteCode(); ValueOperations ops = authTemplate.opsForValue(); String andDelete = ops.getAndDelete(inviteCode); if (andDelete == null) { + this.statisticsService.insertAction(Actions.REGISTER, JSONObject.of("status", "error", "auth", auth, "msg", "邀请码错误", "inviteCode", inviteCode, "address", address)); Result.failed(HttpStatus.NOT_FOUND, "过期或无效的邀请码").writeToResponse(response); return; } + statisticsData.put("inviteCode", inviteCode); } UserVo userVo = new UserVo(username, password, auth); this.userMapper.insert(userVo); - int id = userVo.getId(); + long id = userVo.getId(); String token = this.jwtUtils.createToken(id); response.addHeader("Add-Authorization", token); + this.statisticsService.insertAction(userVo, Actions.REGISTER, statisticsData); Result.success("注册成功!").writeToResponse(response); } - public Result invite() { - String s = genInviteCode(); + public Result invite(UserVo userVo, String address) { + String s = genInviteCode(userVo, address); return Result.success("生成了一个有效期15分钟的邀请码", s); } - public String genInviteCode() { + public String genInviteCode(UserVo actor, String address) { ValueOperations ops = authTemplate.opsForValue(); String s = this.randomUtils.generatorRandomString(8); log.info("生成了新的邀请码:{}", s); + statisticsService.insertAction(actor, Actions.INVITE, address); ops.set(s, "", 15, TimeUnit.MINUTES); return s; } diff --git a/src/main/java/cn/wzpmc/filemanager/utils/AddressArgumentResolver.java b/src/main/java/cn/wzpmc/filemanager/utils/AddressArgumentResolver.java new file mode 100644 index 0000000..580a411 --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/utils/AddressArgumentResolver.java @@ -0,0 +1,31 @@ +package cn.wzpmc.filemanager.utils; + +import cn.wzpmc.filemanager.annotation.Address; +import jakarta.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Slf4j +@Component +public class AddressArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Address.class); + } + + @Override + @Nullable + public Object resolveArgument(@NonNull MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, @NonNull NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { + if (webRequest instanceof ServletWebRequest servletWebRequest) { + return servletWebRequest.getRequest().getRemoteAddr(); + } + return null; + } +} diff --git a/src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java b/src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java index 524c916..155f9df 100644 --- a/src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java +++ b/src/main/java/cn/wzpmc/filemanager/utils/JwtUtils.java @@ -12,7 +12,6 @@ import org.springframework.stereotype.Component; import java.util.Calendar; import java.util.Optional; -import java.util.Random; @Component @Log4j2 @@ -35,7 +34,7 @@ public class JwtUtils { } this.hmacKey = Algorithm.HMAC512(key); } - public String createToken(int uid){ + public String createToken(long uid){ Calendar instance = Calendar.getInstance(); instance.add(Calendar.HOUR,24 * 5); JWTCreator.Builder builder = JWT.create(); diff --git a/src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java b/src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java index 84766d8..8885af6 100644 --- a/src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java +++ b/src/main/java/cn/wzpmc/filemanager/utils/RandomUtils.java @@ -16,4 +16,18 @@ public class RandomUtils { } return builder.toString(); } + public String generatorRandomFileName(int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + double random = Math.random(); + int c = new Random().nextInt(97,122); + if (random < 0.3) { + c = new Random().nextInt(48, 57); + }else if(random < 0.6) { + c = new Random().nextInt(65, 90); + } + builder.append((char) c); + } + return builder.toString(); + } } \ No newline at end of file diff --git a/src/main/java/cn/wzpmc/filemanager/utils/SizeStatisticsDigestInputStream.java b/src/main/java/cn/wzpmc/filemanager/utils/SizeStatisticsDigestInputStream.java new file mode 100644 index 0000000..1a6515e --- /dev/null +++ b/src/main/java/cn/wzpmc/filemanager/utils/SizeStatisticsDigestInputStream.java @@ -0,0 +1,30 @@ +package cn.wzpmc.filemanager.utils; + +import lombok.Getter; + +import java.io.IOException; +import java.io.InputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; + +@Getter +public class SizeStatisticsDigestInputStream extends DigestInputStream { + protected long size = 0; + public SizeStatisticsDigestInputStream(InputStream stream, MessageDigest digest) { + super(stream, digest); + } + + @Override + public int read() throws IOException { + int read = super.read(); + if (read != -1) size++; + return read; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int read = super.read(b, off, len); + if (read != -1) size += read; + return read; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 59fdeb8..6c032d4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -15,8 +15,9 @@ spring: password: "MyCraftAdmin123" servlet: multipart: - max-file-size: 100GB - max-request-size: 100GB + max-file-size: 400GB + max-request-size: 400GB + resolve-lazily: true wzp: filemanager: save-path: "./file"