This commit is contained in:
wanxiaoli 2026-01-07 14:11:03 +08:00
commit 9e889790fd
25 changed files with 2086 additions and 203 deletions

6
data/common_items.json Normal file
View File

@ -0,0 +1,6 @@
{
"taskLocation" : [ "浙江横店", "陕西宝鸡", "甘肃兰州", "标签1", "标签2", "标签3", "标签4" ],
"aircraftName" : [ "载机名称一", "载机名称二", "载机名称三" ],
"sensorDescription" : [ "电阻式传感器", "电容式‌传感器", "电容式‌传感器二" ],
"taskLabel" : [ "试验标签一", "试验标签二", "试验标签三", "试验标签四", "试验标签五" ]
}

View File

@ -12,7 +12,7 @@
<groupId>com.yfd</groupId>
<artifactId>filesmanagesystem</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<packaging>war</packaging>
<name>filesmanagesystem</name>
<description>文件管理系统</description>
<properties>
@ -26,6 +26,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 配置Tomcat依赖作用域为provided支持war包部署到外部Tomcat容器
同时不影响jar包的内嵌Tomcat运行方式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- 生成二维码 -->
<dependency>

View File

@ -0,0 +1,191 @@
package com.yfd.platform.modules.experimentalData.controller;
import com.yfd.platform.modules.experimentalData.dto.ItemReq;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@RestController
@RequestMapping("/api/common-items")
public class CommonItemController {
private static final String FILE_NAME = "common_items.json";
private static final String DATA_DIR = "data";
private final ObjectMapper objectMapper = new ObjectMapper();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/** 查询 */
@GetMapping
public Map<String, Object> get() throws IOException {
return success(readData());
}
/** 新增 */
@PostMapping
public Map<String, Object> add(@RequestBody ItemReq req) throws IOException {
validate(req);
lock.writeLock().lock();
try {
Map<String, List<String>> data = readData();
List<String> list = data.computeIfAbsent(req.getType(), k -> new ArrayList<>());
if (!list.contains(req.getLabel())) {
list.add(req.getLabel());
}
writeData(data);
Map<String, Object> result = new HashMap<>();
result.put(req.getType(), list);
return success(result);
} finally {
lock.writeLock().unlock();
}
}
/** 新增多个标签 */
@PostMapping("/batch")
public Map<String, Object> addBatch(@RequestBody ItemReq req) throws IOException {
validateForBatch(req);
lock.writeLock().lock();
try {
Map<String, List<String>> data = readData();
List<String> list = data.computeIfAbsent(req.getType(), k -> new ArrayList<>());
// 批量添加不重复的标签
for (String label : req.getLabels()) {
if (!list.contains(label)) {
list.add(label);
}
}
writeData(data);
Map<String, Object> result = new HashMap<>();
result.put(req.getType(), list);
return success(result);
} finally {
lock.writeLock().unlock();
}
}
/** 删除 */
@DeleteMapping
public Map<String, Object> delete(@RequestBody ItemReq req) throws IOException {
validate(req);
lock.writeLock().lock();
try {
Map<String, List<String>> data = readData();
List<String> list = data.get(req.getType());
if (list != null) {
list.remove(req.getLabel());
}
writeData(data);
Map<String, Object> result = new HashMap<>();
result.put(req.getType(), list != null ? list : Collections.emptyList());
return success(result);
} finally {
lock.writeLock().unlock();
}
}
/* ================== 工具方法 ================== */
private void validate(ItemReq req) {
if (req == null
|| (!"taskLocation".equals(req.getType()) && !"aircraftName".equals(req.getType())
&& !"sensorDescription".equals(req.getType()) && !"taskLabel".equals(req.getType()))
|| req.getLabel() == null || req.getLabel().trim().isEmpty()) {
throw new IllegalArgumentException("参数无效");
}
}
private void validateForBatch(ItemReq req) {
if (req == null
|| (!"taskLocation".equals(req.getType()) && !"aircraftName".equals(req.getType())
&& !"sensorDescription".equals(req.getType()) && !"taskLabel".equals(req.getType()))
|| req.getLabels() == null || req.getLabels().isEmpty()) {
throw new IllegalArgumentException("参数无效");
}
}
private Map<String, List<String>> readData() throws IOException {
// 获取项目根目录下的data文件夹路径
String userDir = System.getProperty("user.dir");
Path dataDir = Paths.get(userDir, DATA_DIR);
Path filePath = dataDir.resolve(FILE_NAME);
if (!Files.exists(filePath)) {
// 如果项目根目录下没有文件创建默认数据
System.out.println("文件不存在,创建默认数据: " + filePath.toString());
Files.createDirectories(dataDir);
Map<String, List<String>> init = new HashMap<>();
init.put("taskLocation", new ArrayList<>());
init.put("aircraftName", new ArrayList<>());
init.put("sensorDescription", new ArrayList<>());
init.put("taskLabel", new ArrayList<>());
writeData(init);
return init;
}
// 文件存在读取数据
try {
System.out.println("正在读取文件: " + filePath.toString());
Map<String, List<String>> data = objectMapper.readValue(
filePath.toFile(),
new TypeReference<Map<String, List<String>>>() {}
);
System.out.println("文件读取成功,数据大小: " + data.size());
return data;
} catch (IOException e) {
// 如果读取失败返回默认数据
System.err.println("读取文件失败: " + e.getMessage());
Map<String, List<String>> init = new HashMap<>();
init.put("taskLocation", new ArrayList<>());
init.put("aircraftName", new ArrayList<>());
init.put("sensorDescription", new ArrayList<>());
init.put("taskLabel", new ArrayList<>());
return init;
}
}
private void writeData(Map<String, List<String>> data) throws IOException {
String userDir = System.getProperty("user.dir");
Path dataDir = Paths.get(userDir, DATA_DIR);
Path filePath = dataDir.resolve(FILE_NAME);
Files.createDirectories(dataDir);
System.out.println("正在写入文件: " + filePath.toString());
objectMapper.writerWithDefaultPrettyPrinter()
.writeValue(filePath.toFile(), data);
System.out.println("文件写入成功");
}
private Map<String, Object> success(Object data) {
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("data", data);
return result;
}
}

View File

@ -11,11 +11,16 @@ 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.utils.StringUtils;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@ -37,6 +42,8 @@ import java.util.Map;
@RequestMapping("/experimentalData/tstask")
public class TsTaskController {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
//试验任务服务类
@Resource
private ITsTaskService tsTaskService;
@ -214,4 +221,125 @@ public class TsTaskController {
List<TsTask> tsTasks = tsTaskService.listTsTask();
return ResponseResult.successData(tsTasks);
}
@Log(module = "试验数据管理", value = "导出试验任务SQL数据")
@PostMapping("/exportTaskSql")
@ApiOperation("导出试验任务SQL数据")
@PreAuthorize("@el.check('export:tsTask')")
public void exportTaskSql(@RequestParam String taskId, HttpServletResponse response) {
if (StrUtil.isBlank(taskId)) {
throw new RuntimeException("任务ID不能为空");
}
try {
// 调用服务层执行导出操作
byte[] zipData = tsTaskService.exportTaskSqlAsZip(taskId);
// 添加日志记录ZIP数据大小
LOGGER.info("生成的ZIP文件大小: {} 字节", zipData.length);
// 验证ZIP数据是否有效
if (zipData.length == 0) {
throw new RuntimeException("生成的ZIP数据为空");
}
// 获取任务信息用于文件名
TsTask task = tsTaskService.getById(taskId);
String taskCode = "";
// 确保taskCode有值且符合5位数字格式
if (task != null && StrUtil.isNotBlank(task.getTaskCode())) {
taskCode = task.getTaskCode();
// 验证taskCode格式是否正确5位数字
if (!taskCode.matches("^\\d{5}$")) {
// 如果不是5位数字格式需要处理
try {
int codeValue = Integer.parseInt(taskCode);
if (codeValue >= 1 && codeValue <= 99999) {
// 格式化为5位数字字符串
taskCode = String.format("%05d", codeValue);
} else {
throw new IllegalArgumentException("该任务编号超出有效范围: " + taskCode);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("无效的任务编号格式: " + taskCode);
}
}
}
// 设置响应头信息
String fileName = taskCode + "_exportdata.zip";
response.setContentType("application/zip");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.setContentLength(zipData.length);
// 写入响应流
response.getOutputStream().write(zipData);
response.getOutputStream().flush();
LOGGER.info("ZIP文件导出成功文件名: {}, 大小: {} 字节", fileName, zipData.length);
} catch (Exception e) {
LOGGER.error("导出试验任务SQL数据失败", e);
throw new RuntimeException("导出失败:" + e.getMessage());
}
}
/**********************************
* 用途说明: 导入ZIP文件中的试验任务SQL数据
* 参数说明:
* file ZIP文件
* taskCode 任务编号
* localStorageId 本地存储空间标识
* backupStorageId 备份空间标识
* 返回值说明: ResponseResult 返回导入结果
***********************************/
@Log(module = "试验数据管理", value = "导入试验任务SQL数据")
@PostMapping("/importTaskSql")
@ApiOperation("导入试验任务SQL数据")
@PreAuthorize("@el.check('import:tsTask')")
public ResponseResult importTaskSql(@RequestParam("file") MultipartFile file,
@RequestParam("taskCode") String taskCode,
@RequestParam("localStorageId") int localStorageId,
@RequestParam("backupStorageId") int backupStorageId) {
if (file.isEmpty() || StrUtil.isBlank(taskCode)) {
return ResponseResult.error("文件或任务编号不能为空");
}
try {
// 调用服务层执行导入操作
boolean result = tsTaskService.importTaskSqlFromZip(file, taskCode, localStorageId, backupStorageId);
if (result) {
return ResponseResult.success("导入成功");
} else {
return ResponseResult.error("导入失败");
}
} catch (Exception e) {
//log.error("导入试验任务SQL数据失败", e);
return ResponseResult.error("导入失败:" + e.getMessage());
}
}
/**********************************
* 用途说明: 获取最大的任务编号+1
* 参数说明:
* 返回值说明: ResponseResult 返回最大任务编号+1
***********************************/
@Log(module = "试验数据管理", value = "获取最大任务编号")
@GetMapping("/getMaxTaskCode")
@ApiOperation("获取最大任务编号")
// @PreAuthorize("@el.check('select:tsTask')")
public ResponseResult getMaxTaskCode() {
try {
String maxTaskCode = tsTaskService.getMaxTaskCode();
return ResponseResult.successData(maxTaskCode);
} catch (Exception e) {
//log.error("获取最大任务编号失败", e);
return ResponseResult.error("获取最大任务编号失败:" + e.getMessage());
}
}
}

View File

@ -0,0 +1,33 @@
package com.yfd.platform.modules.experimentalData.dto;
import java.util.List;
public class ItemReq {
private String type; // treatment | suggestion
private String label; // 内容
private List<String> labels; // 多个标签
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public List<String> getLabels() {
return labels;
}
public void setLabels(List<String> labels) {
this.labels = labels;
}
}

View File

@ -13,4 +13,10 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface TsTaskMapper extends BaseMapper<TsTask> {
/**
* 查询最大的任务编号
* @return 最大任务编号
*/
String selectMaxTaskCode();
}

View File

@ -264,4 +264,12 @@ public interface ITsFilesService extends IService<TsFiles> {
* 返回值说明: com.yfd.platform.config.ResponseResult 返回文件集合列表
***********************************/
Object listTsFilesById(String id, String taskId, String nodeId);
/**
* 根据任务ID获取文件列表
* @param taskId 任务ID
* @return 文件列表
*/
List<TsFiles> getByTaskId(String taskId);
}

View File

@ -65,4 +65,12 @@ public interface ITsNodesService extends IService<TsNodes> {
* 返回值说明: com.yfd.platform.config.ResponseResult 返回删除成功或者失败
***********************************/
Object confirmDeleteNodes(String id);
/**
* 根据任务ID获取节点列表
* @param taskId 任务ID
* @return 节点列表
*/
List<TsNodes> getByTaskId(String taskId);
}

View File

@ -3,6 +3,7 @@ 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;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
@ -70,4 +71,27 @@ public interface ITsTaskService extends IService<TsTask> {
List<TsTask> listTsTask();
/**
* 导出试验任务相关SQL数据为ZIP文件
* @param taskId 任务ID
* @return ZIP文件字节数组
*/
byte[] exportTaskSqlAsZip(String taskId) throws IOException;
/**
* 从ZIP文件导入试验任务SQL数据
* @param file ZIP文件
* @param taskCode 任务编号
* @param localStorageId 本地存储空间标识
* @param backupStorageId 备份空间标识
* @return 是否导入成功
*/
boolean importTaskSqlFromZip(MultipartFile file, String taskCode, int localStorageId, int backupStorageId) throws IOException;
/**
* 获取最大任务编号+1
* @return 最大任务编号+1
*/
String getMaxTaskCode();
}

View File

@ -253,6 +253,8 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
FileItemResult fileItemResult = fileService.getFileItem(path);
if (fileItemResult == null || fileItemResult.getName() == null) {
LOGGER.error("{}文件没有上传到工作空间,请重新选择上传", fileNameData);
// 跳过处理这个文件避免空指针异常
continue;
}
tsFiles.setUrl(fileItemResult.getUrl());
//如果是压缩文件 类型就给zip
@ -260,7 +262,12 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
if (isValid) {
tsFiles.setType("ZIP");
} else {
tsFiles.setType(fileItemResult.getType().getValue());
// 添加空值检查防止空指针异常
if (fileItemResult.getType() != null) {
tsFiles.setType(fileItemResult.getType().getValue());
} else {
tsFiles.setType("UNKNOWN");
}
}
if (tsFiles.getUpdateTime() == null) {
tsFiles.setUpdateTime(tsFiles.getUploadTime());
@ -6275,6 +6282,12 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
return String.format("%s,%s,%s,%s", parts[0], parts[1], parts[2], parts[3]);
}
@Override
public List<TsFiles> getByTaskId(String taskId) {
LambdaQueryWrapper<TsFiles> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TsFiles::getTaskId, taskId);
return this.list(queryWrapper);
}
}

View File

@ -38,6 +38,7 @@ import com.yfd.platform.system.domain.LoginUser;
import com.yfd.platform.utils.StringUtils;
import com.yfd.platform.utils.TableNameContextHolder;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.val;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -46,6 +47,7 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@ -70,6 +72,8 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
private static final int BATCH_SIZE = 5000;
//试验任务节点表 Mapper
@Resource
private TsNodesMapper tsNodesMapper;
@ -109,53 +113,178 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
@Override
public List<Map<String, Object>> getTsNodesTree(String nodeName, String taskId) {
TsTask tsTask = tsTaskMapper.selectOne(new QueryWrapper<TsTask>().eq("id", taskId));
// 查询所有节点数据
List<Map<String, Object>> result = new ArrayList<>();
// 查询所有节点数据如果taskId为空查询所有任务节点
List<Map<String, Object>> allNodes = getAllNodes(taskId);
// 查找所有根节点parentId为"00"的节点
List<Map<String, Object>> rootNodes = findRootNodes(allNodes, taskId);
// 根节点的基本路径/项目名称/
String basePath = "/" + tsTask.getTaskName() + "/";
// 存储最终结果
List<Map<String, Object>> result = new ArrayList<>();
Map<String, Object> rootNodeData = new HashMap<>();
rootNodeData.put("nodeName", "根节点");
rootNodeData.put("path", "/"+tsTask.getTaskName()+"/");
rootNodeData.put("nodeId", tsTask.getId());
rootNodeData.put("nodeOrder", "0");
rootNodeData.put("taskId", tsTask.getId());
rootNodeData.put("parentId", "00");
result.add(rootNodeData);
// 如果 nodeName 为空返回所有根节点的完整树形结构
if (StringUtils.isEmpty(nodeName)) {
if (!rootNodes.isEmpty()) {
for (Map<String, Object> rootNode : rootNodes) {
rootNode.put("path", basePath);
result.addAll(buildFullTree(rootNode, allNodes));
// 如果taskId为空根据nodeName直接过滤
if (StringUtils.isEmpty(taskId)) {
if (StringUtils.isEmpty(nodeName)) {
// 返回所有节点需要按任务分组构建树
// return buildAllTaskTrees(allNodes);
return result;
} else {
// 根据nodeName查询所有匹配节点构建到根节点的路径
return findNodesByName(allNodes, nodeName);
}
} else {
// 原有逻辑taskId不为空时按任务查询
TsTask tsTask = tsTaskMapper.selectOne(new QueryWrapper<TsTask>().eq("id", taskId));
if (tsTask == null) {
throw new RuntimeException("任务不存在");
}
return result;
}
// 否则返回从根节点到目标节点的树形结构
if (!rootNodes.isEmpty()) {
for (Map<String, Object> rootNode : rootNodes) {
rootNode.put("path", basePath);
List<Map<String, Object>> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName);
if (!tree.isEmpty()) {
List<Map<String, Object>> rootNodes = findRootNodes(allNodes, taskId);
String basePath = "/" + tsTask.getTaskName() + "/";
Map<String, Object> rootNodeData = new HashMap<>();
rootNodeData.put("nodeName", "根节点");
rootNodeData.put("path", basePath);
rootNodeData.put("nodeId", tsTask.getId());
rootNodeData.put("nodeOrder", "0");
rootNodeData.put("taskId", tsTask.getId());
rootNodeData.put("taskName", tsTask.getTaskName());
rootNodeData.put("parentId", "00");
result.add(rootNodeData);
if (StringUtils.isEmpty(nodeName)) {
for (Map<String, Object> rootNode : rootNodes) {
rootNode.put("path", basePath);
result.addAll(buildFullTree(rootNode, allNodes));
}
} else {
for (Map<String, Object> rootNode : rootNodes) {
rootNode.put("path", basePath);
List<Map<String, Object>> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName);
result.addAll(tree);
}
}
return result;
}
}
/**
* 根据节点名称查找所有匹配节点并构建树
*/
private List<Map<String, Object>> findNodesByName(List<Map<String, Object>> allNodes, String nodeName) {
List<Map<String, Object>> result = new ArrayList<>();
// 查找所有匹配的节点
List<Map<String, Object>> matchedNodes = allNodes.stream()
.filter(node -> {
Object nodeNameObj = node.get("nodeName");
if (nodeNameObj instanceof String) {
String currentName = (String) nodeNameObj;
return currentName.toLowerCase().contains(nodeName.toLowerCase());
}
return false;
})
.collect(Collectors.toList());
// 为每个匹配节点构建到根节点的路径
for (Map<String, Object> matchedNode : matchedNodes) {
// 找到该节点的完整路径
List<Map<String, Object>> pathToRoot = findPathToRoot(matchedNode, allNodes);
if (!pathToRoot.isEmpty()) {
result.addAll(pathToRoot);
}
}
// 返回结果
return result;
}
/**
* 查找节点到根节点的路径
*/
private List<Map<String, Object>> findPathToRoot(Map<String, Object> node, List<Map<String, Object>> allNodes) {
List<Map<String, Object>> path = new ArrayList<>();
Map<String, Object> currentNode = node;
// 向上查找直到根节点
while (currentNode != null) {
path.add(0, new HashMap<>(currentNode)); // 添加到路径开头
String parentId = (String) currentNode.get("parentId");
if ("00".equals(parentId)) {
// 找到根节点添加任务信息
String taskId = (String) currentNode.get("taskId");
TsTask task = tsTaskMapper.selectOne(new QueryWrapper<TsTask>().eq("id", taskId));
if (task != null) {
Map<String, Object> taskNode = new HashMap<>();
taskNode.put("nodeName", "根节点");
taskNode.put("path", "/" + task.getTaskName() + "/");
taskNode.put("nodeId", task.getId());
taskNode.put("nodeOrder", "0");
taskNode.put("taskId", task.getId());
taskNode.put("taskName", task.getTaskName());
taskNode.put("parentId", "00");
path.add(0, taskNode);
}
break;
}
// 查找父节点
String finalParentId = parentId;
currentNode = allNodes.stream()
.filter(n -> finalParentId.equals(n.get("nodeId")))
.findFirst()
.orElse(null);
}
return path;
}
// public List<Map<String, Object>> getTsNodesTree(String nodeName, String taskId) {
// TsTask tsTask = tsTaskMapper.selectOne(new QueryWrapper<TsTask>().eq("id", taskId));
// // 查询所有节点数据
// List<Map<String, Object>> allNodes = getAllNodes(taskId);
//
// // 查找所有根节点parentId为"00"的节点
// List<Map<String, Object>> rootNodes = findRootNodes(allNodes, taskId);
//
// // 根节点的基本路径/项目名称/
// String basePath = "/" + tsTask.getTaskName() + "/";
//
// // 存储最终结果
// List<Map<String, Object>> result = new ArrayList<>();
// Map<String, Object> rootNodeData = new HashMap<>();
// rootNodeData.put("nodeName", "根节点");
// rootNodeData.put("path", "/"+tsTask.getTaskName()+"/");
// rootNodeData.put("nodeId", tsTask.getId());
// rootNodeData.put("nodeOrder", "0");
// rootNodeData.put("taskId", tsTask.getId());
// rootNodeData.put("parentId", "00");
// result.add(rootNodeData);
//
// // 如果 nodeName 为空返回所有根节点的完整树形结构
// if (StringUtils.isEmpty(nodeName)) {
// if (!rootNodes.isEmpty()) {
// for (Map<String, Object> rootNode : rootNodes) {
// rootNode.put("path", basePath);
// result.addAll(buildFullTree(rootNode, allNodes));
// }
// }
// return result;
// }
//
// // 否则返回从根节点到目标节点的树形结构
// if (!rootNodes.isEmpty()) {
// for (Map<String, Object> rootNode : rootNodes) {
// rootNode.put("path", basePath);
// List<Map<String, Object>> tree = buildTreeToTargetNode(rootNode, allNodes, nodeName);
// if (!tree.isEmpty()) {
// result.addAll(tree);
// }
// }
// }
//
// // 返回结果
// return result;
// }
/**
* 查询所有节点数据
*
@ -682,7 +811,10 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
} else {
deleteFailCount++;
}
} catch (Exception e) {
}catch (NullPointerException e) {
LOGGER.error("删除节点时发生空指针异常,文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e);
deleteFailCount++;
}catch (Exception e) {
LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e);
deleteFailCount++;
}
@ -723,6 +855,7 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
taskStatusHolder.finishTask(asyncKey);
WebSocketServer.sendMessageTo("试验数据扫描接口完成", "taskId_" + id);
}
}
/**********************************
@ -786,6 +919,15 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
return "扫描完成";
} catch (Exception e) {
LOGGER.error("执行试验数据扫描时发生未知异常: {}", e.getMessage(), e);
//异常的时候删除节点和文件表
LambdaQueryWrapper<TsNodes> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TsNodes::getTaskId, id);
tsNodesMapper.delete(queryWrapper);
LambdaQueryWrapper<TsFiles> queryWrapperTsFile = new LambdaQueryWrapper<>();
queryWrapperTsFile.eq(TsFiles::getTaskId, id);
tsFilesMapper.delete(queryWrapperTsFile);
return "扫描失败:" + e.getMessage();
} finally {
TableNameContextHolder.clear();
@ -975,12 +1117,18 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
// 批量插入文件表忽略重复
if (!tsFilesToCreate.isEmpty()) {
long startBatchInsertFiles = System.currentTimeMillis();
int affectedRowsFiles = tsFilesMapper.batchInsertTsFiles(tsFilesToCreate);
//int affectedRowsFiles = tsFilesMapper.batchInsertTsFiles(tsFilesToCreate);
int affected = 0;
for (int i = 0; i < tsFilesToCreate.size(); i += BATCH_SIZE) {
List<TsFiles> sub =
new ArrayList<>(tsFilesToCreate.subList(i, Math.min(i + BATCH_SIZE, tsFilesToCreate.size())));
affected += this.insertOneBatch(sub);
}
LOGGER.info("[批量插入试验任务文件表] 耗时 {} ms | 实际新增数量: {} 条",
System.currentTimeMillis() - startBatchInsertFiles,
affectedRowsFiles);
affected);
}
// 记录开始时间
long startTimeFiles = System.currentTimeMillis();
// 执行更新操作 taskId, String nodeId
@ -993,6 +1141,14 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
LOGGER.info("文件表中的节点ID更新完成影响 {} 行,总耗时 {} 毫秒", affectedLevelFilesRows, costTimeFiles);
}
/**
* 每批独立事务插入
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public int insertOneBatch(List<TsFiles> batch) {
return tsFilesMapper.batchInsertTsFiles(batch);
}
/**
* 确保路径格式为以 "/" 开头和结尾例如 "/data/test/"
* 若路径为空或非法返回根路径 "/"
@ -1132,4 +1288,12 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
return storageSourceMapper.selectOne(new LambdaQueryWrapper<StorageSource>().eq(StorageSource::getId, id)
);
}
@Override
public List<TsNodes> getByTaskId(String taskId) {
LambdaQueryWrapper<TsNodes> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TsNodes::getTaskId, taskId);
return this.list(queryWrapper);
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -36,11 +37,21 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.*;
@ -48,7 +59,13 @@ import java.util.*;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* <p>
@ -93,6 +110,9 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
private SysDictionaryItemsMapper sysDictionaryItemsMapper;
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
private static final String INITIAL_CODE = "00001";
private static final int MAX_CODE_VALUE = 99999;
@ -532,99 +552,151 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
public boolean deleteTstaskByIds(List<String> dataset) {
Boolean value = false;
LOGGER.info("开始批量删除试验任务,任务数量: {}", dataset.size());
LOGGER.debug("待删除的任务ID列表: {}", dataset);
try {
//循环所有的ID
for (String taskId : dataset) {
//删除项目
TsTask tsTask = tsTaskMapper.selectById(taskId);
TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
// 删除节点表
LambdaQueryWrapper<TsNodes> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(TsNodes::getTaskId, taskId);
tsNodesMapper.delete(deleteWrapper);
try {
LOGGER.info("开始处理任务删除任务ID: {}", taskId);
// 删除文件表
LambdaQueryWrapper<TsFiles> deleteWrapperFiles = new LambdaQueryWrapper<>();
deleteWrapperFiles.eq(TsFiles::getTaskId, taskId);
tsFilesMapper.delete(deleteWrapperFiles);
//todo 删除文件表数据
// tsFilesMapper.deleteSdFilesBytaskId(taskId);
// String path = "/" + tsTask.getTaskName() + "/";
// //调用删除节点 根据任务ID
// Boolean deleteTsnodes = tsNodesService.deleteTsNodesByTaskId(taskId, path);
// //如果删除成功 接着删除节点表数据
// if (deleteTsnodes) {
// LOGGER.info("tsNodes表结删除改成功");
// value = true;
// } else {
// LOGGER.error("tsNodes表结构删除失败");
// value = false;
// }
StorageSource storageSource = getStorageConfig(tsTask.getLocalStorageId());
// 删除 local 中的文件夹 项目文件夹
List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>();
BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
deleteItemData.setName(tsTask.getTaskName());
deleteItemData.setPassword("");
deleteItemData.setPath("/");
deleteItemData.setType(FileTypeEnum.FOLDER);
deleteItemList.add(deleteItemData);
BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
batchDeleteRequest.setDeleteItems(deleteItemList);
batchDeleteRequest.setStorageKey(storageSource.getKey());
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());
int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItemList);
for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) {
boolean flag = false;
try {
if (deleteItem.getType() == FileTypeEnum.FILE) {
flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
} else if (deleteItem.getType() == FileTypeEnum.FOLDER) {
flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName());
}
if (flag) {
deleteSuccessCount++;
} else {
deleteFailCount++;
}
} catch (Exception e) {
LOGGER.error("删除文件/文件夹失败, 文件路径: {}, 文件名称: {}", deleteItem.getPath(), deleteItem.getName(), e);
deleteFailCount++;
//获取试验任务及任务编码
TsTask tsTask = tsTaskMapper.selectById(taskId);
if (tsTask == null) {
LOGGER.warn("未找到指定的任务任务ID: {}", taskId);
continue;
}
}
if (deleteSuccessCount >= 1) {
// 删除当前项目
TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
LOGGER.debug("设置任务编码上下文,任务编码: {}", tsTask.getTaskCode());
// 删除节点表
LambdaQueryWrapper<TsNodes> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(TsNodes::getTaskId, taskId);
int deletedNodes = tsNodesMapper.delete(deleteWrapper);
LOGGER.info("删除任务相关的节点数据任务ID: {}, 删除条数: {}", taskId, deletedNodes);
// 只有当任务文件表存在时才尝试删除文件数据
if (tableExists("ts_files_" + tsTask.getTaskCode())) {
// 删除文件表
LambdaQueryWrapper<TsFiles> deleteWrapperFiles = new LambdaQueryWrapper<>();
deleteWrapperFiles.eq(TsFiles::getTaskId, taskId);
int deletedFiles = tsFilesMapper.delete(deleteWrapperFiles);
LOGGER.info("删除任务相关的文件数据任务ID: {}, 删除条数: {}", taskId, deletedFiles);
} else {
LOGGER.info("任务文件表不存在跳过文件数据删除任务ID: {}", taskId);
}
// 删除本地存储空间中的文件夹
StorageSource localStorageSource = getStorageConfig(tsTask.getLocalStorageId());
LOGGER.info("开始删除本地存储空间中的文件夹任务ID: {}, 本地存储ID: {}", taskId, tsTask.getLocalStorageId());
deleteStorageFolder(localStorageSource, tsTask);
// 如果有备份存储空间也删除备份存储空间中的文件夹
if (tsTask.getBackupStorageId() != null && tsTask.getBackupStorageId() > 0) {
StorageSource backupStorageSource = getStorageConfig(tsTask.getBackupStorageId());
LOGGER.info("开始删除备份存储空间中的文件夹任务ID: {}, 备份存储ID: {}", taskId, tsTask.getBackupStorageId());
deleteStorageFolder(backupStorageSource, tsTask);
}
// 删除当前试验任务
int deleteCount = tsTaskMapper.deleteById(taskId);
if (deleteCount == 1) {
LOGGER.info("tstask表结删除改成功");
LOGGER.info("试验任务删除成功任务ID: {}", taskId);
value = true;
} else {
LOGGER.error("tstask表结构删除失败");
LOGGER.error("试验任务删除失败任务ID: {}, 实际删除条数: {}", taskId, deleteCount);
value = false;
}
} else {
value = false;
} finally {
// 每次处理完一个任务后清理上下文
LOGGER.debug("清理任务上下文任务ID: {}", taskId);
TableNameContextHolder.clear();
}
}
LOGGER.info("批量删除试验任务完成,任务数量: {}", dataset.size());
return value;
} catch (Exception e) {
} finally {
TableNameContextHolder.clear();
LOGGER.error("删除试验任务时发生异常任务ID列表: {}", dataset, e);
return false;
}
}
// 这个方法会递归删除试验任务文件夹及其下的所有子文件夹和文件当调用 fileService.deleteFolder() 方法时存储服务实现会自动处理递归删除逻辑确保整个文件夹树都被清除
//这是文件存储系统的基本特性不论是本地文件系统还是MinIO等云存储服务在删除文件夹时都会删除其包含的所有内容
private void deleteStorageFolder(StorageSource storageSource, TsTask tsTask) {
LOGGER.info("==deleteStorageFolder begin====");
if (storageSource == null) {
LOGGER.warn("存储源配置为空,跳过删除操作,任务名称: {}", tsTask.getTaskName());
return;
}
if (tsTask == null || tsTask.getTaskName() == null) {
LOGGER.warn("任务信息不完整,跳过删除操作");
return;
}
try {
LOGGER.info("准备删除存储文件夹,存储类型: {}, 任务名称: {}", storageSource.getKey(), tsTask.getTaskName());
List<BatchDeleteRequest.DeleteItem> deleteItemList = new ArrayList<>();
BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
deleteItemData.setName(tsTask.getTaskName());
deleteItemData.setPassword("");
deleteItemData.setPath("/");
deleteItemData.setType(FileTypeEnum.FOLDER);
deleteItemList.add(deleteItemData);
BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
batchDeleteRequest.setDeleteItems(deleteItemList);
batchDeleteRequest.setStorageKey(storageSource.getKey());
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());
if (fileService == null) {
LOGGER.error("未能获取到文件服务实例,存储类型: {}", storageSource.getKey());
return;
}
int deleteSuccessCount = 0;
for (BatchDeleteRequest.DeleteItem deleteItem : deleteItemList) {
boolean flag = false;
try {
if (deleteItem.getType() == FileTypeEnum.FILE) {
LOGGER.info("删除文件,存储类型: {}, 路径: {}, 名称: {}",
storageSource.getKey(), deleteItem.getPath(), deleteItem.getName());
flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
} else if (deleteItem.getType() == FileTypeEnum.FOLDER) {
LOGGER.info("删除文件夹,存储类型: {}, 路径: {}, 名称: {}",
storageSource.getKey(), deleteItem.getPath(), deleteItem.getName());
flag = fileService.deleteFolder(deleteItem.getPath(), deleteItem.getName());
}
if (flag) {
deleteSuccessCount++;
LOGGER.info("删除成功 - 存储类型: {}, 类型: {}, 路径: {}, 名称: {}",
storageSource.getKey(), deleteItem.getType(), deleteItem.getPath(), deleteItem.getName());
} else {
LOGGER.warn("删除失败 - 存储类型: {}, 类型: {}, 路径: {}, 名称: {}",
storageSource.getKey(), deleteItem.getType(), deleteItem.getPath(), deleteItem.getName());
}
} catch (Exception e) {
LOGGER.error("删除文件/文件夹失败, 存储类型: {}, 文件路径: {}, 文件名称: {}",
storageSource.getKey(), deleteItem.getPath(), deleteItem.getName(), e);
// 继续处理其他项目不中断整个删除过程
}
}
LOGGER.info("存储文件夹删除完成,存储类型: {}, 成功删除项数量: {}", storageSource.getKey(), deleteSuccessCount);
} catch (Exception e) {
LOGGER.error("删除存储空间文件夹时发生异常, 存储类型: {}", storageSource.getKey(), e);
} catch (Error err) {
// 捕获Error防止JVM退出
LOGGER.error("删除存储空间文件夹时发生严重错误, 存储类型: {}", storageSource.getKey(), err);
// 不重新抛出Error防止Tomcat退出
}
return value;
}
/**********************************
@ -643,19 +715,29 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
boolean allDeletable = true;
for (TsTask tsTask : tsTasks) {
// 检查对应的任务文件表是否存在
if (!tableExists("ts_files_" + tsTask.getTaskCode())) {
// 如果表不存在可以安全删除
continue;
}
TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
// 查询是否有备份路径或非空路径的文件
LambdaQueryWrapper<TsFiles> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TsFiles::getTaskId, tsTask.getId())
.and(wrapper -> wrapper.isNotNull(TsFiles::getBackupPath)
.or().ne(TsFiles::getBackupPath, ""));
try {
// 查询 backup_path 既不为 null 也不为空字符串的记录
LambdaQueryWrapper<TsFiles> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TsFiles::getTaskId, tsTask.getId())
.isNotNull(TsFiles::getBackupPath)
.ne(TsFiles::getBackupPath, "");
int count = tsFilesService.count(queryWrapper);
int count = tsFilesService.count(queryWrapper);
if (count > 0) {
allDeletable = false;
break; // 一旦发现不可删除项提前终止循环
if (count > 0) {
allDeletable = false;
break; // 一旦发现不可删除项提前终止循环
}
} finally {
TableNameContextHolder.clear();
}
}
@ -666,8 +748,6 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
// 使用日志框架代替 printStackTrace
LOGGER.error("确认删除实验任务时发生异常", e);
return ResponseResult.error("查询失败");
} finally {
TableNameContextHolder.clear();
}
}
@ -719,4 +799,608 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
return storageSourceMapper.selectOne(new LambdaQueryWrapper<StorageSource>().eq(StorageSource::getId, id)
);
}
@Override
public byte[] exportTaskSqlAsZip(String taskId) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try(ZipOutputStream zos = new ZipOutputStream(baos)) {
// 生成SQL内容
StringBuilder sqlBuilder = new StringBuilder();
// 1. 添加ts_task表数据导出
TsTask task = this.getById(taskId);
String taskCode = "";
// 确保taskCode有值且符合5位数字格式
if (task != null && StrUtil.isNotBlank(task.getTaskCode())) {
taskCode = task.getTaskCode();
// 验证taskCode格式是否正确5位数字
if (!taskCode.matches("^\\d{5}$")) {
// 如果不是5位数字格式需要处理
try {
int codeValue = Integer.parseInt(taskCode);
if (codeValue >= 1 && codeValue <= 99999) {
// 格式化为5位数字字符串
taskCode = String.format("%05d", codeValue);
} else {
throw new IllegalArgumentException("该任务编号超出有效范围: " + taskCode);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("无效的任务编号格式: " + taskCode);
}
}
}
if (task != null) {
sqlBuilder.append("-- 试验任务数据\n");
sqlBuilder.append(generateInsertSql("ts_task", task)).append("\n\n");
}
//2. 添加ts_nodes表相关记录导出需要注入相应的service
List<TsNodes> nodes = tsNodesService.getByTaskId(taskId);
if (nodes != null && !nodes.isEmpty()) {
sqlBuilder.append("-- 节点数据\n");
for (TsNodes node : nodes) {
sqlBuilder.append(generateInsertSql("ts_nodes", node)).append("\n");
}
sqlBuilder.append("\n");
}
// 3. 添加ts_files表结构和数据导出
if (task != null) {
String dynamicTableName = "ts_files_" + task.getTaskCode();
// 设置动态表名上下文
TableNameContextHolder.setTaskCode(task.getTaskCode());
try {
// 查询该任务的所有文件数据
List<TsFiles> files = tsFilesService.getByTaskId(taskId);
if (files != null && !files.isEmpty()) {
sqlBuilder.append("-- 文件表结构\n");
sqlBuilder.append(generateCreateTableSql(dynamicTableName)).append("\n\n");
sqlBuilder.append("-- 文件数据\n");
for (TsFiles file : files) {
sqlBuilder.append(generateInsertSql(dynamicTableName, file)).append("\n");
}
}
} finally {
// 清理表名上下文
TableNameContextHolder.clear();
}
}
// 将SQL内容写入ZIP文件
ZipEntry sqlEntry = new ZipEntry(taskCode+"_exportdata.sql");
zos.putNextEntry(sqlEntry);
zos.write(sqlBuilder.toString().getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
zos.finish();
// zos.flush();
byte[] result = baos.toByteArray();
LOGGER.info("生成ZIP数据大小: {} 字节", result.length);
return result;
} finally {
//zos.close();
baos.close();
}
}
// 生成INSERT SQL语句的方法
private String generateInsertSql(String tableName, Object entity) {
if (entity == null) {
return "";
}
try {
Class<?> clazz = entity.getClass();
Field[] fields = clazz.getDeclaredFields();
StringBuilder columns = new StringBuilder();
StringBuilder values = new StringBuilder();
for (Field field : fields) {
// 跳过序列化相关字段
if (field.getName().equals("serialVersionUID")) {
continue;
}
// 跳过非数据库字段可以根据实际需要调整条件
if (field.getName().equals("key")) {
continue;
}
// 跳过 ts_nodes 表中不存在的字段
if ("ts_nodes".equals(tableName) && "path".equals(field.getName())) {
continue;
}
// 跳过 ts_files 相关表中不存在的字段
if (tableName.startsWith("ts_files_") &&
("url".equals(field.getName()) ||
"type".equals(field.getName()) ||
"localOnlyFiles".equals(field.getName()) ||
"minioOnlyFiles".equals(field.getName()) ||
"md5mismatchedFiles".equals(field.getName()) ||
"locatMd5".equals(field.getName()) ||
"minioMd5".equals(field.getName()) ||
"fileContent".equals(field.getName()) ||
"md5MismatchedFiles".equals(field.getName()))) {
continue;
}
field.setAccessible(true);
String columnName = camelToUnderscore(field.getName());
Object value = field.get(entity);
columns.append(columnName).append(",");
values.append(formatValue(value)).append(",");
}
// 移除末尾逗号
if (columns.length() > 0) {
columns.deleteCharAt(columns.length() - 1);
values.deleteCharAt(values.length() - 1);
}
return "INSERT INTO " + tableName + " (" + columns + ") VALUES (" + values + ");";
} catch (Exception e) {
LOGGER.error("生成INSERT SQL语句失败", e);
return "";
}
}
// 驼峰命名转下划线
private String camelToUnderscore(String camelCase) {
return camelCase.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
}
// 格式化值
private String formatValue(Object value) {
if (value == null) {
return "NULL";
}
if (value instanceof String) {
return "'" + value.toString().replace("'", "''") + "'";
}
if (value instanceof Date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return "'" + sdf.format(value) + "'";
}
// 处理 java.time 类型
if (value instanceof java.time.LocalDate) {
return "'" + value.toString() + "'";
}
if (value instanceof java.time.LocalDateTime) {
return "'" + ((java.time.LocalDateTime) value).format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "'";
}
return value.toString();
}
// 生成CREATE TABLE SQL语句的方法
private String generateCreateTableSql(String tableName) {
// 使用 LIKE 语句创建与 ts_files 结构相同的新表
return "CREATE TABLE IF NOT EXISTS `" + tableName + "` LIKE `ts_files`;";
}
/**
* 从ZIP文件中导入任务SQL数据
*
* @param file ZIP文件包含SQL文件
* @param taskCode 目标任务编号用于替换SQL中的原始任务编号
* @param localStorageId 本地存储ID
* @param backupStorageId 备份存储ID
* @return 导入成功返回true失败返回false
* @throws IOException 文件读取或解压过程中发生IO异常
*/
@Override
public boolean importTaskSqlFromZip(MultipartFile file, String taskCode, int localStorageId, int backupStorageId) throws IOException {
// 1. 解压ZIP文件
byte[] zipBytes = file.getBytes();
//根据文件名称获取原始任务编号00002_data.sql.zip
String originalFilename = file.getOriginalFilename();
String originalTaskCode = extractTaskCodeFromFileName(originalFilename);
List<SqlFileContent> sqlFiles = extractSqlFromZip(zipBytes);
// 2. 替换 SQL 文件中的任务编号存储空间编号
List<String> allSqlStatements = new ArrayList<>();
for (SqlFileContent sqlFile : sqlFiles) {
LOGGER.debug("原始SQL内容: {}", sqlFile.getContent().substring(0, Math.min(200, sqlFile.getContent().length())));
String modifiedContent = replaceTaskCodeInSql(sqlFile.getContent(), originalTaskCode, taskCode, localStorageId, backupStorageId);
LOGGER.debug("替换后SQL内容: {}", modifiedContent.substring(0, Math.min(200, modifiedContent.length())));
allSqlStatements.addAll(parseSqlStatements(modifiedContent));
}
// 3. 拆分 DDL INSERT 语句合并相同表的 INSERT提高执行效率
List<String> ddlSqlList = new ArrayList<>();
List<String> dmlSqlList = new ArrayList<>();
for (String sql : allSqlStatements) {
String upper = sql.trim().toUpperCase();
if (upper.startsWith("CREATE") || upper.startsWith("DROP") || upper.startsWith("ALTER") || upper.startsWith("SET")) {
ddlSqlList.add(sql);
} else if (upper.startsWith("INSERT")) {
dmlSqlList.add(sql);
}
}
// 先执行 DDL
LOGGER.info("开始执行DDL语句共 {} 条", ddlSqlList.size());
executeSqlImport(ddlSqlList);
// 再合并并执行 DML
List<String> mergedSqlStatements = mergeInsertStatements(dmlSqlList);
LOGGER.info("开始导入数据,共 {} 条INSERT语句合并后 {} 条", dmlSqlList.size(), mergedSqlStatements.size());
long startTime = System.currentTimeMillis();
// 4. 执行 SQL 批量导入
boolean result = executeSqlImport(mergedSqlStatements);
long endTime = System.currentTimeMillis();
LOGGER.info("SQL导入完成耗时 {} ms", (endTime - startTime));
return result;
}
/**
* 从文件名中提取原始任务编号
* @param fileName 文件名 "00002_data.sql.zip"
* @return 任务编号 "00002"
*/
private String extractTaskCodeFromFileName(String fileName) {
if (StrUtil.isBlank(fileName)) {
return null;
}
// 使用正则表达式匹配5位数字开头的文件名
Pattern pattern = Pattern.compile("^(\\d{5})_.*$");
Matcher matcher = pattern.matcher(fileName);
if (matcher.matches()) {
String taskCode = matcher.group(1);
// 验证任务编号范围是否在00001-99999之间
try {
int codeValue = Integer.parseInt(taskCode);
if (codeValue >= 1 && codeValue <= 99999) {
return taskCode;
}
} catch (NumberFormatException e) {
// 解析失败返回null
return null;
}
}
return null;
}
// 解压ZIP文件并提取SQL内容
private List<SqlFileContent> extractSqlFromZip(byte[] zipBytes) throws IOException {
List<SqlFileContent> sqlFiles = new ArrayList<>();
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory() && entry.getName().endsWith(".sql")) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int len;
while ((len = zis.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
// 修改这一行使用 Charset 的名称而不是 Charset 对象
sqlFiles.add(new SqlFileContent(entry.getName(), baos.toString(StandardCharsets.UTF_8.name())));
}
}
}
return sqlFiles;
}
private String replaceTaskCodeInSql(String sqlContent, String originalTaskCode, String newTaskCode, int localStorageId, int backupStorageId) {
if (StrUtil.isBlank(originalTaskCode)) {
return sqlContent;
}
// 同时替换 ts_task 表中的 task_code 值和存储空间ID值
Pattern taskPattern = Pattern.compile(
"(INSERT\\s+INTO\\s+`?ts_task`?\\s*\\([^)]*?task_code[^)]*?\\)\\s*VALUES\\s*\\([^)]*?)('"+Pattern.quote(originalTaskCode)+"')([^)]*)(?<!\\\\)\\s*,\\s*(-?\\d+)\\s*,\\s*(-?\\d+)\\s*\\)",
Pattern.CASE_INSENSITIVE
);
Matcher taskMatcher = taskPattern.matcher(sqlContent);
StringBuffer taskResult = new StringBuffer();
while (taskMatcher.find()) {
String prefix = taskMatcher.group(1);
String suffix = taskMatcher.group(3);
String replacement = prefix + "'" + newTaskCode + "'" + suffix + "," + localStorageId + "," + backupStorageId + ")";
taskMatcher.appendReplacement(taskResult, Matcher.quoteReplacement(replacement));
}
taskMatcher.appendTail(taskResult);
String result = taskResult.toString();
// 替换 ts_files_* 表名
Pattern tableNamePattern = Pattern.compile(
"INSERT\\s+INTO\\s+`?(ts_files_" + Pattern.quote(originalTaskCode) + ")`?",
Pattern.CASE_INSENSITIVE
);
Matcher tableNameMatcher = tableNamePattern.matcher(result);
StringBuffer tableNameResult = new StringBuffer();
while (tableNameMatcher.find()) {
String replacement = "INSERT INTO `ts_files_" + newTaskCode + "`";
tableNameMatcher.appendReplacement(tableNameResult, Matcher.quoteReplacement(replacement));
}
tableNameMatcher.appendTail(tableNameResult);
return tableNameResult.toString();
}
// private String replaceTaskCodeInSql(String sqlContent, String originalTaskCode, String newTaskCode, int localStorageId, int backupStorageId) {
// if (StrUtil.isBlank(originalTaskCode)) {
// return sqlContent;
// }
//
// // 1. 任务编号替换 - 使用精确匹配
// sqlContent = sqlContent.replace("'" + originalTaskCode + "'", "'" + newTaskCode + "'");
//
// // 2. 表名替换
// sqlContent = sqlContent.replace("ts_files_" + originalTaskCode, "ts_files_" + newTaskCode);
//
// // 3. 存储空间ID替换 - 针对INSERT语句进行精确替换
// // 使用更复杂的正则表达式来匹配完整的INSERT语句并替换对应的值
// sqlContent = replaceStorageIdsInInsertStatement(sqlContent, localStorageId, backupStorageId);
//
// return sqlContent;
// }
private String replaceStorageIdsInInsertStatement(String sqlContent, int localStorageId, int backupStorageId) {
// 只匹配 ts_task 表的 INSERT 语句并替换最后两个字段值
Pattern insertPattern = Pattern.compile(
"(INSERT\\s+INTO\\s+`?ts_task`?\\s*\\([^)]*\\)\\s*VALUES\\s*\\([^)]*)(?<!\\\\)\\s*,\\s*(-?\\d+)\\s*,\\s*(-?\\d+)\\s*\\)",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = insertPattern.matcher(sqlContent);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String prefix = matcher.group(1);
// 使用新的存储空间ID替换原有的值
String modifiedInsert = prefix + "," + localStorageId + "," + backupStorageId + ")";
matcher.appendReplacement(result, Matcher.quoteReplacement(modifiedInsert));
}
matcher.appendTail(result);
return result.toString();
}
// 解析SQL语句
private List<String> parseSqlStatements(String sqlContent) {
List<String> sqlList = new ArrayList<>();
String[] lines = sqlContent.split("\n");
StringBuilder currentStatement = new StringBuilder();
LOGGER.debug("开始解析SQL内容总行数: {}", lines.length);
for (String line : lines) {
String trimmedLine = line.trim();
// 跳过纯注释行
if (trimmedLine.startsWith("--")) {
continue;
}
// 跳过空行
if (trimmedLine.isEmpty()) {
continue;
}
// 添加当前行到语句构建器
currentStatement.append(trimmedLine).append(" ");
// 如果当前行以分号结尾说明是一个完整语句
if (trimmedLine.endsWith(";")) {
String statement = currentStatement.toString().trim();
if (!statement.isEmpty()) {
sqlList.add(statement);
LOGGER.debug("解析到SQL语句: {}", statement.length() > 200 ? statement.substring(0, 200) + "..." : statement);
}
currentStatement.setLength(0); // 重置构建器
}
}
// 处理可能残留的未完成语句虽然理论上不应该有
String remaining = currentStatement.toString().trim();
if (!remaining.isEmpty() && remaining.endsWith(";")) {
sqlList.add(remaining);
LOGGER.debug("解析到残留SQL语句: {}", remaining.length() > 200 ? remaining.substring(0, 200) + "..." : remaining);
}
return sqlList;
}
// =========================== 高性能导入部分 ===========================
// 批量执行: 使用batchExecute方法进行批量处理
// INSERT语句合并: 通过mergeInsertStatements方法将多个INSERT语句合并为批量插入
// 事务优化: 导入过程中关闭了外键检查和唯一性检查
// 禁用自动提交: 使用SET AUTOCOMMIT=0来提高性能
@Transactional(rollbackFor = Exception.class)
public boolean executeSqlImport(List<String> sqlStatements) {
int total = 0; // 在方法开始处声明 total 变量
try {
jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS=0;");
jdbcTemplate.execute("SET UNIQUE_CHECKS=0;");
jdbcTemplate.execute("SET AUTOCOMMIT=0;");
int batchSize = 2000;
List<String> batch = new ArrayList<>(batchSize);
for (String sql : sqlStatements) {
if (StringUtils.isBlank(sql)) continue;
batch.add(sql);
if (batch.size() >= batchSize) {
batchExecute(batch);
total += batch.size();
LOGGER.info("已执行 {} 条SQL语句", total);
batch.clear();
}
}
if (!batch.isEmpty()) {
batchExecute(batch);
total += batch.size();
}
jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS=1;");
jdbcTemplate.execute("SET UNIQUE_CHECKS=1;");
jdbcTemplate.execute("COMMIT;");
LOGGER.info("批量导入完成,共执行 {} 条SQL", total);
return true;
} catch (Exception e) {
LOGGER.error("SQL导入失败已执行 {} 条SQL", total, e);
try {
jdbcTemplate.execute("ROLLBACK;");
} catch (Exception rollbackEx) {
LOGGER.error("回滚事务失败", rollbackEx);
}
throw new RuntimeException("SQL导入失败" + e.getMessage(), e);
}
}
private void batchExecute(List<String> sqlList) {
try {
jdbcTemplate.batchUpdate(sqlList.toArray(new String[0]));
} catch (DataAccessException e) {
LOGGER.warn("批量执行失败,分步重试:{}", e.getMessage());
for (String sql : sqlList) {
try {
jdbcTemplate.update(sql);
} catch (Exception ex) {
LOGGER.warn("跳过失败SQL{}", ex.getMessage());
}
}
}
}
// =========================== INSERT 合并优化 ===========================
private List<String> mergeInsertStatements(List<String> sqlStatements) {
Map<String, List<String>> groupedStatements = new LinkedHashMap<>();
Pattern pattern = Pattern.compile(
"INSERT\\s+INTO\\s+`?(\\w+)`?\\s*\\(([^)]+)\\)\\s*VALUES\\s*\\((.*)\\)",
Pattern.CASE_INSENSITIVE
);
for (String sql : sqlStatements) {
Matcher matcher = pattern.matcher(sql);
if (matcher.find()) {
String table = matcher.group(1);
String columns = matcher.group(2);
String values = matcher.group(3);
// 使用表名+列名作为分组键
String key = table + ":" + columns;
groupedStatements.computeIfAbsent(key, k -> new ArrayList<>()).add(values);
} else {
// 无法匹配的语句单独处理
groupedStatements.computeIfAbsent("_others", k -> new ArrayList<>()).add(sql);
}
}
List<String> mergedSql = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : groupedStatements.entrySet()) {
String key = entry.getKey();
List<String> valuesList = entry.getValue();
if ("_others".equals(key)) {
mergedSql.addAll(valuesList);
} else {
String[] parts = key.split(":", 2);
String table = parts[0];
String columns = parts[1];
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO `").append(table).append("` (").append(columns).append(") VALUES ");
for (int i = 0; i < valuesList.size(); i++) {
if (i > 0) {
sb.append(",");
}
sb.append("(").append(valuesList.get(i)).append(")");
}
sb.append(";");
mergedSql.add(sb.toString());
}
}
return mergedSql;
}
// =========================== 内部类定义 ===========================
private static class SqlFileContent {
private final String name;
private final String content;
public SqlFileContent(String name, String content) {
this.name = name;
this.content = content;
}
public String getName() {
return name;
}
public String getContent() {
return content;
}
}
@Override
public String getMaxTaskCode() {
// 查询数据库中最大的任务编号
String maxTaskCode = this.baseMapper.selectMaxTaskCode();
if (StrUtil.isBlank(maxTaskCode)) {
// 如果没有任务编号记录返回默认值
return "00001";
}
try {
// 解析5位数字格式的任务编号并递增
int currentNumber = Integer.parseInt(maxTaskCode);
int nextNumber = currentNumber + 1;
// 确保不超过5位数的最大值
if (nextNumber > 99999) {
throw new IllegalStateException("任务编号已达到最大值99999");
}
// 格式化为5位数字字符串
return String.format("%05d", nextNumber);
} catch (NumberFormatException e) {
// 如果解析失败返回默认值
return "00001";
}
}
}

View File

@ -135,7 +135,28 @@ public class LocalServiceImpl extends AbstractProxyTransferService<LocalParam> {
checkNameSecurity(name);
String fullPath = StringUtils.concat(param.getFilePath(), path, name);
return FileUtil.del(fullPath);
try {
// 检查文件是否存在
File file = new File(fullPath);
if (!file.exists()) {
log.warn("文件不存在: {}", fullPath);
return true; // 文件不存在也算删除成功
}
// 检查并尝试修改文件权限
if (!file.canWrite()) {
log.info("文件无写权限,尝试修改权限: {}", fullPath);
boolean permissionChanged = file.setWritable(true);
if (!permissionChanged) {
log.warn("无法修改文件写权限: {}", fullPath);
}
}
return FileUtil.del(fullPath);
} catch (Exception e) {
log.error("删除文件失败: {}, 错误: {}", fullPath, e.getMessage(), e);
return false;
}
}
@ -144,7 +165,64 @@ public class LocalServiceImpl extends AbstractProxyTransferService<LocalParam> {
checkPathSecurity(path);
checkNameSecurity(name);
return deleteFile(path, name);
String fullPath = StringUtils.concatTrimStartSlashes(param.getFilePath(), name);
log.info("param.getFilePath==========,{}", param.getFilePath());
log.info("fullPath==========,{}", fullPath);
try {
// 检查文件夹是否存在
File folder = new File(fullPath);
if (!folder.exists()) {
log.warn("文件夹不存在: {}", fullPath);
return true; // 文件夹不存在也算删除成功
}
// 检查并尝试修改文件夹权限
if (!folder.canWrite()) {
log.info("文件夹无写权限,尝试修改权限: {}", fullPath);
boolean permissionChanged = folder.setWritable(true);
if (!permissionChanged) {
log.warn("无法修改文件夹写权限: {}", fullPath);
}
}
// 递归修改文件夹内所有文件的权限
//setWritableRecursively(folder);
return FileUtil.del(fullPath);
} catch (Exception e) {
log.error("删除文件夹失败: {}, 错误: {}", fullPath, e.getMessage(), e);
return false;
}
}
/**
* 递归设置文件夹及其内部所有文件的写权限
* @param file 文件或文件夹
*/
private void setWritableRecursively(File file) {
if (file.isFile()) {
if (!file.canWrite()) {
boolean success = file.setWritable(true);
if (!success) {
log.debug("无法设置文件写权限: {}", file.getAbsolutePath());
}
}
} else if (file.isDirectory()) {
if (!file.canWrite()) {
boolean success = file.setWritable(true);
if (!success) {
log.debug("无法设置文件夹写权限: {}", file.getAbsolutePath());
}
}
File[] files = file.listFiles();
if (files != null) {
for (File f : files) {
setWritableRecursively(f);
}
}
}
}

View File

@ -18,7 +18,9 @@ import com.yfd.platform.system.service.IUserService;
import com.yfd.platform.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@ -65,20 +67,47 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
***********************************/
@Override
public String getUsername() {
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginuser = (LoginUser) authentication.getPrincipal();
String acountname =
loginuser.getUser().getNickname();
return acountname;
//return "admin";
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated() ||
authentication instanceof AnonymousAuthenticationToken) {
return "anonymous";
}
if (authentication instanceof UsernamePasswordAuthenticationToken) {
LoginUser loginuser = (LoginUser) authentication.getPrincipal();
return loginuser.getUser().getNickname();
}
return "unknown";
}
@Override
public Map<String, String> getNameInfo() {
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginuser = (LoginUser) authentication.getPrincipal();
// 安全地获取认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 检查认证信息是否有效
if (authentication == null || !authentication.isAuthenticated() ||
authentication instanceof AnonymousAuthenticationToken) {
// 返回默认值或抛出自定义异常
Map<String, String> defaultMap = new HashMap<>();
defaultMap.put("nickname", "Anonymous");
defaultMap.put("username", "anonymous");
return defaultMap;
}
// 确保是 UsernamePasswordAuthenticationToken 类型
if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
Map<String, String> defaultMap = new HashMap<>();
defaultMap.put("nickname", "Unknown");
defaultMap.put("username", "unknown");
return defaultMap;
}
UsernamePasswordAuthenticationToken usernameAuth = (UsernamePasswordAuthenticationToken) authentication;
LoginUser loginuser = (LoginUser) usernameAuth.getPrincipal();
String nickname = loginuser.getUser().getNickname();
String username = loginuser.getUser().getUsername();
Map<String, String> map = new HashMap<>();
@ -87,6 +116,7 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
return map;
}
@Override
public SysUser getUserInfo() {
UsernamePasswordAuthenticationToken authentication =

View File

@ -20,6 +20,18 @@ spring:
url: jdbc:mysql://43.138.168.68:3306/filemanagedb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
username: root
password: ylfw20230626@
# url: jdbc:mysql://127.0.0.1:3307/filemanagedb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
# username: root
# password: 123456
initial-size: 5
min-idle: 5
max-active: 20
test-while-idle: true
test-on-borrow: false
test-on-return: false
validation-query: SELECT 1
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
mvc:
pathmatch:
matching-strategy: ant_path_matcher

View File

@ -1,41 +1,54 @@
server:
port: 8087
port: 8080
tomcat:
connection-timeout: 300000
#密码加密传输,前端公钥加密,后端私钥解密
rsa:
private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
spring:
#应用名称
application:
name: Project-plateform
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
driverClassName: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://120.27.210.161:3306/testdb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
# username: testdb
# password: 27CTfsyJmZRESmsa
url: jdbc:mysql://121.37.111.42:33306/filemanagedb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
username: filemanagedb
password: GAPchydbCKYFjjAa
mvc:
pathmatch:
matching-strategy: ant_path_matcher
servlet:
multipart:
max-file-size: 30MB
max-request-size: 100MB
#应用名称
application:
name: Project-plateform
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://db-container:3306/filemanagedb?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true
username: root
password: pwd@FileMgr
initial-size: 5
min-idle: 5
max-active: 20
validation-query: SELECT 1
mvc:
pathmatch:
matching-strategy: ant_path_matcher
servlet:
multipart:
max-file-size: 50GB
max-request-size: 50GB
tomcat:
max-swallow-size: -1
connection-timeout: 86400000
max-http-form-post-size: -1
redis:
host: localhost
port: 6379
password: MySecurePass123
database: 0
logging:
file:
path: /opt/filemgr/logs
name: logs/projectname.log
level:
com.genersoft.iot: debug
com.genersoft.iot.vmp.storager.dao: info
com.genersoft.iot.vmp.gb28181: info
file:
path: /opt/filemgr/logs/
name: logs/projectname.log
level:
com.genersoft.iot: info
com.genersoft.iot.vmp.storager.dao: info
com.genersoft.iot.vmp.gb28181: info
# 在线文档: swagger-ui生产环境建议关闭
swagger-ui:
enabled: true
enabled: false
mybatis-plus:
configuration:
@ -69,7 +82,7 @@ ip:
file-space: #项目文档空间
system: D:\file\system\ #单独上传的文件
system: /data/local-data/ #单独上传的文件
file-system:
preview:

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.experimentalData.mapper.TsTaskMapper">
<select id="selectMaxTaskCode" resultType="java.lang.String">
SELECT task_code FROM ts_task
ORDER BY task_code DESC
LIMIT 1
</select>
</mapper>

View File

@ -78,3 +78,31 @@ export function importTaskSql(queryParams: any) {
headers: { 'Content-Type': 'multipart/form-data' }
});
}
//
export function commonItems() {
return request({
url: '/api/common-items',
method: 'get'
});
}
export function addCommonItems(queryParams: any) {
return request({
url: '/api/common-items',
method: 'post',
data: queryParams,
});
}
export function delCommonItems(queryParams: any) {
return request({
url: '/api/common-items',
method: 'delete',
data: queryParams,
});
}
export function batchCommonItems(queryParams: any) {
return request({
url: '/api/common-items/batch',
method: 'post',
data: queryParams,
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

View File

@ -1,6 +1,6 @@
import minimatch from "minimatch";
import useRouterData from "@/components/file/useRouterData";
import { removeDuplicateSlashes } from "fast-glob/out/managers/patterns";
// import { removeDuplicateSlashes } from "fast-glob/out/managers/patterns";
import common from "@/components/file/common";
import { useStorage } from '@vueuse/core';
let { storageKey, currentPath } = useRouterData()
@ -8,7 +8,9 @@ let { storageKey, currentPath } = useRouterData()
const zfilePasswordCache = useStorage('zfile-pwd-cache', {});
export default function useFilePwd() {
function removeDuplicateSlashes(path) {
return path.replace(/\\/g, '/').replace(/\/+/g, '/');
}
// 向缓存中写入当前路径密码
let putPathPwd = (pattern, password) => {
if (pattern) {

View File

@ -1,4 +1,4 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
import JSEncrypt from 'jsencrypt';
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +

View File

@ -154,7 +154,7 @@ function getProject() {
setupWebSocket()
ws2 = new WebSocket(userStore.WebSocketUrl + '/websocket/' + "id_extract_" + projectId.value)
setupWebSocket2()
gettreedata()
gettreedata('')
tonstatus(false)
})
}
@ -179,7 +179,7 @@ function tonstatus(ready: any) {
tonloading.value = true
buttonmsg.value = '处理中...'
}
gettreedata()
gettreedata('')
})
//
@ -242,9 +242,13 @@ const treeForm = ref({
//
const treeRef = ref();
const filepath = ref('')
function gettreedata() {
function gettreedata(type:string) {
treeloading.value = true
treeForm.value.taskId = projectId.value
if(type == '节点搜索' && projectId.value !== ''){
treeForm.value.taskId = ''
}else{
treeForm.value.taskId = projectId.value
}
let keyar = projectArr.value.find(item => item.id === projectId.value);
storageKey.value = keyar.key
getTsNodesTree(treeForm.value).then((res: any) => {
@ -252,6 +256,7 @@ function gettreedata() {
treeloading.value = false
if (treedata.value[0]) {
pathid.value = treedata.value[0].nodeId
projectId.value = treedata.value[0].taskId
nodename.value = res.data[0].nodeName
if (nodename.value == '根节点') {
filepath.value = res.data[0].path
@ -273,6 +278,7 @@ const pathid = ref()
const nodename = ref('')
function handleNodeClick(data: any, node: any) {
pathid.value = data.nodeId
projectId.value = data.taskId
// filepath.value = data.path + data.nodeName + '/'
creatform.value.parentId = ''
patharr.value.length = 0
@ -349,7 +355,7 @@ async function submitForm(formEl: any) {
if (projectForme.value.nodeId) {
updateTsNodes(projectForme.value).then((res: any) => {
if (res.code == 0) {
gettreedata()
gettreedata('')
ElMessage.success("修改成功")
frame.value = false
}
@ -358,7 +364,7 @@ async function submitForm(formEl: any) {
} else {
addTsNodes(projectForme.value).then((res: any) => {
if (res.code == 0) {
gettreedata()
gettreedata('')
ElMessage.success("添加成功")
frame.value = false
}
@ -393,7 +399,7 @@ function delSubItem(row: any) {
loading.value = true
deleteTsNodesById({ id: row.nodeId, path: row.path }).then((res: any) => {
if (res.code == 0) {
gettreedata()
gettreedata('')
ElMessage({
type: 'success',
message: '删除成功',
@ -1863,12 +1869,12 @@ const configradio: any = ref(null)
<div class="faulttemplate-box">
<aside id="silderLeft">
<div>
<el-select v-model="projectId" placeholder="请选择项目" @change="gettreedata()">
<el-select v-model="projectId" placeholder="请选择项目" @change="gettreedata('')">
<el-option v-for="item in projectArr" :key="item.id" :label="item.taskName" :value="item.id" />
</el-select>
<div class="tree_sou">
<el-input v-model="treeForm.nodeName" style="width:100%;margin:10px 9px 10px 0px;"
placeholder="节点名称" clearable :suffix-icon="Search" @change="gettreedata()" />
placeholder="节点名称" clearable :suffix-icon="Search" @change="gettreedata('节点搜索')" />
<img src="@/assets/images/addNew.png" style="cursor: pointer;" title="新增子项目"
@click="addSubItem({ nodeId: '00' })" alt="">
<!-- <el-input v-model="treeForm.nodeName" style="width:100%;margin:10px 9px 10px 0px;"

View File

@ -11,11 +11,12 @@ import Page from '@/components/Pagination/page.vue'
import {
tstaskPage, addtsTask, updatetsTask, exportTaskSql, deleteTsTaskById,
deleteTsTaskByIds, confirmDeleteTask, selectTsNodesById, getMaxTaskCode,
importTaskSql
importTaskSql, commonItems, addCommonItems, delCommonItems, batchCommonItems
} from "@/api/testtask";
import { storagesBytype } from "@/api/storage";
import { getDict } from '@/api/dict'
import { getUserInfo } from '@/api/user';
import { st } from "vue-router/dist/router-CWoNjPRp.mjs";
///////
let result2 = ref([])
let result1 = ref([
@ -143,6 +144,7 @@ const frame = ref(false)
//
function addproject() {
frame.value = true
customList.value = []
title.value = "新增试验任务"
projectForme.value = {
taskName: "",//
@ -172,6 +174,11 @@ function addproject() {
//
function editproject(row: any) {
projectForme.value = JSON.parse(JSON.stringify(row))
if( projectForme.value.custom1 !== null&& projectForme.value.custom1 !== undefined && projectForme.value.custom1 !== ''){
customList.value = projectForme.value.custom1.split(',')
}else{
customList.value = []
}
formitemarr.value = projectForme.value.taskProps
projectForme.value.taskDate = [projectForme.value.taskStartdate, projectForme.value.taskEnddate]
title.value = "修改试验任务"
@ -341,13 +348,14 @@ const projectForme: any = ref({
carrierName: "",//
deviceName: "",//
taskCode: "",//
deviceCode: ""//
, taskType: "",
deviceCode: "", //
taskType: "",
testDescribe: "",//
sensorDescribe: "",//
taskProps: "",//
localStorageId: '',
backupStorageId: ''
backupStorageId: '',
custom1: '',//
})
//
async function submitForm(formEl: any) {
@ -366,6 +374,12 @@ async function submitForm(formEl: any) {
projectForme.value.taskStartdate = ''
projectForme.value.taskEnddate = ''
}
//
if(customList.value.length>0){
projectForme.value.custom1 = customList.value.join(',')
}else{
projectForme.value.custom1 = ''
}
if (projectForme.value.id) {
updatetsTask(projectForme.value).then((res: any) => {
if (res.code == 0) {
@ -391,7 +405,9 @@ async function submitForm(formEl: any) {
const moderules = ref({
taskType: [{ required: true, message: "请选择任务类型", trigger: "change" }],
taskDate: [{ required: true, message: "请选择任务时间", trigger: "change" }],
taskPlace: [{ required: true, message: "请输入任务地点", trigger: "blur" }],
taskPlace: [{ required: true, message: "请选择任务地点", trigger: "change" }],
carrierName: [{ required: true, message: "请选择载机名称", trigger: "change" }],
deviceCode: [{ required: true, message: "请输入设备代号_编号", trigger: "blur" }],
localStorageId: [{ required: true, message: "请选择本地存储空间", trigger: "change" }],
backupStorageId: [{ required: true, message: "请选择minio存储空间", trigger: "change" }],
});
@ -556,6 +572,317 @@ function importSubmit(formEl: any) {
function importClose() {
importDialog.value = false
}
//
const taskPlaceOptions = ref([])
const taskPlaceLoading = ref(false)
const selectDialog = ref(false)
const selectData = ref([])
const selectDataAll = ref([])
const selectKeyword = ref('')
const selectDialogLoading = ref(false)
const selectData2 = ref([])
const selectDataAll2 = ref([])
const selectKeyword2 = ref('')
//
const aircraftOptions = ref([])
const aircraftLoading = ref(false)
//
const sensorDescribeOptions = ref([])
const sensorDescribeLoading = ref(false)
//
const customList = ref([])
const customMultpieOptions = ref([])
const customMultpieLoading = ref(false)
const iscustomMultpie = ref(false)
const selectionList = ref([])
const multipleTableRef = ref(null)
function remoteMethod(query) {
let list = []
commonItems().then((res: any) => {
if (res.data.taskLocation) {
list = res.data.taskLocation
}
if (query !== '') {
taskPlaceLoading.value = true;
setTimeout(() => {
taskPlaceLoading.value = false;
taskPlaceOptions.value = list.filter(item => item.includes(query));
}, 200);
} else {
taskPlaceOptions.value = list
}
})
}
function remoteMethod2(query) {
let list = []
commonItems().then((res: any) => {
if (res.data.aircraftName) {
list = res.data.aircraftName
}
if (query !== '') {
aircraftLoading.value = true;
setTimeout(() => {
aircraftLoading.value = false;
aircraftOptions.value = list.filter(item => item.includes(query));
}, 200);
} else {
aircraftOptions.value = list
}
})
}
function remoteMethod3(query) {
let list = []
commonItems().then((res: any) => {
if (res.data.sensorDescription) {
list = res.data.sensorDescription
}
if (query !== '') {
sensorDescribeLoading.value = true;
setTimeout(() => {
sensorDescribeLoading.value = false;
sensorDescribeOptions.value = list.filter(item => item.includes(query));
}, 200);
} else {
sensorDescribeOptions.value = list
}
})
}
function remoteMethod4(query) {
let list = []
commonItems().then((res: any) => {
if (res.data.taskLabel) {
list = res.data.taskLabel
}
if (query !== '') {
aircraftLoading.value = true;
setTimeout(() => {
customMultpieLoading.value = false;
customMultpieOptions.value = list.filter(item => item.includes(query));
}, 200);
} else {
customMultpieOptions.value = list
}
})
}
function saveSelect(type: string) {
let value = ''
if (type === 'taskLocation') {
value = projectForme.value.taskPlace
} else if (type === 'aircraftName') {
value = projectForme.value.carrierName
} else if (type === 'sensorDescription') {
value = projectForme.value.sensorDescribe
}
const params = {
label: value,
type: type
}
addCommonItems(params).then((res: any) => {
if (res.success) {
ElMessage.success('保存成功')
} else {
ElMessage.error('保存失败')
}
})
}
function showSelectList(type: string) {
querySelectList(type)
iscustomMultpie.value = false
selectDialog.value = true
}
function selectDataProcess(data: any, type: string) {
let list = []
data.forEach((item: any) => {
list.push({
name: item,
type: type
})
})
return list
}
function blurChange(type: string, e: any) {
if (type == 'taskLocation' && e.target.value !== '') {
projectForme.value.taskPlace = e.target.value
} else if (type == 'aircraftName' && e.target.value !== '') {
projectForme.value.carrierName = e.target.value
} else if (type == 'sensorDescription' && e.target.value !== '') {
projectForme.value.sensorDescribe = e.target.value
} else if (type == 'taskLabel' && e.target.value !== '') {
const issave = customList.value.indexOf(e.target.value) !== -1;
if (!issave) {
customList.value.push(e.target.value)
}
}
}
function selectKeywordChange(type: string) {
if (type == '1') {
const list = selectDataAll.value
if (selectKeyword.value !== '') {
selectData.value = list.filter(item => item.name.includes(selectKeyword.value))
} else {
selectData.value = selectDataAll.value
}
} else {
const list2 = selectDataAll2.value
if (selectKeyword2.value !== '') {
selectData2.value = list2.filter(item => item.name.includes(selectKeyword2.value))
} else {
selectData2.value = selectDataAll2.value
}
}
}
function selectClose() {
selectDialog.value = false
}
function delSelect(row: any, type: string) {
const params = {
label: row.name,
type: row.type
}
delCommonItems(params).then((res: any) => {
if (res.success) {
ElMessage.success('删除成功')
if (type == '1') {
querySelectList(row.type)
} else {
customList.value.splice(customList.value.indexOf(row.name), 1)
queryCustomMultpieList()
}
} else {
ElMessage.error('删除失败')
}
})
}
function submitSelect(row: any) {
if (row.type === 'taskLocation') {
projectForme.value.taskPlace = row.name
} else if (row.type === 'aircraftName') {
projectForme.value.carrierName = row.name
} else if (row.type === 'sensorDescription') {
projectForme.value.sensorDescribe = row.name
} else if (row.type === 'sensorDescription') {
projectForme.value.sensorDescribe = row.name
}
selectDialog.value = false
}
function querySelectList(type: string) {
selectDialogLoading.value = true
selectData.value = []
selectDataAll.value = []
commonItems().then((res: any) => {
if (type === 'taskLocation') {
if (res.data.taskLocation) {
selectData.value = selectDataProcess(res.data.taskLocation, 'taskLocation')
selectDataAll.value = selectDataProcess(res.data.taskLocation, 'taskLocation')
}
} else if ((type === 'aircraftName')) {
if (res.data.aircraftName) {
selectData.value = selectDataProcess(res.data.aircraftName, 'aircraftName')
selectDataAll.value = selectDataProcess(res.data.aircraftName, 'aircraftName')
}
} else if ((type === 'sensorDescription')) {
if (res.data.aircraftName) {
selectData.value = selectDataProcess(res.data.sensorDescription, 'sensorDescription')
selectDataAll.value = selectDataProcess(res.data.sensorDescription, 'sensorDescription')
}
}
selectDialogLoading.value = false
})
}
function queryCustomMultpieList() {
customMultpieOptions.value = []
commonItems().then((res: any) => {
if (res.data.taskLabel) {
customMultpieOptions.value = res.data.taskLabel
}
let list = []
customMultpieOptions.value.forEach((item: any) => {
list.push({
name: item,
type: 'taskLabel'
})
})
selectData2.value = list
selectDataAll2.value = list
setTimeout(() => {
if (customList.value.length > 0) {
customList.value.forEach((item: any) => {
multipleTableRef.value!.toggleRowSelection(
{
name: item,
type: 'taskLabel',
},
)
})
}
}, 100)
})
}
function saveMultpieSelect() {
console.log(customList.value)
const params = {
type: 'taskLabel',
labels: customList.value
}
batchCommonItems(params).then((res: any) => {
if (res.success) {
ElMessage.success('保存成功')
customMultpieOptions.value = res.data.taskLabel
} else {
ElMessage.error('保存失败')
}
})
}
function showMultpieSelectList() {
commonItems().then((res: any) => {
if (res.data.taskLabel) {
customMultpieOptions.value = res.data.taskLabel
}
iscustomMultpie.value = true
let list = []
customMultpieOptions.value.forEach((item: any) => {
list.push({
name: item,
type: 'taskLabel'
})
})
selectData2.value = list
selectDataAll2.value = list
selectDialog.value = true
setTimeout(() => {
if (customList.value.length > 0) {
customList.value.forEach((item: any) => {
multipleTableRef.value!.toggleRowSelection(
{
name: item,
type: 'taskLabel',
},
)
})
}
}, 100)
})
}
function saveCustomMultpie() {
if (selectionList.value.length === 0) {
ElMessage.warning('请选择试验标签项')
return
} else {
const list = []
selectionList.value.forEach((item: any) => {
list.push(item.name)
})
customList.value = list
}
selectDialog.value = false
}
function customSelectionChange(val: any) {
selectionList.value = val
}
</script>
<template>
@ -679,7 +1006,7 @@ function importClose() {
</span>
</template>
</el-table-column>
<el-table-column v-for="item in result2" :prop="item.code" :label="item.name"
<el-table-column v-for="item in result2" :key="item.code" :prop="item.code" :label="item.name"
width="180"></el-table-column>
</el-table>
<Page :total="total" v-model:size="queryParams.size" v-model:current="queryParams.current"
@ -708,27 +1035,59 @@ function importClose() {
</el-select>
</el-form-item>
<el-form-item label="任务时间" prop="taskDate" style="width: 50%;margin-left: 15px;">
<el-date-picker :disabled="projectForme.id" v-model="projectForme.taskDate" type="daterange"
range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD"
<el-date-picker :disabled="projectForme.modifiableStatus == '1' ? true : false"
v-model="projectForme.taskDate" type="daterange" range-separator="-"
start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" />
</el-form-item>
</div>
<div style="width: 100%;display: flex;justify-content: space-between;align-items: center;">
<el-form-item label="任务地点" prop="taskPlace" style="width: 50%;">
<el-input :disabled="projectForme.id" v-model="projectForme.taskPlace" maxlength="500"
show-word-limit />
<!-- <el-input :disabled="projectForme.id" v-model="projectForme.taskPlace" maxlength="500"
show-word-limit /> -->
<div style="display: flex;width: 100%;">
<el-select :disabled="projectForme.modifiableStatus == '1' ? true : false"
v-model="projectForme.taskPlace" filterable remote reserve-keyword
style="width:100%" placeholder="请选择任务地点" :remote-method="remoteMethod"
:loading="taskPlaceLoading" @blur="blurChange('taskLocation', $event)">
<el-option v-for="item in taskPlaceOptions" :key="item" :label="item" :value="item">
</el-option>
</el-select>
<el-button :disabled="projectForme.modifiableStatus == '1' ? true : false" style="padding: 0px 5px;margin-left: 5px;"
@click="saveSelect('taskLocation')"><img src="@/assets/MenuIcon/save.png" title="保存"
alt=""></el-button>
<el-button :disabled="projectForme.modifiableStatus == '1' ? true : false" style="padding: 0px 5px;margin-left: 5px;"
@click="showSelectList('taskLocation')"><img src="@/assets/MenuIcon/set.png"
title="" alt=""></el-button>
</div>
</el-form-item>
<el-form-item label="任务人员" style="width: 50%;margin-left: 15px;">
<el-input v-model="projectForme.taskPerson" maxlength="500" show-word-limit />
</el-form-item>
</div>
<div style="width: 100%;display: flex;justify-content: space-between;align-items: center;">
<el-form-item label="载机名称" style="width: 50%;">
<el-input :disabled="projectForme.id" v-model="projectForme.carrierName" maxlength="40"
show-word-limit />
<el-form-item label="载机名称" prop="carrierName" style="width: 50%;">
<!-- <el-input :disabled="projectForme.id" v-model="projectForme.carrierName" maxlength="40"
show-word-limit /> -->
<div style="display: flex;width: 100%;">
<el-select :disabled="projectForme.id" v-model="projectForme.carrierName" filterable
remote reserve-keyword style="width:100%" placeholder="请选择载机名称"
:remote-method="remoteMethod2" :loading="aircraftLoading"
@blur="blurChange('aircraftName', $event)">
<el-option v-for="item in aircraftOptions" :key="item" :label="item" :value="item">
</el-option>
</el-select>
<el-button :disabled="projectForme.id" style="padding: 0px 5px;margin-left: 5px;"
@click="saveSelect('aircraftName')"><img src="@/assets/MenuIcon/save.png" title="保存"
alt=""></el-button>
<el-button :disabled="projectForme.id" style="padding: 0px 5px;margin-left: 5px;"
@click="showSelectList('aircraftName')"><img src="@/assets/MenuIcon/set.png"
title="" alt=""></el-button>
</div>
</el-form-item>
<el-form-item label="设备代号_编号" style="width: 50%;margin-left: 15px;">
<el-form-item label="设备代号_编号" prop="deviceCode" style="width: 50%;margin-left: 15px;">
<el-input :disabled="projectForme.id" v-model="projectForme.deviceCode" maxlength="40"
show-word-limit />
</el-form-item>
@ -740,14 +1099,49 @@ function importClose() {
<el-form-item label="试验描述">
<el-input v-model="projectForme.testDescribe" :rows="7" type="textarea" show-word-limit />
</el-form-item>
<el-form-item label="传感器描述">
<el-input v-model="projectForme.sensorDescribe" :rows="7" type="textarea" show-word-limit />
<el-form-item label="试验标签项">
<!-- <el-input v-model="projectForme.testDescribe" :rows="7" type="textarea" show-word-limit /> -->
<div style="display: flex;width: 100%;">
<!-- <el-select v-model="customList" multiple filterable allow-create default-first-option
placeholder="请选择试验标签项">
<el-option v-for="item in customMultpieOptions" :key="item" :label="item" :value="item">
</el-option>
</el-select> -->
<el-select :disabled="projectForme.id" v-model="customList" filterable remote
reserve-keyword style="width:100%" multiple placeholder="请选择试验标签项"
:remote-method="remoteMethod4" :loading="customMultpieLoading"
@blur="blurChange('taskLabel', $event)">
<el-option v-for="item in customMultpieOptions" :key="item" :label="item" :value="item">
</el-option>
</el-select>
<el-button :disabled="projectForme.id" style="padding: 0px 5px;margin-left: 5px;" @click="saveMultpieSelect"><img
src="@/assets/MenuIcon/save.png" title="保存" alt=""></el-button>
<el-button :disabled="projectForme.id" style="padding: 0px 5px;margin-left: 5px;" @click="showMultpieSelectList"><img
src="@/assets/MenuIcon/set.png" title="" alt=""></el-button>
</div>
</el-form-item>
<el-form-item label="传感器描述">
<!-- <el-input v-model="projectForme.sensorDescribe" :rows="7" type="textarea" show-word-limit /> -->
<div style="display: flex;width: 100%;">
<el-select :disabled="projectForme.id" v-model="projectForme.sensorDescribe" filterable
remote reserve-keyword style="width:100%" placeholder="请选择传感器描述"
:remote-method="remoteMethod3" :loading="aircraftLoading"
@blur="blurChange('sensorDescription', $event)">
<el-option v-for="item in sensorDescribeOptions" :key="item" :label="item" :value="item">
</el-option>
</el-select>
<el-button :disabled="projectForme.id" style="padding: 0px 5px;margin-left: 5px;"
@click="saveSelect('sensorDescription')"><img src="@/assets/MenuIcon/save.png"
title="保存" alt=""></el-button>
<el-button :disabled="projectForme.id" style="padding: 0px 5px;margin-left: 5px;"
@click="showSelectList('sensorDescription')"><img src="@/assets/MenuIcon/set.png"
title="" alt=""></el-button>
</div>
</el-form-item>
<!-- <el-form-item label="设备名称">
<el-input v-model="projectForme.deviceName" maxlength="40" show-word-limit />
</el-form-item> -->
<el-form-item v-for="(item, index) in formitemarr" :label="item.name">
<el-form-item v-for="(item, index) in formitemarr" :key="index" :label="item.name">
<div style="width: 100%;display: flex;align-items: center;justify-content: space-between;">
<el-input v-model="item.data" style="width: 92%;" />
<el-button type="primary" @click="dellable(index)">删除</el-button>
@ -842,6 +1236,49 @@ function importClose() {
<el-button type="primary" @click="importSubmit(importFormRef)">导入</el-button>
</div>
</el-dialog>
<el-dialog title="选择" :close-on-click-modal="false" v-model="selectDialog" width="30%" :before-close="selectClose">
<div v-if="iscustomMultpie">
<el-input v-model="selectKeyword2" placeholder="请输入内容" @input="selectKeywordChange('2')"></el-input>
<el-table row-key="name" ref="multipleTableRef" :data="selectData2" border height="440"
style="width: 100%;margin-top:10px;" @selection-change="customSelectionChange">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column prop="name" label="名称">
</el-table-column>
<el-table-column fixed="right" label="操作" width="60" align="center">
<template #default="scope">
<span
style="display: flex;display: -webkit-flex;justify-content: space-around;-webkit-justify-content: space-around; ">
<img src="@/assets/MenuIcon/lbcz_sc.png" alt="" title="删除"
@click="delSelect(scope.row, '2')" style="cursor: pointer;">
</span>
</template>
</el-table-column>
</el-table>
<div style="width: 100%;display: flex;justify-content: end;margin-top: 10px;">
<el-button type="primary" @click="saveCustomMultpie">确定</el-button>
</div>
</div>
<div v-else v-loading="selectDialogLoading" style="height: 500px;">
<el-input v-model="selectKeyword" placeholder="请输入内容" @input="selectKeywordChange('1')"></el-input>
<el-table :data="selectData" border height="440" style="width: 100%;margin-top:10px;">
<el-table-column prop="name" label="名称">
</el-table-column>
<el-table-column fixed="right" label="操作" width="80" align="center">
<template #default="scope">
<span
style="display: flex;display: -webkit-flex;justify-content: space-around;-webkit-justify-content: space-around; ">
<img src="@/assets/MenuIcon/select.png" alt="" title="选择"
@click="submitSelect(scope.row)" style="cursor: pointer;">
<img src="@/assets/MenuIcon/lbcz_sc.png" alt="" title="删除"
@click="delSelect(scope.row, '1')" style="cursor: pointer;">
</span>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</div>
</template>