解决冲突

This commit is contained in:
wangxk 2025-03-07 15:11:33 +08:00
commit 9dccf3c4f9
21 changed files with 1432 additions and 952 deletions

View File

@ -6,10 +6,7 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.platform.annotation.Log; import com.yfd.platform.annotation.Log;
import com.yfd.platform.config.ResponseResult; import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.modules.experimentalData.domain.DualTreeResponse; import com.yfd.platform.modules.experimentalData.domain.*;
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.experimentalData.service.ITsFilesService;
import com.yfd.platform.modules.specialDocument.domain.Files; import com.yfd.platform.modules.specialDocument.domain.Files;
import com.yfd.platform.modules.specialDocument.service.IFilesService; import com.yfd.platform.modules.specialDocument.service.IFilesService;
@ -20,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -53,6 +51,7 @@ public class TsFilesController {
* pageNum 当前页 * pageNum 当前页
* 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "分页查询实验数据管理文档内容!")
@GetMapping("/page") @GetMapping("/page")
@ApiOperation("分页查询实验数据管理文档内容") @ApiOperation("分页查询实验数据管理文档内容")
@PreAuthorize("@el.check('select:tsfiles')") @PreAuthorize("@el.check('select:tsfiles')")
@ -69,6 +68,7 @@ public class TsFilesController {
* pageNum 当前页 * pageNum 当前页
* 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "查询实验数据管理文件夹!")
@GetMapping("/listTsFiles") @GetMapping("/listTsFiles")
@ApiOperation("查询实验数据管理文件夹") @ApiOperation("查询实验数据管理文件夹")
@PreAuthorize("@el.check('select:tsfiles')") @PreAuthorize("@el.check('select:tsfiles')")
@ -84,7 +84,7 @@ public class TsFilesController {
* TsFiles 文档内容 * TsFiles 文档内容
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "新增试验数据管理文档内容!", type = "1") @Log(module = "实验数据管理", value = "新增试验数据管理文档内容!")
@PostMapping("/addTsFiles") @PostMapping("/addTsFiles")
@ApiOperation("新增试验数据管理文档内容") @ApiOperation("新增试验数据管理文档内容")
@ResponseBody @ResponseBody
@ -108,7 +108,7 @@ public class TsFilesController {
* TsFiles 文档内容 * TsFiles 文档内容
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "新增试验数据管理文件夹", type = "1") @Log(module = "实验数据管理", value = "新增试验数据管理文件夹")
@PostMapping("/addTsFile") @PostMapping("/addTsFile")
@ApiOperation("新增试验数据管理文件夹") @ApiOperation("新增试验数据管理文件夹")
@ResponseBody @ResponseBody
@ -129,7 +129,7 @@ public class TsFilesController {
* TsFiles 文档内容 * TsFiles 文档内容
* 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "修改试验数据管理文档内容", type = "1") @Log(module = "试验数据管理", value = "修改试验数据管理文档内容")
@PostMapping("/updateTsFiles") @PostMapping("/updateTsFiles")
@ApiOperation("修改试验数据管理文档内容") @ApiOperation("修改试验数据管理文档内容")
@PreAuthorize("@el.check('update:tsFiles')") @PreAuthorize("@el.check('update:tsFiles')")
@ -145,38 +145,40 @@ public class TsFilesController {
/********************************** /**********************************
* 用途说明: 根据ID删除试验数据管理-文档内容 * 用途说明: 根据ID删除试验数据管理-文档内容
* 参数说明 id 文档内容ID * 参数说明 id 文档内容ID
* 参数说明 type local还是minio
* 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "根据ID删除试验数据管理文档内容", type = "1") @Log(module = "试验数据管理", value = "根据ID删除试验数据管理文档内容")
@PostMapping("/deleteTsFilesById") @PostMapping("/deleteTsFilesById")
@ApiOperation("根据ID删除试验数据管理文档内容") @ApiOperation("根据ID删除试验数据管理文档内容")
@PreAuthorize("@el.check('del:tsFiles')") @PreAuthorize("@el.check('del:tsFiles')")
public ResponseResult deleteTsFilesById(@RequestParam String id) { public ResponseResult deleteTsFilesById(@RequestParam String id, @RequestParam String type) {
if (StrUtil.isBlank(id)) { if (StrUtil.isBlank(id)) {
return ResponseResult.error("参数为空"); return ResponseResult.error("参数为空");
} }
List<String> dataset = Arrays.asList(id); List<String> dataset = Arrays.asList(id);
return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset)); return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset, type));
} }
/********************************** /**********************************
* 用途说明: 批量删除试验数据管理-文档内容 * 用途说明: 批量删除试验数据管理-文档内容
* 参数说明 ids 文档内容id数组 * 参数说明 ids 文档内容id数组
* 参数说明 type local还是minio
* 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "批量删除试验数据管理文档内容", type = "1") @Log(module = "专项文档管理", value = "批量删除试验数据管理文档内容")
@PostMapping("/deleteTsFilesByIds") @PostMapping("/deleteTsFilesByIds")
@ApiOperation("批量删除试验数据管理文档内容") @ApiOperation("批量删除试验数据管理文档内容")
@PreAuthorize("@el.check('del:tsFiles')") @PreAuthorize("@el.check('del:tsFiles')")
public ResponseResult deleteTsFilesByIds(@RequestParam String ids) { public ResponseResult deleteTsFilesByIds(@RequestParam String ids, @RequestParam String type) {
if (StrUtil.isBlank(ids)) { if (StrUtil.isBlank(ids)) {
return ResponseResult.error("参数为空"); return ResponseResult.error("参数为空");
} }
String[] splitIds = ids.split(","); String[] splitIds = ids.split(",");
// 数组转集合 // 数组转集合
List<String> dataset = Arrays.asList(splitIds); List<String> dataset = Arrays.asList(splitIds);
return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset)); return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset, type));
} }
@ -192,6 +194,7 @@ public class TsFilesController {
* 参数说明 parentId 父ID * 参数说明 parentId 父ID
* 返回值说明: com.yfd.platform.config.ResponseResult * 返回值说明: com.yfd.platform.config.ResponseResult
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "压缩文件夹接口!")
@PostMapping("/compress") @PostMapping("/compress")
@ApiOperation("压缩文件夹接口") @ApiOperation("压缩文件夹接口")
public ResponseResult compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId) { public ResponseResult compressFolder(String ids, String compressedFormat, String compressedName, String compressedPath, String covered, String parentId) {
@ -215,6 +218,7 @@ public class TsFilesController {
* 参数说明 parentId 父ID * 参数说明 parentId 父ID
* 返回值说明: com.yfd.platform.config.ResponseResult * 返回值说明: com.yfd.platform.config.ResponseResult
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "解压缩接口!")
@PostMapping("/decompression") @PostMapping("/decompression")
@ApiOperation("解压缩接口") @ApiOperation("解压缩接口")
public ResponseResult decompressionFolder(String id, String decompressionPath, String parentId) { public ResponseResult decompressionFolder(String id, String decompressionPath, String parentId) {
@ -231,7 +235,6 @@ public class TsFilesController {
} }
/** /**
* 移动文件或文件夹 * 移动文件或文件夹
* 参数说明 newPath 新路径 * 参数说明 newPath 新路径
@ -241,6 +244,7 @@ public class TsFilesController {
* 参数说明 Rename 重命名的文件名称 * 参数说明 Rename 重命名的文件名称
* 参数说明 type 覆盖还是重命名 0 1 * 参数说明 type 覆盖还是重命名 0 1
*/ */
@Log(module = "实验数据管理", value = "移动文件文件夹!")
@PostMapping("/moveFileFolder") @PostMapping("/moveFileFolder")
@ApiOperation("移动") @ApiOperation("移动")
public ResponseResult moveFileFolder(@RequestBody MoveCopyFileFolderRequest request) throws IOException { public ResponseResult moveFileFolder(@RequestBody MoveCopyFileFolderRequest request) throws IOException {
@ -273,6 +277,7 @@ public class TsFilesController {
* 参数说明 Rename 重命名的文件名称 * 参数说明 Rename 重命名的文件名称
* 参数说明 type 覆盖还是重命名 0 1 * 参数说明 type 覆盖还是重命名 0 1
*/ */
@Log(module = "实验数据管理", value = "复制文件文件夹!")
@PostMapping("/copyFileFolder") @PostMapping("/copyFileFolder")
@ApiOperation("复制") @ApiOperation("复制")
public ResponseResult copyFileFolder(@RequestBody MoveCopyFileFolderRequest request) throws IOException { public ResponseResult copyFileFolder(@RequestBody MoveCopyFileFolderRequest request) throws IOException {
@ -295,24 +300,26 @@ public class TsFilesController {
} }
/** /**
* 对比两个目录的文件差异 * 对比两个目录的文件差异
* *
* @param localPath 本地目录的相对路径相对于E:\yun\qqq * @param ids 勾选的所有数据ID集合
* @param minioPath MinIO目录的相对路径相对于test-bucket/qqq
* @return 文件差异列表 * @return 文件差异列表
*/ */
@Log(module = "实验数据管理", value = "对比本地和minio的文件差异")
@PostMapping("/compare") @PostMapping("/compare")
@ApiOperation("对比两个目录的文件差异") @ApiOperation("对比两个目录的文件差异")
public ResponseResult compareDirectories(@RequestParam String localPath, @RequestParam String minioPath) { public ResponseResult compareDirectories( String ids, String nodeId, String taskId) {
try { try {
if (StrUtil.isBlank(localPath) && StrUtil.isBlank(minioPath)) { List<String> dataset = new ArrayList<>();
return ResponseResult.error("参数为空"); if (StrUtil.isNotEmpty(ids)) {
String[] splitIds = ids.split(",");
// 数组转集合
dataset = Arrays.asList(splitIds);
} }
TsFiles tsFiles = tsFilesService.compareDirectories(localPath, minioPath); TsFiles tsFiles = tsFilesService.compareDirectories(dataset, nodeId, taskId);
return ResponseResult.successData(tsFiles); return ResponseResult.successData(tsFiles);
} catch (Exception e) { } catch (Exception e) {
return ResponseResult.error("对比失败"); return ResponseResult.error("对比失败");
@ -322,20 +329,18 @@ public class TsFilesController {
/********************************** /**********************************
* 用途说明: 将文件上传到备份空间 * 用途说明: 将文件上传到备份空间
* 参数说明 paths 路径集合 * 参数说明 parameter 数据集合
* 参数说明 names 文件名集合
* 参数说明 sizes 文件大小集合
* 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "将文件上传到备份空间!")
@PostMapping("/uploadToBackup") @PostMapping("/uploadToBackup")
@ApiOperation("将文件上传到备份空间") @ApiOperation("将文件上传到备份空间")
public ResponseResult uploadToBackup(@RequestParam String paths, @RequestParam String names, @RequestParam String sizes) { public ResponseResult uploadToBackup(@RequestBody Parameter parameter) {
if (parameter == null) {
if (StrUtil.isBlank(paths) && StrUtil.isBlank(names)) {
return ResponseResult.error("参数为空"); return ResponseResult.error("参数为空");
} }
Boolean isOk = tsFilesService.uploadToBackup(paths, names, sizes); Boolean isOk = tsFilesService.uploadToBackup(parameter);
if (isOk) { if (isOk) {
return ResponseResult.success(); return ResponseResult.success();
} else { } else {
@ -346,20 +351,19 @@ public class TsFilesController {
/********************************** /**********************************
* 用途说明: 从备份空间下载到工作空间 * 用途说明: 从备份空间下载到工作空间
* 参数说明 paths 路径集合 * 参数说明 parameter 数据集合
* 参数说明 names 文件名集合
* 参数说明 sizes 文件大小集合
* 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "从备份空间下载到工作空间!")
@PostMapping("/downloadToLocal") @PostMapping("/downloadToLocal")
@ApiOperation("从备份空间下载到工作空间") @ApiOperation("从备份空间下载到工作空间")
public ResponseResult downloadToLocal(@RequestParam String paths, @RequestParam String names, @RequestParam String sizes) { public ResponseResult downloadToLocal(@RequestBody Parameter parameter) {
if (StrUtil.isBlank(paths) && StrUtil.isBlank(names)) { if (parameter == null) {
return ResponseResult.error("参数为空"); return ResponseResult.error("参数为空");
} }
Boolean isOk = tsFilesService.downloadToLocal(paths, names, sizes); Boolean isOk = tsFilesService.downloadToLocal(parameter);
if (isOk) { if (isOk) {
return ResponseResult.success(); return ResponseResult.success();
} else { } else {
@ -369,17 +373,16 @@ public class TsFilesController {
} }
/********************************** /**********************************
* 用途说明: 查询本地和备份空间结构树 * 用途说明: 查询本地和备份空间结构树
* 参数说明 taskId 节点ID * 参数说明 taskId 节点ID
* 参数说明 nodeId 任务ID * 参数说明 nodeId 任务ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据
***********************************/ ***********************************/
@Log(module = "实验数据管理", value = "查询本地和备份空间结构树!")
@PostMapping("/listLocalAndBackup") @PostMapping("/listLocalAndBackup")
@ApiOperation("查询本地和备份空间结构树") @ApiOperation("查询本地和备份空间结构树")
public ResponseResult listLocalAndBackup( String taskId, String nodeId) { public ResponseResult listLocalAndBackup(String taskId, String nodeId) {
if (StrUtil.isBlank(taskId) && StrUtil.isBlank(nodeId)) { if (StrUtil.isBlank(taskId) && StrUtil.isBlank(nodeId)) {
@ -391,8 +394,4 @@ public class TsFilesController {
} }
} }

View File

@ -42,6 +42,7 @@ public class TsNodesController {
* taskId 所属任务ID * taskId 所属任务ID
* 返回值说明: 专项文档节点树形结构 * 返回值说明: 专项文档节点树形结构
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "获取试验任务节点树形结构!")
@PostMapping("/getTsNodesTree") @PostMapping("/getTsNodesTree")
@ApiOperation("获取试验任务节点树形结构") @ApiOperation("获取试验任务节点树形结构")
@ResponseBody @ResponseBody
@ -56,7 +57,7 @@ public class TsNodesController {
* 参数说明 tsnodes 试验任务节点信息 * 参数说明 tsnodes 试验任务节点信息
* 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "增加试验任务节点",type = "1") @Log(module = "试验数据管理", value = "增加试验任务节点!")
@PostMapping("/addTsNodes") @PostMapping("/addTsNodes")
@ApiOperation("增加试验任务节点") @ApiOperation("增加试验任务节点")
@PreAuthorize("@el.check('add:tsnodes')") @PreAuthorize("@el.check('add:tsnodes')")
@ -74,7 +75,7 @@ public class TsNodesController {
* 参数说明 tsnodes 试验任务节点信息 * 参数说明 tsnodes 试验任务节点信息
* 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "修改试验任务节点",type = "1") @Log(module = "试验数据管理", value = "修改试验任务节点!")
@PostMapping("/updateTsNodes") @PostMapping("/updateTsNodes")
@ApiOperation("修改试验任务节点") @ApiOperation("修改试验任务节点")
@PreAuthorize("@el.check('update:tsnodes')") @PreAuthorize("@el.check('update:tsnodes')")
@ -91,7 +92,7 @@ public class TsNodesController {
* 参数说明 id 试验任务节点ID * 参数说明 id 试验任务节点ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "根据ID删除试验任务节点",type = "1") @Log(module = "试验数据管理", value = "根据ID删除试验任务节点!")
@PostMapping("/deleteTsNodesById") @PostMapping("/deleteTsNodesById")
@ApiOperation("根据ID删除试验任务节点") @ApiOperation("根据ID删除试验任务节点")
@PreAuthorize("@el.check('del:tsnodes')") @PreAuthorize("@el.check('del:tsnodes')")

View File

@ -46,6 +46,7 @@ public class TsTaskController {
* pageNum 当前页 * pageNum 当前页
* 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "分页查询试验数据管理试验任务管理!")
@GetMapping("/page") @GetMapping("/page")
@ApiOperation("分页查询试验数据管理试验任务管理") @ApiOperation("分页查询试验数据管理试验任务管理")
@PreAuthorize("@el.check('select:tsTask')") @PreAuthorize("@el.check('select:tsTask')")
@ -61,7 +62,7 @@ public class TsTaskController {
* TsTask 试验任务管理 * TsTask 试验任务管理
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "新增试验数据管理试验任务管理!",type = "1") @Log(module = "试验数据管理", value = "新增试验数据管理试验任务管理!")
@PostMapping("/addtsTask") @PostMapping("/addtsTask")
@ApiOperation("新增试验数据管理试验任务管理") @ApiOperation("新增试验数据管理试验任务管理")
@ResponseBody @ResponseBody
@ -85,7 +86,7 @@ public class TsTaskController {
* TsTask 试验任务管理 * TsTask 试验任务管理
* 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "修改试验数据管理试验任务管理",type = "1") @Log(module = "试验数据管理", value = "修改试验数据管理试验任务管理")
@PostMapping("/updatetsTask") @PostMapping("/updatetsTask")
@ApiOperation("修改试验数据管理试验任务管理") @ApiOperation("修改试验数据管理试验任务管理")
@PreAuthorize("@el.check('update:tsTask')") @PreAuthorize("@el.check('update:tsTask')")
@ -107,7 +108,7 @@ public class TsTaskController {
* 参数说明 id 试验数据管理ID * 参数说明 id 试验数据管理ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "根据ID删除试验数据管理试验任务管理",type = "1") @Log(module = "试验数据管理", value = "根据ID删除试验数据管理试验任务管理")
@PostMapping("/deleteTsTaskById") @PostMapping("/deleteTsTaskById")
@ApiOperation("根据ID删除试验数据管理试验任务管理") @ApiOperation("根据ID删除试验数据管理试验任务管理")
@PreAuthorize("@el.check('del:tsTask')") @PreAuthorize("@el.check('del:tsTask')")
@ -128,7 +129,7 @@ public class TsTaskController {
* 参数说明 ids 试验数据管理id数组 * 参数说明 ids 试验数据管理id数组
* 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败
***********************************/ ***********************************/
@Log(module = "试验数据管理", value = "批量删除试验数据管理试验任务管理",type = "1") @Log(module = "试验数据管理", value = "批量删除试验数据管理试验任务管理")
@PostMapping("/deleteTsTaskByIds") @PostMapping("/deleteTsTaskByIds")
@ApiOperation("批量删除试验数据管理试验任务管理") @ApiOperation("批量删除试验数据管理试验任务管理")
@PreAuthorize("@el.check('del:tsTask')") @PreAuthorize("@el.check('del:tsTask')")
@ -153,7 +154,7 @@ public class TsTaskController {
* 返回值说明: 试验数据管理试验任务管理 * 返回值说明: 试验数据管理试验任务管理
***********************************/ ***********************************/
@PostMapping("/list") @PostMapping("/list")
@ApiOperation("查询所有试验数据管理试验任务管理") @ApiOperation("查询所有试验数据管理试验任务管理")
@ResponseBody @ResponseBody
//@PreAuthorize("@el.check('select:devicesignal')") //@PreAuthorize("@el.check('select:devicesignal')")
public ResponseResult listTsTask() { public ResponseResult listTsTask() {

View File

@ -0,0 +1,11 @@
package com.yfd.platform.modules.experimentalData.domain;
import lombok.Data;
import java.util.List;
@Data
public class Parameter {
public List<ParameterList> parameterLists;
// public String parameterLists;
}

View File

@ -0,0 +1,13 @@
package com.yfd.platform.modules.experimentalData.domain;
import lombok.Data;
@Data
public class ParameterList {
public String path;
public String size;
public String name;
public String type;
}

View File

@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Data @Data
@ -98,5 +99,6 @@ public class TreeDTO {
private String path; private String path;
// 子节点列表 // 子节点列表
private List<TreeDTO> children; // 初始化 children 为一个空列表
private List<TreeDTO> children = new ArrayList<>();
} }

View File

@ -166,24 +166,24 @@ public class TsFiles implements Serializable {
// private String creator; // private String creator;
// //
// //
// /** /**
// * 检查本地有而 MinIO 没有的文件 * 检查本地有而 MinIO 没有的文件
// */ */
// @TableField(exist = false) @TableField(exist = false)
// private List<FileItemResult> localOnlyFiles; private List<FileItemResult> localOnlyFiles;
//
//
// /** /**
// * 检查 MinIO 有而本地没有的文件 * 检查 MinIO 有而本地没有的文件
// */ */
// @TableField(exist = false) @TableField(exist = false)
// private List<FileItemResult> minioOnlyFiles; private List<FileItemResult> minioOnlyFiles;
//
//
// /** /**
// * 检查 MD5 不一致的文件 * 检查 MD5 不一致的文件
// */ */
// @TableField(exist = false) @TableField(exist = false)
// private List<FileItemResult> md5MismatchedFiles; private List<FileItemResult> md5MismatchedFiles;
} }

View File

@ -4,8 +4,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.platform.config.ResponseResult; import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.modules.experimentalData.domain.DualTreeResponse; import com.yfd.platform.modules.experimentalData.domain.DualTreeResponse;
import com.yfd.platform.modules.experimentalData.domain.MoveCopyFileFolderRequest; import com.yfd.platform.modules.experimentalData.domain.MoveCopyFileFolderRequest;
import com.yfd.platform.modules.experimentalData.domain.Parameter;
import com.yfd.platform.modules.experimentalData.domain.TsFiles; import com.yfd.platform.modules.experimentalData.domain.TsFiles;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -54,10 +56,11 @@ public interface ITsFilesService extends IService<TsFiles> {
/********************************** /**********************************
* 用途说明: 批量删除试验数据管理-文档内容 * 用途说明: 批量删除试验数据管理-文档内容
* 参数说明 ids 文档内容id数组 * 参数说明 id 文档内容ID
* 参数说明 type local还是minio
* 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败
***********************************/ ***********************************/
String deleteTsFilesByIds(List<String> dataset); String deleteTsFilesByIds(List<String> dataset,String type);
/********************************** /**********************************
@ -83,25 +86,22 @@ public interface ITsFilesService extends IService<TsFiles> {
String decompressionFolder(String id,String decompressionPath,String parentId); String decompressionFolder(String id,String decompressionPath,String parentId);
TsFiles compareDirectories(String localPath, String minioPath);
/********************************** /**********************************
* 用途说明: 将文件上传到备份空间 * 用途说明: 将文件上传到备份空间
* 参数说明 paths 路径集合 * 参数说明 parameter 数据集合
* 参数说明 names 文件名集合
* 参数说明 sizes 文件大小集合
* 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
***********************************/ ***********************************/
Boolean uploadToBackup(String paths, String names, String sizes); Boolean uploadToBackup(Parameter parameter);
/********************************** /**********************************
* 用途说明: 从备份空间下载到工作空间 * 用途说明: 从备份空间下载到工作空间
* 参数说明 paths 路径集合 * 参数说明 parameter 数据集合
* 参数说明 names 文件名集合
* 参数说明 sizes 文件大小集合
* 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败
***********************************/ ***********************************/
Boolean downloadToLocal(String paths, String names, String sizes); Boolean downloadToLocal(Parameter parameter);
/*********************************** /***********************************
* 用途说明新增试验数据管理-文件夹 * 用途说明新增试验数据管理-文件夹
@ -150,4 +150,13 @@ public interface ITsFilesService extends IService<TsFiles> {
* 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据
***********************************/ ***********************************/
DualTreeResponse listLocalAndBackup(String taskId, String nodeId); DualTreeResponse listLocalAndBackup(String taskId, String nodeId);
/**
* 对比两个目录的文件差异
*
* @param dataset 勾选的所有数据ID集合
* @return 文件差异列表
*/
TsFiles compareDirectories(List<String> dataset,String nodeId , String taskId);
} }

View File

@ -47,6 +47,7 @@ public class FilesController {
* pageNum 当前页 * pageNum 当前页
* 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果 * 返回值说明: com.yfd.platform.config.ResponseResult 返回分页查询结果
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "分页查询专项文档管理文档内容")
@GetMapping("/page") @GetMapping("/page")
@ApiOperation("分页查询专项文档管理文档内容") @ApiOperation("分页查询专项文档管理文档内容")
@PreAuthorize("@el.check('select:files')") @PreAuthorize("@el.check('select:files')")
@ -63,7 +64,7 @@ public class FilesController {
* Files 文档内容 * Files 文档内容
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "新增专项文档管理文档内容!",type = "1") @Log(module = "专项文档管理", value = "新增专项文档管理文档内容!")
@PostMapping("/addFiles") @PostMapping("/addFiles")
@ApiOperation("新增专项文档管理文档内容") @ApiOperation("新增专项文档管理文档内容")
@ResponseBody @ResponseBody
@ -87,7 +88,7 @@ public class FilesController {
* Files 文档内容 * Files 文档内容
* 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "修改专项文档管理文档内容",type = "1") @Log(module = "专项文档管理", value = "修改专项文档管理文档内容")
@PostMapping("/updateFiles") @PostMapping("/updateFiles")
@ApiOperation("修改专项文档管理文档内容") @ApiOperation("修改专项文档管理文档内容")
@PreAuthorize("@el.check('update:files')") @PreAuthorize("@el.check('update:files')")
@ -110,7 +111,7 @@ public class FilesController {
* 参数说明 id 文档内容ID * 参数说明 id 文档内容ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "根据ID删除专项文档管理文档内容",type = "1") @Log(module = "专项文档管理", value = "根据ID删除专项文档管理文档内容")
@PostMapping("/deleteFilesById") @PostMapping("/deleteFilesById")
@ApiOperation("根据ID删除专项文档管理文档内容") @ApiOperation("根据ID删除专项文档管理文档内容")
@PreAuthorize("@el.check('del:systemdevice')") @PreAuthorize("@el.check('del:systemdevice')")
@ -128,7 +129,7 @@ public class FilesController {
* 参数说明 ids 文档内容id数组 * 参数说明 ids 文档内容id数组
* 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回批量删除成功或失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "批量删除专项文档管理文档内容",type = "1") @Log(module = "专项文档管理", value = "批量删除专项文档管理文档内容")
@PostMapping("/deleteFilesByIds") @PostMapping("/deleteFilesByIds")
@ApiOperation("批量删除专项文档管理文档内容") @ApiOperation("批量删除专项文档管理文档内容")
@PreAuthorize("@el.check('del:systemdevice')") @PreAuthorize("@el.check('del:systemdevice')")

View File

@ -39,6 +39,7 @@ public class NodesController {
* projectId 所属项目ID * projectId 所属项目ID
* 返回值说明: 专项文档节点树形结构 * 返回值说明: 专项文档节点树形结构
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "获取专项文档节点树形结构")
@PostMapping("/getNodesTree") @PostMapping("/getNodesTree")
@ApiOperation("获取专项文档节点树形结构") @ApiOperation("获取专项文档节点树形结构")
@ResponseBody @ResponseBody
@ -53,7 +54,7 @@ public class NodesController {
* 参数说明 nodes 专项文档节点信息 * 参数说明 nodes 专项文档节点信息
* 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回增加成功或者失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "增加专项文档节点",type = "1") @Log(module = "专项文档管理", value = "增加专项文档节点")
@PostMapping("/addNodes") @PostMapping("/addNodes")
@ApiOperation("增加专项文档节点") @ApiOperation("增加专项文档节点")
@PreAuthorize("@el.check('add:nodes')") @PreAuthorize("@el.check('add:nodes')")
@ -70,7 +71,7 @@ public class NodesController {
* 参数说明 nodes 专项文档节点信息 * 参数说明 nodes 专项文档节点信息
* 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "修改专项文档节点",type = "1") @Log(module = "专项文档管理", value = "修改专项文档节点")
@PostMapping("/updateNodes") @PostMapping("/updateNodes")
@ApiOperation("修改专项文档节点") @ApiOperation("修改专项文档节点")
@PreAuthorize("@el.check('update:nodes')") @PreAuthorize("@el.check('update:nodes')")
@ -87,7 +88,7 @@ public class NodesController {
* 参数说明 id 专项文档节点ID * 参数说明 id 专项文档节点ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
***********************************/ ***********************************/
@Log(module = "专项文档管理", value = "根据ID删除专项文档节点",type = "1") @Log(module = "专项文档管理", value = "根据ID删除专项文档节点")
@PostMapping("/deleteNodesById") @PostMapping("/deleteNodesById")
@ApiOperation("根据ID删除专项文档节点") @ApiOperation("根据ID删除专项文档节点")
@PreAuthorize("@el.check('del:nodes')") @PreAuthorize("@el.check('del:nodes')")

View File

@ -360,9 +360,14 @@ public class NodesServiceImpl extends ServiceImpl<NodesMapper, Nodes> implements
return ResponseResult.error("节点名称已存在!"); return ResponseResult.error("节点名称已存在!");
} }
int valueUpdate = nodesMapper.updateById(nodes); int valueUpdate = nodesMapper.updateById(nodes);
// 4. 递归获取所有子节点ID包含当前节点
List<String> affectedNodeIds = getAllChildNodeIds(nodes.getId());
// 5. 更新相关文件路径
updateFilePaths(affectedNodeIds, nodes.getProjectId(), nodeNameOld, nodeName);
if (valueUpdate == 1) { if (valueUpdate == 1) {
List<String> pathNodes = new ArrayList<>(); List<String> pathNodes = new ArrayList<>();
Nodes nodesData = nodesMapper.selectById(nodes.getParentId()); Nodes nodesData = nodesMapper.selectById(nodes.getParentId());
// 从当前节点向上遍历直到根节点 // 从当前节点向上遍历直到根节点
@ -400,43 +405,85 @@ public class NodesServiceImpl extends ServiceImpl<NodesMapper, Nodes> implements
} }
} }
/**
* 更新文件路径中的节点名称
*/
private void updateFilePaths(List<String> nodeIds, String projectId, String oldName, String newName) {
// 查询需要更新的文件记录
List<Files> filesToUpdate = filesMapper.selectList(
new QueryWrapper<Files>()
.in("node_id", nodeIds)
.eq("project_id", projectId)
);
// 构建路径替换规则
String pathPattern = "/" + oldName; // 正确转义斜杠
String replacement = "/" + newName; // 保留斜杠或结尾不变
// 批量更新文件路径
filesToUpdate.forEach(file -> {
String newPath = file.getFilePath().replace(pathPattern, replacement);
file.setFilePath(newPath);
});
// 批量更新数据库
if (!filesToUpdate.isEmpty()) {
for (Files files : filesToUpdate) {
filesMapper.updateById(files);
}
}
}
/**
* 递归获取所有子节点ID包含当前节点
*/
private List<String> getAllChildNodeIds(String nodeId) {
List<String> nodeIds = new ArrayList<>();
nodeIds.add(nodeId);
// 查询直接子节点
List<Nodes> directChildren = nodesMapper.selectList(
new QueryWrapper<Nodes>().eq("parent_id", nodeId));
// 递归获取子节点的子节点
for (Nodes child : directChildren) {
nodeIds.addAll(getAllChildNodeIds(child.getId()));
}
return nodeIds;
}
/********************************** /**********************************
* 用途说明: 根据ID删除专项文档节点 * 用途说明: 根据ID删除专项文档节点
* 参数说明 id 专项文档节点ID * 参数说明 id 专项文档节点ID
* 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
***********************************/ ***********************************/
@Override @Override
@Transactional(rollbackFor = Exception.class) // 启用事务
public boolean deleteNodesById(String id) { public boolean deleteNodesById(String id) {
Boolean value = false; Boolean value = false;
//根据ID 查询当前数据 // 根据ID 查询当前数据
Nodes nodes = nodesMapper.selectById(id); Nodes nodes = nodesMapper.selectById(id);
// 删除当前节点 if (nodes == null) {
int deleteCount = nodesMapper.deleteById(id); return false; // 节点不存在
//删除当前节点的 文件 }
QueryWrapper<Files> queryWrapper1 = new QueryWrapper<>();
queryWrapper1.eq("node_id", nodes.getId());
queryWrapper1.eq("task_id", nodes.getProjectId());
filesMapper.delete(queryWrapper1);
// 构建节点路径
List<String> pathNodes = new ArrayList<>(); List<String> pathNodes = new ArrayList<>();
Nodes nodesData = nodesMapper.selectById(nodes.getParentId()); Nodes nodesData = nodesMapper.selectById(nodes.getParentId());
// 从当前节点向上遍历直到根节点
while (nodesData != null) { while (nodesData != null) {
pathNodes.add(nodesData.getNodeName()); pathNodes.add(nodesData.getNodeName());
// 如果父节点是 "00"说明已经到了根节点停止遍历
if ("00".equals(nodesData.getParentId())) { if ("00".equals(nodesData.getParentId())) {
break; break;
} }
// 获取父节点 nodesData = nodesMapper.selectById(nodesData.getParentId());
nodesData = nodesMapper.selectById(nodesData.getParentId()); // 修正 nodesData 中获取 parentId
} }
// 反转路径使其从根节点到当前节点
Collections.reverse(pathNodes); Collections.reverse(pathNodes);
String path = String.join("/", pathNodes); String path = String.join("/", pathNodes);
//删除minio准备数据 // 删除 MinIO 中的文件夹
List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>(); List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>();
BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
deleteItemData.setName(nodes.getNodeName()); deleteItemData.setName(nodes.getNodeName());
@ -449,9 +496,9 @@ public class NodesServiceImpl extends ServiceImpl<NodesMapper, Nodes> implements
batchDeleteRequest.setDeleteItems(deleteItemList); batchDeleteRequest.setDeleteItems(deleteItemList);
batchDeleteRequest.setStorageKey("minio"); batchDeleteRequest.setStorageKey("minio");
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey()); AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());
List<BatchDeleteRequest.DeleteItem> deleteItems = batchDeleteRequest.getDeleteItems();
int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems); int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList);
for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) { for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) {
boolean flag = false; boolean flag = false;
try { try {
if (deleteItem.getType() == FileTypeEnum.FILE) { if (deleteItem.getType() == FileTypeEnum.FILE) {
@ -470,34 +517,33 @@ public class NodesServiceImpl extends ServiceImpl<NodesMapper, Nodes> implements
deleteFailCount++; 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) { if (deleteSuccessCount >= 1) {
// 递归删除子节点 // 递归删除子节点
deleteChildren(nodes.getId(), nodes.getProjectId()); deleteChildren(nodes.getId(), nodes.getProjectId());
value = true; // 删除当前节点的文件
filesMapper.delete(
new QueryWrapper<Files>()
.eq("node_id", nodes.getId())
.eq("project_id", nodes.getProjectId())
);
// 删除当前节点
int deleteCount = nodesMapper.deleteById(id);
value = deleteCount > 0;
} else { } else {
value = false; value = false;
} }
return value; return value;
} }
/** /**
* 递归删除子节点 * 递归删除子节点
*
* @param parentId 父节点ID
*/ */
private void deleteChildren(String parentId, String projectId) { private void deleteChildren(String parentId, String projectId) {
// 查询当前节点的所有子节点
// 使用 QueryWrapper 查询当前节点的所有子节点
QueryWrapper<Nodes> queryWrapper = new QueryWrapper<>(); QueryWrapper<Nodes> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", parentId); queryWrapper.eq("parent_id", parentId);
queryWrapper.eq("project_id", projectId); queryWrapper.eq("project_id", projectId);
@ -505,14 +551,19 @@ public class NodesServiceImpl extends ServiceImpl<NodesMapper, Nodes> implements
// 递归删除每个子节点 // 递归删除每个子节点
for (Nodes child : children) { for (Nodes child : children) {
deleteChildren(child.getId(), child.getProjectId()); // 递归删除子节点的子节点 // 删除子节点下的文件
nodesMapper.deleteById(child.getId()); // 删除当前子节点
//批量文件的数据
QueryWrapper<Files> queryWrapper1 = new QueryWrapper<>(); QueryWrapper<Files> queryWrapper1 = new QueryWrapper<>();
queryWrapper1.eq("id", parentId); queryWrapper1.eq("node_id", child.getId()); // 使用子节点的 ID
queryWrapper1.eq("project_id", projectId); queryWrapper1.eq("project_id", projectId);
filesMapper.delete(queryWrapper1); filesMapper.delete(queryWrapper1);
// 递归删除子节点的子节点
deleteChildren(child.getId(), projectId);
// 删除当前子节点
nodesMapper.deleteById(child.getId());
} }
}
// //
// //
// //
@ -526,7 +577,6 @@ public class NodesServiceImpl extends ServiceImpl<NodesMapper, Nodes> implements
// deleteChildren(child.getId()); // 递归删除子节点的子节点 // deleteChildren(child.getId()); // 递归删除子节点的子节点
// nodesMapper.deleteById(child.getId()); // 删除当前子节点 // nodesMapper.deleteById(child.getId()); // 删除当前子节点
// } // }
}
} }

View File

@ -41,6 +41,7 @@ public class FileItemResult implements Serializable {
//用于对比Md5文件 //用于对比Md5文件
private String locatMd5; private String locatMd5;
private String minioMd5; private String minioMd5;
private String id;
/** /**
* 获取路径和名称的组合, 并移除重复的路径分隔符 /. * 获取路径和名称的组合, 并移除重复的路径分隔符 /.

View File

@ -55,9 +55,13 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
return s3FileList(folderPath); return s3FileList(folderPath);
} }
// @Override
// public List<FileItemResult> fileLists(String folderPath) {
// return s3FileLists(folderPath);
// }
@Override @Override
public List<FileItemResult> fileLists(String folderPath) { public List<FileItemResult> fileListData(String path, String name ) {
return s3FileLists(folderPath); return s3FileListData(path,name);
} }
/** /**
* 默认 S3 获取对象下载链接的方法, 如果指定了域名, 则替换为自定义域名. * 默认 S3 获取对象下载链接的方法, 如果指定了域名, 则替换为自定义域名.
@ -157,87 +161,190 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
return fileItemList; return fileItemList;
} }
public List<FileItemResult> s3FileLists(String path) { public List<FileItemResult> s3FileListData(String path, String name) {
List<FileItemResult> fileItemList = new ArrayList<>();
String bucketName = param.getBucketName(); String bucketName = param.getBucketName();
path = StringUtils.trimStartSlashes(path); // 去掉路径开头的斜杠 if(path == null || StringUtils.isEmpty(path)){
return fileItemList;
}
path = ensurePathWithSlash(path); // 确保路径以斜杠开头
String fullPath = StringUtils.trimStartSlashes(StringUtils.concat(param.getBasePath(), path, ZFileConstant.PATH_SEPARATOR)); String fullPath = StringUtils.trimStartSlashes(StringUtils.concat(param.getBasePath(), path, ZFileConstant.PATH_SEPARATOR));
List<FileItemResult> fileItemList = new ArrayList<>();
// 调用递归方法获取文件列表
listFilesInDirectory(bucketName, fullPath, path, fileItemList); // 新增 includeAll 参数控制是否包含所有子项
listFilesInDirectory(bucketName, fullPath, path, name, fileItemList, false);
return fileItemList; return fileItemList;
} }
private void listFilesInDirectory(String bucketName, String fullPath, String path, List<FileItemResult> fileItemList) { private void listFilesInDirectory(
String bucketName,
String fullPath,
String path,
String name,
List<FileItemResult> fileItemList,
boolean includeAll // 新增参数是否强制包含所有内容
) {
ListObjectsRequest listObjectsRequest = new ListObjectsRequest() ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
.withBucketName(bucketName) .withBucketName(bucketName)
.withPrefix(fullPath) // 设置前缀为当前路径 .withPrefix(fullPath)
.withMaxKeys(1000) // 每次最多返回 1000 个对象 .withMaxKeys(1000)
.withDelimiter("/"); // 使用 "/" 作为分隔符 .withDelimiter("/");
ObjectListing objectListing = s3Client.listObjects(listObjectsRequest); ObjectListing objectListing = s3Client.listObjects(listObjectsRequest);
boolean isFirstWhile = true; boolean isFirstWhile = true;
do { do {
if (!isFirstWhile) { if (!isFirstWhile) {
objectListing = s3Client.listNextBatchOfObjects(objectListing); // 处理分页 objectListing = s3Client.listNextBatchOfObjects(objectListing);
} }
// 处理文件 // 处理文件
for (S3ObjectSummary s : objectListing.getObjectSummaries()) { for (S3ObjectSummary s : objectListing.getObjectSummaries()) {
FileItemResult fileItemResult = new FileItemResult(); if (s.getKey().equals(fullPath)) continue;
// 跳过当前目录本身
if (s.getKey().equals(fullPath)) {
continue;
}
// 获取文件名并去除前导斜杠
String fileName = s.getKey().substring(fullPath.length()); String fileName = s.getKey().substring(fullPath.length());
if (fileName.startsWith(ZFileConstant.PATH_SEPARATOR)) { if (fileName.startsWith(ZFileConstant.PATH_SEPARATOR)) {
fileName = fileName.substring(1); // 去掉开头的斜杠 fileName = fileName.substring(1);
} }
fileItemResult.setName(fileName); // 包含条件强制包含 名称匹配
fileItemResult.setSize(s.getSize()); if (includeAll || StrUtil.isEmpty(name) || fileName.equals(name)) {
fileItemResult.setTime(s.getLastModified()); FileItemResult item = new FileItemResult();
fileItemResult.setType(FileTypeEnum.FILE); item.setName(fileName);
fileItemResult.setPath(path); // 当前路径 item.setSize(s.getSize());
item.setTime(s.getLastModified());
// 构造完整路径并生成下载 URL item.setType(FileTypeEnum.FILE);
String fullPathAndName = StringUtils.concat(path, fileItemResult.getName()); item.setPath(path); // 路径保持不变
fileItemResult.setUrl(getDownloadUrl(fullPathAndName)); item.setUrl(getDownloadUrl(ensurePathWithSlash(path) + fileName)); // 确保路径以斜杠开头
fileItemList.add(item);
fileItemList.add(fileItemResult); }
} }
// 处理文件夹 // 处理文件夹
for (String commonPrefix : objectListing.getCommonPrefixes()) { for (String commonPrefix : objectListing.getCommonPrefixes()) {
FileItemResult fileItemResult = new FileItemResult();
// 获取文件夹名称去掉前导路径并修正末尾斜杠
String folderName = commonPrefix.substring(fullPath.length(), commonPrefix.length() - 1); String folderName = commonPrefix.substring(fullPath.length(), commonPrefix.length() - 1);
if (StrUtil.isEmpty(folderName) || StrUtil.equals(folderName, StringUtils.DELIMITER_STR)) { if (StrUtil.isEmpty(folderName) || folderName.equals(StringUtils.DELIMITER_STR)) continue;
continue; // 跳过无效的文件夹名称
// 判断是否匹配名称或需要强制包含
boolean matchFolder = includeAll || StrUtil.isEmpty(name) || folderName.equals(name);
if (matchFolder) {
FileItemResult folderItem = new FileItemResult();
folderItem.setName(folderName);
folderItem.setType(FileTypeEnum.FOLDER);
folderItem.setPath(path); // 路径保持不变
fileItemList.add(folderItem);
} }
fileItemResult.setName(folderName); // 递归处理子文件夹如果匹配则强制包含子项
fileItemResult.setType(FileTypeEnum.FOLDER); String subPath = ensurePathWithSlash(path) + folderName + ZFileConstant.PATH_SEPARATOR; // 确保路径以斜杠开头并正确拼接
fileItemResult.setPath(path); // 当前路径 String subFullPath = commonPrefix;
fileItemList.add(fileItemResult); listFilesInDirectory(
bucketName,
// 递归处理子文件夹 subFullPath,
String subFolderPath = path + folderName + ZFileConstant.PATH_SEPARATOR; // 修正路径拼接 subPath,
String subFolderFullPath = commonPrefix; // 子文件夹的完整路径 name,
listFilesInDirectory(bucketName, subFolderFullPath, subFolderPath, fileItemList); fileItemList,
matchFolder // 关键修改如果父文件夹匹配则强制包含所有子项
);
} }
isFirstWhile = false; isFirstWhile = false;
} while (objectListing.isTruncated()); // 处理分页 } while (objectListing.isTruncated());
} }
// 确保路径以斜杠开头
private String ensurePathWithSlash(String path) {
if (path.startsWith(ZFileConstant.PATH_SEPARATOR)) {
return path;
} else {
return ZFileConstant.PATH_SEPARATOR + path;
}
}
//这个方法有用 获取指定路径下的所有的文件以及文件夹
// public List<FileItemResult> s3FileLists(String path) {
// String bucketName = param.getBucketName();
// path = StringUtils.trimStartSlashes(path); // 去掉路径开头的斜杠
// String fullPath = StringUtils.trimStartSlashes(StringUtils.concat(param.getBasePath(), path, ZFileConstant.PATH_SEPARATOR));
//
// List<FileItemResult> fileItemList = new ArrayList<>();
//
// // 调用递归方法获取文件列表
// listFilesInDirectory(bucketName, fullPath, path, fileItemList);
//
// return fileItemList;
// }
//
// private void listFilesInDirectory(String bucketName, String fullPath, String path, List<FileItemResult> 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 @Override
public FileItemResult getFileItem(String pathAndName) { public FileItemResult getFileItem(String pathAndName) {

View File

@ -262,15 +262,30 @@ public interface BaseFileService {
*/ */
S3Object getObject(String bucketName, String key); S3Object getObject(String bucketName, String key);
/*** // /***
// * 获取指定路径下的文件及文件夹
// *
// * @param folderPath
// * 文件夹路径
// *
// * @return 文件及文件夹列表
// *
// * @throws Exception 获取文件列表中出现的异常 /
// */
// List<FileItemResult> fileLists(String folderPath) throws Exception;
/***
* 获取指定路径下的文件及文件夹 * 获取指定路径下的文件及文件夹
* *
* @param folderPath * @param path
* 文件夹路径 * 文件或者文件夹路径
* @param name
* 文件或者文件夹名称
* *
* @return 文件及文件夹列表 * @return 文件及文件夹列表
* *
* @throws Exception 获取文件列表中出现的异常 * @throws Exception 获取文件列表中出现的异常 /
*/ */
List<FileItemResult> fileLists(String folderPath) throws Exception; List<FileItemResult> fileListData(String path, String name) throws Exception;
} }

View File

@ -29,6 +29,7 @@ import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@ -57,8 +58,6 @@ public class LocalServiceImpl extends AbstractProxyTransferService<LocalParam> {
} }
@Override @Override
public List<FileItemResult> fileList(String folderPath) throws FileNotFoundException { public List<FileItemResult> fileList(String folderPath) throws FileNotFoundException {
checkPathSecurity(folderPath); checkPathSecurity(folderPath);
@ -178,85 +177,164 @@ public class LocalServiceImpl extends AbstractProxyTransferService<LocalParam> {
public ObjectMetadata getObjectMetadata(String name, String path) { public ObjectMetadata getObjectMetadata(String name, String path) {
return null; return null;
} }
@Override @Override
public S3Object getObject(String bucketName, String key) { public S3Object getObject(String bucketName, String key) {
return null; return null;
} }
@Override @Override
public List<FileItemResult> fileLists(String folderPath) throws Exception { public List<FileItemResult> fileListData(String folderPath, String name) throws Exception {
checkPathSecurity(folderPath); checkPathSecurity(folderPath);
List<FileItemResult> fileItemList = new ArrayList<>(); List<FileItemResult> resultList = new ArrayList<>();
String basePath = param.getFilePath();
String fullPath = StringUtils.concat(param.getFilePath() + folderPath); // 处理根目录特殊情况
String fullPath = folderPath.equals("/")
? Paths.get(basePath).toString()
: Paths.get(basePath, folderPath).toString();
File file = new File(fullPath); File targetDir = new File(fullPath);
if (!file.exists()) { if (!targetDir.exists()) {
throw new FileNotFoundException("文件不存在"); throw new FileNotFoundException("路径不存在: " + fullPath);
} }
// 调用递归方法处理文件夹及其内容跳过第一个文件夹 // 不添加目标文件夹本身到结果列表与示例数据结构一致
listFilesInDirectory(file, folderPath, fileItemList, true); // resultList.add(convertToFileItem(targetDir, folderPath));
return fileItemList; // 列出目标目录内容
if (targetDir.isDirectory()) {
String effectiveParentPath = folderPath.endsWith("/")
? folderPath
: folderPath + "/";
listFilesInDirectory(targetDir, effectiveParentPath, resultList);
}
return resultList;
} }
private void listFilesInDirectory(File file, String folderPath, List<FileItemResult> fileItemList, boolean skipFirstFolder) throws IOException { /**
// 跳过第一个文件夹folderPath从它下面的内容开始处理 * 递归列出文件夹下的所有内容
if (skipFirstFolder) { */
// 如果当前文件是 folderPath, 直接跳过开始处理它的子文件夹 private void listFilesInDirectory(File directory, String parentPath, List<FileItemResult> resultList) {
if (file.isDirectory()) { File[] files = directory.listFiles();
File[] files = file.listFiles(); if (files == null) return;
if (files != null) {
for (File f : files) {
// 递归进入下一级文件夹路径应该是 folderPath + 当前文件夹名
listFilesInDirectory(f, folderPath, fileItemList, false);
}
}
}
} else {
// 处理当前的文件夹或文件
if (file.isDirectory()) {
// 对于文件夹路径是 folderPath
fileItemList.add(fileToFileItems(file, folderPath));
// 递归处理当前文件夹内部的文件和文件夹 for (File file : files) {
File[] files = file.listFiles(); // 创建文件项父路径不带文件名
if (files != null) { FileItemResult item = convertToFileItem(file, parentPath);
for (File f : files) { resultList.add(item);
// 递归进入下一级文件夹路径是 folderPath + 当前文件夹名
String newPath = folderPath + file.getName(); // 递归处理子目录
listFilesInDirectory(f, newPath, fileItemList, false); if (file.isDirectory()) {
} String newParentPath = parentPath + file.getName() + "/";
} listFilesInDirectory(file, newParentPath, resultList);
} 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 { private FileItemResult convertToFileItem(File file, String parentPath) {
result.setType(FileTypeEnum.FILE); FileItemResult item = new FileItemResult();
} item.setName(file.getName());
item.setPath(formatPath(parentPath)); // 父路径不带当前文件名
result.setSize(file.length()); item.setType(file.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
// 设置文件的修改时间为 Date 类型 item.setSize(file.isDirectory() ? 0 : file.length());
result.setTime(new Date(file.lastModified())); item.setTime(new Date(file.lastModified()));
return item;
return result;
} }
/**
* 统一格式化路径
*/
private String formatPath(String path) {
// 保证路径以/开头且不以/结尾示例数据结构风格
path = path.replaceAll("/+", "/");
if (!path.startsWith("/")) path = "/" + path;
if (path.endsWith("/") && path.length() > 1) path = path.substring(0, path.length()-1);
return path;
}
//以下是通过路径获取所有的文件以及文件夹
// @Override
// public List<FileItemResult> fileLists(String folderPath) throws Exception {
// checkPathSecurity(folderPath);
//
// List<FileItemResult> 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<FileItemResult> 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 @Override
public void uploadFile(String pathAndName, InputStream inputStream) { public void uploadFile(String pathAndName, InputStream inputStream) {
@ -333,12 +411,10 @@ public class LocalServiceImpl extends AbstractProxyTransferService<LocalParam> {
/** /**
* 检查路径合法性 * 检查路径合法性
* - 只有以 . 开头的允许通过其他的如 ./ ../ 的都是非法获取上层文件夹内容的路径. * - 只有以 . 开头的允许通过其他的如 ./ ../ 的都是非法获取上层文件夹内容的路径.
* *
* @param paths * @param paths 文件路径
* 文件路径 * @throws IllegalArgumentException 文件路径包含非法字符时会抛出此异常
*
* @throws IllegalArgumentException 文件路径包含非法字符时会抛出此异常
*/ */
private static void checkPathSecurity(String... paths) { private static void checkPathSecurity(String... paths) {
for (String path : paths) { for (String path : paths) {
@ -352,12 +428,10 @@ public class LocalServiceImpl extends AbstractProxyTransferService<LocalParam> {
/** /**
* 检查路径合法性 * 检查路径合法性
* - 不为空且不包含 \ / 字符 * - 不为空且不包含 \ / 字符
* *
* @param names * @param names 文件路径
* 文件路径 * @throws IllegalArgumentException 文件名包含非法字符时会抛出此异常
*
* @throws IllegalArgumentException 文件名包含非法字符时会抛出此异常
*/ */
private static void checkNameSecurity(String... names) { private static void checkNameSecurity(String... names) {
for (String name : names) { for (String name : names) {

View File

@ -66,10 +66,10 @@ public class UserController {
@ApiOperation("查询用户信息") @ApiOperation("查询用户信息")
@ResponseBody @ResponseBody
public ResponseResult queryUsers(String orgid, public ResponseResult queryUsers(String orgid,
String username, String institutionId, Page<SysUser> page) { String nickname, Page<SysUser> page) {
Page<Map<String, Object>> mapPage = userService.queryUsers(orgid, Page<Map<String, Object>> mapPage = userService.queryUsers(orgid,
username, institutionId, page); nickname, page);
return ResponseResult.successData(mapPage); return ResponseResult.successData(mapPage);
} }

View File

@ -90,7 +90,7 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
boolean delInRoleUsersByUserid(@Param("userid") String userid,@Param("roleids")String[] roleids); boolean delInRoleUsersByUserid(@Param("userid") String userid,@Param("roleids")String[] roleids);
Page<Map<String, Object>> queryUsers(String orgid, Page<Map<String, Object>> queryUsers(String orgid,
String username,String institutionId, String nickname,
Page<SysUser> page); Page<SysUser> page);
Map<String, String> getOrganizationByid(String id); Map<String, String> getOrganizationByid(String id);

View File

@ -128,7 +128,7 @@ public interface IUserService extends IService<SysUser> {
boolean addUserRoles(String roleid, String userid); boolean addUserRoles(String roleid, String userid);
//Page<SysUser> queryUsers(String orgid, String username, Page<SysUser> page); //Page<SysUser> queryUsers(String orgid, String username, Page<SysUser> page);
Page<Map<String,Object>> queryUsers(String orgid, String username,String institutionId, Page<SysUser> page); Page<Map<String,Object>> queryUsers(String orgid, String nickname, Page<SysUser> page);
/*********************************** /***********************************
* 用途说明根据ID批量删除用户 * 用途说明根据ID批量删除用户

View File

@ -474,32 +474,28 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
@Override @Override
public Page<Map<String, Object>> queryUsers(String orgid, public Page<Map<String, Object>> queryUsers(String orgid,
String username, String nickname,
String institutionId, Page<SysUser> page) {
Page<SysUser> page) { //增加权限控制usertype:0-超级管理员 1-系统管理员 2-普通用户
if(StrUtil.isBlank(institutionId)){ Page<Map<String, Object>> mapPage = null;
institutionId=getUserInfo().getInstitutionId(); SysUser currentUser=this.getUserInfo();
} int usertype=currentUser.getUsertype();
Page<Map<String, Object>> mapPage = sysUserMapper.queryUsers(orgid, if(usertype==0 ||usertype==1) { //管理员才能查询用户数据
username, institutionId, page); mapPage = sysUserMapper.queryUsers(orgid,
List<Map<String, Object>> list = new ArrayList<>(); nickname, page);
List<Map<String, Object>> records = mapPage.getRecords(); List<Map<String, Object>> list = new ArrayList<>();
List<Map<String, Object>> records = mapPage.getRecords();
// String institutionId=getUserInfo().getInstitutionId(); for (Map<String, Object> record : records) {
for (Map<String, Object> record : records) { String id = (String) record.get("id");
String id = (String) record.get("id"); List<SysRole> sysRoles = sysRoleMapper.getRoleByUserId(id);
List<SysRole> sysRoles = sysRoleMapper.getRoleByUserId(id); record.put("roles", sysRoles);
record.put("roles", sysRoles);
if(StrUtil.isNotEmpty(institutionId)){
if(ObjUtil.isNotEmpty(record.get("institution_id"))&&record.get("institution_id").toString().equals(institutionId)){
list.add(record);
}
}else{
list.add(record); list.add(record);
} }
mapPage.setRecords(list);
return mapPage;
}else{
return mapPage;
} }
mapPage.setRecords(list);
return mapPage;
} }
/*********************************** /***********************************

View File

@ -57,9 +57,6 @@
u.usertype, u.usertype,
u.username, u.username,
u.nickname, u.nickname,
u.institution_id,
institution.name as institution_name,
u.institution_type,
u.sex, u.sex,
u.email, u.email,
u.phone, u.phone,
@ -70,19 +67,13 @@
u.lastmodifydate u.lastmodifydate
FROM FROM
sys_user u sys_user u
LEFT JOIN sys_vision_institution institution ON institution.id = u.institution_id
where 1=1 where 1=1
and u.usertype != 0 and u.usertype != 0
<if test="orgid != null and orgid != ''"> <if test="orgid != null">
and u.orgid = #{orgid} and u.orgid = #{orgid}
</if> </if>
<if test="nickname != null">
<if test="username != null and username != ''"> and u.nickname like concat('%',#{nickname},'%')
and u.nickname like concat('%', #{username},'%')
</if>
<if test="institutionId != null and institutionId != ''">
and u.institution_id = #{institutionId}
</if> </if>
ORDER BY u.lastmodifydate DESC ORDER BY u.lastmodifydate DESC
</select> </select>