queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(TsNodes::getTaskId, taskId);
+ return this.list(queryWrapper);
+ }
+
}
diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java
index 0b09e9f..a121dee 100644
--- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java
+++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsTaskServiceImpl.java
@@ -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;
/**
*
@@ -93,6 +110,9 @@ public class TsTaskServiceImpl extends ServiceImpl 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;
@@ -533,98 +553,97 @@ public class TsTaskServiceImpl extends ServiceImpl impleme
Boolean value = false;
try {
-
-
//循环所有的ID
for (String taskId : dataset) {
- //删除项目
- TsTask tsTask = tsTaskMapper.selectById(taskId);
- TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
- // 删除节点表
- LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper<>();
- deleteWrapper.eq(TsNodes::getTaskId, taskId);
- tsNodesMapper.delete(deleteWrapper);
+ try {
+ //获取试验任务及任务编码
+ TsTask tsTask = tsTaskMapper.selectById(taskId);
+ TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
- // 删除文件表
- LambdaQueryWrapper deleteWrapperFiles = new LambdaQueryWrapper<>();
- deleteWrapperFiles.eq(TsFiles::getTaskId, taskId);
- tsFilesMapper.delete(deleteWrapperFiles);
- //todo 删除文件表数据
-// tsFilesMapper.deleteSdFilesBytaskId(taskId);
+ // 删除节点表
+ LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper<>();
+ deleteWrapper.eq(TsNodes::getTaskId, taskId);
+ tsNodesMapper.delete(deleteWrapper);
+ // 删除文件表
+ LambdaQueryWrapper deleteWrapperFiles = new LambdaQueryWrapper<>();
+ deleteWrapperFiles.eq(TsFiles::getTaskId, taskId);
+ tsFilesMapper.delete(deleteWrapperFiles);
-// 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 localStorageSource = getStorageConfig(tsTask.getLocalStorageId());
+ deleteStorageFolder(localStorageSource, tsTask);
- StorageSource storageSource = getStorageConfig(tsTask.getLocalStorageId());
- // 删除 local 中的文件夹 项目文件夹
- List deleteItemList = new ArrayList<>();
- BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
- deleteItemData.setName(tsTask.getTaskName());
- deleteItemData.setPassword("");
- deleteItemData.setPath("/");
- deleteItemData.setType(FileTypeEnum.FOLDER);
- deleteItemList.add(deleteItemData);
-
- BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
- batchDeleteRequest.setDeleteItems(deleteItemList);
- batchDeleteRequest.setStorageKey(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++;
+ // 如果有备份存储空间,也删除备份存储空间中的文件夹
+ if (tsTask.getBackupStorageId() != null && tsTask.getBackupStorageId() > 0) {
+ StorageSource backupStorageSource = getStorageConfig(tsTask.getBackupStorageId());
+ deleteStorageFolder(backupStorageSource, tsTask);
}
- }
-
- if (deleteSuccessCount >= 1) {
- // 删除当前项目
+ // 删除当前试验任务
int deleteCount = tsTaskMapper.deleteById(taskId);
if (deleteCount == 1) {
- LOGGER.info("tstask表结删除改成功");
+ LOGGER.info("tstask表结构删除成功");
value = true;
} else {
LOGGER.error("tstask表结构删除失败");
value = false;
}
- } else {
- value = false;
+ } finally {
+ // 每次处理完一个任务后清理上下文
+ TableNameContextHolder.clear();
}
-
-
}
-
return value;
} catch (Exception e) {
-
- } finally {
- TableNameContextHolder.clear();
+ LOGGER.error("删除试验任务时发生异常", e);
+ return false;
+ }
+ }
+
+
+ // 这个方法会递归删除试验任务文件夹及其下的所有子文件夹和文件。当调用 fileService.deleteFolder() 方法时,存储服务实现会自动处理递归删除逻辑,确保整个文件夹树都被清除。
+ //这是文件存储系统的基本特性,不论是本地文件系统还是MinIO等云存储服务,在删除文件夹时都会删除其包含的所有内容。
+ private void deleteStorageFolder(StorageSource storageSource, TsTask tsTask) {
+ try {
+ List deleteItemList = new ArrayList<>();
+ BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem();
+ deleteItemData.setName(tsTask.getTaskName());
+ deleteItemData.setPassword("");
+ deleteItemData.setPath("/");
+ deleteItemData.setType(FileTypeEnum.FOLDER);
+ deleteItemList.add(deleteItemData);
+
+ BatchDeleteRequest batchDeleteRequest = new BatchDeleteRequest();
+ batchDeleteRequest.setDeleteItems(deleteItemList);
+ batchDeleteRequest.setStorageKey(storageSource.getKey());
+ AbstractBaseFileService> fileService = storageSourceContext.getByStorageKey(batchDeleteRequest.getStorageKey());
+
+ int deleteSuccessCount = 0;
+ 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++;
+ 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);
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.error("删除存储空间文件夹时发生异常, 存储类型: {}", storageSource.getKey(), e);
}
- return value;
}
/**********************************
@@ -645,11 +664,11 @@ public class TsTaskServiceImpl extends ServiceImpl impleme
for (TsTask tsTask : tsTasks) {
TableNameContextHolder.setTaskCode(tsTask.getTaskCode());
- // 查询是否有备份路径或非空路径的文件
+ // 查询 backup_path 既不为 null 也不为空字符串的记录
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TsFiles::getTaskId, tsTask.getId())
- .and(wrapper -> wrapper.isNotNull(TsFiles::getBackupPath)
- .or().ne(TsFiles::getBackupPath, ""));
+ .isNotNull(TsFiles::getBackupPath)
+ .ne(TsFiles::getBackupPath, "");
int count = tsFilesService.count(queryWrapper);
@@ -719,4 +738,608 @@ public class TsTaskServiceImpl extends ServiceImpl impleme
return storageSourceMapper.selectOne(new LambdaQueryWrapper().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 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 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 sqlFiles = extractSqlFromZip(zipBytes);
+
+ // 2. 替换 SQL 文件中的任务编号,存储空间编号。
+ List 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 ddlSqlList = new ArrayList<>();
+ List 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 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 extractSqlFromZip(byte[] zipBytes) throws IOException {
+ List 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)+"')([^)]*)(? parseSqlStatements(String sqlContent) {
+ List 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 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 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 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 mergeInsertStatements(List sqlStatements) {
+ Map> 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 mergedSql = new ArrayList<>();
+ for (Map.Entry> entry : groupedStatements.entrySet()) {
+ String key = entry.getKey();
+ List 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";
+ }
+ }
+
+
+
}
diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java
index 681fa5e..8fe2f12 100644
--- a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java
+++ b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java
@@ -135,7 +135,28 @@ public class LocalServiceImpl extends AbstractProxyTransferService {
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,61 @@ public class LocalServiceImpl extends AbstractProxyTransferService {
checkPathSecurity(path);
checkNameSecurity(name);
- return deleteFile(path, name);
+ String fullPath = StringUtils.concat(param.getFilePath(), path, name);
+ 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);
+ }
+ }
+ }
}
diff --git a/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java b/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java
index cfcfaab..0b7a9dc 100644
--- a/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java
+++ b/java/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java
@@ -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 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 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 defaultMap = new HashMap<>();
+ defaultMap.put("nickname", "Anonymous");
+ defaultMap.put("username", "anonymous");
+ return defaultMap;
+ }
+
+ // 确保是 UsernamePasswordAuthenticationToken 类型
+ if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
+ Map 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 map = new HashMap<>();
@@ -87,6 +116,7 @@ public class UserServiceImpl extends ServiceImpl impleme
return map;
}
+
@Override
public SysUser getUserInfo() {
UsernamePasswordAuthenticationToken authentication =
diff --git a/java/src/main/resources/application-dev.yml b/java/src/main/resources/application-dev.yml
index 9d46199..aa27695 100644
--- a/java/src/main/resources/application-dev.yml
+++ b/java/src/main/resources/application-dev.yml
@@ -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
diff --git a/java/src/main/resources/application-prod.yml b/java/src/main/resources/application-prod.yml
index 5b8a701..f5fca98 100644
--- a/java/src/main/resources/application-prod.yml
+++ b/java/src/main/resources/application-prod.yml
@@ -1,41 +1,50 @@
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
+ 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 +78,7 @@ ip:
file-space: #项目文档空间
- system: D:\file\system\ #单独上传的文件
+ system: /data/local-data/ #单独上传的文件
file-system:
preview:
diff --git a/java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml b/java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml
index 3ed6411..e91c08d 100644
--- a/java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml
+++ b/java/src/main/resources/mapper/experimentalData/TsTaskMapper.xml
@@ -1,5 +1,9 @@
-
+