feat:1.提供打包成 exe 任务 2.给第一、二步添加进度条,第三步暂未添加 3.规范第二步UI控制器里方法逻辑,只负责提交Task给另一线程执行,实际任务由Taskc类的 call方法组织调用相关的生成类和比较类来完成任务的回调 4.调整日志输出格式,release.log为标准输出格式,其它则仅供调试使用
This commit is contained in:
parent
1bf1579e43
commit
204b1f38bf
40
build.gradle
40
build.gradle
|
|
@ -1,3 +1,5 @@
|
|||
import org.panteleyev.jpackage.ImageType
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
|
|
@ -12,8 +14,7 @@ plugins {
|
|||
id 'org.openjfx.javafxplugin' version '0.1.0'
|
||||
// 不再使用模块插件,移除 org.javamodularity.moduleplugin
|
||||
// id 'org.javamodularity.moduleplugin' version '1.8.12'
|
||||
// 你可以根据需要保留 jlink 插件,但推荐取消模块化后暂时不用它
|
||||
id 'org.beryx.jlink' version '2.26.0' apply false
|
||||
id("org.panteleyev.jpackageplugin") version "1.6.1"
|
||||
}
|
||||
|
||||
group = project_group
|
||||
|
|
@ -62,7 +63,8 @@ dependencies {
|
|||
implementation "org.openjfx:javafx-controls:$javafxVersion:$javafxPlatform"
|
||||
implementation "org.openjfx:javafx-fxml:$javafxVersion:$javafxPlatform"
|
||||
|
||||
// 你项目的其他依赖(老库走classpath)
|
||||
implementation 'ch.qos.logback:logback-classic:1.5.18'
|
||||
implementation 'ch.qos.logback:logback-core:1.5.18'
|
||||
implementation 'ch.qos.logback:logback-classic:1.5.6'
|
||||
implementation 'commons-cli:commons-cli:1.9.0'
|
||||
implementation 'com.alibaba:easyexcel:4.0.3'
|
||||
|
|
@ -145,7 +147,35 @@ tasks.register('buildFatJar', Jar) {
|
|||
}
|
||||
with jar // 继承主 jar 的内容
|
||||
|
||||
archiveBaseName.set('doc-check-tool-cli')
|
||||
archiveVersion.set('1.0')
|
||||
archiveBaseName.set(project_name)
|
||||
archiveVersion.set(project_version)
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
// =================== 轻便版打包 ===================
|
||||
|
||||
tasks.register('buildPortable', Exec) {
|
||||
group = 'distribution'
|
||||
description = 'Build portable EXE (no installer)'
|
||||
|
||||
dependsOn buildFatJar
|
||||
|
||||
commandLine 'jpackage',
|
||||
'--name', 'DocCheckTool',
|
||||
'--input', "$buildDir/libs",
|
||||
'--main-jar', "${project_name}-${project_version}.jar",
|
||||
'--main-class', application.mainClass.get(),
|
||||
'--type', 'app-image', // ⚠️ 轻便版不生成安装器
|
||||
'--app-version', project_version,
|
||||
'--vendor', 'r3944realms',
|
||||
'--dest', "$buildDir/distributions",
|
||||
'--java-options', '-Dfile.encoding=UTF-8',
|
||||
'--java-options', '-Xmx512m',
|
||||
'--java-options', '-Xms256m',
|
||||
'--verbose',
|
||||
'--icon', file('src/main/resources/img/logo256x.ico').absolutePath
|
||||
|
||||
doFirst {
|
||||
mkdir "$buildDir/distributions"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
project_group =top.r3944realms.docchecktoolrefacored
|
||||
project_version = 1.0-SNAPSHOT
|
||||
project_name = doc-check-tool
|
||||
project_version = 1.0
|
||||
|
|
@ -4,7 +4,7 @@ import javafx.stage.DirectoryChooser;
|
|||
import javafx.stage.FileChooser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.core.Setting;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
|
@ -38,10 +38,10 @@ public enum System {
|
|||
if (Files.exists(configPath)) {
|
||||
try (InputStream input = new FileInputStream(configPath.toFile())) {
|
||||
properties.load(input);
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "配置文件加载成功: {}", configPath);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "配置文件加载成功: {}", configPath);
|
||||
INSTANCE.setting = propertiesToSetting(properties);
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "读取配置文件失败: {}, 使用默认配置", e.getMessage());
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "读取配置文件失败: {}, 使用默认配置", e.getMessage());
|
||||
INSTANCE.setting = defaultSetting();
|
||||
settingToProperties(INSTANCE.setting, properties);
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ public enum System {
|
|||
INSTANCE.setting = defaultSetting();
|
||||
settingToProperties(INSTANCE.setting, properties);
|
||||
saveSettings(); // 首次启动保存默认配置
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "配置文件不存在,已创建默认配置: {}", configPath);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "配置文件不存在,已创建默认配置: {}", configPath);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
|
|
@ -72,13 +72,13 @@ public enum System {
|
|||
try (OutputStream output = new FileOutputStream(configPath.toFile())) {
|
||||
properties.store(new OutputStreamWriter(output, StandardCharsets.UTF_8),
|
||||
"DocCheckTool Configuration");
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "配置文件保存成功: {}", configPath);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "配置文件保存成功: {}", configPath);
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "保存配置文件失败: {}", e.getMessage());
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "保存配置文件失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "创建配置目录失败: {}", e.getMessage());
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "创建配置目录失败: {}", e.getMessage());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
|
@ -86,14 +86,28 @@ public enum System {
|
|||
|
||||
/** 获取配置文件路径 */
|
||||
private static Path getConfigPath() {
|
||||
String userHome = java.lang.System.getProperty("user.home");
|
||||
return Paths.get(userHome, ".docchecktool", CONFIG_FILE_NAME);
|
||||
try {
|
||||
// 获取程序运行目录(Jar 所在目录)
|
||||
File jarFile = new File(System.class.getProtectionDomain()
|
||||
.getCodeSource().getLocation().toURI());
|
||||
File appDir = jarFile.getParentFile();
|
||||
Path configDir = appDir.toPath().resolve("config");
|
||||
if (!Files.exists(configDir)) {
|
||||
Files.createDirectories(configDir);
|
||||
}
|
||||
return configDir.resolve(CONFIG_FILE_NAME);
|
||||
} catch (Exception e) {
|
||||
// 出错 fallback 到当前工作目录
|
||||
Path fallbackDir = Paths.get(java.lang.System.getProperty("user.dir"), "config");
|
||||
try { Files.createDirectories(fallbackDir); } catch (IOException ignored) {}
|
||||
return fallbackDir.resolve(CONFIG_FILE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
/** 将Setting对象转换为Properties */
|
||||
private static void settingToProperties(Setting setting, Properties props) {
|
||||
props.setProperty("singleTimeout", String.valueOf(setting.getSingleTimeout()));
|
||||
props.setProperty("totalTimeout", String.valueOf(setting.getTotalTimeout()));
|
||||
props.setProperty("singleTimeout", String.valueOf(setting.getScanTimeout()));
|
||||
props.setProperty("totalTimeout", String.valueOf(setting.getTaskTimeout()));
|
||||
props.setProperty("enableStep", String.valueOf(setting.isEnableStep()));
|
||||
}
|
||||
|
||||
|
|
@ -102,23 +116,23 @@ public enum System {
|
|||
Setting s = new Setting();
|
||||
|
||||
try {
|
||||
s.setSingleTimeout(Long.parseLong(props.getProperty("singleTimeout", String.valueOf(DEFAULT_SINGLE_TIMEOUT))));
|
||||
s.setScanTimeout(Long.parseLong(props.getProperty("scanTimeOutS", String.valueOf(DEFAULT_SINGLE_TIMEOUT))));
|
||||
} catch (NumberFormatException e) {
|
||||
s.setSingleTimeout(DEFAULT_SINGLE_TIMEOUT);
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "singleTimeout格式错误,使用默认值{}", DEFAULT_SINGLE_TIMEOUT);
|
||||
s.setScanTimeout(DEFAULT_SINGLE_TIMEOUT);
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "scanTimeOutS格式错误,使用默认值{}", DEFAULT_SINGLE_TIMEOUT);
|
||||
}
|
||||
|
||||
try {
|
||||
s.setTotalTimeout(Long.parseLong(props.getProperty("totalTimeout", String.valueOf(DEFAULT_TOTAL_TIMEOUT))));
|
||||
s.setTaskTimeout(Long.parseLong(props.getProperty("taskTimeOutS", String.valueOf(DEFAULT_TOTAL_TIMEOUT))));
|
||||
} catch (NumberFormatException e) {
|
||||
s.setTotalTimeout(DEFAULT_TOTAL_TIMEOUT);
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "totalTimeout格式错误,使用默认值{}", DEFAULT_TOTAL_TIMEOUT);
|
||||
s.setTaskTimeout(DEFAULT_TOTAL_TIMEOUT);
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "taskTimeOutS格式错误,使用默认值{}", DEFAULT_TOTAL_TIMEOUT);
|
||||
}
|
||||
try {
|
||||
s.setEnableStep(Boolean.parseBoolean(props.getProperty("enableStep", String.valueOf(Boolean.FALSE))));
|
||||
} catch (Exception e) {
|
||||
s.setEnableStep(DEFAULT_ENABLE_STEP);
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "enableStep格式错误,使用默认值{}", DEFAULT_TOTAL_TIMEOUT);
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "enableStep格式错误,使用默认值{}", DEFAULT_TOTAL_TIMEOUT);
|
||||
}
|
||||
|
||||
return s;
|
||||
|
|
@ -127,8 +141,8 @@ public enum System {
|
|||
/** 获取默认Setting */
|
||||
private static Setting defaultSetting() {
|
||||
Setting s = new Setting();
|
||||
s.setSingleTimeout(DEFAULT_SINGLE_TIMEOUT);
|
||||
s.setTotalTimeout(DEFAULT_TOTAL_TIMEOUT);
|
||||
s.setScanTimeout(DEFAULT_SINGLE_TIMEOUT);
|
||||
s.setTaskTimeout(DEFAULT_TOTAL_TIMEOUT);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
@ -185,4 +199,11 @@ public enum System {
|
|||
}
|
||||
return directoryChooser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CPU核心数
|
||||
*/
|
||||
public static Integer getAvailableProcessors() {
|
||||
return Runtime.getRuntime().availableProcessors();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
|||
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
||||
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
|
||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
|
|
@ -103,7 +103,7 @@ public class CliProcessor {
|
|||
printHelp(options);
|
||||
System.exit(1);
|
||||
} catch (Exception e) {
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "Error processing CLI command", e);
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "Error processing CLI command", e);
|
||||
System.err.println("Error: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
package top.r3944realms.docchecktoolrefactored.core;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.docchecktoolrefactored.System;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class AddressFileComparator {
|
||||
|
|
@ -17,7 +27,20 @@ public class AddressFileComparator {
|
|||
PAGE_LEVEL, // 页面级比较
|
||||
FILE_LEVEL // 文件级比较
|
||||
}
|
||||
// 进度回调接口
|
||||
public interface ProgressCallback {
|
||||
default void onPhaseStarted(Phase phase) {}
|
||||
default void onPhaseProgress(Phase phase, int current, int total) {}
|
||||
default void onPhaseCompleted(Phase phase) {}
|
||||
}
|
||||
|
||||
// 比较阶段
|
||||
public enum Phase {
|
||||
READ_PHYSICAL_CSV,
|
||||
READ_LOGICAL_CSV,
|
||||
COMPARE_FORWARD,
|
||||
COMPARE_BACKWARD
|
||||
}
|
||||
@Getter
|
||||
public static class ComparisonResult {
|
||||
private final int physicalRecordsCount; // 物理文件记录数
|
||||
|
|
@ -40,224 +63,270 @@ public class AddressFileComparator {
|
|||
this.pathMismatchResults = pathMismatchResults;
|
||||
this.pageCountMismatchResults = pageCountMismatchResults;
|
||||
}
|
||||
public static @NotNull String generateResult(AddressFileComparator.CompareMode compareMode, AddressFileComparator.ComparisonResult result) {
|
||||
StringBuilder resultText = new StringBuilder();
|
||||
resultText.append("读取物理地址文件记录数: ").append(result.getPhysicalRecordsCount()).append("\n");
|
||||
resultText.append("读取逻辑地址文件记录数: ").append(result.getLogicalRecordsCount()).append("\n\n");
|
||||
|
||||
// 为向后兼容保留原来的构造函数
|
||||
public ComparisonResult(int physicalRecordsCount,
|
||||
int logicalRecordsCount,
|
||||
List<String> forwardResults,
|
||||
List<String> backwardResults,
|
||||
List<String> pathMismatchResults) {
|
||||
this(physicalRecordsCount, logicalRecordsCount, forwardResults,
|
||||
backwardResults, pathMismatchResults, new ArrayList<>());
|
||||
if (!result.getPathMismatchResults().isEmpty()) {
|
||||
resultText.append("文件名相同但路径不一致的记录数量: ")
|
||||
.append(result.getPathMismatchResults().size()).append("\n");
|
||||
result.getPathMismatchResults().forEach(s -> resultText.append("\t").append(s).append("\n"));
|
||||
} else resultText.append("没有路径错误\n\n");
|
||||
|
||||
if (compareMode == AddressFileComparator.CompareMode.FILE_LEVEL &&
|
||||
!result.getPageCountMismatchResults().isEmpty()) {
|
||||
resultText.append("文件名和路径相同但页数不一致的记录数量: ")
|
||||
.append(result.getPageCountMismatchResults().size()).append("\n");
|
||||
result.getPageCountMismatchResults().forEach(s -> resultText.append("\t").append(s).append("\n"));
|
||||
}
|
||||
|
||||
if (!result.getForwardComparisonResults().isEmpty()) {
|
||||
resultText.append("物理文件在逻辑文件中未找到的记录数量: ")
|
||||
.append(result.getForwardComparisonResults().size()).append("\n");
|
||||
result.getForwardComparisonResults().forEach(s -> resultText.append("\t").append(s).append("\n"));
|
||||
} else resultText.append("没有物理存在而逻辑不存在的文件\n\n");
|
||||
|
||||
if (!result.getBackwardComparisonResults().isEmpty()) {
|
||||
resultText.append("逻辑文件在物理文件中未找到的记录数量: ")
|
||||
.append(result.getBackwardComparisonResults().size()).append("\n");
|
||||
result.getBackwardComparisonResults().forEach(s -> resultText.append("\t").append(s).append("\n"));
|
||||
} else resultText.append("没有逻辑存在而物理不存在的文件\n");
|
||||
|
||||
if (result.getPathMismatchResults().isEmpty() &&
|
||||
result.getForwardComparisonResults().isEmpty() &&
|
||||
result.getBackwardComparisonResults().isEmpty()) {
|
||||
resultText.append("\n所有文件比对一致,无差异。\n");
|
||||
}
|
||||
|
||||
return resultText.toString();
|
||||
}
|
||||
public static String generateComparisonResults(ComparisonResult result, CompareMode compareMode) {
|
||||
return generateComparisonResults(
|
||||
result.physicalRecordsCount,
|
||||
result.logicalRecordsCount,
|
||||
result.forwardComparisonResults,
|
||||
result.backwardComparisonResults,
|
||||
result.pathMismatchResults,
|
||||
result.pageCountMismatchResults,
|
||||
compareMode
|
||||
);
|
||||
}
|
||||
|
||||
private static String generateComparisonResults(int physicalCount, int logicalCount,
|
||||
List<String> forwardResults, List<String> backwardResults,
|
||||
List<String> pathMismatchResults, List<String> pageCountMismatchResults,
|
||||
CompareMode compareMode) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("=== 文件比较结果 ===\n");
|
||||
sb.append("物理地址文件记录数: ").append(physicalCount).append("\n");
|
||||
sb.append("逻辑地址文件记录数: ").append(logicalCount).append("\n");
|
||||
|
||||
if (pathMismatchResults.isEmpty()) {
|
||||
sb.append("没有路径错误\n");
|
||||
} else {
|
||||
sb.append("文件名相同但路径不一致的记录数量: ").append(pathMismatchResults.size()).append("\n");
|
||||
pathMismatchResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
|
||||
if (compareMode == CompareMode.FILE_LEVEL) {
|
||||
if (pageCountMismatchResults.isEmpty()) {
|
||||
sb.append("没有页数错误\n");
|
||||
} else {
|
||||
sb.append("文件名和路径相同但页数不一致的记录数量: ")
|
||||
.append(pageCountMismatchResults.size()).append("\n");
|
||||
pageCountMismatchResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardResults.isEmpty()) {
|
||||
sb.append("没有物理存在而逻辑不存在的文件\n");
|
||||
} else {
|
||||
sb.append("物理文件在逻辑文件中未找到的记录数量: ").append(forwardResults.size()).append("\n");
|
||||
forwardResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
|
||||
if (backwardResults.isEmpty()) {
|
||||
sb.append("没有逻辑存在而物理不存在的文件\n");
|
||||
} else {
|
||||
sb.append("逻辑文件在物理文件中未找到的记录数量: ").append(backwardResults.size()).append("\n");
|
||||
backwardResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
|
||||
sb.append("=== 比较完成 ===");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ComparisonResult compareFiles(String physicalAddressFilePath, String logicalAddressFilePath) {
|
||||
return compareFiles(physicalAddressFilePath, logicalAddressFilePath, CompareMode.PAGE_LEVEL);
|
||||
private final ExecutorService executor;
|
||||
public AddressFileComparator(int threadPoolSize) {
|
||||
this.executor = Executors.newFixedThreadPool(threadPoolSize);
|
||||
}
|
||||
|
||||
public ComparisonResult compareFiles(String physicalAddressFilePath, String logicalAddressFilePath, CompareMode compareMode) {
|
||||
List<String[]> physicalRecords = readCSV(physicalAddressFilePath);
|
||||
List<String[]> logicalRecords = readCSV(logicalAddressFilePath);
|
||||
|
||||
// 记录读取的行数(不包括标题行)
|
||||
int physicalCount = physicalRecords.size();
|
||||
int logicalCount = logicalRecords.size();
|
||||
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "读取物理地址文件记录数: {}, 读取逻辑地址文件记录数: {}", physicalCount, logicalCount);
|
||||
|
||||
List<String> forwardComparisonResults = new ArrayList<>(); // 物理文件在逻辑文件中未找到
|
||||
List<String> backwardComparisonResults = new ArrayList<>(); // 逻辑文件在物理文件中未找到
|
||||
List<String> pathMismatchResults = new ArrayList<>(); // 文件名相同但路径不一致
|
||||
List<String> pageCountMismatchResults = new ArrayList<>(); // 文件名和路径相同但页数不一致(仅文件级)
|
||||
|
||||
// 正向比较:遍历物理文件,检查是否在逻辑文件中存在
|
||||
for (String[] physicalRecord : physicalRecords) {
|
||||
// 确保数据行有足够列数
|
||||
if (compareMode == CompareMode.PAGE_LEVEL && physicalRecord.length < 2) continue;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && physicalRecord.length < 3) continue;
|
||||
|
||||
String physicalFileName = physicalRecord[0];
|
||||
String physicalAddress = physicalRecord[1];
|
||||
String physicalPageCount = compareMode == CompareMode.FILE_LEVEL ? physicalRecord[2] : null;
|
||||
|
||||
boolean found = false;
|
||||
for (String[] logicalRecord : logicalRecords) {
|
||||
// 确保数据行有足够列数
|
||||
if (compareMode == CompareMode.PAGE_LEVEL && logicalRecord.length < 2) continue;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && logicalRecord.length < 3) continue;
|
||||
|
||||
String logicalFileName = logicalRecord[0];
|
||||
String logicalAddress = logicalRecord[1];
|
||||
String logicalPageCount = compareMode == CompareMode.FILE_LEVEL ? logicalRecord[2] : null;
|
||||
|
||||
if (physicalFileName.equals(logicalFileName)) {
|
||||
found = true;
|
||||
// 文件名相同,比较路径
|
||||
if (!physicalAddress.equals(logicalAddress)) {
|
||||
pathMismatchResults.add("文件名=" + physicalFileName +
|
||||
", 物理地址=" + physicalAddress +
|
||||
", 逻辑地址=" + logicalAddress);
|
||||
}
|
||||
// 如果是文件级比较且路径相同,再比较页数
|
||||
else if (compareMode == CompareMode.FILE_LEVEL &&
|
||||
physicalPageCount != null && logicalPageCount != null &&
|
||||
!physicalPageCount.equals(logicalPageCount)) {
|
||||
pageCountMismatchResults.add("文件名=" + physicalFileName +
|
||||
", 物理地址=" + physicalAddress +
|
||||
", 逻辑地址=" + logicalAddress +
|
||||
", 物理页数=" + physicalPageCount +
|
||||
", 逻辑页数=" + logicalPageCount);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果在逻辑文件中未找到该物理文件记录
|
||||
if (!found) {
|
||||
String result = "文件名=" + physicalFileName + ", 物理地址=" + physicalAddress;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && physicalPageCount != null) {
|
||||
result += ", 物理页数=" + physicalPageCount;
|
||||
}
|
||||
forwardComparisonResults.add(result);
|
||||
}
|
||||
public AddressFileComparator() {
|
||||
this.executor = Executors.newFixedThreadPool(System.getAvailableProcessors());
|
||||
}
|
||||
@Setter
|
||||
private ProgressCallback progressCallback;
|
||||
// 安全调用回调方法
|
||||
private void safeOnPhaseStarted(Phase phase) {
|
||||
if (progressCallback != null) {
|
||||
progressCallback.onPhaseStarted(phase);
|
||||
}
|
||||
|
||||
// 反向比较:遍历逻辑文件,检查是否在物理文件中存在
|
||||
for (String[] logicalRecord : logicalRecords) {
|
||||
// 确保数据行有足够列数
|
||||
if (compareMode == CompareMode.PAGE_LEVEL && logicalRecord.length < 2) continue;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && logicalRecord.length < 3) continue;
|
||||
|
||||
String logicalFileName = logicalRecord[0];
|
||||
String logicalAddress = logicalRecord[1];
|
||||
String logicalPageCount = compareMode == CompareMode.FILE_LEVEL ? logicalRecord[2] : null;
|
||||
|
||||
boolean found = false;
|
||||
for (String[] physicalRecord : physicalRecords) {
|
||||
// 确保数据行有足够列数
|
||||
if (compareMode == CompareMode.PAGE_LEVEL && physicalRecord.length < 2) continue;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && physicalRecord.length < 3) continue;
|
||||
|
||||
String physicalFileName = physicalRecord[0];
|
||||
|
||||
if (logicalFileName.equals(physicalFileName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果在物理文件中未找到该逻辑文件记录
|
||||
if (!found) {
|
||||
String result = "文件名=" + logicalFileName + ", 逻辑地址=" + logicalAddress;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && logicalPageCount != null) {
|
||||
result += ", 逻辑页数=" + logicalPageCount;
|
||||
}
|
||||
backwardComparisonResults.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 将比较结果记录到日志中
|
||||
logComparisonResults(physicalCount, logicalCount, forwardComparisonResults,
|
||||
backwardComparisonResults, pathMismatchResults, pageCountMismatchResults, compareMode);
|
||||
|
||||
return new ComparisonResult(
|
||||
physicalCount,
|
||||
logicalCount,
|
||||
forwardComparisonResults,
|
||||
backwardComparisonResults,
|
||||
pathMismatchResults,
|
||||
pageCountMismatchResults
|
||||
);
|
||||
}
|
||||
|
||||
private List<String[]> readCSV(String filePath) {
|
||||
private void safeOnPhaseProgress(Phase phase, int current, int total) {
|
||||
if (progressCallback != null) {
|
||||
progressCallback.onPhaseProgress(phase, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
private void safeOnPhaseCompleted(Phase phase) {
|
||||
if (progressCallback != null) {
|
||||
progressCallback.onPhaseCompleted(phase);
|
||||
}
|
||||
}
|
||||
public void shutdown() {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
public CompletableFuture<ComparisonResult> compareFiles(String physicalFilePath, String logicalFilePath, CompareMode compareMode) {
|
||||
// 读取物理文件
|
||||
CompletableFuture<List<String[]>> physicalFuture = CompletableFuture.supplyAsync(() -> {
|
||||
safeOnPhaseStarted(Phase.READ_PHYSICAL_CSV);
|
||||
List<String[]> list = readCSV(physicalFilePath, progressCallback, Phase.READ_PHYSICAL_CSV);
|
||||
safeOnPhaseCompleted(Phase.READ_PHYSICAL_CSV);
|
||||
return list;
|
||||
}, executor);
|
||||
|
||||
// 读取逻辑文件
|
||||
CompletableFuture<List<String[]>> logicalFuture = CompletableFuture.supplyAsync(() -> {
|
||||
safeOnPhaseStarted(Phase.READ_LOGICAL_CSV);
|
||||
List<String[]> list = readCSV(logicalFilePath, progressCallback, Phase.READ_LOGICAL_CSV);
|
||||
safeOnPhaseCompleted(Phase.READ_LOGICAL_CSV);
|
||||
return list;
|
||||
}, executor);
|
||||
|
||||
// 两个 CSV 读取完成后进行比较
|
||||
return physicalFuture.thenCombineAsync(logicalFuture, (physicalRecords, logicalRecords) -> {
|
||||
int physicalCount = physicalRecords.size();
|
||||
int logicalCount = logicalRecords.size();
|
||||
|
||||
// 线程安全集合
|
||||
List<String> forwardComparisonResults = Collections.synchronizedList(new ArrayList<>());
|
||||
List<String> backwardComparisonResults = Collections.synchronizedList(new ArrayList<>());
|
||||
List<String> pathMismatchResults = Collections.synchronizedList(new ArrayList<>());
|
||||
List<String> pageCountMismatchResults = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
Map<String, String[]> logicalMap = logicalRecords.parallelStream()
|
||||
.filter(r -> r.length >= (compareMode == CompareMode.FILE_LEVEL ? 3 : 2))
|
||||
.collect(Collectors.toConcurrentMap(r -> r[0], r -> r));
|
||||
|
||||
// 正向比较
|
||||
safeOnPhaseStarted(Phase.COMPARE_FORWARD);
|
||||
AtomicInteger forwardCounter = new AtomicInteger(0);
|
||||
physicalRecords.parallelStream()
|
||||
.filter(r -> r.length >= (compareMode == CompareMode.FILE_LEVEL ? 3 : 2))
|
||||
.forEach(physicalRecord -> {
|
||||
String physicalFileName = physicalRecord[0];
|
||||
String physicalAddress = physicalRecord[1];
|
||||
String physicalPageCount = compareMode == CompareMode.FILE_LEVEL ? physicalRecord[2] : null;
|
||||
|
||||
String[] logicalRecord = logicalMap.get(physicalFileName);
|
||||
if (logicalRecord == null) {
|
||||
String result = "文件名=" + physicalFileName + ", 物理地址=" + physicalAddress;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && physicalPageCount != null) {
|
||||
result += ", 物理页数=" + physicalPageCount;
|
||||
}
|
||||
forwardComparisonResults.add(result);
|
||||
} else {
|
||||
if (!physicalAddress.equals(logicalRecord[1])) {
|
||||
pathMismatchResults.add("文件名=" + physicalFileName +
|
||||
", 物理地址=" + physicalAddress +
|
||||
", 逻辑地址=" + logicalRecord[1]);
|
||||
}
|
||||
if (compareMode == CompareMode.FILE_LEVEL &&
|
||||
physicalPageCount != null && !physicalPageCount.equals(logicalRecord[2])) {
|
||||
pageCountMismatchResults.add("文件名=" + physicalFileName +
|
||||
", 物理地址=" + physicalAddress +
|
||||
", 逻辑地址=" + logicalRecord[1] +
|
||||
", 物理页数=" + physicalPageCount +
|
||||
", 逻辑页数=" + logicalRecord[2]);
|
||||
}
|
||||
}
|
||||
|
||||
int done = forwardCounter.incrementAndGet();
|
||||
safeOnPhaseProgress(Phase.COMPARE_FORWARD, done, physicalRecords.size());
|
||||
});
|
||||
safeOnPhaseCompleted(Phase.COMPARE_FORWARD);
|
||||
|
||||
// 反向比较
|
||||
safeOnPhaseStarted(Phase.COMPARE_BACKWARD);
|
||||
AtomicInteger backwardCounter = new AtomicInteger(0);
|
||||
Map<String, String[]> physicalMap = physicalRecords.parallelStream()
|
||||
.filter(r -> r.length >= (compareMode == CompareMode.FILE_LEVEL ? 3 : 2))
|
||||
.collect(Collectors.toConcurrentMap(r -> r[0], r -> r));
|
||||
|
||||
logicalRecords.parallelStream()
|
||||
.filter(r -> r.length >= (compareMode == CompareMode.FILE_LEVEL ? 3 : 2))
|
||||
.forEach(logicalRecord -> {
|
||||
String logicalFileName = logicalRecord[0];
|
||||
String logicalAddress = logicalRecord[1];
|
||||
String logicalPageCount = compareMode == CompareMode.FILE_LEVEL ? logicalRecord[2] : null;
|
||||
|
||||
if (!physicalMap.containsKey(logicalFileName)) {
|
||||
String result = "文件名=" + logicalFileName + ", 逻辑地址=" + logicalAddress;
|
||||
if (compareMode == CompareMode.FILE_LEVEL && logicalPageCount != null) {
|
||||
result += ", 逻辑页数=" + logicalPageCount;
|
||||
}
|
||||
backwardComparisonResults.add(result);
|
||||
}
|
||||
|
||||
int done = backwardCounter.incrementAndGet();
|
||||
safeOnPhaseProgress(Phase.COMPARE_BACKWARD, done, logicalRecords.size());
|
||||
});
|
||||
safeOnPhaseCompleted(Phase.COMPARE_BACKWARD);
|
||||
|
||||
return new ComparisonResult(
|
||||
physicalCount,
|
||||
logicalCount,
|
||||
forwardComparisonResults,
|
||||
backwardComparisonResults,
|
||||
pathMismatchResults,
|
||||
pageCountMismatchResults
|
||||
);
|
||||
}, executor);
|
||||
}
|
||||
|
||||
private List<String[]> readCSV(String filePath, ProgressCallback callback, Phase phase) {
|
||||
List<String[]> records = new ArrayList<>();
|
||||
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "CSV文件不存在: {}", filePath);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "{} CSV文件不存在: {}", phase, filePath);
|
||||
return records;
|
||||
}
|
||||
|
||||
BufferedReader reader = new BufferedReader(new FileReader(file));
|
||||
String line;
|
||||
boolean isFirstLine = true;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// 跳过首行(标题行)
|
||||
if (isFirstLine) {
|
||||
isFirstLine = false;
|
||||
continue;
|
||||
}
|
||||
// 按逗号分隔CSV行数据
|
||||
String[] data = line.split(",");
|
||||
records.add(data);
|
||||
}
|
||||
|
||||
List<String> allLines = reader.lines().skip(1).toList(); // 跳过标题行
|
||||
reader.close();
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "成功读取CSV文件,共 {} 行记录", records.size());
|
||||
|
||||
int total = allLines.size();
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
for (String line : allLines) {
|
||||
records.add(line.split(","));
|
||||
int done = counter.incrementAndGet();
|
||||
if (done % 100 == 0 || done == total) { // 每100行或结束更新
|
||||
callback.onPhaseProgress(phase, done, total);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "读取CSV文件时出错: {}", e.getMessage(), e);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "{} CSV文件读取出错: {}", phase, e.getMessage(), e);
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
private void logComparisonResults(int physicalCount, int logicalCount,
|
||||
List<String> forwardResults, List<String> backwardResults,
|
||||
List<String> pathMismatchResults, List<String> pageCountMismatchResults,
|
||||
CompareMode compareMode) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("=== 文件比较结果 ===\n");
|
||||
sb.append("物理地址文件记录数: ").append(physicalCount).append("\n");
|
||||
sb.append("逻辑地址文件记录数: ").append(logicalCount).append("\n");
|
||||
|
||||
if (pathMismatchResults.isEmpty()) {
|
||||
sb.append("没有路径错误\n");
|
||||
} else {
|
||||
sb.append("文件名相同但路径不一致的记录数量: ").append(pathMismatchResults.size()).append("\n");
|
||||
pathMismatchResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
|
||||
if (compareMode == CompareMode.FILE_LEVEL) {
|
||||
if (pageCountMismatchResults.isEmpty()) {
|
||||
sb.append("没有页数错误\n");
|
||||
} else {
|
||||
sb.append("文件名和路径相同但页数不一致的记录数量: ")
|
||||
.append(pageCountMismatchResults.size()).append("\n");
|
||||
pageCountMismatchResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardResults.isEmpty()) {
|
||||
sb.append("没有物理存在而逻辑不存在的文件\n");
|
||||
} else {
|
||||
sb.append("物理文件在逻辑文件中未找到的记录数量: ").append(forwardResults.size()).append("\n");
|
||||
forwardResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
|
||||
if (backwardResults.isEmpty()) {
|
||||
sb.append("没有逻辑存在而物理不存在的文件\n");
|
||||
} else {
|
||||
sb.append("逻辑文件在物理文件中未找到的记录数量: ").append(backwardResults.size()).append("\n");
|
||||
backwardResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||
}
|
||||
|
||||
sb.append("=== 比较完成 ===");
|
||||
|
||||
log.info(LoggerHelper.RELEASE_MARKER, sb.toString()); // 一次性输出, 减少 I/O
|
||||
}
|
||||
|
||||
// 为向后兼容保留原来的日志方法
|
||||
private void logComparisonResults(int physicalCount, int logicalCount,
|
||||
List<String> forwardResults, List<String> backwardResults,
|
||||
List<String> pathMismatchResults) {
|
||||
logComparisonResults(physicalCount, logicalCount, forwardResults, backwardResults,
|
||||
pathMismatchResults, new ArrayList<>(), CompareMode.PAGE_LEVEL);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,19 @@ public interface AddressFileGenerator {
|
|||
int FILE_TYPE = 2;
|
||||
|
||||
/**
|
||||
* 回调接口
|
||||
*/
|
||||
interface Callback {
|
||||
void onProgress(String message);
|
||||
void onSuccess(String outputPath);
|
||||
void onError(String errorMessage);
|
||||
* 进度回调接口
|
||||
*/
|
||||
|
||||
interface ProgressCallback {
|
||||
default void onPhaseStarted(Phase phase) {}
|
||||
default void onPhaseProgress(Phase phase, int current, int total) {}
|
||||
default void onPhaseCompleted(Phase phase) {}
|
||||
}
|
||||
enum Phase {
|
||||
GENERATE_LOGICAL,
|
||||
GENERATE_PHYSICAL
|
||||
}
|
||||
void setProgressCallback(ProgressCallback callback);
|
||||
|
||||
/**
|
||||
* 生成地址文件
|
||||
|
|
@ -29,12 +35,10 @@ public interface AddressFileGenerator {
|
|||
* @param folderPath 文件夹路径
|
||||
* @param outputFile 输出文件
|
||||
* @param folderType 文件夹类型 ({@link AddressFileGenerator#PAGE_TYPE "页面级"} 或 {@link AddressFileGenerator#FILE_TYPE "文件级"})
|
||||
* @param callback {@link Callback 回调接口}
|
||||
*/
|
||||
void generateAddressFile(
|
||||
String folderPath,
|
||||
File outputFile,
|
||||
@MagicConstant(intValues = {PAGE_TYPE, FILE_TYPE}) int folderType,
|
||||
Callback callback
|
||||
@MagicConstant(intValues = {PAGE_TYPE, FILE_TYPE}) int folderType
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package top.r3944realms.docchecktoolrefactored.core;
|
||||
|
||||
import com.sun.scenario.Settings;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.System;
|
||||
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
||||
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
|
||||
import top.r3944realms.docchecktoolrefactored.model.FileMetadata;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
|
@ -41,7 +41,7 @@ public class DuplicateFinder {
|
|||
@Setter
|
||||
private ProgressCallback progressCallback;
|
||||
private static final int PROGRESS_REPORT_INTERVAL = 100;
|
||||
private static final int BATCH_SIZE = 100;
|
||||
@Getter
|
||||
private final List<Exception> errors = new CopyOnWriteArrayList<>();
|
||||
@Getter
|
||||
private long timeout = -1;
|
||||
|
|
@ -49,16 +49,14 @@ public class DuplicateFinder {
|
|||
this.fileScanner = Objects.requireNonNull(fileScanner);
|
||||
this.hashCalculator = Objects.requireNonNull(hashCalculator);
|
||||
this.enableProgress = enableProgress;
|
||||
// 根据CPU核心数设置线程池大小
|
||||
int poolSize = Runtime.getRuntime().availableProcessors();
|
||||
this.executorService = Executors.newFixedThreadPool(poolSize);
|
||||
this.executorService = Executors.newFixedThreadPool(System.getAvailableProcessors());
|
||||
}
|
||||
public DuplicateFinder(FileScanner fileScanner, FileHashCalculator hashCalculator) {
|
||||
this(fileScanner, hashCalculator, false);
|
||||
}
|
||||
|
||||
public DuplicateFinder applySetting(Setting setting) {
|
||||
this.timeout = setting.getSingleTimeout();
|
||||
this.timeout = setting.getScanTimeout();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +111,7 @@ public class DuplicateFinder {
|
|||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
if (progressCallback != null) progressCallback.onPhaseCompleted(Phase.CALCULATE_HASH);
|
||||
if (enableProgress) System.out.println();
|
||||
if (enableProgress) java.lang.System.out.println();
|
||||
|
||||
// -----------------------------
|
||||
// 第三阶段:构建结果
|
||||
|
|
@ -154,13 +152,13 @@ public class DuplicateFinder {
|
|||
meta.setSize(Files.size(file));
|
||||
sizeGroups.computeIfAbsent(meta.getSize(), k -> new ArrayList<>()).add(meta);
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Failed to get file's size: {}", file);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Failed to get file's size: {}", file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onScanComplete() {}
|
||||
@Override public void onError(Path file, Exception e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Error on scanning file: {}, {}", file, e.getMessage());
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Error on scanning file: {}, {}", file, e.getMessage());
|
||||
errors.add(e);
|
||||
}
|
||||
};
|
||||
|
|
@ -188,7 +186,7 @@ public class DuplicateFinder {
|
|||
current,
|
||||
total);
|
||||
|
||||
System.out.print(progressBar);
|
||||
java.lang.System.out.print(progressBar);
|
||||
}
|
||||
private void processFile(FileMetadata file,
|
||||
Map<String, List<FileMetadata>> hashGroups,
|
||||
|
|
@ -214,11 +212,12 @@ public class DuplicateFinder {
|
|||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Failed to calculate file's hash: {}, {}", file.getPath(), e.getMessage());
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Failed to calculate file's hash: {}, {}", file.getPath(), e.getMessage());
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
public void shutdown() {
|
||||
fileScanner.cancel();
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ package top.r3944realms.docchecktoolrefactored.core;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
||||
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
|
|
@ -49,7 +49,7 @@ public class HashFileGenerator {
|
|||
|
||||
@Override
|
||||
public void onError(Path path, Exception e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Error scanning path: {} - {}", path, e.getMessage());
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Error scanning path: {} - {}", path, e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ public class HashFileGenerator {
|
|||
listener.onProgressUpdate(processed, totalFiles);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "无法计算该文件哈希值: {} - {}", file, e.getMessage());
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "无法计算该文件哈希值: {} - {}", file, e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +1,78 @@
|
|||
package top.r3944realms.docchecktoolrefactored.core;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.io.reader.CatalogFileReader;
|
||||
import top.r3944realms.docchecktoolrefactored.io.reader.CatalogFileReaderFactory;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class LogicalAddressFileGenerator implements AddressFileGenerator {
|
||||
private ProgressCallback callback;
|
||||
// 安全调用回调方法
|
||||
private void safeOnPhaseStarted(Phase phase) {
|
||||
if (callback != null) {
|
||||
callback.onPhaseStarted(phase);
|
||||
}
|
||||
}
|
||||
|
||||
private void safeOnPhaseProgress(Phase phase, int current, int total) {
|
||||
if (callback != null) {
|
||||
callback.onPhaseProgress(phase, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
private void safeOnPhaseCompleted(Phase phase) {
|
||||
if (callback != null) {
|
||||
callback.onPhaseCompleted(phase);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateAddressFile(String catalogFilePath, File outputFile, int folderType, Callback callback) {
|
||||
callback.onProgress("正在生成逻辑地址文件...");
|
||||
public void setProgressCallback(ProgressCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
@Override
|
||||
public void generateAddressFile(String catalogFilePath, File outputFile, int folderType) {
|
||||
safeOnPhaseStarted(Phase.GENERATE_LOGICAL);
|
||||
try {
|
||||
// 使用工厂模式创建相应的文件读取器
|
||||
List<Record> records = readCatalogFile(catalogFilePath);
|
||||
|
||||
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, 1, 3); // 1/3
|
||||
// 过滤掉 可能的 输出文件记录 -- 注2: 避免将输出文件作为输入处理
|
||||
String outputFileName = outputFile.getName();
|
||||
records = records.stream()
|
||||
.filter(record -> !record.archiveCode.equalsIgnoreCase(outputFileName))
|
||||
.toList();
|
||||
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, 2, 3); // 2/3
|
||||
try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.UTF_8)) {
|
||||
if (folderType == PAGE_TYPE) {
|
||||
// 页面级逻辑:为每页生成一行数据
|
||||
generatePageLevelFile(writer, records);
|
||||
generatePageLevelFile(writer, records, callback);
|
||||
} else if (folderType == FILE_TYPE) {
|
||||
// 文件级逻辑:每个档案只生成一行数据
|
||||
generateFileLevelFile(writer, records);
|
||||
generateFileLevelFile(writer, records, callback);
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的文件夹类型: " + folderType);
|
||||
}
|
||||
}
|
||||
|
||||
callback.onSuccess("逻辑地址文件生成成功: " + outputFile.getAbsolutePath());
|
||||
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, 3, 3); // 3/3
|
||||
safeOnPhaseCompleted(Phase.GENERATE_LOGICAL);
|
||||
} catch (Exception e) {
|
||||
callback.onError("生成逻辑地址文件时出错: " + e.getMessage());
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "生成逻辑地址文件时出错: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 生成页面级逻辑地址文件
|
||||
*/
|
||||
private void generatePageLevelFile(PrintWriter writer, List<Record> records) {
|
||||
private void generatePageLevelFile(PrintWriter writer, List<Record> records, ProgressCallback callback) {
|
||||
// 写入CSV头部
|
||||
writer.println("逻辑文件名,逻辑地址");
|
||||
int totalRecords = records.stream().mapToInt(r -> r.page).sum();
|
||||
int current = 0;
|
||||
|
||||
// 处理每条记录
|
||||
for (Record record : records) {
|
||||
|
|
@ -60,6 +89,8 @@ public class LogicalAddressFileGenerator implements AddressFileGenerator {
|
|||
|
||||
// 写入CSV行
|
||||
writer.printf("%s,%s%n", logicalFileName, logicalAddress);
|
||||
current++;
|
||||
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, current, totalRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,9 +98,11 @@ public class LogicalAddressFileGenerator implements AddressFileGenerator {
|
|||
/**
|
||||
* 生成文件级逻辑地址文件
|
||||
*/
|
||||
private void generateFileLevelFile(PrintWriter writer, List<Record> records) {
|
||||
private void generateFileLevelFile(PrintWriter writer, List<Record> records, ProgressCallback callback) {
|
||||
// 写入CSV头部(包含页数列)
|
||||
writer.println("逻辑文件名,逻辑地址,页数");
|
||||
int totalRecords = records.stream().mapToInt(r -> r.page).sum();
|
||||
int current = 0;
|
||||
|
||||
// 处理每条记录
|
||||
for (Record record : records) {
|
||||
|
|
@ -81,6 +114,7 @@ public class LogicalAddressFileGenerator implements AddressFileGenerator {
|
|||
|
||||
// 写入CSV行,包含页数
|
||||
writer.printf("%s,%s,%d%n", /* 逻辑文件名(就是档号)*/ archiveCode, logicalAddress, page);
|
||||
safeOnPhaseProgress(Phase.GENERATE_LOGICAL, current, totalRecords);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package top.r3944realms.docchecktoolrefactored.core;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
|
|
@ -11,39 +12,85 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
@Slf4j
|
||||
public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
||||
private ProgressCallback callback;
|
||||
// 安全调用回调方法
|
||||
private void safeOnPhaseStarted(Phase phase) {
|
||||
if (callback != null) {
|
||||
callback.onPhaseStarted(phase);
|
||||
}
|
||||
}
|
||||
|
||||
private void safeOnPhaseProgress(Phase phase, int current, int total) {
|
||||
if (callback != null) {
|
||||
callback.onPhaseProgress(phase, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
private void safeOnPhaseCompleted(Phase phase) {
|
||||
if (callback != null) {
|
||||
callback.onPhaseCompleted(phase);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateAddressFile(String folderPath, File outputFile, int folderType, Callback callback) {
|
||||
callback.onProgress("正在生成物理地址文件...");
|
||||
public void setProgressCallback(ProgressCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateAddressFile(String folderPath, File outputFile, int folderType) {
|
||||
safeOnPhaseStarted(Phase.GENERATE_PHYSICAL);
|
||||
try {
|
||||
File rootFolder = new File(folderPath);
|
||||
if (!rootFolder.exists() || !rootFolder.isDirectory()) {
|
||||
callback.onError("所选路径不存在或不是一个有效的文件夹。");
|
||||
safeOnPhaseCompleted(Phase.GENERATE_PHYSICAL);
|
||||
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存输出文件的绝对路径,用于后续比较
|
||||
String outputFilePath = outputFile.getAbsolutePath();
|
||||
// 统计总文件数(页面级:图片数;文件级:PDF数)
|
||||
int totalFiles = countFiles(rootFolder, folderType);
|
||||
|
||||
// 计数器
|
||||
int[] counter = new int[]{0};
|
||||
|
||||
// 写入CSV文件
|
||||
try (PrintWriter writer = new PrintWriter(outputFile, StandardCharsets.UTF_8)) {
|
||||
if (folderType == AddressFileGenerator.PAGE_TYPE) {
|
||||
// 页面级逻辑:处理所有图片文件
|
||||
if (folderType == PAGE_TYPE) {
|
||||
writer.println("物理文件名,物理地址");
|
||||
processPageLevelFolder(rootFolder, writer, outputFilePath);
|
||||
} else if (folderType == AddressFileGenerator.FILE_TYPE) {
|
||||
// 文件级逻辑:处理PDF文件
|
||||
processPageLevelFolder(rootFolder, writer, outputFile.getAbsolutePath(), callback, counter, totalFiles);
|
||||
} else if (folderType == FILE_TYPE) {
|
||||
writer.println("物理文件名,物理地址,页数");
|
||||
processFileLevelFolder(rootFolder, writer, outputFilePath);
|
||||
processFileLevelFolder(rootFolder, writer, outputFile.getAbsolutePath(), callback, counter, totalFiles);
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的文件夹类型: " + folderType);
|
||||
}
|
||||
}
|
||||
|
||||
callback.onSuccess("物理地址文件生成成功: " + outputFile.getAbsolutePath());
|
||||
safeOnPhaseCompleted(Phase.GENERATE_PHYSICAL);
|
||||
} catch (Exception e) {
|
||||
callback.onError("生成物理地址文件时出错: " + e.getMessage());
|
||||
safeOnPhaseCompleted(Phase.GENERATE_PHYSICAL);
|
||||
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, 0, 0);
|
||||
log.error("生成物理地址文件失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
/** 递归统计总文件数 */
|
||||
private int countFiles(File folder, int folderType) {
|
||||
int count = 0;
|
||||
File[] files = folder.listFiles(file -> !file.isHidden());
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isFile()) {
|
||||
if ((folderType == PAGE_TYPE && isImageFile(file.getName()))
|
||||
|| (folderType == FILE_TYPE && isPdfFile(file.getName()))) {
|
||||
count++;
|
||||
}
|
||||
} else if (file.isDirectory()) {
|
||||
count += countFiles(file, folderType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
/**
|
||||
* 处理页面级文件夹及其内部文件
|
||||
*
|
||||
|
|
@ -51,26 +98,20 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
|||
* @param writer PrintWriter对象
|
||||
* @param outputFilePath 输出文件的绝对路径
|
||||
*/
|
||||
private void processPageLevelFolder(File folder, PrintWriter writer, String outputFilePath) {
|
||||
private void processPageLevelFolder(File folder, PrintWriter writer, String outputFilePath, ProgressCallback callback, int[] counter, int total) {
|
||||
// 获取该文件夹下的所有非隐藏文件和文件夹
|
||||
File[] filesAndFolders = folder.listFiles(file -> !file.isHidden());
|
||||
|
||||
if (filesAndFolders != null) {
|
||||
for (File file : filesAndFolders) {
|
||||
// 跳过输出文件本身,避免将生成的CSV文件也作为数据处理
|
||||
if (file.getAbsolutePath().equals(outputFilePath)) {
|
||||
continue;
|
||||
}
|
||||
if (file.getAbsolutePath().equals(outputFilePath)) continue;
|
||||
|
||||
if (file.isFile()) {
|
||||
String fileName = file.getName();
|
||||
// 只处理图片文件,跳过其他类型的文件
|
||||
if (!isImageFile(fileName)) {
|
||||
continue;
|
||||
}
|
||||
// 只处理图片文件,跳过其他类型的文件
|
||||
if (file.isFile() && isImageFile(file.getName())) {
|
||||
|
||||
// 移除文件扩展名
|
||||
String fileNameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
String fileNameWithoutExt = file.getName().substring(0, file.getName().lastIndexOf('.'));
|
||||
|
||||
// 格式化文件名,确保最后一个部分是4位数字
|
||||
String formattedFileName = formatFileName(fileNameWithoutExt);
|
||||
|
|
@ -80,9 +121,12 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
|||
|
||||
// 写入CSV行
|
||||
writer.printf("%s,%s%n", formattedFileName, physicalAddress);
|
||||
|
||||
counter[0]++;
|
||||
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, counter[0], total);
|
||||
} else if (file.isDirectory()) {
|
||||
// 递归处理子文件夹
|
||||
processPageLevelFolder(file, writer, outputFilePath);
|
||||
processPageLevelFolder(file, writer, outputFilePath, callback, counter, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +139,7 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
|||
* @param writer PrintWriter对象
|
||||
* @param outputFilePath 输出文件的绝对路径
|
||||
*/
|
||||
private void processFileLevelFolder(File folder, PrintWriter writer, String outputFilePath) {
|
||||
private void processFileLevelFolder(File folder, PrintWriter writer, String outputFilePath, ProgressCallback callback, int[] counter, int total) {
|
||||
// 获取该文件夹下的所有非隐藏文件和文件夹
|
||||
File[] filesAndFolders = folder.listFiles(file -> !file.isHidden());
|
||||
|
||||
|
|
@ -106,12 +150,9 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (file.isFile()) {
|
||||
String fileName = file.getName();
|
||||
// 只处理PDF文件
|
||||
if (isPdfFile(fileName)) {
|
||||
if (file.isFile()&& isPdfFile(file.getName())) {
|
||||
// 移除文件扩展名
|
||||
String fileNameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
String fileNameWithoutExt = file.getName().substring(0, file.getName().lastIndexOf('.'));
|
||||
|
||||
// 生成物理地址路径(使用与页面级相同的逻辑)
|
||||
String physicalAddress = generatePhysicalAddress(file.getAbsolutePath(), fileNameWithoutExt);
|
||||
|
|
@ -121,10 +162,12 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
|||
|
||||
// 写入CSV行
|
||||
writer.printf("%s,%s,%d%n", fileNameWithoutExt, physicalAddress, pageCount);
|
||||
}
|
||||
|
||||
counter[0]++;
|
||||
safeOnPhaseProgress(Phase.GENERATE_PHYSICAL, counter[0], total);
|
||||
} else if (file.isDirectory()) {
|
||||
// 递归处理子文件夹
|
||||
processFileLevelFolder(file, writer, outputFilePath);
|
||||
processFileLevelFolder(file, writer, outputFilePath, callback, counter, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -137,14 +180,13 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
|||
* @return 页数
|
||||
*/
|
||||
private int getPdfPageCount(File pdfFile) {
|
||||
try {
|
||||
// 使用Apache PDFBox库获取PDF页数
|
||||
PDDocument document = Loader.loadPDF(pdfFile);
|
||||
// 使用Apache PDFBox库获取PDF页数
|
||||
try (PDDocument document = Loader.loadPDF(pdfFile)){
|
||||
int pageCount = document.getNumberOfPages();
|
||||
document.close();
|
||||
return pageCount;
|
||||
} catch (Exception e) {
|
||||
log.warn(LoggerHelper.RELEASE_MARKER, "无法获取PDF文件页数: {}", pdfFile.getAbsolutePath(), e);
|
||||
log.warn(LoggerMarker.RELEASE_MARKER, "无法获取PDF文件页数: {}", pdfFile.getAbsolutePath(), e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
@Getter
|
||||
@Slf4j
|
||||
public class Setting {
|
||||
private long singleTimeout = 30;
|
||||
private long totalTimeout = 60 * 5;
|
||||
private long scanTimeout = 30;
|
||||
private long taskTimeout = 60 * 5;
|
||||
private boolean enableStep = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.linuxense.javadbf.DBFRow;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
|
@ -38,29 +38,29 @@ public class DbfFileReader implements CatalogFileReader {
|
|||
DBFReader reader = new DBFReader(fis)
|
||||
) {
|
||||
int fieldCount = reader.getFieldCount();
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "开始读取DBF文件: {}, DBF文件字段数: {}",filePath, fieldCount);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "开始读取DBF文件: {}, DBF文件字段数: {}",filePath, fieldCount);
|
||||
|
||||
// 查找"档号"和"页数"字段的索引
|
||||
int archiveCodeIndex = -1;
|
||||
int pageIndex = -1;
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
if (archiveCodeIndex != -1 && pageIndex != -1) {
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "已找到所需字段,跳出循环,档号: {}, 页数: {}", archiveCodeIndex, pageIndex);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "已找到所需字段,跳出循环,档号: {}, 页数: {}", archiveCodeIndex, pageIndex);
|
||||
break;
|
||||
}
|
||||
String fieldName = reader.getField(i).getName();
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "发现字段: {}", fieldName);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "发现字段: {}", fieldName);
|
||||
if (ARCHIVE_CODE_TAG_CANDIDATES.contains(fieldName)) {
|
||||
archiveCodeIndex = i;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "匹配到档号字段: {}, 索引: {}", fieldName, archiveCodeIndex);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "匹配到档号字段: {}, 索引: {}", fieldName, archiveCodeIndex);
|
||||
} else if (PAGE_COUNT_TAG_CANDIDATES.contains(fieldName)) {
|
||||
pageIndex = i;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "匹配到页数字段: {}, 索引: {}", fieldName, pageIndex);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "匹配到页数字段: {}, 索引: {}", fieldName, pageIndex);
|
||||
}
|
||||
|
||||
}
|
||||
if (archiveCodeIndex == -1 || pageIndex == -1) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "未找到必要字段,档号: {}, 页数: {}",
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "未找到必要字段,档号: {}, 页数: {}",
|
||||
archiveCodeIndex == -1 ? "未找到" : archiveCodeIndex,
|
||||
pageIndex == -1 ? "未找到" : pageIndex
|
||||
);
|
||||
|
|
@ -89,7 +89,7 @@ public class DbfFileReader implements CatalogFileReader {
|
|||
return Integer.parseInt(i.toString().trim());
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "无法将页数值转换为整数: {}", i);
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "无法将页数值转换为整数: {}", i);
|
||||
return 0;
|
||||
}
|
||||
}).orElse(0);
|
||||
|
|
@ -98,17 +98,17 @@ public class DbfFileReader implements CatalogFileReader {
|
|||
if (!archiveCode.isEmpty() && page > 0) {
|
||||
records.add(new LogicalAddressFileGenerator.Record(archiveCode, page));
|
||||
validRecords++;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||
} else {
|
||||
skippedRecords++;
|
||||
if (!archiveCode.isEmpty() || page > 0) {
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "跳过无效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "跳过无效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info(LoggerHelper.RELEASE_MARKER, "DBF文件读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "DBF文件读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "读取DBF文件失败: {}", filePath, e);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "读取DBF文件失败: {}", filePath, e);
|
||||
throw new UncheckedIOException("DBF文件读取异常", e);
|
||||
}
|
||||
return records;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import org.apache.poi.ss.usermodel.*;
|
|||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
|
@ -27,33 +27,33 @@ public class ExcelFileReader implements CatalogFileReader {
|
|||
}
|
||||
private List<LogicalAddressFileGenerator.Record> readExcelFile(File file, boolean isXlsx) throws Exception {
|
||||
List<LogicalAddressFileGenerator.Record> records = new ArrayList<>();
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "开始解析Excel文件,格式: {}", isXlsx ? "xlsx" : "xls");
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "开始解析Excel文件,格式: {}", isXlsx ? "xlsx" : "xls");
|
||||
try (FileInputStream fis = new FileInputStream(file);
|
||||
Workbook workbook = isXlsx ? new XSSFWorkbook(fis) : new HSSFWorkbook(fis)) {
|
||||
// 获取第一个工作表
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "读取工作表: {}", sheet.getSheetName());
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "读取工作表: {}", sheet.getSheetName());
|
||||
|
||||
// 获取标题行
|
||||
Row headerRow = sheet.getRow(0);
|
||||
if (headerRow == null) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "Excel文件缺少标题行");
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "Excel文件缺少标题行");
|
||||
throw new IllegalArgumentException("Excel文件缺少标题行");
|
||||
}
|
||||
// 查找"档号"和"页数"列的索引
|
||||
int archiveCodeIndex = -1;
|
||||
int pageIndex = -1;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "开始查找'档号'和'页数'列的索引");
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "开始查找'档号'和'页数'列的索引");
|
||||
boolean foundExactMatch = false;
|
||||
for (Cell cell : headerRow) {
|
||||
String cellValue = getCellValueAsString(cell).trim();
|
||||
if (FIELD_ARCHIVE_CODE.equals(cellValue)) {
|
||||
archiveCodeIndex = cell.getColumnIndex();
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "找到'档号'列,索引: {}", archiveCodeIndex);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "找到'档号'列,索引: {}", archiveCodeIndex);
|
||||
} else if (FIELD_PAGE.equals(cellValue)) {
|
||||
pageIndex = cell.getColumnIndex();
|
||||
foundExactMatch = true;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "找到精确匹配'页数'列,索引: {}", pageIndex);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "找到精确匹配'页数'列,索引: {}", pageIndex);
|
||||
}
|
||||
}
|
||||
// 如果没有精确匹配,进行模糊查找
|
||||
|
|
@ -62,13 +62,13 @@ public class ExcelFileReader implements CatalogFileReader {
|
|||
String cellValue = getCellValueAsString(cell).trim();
|
||||
if (cellValue.contains(FIELD_PAGE)) {
|
||||
pageIndex = cell.getColumnIndex();
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "找到模糊匹配'页数'列,索引: {}", pageIndex);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "找到模糊匹配'页数'列,索引: {}", pageIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 检查是否找到必需的列
|
||||
if (archiveCodeIndex == -1 || pageIndex == -1) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "未找到必要字段,档号: {}, 页数: {}",
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "未找到必要字段,档号: {}, 页数: {}",
|
||||
archiveCodeIndex == -1 ? "未找到" : archiveCodeIndex,
|
||||
pageIndex == -1 ? "未找到" : pageIndex
|
||||
);
|
||||
|
|
@ -84,7 +84,7 @@ public class ExcelFileReader implements CatalogFileReader {
|
|||
int validRecords = 0;
|
||||
int skippedRecords = 0;
|
||||
|
||||
for (int i = 1; i < totalRows; i++) {
|
||||
for (int i = 1; i <= totalRows; i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
skippedRecords++;
|
||||
|
|
@ -116,12 +116,12 @@ public class ExcelFileReader implements CatalogFileReader {
|
|||
// 只有数据有效时才添加记录
|
||||
records.add(new LogicalAddressFileGenerator.Record(archiveCode, page));
|
||||
validRecords++;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||
}
|
||||
|
||||
log.info(LoggerHelper.RELEASE_MARKER, "数据读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "数据读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
||||
} catch (Exception e) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "读取Excel文件时发生错误: {}", e.getMessage(), e);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "读取Excel文件时发生错误: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
return records;
|
||||
|
|
@ -189,7 +189,7 @@ public class ExcelFileReader implements CatalogFileReader {
|
|||
return 0;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "无法将单元格值转换为整数: {}", cell);
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "无法将单元格值转换为整数: {}", cell);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import org.w3c.dom.Node;
|
|||
import org.w3c.dom.NodeList;
|
||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
|
@ -43,11 +43,11 @@ public class XmlFileReader implements CatalogFileReader {
|
|||
Document doc = builder.parse(file);
|
||||
doc.getDocumentElement().normalize();
|
||||
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "开始解析XML文件: {}, 根元素: {}",
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "开始解析XML文件: {}, 根元素: {}",
|
||||
file.getName(), doc.getDocumentElement().getNodeName());
|
||||
// 查找记录元素
|
||||
NodeList recordNodes = findAllRecordNodes(doc);
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "找到 {} 个潜在记录节点", recordNodes.getLength());
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "找到 {} 个潜在记录节点", recordNodes.getLength());
|
||||
|
||||
// 解析每个记录元素
|
||||
int validCount = 0;
|
||||
|
|
@ -62,16 +62,16 @@ public class XmlFileReader implements CatalogFileReader {
|
|||
if (record != null) {
|
||||
records.add(record);
|
||||
validCount++;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "解析到有效记录: {}", record);
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "解析到有效记录: {}", record);
|
||||
} else {
|
||||
invalidCount++;
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "跳过无效记录节点");
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "跳过无效记录节点");
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info(LoggerHelper.RELEASE_MARKER, "XML解析完成 - 有效记录: {}, 无效记录: {}", validCount, invalidCount);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "XML解析完成 - 有效记录: {}, 无效记录: {}", validCount, invalidCount);
|
||||
} catch (Exception e) {
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "解析XML文件失败: {}", filePath, e);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "解析XML文件失败: {}", filePath, e);
|
||||
throw new Exception("解析XML文件失败: " + filePath, e);
|
||||
}
|
||||
return records;
|
||||
|
|
@ -93,11 +93,11 @@ public class XmlFileReader implements CatalogFileReader {
|
|||
for (String tagName : RECORD_TAG_CANDIDATES) {
|
||||
NodeList nodes = doc.getElementsByTagName(tagName);
|
||||
if (nodes.getLength() > 0) {
|
||||
log.debug(LoggerHelper.DEBUG_MARKER, "使用标签名 '{}' 找到 {} 个记录节点", tagName, nodes.getLength());
|
||||
log.debug(LoggerMarker.DEBUG_MARKER, "使用标签名 '{}' 找到 {} 个记录节点", tagName, nodes.getLength());
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "未找到任何记录节点,尝试的标签名: {}", RECORD_TAG_CANDIDATES);
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "未找到任何记录节点,尝试的标签名: {}", RECORD_TAG_CANDIDATES);
|
||||
return new EmptyNodeList(); // 返回空节点列表而不是null
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ public class XmlFileReader implements CatalogFileReader {
|
|||
String pageStr = findFirstNonEmptyTextContent(recordElement, PAGE_COUNT_TAG_CANDIDATES);
|
||||
|
||||
if (archiveCode == null || archiveCode.isEmpty()) {
|
||||
log.debug(LoggerHelper.RELEASE_MARKER, "记录缺少档号字段");
|
||||
log.debug(LoggerMarker.RELEASE_MARKER, "记录缺少档号字段");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -115,11 +115,11 @@ public class XmlFileReader implements CatalogFileReader {
|
|||
if (page > 0) {
|
||||
return new LogicalAddressFileGenerator.Record(archiveCode, page);
|
||||
} else {
|
||||
log.debug(LoggerHelper.RELEASE_MARKER, "无效的页数值: {}", pageStr);
|
||||
log.debug(LoggerMarker.RELEASE_MARKER, "无效的页数值: {}", pageStr);
|
||||
return null;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn(LoggerHelper.RELEASE_MARKER, "页数字段格式错误: {}", pageStr);
|
||||
log.warn(LoggerMarker.RELEASE_MARKER, "页数字段格式错误: {}", pageStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package top.r3944realms.docchecktoolrefactored.io.scanner;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* The interface File scanner.
|
||||
*/
|
||||
public interface FileScanner {
|
||||
public interface FileScanner extends AutoCloseable {
|
||||
/**
|
||||
* 扫描指定路径下的文件
|
||||
*
|
||||
|
|
@ -39,6 +40,11 @@ public interface FileScanner {
|
|||
throw new UnsupportedOperationException("Please implement FileScanner, ProgressAwareListener.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消扫描
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
/**
|
||||
* 文件扫描监听器
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package top.r3944realms.docchecktoolrefactored.io.scanner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
|
@ -16,7 +16,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Slf4j
|
||||
public class RobustParallelScanner implements FileScanner, AutoCloseable {
|
||||
public class RobustParallelScanner implements FileScanner {
|
||||
private final ForkJoinPool forkJoinPool;
|
||||
private volatile boolean cancelled = false;
|
||||
private final int maxDepth;
|
||||
|
|
@ -60,7 +60,7 @@ public class RobustParallelScanner implements FileScanner, AutoCloseable {
|
|||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn(LoggerHelper.TRACE_MARKER, "Failed to pre-scan: {}", dir, e);
|
||||
log.warn(LoggerMarker.TRACE_MARKER, "Failed to pre-scan: {}", dir, e);
|
||||
}
|
||||
}
|
||||
private void scanInternal(Path rootPath, FileScanListener listener, AtomicLong totalFiles) {
|
||||
|
|
@ -83,7 +83,7 @@ public class RobustParallelScanner implements FileScanner, AutoCloseable {
|
|||
}
|
||||
}).get(timeout, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Scan timeout: {}", rootPath, e);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Scan timeout: {}", rootPath, e);
|
||||
forkJoinPool.shutdownNow();
|
||||
listener.onError(rootPath, new TimeoutException("扫描超时30秒"));
|
||||
} catch (Exception e) {
|
||||
|
|
@ -140,6 +140,8 @@ public class RobustParallelScanner implements FileScanner, AutoCloseable {
|
|||
throw new IOException("系统目录禁止访问: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelled = true;
|
||||
forkJoinPool.shutdownNow();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import javafx.scene.input.KeyEvent;
|
|||
import javafx.scene.layout.BorderPane;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
|
@ -39,10 +39,10 @@ public class LoginStageController implements Initializable {
|
|||
String password = passwordField.getText();
|
||||
|
||||
if ("admin".equals(username) && "admin".equals(password)) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "{} Login successful", username);
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "{} Login successful", username);
|
||||
SceneManager.switchMainView();
|
||||
} else {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "Invalid username or password");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "Invalid username or password");
|
||||
DialogUtil.showErrorDialog("错误", null, "用户名或密码错误!");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import lombok.Setter;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.Main;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -76,7 +76,7 @@ public class SceneManager {
|
|||
});
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Failed to load main view", e);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Failed to load main view", e);
|
||||
DialogUtil.showErrorDialog("错误", "加载主界面失败", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ public class SceneManager {
|
|||
openStages.add(settingStage);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Failed to open setting view: {}", e.getMessage(), e);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Failed to open setting view: {}", e.getMessage(), e);
|
||||
DialogUtil.showErrorDialog("错误", "加载设置窗口失败", "无法加载设置窗口: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -149,7 +149,7 @@ public class SceneManager {
|
|||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "Failed to load view: {}", fxmlPath, e);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "Failed to load view: {}", fxmlPath, e);
|
||||
DialogUtil.showErrorDialog("错误", "加载视图失败", "无法加载视图: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class SettingDialogController implements Initializable {
|
|||
|
||||
@FXML private CheckBox enableStepCB;
|
||||
@FXML private Button resetB, saveB, cancelB;
|
||||
@FXML private Spinner<Long> scanTotalTimeOutS, scanSingleTimeOutS;
|
||||
@FXML private Spinner<Long> scanTimeOutS, taskTimeOutS;
|
||||
|
||||
private Setting setting;
|
||||
|
||||
|
|
@ -35,14 +35,14 @@ public class SettingDialogController implements Initializable {
|
|||
setting = System.getSetting();
|
||||
|
||||
// 初始化 Spinner
|
||||
scanSingleTimeOutS.setValueFactory(new LongSpinnerValueFactory(1, 3600, setting.getSingleTimeout()));
|
||||
scanSingleTimeOutS.setEditable(true);
|
||||
scanTotalTimeOutS.setValueFactory(new LongSpinnerValueFactory(1, 3600 * 24, setting.getTotalTimeout()));
|
||||
scanTotalTimeOutS.setEditable(true);
|
||||
scanTimeOutS.setValueFactory(new LongSpinnerValueFactory(1, 3600, setting.getScanTimeout()));
|
||||
scanTimeOutS.setEditable(true);
|
||||
taskTimeOutS.setValueFactory(new LongSpinnerValueFactory(1, 3600 * 24, setting.getTaskTimeout()));
|
||||
taskTimeOutS.setEditable(true);
|
||||
|
||||
// 添加焦点离开时校验
|
||||
addSpinnerValidation(scanSingleTimeOutS, SINGLE_MIN, SINGLE_MAX);
|
||||
addSpinnerValidation(scanTotalTimeOutS, TOTAL_MIN, TOTAL_MAX);
|
||||
addSpinnerValidation(scanTimeOutS, SINGLE_MIN, SINGLE_MAX);
|
||||
addSpinnerValidation(taskTimeOutS, TOTAL_MIN, TOTAL_MAX);
|
||||
enableStepCB.setSelected(setting.isEnableStep());
|
||||
}
|
||||
|
||||
|
|
@ -50,8 +50,8 @@ public class SettingDialogController implements Initializable {
|
|||
@FXML
|
||||
void onSave(ActionEvent actionEvent) {
|
||||
// 更新配置对象
|
||||
setting.setSingleTimeout(scanSingleTimeOutS.getValue());
|
||||
setting.setTotalTimeout(scanTotalTimeOutS.getValue());
|
||||
setting.setScanTimeout(scanTimeOutS.getValue());
|
||||
setting.setTaskTimeout(taskTimeOutS.getValue());
|
||||
setting.setEnableStep(enableStepCB.isSelected());
|
||||
// 保存到配置文件
|
||||
System.saveSettingsNow();
|
||||
|
|
@ -66,8 +66,8 @@ public class SettingDialogController implements Initializable {
|
|||
/** 重置为默认值 */
|
||||
@FXML
|
||||
void onReset(ActionEvent actionEvent) {
|
||||
scanSingleTimeOutS.getValueFactory().setValue(30L); // 默认单次超时
|
||||
scanTotalTimeOutS.getValueFactory().setValue(300L); // 默认总超时
|
||||
scanTimeOutS.getValueFactory().setValue(30L); // 默认单次超时
|
||||
taskTimeOutS.getValueFactory().setValue(300L); // 默认总超时
|
||||
enableStepCB.setSelected(false);
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +79,12 @@ public class SettingDialogController implements Initializable {
|
|||
|
||||
@FXML
|
||||
void onCheckOne(MouseDragEvent mouseDragEvent) {
|
||||
validateSpinnerValue(scanSingleTimeOutS, 30, 3600);
|
||||
validateSpinnerValue(scanTimeOutS, 30, 3600);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onCheckTwo(MouseDragEvent mouseDragEvent) {
|
||||
validateSpinnerValue(scanTotalTimeOutS, 60, 3600 * 24);
|
||||
validateSpinnerValue(taskTimeOutS, 60, 3600 * 24);
|
||||
}
|
||||
@FXML
|
||||
void onSettingThree(ActionEvent actionEvent) {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import top.r3944realms.docchecktoolrefactored.System;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.SceneManager;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.task.DuplicateDocumentDetectionTask;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.utils.ProgressBarUtil;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.utils.ProgressBar;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
|
@ -23,12 +23,12 @@ import java.io.File;
|
|||
@Slf4j
|
||||
public class DuplicateDocumentPaneController {
|
||||
|
||||
@FXML private TextArea result1B;
|
||||
@FXML private TextArea result1TA;
|
||||
@FXML private TextField loadFolder1TF;
|
||||
@FXML private Button selectLoadFolder1B;
|
||||
@FXML private Button start1B;
|
||||
@FXML private Button cancel1B;
|
||||
private final ProgressBarUtil progressBarUtil = new ProgressBarUtil();
|
||||
private final ProgressBar progressBar = new ProgressBar();
|
||||
private DuplicateDocumentDetectionTask currentTask; // 保存任务引用
|
||||
/**
|
||||
* On select folder.
|
||||
|
|
@ -51,72 +51,75 @@ public class DuplicateDocumentPaneController {
|
|||
* @param actionEvent the action event
|
||||
*/
|
||||
@FXML void onStart(ActionEvent actionEvent) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了开始查重按钮");
|
||||
String folderPath = loadFolder1TF.getText();
|
||||
if (folderPath == null || folderPath.trim().isEmpty()) {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "未选择文件夹,无法进行查重");
|
||||
result1B.setText("请选择要检查的文件夹。");
|
||||
return;
|
||||
}
|
||||
// 禁用开始按钮避免重复点击
|
||||
start1B.setDisable(true);
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户点击了开始查重按钮");
|
||||
String folderPath = loadFolder1TF.getText();
|
||||
if (folderPath == null || folderPath.trim().isEmpty()) {
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "未选择文件夹,无法进行查重");
|
||||
result1TA.setText("请选择要检查的文件夹。");
|
||||
start1B.setDisable(false);
|
||||
return;
|
||||
}
|
||||
cancel1B.setDisable(false);
|
||||
// 显示进度条窗口
|
||||
progressBarUtil.showProgress(SceneManager.getPrimaryStage(), "重复文件检测", "正在初始化扫描...");
|
||||
progressBar.showProgress(SceneManager.getPrimaryStage(), "重复文件检测", "正在初始化扫描...");
|
||||
|
||||
// 创建并启动后台任务
|
||||
// 创建后台任务
|
||||
DuplicateDocumentDetectionTask task = new DuplicateDocumentDetectionTask(folderPath);
|
||||
// 保存到字段
|
||||
currentTask = task;
|
||||
// 绑定任务属性到UI
|
||||
ChangeListener<Number> progressChangeListener = (obs, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
progressBarUtil.updateProgress(newVal.doubleValue(),
|
||||
task.getMessage() != null ? task.getMessage() : "处理中...");
|
||||
if (task.getMessage() != null) {
|
||||
progressBar.updateProgress(newVal.doubleValue(), task.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
task.progressProperty().addListener(progressChangeListener);
|
||||
|
||||
// 绑定任务的消息到结果文本区域
|
||||
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
|
||||
result1B.setText(newValue);
|
||||
result1TA.setText(newValue);
|
||||
};
|
||||
task.messageProperty().addListener(messageChangeListener);
|
||||
|
||||
// 绑定取消按钮 -> task.cancel()
|
||||
progressBar.setOnCancel(() -> {
|
||||
if (currentTask != null && currentTask.isRunning()) {
|
||||
currentTask.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
// 当任务完成时显示完整结果
|
||||
task.setOnSucceeded(e -> {
|
||||
progressBarUtil.closeProgress();
|
||||
result1B.setText(task.getValue());
|
||||
progressBar.closeProgress();
|
||||
result1TA.setText(task.getValue());
|
||||
start1B.setDisable(false);
|
||||
cancel1B.setDisable(true);
|
||||
log.info(LoggerHelper.RELEASE_MARKER, "查重任务完成,结果如下:{}", task.getValue());
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "查重任务完成,结果如下:{}", task.getValue());
|
||||
});
|
||||
|
||||
// 处理任务失败情况
|
||||
task.setOnFailed(e -> {
|
||||
progressBarUtil.closeProgress();
|
||||
progressBar.closeProgress();
|
||||
Throwable exception = task.getException();
|
||||
result1B.setText("检测过程中发生错误: " + exception.getMessage());
|
||||
result1TA.setText("检测过程中发生错误: " + exception.getMessage());
|
||||
start1B.setDisable(false);
|
||||
cancel1B.setDisable(true);
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "查重任务失败", exception);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "查重任务失败", exception);
|
||||
});
|
||||
|
||||
// 处理任务取消情况
|
||||
task.setOnCancelled(e -> {
|
||||
progressBarUtil.closeProgress();
|
||||
result1B.appendText("\n检测已取消");
|
||||
progressBar.closeProgress();
|
||||
result1TA.appendText("\n检测已取消");
|
||||
start1B.setDisable(false);
|
||||
cancel1B.setDisable(true);
|
||||
currentTask.progressProperty().removeListener(progressChangeListener);
|
||||
currentTask.messageProperty().removeListener(messageChangeListener);
|
||||
log.info(LoggerHelper.RELEASE_MARKER, "查重任务已被取消");
|
||||
});
|
||||
// 绑定取消按钮 -> task.cancel()
|
||||
progressBarUtil.setOnCancel(() -> {
|
||||
if (currentTask != null && currentTask.isRunning()) {
|
||||
currentTask.cancel();
|
||||
}
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "查重任务已被取消");
|
||||
});
|
||||
// 在新线程中执行任务
|
||||
Thread thread = new Thread(task);
|
||||
|
|
@ -128,7 +131,7 @@ public class DuplicateDocumentPaneController {
|
|||
if (currentTask != null && currentTask.isRunning()) {
|
||||
currentTask.cancel(); // 触发 setOnCancelled
|
||||
} else {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "没有正在运行的任务可取消");
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "没有正在运行的任务可取消");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package top.r3944realms.docchecktoolrefactored.ui.module;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
|
|
@ -9,12 +11,15 @@ import javafx.scene.control.TextArea;
|
|||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.FileChooser;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.docchecktoolrefactored.System;
|
||||
import top.r3944realms.docchecktoolrefactored.core.AddressFileComparator;
|
||||
import top.r3944realms.docchecktoolrefactored.core.AddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.core.PhysicalAddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.SceneManager;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.task.AddressFileComparisonTask;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.task.AddressFileGenerationTask;
|
||||
import top.r3944realms.docchecktoolrefactored.ui.utils.ProgressBar;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
|
|
@ -24,7 +29,6 @@ import java.util.ResourceBundle;
|
|||
/**
|
||||
* The type Path check pane controller.
|
||||
*/
|
||||
//TODO: 应该交给Platform:runLater;
|
||||
@Slf4j
|
||||
public class PathCheckPaneController implements Initializable {
|
||||
@FXML private ChoiceBox<Mode> loadFolderType2CB;
|
||||
|
|
@ -41,17 +45,16 @@ public class PathCheckPaneController implements Initializable {
|
|||
private String physicalAddressFilePath = null;
|
||||
|
||||
|
||||
// 逻辑地址文件生成器实例
|
||||
private final LogicalAddressFileGenerator generator = new LogicalAddressFileGenerator();
|
||||
private final PhysicalAddressFileGenerator paGenerator = new PhysicalAddressFileGenerator();
|
||||
|
||||
private final ProgressBar progressBar = new ProgressBar(false);
|
||||
private final ProgressBar cancelableProgressBar = new ProgressBar();
|
||||
private Task<?> currentTask; // 保存任务引用
|
||||
/**
|
||||
* On select lc.
|
||||
*
|
||||
* @param actionEvent the action event
|
||||
*/
|
||||
@FXML void onSelectLC(ActionEvent actionEvent) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了选择目录文件按钮");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户点击了选择目录文件按钮");
|
||||
FileChooser fileChooser = System.getFileChooser();
|
||||
fileChooser.setTitle("选择目录文件");
|
||||
|
||||
|
|
@ -71,9 +74,9 @@ public class PathCheckPaneController implements Initializable {
|
|||
if (selectedFile != null) {
|
||||
loadCatalog2TF.setText(selectedFile.getAbsolutePath());
|
||||
System.setLastModifiedFile(selectedFile);
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "选择的目录文件路径为:{}", selectedFile.getAbsolutePath());
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "选择的目录文件路径为:{}", selectedFile.getAbsolutePath());
|
||||
}else{
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "用户未选择任何文件夹");
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "用户未选择任何文件夹");
|
||||
result2TA.setText("未选择任何文件夹,请重新选择。");
|
||||
}
|
||||
}
|
||||
|
|
@ -92,13 +95,13 @@ public class PathCheckPaneController implements Initializable {
|
|||
} else if (selectedMode == Mode.FILE_TYPE) {
|
||||
directoryChooser.setTitle("选择文件级文件夹");
|
||||
}
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户选择的模式为:{}", selectedMode);
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户选择的模式为:{}", selectedMode);
|
||||
|
||||
File selectedDirectory = directoryChooser.showDialog(selectJPGFolder2B.getScene().getWindow());
|
||||
|
||||
if (selectedDirectory != null) {
|
||||
loadJPGFolder2TF.setText(selectedDirectory.getAbsolutePath());
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "选择的{}文件夹路径为:{}", selectedMode,selectedDirectory.getAbsolutePath());
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "选择的{}文件夹路径为:{}", selectedMode,selectedDirectory.getAbsolutePath());
|
||||
}
|
||||
System.setLastModifiedFile(selectedDirectory);
|
||||
}
|
||||
|
|
@ -109,10 +112,12 @@ public class PathCheckPaneController implements Initializable {
|
|||
* @param actionEvent the action event
|
||||
*/
|
||||
@FXML void onGenerateLA(ActionEvent actionEvent) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了生成逻辑地址文件按钮");
|
||||
generateLogicalAddress2B.setDisable(true);
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户点击了生成逻辑地址文件按钮");
|
||||
String filePath = loadCatalog2TF.getText();
|
||||
if (filePath.isEmpty()) {
|
||||
result2TA.setText("请先选择目录文件。");
|
||||
generateLogicalAddress2B.setDisable(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -124,10 +129,12 @@ public class PathCheckPaneController implements Initializable {
|
|||
File outputFile = fileChooser.showSaveDialog(generateLogicalAddress2B.getScene().getWindow());
|
||||
if (outputFile == null) {
|
||||
result2TA.setText("未选择保存位置");
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "用户未选择任何文件");
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "用户未选择任何文件");
|
||||
generateLogicalAddress2B.setDisable(true);
|
||||
return;
|
||||
}
|
||||
System.setLastModifiedFile(outputFile);
|
||||
progressBar.showProgress(SceneManager.getPrimaryStage(), "生成逻辑路径文件", "正在初始化...");
|
||||
// 正确处理文件扩展名 - 只有在没有.csv扩展名时才添加
|
||||
final File finalOutputFile;
|
||||
if (!outputFile.getName().toLowerCase().endsWith(".csv")) {
|
||||
|
|
@ -138,33 +145,54 @@ public class PathCheckPaneController implements Initializable {
|
|||
|
||||
// 保存生成的文件路径
|
||||
logicalAddressFilePath = finalOutputFile.getAbsolutePath();
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "选择的输出文件路径: {}", logicalAddressFilePath);
|
||||
// 创建后台任务来处理文件生成
|
||||
Thread backgroundThread = new Thread(() -> {
|
||||
// 获取当前选择的文件夹类型
|
||||
Mode selectedMode = loadFolderType2CB.getValue();
|
||||
int folderType = (selectedMode == Mode.PAGE_TYPE) ? AddressFileGenerator.PAGE_TYPE : AddressFileGenerator.FILE_TYPE;
|
||||
|
||||
generator.generateAddressFile(filePath, finalOutputFile, folderType, new LogicalAddressFileGenerator.Callback() {
|
||||
@Override
|
||||
public void onProgress(String message) {
|
||||
javafx.application.Platform.runLater(() -> result2TA.setText(message));
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "选择的输出文件路径: {}", logicalAddressFilePath);
|
||||
Mode selectedMode = loadFolderType2CB.getValue();
|
||||
// 创建后台任务
|
||||
AddressFileGenerationTask task = new AddressFileGenerationTask(filePath, outputFile, selectedMode.number, true);
|
||||
// 绑定任务属性到UI
|
||||
ChangeListener<Number> progressChangeListener = (obs, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
if (task.getMessage() != null) {
|
||||
progressBar.updateProgress(newVal.doubleValue(), task.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
task.progressProperty().addListener(progressChangeListener);
|
||||
|
||||
@Override
|
||||
public void onSuccess(String outputPath) {
|
||||
javafx.application.Platform.runLater(() -> result2TA.setText(outputPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String errorMessage) {
|
||||
javafx.application.Platform.runLater(() -> result2TA.setText(errorMessage));
|
||||
}
|
||||
});
|
||||
// 绑定任务的消息到结果文本区域
|
||||
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
|
||||
result2TA.setText(newValue);
|
||||
};
|
||||
task.messageProperty().addListener(messageChangeListener);
|
||||
// 当任务完成时显示完整结果
|
||||
task.setOnSucceeded(e -> {
|
||||
progressBar.closeProgress();
|
||||
result2TA.setText(task.getValue());
|
||||
generateLogicalAddress2B.setDisable(false);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "生成逻辑路径 csv 文件任务完成,输出csv文件路径:{}", task.getValue());
|
||||
});
|
||||
|
||||
// 处理任务失败情况
|
||||
task.setOnFailed(e -> {
|
||||
progressBar.closeProgress();
|
||||
Throwable exception = task.getException();
|
||||
result2TA.setText("检测过程中发生错误: " + exception.getMessage());
|
||||
generateLogicalAddress2B.setDisable(false);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "生成逻辑路径 csv 文件任务失败", exception);
|
||||
});
|
||||
|
||||
backgroundThread.start();
|
||||
// 处理任务取消情况
|
||||
task.setOnCancelled(e -> {
|
||||
progressBar.closeProgress();
|
||||
result2TA.appendText("\n检测已取消");
|
||||
generateLogicalAddress2B.setDisable(false);
|
||||
currentTask.progressProperty().removeListener(progressChangeListener);
|
||||
currentTask.messageProperty().removeListener(messageChangeListener);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "生成逻辑路径 csv 文件任务已被取消");
|
||||
});
|
||||
Thread thread = new Thread(task);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -173,14 +201,17 @@ public class PathCheckPaneController implements Initializable {
|
|||
* @param actionEvent the action event
|
||||
*/
|
||||
@FXML void onGeneratePA(ActionEvent actionEvent) {
|
||||
generatePhysicalAddress2B.setDisable(true);
|
||||
String folderPath = loadJPGFolder2TF.getText();
|
||||
if (folderPath.isEmpty()) {
|
||||
result2TA.setText("请先选择文件夹。");
|
||||
generatePhysicalAddress2B.setDisable(false);
|
||||
return;
|
||||
}
|
||||
File folder = new File(folderPath);
|
||||
if(!folder.exists() || !folder.isDirectory()) {
|
||||
result2TA.setText("所选路径不存在或不是一个有效的文件夹。");
|
||||
generatePhysicalAddress2B.setDisable(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -193,10 +224,11 @@ public class PathCheckPaneController implements Initializable {
|
|||
|
||||
if (outputFile == null) {
|
||||
result2TA.setText("未选择保存位置");
|
||||
generatePhysicalAddress2B.setDisable(false);
|
||||
return;
|
||||
}
|
||||
System.setLastModifiedFile(outputFile);
|
||||
|
||||
progressBar.showProgress(SceneManager.getPrimaryStage(), "生成物理路径文件", "正在初始化...");
|
||||
// 正确处理文件扩展名 - 只有在没有.csv扩展名时才添加
|
||||
final File finalOutputFile;
|
||||
if (!outputFile.getName().toLowerCase().endsWith(".csv")) {
|
||||
|
|
@ -208,31 +240,56 @@ public class PathCheckPaneController implements Initializable {
|
|||
// 保存生成的文件路径
|
||||
physicalAddressFilePath = finalOutputFile.getAbsolutePath();
|
||||
|
||||
// 创建后台任务来处理文件生成
|
||||
Thread backgroundThread = new Thread(() -> {
|
||||
// 获取当前选择的文件夹类型
|
||||
Mode selectedMode = loadFolderType2CB.getValue();
|
||||
int folderType = (selectedMode == Mode.PAGE_TYPE) ? AddressFileGenerator.PAGE_TYPE : AddressFileGenerator.FILE_TYPE;
|
||||
|
||||
paGenerator.generateAddressFile(folderPath, finalOutputFile, folderType, new AddressFileGenerator.Callback() {
|
||||
@Override
|
||||
public void onProgress(String message) {
|
||||
javafx.application.Platform.runLater(() -> result2TA.setText(message));
|
||||
//
|
||||
Mode selectedMode = loadFolderType2CB.getValue();
|
||||
// 创建后台任务
|
||||
AddressFileGenerationTask task = new AddressFileGenerationTask(folderPath, outputFile, selectedMode.number, false);
|
||||
// 保存到字段
|
||||
currentTask = task;
|
||||
// 绑定任务属性到UI
|
||||
ChangeListener<Number> progressChangeListener = (obs, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
if (task.getMessage() != null) {
|
||||
progressBar.updateProgress(newVal.doubleValue(), task.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
task.progressProperty().addListener(progressChangeListener);
|
||||
|
||||
@Override
|
||||
public void onSuccess(String outputPath) {
|
||||
javafx.application.Platform.runLater(() -> result2TA.setText(outputPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String errorMessage) {
|
||||
javafx.application.Platform.runLater(() -> result2TA.setText(errorMessage));
|
||||
}
|
||||
});
|
||||
// 绑定任务的消息到结果文本区域
|
||||
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
|
||||
result2TA.setText(newValue);
|
||||
};
|
||||
task.messageProperty().addListener(messageChangeListener);
|
||||
// 当任务完成时显示完整结果
|
||||
task.setOnSucceeded(e -> {
|
||||
progressBar.closeProgress();
|
||||
result2TA.setText(task.getValue());
|
||||
generatePhysicalAddress2B.setDisable(false);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "生成物理路径 csv 文件任务完成,输出csv文件路径:{}", task.getValue());
|
||||
});
|
||||
|
||||
backgroundThread.start();
|
||||
// 处理任务失败情况
|
||||
task.setOnFailed(e -> {
|
||||
progressBar.closeProgress();
|
||||
Throwable exception = task.getException();
|
||||
result2TA.setText("检测过程中发生错误: " + exception.getMessage());
|
||||
generatePhysicalAddress2B.setDisable(false);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "生成物理路径 csv 文件任务失败", exception);
|
||||
});
|
||||
|
||||
// 处理任务取消情况
|
||||
task.setOnCancelled(e -> {
|
||||
progressBar.closeProgress();
|
||||
result2TA.appendText("\n检测已取消");
|
||||
generatePhysicalAddress2B.setDisable(false);
|
||||
currentTask.progressProperty().removeListener(progressChangeListener);
|
||||
currentTask.messageProperty().removeListener(messageChangeListener);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "生成物理路径 csv 文件任务已被取消");
|
||||
});
|
||||
Thread thread = new Thread(task);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -245,76 +302,88 @@ public class PathCheckPaneController implements Initializable {
|
|||
*/
|
||||
@FXML
|
||||
void onStart(ActionEvent actionEvent) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了开始对比按钮");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户点击了开始对比按钮");
|
||||
|
||||
// 检查是否已生成两个文件
|
||||
if (logicalAddressFilePath == null || physicalAddressFilePath == null) {
|
||||
result2TA.setText("请先生成逻辑地址文件和物理地址文件。");
|
||||
return;
|
||||
}
|
||||
start2B.setDisable(true);
|
||||
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "逻辑地址文件路径为:{}", logicalAddressFilePath);
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "物理地址文件路径为:{}", physicalAddressFilePath);
|
||||
|
||||
// 使用新创建的核心类进行文件比较
|
||||
AddressFileComparator comparator = new AddressFileComparator();
|
||||
AddressFileComparator.ComparisonResult result = comparator.compareFiles(physicalAddressFilePath, logicalAddressFilePath);
|
||||
|
||||
// 显示比对结果
|
||||
StringBuilder resultText = new StringBuilder();
|
||||
|
||||
// 显示读取的行数
|
||||
resultText.append("读取物理地址文件记录数: ").append(result.getPhysicalRecordsCount()).append("\n");
|
||||
resultText.append("读取逻辑地址文件记录数: ").append(result.getLogicalRecordsCount()).append("\n\n");
|
||||
|
||||
// 显示路径不一致的结果
|
||||
if (!result.getPathMismatchResults().isEmpty()) {
|
||||
resultText.append("文件名相同但路径不一致的记录数量: ").append(result.getPathMismatchResults().size()).append("\n");
|
||||
for (String mismatch : result.getPathMismatchResults()) {
|
||||
resultText.append("\t").append(mismatch).append("\n");
|
||||
// 显示进度条窗口
|
||||
cancelableProgressBar.showProgress(SceneManager.getPrimaryStage(), "文件查漏检查", "正在初始化...");
|
||||
// 创建后台任务
|
||||
AddressFileComparisonTask task = getAddressFileComparisonTask();
|
||||
// 保存到字段
|
||||
this.currentTask = task;
|
||||
// 绑定任务属性到UI
|
||||
ChangeListener<Number> progressChangeListener = (obs, oldVal, newVal) -> {
|
||||
if (newVal != null) {
|
||||
if (task.getMessage() != null) {
|
||||
cancelableProgressBar.updateProgress(newVal.doubleValue(), task.getMessage());
|
||||
}
|
||||
}
|
||||
resultText.append("\n");
|
||||
} else {
|
||||
resultText.append("没有路径错误\n\n");
|
||||
}
|
||||
};
|
||||
task.progressProperty().addListener(progressChangeListener);
|
||||
|
||||
// 显示物理文件在逻辑文件中未找到的结果
|
||||
if (!result.getForwardComparisonResults().isEmpty()) {
|
||||
resultText.append("物理文件在逻辑文件中未找到的记录数量: ").append(result.getForwardComparisonResults().size()).append("\n");
|
||||
for (String forward : result.getForwardComparisonResults()) {
|
||||
resultText.append("\t").append(forward).append("\n");
|
||||
// 绑定任务的消息到结果文本区域
|
||||
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
|
||||
result2TA.setText(newValue);
|
||||
};
|
||||
task.messageProperty().addListener(messageChangeListener);
|
||||
|
||||
// 绑定取消按钮 -> task.cancel()
|
||||
cancelableProgressBar.setOnCancel(() -> {
|
||||
if (currentTask != null && currentTask.isRunning()) {
|
||||
currentTask.cancel();
|
||||
}
|
||||
resultText.append("\n");
|
||||
} else {
|
||||
resultText.append("没有物理存在而逻辑不存在的文件\n\n");
|
||||
}
|
||||
});
|
||||
|
||||
// 显示逻辑文件在物理文件中未找到的结果
|
||||
if (!result.getBackwardComparisonResults().isEmpty()) {
|
||||
resultText.append("逻辑文件在物理文件中未找到的记录数量: ").append(result.getBackwardComparisonResults().size()).append("\n");
|
||||
for (String backward : result.getBackwardComparisonResults()) {
|
||||
resultText.append("\t").append(backward).append("\n");
|
||||
}
|
||||
} else {
|
||||
resultText.append("没有逻辑存在而物理不存在的文件\n");
|
||||
}
|
||||
task.setOnSucceeded(event -> {
|
||||
cancelableProgressBar.closeProgress();
|
||||
start2B.setDisable(false);
|
||||
result2TA.setText(AddressFileComparator.ComparisonResult.generateComparisonResults(task.getValue(), loadFolderType2CB.getValue().compareMode));
|
||||
result2TA.setText(AddressFileComparator.ComparisonResult.generateComparisonResults(task.getValue(), loadFolderType2CB.getValue().compareMode));
|
||||
//内部比较器已有,此处忽略日志打印
|
||||
});
|
||||
|
||||
// 如果所有结果都为空,则显示一致信息
|
||||
if (result.getPathMismatchResults().isEmpty() &&
|
||||
result.getForwardComparisonResults().isEmpty() &&
|
||||
result.getBackwardComparisonResults().isEmpty()) {
|
||||
resultText = new StringBuilder("所有文件比对一致,无差异。\n");
|
||||
resultText.append("读取物理地址文件记录数: ").append(result.getPhysicalRecordsCount()).append("\n");
|
||||
resultText.append("读取逻辑地址文件记录数: ").append(result.getLogicalRecordsCount()).append("\n");
|
||||
}
|
||||
task.setOnFailed(event -> {
|
||||
cancelableProgressBar.closeProgress();
|
||||
Throwable exception = task.getException();
|
||||
result2TA.setText("文件比对失败: " + task.getException().getMessage());
|
||||
start2B.setDisable(false);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "查漏任务失败", exception);
|
||||
});
|
||||
|
||||
result2TA.setText(resultText.toString());
|
||||
task.setOnCancelled(event -> {
|
||||
cancelableProgressBar.closeProgress();
|
||||
result2TA.appendText("\n检测已取消");
|
||||
start2B.setDisable(false);
|
||||
currentTask.progressProperty().removeListener(progressChangeListener);
|
||||
currentTask.messageProperty().removeListener(messageChangeListener);
|
||||
log.info(LoggerMarker.RELEASE_MARKER, "查漏任务取消");
|
||||
});
|
||||
|
||||
// 异步执行任务
|
||||
Thread thread = new Thread(task);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private @NotNull AddressFileComparisonTask getAddressFileComparisonTask() {
|
||||
Mode selectedMode = loadFolderType2CB.getValue();
|
||||
AddressFileComparator.CompareMode compareMode = (selectedMode == Mode.PAGE_TYPE) ?
|
||||
AddressFileComparator.CompareMode.PAGE_LEVEL :
|
||||
AddressFileComparator.CompareMode.FILE_LEVEL;
|
||||
|
||||
|
||||
|
||||
|
||||
// 创建异步任务
|
||||
return new AddressFileComparisonTask(
|
||||
physicalAddressFilePath,
|
||||
logicalAddressFilePath,
|
||||
compareMode,
|
||||
System.getSetting().getTaskTimeout()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -330,7 +399,7 @@ public class PathCheckPaneController implements Initializable {
|
|||
/**
|
||||
* Jpg mode. 文件以JPG
|
||||
*/
|
||||
PAGE_TYPE("jpg") {
|
||||
PAGE_TYPE("jpg", AddressFileGenerator.PAGE_TYPE, AddressFileComparator.CompareMode.PAGE_LEVEL) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "页面级";
|
||||
|
|
@ -339,7 +408,7 @@ public class PathCheckPaneController implements Initializable {
|
|||
/**
|
||||
* Pdf mode. 文件以PDF
|
||||
*/
|
||||
FILE_TYPE("pdf") {
|
||||
FILE_TYPE("pdf", AddressFileGenerator.FILE_TYPE, AddressFileComparator.CompareMode.FILE_LEVEL) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "文件级";
|
||||
|
|
@ -348,10 +417,14 @@ public class PathCheckPaneController implements Initializable {
|
|||
/**
|
||||
* The Id.
|
||||
*/
|
||||
final String id;
|
||||
public final String id;
|
||||
public final int number;
|
||||
public final AddressFileComparator.CompareMode compareMode;
|
||||
|
||||
Mode(String id) {
|
||||
Mode(String id, int number, AddressFileComparator.CompareMode compareMode) {
|
||||
this.id = id;
|
||||
this.number = number;
|
||||
this.compareMode = compareMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import top.r3944realms.docchecktoolrefactored.System;
|
||||
import top.r3944realms.docchecktoolrefactored.core.HashFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.core.MD5HashCalculator;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||
import top.r3944realms.docchecktoolrefactored.util.LoggerMarker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
|
@ -53,7 +53,7 @@ public class StorageCarrierPaneController {
|
|||
|
||||
@FXML
|
||||
void onSelectLD(ActionEvent event) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户点击选择文件夹按钮");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户点击选择文件夹按钮");
|
||||
DirectoryChooser directoryChooser = System.getDirectoryChooser();
|
||||
directoryChooser.setTitle("选择要检查的文件夹(页面级文件夹和文件级文件夹等不包括目录文件夹)");
|
||||
|
||||
|
|
@ -81,18 +81,18 @@ public class StorageCarrierPaneController {
|
|||
loadDigitalOutcomes.setText(currentText + File.pathSeparator + folderPath);
|
||||
}
|
||||
}
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户选择了文件夹: {}", selectedFolder.getAbsolutePath());
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户选择了文件夹: {}", selectedFolder.getAbsolutePath());
|
||||
} else {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户取消了文件夹选择");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户取消了文件夹选择");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onClearSelectedFolders(ActionEvent event) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户点击清除已选择文件夹按钮");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户点击清除已选择文件夹按钮");
|
||||
loadDigitalOutcomes.setText("");
|
||||
result7TA.setText("已清除所有已选择的文件夹");
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "已清除所有已选择的文件夹");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "已清除所有已选择的文件夹");
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ public class StorageCarrierPaneController {
|
|||
|
||||
@FXML
|
||||
void onSelectLC(ActionEvent event) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户点击选择RAR文件按钮");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户点击选择RAR文件按钮");
|
||||
FileChooser fileChooser = System.getFileChooser();
|
||||
fileChooser.setTitle("选择一个 .rar 文件");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("RAR Files", "*.rar"));
|
||||
|
|
@ -108,48 +108,48 @@ public class StorageCarrierPaneController {
|
|||
if (selectedFile != null) {
|
||||
System.setLastModifiedFile(selectedFile);
|
||||
loadCompressedFile.setText(selectedFile.getAbsolutePath());
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户选择了RAR文件: {}", selectedFile.getAbsolutePath());
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户选择了RAR文件: {}", selectedFile.getAbsolutePath());
|
||||
} else {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户取消了RAR文件选择");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户取消了RAR文件选择");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onCaculateHash(ActionEvent event) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "开始计算RAR文件的MD5哈希值");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "开始计算RAR文件的MD5哈希值");
|
||||
String filePath = loadCompressedFile.getText();
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "未选择RAR文件,无法计算哈希值");
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "未选择RAR文件,无法计算哈希值");
|
||||
result7TA.setText("请先选择一个 .rar 文件");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = new File(filePath);
|
||||
if (!file.exists() || !file.isFile() || !filePath.endsWith(".rar")) {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "选择的文件无效或不是RAR文件: {}", filePath);
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "选择的文件无效或不是RAR文件: {}", filePath);
|
||||
result7TA.setText("所选文件不存在或不是一个有效的 .rar 文件");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "开始计算文件哈希值: {}", filePath);
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "开始计算文件哈希值: {}", filePath);
|
||||
MD5HashCalculator hashCalculator = new MD5HashCalculator();
|
||||
String hashResult = hashCalculator.calculateHash(file.toPath());
|
||||
result7TA.setText("计算结果:\n" + hashResult);
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "文件哈希值计算完成: {}", hashResult);
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "文件哈希值计算完成: {}", hashResult);
|
||||
} catch (IOException e) {
|
||||
log.error(LoggerHelper.DEBUG_MARKER, "计算文件哈希值时出错: {}", filePath, e);
|
||||
log.error(LoggerMarker.DEBUG_MARKER, "计算文件哈希值时出错: {}", filePath, e);
|
||||
result7TA.setText("计算哈希值时出错: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onGenerateHF(ActionEvent event) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "开始生成哈希列表文件");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "开始生成哈希列表文件");
|
||||
String folderPathsText = loadDigitalOutcomes.getText();
|
||||
if (folderPathsText == null || folderPathsText.isEmpty()) {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "未选择文件夹,无法生成哈希列表文件");
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "未选择文件夹,无法生成哈希列表文件");
|
||||
result7TA.setText("请先选择一个文件夹");
|
||||
return;
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ public class StorageCarrierPaneController {
|
|||
if (folder.exists() && folder.isDirectory()) {
|
||||
folders.add(folder);
|
||||
} else {
|
||||
log.warn(LoggerHelper.DEBUG_MARKER, "选择的路径无效或不是文件夹: {}", path);
|
||||
log.warn(LoggerMarker.DEBUG_MARKER, "选择的路径无效或不是文件夹: {}", path);
|
||||
result7TA.setText("所选路径不存在或不是一个有效的文件夹: " + path);
|
||||
return;
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ public class StorageCarrierPaneController {
|
|||
File outputFile = fileChooser.showSaveDialog(selectLoadDigitalOutcomes7B.getScene().getWindow());
|
||||
|
||||
if (outputFile == null) {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "用户取消了文件保存操作");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "用户取消了文件保存操作");
|
||||
result7TA.setText("未选择保存位置");
|
||||
return;
|
||||
}
|
||||
|
|
@ -193,13 +193,13 @@ public class StorageCarrierPaneController {
|
|||
finalOutputFile = outputFile;
|
||||
}
|
||||
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "选择的输出文件路径: {}", finalOutputFile.getAbsolutePath());
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "选择的输出文件路径: {}", finalOutputFile.getAbsolutePath());
|
||||
|
||||
// 创建后台任务
|
||||
Task<String> task = new Task<>() {
|
||||
@Override
|
||||
protected String call() throws Exception {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "开始执行哈希文件生成任务");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "开始执行哈希文件生成任务");
|
||||
updateMessage("开始生成哈希文件...");
|
||||
|
||||
HashFileGenerator generator = new HashFileGenerator();
|
||||
|
|
@ -213,11 +213,11 @@ public class StorageCarrierPaneController {
|
|||
updateProgress(current, total);
|
||||
updateMessage("处理文件: " + current + "/" + total);
|
||||
if (current % 500 == 0 || current == total) { // 每500个文件或完成时记录一次日志
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "处理进度: {}/{}", current, total);
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "处理进度: {}/{}", current, total);
|
||||
}
|
||||
});
|
||||
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "哈希文件生成任务完成,输出文件: {}", finalOutputFile.getAbsolutePath());
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "哈希文件生成任务完成,输出文件: {}", finalOutputFile.getAbsolutePath());
|
||||
return "哈希列表文件已生成: " + finalOutputFile.getAbsolutePath();
|
||||
}
|
||||
};
|
||||
|
|
@ -229,7 +229,7 @@ public class StorageCarrierPaneController {
|
|||
|
||||
// 任务成功完成
|
||||
task.setOnSucceeded(e -> {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "哈希文件生成任务成功完成");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "哈希文件生成任务成功完成");
|
||||
result7TA.setText(task.getValue());
|
||||
});
|
||||
|
||||
|
|
@ -237,13 +237,13 @@ public class StorageCarrierPaneController {
|
|||
task.setOnFailed(e -> {
|
||||
Throwable exception = task.getException();
|
||||
String errorMsg = "生成哈希文件时出错: " + (exception != null ? exception.getMessage() : "未知错误");
|
||||
log.error(LoggerHelper.RELEASE_MARKER, "哈希文件生成任务失败", exception);
|
||||
log.error(LoggerMarker.RELEASE_MARKER, "哈希文件生成任务失败", exception);
|
||||
result7TA.setText(errorMsg);
|
||||
});
|
||||
|
||||
// 任务取消处理
|
||||
task.setOnCancelled(e -> {
|
||||
log.info(LoggerHelper.DEBUG_MARKER, "哈希文件生成任务被用户取消");
|
||||
log.info(LoggerMarker.DEBUG_MARKER, "哈希文件生成任务被用户取消");
|
||||
result7TA.setText("哈希文件生成操作已取消");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
package top.r3944realms.docchecktoolrefactored.ui.task;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.core.AddressFileComparator;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@Slf4j
|
||||
public class AddressFileComparisonTask extends Task<AddressFileComparator.ComparisonResult> {
|
||||
|
||||
private final String physicalFilePath;
|
||||
private final String logicalFilePath;
|
||||
private final AddressFileComparator.CompareMode compareMode;
|
||||
private final AddressFileComparator comparator;
|
||||
private final long timeoutSeconds; // 超时秒数
|
||||
|
||||
public AddressFileComparisonTask(String physicalFilePath,
|
||||
String logicalFilePath,
|
||||
AddressFileComparator.CompareMode compareMode,
|
||||
long timeoutSeconds) {
|
||||
this.physicalFilePath = physicalFilePath;
|
||||
this.logicalFilePath = logicalFilePath;
|
||||
this.compareMode = compareMode;
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
comparator = new AddressFileComparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AddressFileComparator.ComparisonResult call() throws Exception {
|
||||
updateMessage("初始化文件比较...");
|
||||
comparator.setProgressCallback(new AddressFileComparator.ProgressCallback() {
|
||||
@Override
|
||||
public void onPhaseStarted(AddressFileComparator.Phase phase) {
|
||||
// 阶段开始提示
|
||||
switch (phase) {
|
||||
case READ_PHYSICAL_CSV -> updateMessage("正在读取物理地址 CSV 文件...");
|
||||
case READ_LOGICAL_CSV -> updateMessage("正在读取逻辑地址 CSV 文件...");
|
||||
case COMPARE_FORWARD -> updateMessage("正在执行正向比较(物理 → 逻辑)...");
|
||||
case COMPARE_BACKWARD -> updateMessage("正在执行反向比较(逻辑 → 物理)...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhaseProgress(AddressFileComparator.Phase phase, int current, int total) {
|
||||
if (total <= 0) return;
|
||||
|
||||
updateProgress(current, total);
|
||||
|
||||
// 阶段进度提示
|
||||
switch (phase) {
|
||||
case READ_PHYSICAL_CSV -> updateMessage(String.format("读取物理地址 CSV: %d/%d", current, total));
|
||||
case READ_LOGICAL_CSV -> updateMessage(String.format("读取逻辑地址 CSV: %d/%d", current, total));
|
||||
case COMPARE_FORWARD -> updateMessage(String.format("正向比较: %d/%d", current, total));
|
||||
case COMPARE_BACKWARD -> updateMessage(String.format("反向比较: %d/%d", current, total));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhaseCompleted(AddressFileComparator.Phase phase) {
|
||||
// 阶段完成提示
|
||||
switch (phase) {
|
||||
case READ_PHYSICAL_CSV -> updateMessage("物理地址 CSV 文件读取完成");
|
||||
case READ_LOGICAL_CSV -> updateMessage("逻辑地址 CSV 文件读取完成");
|
||||
case COMPARE_FORWARD -> updateMessage("正向比较完成");
|
||||
case COMPARE_BACKWARD -> updateMessage("反向比较完成");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// 构建显示文本
|
||||
return comparator.compareFiles(physicalFilePath, logicalFilePath, compareMode)
|
||||
.get(timeoutSeconds, TimeUnit.SECONDS);
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
updateMessage("文件比对超时,请检查文件大小或电脑性能。");
|
||||
log.error("文件比对超时", e);
|
||||
throw e;
|
||||
} finally {
|
||||
comparator.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
comparator.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package top.r3944realms.docchecktoolrefactored.ui.task;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import top.r3944realms.docchecktoolrefactored.System;
|
||||
import top.r3944realms.docchecktoolrefactored.core.AddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||
import top.r3944realms.docchecktoolrefactored.core.PhysicalAddressFileGenerator;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static java.util.concurrent.Executors.*;
|
||||
|
||||
@Slf4j
|
||||
public class AddressFileGenerationTask extends Task<String> {
|
||||
|
||||
private final String sourcePath;
|
||||
private final File outputFile;
|
||||
private final int folderType;
|
||||
private final AddressFileGenerator generator;
|
||||
|
||||
public AddressFileGenerationTask(String sourcePath,
|
||||
File outputFile,
|
||||
int folderType,
|
||||
boolean isLogic) {
|
||||
this.sourcePath = sourcePath;
|
||||
this.outputFile = outputFile;
|
||||
this.folderType = folderType;
|
||||
this.generator = isLogic ? new LogicalAddressFileGenerator() : new PhysicalAddressFileGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String call() {
|
||||
updateMessage("初始化生成任务...");
|
||||
generator.setProgressCallback(new AddressFileGenerator.ProgressCallback() {
|
||||
@Override
|
||||
public void onPhaseStarted(AddressFileGenerator.Phase phase) {
|
||||
switch (phase) {
|
||||
case GENERATE_LOGICAL -> updateMessage("正在生成逻辑地址 CSV 文件 ...");
|
||||
case GENERATE_PHYSICAL -> updateMessage("正在生成物理地址 CSV 文件 ...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhaseProgress(AddressFileGenerator.Phase phase, int current, int total) {
|
||||
if (total > 0) {
|
||||
updateProgress(current, total);
|
||||
switch (phase) {
|
||||
case GENERATE_LOGICAL -> updateMessage(String.format("在生成逻辑地址 CSV : %d/%d", current, total));
|
||||
case GENERATE_PHYSICAL -> updateMessage(String.format("在生成物理地址 CSV : %d/%d", current, total));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhaseCompleted(AddressFileGenerator.Phase phase) {
|
||||
switch (phase) {
|
||||
case GENERATE_LOGICAL -> updateMessage("已完成生成逻辑地址 CSV 文件任务");
|
||||
case GENERATE_PHYSICAL -> updateMessage("已完成生成物理地址 CSV 文件任务");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
ExecutorService executor = newSingleThreadExecutor();
|
||||
Future<?> future = executor.submit(() -> {
|
||||
generator.generateAddressFile(sourcePath, outputFile, folderType);
|
||||
});
|
||||
|
||||
try {
|
||||
// 等待执行完成或超时
|
||||
future.get(System.getSetting().getTaskTimeout(), TimeUnit.SECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
future.cancel(true); // 尝试中断
|
||||
this.cancel(); // 取消 Task
|
||||
throw new RuntimeException("生成任务超时 (>" + System.getSetting().getTaskTimeout() + "s)", e);
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw new RuntimeException("生成任务失败", e.getCause());
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
return outputFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
log.info("生成任务已取消: {}", outputFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package top.r3944realms.docchecktoolrefactored.ui.task;
|
|||
|
||||
import javafx.concurrent.Task;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.docchecktoolrefactored.System;
|
||||
import top.r3944realms.docchecktoolrefactored.core.DuplicateFinder;
|
||||
import top.r3944realms.docchecktoolrefactored.core.MD5HashCalculator;
|
||||
|
|
@ -111,9 +112,9 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
|
|||
findThread.start();
|
||||
|
||||
// 等待扫描完成,设置超时时间(例如5分钟)
|
||||
long totalTimeout = System.getSetting().getTotalTimeout();
|
||||
long totalTimeout = System.getSetting().getTaskTimeout();
|
||||
if (!latch.await(totalTimeout, TimeUnit.SECONDS)) {
|
||||
scanner.cancel();
|
||||
duplicateFinder.shutdown();
|
||||
throw new TimeoutException(String.format("扫描超时(%d秒)", totalTimeout));
|
||||
}
|
||||
|
||||
|
|
@ -127,18 +128,18 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
|
|||
|
||||
// 检查是否被取消
|
||||
if (isCancelled()) {
|
||||
scanner.cancel();
|
||||
duplicateFinder.shutdown();
|
||||
return "操作已被取消";
|
||||
}
|
||||
|
||||
// 检查是否超时
|
||||
if (java.lang.System.currentTimeMillis() - start > totalTimeout * 1000L) {
|
||||
scanner.cancel();
|
||||
duplicateFinder.shutdown();
|
||||
throw new TimeoutException(String.format("扫描超时(%d秒)", totalTimeout));
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scanner.cancel();
|
||||
duplicateFinder.shutdown();
|
||||
Thread.currentThread().interrupt();
|
||||
return "操作被中断";
|
||||
}
|
||||
|
|
@ -149,8 +150,11 @@ public class DuplicateDocumentDetectionTask extends Task<String>{
|
|||
}
|
||||
|
||||
List<DuplicateGroup> duplicateGroups = resultRef.get();
|
||||
|
||||
// 构建最终结果
|
||||
return generateResult(duplicateGroups, totalFiles);
|
||||
}
|
||||
|
||||
private static @NotNull String generateResult(List<DuplicateGroup> duplicateGroups, AtomicInteger totalFiles) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,15 @@ public class DialogUtil {
|
|||
public static boolean showExitConfirmation(Window owner) {
|
||||
return showConfirmationDialog("确认退出", "您确定要退出程序吗?", "请确认您的操作");
|
||||
}
|
||||
/**
|
||||
* Show exit confirmation boolean.
|
||||
*
|
||||
* @param owner the owner
|
||||
* @return the boolean
|
||||
*/
|
||||
public static boolean showCancelConfirmation(Window owner) {
|
||||
return showConfirmationDialog("确认取消", "您确定要取消该任务吗?", "请确认您的操作");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation dialog boolean.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import javafx.application.Platform;
|
|||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
|
|
@ -15,14 +14,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
/**
|
||||
* 进度条窗口工具类,支持取消按钮
|
||||
*/
|
||||
public class ProgressBarUtil {
|
||||
public class ProgressBar {
|
||||
|
||||
private Stage progressStage;
|
||||
private ProgressBar progressBar;
|
||||
private javafx.scene.control.ProgressBar progressBar;
|
||||
private Label messageLabel;
|
||||
private Button cancelButton;
|
||||
private final boolean isCancelable;
|
||||
private final AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
private Runnable onCancelCallback;
|
||||
|
||||
public ProgressBar(boolean isCancelable) {
|
||||
this.isCancelable = isCancelable;
|
||||
}
|
||||
public ProgressBar() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示进度条窗口
|
||||
* @param ownerStage 父窗口
|
||||
|
|
@ -34,41 +42,57 @@ public class ProgressBarUtil {
|
|||
progressStage = new Stage();
|
||||
progressStage.initOwner(ownerStage);
|
||||
progressStage.initStyle(StageStyle.UTILITY);
|
||||
if (isCancelable) {
|
||||
progressStage.setOnCloseRequest(windowEvent -> {
|
||||
if (!DialogUtil.showCancelConfirmation(progressStage.getOwner())) {
|
||||
windowEvent.consume();
|
||||
}
|
||||
else {
|
||||
cancelled.set(true);
|
||||
if (onCancelCallback != null) {
|
||||
onCancelCallback.run();
|
||||
}
|
||||
closeProgress();
|
||||
}
|
||||
});
|
||||
}
|
||||
progressStage.initModality(Modality.APPLICATION_MODAL);
|
||||
progressStage.setTitle(title);
|
||||
progressStage.setResizable(false);
|
||||
|
||||
// 创建进度条
|
||||
progressBar = new ProgressBar();
|
||||
progressBar = new javafx.scene.control.ProgressBar();
|
||||
progressBar.setPrefWidth(300);
|
||||
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
|
||||
progressBar.setProgress(javafx.scene.control.ProgressBar.INDETERMINATE_PROGRESS);
|
||||
|
||||
// 创建消息标签
|
||||
messageLabel = new Label(initialMessage);
|
||||
|
||||
// 创建取消按钮
|
||||
cancelButton = new Button("取消");
|
||||
cancelButton.setOnAction(e -> {
|
||||
cancelled.set(true);
|
||||
if (onCancelCallback != null) {
|
||||
onCancelCallback.run();
|
||||
}
|
||||
closeProgress();
|
||||
});
|
||||
|
||||
// 如果 setOnCancel 先调用过,绑定回调
|
||||
if (onCancelCallback != null) {
|
||||
if (isCancelable) {
|
||||
// 创建取消按钮
|
||||
cancelButton = new Button("取消");
|
||||
cancelButton.setOnAction(e -> {
|
||||
cancelled.set(true);
|
||||
onCancelCallback.run();
|
||||
if (onCancelCallback != null) {
|
||||
onCancelCallback.run();
|
||||
}
|
||||
closeProgress();
|
||||
});
|
||||
|
||||
// 如果 setOnCancel 先调用过,绑定回调
|
||||
if (onCancelCallback != null) {
|
||||
cancelButton.setOnAction(e -> {
|
||||
cancelled.set(true);
|
||||
onCancelCallback.run();
|
||||
closeProgress();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 布局
|
||||
VBox root = new VBox(10, messageLabel, progressBar, cancelButton);
|
||||
root.setStyle("-fx-padding: 20; -fx-alignment: center;");
|
||||
VBox root = new VBox(10, messageLabel, progressBar);
|
||||
if (isCancelable) root.getChildren().add(cancelButton);
|
||||
|
||||
root.setStyle("-fx-padding: 20; -fx-alignment: center;");
|
||||
Scene scene = new Scene(root);
|
||||
progressStage.setScene(scene);
|
||||
progressStage.sizeToScene();
|
||||
|
|
@ -118,33 +142,35 @@ public class ProgressBarUtil {
|
|||
* @param task 要执行的任务(可检查 isCancelled() 中途退出)
|
||||
*/
|
||||
public static void showAndExecute(Stage ownerStage, String title, String initialMessage, CancellableTask task) {
|
||||
ProgressBarUtil progressBarUtil = new ProgressBarUtil();
|
||||
progressBarUtil.showProgress(ownerStage, title, initialMessage);
|
||||
ProgressBar progressBar = new ProgressBar();
|
||||
progressBar.showProgress(ownerStage, title, initialMessage);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
task.run(progressBarUtil);
|
||||
task.run(progressBar);
|
||||
} finally {
|
||||
progressBarUtil.closeProgress();
|
||||
progressBar.closeProgress();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@FunctionalInterface
|
||||
public interface CancellableTask {
|
||||
void run(ProgressBarUtil util);
|
||||
void run(ProgressBar util);
|
||||
}
|
||||
public void setOnCancel(Runnable onCancel) {
|
||||
if (cancelButton != null) {
|
||||
cancelButton.setOnAction(e -> {
|
||||
cancelled.set(true);
|
||||
if (onCancel != null) {
|
||||
onCancel.run();
|
||||
}
|
||||
closeProgress();
|
||||
});
|
||||
} else {
|
||||
// 保存回调,稍后在按钮创建后绑定
|
||||
this.onCancelCallback = onCancel;
|
||||
}
|
||||
if (isCancelable) {
|
||||
if (cancelButton != null) {
|
||||
cancelButton.setOnAction(e -> {
|
||||
cancelled.set(true);
|
||||
if (onCancel != null) {
|
||||
onCancel.run();
|
||||
}
|
||||
closeProgress();
|
||||
});
|
||||
} else {
|
||||
// 保存回调,稍后在按钮创建后绑定
|
||||
this.onCancelCallback = onCancel;
|
||||
}
|
||||
} else throw new UnsupportedOperationException("Cancellable task is not supported");
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ public class FileUtil {
|
|||
Path path = Paths.get(filePath).normalize();
|
||||
File file = path.toFile();
|
||||
if (!file.exists()) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "文件不存在: {}", filePath);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "文件不存在: {}", filePath);
|
||||
throw new NoSuchFileException("文件不存在: " + filePath);
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ public class FileUtil {
|
|||
File file = path.toFile();
|
||||
|
||||
if (!file.exists()) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "文件不存在: {}", filePath);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "文件不存在: {}", filePath);
|
||||
throw new NoSuchFileException("文件不存在: " + filePath);
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ public class FileUtil {
|
|||
.toList();
|
||||
|
||||
if (!supportedExtensions.contains(fileExtension)) {
|
||||
log.error(LoggerHelper.TRACE_MARKER, "不支持的文件格式: {}", fileExtension);
|
||||
log.error(LoggerMarker.TRACE_MARKER, "不支持的文件格式: {}", fileExtension);
|
||||
throw new IllegalArgumentException("不支持的文件格式,预期: "
|
||||
+ supportedExtensions + ",实际: " + fileExtension);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package top.r3944realms.docchecktoolrefactored.util;
|
|||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
public class LoggerHelper {
|
||||
public class LoggerMarker {
|
||||
public static final Marker TRACE_MARKER = MarkerFactory.getMarker("TRACE");
|
||||
public static final Marker DEBUG_MARKER = MarkerFactory.getMarker("DEBUG");
|
||||
public static final Marker RELEASE_MARKER = MarkerFactory.getMarker("RELEASE");
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
<RowConstraints maxHeight="592.6666666666666" prefHeight="581.3333536783855" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<TextArea fx:id="result1B" editable="false" prefHeight="414.0" prefWidth="683.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
|
||||
<TextArea fx:id="result1TA" editable="false" prefHeight="414.0" prefWidth="683.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
<GridPane.margin>
|
||||
<Insets left="10.0" />
|
||||
</GridPane.margin></Label>
|
||||
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="批量取出数字化成果的哈希值,采用对比法查找重复文件,导出重复文件搜索结果,进行人工一一比对,并将比对台帐和统计结果填入查重登记表(附件1)。 " wrapText="true" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="3">
|
||||
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="1.点击“选择文件夹”按钮,载入需要查找重复文件的数据(一般页面级文件和文件级文件分批载入检查)。 2.点击“开始检查”按钮,软件将对选定区域的数据批量计算文件哈希值,并对比查找重复文件,“结果反馈”区域将显示扫描文件数量、重复文件组和重复文件数量。 3.根据软件反馈的重复文件组,逐一核实确认是否为重复文件。 4.将确认后的检查结果填入查重登记表(附件1)。 ;" wrapText="true" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="3">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@
|
|||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
</Button>
|
||||
<TextArea editable="false" prefHeight="450.0" prefWidth="400.0" text="①获取数字化成果文件的物理存储路径(实际存储位置) ②处理案卷级/文件级目录生成逻辑存储路径(理论存储位置) ③自动对比物理路径与逻辑路径,识别以下问题: 1)文件漏扫/存储路径错误/命名不规范的文件 2)目录数据库档号错误或页数著录。 ④统计结果填入《查遗漏、查存储路径和命名规范登记表》(附件2)。 " wrapText="true" GridPane.columnIndex="4" GridPane.rowIndex="4">
|
||||
<TextArea editable="false" prefHeight="450.0" prefWidth="400.0" text="1.选择“页面级”,载入目录,生成页面级逻辑地址;载入目录对应的页面级数据,生成页面级物理地址,用软件比对出差异,记录软件反馈结果。 2.选择“文件级”,载入目录,生成文件级逻辑地址;载入目录对应的文件级数据,生成文件级物理地址,用软件比对出差异,记录软件反馈结果。 3.逐一核实差异存在的原因,以判断目录和扫描数据存在的问题。 4.根据核实的情况填写《查遗漏、查存储路径和命名规范登记表》(附件2)。 " wrapText="true" GridPane.columnIndex="4" GridPane.rowIndex="4">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17">
|
||||
<children>
|
||||
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" scrollLeft="1.0" text="工作内容: ①汇总前两步检查结果,计算合格率(要求100%) ②若合格率达标,按总页数5%比例抽检: 著录准确性/规范性/完整性(要求100%合格率) 图像清晰度/倾斜度/黑边(要求95%以上的合格率) ③结果填入《质量检查登记表》(附件3)。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" scrollLeft="1.0" text="工作内容: 1.汇总前两步检查结果,计算合格率,合格率不是100%则验收不通过,要求整改。 2.若合格率达到100%,则按总页数5%比例抽检: ①著录准确性/规范性/完整性(要求100%合格率); ②图像清晰度/倾斜度/黑边(要求95%以上的合格率); ③结果填入《质量检查登记表》(附件3)。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<font>
|
||||
<Font size="18.0" />
|
||||
</font></TextArea>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" text="工作内容: ①检查档案管理系统或电子目录的挂接准确率(要求100%) ②逐件验证数字化成果与目录的关联性 ③结果填入《挂接检查登记表》(附件5)。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" text="工作内容: 1.检查档案管理系统或电子目录的挂接准确率(要求100%) 2.逐件验证数字化成果与目录的关联性。如挂接内容与档案目录不一致,则验收不通过,要求整改。 3.验证结果填入《挂接检查登记表》(附件5)。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<font>
|
||||
<Font size="18.0" />
|
||||
</font></TextArea>
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@
|
|||
<Font size="14.0" />
|
||||
</font>
|
||||
</Button>
|
||||
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="①对照《存储载体检查登记表》(附件7)检查并记录存储载体的类型/数量/内容/可读性情况。 ②将数字化成果(包括单页、多页文件及目录)打包生成"数字化验收检测包.rar"(含目录、哈希值列表、检测文件) ③计算并验证压缩包的MD5或哈希值" wrapText="true" GridPane.columnIndex="3" GridPane.rowIndex="3">
|
||||
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="1.对照《存储载体检查登记表》(附件7)检查并记录存储载体的类型/数量/内容/可读性情况。 2.计算数字化成果(包括单页、多页文件)的MD5码,生成列表文件,保存在目录所在文件夹; 3.将列表文件、目录文件、检测过程文件(第2步生成的逻辑地址、物理地址等csv文件)打包生成"数字化验收检测包.rar" 4.计算并验证压缩包的MD5码或哈希值 5.结果填入《存储载体检查登记表》(附件7)" wrapText="true" GridPane.columnIndex="3" GridPane.rowIndex="3">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
|
|
|
|||
|
|
@ -27,22 +27,22 @@
|
|||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label text="单个扫描超时时间:" GridPane.rowIndex="1">
|
||||
<Label text="步骤扫描超时时间:" GridPane.rowIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
</Label>
|
||||
<Spinner fx:id="scanSingleTimeOutS" onMouseDragExited="#onCheckOne" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||
<Spinner fx:id="scanTimeOutS" onMouseDragExited="#onCheckOne" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
</Spinner>
|
||||
<Label prefWidth="114.0" text="总扫描超时时间:" GridPane.rowIndex="2">
|
||||
<Label prefWidth="114.0" text="步骤任务超时时间:" GridPane.rowIndex="2">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
</Label>
|
||||
<Spinner fx:id="scanTotalTimeOutS" onMouseDragExited="#onCheckTwo" GridPane.columnIndex="1" GridPane.rowIndex="2">
|
||||
<Spinner fx:id="taskTimeOutS" onMouseDragExited="#onCheckTwo" GridPane.columnIndex="1" GridPane.rowIndex="2">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</GridPane.margin>
|
||||
|
|
|
|||
BIN
src/main/resources/img/logo256x.ico
Normal file
BIN
src/main/resources/img/logo256x.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
|
|
@ -1,53 +1,101 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="10 seconds">
|
||||
<property name="APP_NAME" value="DocCheckTool" />
|
||||
<property name="LOG_HOME" value="${log.dir:-logs}/${APP_NAME}"/>
|
||||
<property name="ENCODER_PATTERN"
|
||||
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" />
|
||||
|
||||
<property name="APP_NAME" value="DocCheckTool"/>
|
||||
<property name="LOG_HOME" value="${APP_NAME}/${log.dir:-logs}"/>
|
||||
|
||||
<!-- 日志格式 -->
|
||||
<property name="RELEASE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%level] %msg%n"/>
|
||||
<property name="DEBUG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n"/>
|
||||
<property name="TRACKER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n"/>
|
||||
|
||||
<contextName>${APP_NAME}</contextName>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<Pattern>${ENCODER_PATTERN}</Pattern>
|
||||
<encoder>
|
||||
<pattern>${DEBUG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="FILE"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
|
||||
<!-- RELEASE 文件日志 -->
|
||||
<appender name="RELEASE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/release.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/output.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<fileNamePattern>${LOG_HOME}/archive/release.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${ENCODER_PATTERN}</pattern>
|
||||
<encoder>
|
||||
<pattern>${RELEASE_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="ERROR_FILE"
|
||||
class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
|
||||
<!-- DEBUG 文件日志 -->
|
||||
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/debug/debug.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<fileNamePattern>${LOG_HOME}/archive/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${ENCODER_PATTERN}</pattern>
|
||||
<encoder>
|
||||
<pattern>${DEBUG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
</appender>
|
||||
<appender name="SYNC_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
|
||||
<!-- TRACKER 文件日志(默认不启用) -->
|
||||
<appender name="TRACKER_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_HOME}/debug/tracker.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_HOME}/sync.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<fileNamePattern>${LOG_HOME}/archive/tracker.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${ENCODER_PATTERN}</pattern>
|
||||
<encoder>
|
||||
<pattern>${TRACKER_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<logger name="log.sync" level="DEBUG" addtivity="true">
|
||||
<appender-ref ref="SYNC_FILE" />
|
||||
|
||||
<!-- 全局 Marker TurboFilter -->
|
||||
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
|
||||
<Marker>RELEASE</Marker>
|
||||
<OnMatch>ACCEPT</OnMatch>
|
||||
<OnMismatch>DENY</OnMismatch>
|
||||
</turboFilter>
|
||||
|
||||
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
|
||||
<Marker>DEBUG</Marker>
|
||||
<OnMatch>ACCEPT</OnMatch>
|
||||
<OnMismatch>NEUTRAL</OnMismatch>
|
||||
</turboFilter>
|
||||
|
||||
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
|
||||
<Marker>TRACKER</Marker>
|
||||
<OnMatch>ACCEPT</OnMatch>
|
||||
<OnMismatch>DENY</OnMismatch>
|
||||
</turboFilter>
|
||||
|
||||
<!-- Logger 定义 -->
|
||||
<logger name="log.release" level="INFO" additivity="false">
|
||||
<appender-ref ref="RELEASE_FILE"/>
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
<appender-ref ref="ERROR_FILE" />
|
||||
|
||||
<logger name="log.debug" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="DEBUG_FILE"/>
|
||||
<appender-ref ref="RELEASE_FILE"/>
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
|
||||
<logger name="log.tracker" level="TRACE" additivity="false">
|
||||
<appender-ref ref="TRACKER_FILE"/>
|
||||
<appender-ref ref="DEBUG_FILE"/>
|
||||
<appender-ref ref="RELEASE_FILE"/>
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
|
||||
<!-- ROOT Logger -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="RELEASE_FILE"/>
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
</configuration>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user