From dd2c65eabbd78e808d74aea8b742a8b9d327721c Mon Sep 17 00:00:00 2001 From: lilin Date: Fri, 23 May 2025 17:46:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/component/TaskStatusHolder.java | 16 + .../controller/TsFilesController.java | 45 +- .../controller/TsNodesController.java | 78 +- .../controller/TsTaskController.java | 14 +- .../experimentalData/domain/TsNodes.java | 7 +- .../experimentalData/domain/TsTask.java | 39 +- .../service/ITsFilesService.java | 6 +- .../service/ITsNodesService.java | 12 +- .../service/ITsTaskService.java | 11 +- .../service/impl/TsFilesServiceImpl.java | 248 ++--- .../service/impl/TsNodesServiceImpl.java | 467 ++++++++- .../service/impl/TsTaskServiceImpl.java | 208 +++- .../controller/NodesController.java | 142 ++- .../controller/ProjectController.java | 6 +- .../modules/specialDocument/domain/Nodes.java | 7 + .../specialDocument/domain/Project.java | 5 +- .../service/INodesService.java | 17 +- .../service/IProjectService.java | 6 +- .../service/impl/FilesServiceImpl.java | 14 +- .../service/impl/NodesServiceImpl.java | 888 +++++++++++++++++- .../service/impl/ProjectServiceImpl.java | 164 +++- .../storage/context/StorageSourceContext.java | 3 + .../controller/file/FileController.java | 2 +- .../storage/model/enums/StorageTypeEnum.java | 3 +- .../system/service/impl/UserServiceImpl.java | 3 +- 25 files changed, 2133 insertions(+), 278 deletions(-) diff --git a/java/src/main/java/com/yfd/platform/component/TaskStatusHolder.java b/java/src/main/java/com/yfd/platform/component/TaskStatusHolder.java index d74e63b..2b0d381 100644 --- a/java/src/main/java/com/yfd/platform/component/TaskStatusHolder.java +++ b/java/src/main/java/com/yfd/platform/component/TaskStatusHolder.java @@ -19,6 +19,22 @@ public class TaskStatusHolder { return dataset + ":" ; } + // 生成专项扫描唯一Key:project_id + public String specialGenerateKey(String id) { + return id + ":"; + } + // 生成专项文档上传唯一Key:project_id+上传: + public String documentUploadKey(String id) { + return id + "上传:"; + } + + // 生成试验任务扫描唯一Key:task_id + public String testDatascanKey(String id) { + return id + ":"; + } + + + // 原子性检查并标记任务开始 diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java index e2cf8e2..d4e1f95 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java @@ -193,18 +193,19 @@ public class TsFilesController { * 参数说明 compressedPath 压缩文件路径 * 参数说明 covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 * 参数说明 parentId 父ID + * 参数说明 path 根目录 /项目名/节点名称/ * 返回值说明: com.yfd.platform.config.ResponseResult ***********************************/ @Log(module = "实验数据管理", value = "压缩文件夹接口!") @PostMapping("/compress") @ApiOperation("压缩文件夹接口") - public ResponseResult compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId) { + public ResponseResult compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId,String path) { try { - if (StrUtil.isBlank(ids) && StrUtil.isBlank(compressedFormat) && StrUtil.isBlank(compressedName) && StrUtil.isBlank(compressedPath)) { + if (StrUtil.isBlank(ids) && StrUtil.isBlank(compressedFormat) && StrUtil.isBlank(compressedName) && StrUtil.isBlank(compressedPath)&& StrUtil.isBlank(path)) { return ResponseResult.error("参数为空"); } - return ResponseResult.success(tsFilesService.compressFolder(ids, compressedFormat, compressedName, compressedPath, covered, parentId)); + return ResponseResult.success(tsFilesService.compressFolder(ids, compressedFormat, compressedName, compressedPath, covered, parentId, path)); } catch (Exception e) { System.out.print("压缩异常原因" + e); return ResponseResult.error("压缩失败"); @@ -216,18 +217,19 @@ public class TsFilesController { * 参数说明 id 要解压的文件id * 参数说明 decompressionPath 解压缩路径 * 参数说明 parentId 父ID + * 参数说明 path 根目录 /项目名/节点名称/ * 返回值说明: com.yfd.platform.config.ResponseResult ***********************************/ @Log(module = "实验数据管理", value = "解压缩接口!") @PostMapping("/decompression") @ApiOperation("解压缩接口") - public ResponseResult decompressionFolder(String id, String decompressionPath, String parentId) { + public ResponseResult decompressionFolder(String id, String decompressionPath, String parentId,String path) { try { if (StrUtil.isBlank(id)) { return ResponseResult.error("参数为空"); } - return ResponseResult.success(tsFilesService.decompressionFolder(id, decompressionPath, parentId)); + return ResponseResult.success(tsFilesService.decompressionFolder(id, decompressionPath, parentId,path)); } catch (Exception e) { System.out.print("解压缩异常原因" + e); return ResponseResult.error("解压缩失败"); @@ -369,8 +371,6 @@ public class TsFilesController { Page tsfilesPage = tsFilesService.compareLocal(dataset, nodeId, taskId, page); - // List dtos = tsFilesService.compareLocal(dataset, nodeId, taskId); -// DualTreeResponse response = tsFilesService.compareLocal(dataset, nodeId, taskId); return ResponseResult.successData(tsfilesPage); } catch (Exception e) { return ResponseResult.error("对比失败"); @@ -600,6 +600,37 @@ public class TsFilesController { } } + /********************************** + * 用途说明: 获取异步信息 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Log(module = "获取异步信息", value = "获取异步信息!") + @PostMapping("/automaticFileBackupData") + @ApiOperation("获取异步信息") + public ResponseResult automaticFileBackupData(String id) throws Exception { + if (StrUtil.isEmpty(id) ) { + return ResponseResult.error("参数为空"); + } + List dataset = StrUtil.split(id, ","); + + // 生成唯一Key + String asyncKey = taskStatusHolder.generateKeybyId(dataset); + + // 检查任务是否已存在 + String existingStatus = taskStatusHolder.getStatus(asyncKey); + if ("TASK_NOT_FOUND".equals(existingStatus)) { + return ResponseResult.success("1"); + } + if ("IN_PROGRESS".equals(existingStatus)) { + return ResponseResult.success("0"); + } else if ("COMPLETED".equals(existingStatus)) { + return ResponseResult.success("自动备份任务已完成!"); + } + + return null; + } + /********************************** * 用途说明: 实时获取轨迹数据 * 参数说明 id 文件的ID diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsNodesController.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsNodesController.java index 69d2a04..29be37a 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsNodesController.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsNodesController.java @@ -4,12 +4,14 @@ package com.yfd.platform.modules.experimentalData.controller; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.yfd.platform.annotation.Log; +import com.yfd.platform.component.TaskStatusHolder; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.modules.experimentalData.domain.TsNodes; import com.yfd.platform.modules.experimentalData.service.ITsNodesService; import com.yfd.platform.modules.experimentalData.service.ITsTaskService; import com.yfd.platform.modules.specialDocument.domain.Nodes; import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @@ -35,6 +37,10 @@ public class TsNodesController { private ITsNodesService tsNodesService; + @Autowired + private TaskStatusHolder taskStatusHolder; + + /*********************************** * 用途说明:获取试验任务节点 树形结构 * 参数说明 @@ -48,7 +54,7 @@ public class TsNodesController { @ResponseBody @PreAuthorize("@el.check('select:tsnodes')") public ResponseResult getTsNodesTree(String nodeName, String taskId) { - List> list = tsNodesService.getTsNodesTree(nodeName,taskId); + List> list = tsNodesService.getTsNodesTree(nodeName, taskId); return ResponseResult.successData(list); } @@ -63,7 +69,7 @@ public class TsNodesController { @PreAuthorize("@el.check('add:tsnodes')") public ResponseResult addTsNodes(@RequestBody TsNodes tsnodes) { //参数校验 对象 节点名称 所属任务ID - if (ObjUtil.isEmpty(tsnodes) && StrUtil.isBlank(tsnodes.getNodeName()) && StrUtil.isBlank(tsnodes.getTaskId()) ) { + if (ObjUtil.isEmpty(tsnodes) && StrUtil.isBlank(tsnodes.getNodeName()) && StrUtil.isBlank(tsnodes.getTaskId())) { return ResponseResult.error("参数为空"); } return tsNodesService.addTsNodes(tsnodes); @@ -90,17 +96,18 @@ public class TsNodesController { /********************************** * 用途说明: 根据ID删除试验任务节点 * 参数说明 id 试验任务节点ID + * 参数说明 path 路径 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 ***********************************/ @Log(module = "试验数据管理", value = "根据ID删除试验任务节点!") @PostMapping("/deleteTsNodesById") @ApiOperation("根据ID删除试验任务节点") @PreAuthorize("@el.check('del:tsnodes')") - public ResponseResult deleteTsNodesById(@RequestParam String id) { + public ResponseResult deleteTsNodesById(@RequestParam String id,String path) { if (StrUtil.isBlank(id)) { return ResponseResult.error("参数为空"); } - boolean isOk = tsNodesService.deleteTsNodesById(id); + boolean isOk = tsNodesService.deleteTsNodesById(id,path); if (isOk) { return ResponseResult.success(); } else { @@ -108,4 +115,67 @@ public class TsNodesController { } } + + /********************************** + * 用途说明: 试验数据扫描接口 + * 参数说明 id 试验任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Log(module = "试验数据扫描接口", value = "试验数据扫描接口!") + @PostMapping("/testDataScanById") + @ApiOperation("试验数据扫描接口通过试验任务ID") + public ResponseResult testDataScanByIdAsync(String id) throws Exception { + if (StrUtil.isEmpty(id)) { + return ResponseResult.error("参数为空"); + } + // 生成唯一Key + String asyncKey = taskStatusHolder.testDatascanKey(id); + + // 检查任务是否已存在 + String existingStatus = taskStatusHolder.getStatus(asyncKey); + if ("IN_PROGRESS".equals(existingStatus)) { + return ResponseResult.success("试验数据扫描任务正在处理中!"); + } else if ("COMPLETED".equals(existingStatus)) { + return ResponseResult.success("验数据扫描任务已完成!"); + } + // 原子性启动新任务 + if (taskStatusHolder.startTaskIfAbsent(asyncKey)) { + // 直接异步执行并推送结果 + tsNodesService.testDataScanByIdAsync(id); + return ResponseResult.success("验数据扫描任务开始处理!"); + } else { + return ResponseResult.success("验数据扫描任务已由其他请求启动!"); + } + } + + /********************************** + * 用途说明: 获取异步信息 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Log(module = "获取异步信息", value = "获取异步信息!") + @PostMapping("/obtaintestData") + @ApiOperation("获取异步信息") + public ResponseResult obtaintestData(String id) throws Exception { + if (StrUtil.isEmpty(id)) { + return ResponseResult.error("id为空"); + } + + // 生成唯一Key + String asyncKey = taskStatusHolder.testDatascanKey(id); + + // 检查任务是否已存在 + String existingStatus = taskStatusHolder.getStatus(asyncKey); + if ("TASK_NOT_FOUND".equals(existingStatus)) { + return ResponseResult.success("1"); + } + if ("IN_PROGRESS".equals(existingStatus)) { + return ResponseResult.success("0"); + } else if ("COMPLETED".equals(existingStatus)) { + return ResponseResult.success("专项文档扫描任务已完成!"); + } + + return null; + } + } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsTaskController.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsTaskController.java index 20830b2..6137dfd 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsTaskController.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsTaskController.java @@ -43,8 +43,12 @@ public class TsTaskController { * endDate (结束日期) * taskPlace 任务地点 * taskPerson 任务人员 - * carrierType 载体类型 - * deviceCode 设备 + * taskCode 任务编号 + * taskType 任务类型 + * carrierName 任务人员 + * deviceCode 任务人员 + * testDescribe 任务人员 + * sensorDescribe 任务人员 * pageNum 当前页 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 ***********************************/ @@ -52,9 +56,9 @@ public class TsTaskController { @GetMapping("/page") @ApiOperation("分页查询试验数据管理试验任务管理") @PreAuthorize("@el.check('select:tsTask')") - public ResponseResult getTsTaskPage(String taskName, String startDate, String endDate,String taskPlace, String taskPerson, String carrierType, String deviceCode, Page page) { + public ResponseResult getTsTaskPage(String taskName, String startDate, String endDate,String taskPlace, String taskPerson, String taskCode, String taskType,String carrierName,String deviceCode,String testDescribe,String sensorDescribe, Page page) { //分页查询 - Page sdProjectPage = tsTaskService.getTsTaskPage(taskName, startDate, endDate,taskPlace,taskPerson,carrierType,deviceCode, page); + Page sdProjectPage = tsTaskService.getTsTaskPage(taskName, startDate, endDate,taskPlace,taskPerson,taskCode,taskType,carrierName,deviceCode,testDescribe,sensorDescribe, page); return ResponseResult.successData(sdProjectPage); } @@ -163,7 +167,7 @@ public class TsTaskController { //@PreAuthorize("@el.check('select:devicesignal')") public ResponseResult listTsTask() { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.orderByDesc(TsTask::getTaskDate); + queryWrapper.orderByDesc(TsTask::getTaskStartdate); List tsTasks = tsTaskService.list(queryWrapper); return ResponseResult.successData(tsTasks); } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsNodes.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsNodes.java index 1d801fe..0683b14 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsNodes.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsNodes.java @@ -1,6 +1,7 @@ package com.yfd.platform.modules.experimentalData.domain; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -79,5 +80,9 @@ public class TsNodes implements Serializable { */ private String custom3; - + /** + * TODO路径用于拼接 + */ + @TableField(exist = false) + private String path; } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsTask.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsTask.java index da31b02..6a6c3c2 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsTask.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsTask.java @@ -64,11 +64,11 @@ public class TsTask implements Serializable { */ private String deviceName; - /** - * 任务开始时间 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") - private Timestamp taskDate; +// /** +// * 任务开始时间 +// */ +// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") +// private Timestamp taskDate; /** * 任务地点 @@ -111,5 +111,34 @@ public class TsTask implements Serializable { */ private String custom3; + /** + * 任务类型 + */ + private String taskType; + + /** + * 试验描述 + */ + private String testDescribe; + + /** + * 传感器描述 + */ + private String sensorDescribe; + + /** + * 任务开始时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private LocalDate taskStartdate; + + /** + * 任务结束时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private LocalDate taskEnddate; + + + } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java index 179f537..51bdacc 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java @@ -69,18 +69,20 @@ public interface ITsFilesService extends IService { * 参数说明 compressedPath 压缩文件路径 * 参数说明 covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 * 参数说明 parentId 父ID + * 参数说明 path 根目录 /项目名/节点名称/ * 返回值说明: com.yfd.platform.config.ResponseResult ***********************************/ - String compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId) throws FileNotFoundException; + String compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId,String path) throws FileNotFoundException; /********************************** * 用途说明: 解压缩接口 * 参数说明 id 要解压的文件id * 参数说明 decompressionPath 解压缩路径 * 参数说明 parentId 父ID + * 参数说明 path 根目录 /项目名/节点名称/ * 返回值说明: com.yfd.platform.config.ResponseResult ***********************************/ - String decompressionFolder(String id, String decompressionPath, String parentId); + String decompressionFolder(String id, String decompressionPath, String parentId, String path); /********************************** * 用途说明: 将文件上传到备份空间 diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsNodesService.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsNodesService.java index a0251b2..71d6501 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsNodesService.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsNodesService.java @@ -43,10 +43,18 @@ public interface ITsNodesService extends IService { /********************************** * 用途说明: 根据ID删除试验任务节点 * 参数说明 id 试验任务节点ID + * 参数说明 path 路径 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 ***********************************/ - boolean deleteTsNodesById(String id); + boolean deleteTsNodesById(String id,String path); - boolean deleteTsNodesByTaskId(String taskId); + boolean deleteTsNodesByTaskId(String taskId,String path); + + /********************************** + * 用途说明: 试验数据扫描接口 + * 参数说明 id 试验任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + void testDataScanByIdAsync(String id) throws Exception; } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsTaskService.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsTaskService.java index eb38480..1a8870b 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsTaskService.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsTaskService.java @@ -16,6 +16,7 @@ import java.util.List; */ public interface ITsTaskService extends IService { + /********************************** * 用途说明: 分页查询试验数据管理-试验任务管理 * 参数说明 @@ -24,12 +25,16 @@ public interface ITsTaskService extends IService { * endDate (结束日期) * taskPlace 任务地点 * taskPerson 任务人员 - * carrierType 载体类型 - * deviceCode 设备 + * taskCode 任务编号 + * taskType 任务类型 + * carrierName 任务人员 + * deviceCode 任务人员 + * testDescribe 任务人员 + * sensorDescribe 任务人员 * pageNum 当前页 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 ***********************************/ - Page getTsTaskPage(String taskName, String startDate, String endDate, String taskPlace, String taskPerson, String carrierType, String deviceCode, Page page); + Page getTsTaskPage(String taskName, String startDate, String endDate, String taskPlace, String taskPerson, String taskCode, String taskType,String carrierName,String deviceCode,String testDescribe,String sensorDescribe, Page page); /*********************************** * 用途说明:新增试验数据管理-试验任务管理 diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java index cc76112..fdb7f69 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java @@ -32,6 +32,8 @@ import com.yfd.platform.component.TaskStatusHolder; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.modules.experimentalData.domain.*; import com.yfd.platform.modules.experimentalData.mapper.TsFilesMapper; +import com.yfd.platform.modules.experimentalData.mapper.TsNodesMapper; +import com.yfd.platform.modules.experimentalData.mapper.TsTaskMapper; import com.yfd.platform.modules.experimentalData.service.ITsFilesService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yfd.platform.modules.storage.context.StorageSourceContext; @@ -111,6 +113,12 @@ public class TsFilesServiceImpl extends ServiceImpl impl private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + @Resource + private TsTaskMapper tsTaskMapper; + + @Resource + private TsNodesMapper tsNodesMapper; + // 从数据库获取的压缩类型列表 private List compressSuffixes; private final Set addedEntries = new HashSet<>(); @@ -239,8 +247,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl if (tsFiles.getUpdateTime() == null) { tsFiles.setUpdateTime(tsFiles.getUploadTime()); } - String ProcessingPath = processingPath(tsFiles.getWorkPath(), nodeId); - tsFiles.setWorkPath(ProcessingPath); +// String ProcessingPath = processingPath(tsFiles.getWorkPath(), nodeId); +// tsFiles.setWorkPath(ProcessingPath); } tsFilesPage.setRecords(records); // 同步到 tsFilesPage System.out.println("Updated records: " + records); @@ -332,10 +340,10 @@ public class TsFilesServiceImpl extends ServiceImpl impl queryWrapper.isNotNull("work_path"); List tsFiles = tsFilesMapper.selectList(queryWrapper); - for (TsFiles tsFiles1 : tsFiles) { - String ProcessingPath = processingPath(tsFiles1.getWorkPath(), nodeId); - tsFiles1.setWorkPath(ProcessingPath); - } +// for (TsFiles tsFiles1 : tsFiles) { +// String ProcessingPath = processingPath(tsFiles1.getWorkPath(), nodeId); +// tsFiles1.setWorkPath(ProcessingPath); +// } return tsFiles; } @@ -475,7 +483,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl public ResponseResult addTsFile(TsFiles tsFiles) throws IOException { if (tsFiles.getIsFile().equals("FILE")) { - StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); + StorageSourceConfig config = getStorageConfig("filePath", "local"); String basePath = config.getValue() + tsFiles.getWorkPath(); // 拼接完整的文件路径 Path filePath = Paths.get(basePath, tsFiles.getFileName() + ".txt"); @@ -1107,14 +1115,13 @@ public class TsFilesServiceImpl extends ServiceImpl impl * 参数说明 compressedPath 压缩文件路径 * 参数说明 covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 * 参数说明 parentId 父ID + * 参数说明 path 根目录 /项目名/节点名称/ * 返回值说明: com.yfd.platform.config.ResponseResult ***********************************/ @Override - public String compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId) throws FileNotFoundException { + public String compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId, String path) throws FileNotFoundException { //获取本地根路径 - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("name", "filePath"); - StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local"); compressedPath = normalizePath(compressedPath); @@ -1137,16 +1144,16 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 构建完整路径 String sourceDir = storageSourceConfig.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName(); - Path path = Paths.get(sourceDir); + Path workpath = Paths.get(sourceDir); // 检查路径是否存在 - if (!Files.exists(path)) { + if (!Files.exists(workpath)) { throw new FileNotFoundException("路径不存在: " + path); } // 添加到压缩列表 - sourceDirs.add(path); // 修复:使用可变集合 + sourceDirs.add(workpath); // 修复:使用可变集合 } try { - String finalParentId = ensureFullPathExists(compressedPath, filesList.get(0).getNodeId(), filesList.get(0).getTaskId()); + String finalParentId = ensureFullPathExists(compressedPath, filesList.get(0).getNodeId(), filesList.get(0).getTaskId(), path); parentId = finalParentId; LOGGER.info("路径已全部存在,最终目录ID: {}", finalParentId); } catch (RuntimeException e) { @@ -1158,7 +1165,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 规范化压缩包存放路径 // 如果 compressedPath 是根目录,使用当前工作目录 - Path zipFilePath = Paths.get(storageSourceConfig.getValue() + compressedPath + compressedName + "." + compressedFormat); + Path zipFilePath = Paths.get(storageSourceConfig.getValue() + path + compressedPath + compressedName + "." + compressedFormat); // 检查目标路径是否可写 if (!Files.isWritable(zipFilePath.getParent())) { @@ -1173,11 +1180,13 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 调用压缩方法 Boolean value = compressToSameDirectory(sourceDirs, compressedFormat, zipFilePath); if (value) { + + String workPath = normalizePath(path + compressedPath); //表结构增加 nodeId, String TaskId TsFiles tsFiles = new TsFiles(); tsFiles.setTaskId(filesList.get(0).getTaskId()); tsFiles.setNodeId(filesList.get(0).getNodeId()); - tsFiles.setWorkPath(compressedPath); + tsFiles.setWorkPath(workPath); tsFiles.setIsFile("FILE"); // 获取文件大小(字节) @@ -1208,7 +1217,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl QueryWrapper queryWrapperTsFiles = new QueryWrapper<>(); queryWrapperTsFiles.eq("node_id", filesList.get(0).getNodeId()); queryWrapperTsFiles.eq("task_id", filesList.get(0).getTaskId()); - queryWrapperTsFiles.eq("work_path", compressedPath); + queryWrapperTsFiles.eq("work_path", workPath); queryWrapperTsFiles.eq("file_name", zipFileName); queryWrapperTsFiles.eq("parent_id", filesList.get(0).getParentId()); TsFiles tsFilesdata = tsFilesMapper.selectOne(queryWrapperTsFiles); @@ -1259,36 +1268,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl * @param nodeId 当前节点ID(如 38240bbb160450e03de33df911937d59) * @return 最终目录的数据库ID(即最后一级目录的ID) */ - public String ensureFullPathExists(String compressedPath, String nodeId, String taskId) { - // 1. 解析路径,提取有效层级目录名(跳过nodeId部分) - List pathSegments = Arrays.stream(compressedPath.split("/")) - .filter(s -> !s.isEmpty()) - .collect(Collectors.toList()); - - // 校验路径是否以nodeId开头 - if (pathSegments.isEmpty() || !pathSegments.get(0).equals(nodeId)) { - throw new RuntimeException("路径必须包含当前节点ID"); - } - String nodePath = "/" + nodeId + "/"; - if (compressedPath.equals(nodePath)) { - return "00"; - } - - - // 提取实际要处理的目录层级(去掉开头的nodeId) - List dirSegments = null; - if (pathSegments.size() > 1) { - // 如果 pathSegments 大于 1,去掉 nodeId - dirSegments = pathSegments.subList(1, pathSegments.size()); - } - if (dirSegments.isEmpty()) { - throw new RuntimeException("路径缺少有效目录层级"); - } - - - // 2. 初始化根目录信息(基于nodeId) - String parentId = "00"; // 根目录的父ID为0 - String baseFsPath = "/" + nodeId + "/"; // 本地存储基础路径 + public String ensureFullPathExists(String compressedPath, String nodeId, String taskId, String currentPath) { // 设置当前时间 LocalDateTime now = LocalDateTime.now(); // 转换为 Timestamp @@ -1298,13 +1278,42 @@ public class TsFilesServiceImpl extends ServiceImpl impl UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + + // 1. 解析路径,提取有效层级目录名(跳过nodeId部分) + List pathSegments = Arrays.stream(compressedPath.split("/")) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + +// // 校验路径是否以nodeId开头 +// if (pathSegments.isEmpty() || !pathSegments.get(0).equals(nodeId)) { +// throw new RuntimeException("路径必须包含当前节点ID"); +// } +// String nodePath = "/" + nodeId + "/"; +// if (compressedPath.equals(nodePath)) { +// return "00"; +// } + + + // 提取实际要处理的目录层级(去掉开头的nodeId) +// List dirSegments = null; +// if (pathSegments.size() > 1) { +// // 如果 pathSegments 大于 1,去掉 nodeId +// dirSegments = pathSegments.subList(1, pathSegments.size()); +// } +// if (dirSegments.isEmpty()) { +// throw new RuntimeException("路径缺少有效目录层级"); +// } + + + // 2. 初始化根目录信息(基于nodeId) + String parentId = "00"; // 根目录的父ID为0 + // 3. 逐层处理目录(从第一个有效目录开始) - String currentPath = baseFsPath; - for (String dirName : dirSegments) { - // 检查当前层级目录是否存在 + for (String dirName : pathSegments) { + //如果为空 查询文件表 // 检查当前层级目录是否存在 TsFiles dir = tsFilesMapper.selectOne(new QueryWrapper() - .eq("node_id", nodeId) .eq("task_id", taskId) + .eq("node_id", nodeId) .eq("parent_id", parentId) .eq("work_path", currentPath) .eq("file_name", dirName)); @@ -1740,25 +1749,26 @@ public class TsFilesServiceImpl extends ServiceImpl impl * 参数说明 id 要解压的文件id * 参数说明 decompressionPath 解压缩路径 * 参数说明 parentId 父ID + * 参数说明 path 根目录 /项目名/节点名称/ * 返回值说明: com.yfd.platform.config.ResponseResult ***********************************/ @Override - public String decompressionFolder(String id, String decompressionPath, String parentId) { + public String decompressionFolder(String id, String decompressionPath, String parentId, String path) { // 查询本地文件路径根目录(如 E:\yun) - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("name", "filePath"); - StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local"); decompressionPath = normalizePath(decompressionPath); // 1. 获取压缩包记录 TsFiles zipFileRecord = tsFilesMapper.selectById(id); try { - String finalParentId = ensureFullPathExists(decompressionPath, zipFileRecord.getNodeId(), zipFileRecord.getTaskId()); + String finalParentId = ensureFullPathExists(decompressionPath, zipFileRecord.getNodeId(), zipFileRecord.getTaskId(), path); parentId = finalParentId; LOGGER.info("路径已全部存在,最终目录ID: {}", finalParentId); } catch (RuntimeException e) { LOGGER.error("路径创建失败: {}", e.getMessage()); return "路径创建失败!"; } + //解压以后的路径 + String extractPath = normalizePath(path + decompressionPath); String zipName = getFileNameWithoutExtension(zipFileRecord.getFileName()); // 示例:222 try { @@ -1770,10 +1780,10 @@ public class TsFilesServiceImpl extends ServiceImpl impl Path destRoot; if (hasFolder) { // 如果有文件夹,创建子目录(如 E:/yun/333/222/) - destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath, zipName); + destRoot = Paths.get(storageSourceConfig.getValue(), extractPath, zipName); } else { // 如果只有文件,直接解压到目标目录(如 E:/yun/333/) - destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath); + destRoot = Paths.get(storageSourceConfig.getValue(), extractPath); } Files.createDirectories(destRoot); @@ -1789,7 +1799,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl TsFiles rootFolder = createFolderRecord( zipName, // 文件夹名称222 parentId, // 父ID是333的ID - buildFolderPath(decompressionPath), // 修正为 /333/222/ + buildFolderPath(extractPath), // 修正为 /333/222/ zipFileRecord.getTaskId(), zipFileRecord.getNodeId(), zipFileRecord.getUploader() @@ -1800,7 +1810,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl processFolderContents( unzippedRoot, rootFolderId, - buildFolderPath(decompressionPath + "/" + zipName + "/"), // 修正为 /333/222/ + buildFolderPath(extractPath + "/" + zipName + "/"), // 修正为 /333/222/ zipFileRecord.getTaskId(), zipFileRecord.getNodeId(), zipFileRecord.getUploader() @@ -2509,17 +2519,17 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 递归查询每个记录的子节点,并添加到 records 中 List allFiles = new ArrayList<>(); - if(StringUtils.isNotBlank(nodeId)&&StringUtils.isNotBlank(taskId)){ + if (StringUtils.isNotBlank(nodeId) && StringUtils.isNotBlank(taskId)) { for (TsFiles tsFiles : parentFiles) { - tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 +// tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 // 如果备份路径为空 增加 将当前节点加入结果列表 if (StringUtils.isEmpty(tsFiles.getBackupPath())) { allFiles.add(tsFiles); } } - }else { + } else { for (TsFiles tsFiles : parentFiles) { - tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 +// tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 // 如果备份路径为空 增加 将当前节点加入结果列表 if (StringUtils.isEmpty(tsFiles.getBackupPath()) && "FILE".equals(tsFiles.getIsFile())) { allFiles.add(tsFiles); @@ -2568,7 +2578,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 对每个子节点递归查询其子节点 for (TsFiles child : children) { - child.setWorkPath(processingPath(child.getWorkPath(), child.getNodeId())); // 优化点11:路径处理内聚 + // child.setWorkPath(processingPath(child.getWorkPath(), child.getNodeId())); // 优化点11:路径处理内聚 if (StringUtils.isEmpty(child.getBackupPath())) { allFiles.add(child); } @@ -2609,7 +2619,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 递归查询每个记录的子节点,并添加到 records 中 List allFiles = new ArrayList<>(); for (TsFiles tsFiles : records) { - tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 + // tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 // 如果备份路径为空 增加 将当前节点加入结果列表 if (StringUtils.isEmpty(tsFiles.getBackupPath())) { allFiles.add(tsFiles); @@ -2661,17 +2671,17 @@ public class TsFilesServiceImpl extends ServiceImpl impl List parentFiles = tsFilesMapper.selectList(queryWrapper); // 递归查询每个记录的子节点,并添加到 records 中 List allFiles = new ArrayList<>(); - if(StringUtils.isNotBlank(nodeId)&&StringUtils.isNotBlank(taskId)){ + if (StringUtils.isNotBlank(nodeId) && StringUtils.isNotBlank(taskId)) { for (TsFiles tsFiles : parentFiles) { - tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 + // tsFiles.setWorkPath(processingPath(tsFiles.getWorkPath(), tsFiles.getNodeId())); // 优化点11:路径处理内聚 // 如果备份路径为空 增加 将当前节点加入结果列表 if (StringUtils.isEmpty(tsFiles.getWorkPath())) { allFiles.add(tsFiles); } } - }else { + } else { for (TsFiles tsFiles : parentFiles) { - tsFiles.setBackupPath(processingPath(tsFiles.getBackupPath(), tsFiles.getNodeId())); + // tsFiles.setBackupPath(processingPath(tsFiles.getBackupPath(), tsFiles.getNodeId())); // 如果工作路径为空 增加 将当前节点加入结果列表 if (StringUtils.isEmpty(tsFiles.getWorkPath())) { allFiles.add(tsFiles); @@ -2684,8 +2694,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - - // ==================== 3. 内存分页处理 ==================== int total = allFiles.size(); int pageSize = (int) page.getSize(); @@ -2718,7 +2726,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl if (children != null && !children.isEmpty()) { // 对每个子节点递归查询其子节点 for (TsFiles child : children) { - child.setBackupPath(processingPath(child.getBackupPath(), child.getNodeId())); + // child.setBackupPath(processingPath(child.getBackupPath(), child.getNodeId())); if (StringUtils.isEmpty(child.getWorkPath())) { allFiles.add(child); } @@ -2760,7 +2768,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 递归查询每个记录的子节点,并添加到 records 中 List allFiles = new ArrayList<>(); for (TsFiles tsFiles : records) { - tsFiles.setBackupPath(processingPath(tsFiles.getBackupPath(), tsFiles.getNodeId())); + //tsFiles.setBackupPath(processingPath(tsFiles.getBackupPath(), tsFiles.getNodeId())); // 如果工作路径为空 增加 将当前节点加入结果列表 if (StringUtils.isEmpty(tsFiles.getWorkPath())) { allFiles.add(tsFiles); @@ -2785,8 +2793,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl @Override public Page compareMd5(List dataset, String nodeId, String taskId, Page page) { // 获取本地文件路径根目录和存储空间名称 - StorageSourceConfig filePathConfig = getStorageConfig("filePath"); - StorageSourceConfig bucketConfig = getStorageConfig("bucketName"); + StorageSourceConfig filePathConfig = getStorageConfig("filePath", "local"); + StorageSourceConfig bucketConfig = getStorageConfig("bucketName", "minio"); + // ================ 1. 执行原始分页查询 ================ QueryWrapper queryWrapper = buildQueryWrapper(dataset, nodeId, taskId); @@ -2827,8 +2836,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl String minioMD5 = getMinioMD5Data(bucketConfig.getValue(), tsFile.getBackupPath(), tsFile.getFileName()); // 路径处理 - tsFile.setWorkPath(processingPath(tsFile.getWorkPath(), tsFile.getNodeId())); - tsFile.setBackupPath(processingPath(tsFile.getBackupPath(), tsFile.getNodeId())); + //tsFile.setWorkPath(processingPath(tsFile.getWorkPath(), tsFile.getNodeId())); + // tsFile.setBackupPath(processingPath(tsFile.getBackupPath(), tsFile.getNodeId())); // 设置MD5字段(即使不满足条件也保留字段) tsFile.setLocatMd5(localMD5); @@ -2861,8 +2870,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl @Override public List compareMd5List(List dataset, String nodeId, String taskId) { // 获取本地文件路径根目录和存储空间名称 - StorageSourceConfig filePathConfig = getStorageConfig("filePath"); - StorageSourceConfig bucketConfig = getStorageConfig("bucketName"); + StorageSourceConfig filePathConfig = getStorageConfig("filePath", "local"); + StorageSourceConfig bucketConfig = getStorageConfig("bucketName", "minio"); // ================ 1. 执行原始分页查询 ================ QueryWrapper queryWrapper = buildQueryWrapper(dataset, nodeId, taskId); @@ -2898,8 +2907,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl String minioMD5 = getMinioMD5Data(bucketConfig.getValue(), tsFile.getBackupPath(), tsFile.getFileName()); // 路径处理 - tsFile.setWorkPath(processingPath(tsFile.getWorkPath(), tsFile.getNodeId())); - tsFile.setBackupPath(processingPath(tsFile.getBackupPath(), tsFile.getNodeId())); + // tsFile.setWorkPath(processingPath(tsFile.getWorkPath(), tsFile.getNodeId())); + // tsFile.setBackupPath(processingPath(tsFile.getBackupPath(), tsFile.getNodeId())); // 设置MD5字段(即使不满足条件也保留字段) tsFile.setLocatMd5(localMD5); @@ -3006,9 +3015,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl } // 辅助方法:获取存储配置 - private StorageSourceConfig getStorageConfig(String name) { + private StorageSourceConfig getStorageConfig(String name, String type) { return storageSourceConfigMapper.selectOne( - new QueryWrapper().eq("name", name) + new QueryWrapper().eq("name", name).eq("type", type) ); } @@ -3051,12 +3060,11 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 转换为 Timestamp Timestamp currentTime = Timestamp.valueOf(now); - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("name", "filePath"); - StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); - QueryWrapper queryWrapper1 = new QueryWrapper<>(); - queryWrapper1.eq("name", "bucketName"); - StorageSourceConfig storageSourceConfig1 = storageSourceConfigMapper.selectOne(queryWrapper1); + + StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local"); + + StorageSourceConfig storageSourceConfig1 = getStorageConfig("bucketName", "minio"); + List parameterLists = parameter.getParameterLists(); @@ -3411,14 +3419,11 @@ public class TsFilesServiceImpl extends ServiceImpl impl UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); LoginUser loginuser = (LoginUser) authentication.getPrincipal(); - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("name", "filePath"); - StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + StorageSourceConfig storageSourceConfig = getStorageConfig("filePath", "local"); + + StorageSourceConfig storageSourceConfig1 = getStorageConfig("bucketName", "minio"); - QueryWrapper queryWrapper1 = new QueryWrapper<>(); - queryWrapper1.eq("name", "bucketName"); - StorageSourceConfig storageSourceConfig1 = storageSourceConfigMapper.selectOne(queryWrapper1); List parameterLists = parameter.getParameterLists(); for (ParameterList parameterListData : parameterLists) { //创建文件夹 @@ -3631,8 +3636,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl @Transactional(rollbackFor = Exception.class) public String moveFileFolder(MoveCopyFileFolderRequest reques) throws IOException { // 1. 获取存储根路径 - StorageSourceConfig config = storageSourceConfigMapper.selectOne( - new QueryWrapper().eq("name", "filePath")); + StorageSourceConfig config = getStorageConfig("filePath", "local"); + + Path rootPath = Paths.get(config.getValue()); String newPath = reques.getNewPath(); @@ -3912,8 +3918,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl @Transactional(rollbackFor = Exception.class) public String copyFileFolder(MoveCopyFileFolderRequest reques) throws IOException { // 1. 获取存储根路径 - StorageSourceConfig config = storageSourceConfigMapper.selectOne( - new QueryWrapper().eq("name", "filePath")); + StorageSourceConfig config = getStorageConfig("filePath", "local"); + + Path rootPath = Paths.get(config.getValue()); String newPath = reques.getNewPath(); @@ -4329,8 +4336,15 @@ public class TsFilesServiceImpl extends ServiceImpl impl FileItemResult fileItemResult = new FileItemResult(); TsFiles tsFiles = tsFilesMapper.selectById(id); String fileNameData = tsFiles.getFileName(); - String workPath = tsFiles.getWorkPath(); - String path = workPath + fileNameData; + String path = ""; + if ("local".equals(type)) { + String workPath = tsFiles.getWorkPath(); + path = workPath + fileNameData; + } else { + String backupPath = tsFiles.getBackupPath(); + path = backupPath + fileNameData; + } + //准备获取文件的信息 AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(type); fileItemResult = fileService.getFileItem(path); @@ -4402,13 +4416,22 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } // 设置路径字段 +// if (isLocal) { +// String ProcessingPath = processingPath(node.getWorkPath(), node.getNodeId()); +// dto.setPath(ProcessingPath); +// } else { +// String ProcessingPath = processingPath(node.getBackupPath(), node.getNodeId()); +// dto.setPath(ProcessingPath); +// } + // 设置路径字段 if (isLocal) { - String ProcessingPath = processingPath(node.getWorkPath(), node.getNodeId()); + String ProcessingPath = node.getWorkPath(); dto.setPath(ProcessingPath); } else { - String ProcessingPath = processingPath(node.getBackupPath(), node.getNodeId()); + String ProcessingPath = node.getBackupPath(); dto.setPath(ProcessingPath); } + dto.setUrl(null); dto.setType(null); @@ -4455,8 +4478,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl } currentTaskFuture = executorService.submit(() -> { TsFiles tsFiles = tsFilesMapper.selectById(id); - StorageSourceConfig config = storageSourceConfigMapper.selectOne( - new QueryWrapper().eq("name", "filePath")); + StorageSourceConfig config = getStorageConfig("filePath", "local"); + + Path filePath = Paths.get(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName()); try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toFile()))) { @@ -4573,7 +4597,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl @Override public String readFileContent(String id) throws IOException { - StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); + StorageSourceConfig config = getStorageConfig("filePath", "local"); + TsFiles tsFiles = tsFilesMapper.selectById(id); // 1. 路径标准化与安全校验 Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName()); @@ -4596,7 +4621,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl * 验证路径是否合法并转换为标准化路径 */ private Path validateAndNormalizePath(String filePath) throws SecurityException { - StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); + StorageSourceConfig config = getStorageConfig("filePath", "local"); Path targetPath = Paths.get(filePath).normalize(); Path baseDirPath = Paths.get(config.getValue()); @@ -4623,7 +4648,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl */ @Override public void saveFileContent(String id, String content) throws SecurityException, IOException { - StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); + + StorageSourceConfig config = getStorageConfig("filePath", "local"); TsFiles tsFiles = tsFilesMapper.selectById(id); // 1. 路径标准化与安全校验 Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName()); @@ -4646,7 +4672,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl ***********************************/ @Override public void batchUpdateFile(String id, List modifications) throws IOException { - StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); + StorageSourceConfig config = getStorageConfig("filePath", "local"); TsFiles tsFile = tsFilesMapper.selectById(id); if (tsFile == null) { throw new IllegalArgumentException("文件ID不存在: " + id); diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java index 866d39f..5d06fbe 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java @@ -2,30 +2,38 @@ package com.yfd.platform.modules.experimentalData.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.yfd.platform.component.TaskStatusHolder; import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.exception.file.InvalidStorageSourceException; +import com.yfd.platform.modules.config.model.request.FileListRequest; import com.yfd.platform.modules.experimentalData.domain.TsFiles; import com.yfd.platform.modules.experimentalData.domain.TsNodes; +import com.yfd.platform.modules.experimentalData.domain.TsTask; import com.yfd.platform.modules.experimentalData.mapper.TsFilesMapper; import com.yfd.platform.modules.experimentalData.mapper.TsNodesMapper; +import com.yfd.platform.modules.experimentalData.mapper.TsTaskMapper; import com.yfd.platform.modules.experimentalData.service.ITsFilesService; import com.yfd.platform.modules.experimentalData.service.ITsNodesService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.modules.specialDocument.domain.Files; import com.yfd.platform.modules.specialDocument.domain.Nodes; -import com.yfd.platform.modules.specialDocument.mapper.NodesMapper; +import com.yfd.platform.modules.specialDocument.domain.Project; import com.yfd.platform.modules.storage.context.StorageSourceContext; -import com.yfd.platform.modules.storage.mapper.StorageSourceMapper; -import com.yfd.platform.modules.storage.model.entity.StorageSource; import com.yfd.platform.modules.storage.model.enums.FileTypeEnum; import com.yfd.platform.modules.storage.model.request.BatchDeleteRequest; import com.yfd.platform.modules.storage.model.request.NewFolderRequest; -import com.yfd.platform.modules.storage.model.request.RenameFolderRequest; +import com.yfd.platform.modules.storage.model.result.FileItemResult; +import com.yfd.platform.modules.storage.service.StorageSourceService; import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService; import com.yfd.platform.system.domain.LoginUser; import com.yfd.platform.utils.StringUtils; import io.netty.channel.ChannelInboundHandlerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -68,8 +76,22 @@ public class TsNodesServiceImpl extends ServiceImpl impl @Resource private StorageSourceContext storageSourceContext; + @Autowired + private TaskStatusHolder taskStatusHolder; + + //试验任务Mapper + @Resource + private TsTaskMapper tsTaskMapper; + + @Resource + private StorageSourceService storageSourceService; + + //顶级父节点 Top level parent node + public static final String TOP_LEVEL_PARENT_NODE = "00"; + @Override public List> getTsNodesTree(String nodeName, String taskId) { + TsTask tsTask = tsTaskMapper.selectOne(new QueryWrapper().eq("id", taskId)); // 查询所有节点数据 List> allNodes = getAllNodes(taskId); @@ -81,12 +103,16 @@ public class TsNodesServiceImpl extends ServiceImpl impl return new ArrayList<>(); } + // 根节点的基本路径:/项目名称/ + String basePath = "/" + tsTask.getTaskName() + "/"; + // 存储最终结果 List> result = new ArrayList<>(); // 如果 nodeName 为空,返回所有根节点的完整树形结构 if (StringUtils.isEmpty(nodeName)) { for (Map rootNode : rootNodes) { + rootNode.put("path", basePath); result.addAll(buildFullTree(rootNode, allNodes)); } return result; @@ -94,6 +120,7 @@ public class TsNodesServiceImpl extends ServiceImpl impl // 否则,返回从根节点到目标节点的树形结构 for (Map rootNode : rootNodes) { + rootNode.put("path", basePath); List> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName); if (!tree.isEmpty()) { result.addAll(tree); @@ -162,6 +189,13 @@ public class TsNodesServiceImpl extends ServiceImpl impl // 查找当前节点的所有子节点 List> children = findChildren(allNodes, currentNode.get("nodeId").toString()); + // 为每个子节点设置路径:父路径 + 父节点名称 + "/" + for (Map child : children) { + String parentPath = (String) currentNode.get("path"); + String parentName = (String) currentNode.get("nodeName"); + child.put("path", parentPath + parentName + "/"); + } + // 递归构建子树 List> tree = new ArrayList<>(); for (Map child : children) { @@ -213,6 +247,12 @@ public class TsNodesServiceImpl extends ServiceImpl impl // 查找当前节点的所有子节点 List> children = findChildren(allNodes, currentNode.get("nodeId").toString()); + for (Map child : children) { + String parentPath = (String) currentNode.get("path"); + String parentName = (String) currentNode.get("nodeName"); + child.put("path", parentPath + parentName + "/"); + } + // 递归查找目标节点 for (Map child : children) { List> childTree = buildTreeToTargetNode(child, allNodes, nodeName); @@ -242,7 +282,15 @@ public class TsNodesServiceImpl extends ServiceImpl impl @Transactional(rollbackFor = Exception.class) // 添加事务注解,遇到异常时回滚 public ResponseResult addTsNodes(TsNodes tsnodes) { - + //查询文件表 如果有一样的名称不能新增 + List tsFiles = tsFilesMapper.selectList(new QueryWrapper() + .eq("task_id", tsnodes.getTaskId()) + .eq("parent_id", "00") + .eq("file_name", tsnodes.getNodeName()) + .eq("is_file", "FOLDER")); + if (tsFiles.size() > 0) { + return ResponseResult.error("数据管理中存在该文件夹!"); + } // 校验文件名是否包含非法字符 String nodeName = tsnodes.getNodeName(); if (containsInvalidCharacters(nodeName)) { @@ -282,20 +330,38 @@ public class TsNodesServiceImpl extends ServiceImpl impl int valueAdded = tsNodesMapper.insert(tsnodes); if (valueAdded == 1) { LOGGER.info("tsnodes表结构增加成功"); - //判断文件夹是否创建 - AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); - boolean flag = fileService.isFolderCreated(File.separator + tsnodes.getNodeId()); - //如果是false 说明没有创建 那就新建一个文件夹 - if (!flag) { - //本地创建文件夹 - AbstractBaseFileService fileServiceData = storageSourceContext.getByStorageKey("local"); - boolean flagData = fileServiceData.newFolder(File.separator, tsnodes.getNodeId()); - if (!flagData) { - LOGGER.error("创建节点文件夹失败!"); - return ResponseResult.error("新增节点失败!"); - } + + + //新增节点的时候 创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setName(tsnodes.getNodeName());//新建的文件夹名称,示例值(/a/b/c) + newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + newFolderRequest.setPath(tsnodes.getPath());//请求路径,示例值(/) + newFolderRequest.setStorageKey("local");//存储源 key,示例值(local minio sdlocal) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + if (flag) { + return ResponseResult.success(); + } else { + LOGGER.error("节点新增成功,但是local创建文件失败"); + return ResponseResult.error(); } - return ResponseResult.success(); + + +// //判断文件夹是否创建 +// AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); +// boolean flag = fileService.isFolderCreated(File.separator + tsnodes.getNodeId()); +// //如果是false 说明没有创建 那就新建一个文件夹 +// if (!flag) { +// //本地创建文件夹 +// AbstractBaseFileService fileServiceData = storageSourceContext.getByStorageKey("local"); +// boolean flagData = fileServiceData.newFolder(File.separator, tsnodes.getNodeId()); +// if (!flagData) { +// LOGGER.error("创建节点文件夹失败!"); +// return ResponseResult.error("新增节点失败!"); +// } +// } +// return ResponseResult.success(); } else { LOGGER.error("tsnodes表结构增加失败"); return ResponseResult.error(); @@ -367,10 +433,11 @@ public class TsNodesServiceImpl extends ServiceImpl impl /********************************** * 用途说明: 根据ID删除试验任务节点 * 参数说明 id 试验任务节点ID + * 参数说明 path 路径 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 ***********************************/ @Override - public boolean deleteTsNodesById(String id) { + public boolean deleteTsNodesById(String id, String path) { //根据ID 查询当前数据 TsNodes tsNodes = tsNodesMapper.selectById(id); //删除之前 先拼路径 然后删除本地和minio的文件夹 最后删除表结构 @@ -391,15 +458,56 @@ public class TsNodesServiceImpl extends ServiceImpl impl tsFilesService.deleteTsFilesByIds(dataset, "local"); } - // 递归删除子节点 - deleteChildren(tsNodes.getNodeId(), tsNodes.getTaskId()); - // 删除当前节点 - int deleteCount = tsNodesMapper.deleteById(id); - if (deleteCount == 1) { - LOGGER.info("tsnodes表结删除改成功"); - return true; +// // 递归删除子节点 +// deleteChildren(tsNodes.getNodeId(), tsNodes.getTaskId()); + + + //删除当前节点的文件夹 todo 这个地方改动 + // 删除 sdlocal 中的文件夹 + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(tsNodes.getNodeName()); + deleteItemData.setPassword(""); + deleteItemData.setPath(path); + deleteItemData.setType(FileTypeEnum.FOLDER); + deleteItemList.add(deleteItemData); + + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("local"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList); + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) { + boolean flag = false; + try { + if (deleteItem.getType() == FileTypeEnum.FILE) { + flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName()); + } else if (deleteItem.getType() == FileTypeEnum.FOLDER) { + flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName()); + } + + if (flag) { + deleteSuccessCount++; + } else { + deleteFailCount++; + } + } catch (Exception e) { + LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e); + deleteFailCount++; + } + } + if (deleteSuccessCount >= 1) { + // 删除当前节点 + int deleteCount = tsNodesMapper.deleteById(id); + if (deleteCount == 1) { + LOGGER.info("tsnodes表结删除改成功"); + return true; + } else { + LOGGER.error("tsnodes表结构删除失败"); + return false; + } } else { - LOGGER.error("tsnodes表结构删除失败"); return false; } } @@ -446,21 +554,58 @@ public class TsNodesServiceImpl extends ServiceImpl impl * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 ***********************************/ @Override - public boolean deleteTsNodesByTaskId(String taskId) { + public boolean deleteTsNodesByTaskId(String taskId, String path) { Boolean value = false; //根据任务ID 查询所有的集合 不管层级结构全部都删除 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("task_id", taskId); List tsNodesList = tsNodesMapper.selectList(queryWrapper); - if (tsNodesList == null && tsNodesList.isEmpty()) { + if (tsNodesList.size() == 0) { return true; } for (TsNodes tsNodes : tsNodesList) { //删除文件表 Boolean deleteTsFiles = tsFilesService.deleteTsFilesByNodeId(tsNodes.getNodeId(), tsNodes.getTaskId()); - //如果删除成功 接着删除节点表数据 - if (deleteTsFiles) { + + + //删除当前节点的文件夹 todo 这个地方改动 + // 删除 sdlocal 中的文件夹 + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(tsNodes.getNodeName()); + deleteItemData.setPassword(""); + deleteItemData.setPath(path); + deleteItemData.setType(FileTypeEnum.FOLDER); + deleteItemList.add(deleteItemData); + + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("local"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList); + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) { + boolean flag = false; + try { + if (deleteItem.getType() == FileTypeEnum.FILE) { + flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName()); + } else if (deleteItem.getType() == FileTypeEnum.FOLDER) { + flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName()); + } + + if (flag) { + deleteSuccessCount++; + } else { + deleteFailCount++; + } + } catch (Exception e) { + LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e); + deleteFailCount++; + } + } + + if (deleteSuccessCount >= 1) { // 删除当前节点 int deleteCount = tsNodesMapper.deleteById(tsNodes.getNodeId()); if (deleteCount == 1) { @@ -470,9 +615,269 @@ public class TsNodesServiceImpl extends ServiceImpl impl LOGGER.error("tsnodes表结构删除失败"); value = false; } + } else { + value = false; } } return value; } + /********************************** + * 用途说明: 试验数据扫描接口 + * 参数说明 id 试验任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Override + @Async("asyncExecutor") + public void testDataScanByIdAsync(String id) throws Exception { + try { + // 执行扫描并且插入数据库 + this.testDataScanById(id); + } finally { + // 生成唯一Key + String asyncKey = taskStatusHolder.testDatascanKey(id); + // 无论成功失败都标记完成 + taskStatusHolder.finishTask(asyncKey); + } + } + + private String testDataScanById(String id) throws Exception { + + //查询试验任务信息 + TsTask tsTask = tsTaskMapper.selectById(id); + + + //获取文件列表 + String absolutePath = "/" + tsTask.getTaskName() + "/"; + FileListRequest fileListRequest = buildFileRequest(absolutePath); + String storageKey = fileListRequest.getStorageKey(); + Integer storageId = storageSourceService.findIdByKey(storageKey); + if (storageId == null) { + throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); + } + // 处理请求参数默认值 + fileListRequest.handleDefaultValue(); + AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); + //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件,递归增加 + List fileItemList = fileService.fileList(fileListRequest.getPath()); + + //获取数据库父节点为0的数据 任务ID 上级节点时00 + List tsNodes = tsNodesMapper.selectList(new LambdaQueryWrapper().eq(TsNodes::getParentId, "00").eq(TsNodes::getTaskId, id)); + + // 步骤 1:提取现有的 nodeName + Set existingNodeNames = tsNodes.stream().map(TsNodes::getNodeName).collect(Collectors.toSet()); + + // 步骤 2:筛选新增数据 找到需要新增到数据库的文件夹 这个属于第一层架 + List fileItemNewList = fileItemList.stream().filter(fileItem -> !existingNodeNames.contains(fileItem.getName())).collect(Collectors.toList()); + firstLayerData(fileItemNewList, id); + + return "扫描完成"; + } + + /** + * 第一层下面应该只有文件夹 + * + * @param fileItemList 项目下面第一层级的所有数据 + * @param taskId 所属任务ID + * @throws Exception + */ + public void firstLayerData(List fileItemList, String taskId) throws Exception { + + for (FileItemResult item : fileItemList) { + //思路就是 如果是文件夹 就查询一下 没有就新增, 新的的时候递归往下走 + if (item.getType() == FileTypeEnum.FOLDER) { + //先查询有没有 如果没有就新增 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TsNodes::getTaskId, taskId); + queryWrapper.eq(TsNodes::getParentId, TOP_LEVEL_PARENT_NODE); + queryWrapper.eq(TsNodes::getNodeName, item.getName()); + TsNodes nodeData = tsNodesMapper.selectOne(queryWrapper); + //如果没有 新增 并且递归 + if (nodeData == null) { + TsNodes node = savetsNodes(taskId, TOP_LEVEL_PARENT_NODE, item.getName()); + otherLevelsData(taskId, node.getNodeId(), item.getName(), item.getPath(), TOP_LEVEL_PARENT_NODE); + } else { + otherLevelsData(taskId, nodeData.getNodeId(), item.getName(), item.getPath(), TOP_LEVEL_PARENT_NODE); + } + + } +// else { +// //获取节点名称 +// String nodeName = getLastPathSegment(item.getPath()); +// //获取节点信息 主要用到ID +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("node_name", nodeName); +// queryWrapper.eq("parent_id", TOP_LEVEL_PARENT_NODE); +// Nodes node = nodesMapper.selectOne(queryWrapper); +// +// //新增之前先查询 +// LambdaQueryWrapper queryWrapper1 = new LambdaQueryWrapper<>(); +// queryWrapper1.eq(Files::getProjectId, projectId); +// queryWrapper1.eq(Files::getNodeId, node.getId()); +// queryWrapper1.eq(Files::getFilePath, item.getPath()); +// queryWrapper1.eq(Files::getFileName, item.getName()); +// Files files = filesMapper.selectOne(queryWrapper1); +// if (files == null) { +// //保存文件信息 +// saveFiles(projectId, node.getId(), item.getPath(), item.getName(), String.valueOf(item.getSize())); +// } +// } + } + } + + /** + * @param taskId 所属项目ID + * @param nodeId 节点ID + * @param nodeName 节点名称 + * @param path 路径 + * @param parentId 父级ID + * @return + */ + private void otherLevelsData(String taskId, String nodeId, String nodeName, String path, String parentId) throws Exception { + + //通过节点的路径加名称 查询下面的文件及文件夹 + String absolutePath = path + nodeName + "/"; + //获取文件列表 + FileListRequest fileListRequest = buildFileRequest(absolutePath); + String storageKey = fileListRequest.getStorageKey(); + Integer storageId = storageSourceService.findIdByKey(storageKey); + if (storageId == null) { + throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); + } + // 处理请求参数默认值 + fileListRequest.handleDefaultValue(); + AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); + //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件,递归增加 + List fileItemList = fileService.fileList(fileListRequest.getPath()); + + for (FileItemResult item : fileItemList) { + //思路就是 如果是文件夹 就查询一下 没有就新增, 新的的时候递归往下走 + if (item.getType() == FileTypeEnum.FOLDER) { + //先查询有没有 如果没有就新增 条件 节点ID 任务ID 上级ID 工作空间路径 文件名称 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TsFiles::getTaskId, taskId); + queryWrapper.eq(TsFiles::getNodeId, nodeId); + queryWrapper.eq(TsFiles::getParentId, parentId); + queryWrapper.eq(TsFiles::getFileName, item.getName()); + queryWrapper.eq(TsFiles::getWorkPath, item.getPath()); + TsFiles tsFiles = tsFilesMapper.selectOne(queryWrapper); + //如果没有 新增 并且递归 + if (tsFiles == null) { + //保存文件 + TsFiles tsFilesData = savetsFiles(taskId, nodeId, item.getName(), item.getPath(), parentId, "FOLDER", String.valueOf(item.getSize())); + otherLevelsData(taskId, nodeId, item.getName(), item.getPath(), tsFilesData.getId()); + + } else { + otherLevelsData(taskId, nodeId, item.getName(), item.getPath(), tsFiles.getId()); + } + } else { + //todo 如果是文件 直接新增就可以了 不需要其他的操作 + //先查询有没有 如果没有就新增 条件 节点ID 任务ID 上级ID 工作空间路径 文件名称 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TsFiles::getTaskId, taskId); + queryWrapper.eq(TsFiles::getNodeId, nodeId); + queryWrapper.eq(TsFiles::getParentId, parentId); + queryWrapper.eq(TsFiles::getFileName, item.getName()); + queryWrapper.eq(TsFiles::getWorkPath, item.getPath()); + TsFiles tsFiles = tsFilesMapper.selectOne(queryWrapper); + if (tsFiles == null) { + + // 获取文件大小(字节) + long fileSizeInBytes = item.getSize(); + // 转换为 MB 并保留两位小数 + double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0); + String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 + + //保存文件信息 + TsFiles tsFilesData = savetsFiles(taskId, nodeId, item.getName(), item.getPath(), parentId, "FILE", fileSizeFormatted); + LOGGER.info("保存文件信息:{}", item.getPath() + item.getName()); + } + } + } + } + + /** + * @param taskId 所属项目ID + * @param nodeId 节点ID + * @param fileName 名称 + * @param path 路径 + * @param parentId 父级ID + * @param isFile 文件还是文件夹 + * @param fileSize 文件大小 + * @return + */ + private TsFiles savetsFiles(String taskId, String nodeId, String fileName, String path, String parentId, String isFile, String fileSize) { + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + + //保存文件信息 + TsFiles tsFiles1 = new TsFiles(); + //任务 + tsFiles1.setTaskId(taskId); + //节点 + tsFiles1.setNodeId(nodeId); + //文件 文件夹 区分 + tsFiles1.setIsFile(isFile); + //上级ID + tsFiles1.setParentId(parentId); + //文件名称 + tsFiles1.setFileName(fileName); + //工作空间路径 + tsFiles1.setWorkPath(path); + tsFiles1.setUploadTime(currentTime); + + if ("null".equals(fileSize)) { + tsFiles1.setFileSize("0.001"); + } else { + //文件大小 + if ("0.000".equals(fileSize)) { + tsFiles1.setFileSize("0.001"); + + } else { + tsFiles1.setFileSize(fileSize); + } + } + + tsFilesMapper.insert(tsFiles1); + return tsFiles1; + } + + /** + * @param taskId 所属任务ID + * @param parentId 父节点ID + * @param nodeName 节点名称 + * @return + */ + private TsNodes savetsNodes(String taskId, String parentId, String nodeName) { + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + //获取当前登录用户 + QueryWrapper queryWrapperNodeOrder = new QueryWrapper<>(); + int orderno = this.count(queryWrapperNodeOrder.eq("parent_id", TOP_LEVEL_PARENT_NODE)) + 1; + + TsNodes node = new TsNodes(); + node.setTaskId(taskId); + node.setParentId(parentId); + node.setNodeName(nodeName); + node.setCreateTime(currentTime); + node.setNodeOrder(orderno); + tsNodesMapper.insert(node); + return node; + } + + private FileListRequest buildFileRequest(String path) { + FileListRequest fileListRequest = new FileListRequest(); + fileListRequest.setOrderBy("time"); + fileListRequest.setOrderDirection("desc"); + fileListRequest.setPassword(""); + fileListRequest.setPath(path); + fileListRequest.setStorageKey("local"); + + return fileListRequest; + } + } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java index 95b16de..f2462b3 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java @@ -1,8 +1,10 @@ package com.yfd.platform.modules.experimentalData.service.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.yfd.platform.modules.experimentalData.domain.TsTask; import com.yfd.platform.modules.experimentalData.mapper.TsTaskMapper; @@ -10,16 +12,24 @@ import com.yfd.platform.modules.experimentalData.service.ITsFilesService; import com.yfd.platform.modules.experimentalData.service.ITsNodesService; import com.yfd.platform.modules.experimentalData.service.ITsTaskService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.modules.specialDocument.domain.Project; +import com.yfd.platform.modules.storage.context.StorageSourceContext; +import com.yfd.platform.modules.storage.model.enums.FileTypeEnum; +import com.yfd.platform.modules.storage.model.request.BatchDeleteRequest; +import com.yfd.platform.modules.storage.model.request.NewFolderRequest; +import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService; import com.yfd.platform.utils.StringUtils; import io.netty.channel.ChannelInboundHandlerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; /** @@ -44,6 +54,13 @@ public class TsTaskServiceImpl extends ServiceImpl impleme @Resource private ITsNodesService tsNodesService; + @Resource + private StorageSourceContext storageSourceContext; + + + private static final String INITIAL_CODE = "00001"; + private static final int MAX_CODE_VALUE = 99999; + /********************************** * 用途说明: 分页查询试验数据管理-试验任务管理 * 参数说明 @@ -52,13 +69,17 @@ public class TsTaskServiceImpl extends ServiceImpl impleme * endDate (结束日期) * taskPlace 任务地点 * taskPerson 任务人员 - * carrierType 载体类型 - * deviceCode 设备 + * taskCode 任务编号 + * taskType 任务类型 + * carrierName 载体名称 + * deviceCode 设备代号_编号 + * testDescribe 试验描述 + * sensorDescribe 传感器描述 * pageNum 当前页 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 ***********************************/ @Override - public Page getTsTaskPage(String taskName, String startDate, String endDate, String taskPlace, String taskPerson, String carrierType, String deviceCode, Page page) { + public Page getTsTaskPage(String taskName, String startDate, String endDate, String taskPlace, String taskPerson, String taskCode, String taskType, String carrierName, String deviceCode, String testDescribe, String sensorDescribe, Page page) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); //如果任务名称 taskName 不为空 @@ -76,15 +97,32 @@ public class TsTaskServiceImpl extends ServiceImpl impleme queryWrapper.like(TsTask::getTaskPerson, taskPerson); } - //如果操作类型 carrierType 不为空 - if (StringUtils.isNotEmpty(carrierType)) { - queryWrapper.like(TsTask::getCarrierType, carrierType); + //如果任务编号 taskCode 不为空 + if (StringUtils.isNotEmpty(taskCode)) { + queryWrapper.like(TsTask::getTaskCode, taskCode); } - //如果设备 deviceCode 不为空 + //如果任务类型 taskType 不为空 + if (StringUtils.isNotEmpty(taskType)) { + queryWrapper.eq(TsTask::getTaskType, taskType); + } + //如果载体名称 carrierName 不为空 + if (StringUtils.isNotEmpty(carrierName)) { + queryWrapper.like(TsTask::getCarrierName, carrierName); + } + //如果设备代号_编号 deviceCode 不为空 if (StringUtils.isNotEmpty(deviceCode)) { queryWrapper.like(TsTask::getDeviceCode, deviceCode); } + //如果试验描述 testDescribe 不为空 + if (StringUtils.isNotEmpty(testDescribe)) { + queryWrapper.like(TsTask::getTestDescribe, testDescribe); + } + //如果传感器描述 sensorDescribe 不为空 + if (StringUtils.isNotEmpty(sensorDescribe)) { + queryWrapper.like(TsTask::getSensorDescribe, sensorDescribe); + } + //开始时间startDate DateTime parseStartDate = DateUtil.parse(startDate); @@ -92,9 +130,9 @@ public class TsTaskServiceImpl extends ServiceImpl impleme DateTime parseEndDate = DateUtil.parse(endDate); //开始时间和结束时间不为空 查询条件>=开始时间 <结束时间 if (parseStartDate != null && parseEndDate != null) { - queryWrapper.ge(TsTask::getTaskDate, parseStartDate).lt(TsTask::getTaskDate, parseEndDate); + queryWrapper.ge(TsTask::getTaskStartdate, parseStartDate).lt(TsTask::getTaskEnddate, parseEndDate); } - queryWrapper.orderByDesc(TsTask::getTaskDate); + queryWrapper.orderByDesc(TsTask::getTaskStartdate); //分页查询 Page tsTaskPage = tsTaskMapper.selectPage(page, queryWrapper); return tsTaskPage; @@ -108,6 +146,15 @@ public class TsTaskServiceImpl extends ServiceImpl impleme ***********************************/ @Override public Boolean addSdproject(TsTask tsTask) { + //todo 新增实验任务的时候创建一个本地的文件夹 + //生成任务编号 + String taskCode = generateNextTsTaskCode(); + tsTask.setTaskCode(taskCode); + + //生成任务名称 任务开始时间_结束时间_地点_载机名称_设备代号_编号 + String taskName = buildTaskName(tsTask); + tsTask.setTaskName(taskName); + // 设置当前时间 LocalDateTime now = LocalDateTime.now(); // 转换为 Timestamp @@ -115,12 +162,54 @@ public class TsTaskServiceImpl extends ServiceImpl impleme tsTask.setCreateTime(currentTime); int valueAdded = tsTaskMapper.insert(tsTask); if (valueAdded == 1) { - return true; + + // 根节点路径 + String path = "/"; + //新增节点的时候 创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setName(tsTask.getTaskName());//新建的文件夹名称,示例值(/a/b/c) + newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + newFolderRequest.setPath(path);//请求路径,示例值(/) + newFolderRequest.setStorageKey("local");//存储源 key,示例值(local minio) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + if (flag) { + return true; + } else { + LOGGER.error("试验任务增加成功,但是本地试验任务文件夹创建失败"); + return false; + } } else { return false; } } + + public static String buildTaskName(TsTask tsTask) { + List parts = new ArrayList<>(); + + // 按顺序添加非空字段 + addIfNotEmpty(parts, String.valueOf(tsTask.getTaskStartdate())); + addIfNotEmpty(parts, String.valueOf(tsTask.getTaskEnddate())); + addIfNotEmpty(parts, tsTask.getTaskPlace()); + addIfNotEmpty(parts, tsTask.getCarrierName()); + addIfNotEmpty(parts, tsTask.getDeviceCode()); + + // 使用下划线连接非空部分 + return String.join("_", parts); + } + + private static void addIfNotEmpty(List list, String value) { + if (StringUtils.isNotBlank(value)) { // 使用Apache Commons Lang的空判断 + list.add(value.trim()); // 去除首尾空格 + } + } + + // 备用方案(不使用第三方库) + private static boolean isNotEmpty(String value) { + return value != null && !value.trim().isEmpty(); + } + /********************************** * 用途说明: 修改试验数据管理-试验任务管理 * 参数说明 @@ -129,6 +218,10 @@ public class TsTaskServiceImpl extends ServiceImpl impleme ***********************************/ @Override public boolean updatetsTask(TsTask tsTask) { + + //生成任务名称 任务开始时间_结束时间_地点_载机名称_设备代号_编号 + String taskName = buildTaskName(tsTask); + tsTask.setTaskName(taskName); int valueUpdate = tsTaskMapper.updateById(tsTask); if (valueUpdate == 1) { return true; @@ -147,28 +240,105 @@ public class TsTaskServiceImpl extends ServiceImpl impleme Boolean value = false; //循环所有的ID - for(String taskId : dataset){ + for (String taskId : dataset) { + + TsTask tsTask = tsTaskMapper.selectById(taskId); + String path = "/" + tsTask.getTaskName() + "/"; //调用删除节点 根据任务ID - Boolean deleteTsnodes = tsNodesService.deleteTsNodesByTaskId(taskId); + Boolean deleteTsnodes = tsNodesService.deleteTsNodesByTaskId(taskId, path); //如果删除成功 接着删除节点表数据 - if(deleteTsnodes){ + if (deleteTsnodes) { LOGGER.info("tsNodes表结删除改成功"); value = true; - }else { + } else { LOGGER.error("tsNodes表结构删除失败"); value = false; } - int deleteCount = tsTaskMapper.deleteById(taskId); - if (deleteCount == 1) { - LOGGER.info("tstask表结删除改成功"); - value = true; + + // 删除 local 中的文件夹 项目文件夹 + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(tsTask.getTaskName()); + deleteItemData.setPassword(""); + deleteItemData.setPath("/"); + deleteItemData.setType(FileTypeEnum.FOLDER); + deleteItemList.add(deleteItemData); + + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("local"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList); + + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) { + boolean flag = false; + try { + if (deleteItem.getType() == FileTypeEnum.FILE) { + flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName()); + } else if (deleteItem.getType() == FileTypeEnum.FOLDER) { + flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName()); + } + + if (flag) { + deleteSuccessCount++; + } else { + deleteFailCount++; + } + } catch (Exception e) { + LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e); + deleteFailCount++; + } + } + + if (deleteSuccessCount >= 1) { + // 删除当前项目 + int deleteCount = tsTaskMapper.deleteById(taskId); + if (deleteCount == 1) { + LOGGER.info("tstask表结删除改成功"); + value = true; + } else { + LOGGER.error("tstask表结构删除失败"); + value = false; + } } else { - LOGGER.error("tstask表结构删除失败"); value = false; } + + } return value; } + + @Transactional(rollbackFor = Exception.class) + public synchronized String generateNextTsTaskCode() { + // 1. 使用悲观锁查询最大编号 + TsTask maxTsTask = this.getOne( + new QueryWrapper() + .select("MAX(task_code) AS taskCode") + .last("FOR UPDATE") + ); + + + // 2. 处理空表情况 + String currentMax = (maxTsTask != null && maxTsTask.getTaskCode() != null) + ? maxTsTask.getTaskCode() + : INITIAL_CODE; + + // 3. 验证并生成新编号 + try { + int numericCode = Integer.parseInt(currentMax); + if (numericCode < 0 || numericCode > MAX_CODE_VALUE) { + throw new IllegalStateException("当前设备编号超出有效范围: " + currentMax); + } + if (numericCode == MAX_CODE_VALUE) { + throw new IllegalStateException("设备编号已达最大值" + MAX_CODE_VALUE); + } + return String.format("%05d", numericCode + 1); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("无效的设备编号格式: " + currentMax); + } + + } } diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/NodesController.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/NodesController.java index cdb8a77..da52f7f 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/NodesController.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/NodesController.java @@ -4,14 +4,17 @@ package com.yfd.platform.modules.specialDocument.controller; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.yfd.platform.annotation.Log; +import com.yfd.platform.component.TaskStatusHolder; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.modules.specialDocument.domain.Nodes; import com.yfd.platform.modules.specialDocument.service.INodesService; import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -32,6 +35,9 @@ public class NodesController { @Resource private INodesService nodesService; + @Autowired + private TaskStatusHolder taskStatusHolder; + /*********************************** * 用途说明:获取专项文档节点 树形结构 * 参数说明 @@ -44,8 +50,8 @@ public class NodesController { @ApiOperation("获取专项文档节点树形结构") @ResponseBody @PreAuthorize("@el.check('select:nodes')") - public ResponseResult getNodesTree(String nodeName,String projectId) { - List> list = nodesService.getNodesTree(nodeName,projectId); + public ResponseResult getNodesTree(String nodeName, String projectId) { + List> list = nodesService.getNodesTree(nodeName, projectId); return ResponseResult.successData(list); } @@ -60,7 +66,7 @@ public class NodesController { @PreAuthorize("@el.check('add:nodes')") public ResponseResult addNodes(@RequestBody Nodes nodes) { //参数校验 对象 节点名称 所属项目ID - if (ObjUtil.isEmpty(nodes) && StrUtil.isBlank(nodes.getNodeName()) && StrUtil.isBlank(nodes.getProjectId()) ) { + if (ObjUtil.isEmpty(nodes) && StrUtil.isBlank(nodes.getNodeName()) && StrUtil.isBlank(nodes.getProjectId())) { return ResponseResult.error("参数为空"); } return nodesService.addNodes(nodes); @@ -86,17 +92,18 @@ public class NodesController { /********************************** * 用途说明: 根据ID删除专项文档节点 * 参数说明 id 专项文档节点ID + * 参数说明 path 路径 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 ***********************************/ @Log(module = "专项文档管理", value = "根据ID删除专项文档节点") @PostMapping("/deleteNodesById") @ApiOperation("根据ID删除专项文档节点") @PreAuthorize("@el.check('del:nodes')") - public ResponseResult deleteNodesById(@RequestParam String id) { + public ResponseResult deleteNodesById(@RequestParam String id, String path) { if (StrUtil.isBlank(id)) { return ResponseResult.error("参数为空"); } - boolean isOk = nodesService.deleteNodesById(id); + boolean isOk = nodesService.deleteNodesById(id,path); if (isOk) { return ResponseResult.success(); } else { @@ -105,6 +112,131 @@ public class NodesController { } + /********************************** + * 用途说明: 专项文档管理扫描接口 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Log(module = "专项文档管理扫描接口", value = "专项文档管理扫描接口!") + @PostMapping("/specialScanById") + @ApiOperation("专项文档管理扫描接口通过所属项目ID") + public ResponseResult specialScanByIdAsync(String id) throws Exception { + if (StrUtil.isEmpty(id)) { + return ResponseResult.error("参数为空"); + } + // 生成唯一Key + String asyncKey = taskStatusHolder.specialGenerateKey(id); + + // 检查任务是否已存在 + String existingStatus = taskStatusHolder.getStatus(asyncKey); + if ("IN_PROGRESS".equals(existingStatus)) { + return ResponseResult.success("专项文档扫描任务正在处理中!"); + } else if ("COMPLETED".equals(existingStatus)) { + return ResponseResult.success("专项文档扫描任务已完成!"); + } + // 原子性启动新任务 + if (taskStatusHolder.startTaskIfAbsent(asyncKey)) { + // 直接异步执行并推送结果 + nodesService.specialScanByIdAsync(id); + return ResponseResult.success("专项文档扫描任务开始处理!"); + } else { + return ResponseResult.success("专项文档扫描任务已由其他请求启动!"); + } + } + + /********************************** + * 用途说明: 获取异步信息 + * 参数说明 id 所属项目ID + * 参数说明 type 要查询的信息类型 0扫描 1上传扫描 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Log(module = "获取异步信息", value = "获取异步信息!") + @PostMapping("/obtainInformationAsync") + @ApiOperation("获取异步信息") + public ResponseResult obtainInformationAsync(String id, String type) throws Exception { + if (StrUtil.isEmpty(id)) { + return ResponseResult.error("id为空"); + } + if (StrUtil.isEmpty(type)) { + return ResponseResult.error("类型为空"); + } + if ("0".equals(type)) { + + + // 生成唯一Key + String asyncKey = taskStatusHolder.specialGenerateKey(id); + + // 检查任务是否已存在 + String existingStatus = taskStatusHolder.getStatus(asyncKey); + if ("TASK_NOT_FOUND".equals(existingStatus)) { + return ResponseResult.success("1"); + } + if ("IN_PROGRESS".equals(existingStatus)) { + return ResponseResult.success("0"); + } else if ("COMPLETED".equals(existingStatus)) { + return ResponseResult.success("专项文档扫描任务已完成!"); + } + + } else { + // 生成唯一Key + String asyncKey = taskStatusHolder.documentUploadKey(id); + + // 检查任务是否已存在 + String existingStatus = taskStatusHolder.getStatus(asyncKey); + if ("TASK_NOT_FOUND".equals(existingStatus)) { + return ResponseResult.success("1"); + } + if ("IN_PROGRESS".equals(existingStatus)) { + return ResponseResult.success("0"); + } else if ("COMPLETED".equals(existingStatus)) { + return ResponseResult.success("专项文档扫描任务已完成!"); + } + + } + + + return null; + } + + /********************************** + * 用途说明: 专项文档管理文档上传接口 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Log(module = "专项文档管理文档上传", value = "专项文档管理文档上传接口!") + @PostMapping("/documentUploadById") + @ApiOperation("专项文档管理文档上传接口通过所属项目ID") + public ResponseResult documentUploadByIdAsync(String id, String fileName) throws Exception { + if (StrUtil.isEmpty(id)) { + return ResponseResult.error("参数为空"); + } + + if (StrUtil.isEmpty(fileName)) { + return ResponseResult.error("参数为空"); + } + + + // 生成唯一Key + String asyncKey = taskStatusHolder.documentUploadKey(id); + + // 检查任务是否已存在 + String existingStatus = taskStatusHolder.getStatus(asyncKey); + if ("IN_PROGRESS".equals(existingStatus)) { + return ResponseResult.success("专项文档扫描任务正在处理中!"); + } else if ("COMPLETED".equals(existingStatus)) { + return ResponseResult.success("专项文档扫描任务已完成!"); + } + // 原子性启动新任务 + if (taskStatusHolder.startTaskIfAbsent(asyncKey)) { + // 直接异步执行并推送结果 + nodesService.documentUploadByIdAsync(id, fileName); + return ResponseResult.success("专项文档扫描任务开始处理!"); + } else { + return ResponseResult.success("专项文档扫描任务已由其他请求启动"); + } + } + + // /********************************** // * 用途说明: 批量删除专项文档节点 // * 参数说明 ids 专项文档节点id数组 diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/ProjectController.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/ProjectController.java index 100949c..dec0dba 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/ProjectController.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/ProjectController.java @@ -39,7 +39,7 @@ public class ProjectController { /********************************** * 用途说明: 分页查询专项文档管理-项目管理 * 参数说明 - * projectCode 项目编号 + * description 项目描述 * projectType 项目类型 *projectName 项目名称 * pageNum 当前页 @@ -48,9 +48,9 @@ public class ProjectController { @GetMapping("/page") @ApiOperation("分页查询专项文档管理项目管理") @PreAuthorize("@el.check('select:project')") - public ResponseResult getSdProjectPage(String projectCode, String projectType, String projectName, Page page) { + public ResponseResult getSdProjectPage(String description, String projectType, String projectName, Page page) { //分页查询 - Page sdProjectPage = projectService.getSdProjectPage(projectCode, projectType, projectName, page); + Page sdProjectPage = projectService.getSdProjectPage(description, projectType, projectName, page); return ResponseResult.successData(sdProjectPage); } diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Nodes.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Nodes.java index 291f100..073c785 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Nodes.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Nodes.java @@ -92,4 +92,11 @@ public class Nodes implements Serializable { private String overallPath; + /** + * TODO路径用于拼接 + */ + @TableField(exist = false) + private String path; + + } diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Project.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Project.java index 67ff9dd..6764dce 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Project.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Project.java @@ -1,8 +1,6 @@ package com.yfd.platform.modules.specialDocument.domain; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.*; import java.sql.Timestamp; import java.time.LocalDate; @@ -63,6 +61,7 @@ public class Project implements Serializable { * 项目启动时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField(updateStrategy = FieldStrategy.IGNORED) private Timestamp projectTime; /** diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/INodesService.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/INodesService.java index 7146942..ab922ee 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/INodesService.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/INodesService.java @@ -44,7 +44,22 @@ public interface INodesService extends IService { /********************************** * 用途说明: 根据ID删除专项文档节点 * 参数说明 id 专项文档节点ID + * 参数说明 path 路径 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 ***********************************/ - boolean deleteNodesById(String id); + boolean deleteNodesById(String id, String path); + + /********************************** + * 用途说明: 专项文档管理扫描接口 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + void specialScanByIdAsync(String id) throws Exception; + + /********************************** + * 用途说明: 专项文档管理文档上传接口 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + void documentUploadByIdAsync(String id,String fileName); } diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IProjectService.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IProjectService.java index e00a212..f8bc4b0 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IProjectService.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IProjectService.java @@ -19,13 +19,13 @@ public interface IProjectService extends IService { /********************************** * 用途说明: 分页查询专项文档管理-项目管理 * 参数说明 - * projectCode 项目编号 + * description 项目描述 * projectType 项目类型 *projectName 项目名称 * pageNum 当前页 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 ***********************************/ - Page getSdProjectPage(String projectCode, String projectType, String projectName, Page page); + Page getSdProjectPage(String description, String projectType, String projectName, Page page); /*********************************** * 用途说明:新增专项文档管理-项目管理 @@ -44,4 +44,6 @@ public interface IProjectService extends IService { boolean updateSdproject(Project project); boolean deleteProjectByIds(List dataset); + + String generateNextProjectCode(); } diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java index cf4fc4c..7e47441 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java @@ -111,7 +111,7 @@ public class FilesServiceImpl extends ServiceImpl implements fileListRequest.setOrderDirection("desc"); fileListRequest.setPassword(""); fileListRequest.setPath(filePath); - fileListRequest.setStorageKey("minio"); + fileListRequest.setStorageKey("sdlocal"); String storageKey = fileListRequest.getStorageKey(); Integer storageId = storageSourceService.findIdByKey(storageKey); @@ -225,7 +225,7 @@ public class FilesServiceImpl extends ServiceImpl implements //校验是否真正上传 String pathAndName = files.getFilePath()+"/" + name; //准备获取文件的信息 - AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("minio"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("sdlocal"); FileItemResult fileItemResult = fileService.getFileItem(pathAndName); if (fileItemResult == null || fileItemResult.getName() == null) { return ResponseResult.error(name + "文件没有上传到空间,请重新选择上传!"); @@ -342,11 +342,11 @@ public class FilesServiceImpl extends ServiceImpl implements deleteItemData.setType(FileTypeEnum.FILE); deleteItemList.add(deleteItemData); - //首先通过ID集合查询所有的内容 然后放到List deleteItems里面 放好以后删除数据库 然后删除minio + //首先通过ID集合查询所有的内容 然后放到List deleteItems里面 放好以后删除数据库 然后删除本地sdlocal BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); batchDeleteRequest.setDeleteItems(deleteItemList); - batchDeleteRequest.setStorageKey("minio"); + batchDeleteRequest.setStorageKey("sdlocal"); AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); List deleteItems = batchDeleteRequest.getDeleteItems(); @@ -399,7 +399,7 @@ public class FilesServiceImpl extends ServiceImpl implements } - // 修改 MinIO 文件名的方法 + // 修改 sdlocal 文件名的方法 private boolean updateMinioFileName(Files filesData, Files files) { try { RenameFileRequest renameFileRequest = new RenameFileRequest(); @@ -407,12 +407,12 @@ public class FilesServiceImpl extends ServiceImpl implements renameFileRequest.setNewName(files.getFileName()); renameFileRequest.setPassword(""); renameFileRequest.setPath(filesData.getFilePath()); - renameFileRequest.setStorageKey("minio"); + renameFileRequest.setStorageKey("sdlocal"); AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(renameFileRequest.getStorageKey()); return fileService.renameFile(renameFileRequest.getPath(), renameFileRequest.getName(), renameFileRequest.getNewName()); } catch (Exception e) { - LOGGER.error("MinIO 修改文件名时发生异常", e); + LOGGER.error("sdlocal 修改文件名时发生异常", e); return false; } } diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/NodesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/NodesServiceImpl.java index 18a2396..f5196ad 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/NodesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/NodesServiceImpl.java @@ -2,43 +2,64 @@ package com.yfd.platform.modules.specialDocument.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.StrUtil; +import com.amazonaws.util.IOUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.yfd.platform.component.TaskStatusHolder; import com.yfd.platform.config.ResponseResult; -import com.yfd.platform.modules.experimentalData.domain.TsFiles; -import com.yfd.platform.modules.experimentalData.domain.TsNodes; +import com.yfd.platform.exception.file.InvalidStorageSourceException; +import com.yfd.platform.modules.config.model.request.FileListRequest; + import com.yfd.platform.modules.specialDocument.domain.Files; import com.yfd.platform.modules.specialDocument.domain.Nodes; +import com.yfd.platform.modules.specialDocument.domain.Project; import com.yfd.platform.modules.specialDocument.mapper.FilesMapper; import com.yfd.platform.modules.specialDocument.mapper.NodesMapper; import com.yfd.platform.modules.specialDocument.mapper.ProjectMapper; import com.yfd.platform.modules.specialDocument.service.INodesService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yfd.platform.modules.storage.context.StorageSourceContext; -import com.yfd.platform.modules.storage.controller.file.FileOperatorController; +import com.yfd.platform.modules.storage.mapper.StorageSourceConfigMapper; +import com.yfd.platform.modules.storage.model.entity.StorageSourceConfig; import com.yfd.platform.modules.storage.model.enums.FileTypeEnum; import com.yfd.platform.modules.storage.model.request.BatchDeleteRequest; import com.yfd.platform.modules.storage.model.request.NewFolderRequest; import com.yfd.platform.modules.storage.model.request.RenameFolderRequest; +import com.yfd.platform.modules.storage.model.result.FileItemResult; +import com.yfd.platform.modules.storage.service.StorageSourceService; import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService; import com.yfd.platform.system.domain.LoginUser; -import com.yfd.platform.system.domain.SysDictionaryItems; import com.yfd.platform.utils.StringUtils; import io.netty.channel.ChannelInboundHandlerAdapter; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; /** *

@@ -63,11 +84,27 @@ public class NodesServiceImpl extends ServiceImpl implements private FilesMapper filesMapper; @Resource - private FileOperatorController fileOperatorController; + private StorageSourceConfigMapper storageSourceConfigMapper; @Resource private StorageSourceContext storageSourceContext; + @Resource + private StorageSourceService storageSourceService; + + @Autowired + private TaskStatusHolder taskStatusHolder; + + //专项项目表Mapper + @Resource + private ProjectMapper projectMapper; + + //顶级父节点 Top level parent node + public static final String TOP_LEVEL_PARENT_NODE = "00"; + + private static final String INITIAL_CODE = "00001"; + private static final int MAX_CODE_VALUE = 99999; + /*********************************** * 用途说明:获取专项文档节点 树形结构 * 参数说明 @@ -77,6 +114,8 @@ public class NodesServiceImpl extends ServiceImpl implements ***********************************/ @Override public List> getNodesTree(String nodeName, String projectId) { + Project project = projectMapper.selectById(projectId); + // 查询所有节点数据 List> allNodes = getAllNodes(projectId); // 查找所有根节点(parentId为"00"的节点) @@ -85,18 +124,22 @@ public class NodesServiceImpl extends ServiceImpl implements if (rootNodes.isEmpty()) { return new ArrayList<>(); } + // 根节点的基本路径:/项目名称/ + String basePath = "/" + project.getProjectName() + "/"; // 存储最终结果 List> result = new ArrayList<>(); // 如果 nodeName 为空,返回所有根节点的完整树形结构 if (StringUtils.isEmpty(nodeName)) { for (Map rootNode : rootNodes) { + rootNode.put("path",basePath); result.addAll(buildFullTree(rootNode, allNodes)); } return result; } // 否则,返回从根节点到目标节点的树形结构 for (Map rootNode : rootNodes) { + rootNode.put("path",basePath); List> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName); if (!tree.isEmpty()) { result.addAll(tree); @@ -116,6 +159,14 @@ public class NodesServiceImpl extends ServiceImpl implements private List> buildFullTree(Map currentNode, List> allNodes) { // 查找当前节点的所有子节点 List> children = findChildren(allNodes, currentNode.get("id").toString()); + + // 为每个子节点设置路径:父路径 + 父节点名称 + "/" + for (Map child : children) { + String parentPath = (String) currentNode.get("path"); + String parentName = (String) currentNode.get("nodeName"); + child.put("path", parentPath + parentName + "/"); + } + // 递归构建子树 List> tree = new ArrayList<>(); for (Map child : children) { @@ -146,6 +197,11 @@ public class NodesServiceImpl extends ServiceImpl implements // 查找当前节点的所有子节点 List> children = findChildren(allNodes, currentNode.get("id").toString()); + for (Map child : children) { + String parentPath = (String) currentNode.get("path"); + String parentName = (String) currentNode.get("nodeName"); + child.put("path", parentPath + parentName + "/"); + } // 递归查找目标节点 for (Map child : children) { @@ -165,28 +221,6 @@ public class NodesServiceImpl extends ServiceImpl implements // 返回包含所有符合条件的树结构的列表 return result; - -// // 如果当前节点是目标节点,返回当前节点 -// if (currentNode.get("nodeName") instanceof String && ((String) currentNode.get("nodeName")).contains(nodeName)) { -// return Collections.singletonList(currentNode); -// } -//// if (nodeName.equals(currentNode.get("nodeName"))) { -//// return Collections.singletonList(currentNode); -//// } -// // 查找当前节点的所有子节点 -// List> children = findChildren(allNodes, currentNode.get("id").toString()); -// // 递归查找目标节点 -// for (Map child : children) { -// List> childTree = buildTreeToTargetNode(child, allNodes, nodeName); -// if (!childTree.isEmpty()) { -// // 如果找到目标节点,将当前节点加入树中 -// Map nodeWithChildren = new HashMap<>(currentNode); -// nodeWithChildren.put("children", childTree); -// return Collections.singletonList(nodeWithChildren); -// } -// } -// // 如果未找到目标节点,返回空列表 -// return new ArrayList<>(); } /** @@ -268,6 +302,7 @@ public class NodesServiceImpl extends ServiceImpl implements return ResponseResult.error("文件名包含非法字符(<>:\"/\\|?*)!"); } + //获取当前登录用户 UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); @@ -315,20 +350,20 @@ public class NodesServiceImpl extends ServiceImpl implements } // 反转路径,使其从根节点到当前节点 Collections.reverse(pathNodes); - String path = "/" + String.join("/", pathNodes); + //新增节点的时候 创建文件夹 NewFolderRequest newFolderRequest = new NewFolderRequest(); newFolderRequest.setName(nodes.getNodeName());//新建的文件夹名称,示例值(/a/b/c) newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) - newFolderRequest.setPath(path);//请求路径,示例值(/) - newFolderRequest.setStorageKey("minio");//存储源 key,示例值(local minio) + newFolderRequest.setPath(nodes.getPath());//请求路径,示例值(/) + newFolderRequest.setStorageKey("sdlocal");//存储源 key,示例值(local minio sdlocal) AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); if (flag) { return ResponseResult.success(); } else { - LOGGER.error("节点新增成功,但是minio创建文件失败"); + LOGGER.error("节点新增成功,但是sdlocal创建文件失败"); return ResponseResult.error(); } @@ -361,6 +396,9 @@ public class NodesServiceImpl extends ServiceImpl implements nodeNameOld = nodesold.getNodeName(); } + //获取专项文档项目 + Project project = projectMapper.selectById(nodes.getProjectId()); + //获取当前登录用户 UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); @@ -411,14 +449,14 @@ public class NodesServiceImpl extends ServiceImpl implements renameFolderRequest.setName(nodeNameOld);//重命名的原文件夹名称,示例值(movie) renameFolderRequest.setNewName(nodeName);// 重命名后的文件名称,示例值(music) renameFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) - renameFolderRequest.setPath(path);//请求路径,示例值(/) - renameFolderRequest.setStorageKey("minio");//存储源 key,示例值(local minio) + renameFolderRequest.setPath(nodes.getPath());//请求路径,示例值(/) + renameFolderRequest.setStorageKey("sdlocal");//存储源 key,示例值(local minio sdlocal) AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(renameFolderRequest.getStorageKey()); boolean flag = fileService.renameFolder(renameFolderRequest.getPath(), renameFolderRequest.getName(), renameFolderRequest.getNewName()); if (flag) { return ResponseResult.success("重命名成功"); } else { - LOGGER.error("节点修改成功,但是minio修改文件名失败"); + LOGGER.error("节点修改成功,但是sdlocal修改文件名失败"); return ResponseResult.error("重命名失败"); } } else { @@ -492,11 +530,13 @@ public class NodesServiceImpl extends ServiceImpl implements ***********************************/ @Override @Transactional(rollbackFor = Exception.class) // 启用事务 - public boolean deleteNodesById(String id) { + public boolean deleteNodesById(String id,String path) { Boolean value = false; + // 根据ID 查询当前数据 Nodes nodes = nodesMapper.selectById(id); + if (nodes == null) { return false; // 节点不存在 } @@ -512,9 +552,9 @@ public class NodesServiceImpl extends ServiceImpl implements nodesData = nodesMapper.selectById(nodesData.getParentId()); } Collections.reverse(pathNodes); - String path = String.join("/", pathNodes); - // 删除 MinIO 中的文件夹 + + // 删除 sdlocal 中的文件夹 List deleteItemList = new ArrayList<>(); BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); deleteItemData.setName(nodes.getNodeName()); @@ -525,7 +565,7 @@ public class NodesServiceImpl extends ServiceImpl implements BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); batchDeleteRequest.setDeleteItems(deleteItemList); - batchDeleteRequest.setStorageKey("minio"); + batchDeleteRequest.setStorageKey("sdlocal"); AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList); @@ -570,6 +610,7 @@ public class NodesServiceImpl extends ServiceImpl implements return value; } + /** * 递归删除子节点 */ @@ -597,4 +638,771 @@ public class NodesServiceImpl extends ServiceImpl implements } -} \ No newline at end of file + /********************************** + * 用途说明: 专项文档管理扫描接口 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Override + @Async("asyncExecutor") + public void specialScanByIdAsync(String id) throws Exception { + try { + // 执行扫描并且插入数据库 + this.specialScanById(id); + } finally { + // 生成唯一Key + String asyncKey = taskStatusHolder.specialGenerateKey(id); + // 无论成功失败都标记完成 + taskStatusHolder.finishTask(asyncKey); + } + } + + + /********************************** + * 用途说明: 专项文档管理扫描接口实际接口 + * 参数说明 id 所属项目ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + public String specialScanById(String id) throws Exception { + + //查询项目信息 + Project project = projectMapper.selectById(id); + + + //获取文件列表 + String absolutePath = "/" + project.getProjectName() + "/"; + FileListRequest fileListRequest = buildFileRequest(absolutePath); + String storageKey = fileListRequest.getStorageKey(); + Integer storageId = storageSourceService.findIdByKey(storageKey); + if (storageId == null) { + throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); + } + // 处理请求参数默认值 + fileListRequest.handleDefaultValue(); + AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); + //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件,递归增加 + List fileItemList = fileService.fileList(fileListRequest.getPath()); + + //获取数据库父节点为0的文件夹数据 通过所属项目ID和父节点查询 + List nodesList = nodesMapper.selectList(new LambdaQueryWrapper().eq(Nodes::getParentId, "00").eq(Nodes::getProjectId, id)); + + // 步骤 1:提取现有的 nodeName + Set existingNodeNames = nodesList.stream().map(Nodes::getNodeName).collect(Collectors.toSet()); + + // 步骤 2:筛选新增数据 找到需要新增到数据库的文件夹 + List fileItemNewList = fileItemList.stream().filter(fileItem -> !existingNodeNames.contains(fileItem.getName())).collect(Collectors.toList()); + firstLayerData(fileItemNewList, id); + + return "扫描完成"; + } + + + /** + * 第一层下面应该只有文件夹 + * + * @param fileItemList 项目下面第一层级的所有数据 + * @param projectId 项目ID + * @throws Exception + */ + public void firstLayerData(List fileItemList, String projectId) throws Exception { + + int index = 1; + for (FileItemResult item : fileItemList) { + //思路就是 如果是文件夹 就查询一下 没有就新增, 新的的时候递归往下走 + if (item.getType() == FileTypeEnum.FOLDER) { + //先查询有没有 如果没有就新增 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Nodes::getProjectId, projectId); + queryWrapper.eq(Nodes::getParentId, TOP_LEVEL_PARENT_NODE); + queryWrapper.eq(Nodes::getNodeName, item.getName()); +// queryWrapper.eq(Nodes::getNodeOrder, obtainNodeType(index)); + Nodes nodeData = nodesMapper.selectOne(queryWrapper); + //如果没有 新增 并且递归 + if (nodeData == null){ + Nodes node = saveNodes(projectId, TOP_LEVEL_PARENT_NODE, item.getName(), obtainNodeType(index)); + otherLevelsData(projectId, node.getId(), item.getName(), item.getPath(), index); + }else{ + otherLevelsData(projectId, nodeData.getId(), item.getName(), item.getPath(), index); + } + + } else { + //获取节点名称 + String nodeName = getLastPathSegment(item.getPath()); + //获取节点信息 主要用到ID + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_name", nodeName); + queryWrapper.eq("parent_id", TOP_LEVEL_PARENT_NODE); + Nodes node = nodesMapper.selectOne(queryWrapper); + + //新增之前先查询 + LambdaQueryWrapper queryWrapper1 = new LambdaQueryWrapper<>(); + queryWrapper1.eq(Files::getProjectId, projectId); + queryWrapper1.eq(Files::getNodeId, node.getId()); + queryWrapper1.eq(Files::getFilePath, item.getPath()); + queryWrapper1.eq(Files::getFileName, item.getName()); + Files files = filesMapper.selectOne(queryWrapper1); + if (files == null){ + // 获取文件大小(字节) + long fileSizeInBytes = item.getSize(); + // 转换为 MB 并保留两位小数 + double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0); + String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 + //保存文件信息 + saveFiles(projectId, node.getId(), item.getPath(), item.getName(), fileSizeFormatted); + } + } + } + } + + /** + * @param projectId 项目的ID + * @param parentId 父级ID + * @param nodeName 节点名称 + * @param path 路径 + * @return + */ + private void otherLevelsData(String projectId, String parentId, String nodeName, String path, int index) throws Exception { + + index++; + + String absolutePath = path + nodeName + "/"; + //获取文件列表 + FileListRequest fileListRequest = buildFileRequest(absolutePath); + String storageKey = fileListRequest.getStorageKey(); + Integer storageId = storageSourceService.findIdByKey(storageKey); + if (storageId == null) { + throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); + } + // 处理请求参数默认值 + fileListRequest.handleDefaultValue(); + AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); + //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件,递归增加 + List fileItemList = fileService.fileList(fileListRequest.getPath()); + + for (FileItemResult item : fileItemList) { + //思路就是 如果是文件夹 就查询一下 没有就新增, 新的的时候递归往下走 + if (item.getType() == FileTypeEnum.FOLDER) { + + //先查询有没有 如果没有就新增 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Nodes::getProjectId, projectId); + queryWrapper.eq(Nodes::getParentId, TOP_LEVEL_PARENT_NODE); + queryWrapper.eq(Nodes::getNodeName, item.getName()); +// queryWrapper.eq(Nodes::getNodeOrder, obtainNodeType(index)); + Nodes nodeData = nodesMapper.selectOne(queryWrapper); + //如果没有 新增 并且递归 + if (nodeData == null){ + //保存节点 + Nodes node = saveNodes(projectId, parentId, item.getName(), obtainNodeType(index)); + otherLevelsData(projectId, node.getId(), item.getName(), item.getPath(), index); + }else{ + otherLevelsData(projectId, nodeData.getId(), item.getName(), item.getPath(), index); + } + } else { + //todo 如果是文件 直接新增就可以了 不需要其他的操作 + + + //新增之前先查询 + LambdaQueryWrapper queryWrapper1 = new LambdaQueryWrapper<>(); + queryWrapper1.eq(Files::getProjectId, projectId); + queryWrapper1.eq(Files::getNodeId, parentId); + queryWrapper1.eq(Files::getFilePath, item.getPath()); + queryWrapper1.eq(Files::getFileName, item.getName()); + Files files = filesMapper.selectOne(queryWrapper1); + if (files == null){ + // 获取文件大小(字节) + long fileSizeInBytes = item.getSize(); + // 转换为 MB 并保留两位小数 + double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0); + String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 + //保存文件 + saveFiles(projectId, parentId, item.getPath(), item.getName(), fileSizeFormatted); + } + + } + } + } + + + private String obtainNodeType(int index) { + Map map = new HashMap<>(); + map.put(1, "01"); + map.put(2, "02"); + map.put(3, "03"); + map.put(4, "04"); + // 使用 Math.min 和 Math.max 保证 index 在 [1,4] 范围内 + int adjustedIndex = Math.min(Math.max(index, 1), 4); + return map.get(adjustedIndex); + } + + private FileListRequest buildFileRequest(String path) { + FileListRequest fileListRequest = new FileListRequest(); + fileListRequest.setOrderBy("time"); + fileListRequest.setOrderDirection("desc"); + fileListRequest.setPassword(""); + fileListRequest.setPath(path); + fileListRequest.setStorageKey("sdlocal"); + + return fileListRequest; + } + + /** + * @param projectId 项目ID + * @param parentId 父节点ID + * @param nodeName 节点名称 + * @param nodeType 节点类型 + * @return + */ + private Nodes saveNodes(String projectId, String parentId, String nodeName, String nodeType) { + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + //获取当前登录用户 + QueryWrapper queryWrapperNodeOrder = new QueryWrapper<>(); + int orderno = this.count(queryWrapperNodeOrder.eq("parent_id", TOP_LEVEL_PARENT_NODE)) + 1; + + Nodes node = new Nodes(); + node.setProjectId(projectId); + node.setParentId(parentId); + node.setNodeName(nodeName); + node.setNodeType(nodeType); + node.setCreateTime(currentTime); + node.setNodeOrder(orderno); + nodesMapper.insert(node); + return node; + } + + /** + * 保存文件 + * + * @param projectId + * @param nodeId + * @param filePath + * @param fileName + * @param fileSize + */ + private void saveFiles(String projectId, String nodeId, String filePath, String fileName, String fileSize) { + //获取当前登录用户 上传人是当前登录人 +// UsernamePasswordAuthenticationToken authentication = +// (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); +// LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + + Files files = new Files(); + files.setProjectId(projectId); + files.setNodeId(nodeId); + files.setFilePath(filePath); + files.setUploadTime(currentTime); + files.setUploader("admin"); + //files.setUploader(loginuser.getUsername()); + files.setFileName(fileName); + files.setFileSize(fileSize); + filesMapper.insert(files); + } + + /** + * @param path + * @return + */ + public static String getLastPathSegment(String path) { + if (path == null || path.trim().isEmpty()) { + return ""; + } + // 标准化路径处理 + String normalizedPath = path + .replaceAll("\\\\+", "/") // 替换所有反斜杠为斜杠 + .replaceAll("/+", "/") // 合并连续斜杠 + .replaceAll("^/|/$", ""); // 去除首尾斜杠 + + // 分割路径并过滤空段 + List segments = Arrays.stream(normalizedPath.split("/")) + .filter(segment -> !segment.isEmpty()) + .collect(Collectors.toList()); + + return segments.isEmpty() ? "" : segments.get(segments.size() - 1); + } + + + @Override + @Async("asyncExecutor") + public void documentUploadByIdAsync(String id, String fileName) { + try { + + // 查询本地文件路径根目录(如 E:\yun) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("name", "filePath"); + queryWrapper.eq("type", "sdlocal"); + StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + + //解压以后的文件夹名称 + String zipName = getFileNameWithoutExtension(fileName); // 示例:222 + + String decompressionPath = "/" + "temporary"; + //构建要解压的zip文件路径 例如 D:\yun\temporary\111.zip + Path zipFilePath = Paths.get(storageSourceConfig.getValue(), decompressionPath, fileName); + + // 4. 构建解压目标路径 例如 D:\yun\temporary\111 + Path destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath, zipName); + + //查询项目信息 + Project project = projectMapper.selectById(id); + //todo 如果项目不存在 直接拷贝到目标路径下 拷贝完成以后 然后新增一个项目结构 然后走一遍扫描 + + //源文件夹路径 + String sourceFolderPath = "/" + "temporary" + "/" + zipName + "/"; + //目标文件夹路径 + String targetFolderPath = "/" + project.getProjectName() + "/"; + + //首先执行解压缩 + unzipToTemp(zipFilePath, String.valueOf(destRoot)); + // 执行上传并且插入数据库 + this.documentUploadById(id,sourceFolderPath,targetFolderPath,storageSourceConfig.getValue()); + + //开始执行更新表数据 名称是去掉后缀以后的 + uploadProject(zipName); + + } catch (IOException e) { + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + // 生成唯一Key + String asyncKey = taskStatusHolder.documentUploadKey(id); + // 无论成功失败都标记完成 + taskStatusHolder.finishTask(asyncKey); + } + } + //todo 首先得上传到一个临时目录 然后找到这个目录我去解压缩到当前文件夹 解压缩以后 然后开始循环 我通过第一层去查询 如果没有就新增 如果有就循环 看看需不需要覆盖 + + + /********************************** + * 用途说明: 专项文档管理扫描接口实际接口 + * 参数说明 id 所属项目ID + * 参数说明 destRoot 解压以后存放的路径 + * 参数说明 zipName 解压以后的文件夹名称 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + public String documentUploadById(String id, String sourceFolderPath, String targetFolderPath,String value) throws Exception { + + //源目录 + File sourceDir = new File(value + sourceFolderPath); + //目标目录 + File targetDir = new File( value + targetFolderPath); + + if (!targetDir.exists()) { + FileUtils.copyDirectory(sourceDir, targetDir); + LOGGER.info("目标文件没有,全部更新过去"); + return null; + } + + FileListRequest sourcefileListRequest = buildFileRequest(sourceFolderPath); + FileListRequest targetfileListRequest = buildFileRequest(targetFolderPath); + + //获取源文件列表 包含文件和文件夹 + List sourceFileItemList = obtainFileItemResultData(sourcefileListRequest); + + //获取目标文件列表 包含文件和文件夹 + List targetFileItemList = obtainFileItemResultData(targetfileListRequest); + + for (FileItemResult fileItemResult : sourceFileItemList) { + + String a1 = fileItemResult.getType() + fileItemResult.getName(); + FileItemResult targetFileItem = targetFileItemList.stream() + .filter(t -> (t.getType() + t.getName()).equals(a1)) + .findFirst() // 获取第一个匹配项(返回 Optional) + .orElse(null); + + // 检查对象存在 + if (targetFileItem != null) { + //如果存在 判断是文件夹还是文件 如果是文件夹 则继续递归 + if (fileItemResult.getType() == FileTypeEnum.FOLDER) { + try { + documentUploadById(id, fileItemResult.getPath() + "/" + fileItemResult.getName(), targetFileItem.getPath()+ "/" + targetFileItem.getName(),value); + } catch (Exception e) { + throw new RuntimeException(e); + } + }else { + if (fileItemResult.getType() == FileTypeEnum.FILE) { + //源路径 + String sourcePath = value + sourceFolderPath + "/" + fileItemResult.getName(); + + //目标路径 + String targetPath = value + targetFolderPath + "/" + fileItemResult.getName(); + + //根据路径获取MD5值比较 如果相同则不进行操作 如果不同则重命名目标路径下的文件名称以及表结构 把现在源路径下的文件拷贝到目标路径下 新增数据库文件记录 + // 计算源文件MD5 + File sourceLocalFile = new File(sourcePath); + String sourceLocalMD5 = calculateMD5Data(new FileInputStream(sourceLocalFile)); + + // 计算源文件MD5 + File targetLocalFile = new File(targetPath); + String targetLocalMD5 = calculateMD5Data(new FileInputStream(targetLocalFile)); + + // 如果MD5值不相同则进行操作 //如果一致 则直接复制源文件到目标文件 + if (StringUtils.isNoneEmpty(sourceLocalMD5, targetLocalMD5) && !sourceLocalMD5.equals(targetLocalMD5)) { + //拷贝文件到目标目录 将原来的文件名更改以及将数据库表结构更改 + //更改目标路径下的文件名称 + renameFile(targetPath); + //将源目录文件 复制到 目标目录文件 + copyWithOverride(sourcePath,targetPath); + + }else { + //将源目录文件 复制到 目标目录文件 todo这个地方是覆盖 + copyWithOverride(sourcePath,targetPath); + } + + } + } + + + }else{ + //如果为空对象不存在 则直接拷贝 + String sourcePath = value + sourceFolderPath; + //目标路径 就是源路径去掉/temporary的部分 + String targetPath = sourcePath.replace("/" + "temporary", ""); + //将源目录文件 复制到 目标目录文件 + copyFolderWithOverride(sourcePath,targetPath); + + } + } + + + return "上传完成"; + } + + + public String uploadProject(String projectName) throws Exception { + + //查询项目信息 + LambdaQueryWrapper projectLambdaQueryWrapper = new LambdaQueryWrapper<>(); + projectLambdaQueryWrapper.eq(Project::getProjectName, projectName); + Project project = projectMapper.selectOne(projectLambdaQueryWrapper); + //如果项目不存在 + if(project == null){ + String projectCode = this.generateNextProjectCode(); + project.setProjectCode("1"); + + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + project.setCreateTime(currentTime); + int valueAdded = projectMapper.insert(project); + if (valueAdded == 1) { + + // 根节点路径 + String path = "/"; + //新增节点的时候 创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setName(project.getProjectName());//新建的文件夹名称,示例值(/a/b/c) + newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + newFolderRequest.setPath(path);//请求路径,示例值(/) + newFolderRequest.setStorageKey("sdlocal");//存储源 key,示例值(local minio) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + } + } + + + + + //获取文件列表 + String absolutePath = "/" + project.getProjectName() + "/"; + FileListRequest fileListRequest = buildFileRequest(absolutePath); + String storageKey = fileListRequest.getStorageKey(); + Integer storageId = storageSourceService.findIdByKey(storageKey); + if (storageId == null) { + throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); + } + // 处理请求参数默认值 + fileListRequest.handleDefaultValue(); + AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); + //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件,递归增加 + List fileItemList = fileService.fileList(fileListRequest.getPath()); + + //获取数据库父节点为0的文件夹数据 通过所属项目ID和父节点查询 + List nodesList = nodesMapper.selectList(new LambdaQueryWrapper().eq(Nodes::getParentId, "00").eq(Nodes::getParentId, project.getId())); + + // 步骤 1:提取现有的 nodeName + Set existingNodeNames = nodesList.stream().map(Nodes::getNodeName).collect(Collectors.toSet()); + + // 步骤 2:筛选新增数据 找到需要新增到数据库的文件夹 + List fileItemNewList = fileItemList.stream().filter(fileItem -> !existingNodeNames.contains(fileItem.getName())).collect(Collectors.toList()); + firstLayerData(fileItemNewList, project.getId()); + + return "扫描完成"; + } + + + + + // 编号生成工具方法 + @Transactional(rollbackFor = Exception.class) + public synchronized String generateNextProjectCode() { + // 1. 使用悲观锁查询最大编号 + Project maxProject = projectMapper.selectOne( + new QueryWrapper() + .select("MAX(project_code) AS projectCode") + .last("FOR UPDATE") + ); + + // 2. 处理空表情况 + String currentMax = (maxProject != null && maxProject.getProjectCode() != null) + ? maxProject.getProjectCode() + : INITIAL_CODE; + + // 3. 验证并生成新编号 + try { + int numericCode = Integer.parseInt(currentMax); + if (numericCode < 0 || numericCode > MAX_CODE_VALUE) { + throw new IllegalStateException("当前项目编号超出有效范围: " + currentMax); + } + if (numericCode == MAX_CODE_VALUE) { + throw new IllegalStateException("项目编号已达最大值" + MAX_CODE_VALUE); + } + return String.format("%05d", numericCode + 1); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("无效的项目编号格式: " + currentMax); + } + + } + + + private static void copyFolderWithOverride(String source, String target) throws IOException { + File srcDir = new File(source); + File destDir = new File(target); + + // 删除目标文件夹(确保完全覆盖) + if (destDir.exists()) { + FileUtils.deleteDirectory(destDir); + } + + // 递归拷贝文件夹及内容 + FileUtils.copyDirectory(srcDir, destDir); + System.out.println("成功覆盖文件夹: " + target); + } + + private static void copyWithOverride(String source, String target) throws IOException { + File srcFile = new File(source); + File destFile = new File(target); + + // 强制覆盖已存在的文件 + FileUtils.copyFile(srcFile, destFile, true); + System.out.println("成功覆盖文件: " + target); + } + + public static String renameFile(String originalPath) throws IOException { + File originalFile = new File(originalPath); + if (!originalFile.exists()) { + throw new IOException("原文件不存在: " + originalPath); + } + + // 生成新文件名(保留扩展名) + String baseName = FilenameUtils.getBaseName(originalPath); + String extension = FilenameUtils.getExtension(originalPath); + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String newFileName = baseName + "_" + timestamp + "." + extension; + + Path originalFilePath = originalFile.toPath(); + Path newFilePath = Paths.get(originalFile.getParent(), newFileName); + + // 处理目标文件已存在的情况 + if (java.nio.file.Files.exists(newFilePath)) { + java.nio.file.Files.delete(newFilePath); // 删除已存在的目标文件 + } + + // 移动文件 + java.nio.file.Files.move(originalFilePath, newFilePath); + System.out.println("重命名成功: " + originalPath + " → " + newFilePath.toString()); + return newFilePath.toString(); + } + + + // 优化后的 MD5 计算方法(线程安全) + public String calculateMD5Data(InputStream inputStream) throws IOException, NoSuchAlgorithmException { + try (InputStream is = inputStream) { // 确保流关闭 + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + md.update(buffer, 0, bytesRead); + } + return Hex.encodeHexString(md.digest()); + } + } + + + + + + //获取路径下的所有文件和文件夹 + private List obtainFileItemResultData(FileListRequest fileListRequest) throws Exception { + + String storageKey = fileListRequest.getStorageKey(); + Integer storageId = storageSourceService.findIdByKey(storageKey); + if (storageId == null) { + throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); + } + // 处理请求参数默认值 + fileListRequest.handleDefaultValue(); + AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); + //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件,递归增加 + List fileItemList = fileService.fileList(fileListRequest.getPath()); + return fileItemList; + } + + // 新增:统一解压入口方法(保持原有逻辑结构) + + /** + * @param zipFilePath 要解压的zip文件的路径 /path/to/archive.zip + * @param baseDir 解压后的文件存放的根目录路径 + * @return + * @throws IOException + */ + private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException { + String fileName = zipFilePath.getFileName().toString(); + String ext = getFileExtension(fileName).toLowerCase(); + + // 根据文件类型调用不同解压实现 + if (ext.equals(".zip")) { + return unzipFile(zipFilePath, baseDir); +// } else if (fileName.endsWith(".tar.gz")) { +// return unTarGzFile(zipFilePath, baseDir); +// } else if (ext.equals(".tar")) { +// return unTarFile(zipFilePath, baseDir); +// } else if (ext.equals(".7z")) { +// return un7zFile(zipFilePath, baseDir); + } else { + throw new IllegalArgumentException("不支持的压缩格式: " + ext); + } + } + + // 获取文件扩展名的方法 + private String getFileExtension(String filename) { + + if (filename.endsWith(".tar.gz")) { + return ".tar.gz"; + } + // 检查是否是 .tar 格式 + if (filename.endsWith(".tar")) { + return ".tar"; // 去掉 ".tar" + } + // 检查是否是 .zip 格式 + if (filename.endsWith(".zip")) { + return ".zip"; // 去掉 ".zip" + } + // 检查是否是 .7z 格式 + if (filename.endsWith(".7z")) { + return ".7z"; // 去掉 ".7z" + } + if (filename != null && filename.contains(".")) { + return filename.substring(filename.lastIndexOf(".")); + } + return ""; // 如果没有扩展名,返回空字符串 + } + + private String getFileNameWithoutExtension(String fileName) { + // 检查是否是 .tar.gz 格式 + if (fileName.endsWith(".tar.gz")) { + return fileName.substring(0, fileName.length() - 7); // 去掉 ".tar.gz" + } + // 检查是否是 .tar 格式 + if (fileName.endsWith(".tar")) { + return fileName.substring(0, fileName.length() - 4); // 去掉 ".tar" + } + // 检查是否是 .zip 格式 + if (fileName.endsWith(".zip")) { + return fileName.substring(0, fileName.length() - 4); // 去掉 ".zip" + } + // 检查是否是 .7z 格式 + if (fileName.endsWith(".7z")) { + return fileName.substring(0, fileName.length() - 3); // 去掉 ".7z" + } + // 对其他普通文件名,直接返回去除扩展名的部分 + int dotIndex = fileName.lastIndexOf('.'); + return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex); + } + + + private File unzipFile(Path sourcePath, String baseDir) throws IOException { + // 创建目标目录 + Path destRoot = Paths.get(baseDir); + java.nio.file.Files.createDirectories(destRoot); + + try (ZipInputStream zis = new ZipInputStream( + java.nio.file.Files.newInputStream(sourcePath), StandardCharsets.UTF_8)) { + + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + try { + // 跳过 macOS 系统文件 + if (entry.getName().startsWith("__MACOSX")) { + continue; + } + + // 标准化路径并处理目录标识 + String entryName = entry.getName() + .replace("\\", "/") + .replaceFirst("^/+", ""); // 去除开头的斜杠 + + // 检测是否为目录(兼容以'/'结尾的条目) + boolean isDirectory = entry.isDirectory() || entry.getName().endsWith("/"); + + // 调整路径:去除顶层目录(假设所有文件在单一顶层目录下) + if (entryName.contains("/")) { + int firstSlash = entryName.indexOf('/'); + entryName = entryName.substring(firstSlash + 1); + + // 若处理后名称为空,则跳过顶层目录条目 + if (entryName.isEmpty() && isDirectory) { + continue; + } + } + + Path targetPath = destRoot.resolve(entryName).normalize(); + validatePathSafetya(targetPath, destRoot); + + // 处理目录 + if (isDirectory) { + java.nio.file.Files.createDirectories(targetPath); + } + // 处理文件 + else { + if (java.nio.file.Files.exists(targetPath)) { + LOGGER.warn("文件已存在,跳过覆盖: {}", targetPath); + continue; + } + // 确保父目录存在 + java.nio.file.Files.createDirectories(targetPath.getParent()); + try (OutputStream os = java.nio.file.Files.newOutputStream(targetPath)) { + IOUtils.copy(zis, os); + } + } + } finally { + zis.closeEntry(); // 确保每个条目只关闭一次 + } + } + } + + return destRoot.toFile(); + } + + /** + * 路径安全验证(增强版) + */ + private void validatePathSafetya(Path targetPath, Path destRoot) throws IOException { + Path normalizedDest = destRoot.toAbsolutePath().normalize(); + Path normalizedTarget = targetPath.toAbsolutePath().normalize(); + + if (!normalizedTarget.startsWith(normalizedDest)) { + throw new IOException("路径安全验证失败:" + normalizedTarget + + " 超出根目录 " + normalizedDest); + } + } + +} + diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java index 913a98c..6c578f9 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java @@ -1,25 +1,33 @@ package com.yfd.platform.modules.specialDocument.service.impl; +import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.modules.specialDocument.domain.Files; import com.yfd.platform.modules.specialDocument.domain.Nodes; import com.yfd.platform.modules.specialDocument.domain.Project; import com.yfd.platform.modules.specialDocument.mapper.ProjectMapper; import com.yfd.platform.modules.specialDocument.service.INodesService; import com.yfd.platform.modules.specialDocument.service.IProjectService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.yfd.platform.system.domain.SysDictionaryItems; +import com.yfd.platform.modules.storage.context.StorageSourceContext; +import com.yfd.platform.modules.storage.model.enums.FileTypeEnum; +import com.yfd.platform.modules.storage.model.request.BatchDeleteRequest; +import com.yfd.platform.modules.storage.model.request.NewFolderRequest; +import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService; import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; import com.yfd.platform.utils.StringUtils; import io.netty.channel.ChannelInboundHandlerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; /** @@ -35,6 +43,10 @@ public class ProjectServiceImpl extends ServiceImpl impl private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class); + private static final String INITIAL_CODE = "00001"; + private static final int MAX_CODE_VALUE = 99999; + + //专项项目表Mapper @Resource private ProjectMapper projectMapper; @@ -48,22 +60,25 @@ public class ProjectServiceImpl extends ServiceImpl impl @Resource private SysDictionaryItemsMapper sysDictionaryItemsMapper; + @Resource + private StorageSourceContext storageSourceContext; + /********************************** * 用途说明: 分页查询专项文档管理-项目管理 * 参数说明 - * projectCode 项目编号 + * description 项目描述 * projectType 项目类型 *projectName 项目名称 * pageNum 当前页 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 ***********************************/ @Override - public Page getSdProjectPage(String projectCode, String projectType, String projectName, Page page) { + public Page getSdProjectPage(String description, String projectType, String projectName, Page page) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - //如果项目编号 projectCode 不为空 - if (StringUtils.isNotEmpty(projectCode)) { - queryWrapper.like(Project::getProjectCode, projectCode); + //如果项目描述 description 不为空 + if (StringUtils.isNotEmpty(description)) { + queryWrapper.like(Project::getDescription, description); } //如果项目类型 projectType 不为空 if (StringUtils.isNotEmpty(projectType)) { @@ -99,6 +114,41 @@ public class ProjectServiceImpl extends ServiceImpl impl ***********************************/ @Override public Boolean addSdproject(Project project) { + //创建项目的时候要创建一个本地的文件夹 路径的话去找专项的存储路径 + //生成项目编号 + String projectCode = generateNextProjectCode(); + project.setProjectCode(projectCode); + + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + project.setCreateTime(currentTime); + int valueAdded = projectMapper.insert(project); + if (valueAdded == 1) { + + // 根节点路径 + String path = "/"; + //新增节点的时候 创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setName(project.getProjectName());//新建的文件夹名称,示例值(/a/b/c) + newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + newFolderRequest.setPath(path);//请求路径,示例值(/) + newFolderRequest.setStorageKey("sdlocal");//存储源 key,示例值(local minio) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + if (flag) { + return true; + } else { + LOGGER.error("节点新增成功,但是本地专项文件夹创建失败"); + return false; + } + + } else { + return false; + } + + // //查询字典表获取项目类型对应数据字典 // QueryWrapper queryWrapperSysDictionary = new QueryWrapper<>(); // queryWrapperSysDictionary.eq("parentcode", "compressType"); @@ -122,17 +172,38 @@ public class ProjectServiceImpl extends ServiceImpl impl // if(){ // // } - // 设置当前时间 - LocalDateTime now = LocalDateTime.now(); - // 转换为 Timestamp - Timestamp currentTime = Timestamp.valueOf(now); - project.setCreateTime(currentTime); - int valueAdded = projectMapper.insert(project); - if (valueAdded == 1) { - return true; - } else { - return false; + + } + + // 编号生成工具方法 + @Transactional(rollbackFor = Exception.class) + public synchronized String generateNextProjectCode() { + // 1. 使用悲观锁查询最大编号 + Project maxProject = this.getOne( + new QueryWrapper() + .select("MAX(project_code) AS projectCode") + .last("FOR UPDATE") + ); + + // 2. 处理空表情况 + String currentMax = (maxProject != null && maxProject.getProjectCode() != null) + ? maxProject.getProjectCode() + : INITIAL_CODE; + + // 3. 验证并生成新编号 + try { + int numericCode = Integer.parseInt(currentMax); + if (numericCode < 0 || numericCode > MAX_CODE_VALUE) { + throw new IllegalStateException("当前项目编号超出有效范围: " + currentMax); + } + if (numericCode == MAX_CODE_VALUE) { + throw new IllegalStateException("项目编号已达最大值" + MAX_CODE_VALUE); + } + return String.format("%05d", numericCode + 1); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("无效的项目编号格式: " + currentMax); } + } /********************************** @@ -152,7 +223,9 @@ public class ProjectServiceImpl extends ServiceImpl impl // if(sysDictionaryItems != null){ // project.setProjectType(sysDictionaryItems.getDictName()); // } - + if (project.getProjectTime() == null || project.getProjectTime().equals("")) { + project.setProjectTime(null); + } int valueUpdate = projectMapper.updateById(project); if (valueUpdate == 1) { @@ -173,6 +246,8 @@ public class ProjectServiceImpl extends ServiceImpl impl //循环所有的ID for (String id : dataset) { + Project project = projectMapper.selectById(id); + String path = "/" + project.getProjectName() + "/"; QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("project_id", id); queryWrapper.eq("parent_id", "00"); @@ -181,7 +256,7 @@ public class ProjectServiceImpl extends ServiceImpl impl if (nodesList != null || !nodesList.isEmpty()) { for (Nodes nodes : nodesList) { //调用删除节点 根据任务ID - Boolean deleteTsnodes = nodesService.deleteNodesById(nodes.getId()); + Boolean deleteTsnodes = nodesService.deleteNodesById(nodes.getId(), path); //如果删除成功 接着删除节点表数据 if (deleteTsnodes) { LOGGER.info("nodes表结删除改成功"); @@ -193,15 +268,56 @@ public class ProjectServiceImpl extends ServiceImpl impl } } - // 删除当前项目 - int deleteCount = projectMapper.deleteById(id); - if (deleteCount == 1) { - LOGGER.info("project表结删除改成功"); - value = true; + // 删除 sdlocal 中的文件夹 项目文件夹 + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(project.getProjectName()); + deleteItemData.setPassword(""); + deleteItemData.setPath("/"); + deleteItemData.setType(FileTypeEnum.FOLDER); + deleteItemList.add(deleteItemData); + + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("sdlocal"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList); + + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) { + boolean flag = false; + try { + if (deleteItem.getType() == FileTypeEnum.FILE) { + flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName()); + } else if (deleteItem.getType() == FileTypeEnum.FOLDER) { + flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName()); + } + + if (flag) { + deleteSuccessCount++; + } else { + deleteFailCount++; + } + } catch (Exception e) { + LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e); + deleteFailCount++; + } + } + + if (deleteSuccessCount >= 1) { + // 删除当前项目 + int deleteCount = projectMapper.deleteById(id); + if (deleteCount == 1) { + LOGGER.info("project表结删除改成功,本地文件夹删除成功"); + value = true; + } else { + LOGGER.error("project表结构删除失败,本地文件夹删除失败"); + value = false; + } } else { - LOGGER.error("project表结构删除失败"); value = false; } + + } return value; } diff --git a/java/src/main/java/com/yfd/platform/modules/storage/context/StorageSourceContext.java b/java/src/main/java/com/yfd/platform/modules/storage/context/StorageSourceContext.java index f680457..52ebf4b 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/context/StorageSourceContext.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/context/StorageSourceContext.java @@ -178,6 +178,9 @@ public class StorageSourceContext { */ private AbstractBaseFileService getInitStorageBeanByStorageId(Integer storageId) { String keyById = storageSourceMapper.findKeyById(storageId); + StorageSource storageSource = storageSourceMapper.findByStorageKey(keyById); + String type = String.valueOf(storageSource.getType()); + StorageTypeEnum storageTypeEnum = Optional.ofNullable(storageSourceMapper.findByStorageKey(keyById)).map(StorageSource::getType).orElse(null); for (AbstractBaseFileService value : storageTypeEnumFileServiceMap.values()) { if (Objects.equals(value.getStorageTypeEnum(), storageTypeEnum)) { diff --git a/java/src/main/java/com/yfd/platform/modules/storage/controller/file/FileController.java b/java/src/main/java/com/yfd/platform/modules/storage/controller/file/FileController.java index 2598273..594a651 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/controller/file/FileController.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/controller/file/FileController.java @@ -1 +1 @@ -package com.yfd.platform.modules.storage.controller.file; import cn.hutool.core.bean.BeanUtil; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiSort; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.exception.file.InvalidStorageSourceException; import com.yfd.platform.modules.config.model.request.FileListRequest; import com.yfd.platform.modules.storage.chain.FileChain; import com.yfd.platform.modules.storage.chain.FileContext; import com.yfd.platform.modules.storage.context.StorageSourceContext; import com.yfd.platform.modules.storage.convert.StorageSourceConvert; import com.yfd.platform.modules.storage.model.entity.StorageSource; import com.yfd.platform.modules.storage.model.result.FileInfoResult; import com.yfd.platform.modules.storage.model.result.FileItemResult; import com.yfd.platform.modules.storage.model.result.StorageSourceResult; import com.yfd.platform.modules.storage.service.StorageSourceService; import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import java.util.List; import java.util.stream.Collectors; /** * 文件列表相关接口, 如展示存储源列表, 展示文件列表, 搜索文件列表等. * * @author zhengsl */ @Api(tags = "文件列表模块") @ApiSort(2) @Slf4j @RequestMapping("/api/storage") @RestController public class FileController { @Resource private StorageSourceContext storageSourceContext; @Resource private StorageSourceService storageSourceService; @Resource private FileChain fileChain; @Resource private StorageSourceConvert storageSourceConvert; @ApiOperationSupport(order = 1) @ApiOperation(value = "获取存储源列表", notes = "获取所有已启用的存储源, 并且按照后台顺序排序") @GetMapping("/list") public ResponseResult storageList() { List storageList = storageSourceService.findAllEnableOrderByOrderNum(); List storageSourceResultList = storageSourceConvert.entityToResultList (storageList); return ResponseResult.successData(storageSourceResultList); } @ApiOperationSupport(order = 2) @ApiOperation(value = "获取文件列表", notes = "获取某个存储源下, 指定路径的文件&文件夹列表") @PostMapping("/files") public ResponseResult list(@Valid @RequestBody FileListRequest fileListRequest) throws Exception { String storageKey = fileListRequest.getStorageKey(); Integer storageId = storageSourceService.findIdByKey(storageKey); if (storageId == null) { throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); } // 处理请求参数默认值 fileListRequest.handleDefaultValue(); // 获取文件列表 AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); List fileItemList = fileService.fileList(fileListRequest.getPath()); // 执行责任链 FileContext fileContext = FileContext.builder() .storageId(storageId) .fileListRequest(fileListRequest) .fileItemList(fileItemList).build(); fileChain.execute(fileContext); return ResponseResult.successData(new FileInfoResult(fileContext.getFileItemList(), fileContext.getPasswordPattern())); } } \ No newline at end of file +package com.yfd.platform.modules.storage.controller.file; import cn.hutool.core.bean.BeanUtil; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiSort; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.exception.file.InvalidStorageSourceException; import com.yfd.platform.modules.config.model.request.FileListRequest; import com.yfd.platform.modules.storage.chain.FileChain; import com.yfd.platform.modules.storage.chain.FileContext; import com.yfd.platform.modules.storage.context.StorageSourceContext; import com.yfd.platform.modules.storage.convert.StorageSourceConvert; import com.yfd.platform.modules.storage.model.entity.StorageSource; import com.yfd.platform.modules.storage.model.result.FileInfoResult; import com.yfd.platform.modules.storage.model.result.FileItemResult; import com.yfd.platform.modules.storage.model.result.StorageSourceResult; import com.yfd.platform.modules.storage.service.StorageSourceService; import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import java.util.List; import java.util.stream.Collectors; /** * 文件列表相关接口, 如展示存储源列表, 展示文件列表, 搜索文件列表等. * * @author zhengsl */ @Api(tags = "文件列表模块") @ApiSort(2) @Slf4j @RequestMapping("/api/storage") @RestController public class FileController { @Resource private StorageSourceContext storageSourceContext; @Resource private StorageSourceService storageSourceService; @Resource private FileChain fileChain; @Resource private StorageSourceConvert storageSourceConvert; @ApiOperationSupport(order = 1) @ApiOperation(value = "获取存储源列表", notes = "获取所有已启用的存储源, 并且按照后台顺序排序") @GetMapping("/list") public ResponseResult storageList() { List storageList = storageSourceService.findAllEnableOrderByOrderNum(); List storageSourceResultList = storageSourceConvert.entityToResultList (storageList); return ResponseResult.successData(storageSourceResultList); } @ApiOperationSupport(order = 2) @ApiOperation(value = "获取文件列表", notes = "获取某个存储源下, 指定路径的文件&文件夹列表") @PostMapping("/files") public ResponseResult list(@Valid @RequestBody FileListRequest fileListRequest) throws Exception { String storageKey = fileListRequest.getStorageKey(); Integer storageId = storageSourceService.findIdByKey(storageKey); if (storageId == null) { throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey); } // 处理请求参数默认值 fileListRequest.handleDefaultValue(); // 获取文件列表 AbstractBaseFileService fileService = storageSourceContext.getByStorageId(storageId); List fileItemList = fileService.fileList(fileListRequest.getPath()); List fileItemListss = fileService.fileListData(fileListRequest.getPath(), "4a547ee5b2b49ce2d9c2b95ee92766c2"); // 执行责任链 FileContext fileContext = FileContext.builder() .storageId(storageId) .fileListRequest(fileListRequest) .fileItemList(fileItemList).build(); fileChain.execute(fileContext); return ResponseResult.successData(new FileInfoResult(fileContext.getFileItemList(), fileContext.getPasswordPattern())); } } \ No newline at end of file diff --git a/java/src/main/java/com/yfd/platform/modules/storage/model/enums/StorageTypeEnum.java b/java/src/main/java/com/yfd/platform/modules/storage/model/enums/StorageTypeEnum.java index c88bfc7..ae63d06 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/model/enums/StorageTypeEnum.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/model/enums/StorageTypeEnum.java @@ -18,9 +18,10 @@ import java.util.Map; public enum StorageTypeEnum implements IEnum { /** - * 当前系统支持的所有存储源类型 + * 当前系统支持的所有存储源类型 TODO: 增加了专项文档本地文件服务测试 */ LOCAL("local", "本地存储"), + SDLOCAL("sdlocal", "专项文档本地文件服务测试"), ALIYUN("aliyun", "阿里云 OSS"), WEBDAV("webdav", "WebDAV"), TENCENT("tencent", "腾讯云 COS"), diff --git a/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java b/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java index 0e391ee..9613f64 100644 --- a/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java @@ -232,6 +232,7 @@ public class UserServiceImpl extends ServiceImpl impleme //根据修改 boolean ok = this.updateById(sysUser); if (ok) { + //将 roleids 按逗号分割为数组 roles,遍历每个角色ID。若用户原有角色中不包含该角色,调用 addUserRoles 方法向关联表(如 sys_role_users)插入新记录。 if (StrUtil.isNotEmpty(roleids)) { String[] roles = roleids.split(","); List list = sysUserMapper.getRoleid(sysUser.getId()); @@ -244,7 +245,7 @@ public class UserServiceImpl extends ServiceImpl impleme sysUser.getId()); } } - //删除不包含的角色 + //删除不包含的角色 调用 delInRoleUsersByUserid 方法,删除用户原有但不在 roles 中的角色关联 sysUserMapper.delInRoleUsersByUserid(sysUser.getId(), roles); } else {