From ed4239ca80c98b9a5656bddef23a1752659d44ac Mon Sep 17 00:00:00 2001 From: xiaotao Date: Thu, 29 Jul 2021 20:41:00 +0800 Subject: [PATCH 01/18] =?UTF-8?q?feat=20=E9=A6=96=E6=AC=A1=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E5=92=B8=E9=B1=BC=E4=BA=91=E8=87=AA=E5=8A=A8=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E6=95=B0=E6=8D=AE=E5=BA=93=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +-- .../init/ConfigureInitializer.java | 2 +- .../init/DatabaseInitializer.java | 62 +++++++++++++++++++ .../init/DefaultAdminCreator.java | 2 +- db.sql => src/main/resources/db.sql | 2 +- 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/init/DatabaseInitializer.java rename db.sql => src/main/resources/db.sql (97%) diff --git a/README.md b/README.md index ba721e9e..6f533cc1 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,9 @@ ## 快速开始 -### 创建和初始化数据库 -``` -mysql> SOURCE db.sql -``` +### 初始化数据库 +手动创建好空数据库后,只需在`script`目录中的`start.sh`或`start.bat`配置好数据库名,咸鱼云第一次启动时将自动初始化数据表 +> 注意:需要保证数据库为空,不含任何数据表 ### 构建项目 ```shell diff --git a/src/main/java/com/xiaotao/saltedfishcloud/init/ConfigureInitializer.java b/src/main/java/com/xiaotao/saltedfishcloud/init/ConfigureInitializer.java index 223db169..183f78d1 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/init/ConfigureInitializer.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/init/ConfigureInitializer.java @@ -20,7 +20,7 @@ import javax.annotation.Resource; @Component @Slf4j -@Order(1) +@Order(2) public class ConfigureInitializer implements ApplicationRunner { @Resource private ConfigDao configDao; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/init/DatabaseInitializer.java b/src/main/java/com/xiaotao/saltedfishcloud/init/DatabaseInitializer.java new file mode 100644 index 00000000..be8689ab --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/init/DatabaseInitializer.java @@ -0,0 +1,62 @@ +package com.xiaotao.saltedfishcloud.init; + +import lombok.extern.slf4j.Slf4j; +import lombok.var; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.datasource.init.ScriptUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +@Component +@Order(1) +@Slf4j +public class DatabaseInitializer implements ApplicationRunner { + @Resource + private DataSource dataSource; + + /** + * 判断数据库中是否存在数据表
+ * 执行后数据库连接不会被关闭 + * @author xiaotao mjt233@qq.com + * @param conn 数据库连接 + */ + private boolean isTableExist(Connection conn) throws SQLException { + var stat = conn.createStatement(); + // 获取当前数据库名 + var res = stat.executeQuery("SELECT database() AS db_name"); + res.next(); + var dbName = res.getString("db_name"); + + // 获取当前数据库中的所有数据表 + res = stat.executeQuery("SELECT table_name FROM information_schema.columns WHERE table_schema = '" + dbName + "' GROUP BY table_name"); + boolean ret = res.next(); + res.close(); + return ret; + } + + /** + * SpringBoot启动时执行的方法(ApplicationRunner接口) + * @author xiaotao mjt233@qq.com + */ + @Override + public void run(ApplicationArguments args) throws Exception { + var con = dataSource.getConnection(); + + // 若数据库无数据表则先初始化 + if (!isTableExist(con)) { + log.info("[数据库]正在初始化数据表..."); + var resource = new ClassPathResource("db.sql"); + ScriptUtils.executeSqlScript(con, resource); + log.info("[数据库]数据表初始化完成(好耶)"); + } + con.close(); + + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/init/DefaultAdminCreator.java b/src/main/java/com/xiaotao/saltedfishcloud/init/DefaultAdminCreator.java index 1546f570..b3eaeef4 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/init/DefaultAdminCreator.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/init/DefaultAdminCreator.java @@ -14,7 +14,7 @@ import javax.annotation.Resource; @Component @Slf4j -@Order(2) +@Order(3) public class DefaultAdminCreator implements ApplicationRunner { @Resource private UserDao userDao; diff --git a/db.sql b/src/main/resources/db.sql similarity index 97% rename from db.sql rename to src/main/resources/db.sql index 4760c876..b91fbfd7 100644 --- a/db.sql +++ b/src/main/resources/db.sql @@ -83,7 +83,7 @@ CREATE TABLE `user` ( `quota` int unsigned DEFAULT '10', PRIMARY KEY (`id`), UNIQUE KEY `user_index` (`user`) -) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; -- Gitee From 78d81e798ee5db0ed1b4fa4c8eb92e8d356762d2 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 31 Jul 2021 22:49:39 +0800 Subject: [PATCH 02/18] =?UTF-8?q?feat=20=E5=AE=8C=E6=88=90=E6=96=AD?= =?UTF-8?q?=E7=82=B9=E7=BB=AD=E4=BC=A0=E5=9F=BA=E6=9C=AC=E6=A1=86=E6=9E=B6?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ControllerAdvice.java | 11 +++ .../breakpoint/BreakPointController.java | 22 +++++ .../breakpoint/BreakPointControllerImpl.java | 45 ++++++++++ .../service/breakpoint/ProxyProcessor.java | 27 ++++++ .../service/breakpoint/TaskManager.java | 80 +++++++++++++++++ .../breakpoint/annotation/BreakPoint.java | 13 +++ .../breakpoint/config/MappingInitializer.java | 43 +++++++++ .../breakpoint/entity/TaskMetadata.java | 40 +++++++++ .../saltedfishcloud/utils/PathUtils.java | 8 ++ ...3\206\345\220\210.postman_collection.json" | 88 +++++++++++++++++++ 10 files changed, 377 insertions(+) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/BreakPoint.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java index 4ca32c26..7c599103 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java @@ -5,7 +5,9 @@ import com.xiaotao.saltedfishcloud.po.JsonResult; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -13,6 +15,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolationException; import java.io.FileNotFoundException; import java.nio.file.NoSuchFileException; +import java.util.List; /** * 全局异常处理,捕获进入控制器的异常并进行处理 @@ -29,6 +32,14 @@ public class ControllerAdvice { return JsonResult.getInstance(422, null, sb.toString()); } + @ExceptionHandler(BindException.class) + public JsonResult validError(BindException e) { + List errors = e.getFieldErrors(); + StringBuilder sb = new StringBuilder(); + errors.forEach(error -> sb.append(error.getField()).append(' ').append(error.getDefaultMessage()).append(";")); + return JsonResult.getInstance(422, null, sb.toString()); + } + @ExceptionHandler({ConstraintViolationException.class, IllegalArgumentException.class}) public JsonResult paramsError(Exception e) { return JsonResult.getInstance(422, null, e.getMessage()); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java new file mode 100644 index 00000000..b739cf93 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java @@ -0,0 +1,22 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint; + +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +public interface BreakPointController { + + @ResponseBody + @PostMapping + TaskMetadata createTask(TaskMetadata data) throws Exception; + + @ResponseBody + @GetMapping + TaskMetadata queryTask(String id) throws Exception; + + @ResponseBody + @DeleteMapping + Object clearTask(String id) throws Exception; +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java new file mode 100644 index 00000000..d2ba54be --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java @@ -0,0 +1,45 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint; + +import com.xiaotao.saltedfishcloud.po.JsonResult; +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; +import lombok.var; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.NoSuchFileException; + +@Component +public class BreakPointControllerImpl implements BreakPointController { + private final TaskManager manager; + public BreakPointControllerImpl(TaskManager manager) { + this.manager = manager; + } + + @Override + public TaskMetadata createTask(@Validated TaskMetadata data) throws IOException { + String taskId = manager.createTask(data); + data.setTaskId(taskId); + return data; + } + + @Override + public TaskMetadata queryTask(String id) throws Exception { + var res = manager.queryTask(id); + if (res == null) { + throw new FileNotFoundException("任务不存在或已释放"); + } + return res; + } + + @Override + public Object clearTask(String id) throws Exception { + try { + manager.clear(id); + } catch (NoSuchFileException e) { + return JsonResult.getInstance(404, null, "任务不存在或已释放"); + } + return JsonResult.getInstance(); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java new file mode 100644 index 00000000..26836399 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java @@ -0,0 +1,27 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint; + +import lombok.var; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@Component +@Aspect +public class ProxyProcessor { + @Around("@annotation(com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint)") + public Object proxy(ProceedingJoinPoint pjp) throws Throwable { + var req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + if (req.getParameter("breakpoint") == null) { + return pjp.proceed(); + } + + var id = req.getParameter("breakpoint_id"); + + var args = pjp.getArgs(); + + return pjp.proceed(args); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java new file mode 100644 index 00000000..17a52c07 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java @@ -0,0 +1,80 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; +import com.xiaotao.saltedfishcloud.utils.PathUtils; +import lombok.extern.slf4j.Slf4j; +import lombok.var; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +/** + * 断点续传任务管理器,管理 + */ +@Slf4j +@Component +public class TaskManager { + private final ObjectMapper mapper = new ObjectMapper(); + + /** + * 获取任务数据文件夹路径 + * @param id 任务ID + */ + private Path getTaskDir(String id) { + return Paths.get(PathUtils.getTempDirectory() + "/xyy/" + id); + } + + /** + * 创建断点续传任务 + * @param info 任务元数据 + * @return 创建成功后的任务ID + * @throws IOException 任务数据存储目录无法写入 + */ + public String createTask(TaskMetadata info) throws IOException { + var id = UUID.randomUUID().toString(); + info.setTaskId(id); + var taskDir = getTaskDir(id); + Files.createDirectories(taskDir); + Files.write(Paths.get(taskDir + "/metadata.json"), mapper.writeValueAsBytes(info)); + + log.debug("Create Breakpoint Task:" + taskDir); + return id; + } + + /** + * 查询任务信息 + * @param id 任务ID + * @return 任务信息 + * @throws IOException 任务不存在或目录不可读 + */ + public TaskMetadata queryTask(String id) throws IOException { + var metadataPath = Paths.get(getTaskDir(id) +"/metadata.json"); + if (!Files.exists(metadataPath)) { + return null; + } + + return mapper.readValue(Files.readAllBytes(metadataPath), TaskMetadata.class); + } + + /** + * 清理指定的任务数据 + * @param id 任务ID + * @throws IOException 目录不可写或任务不存在 + */ + public void clear(String id) throws IOException { + var taskPath = getTaskDir(id); + Files.list(taskPath).forEach(path -> { + try { + Files.delete(path); + } catch (IOException e) { + e.printStackTrace(); + } + }); + Files.delete(taskPath); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/BreakPoint.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/BreakPoint.java new file mode 100644 index 00000000..3157d55d --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/BreakPoint.java @@ -0,0 +1,13 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.annotation; + +import java.lang.annotation.*; + +/** + * 标记控制器方法支持使用断点续传 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface BreakPoint { + +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java new file mode 100644 index 00000000..e3049fc3 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java @@ -0,0 +1,43 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.config; + +import com.xiaotao.saltedfishcloud.service.breakpoint.BreakPointController; +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; +import lombok.var; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.lang.reflect.Method; + +/** + * 初始化断点续传管理API,注册路由 + */ +@Component +public class MappingInitializer { + private final BreakPointController controller; + private final RequestMappingHandlerMapping mappingHandler; + + MappingInitializer(BreakPointController controller, RequestMappingHandlerMapping mappingHandler) throws NoSuchMethodException { + this.controller = controller; + this.mappingHandler = mappingHandler; + init(); + } + + /** + * 执行初始化,注册创建、查询与删除任务的路由 + */ + private void init() throws NoSuchMethodException { + var createMethod = controller.getClass().getMethod("createTask", TaskMetadata.class); + var queryMethod = controller.getClass().getMethod("queryTask", String.class); + var deleteMethod = controller.getClass().getMethod("clearTask", String.class); + registerMapping(createMethod, RequestMethod.POST); + registerMapping(queryMethod, RequestMethod.GET); + registerMapping(deleteMethod, RequestMethod.DELETE); + } + + private void registerMapping(Method handlerMethod, RequestMethod...method) { + var info = RequestMappingInfo.paths("/api/breakpoint").methods(method).build(); + mappingHandler.registerMapping(info, controller, handlerMethod); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java new file mode 100644 index 00000000..20347288 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java @@ -0,0 +1,40 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import jdk.nashorn.internal.objects.annotations.Getter; +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class TaskMetadata { + private String taskId; + /** + * 文件名 + */ + @NotBlank + private String fileName; + /** + * 文件长度 + */ + private int length; + + /** + * 每个分块的大小(默认2MiB) + */ + private int chunkSize = 2097152; + + /** + * 获取整个任务的总分块数量 + */ + @JsonInclude + public int getChunkCount() { + return (int)Math.ceil((double)length / chunkSize); + } + +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/utils/PathUtils.java b/src/main/java/com/xiaotao/saltedfishcloud/utils/PathUtils.java index bb9b0625..1de2a4bd 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/utils/PathUtils.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/utils/PathUtils.java @@ -5,10 +5,18 @@ import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.helper.PathBuilder; import com.xiaotao.saltedfishcloud.po.User; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; public class PathUtils { + /** + * 获取系统临时目录 + */ + public static String getTempDirectory() { + return System.getProperty("java.io.tmpdir"); + } /** * 提取一个文件的完整本地路径中 相对网盘的路径
diff --git "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" index 576861db..47a1965f 100644 --- "a/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" +++ "b/\345\222\270\351\261\274\344\272\221Postman\346\265\213\350\257\225\351\233\206\345\220\210.postman_collection.json" @@ -327,6 +327,94 @@ { "name": "系统资源操作", "item": [ + { + "name": "断点续传任务", + "item": [ + { + "name": "创建断点续传任务", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "{{addr}}/api/breakpoint?fileName=test.mp4&length=2097153", + "host": [ + "{{addr}}" + ], + "path": [ + "api", + "breakpoint" + ], + "query": [ + { + "key": "fileName", + "value": "test.mp4", + "description": "文件名" + }, + { + "key": "length", + "value": "2097153", + "description": "文件长度" + } + ] + }, + "description": "创建断点续传任务\r\n\r\n- URI:`/api/breakpoint/`\r\n- 动作:POST\r\n\r\n### 参数\r\n- fileName - 文件名\r\n- length - 文件长度\r\n\r\n### 响应\r\n创建成功后响应任务信息\r\n```json\r\n{\r\n \"taskId\": \"67ab0bc9-92d0-4b4c-ae22-f9e4b4196bec\", // 任务ID\r\n \"fileName\": \"test.mp4\", // 文件名\r\n \"length\": 2097153, // 文件长度\r\n \"chunkSize\": 2097152, // 每分块长度\r\n \"chunkCount\": 2 // 总分块数量\r\n}\r\n```" + }, + "response": [] + }, + { + "name": "获取任务信息", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{addr}}/api/breakpoint?id=b2c1ad18-6b93-4da3-872a-4f82dfeb0a8f", + "host": [ + "{{addr}}" + ], + "path": [ + "api", + "breakpoint" + ], + "query": [ + { + "key": "id", + "value": "b2c1ad18-6b93-4da3-872a-4f82dfeb0a8f", + "description": "任务ID" + } + ] + }, + "description": "获取断点续传任务的详细信息\r\n\r\n- URI:`/api/breakpoint/`\r\n- 动作:GET\r\n\r\n### 参数\r\n- id - 要查询的任务ID\r\n\r\n### 响应\r\n创建成功后响应任务信息\r\n```json\r\n{\r\n \"taskId\": \"67ab0bc9-92d0-4b4c-ae22-f9e4b4196bec\", // 任务ID\r\n \"fileName\": \"test.mp4\", // 文件名\r\n \"length\": 2097153, // 文件长度\r\n \"chunkSize\": 2097152, // 每分块长度\r\n \"chunkCount\": 2 // 总分块数量\r\n}\r\n```" + }, + "response": [] + }, + { + "name": "结束断点续传任务", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{addr}}/api/breakpoint?id=b2c1ad18-6b93-4da3-872a-4f82dfeb0a8f", + "host": [ + "{{addr}}" + ], + "path": [ + "api", + "breakpoint" + ], + "query": [ + { + "key": "id", + "value": "b2c1ad18-6b93-4da3-872a-4f82dfeb0a8f", + "description": "任务ID" + } + ] + }, + "description": "结束断点续传任务,操作将使服务器释放对应的资源,同时会导致该断点续传任务不再可用\r\n\r\n- URI:`/api/breakpoint/`\r\n- 动作:DELETE\r\n\r\n### 参数:\r\n- id - 要结束的任务ID" + }, + "response": [] + } + ] + }, { "name": "解析节点ID", "request": { -- Gitee From 5e9ae1228990769520fe0b8aafd53c08671e241e Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 31 Jul 2021 23:07:51 +0800 Subject: [PATCH 03/18] =?UTF-8?q?feat=20=E9=80=9A=E8=BF=87AOP=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=96=AD=E7=82=B9=E7=BB=AD=E4=BC=A0=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ControllerAdvice.java | 7 ++++++- .../controller/FileController.java | 2 ++ .../service/breakpoint/ProxyProcessor.java | 15 ++++++++++++--- .../service/breakpoint/TaskManager.java | 4 ++-- .../BreakPointTaskNotFoundException.java | 13 +++++++++++++ 5 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/BreakPointTaskNotFoundException.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java index 7c599103..7ecce03d 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java @@ -2,6 +2,7 @@ package com.xiaotao.saltedfishcloud.controller; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.security.access.AccessDeniedException; @@ -57,7 +58,11 @@ public class ControllerAdvice { return JsonResult.getInstance(403, null, e.getMessage()); } - @ExceptionHandler({FileNotFoundException.class, NoSuchFileException.class}) + @ExceptionHandler({ + FileNotFoundException.class, + NoSuchFileException.class, + BreakPointTaskNotFoundException.class + }) public JsonResult handle(Exception e) { if (log.isDebugEnabled()) { e.printStackTrace(); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java index beabf7ef..5a665594 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java @@ -12,6 +12,7 @@ import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.po.param.FileCopyOrMoveInfo; import com.xiaotao.saltedfishcloud.po.param.FileNameList; import com.xiaotao.saltedfishcloud.po.param.NamePair; +import com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint; import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.service.http.ResponseService; import com.xiaotao.saltedfishcloud.utils.URLUtils; @@ -74,6 +75,7 @@ public class FileController { * @param md5 文件MD5 */ @PutMapping("file/**") + @BreakPoint public JsonResult upload(HttpServletRequest request, @PathVariable @UID(true) int uid, @RequestParam("file") MultipartFile file, diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java index 26836399..6d73b050 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java @@ -1,5 +1,6 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; import lombok.var; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -8,18 +9,26 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import javax.annotation.Resource; + @Component @Aspect public class ProxyProcessor { + @Resource + private TaskManager manager; + @Around("@annotation(com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint)") public Object proxy(ProceedingJoinPoint pjp) throws Throwable { var req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); - if (req.getParameter("breakpoint") == null) { + var id = req.getParameter("breakpoint_id"); + if (id == null) { return pjp.proceed(); } - var id = req.getParameter("breakpoint_id"); - + var taskInfo = manager.queryTask(id); + if (taskInfo == null) { + throw new BreakPointTaskNotFoundException(id); + } var args = pjp.getArgs(); return pjp.proceed(args); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java index 17a52c07..13ece9ac 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java @@ -49,8 +49,8 @@ public class TaskManager { /** * 查询任务信息 * @param id 任务ID - * @return 任务信息 - * @throws IOException 任务不存在或目录不可读 + * @return 任务信息,若任务不存在则返回Null + * @throws IOException 目录读取出错 */ public TaskMetadata queryTask(String id) throws IOException { var metadataPath = Paths.get(getTaskDir(id) +"/metadata.json"); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/BreakPointTaskNotFoundException.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/BreakPointTaskNotFoundException.java new file mode 100644 index 00000000..76d90a0b --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/BreakPointTaskNotFoundException.java @@ -0,0 +1,13 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.exception; + +/** + * 断点续传任务不存在 + */ +public class BreakPointTaskNotFoundException extends RuntimeException { + /** + * @param id 任务ID + */ + public BreakPointTaskNotFoundException(String id) { + super("断点续传任务" + id + "不存在或已被移除"); + } +} -- Gitee From 5b929c209630b4ad3ddd67b24737dc3b95f272e3 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 31 Jul 2021 23:11:01 +0800 Subject: [PATCH 04/18] =?UTF-8?q?refactor=20=E6=97=A0=E6=95=88=E6=96=AD?= =?UTF-8?q?=E7=82=B9=E7=BB=AD=E4=BC=A0=E4=BB=BB=E5=8A=A1ID=E6=8A=9B?= =?UTF-8?q?=E5=87=BA=E7=BB=9F=E4=B8=80=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/breakpoint/BreakPointControllerImpl.java | 6 +++--- .../saltedfishcloud/service/breakpoint/TaskManager.java | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java index d2ba54be..e7d4ee75 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java @@ -2,11 +2,11 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; import lombok.var; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.NoSuchFileException; @@ -28,7 +28,7 @@ public class BreakPointControllerImpl implements BreakPointController { public TaskMetadata queryTask(String id) throws Exception { var res = manager.queryTask(id); if (res == null) { - throw new FileNotFoundException("任务不存在或已释放"); + throw new BreakPointTaskNotFoundException(id); } return res; } @@ -38,7 +38,7 @@ public class BreakPointControllerImpl implements BreakPointController { try { manager.clear(id); } catch (NoSuchFileException e) { - return JsonResult.getInstance(404, null, "任务不存在或已释放"); + throw new BreakPointTaskNotFoundException(id); } return JsonResult.getInstance(); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java index 13ece9ac..cf0081a4 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java @@ -2,6 +2,7 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; import com.fasterxml.jackson.databind.ObjectMapper; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; import com.xiaotao.saltedfishcloud.utils.PathUtils; import lombok.extern.slf4j.Slf4j; import lombok.var; @@ -55,7 +56,7 @@ public class TaskManager { public TaskMetadata queryTask(String id) throws IOException { var metadataPath = Paths.get(getTaskDir(id) +"/metadata.json"); if (!Files.exists(metadataPath)) { - return null; + throw new BreakPointTaskNotFoundException(id); } return mapper.readValue(Files.readAllBytes(metadataPath), TaskMetadata.class); @@ -68,6 +69,9 @@ public class TaskManager { */ public void clear(String id) throws IOException { var taskPath = getTaskDir(id); + if (!Files.exists(taskPath)) { + throw new BreakPointTaskNotFoundException(id); + } Files.list(taskPath).forEach(path -> { try { Files.delete(path); -- Gitee From 8d13ed615e2bf5e65e4b07cd3ac2120c59a89ab5 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 31 Jul 2021 23:26:08 +0800 Subject: [PATCH 05/18] =?UTF-8?q?docs=20=E5=AE=8C=E5=96=84=E6=96=AD?= =?UTF-8?q?=E7=82=B9=E7=BB=AD=E4=BC=A0=E6=A8=A1=E5=9D=97=E7=9A=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../breakpoint/BreakPointController.java | 18 ++++++++++++++++++ .../breakpoint/BreakPointControllerImpl.java | 3 +++ .../service/breakpoint/ProxyProcessor.java | 7 +++++++ .../service/breakpoint/TaskManager.java | 14 +++++++++++++- .../breakpoint/config/MappingInitializer.java | 1 + .../breakpoint/entity/TaskMetadata.java | 3 +++ 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java index b739cf93..7f826d77 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java @@ -6,16 +6,34 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; +/** + * 断点续传管理控制器接口,用于处理和响应Web请求的断点续传任务管理API + */ public interface BreakPointController { + /** + * 创建断点续传任务 + * @param data 任务元数据,应当包含fileName和length + * @return 任务被创建完后完整的任务元数据,必须包含任务ID + */ @ResponseBody @PostMapping TaskMetadata createTask(TaskMetadata data) throws Exception; + + /** + * 查询断点续传任务信息状态 + * @param id 要查询的ID + * @return 任务数据信息 + */ @ResponseBody @GetMapping TaskMetadata queryTask(String id) throws Exception; + /** + * 移除断点续传任务并释放资源 + * @param id 要查询的ID + */ @ResponseBody @DeleteMapping Object clearTask(String id) throws Exception; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java index e7d4ee75..acafba69 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java @@ -10,6 +10,9 @@ import org.springframework.validation.annotation.Validated; import java.io.IOException; import java.nio.file.NoSuchFileException; +/** + * 断点续传管理控制器的实现类 + */ @Component public class BreakPointControllerImpl implements BreakPointController { private final TaskManager manager; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java index 6d73b050..7266baba 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java @@ -11,6 +11,13 @@ import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; +/** + * 断点续传的代理处理器,处理被注解{@link com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint}标记的控制器方法
+ * 当HTTP请求的URI Query String Parameter中包含breakpoint_id时,表示文件上传使用了断点续传
+ * 若断点续传任务已完成,将拼装成完整文件,同时构造MultipartFile用于替换控制器对应的原参数,控制器能直接访问处理完成的断点续传文件
+ *
+ * 控制器方法成功执行无异常后,将会释放对应的断点续传任务数据 + */ @Component @Aspect public class ProxyProcessor { diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java index cf0081a4..64ddb5ea 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java @@ -9,13 +9,14 @@ import lombok.var; import org.springframework.stereotype.Component; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.UUID; /** - * 断点续传任务管理器,管理 + * 断点续传任务管理器,管理任务的创建,查询,删除和文件块的存储 */ @Slf4j @Component @@ -81,4 +82,15 @@ public class TaskManager { }); Files.delete(taskPath); } + + /** + * 保存部分的断点续传任务文件片段 + * @TODO 方法待实现 + * @param id 任务ID + * @param part 文件块编号(从1开始) + * @param stream 文件流 + */ + public void save(String id, int part, InputStream stream) { + + } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java index e3049fc3..1ec109b7 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java @@ -18,6 +18,7 @@ public class MappingInitializer { private final BreakPointController controller; private final RequestMappingHandlerMapping mappingHandler; + // 通过构造方法获取依赖的Bean,随后执行初始化任务 MappingInitializer(BreakPointController controller, RequestMappingHandlerMapping mappingHandler) throws NoSuchMethodException { this.controller = controller; this.mappingHandler = mappingHandler; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java index 20347288..4513bba6 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java @@ -10,6 +10,9 @@ import lombok.Setter; import javax.validation.constraints.NotBlank; +/** + * 断点续传任务元数据信息类 + */ @Data @JsonIgnoreProperties(ignoreUnknown = true) public class TaskMetadata { -- Gitee From e3de1e74ef089945d3f96fac639038187e2c5d31 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sun, 1 Aug 2021 09:55:25 +0800 Subject: [PATCH 06/18] =?UTF-8?q?refactor=20=E4=BD=BF=E7=94=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=B1=BB=E7=AE=A1=E7=90=86=E6=96=AD=E7=82=B9=E7=BB=AD?= =?UTF-8?q?=E4=BC=A0Bean=E7=9A=84=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../breakpoint/BreakPointControllerImpl.java | 2 - .../service/breakpoint/ProxyProcessor.java | 8 +-- .../service/breakpoint/TaskManager.java | 2 - .../config/BreakPointConfigurator.java | 52 +++++++++++++++++++ .../breakpoint/config/MappingInitializer.java | 2 - .../breakpoint/entity/TaskMetadata.java | 4 -- 6 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java index acafba69..b8e66f20 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java @@ -4,7 +4,6 @@ import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; import lombok.var; -import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import java.io.IOException; @@ -13,7 +12,6 @@ import java.nio.file.NoSuchFileException; /** * 断点续传管理控制器的实现类 */ -@Component public class BreakPointControllerImpl implements BreakPointController { private final TaskManager manager; public BreakPointControllerImpl(TaskManager manager) { diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java index 7266baba..1b9372f1 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java @@ -5,7 +5,6 @@ import lombok.var; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -18,11 +17,12 @@ import javax.annotation.Resource; *
* 控制器方法成功执行无异常后,将会释放对应的断点续传任务数据 */ -@Component @Aspect public class ProxyProcessor { - @Resource - private TaskManager manager; + private final TaskManager manager; + public ProxyProcessor(TaskManager taskManager) { + manager = taskManager; + } @Around("@annotation(com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint)") public Object proxy(ProceedingJoinPoint pjp) throws Throwable { diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java index 64ddb5ea..d0c941c5 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java @@ -6,7 +6,6 @@ import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNo import com.xiaotao.saltedfishcloud.utils.PathUtils; import lombok.extern.slf4j.Slf4j; import lombok.var; -import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; @@ -19,7 +18,6 @@ import java.util.UUID; * 断点续传任务管理器,管理任务的创建,查询,删除和文件块的存储 */ @Slf4j -@Component public class TaskManager { private final ObjectMapper mapper = new ObjectMapper(); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java new file mode 100644 index 00000000..769bb7c3 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java @@ -0,0 +1,52 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.config; + +import com.xiaotao.saltedfishcloud.service.breakpoint.BreakPointController; +import com.xiaotao.saltedfishcloud.service.breakpoint.BreakPointControllerImpl; +import com.xiaotao.saltedfishcloud.service.breakpoint.ProxyProcessor; +import com.xiaotao.saltedfishcloud.service.breakpoint.TaskManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import javax.annotation.Resource; + +/** + * 断点续传相关Bean自动配置类 + */ +@Configuration +public class BreakPointConfigurator { + @Resource + private RequestMappingHandlerMapping mapping; + + /** + * 控制路由注册 + */ + @Bean + public MappingInitializer mappingInitializer() throws NoSuchMethodException { + return new MappingInitializer(controller(), mapping); + } + + /** + * 任务管理API控制器 + */ + @Bean + public BreakPointController controller() { + return new BreakPointControllerImpl(taskManager()); + } + + /** + * 任务管理器 + */ + @Bean + public TaskManager taskManager() { + return new TaskManager(); + } + + /** + * 控制器代理增强 + */ + @Bean + public ProxyProcessor proxyProcessor() { + return new ProxyProcessor(taskManager()); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java index 1ec109b7..f2a86f81 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java @@ -3,7 +3,6 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.config; import com.xiaotao.saltedfishcloud.service.breakpoint.BreakPointController; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; import lombok.var; -import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -13,7 +12,6 @@ import java.lang.reflect.Method; /** * 初始化断点续传管理API,注册路由 */ -@Component public class MappingInitializer { private final BreakPointController controller; private final RequestMappingHandlerMapping mappingHandler; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java index 4513bba6..5ba0a0c6 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java @@ -1,12 +1,8 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.entity; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; -import jdk.nashorn.internal.objects.annotations.Getter; -import lombok.AccessLevel; import lombok.Data; -import lombok.Setter; import javax.validation.constraints.NotBlank; -- Gitee From a27f8d87c19aebf30eb7c3badbe035d3c4d3799f Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sun, 1 Aug 2021 19:55:41 +0800 Subject: [PATCH 07/18] =?UTF-8?q?feat=20=E5=AE=9E=E7=8E=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=88=87=E7=89=87=E5=88=86=E5=9D=97=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ControllerAdvice.java | 4 +- .../breakpoint/BreakPointController.java | 10 ++-- .../breakpoint/BreakPointControllerImpl.java | 13 +++-- .../service/breakpoint/ProxyProcessor.java | 6 +-- .../service/breakpoint/TaskManager.java | 50 +++++++++++-------- .../breakpoint/config/MappingInitializer.java | 10 +++- .../breakpoint/entity/TaskMetadata.java | 34 +++++++++++-- .../breakpoint/entity/TaskStatMetadata.java | 40 +++++++++++++++ ...eption.java => TaskNotFoundException.java} | 4 +- .../service/breakpoint/utils/PartParser.java | 50 +++++++++++++++++++ .../breakpoint/utils/TaskStorePath.java | 33 ++++++++++++ .../breakpoint/utils/PartParserTest.java | 23 +++++++++ 12 files changed, 236 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java rename src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/{BreakPointTaskNotFoundException.java => TaskNotFoundException.java} (63%) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java create mode 100644 src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java index 7ecce03d..b3c7d307 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java @@ -2,7 +2,7 @@ package com.xiaotao.saltedfishcloud.controller; import com.xiaotao.saltedfishcloud.exception.HasResultException; import com.xiaotao.saltedfishcloud.po.JsonResult; -import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.security.access.AccessDeniedException; @@ -61,7 +61,7 @@ public class ControllerAdvice { @ExceptionHandler({ FileNotFoundException.class, NoSuchFileException.class, - BreakPointTaskNotFoundException.class + TaskNotFoundException.class }) public JsonResult handle(Exception e) { if (log.isDebugEnabled()) { diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java index 7f826d77..705310f8 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointController.java @@ -1,16 +1,18 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; /** * 断点续传管理控制器接口,用于处理和响应Web请求的断点续传任务管理API */ public interface BreakPointController { + @ResponseBody + @PostMapping + Object uploadPart(MultipartFile file, @PathVariable String id, @PathVariable String part) throws Exception; + /** * 创建断点续传任务 * @param data 任务元数据,应当包含fileName和length diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java index b8e66f20..d846868d 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java @@ -2,9 +2,10 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; -import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; import lombok.var; import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.NoSuchFileException; @@ -18,6 +19,12 @@ public class BreakPointControllerImpl implements BreakPointController { this.manager = manager; } + @Override + public Object uploadPart(MultipartFile file, String id, String part) throws IOException { + manager.save(id, part, file.getInputStream()); + return JsonResult.getInstance(); + } + @Override public TaskMetadata createTask(@Validated TaskMetadata data) throws IOException { String taskId = manager.createTask(data); @@ -29,7 +36,7 @@ public class BreakPointControllerImpl implements BreakPointController { public TaskMetadata queryTask(String id) throws Exception { var res = manager.queryTask(id); if (res == null) { - throw new BreakPointTaskNotFoundException(id); + throw new TaskNotFoundException(id); } return res; } @@ -39,7 +46,7 @@ public class BreakPointControllerImpl implements BreakPointController { try { manager.clear(id); } catch (NoSuchFileException e) { - throw new BreakPointTaskNotFoundException(id); + throw new TaskNotFoundException(id); } return JsonResult.getInstance(); } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java index 1b9372f1..4926d8a5 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java @@ -1,6 +1,6 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; -import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; import lombok.var; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -8,8 +8,6 @@ import org.aspectj.lang.annotation.Aspect; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import javax.annotation.Resource; - /** * 断点续传的代理处理器,处理被注解{@link com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint}标记的控制器方法
* 当HTTP请求的URI Query String Parameter中包含breakpoint_id时,表示文件上传使用了断点续传
@@ -34,7 +32,7 @@ public class ProxyProcessor { var taskInfo = manager.queryTask(id); if (taskInfo == null) { - throw new BreakPointTaskNotFoundException(id); + throw new TaskNotFoundException(id); } var args = pjp.getArgs(); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java index d0c941c5..48f5bfb8 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java @@ -2,16 +2,19 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; import com.fasterxml.jackson.databind.ObjectMapper; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; -import com.xiaotao.saltedfishcloud.service.breakpoint.exception.BreakPointTaskNotFoundException; -import com.xiaotao.saltedfishcloud.utils.PathUtils; +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskStatMetadata; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; +import com.xiaotao.saltedfishcloud.service.breakpoint.utils.PartParser; +import com.xiaotao.saltedfishcloud.service.breakpoint.utils.TaskStorePath; import lombok.extern.slf4j.Slf4j; import lombok.var; +import org.springframework.util.StreamUtils; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.UUID; /** @@ -21,13 +24,6 @@ import java.util.UUID; public class TaskManager { private final ObjectMapper mapper = new ObjectMapper(); - /** - * 获取任务数据文件夹路径 - * @param id 任务ID - */ - private Path getTaskDir(String id) { - return Paths.get(PathUtils.getTempDirectory() + "/xyy/" + id); - } /** * 创建断点续传任务 @@ -38,9 +34,9 @@ public class TaskManager { public String createTask(TaskMetadata info) throws IOException { var id = UUID.randomUUID().toString(); info.setTaskId(id); - var taskDir = getTaskDir(id); + var taskDir = TaskStorePath.getRoot(id); Files.createDirectories(taskDir); - Files.write(Paths.get(taskDir + "/metadata.json"), mapper.writeValueAsBytes(info)); + Files.write(TaskStorePath.getMetadata(id),mapper.writeValueAsBytes(info)); log.debug("Create Breakpoint Task:" + taskDir); return id; @@ -52,13 +48,14 @@ public class TaskManager { * @return 任务信息,若任务不存在则返回Null * @throws IOException 目录读取出错 */ - public TaskMetadata queryTask(String id) throws IOException { - var metadataPath = Paths.get(getTaskDir(id) +"/metadata.json"); + public TaskStatMetadata queryTask(String id) throws IOException { + var metadataPath = TaskStorePath.getMetadata(id); if (!Files.exists(metadataPath)) { - throw new BreakPointTaskNotFoundException(id); + throw new TaskNotFoundException(id); } - return mapper.readValue(Files.readAllBytes(metadataPath), TaskMetadata.class); + var basicInfo = mapper.readValue(Files.readAllBytes(metadataPath), TaskMetadata.class); + return new TaskStatMetadata(basicInfo); } /** @@ -67,9 +64,9 @@ public class TaskManager { * @throws IOException 目录不可写或任务不存在 */ public void clear(String id) throws IOException { - var taskPath = getTaskDir(id); + var taskPath = TaskStorePath.getRoot(id); if (!Files.exists(taskPath)) { - throw new BreakPointTaskNotFoundException(id); + throw new TaskNotFoundException(id); } Files.list(taskPath).forEach(path -> { try { @@ -83,12 +80,23 @@ public class TaskManager { /** * 保存部分的断点续传任务文件片段 - * @TODO 方法待实现 * @param id 任务ID * @param part 文件块编号(从1开始) * @param stream 文件流 */ - public void save(String id, int part, InputStream stream) { - + public void save(String id, String part, InputStream stream) throws IOException { + var root = TaskStorePath.getRoot(id); + if (!Files.exists(root)) { + throw new TaskNotFoundException(id); + } + var parts = PartParser.parse(part); + var taskInfo = queryTask(id); + for (int i : parts) { + var size = taskInfo.getPartSize(i); + var out = Files.newOutputStream(TaskStorePath.getPartFile(id, i)); + StreamUtils.copyRange(stream, out, 0, size - 1); + out.close(); + } + stream.close(); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java index f2a86f81..d5404187 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/MappingInitializer.java @@ -4,6 +4,7 @@ import com.xiaotao.saltedfishcloud.service.breakpoint.BreakPointController; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; import lombok.var; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -13,6 +14,7 @@ import java.lang.reflect.Method; * 初始化断点续传管理API,注册路由 */ public class MappingInitializer { + private static final String PREFIX = "/api/breakpoint"; private final BreakPointController controller; private final RequestMappingHandlerMapping mappingHandler; @@ -30,13 +32,19 @@ public class MappingInitializer { var createMethod = controller.getClass().getMethod("createTask", TaskMetadata.class); var queryMethod = controller.getClass().getMethod("queryTask", String.class); var deleteMethod = controller.getClass().getMethod("clearTask", String.class); + var uploadMethod = controller.getClass().getMethod("uploadPart", MultipartFile.class, String.class, String.class); registerMapping(createMethod, RequestMethod.POST); registerMapping(queryMethod, RequestMethod.GET); registerMapping(deleteMethod, RequestMethod.DELETE); + registerMapping(PREFIX + "/{id}/{part}", uploadMethod); } private void registerMapping(Method handlerMethod, RequestMethod...method) { - var info = RequestMappingInfo.paths("/api/breakpoint").methods(method).build(); + registerMapping(PREFIX, handlerMethod, method); + } + + private void registerMapping(String url, Method handlerMethod, RequestMethod...method) { + var info = RequestMappingInfo.paths(url).methods(method).build(); mappingHandler.registerMapping(info, controller, handlerMethod); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java index 5ba0a0c6..52aade84 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java @@ -2,7 +2,9 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.entity; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AccessLevel; import lombok.Data; +import lombok.Setter; import javax.validation.constraints.NotBlank; @@ -26,14 +28,38 @@ public class TaskMetadata { /** * 每个分块的大小(默认2MiB) */ + @Setter(AccessLevel.NONE) private int chunkSize = 2097152; + @Setter(AccessLevel.NONE) + private int chunkCount = 0; + + public void setLength(int length) { + this.length = length; + this.chunkCount = (int)Math.ceil((double)length / chunkSize); + } + + /** + * 取最后一个文件块的大小 + */ + public int getLastChunkSize() { + int res = length % chunkSize; + return res == 0 ? chunkSize : res; + } + /** - * 获取整个任务的总分块数量 + * 获取某个文件块的大小 + * @param part 文件块序号 */ - @JsonInclude - public int getChunkCount() { - return (int)Math.ceil((double)length / chunkSize); + public int getPartSize(int part) { + if (part > chunkCount) { + throw new IndexOutOfBoundsException(); + } + if (part == chunkCount) { + return getLastChunkSize(); + } else { + return chunkSize; + } } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java new file mode 100644 index 00000000..c0d7e082 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java @@ -0,0 +1,40 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.entity; + +import com.xiaotao.saltedfishcloud.service.breakpoint.utils.PartParser; +import com.xiaotao.saltedfishcloud.service.breakpoint.utils.TaskStorePath; +import lombok.var; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; + +/** + * 任务状态数据,除了元数据本身,还附加了任务的当前完成状态 + */ +public class TaskStatMetadata extends TaskMetadata { + private final ArrayList finish = new ArrayList<>(); + + public ArrayList getFinish() { + return finish; + } + + public TaskStatMetadata(TaskMetadata data) throws IOException { + setTaskId(data.getTaskId()); + setFileName(data.getFileName()); + setLength(data.getLength()); + fresh(); + } + + + public void fresh() throws IOException { + Files.list(TaskStorePath.getRoot(getTaskId())) + .filter(e -> e.endsWith(".part")) + .forEach(path -> { + var p = path.toString().replaceAll(".part", ""); + for (int i : PartParser.parse(p)) { + finish.add(i); + } + }); + } + +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/BreakPointTaskNotFoundException.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/TaskNotFoundException.java similarity index 63% rename from src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/BreakPointTaskNotFoundException.java rename to src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/TaskNotFoundException.java index 76d90a0b..7e996ba6 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/BreakPointTaskNotFoundException.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/exception/TaskNotFoundException.java @@ -3,11 +3,11 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.exception; /** * 断点续传任务不存在 */ -public class BreakPointTaskNotFoundException extends RuntimeException { +public class TaskNotFoundException extends RuntimeException { /** * @param id 任务ID */ - public BreakPointTaskNotFoundException(String id) { + public TaskNotFoundException(String id) { super("断点续传任务" + id + "不存在或已被移除"); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java new file mode 100644 index 00000000..e68dc664 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java @@ -0,0 +1,50 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.utils; + +import lombok.var; + +public class PartParser { + /** + * 验证文件块格式是否正确 + * @param part 文件块 + */ + public static boolean validate(String part) { + if (!part.matches("^(\\d+-)?\\d+$")) { + return false; + } + + if(part.matches("^\\d+$")) { + return true; + } + + var s = part.split("-"); + try { + if (Integer.parseInt(s[1]) <= Integer.parseInt(s[0])) { + return false; + } + } catch (NumberFormatException e) { + return false; + } + return true; + } + + /** + * 解析文件块描述为文件块数组 + * @param part 文件块 + */ + public static int[] parse(String part) { + if (!validate(part)) { + throw new IllegalArgumentException("无效的文件块描述:" + part); + } + int[] pair; + var t = part.split("-", 2); + pair = new int[2]; + pair[0] = Integer.parseInt(t[0]); + pair[1] = Integer.parseInt(t[1]); + var size = pair[1] - pair[0] + 1; + int[] res = new int[size]; + for (int i = 0; i < res.length; i++) { + res[i] = pair[0] + i; + } + return res; + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java new file mode 100644 index 00000000..8e682ae3 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java @@ -0,0 +1,33 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.utils; + +import com.xiaotao.saltedfishcloud.utils.PathUtils; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class TaskStorePath { + /** + * 获取任务数据文件夹路径 + * @param id 任务ID + */ + public static Path getRoot(String id) { + return Paths.get(PathUtils.getTempDirectory() + "/xyy/" + id); + } + + /** + * 获取文件块路径 + * @param id 任务ID + * @param part 文件块 + */ + public static Path getPartFile(String id, int part) { + return Paths.get(getRoot(id) + "/" + part + ".part"); + } + + /** + * 获取任务元数据路径 + * @param id 任务ID + */ + public static Path getMetadata(String id) { + return Paths.get(getRoot(id) + "/metadata.json"); + } +} diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java new file mode 100644 index 00000000..f979a9c8 --- /dev/null +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java @@ -0,0 +1,23 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + + +class PartParserTest { + + @Test + void parse() { + int[] parse = PartParser.parse("11-13"); + for (int i : parse) { + System.out.println(i); + } + } + + @Test + void validate() { + assertTrue(PartParser.validate("1-1")); + assertFalse(PartParser.validate("1a-1")); + } +} -- Gitee From 093aed3644899fa1a2d5d9188db7f9ea19ccc9d6 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Mon, 2 Aug 2021 23:20:28 +0800 Subject: [PATCH 08/18] =?UTF-8?q?feat=20=E5=AE=8C=E6=88=90=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E8=BE=93=E5=85=A5=E6=B5=81=E5=92=8C=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E6=B5=81=E7=94=9F=E6=88=90=E5=99=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../breakpoint/MergeMultipartFile.java | 64 +++++++++++ .../service/breakpoint/TaskManager.java | 2 - .../breakpoint/entity/TaskMetadata.java | 33 +++--- .../breakpoint/entity/TaskStatMetadata.java | 66 +++++++++--- .../merge/InputStreamGenerator.java | 16 +++ .../breakpoint/merge/MergeInputStream.java | 100 ++++++++++++++++++ ...MultipleFileMergeInputStreamGenerator.java | 25 +++++ .../breakpoint/utils/TaskStorePath.java | 3 + .../service/breakpoint/merge/MergeTest.java | 20 ++++ 9 files changed, 295 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/InputStreamGenerator.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeInputStream.java create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MultipleFileMergeInputStreamGenerator.java create mode 100644 src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java new file mode 100644 index 00000000..380ebef0 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java @@ -0,0 +1,64 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint; + +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskStatMetadata; +import lombok.var; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +public class MergeMultipartFile implements MultipartFile { + private final TaskStatMetadata taskData; + public MergeMultipartFile(TaskStatMetadata data) { + this.taskData = data; + } + @Override + public String getName() { + return taskData.getFileName(); + } + + @Override + public String getOriginalFilename() { + return taskData.getFileName(); + } + + @Override + public String getContentType() { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public long getSize() { + return taskData.getLength(); + } + + @Override + public byte[] getBytes() throws IOException { + byte[] ret = new byte[Math.toIntExact(getSize())]; + var in = getInputStream(); + var r = in.read(ret); + in.close(); + if (r != getSize()) { + throw new IOException("不完整的流"); + } + return ret; + } + + @Override + public InputStream getInputStream() throws IOException { + return taskData.getMergeInputStream(); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + Files.copy(getInputStream(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java index 48f5bfb8..d7cdaa2f 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java @@ -13,8 +13,6 @@ import org.springframework.util.StreamUtils; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.util.UUID; /** diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java index 52aade84..f96e5595 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskMetadata.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AccessLevel; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.Setter; import javax.validation.constraints.NotBlank; @@ -12,18 +13,19 @@ import javax.validation.constraints.NotBlank; * 断点续传任务元数据信息类 */ @Data +@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class TaskMetadata { - private String taskId; + protected String taskId; /** * 文件名 */ @NotBlank - private String fileName; + protected String fileName; /** * 文件长度 */ - private int length; + private long length; /** * 每个分块的大小(默认2MiB) @@ -34,29 +36,30 @@ public class TaskMetadata { @Setter(AccessLevel.NONE) private int chunkCount = 0; - public void setLength(int length) { - this.length = length; - this.chunkCount = (int)Math.ceil((double)length / chunkSize); - } + private long lastChunkSize = 0; - /** - * 取最后一个文件块的大小 - */ - public int getLastChunkSize() { - int res = length % chunkSize; - return res == 0 ? chunkSize : res; + public TaskMetadata(String taskId, @NotBlank String fileName, long length) { + this.taskId = taskId; + this.fileName = fileName; + setLength(length); } + public void setLength(long length) { + this.length = length; + this.chunkCount = (int)Math.ceil((double)length / chunkSize); + long t = length % chunkSize; + lastChunkSize = t == 0 ? chunkSize : t; + } /** * 获取某个文件块的大小 * @param part 文件块序号 */ - public int getPartSize(int part) { + public long getPartSize(int part) { if (part > chunkCount) { throw new IndexOutOfBoundsException(); } if (part == chunkCount) { - return getLastChunkSize(); + return lastChunkSize; } else { return chunkSize; } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java index c0d7e082..245aac6d 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java @@ -1,40 +1,72 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.entity; -import com.xiaotao.saltedfishcloud.service.breakpoint.utils.PartParser; +import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; +import com.xiaotao.saltedfishcloud.service.breakpoint.merge.InputStreamGenerator; +import com.xiaotao.saltedfishcloud.service.breakpoint.merge.MergeInputStream; +import com.xiaotao.saltedfishcloud.service.breakpoint.merge.MultipleFileMergeInputStreamGenerator; import com.xiaotao.saltedfishcloud.service.breakpoint.utils.TaskStorePath; +import lombok.Getter; import lombok.var; +import javax.validation.constraints.NotBlank; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; -import java.util.ArrayList; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; /** * 任务状态数据,除了元数据本身,还附加了任务的当前完成状态 */ public class TaskStatMetadata extends TaskMetadata { - private final ArrayList finish = new ArrayList<>(); - public ArrayList getFinish() { - return finish; - } + @Getter + private List finishPart; + /** + * @param data 基础的任务元数据 + * @throws TaskNotFoundException 任务ID不存在 + */ public TaskStatMetadata(TaskMetadata data) throws IOException { - setTaskId(data.getTaskId()); - setFileName(data.getFileName()); - setLength(data.getLength()); + super(data.getTaskId(), data.getFileName(), data.getLength()); + if (!Files.exists(TaskStorePath.getRoot(data.getTaskId()))) { + throw new TaskNotFoundException(data.getTaskId()); + } fresh(); } + /** + * 更新任务的完成状态信息。 + * @TODO 通过文件大小进行数据校验 + */ public void fresh() throws IOException { - Files.list(TaskStorePath.getRoot(getTaskId())) - .filter(e -> e.endsWith(".part")) - .forEach(path -> { - var p = path.toString().replaceAll(".part", ""); - for (int i : PartParser.parse(p)) { - finish.add(i); - } - }); + finishPart = Files.list(TaskStorePath.getRoot(getTaskId())) + .filter(e -> e.toString().endsWith(".part")) + .map(e -> Integer.parseInt(e.getFileName().toString().replaceAll(".part", ""))) + .sorted() + .collect(Collectors.toList()); + } + + /** + * 返回该分块任务是否已完成 + */ + public boolean isFinish() { + return finishPart.size() == getChunkCount(); + } + + public MergeInputStream getMergeInputStream() throws IOException { + if (!this.isFinish()) { + throw new IllegalStateException("断点续传任务未完成,文件块不完整"); + } + int len = getChunkCount(); + Path[] paths = new Path[len]; + for (Integer integer : finishPart) { + paths[integer - 1] = TaskStorePath.getPartFile(taskId, integer); + } + return new MergeInputStream(new MultipleFileMergeInputStreamGenerator(paths)); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/InputStreamGenerator.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/InputStreamGenerator.java new file mode 100644 index 00000000..b598ee6c --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/InputStreamGenerator.java @@ -0,0 +1,16 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.merge; + +import java.io.IOException; +import java.io.InputStream; + +public interface InputStreamGenerator { + /** + * 获取下一个InputStream,如果不可再次获取应返回null + */ + InputStream next() throws IOException; + + /** + * 如果存在可用的新InputStream,返回true + */ + boolean hasNext(); +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeInputStream.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeInputStream.java new file mode 100644 index 00000000..aed12bb6 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeInputStream.java @@ -0,0 +1,100 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.merge; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 合并的输入流
+ * 将依照InputStreamGenerator获取多个输入流并在内部按顺序读取和关闭 + */ +public class MergeInputStream extends InputStream { + + private final InputStreamGenerator generator; + + private InputStream currentStream; + + private boolean atEnd = false; + + /** + * @param generator 输入流生成器 + */ + public MergeInputStream(InputStreamGenerator generator) throws IOException { + this.generator = generator; + nextStream(); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (atEnd) return -1; + + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + int ret = 0; + while (len > 0) { + int s = currentStream.read(b, off + ret, len); + if (s == -1) { + if (nextStream()) { + continue; + } else { + if (ret == 0) { + ret = -1; + } + atEnd = true; + break; + } + } + ret += s; + len -= s; + } + return ret; + } + + /** + * 关闭当前流,并切换到下一个文件块的流
+ * 切换成功返回true,如果所有文件块已经读取完毕,则返回false + */ + private boolean nextStream() throws IOException { + if (currentStream != null) { + currentStream.close(); + } + if (generator.hasNext()) { + currentStream = generator.next(); + } else { + return false; + } + return true; + } + + @Override + public void close() throws IOException { + currentStream.close(); + } + + @Override + public int read() throws IOException { + if (atEnd) return -1; + boolean finish = false; + int ret = 0; + while (!finish) { + ret = currentStream.read(); + if (ret == -1) { + if (!nextStream()) { + return -1; + } + } else { + finish = true; + } + } + return ret; + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MultipleFileMergeInputStreamGenerator.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MultipleFileMergeInputStreamGenerator.java new file mode 100644 index 00000000..846aa20b --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MultipleFileMergeInputStreamGenerator.java @@ -0,0 +1,25 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.merge; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +public class MultipleFileMergeInputStreamGenerator implements InputStreamGenerator { + private final Path[] paths; + private int index = 0; + public MultipleFileMergeInputStreamGenerator(Path...paths) { + this.paths = paths; + } + + @Override + public InputStream next() throws IOException { + if (!hasNext()) return null; + return Files.newInputStream(paths[index++]); + } + + @Override + public boolean hasNext() { + return index < paths.length; + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java index 8e682ae3..a60a4086 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java @@ -11,6 +11,9 @@ public class TaskStorePath { * @param id 任务ID */ public static Path getRoot(String id) { + if (id == null) { + throw new NullPointerException(); + } return Paths.get(PathUtils.getTempDirectory() + "/xyy/" + id); } diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java new file mode 100644 index 00000000..e4313a73 --- /dev/null +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java @@ -0,0 +1,20 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.merge; + +import com.xiaotao.saltedfishcloud.service.breakpoint.TaskManager; +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskStatMetadata; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +class MergeTest { + @Test + void mergeTest() throws IOException { + TaskManager manager = new TaskManager(); + TaskStatMetadata stat = manager.queryTask("f3852c6c-8f1a-47ba-a4fe-8c99b91d5906"); + MergeInputStream mergeInputStream = stat.getMergeInputStream(); + Files.copy(mergeInputStream, Paths.get("D:\\pack\\test.zip"), StandardCopyOption.REPLACE_EXISTING); + } +} -- Gitee From 8645618db1525b3f424c8232ae5841ddf7ffb242 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Tue, 3 Aug 2021 12:07:47 +0800 Subject: [PATCH 09/18] =?UTF-8?q?feat=20=E8=AF=86=E5=88=AB=E5=92=8C?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E6=97=B6?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=E4=B8=8E=E7=9B=AE=E5=BD=95=E5=90=8D?= =?UTF-8?q?=E5=86=B2=E7=AA=81=EF=BC=8C=E5=B9=B6=E5=93=8D=E5=BA=94=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../exception/UnableOverwriteException.java | 11 +++++++++++ .../saltedfishcloud/service/file/StoreService.java | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/exception/UnableOverwriteException.java diff --git a/pom.xml b/pom.xml index 4a601810..d7f41c57 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.xiaotao saltedfishcloud - 1.1.9.1-RELEASE + 1.1.9.2-RELEASE saltedfishcloud 咸鱼云网盘 diff --git a/src/main/java/com/xiaotao/saltedfishcloud/exception/UnableOverwriteException.java b/src/main/java/com/xiaotao/saltedfishcloud/exception/UnableOverwriteException.java new file mode 100644 index 00000000..c96e6148 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/exception/UnableOverwriteException.java @@ -0,0 +1,11 @@ +package com.xiaotao.saltedfishcloud.exception; + +public class UnableOverwriteException extends HasResultException { + public UnableOverwriteException(String message) { + super(message); + } + + public UnableOverwriteException(Integer code, String msg) { + super(code, msg); + } +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java index 1e4af3e9..2e928184 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/file/StoreService.java @@ -3,6 +3,7 @@ package com.xiaotao.saltedfishcloud.service.file; import com.xiaotao.saltedfishcloud.config.DiskConfig; import com.xiaotao.saltedfishcloud.config.StoreType; import com.xiaotao.saltedfishcloud.exception.HasResultException; +import com.xiaotao.saltedfishcloud.exception.UnableOverwriteException; import com.xiaotao.saltedfishcloud.po.file.BasicFileInfo; import com.xiaotao.saltedfishcloud.po.file.DirCollection; import com.xiaotao.saltedfishcloud.po.file.FileInfo; @@ -136,6 +137,8 @@ public class StoreService { * @param targetDir 保存到的目标网盘目录位置(注意:不是本地真是路径) * @param fileInfo 文件信息 * @throws HasResultException 存储文件出错 + * @throws DuplicateKeyException UNIQUE模式下两个不相同的文件发生MD5碰撞 + * @throws UnableOverwriteException 保存位置存在同名的目录 */ public void store(int uid, InputStream input, String targetDir, FileInfo fileInfo) throws HasResultException, IOException { Path md5Target = Paths.get(DiskConfig.uniquePathHandler.getStorePath(uid, targetDir, fileInfo)); @@ -152,6 +155,9 @@ public class StoreService { } } Path rawTarget = Paths.get(DiskConfig.rawPathHandler.getStorePath(uid, targetDir, fileInfo)); + if (Files.exists(rawTarget) && Files.isDirectory(rawTarget)) { + throw new UnableOverwriteException(409, "已存在同名目录: " + targetDir + "/" + fileInfo.getName()); + } FileUtils.createParentDirectory(rawTarget); if (DiskConfig.STORE_TYPE == StoreType.UNIQUE) { log.info("create hard link:" + md5Target + " <==> " + rawTarget); -- Gitee From ea0bdde7ae338186966155433dfbc3b105391efc Mon Sep 17 00:00:00 2001 From: xiaotao Date: Tue, 3 Aug 2021 14:36:59 +0800 Subject: [PATCH 10/18] =?UTF-8?q?feat=20=E5=AE=9E=E7=8E=B0=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=99=A8=E9=80=9A=E8=BF=87=E6=B3=A8=E8=A7=A3=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=96=AD=E7=82=B9=E7=BB=AD=E4=BC=A0=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ControllerAdvice.java | 1 + .../controller/FileController.java | 7 +++++- .../breakpoint/MergeMultipartFile.java | 3 +++ .../service/breakpoint/ProxyProcessor.java | 25 ++++++++++++++++++- .../breakpoint/annotation/MergeFile.java | 12 +++++++++ 5 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/MergeFile.java diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java index b3c7d307..376db37e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/ControllerAdvice.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import javax.servlet.http.HttpServletResponse; import javax.validation.ConstraintViolationException; import java.io.FileNotFoundException; import java.nio.file.NoSuchFileException; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java index 5a665594..88145185 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/FileController.java @@ -12,7 +12,9 @@ import com.xiaotao.saltedfishcloud.po.file.FileInfo; import com.xiaotao.saltedfishcloud.po.param.FileCopyOrMoveInfo; import com.xiaotao.saltedfishcloud.po.param.FileNameList; import com.xiaotao.saltedfishcloud.po.param.NamePair; +import com.xiaotao.saltedfishcloud.service.breakpoint.MergeMultipartFile; import com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint; +import com.xiaotao.saltedfishcloud.service.breakpoint.annotation.MergeFile; import com.xiaotao.saltedfishcloud.service.file.FileService; import com.xiaotao.saltedfishcloud.service.http.ResponseService; import com.xiaotao.saltedfishcloud.utils.URLUtils; @@ -78,8 +80,11 @@ public class FileController { @BreakPoint public JsonResult upload(HttpServletRequest request, @PathVariable @UID(true) int uid, - @RequestParam("file") MultipartFile file, + @RequestParam(value = "file", required = false) @MergeFile MultipartFile file, @RequestParam(value = "md5", required = false) String md5) throws HasResultException, IOException { + if (file == null || file.isEmpty()) { + return JsonResult.getInstance(400, null, "文件为空"); + } String requestPath = URLUtils.getRequestFilePath(PREFIX + uid + "/file", request); int i = fileService.saveFile(uid, file, requestPath, md5); return JsonResult.getInstance(i); diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java index 380ebef0..a6ba296f 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/MergeMultipartFile.java @@ -13,6 +13,9 @@ import java.nio.file.StandardCopyOption; public class MergeMultipartFile implements MultipartFile { private final TaskStatMetadata taskData; public MergeMultipartFile(TaskStatMetadata data) { + if (!data.isFinish()) { + throw new IllegalStateException("任务未完成,无法合并"); + } this.taskData = data; } @Override diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java index 4926d8a5..5a042723 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java @@ -1,13 +1,17 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; +import com.xiaotao.saltedfishcloud.service.breakpoint.annotation.MergeFile; import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; import lombok.var; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import java.lang.reflect.Parameter; + /** * 断点续传的代理处理器,处理被注解{@link com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint}标记的控制器方法
* 当HTTP请求的URI Query String Parameter中包含breakpoint_id时,表示文件上传使用了断点续传
@@ -24,8 +28,10 @@ public class ProxyProcessor { @Around("@annotation(com.xiaotao.saltedfishcloud.service.breakpoint.annotation.BreakPoint)") public Object proxy(ProceedingJoinPoint pjp) throws Throwable { + // 判断是否使用断点续传任务 var req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); var id = req.getParameter("breakpoint_id"); + // 如果不是断点续传任务,则跳过处理 if (id == null) { return pjp.proceed(); } @@ -35,7 +41,24 @@ public class ProxyProcessor { throw new TaskNotFoundException(id); } var args = pjp.getArgs(); + var sign = pjp.getSignature(); + + // 控制器参数偷梁换柱,替换掉被@MergeFile标记的MultipartFile类型参数 + if (sign instanceof MethodSignature) { + var params = ((MethodSignature) sign).getMethod().getParameters(); + int index = 0; + for (Parameter param : params) { + if (param.getAnnotation(MergeFile.class) != null) { + args[index] = new MergeMultipartFile(manager.queryTask(id)); + } + ++index; + } + } else { + throw new UnsupportedOperationException(); + } - return pjp.proceed(args); + var ret = pjp.proceed(args); + manager.clear(id); + return ret; } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/MergeFile.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/MergeFile.java new file mode 100644 index 00000000..3050b1f5 --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/annotation/MergeFile.java @@ -0,0 +1,12 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.annotation; + +import java.lang.annotation.*; + +/** + * 标记控制器方法的MultiFile合并分块任务文件 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface MergeFile { +} -- Gitee From 4c9c63dc29651e4605a2414ed6b91b50e80eab14 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Tue, 3 Aug 2021 14:46:24 +0800 Subject: [PATCH 11/18] =?UTF-8?q?chore=20=E7=89=88=E6=9C=AC=E5=8F=B7?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=BA1.1.10-SNAPSHOT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d7f41c57..42579cf7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.xiaotao saltedfishcloud - 1.1.9.2-RELEASE + 1.1.10-SNAPSHOT saltedfishcloud 咸鱼云网盘 -- Gitee From d6973a54dd7eb6deed4eaf7f016c34a3b65ac2d7 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Tue, 3 Aug 2021 23:55:14 +0800 Subject: [PATCH 12/18] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8D=E5=88=86?= =?UTF-8?q?=E5=9D=97=E4=B8=8A=E4=BC=A0=E4=B8=8D=E4=BD=BF=E7=94=A8=E8=8C=83?= =?UTF-8?q?=E5=9B=B4=E8=BF=9E=E7=BB=AD=E6=96=87=E4=BB=B6=E5=9D=97=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E7=BB=84=E8=B6=8A=E7=95=8C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../saltedfishcloud/service/breakpoint/utils/PartParser.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 42579cf7..622a9b55 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.xiaotao saltedfishcloud - 1.1.10-SNAPSHOT + 1.1.10.1-SNAPSHOT saltedfishcloud 咸鱼云网盘 diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java index e68dc664..37681908 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java @@ -37,8 +37,11 @@ public class PartParser { } int[] pair; var t = part.split("-", 2); - pair = new int[2]; + pair = new int[t.length]; pair[0] = Integer.parseInt(t[0]); + if (t.length == 1) { + return pair; + } pair[1] = Integer.parseInt(t[1]); var size = pair[1] - pair[0] + 1; int[] res = new int[size]; -- Gitee From ccd1722e39517ec36050edd97a9352b1cc3d6af8 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Wed, 11 Aug 2021 14:38:25 +0800 Subject: [PATCH 13/18] =?UTF-8?q?refactor=20=E8=B0=83=E6=95=B4=E6=96=AD?= =?UTF-8?q?=E7=82=B9=E7=BB=AD=E4=BC=A0=E5=8C=85=E7=BB=93=E6=9E=84=E5=92=8C?= =?UTF-8?q?=E6=8A=BD=E5=87=BATaskManager=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../breakpoint/BreakPointControllerImpl.java | 6 +-- .../service/breakpoint/ProxyProcessor.java | 1 + .../config/BreakPointConfigurator.java | 5 ++- .../breakpoint/entity/TaskStatMetadata.java | 7 +--- .../breakpoint/manager/TaskManager.java | 37 +++++++++++++++++++ .../impl/DefaultTaskManager.java} | 13 +++++-- .../{ => manager/impl}/utils/PartParser.java | 2 +- .../impl}/utils/TaskStorePath.java | 2 +- .../service/breakpoint/merge/MergeTest.java | 4 +- .../breakpoint/utils/PartParserTest.java | 1 + 10 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/TaskManager.java rename src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/{TaskManager.java => manager/impl/DefaultTaskManager.java} (88%) rename src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/{ => manager/impl}/utils/PartParser.java (94%) rename src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/{ => manager/impl}/utils/TaskStorePath.java (91%) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java index d846868d..4638f04e 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/BreakPointControllerImpl.java @@ -3,11 +3,11 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; import com.xiaotao.saltedfishcloud.po.JsonResult; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.TaskManager; import lombok.var; import org.springframework.validation.annotation.Validated; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.nio.file.NoSuchFileException; /** @@ -20,13 +20,13 @@ public class BreakPointControllerImpl implements BreakPointController { } @Override - public Object uploadPart(MultipartFile file, String id, String part) throws IOException { + public Object uploadPart(MultipartFile file, String id, String part) throws Exception { manager.save(id, part, file.getInputStream()); return JsonResult.getInstance(); } @Override - public TaskMetadata createTask(@Validated TaskMetadata data) throws IOException { + public TaskMetadata createTask(@Validated TaskMetadata data) throws Exception { String taskId = manager.createTask(data); data.setTaskId(taskId); return data; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java index 5a042723..22a2bdb4 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/ProxyProcessor.java @@ -2,6 +2,7 @@ package com.xiaotao.saltedfishcloud.service.breakpoint; import com.xiaotao.saltedfishcloud.service.breakpoint.annotation.MergeFile; import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.TaskManager; import lombok.var; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java index 769bb7c3..784a5277 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/config/BreakPointConfigurator.java @@ -3,7 +3,8 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.config; import com.xiaotao.saltedfishcloud.service.breakpoint.BreakPointController; import com.xiaotao.saltedfishcloud.service.breakpoint.BreakPointControllerImpl; import com.xiaotao.saltedfishcloud.service.breakpoint.ProxyProcessor; -import com.xiaotao.saltedfishcloud.service.breakpoint.TaskManager; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.TaskManager; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.DefaultTaskManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -39,7 +40,7 @@ public class BreakPointConfigurator { */ @Bean public TaskManager taskManager() { - return new TaskManager(); + return new DefaultTaskManager(); } /** diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java index 245aac6d..a7c08f30 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java @@ -1,19 +1,14 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.entity; import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; -import com.xiaotao.saltedfishcloud.service.breakpoint.merge.InputStreamGenerator; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.utils.TaskStorePath; import com.xiaotao.saltedfishcloud.service.breakpoint.merge.MergeInputStream; import com.xiaotao.saltedfishcloud.service.breakpoint.merge.MultipleFileMergeInputStreamGenerator; -import com.xiaotao.saltedfishcloud.service.breakpoint.utils.TaskStorePath; import lombok.Getter; -import lombok.var; -import javax.validation.constraints.NotBlank; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/TaskManager.java new file mode 100644 index 00000000..de0d86cb --- /dev/null +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/TaskManager.java @@ -0,0 +1,37 @@ +package com.xiaotao.saltedfishcloud.service.breakpoint.manager; + +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; +import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskStatMetadata; + +import java.io.InputStream; + +public interface TaskManager { + + /** + * 创建断点续传任务 + * @param info 任务元数据 + * @return 创建成功后的任务ID + */ + String createTask(TaskMetadata info) throws Exception; + + /** + * 查询任务信息 + * @param id 任务ID + * @return 任务信息,若任务不存在则返回Null + */ + TaskStatMetadata queryTask(String id) throws Exception; + + /** + * 清理指定的任务数据 + * @param id 任务ID + */ + void clear(String id) throws Exception; + + /** + * 保存部分的断点续传任务文件片段 + * @param id 任务ID + * @param part 文件块编号(从1开始) + * @param stream 文件流 + */ + void save(String id, String part, InputStream stream) throws Exception; +} diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/DefaultTaskManager.java similarity index 88% rename from src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java rename to src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/DefaultTaskManager.java index d7cdaa2f..0bd96d56 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/TaskManager.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/DefaultTaskManager.java @@ -1,11 +1,12 @@ -package com.xiaotao.saltedfishcloud.service.breakpoint; +package com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl; import com.fasterxml.jackson.databind.ObjectMapper; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskMetadata; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskStatMetadata; import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundException; -import com.xiaotao.saltedfishcloud.service.breakpoint.utils.PartParser; -import com.xiaotao.saltedfishcloud.service.breakpoint.utils.TaskStorePath; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.TaskManager; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.utils.PartParser; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.utils.TaskStorePath; import lombok.extern.slf4j.Slf4j; import lombok.var; import org.springframework.util.StreamUtils; @@ -19,7 +20,7 @@ import java.util.UUID; * 断点续传任务管理器,管理任务的创建,查询,删除和文件块的存储 */ @Slf4j -public class TaskManager { +public class DefaultTaskManager implements TaskManager { private final ObjectMapper mapper = new ObjectMapper(); @@ -29,6 +30,7 @@ public class TaskManager { * @return 创建成功后的任务ID * @throws IOException 任务数据存储目录无法写入 */ + @Override public String createTask(TaskMetadata info) throws IOException { var id = UUID.randomUUID().toString(); info.setTaskId(id); @@ -46,6 +48,7 @@ public class TaskManager { * @return 任务信息,若任务不存在则返回Null * @throws IOException 目录读取出错 */ + @Override public TaskStatMetadata queryTask(String id) throws IOException { var metadataPath = TaskStorePath.getMetadata(id); if (!Files.exists(metadataPath)) { @@ -61,6 +64,7 @@ public class TaskManager { * @param id 任务ID * @throws IOException 目录不可写或任务不存在 */ + @Override public void clear(String id) throws IOException { var taskPath = TaskStorePath.getRoot(id); if (!Files.exists(taskPath)) { @@ -82,6 +86,7 @@ public class TaskManager { * @param part 文件块编号(从1开始) * @param stream 文件流 */ + @Override public void save(String id, String part, InputStream stream) throws IOException { var root = TaskStorePath.getRoot(id); if (!Files.exists(root)) { diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/utils/PartParser.java similarity index 94% rename from src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java rename to src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/utils/PartParser.java index 37681908..3f2a63af 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParser.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/utils/PartParser.java @@ -1,4 +1,4 @@ -package com.xiaotao.saltedfishcloud.service.breakpoint.utils; +package com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.utils; import lombok.var; diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/utils/TaskStorePath.java similarity index 91% rename from src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java rename to src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/utils/TaskStorePath.java index a60a4086..d1a462b8 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/TaskStorePath.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/manager/impl/utils/TaskStorePath.java @@ -1,4 +1,4 @@ -package com.xiaotao.saltedfishcloud.service.breakpoint.utils; +package com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.utils; import com.xiaotao.saltedfishcloud.utils.PathUtils; diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java index e4313a73..859aa17d 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/merge/MergeTest.java @@ -1,6 +1,6 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.merge; -import com.xiaotao.saltedfishcloud.service.breakpoint.TaskManager; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.DefaultTaskManager; import com.xiaotao.saltedfishcloud.service.breakpoint.entity.TaskStatMetadata; import org.junit.jupiter.api.Test; @@ -12,7 +12,7 @@ import java.nio.file.StandardCopyOption; class MergeTest { @Test void mergeTest() throws IOException { - TaskManager manager = new TaskManager(); + DefaultTaskManager manager = new DefaultTaskManager(); TaskStatMetadata stat = manager.queryTask("f3852c6c-8f1a-47ba-a4fe-8c99b91d5906"); MergeInputStream mergeInputStream = stat.getMergeInputStream(); Files.copy(mergeInputStream, Paths.get("D:\\pack\\test.zip"), StandardCopyOption.REPLACE_EXISTING); diff --git a/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java index f979a9c8..980c1425 100644 --- a/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java +++ b/src/test/java/com/xiaotao/saltedfishcloud/service/breakpoint/utils/PartParserTest.java @@ -1,5 +1,6 @@ package com.xiaotao.saltedfishcloud.service.breakpoint.utils; +import com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.utils.PartParser; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -- Gitee From 0206e544983ada1cc384572997f7b9b3239b7252 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Thu, 12 Aug 2021 16:20:14 +0800 Subject: [PATCH 14/18] =?UTF-8?q?pref=20=E6=96=AD=E7=82=B9=E7=BB=AD?= =?UTF-8?q?=E4=BC=A0=E4=B8=8A=E4=BC=A0=E5=88=86=E7=89=87=E6=97=B6=E4=B8=8D?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=B7=B2=E4=B8=8A=E4=BC=A0=E5=88=86=E7=89=87?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=87=8F=E5=B0=91IO=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/breakpoint/entity/TaskStatMetadata.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java index a7c08f30..c3a4a62c 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java @@ -17,19 +17,17 @@ import java.util.stream.Collectors; */ public class TaskStatMetadata extends TaskMetadata { - @Getter private List finishPart; /** * @param data 基础的任务元数据 * @throws TaskNotFoundException 任务ID不存在 */ - public TaskStatMetadata(TaskMetadata data) throws IOException { + public TaskStatMetadata(TaskMetadata data) { super(data.getTaskId(), data.getFileName(), data.getLength()); if (!Files.exists(TaskStorePath.getRoot(data.getTaskId()))) { throw new TaskNotFoundException(data.getTaskId()); } - fresh(); } @@ -45,6 +43,13 @@ public class TaskStatMetadata extends TaskMetadata { .collect(Collectors.toList()); } + public List getFinishPart() throws IOException { + if (finishPart == null) { + fresh(); + } + return finishPart; + } + /** * 返回该分块任务是否已完成 */ -- Gitee From a8eeae3bca7bcb1940d9483e77918bf539d87b14 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Thu, 12 Aug 2021 16:24:00 +0800 Subject: [PATCH 15/18] =?UTF-8?q?chore=201.1.10-2SNAPSHOT=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 622a9b55..e133a618 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.xiaotao saltedfishcloud - 1.1.10.1-SNAPSHOT + 1.1.10.2-SNAPSHOT saltedfishcloud 咸鱼云网盘 -- Gitee From aa49a9d8ae342a59a3d0b5a822f92ea3eabf48c4 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 14 Aug 2021 14:25:50 +0800 Subject: [PATCH 16/18] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8D=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=90=8C=E6=AD=A5=E6=97=B6=E9=97=B4=E5=90=8E=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E9=87=8D=E5=90=AF=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=89=8D?= =?UTF-8?q?=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/DebugController.java | 15 ++++++++++++++- .../service/config/ConfigService.java | 3 +++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/controller/admin/DebugController.java b/src/main/java/com/xiaotao/saltedfishcloud/controller/admin/DebugController.java index 89c4ba4b..bc3b94b4 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/controller/admin/DebugController.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/controller/admin/DebugController.java @@ -1,10 +1,13 @@ package com.xiaotao.saltedfishcloud.controller.admin; import com.xiaotao.saltedfishcloud.config.DiskConfig; +import com.xiaotao.saltedfishcloud.dao.ConfigDao; import com.xiaotao.saltedfishcloud.enums.ReadOnlyLevel; import com.xiaotao.saltedfishcloud.po.JsonResult; +import lombok.var; import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; import javax.annotation.security.RolesAllowed; import java.util.LinkedHashMap; @@ -13,6 +16,8 @@ import java.util.LinkedHashMap; @RestController public class DebugController { public static final String prefix = "/api/admin/debug/"; + @Resource + private ConfigDao configDao; @PutMapping("readOnly") public JsonResult setReadOnlyLevel(@RequestParam String level) { @@ -34,8 +39,16 @@ public class DebugController { @GetMapping("options") public JsonResult getAllOptions() { LinkedHashMap data = JsonResult.getDataMap(); + var conf = configDao.getAllConfig(); + if (conf != null) { + conf.forEach(e -> { + data.put(e.getKey().toString(), e.getValue()); + }); + } + data.put("READ_ONLY_LEVEL", DiskConfig.getReadOnlyLevel()); data.put("read_only_level", DiskConfig.getReadOnlyLevel()); data.put("sync_delay", DiskConfig.SYNC_DELAY); - return JsonResult.getInstance(data); + + return JsonResult.getInstance(1, data, "小写字段将在后续版本中废弃"); } } diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/config/ConfigService.java b/src/main/java/com/xiaotao/saltedfishcloud/service/config/ConfigService.java index a4f29d81..f7852a39 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/config/ConfigService.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/config/ConfigService.java @@ -39,6 +39,9 @@ public class ConfigService { switch (key) { case STORE_TYPE: return setStoreType(StoreType.valueOf(value)); case REG_CODE: setInviteRegCode(value); return true; + case SYNC_DELAY: + DiskConfig.SYNC_DELAY = Integer.parseInt(value); + configDao.setConfigure(key, value); default: configDao.setConfigure(key, value); } -- Gitee From 92ecf5615f474dcfd9d4382cc03ef2d1683efad2 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sat, 14 Aug 2021 17:45:44 +0800 Subject: [PATCH 17/18] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8DTaskStateMetadata?= =?UTF-8?q?=E7=9A=84isFinish=E6=96=B9=E6=B3=95=E4=B8=8D=E4=BC=9A=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E4=BB=BB=E5=8A=A1=E4=BF=A1=E6=81=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/breakpoint/entity/TaskStatMetadata.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java index c3a4a62c..6de3f0e6 100644 --- a/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java +++ b/src/main/java/com/xiaotao/saltedfishcloud/service/breakpoint/entity/TaskStatMetadata.java @@ -4,7 +4,7 @@ import com.xiaotao.saltedfishcloud.service.breakpoint.exception.TaskNotFoundExce import com.xiaotao.saltedfishcloud.service.breakpoint.manager.impl.utils.TaskStorePath; import com.xiaotao.saltedfishcloud.service.breakpoint.merge.MergeInputStream; import com.xiaotao.saltedfishcloud.service.breakpoint.merge.MultipleFileMergeInputStreamGenerator; -import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.nio.file.Files; @@ -15,6 +15,7 @@ import java.util.stream.Collectors; /** * 任务状态数据,除了元数据本身,还附加了任务的当前完成状态 */ +@Slf4j public class TaskStatMetadata extends TaskMetadata { private List finishPart; @@ -54,7 +55,14 @@ public class TaskStatMetadata extends TaskMetadata { * 返回该分块任务是否已完成 */ public boolean isFinish() { - return finishPart.size() == getChunkCount(); + try { + return getFinishPart().size() == getChunkCount(); + } catch (IOException e) { + if (log.isDebugEnabled()) { + e.printStackTrace(); + } + return false; + } } public MergeInputStream getMergeInputStream() throws IOException { -- Gitee From 41c80910ab46f44bc65fdcc0dddf53981c32bbb5 Mon Sep 17 00:00:00 2001 From: xiaotao Date: Sun, 15 Aug 2021 00:31:09 +0800 Subject: [PATCH 18/18] =?UTF-8?q?chore=20=E7=89=88=E6=9C=AC=E5=8F=B7?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=B8=BA1.2.0-RELEASE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e133a618..66f833c6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.xiaotao saltedfishcloud - 1.1.10.2-SNAPSHOT + 1.2.0-RELEASE saltedfishcloud 咸鱼云网盘 -- Gitee