From 8ea12a93f775c9fe0fb613cc3f4d3859551f3946 Mon Sep 17 00:00:00 2001 From: lilin Date: Mon, 3 Mar 2025 11:26:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yfd/platform/config/SwaggerConfig.java | 28 + .../controller/TsFilesController.java | 398 +++ .../controller/TsNodesController.java | 110 + .../controller/TsTaskController.java | 163 ++ .../domain/DualTreeResponse.java | 18 + .../domain/FileCompareResult.java | 25 + .../domain/MoveCopyFileFolderRequest.java | 16 + .../experimentalData/domain/TreeDTO.java | 102 + .../experimentalData/domain/TsFiles.java | 189 ++ .../experimentalData/domain/TsNodes.java | 83 + .../experimentalData/domain/TsTask.java | 115 + .../mapper/TsFilesMapper.java | 16 + .../mapper/TsNodesMapper.java | 16 + .../experimentalData/mapper/TsTaskMapper.java | 16 + .../service/ITsFilesService.java | 153 + .../service/ITsNodesService.java | 49 + .../service/ITsTaskService.java | 47 + .../service/impl/TsFilesServiceImpl.java | 2552 +++++++++++++++++ .../service/impl/TsNodesServiceImpl.java | 564 ++++ .../service/impl/TsTaskServiceImpl.java | 124 + .../controller/FilesController.java | 147 + .../controller/NodesController.java | 131 + .../controller/ProjectController.java | 165 ++ .../modules/specialDocument/domain/Files.java | 114 + .../modules/specialDocument/domain/Nodes.java | 95 + .../specialDocument/domain/Project.java | 95 + .../specialDocument/mapper/FilesMapper.java | 16 + .../specialDocument/mapper/NodesMapper.java | 16 + .../specialDocument/mapper/ProjectMapper.java | 16 + .../service/IFilesService.java | 58 + .../service/INodesService.java | 50 + .../service/IProjectService.java | 43 + .../service/impl/FilesServiceImpl.java | 383 +++ .../service/impl/NodesServiceImpl.java | 532 ++++ .../service/impl/ProjectServiceImpl.java | 114 + .../storage/model/result/FileItemResult.java | 5 + .../service/base/AbstractBaseFileService.java | 2 + .../base/AbstractS3BaseFileService.java | 328 ++- .../storage/service/base/BaseFileService.java | 58 + .../service/impl/LocalServiceImpl.java | 98 +- .../service/impl/MinIOServiceImpl.java | 1 + .../SysDictionaryItemsController.java | 19 + .../com/yfd/platform/utils/CodeGenerator.java | 10 +- .../mapper/experimentalData/FilesMapper.xml | 5 + .../mapper/experimentalData/NodesMapper.xml | 5 + .../mapper/experimentalData/TaskMapper.xml | 5 + .../mapper/experimentalData/TsFilesMapper.xml | 5 + .../mapper/experimentalData/TsNodesMapper.xml | 5 + .../mapper/experimentalData/TsTaskMapper.xml | 5 + .../mapper/specialDocument/FilesMapper.xml | 5 + .../mapper/specialDocument/NodesMapper.xml | 5 + .../mapper/specialDocument/ProjectMapper.xml | 5 + 52 files changed, 7309 insertions(+), 16 deletions(-) create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsNodesController.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsTaskController.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/domain/DualTreeResponse.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/domain/FileCompareResult.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/domain/MoveCopyFileFolderRequest.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsFiles.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsNodes.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsTask.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsFilesMapper.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsNodesMapper.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsTaskMapper.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsNodesService.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsTaskService.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/controller/NodesController.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/controller/ProjectController.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Files.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Nodes.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Project.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/FilesMapper.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/NodesMapper.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/ProjectMapper.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/service/INodesService.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/service/IProjectService.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/NodesServiceImpl.java create mode 100644 java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java create mode 100644 java/src/main/resources/mapper/experimentalData/FilesMapper.xml create mode 100644 java/src/main/resources/mapper/experimentalData/NodesMapper.xml create mode 100644 java/src/main/resources/mapper/experimentalData/TaskMapper.xml create mode 100644 java/src/main/resources/mapper/experimentalData/TsFilesMapper.xml create mode 100644 java/src/main/resources/mapper/experimentalData/TsNodesMapper.xml create mode 100644 java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml create mode 100644 java/src/main/resources/mapper/specialDocument/FilesMapper.xml create mode 100644 java/src/main/resources/mapper/specialDocument/NodesMapper.xml create mode 100644 java/src/main/resources/mapper/specialDocument/ProjectMapper.xml diff --git a/java/src/main/java/com/yfd/platform/config/SwaggerConfig.java b/java/src/main/java/com/yfd/platform/config/SwaggerConfig.java index 8b7e6df..0a12ec5 100644 --- a/java/src/main/java/com/yfd/platform/config/SwaggerConfig.java +++ b/java/src/main/java/com/yfd/platform/config/SwaggerConfig.java @@ -53,6 +53,34 @@ public class SwaggerConfig { .enable(swaggerEnabled); } + @Bean + public Docket createSpecialDocumentApi() { + return new Docket(DocumentationType.OAS_30) + .apiInfo(apiInfo()) +// .globalRequestParameters(generateRequestParameters()) + .groupName("3. 专项文档管理") + .select() + .apis(RequestHandlerSelectors.basePackage("com.yfd.platform.modules.specialDocument.controller")) + .paths(PathSelectors.any()) + .build() + .pathMapping("/") + .enable(swaggerEnabled); + } + + + @Bean + public Docket createExperimentalDataApi() { + return new Docket(DocumentationType.OAS_30) + .apiInfo(apiInfo()) +// .globalRequestParameters(generateRequestParameters()) + .groupName("4. 试验数据管理") + .select() + .apis(RequestHandlerSelectors.basePackage("com.yfd.platform.modules.experimentalData.controller")) + .paths(PathSelectors.any()) + .build() + .pathMapping("/") + .enable(swaggerEnabled); + } /** * 获取通用的全局参数 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 new file mode 100644 index 0000000..3fbb501 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java @@ -0,0 +1,398 @@ +package com.yfd.platform.modules.experimentalData.controller; + + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.modules.experimentalData.domain.DualTreeResponse; +import com.yfd.platform.modules.experimentalData.domain.FileCompareResult; +import com.yfd.platform.modules.experimentalData.domain.MoveCopyFileFolderRequest; +import com.yfd.platform.modules.experimentalData.domain.TsFiles; +import com.yfd.platform.modules.experimentalData.service.ITsFilesService; +import com.yfd.platform.modules.specialDocument.domain.Files; +import com.yfd.platform.modules.specialDocument.service.IFilesService; +import io.swagger.annotations.ApiOperation; +import org.springframework.http.ResponseEntity; +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; + +/** + *

+ * 试验任务文档表 前端控制器 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@RestController +@RequestMapping("/experimentalData/ts-files") +public class TsFilesController { + + //实验任务文档表 + @Resource + private ITsFilesService tsFilesService; + + /********************************** + * 用途说明: 分页查询试验数据管理-文档内容 + * 参数说明 + * id id + * fileName 文件名称 + * startDate (开始日期) + * endDate (结束日期) + * keywords 关键字 + *nodeId 节点ID + *taskId 任务ID + *isFile 文件夹、文件区分 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @GetMapping("/page") + @ApiOperation("分页查询实验数据管理文档内容") + @PreAuthorize("@el.check('select:tsfiles')") + public ResponseResult getTsFilesPage(String id, String fileName, String startDate, String endDate, String keywords, String nodeId, String taskId, String childNode, Page page) throws Exception { + //分页查询 + Page tsfilesPage = tsFilesService.getTsFilesPage(id, fileName, startDate, endDate, keywords, nodeId, taskId, fileName, childNode, page); + return ResponseResult.successData(tsfilesPage); + } + + + /********************************** + * 用途说明: 查询实验数据管理文件夹 + * 参数说明 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @GetMapping("/listTsFiles") + @ApiOperation("查询实验数据管理文件夹") + @PreAuthorize("@el.check('select:tsfiles')") + public ResponseResult getsListTsFiles(String id, String path) throws Exception { + //分页查询 + List tsfiles = tsFilesService.getsListTsFiles(id, path); + return ResponseResult.successData(tsfiles); + } + + /*********************************** + * 用途说明:新增试验数据管理-文档内容 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Log(module = "实验数据管理", value = "新增试验数据管理文档内容!", type = "1") + @PostMapping("/addTsFiles") + @ApiOperation("新增试验数据管理文档内容") + @ResponseBody + @PreAuthorize("@el.check('add:tsFiles')") + public ResponseResult addTsFiles(@RequestBody TsFiles tsFiles) { + //对象不能为空 + if (ObjUtil.isEmpty(tsFiles)) { + return ResponseResult.error("参数为空"); + } + Boolean isOk = tsFilesService.addTsFiles(tsFiles); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明:新增试验数据管理-文件夹 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Log(module = "实验数据管理", value = "新增试验数据管理文件夹", type = "1") + @PostMapping("/addTsFile") + @ApiOperation("新增试验数据管理文件夹") + @ResponseBody + @PreAuthorize("@el.check('add:tsFiles')") + public ResponseResult addTsFile(@RequestBody TsFiles tsFiles) { + //对象不能为空 + if (ObjUtil.isEmpty(tsFiles)) { + return ResponseResult.error("参数为空"); + } + return tsFilesService.addTsFile(tsFiles); + + } + + + /********************************** + * 用途说明: 修改试验数据管理-文档内容 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "修改试验数据管理文档内容", type = "1") + @PostMapping("/updateTsFiles") + @ApiOperation("修改试验数据管理文档内容") + @PreAuthorize("@el.check('update:tsFiles')") + public ResponseResult updateTsFiles(@RequestBody TsFiles tsFiles) { + //对象不能为空 + if (ObjUtil.isEmpty(tsFiles) && StrUtil.isBlank(tsFiles.getId())) { + return ResponseResult.error("参数为空"); + } + return tsFilesService.updateTsFiles(tsFiles); + + } + + /********************************** + * 用途说明: 根据ID删除试验数据管理-文档内容 + * 参数说明 id 文档内容ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "根据ID删除试验数据管理文档内容", type = "1") + @PostMapping("/deleteTsFilesById") + @ApiOperation("根据ID删除试验数据管理文档内容") + @PreAuthorize("@el.check('del:tsFiles')") + public ResponseResult deleteTsFilesById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + List dataset = Arrays.asList(id); + return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset)); + } + + + /********************************** + * 用途说明: 批量删除试验数据管理-文档内容 + * 参数说明 ids 文档内容id数组 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 + ***********************************/ + @Log(module = "专项文档管理", value = "批量删除试验数据管理文档内容", type = "1") + @PostMapping("/deleteTsFilesByIds") + @ApiOperation("批量删除试验数据管理文档内容") + @PreAuthorize("@el.check('del:tsFiles')") + public ResponseResult deleteTsFilesByIds(@RequestParam String ids) { + if (StrUtil.isBlank(ids)) { + return ResponseResult.error("参数为空"); + } + String[] splitIds = ids.split(","); + // 数组转集合 + List dataset = Arrays.asList(splitIds); + return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset)); + } + + + /**************************压缩 解压缩********************************/ + + /********************************** + * 用途说明: 压缩文件夹接口 + * 参数说明 ids 文件id数组 + * 参数说明 compressedFormat 压缩文件格式 + * 参数说明 compressedName 压缩文件名称 + * 参数说明 compressedPath 压缩文件路径 + * 参数说明 covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 + * 参数说明 parentId 父ID + * 返回值说明: com.yfd.platform.config.ResponseResult + ***********************************/ + @PostMapping("/compress") + @ApiOperation("压缩文件夹接口") + public ResponseResult compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId) { + try { + if (StrUtil.isBlank(ids) && StrUtil.isBlank(compressedFormat) && StrUtil.isBlank(compressedName) && StrUtil.isBlank(compressedPath)) { + return ResponseResult.error("参数为空"); + } + + return ResponseResult.success(tsFilesService.compressFolder(ids, compressedFormat, compressedName, compressedPath, covered, parentId)); + } catch (Exception e) { + System.out.print("压缩异常原因" + e); + return ResponseResult.error("压缩失败"); + } + } + + + /********************************** + * 用途说明: 解压缩接口 + * 参数说明 id 要解压的文件id + * 参数说明 decompressionPath 解压缩路径 + * 参数说明 parentId 父ID + * 返回值说明: com.yfd.platform.config.ResponseResult + ***********************************/ + @PostMapping("/decompression") + @ApiOperation("解压缩接口") + public ResponseResult decompressionFolder(String id, String decompressionPath, String parentId) { + try { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + + return ResponseResult.success(tsFilesService.decompressionFolder(id, decompressionPath, parentId)); + } catch (Exception e) { + System.out.print("解压缩异常原因" + e); + return ResponseResult.error("解压缩失败"); + } + } + + + + /** + * 移动文件或文件夹 + * 参数说明 newPath 新路径 + * 参数说明 oldpaths 原路径 + * 参数说明 ParentId 父ID + * 参数说明 newFileName 勾选的所有文件名称 + * 参数说明 Rename 重命名的文件名称 + * 参数说明 type 覆盖还是重命名 0 1 + */ + @PostMapping("/moveFileFolder") + @ApiOperation("移动") + public ResponseResult moveFileFolder(@RequestBody MoveCopyFileFolderRequest request) throws IOException { + + try { + if (request == null) { + throw new IllegalArgumentException("请求参数不能为空"); + } + if (request.getNewFileName() == null || request.getNewFileName().isEmpty()) { + throw new IllegalArgumentException("newFileName 不能为空"); + } + if (request.getNewPath() == null || request.getOldpaths() == null) { + throw new IllegalArgumentException("路径参数不能为空"); + } + + return ResponseResult.success(tsFilesService.moveFileFolder(request)); + } catch (Exception e) { + System.out.print("移动异常原因" + e); + return ResponseResult.error("移动失败"); + } + } + + + /** + * 复制文件或文件夹 + * 参数说明 newPath 新路径 + * 参数说明 oldpaths 原路径 + * 参数说明 ParentId 父ID + * 参数说明 newFileName 勾选的所有文件名称 + * 参数说明 Rename 重命名的文件名称 + * 参数说明 type 覆盖还是重命名 0 1 + */ + @PostMapping("/copyFileFolder") + @ApiOperation("复制") + public ResponseResult copyFileFolder(@RequestBody MoveCopyFileFolderRequest request) throws IOException { + + try { + if (request == null) { + throw new IllegalArgumentException("请求参数不能为空"); + } + if (request.getNewFileName() == null || request.getNewFileName().isEmpty()) { + throw new IllegalArgumentException("newFileName 不能为空"); + } + if (request.getNewPath() == null || request.getOldpaths() == null) { + throw new IllegalArgumentException("路径参数不能为空"); + } + return ResponseResult.success(tsFilesService.copyFileFolder(request)); + } catch (Exception e) { + System.out.print("复制异常原因" + e); + return ResponseResult.error("复制失败"); + } + } + + + + + /** + * 对比两个目录的文件差异 + * + * @param localPath 本地目录的相对路径(相对于E:\yun\qqq) + * @param minioPath MinIO目录的相对路径(相对于test-bucket/qqq) + * @return 文件差异列表 + */ + @PostMapping("/compare") + @ApiOperation("对比两个目录的文件差异") + public ResponseResult compareDirectories(@RequestParam String localPath, @RequestParam String minioPath) { + + try { + if (StrUtil.isBlank(localPath) && StrUtil.isBlank(minioPath)) { + return ResponseResult.error("参数为空"); + } + TsFiles tsFiles = tsFilesService.compareDirectories(localPath, minioPath); + return ResponseResult.successData(tsFiles); + } catch (Exception e) { + return ResponseResult.error("对比失败"); + } + } + + + /********************************** + * 用途说明: 将文件上传到备份空间 + * 参数说明 paths 路径集合 + * 参数说明 names 文件名集合 + * 参数说明 sizes 文件大小集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @PostMapping("/uploadToBackup") + @ApiOperation("将文件上传到备份空间") + public ResponseResult uploadToBackup(@RequestParam String paths, @RequestParam String names, @RequestParam String sizes) { + + + if (StrUtil.isBlank(paths) && StrUtil.isBlank(names)) { + return ResponseResult.error("参数为空"); + } + Boolean isOk = tsFilesService.uploadToBackup(paths, names, sizes); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + + } + + /********************************** + * 用途说明: 从备份空间下载到工作空间 + * 参数说明 paths 路径集合 + * 参数说明 names 文件名集合 + * 参数说明 sizes 文件大小集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @PostMapping("/downloadToLocal") + @ApiOperation("从备份空间下载到工作空间") + public ResponseResult downloadToLocal(@RequestParam String paths, @RequestParam String names, @RequestParam String sizes) { + + + if (StrUtil.isBlank(paths) && StrUtil.isBlank(names)) { + return ResponseResult.error("参数为空"); + } + Boolean isOk = tsFilesService.downloadToLocal(paths, names, sizes); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + + } + + + + + /********************************** + * 用途说明: 查询本地和备份空间结构树 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 + ***********************************/ + @PostMapping("/listLocalAndBackup") + @ApiOperation("查询本地和备份空间结构树") + public ResponseResult listLocalAndBackup( String taskId, String nodeId) { + + + if (StrUtil.isBlank(taskId) && StrUtil.isBlank(nodeId)) { + return ResponseResult.error("参数为空"); + } + //查询本地树和minio树 + DualTreeResponse response = tsFilesService.listLocalAndBackup(taskId, nodeId); + return ResponseResult.successData(response); + } + + + + + + +} 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 new file mode 100644 index 0000000..7109a23 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsNodesController.java @@ -0,0 +1,110 @@ +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.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.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +/** + *

+ * 试验任务节点表 前端控制器 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@RestController +@RequestMapping("/experimentalData/ts-nodes") +public class TsNodesController { + + + //试验任务节点服务类 + @Resource + private ITsNodesService tsNodesService; + + + /*********************************** + * 用途说明:获取试验任务节点 树形结构 + * 参数说明 + * nodeName 节点名称 + * taskId 所属任务ID + * 返回值说明: 专项文档节点树形结构 + ***********************************/ + @PostMapping("/getTsNodesTree") + @ApiOperation("获取试验任务节点树形结构") + @ResponseBody + @PreAuthorize("@el.check('select:tsnodes')") + public ResponseResult getTsNodesTree(String nodeName, String taskId) { + List> list = tsNodesService.getTsNodesTree(nodeName,taskId); + return ResponseResult.successData(list); + } + + /********************************** + * 用途说明: 增加试验任务节点 + * 参数说明 tsnodes 试验任务节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "增加试验任务节点",type = "1") + @PostMapping("/addTsNodes") + @ApiOperation("增加试验任务节点") + @PreAuthorize("@el.check('add:tsnodes')") + public ResponseResult addTsNodes(@RequestBody TsNodes tsnodes) { + //参数校验 对象 节点名称 所属任务ID + if (ObjUtil.isEmpty(tsnodes) && StrUtil.isBlank(tsnodes.getNodeName()) && StrUtil.isBlank(tsnodes.getTaskId()) ) { + return ResponseResult.error("参数为空"); + } + return tsNodesService.addTsNodes(tsnodes); + } + + + /********************************** + * 用途说明: 修改试验任务节点 + * 参数说明 tsnodes 试验任务节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "修改试验任务节点",type = "1") + @PostMapping("/updateTsNodes") + @ApiOperation("修改试验任务节点") + @PreAuthorize("@el.check('update:tsnodes')") + public ResponseResult updateTsNodes(@RequestBody TsNodes tsnodes) { + //参数校验 对象 节点名称 所属任务ID id + if (ObjUtil.isEmpty(tsnodes) && StrUtil.isBlank(tsnodes.getNodeName()) && StrUtil.isBlank(tsnodes.getTaskId()) && StrUtil.isBlank(tsnodes.getNodeId())) { + return ResponseResult.error("参数为空"); + } + return tsNodesService.updateTsNodes(tsnodes); + } + + /********************************** + * 用途说明: 根据ID删除试验任务节点 + * 参数说明 id 试验任务节点ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "根据ID删除试验任务节点",type = "1") + @PostMapping("/deleteTsNodesById") + @ApiOperation("根据ID删除试验任务节点") + @PreAuthorize("@el.check('del:tsnodes')") + public ResponseResult deleteTsNodesById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + boolean isOk = tsNodesService.deleteTsNodesById(id); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + +} 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 new file mode 100644 index 0000000..219343c --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsTaskController.java @@ -0,0 +1,163 @@ +package com.yfd.platform.modules.experimentalData.controller; + + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.modules.experimentalData.domain.TsTask; +import com.yfd.platform.modules.experimentalData.service.ITsTaskService; +import com.yfd.platform.modules.specialDocument.domain.Project; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 试验任务表 前端控制器 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@RestController +@RequestMapping("/experimentalData/tstask") +public class TsTaskController { + + //试验任务服务类 + @Resource + private ITsTaskService tsTaskService; + + /********************************** + * 用途说明: 分页查询试验数据管理-试验任务管理 + * 参数说明 + * taskName 任务名称 + * startDate (开始日期) + * endDate (结束日期) + * taskPlace 任务地点 + * taskPerson 任务人员 + * carrierType 载体类型 + * deviceCode 设备 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @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) { + //分页查询 + Page sdProjectPage = tsTaskService.getTsTaskPage(taskName, startDate, endDate,taskPlace,taskPerson,carrierType,deviceCode, page); + return ResponseResult.successData(sdProjectPage); + } + + /*********************************** + * 用途说明:新增试验数据管理-试验任务管理 + * 参数说明 + * TsTask 试验任务管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "新增试验数据管理试验任务管理!",type = "1") + @PostMapping("/addtsTask") + @ApiOperation("新增试验数据管理试验任务管理") + @ResponseBody + @PreAuthorize("@el.check('add:tsTask')") + public ResponseResult addtsTask(@RequestBody TsTask tsTask) { + //对象不能为空 + if (ObjUtil.isEmpty(tsTask)) { + return ResponseResult.error("参数为空"); + } + Boolean isOk = tsTaskService.addSdproject(tsTask); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 修改试验数据管理-试验任务管理 + * 参数说明 + * TsTask 试验任务管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "修改试验数据管理试验任务管理",type = "1") + @PostMapping("/updatetsTask") + @ApiOperation("修改试验数据管理试验任务管理") + @PreAuthorize("@el.check('update:tsTask')") + public ResponseResult updatetsTask(@RequestBody TsTask tsTask) { + //对象不能为空 + if (ObjUtil.isEmpty(tsTask) && StrUtil.isBlank(tsTask.getId())) { + return ResponseResult.error("参数为空"); + } + boolean isOk = tsTaskService.updatetsTask(tsTask); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 根据ID试验数据管理-试验任务管理 + * 参数说明 id 试验数据管理ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Log(module = "试验数据管理", value = "根据ID删除试验数据管理试验任务管理",type = "1") + @PostMapping("/deleteTsTaskById") + @ApiOperation("根据ID删除试验数据管理试验任务管理") + @PreAuthorize("@el.check('del:tsTask')") + public ResponseResult deleteTsTaskById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + boolean isOk = tsTaskService.removeById(id); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 批量删除试验数据管理-试验任务管理 + * 参数说明 ids 试验数据管理id数组 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 + ***********************************/ + @Log(module = "试验数据管理", value = "批量删除试验数据管理试验任务管理",type = "1") + @PostMapping("/deleteTsTaskByIds") + @ApiOperation("批量删除试验数据管理试验任务管理") + @PreAuthorize("@el.check('del:tsTask')") + public ResponseResult deleteTsTaskByIds(@RequestParam String ids) { + if (StrUtil.isBlank(ids)) { + return ResponseResult.error("参数为空"); + } + String[] splitIds = ids.split(","); + // 数组转集合 + List dataset = Arrays.asList(splitIds); + boolean isOk = tsTaskService.removeByIds(dataset); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明: 查询所有试验数据管理-试验任务管理 + * 参数说明 + * 返回值说明: 试验数据管理试验任务管理 + ***********************************/ + @PostMapping("/list") + @ApiOperation("查询所有试验数据管理试验任务管理") + @ResponseBody + //@PreAuthorize("@el.check('select:devicesignal')") + public ResponseResult listTsTask() { + List tsTasks = tsTaskService.list(); + return ResponseResult.successData(tsTasks); + } +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/DualTreeResponse.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/DualTreeResponse.java new file mode 100644 index 0000000..e971fd0 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/DualTreeResponse.java @@ -0,0 +1,18 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import lombok.Data; + +import java.util.List; + +@Data +public class DualTreeResponse { + + private List localTrees; // 本地树列表(workPath) + private List minioTrees; // Minio 树列表(backupPath) + // 添加带参数的构造函数 + public DualTreeResponse(List localTrees, List minioTrees) { + this.localTrees = localTrees; + this.minioTrees = minioTrees; + } + +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/FileCompareResult.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/FileCompareResult.java new file mode 100644 index 0000000..b53525c --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/FileCompareResult.java @@ -0,0 +1,25 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import lombok.Data; + +@Data +public class FileCompareResult { + + // 文件相对路径(相对于对比根目录) + private String relativePath; + + // 文件状态: + // LOCAL_MISSING - 本地缺失 + // REMOTE_MISSING - MinIO缺失 + // MD5_MISMATCH - MD5不一致 + private String status; + + // 本地文件的MD5哈希值 + private String localMd5; + + // MinIO文件的MD5哈希值 + private String remoteMd5; + + // 是否是目录 + private boolean isDirectory; +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/MoveCopyFileFolderRequest.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/MoveCopyFileFolderRequest.java new file mode 100644 index 0000000..b4e73fd --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/MoveCopyFileFolderRequest.java @@ -0,0 +1,16 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import lombok.Data; + +import java.util.List; +@Data +public class MoveCopyFileFolderRequest { + + private String newPath; + private String oldpaths; + private String parentId; + private String newFileName; + private String rename; + private String type; + +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java new file mode 100644 index 0000000..46429ca --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java @@ -0,0 +1,102 @@ +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.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.sql.Timestamp; +import java.util.List; + +@Data +public class TreeDTO { + + /** + * 文档ID + */ + private String id; + + /** + * 节点ID + */ + private String nodeId; + + /** + * 任务ID + */ + private String taskId; + + /** + * 上级节点:顶级:“00” + */ + private String parentId; + + /** + * 文件夹、文件区分 + */ + private String isFile; + + /** + * 文件名称 + */ + private String fileName; + + /** + * M + */ + private String fileSize; + + /** + * 工作空间存储路径 + */ + private String workPath; + + /** + * 备份空间存储路径 + */ + private String backupPath; + + /** + * 关键字 + */ + private String keywords; + + /** + * 文件描述 + */ + private String description; + + /** + * 上传时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp uploadTime; + + /** + * 上传人 + */ + private String uploader; + + /** + * 修改时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp updateTime; + /** + * 访问路径URL:TODO 增加用于前端展示 + */ + @TableField(exist = false) + private String url; + + /** + * 类型 FILE FOLDER:TODO 增加用于前端展示 + */ + @TableField(exist = false) + private String type; + // 路径字段 + private String path; + + // 子节点列表 + private List children; +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsFiles.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsFiles.java new file mode 100644 index 0000000..95e8c94 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsFiles.java @@ -0,0 +1,189 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.io.Serializable; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.yfd.platform.modules.storage.model.result.FileItemResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 试验任务文档表 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("ts_files") +public class TsFiles implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 文档ID + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 节点ID + */ + private String nodeId; + + /** + * 任务ID + */ + private String taskId; + + /** + * 上级节点:顶级:“00” + */ + private String parentId; + + /** + * 文件夹、文件区分 + */ + private String isFile; + + /** + * 文件名称 + */ + private String fileName; + + /** + * M + */ + private String fileSize; + + /** + * 工作空间存储路径 + */ + private String workPath; + + /** + * 备份空间存储路径 + */ + private String backupPath; + + /** + * 关键字 + */ + private String keywords; + + /** + * 文件描述 + */ + private String description; + + /** + * 上传时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp uploadTime; + + /** + * 上传人 + */ + private String uploader; + + /** + * 修改时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp updateTime; + + /** + * 备份1 + */ + private String custom1; + + /** + * 备份2 + */ + private String custom2; + + /** + * 备份3 + */ + private String custom3; + + /** + * 访问路径URL:TODO 增加用于前端展示 + */ + @TableField(exist = false) + private String url; + + /** + * 类型 FILE FOLDER:TODO 增加用于前端展示 + */ + @TableField(exist = false) + private String type; + +// /** +// * 试验任务节点表 FOLDER:TODO 增加用于前端展示 +// */ +// @TableField(exist = false) +// private List tsNodesList; + + + +// /** +// * 节点顺序 +// */ +// @TableField(exist = false) +// private Integer nodeOrder; +// +// /** +// * 节点名称 +// */ +// @TableField(exist = false) +// private String nodeName; +// +// /** +// * 创建时间 +// */ +// @TableField(exist = false) +// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") +// private Timestamp createTime; +// +// /** +// * 创建人 +// */ +// @TableField(exist = false) +// private String creator; +// +// +// /** +// * 检查本地有而 MinIO 没有的文件 +// */ +// @TableField(exist = false) +// private List localOnlyFiles; +// +// +// /** +// * 检查 MinIO 有而本地没有的文件 +// */ +// @TableField(exist = false) +// private List minioOnlyFiles; +// +// +// /** +// * 检查 MD5 不一致的文件 +// */ +// @TableField(exist = false) +// private List md5MismatchedFiles; + +} 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 new file mode 100644 index 0000000..1d801fe --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsNodes.java @@ -0,0 +1,83 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 试验任务节点表 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("ts_nodes") +public class TsNodes implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 节点ID + */ + @TableId(type = IdType.ASSIGN_UUID) + private String nodeId; + + /** + * 所属任务ID + */ + private String taskId; + + /** + * 上级节点:顶级:“00” + */ + private String parentId; + + /** + * 节点顺序 + */ + private Integer nodeOrder; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp createTime; + + /** + * 创建人 + */ + private String creator; + + /** + * 备注1 + */ + private String custom1; + + /** + * 备注2 + */ + private String custom2; + + /** + * 备注3 + */ + private String custom3; + + +} 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 new file mode 100644 index 0000000..da31b02 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TsTask.java @@ -0,0 +1,115 @@ +package com.yfd.platform.modules.experimentalData.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 试验任务表 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("ts_task") +public class TsTask implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 任务ID + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 任务编号 + */ + private String taskCode; + + /** + * 任务名称 + */ + private String taskName; + + /** + * 载体类型 + */ + private String carrierType; + + /** + * 载体名称 + */ + private String carrierName; + + /** + * 设备编号 + */ + private String deviceCode; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 任务开始时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp taskDate; + + /** + * 任务地点 + */ + private String taskPlace; + + /** + * 任务人员 + */ + private String taskPerson; + + /** + * 可扩展的Json对象 + */ + private String taskProps; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp createTime; + + /** + * 创建人 + */ + private String creator; + + /** + * 备注1 + */ + private String custom1; + + /** + * 备注2 + */ + private String custom2; + + /** + * 备注3 + */ + private String custom3; + + +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsFilesMapper.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsFilesMapper.java new file mode 100644 index 0000000..58f858e --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsFilesMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.modules.experimentalData.mapper; + +import com.yfd.platform.modules.experimentalData.domain.TsFiles; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 试验任务文档表 Mapper 接口 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +public interface TsFilesMapper extends BaseMapper { + +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsNodesMapper.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsNodesMapper.java new file mode 100644 index 0000000..4e0f4b5 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsNodesMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.modules.experimentalData.mapper; + +import com.yfd.platform.modules.experimentalData.domain.TsNodes; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 试验任务节点表 Mapper 接口 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +public interface TsNodesMapper extends BaseMapper { + +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsTaskMapper.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsTaskMapper.java new file mode 100644 index 0000000..869cd3d --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/mapper/TsTaskMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.modules.experimentalData.mapper; + +import com.yfd.platform.modules.experimentalData.domain.TsTask; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 试验任务表 Mapper 接口 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +public interface TsTaskMapper extends BaseMapper { + +} 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 new file mode 100644 index 0000000..3d9d565 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java @@ -0,0 +1,153 @@ +package com.yfd.platform.modules.experimentalData.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.modules.experimentalData.domain.DualTreeResponse; +import com.yfd.platform.modules.experimentalData.domain.MoveCopyFileFolderRequest; +import com.yfd.platform.modules.experimentalData.domain.TsFiles; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +/** + *

+ * 试验任务文档表 服务类 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +public interface ITsFilesService extends IService { + + /********************************** + * 用途说明: 分页查询试验数据管理-文档内容 + * 参数说明 + * id id + * fileName 文件名称 + * startDate (开始日期) + * endDate (结束日期) + * keywords 关键字 + *nodeId 节点ID + *taskId 任务ID + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + Page getTsFilesPage(String id,String fileName, String startDate, String endDate, String keywords, String nodeId, String taskId, String fileName1,String childNode, Page page) throws Exception; + + /*********************************** + * 用途说明:新增试验数据管理-文档内容 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + Boolean addTsFiles(TsFiles tsFiles); + + /********************************** + * 用途说明: 修改试验数据管理-文档内容 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + ResponseResult updateTsFiles(TsFiles tsFiles); + + /********************************** + * 用途说明: 批量删除试验数据管理-文档内容 + * 参数说明 ids 文档内容id数组 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 + ***********************************/ + String deleteTsFilesByIds(List dataset); + + + /********************************** + * 用途说明: 压缩文件夹接口 + * 参数说明 ids 文件id数组 + * 参数说明 compressedFormat 压缩文件格式 + * 参数说明 compressedName 压缩文件名称 + * 参数说明 compressedPath 压缩文件路径 + * 参数说明 covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 + * 参数说明 parentId 父ID + * 返回值说明: com.yfd.platform.config.ResponseResult + ***********************************/ + String compressFolder(String ids,String compressedFormat,String compressedName,String compressedPath,String covered,String parentId) throws FileNotFoundException; + + + /********************************** + * 用途说明: 解压缩接口 + * 参数说明 id 要解压的文件id + * 参数说明 decompressionPath 解压缩路径 + * 参数说明 parentId 父ID + * 返回值说明: com.yfd.platform.config.ResponseResult + ***********************************/ + String decompressionFolder(String id,String decompressionPath,String parentId); + + + TsFiles compareDirectories(String localPath, String minioPath); + + /********************************** + * 用途说明: 将文件上传到备份空间 + * 参数说明 paths 路径集合 + * 参数说明 names 文件名集合 + * 参数说明 sizes 文件大小集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + Boolean uploadToBackup(String paths, String names, String sizes); + + /********************************** + * 用途说明: 从备份空间下载到工作空间 + * 参数说明 paths 路径集合 + * 参数说明 names 文件名集合 + * 参数说明 sizes 文件大小集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + Boolean downloadToLocal(String paths, String names, String sizes); + + /*********************************** + * 用途说明:新增试验数据管理-文件夹 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + ResponseResult addTsFile(TsFiles tsFiles); + + /********************************** + * 用途说明: 查询实验数据管理文件夹 + * 参数说明 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + List getsListTsFiles(String id,String path); + + + + /** + * 移动文件或文件夹 + * 参数说明 newPath 新路径 + * 参数说明 oldpaths 原路径 + * 参数说明 ParentId 父ID + * 参数说明 newFileName 父ID + * 参数说明 Rename 重命名的文件名称 + * 参数说明 type 覆盖还是重命名 0 1 + */ + String moveFileFolder(MoveCopyFileFolderRequest request)throws IOException; + + /** + * 复制文件或文件夹 + * 参数说明 newPath 新路径 + * 参数说明 oldpaths 原路径 + * 参数说明 ParentId 父ID + * 参数说明 newFileName 父ID + * 参数说明 Rename 重命名的文件名称 + * 参数说明 type 覆盖还是重命名 0 1 + */ + String copyFileFolder(MoveCopyFileFolderRequest request)throws IOException; + + /********************************** + * 用途说明: 查询本地和备份空间结构树 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 + ***********************************/ + DualTreeResponse listLocalAndBackup(String taskId, String nodeId); +} 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 new file mode 100644 index 0000000..d5f09da --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsNodesService.java @@ -0,0 +1,49 @@ +package com.yfd.platform.modules.experimentalData.service; + +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.modules.experimentalData.domain.TsNodes; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 试验任务节点表 服务类 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +public interface ITsNodesService extends IService { + + /*********************************** + * 用途说明:获取试验任务节点 树形结构 + * 参数说明 + * nodeName 节点名称 + * taskId 所属任务ID + * 返回值说明: 专项文档节点树形结构 + ***********************************/ + List> getTsNodesTree(String nodeName, String taskId); + + /********************************** + * 用途说明: 增加试验任务节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + ResponseResult addTsNodes(TsNodes tsnodes); + + /********************************** + * 用途说明: 修改试验任务节点 + * 参数说明 tsnodes 试验任务节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + ResponseResult updateTsNodes(TsNodes tsnodes); + + /********************************** + * 用途说明: 根据ID删除试验任务节点 + * 参数说明 id 试验任务节点ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + boolean deleteTsNodesById(String id); +} 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 new file mode 100644 index 0000000..951091c --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsTaskService.java @@ -0,0 +1,47 @@ +package com.yfd.platform.modules.experimentalData.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.modules.experimentalData.domain.TsTask; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 试验任务表 服务类 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +public interface ITsTaskService extends IService { + + /********************************** + * 用途说明: 分页查询试验数据管理-试验任务管理 + * 参数说明 + * taskName 任务名称 + * startDate (开始日期) + * endDate (结束日期) + * taskPlace 任务地点 + * taskPerson 任务人员 + * carrierType 载体类型 + * deviceCode 设备 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + Page getTsTaskPage(String taskName, String startDate, String endDate, String taskPlace, String taskPerson, String carrierType, String deviceCode, Page page); + + /*********************************** + * 用途说明:新增试验数据管理-试验任务管理 + * 参数说明 + * TsTask 试验任务管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + Boolean addSdproject(TsTask tsTask); + + /********************************** + * 用途说明: 修改试验数据管理-试验任务管理 + * 参数说明 + * TsTask 试验任务管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + boolean updatetsTask(TsTask tsTask); +} 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 new file mode 100644 index 0000000..8e09248 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java @@ -0,0 +1,2552 @@ +package com.yfd.platform.modules.experimentalData.service.impl; + +import cn.hutool.core.collection.CollUtil; + + +import java.nio.file.*; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + + +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.amazonaws.util.IOUtils; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.qiniu.storage.model.FileInfo; +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.service.ITsFilesService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.modules.storage.chain.FileChain; +import com.yfd.platform.modules.storage.context.StorageSourceContext; +import com.yfd.platform.modules.storage.mapper.StorageSourceConfigMapper; +import com.yfd.platform.modules.storage.mapper.StorageSourceMapper; +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.RenameFileRequest; +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.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + *

+ * 试验任务文档表 服务实现类 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@Service +public class TsFilesServiceImpl extends ServiceImpl implements ITsFilesService { + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class); + + + //试验任务文档表 Mapper + @Resource + private TsFilesMapper tsFilesMapper; + + //试验任务节点表 Mapper + @Resource + private TsNodesMapper tsNodesMapper; + + + //数据源Mapper + @Resource + private StorageSourceMapper storageSourceMapper; + + + @Resource + private StorageSourceContext storageSourceContext; + + @Resource + private StorageSourceService storageSourceService; + + @Resource + private FileChain fileChain; + + @Resource + private StorageSourceConfigMapper storageSourceConfigMapper; + + + /********************************** + * 用途说明: 分页查询试验数据管理-文档内容 + * 参数说明 + *id id + * fileName 文件名称 + * startDate (开始日期) + * endDate (结束日期) + * keywords 关键字 + *nodeId 节点ID + *taskId 任务ID + *isFile 文件夹、文件区分 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @Override + public Page getTsFilesPage(String id, String fileName, String startDate, String endDate, String keywords, String nodeId, String taskId, String fileName1, String childNode, Page page) throws Exception { + + + QueryWrapper queryWrapper = new QueryWrapper<>(); + + // 固定条件过滤 + if (StringUtils.isNotBlank(taskId)) queryWrapper.eq("task_id", taskId); + if (StringUtils.isNotBlank(nodeId)) queryWrapper.eq("node_id", nodeId); + + // 模糊搜索 + if (StringUtils.isNotBlank(fileName)) queryWrapper.like("file_name", fileName); + if (StringUtils.isNotBlank(keywords)) queryWrapper.like("keywords", keywords); + + // 时间范围过滤 + if (StringUtils.isNotBlank(startDate)) queryWrapper.ge("upload_time", startDate); + if (StringUtils.isNotBlank(endDate)) queryWrapper.le("upload_time", endDate); + boolean hasSearchCondition = StringUtils.isNotBlank(fileName) || StringUtils.isNotBlank(keywords) || StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate); + + // 处理层级关系条件 + if (StringUtils.isNotBlank(id)) { + // 有ID时:根据childNode决定是否递归子节点 如果filename + if (childNode != null && childNode.equals("0") && hasSearchCondition) { + List childIds = getAllChildIds(id); + queryWrapper.in("parent_id", childIds); + } else { + // 其他情况:childNode=1 或 无搜索条件时 按当前层级查询 + queryWrapper.eq("parent_id", id); + } + } else { + //如果说ID是空 说明是顶级节点 + if (childNode != null) { + if (childNode.equals("0")) { + if (hasSearchCondition) { + // 递归获取根目录下所有子节点ID(包含多级) + List allChildIds = getAllChildIds("00"); // 注意这里传入字符串"00" + allChildIds.add("00"); // 包含根目录的直接子节点 + queryWrapper.in("parent_id", allChildIds); + } else { + // 无搜索条件时,仅查询根目录的直接子节点 + queryWrapper.eq("parent_id", "00"); + } + } else if (childNode.equals("1")) { + // 始终只查根目录的直接子节点 + queryWrapper.eq("parent_id", "00"); + } + } + +// // 无ID时:childNode=1则查根目录,childNode=0查全表 +// if (childNode != null && childNode.equals("1")) { +// queryWrapper.eq("parent_id", "00"); +// } + } + // + queryWrapper.isNotNull("work_path"); + //排序 + queryWrapper.orderByDesc("is_file");//时间 +// queryWrapper.orderByDesc("upload_time");//时间 + //分页查询 + Page tsFilesPage = tsFilesMapper.selectPage(page, queryWrapper); + if (tsFilesPage == null) { + tsFilesPage = new Page<>(); + tsFilesPage.setRecords(new ArrayList<>()); // 确保 records 被初始化 + } + List records = tsFilesPage.getRecords(); + for (TsFiles tsFiles : records) { + //如果是文件夹 跳过循环 + if (tsFiles.getIsFile().equals("FOLDER")) { + if(tsFiles.getUpdateTime() == null){ + tsFiles.setUpdateTime(tsFiles.getUploadTime()); + } + continue; + } + String workPath = tsFiles.getWorkPath(); + String fileNameData = tsFiles.getFileName(); + //主要是用于文件路径加名称 + String path = workPath + fileNameData; + //准备获取文件的信息 + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); + FileItemResult fileItemResult = fileService.getFileItem(path); + tsFiles.setUrl(fileItemResult.getUrl()); + //如果是压缩文件 类型就给zip + boolean isValid = hasValidExtension(fileItemResult.getName()); + if (isValid) { + tsFiles.setType("ZIP"); + } else { + tsFiles.setType(fileItemResult.getType().getValue()); + } + if(tsFiles.getUpdateTime() == null){ + tsFiles.setUpdateTime(tsFiles.getUploadTime()); + } + } + tsFilesPage.setRecords(records); // 同步到 tsFilesPage + System.out.println("Updated records: " + records); + return tsFilesPage; + + } + + public boolean hasValidExtension(String name) { + if (name == null) { + return false; // 如果传入的文件名为空,返回 false + } + // 判断文件名后缀是否是 .zip 或 .tar + return name.endsWith(".zip") || name.endsWith(".tar"); + } + + // 递归获取所有子节点ID(包含隔代子节点) + private List getAllChildIds(String parentId) { + List childIds = new ArrayList<>(); + List children = tsFilesMapper.selectList( + new QueryWrapper().select("id").eq("parent_id", parentId).eq("is_file", "FOLDER") // 只遍历文件夹节点 + ); + for (TsFiles child : children) { + childIds.add(child.getId()); + childIds.addAll(getAllChildIds(child.getId())); + } + return childIds; + } + + /********************************** + * 用途说明: 查询实验数据管理文件夹 + * 参数说明 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @Override + public List getsListTsFiles(String id, String path) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("is_file", "FOLDER"); + if (StringUtils.isNotBlank(id)) { + queryWrapper.eq("parent_id", id); + } + if (StringUtils.isNotBlank(path)) { + queryWrapper.eq("work_path", path); + } + queryWrapper.isNotNull("work_path"); + List tsFiles = tsFilesMapper.selectList(queryWrapper); + return tsFiles; + } + + +// /********************************** +// * 用途说明: 分页查询试验数据管理-文件夹下内容 +// * 参数说明 +// * id id +// * pageNum 当前页 +// * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 +// ***********************************/ +// @Override +// public Page getsubFilepage(String id, Page page) { +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("parent_id", id); +// //分页查询 +// Page subFilepage = tsFilesMapper.selectPage(page, queryWrapper); +// if (subFilepage == null) { +// subFilepage = new Page<>(); +// subFilepage.setRecords(new ArrayList<>()); // 确保 records 被初始化 +// } +// List records = subFilepage.getRecords(); +// for (TsFiles tsFiles : records) { +// //如果是文件夹 跳过循环 +// if (tsFiles.getIsFile().equals("FOLDER")) { +// continue; +// } +// String workPath = tsFiles.getWorkPath(); +// String fileNameData = tsFiles.getFileName(); +// //主要是用于文件路径加名称 +// String path = workPath + fileNameData; +// //准备获取文件的信息 +// AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); +// FileItemResult fileItemResult = fileService.getFileItem(path); +// tsFiles.setUrl(fileItemResult.getUrl()); +// //如果是压缩文件 类型就给zip +// boolean isValid = hasValidExtension(fileItemResult.getName()); +// if (isValid) { +// tsFiles.setType("ZIP"); +// } else { +// tsFiles.setType(fileItemResult.getType().getValue()); +// } +// } +// subFilepage.setRecords(records); // 同步到 tsFilesPage +// return subFilepage; +// } + + + /*********************************** + * 用途说明:新增试验数据管理-文档内容 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Override + public Boolean addTsFiles(TsFiles tsFiles) { + + Boolean value = false; + //文件名称和大小 因为支持多个上传所以用,分隔 + List names = Arrays.asList(tsFiles.getFileName().split(",")); + List sizes = Arrays.asList(tsFiles.getFileSize().split(",")); + //上传人是当前登录人 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + + // 数据校验 + if (names.size() != sizes.size()) { + LOGGER.error("文件名称和文件大小的列表长度不一致"); + return false; + } + + List filesToSave = new ArrayList<>(); + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + tsFiles.setUploadTime(currentTime); + + for (int i = 0; i < names.size(); i++) { + String name = names.get(i).trim(); + String sizeStr = sizes.get(i).trim(); + // 校验文件大小是否可以转换成数值 + try { + TsFiles files1 = new TsFiles(); + files1.setTaskId(tsFiles.getTaskId()); + files1.setIsFile(tsFiles.getIsFile()); + files1.setParentId(tsFiles.getParentId()); + files1.setNodeId(tsFiles.getNodeId()); + files1.setWorkPath(tsFiles.getWorkPath()); + files1.setBackupPath(tsFiles.getBackupPath()); + files1.setKeywords(tsFiles.getKeywords()); + files1.setDescription(tsFiles.getDescription()); + files1.setUploadTime(tsFiles.getUploadTime()); + files1.setUploader(loginuser.getUsername()); + files1.setFileName(name); + files1.setFileSize(sizeStr); + filesToSave.add(files1); + } catch (NumberFormatException e) { + LOGGER.error("文件大小必须是有效的数字"); + } + } + if (filesToSave.size() > 0) { + //循环新增 + for (TsFiles filess : filesToSave) { + int valueAdded = tsFilesMapper.insert(filess); + if (valueAdded == 1) { + value = true; + } else { + value = false; + } + } + } + return value; + } + + + /*********************************** + * 用途说明:新增试验数据管理-文件夹 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class)// 添加事务注解,遇到异常时回滚 + public ResponseResult addTsFile(TsFiles tsFiles) { + + //上传人是当前登录人 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + tsFiles.setUploadTime(currentTime); + tsFiles.setUploader(loginuser.getUsername()); + tsFiles.setFileSize("0"); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_id", tsFiles.getNodeId()); + queryWrapper.eq("task_id", tsFiles.getTaskId()); + queryWrapper.eq("parent_id", tsFiles.getParentId()); + queryWrapper.eq("file_name", tsFiles.getFileName()); + int count = tsFilesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("文件夹名称已存在!"); + } + //本地创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setStorageKey("local"); + newFolderRequest.setPath(tsFiles.getWorkPath()); + newFolderRequest.setPassword(null); + newFolderRequest.setName(tsFiles.getFileName()); + + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + if (flag) { + int valueAdded = tsFilesMapper.insert(tsFiles); + if (valueAdded == 1) { + LOGGER.info("表结构和本地都创建文件夹成功"); + return ResponseResult.success(); + } else { + LOGGER.error("本地创建文件夹成功,表结构新增失败"); + return ResponseResult.error(); + } + } else { + LOGGER.error("本地创建文件夹失败"); + return ResponseResult.error(); + } + } + + + /********************************** + * 用途说明: 修改试验数据管理-文档内容 + * 参数说明 + * TsFiles 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class)// 添加事务注解,遇到异常时回滚 + public ResponseResult updateTsFiles(TsFiles tsFiles) { + // 修改之前查询表中的文件名是否修改,如果发生变动先修改 minio 然后再修改表结构 + TsFiles filesData = tsFilesMapper.selectById(tsFiles.getId()); + + // 判断文件名是否修改 不一致需要修改 + if (!tsFiles.getFileName().equals(filesData.getFileName())) { + + //校验文件夹会不会重命名 + if ("FOLDER".equals(tsFiles.getIsFile())) { + //如果是文件夹 递归修改它下面的所有文件的路径 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_id", tsFiles.getNodeId()); + queryWrapper.eq("task_id", tsFiles.getTaskId()); + queryWrapper.eq("parent_id", tsFiles.getParentId()); + queryWrapper.eq("file_name", tsFiles.getFileName()); + int count = tsFilesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("文件夹名称已存在!"); + } + + //查询数据源 + RenameFileRequest renameFileRequest = new RenameFileRequest(); + renameFileRequest.setName(filesData.getFileName()); + renameFileRequest.setNewName(tsFiles.getFileName()); + renameFileRequest.setPassword(""); + renameFileRequest.setPath(filesData.getWorkPath()); + renameFileRequest.setStorageKey("local"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(renameFileRequest.getStorageKey()); + Boolean flag = fileService.renameFile(renameFileRequest.getPath(), renameFileRequest.getName(), renameFileRequest.getNewName()); + //如果是true 说明至少有一个修改了文件夹 + if (flag) { + //递归下面所有的文件文件夹 路径都需要修改 + List resultList = new ArrayList<>(); + + // 获取父路径(去除自身) + String parentPath = filesData.getWorkPath(); + String[] pathParts = parentPath.split("/"); + int subscript = pathParts.length - 1; + //ID 旧的文件名 新的文件名 + querySubFilesAndUpdatePaths(resultList, tsFiles.getId(), filesData.getFileName(), tsFiles.getFileName(), subscript); + // 修改数据库 + int valueUpdate = tsFilesMapper.updateById(tsFiles); + if (valueUpdate == 1) { + LOGGER.error("\"local和minio修改成功,表结构修改成功\""); + return ResponseResult.success(); + } else { + LOGGER.error("local和minio修改成功,表结构修改失败"); + return ResponseResult.error(); + } + } else { + LOGGER.error("local和minio修改失败"); + return ResponseResult.error(); + } + } else { + //如果是文件 直接修改就可以 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_id", tsFiles.getNodeId()); + queryWrapper.eq("task_id", tsFiles.getTaskId()); + queryWrapper.eq("parent_id", tsFiles.getParentId()); + queryWrapper.eq("file_name", tsFiles.getFileName()); + int count = tsFilesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("文件名称已存在!"); + } + //查询数据源 + RenameFileRequest renameFileRequest = new RenameFileRequest(); + renameFileRequest.setName(filesData.getFileName()); + renameFileRequest.setNewName(tsFiles.getFileName()); + renameFileRequest.setPassword(""); + renameFileRequest.setPath(filesData.getWorkPath()); + renameFileRequest.setStorageKey("local"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(renameFileRequest.getStorageKey()); + Boolean flag = fileService.renameFile(renameFileRequest.getPath(), renameFileRequest.getName(), renameFileRequest.getNewName()); + //如果是true 说明至少有一个修改了文件夹 + if (flag) { + // 修改数据库 + int valueUpdate = tsFilesMapper.updateById(tsFiles); + if (valueUpdate == 1) { + LOGGER.error("\"local和minio修改成功,表结构修改成功\""); + return ResponseResult.success(); + } else { + LOGGER.error("local和minio修改成功,表结构修改失败"); + return ResponseResult.error(); + } + } else { + LOGGER.error("local和minio修改失败"); + return ResponseResult.error(); + } + } + } else { + // 如果文件名没有修改,仅更新数据库 + int valueUpdate = tsFilesMapper.updateById(tsFiles); + if (valueUpdate == 1) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + } + + + private void querySubFilesAndUpdatePaths(List resultList, String id, String oldName, String newName, int subscript) { + // 构造 QueryWrapper 查询当前文件夹下的文件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", id); + // 查询当前文件夹下的所有子项 + List subFiles = tsFilesMapper.selectList(queryWrapper); + // 确保 subFiles 不为空 + if (subFiles == null || subFiles.isEmpty()) { + return; + } + + // 将查询到的结果添加到结果列表 + resultList.addAll(subFiles); + + // 遍历每个子项,更新路径并递归查询其子项 + for (TsFiles subFile : subFiles) { + // 获取原始路径 + String oldWorkPath = subFile.getWorkPath(); + // 替换路径中的 oldName 为 newName,仅替换路径数组中指定下标的部分 + String newWorkPath = updatePathAtSubscript(oldWorkPath, oldName, newName, subscript); + // 更新数据库中的路径 + subFile.setWorkPath(newWorkPath); + tsFilesMapper.updateById(subFile); + // 递归查询每个子项的子文件 + if ("FOLDER".equals(subFile.getIsFile())) { + querySubFilesAndUpdatePaths(resultList, subFile.getId(), oldName, newName, subscript); + } + querySubFilesAndUpdatePaths(resultList, subFile.getId(), oldName, newName, subscript); + } + } + + // 辅助方法,更新路径中特定下标的部分 + private String updatePathAtSubscript(String oldPath, String oldName, String newName, int subscript) { + // 过滤空值 + List pathParts = Arrays.stream(oldPath.split("/")) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + + // 确保路径数组下标有效 + if (pathParts.size() > subscript) { + pathParts.set(subscript, pathParts.get(subscript).replace(oldName, newName)); + } + + // 拼接成新的路径 + return "/" + String.join("/", pathParts) + "/"; + } + + /********************************** + * 用途说明: 批量删除试验数据管理-文档内容 + * 参数说明 ids 文档内容id数组 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class)// 添加事务注解,遇到异常时回滚 + public String deleteTsFilesByIds(List dataset) { + List filesList = tsFilesMapper.selectBatchIds(dataset); + + int LocalSuccessCount = 0, LocalFailCount = 0, Localtotal = CollUtil.size(dataset); + //Todo 最直接的办法 循环出来 一条一条删除 + for (TsFiles files : filesList) { + + //判断是文件还是文件夹 + if ("FOLDER".equals(files.getIsFile())) { + //如果是文件夹 + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(files.getFileName()); + deleteItemData.setPassword(""); + deleteItemData.setPath(files.getWorkPath()); + deleteItemData.setType(FileTypeEnum.FOLDER); + deleteItemList.add(deleteItemData); + //首先通过ID集合查询所有的内容 然后放到List deleteItems里面 放好以后删除数据库 然后删除minio + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("local"); + + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + List deleteItems = batchDeleteRequest.getDeleteItems(); + + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems); + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) { + + 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 (totalCount > 1) { + //return ResponseResult.success("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + } else { + //return totalCount == deleteSuccessCount ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + } + + //如果是1 说明成功删除 + if (deleteSuccessCount >= 1) { + int valueDelete = deleteFolder(files.getId()); + if (valueDelete > 0) { + LocalSuccessCount = valueDelete; + LOGGER.info("表结构成功删除"); + } else { + System.out.println("没有记录被删除"); + } + } else { + LocalFailCount++; + } + } else { + //如果是文件 + + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(files.getFileName()); + deleteItemData.setPassword(""); + deleteItemData.setPath(files.getWorkPath()); + deleteItemData.setType(FileTypeEnum.FILE); + deleteItemList.add(deleteItemData); + //首先通过ID集合查询所有的内容 然后放到List deleteItems里面 放好以后删除数据库 然后删除minio + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("local"); + + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + List deleteItems = batchDeleteRequest.getDeleteItems(); + + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems); + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) { + + 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 (totalCount > 1) { + //return ResponseResult.success("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + } else { + //return totalCount == deleteSuccessCount ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + + } + //如果是1 说明成功删除 + if (deleteSuccessCount == 1) { + int valueDelete = tsFilesMapper.deleteById(files.getId()); + if (valueDelete > 0) { + LocalSuccessCount++; + LOGGER.info("表结构成功删除"); + } else { + System.out.println("没有记录被删除"); + } + } else { + LocalFailCount++; + } + } + } + return "Local批量删除 " + Localtotal + " 个, 删除成功 " + LocalSuccessCount + " 个, 失败 " + LocalFailCount + " 个."; + } + + // 调用删除方法并返回删除数量 + public int deleteFolder(String folderId) { + return deleteFolderAndSubFiles(folderId); // 返回删除的记录数量 + } + + // 递归删除文件夹及其子文件,并返回删除的记录数量 + public int deleteFolderAndSubFiles(String folderId) { + int deletedCount = 0; // 记录删除的数量 + + // 查询该文件夹下的所有子文件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", folderId); // 根据父ID查询所有子文件 + List subFiles = tsFilesMapper.selectList(queryWrapper); + + // 递归删除子文件 + for (TsFiles subFile : subFiles) { + // 递归调用删除子文件的逻辑 + deletedCount += deleteFolderAndSubFiles(subFile.getId()); // 累加删除数量 + + // 删除子文件记录(从数据库中删除) + tsFilesMapper.deleteById(subFile.getId()); + deletedCount++; // 每删除一个文件,数量加1 + } + + // 删除文件夹本身(根据传入的文件夹 ID 删除) + tsFilesMapper.deleteById(folderId); + deletedCount++; // 删除文件夹本身,数量加1 + + return deletedCount; // 返回删除的数量 + } + + + /********************************** + * 用途说明: 压缩文件夹接口 + * 参数说明 ids 文件id数组 + * 参数说明 compressedFormat 压缩文件格式 + * 参数说明 compressedName 压缩文件名称 + * 参数说明 compressedPath 压缩文件路径 + * 参数说明 covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 + * 参数说明 parentId 父ID + * 返回值说明: com.yfd.platform.config.ResponseResult + ***********************************/ + @Override + public String compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId) throws FileNotFoundException { + //获取本地根路径 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("name", "filePath"); + StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + String returnResult = ""; + // 创建要压缩的文件夹列表 + List sourceDirs = new ArrayList<>(); // 修复:使用 ArrayList + String[] splitIds = ids.split(","); + // 数组转集合 + List dataset = Arrays.asList(splitIds); // 使用 Arrays.asList 创建不可变列表 + List filesList = tsFilesMapper.selectBatchIds(dataset); + // 检查 filesList 是否为空 + if (filesList == null || filesList.isEmpty()) { + throw new RuntimeException("未找到要压缩的文件或文件夹"); + } + for (TsFiles tsFiles : filesList) { + // 检查 tsFiles 的字段是否为空 + if (tsFiles.getWorkPath() == null || tsFiles.getFileName() == null) { + throw new RuntimeException("文件或文件夹信息不完整"); + } + + // 构建完整路径 + String sourceDir = storageSourceConfig.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName(); + Path path = Paths.get(sourceDir); + // 检查路径是否存在 + if (!Files.exists(path)) { + throw new FileNotFoundException("路径不存在: " + path); + } + // 添加到压缩列表 + sourceDirs.add(path); // 修复:使用可变集合 + } + + // 规范化压缩包存放路径 + // 如果 compressedPath 是根目录,使用当前工作目录 + Path zipFilePath = Paths.get(storageSourceConfig.getValue() + compressedPath + compressedName + "." + compressedFormat); + + // 检查目标路径是否可写 + if (!Files.isWritable(zipFilePath.getParent())) { + throw new RuntimeException("目标路径不可写: " + zipFilePath.getParent()); + } + + String zipFileName = compressedName + "." + compressedFormat; + + // 使用 try-with-resources 确保 zipOut 在使用后关闭 + try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(new FileOutputStream(zipFilePath.toFile()))) { + // 设置压缩级别,可以选择调整 + zipOut.setLevel(ZipArchiveOutputStream.STORED); // 或者使用其他的压缩级别 + // 调用压缩方法 + Boolean value = compressDirectoriesToZip(sourceDirs, zipOut); + if (value) { + //表结构增加 nodeId, String TaskId + TsFiles tsFiles = new TsFiles(); + tsFiles.setTaskId(filesList.get(0).getTaskId()); + tsFiles.setNodeId(filesList.get(0).getNodeId()); + tsFiles.setWorkPath(compressedPath); + tsFiles.setIsFile("FILE"); + tsFiles.setParentId(parentId); + tsFiles.setBackupPath(""); + tsFiles.setKeywords(""); + tsFiles.setDescription(""); + tsFiles.setFileName(zipFileName); + + //上传人是当前登录人 + UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + + List filesToSave = new ArrayList<>(); + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + tsFiles.setUploadTime(currentTime); + tsFiles.setUploader(loginuser.getUsername()); + // 通过 nodeid taskid 名称查询 如果存在就不 新增 + + 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("file_name", zipFileName); + queryWrapperTsFiles.eq("parent_id", filesList.get(0).getParentId()); + TsFiles tsFilesdata = tsFilesMapper.selectOne(queryWrapperTsFiles); + //covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 + if ("0".equals(covered)) { + //如果不为空 更新updateTime时间 + if (tsFilesdata != null) { + tsFilesdata.setUpdateTime(currentTime); + tsFilesMapper.updateById(tsFilesdata); + } else { + //如果为空就新增 + int valueAdded = tsFilesMapper.insert(tsFiles); + returnResult = "压缩成功"; + } + + } else { + // 1提示文件存在 如果不为空返回提示 + if (tsFilesdata != null) { + returnResult = "文件或文件夹已存在!"; + } else { + //如果为空就新增 + int valueAdded = tsFilesMapper.insert(tsFiles); + returnResult = "压缩成功"; + } + } + returnResult = "压缩成功"; + } else { + returnResult = "压缩失败"; + } + } catch (IOException e) { + e.printStackTrace(); + } + return returnResult; + } + + private boolean compressDirectoriesToZip(List sourceDirs, ZipArchiveOutputStream zipOut) { + // 用于记录已处理的规范路径,防止循环引用 + Set processedPaths = new HashSet<>(); + for (Path sourceDir : sourceDirs) { + // 遍历时传入已处理路径集合 + if (!compressDirectoryToZip(sourceDir, zipOut, "", processedPaths)) { + return false; + } + } + return true; + } + + + /** + * 递归压缩目录到ZIP文件 + * + * @param sourceDir 当前要压缩的目录/文件路径 + * @param zipOut ZIP输出流 + * @param parentDir 当前路径在ZIP中的父目录(用于构建层级) + * @param processedPaths 已处理的规范路径集合(用于检测循环引用) + * @return 压缩成功返回true,否则false + */ + private boolean compressDirectoryToZip(Path sourceDir, ZipArchiveOutputStream zipOut, + String parentDir, Set processedPaths) { + try { + // --- 防循环处理 Start --- + // 获取规范路径(解析符号链接),用于检测重复目录 + Path canonicalPath = sourceDir.toRealPath(); + + // 如果是目录且已处理过,直接跳过 + if (Files.isDirectory(sourceDir) && processedPaths.contains(canonicalPath)) { + return true; // 已处理,避免循环 + } + // 将当前目录加入已处理集合(先添加再处理子项) + processedPaths.add(canonicalPath); + // --- 防循环处理 End --- + + File currentFile = sourceDir.toFile(); + + // 处理文件(当sourceDirs包含文件时) + if (!currentFile.isDirectory()) { + // 直接压缩单个文件 + addFileToZip(currentFile, zipOut, parentDir); + return true; + } + + // 获取目录下的文件列表(处理空目录和权限问题) + File[] files = currentFile.listFiles(); + if (files == null) { + System.err.println("无法访问目录: " + sourceDir); + return false; // 目录不可访问 + } + + // 遍历目录内容 + for (File file : files) { + String entryName = parentDir + file.getName(); + + if (file.isDirectory()) { + // 创建目录条目(以/结尾) + zipOut.putArchiveEntry(new ZipArchiveEntry(entryName + "/")); + zipOut.closeArchiveEntry(); + + // 递归处理子目录,传入更新后的父路径和已处理集合 + if (!compressDirectoryToZip(file.toPath(), zipOut, entryName + "/", processedPaths)) { + return false; + } + } else { + // 压缩文件 + addFileToZip(file, zipOut, entryName); + } + } + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将单个文件添加到ZIP流 + * + * @param file 要添加的文件 + * @param zipOut ZIP输出流 + * @param entryName 文件在ZIP中的路径 + */ + private void addFileToZip(File file, ZipArchiveOutputStream zipOut, String entryName) throws IOException { + zipOut.putArchiveEntry(new ZipArchiveEntry(entryName)); + try (FileInputStream fis = new FileInputStream(file)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = fis.read(buffer)) > 0) { + zipOut.write(buffer, 0, len); + } + } + zipOut.closeArchiveEntry(); + } + + + /*************************************解压缩*******************************************/ + + /********************************** + * 用途说明: 解压缩接口 + * 参数说明 id 要解压的文件id + * 参数说明 decompressionPath 解压缩路径 + * 参数说明 parentId 父ID + * 返回值说明: com.yfd.platform.config.ResponseResult + ***********************************/ + @Override + public String decompressionFolder(String id, String decompressionPath, String parentId) { + // 查询本地文件路径根目录(如 E:\yun) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("name", "filePath"); + StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + + // 获取压缩文件记录 + TsFiles zipFileRecord = tsFilesMapper.selectById(id); + String localRootDir = storageSourceConfig.getValue() + decompressionPath; + + try { + // 解压到临时目录 + Path zipFilePath = Paths.get(localRootDir, zipFileRecord.getFileName()); + File unzippedRoot = unzipToTemp(zipFilePath, localRootDir); + + // 递归保存结构到数据库 + saveFolderStructure(unzippedRoot, parentId, decompressionPath, zipFileRecord.getTaskId(), zipFileRecord.getNodeId(), zipFileRecord.getUploader()); + + return "解压存储成功"; + } catch (Exception e) { + throw new RuntimeException("操作失败: " + e.getMessage()); + } + } + + /** + * 保存文件夹结构 + * + * @param currentFile 当前文件/目录 + * @param parentId 父记录ID + * @param parentWorkPath 父工作路径 + * @param taskId 任务ID + * @param nodeId 节点ID + * @param uploader 上传人 + */ + + /** + * 保存文件夹结构(分阶段处理:先处理目录,再处理文件) + * + * @param currentFile 当前文件/目录 + * @param parentId 父记录ID + * @param parentWorkPath 父工作路径 + * @param taskId 任务ID + * @param nodeId 节点ID + * @param uploader 上传人 + */ + private void saveFolderStructure(File currentFile, String parentId, String parentWorkPath, String taskId, String nodeId, String uploader) { + // 阶段1:处理目录结构 + processFolders(currentFile, parentId, parentWorkPath, taskId, nodeId, uploader); + + // 阶段2:处理文件 + processFiles(currentFile, parentId, parentWorkPath, taskId, nodeId, uploader); + } + + /** + * 阶段1:递归创建目录结构 + */ + private void processFolders(File currentFile, String parentId, String parentWorkPath, + String taskId, String nodeId, String uploader) { + // 如果是文件直接返回 + if (currentFile.isFile()) return; + // 创建或更新当前目录记录 + String currentFolderId = createOrUpdateFolderRecord(currentFile, parentId, parentWorkPath, taskId, nodeId, uploader); + // 递归处理子目录(先处理所有子目录) + File[] children = currentFile.listFiles(); + if (children != null) { + for (File child : children) { + if (child.isDirectory()) { + processFolders( + child, + currentFolderId, + buildFolderPath(parentWorkPath + currentFile.getName()), + taskId, + nodeId, + uploader + ); + } + } + } + } + + /** + * 阶段2:处理文件(此时目录结构已存在) + */ + /** + * 优化后的文件处理逻辑 + */ + private void processFiles(File currentFile, String parentId, String parentWorkPath, + String taskId, String nodeId, String uploader) { + // 如果是文件直接处理 + if (currentFile.isFile()) { + String actualParentId = findActualParentIdForFile(currentFile, taskId, nodeId); + createFileRecord(currentFile, actualParentId, parentWorkPath, taskId, nodeId, uploader); + return; + } + + // 处理目录中的文件 + File[] children = currentFile.listFiles(); + if (children != null) { + for (File child : children) { + if (child.isFile()) { + // 关键修改:根据文件实际路径查询父目录ID + String actualParentId = findActualParentIdForFile(child, taskId, nodeId); + createFileRecord(child, actualParentId, parentWorkPath, taskId, nodeId, uploader); + } else { + processFiles( + child, + parentId, + buildFolderPath(parentWorkPath + child.getName() + "/"), // 更新父路径 + taskId, + nodeId, + uploader + ); + } + } + } + } + + /** + * 根据文件物理路径查询对应的父目录ID + */ + private String findActualParentIdForFile(File file, String taskId, String nodeId) { + // 获取文件的逻辑父路径 + String parentWorkPath = buildCurrentPathForParent(file); + + // 查询数据库获取目录ID + TsFiles parentDir = tsFilesMapper.selectOne(new QueryWrapper() + .eq("task_id", taskId) + .eq("node_id", nodeId) + .eq("work_path", parentWorkPath) + .eq("is_file", "FOLDER")); + + return parentDir != null ? parentDir.getParentId() : null; + } + + /** + * 构建文件的父目录路径(优化版) + */ + private String buildCurrentPathForParent(File file) { + // 获取完整逻辑路径(示例:/99/解压目录/subfolder/) + String fullPath = buildCurrentPath("", file); + + // 确保路径中的多个 "/" 被替换成一个 "/" + fullPath = fullPath.replaceAll("/+", "/"); + + // 截取父目录部分 + if (file.isFile()) { + // 确保在路径中有 "/" 存在,且去除文件名 + int lastSlash = fullPath.lastIndexOf('/'); + if (lastSlash != -1) { + return fullPath.substring(0, lastSlash + 1); + } + // 如果路径中没有找到 "/",说明路径仅是文件名,返回空路径 + return "/"; + } + + // 对于文件夹路径,直接返回完整路径 + return fullPath; + } + + + /** + * 创建文件记录 + */ + private void createFileRecord(File file, String parentId, String parentWorkPath, + String taskId, String nodeId, String uploader) { + TsFiles record = buildCommonRecord(file, parentId, parentWorkPath, taskId, nodeId, uploader); + record.setIsFile("FILE"); + record.setFileSize(String.valueOf(file.length())); + QueryWrapper query = new QueryWrapper() + .eq("node_id", nodeId) + .eq("task_id", taskId) + .eq("work_path", record.getWorkPath()) + .eq("file_name", file.getName()) + .eq("is_file", "FILE"); // 确保是文件类型 + + TsFiles existing = tsFilesMapper.selectOne(query); + if (existing != null) { + existing.setUpdateTime(new Timestamp(System.currentTimeMillis())); + tsFilesMapper.updateById(existing); + } else { + tsFilesMapper.insert(record); + } + } + + // 文件夹路径标准化(确保以/结尾) + private String buildFolderPath(String path) { + return path.endsWith("/") ? path : path + "/"; + } + + /** + * 创建或更新目录记录(返回生成的目录ID) + */ + private String createOrUpdateFolderRecord(File folder, String parentId, String parentWorkPath, + String taskId, String nodeId, String uploader) { + TsFiles record = buildCommonRecord(folder, parentId, parentWorkPath, taskId, nodeId, uploader); + record.setIsFile("FOLDER"); + record.setFileSize("0"); + + QueryWrapper query = new QueryWrapper() + .eq("node_id", nodeId) + .eq("task_id", taskId) + .eq("work_path", record.getWorkPath()) + .eq("file_name", folder.getName()) + .eq("is_file", "FOLDER"); // 增加类型过滤 + + TsFiles existing = tsFilesMapper.selectOne(query); + if (existing != null) { + existing.setUpdateTime(new Timestamp(System.currentTimeMillis())); + tsFilesMapper.updateById(existing); + return existing.getId(); + } else { + tsFilesMapper.insert(record); + return record.getId(); + } + } + + + /** + * 公共记录构建方法 + */ + private TsFiles buildCommonRecord(File file, String parentId, String parentWorkPath, + String taskId, String nodeId, String uploader) { + TsFiles record = new TsFiles(); + record.setNodeId(nodeId); + record.setTaskId(taskId); + record.setParentId(parentId); + record.setFileName(file.getName()); + record.setWorkPath(buildCurrentPath(parentWorkPath, file)); + record.setUploadTime(new Timestamp(System.currentTimeMillis())); + record.setUploader(uploader); + return record; + } + + + /** + * 构建当前文件或文件夹的 work_path + * + * @return 当前文件或文件夹的 work_path + */ + private String buildCurrentPath(String parentPath, File file) { + // 1. 获取存储根路径(如 E:\yun) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("name", "filePath"); + StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + String rootPath = storageSourceConfig.getValue(); + + // 2. 计算相对路径(统一使用正斜杠) + String relativePath = file.getAbsolutePath() + .replace(rootPath, "") + .replace("\\", "/"); + + // 3. 处理文件路径 + if (file.isFile()) { + // 示例:relativePath = "99/解压目录/subfolder/file.txt" → "99/解压目录/subfolder/" + int lastSlash = relativePath.lastIndexOf('/'); + return (lastSlash != -1 ? relativePath.substring(0, lastSlash + 1) : ""); + } else { + // 目录路径:确保以/结尾 + int lastSlash = relativePath.lastIndexOf('/'); + return (lastSlash != -1 ? relativePath.substring(0, lastSlash + 1) : ""); + } + } + + + /** + * 解压 ZIP 文件到临时目录 + * + * @param zipFilePath ZIP 文件路径 + * @param baseDir 基础解压目录(如 /var/storage) + * @return 解压后的根目录 File 对象 + */ + private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException { + // 1. 直接构建目标路径(baseDir/zipName) + String zipName = getFileNameWithoutExtension(zipFilePath.getFileName().toString()); + Path destRoot = Paths.get(baseDir, zipName); + Files.createDirectories(destRoot); // 确保目标目录存在 + + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath.toFile()))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (entry.getName().startsWith("__MACOSX/")) { + zis.closeEntry(); + continue; + } + + // 2. 直接解压到目标路径(不再嵌套临时目录) + Path destPath = destRoot.resolve(entry.getName()).normalize(); + validatePathSafety(destPath, destRoot); // 确保路径安全 + + // 3. 处理目录 + if (entry.isDirectory()) { + Files.createDirectories(destPath); + } + // 4. 处理文件 + else { + // 确保父目录存在 + Files.createDirectories(destPath.getParent()); + + // 写入文件(使用缓冲提升性能) + try (BufferedOutputStream bos = new BufferedOutputStream( + Files.newOutputStream(destPath, StandardOpenOption.CREATE_NEW))) { + byte[] buffer = new byte[8192]; + int len; + while ((len = zis.read(buffer)) > 0) { + bos.write(buffer, 0, len); + } + } + } + zis.closeEntry(); + } + } + return destRoot.toFile(); + } + + + /** + * 校验路径安全性(防御路径穿越攻击) + */ + private void validatePathSafety(Path destPath, Path tempRoot) throws IOException { + if (!destPath.startsWith(tempRoot)) { + throw new IOException("检测到非法路径穿越: " + destPath); + } + } + + /** + * 获取无扩展名的文件名 + */ + private String getFileNameWithoutExtension(String fileName) { + int dotIndex = fileName.lastIndexOf('.'); + return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex); + } + + + /*******************************************************本地和minio对比开始****************************************************************/ + + @Override + public TsFiles compareDirectories(String localPath, String minioPath) { + //查询本地文件路径根目录 如 E:\yun + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("name", "filePath"); + StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper); + + //查询存储空间名称根目录 如test-bucket + QueryWrapper queryWrapper1 = new QueryWrapper<>(); + queryWrapper1.eq("name", "bucketName"); + StorageSourceConfig storageSourceConfig1 = storageSourceConfigMapper.selectOne(queryWrapper1); + + // 获取完整路径 + String fullLocalPath = localPath; + String fullMinioPath = minioPath; + List results = new ArrayList<>(); + + String localBasePath = storageSourceConfig.getValue() + localPath; + // 获取文件列表 + Map localFiles = listLocalFiles(fullLocalPath); + TsFiles tsFiles = new TsFiles(); + try { + // 获取minio文件列表 + AbstractBaseFileService fileServiceMinio = storageSourceContext.getByStorageKey("minio"); + List fileItemListMinio = fileServiceMinio.fileLists(fullMinioPath); + // 获取本地文件列表 + AbstractBaseFileService fileServiceLocal = storageSourceContext.getByStorageKey("local"); + List fileItemListLocal = fileServiceLocal.fileLists(fullLocalPath); + // 对比文件 + tsFiles = compareFiles(fileItemListMinio, fileItemListLocal, localBasePath, storageSourceConfig1.getValue()); + + } catch (Exception e) { + e.printStackTrace(); + } + return tsFiles; + } + + + public String calculateMD5(InputStream inputStream) throws Exception { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + md.update(buffer, 0, bytesRead); + } + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + + public TsFiles compareFiles(List fileItemListMinio, List fileItemListLocal, String localBasePath, String bucketName) { + TsFiles tsFiles = new TsFiles(); + + // 将 MinIO 文件列表转换为 Map(文件名 -> 文件信息) + Map minioFileMap = new HashMap<>(); + for (FileItemResult file : fileItemListMinio) { + minioFileMap.put(file.getName(), file); + } + + // 将本地文件列表转换为 Map(文件名 -> 文件信息) + Map localFileMap = new HashMap<>(); + for (FileItemResult file : fileItemListLocal) { + localFileMap.put(file.getName(), file); + } + + // 检查本地有而 MinIO 没有的文件 + List localOnlyFiles = new ArrayList<>(); + for (FileItemResult localFile : fileItemListLocal) { + if (!minioFileMap.containsKey(localFile.getName())) { + localOnlyFiles.add(localFile); + } + } + LOGGER.info("本地有而 MinIO 没有的文件:" + localOnlyFiles); + + // 检查 MinIO 有而本地没有的文件 + List minioOnlyFiles = new ArrayList<>(); + for (FileItemResult minioFile : fileItemListMinio) { + if (!localFileMap.containsKey(minioFile.getName())) { + minioOnlyFiles.add(minioFile); + } + } + LOGGER.info("MinIO 有而本地没有的文件:" + minioOnlyFiles); + + // 检查文件大小不一致的文件 + List sizeMismatchedFiles = new ArrayList<>(); + // 检查 MD5 不一致的文件 + //List md5MismatchedFiles = new ArrayList<>(); + + // 检查 MD5 不一致的文件 + List md5MismatchedFiles = new ArrayList<>(); + + // 遍历 MinIO 文件 + for (FileItemResult minioFile : fileItemListMinio) { + // 检查 MinIO 文件是否在本地存在 + if (localFileMap.containsKey(minioFile.getName())) { + FileItemResult localFile = localFileMap.get(minioFile.getName()); + + // 检查文件是否为文件夹,如果是文件夹,跳过 + File localFilePath = new File(localBasePath + localFile.getName()); + if (localFilePath.isDirectory()) { + LOGGER.error("路径指向的是一个文件夹,跳过该文件夹:" + localFilePath.getAbsolutePath()); + continue; // 跳过文件夹 + } + + // 检查文件大小是否匹配 + if (minioFile.getSize() != null && localFile.getSize() != null && minioFile.getSize().longValue() != localFile.getSize().longValue()) { + sizeMismatchedFiles.add(minioFile.getName()); + } + // 如果文件大小一致,进一步检查 MD5 + try { + // 计算本地文件的 MD5 + String localMD5 = calculateMD5(new FileInputStream(localFilePath)); + if (localMD5 == null || localMD5.isEmpty()) { + continue; + } + + // 获取 MinIO 文件的 MD5 + String minioMD5; + AbstractBaseFileService fileServiceMinio = storageSourceContext.getByStorageKey("minio"); + String key = StringUtils.concat(minioFile.getPath(), minioFile.getName()); // 构造文件的 key + if (minioFile.getSize() <= 5 * 1024 * 1024 * 1024L) { // 小于 5GB,使用 ETag + ObjectMetadata metadata = fileServiceMinio.getObjectMetadata(bucketName, key); + minioMD5 = metadata.getETag().replace("\"", ""); // 去除 ETag 的引号 + } else { // 大于 5GB,手动计算 MD5 + S3Object s3Object = fileServiceMinio.getObject(bucketName, key); + minioMD5 = calculateMD5(s3Object.getObjectContent()); + } + + // 比较 MD5 + if (!minioMD5.equals(localMD5)) { + minioFile.setLocatMd5(localMD5); + minioFile.setMinioMd5(minioMD5); + md5MismatchedFiles.add(minioFile); + } + } catch (Exception e) { + LOGGER.error("计算 MD5 失败:" + e.getMessage()); + } + + } else { + // 如果 MinIO 中没有该文件,本地有的情况下,跳过该文件 + LOGGER.info("MinIO 中没有该文件,本地有文件,跳过对比: " + minioFile.getName()); + } + } +// tsFiles.setLocalOnlyFiles(localOnlyFiles); +// tsFiles.setMinioOnlyFiles(minioOnlyFiles); +// tsFiles.setMd5MismatchedFiles(md5MismatchedFiles); + LOGGER.info("文件大小不一致的文件:" + sizeMismatchedFiles); + LOGGER.info("文件大小一致但 MD5 不一致的文件:" + md5MismatchedFiles); + return tsFiles; + + } + + + /** + * 递归列出本地文件 + * + * @param path 要列出的本地目录路径 + * @return 文件信息Map,key为相对路径,value为文件信息 + */ + private Map listLocalFiles(String path) { + Map files = new HashMap<>(); + File root = new File(path); + listLocalFilesRecursive(root, root.getAbsolutePath(), files); + return files; + } + + /** + * 递归列出本地文件 + * + * @param file 当前文件/目录 + * @param basePath 基础路径(用于计算相对路径) + * @param results 结果Map + */ + private void listLocalFilesRecursive(File file, String basePath, Map results) { + String relativePath = file.getAbsolutePath().substring(basePath.length()) + .replace("\\", "/"); + if (relativePath.startsWith("/")) relativePath = relativePath.substring(1); + + if (file.isDirectory()) { + results.put(relativePath + "/", new FileInfo()); + for (File f : file.listFiles()) { + listLocalFilesRecursive(f, basePath, results); + } + } else { + results.put(relativePath, new FileInfo()); + //results.put(relativePath, new FileInfo(calculateMD5(file), false)); + } + } + + /** + * 计算文件的MD5哈希值(支持大文件流式处理) + * + * @param file 要计算的文件对象 + * @return 32位小写十六进制MD5字符串,计算失败返回null + */ + private String calculateMD5(File file) { + // 前置校验 + if (!file.exists() || file.isDirectory()) { + return null; + } + + final int BUFFER_SIZE = 8192; // 8KB缓冲区 + try (// 自动关闭资源 + InputStream is = new FileInputStream(file); + // 使用DigestInputStream自动处理摘要计算 + DigestInputStream dis = new DigestInputStream(is, MessageDigest.getInstance("MD5"))) { + + // 读取缓冲区(仅用于更新摘要,无需处理数据) + byte[] buffer = new byte[BUFFER_SIZE]; + // 循环读取直到文件结束(read方法返回值被忽略,重点是通过读取更新摘要) + while (dis.read(buffer) != -1) { /* 空循环体 */ } + + // 获取最终的摘要字节数组 + byte[] digest = dis.getMessageDigest().digest(); + + // 将字节数组转换为十六进制字符串 + StringBuilder hexString = new StringBuilder(); + for (byte b : digest) { + // 确保每个字节转换为两位十六进制 + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); // 补零 + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + // Java标准库保证MD5存在,此处异常仅为防御性编程 + throw new RuntimeException("MD5 algorithm not found", e); + } catch (IOException e) { + System.err.println("Error reading file: " + file.getAbsolutePath()); + return null; + } + } + + +/*******************************************************本地和minio对比结束****************************************************************/ + + + /******************************************************对比结束以后开始同步***************************************************************************************/ + /********************************** + * 用途说明: 将文件上传到备份空间 + * 参数说明 paths 路径集合 + * 参数说明 names 文件名集合 + * 参数说明 sizes 文件大小集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Override + public Boolean uploadToBackup(String paths, String names, String sizes) { + Boolean value = true; + + //上传人是当前登录人 +// UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); +// LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + + 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); + + List pathsList = Arrays.asList(paths.split(",")); + List nameList = Arrays.asList(names.split(",")); + List sizeList = Arrays.asList(sizes.split(",")); + + for (int i = 0; i < nameList.size(); i++) { + String name = nameList.get(i).trim(); + String path = pathsList.get(i).trim(); + // 确保 path 最前面有 / + if (!path.startsWith("/")) { + path = "/" + path; + } + // 确保 path 最后面有 / + if (!path.endsWith("/")) { + path = path + "/"; + } + String size = sizeList.get(i).trim(); + + String localPath = storageSourceConfig.getValue() + path;//localPath是本地的路径 + String minioPath = path;//// MinIO上传目标路径 + // 使用Java NIO的Path类来构造压缩文件的完整路径,存储在localPath目录下 + Path FilePath = Paths.get(localPath, name); + // 4. 上传压缩文件到MinIO + File zipFile = FilePath.toFile(); // 获取本地文件 + + + //判断是不是文件夹 如果是 增加节点表 + File localFilePath = new File(localPath + name); + if (localFilePath.isDirectory()) { + + String nodeName = path.replaceAll("(^/|^\\\\|/|\\\\$)", ""); + QueryWrapper queryWrapperNode = new QueryWrapper<>(); + queryWrapperNode.eq("node_name", nodeName); + TsNodes tsNodesData = tsNodesMapper.selectOne(queryWrapperNode); + //增加节点表 + TsNodes tsnodes = new TsNodes(); + tsnodes.setParentId(tsNodesData.getNodeId());//上级节点的 节点ID + tsnodes.setTaskId(tsNodesData.getTaskId()); //任务ID + tsnodes.setNodeName(name);//节点名称 是文件名 + //创建人是当前登录人 + //tsnodes.setCreator(loginuser.getUsername()); + tsnodes.setCreator(null); + //当前操作时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + tsnodes.setCreateTime(currentTime); + //通过获取上级节点的条数 设置节点顺序 + QueryWrapper queryWrapperNodeOrder = new QueryWrapper<>(); + queryWrapperNodeOrder.eq("parent_id", tsnodes.getParentId()); + int orderno = tsNodesMapper.selectCount(queryWrapperNodeOrder) + 1; + //序号 + tsnodes.setNodeOrder(orderno); + + //判断 local或者minio有没有成功 + //新增节点的时候 创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setName(tsnodes.getNodeName());//新建的文件夹名称,示例值(/a/b/c) + newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + newFolderRequest.setPath(minioPath);//请求路径,示例值(/) + newFolderRequest.setStorageKey("minio");//存储源 key,示例值(local minio) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + + int valueAdded = tsNodesMapper.insert(tsnodes); + if (valueAdded == 1) { + value = true; + } else { + value = false; + } + + + } else { + //如果是文件 + boolean flag1 = false; + try { + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("minio"); + flag1 = fileService.UploadFiles(storageSourceConfig1.getValue(), minioPath + name, zipFile); + } catch (Exception e) { + e.printStackTrace(); + value = false; + } + //如果minio增加成功 新增表结构 + if (flag1) { + QueryWrapper queryWrapper2 = new QueryWrapper<>(); + queryWrapper2.eq("file_name", name);//名称 + queryWrapper2.eq("work_path", path);//路径 + List tsFiles = tsFilesMapper.selectList(queryWrapper2); + //如果说没有的话 我就新增 有的话就不变 + if (tsFiles == null || tsFiles.isEmpty()) { + QueryWrapper queryWrapper3 = new QueryWrapper<>(); + queryWrapper3.eq("work_path", path);//路径 + List tsFilesList = tsFilesMapper.selectList(queryWrapper3); + + TsFiles files1 = new TsFiles(); + files1.setTaskId(tsFilesList.get(0).getTaskId()); + files1.setNodeId(tsFilesList.get(0).getNodeId()); + files1.setFileName(name); + files1.setFileSize(size); + files1.setWorkPath(path); + files1.setBackupPath(path); + files1.setKeywords(null); + files1.setDescription(null); + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + files1.setUploadTime(currentTime); + //获取登录人 + //files1.setUploader(loginuser.getUsername()); + files1.setUploader(null); + int valueAdded = tsFilesMapper.insert(files1); + if (valueAdded == 1) { + value = true; + } else { + value = false; + } + } + } + } + } + return value; + } + + /********************************** + * 用途说明: 从备份空间下载到工作空间 + * 参数说明 paths 路径集合 + * 参数说明 names 文件名集合 + * 参数说明 sizes 文件大小集合 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Override + public Boolean downloadToLocal(String paths, String names, String sizes) { + Boolean value = true; + + //上传人是当前登录人 +// UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); +// LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + + 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); + + List pathsList = Arrays.asList(paths.split(",")); + List nameList = Arrays.asList(names.split(",")); + List sizeList = Arrays.asList(sizes.split(",")); + + for (int i = 0; i < nameList.size(); i++) { + String name = nameList.get(i).trim(); + String path = pathsList.get(i).trim(); + // 确保 path 最前面有 / + if (!path.startsWith("/")) { + path = "/" + path; + } + // 确保 path 最后面有 / + if (!path.endsWith("/")) { + path = path + "/"; + } + String size = sizeList.get(i).trim(); + + String localPath = storageSourceConfig.getValue() + path + name;//localPath是本地的路径 + String minioPath = path + name;//// MinIO上传目标路径 + + FileOutputStream outputStream = null; // 输出流 + S3ObjectInputStream inputStream = null; // 输入流 + S3Object s3Object = null; + + try { + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("minio"); + // 获取 MinIO 文件对象 + s3Object = fileService.getObject(storageSourceConfig1.getValue(), minioPath); + // 获取文件输入流 + inputStream = s3Object.getObjectContent(); + + // 创建本地文件 + File localFile = new File(localPath); + if (!localFile.getParentFile().exists()) { + localFile.getParentFile().mkdirs(); // 创建父目录 + } + + // 将文件内容写入本地文件 + outputStream = new FileOutputStream(localFile); + IOUtils.copy(inputStream, outputStream); + System.out.println("文件下载成功:" + localPath); + } catch (Exception e) { + System.err.println("文件下载失败:" + e.getMessage()); + } finally { + // 关闭流 + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + System.err.println("关闭输入流失败:" + e.getMessage()); + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + System.err.println("关闭输出流失败:" + e.getMessage()); + } + } + if (s3Object != null) { + try { + s3Object.close(); + } catch (IOException e) { + System.err.println("关闭 S3Object 失败:" + e.getMessage()); + } + } + } + + QueryWrapper queryWrapper2 = new QueryWrapper<>(); + queryWrapper2.eq("file_name", name);//名称 + queryWrapper2.eq("work_path", path);//路径 + List tsFiles = tsFilesMapper.selectList(queryWrapper2); + //如果说没有的话 我就新增 有的话就不变 + if (tsFiles == null || tsFiles.isEmpty()) { + QueryWrapper queryWrapper3 = new QueryWrapper<>(); + queryWrapper3.eq("work_path", path);//路径 + List tsFilesList = tsFilesMapper.selectList(queryWrapper3); + + TsFiles files1 = new TsFiles(); + files1.setTaskId(tsFilesList.get(0).getTaskId()); + files1.setNodeId(tsFilesList.get(0).getNodeId()); + files1.setFileName(name); + files1.setFileSize(size); + files1.setWorkPath(path); + files1.setBackupPath(path); + files1.setKeywords(null); + files1.setDescription(null); + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + files1.setUploadTime(currentTime); + //获取登录人 + //files1.setUploader(loginuser.getUsername()); + files1.setUploader(null); + int valueAdded = tsFilesMapper.insert(files1); + if (valueAdded == 1) { + value = true; + } else { + value = false; + } + } + } + return value; + } + + + // 辅助方法:关闭资源时,避免抛出异常 + private void closeQuietly(AutoCloseable resource) { + if (resource != null) { + try { + resource.close(); + } catch (Exception e) { + System.err.println("关闭资源时出现异常:" + e.getMessage()); + } + } + } + + /*********************************************************************************************************************************************/ + + + /**********************************************************移动文件或者文件夹结束***********************************************************************************/ + /** + * 移动文件或文件夹 + * 参数说明 newPath 新路径 + * 参数说明 oldpaths 原路径 + * 参数说明 ParentId 父ID + * 参数说明 newFileName + * 参数说明 Rename 重命名的文件名称 + * 参数说明 type 覆盖还是重命名 0 1 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String moveFileFolder(MoveCopyFileFolderRequest reques) throws IOException { + // 1. 获取存储根路径 + StorageSourceConfig config = storageSourceConfigMapper.selectOne( + new QueryWrapper().eq("name", "filePath")); + Path rootPath = Paths.get(config.getValue()); + + String newPath = reques.getNewPath(); + String oldpaths = reques.getOldpaths(); + String parentId = reques.getParentId(); + String rename = reques.getRename(); + String type = reques.getType(); + String[] newFileNames = reques.getNewFileName().split(","); + // 数组转集合 + List newFileNameList = Arrays.asList(newFileNames); + // 2. 构建目标父目录的物理路径(如 E:\yun\22) + Path targetParentPhysicalPath = buildPhysicalPath(newPath, rootPath); + + // 3. 循环处理所有选中的文件/文件夹 + for (String fileName : newFileNameList) { + // 构建原文件物理路径(如 E:\yun\11\file.txt) + Path sourcePath = buildPhysicalPath(oldpaths + "/" + fileName, rootPath); + if (!Files.exists(sourcePath)) { + throw new IOException("源文件不存在: " + sourcePath); + } + + // ==== 新增关键校验 ==== + // 1. 禁止移动到自身 + if (sourcePath.equals(targetParentPhysicalPath)) { + throw new IllegalArgumentException("不能移动到自己所在目录"); + } + + // 2. 禁止文件夹移动到子目录 + if (Files.isDirectory(sourcePath)) { + if (targetParentPhysicalPath.startsWith(sourcePath)) { + throw new IllegalArgumentException("禁止将文件夹移动到自己内部"); + } + } + + // 生成目标文件名(处理重命名和冲突) + String targetFileName = generateTargetFileName(targetParentPhysicalPath, fileName, rename, type); + Path targetPath = targetParentPhysicalPath.resolve(targetFileName); + + // 3. 再次检查自移动 + if (sourcePath.equals(targetPath)) { + throw new IllegalArgumentException("不能移动到自己当前位置"); + } + + // 执行移动操作 + moveFile(sourcePath, targetPath, type); + + // 更新数据库记录 + updateDatabase(parentId, oldpaths, fileName, newPath, targetFileName, type); + } + return "移动成功"; + } + + + private String generateTargetFileName(Path targetParentPhysicalPath, String originalName, String rename, String type) { + if ("1".equals(type)) { + // 重命名模式:直接使用用户指定名称 + return rename; + } else { +// // 覆盖模式:处理文件名冲突(自动添加序号) +// String baseName = originalName.lastIndexOf('.') != -1 ? +// originalName.substring(0, originalName.lastIndexOf('.')) : originalName; +// String extension = originalName.lastIndexOf('.') != -1 ? +// originalName.substring(originalName.lastIndexOf('.')) : ""; +// +// int counter = 1; +// String newName = originalName; +// while (Files.exists(targetParentPhysicalPath.resolve(newName))) { +// newName = baseName + "(" + counter + ")" + extension; +// counter++; +// } + return originalName; + } + } + + /** + * 构建完整的物理路径(基于数据库中的 work_path) + */ + private Path buildPhysicalPath(String dbPath, Path root) { + // 统一替换所有斜杠为系统分隔符(兼容不同系统) + String systemPath = dbPath.replace("/", File.separator) + .replace("\\", File.separator); + + // 处理根路径特殊情况(如 "/" 或空字符串) + if (systemPath.equals(File.separator) || systemPath.isEmpty()) { + return root; + } + + // 修复正则表达式:使用 Pattern.quote 处理特殊字符 + String separatorRegex = Pattern.quote(File.separator); + systemPath = systemPath.replaceAll("^" + separatorRegex + "+", ""); + + try { + Path resolvedPath = root.resolve(systemPath).normalize(); + + // 二次校验防止路径越狱 + if (!resolvedPath.startsWith(root)) { + throw new SecurityException("非法路径访问: " + dbPath); + } + return resolvedPath; + } catch (InvalidPathException e) { + throw new IllegalArgumentException("无效路径格式: " + dbPath, e); + } + } + + + /** + * 执行文件移动操作 + */ + private void moveFile(Path sourcePath, Path targetPath, String type) throws IOException { + if ("0".equals(type)) { + // 覆盖模式:强制替换目标文件 + Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } else { + // 重命名模式:检查目标文件是否存在 + if (Files.exists(targetPath)) { + throw new IOException("目标文件已存在:" + targetPath); + } + Files.move(sourcePath, targetPath); + } + + // 移动后检查原路径是否为空,若为空则重新创建空文件夹(可选) + if (isPathEmpty(sourcePath.getParent())) { + Files.createDirectories(sourcePath.getParent()); + } + } + + /** + * 更新数据库中的父ID和路径 + */ + private void updateDatabase(String parentId, String oldpaths, String fileName, + String newPath, String targetFileName, String type) { + // 1. 构建完整路径 + String oldWorkPath = oldpaths.endsWith("/") ? oldpaths : oldpaths + "/"; + String newWorkPath = newPath.endsWith("/") ? newPath : newPath + "/"; + + // 2. 查询原记录 + TsFiles fileRecord = tsFilesMapper.selectOne( + new QueryWrapper().eq("work_path", oldWorkPath) + .eq("file_name", fileName)); + if (fileRecord == null) { + throw new RuntimeException("数据库记录不存在:" + oldWorkPath + fileName); + } + + // 3. 查询目标位置是否已存在记录 + TsFiles newfileRecord = tsFilesMapper.selectOne( + new QueryWrapper().eq("work_path", newWorkPath) + .eq("file_name", targetFileName)); + + // 4. 根据 type 处理记录 + if ("0".equals(type)) { + // 覆盖模式 + if (newfileRecord != null) { + // 更新目标记录 + newfileRecord.setParentId(parentId); + newfileRecord.setWorkPath(newWorkPath); + newfileRecord.setFileName(targetFileName); + newfileRecord.setUpdateTime(new Timestamp(System.currentTimeMillis())); // 更新时间 + tsFilesMapper.updateById(newfileRecord); + // 删除原记录 + tsFilesMapper.deleteById(fileRecord.getId()); + } else { + // 插入新记录 + TsFiles newRecord = new TsFiles(); + newRecord.setNodeId(fileRecord.getNodeId()); + newRecord.setTaskId(fileRecord.getTaskId()); + newRecord.setIsFile(fileRecord.getIsFile()); + newRecord.setParentId(parentId); + newRecord.setFileName(targetFileName); + newRecord.setWorkPath(newWorkPath); + newRecord.setFileSize(fileRecord.getFileSize()); + newRecord.setKeywords(fileRecord.getKeywords()); + newRecord.setDescription(fileRecord.getDescription()); + newRecord.setUploadTime(new Timestamp(System.currentTimeMillis())); // 上传时间 + newRecord.setUploader(fileRecord.getUploader()); // 上传人 + tsFilesMapper.insert(newRecord); + // 如果是文件夹,递归删除子项 + if ("FOLDER".equals(fileRecord.getIsFile())) { + // 先移动子项再删除原记录 + moveChildrenRecords(oldWorkPath, newWorkPath + targetFileName + "/", newRecord.getId(), type, fileRecord.getId()); + } + } + // 删除原记录 + List ids = new ArrayList<>(); + ids.add(fileRecord.getId()); + deleteTsFilesByIds(ids); + } else { + // 重命名模式:插入新记录 + TsFiles newRecord = new TsFiles(); + newRecord.setNodeId(fileRecord.getNodeId()); + newRecord.setTaskId(fileRecord.getTaskId()); + newRecord.setIsFile(fileRecord.getIsFile()); + newRecord.setParentId(parentId); + newRecord.setFileName(targetFileName); + newRecord.setWorkPath(newWorkPath); + newRecord.setFileSize(fileRecord.getFileSize()); + newRecord.setKeywords(fileRecord.getKeywords()); + newRecord.setDescription(fileRecord.getDescription()); + newRecord.setUploadTime(new Timestamp(System.currentTimeMillis())); // 上传时间 + newRecord.setUploader(fileRecord.getUploader()); // 上传人 + tsFilesMapper.insert(newRecord); + tsFilesMapper.deleteById(fileRecord.getId()); + } + } + + /** + * 递归移动子项记录(覆盖模式下先移动后删除) + */ + private void moveChildrenRecords(String oldPrefix, String newPrefix, String parentId, String type, String oldparentId) { + //查询原来的 + List children = tsFilesMapper.selectList( + new QueryWrapper().eq("parent_id", oldparentId)); + + for (TsFiles child : children) { + // 构建子项新路径 + String childNewPath = newPrefix; + + // 处理子项记录 + if ("0".equals(type)) { + // 覆盖模式:更新或插入 + TsFiles existingChild = tsFilesMapper.selectOne( + new QueryWrapper().eq("work_path", childNewPath) + .eq("file_name", child.getFileName())); + + if (existingChild != null) { + existingChild.setParentId(parentId); + existingChild.setWorkPath(childNewPath); + existingChild.setUpdateTime(new Timestamp(System.currentTimeMillis())); + tsFilesMapper.updateById(existingChild); + } else { + TsFiles newChild = new TsFiles(); + newChild.setNodeId(child.getNodeId()); + newChild.setTaskId(child.getTaskId()); + newChild.setIsFile(child.getIsFile()); + newChild.setParentId(parentId); + newChild.setFileName(child.getFileName()); + newChild.setWorkPath(childNewPath); + newChild.setFileSize(child.getFileSize()); + newChild.setKeywords(child.getKeywords()); + newChild.setDescription(child.getDescription()); + newChild.setUploadTime(new Timestamp(System.currentTimeMillis())); + newChild.setUploader(child.getUploader()); + tsFilesMapper.insert(newChild); + // 递归处理子文件夹 + if ("FOLDER".equals(child.getIsFile())) { + moveChildrenRecords(oldPrefix, newPrefix + newChild.getFileName() + "/", newChild.getId(), type, child.getId()); + } + } + } + } + } + + private String getRelativePath(String fullPath, String oldPrefix) { + // 防御性校验:严格路径匹配 + if (!fullPath.startsWith(oldPrefix)) { + throw new IllegalArgumentException("路径不隶属于旧目录: " + fullPath); + } + String relative = fullPath.substring(oldPrefix.length()); + return relative.startsWith("/") ? relative.substring(1) : relative; // 去除开头的/ + } + + + /** + * 检查路径是否为空 + */ + private boolean isPathEmpty(Path path) throws IOException { + if (Files.isDirectory(path)) { + try (DirectoryStream dirStream = Files.newDirectoryStream(path)) { + return !dirStream.iterator().hasNext(); + } + } + return false; + } + + + /**********************************************************移动文件或者文件夹结束***********************************************************************************/ + + + /**********************************************************复制文件或者文件夹开始***********************************************************************************/ + + + @Override + @Transactional(rollbackFor = Exception.class) + public String copyFileFolder(MoveCopyFileFolderRequest reques) throws IOException { + // 1. 获取存储根路径 + StorageSourceConfig config = storageSourceConfigMapper.selectOne( + new QueryWrapper().eq("name", "filePath")); + Path rootPath = Paths.get(config.getValue()); + + String newPath = reques.getNewPath(); + String oldpaths = reques.getOldpaths(); + String parentId = reques.getParentId(); + String rename = reques.getRename(); + String type = reques.getType(); + String[] newFileNames = reques.getNewFileName().split(","); + // 数组转集合 + List newFileNameList = Arrays.asList(newFileNames); + + // 2. 构建目标父目录的物理路径 + Path targetParentPhysicalPath = buildPhysicalPath(newPath, rootPath); + + // 3. 循环处理所有选中的文件/文件夹 + for (String fileName : newFileNameList) { + // 构建原文件物理路径 + Path sourcePath = buildPhysicalPath(oldpaths + "/" + fileName, rootPath); + + + if (!Files.exists(sourcePath)) { + throw new IOException("源文件不存在: " + sourcePath); + } + + // ==== 新增关键校验 ==== + // 1. 禁止移动到自身 + if (sourcePath.equals(targetParentPhysicalPath)) { + throw new IllegalArgumentException("不能复制到自己所在目录"); + } + + // 2. 禁止文件夹移动到子目录 + if (Files.isDirectory(sourcePath)) { + if (targetParentPhysicalPath.startsWith(sourcePath)) { + throw new IllegalArgumentException("禁止将文件夹复制到自己内部"); + } + } + + // 生成目标文件名(处理重命名和冲突) + String targetFileName = generateTargetFileName(targetParentPhysicalPath, fileName, rename, type); + Path targetPath = targetParentPhysicalPath.resolve(targetFileName); + + // 3. 再次检查自移动 + if (sourcePath.equals(targetPath)) { + throw new IllegalArgumentException("不能复制到自己当前位置"); + } + // 执行物理复制 + copyPhysicalFile(sourcePath, targetPath, type); + + // 5. 插入新记录到数据库 + insertDatabaseRecord(parentId, oldpaths, fileName, newPath, targetFileName, type); + } + return "复制成功"; + } + + + + + //========================= 核心辅助方法 =========================// + + /** + * 复制物理文件/文件夹(带递归) + */ + private void copyPhysicalFile(Path source, Path target, String type) throws IOException { + if (Files.isDirectory(source)) { + Files.createDirectories(target); + try (DirectoryStream dirStream = Files.newDirectoryStream(source)) { + for (Path child : dirStream) { + String childName = child.getFileName().toString(); + copyPhysicalFile(child, target.resolve(childName), type); + } + } + } else { + if ("0".equals(type)) { // 覆盖模式 + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + } else { // 重命名模式 + if (Files.exists(target)) throw new IOException("目标文件已存在: " + target); + Files.copy(source, target); + } + } + } + + /** + * 插入数据库记录(主记录) + */ + private void insertDatabaseRecord(String parentId, String oldpaths, String fileName, + String newPath, String targetFileName, String type) { + // 获取用户信息 + // 获取当前用户和时间 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now()); + //判断是不是覆盖 如果是覆盖 更新时间 + + + // 查询原记录 + TsFiles original = tsFilesMapper.selectOne(new QueryWrapper() + .eq("work_path", oldpaths) + .eq("file_name", fileName) + ); + //查询新纪录 + TsFiles neworiginal = tsFilesMapper.selectOne(new QueryWrapper() + .eq("work_path", newPath) + .eq("file_name", targetFileName) + ); + + //如果是覆盖 并且通过新路径和名称可以查询到 那就更新时间 + if ("0".equals(type) && neworiginal != null) { + + neworiginal.setUpdateTime(currentTime); + tsFilesMapper.updateById(neworiginal); + if ("FOLDER".equals(original.getIsFile())) { + copyChildrenRecords( + formatDbPath(oldpaths), + formatDbPath(newPath), + original.getId(), + neworiginal.getId(), + type + ); + } + + } else { + // 创建新记录 + TsFiles newRecord = new TsFiles(); + newRecord.setNodeId(original.getNodeId()); + newRecord.setTaskId(original.getTaskId()); + newRecord.setIsFile(original.getIsFile()); + newRecord.setParentId(parentId); + newRecord.setFileName(targetFileName); + newRecord.setWorkPath(formatDbPath(newPath)); + newRecord.setFileSize(original.getFileSize()); + newRecord.setKeywords(original.getKeywords()); + newRecord.setDescription(original.getDescription()); + newRecord.setUploadTime(currentTime); + newRecord.setUploader(loginuser.getUsername()); + + tsFilesMapper.insert(newRecord); + // 递归处理子项 + if ("FOLDER".equals(original.getIsFile())) { + copyChildrenRecords( + formatDbPath(oldpaths), + formatDbPath(newPath + newRecord.getFileName() + "/"), + original.getId(), + newRecord.getId(), + type + ); + } + } + + + } + + /** + * 递归复制子项数据库记录(关键优化点) + */ + private void copyChildrenRecords(String oldPrefix, String newPrefix, + String originalParentId, String newParentId, String type) { + List children = tsFilesMapper.selectList( + new QueryWrapper().eq("parent_id", originalParentId) + ); + // 获取当前用户和时间 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now()); + + for (TsFiles child : children) { + // 处理当前子项 + TsFiles processedChild = processChildRecord( + child, newParentId, newPrefix, oldPrefix, type, loginuser, currentTime + ); + + // 递归处理子文件夹 + if ("FOLDER".equals(child.getIsFile())) { + copyChildrenRecords( + oldPrefix, + newPrefix, + child.getId(), + processedChild.getId(), // 使用新记录的ID作为父ID + type + ); + } + } + } + + /** + * 处理单个子项记录 + */ + private TsFiles processChildRecord(TsFiles child, String newParentId, String newPrefix, + String oldPrefix, String type, LoginUser user, Timestamp now) { + if ("0".equals(type)) { // 覆盖模式 + TsFiles existing = tsFilesMapper.selectOne( + new QueryWrapper() + .eq("work_path", buildNewWorkPath(child, oldPrefix, newPrefix)) + .eq("file_name", child.getFileName()) + ); + if (existing != null) { + updateExisting(existing, child, now, user); + return existing; + } else { + return insertNewChild(child, newParentId, buildNewWorkPath(child, oldPrefix, newPrefix), + user, now, null); + } + } else { // 重命名模式 + return insertNewChild(child, newParentId, newPrefix, + user, now, child.getFileName()); + } + } + + /** + * 更新现有记录(覆盖模式) + */ + private void updateExisting(TsFiles existing, TsFiles source, Timestamp time, LoginUser user) { + existing.setFileSize(source.getFileSize()); + existing.setDescription(source.getDescription()); + existing.setUpdateTime(time); + existing.setUploader(user.getUsername()); + tsFilesMapper.updateById(existing); + } + + /** + * 插入新子项记录 + */ + private TsFiles insertNewChild(TsFiles source, String parentId, String newWorkPath, + LoginUser user, Timestamp time, String customName) { + TsFiles newChild = new TsFiles(); + newChild.setNodeId(source.getNodeId()); + newChild.setTaskId(source.getTaskId()); + newChild.setIsFile(source.getIsFile()); + newChild.setParentId(parentId); + newChild.setFileName(customName != null ? customName : source.getFileName()); + newChild.setWorkPath(newWorkPath); + newChild.setFileSize(source.getFileSize()); + newChild.setKeywords(source.getKeywords()); + newChild.setDescription(source.getDescription()); + newChild.setUploadTime(time); + newChild.setUploader(user.getUsername()); + tsFilesMapper.insert(newChild); + return newChild; + } + +//========================= 工具方法 =========================// + + /** + * 构建安全的新路径(精确替换前缀) + */ + private String buildNewWorkPath(TsFiles child, String oldPrefix, String newPrefix) { + // 确保旧前缀是父路径(如 /a/b/ 而不是 /a/b/file.txt) + String childPath = child.getWorkPath(); + + // 关键修复:严格匹配父路径前缀 + if (!childPath.startsWith(oldPrefix)) { + throw new IllegalStateException("路径前缀不匹配: " + childPath); + } + + // 计算子项相对路径(剥离旧前缀) + String relativePath = childPath.substring(oldPrefix.length()); + + // 拼接新路径(避免重复层级) + return newPrefix + relativePath; + } + + /** + * 格式化数据库路径(统一以/结尾) + */ + private String formatDbPath(String path) { + if (path == null || path.isEmpty()) return "/"; + return path.endsWith("/") ? path : path + "/"; + } + + /** + * 获取当前用户(封装重复逻辑) + */ + private LoginUser getCurrentUser() { + return (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + } + + /** + * 关键安全校验 + */ + private void validateCopySafety(Path source, Path targetParent) { + // 禁止复制到自身目录 + if (source.equals(targetParent)) { + throw new IllegalArgumentException("不能复制到自身目录"); + } + + // 禁止文件夹嵌套复制 + if (Files.isDirectory(source) && targetParent.startsWith(source)) { + throw new IllegalArgumentException("禁止将文件夹复制到自身内部"); + } + } + + + /**********************************************************复制文件或者文件夹结束***********************************************************************************/ + + /********************************** + * 用途说明: 查询本地和备份空间结构树 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 + ***********************************/ + @Override + public DualTreeResponse listLocalAndBackup(String taskId, String nodeId) { + // 1. 查询根节点 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_id",nodeId); + queryWrapper.eq("task_id",taskId); + List rootNodes = tsFilesMapper.selectList(queryWrapper); + + // 2. 构建本地树(workPath) + List localTrees = new ArrayList<>(); + for (TsFiles rootNode : rootNodes) { + TreeDTO localTree = buildTree(rootNode, true); + if (localTree != null) { + localTrees.add(localTree); + } + } + + // 3. 构建 Minio 树(backupPath) + List minioTrees = new ArrayList<>(); + for (TsFiles rootNode : rootNodes) { + TreeDTO minioTree = buildTree(rootNode, false); + if (minioTree != null) { + minioTrees.add(minioTree); + } + } + + // 4. 返回双树结构 + return new DualTreeResponse(localTrees, minioTrees); + } + + /** + * 递归构建树形结构 + * + * @param tsFiles 当前节点 + * @param isLocal 是否是本地树(true: workPath, false: backupPath) + * @return 构建好的树节点 + */ + private TreeDTO buildTree(TsFiles tsFiles, boolean isLocal) { + // 1. 将当前节点转换为 DTO + TreeDTO dto = convertToDTO(tsFiles, isLocal); + + // 2. 如果当前节点路径为空,则直接返回(不构建子树) + if (dto.getPath() == null || dto.getPath().trim().isEmpty()) { + return null; + } + + // 3. 查询当前节点的子节点 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id",tsFiles.getId()); + List children = tsFilesMapper.selectList(queryWrapper); + + if (children != null && !children.isEmpty()) { + // 4. 递归构建子树 + for (TsFiles child : children) { + TreeDTO childDto = buildTree(child, isLocal); + if (childDto != null) { + dto.getChildren().add(childDto); + } + } + } + + + // 5. 返回构建好的树节点 + return dto; + } + /** + * 将 FileNode 转换为 TreeDTO + * + * @param node 当前节点 + * @param isLocal 是否是本地树(true: workPath, false: backupPath) + * @return 转换后的 DTO + */ + private TreeDTO convertToDTO(TsFiles node, boolean isLocal) { + TreeDTO dto = new TreeDTO(); + // 复制公共字段 + dto.setId(node.getId()); + dto.setNodeId(node.getNodeId()); + dto.setTaskId(node.getTaskId()); + dto.setIsFile(node.getIsFile()); + dto.setParentId(node.getParentId()); + dto.setFileName(node.getFileName()); + dto.setFileSize(node.getFileSize()); + dto.setKeywords(node.getKeywords()); + dto.setDescription(node.getDescription()); + dto.setUploadTime(node.getUploadTime()); + dto.setUploader(node.getUploader()); + dto.setUpdateTime(node.getUpdateTime()); + // 设置路径字段 + dto.setPath(isLocal ? node.getWorkPath() : node.getBackupPath()); + return dto; + } + +} 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 new file mode 100644 index 0000000..7c3e59a --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java @@ -0,0 +1,564 @@ +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.QueryWrapper; +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.modules.experimentalData.mapper.TsFilesMapper; +import com.yfd.platform.modules.experimentalData.mapper.TsNodesMapper; +import com.yfd.platform.modules.experimentalData.service.ITsNodesService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.modules.specialDocument.domain.Nodes; +import com.yfd.platform.modules.specialDocument.mapper.NodesMapper; +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.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.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.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.*; + +/** + *

+ * 试验任务节点表 服务实现类 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@Service +public class TsNodesServiceImpl extends ServiceImpl implements ITsNodesService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class); + + //试验任务节点表 Mapper + @Resource + private TsNodesMapper tsNodesMapper; + + //试验任务文档表 Mapper + @Resource + private TsFilesMapper tsFilesMapper; + + //数据源Mapper + @Resource + private StorageSourceMapper storageSourceMapper; + + @Resource + private StorageSourceContext storageSourceContext; + + @Override + public List> getTsNodesTree(String nodeName, String taskId) { + // 查询所有节点数据 + List> allNodes = getAllNodes(taskId); + + // 查找所有根节点(parentId为"00"的节点) + List> rootNodes = findRootNodes(allNodes, taskId); + + // 如果未找到根节点,返回空列表 + if (rootNodes.isEmpty()) { + return new ArrayList<>(); + } + + // 存储最终结果 + List> result = new ArrayList<>(); + + // 如果 nodeName 为空,返回所有根节点的完整树形结构 + if (StringUtils.isEmpty(nodeName)) { + for (Map rootNode : rootNodes) { + result.addAll(buildFullTree(rootNode, allNodes)); + } + return result; + } + + // 否则,返回从根节点到目标节点的树形结构 + for (Map rootNode : rootNodes) { + List> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName); + if (!tree.isEmpty()) { + result.addAll(tree); + } + } + + // 返回结果 + return result; + } + + /** + * 查询所有节点数据 + * + * @param taskId 任务ID(用于精确查询) + * @return 返回所有节点列表 + */ + private List> getAllNodes(String taskId) { + // 创建查询条件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.select( + "node_id as nodeId", // 节点ID + "node_name as nodeName", // 节点名称 + "task_id as taskId", // 所属任务ID + "parent_id as parentId", // 父节点ID + "node_order as nodeOrder", // 节点顺序 + "create_time as createTime", // 创建时间 + "creator" // 创建人 + ); + + // 如果任务ID不为空,添加精确查询条件 + if (StringUtils.isNotEmpty(taskId)) { + queryWrapper.eq("task_id", taskId); + } + // 按节点顺序升序排序 + queryWrapper.orderByAsc("node_order"); + + // 查询所有符合条件的节点 + return tsNodesMapper.selectMaps(queryWrapper); + } + + /** + * 查找所有根节点(parentId为"00"的节点) + * + * @param allNodes 所有节点数据 + * @param taskId 任务ID + * @return 返回所有根节点列表 + */ + private List> findRootNodes(List> allNodes, String taskId) { + List> rootNodes = new ArrayList<>(); + for (Map node : allNodes) { + if ("00".equals(node.get("parentId").toString()) && taskId.equals(node.get("taskId").toString())) { + rootNodes.add(node); + } + } + return rootNodes; + } + + /** + * 构建完整的树形结构 + * + * @param currentNode 当前节点 + * @param allNodes 所有节点数据 + * @return 返回当前节点的子树 + */ + private List> buildFullTree(Map currentNode, List> allNodes) { + // 查找当前节点的所有子节点 + List> children = findChildren(allNodes, currentNode.get("nodeId").toString()); + + // 递归构建子树 + List> tree = new ArrayList<>(); + for (Map child : children) { + List> childTree = buildFullTree(child, allNodes); + if (!childTree.isEmpty()) { + tree.addAll(childTree); + } + } + + // 将当前节点加入树中 + Map nodeWithChildren = new HashMap<>(currentNode); + if (!tree.isEmpty()) { + nodeWithChildren.put("children", tree); + } + + return Collections.singletonList(nodeWithChildren); + } + + /** + * 查找当前节点的所有子节点 + * + * @param allNodes 所有节点数据 + * @param parentId 父节点ID + * @return 返回所有子节点列表 + */ + private List> findChildren(List> allNodes, String parentId) { + List> children = new ArrayList<>(); + for (Map node : allNodes) { + if (parentId.equals(node.get("parentId").toString())) { + children.add(node); + } + } + return children; + } + + /** + * 构建从根节点到目标节点的树形结构 + * + * @param currentNode 当前节点 + * @param allNodes 所有节点数据 + * @param nodeName 目标节点名称 + * @return 返回从根节点到目标节点的树形结构 + */ + private List> buildTreeToTargetNode(Map currentNode, List> allNodes, String nodeName) { + + + List> result = new ArrayList<>(); + + // 查找当前节点的所有子节点 + List> children = findChildren(allNodes, currentNode.get("nodeId").toString()); + + // 递归查找目标节点 + for (Map child : children) { + List> childTree = buildTreeToTargetNode(child, allNodes, nodeName); + if (!childTree.isEmpty()) { + // 如果找到目标节点,将当前节点加入树中,并将其作为子节点 + Map nodeWithChildren = new HashMap<>(currentNode); + nodeWithChildren.put("children", childTree); + result.add(nodeWithChildren); // 将当前节点加入结果列表 + } + } + + // 如果当前节点符合条件且没有被添加到result,则将其添加 + if (currentNode.get("nodeName") instanceof String && ((String) currentNode.get("nodeName")).contains(nodeName) && result.isEmpty()) { + result.add(currentNode); // 将当前节点添加到结果列表 + } + + // 返回包含所有符合条件的树结构的列表 + 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("nodeId").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<>(); + } + + + /********************************** + * 用途说明: 增加试验任务节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class) // 添加事务注解,遇到异常时回滚 + public ResponseResult addTsNodes(TsNodes tsnodes) { + + // 差不多的流程 就是提出来 然后判断 如果两个 中有一个是true + //获取当前登录用户 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + //创建人是当前登录人 + tsnodes.setCreator(loginuser.getUsername()); + + //当前操作时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + tsnodes.setCreateTime(currentTime); + + //通过获取上级节点的条数 设置节点顺序 + QueryWrapper queryWrapperNodeOrder = new QueryWrapper<>(); + int orderno = this.count(queryWrapperNodeOrder.eq("parent_id", tsnodes.getParentId())) + 1; + //判断节点名称是否存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_name", tsnodes.getNodeName());//名称 + queryWrapper.eq("parent_id", tsnodes.getParentId());//父节点 + int count = tsNodesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("节点名称已存在!"); + } + //序号 + tsnodes.setNodeOrder(orderno); + //查询数据源 + List storageSources = storageSourceMapper.findAllOrderByOrderNum(); + + // 获取路径 + List pathNodes = new ArrayList<>(); + TsNodes nodesData = tsNodesMapper.selectById(tsnodes.getParentId()); + // 从当前节点向上遍历,直到根节点 + while (nodesData != null) { + pathNodes.add(nodesData.getNodeName()); + // 如果父节点是 "00",说明已经到了根节点,停止遍历 + if ("00".equals(nodesData.getParentId())) { + break; + } + // 获取父节点 + nodesData = tsNodesMapper.selectById(nodesData.getParentId()); // 修正:从 nodesData 中获取 parentId + } + // 反转路径,使其从根节点到当前节点 + Collections.reverse(pathNodes); + String path = "/" + String.join("/", pathNodes); + + //判断 local或者minio有没有成功 + List results = new ArrayList<>(); + for (StorageSource storageSource : storageSources) { + //新增节点的时候 创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setName(tsnodes.getNodeName());//新建的文件夹名称,示例值(/a/b/c) + newFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + newFolderRequest.setPath(path);//请求路径,示例值(/) + newFolderRequest.setStorageKey(storageSource.getType().toString());//存储源 key,示例值(local minio) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + results.add(flag); + } + // 使用Java 8的Stream API检查列表中是否包含true + boolean hasTrue = results.stream().anyMatch(Boolean::booleanValue); + //如果是true 说明至少有一个生成了文件夹 下一步建立表数据 + if (hasTrue) { + int valueAdded = tsNodesMapper.insert(tsnodes); + if (valueAdded == 1) { + LOGGER.info("local和minio新增成功,表结构增加成功"); + return ResponseResult.success(); + } else { + LOGGER.error("local和minio新增成功,表结构增加失败"); + return ResponseResult.error(); + } + + } else { + LOGGER.error("local和minio新增失败"); + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 修改试验任务节点 + * 参数说明 tsnodes 试验任务节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Override + public ResponseResult updateTsNodes(TsNodes tsnodes) { + //查询没改之前的节点名称 + TsNodes nodesold = tsNodesMapper.selectById(tsnodes.getNodeId()); + //新的节点名称 + String nodeName = tsnodes.getNodeName(); + //老的节点名称 + String nodeNameOld = null; + if (ObjUtil.isNotEmpty(nodesold)) { + nodeNameOld = nodesold.getNodeName(); + } + + //获取当前登录用户 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + //创建人是当前登录人 + tsnodes.setCreator(loginuser.getUsername()); + + //判断节点名称是否存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_name", tsnodes.getNodeName());//名称 + queryWrapper.eq("parent_id", tsnodes.getParentId());//父节点 + int count = tsNodesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("节点名称已存在!"); + } + + //查询数据源 + List storageSources = storageSourceMapper.findAllOrderByOrderNum(); + + List pathNodes = new ArrayList<>(); + TsNodes nodesData = tsNodesMapper.selectById(tsnodes.getParentId()); + // 从当前节点向上遍历,直到根节点 + while (nodesData != null) { + pathNodes.add(nodesData.getNodeName()); + // 如果父节点是 "00",说明已经到了根节点,停止遍历 + if ("00".equals(nodesData.getParentId())) { + break; + } + // 获取父节点 + nodesData = tsNodesMapper.selectById(nodesData.getParentId()); // 修正:从 nodesData 中获取 parentId + } + // 反转路径,使其从根节点到当前节点 + Collections.reverse(pathNodes); + String path = String.join("/", pathNodes); + + //判断 local或者minio有没有成功 + List results = new ArrayList<>(); + for (StorageSource storageSource : storageSources) { + //修改文件夹名称 + RenameFolderRequest renameFolderRequest = new RenameFolderRequest(); + renameFolderRequest.setName(nodeNameOld);//重命名的原文件夹名称,示例值(movie) + renameFolderRequest.setNewName(nodeName);// 重命名后的文件名称,示例值(music) + renameFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + renameFolderRequest.setPath(path);//请求路径,示例值(/) + renameFolderRequest.setStorageKey(storageSource.getType().toString());//存储源 key,示例值(local minio) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(renameFolderRequest.getStorageKey()); + boolean flag = fileService.renameFolder(renameFolderRequest.getPath(), renameFolderRequest.getName(), renameFolderRequest.getNewName()); + results.add(flag); + } + + // 使用Java 8的Stream API检查列表中是否包含true + boolean hasTrue = results.stream().anyMatch(Boolean::booleanValue); + //如果是true 说明至少有一个生成了文件夹 下一步建立表数据 + if (hasTrue) { + int valueAdded = tsNodesMapper.updateById(tsnodes); + if (valueAdded == 1) { + LOGGER.info("local和minio修改成功,表结构增加成功"); + return ResponseResult.success(); + } else { + LOGGER.error("local和minio修改成功,表结构增加失败"); + return ResponseResult.error(); + } + + } else { + LOGGER.error("local和minio修改失败"); + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 根据ID删除试验任务节点 + * 参数说明 id 试验任务节点ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Override + public boolean deleteTsNodesById(String id) { + + +// try { + + //根据ID 查询当前数据 + TsNodes tsNodes = tsNodesMapper.selectById(id); + //删除之前 先拼路径 然后删除本地和minio的文件夹 最后删除表结构 + // 删除当前节点 + int deleteCount = tsNodesMapper.deleteById(id); + //删除当前节点的 文件 + QueryWrapper queryWrapper1 = new QueryWrapper<>(); + queryWrapper1.eq("node_id", tsNodes.getNodeId()); + queryWrapper1.eq("task_id", tsNodes.getTaskId()); + tsFilesMapper.delete(queryWrapper1); + + + List pathNodes = new ArrayList<>(); + TsNodes nodesData = tsNodesMapper.selectById(tsNodes.getParentId()); + // 从当前节点向上遍历,直到根节点 + while (nodesData != null) { + pathNodes.add(nodesData.getNodeName()); + // 如果父节点是 "00",说明已经到了根节点,停止遍历 + if ("00".equals(nodesData.getParentId())) { + break; + } + // 获取父节点 + nodesData = tsNodesMapper.selectById(nodesData.getParentId()); // 修正:从 nodesData 中获取 parentId + } + // 反转路径,使其从根节点到当前节点 + Collections.reverse(pathNodes); + String path = String.join("/", pathNodes); + + 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); + + //查询数据源 + List storageSources = storageSourceMapper.findAllOrderByOrderNum(); + //判断 local或者minio有没有成功 + List results = new ArrayList<>(); + for (StorageSource storageSource : storageSources) { + + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey(storageSource.getKey()); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + List deleteItems = batchDeleteRequest.getDeleteItems(); + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems); + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) { + 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 (totalCount > 1) { + //return ResponseResult.success("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + } else { + //return totalCount == deleteSuccessCount ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + + } + //如果是1 说明成功删除 + if (deleteSuccessCount >= 1) { + results.add(true); + } else { + results.add(false); + } + } + // 使用Java 8的Stream API检查列表中是否包含true + boolean hasTrue = results.stream().anyMatch(Boolean::booleanValue); + if (hasTrue) { + // 递归删除子节点 + deleteChildren(tsNodes.getNodeId(), tsNodes.getTaskId()); + } + return hasTrue; + +// } catch (Exception e) { +// // 如果发生异常,返回 false +// return false; +// } + + } + + /** + * 递归删除子节点 + * + * @param parentId 父节点ID + */ + private void deleteChildren(String parentId, String taskId) { + // 使用 QueryWrapper 查询当前节点的所有子节点 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", parentId); // parent_id = #{parentId} + queryWrapper.eq("task_id", taskId); // parent_id = #{parentId} + List children = tsNodesMapper.selectList(queryWrapper); + + // 递归删除每个子节点 + for (TsNodes child : children) { + deleteChildren(child.getNodeId(), child.getTaskId()); // 递归删除子节点的子节点 + tsNodesMapper.deleteById(child.getNodeId()); // 删除当前子节点 + //批量文件的数据 + QueryWrapper queryWrapper1 = new QueryWrapper<>(); + queryWrapper1.eq("node_id", parentId); + queryWrapper1.eq("task_id", taskId); + tsFilesMapper.delete(queryWrapper1); + } + } +} 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 new file mode 100644 index 0000000..34dada0 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java @@ -0,0 +1,124 @@ +package com.yfd.platform.modules.experimentalData.service.impl; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.modules.experimentalData.domain.TsTask; +import com.yfd.platform.modules.experimentalData.mapper.TsTaskMapper; +import com.yfd.platform.modules.experimentalData.service.ITsTaskService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.utils.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + *

+ * 试验任务表 服务实现类 + *

+ * + * @author LiMengNan + * @since 2025-01-21 + */ +@Service +public class TsTaskServiceImpl extends ServiceImpl implements ITsTaskService { + + //试验任务Mapper + @Resource + private TsTaskMapper tsTaskMapper; + + /********************************** + * 用途说明: 分页查询试验数据管理-试验任务管理 + * 参数说明 + * taskName 任务名称 + * startDate (开始日期) + * endDate (结束日期) + * taskPlace 任务地点 + * taskPerson 任务人员 + * carrierType 载体类型 + * deviceCode 设备 + * 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) { + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + //如果任务名称 taskName 不为空 + if (StringUtils.isNotEmpty(taskName)) { + queryWrapper.like(TsTask::getTaskName, taskName); + } + + //如果任务地点 taskPlace 不为空 + if (StringUtils.isNotEmpty(taskPlace)) { + queryWrapper.like(TsTask::getTaskPlace, taskPlace); + } + + //如果任务人员 taskPerson 不为空 + if (StringUtils.isNotEmpty(taskPerson)) { + queryWrapper.like(TsTask::getTaskPerson, taskPerson); + } + + //如果操作类型 carrierType 不为空 + if (StringUtils.isNotEmpty(carrierType)) { + queryWrapper.like(TsTask::getCarrierType, carrierType); + } + + //如果设备 deviceCode 不为空 + if (StringUtils.isNotEmpty(deviceCode)) { + queryWrapper.like(TsTask::getDeviceCode, deviceCode); + } + + //开始时间startDate + DateTime parseStartDate = DateUtil.parse(startDate); + //时间endDate不为空 + DateTime parseEndDate = DateUtil.parse(endDate); + //开始时间和结束时间不为空 查询条件>=开始时间 <结束时间 + if (parseStartDate != null && parseEndDate != null) { + queryWrapper.ge(TsTask::getTaskDate, parseStartDate).lt(TsTask::getTaskDate, parseEndDate); + } + //分页查询 + Page tsTaskPage = tsTaskMapper.selectPage(page, queryWrapper); + return tsTaskPage; + } + + /*********************************** + * 用途说明:新增试验数据管理-试验任务管理 + * 参数说明 + * TsTask 试验任务管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Override + public Boolean addSdproject(TsTask tsTask) { + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + tsTask.setCreateTime(currentTime); + int valueAdded = tsTaskMapper.insert(tsTask); + if (valueAdded == 1) { + return true; + } else { + return false; + } + } + + /********************************** + * 用途说明: 修改试验数据管理-试验任务管理 + * 参数说明 + * TsTask 试验任务管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Override + public boolean updatetsTask(TsTask tsTask) { + int valueUpdate = tsTaskMapper.updateById(tsTask); + if (valueUpdate == 1) { + return true; + } else { + return false; + } + } +} diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java new file mode 100644 index 0000000..0f8c1cf --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java @@ -0,0 +1,147 @@ +package com.yfd.platform.modules.specialDocument.controller; + + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.modules.specialDocument.domain.Files; +import com.yfd.platform.modules.specialDocument.service.IFilesService; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 专项文档表 前端控制器 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@RestController +@RequestMapping("/specialDocument/sd_files") +public class FilesController { + + //专项文档服务类 + @Resource + private IFilesService filesService; + + + /********************************** + * 用途说明: 分页查询专项文档管理-文档内容 + * 参数说明 + * fileName 文件名称 + * startDate (开始日期) + * endDate (结束日期) + * keywords 关键字 + *nodeId 节点ID + *projectId 所属项目ID + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @GetMapping("/page") + @ApiOperation("分页查询专项文档管理文档内容") + @PreAuthorize("@el.check('select:files')") + public ResponseResult getFilesPage(String fileName, String startDate, String endDate, String keywords, String nodeId,String projectId, Page page) throws Exception { + //分页查询 + Page filesPage = filesService.getFilesPage(fileName, startDate, endDate, keywords, nodeId, projectId, fileName, page); + return ResponseResult.successData(filesPage); + } + + + /*********************************** + * 用途说明:新增专项文档管理-文档内容 + * 参数说明 + * Files 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "新增专项文档管理文档内容!",type = "1") + @PostMapping("/addFiles") + @ApiOperation("新增专项文档管理文档内容") + @ResponseBody + @PreAuthorize("@el.check('add:files')") + public ResponseResult addFiles(@RequestBody Files files) { + //对象不能为空 + if (ObjUtil.isEmpty(files)) { + return ResponseResult.error("参数为空"); + } + Boolean isOk = filesService.addFiles(files); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 修改专项文档管理-文档内容 + * 参数说明 + * Files 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "修改专项文档管理文档内容",type = "1") + @PostMapping("/updateFiles") + @ApiOperation("修改专项文档管理文档内容") + @PreAuthorize("@el.check('update:files')") + public ResponseResult updateFiles(@RequestBody Files files) { + //对象不能为空 + if (ObjUtil.isEmpty(files) && StrUtil.isBlank(files.getId())) { + return ResponseResult.error("参数为空"); + } + boolean isOk = filesService.updateFiles(files); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + + /********************************** + * 用途说明: 根据ID删除专项文档管理-文档内容 + * 参数说明 id 文档内容ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "根据ID删除专项文档管理文档内容",type = "1") + @PostMapping("/deleteFilesById") + @ApiOperation("根据ID删除专项文档管理文档内容") + @PreAuthorize("@el.check('del:systemdevice')") + public ResponseResult deleteFilesById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + List dataset = Arrays.asList(id); + return ResponseResult.success(filesService.deleteFilesByIds(dataset)); + } + + + /********************************** + * 用途说明: 批量删除专项文档管理-文档内容 + * 参数说明 ids 文档内容id数组 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 + ***********************************/ + @Log(module = "专项文档管理", value = "批量删除专项文档管理文档内容",type = "1") + @PostMapping("/deleteFilesByIds") + @ApiOperation("批量删除专项文档管理文档内容") + @PreAuthorize("@el.check('del:systemdevice')") + public ResponseResult deleteFilesByIds(@RequestParam String ids) { + if (StrUtil.isBlank(ids)) { + return ResponseResult.error("参数为空"); + } + String[] splitIds = ids.split(","); + // 数组转集合 + List dataset = Arrays.asList(splitIds); + return ResponseResult.success(filesService.deleteFilesByIds(dataset)); + } + + + +} 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 new file mode 100644 index 0000000..04d31b9 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/NodesController.java @@ -0,0 +1,131 @@ +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.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.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + *

+ * 专项文档节点表 前端控制器 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@RestController +@RequestMapping("/specialDocument/sd_nodes") +public class NodesController { + + //专项文档节点服务类 + @Resource + private INodesService nodesService; + + /*********************************** + * 用途说明:获取专项文档节点 树形结构 + * 参数说明 + * nodeName 节点名称 + * projectId 所属项目ID + * 返回值说明: 专项文档节点树形结构 + ***********************************/ + @PostMapping("/getNodesTree") + @ApiOperation("获取专项文档节点树形结构") + @ResponseBody + @PreAuthorize("@el.check('select:nodes')") + public ResponseResult getNodesTree(String nodeName,String projectId) { + List> list = nodesService.getNodesTree(nodeName,projectId); + return ResponseResult.successData(list); + } + + /********************************** + * 用途说明: 增加专项文档节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "增加专项文档节点",type = "1") + @PostMapping("/addNodes") + @ApiOperation("增加专项文档节点") + @PreAuthorize("@el.check('add:nodes')") + public ResponseResult addNodes(@RequestBody Nodes nodes) { + //参数校验 对象 节点名称 所属项目ID + if (ObjUtil.isEmpty(nodes) && StrUtil.isBlank(nodes.getNodeName()) && StrUtil.isBlank(nodes.getProjectId()) ) { + return ResponseResult.error("参数为空"); + } + return nodesService.addNodes(nodes); + } + + /********************************** + * 用途说明: 修改专项文档节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "修改专项文档节点",type = "1") + @PostMapping("/updateNodes") + @ApiOperation("修改专项文档节点") + @PreAuthorize("@el.check('update:nodes')") + public ResponseResult updateNodes(@RequestBody Nodes nodes) { + //参数校验 对象 节点名称 所属项目ID id + if (ObjUtil.isEmpty(nodes) && StrUtil.isBlank(nodes.getNodeName()) && StrUtil.isBlank(nodes.getProjectId()) && StrUtil.isBlank(nodes.getId())) { + return ResponseResult.error("参数为空"); + } + return nodesService.updateNodes(nodes); + } + + /********************************** + * 用途说明: 根据ID删除专项文档节点 + * 参数说明 id 专项文档节点ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "根据ID删除专项文档节点",type = "1") + @PostMapping("/deleteNodesById") + @ApiOperation("根据ID删除专项文档节点") + @PreAuthorize("@el.check('del:nodes')") + public ResponseResult deleteNodesById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + boolean isOk = nodesService.deleteNodesById(id); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + +// /********************************** +// * 用途说明: 批量删除专项文档节点 +// * 参数说明 ids 专项文档节点id数组 +// * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 +// ***********************************/ +// @Log(module = "专项文档管理", value = "批量删除专项文档节点",type = "1") +// @PostMapping("/deleteNodesByIds") +// @ApiOperation("批量删除专项文档节点") +// @PreAuthorize("@el.check('del:nodes')") +// public ResponseResult deleteSdprojectByIds(@RequestParam String ids) { +// if (StrUtil.isBlank(ids)) { +// return ResponseResult.error("参数为空"); +// } +// String[] splitIds = ids.split(","); +// // 数组转集合 +// List dataset = Arrays.asList(splitIds); +// boolean isOk = nodesService.removeByIds(dataset); +// if (isOk) { +// return ResponseResult.success(); +// } else { +// return ResponseResult.error(); +// } +// } + +} 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 new file mode 100644 index 0000000..207e9bd --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/ProjectController.java @@ -0,0 +1,165 @@ +package com.yfd.platform.modules.specialDocument.controller; + + +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.annotation.Log; +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.modules.specialDocument.domain.Project; +import com.yfd.platform.modules.specialDocument.service.IProjectService; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + *

+ * 专项项目表 前端控制器 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@RestController +@RequestMapping("/specialDocument/project") +public class ProjectController { + + //专项项目服务类 + @Resource + private IProjectService projectService; + + /********************************** + * 用途说明: 分页查询专项文档管理-项目管理 + * 参数说明 + * projectCode 项目编号 + * projectType 项目类型 + *projectName 项目名称 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @GetMapping("/page") + @ApiOperation("分页查询专项文档管理项目管理") + @PreAuthorize("@el.check('select:project')") + public ResponseResult getSdProjectPage(String projectCode, String projectType, String projectName, Page page) { + //分页查询 + Page sdProjectPage = projectService.getSdProjectPage(projectCode, projectType, projectName, page); + return ResponseResult.successData(sdProjectPage); + } + + /*********************************** + * 用途说明:新增专项文档管理-项目管理 + * 参数说明 + * Project 项目管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "新增专项文档管理项目管理!",type = "1") + @PostMapping("/addSdproject") + @ApiOperation("新增专项文档管理项目管理") + @ResponseBody + @PreAuthorize("@el.check('add:project')") + public ResponseResult addSdproject(@RequestBody Project project) { + //对象不能为空 + if (ObjUtil.isEmpty(project)) { + return ResponseResult.error("参数为空"); + } + Boolean isOk = projectService.addSdproject(project); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + + /********************************** + * 用途说明: 修改专项文档管理-项目管理 + * 参数说明 + * Project 项目管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "修改专项文档管理项目管理",type = "1") + @PostMapping("/updateSdproject") + @ApiOperation("修改专项文档管理项目管理") + @PreAuthorize("@el.check('update:project')") + public ResponseResult updateSdproject(@RequestBody Project project) { + //对象不能为空 + if (ObjUtil.isEmpty(project) && StrUtil.isBlank(project.getId())) { + return ResponseResult.error("参数为空"); + } + boolean isOk = projectService.updateSdproject(project); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + + /********************************** + * 用途说明: 根据ID删除专项文档管理-项目管理 + * 参数说明 id 项目管理ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Log(module = "专项文档管理", value = "根据ID删除专项文档管理项目管理",type = "1") + @PostMapping("/deleteSdprojectById") + @ApiOperation("根据ID删除专项文档管理项目管理") + @PreAuthorize("@el.check('del:project')") + public ResponseResult deleteSdprojectById(@RequestParam String id) { + if (StrUtil.isBlank(id)) { + return ResponseResult.error("参数为空"); + } + boolean isOk = projectService.removeById(id); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + + /********************************** + * 用途说明: 批量删除专项文档管理-项目管理 + * 参数说明 ids 项目管理id数组 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 + ***********************************/ + @Log(module = "专项文档管理", value = "批量删除专项文档管理项目管理",type = "1") + @PostMapping("/deleteSdprojectByIds") + @ApiOperation("批量删除专项文档管理项目管理") + @PreAuthorize("@el.check('del:project')") + public ResponseResult deleteSdprojectByIds(@RequestParam String ids) { + if (StrUtil.isBlank(ids)) { + return ResponseResult.error("参数为空"); + } + String[] splitIds = ids.split(","); + // 数组转集合 + List dataset = Arrays.asList(splitIds); + boolean isOk = projectService.removeByIds(dataset); + if (isOk) { + return ResponseResult.success(); + } else { + return ResponseResult.error(); + } + } + + /*********************************** + * 用途说明: 查询所有专项文档管理-项目管理 + * 参数说明 + * 返回值说明: 专项文档管理项目管理数据 + ***********************************/ + @PostMapping("/list") + @ApiOperation("查询所有专项文档管理项目管理") + @ResponseBody + //@PreAuthorize("@el.check('select:devicesignal')") + public ResponseResult listSdproject() { + List projects = projectService.list(); + return ResponseResult.successData(projects); + } + + +} diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Files.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Files.java new file mode 100644 index 0000000..492c6ec --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Files.java @@ -0,0 +1,114 @@ +package com.yfd.platform.modules.specialDocument.domain; + +import java.math.BigDecimal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import java.nio.file.Path; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.io.Serializable; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 专项文档表 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sd_files") +public class Files implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 文档ID + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 所属项目ID + */ + private String projectId; + + /** + * 节点ID + */ + private String nodeId; + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件对象存储路径 + */ + private String filePath; + + /** + * 关键字 + */ + private String keywords; + + /** + * 文件描述 + */ + private String description; + + /** + * M + */ + private String fileSize; + + /** + * 上传时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp uploadTime; + + /** + * 上传人 + */ + private String uploader; + + /** + * 备份1 + */ + private String custom1; + + /** + * 备份2 + */ + private String custom2; + + /** + * 备份3 + */ + private String custom3; + + /** + * 访问路径URL:TODO 增加用于前端展示 + */ + @TableField(exist = false) + private String url; + + /** + * 类型 FILE FOLDER:TODO 增加用于前端展示 + */ + @TableField(exist = false) + private String type; + +} 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 new file mode 100644 index 0000000..291f100 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Nodes.java @@ -0,0 +1,95 @@ +package com.yfd.platform.modules.specialDocument.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; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 专项文档节点表 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sd_nodes") +public class Nodes implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 节点ID + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 所属项目ID + */ + private String projectId; + + /** + * 项目父节点ID 00 + */ + private String parentId; + + /** + * 节点顺序 + */ + private Integer nodeOrder; + + /** + * 节点类型:00-项目 01-子项 02-课题 03-年度 04-主题 + */ + private String nodeType; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp createTime; + + /** + * 创建人 + */ + private String creator; + + /** + * 备用字段1 + */ + private String custom1; + + /** + * 备用字段2 + */ + private String custom2; + + /** + * 备用字段3 + */ + private String custom3; + + /** + * TODO路径用于拼接 + */ + @TableField(exist = false) + private String overallPath; + + +} 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 new file mode 100644 index 0000000..67ff9dd --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/domain/Project.java @@ -0,0 +1,95 @@ +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 java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 专项项目表 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("sd_project") +public class Project implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 项目ID + */ + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 项目编号 + */ + private String projectCode; + + /** + * 项目类型:自定义 + */ + private String projectType; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 项目描述 + */ + private String description; + + /** + * 可扩展的Json对象 + */ + private String projectProps; + + /** + * 项目启动时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp projectTime; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Timestamp createTime; + + /** + * 创建人 + */ + private String creator; + + /** + * 备用字段1 + */ + private String custom1; + + /** + * 备用字段2 + */ + private String custom2; + + /** + * 备用字段3 + */ + private String custom3; + + +} diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/FilesMapper.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/FilesMapper.java new file mode 100644 index 0000000..8526c2c --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/FilesMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.modules.specialDocument.mapper; + +import com.yfd.platform.modules.specialDocument.domain.Files; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 专项文档表 Mapper 接口 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +public interface FilesMapper extends BaseMapper { + +} diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/NodesMapper.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/NodesMapper.java new file mode 100644 index 0000000..62c54c8 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/NodesMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.modules.specialDocument.mapper; + +import com.yfd.platform.modules.specialDocument.domain.Nodes; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 专项文档节点表 Mapper 接口 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +public interface NodesMapper extends BaseMapper { + +} diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/ProjectMapper.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/ProjectMapper.java new file mode 100644 index 0000000..3769978 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/mapper/ProjectMapper.java @@ -0,0 +1,16 @@ +package com.yfd.platform.modules.specialDocument.mapper; + +import com.yfd.platform.modules.specialDocument.domain.Project; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 专项项目表 Mapper 接口 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +public interface ProjectMapper extends BaseMapper { + +} diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java new file mode 100644 index 0000000..6c9b85f --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java @@ -0,0 +1,58 @@ +package com.yfd.platform.modules.specialDocument.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.modules.specialDocument.domain.Files; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + *

+ * 专项文档表 服务类 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +public interface IFilesService extends IService { + + + /********************************** + * 用途说明: 分页查询专项文档管理-文档内容 + * 参数说明 + * fileName 文件名称 + * startDate (开始日期) + * endDate (结束日期) + * keywords 关键字 + *nodeId 节点ID + *projectId 所属项目ID + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + Page getFilesPage(String fileName, String startDate, String endDate, String keywords, String nodeId, String projectId, String fileName1, Page page) throws Exception; + + /*********************************** + * 用途说明:新增专项文档管理-文档内容 + * 参数说明 + * Files 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + Boolean addFiles(Files files); + + /********************************** + * 用途说明: 修改专项文档管理-文档内容 + * 参数说明 + * Files 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + boolean updateFiles(Files files); + + /********************************** + * 用途说明: 根据ID删除专项文档管理-文档内容 + * 参数说明 id 文档内容ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ********************************** + * @return*/ + String deleteFilesByIds(List dataset); +} 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 new file mode 100644 index 0000000..7146942 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/INodesService.java @@ -0,0 +1,50 @@ +package com.yfd.platform.modules.specialDocument.service; + +import com.yfd.platform.config.ResponseResult; +import com.yfd.platform.modules.specialDocument.domain.Nodes; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; +import java.util.Map; + +/** + *

+ * 专项文档节点表 服务类 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +public interface INodesService extends IService { + + + /*********************************** + * 用途说明:获取专项文档节点 树形结构 + * 参数说明 + * nodeName 节点名称 + * projectId 所属项目ID + * 返回值说明: 专项文档节点树形结构 + ***********************************/ + List> getNodesTree(String nodeName,String projectId); + + /********************************** + * 用途说明: 增加专项文档节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + ResponseResult addNodes(Nodes nodes); + + /********************************** + * 用途说明: 修改专项文档节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + ResponseResult updateNodes(Nodes nodes); + + /********************************** + * 用途说明: 根据ID删除专项文档节点 + * 参数说明 id 专项文档节点ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + boolean deleteNodesById(String id); +} 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 new file mode 100644 index 0000000..e267dff --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IProjectService.java @@ -0,0 +1,43 @@ +package com.yfd.platform.modules.specialDocument.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.modules.specialDocument.domain.Project; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 专项项目表 服务类 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +public interface IProjectService extends IService { + + /********************************** + * 用途说明: 分页查询专项文档管理-项目管理 + * 参数说明 + * projectCode 项目编号 + * projectType 项目类型 + *projectName 项目名称 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + Page getSdProjectPage(String projectCode, String projectType, String projectName, Page page); + + /*********************************** + * 用途说明:新增专项文档管理-项目管理 + * 参数说明 + * Project 项目管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + Boolean addSdproject(Project project); + + /********************************** + * 用途说明: 修改专项文档管理-项目管理 + * 参数说明 + * Project 项目管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + boolean updateSdproject(Project project); +} 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 new file mode 100644 index 0000000..58009a3 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java @@ -0,0 +1,383 @@ +package com.yfd.platform.modules.specialDocument.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.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.mapper.FilesMapper; +import com.yfd.platform.modules.specialDocument.service.IFilesService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +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.controller.file.FileController; +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.RenameFileRequest; +import com.yfd.platform.modules.storage.model.result.FileInfoResult; +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.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + *

+ * 专项文档表 服务实现类 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@Service +public class FilesServiceImpl extends ServiceImpl implements IFilesService { + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class); + + //专项文档表 Mapper + @Resource + private FilesMapper filesMapper; + + @Autowired + private FileController fileController; + + @Resource + private StorageSourceContext storageSourceContext; + + @Resource + private StorageSourceService storageSourceService; + + @Resource + private FileChain fileChain; + + + /********************************** + * 用途说明: 分页查询专项文档管理-文档内容 + * 参数说明 + * fileName 文件名称 + * startDate (开始日期) + * endDate (结束日期) + * keywords 关键字 + *nodeId 节点ID + *projectId 所属项目ID + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @Override + public Page getFilesPage(String fileName, String startDate, String endDate, String keywords, String nodeId, String projectId, String fileName1, Page page) throws Exception { + //先查询路径下的所有文件 + //首先通过项目ID 和节点ID去查询表 获取一个路径 如果不是空 就调用minio的获取文件列表接口 查询的数据放在集合中 + FileInfoResult fileInfoResult = null; + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("project_id", projectId); + queryWrapper.eq("node_id", nodeId); + List filess = filesMapper.selectList(queryWrapper); + String filePath = null; + if (filess != null && !filess.isEmpty()) { + for (Files files : filess) { + filePath = files.getFilePath(); + if (filePath != null && !filePath.isEmpty()) { + break; // 如果找到非空的 filePath,终止循环 + } + } + } + if (filePath != null) { + FileListRequest fileListRequest = new FileListRequest(); + fileListRequest.setOrderBy("time"); + fileListRequest.setOrderDirection("desc"); + fileListRequest.setPassword(""); + fileListRequest.setPath(filePath); + fileListRequest.setStorageKey("minio"); + + 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); + fileInfoResult = new FileInfoResult(fileContext.getFileItemList(), fileContext.getPasswordPattern()); + } + + //分页查询专项文档管理-文档内容 + LambdaQueryWrapper queryWrapperfiles = new LambdaQueryWrapper<>(); + //如果文件名称 不为空 + if (StringUtils.isNotEmpty(fileName)) { + queryWrapperfiles.like(Files::getFileName, fileName); + } + //如果关键字 不为空 + if (StringUtils.isNotEmpty(keywords)) { + queryWrapperfiles.like(Files::getKeywords, keywords); + } + //开始时间startDate + DateTime parseStartDate = DateUtil.parse(startDate); + //时间endDate不为空 + DateTime parseEndDate = DateUtil.parse(endDate); + //开始时间和结束时间不为空 查询条件>=开始时间 <结束时间 + if (parseStartDate != null && parseEndDate != null) { + queryWrapperfiles.ge(Files::getUploadTime, parseStartDate).lt(Files::getUploadTime, parseEndDate); + } + queryWrapperfiles.eq(Files::getProjectId, projectId);//所属项目ID + queryWrapperfiles.eq(Files::getNodeId, nodeId);//节点ID + queryWrapperfiles.orderByDesc(Files::getUploadTime);//时间 + //分页查询 + Page filesPage = filesMapper.selectPage(page, queryWrapperfiles); + //处理文件内容 + List records = filesPage.getRecords(); + for (Files files : records) { + //循环从minio拿出来的数据 然后把url放到files里面 + files.setUrl(""); + files.setType(""); + List filelist = fileInfoResult.getFiles(); + if (filelist != null && !filelist.isEmpty()) { + for (FileItemResult fileItemResult : filelist) { + if (fileItemResult.getUrl() == null || fileItemResult.getUrl().isEmpty()) { + continue; + } + if (files.getFileName().equals(fileItemResult.getName())) { + files.setUrl(fileItemResult.getUrl()); + files.setType(fileItemResult.getType().getValue()); + break; + } + } + + } + } + filesPage.setRecords(records); + return filesPage; + } + + + /*********************************** + * 用途说明:新增专项文档管理-文档内容 + * 参数说明 + * Files 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Override + public Boolean addFiles(Files files) { + Boolean value = false; + // String[] splitIds = ids.split(","); BigDecimal + List names = Arrays.asList(files.getFileName().split(",")); + List sizes = Arrays.asList(files.getFileSize().split(",")); + + // 差不多的流程 就是提出来 然后判断 如果两个 中有一个是true + //获取当前登录用户 上传人是当前登录人 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + + + // 数据校验 + if (names.size() != sizes.size()) { + LOGGER.error("文件名称和文件大小的列表长度不一致"); + return false; + } + List filesToSave = new ArrayList<>(); + // 设置当前时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + files.setUploadTime(currentTime); + + for (int i = 0; i < names.size(); i++) { + String name = names.get(i).trim(); + String sizeStr = sizes.get(i).trim(); + // 校验文件大小是否可以转换成数值 + try { + Files files1 = new Files(); + files1.setProjectId(files.getProjectId()); + files1.setNodeId(files.getNodeId()); + files1.setFilePath(files.getFilePath()); + files1.setKeywords(files.getKeywords()); + files1.setDescription(files.getDescription()); + files1.setUploadTime(files.getUploadTime()); + files1.setUploader(loginuser.getUsername()); + files1.setFileName(name); + files1.setFileSize(sizeStr); + filesToSave.add(files1); + } catch (NumberFormatException e) { + LOGGER.error("文件大小必须是有效的数字"); + } + } + if(filesToSave.size()>0){ + //循环新增 + for(Files filess : filesToSave){ + int valueAdded = filesMapper.insert(filess); + if (valueAdded == 1) { + value = true; + } else { + value = false; + } + } + } + return value; + } + + /********************************** + * 用途说明: 修改专项文档管理-文档内容 + * 参数说明 + * Files 文档内容 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class)// 添加事务注解,遇到异常时回滚 + public boolean updateFiles(Files files) { + // 修改之前查询表中的文件名是否修改,如果发生变动先修改 minio 然后再修改表结构 + Files filesData = filesMapper.selectById(files.getId()); + + // 判断文件名是否修改 + if (!files.getFileName().equals(filesData.getFileName())) { + // 修改数据库 + int valueUpdate = filesMapper.updateById(files); + if (valueUpdate != 1) { + LOGGER.error("表结构修改失败"); + throw new RuntimeException("更新数据库失败"); + } + + // 修改 MinIO 文件名 + boolean minioUpdateSuccess = updateMinioFileName(filesData, files); + if (!minioUpdateSuccess) { + // 如果 MinIO 修改失败,抛出异常,触发事务回滚 + LOGGER.error("表结构修改成功,MinIO 修改失败"); + throw new RuntimeException("在MinIO中重命名文件失败."); + } + LOGGER.info("MinIO 和表结构都修改成功"); + return true; + + } else { + // 如果文件名没有修改,仅更新数据库 + int valueUpdate = filesMapper.updateById(files); + return valueUpdate == 1; + } + } + + + /********************************** + * 用途说明: 根据ID删除专项文档管理-文档内容 + * 参数说明 id 文档内容ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ********************************** + * @return*/ + @Override + @Transactional(rollbackFor = Exception.class)// 添加事务注解,遇到异常时回滚 + public String deleteFilesByIds(List dataset) { + + List filesList = filesMapper.selectBatchIds(dataset); + + int SuccessCount = 0, FailCount = 0, total = CollUtil.size(dataset); + //Todo 最直接的办法 循环出来 一条一条删除 + for (Files files : filesList) { + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(files.getFileName()); + deleteItemData.setPassword(""); + deleteItemData.setPath(files.getFilePath()); + deleteItemData.setType(FileTypeEnum.FILE); + deleteItemList.add(deleteItemData); + + //首先通过ID集合查询所有的内容 然后放到List deleteItems里面 放好以后删除数据库 然后删除minio + + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("minio"); + + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + List deleteItems = batchDeleteRequest.getDeleteItems(); + + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems); + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) { + + 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 (totalCount > 1) { + //return ResponseResult.success("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + } else { + //return totalCount == deleteSuccessCount ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + + } + //如果是1 说明成功删除 + if (deleteSuccessCount == 1) { + int valueDelete = filesMapper.deleteById(files.getId()); + if (valueDelete > 0) { + SuccessCount++; + LOGGER.info("表结构成功删除"); + } else { + System.out.println("没有记录被删除"); + } + } else { + FailCount++; + } + + } + return "批量删除 " + total + " 个, 删除成功 " + SuccessCount + " 个, 失败 " + FailCount + " 个."; + } + + + // 修改 MinIO 文件名的方法 + private boolean updateMinioFileName(Files filesData, Files files) { + try { + RenameFileRequest renameFileRequest = new RenameFileRequest(); + renameFileRequest.setName(filesData.getFileName()); + renameFileRequest.setNewName(files.getFileName()); + renameFileRequest.setPassword(""); + renameFileRequest.setPath(filesData.getFilePath()); + renameFileRequest.setStorageKey("minio"); + + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(renameFileRequest.getStorageKey()); + return fileService.renameFile(renameFileRequest.getPath(), renameFileRequest.getName(), renameFileRequest.getNewName()); + } catch (Exception e) { + LOGGER.error("MinIO 修改文件名时发生异常", 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 new file mode 100644 index 0000000..a4a2085 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/NodesServiceImpl.java @@ -0,0 +1,532 @@ +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.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +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.modules.specialDocument.domain.Files; +import com.yfd.platform.modules.specialDocument.domain.Nodes; +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.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.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.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.*; + +/** + *

+ * 专项文档节点表 服务实现类 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@Service +public class NodesServiceImpl extends ServiceImpl implements INodesService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class); + + + //专项文档节点表 Mapper + @Resource + private NodesMapper nodesMapper; + + //专项文档表 Mapper + @Resource + private FilesMapper filesMapper; + + @Resource + private FileOperatorController fileOperatorController; + + @Resource + private StorageSourceContext storageSourceContext; + + /*********************************** + * 用途说明:获取专项文档节点 树形结构 + * 参数说明 + * nodeName 节点名称 + * projectId 所属项目ID + * 返回值说明: 专项文档节点树形结构 + ***********************************/ + @Override + public List> getNodesTree(String nodeName, String projectId) { + // 查询所有节点数据 + List> allNodes = getAllNodes(projectId); + // 查找所有根节点(parentId为"00"的节点) + List> rootNodes = findRootNodes(allNodes, projectId); + // 如果未找到根节点,返回空列表 + if (rootNodes.isEmpty()) { + return new ArrayList<>(); + } + + // 存储最终结果 + List> result = new ArrayList<>(); + // 如果 nodeName 为空,返回所有根节点的完整树形结构 + if (StringUtils.isEmpty(nodeName)) { + for (Map rootNode : rootNodes) { + result.addAll(buildFullTree(rootNode, allNodes)); + } + return result; + } + // 否则,返回从根节点到目标节点的树形结构 + for (Map rootNode : rootNodes) { + List> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName); + if (!tree.isEmpty()) { + result.addAll(tree); + } + } + // 返回结果 + return result; + } + + /** + * 构建完整的树形结构 + * + * @param currentNode 当前节点 + * @param allNodes 所有节点数据 + * @return 返回当前节点的子树 + */ + private List> buildFullTree(Map currentNode, List> allNodes) { + // 查找当前节点的所有子节点 + List> children = findChildren(allNodes, currentNode.get("id").toString()); + // 递归构建子树 + List> tree = new ArrayList<>(); + for (Map child : children) { + List> childTree = buildFullTree(child, allNodes); + if (!childTree.isEmpty()) { + tree.addAll(childTree); + } + } + // 将当前节点加入树中 + Map nodeWithChildren = new HashMap<>(currentNode); + if (!tree.isEmpty()) { + nodeWithChildren.put("children", tree); + } + return Collections.singletonList(nodeWithChildren); + } + + /** + * 构建从根节点到目标节点的树形结构 + * + * @param currentNode 当前节点 + * @param allNodes 所有节点数据 + * @param nodeName 目标节点名称 + * @return 返回从根节点到目标节点的树形结构 + */ + private List> buildTreeToTargetNode(Map currentNode, List> allNodes, String nodeName) { + + List> result = new ArrayList<>(); + + // 查找当前节点的所有子节点 + 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); + result.add(nodeWithChildren); // 将当前节点加入结果列表 + } + } + + // 如果当前节点符合条件且没有被添加到result,则将其添加 + if (currentNode.get("nodeName") instanceof String && ((String) currentNode.get("nodeName")).contains(nodeName) && result.isEmpty()) { + result.add(currentNode); // 将当前节点添加到结果列表 + } + + // 返回包含所有符合条件的树结构的列表 + 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<>(); + } + + /** + * 查找当前节点的所有子节点 + * + * @param allNodes 所有节点数据 + * @param parentId 父节点ID + * @return 返回所有子节点列表 + */ + private List> findChildren(List> allNodes, String parentId) { + List> children = new ArrayList<>(); + for (Map node : allNodes) { + if (parentId.equals(node.get("parentId").toString())) { + children.add(node); + } + } + return children; + } + + /** + * 查找所有根节点(parentId为"00"的节点) + * + * @param allNodes 所有节点数据 + * @param projectId 项目ID + * @return 返回所有根节点列表 + */ + private List> findRootNodes(List> allNodes, String projectId) { + List> rootNodes = new ArrayList<>(); + for (Map node : allNodes) { + if ("00".equals(node.get("parentId").toString()) && projectId.equals(node.get("projectId").toString())) { + rootNodes.add(node); + } + } + return rootNodes; + } + + /** + * 查询所有节点数据 + * + * @param projectId 项目ID(用于精确查询) + * @return 返回所有节点列表 + */ + private List> getAllNodes(String projectId) { + // 创建查询条件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.select( + "id as id", // 节点ID + "node_name as nodeName", // 节点名称 + "project_id as projectId", // 项目ID + "parent_id as parentId", // 父节点ID + "node_order as nodeOrder", // 节点顺序 + "node_type as nodeType", // 节点类型 + "create_time as createTime", // 创建时间 + "creator" // 创建人 + ); + // 如果项目ID不为空,添加精确查询条件 + if (StringUtils.isNotEmpty(projectId)) { + queryWrapper.eq("project_id", projectId); + } + // 按节点顺序升序排序 + queryWrapper.orderByAsc("node_order"); + // 查询所有符合条件的节点 + return nodesMapper.selectMaps(queryWrapper); + } + + + /********************************** + * 用途说明: 增加专项文档节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class) // 添加事务注解,遇到异常时回滚 + public ResponseResult addNodes(Nodes nodes) { + + //获取当前登录用户 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + //创建人是当前登录人 + nodes.setCreator(loginuser.getUsername()); + + //当前操作时间 + LocalDateTime now = LocalDateTime.now(); + // 转换为 Timestamp + Timestamp currentTime = Timestamp.valueOf(now); + nodes.setCreateTime(currentTime); + + //通过获取上级节点的条数 设置节点顺序 + QueryWrapper queryWrapperNodeOrder = new QueryWrapper<>(); + int orderno = this.count(queryWrapperNodeOrder.eq("parent_id", nodes.getParentId())) + 1; + //判断节点名称是否存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_name", nodes.getNodeName());//名称 + queryWrapper.eq("parent_id", nodes.getParentId());//父节点 + int count = nodesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("节点名称已存在!"); + } + //序号 + nodes.setNodeOrder(orderno); + int valueAdded = nodesMapper.insert(nodes); + if (valueAdded == 1) { + List pathNodes = new ArrayList<>(); + Nodes nodesData = nodesMapper.selectById(nodes.getParentId()); + // 从当前节点向上遍历,直到根节点 + while (nodesData != null) { + pathNodes.add(nodesData.getNodeName()); + // 如果父节点是 "00",说明已经到了根节点,停止遍历 + if ("00".equals(nodesData.getParentId())) { + break; + } + // 获取父节点 + nodesData = nodesMapper.selectById(nodesData.getParentId()); // 修正:从 nodesData 中获取 parentId + } + // 反转路径,使其从根节点到当前节点 + 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) + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + if (flag) { + return ResponseResult.success(); + } else { + LOGGER.error("节点新增成功,但是minio创建文件失败"); + return ResponseResult.error(); + } + + } else { + LOGGER.error("节点新增失败"); + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 修改专项文档节点 + * 参数说明 nodes 专项文档节点信息 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Override + @Transactional(rollbackFor = Exception.class) // 添加事务注解,遇到异常时回滚 + public ResponseResult updateNodes(Nodes nodes) { + //查询没改之前的节点名称 + Nodes nodesold = nodesMapper.selectById(nodes.getId()); + //新的节点名称 + String nodeName = nodes.getNodeName(); + //老的节点名称 + String nodeNameOld = null; + if (ObjUtil.isNotEmpty(nodesold)) { + nodeNameOld = nodesold.getNodeName(); + } + + //获取当前登录用户 + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + //创建人是当前登录人 + nodes.setCreator(loginuser.getUsername()); + + //判断节点名称是否存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_name", nodes.getNodeName()); + queryWrapper.eq("parent_id", nodes.getParentId());//父节点 + int count = nodesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("节点名称已存在!"); + } + int valueUpdate = nodesMapper.updateById(nodes); + if (valueUpdate == 1) { + + + List pathNodes = new ArrayList<>(); + Nodes nodesData = nodesMapper.selectById(nodes.getParentId()); + // 从当前节点向上遍历,直到根节点 + while (nodesData != null) { + pathNodes.add(nodesData.getNodeName()); + // 如果父节点是 "00",说明已经到了根节点,停止遍历 + if ("00".equals(nodesData.getParentId())) { + break; + } + // 获取父节点 + nodesData = nodesMapper.selectById(nodesData.getParentId()); // 修正:从 nodesData 中获取 parentId + } + // 反转路径,使其从根节点到当前节点 + Collections.reverse(pathNodes); + String path = String.join("/", pathNodes); + + //修改文件名称 + RenameFolderRequest renameFolderRequest = new RenameFolderRequest(); + renameFolderRequest.setName(nodeNameOld);//重命名的原文件夹名称,示例值(movie) + renameFolderRequest.setNewName(nodeName);// 重命名后的文件名称,示例值(music) + renameFolderRequest.setPassword("");//文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码,示例值(123456) + renameFolderRequest.setPath(path);//请求路径,示例值(/) + renameFolderRequest.setStorageKey("minio");//存储源 key,示例值(local minio) + 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修改文件名失败"); + return ResponseResult.error("重命名失败"); + } + } else { + LOGGER.error("节点修改失败"); + return ResponseResult.error(); + } + } + + /********************************** + * 用途说明: 根据ID删除专项文档节点 + * 参数说明 id 专项文档节点ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 + ***********************************/ + @Override + public boolean deleteNodesById(String id) { + Boolean value = false; + + //根据ID 查询当前数据 + Nodes nodes = nodesMapper.selectById(id); + // 删除当前节点 + int deleteCount = nodesMapper.deleteById(id); + //删除当前节点的 文件 + QueryWrapper queryWrapper1 = new QueryWrapper<>(); + queryWrapper1.eq("node_id", nodes.getId()); + queryWrapper1.eq("task_id", nodes.getProjectId()); + filesMapper.delete(queryWrapper1); + + + List pathNodes = new ArrayList<>(); + Nodes nodesData = nodesMapper.selectById(nodes.getParentId()); + // 从当前节点向上遍历,直到根节点 + while (nodesData != null) { + pathNodes.add(nodesData.getNodeName()); + // 如果父节点是 "00",说明已经到了根节点,停止遍历 + if ("00".equals(nodesData.getParentId())) { + break; + } + // 获取父节点 + nodesData = nodesMapper.selectById(nodesData.getParentId()); // 修正:从 nodesData 中获取 parentId + } + // 反转路径,使其从根节点到当前节点 + Collections.reverse(pathNodes); + String path = String.join("/", pathNodes); + + //删除minio准备数据 + List deleteItemList = new ArrayList<>(); + BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); + deleteItemData.setName(nodes.getNodeName()); + deleteItemData.setPassword(""); + deleteItemData.setPath(path); + deleteItemData.setType(FileTypeEnum.FOLDER); + deleteItemList.add(deleteItemData); + + BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest(); + batchDeleteRequest.setDeleteItems(deleteItemList); + batchDeleteRequest.setStorageKey("minio"); + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); + List deleteItems = batchDeleteRequest.getDeleteItems(); + int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems); + for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) { + 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 (totalCount > 1) { + //return ResponseResult.success("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + } else { + //return totalCount == deleteSuccessCount ? ResponseResult.success("删除成功") : ResponseResult.error("删除失败"); + LOGGER.error("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个."); + + } + //如果是1 说明成功删除 + if (deleteSuccessCount >= 1) { + // 递归删除子节点 + deleteChildren(nodes.getId(), nodes.getProjectId()); + + value = true; + } else { + value = false; + } + return value; + } + + /** + * 递归删除子节点 + * + * @param parentId 父节点ID + */ + private void deleteChildren(String parentId, String projectId) { + + // 使用 QueryWrapper 查询当前节点的所有子节点 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", parentId); + queryWrapper.eq("project_id", projectId); + List children = nodesMapper.selectList(queryWrapper); + + // 递归删除每个子节点 + for (Nodes child : children) { + deleteChildren(child.getId(), child.getProjectId()); // 递归删除子节点的子节点 + nodesMapper.deleteById(child.getId()); // 删除当前子节点 + //批量文件的数据 + QueryWrapper queryWrapper1 = new QueryWrapper<>(); + queryWrapper1.eq("id", parentId); + queryWrapper1.eq("project_id", projectId); + filesMapper.delete(queryWrapper1); + } +// +// +// +// // 使用 QueryWrapper 查询当前节点的所有子节点 +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("parent_id", parentId); // parent_id = #{parentId} +// List children = nodesMapper.selectList(queryWrapper); +// +// // 递归删除每个子节点 +// for (Nodes child : children) { +// deleteChildren(child.getId()); // 递归删除子节点的子节点 +// nodesMapper.deleteById(child.getId()); // 删除当前子节点 +// } + } + + +} \ No newline at end of file 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 new file mode 100644 index 0000000..7faa8bd --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java @@ -0,0 +1,114 @@ +package com.yfd.platform.modules.specialDocument.service.impl; + +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.Project; +import com.yfd.platform.modules.specialDocument.mapper.ProjectMapper; +import com.yfd.platform.modules.specialDocument.service.IProjectService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; +import com.yfd.platform.utils.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; + +/** + *

+ * 专项项目表 服务实现类 + *

+ * + * @author LiMengNan + * @since 2025-01-20 + */ +@Service +public class ProjectServiceImpl extends ServiceImpl implements IProjectService { + + //专项项目表Mapper + @Resource + private ProjectMapper projectMapper; + + /********************************** + * 用途说明: 分页查询专项文档管理-项目管理 + * 参数说明 + * projectCode 项目编号 + * projectType 项目类型 + *projectName 项目名称 + * pageNum 当前页 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 + ***********************************/ + @Override + public Page getSdProjectPage(String projectCode, String projectType, String projectName, Page page) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + //如果项目编号 projectCode 不为空 + if (StringUtils.isNotEmpty(projectCode)) { + queryWrapper.like(Project::getProjectCode, projectCode); + } + //如果项目类型 projectType 不为空 + if (StringUtils.isNotEmpty(projectType)) { + queryWrapper.like(Project::getProjectType, projectType); + } + //如果项目名称 projectName 不为空 + if (StringUtils.isNotEmpty(projectName)) { + queryWrapper.like(Project::getProjectName, projectName); + } + //根据创建时间排序 + queryWrapper.orderByAsc(Project::getCreateTime); + //分页查询 + Page sdProjectPage = projectMapper.selectPage(page, queryWrapper); + return sdProjectPage; + } + + /*********************************** + * 用途说明:新增专项文档管理-项目管理 + * 参数说明 + * Project 项目管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 + ***********************************/ + @Override + public Boolean addSdproject(Project project) { + //TODO 01.21沟通以后说是先不用管重复校验问题 +// //通过项目名称 项目编号查询 查看是否存在 +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// if(project.getProjectCode() != null){ +// queryWrapper.eq("project_code",project.getProjectCode()); +// } +// if(project.getProjectName() != null){ +// queryWrapper.eq("project_name",project.getProjectName()); +// } +// List projects = projectMapper.selectList(queryWrapper); +// 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; + } + } + + /********************************** + * 用途说明: 修改专项文档管理-项目管理 + * 参数说明 + * Project 项目管理 + * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 + ***********************************/ + @Override + public boolean updateSdproject(Project project) { + int valueUpdate = projectMapper.updateById(project); + if (valueUpdate == 1) { + return true; + } else { + return false; + } + } +} diff --git a/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java b/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java index d7a633a..b5f223f 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java @@ -19,6 +19,7 @@ import java.util.Date; @ApiModel(value="文件列表信息结果类") public class FileItemResult implements Serializable { + @ApiModelProperty(value = "文件名", example = "a.mp4") private String name; @@ -37,6 +38,10 @@ public class FileItemResult implements Serializable { @ApiModelProperty(value = "下载地址", example = "http://www.example.com/a.mp4") private String url; + //用于对比Md5文件 + private String locatMd5; + private String minioMd5; + /** * 获取路径和名称的组合, 并移除重复的路径分隔符 /. * diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractBaseFileService.java b/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractBaseFileService.java index 123bc1f..15df971 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractBaseFileService.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractBaseFileService.java @@ -2,6 +2,7 @@ package com.yfd.platform.modules.storage.service.base; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; +import com.amazonaws.services.s3.model.S3Object; import com.yfd.platform.exception.init.InitializeStorageSourceException; import com.yfd.platform.modules.storage.model.param.IStorageParam; import com.yfd.platform.utils.CodeMsg; @@ -92,4 +93,5 @@ public abstract class AbstractBaseFileService

implement return false; } + public abstract S3Object getObject(String bucketName, String key); } diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java b/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java index dba6396..d63bfab 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java @@ -13,12 +13,22 @@ import com.yfd.platform.modules.storage.model.enums.FileTypeEnum; import com.yfd.platform.modules.storage.model.param.S3BaseParam; import com.yfd.platform.modules.storage.model.result.FileItemResult; import com.yfd.platform.utils.StringUtils; +import io.netty.channel.ChannelInboundHandlerAdapter; import lombok.extern.slf4j.Slf4j; +import lombok.var; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Resource; +import javax.rmi.CORBA.Util; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; @@ -30,6 +40,9 @@ import java.util.List; @Slf4j public abstract class AbstractS3BaseFileService

extends AbstractBaseFileService

{ + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class); + + protected AmazonS3 s3Client; public static final InputStream EMPTY_INPUT_STREAM = new ByteArrayInputStream(new byte[0]); @@ -42,10 +55,14 @@ public abstract class AbstractS3BaseFileService

extends A return s3FileList(folderPath); } - + @Override + public List fileLists(String folderPath) { + return s3FileLists(folderPath); + } /** * 默认 S3 获取对象下载链接的方法, 如果指定了域名, 则替换为自定义域名. - * @return S3 对象访问地址 + * + * @return S3 对象访问地址 */ @Override public String getDownloadUrl(String pathAndName) { @@ -80,8 +97,9 @@ public abstract class AbstractS3BaseFileService

extends A /** * 获取 S3 指定目录下的对象列表 - * @param path 路径 - * @return 指定目录下的对象列表 + * + * @param path 路径 + * @return 指定目录下的对象列表 */ public List s3FileList(String path) { String bucketName = param.getBucketName(); @@ -139,6 +157,88 @@ public abstract class AbstractS3BaseFileService

extends A return fileItemList; } + public List s3FileLists(String path) { + String bucketName = param.getBucketName(); + path = StringUtils.trimStartSlashes(path); // 去掉路径开头的斜杠 + String fullPath = StringUtils.trimStartSlashes(StringUtils.concat(param.getBasePath(), path, ZFileConstant.PATH_SEPARATOR)); + + List fileItemList = new ArrayList<>(); + + // 调用递归方法获取文件列表 + listFilesInDirectory(bucketName, fullPath, path, fileItemList); + + return fileItemList; + } + + private void listFilesInDirectory(String bucketName, String fullPath, String path, List fileItemList) { + ListObjectsRequest listObjectsRequest = new ListObjectsRequest() + .withBucketName(bucketName) + .withPrefix(fullPath) // 设置前缀为当前路径 + .withMaxKeys(1000) // 每次最多返回 1000 个对象 + .withDelimiter("/"); // 使用 "/" 作为分隔符 + + ObjectListing objectListing = s3Client.listObjects(listObjectsRequest); + boolean isFirstWhile = true; + + do { + if (!isFirstWhile) { + objectListing = s3Client.listNextBatchOfObjects(objectListing); // 处理分页 + } + + // 处理文件 + for (S3ObjectSummary s : objectListing.getObjectSummaries()) { + FileItemResult fileItemResult = new FileItemResult(); + + // 跳过当前目录本身 + if (s.getKey().equals(fullPath)) { + continue; + } + + // 获取文件名并去除前导斜杠 + String fileName = s.getKey().substring(fullPath.length()); + if (fileName.startsWith(ZFileConstant.PATH_SEPARATOR)) { + fileName = fileName.substring(1); // 去掉开头的斜杠 + } + + fileItemResult.setName(fileName); + fileItemResult.setSize(s.getSize()); + fileItemResult.setTime(s.getLastModified()); + fileItemResult.setType(FileTypeEnum.FILE); + fileItemResult.setPath(path); // 当前路径 + + // 构造完整路径并生成下载 URL + String fullPathAndName = StringUtils.concat(path, fileItemResult.getName()); + fileItemResult.setUrl(getDownloadUrl(fullPathAndName)); + + fileItemList.add(fileItemResult); + } + + // 处理文件夹 + for (String commonPrefix : objectListing.getCommonPrefixes()) { + FileItemResult fileItemResult = new FileItemResult(); + + // 获取文件夹名称,去掉前导路径并修正末尾斜杠 + String folderName = commonPrefix.substring(fullPath.length(), commonPrefix.length() - 1); + if (StrUtil.isEmpty(folderName) || StrUtil.equals(folderName, StringUtils.DELIMITER_STR)) { + continue; // 跳过无效的文件夹名称 + } + + fileItemResult.setName(folderName); + fileItemResult.setType(FileTypeEnum.FOLDER); + fileItemResult.setPath(path); // 当前路径 + fileItemList.add(fileItemResult); + + // 递归处理子文件夹 + String subFolderPath = path + folderName + ZFileConstant.PATH_SEPARATOR; // 修正路径拼接 + String subFolderFullPath = commonPrefix; // 子文件夹的完整路径 + listFilesInDirectory(bucketName, subFolderFullPath, subFolderPath, fileItemList); + } + + isFirstWhile = false; + } while (objectListing.isTruncated()); // 处理分页 + } + + @Override public FileItemResult getFileItem(String pathAndName) { String fileName = FileUtil.getName(pathAndName); @@ -175,14 +275,171 @@ public abstract class AbstractS3BaseFileService

extends A return true; } +// @Override +// public boolean deleteFolder(String path, String name) { +// String fullPath = StringUtils.concat(param.getBasePath(), path, name); +// fullPath = StringUtils.trimStartSlashes(fullPath); +// s3Client.deleteObject(param.getBucketName(), fullPath + '/'); +// return true; +// } + + //todo 改造以后的删除文件夹 上面是改造之前原本的 + /** + * 删除文件夹 + * + * @param path 文件夹路径 + * @param name 文件夹名称 + * @return 是否删除成功 + */ @Override public boolean deleteFolder(String path, String name) { + // 构造完整路径 String fullPath = StringUtils.concat(param.getBasePath(), path, name); fullPath = StringUtils.trimStartSlashes(fullPath); - s3Client.deleteObject(param.getBucketName(), fullPath + '/'); - return true; + if (!fullPath.endsWith("/")) { + fullPath += "/"; // 确保路径以 / 结尾 + } + try { + // 删除本地文件夹及其内容 + deleteLocalFolder(fullPath); + // 删除 Minio 文件夹及其内容 + deleteMinioFolder(param.getBucketName(), fullPath); + return true; + } catch (Exception e) { + LOGGER.error("删除文件夹失败: " + e.getMessage(), e); + return false; + } } + + // 递归删除本地文件夹及其内容 + private void deleteLocalFolder(String fullPath) { + File folder = new File(fullPath); + if (folder.exists() && folder.isDirectory()) { + try { + deleteLocalFolderRecursive(folder); + LOGGER.info("本地文件夹删除成功: " + fullPath); + } catch (IOException e) { + LOGGER.error("本地文件夹删除失败: " + e.getMessage()); + } + } else { + LOGGER.error("本地文件夹不存在,跳过删除: " + fullPath); + } + } + + // 递归删除本地文件夹及其内容 + private void deleteLocalFolderRecursive(File folder) throws IOException { + File[] files = folder.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteLocalFolderRecursive(file); // 递归删除子文件夹 + } else { + if (!file.delete()) { + throw new IOException("无法删除文件: " + file.getAbsolutePath()); + } + } + } + } + if (!folder.delete()) { + throw new IOException("无法删除文件夹: " + folder.getAbsolutePath()); + } + } + // 删除 Minio 文件夹及其内容 + private void deleteMinioFolder(String bucketName, String folderPath) { + try { + // 列出文件夹下的所有对象 + ListObjectsV2Request listRequest = new ListObjectsV2Request() + .withBucketName(bucketName) + .withPrefix(folderPath); // 前缀匹配文件夹路径 + + ListObjectsV2Result listResult; + List objectsToDelete = new ArrayList<>(); + + do { + listResult = s3Client.listObjectsV2(listRequest); + + // 收集需要删除的对象 + for (S3ObjectSummary objectSummary : listResult.getObjectSummaries()) { + objectsToDelete.add(new DeleteObjectsRequest.KeyVersion(objectSummary.getKey())); + } + + // 如果对象数量超过单次请求限制,继续分页列出 + listRequest.setContinuationToken(listResult.getNextContinuationToken()); + } while (listResult.isTruncated()); + + // 批量删除对象 + if (!objectsToDelete.isEmpty()) { + DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(bucketName) + .withKeys(objectsToDelete); + s3Client.deleteObjects(deleteRequest); + LOGGER.info("Minio 文件夹删除成功: " + folderPath); + } else { + LOGGER.error("Minio 文件夹不存在,跳过删除: " + folderPath); + } + } catch (Exception e) { + LOGGER.error("Minio 文件夹删除失败: " + e.getMessage()); + } + } + + + /** + * + * @param path + * 文件路径 + * + * @param name + * 桶名称 + * + * @param zipFile + * 新文件名称 + * + * @return + */ + @Override + public boolean UploadFiles(String name, String path, File zipFile) { + PutObjectResult putObjectResult = s3Client.putObject( name, path, zipFile); + return putObjectResult != null; + } + + /** + * 创建新文件夹 + * + * @param bucketName + * 桶名称 + * + * @param key + * 路径+文件夹名称 + * + * @return ObjectMetadata对象 + */ + @Override + public ObjectMetadata getObjectMetadata(String bucketName, String key) { + ObjectMetadata objectMetadata = s3Client.getObjectMetadata(bucketName,key); + return objectMetadata; + } + + + /** + * + * + * @param bucketName + * 桶名称 + * + * @param key + * 路径+文件夹名称 + * + * @return S3Object对象 + */ + @Override + public S3Object getObject(String bucketName, String key) { + S3Object s3Object = s3Client.getObject(bucketName,key); + return s3Object; + } + + + + @Override public boolean renameFile(String path, String name, String newName) { String srcPath = StringUtils.concatTrimStartSlashes(param.getBasePath(), path, name); @@ -194,11 +451,68 @@ public abstract class AbstractS3BaseFileService

extends A return true; } + //todo 改造之前的重命名 +// @Override +// public boolean renameFolder(String path, String name, String newName) { +// throw new UnsupportedOperationException("该存储类型不支持此操作"); +// } + @Override public boolean renameFolder(String path, String name, String newName) { - throw new UnsupportedOperationException("该存储类型不支持此操作"); + try { + // 规范化路径 + String oldPrefix = null; + String newPrefix = null; + if (StringUtils.isEmpty(path)) { + oldPrefix = path + name; + newPrefix = path + newName; + } else { + oldPrefix = path + "/" + name; + newPrefix = path + "/" + newName; + } + + String bucketName = param.getBucketName(); + // 打印路径,检查是否正确 + System.out.println("Old Prefix: " + oldPrefix); + System.out.println("New Prefix: " + newPrefix); + // 列出旧路径下的所有对象 + ListObjectsV2Request request = new ListObjectsV2Request() + .withBucketName(bucketName) + .withPrefix(oldPrefix) + .withMaxKeys(500); // 每次查询最多返回 500 个对象 + ListObjectsV2Result result; + do { + result = s3Client.listObjectsV2(request); + System.out.println("Object Summaries: " + result.getObjectSummaries()); + // 遍历对象并重命名 + for (S3ObjectSummary objectSummary : result.getObjectSummaries()) { + String oldKey = objectSummary.getKey(); + String newKey = oldKey.replace(oldPrefix, newPrefix); + // 打印关键变量 + System.out.println("Old Key: " + oldKey); + System.out.println("New Key: " + newKey); + // 获取源对象的元数据 + ObjectMetadata metadata = s3Client.getObjectMetadata(bucketName, oldKey); + // 使用 CopyObjectRequest 复制对象 + CopyObjectRequest copyObjectRequest = new CopyObjectRequest(bucketName, oldKey, bucketName, newKey) + .withNewObjectMetadata(metadata); + s3Client.copyObject(copyObjectRequest); + // 删除旧对象 + s3Client.deleteObject(bucketName, oldKey); + } + + // 如果对象数量超过单次查询限制,继续查询下一页 + request.setContinuationToken(result.getNextContinuationToken()); + } while (result.isTruncated()); + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } } + @Override public String getUploadUrl(String path, String name, Long size) { String bucketName = param.getBucketName(); diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java b/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java index f46cfa8..446309c 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java @@ -1,9 +1,12 @@ package com.yfd.platform.modules.storage.service.base; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; import com.yfd.platform.modules.storage.model.enums.StorageTypeEnum; import com.yfd.platform.modules.storage.model.result.FileItemResult; +import java.io.File; import java.util.List; /** @@ -215,4 +218,59 @@ public interface BaseFileService { */ StorageTypeEnum getStorageTypeEnum(); + /** + * 创建新文件夹 + * + * @param name + * 桶名称 + * + * @param path + * 文件夹名称 + * + * @param zipFile + * 本地文件 + * + * @return 是否创建成功 + */ + boolean UploadFiles(String name, String path, File zipFile); + + + /** + * 创建新文件夹 + * + * @param bucketName + * 桶名称 + * + * @param key + * 路径+文件夹名称 + * + * @return ObjectMetadata对象 + */ + ObjectMetadata getObjectMetadata(String bucketName, String key); + + + /** + * 创建新文件夹 + * + * @param bucketName + * 桶名称 + * + * @param key + * 路径+文件夹名称 + * + * @return ObjectMetadata对象 + */ + S3Object getObject(String bucketName, String key); + + /*** + * 获取指定路径下的文件及文件夹 + * + * @param folderPath + * 文件夹路径 + * + * @return 文件及文件夹列表 + * + * @throws Exception 获取文件列表中出现的异常 + */ + List fileLists(String folderPath) throws Exception; } diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java index 6dc2cbe..a670e9f 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java @@ -5,6 +5,8 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; import com.yfd.platform.constant.ZFileConstant; import com.yfd.platform.exception.init.InitializeStorageSourceException; import com.yfd.platform.modules.storage.model.enums.FileTypeEnum; @@ -25,11 +27,9 @@ import org.springframework.http.*; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; +import java.io.*; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -57,6 +57,8 @@ public class LocalServiceImpl extends AbstractProxyTransferService { } + + @Override public List fileList(String folderPath) throws FileNotFoundException { checkPathSecurity(folderPath); @@ -167,6 +169,94 @@ public class LocalServiceImpl extends AbstractProxyTransferService { return StorageTypeEnum.LOCAL; } + @Override + public boolean UploadFiles(String name, String path, File zipFile) { + return false; + } + + @Override + public ObjectMetadata getObjectMetadata(String name, String path) { + return null; + } + @Override + public S3Object getObject(String bucketName, String key) { + return null; + } + + @Override + public List fileLists(String folderPath) throws Exception { + checkPathSecurity(folderPath); + + List fileItemList = new ArrayList<>(); + + String fullPath = StringUtils.concat(param.getFilePath() + folderPath); + + File file = new File(fullPath); + + if (!file.exists()) { + throw new FileNotFoundException("文件不存在"); + } + + // 调用递归方法处理文件夹及其内容(跳过第一个文件夹) + listFilesInDirectory(file, folderPath, fileItemList, true); + + return fileItemList; + } + + private void listFilesInDirectory(File file, String folderPath, List fileItemList, boolean skipFirstFolder) throws IOException { + // 跳过第一个文件夹(folderPath),从它下面的内容开始处理 + if (skipFirstFolder) { + // 如果当前文件是 folderPath, 直接跳过,开始处理它的子文件夹 + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + // 递归进入下一级文件夹,路径应该是 folderPath + 当前文件夹名 + listFilesInDirectory(f, folderPath, fileItemList, false); + } + } + } + } else { + // 处理当前的文件夹或文件 + if (file.isDirectory()) { + // 对于文件夹,路径是 folderPath + fileItemList.add(fileToFileItems(file, folderPath)); + + // 递归处理当前文件夹内部的文件和文件夹 + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + // 递归进入下一级文件夹,路径是 folderPath + 当前文件夹名 + String newPath = folderPath + file.getName(); + listFilesInDirectory(f, newPath, fileItemList, false); + } + } + } else { + // 对于文件,路径是 folderPath + fileItemList.add(fileToFileItems(file, folderPath)); + } + } + } + //lilin增加 + private FileItemResult fileToFileItems(File file, String folderPath) { + FileItemResult result = new FileItemResult(); + result.setName(file.getName()); + result.setPath(folderPath); // 这里返回的是父目录路径,不包含文件名 + + // 使用 FileTypeEnum 设置文件类型 + if (file.isDirectory()) { + result.setType(FileTypeEnum.FOLDER); + } else { + result.setType(FileTypeEnum.FILE); + } + + result.setSize(file.length()); + // 设置文件的修改时间为 Date 类型 + result.setTime(new Date(file.lastModified())); + + return result; + } + @Override public void uploadFile(String pathAndName, InputStream inputStream) { diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java index 5655c9a..324c8e7 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java @@ -4,6 +4,7 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.client.builder.AwsClientBuilder; import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.ObjectMetadata; import com.yfd.platform.modules.storage.model.enums.StorageTypeEnum; import com.yfd.platform.modules.storage.model.param.MinIOParam; import com.yfd.platform.modules.storage.service.base.AbstractS3BaseFileService; diff --git a/java/src/main/java/com/yfd/platform/system/controller/SysDictionaryItemsController.java b/java/src/main/java/com/yfd/platform/system/controller/SysDictionaryItemsController.java index 6bacb90..cfc42f5 100644 --- a/java/src/main/java/com/yfd/platform/system/controller/SysDictionaryItemsController.java +++ b/java/src/main/java/com/yfd/platform/system/controller/SysDictionaryItemsController.java @@ -258,5 +258,24 @@ public class SysDictionaryItemsController { } } + /*********************************** + * 用途说明: 根据父项编码查询数据字典项数据 + * 参数说明 + * parentcode 父项编码 + * 返回值说明: 变电站信息 + ***********************************/ + @PostMapping("/list") + @ApiOperation("根据父项编码查询数据字典项数据") + @ResponseBody + public ResponseResult listSysDictionaryItems(@RequestParam String parentcode) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if(parentcode != null){ + queryWrapper.eq("parentcode",parentcode); + } + queryWrapper.orderByAsc("orderno"); + List> maps = sysDictionaryItemsService.listMaps(queryWrapper); + return ResponseResult.successData(maps); + } + } diff --git a/java/src/main/java/com/yfd/platform/utils/CodeGenerator.java b/java/src/main/java/com/yfd/platform/utils/CodeGenerator.java index 4da0c7a..ca7acdd 100644 --- a/java/src/main/java/com/yfd/platform/utils/CodeGenerator.java +++ b/java/src/main/java/com/yfd/platform/utils/CodeGenerator.java @@ -52,7 +52,7 @@ public class CodeGenerator { // 全局配置 GlobalConfig gc = new GlobalConfig(); - String projectPath = System.getProperty("user.dir"); + String projectPath = System.getProperty("user.dir")+"/java"; gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("LiMengNan"); gc.setOpen(false); @@ -61,10 +61,10 @@ public class CodeGenerator { // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); - dsc.setUrl("jdbc:mysql://43.138.168.68:3306/ehmsdb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true"); + dsc.setUrl("jdbc:mysql://121.37.111.42:3306/filemanagedb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); - dsc.setUsername("root"); - dsc.setPassword("ylfw20230626@"); + dsc.setUsername("filemanagedb"); + dsc.setPassword("GAPchydbCKYFjjAa"); mpg.setDataSource(dsc); // 包配置 @@ -168,7 +168,7 @@ public class CodeGenerator { //rca_project,rca_projectanalysisrd,rca_projectinvestigaterd,rca_projectreport,rca_projectsummary,rca_projecttarget,rca_projecttrackrd,rca_analysisguide,rca_eventeffect,rca_failurecase,rca_failurecause,rca_failureclass,rca_failuremode strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); strategy.setControllerMappingHyphenStyle(true); - strategy.setTablePrefix("vis_"); + strategy.setTablePrefix(""); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); diff --git a/java/src/main/resources/mapper/experimentalData/FilesMapper.xml b/java/src/main/resources/mapper/experimentalData/FilesMapper.xml new file mode 100644 index 0000000..85dd759 --- /dev/null +++ b/java/src/main/resources/mapper/experimentalData/FilesMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/experimentalData/NodesMapper.xml b/java/src/main/resources/mapper/experimentalData/NodesMapper.xml new file mode 100644 index 0000000..82f8d4c --- /dev/null +++ b/java/src/main/resources/mapper/experimentalData/NodesMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/experimentalData/TaskMapper.xml b/java/src/main/resources/mapper/experimentalData/TaskMapper.xml new file mode 100644 index 0000000..8dbf619 --- /dev/null +++ b/java/src/main/resources/mapper/experimentalData/TaskMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/experimentalData/TsFilesMapper.xml b/java/src/main/resources/mapper/experimentalData/TsFilesMapper.xml new file mode 100644 index 0000000..95e280c --- /dev/null +++ b/java/src/main/resources/mapper/experimentalData/TsFilesMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/experimentalData/TsNodesMapper.xml b/java/src/main/resources/mapper/experimentalData/TsNodesMapper.xml new file mode 100644 index 0000000..321179a --- /dev/null +++ b/java/src/main/resources/mapper/experimentalData/TsNodesMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml b/java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml new file mode 100644 index 0000000..3ed6411 --- /dev/null +++ b/java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/specialDocument/FilesMapper.xml b/java/src/main/resources/mapper/specialDocument/FilesMapper.xml new file mode 100644 index 0000000..4e5cae0 --- /dev/null +++ b/java/src/main/resources/mapper/specialDocument/FilesMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/specialDocument/NodesMapper.xml b/java/src/main/resources/mapper/specialDocument/NodesMapper.xml new file mode 100644 index 0000000..8afc5e2 --- /dev/null +++ b/java/src/main/resources/mapper/specialDocument/NodesMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/java/src/main/resources/mapper/specialDocument/ProjectMapper.xml b/java/src/main/resources/mapper/specialDocument/ProjectMapper.xml new file mode 100644 index 0000000..ba7fc4e --- /dev/null +++ b/java/src/main/resources/mapper/specialDocument/ProjectMapper.xml @@ -0,0 +1,5 @@ + + + + +