feat:界面变动,逻辑优化
This commit is contained in:
parent
dcfc19aafa
commit
1bf1579e43
15
build.gradle
15
build.gradle
|
|
@ -64,7 +64,6 @@ dependencies {
|
||||||
|
|
||||||
// 你项目的其他依赖(老库走classpath)
|
// 你项目的其他依赖(老库走classpath)
|
||||||
implementation 'ch.qos.logback:logback-classic:1.5.6'
|
implementation 'ch.qos.logback:logback-classic:1.5.6'
|
||||||
implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0'
|
|
||||||
implementation 'commons-cli:commons-cli:1.9.0'
|
implementation 'commons-cli:commons-cli:1.9.0'
|
||||||
implementation 'com.alibaba:easyexcel:4.0.3'
|
implementation 'com.alibaba:easyexcel:4.0.3'
|
||||||
implementation 'org.apache.pdfbox:pdfbox:3.0.5'
|
implementation 'org.apache.pdfbox:pdfbox:3.0.5'
|
||||||
|
|
@ -131,18 +130,22 @@ tasks.register('runCli', JavaExec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可选:创建生成可执行JAR的任务
|
// 可选:创建生成可执行JAR的任务
|
||||||
tasks.register('buildCliJar', Jar) {
|
tasks.register('buildFatJar', Jar) {
|
||||||
group = 'build'
|
group = 'build'
|
||||||
description = 'Builds a standalone JAR for CLI mode'
|
description = 'Builds a standalone JAR with all dependencies'
|
||||||
|
|
||||||
manifest {
|
manifest {
|
||||||
attributes 'Main-Class': 'top.r3944realms.docchecktoolrefactored.Main'
|
attributes(
|
||||||
|
'Main-Class': 'top.r3944realms.docchecktoolrefactored.Main'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
from {
|
from {
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
} with jar
|
}
|
||||||
|
with jar // 继承主 jar 的内容
|
||||||
|
|
||||||
archiveBaseName = 'doc-check-tool-cli'
|
archiveBaseName.set('doc-check-tool-cli')
|
||||||
|
archiveVersion.set('1.0')
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# 改动
|
||||||
|
1. 改名
|
||||||
|
2. 界面颜色
|
||||||
|
3.
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
package top.r3944realms.docchecktoolrefactored;
|
package top.r3944realms.docchecktoolrefactored;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import top.r3944realms.docchecktoolrefactored.ui.SceneManager;
|
import top.r3944realms.docchecktoolrefactored.ui.SceneManager;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class JavaFxApplication extends Application {
|
public class JavaFxApplication extends Application {
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
|
|
@ -13,6 +16,7 @@ public class JavaFxApplication extends Application {
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) throws Exception {
|
||||||
SceneManager.init(primaryStage);
|
SceneManager.init(primaryStage);
|
||||||
|
primaryStage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/img/icon.jpg"))));
|
||||||
SceneManager.switchLoginView();
|
SceneManager.switchLoginView();
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package top.r3944realms.docchecktoolrefactored;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.r3944realms.docchecktoolrefactored.cil.CliProcessor;
|
import top.r3944realms.docchecktoolrefactored.cil.CliProcessor;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.core.Setting;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -12,14 +13,13 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The entry point of application.
|
* The entry point of application.
|
||||||
*
|
*
|
||||||
* @param args the input arguments
|
* @param args the input arguments
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("DataFlowIssue")
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
System.init();
|
||||||
// log.info(StringUtil.NO_BUG);
|
// log.info(StringUtil.NO_BUG);
|
||||||
// 检查是否有 --cli 参数
|
// 检查是否有 --cli 参数
|
||||||
List<String> list = Arrays.asList(args);
|
List<String> list = Arrays.asList(args);
|
||||||
|
|
|
||||||
188
src/main/java/top/r3944realms/docchecktoolrefactored/System.java
Normal file
188
src/main/java/top/r3944realms/docchecktoolrefactored/System.java
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
package top.r3944realms.docchecktoolrefactored;
|
||||||
|
|
||||||
|
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 java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public enum System {
|
||||||
|
INSTANCE;
|
||||||
|
private volatile Setting setting;
|
||||||
|
private volatile File lastModifiedFile;
|
||||||
|
private static final String CONFIG_FILE_NAME = "config.ini";
|
||||||
|
private static final Properties properties = new Properties();
|
||||||
|
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
// 默认值
|
||||||
|
private static final long DEFAULT_SINGLE_TIMEOUT = 30;
|
||||||
|
private static final long DEFAULT_TOTAL_TIMEOUT = 300;
|
||||||
|
private static final boolean DEFAULT_ENABLE_STEP = false;
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
loadSettings();
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(System::saveSettings));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加载配置文件 */
|
||||||
|
private static void loadSettings() {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
Path configPath = getConfigPath();
|
||||||
|
if (Files.exists(configPath)) {
|
||||||
|
try (InputStream input = new FileInputStream(configPath.toFile())) {
|
||||||
|
properties.load(input);
|
||||||
|
log.debug(LoggerHelper.DEBUG_MARKER, "配置文件加载成功: {}", configPath);
|
||||||
|
INSTANCE.setting = propertiesToSetting(properties);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(LoggerHelper.DEBUG_MARKER, "读取配置文件失败: {}, 使用默认配置", e.getMessage());
|
||||||
|
INSTANCE.setting = defaultSetting();
|
||||||
|
settingToProperties(INSTANCE.setting, properties);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
INSTANCE.setting = defaultSetting();
|
||||||
|
settingToProperties(INSTANCE.setting, properties);
|
||||||
|
saveSettings(); // 首次启动保存默认配置
|
||||||
|
log.debug(LoggerHelper.DEBUG_MARKER, "配置文件不存在,已创建默认配置: {}", configPath);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存配置文件 */
|
||||||
|
private static void saveSettings() {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (INSTANCE.setting != null) {
|
||||||
|
Path configPath = getConfigPath();
|
||||||
|
Path configDir = configPath.getParent();
|
||||||
|
if (!Files.exists(configDir)) {
|
||||||
|
Files.createDirectories(configDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
settingToProperties(INSTANCE.setting, properties);
|
||||||
|
|
||||||
|
try (OutputStream output = new FileOutputStream(configPath.toFile())) {
|
||||||
|
properties.store(new OutputStreamWriter(output, StandardCharsets.UTF_8),
|
||||||
|
"DocCheckTool Configuration");
|
||||||
|
log.debug(LoggerHelper.DEBUG_MARKER, "配置文件保存成功: {}", configPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(LoggerHelper.DEBUG_MARKER, "保存配置文件失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(LoggerHelper.DEBUG_MARKER, "创建配置目录失败: {}", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取配置文件路径 */
|
||||||
|
private static Path getConfigPath() {
|
||||||
|
String userHome = java.lang.System.getProperty("user.home");
|
||||||
|
return Paths.get(userHome, ".docchecktool", 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("enableStep", String.valueOf(setting.isEnableStep()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将Properties转换为Setting对象 */
|
||||||
|
private static Setting propertiesToSetting(Properties props) {
|
||||||
|
Setting s = new Setting();
|
||||||
|
|
||||||
|
try {
|
||||||
|
s.setSingleTimeout(Long.parseLong(props.getProperty("singleTimeout", String.valueOf(DEFAULT_SINGLE_TIMEOUT))));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
s.setSingleTimeout(DEFAULT_SINGLE_TIMEOUT);
|
||||||
|
log.error(LoggerHelper.DEBUG_MARKER, "singleTimeout格式错误,使用默认值{}", DEFAULT_SINGLE_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
s.setTotalTimeout(Long.parseLong(props.getProperty("totalTimeout", String.valueOf(DEFAULT_TOTAL_TIMEOUT))));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
s.setTotalTimeout(DEFAULT_TOTAL_TIMEOUT);
|
||||||
|
log.error(LoggerHelper.DEBUG_MARKER, "totalTimeout格式错误,使用默认值{}", 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取默认Setting */
|
||||||
|
private static Setting defaultSetting() {
|
||||||
|
Setting s = new Setting();
|
||||||
|
s.setSingleTimeout(DEFAULT_SINGLE_TIMEOUT);
|
||||||
|
s.setTotalTimeout(DEFAULT_TOTAL_TIMEOUT);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 手动保存 */
|
||||||
|
public static void saveSettingsNow() {
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重新加载 */
|
||||||
|
public static void reloadSettings() {
|
||||||
|
loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取Setting对象 */
|
||||||
|
public static Setting getSetting() {
|
||||||
|
return INSTANCE.setting;
|
||||||
|
}
|
||||||
|
/** 获取File 对象 */
|
||||||
|
public static File getlastModifiedFile() {
|
||||||
|
return INSTANCE.lastModifiedFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取配置目录路径 */
|
||||||
|
public static String getConfigDirectory() {
|
||||||
|
return getConfigPath().getParent().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取配置文件路径 */
|
||||||
|
public static String getConfigFilePath() {
|
||||||
|
return getConfigPath().toString();
|
||||||
|
}
|
||||||
|
public static void setLastModifiedFile(File lastModifiedFile) {
|
||||||
|
INSTANCE.lastModifiedFile = lastModifiedFile;
|
||||||
|
}
|
||||||
|
public static FileChooser getFileChooser() {
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
File lastFile = getlastModifiedFile();
|
||||||
|
if (lastFile != null) {
|
||||||
|
File parentDir = lastFile.getParentFile();
|
||||||
|
if (parentDir != null && parentDir.exists() && parentDir.isDirectory()) {
|
||||||
|
fileChooser.setInitialDirectory(parentDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileChooser;
|
||||||
|
}
|
||||||
|
public static DirectoryChooser getDirectoryChooser() {
|
||||||
|
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||||
|
File lastFile = getlastModifiedFile();
|
||||||
|
if (lastFile != null) {
|
||||||
|
File parentDir = lastFile.getParentFile();
|
||||||
|
if (parentDir != null && parentDir.exists() && parentDir.isDirectory()) {
|
||||||
|
directoryChooser.setInitialDirectory(parentDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return directoryChooser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
||||||
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
|
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
|
||||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -102,7 +103,7 @@ public class CliProcessor {
|
||||||
printHelp(options);
|
printHelp(options);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error processing CLI command", e);
|
log.error(LoggerHelper.DEBUG_MARKER, "Error processing CLI command", e);
|
||||||
System.err.println("Error: " + e.getMessage());
|
System.err.println("Error: " + e.getMessage());
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package top.r3944realms.docchecktoolrefactored.core;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -64,8 +65,7 @@ public class AddressFileComparator {
|
||||||
int physicalCount = physicalRecords.size();
|
int physicalCount = physicalRecords.size();
|
||||||
int logicalCount = logicalRecords.size();
|
int logicalCount = logicalRecords.size();
|
||||||
|
|
||||||
log.info("读取物理地址文件记录数: {}", physicalCount);
|
log.info(LoggerHelper.DEBUG_MARKER, "读取物理地址文件记录数: {}, 读取逻辑地址文件记录数: {}", physicalCount, logicalCount);
|
||||||
log.info("读取逻辑地址文件记录数: {}", logicalCount);
|
|
||||||
|
|
||||||
List<String> forwardComparisonResults = new ArrayList<>(); // 物理文件在逻辑文件中未找到
|
List<String> forwardComparisonResults = new ArrayList<>(); // 物理文件在逻辑文件中未找到
|
||||||
List<String> backwardComparisonResults = new ArrayList<>(); // 逻辑文件在物理文件中未找到
|
List<String> backwardComparisonResults = new ArrayList<>(); // 逻辑文件在物理文件中未找到
|
||||||
|
|
@ -178,7 +178,7 @@ public class AddressFileComparator {
|
||||||
try {
|
try {
|
||||||
File file = new File(filePath);
|
File file = new File(filePath);
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
log.error("CSV文件不存在: {}", filePath);
|
log.error(LoggerHelper.RELEASE_MARKER, "CSV文件不存在: {}", filePath);
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,10 +198,10 @@ public class AddressFileComparator {
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.close();
|
reader.close();
|
||||||
log.info("成功读取CSV文件,共 {} 行记录", records.size());
|
log.info(LoggerHelper.DEBUG_MARKER, "成功读取CSV文件,共 {} 行记录", records.size());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("读取CSV文件时出错: {}", e.getMessage(), e);
|
log.error(LoggerHelper.RELEASE_MARKER, "读取CSV文件时出错: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
|
|
@ -211,49 +211,46 @@ public class AddressFileComparator {
|
||||||
List<String> forwardResults, List<String> backwardResults,
|
List<String> forwardResults, List<String> backwardResults,
|
||||||
List<String> pathMismatchResults, List<String> pageCountMismatchResults,
|
List<String> pathMismatchResults, List<String> pageCountMismatchResults,
|
||||||
CompareMode compareMode) {
|
CompareMode compareMode) {
|
||||||
log.info("=== 文件比较结果 ===");
|
StringBuilder sb = new StringBuilder();
|
||||||
log.info("物理地址文件记录数: {}", physicalCount);
|
|
||||||
log.info("逻辑地址文件记录数: {}", logicalCount);
|
sb.append("=== 文件比较结果 ===\n");
|
||||||
|
sb.append("物理地址文件记录数: ").append(physicalCount).append("\n");
|
||||||
|
sb.append("逻辑地址文件记录数: ").append(logicalCount).append("\n");
|
||||||
|
|
||||||
if (pathMismatchResults.isEmpty()) {
|
if (pathMismatchResults.isEmpty()) {
|
||||||
log.info("没有路径错误");
|
sb.append("没有路径错误\n");
|
||||||
} else {
|
} else {
|
||||||
log.info("文件名相同但路径不一致的记录数量: {}", pathMismatchResults.size());
|
sb.append("文件名相同但路径不一致的记录数量: ").append(pathMismatchResults.size()).append("\n");
|
||||||
for (String result : pathMismatchResults) {
|
pathMismatchResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||||
log.info("\t{}", result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compareMode == CompareMode.FILE_LEVEL) {
|
if (compareMode == CompareMode.FILE_LEVEL) {
|
||||||
if (pageCountMismatchResults.isEmpty()) {
|
if (pageCountMismatchResults.isEmpty()) {
|
||||||
log.info("没有页数错误");
|
sb.append("没有页数错误\n");
|
||||||
} else {
|
} else {
|
||||||
log.info("文件名和路径相同但页数不一致的记录数量: {}", pageCountMismatchResults.size());
|
sb.append("文件名和路径相同但页数不一致的记录数量: ")
|
||||||
for (String result : pageCountMismatchResults) {
|
.append(pageCountMismatchResults.size()).append("\n");
|
||||||
log.info("\t{}", result);
|
pageCountMismatchResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forwardResults.isEmpty()) {
|
if (forwardResults.isEmpty()) {
|
||||||
log.info("没有物理存在而逻辑不存在的文件");
|
sb.append("没有物理存在而逻辑不存在的文件\n");
|
||||||
} else {
|
} else {
|
||||||
log.info("物理文件在逻辑文件中未找到的记录数量: {}", forwardResults.size());
|
sb.append("物理文件在逻辑文件中未找到的记录数量: ").append(forwardResults.size()).append("\n");
|
||||||
for (String result : forwardResults) {
|
forwardResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||||
log.info("\t{}", result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backwardResults.isEmpty()) {
|
if (backwardResults.isEmpty()) {
|
||||||
log.info("没有逻辑存在而物理不存在的文件");
|
sb.append("没有逻辑存在而物理不存在的文件\n");
|
||||||
} else {
|
} else {
|
||||||
log.info("逻辑文件在物理文件中未找到的记录数量: {}", backwardResults.size());
|
sb.append("逻辑文件在物理文件中未找到的记录数量: ").append(backwardResults.size()).append("\n");
|
||||||
for (String result : backwardResults) {
|
backwardResults.forEach(result -> sb.append("\t").append(result).append("\n"));
|
||||||
log.info("\t{}", result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("=== 比较完成 ===");
|
sb.append("=== 比较完成 ===");
|
||||||
|
|
||||||
|
log.info(LoggerHelper.RELEASE_MARKER, sb.toString()); // 一次性输出, 减少 I/O
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为向后兼容保留原来的日志方法
|
// 为向后兼容保留原来的日志方法
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import java.io.File;
|
||||||
|
|
||||||
public interface AddressFileGenerator {
|
public interface AddressFileGenerator {
|
||||||
/**
|
/**
|
||||||
* 页面级
|
* 页面级 JPG那种
|
||||||
*/
|
*/
|
||||||
int PAGE_TYPE = 1;
|
int PAGE_TYPE = 1;
|
||||||
/**
|
/**
|
||||||
* 文件级
|
* 文件级 PDF那种
|
||||||
*/
|
*/
|
||||||
int FILE_TYPE = 2;
|
int FILE_TYPE = 2;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
package top.r3944realms.docchecktoolrefactored.core;
|
package top.r3944realms.docchecktoolrefactored.core;
|
||||||
|
|
||||||
|
import com.sun.scenario.Settings;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
||||||
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
|
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
|
||||||
import top.r3944realms.docchecktoolrefactored.model.FileMetadata;
|
import top.r3944realms.docchecktoolrefactored.model.FileMetadata;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -17,64 +21,103 @@ import java.util.stream.Collectors;
|
||||||
/**
|
/**
|
||||||
* 重复文件查找核心类
|
* 重复文件查找核心类
|
||||||
*/
|
*/
|
||||||
|
//TODO;代替DuplicateDocumentDetectionTask
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DuplicateFinder {
|
public class DuplicateFinder {
|
||||||
private final FileScanner fileScanner;
|
private final FileScanner fileScanner;
|
||||||
private final FileHashCalculator hashCalculator;
|
private final FileHashCalculator hashCalculator;
|
||||||
private final boolean enableProgress;
|
private final boolean enableProgress;
|
||||||
|
private final ExecutorService executorService;
|
||||||
|
// 进度回调接口
|
||||||
|
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 {
|
||||||
|
GROUP_BY_SIZE, // 按大小分组阶段
|
||||||
|
CALCULATE_HASH // 计算哈希阶段
|
||||||
|
}
|
||||||
|
@Setter
|
||||||
|
private ProgressCallback progressCallback;
|
||||||
|
private static final int PROGRESS_REPORT_INTERVAL = 100;
|
||||||
|
private static final int BATCH_SIZE = 100;
|
||||||
|
private final List<Exception> errors = new CopyOnWriteArrayList<>();
|
||||||
|
@Getter
|
||||||
|
private long timeout = -1;
|
||||||
public DuplicateFinder(FileScanner fileScanner, FileHashCalculator hashCalculator, boolean enableProgress) {
|
public DuplicateFinder(FileScanner fileScanner, FileHashCalculator hashCalculator, boolean enableProgress) {
|
||||||
this.fileScanner = Objects.requireNonNull(fileScanner);
|
this.fileScanner = Objects.requireNonNull(fileScanner);
|
||||||
this.hashCalculator = Objects.requireNonNull(hashCalculator);
|
this.hashCalculator = Objects.requireNonNull(hashCalculator);
|
||||||
this.enableProgress = enableProgress;
|
this.enableProgress = enableProgress;
|
||||||
|
// 根据CPU核心数设置线程池大小
|
||||||
|
int poolSize = Runtime.getRuntime().availableProcessors();
|
||||||
|
this.executorService = Executors.newFixedThreadPool(poolSize);
|
||||||
}
|
}
|
||||||
public DuplicateFinder(FileScanner fileScanner, FileHashCalculator hashCalculator) {
|
public DuplicateFinder(FileScanner fileScanner, FileHashCalculator hashCalculator) {
|
||||||
this(fileScanner, hashCalculator, false);
|
this(fileScanner, hashCalculator, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DuplicateFinder applySetting(Setting setting) {
|
||||||
|
this.timeout = setting.getSingleTimeout();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找重复文件
|
* 查找重复文件
|
||||||
* @param rootDir 要扫描的根目录
|
* @param rootDir 要扫描的根目录
|
||||||
* @return 按哈希值分组的重复文件列表
|
* @return 按哈希值分组的重复文件列表
|
||||||
*/
|
*/
|
||||||
public List<DuplicateGroup> findDuplicates(Path rootDir) throws IOException {
|
public List<DuplicateGroup> findDuplicates(Path rootDir) throws IOException {
|
||||||
|
// 清理错误列表
|
||||||
|
errors.clear();
|
||||||
|
// -----------------------------
|
||||||
// 第一阶段:按文件大小分组
|
// 第一阶段:按文件大小分组
|
||||||
|
// -----------------------------
|
||||||
|
if (progressCallback != null) {
|
||||||
|
progressCallback.onPhaseStarted(Phase.GROUP_BY_SIZE);
|
||||||
|
}
|
||||||
Map<Long, List<FileMetadata>> sizeGroups = groupFilesBySize(rootDir);
|
Map<Long, List<FileMetadata>> sizeGroups = groupFilesBySize(rootDir);
|
||||||
|
|
||||||
// 计算需要处理的总文件数(大小分组中可能有重复的文件)
|
if (progressCallback != null) {
|
||||||
int totalFilesToProcess = sizeGroups.values().stream()
|
progressCallback.onPhaseCompleted(Phase.GROUP_BY_SIZE);
|
||||||
.filter(group -> group.size() > 1)
|
|
||||||
.mapToInt(List::size)
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
if (totalFilesToProcess == 0) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第二阶段:对可能重复的文件计算哈希
|
// -----------------------------
|
||||||
|
// 第二阶段:按文件组计算哈希
|
||||||
|
// -----------------------------
|
||||||
|
if (progressCallback != null) progressCallback.onPhaseStarted(Phase.CALCULATE_HASH);
|
||||||
|
|
||||||
Map<String, List<FileMetadata>> hashGroups = new ConcurrentHashMap<>();
|
Map<String, List<FileMetadata>> hashGroups = new ConcurrentHashMap<>();
|
||||||
AtomicInteger processedFiles = new AtomicInteger(0);
|
AtomicInteger processedFiles = new AtomicInteger(0);
|
||||||
|
|
||||||
sizeGroups.values().parallelStream()
|
// 获取候选文件组(每组内至少2个文件)
|
||||||
.filter(group -> group.size() > 1) // 只处理可能重复的文件
|
List<List<FileMetadata>> candidateGroups = sizeGroups.values().stream()
|
||||||
.forEach(group -> group.parallelStream().forEach(file -> {
|
.filter(group -> group.size() > 1)
|
||||||
try {
|
.toList();
|
||||||
String hash = hashCalculator.calculateHash(file.getPath());
|
|
||||||
file.setHash(hash);
|
int totalFilesToProcess = candidateGroups.stream().mapToInt(List::size).sum();
|
||||||
hashGroups.computeIfAbsent(hash, k -> new ArrayList<>()).add(file);
|
if (totalFilesToProcess == 0) return Collections.emptyList();
|
||||||
// 更新进度
|
|
||||||
int current = processedFiles.incrementAndGet();
|
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||||
if (enableProgress) {
|
|
||||||
printProgress("Calculating hashes", current, totalFilesToProcess);
|
// 分批提交线程池,每组作为一个批次
|
||||||
}
|
for (List<FileMetadata> group : candidateGroups) {
|
||||||
} catch (IOException e) {
|
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||||
// 记录错误但继续处理其他文件
|
for (FileMetadata file : group) {
|
||||||
log.error("Failed to calculate file's hash: {}, {}", file.getPath(), e.getMessage());
|
processFile(file, hashGroups, processedFiles, totalFilesToProcess);
|
||||||
}
|
}
|
||||||
}));
|
}, executorService);
|
||||||
if (enableProgress) {
|
futures.add(future);
|
||||||
System.out.println(); // 完成进度条后换行
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||||
|
|
||||||
|
if (progressCallback != null) progressCallback.onPhaseCompleted(Phase.CALCULATE_HASH);
|
||||||
|
if (enableProgress) System.out.println();
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
// 第三阶段:构建结果
|
// 第三阶段:构建结果
|
||||||
|
// -----------------------------
|
||||||
return hashGroups.values().stream()
|
return hashGroups.values().stream()
|
||||||
.filter(group -> group.size() > 1)
|
.filter(group -> group.size() > 1)
|
||||||
.map(group -> new DuplicateGroup(
|
.map(group -> new DuplicateGroup(
|
||||||
|
|
@ -91,13 +134,15 @@ public class DuplicateFinder {
|
||||||
*/
|
*/
|
||||||
private Map<Long, List<FileMetadata>> groupFilesBySize(Path rootDir) throws IOException {
|
private Map<Long, List<FileMetadata>> groupFilesBySize(Path rootDir) throws IOException {
|
||||||
Map<Long, List<FileMetadata>> sizeGroups = new ConcurrentHashMap<>();
|
Map<Long, List<FileMetadata>> sizeGroups = new ConcurrentHashMap<>();
|
||||||
|
boolean flag = timeout != -1 && timeout > 0;
|
||||||
FileScanner.ProgressAwareListener listener = new FileScanner.ProgressAwareListener() {
|
FileScanner.ProgressAwareListener listener = new FileScanner.ProgressAwareListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgressUpdate(int current, int total) {
|
public void onProgressUpdate(int current, int total) {
|
||||||
if (enableProgress) {
|
if (enableProgress) {
|
||||||
printProgress("Scanning files", current, total);
|
printProgress("Scanning files", current, total);
|
||||||
} else {
|
}
|
||||||
log.info("Scanning progress: {} / {} ", current, total);
|
if (progressCallback != null) {
|
||||||
|
progressCallback.onPhaseProgress(Phase.GROUP_BY_SIZE, current, total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,19 +154,20 @@ public class DuplicateFinder {
|
||||||
meta.setSize(Files.size(file));
|
meta.setSize(Files.size(file));
|
||||||
sizeGroups.computeIfAbsent(meta.getSize(), k -> new ArrayList<>()).add(meta);
|
sizeGroups.computeIfAbsent(meta.getSize(), k -> new ArrayList<>()).add(meta);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to get file's size: {}", file);
|
log.error(LoggerHelper.TRACE_MARKER, "Failed to get file's size: {}", file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onScanComplete() {}
|
@Override public void onScanComplete() {}
|
||||||
@Override public void onError(Path file, Exception e) {
|
@Override public void onError(Path file, Exception e) {
|
||||||
log.error("Error on scanning file: {}, {}", file, e.getMessage());
|
log.error(LoggerHelper.TRACE_MARKER, "Error on scanning file: {}, {}", file, e.getMessage());
|
||||||
|
errors.add(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if(enableProgress)
|
if(enableProgress)
|
||||||
fileScanner.scanWithProgress(rootDir, listener);
|
if (flag) fileScanner.scanWithProgress(rootDir, listener, timeout); else fileScanner.scanWithProgress(rootDir, listener);
|
||||||
else
|
else
|
||||||
fileScanner.scan(rootDir, listener);
|
if (flag) fileScanner.scan(rootDir, listener, timeout); else fileScanner.scan(rootDir, listener);
|
||||||
return sizeGroups;
|
return sizeGroups;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -144,4 +190,35 @@ public class DuplicateFinder {
|
||||||
|
|
||||||
System.out.print(progressBar);
|
System.out.print(progressBar);
|
||||||
}
|
}
|
||||||
|
private void processFile(FileMetadata file,
|
||||||
|
Map<String, List<FileMetadata>> hashGroups,
|
||||||
|
AtomicInteger processedFiles,
|
||||||
|
int totalFilesToProcess) {
|
||||||
|
try {
|
||||||
|
String hash = file.getSize() > 10_000_000 ?
|
||||||
|
hashCalculator.calculateHashStreaming(file.getPath()) :
|
||||||
|
hashCalculator.calculateHash(file.getPath());
|
||||||
|
|
||||||
|
file.setHash(hash);
|
||||||
|
synchronized (hashGroups) {
|
||||||
|
hashGroups.computeIfAbsent(hash, k -> new ArrayList<>()).add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
int current = processedFiles.incrementAndGet();
|
||||||
|
if (enableProgress && (current % PROGRESS_REPORT_INTERVAL == 0 || current == totalFilesToProcess)) {
|
||||||
|
printProgress("Calculating hashes", current, totalFilesToProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressCallback != null) {
|
||||||
|
progressCallback.onPhaseProgress(Phase.CALCULATE_HASH, current, totalFilesToProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(LoggerHelper.TRACE_MARKER, "Failed to calculate file's hash: {}, {}", file.getPath(), e.getMessage());
|
||||||
|
errors.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void shutdown() {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,13 @@ public interface FileHashCalculator {
|
||||||
*/
|
*/
|
||||||
String calculateHash(Path file) throws IOException;
|
String calculateHash(Path file) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式计算文件哈希值(适用于大文件)
|
||||||
|
* @param file 要计算的文件路径
|
||||||
|
* @return 文件的哈希值字符串
|
||||||
|
*/
|
||||||
|
String calculateHashStreaming(Path file) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算文件部分hash值方法
|
* 计算文件部分hash值方法
|
||||||
* @param file 要计算的文件路径
|
* @param file 要计算的文件路径
|
||||||
|
|
@ -36,7 +43,15 @@ public interface FileHashCalculator {
|
||||||
* @return 块大小
|
* @return 块大小
|
||||||
*/
|
*/
|
||||||
default int getPartialSize() {
|
default int getPartialSize() {
|
||||||
return 4096;// 4 * 1024
|
return 4096;// 4KB
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流式处理的缓冲区大小
|
||||||
|
* @return 缓冲区大小
|
||||||
|
*/
|
||||||
|
default int getStreamingBufferSize() {
|
||||||
|
return 8192; // 8KB
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package top.r3944realms.docchecktoolrefactored.core;
|
package top.r3944realms.docchecktoolrefactored.core;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
|
|
@ -13,7 +15,7 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
@Slf4j
|
||||||
public class HashFileGenerator {
|
public class HashFileGenerator {
|
||||||
|
|
||||||
public interface ProgressListener {
|
public interface ProgressListener {
|
||||||
|
|
@ -47,7 +49,7 @@ public class HashFileGenerator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Path path, Exception e) {
|
public void onError(Path path, Exception e) {
|
||||||
System.err.println("Error scanning path: " + path + " - " + e.getMessage());
|
log.error(LoggerHelper.TRACE_MARKER, "Error scanning path: {} - {}", path, e.getMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -74,7 +76,7 @@ public class HashFileGenerator {
|
||||||
listener.onProgressUpdate(processed, totalFiles);
|
listener.onProgressUpdate(processed, totalFiles);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println("无法计算该文件哈希值: " + file + " - " + e.getMessage());
|
log.error(LoggerHelper.DEBUG_MARKER, "无法计算该文件哈希值: {} - {}", file, e.getMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
package top.r3944realms.docchecktoolrefactored.core;
|
package top.r3944realms.docchecktoolrefactored.core;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.DigestInputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
|
||||||
|
import static org.apache.commons.codec.digest.MessageDigestAlgorithms.MD5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MD5哈希计算实现
|
* MD5哈希计算实现
|
||||||
*/
|
*/
|
||||||
public class MD5HashCalculator implements FileHashCalculator {
|
public class MD5HashCalculator implements FileHashCalculator {
|
||||||
private static final int BUFFER_SIZE = 8192;
|
private static final int BUFFER_SIZE = 8192;
|
||||||
|
private static final HexFormat HEX_FORMAT = HexFormat.of();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String calculateHash(Path file) throws IOException {
|
public String calculateHash(Path file) throws IOException {
|
||||||
|
|
@ -29,6 +35,25 @@ public class MD5HashCalculator implements FileHashCalculator {
|
||||||
throw new RuntimeException("MD5算法不可用", e);
|
throw new RuntimeException("MD5算法不可用", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public String calculateHashStreaming(Path file) throws IOException {
|
||||||
|
try (InputStream is = Files.newInputStream(file);
|
||||||
|
DigestInputStream dis = new DigestInputStream(is, MessageDigest.getInstance(MD5))) {
|
||||||
|
|
||||||
|
byte[] buffer = new byte[getStreamingBufferSize()];
|
||||||
|
// 读取整个文件以更新摘要
|
||||||
|
while (dis.read(buffer) != -1) {
|
||||||
|
// 只需读取即可,DigestInputStream会自动更新摘要
|
||||||
|
//noinspection UnnecessaryContinue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] digest = dis.getMessageDigest().digest();
|
||||||
|
return HEX_FORMAT.formatHex(digest);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("MD5 algorithm not available", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <h1>读取文件头部:</h1>
|
* <h1>读取文件头部:</h1>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package top.r3944realms.docchecktoolrefactored.core;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
@ -143,7 +144,7 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
||||||
document.close();
|
document.close();
|
||||||
return pageCount;
|
return pageCount;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("无法获取PDF文件页数: {}", pdfFile.getAbsolutePath(), e);
|
log.warn(LoggerHelper.RELEASE_MARKER, "无法获取PDF文件页数: {}", pdfFile.getAbsolutePath(), e);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +216,7 @@ public class PhysicalAddressFileGenerator implements AddressFileGenerator {
|
||||||
for (int i = 0; i < pathParts.length; i++) {
|
for (int i = 0; i < pathParts.length; i++) {
|
||||||
if (!foundPrefix) {
|
if (!foundPrefix) {
|
||||||
// 检查当前部分是否包含prefix
|
// 检查当前部分是否包含prefix
|
||||||
if (pathParts[i].contains(prefix)) {
|
if (pathParts[i].equals(prefix)) {
|
||||||
foundPrefix = true;
|
foundPrefix = true;
|
||||||
resultPath.append(pathParts[i]);
|
resultPath.append(pathParts[i]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package top.r3944realms.docchecktoolrefactored.core;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ScanningException extends RuntimeException {
|
||||||
|
public final List<Exception> exceptions;
|
||||||
|
public ScanningException(List<Exception> exceptions) {
|
||||||
|
this.exceptions = exceptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package top.r3944realms.docchecktoolrefactored.core;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@Slf4j
|
||||||
|
public class Setting {
|
||||||
|
private long singleTimeout = 30;
|
||||||
|
private long totalTimeout = 60 * 5;
|
||||||
|
private boolean enableStep = false;
|
||||||
|
}
|
||||||
|
|
@ -5,12 +5,14 @@ import com.linuxense.javadbf.DBFRow;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
@ -20,8 +22,11 @@ import java.util.Optional;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DbfFileReader implements CatalogFileReader {
|
public class DbfFileReader implements CatalogFileReader {
|
||||||
// 常量定义(避免硬编码)
|
// 常量定义(避免硬编码)
|
||||||
private static final String FIELD_ARCHIVE_CODE = "档号";
|
private static final List<String> ARCHIVE_CODE_TAG_CANDIDATES =
|
||||||
private static final String FIELD_PAGE = "页数";
|
Arrays.asList("档号", "dangan", "fileNo", "DH");
|
||||||
|
|
||||||
|
private static final List<String> PAGE_COUNT_TAG_CANDIDATES =
|
||||||
|
Arrays.asList("页数", "pages", "pageCount", "YS");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<LogicalAddressFileGenerator.Record> readCatalogFile(String filePath) throws Exception {
|
public List<LogicalAddressFileGenerator.Record> readCatalogFile(String filePath) throws Exception {
|
||||||
|
|
@ -33,36 +38,35 @@ public class DbfFileReader implements CatalogFileReader {
|
||||||
DBFReader reader = new DBFReader(fis)
|
DBFReader reader = new DBFReader(fis)
|
||||||
) {
|
) {
|
||||||
int fieldCount = reader.getFieldCount();
|
int fieldCount = reader.getFieldCount();
|
||||||
log.debug("开始读取DBF文件: {}, DBF文件字段数: {}",filePath, fieldCount);
|
log.debug(LoggerHelper.DEBUG_MARKER, "开始读取DBF文件: {}, DBF文件字段数: {}",filePath, fieldCount);
|
||||||
|
|
||||||
// 查找"档号"和"页数"字段的索引
|
// 查找"档号"和"页数"字段的索引
|
||||||
int archiveCodeIndex = -1;
|
int archiveCodeIndex = -1;
|
||||||
int pageIndex = -1;
|
int pageIndex = -1;
|
||||||
for (int i = 0; i < fieldCount; i++) {
|
for (int i = 0; i < fieldCount; i++) {
|
||||||
if (archiveCodeIndex != -1 && pageIndex != -1) {
|
if (archiveCodeIndex != -1 && pageIndex != -1) {
|
||||||
log.debug("已找到所需字段,跳出循环,档号: {}, 页数: {}", archiveCodeIndex, pageIndex);
|
log.debug(LoggerHelper.DEBUG_MARKER, "已找到所需字段,跳出循环,档号: {}, 页数: {}", archiveCodeIndex, pageIndex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
String fieldName = reader.getField(i).getName();
|
String fieldName = reader.getField(i).getName();
|
||||||
log.debug("发现字段: {}", fieldName);
|
log.debug(LoggerHelper.DEBUG_MARKER, "发现字段: {}", fieldName);
|
||||||
if (FIELD_ARCHIVE_CODE.equals(fieldName)) {
|
if (ARCHIVE_CODE_TAG_CANDIDATES.contains(fieldName)) {
|
||||||
archiveCodeIndex = i;
|
archiveCodeIndex = i;
|
||||||
log.debug("找到‘档号’字段,索引: {}", archiveCodeIndex);
|
log.debug(LoggerHelper.DEBUG_MARKER, "匹配到档号字段: {}, 索引: {}", fieldName, archiveCodeIndex);
|
||||||
} else if (FIELD_PAGE.equals(fieldName)) {
|
} else if (PAGE_COUNT_TAG_CANDIDATES.contains(fieldName)) {
|
||||||
pageIndex = i;
|
pageIndex = i;
|
||||||
log.debug("找到‘页数’字段,索引: {}", pageIndex);
|
log.debug(LoggerHelper.DEBUG_MARKER, "匹配到页数字段: {}, 索引: {}", fieldName, pageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (archiveCodeIndex == -1 || pageIndex == -1) {
|
if (archiveCodeIndex == -1 || pageIndex == -1) {
|
||||||
log.error("未找到必要字段,档号: {}, 页数: {}",
|
log.error(LoggerHelper.RELEASE_MARKER, "未找到必要字段,档号: {}, 页数: {}",
|
||||||
archiveCodeIndex == -1 ? "未找到" : archiveCodeIndex,
|
archiveCodeIndex == -1 ? "未找到" : archiveCodeIndex,
|
||||||
pageIndex == -1 ? "未找到" : pageIndex
|
pageIndex == -1 ? "未找到" : pageIndex
|
||||||
);
|
);
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
String.format("DBF文件缺少必要字段: %s=%s, %s=%s",
|
String.format("DBF文件缺少必要字段: 档号=%s, 页数=%s",
|
||||||
FIELD_ARCHIVE_CODE, archiveCodeIndex == -1,
|
archiveCodeIndex == -1, pageIndex == -1
|
||||||
FIELD_PAGE, pageIndex == -1
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +89,7 @@ public class DbfFileReader implements CatalogFileReader {
|
||||||
return Integer.parseInt(i.toString().trim());
|
return Integer.parseInt(i.toString().trim());
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
log.warn("无法将页数值转换为整数: {}", i);
|
log.warn(LoggerHelper.DEBUG_MARKER, "无法将页数值转换为整数: {}", i);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}).orElse(0);
|
}).orElse(0);
|
||||||
|
|
@ -94,17 +98,17 @@ public class DbfFileReader implements CatalogFileReader {
|
||||||
if (!archiveCode.isEmpty() && page > 0) {
|
if (!archiveCode.isEmpty() && page > 0) {
|
||||||
records.add(new LogicalAddressFileGenerator.Record(archiveCode, page));
|
records.add(new LogicalAddressFileGenerator.Record(archiveCode, page));
|
||||||
validRecords++;
|
validRecords++;
|
||||||
log.debug("读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
log.debug(LoggerHelper.DEBUG_MARKER, "读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||||
} else {
|
} else {
|
||||||
skippedRecords++;
|
skippedRecords++;
|
||||||
if (!archiveCode.isEmpty() || page > 0) {
|
if (!archiveCode.isEmpty() || page > 0) {
|
||||||
log.debug("跳过无效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
log.debug(LoggerHelper.DEBUG_MARKER, "跳过无效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("DBF文件读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
log.info(LoggerHelper.RELEASE_MARKER, "DBF文件读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("读取DBF文件失败: {}", filePath, e);
|
log.error(LoggerHelper.RELEASE_MARKER, "读取DBF文件失败: {}", filePath, e);
|
||||||
throw new UncheckedIOException("DBF文件读取异常", e);
|
throw new UncheckedIOException("DBF文件读取异常", e);
|
||||||
}
|
}
|
||||||
return records;
|
return records;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
@ -26,33 +27,33 @@ public class ExcelFileReader implements CatalogFileReader {
|
||||||
}
|
}
|
||||||
private List<LogicalAddressFileGenerator.Record> readExcelFile(File file, boolean isXlsx) throws Exception {
|
private List<LogicalAddressFileGenerator.Record> readExcelFile(File file, boolean isXlsx) throws Exception {
|
||||||
List<LogicalAddressFileGenerator.Record> records = new ArrayList<>();
|
List<LogicalAddressFileGenerator.Record> records = new ArrayList<>();
|
||||||
log.debug("开始解析Excel文件,格式: {}", isXlsx ? "xlsx" : "xls");
|
log.debug(LoggerHelper.DEBUG_MARKER, "开始解析Excel文件,格式: {}", isXlsx ? "xlsx" : "xls");
|
||||||
try (FileInputStream fis = new FileInputStream(file);
|
try (FileInputStream fis = new FileInputStream(file);
|
||||||
Workbook workbook = isXlsx ? new XSSFWorkbook(fis) : new HSSFWorkbook(fis)) {
|
Workbook workbook = isXlsx ? new XSSFWorkbook(fis) : new HSSFWorkbook(fis)) {
|
||||||
// 获取第一个工作表
|
// 获取第一个工作表
|
||||||
Sheet sheet = workbook.getSheetAt(0);
|
Sheet sheet = workbook.getSheetAt(0);
|
||||||
log.debug("读取工作表: {}", sheet.getSheetName());
|
log.debug(LoggerHelper.DEBUG_MARKER, "读取工作表: {}", sheet.getSheetName());
|
||||||
|
|
||||||
// 获取标题行
|
// 获取标题行
|
||||||
Row headerRow = sheet.getRow(0);
|
Row headerRow = sheet.getRow(0);
|
||||||
if (headerRow == null) {
|
if (headerRow == null) {
|
||||||
log.error("Excel文件缺少标题行");
|
log.error(LoggerHelper.RELEASE_MARKER, "Excel文件缺少标题行");
|
||||||
throw new IllegalArgumentException("Excel文件缺少标题行");
|
throw new IllegalArgumentException("Excel文件缺少标题行");
|
||||||
}
|
}
|
||||||
// 查找"档号"和"页数"列的索引
|
// 查找"档号"和"页数"列的索引
|
||||||
int archiveCodeIndex = -1;
|
int archiveCodeIndex = -1;
|
||||||
int pageIndex = -1;
|
int pageIndex = -1;
|
||||||
log.debug("开始查找'档号'和'页数'列的索引");
|
log.debug(LoggerHelper.DEBUG_MARKER, "开始查找'档号'和'页数'列的索引");
|
||||||
boolean foundExactMatch = false;
|
boolean foundExactMatch = false;
|
||||||
for (Cell cell : headerRow) {
|
for (Cell cell : headerRow) {
|
||||||
String cellValue = getCellValueAsString(cell).trim();
|
String cellValue = getCellValueAsString(cell).trim();
|
||||||
if (FIELD_ARCHIVE_CODE.equals(cellValue)) {
|
if (FIELD_ARCHIVE_CODE.equals(cellValue)) {
|
||||||
archiveCodeIndex = cell.getColumnIndex();
|
archiveCodeIndex = cell.getColumnIndex();
|
||||||
log.debug("找到'档号'列,索引: {}", archiveCodeIndex);
|
log.debug(LoggerHelper.DEBUG_MARKER, "找到'档号'列,索引: {}", archiveCodeIndex);
|
||||||
} else if (FIELD_PAGE.equals(cellValue)) {
|
} else if (FIELD_PAGE.equals(cellValue)) {
|
||||||
pageIndex = cell.getColumnIndex();
|
pageIndex = cell.getColumnIndex();
|
||||||
foundExactMatch = true;
|
foundExactMatch = true;
|
||||||
log.debug("找到精确匹配'页数'列,索引: {}", pageIndex);
|
log.debug(LoggerHelper.DEBUG_MARKER, "找到精确匹配'页数'列,索引: {}", pageIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果没有精确匹配,进行模糊查找
|
// 如果没有精确匹配,进行模糊查找
|
||||||
|
|
@ -61,13 +62,13 @@ public class ExcelFileReader implements CatalogFileReader {
|
||||||
String cellValue = getCellValueAsString(cell).trim();
|
String cellValue = getCellValueAsString(cell).trim();
|
||||||
if (cellValue.contains(FIELD_PAGE)) {
|
if (cellValue.contains(FIELD_PAGE)) {
|
||||||
pageIndex = cell.getColumnIndex();
|
pageIndex = cell.getColumnIndex();
|
||||||
log.debug("找到模糊匹配'页数'列,索引: {}", pageIndex);
|
log.debug(LoggerHelper.DEBUG_MARKER, "找到模糊匹配'页数'列,索引: {}", pageIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 检查是否找到必需的列
|
// 检查是否找到必需的列
|
||||||
if (archiveCodeIndex == -1 || pageIndex == -1) {
|
if (archiveCodeIndex == -1 || pageIndex == -1) {
|
||||||
log.error("未找到必要字段,档号: {}, 页数: {}",
|
log.error(LoggerHelper.RELEASE_MARKER, "未找到必要字段,档号: {}, 页数: {}",
|
||||||
archiveCodeIndex == -1 ? "未找到" : archiveCodeIndex,
|
archiveCodeIndex == -1 ? "未找到" : archiveCodeIndex,
|
||||||
pageIndex == -1 ? "未找到" : pageIndex
|
pageIndex == -1 ? "未找到" : pageIndex
|
||||||
);
|
);
|
||||||
|
|
@ -83,7 +84,7 @@ public class ExcelFileReader implements CatalogFileReader {
|
||||||
int validRecords = 0;
|
int validRecords = 0;
|
||||||
int skippedRecords = 0;
|
int skippedRecords = 0;
|
||||||
|
|
||||||
for (int i = 1; i <= totalRows; i++) {
|
for (int i = 1; i < totalRows; i++) {
|
||||||
Row row = sheet.getRow(i);
|
Row row = sheet.getRow(i);
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
skippedRecords++;
|
skippedRecords++;
|
||||||
|
|
@ -115,12 +116,12 @@ public class ExcelFileReader implements CatalogFileReader {
|
||||||
// 只有数据有效时才添加记录
|
// 只有数据有效时才添加记录
|
||||||
records.add(new LogicalAddressFileGenerator.Record(archiveCode, page));
|
records.add(new LogicalAddressFileGenerator.Record(archiveCode, page));
|
||||||
validRecords++;
|
validRecords++;
|
||||||
log.debug("读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
log.debug(LoggerHelper.DEBUG_MARKER, "读取有效记录 - 档号: {}, 页数: {}", archiveCode, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("数据读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
log.info(LoggerHelper.RELEASE_MARKER, "数据读取完成,有效记录: {} 条,跳过记录: {} 条", validRecords, skippedRecords);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("读取Excel文件时发生错误: {}", e.getMessage(), e);
|
log.error(LoggerHelper.RELEASE_MARKER, "读取Excel文件时发生错误: {}", e.getMessage(), e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return records;
|
return records;
|
||||||
|
|
@ -188,7 +189,7 @@ public class ExcelFileReader implements CatalogFileReader {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
log.warn("无法将单元格值转换为整数: {}", cell);
|
log.warn(LoggerHelper.DEBUG_MARKER, "无法将单元格值转换为整数: {}", cell);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||||
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
import top.r3944realms.docchecktoolrefactored.util.FileUtil;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
@ -23,10 +24,10 @@ public class XmlFileReader implements CatalogFileReader {
|
||||||
Arrays.asList("row", "record", "data", "item", "档案");
|
Arrays.asList("row", "record", "data", "item", "档案");
|
||||||
|
|
||||||
private static final List<String> ARCHIVE_CODE_TAG_CANDIDATES =
|
private static final List<String> ARCHIVE_CODE_TAG_CANDIDATES =
|
||||||
Arrays.asList("档号", "dangan", "fileNo");
|
Arrays.asList("档号", "dangan", "fileNo", "DH");
|
||||||
|
|
||||||
private static final List<String> PAGE_COUNT_TAG_CANDIDATES =
|
private static final List<String> PAGE_COUNT_TAG_CANDIDATES =
|
||||||
Arrays.asList("页数", "pages", "pageCount");
|
Arrays.asList("页数", "pages", "pageCount", "YS");
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -42,11 +43,11 @@ public class XmlFileReader implements CatalogFileReader {
|
||||||
Document doc = builder.parse(file);
|
Document doc = builder.parse(file);
|
||||||
doc.getDocumentElement().normalize();
|
doc.getDocumentElement().normalize();
|
||||||
|
|
||||||
log.debug("开始解析XML文件: {}, 根元素: {}",
|
log.debug(LoggerHelper.DEBUG_MARKER, "开始解析XML文件: {}, 根元素: {}",
|
||||||
file.getName(), doc.getDocumentElement().getNodeName());
|
file.getName(), doc.getDocumentElement().getNodeName());
|
||||||
// 查找记录元素
|
// 查找记录元素
|
||||||
NodeList recordNodes = findAllRecordNodes(doc);
|
NodeList recordNodes = findAllRecordNodes(doc);
|
||||||
log.debug("找到 {} 个潜在记录节点", recordNodes.getLength());
|
log.debug(LoggerHelper.DEBUG_MARKER, "找到 {} 个潜在记录节点", recordNodes.getLength());
|
||||||
|
|
||||||
// 解析每个记录元素
|
// 解析每个记录元素
|
||||||
int validCount = 0;
|
int validCount = 0;
|
||||||
|
|
@ -61,16 +62,16 @@ public class XmlFileReader implements CatalogFileReader {
|
||||||
if (record != null) {
|
if (record != null) {
|
||||||
records.add(record);
|
records.add(record);
|
||||||
validCount++;
|
validCount++;
|
||||||
log.debug("解析到有效记录: {}", record);
|
log.debug(LoggerHelper.DEBUG_MARKER, "解析到有效记录: {}", record);
|
||||||
} else {
|
} else {
|
||||||
invalidCount++;
|
invalidCount++;
|
||||||
log.debug("跳过无效记录节点");
|
log.debug(LoggerHelper.DEBUG_MARKER, "跳过无效记录节点");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("XML解析完成 - 有效记录: {}, 无效记录: {}", validCount, invalidCount);
|
log.info(LoggerHelper.RELEASE_MARKER, "XML解析完成 - 有效记录: {}, 无效记录: {}", validCount, invalidCount);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("解析XML文件失败: {}", filePath, e);
|
log.error(LoggerHelper.RELEASE_MARKER, "解析XML文件失败: {}", filePath, e);
|
||||||
throw new Exception("解析XML文件失败: " + filePath, e);
|
throw new Exception("解析XML文件失败: " + filePath, e);
|
||||||
}
|
}
|
||||||
return records;
|
return records;
|
||||||
|
|
@ -92,11 +93,11 @@ public class XmlFileReader implements CatalogFileReader {
|
||||||
for (String tagName : RECORD_TAG_CANDIDATES) {
|
for (String tagName : RECORD_TAG_CANDIDATES) {
|
||||||
NodeList nodes = doc.getElementsByTagName(tagName);
|
NodeList nodes = doc.getElementsByTagName(tagName);
|
||||||
if (nodes.getLength() > 0) {
|
if (nodes.getLength() > 0) {
|
||||||
log.debug("使用标签名 '{}' 找到 {} 个记录节点", tagName, nodes.getLength());
|
log.debug(LoggerHelper.DEBUG_MARKER, "使用标签名 '{}' 找到 {} 个记录节点", tagName, nodes.getLength());
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.warn("未找到任何记录节点,尝试的标签名: {}", RECORD_TAG_CANDIDATES);
|
log.warn(LoggerHelper.DEBUG_MARKER, "未找到任何记录节点,尝试的标签名: {}", RECORD_TAG_CANDIDATES);
|
||||||
return new EmptyNodeList(); // 返回空节点列表而不是null
|
return new EmptyNodeList(); // 返回空节点列表而不是null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +106,7 @@ public class XmlFileReader implements CatalogFileReader {
|
||||||
String pageStr = findFirstNonEmptyTextContent(recordElement, PAGE_COUNT_TAG_CANDIDATES);
|
String pageStr = findFirstNonEmptyTextContent(recordElement, PAGE_COUNT_TAG_CANDIDATES);
|
||||||
|
|
||||||
if (archiveCode == null || archiveCode.isEmpty()) {
|
if (archiveCode == null || archiveCode.isEmpty()) {
|
||||||
log.debug("记录缺少档号字段");
|
log.debug(LoggerHelper.RELEASE_MARKER, "记录缺少档号字段");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,11 +115,11 @@ public class XmlFileReader implements CatalogFileReader {
|
||||||
if (page > 0) {
|
if (page > 0) {
|
||||||
return new LogicalAddressFileGenerator.Record(archiveCode, page);
|
return new LogicalAddressFileGenerator.Record(archiveCode, page);
|
||||||
} else {
|
} else {
|
||||||
log.debug("无效的页数值: {}", pageStr);
|
log.debug(LoggerHelper.RELEASE_MARKER, "无效的页数值: {}", pageStr);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
log.warn("页数字段格式错误: {}", pageStr);
|
log.warn(LoggerHelper.RELEASE_MARKER, "页数字段格式错误: {}", pageStr);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,23 @@ public interface FileScanner {
|
||||||
default void scanWithProgress(Path rootPath, ProgressAwareListener listener) {
|
default void scanWithProgress(Path rootPath, ProgressAwareListener listener) {
|
||||||
throw new UnsupportedOperationException("Please implement FileScanner, ProgressAwareListener.");
|
throw new UnsupportedOperationException("Please implement FileScanner, ProgressAwareListener.");
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 扫描指定路径下的文件 (带超时)
|
||||||
|
*
|
||||||
|
* @param rootPath 根路径
|
||||||
|
* @param listener 文件发现监听器
|
||||||
|
* @param timeout 超时(s)
|
||||||
|
*/
|
||||||
|
default void scan(Path rootPath, FileScanListener listener, long timeout) {
|
||||||
|
throw new UnsupportedOperationException("Please implement FileScanner, FileScannerListener.");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 扫描指定路径下的文件(带进度反馈)(带超时)
|
||||||
|
* @param timeout 超时(s)
|
||||||
|
*/
|
||||||
|
default void scanWithProgress(Path rootPath, ProgressAwareListener listener, long timeout) {
|
||||||
|
throw new UnsupportedOperationException("Please implement FileScanner, ProgressAwareListener.");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件扫描监听器
|
* 文件扫描监听器
|
||||||
|
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
package top.r3944realms.docchecktoolrefactored.io.scanner;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ForkJoinPool;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type Parallel file scanner.
|
|
||||||
* <p>
|
|
||||||
* 这个没法正常使用,目前遇到的问题
|
|
||||||
* <p>
|
|
||||||
* * 目录遍历时遇到权限问题(静默失败)
|
|
||||||
* <p>
|
|
||||||
* * 存在符号链接循环
|
|
||||||
* <p>
|
|
||||||
* * 文件系统驱动程序卡死
|
|
||||||
* <p>
|
|
||||||
* * JVM与NTFS文件系统兼容性问题
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Deprecated
|
|
||||||
public class ParallelFileScanner implements FileScanner ,AutoCloseable {
|
|
||||||
private final ForkJoinPool forkJoinPool;
|
|
||||||
private volatile boolean cancelled = false;
|
|
||||||
/**
|
|
||||||
* 使用默认并行度(CPU核心数)
|
|
||||||
*/
|
|
||||||
public ParallelFileScanner() {
|
|
||||||
this(Runtime.getRuntime().availableProcessors());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new Parallel file scanner.
|
|
||||||
*
|
|
||||||
* @param parallelism 并行度(线程数)
|
|
||||||
*/
|
|
||||||
public ParallelFileScanner(int parallelism) {
|
|
||||||
this.forkJoinPool = new ForkJoinPool(parallelism);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scan(Path rootPath, FileScanListener listener) {
|
|
||||||
scanInternal(rootPath, listener, null);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void scanWithProgress(Path rootPath, ProgressAwareListener listener) {
|
|
||||||
// 先快速统计总文件数
|
|
||||||
long totalFiles = countFiles(rootPath);
|
|
||||||
scanInternal(rootPath, listener, totalFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long countFiles(Path rootPath) {
|
|
||||||
try(Stream<Path> pathStream = Files.walk(rootPath)
|
|
||||||
.parallel()
|
|
||||||
.filter(Files::isRegularFile)) {
|
|
||||||
return pathStream.count();
|
|
||||||
} catch (IOException e) {
|
|
||||||
return -1; // 表示无法确定总数
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void scanInternal(Path rootPath, FileScanListener listener, Long totalFiles) {
|
|
||||||
log.debug("ThreadPool Status: {}", forkJoinPool.isShutdown() ? "Closed" : "Running");
|
|
||||||
forkJoinPool.submit(() -> { // 方法没问题,可能就是在线程这里被卡死了
|
|
||||||
try {
|
|
||||||
AtomicInteger processed = new AtomicInteger(0);
|
|
||||||
log.debug("Scanning files in {}", rootPath);
|
|
||||||
// 收集所有文件到List(避免Stream被重复使用)
|
|
||||||
@SuppressWarnings("resource") List<Path> files = Files.walk(rootPath)
|
|
||||||
.peek(p -> log.trace("visiting: {}", p))
|
|
||||||
.parallel()
|
|
||||||
.filter(p -> {
|
|
||||||
boolean isRegular = Files.isRegularFile(p);
|
|
||||||
if (!isRegular) {
|
|
||||||
log.debug("Skip non-regular : {} ", p);
|
|
||||||
}
|
|
||||||
return isRegular;
|
|
||||||
})
|
|
||||||
.peek(p -> log.trace("Found file: {}", p))
|
|
||||||
.toList(); // 立即消费Stream
|
|
||||||
if (files.isEmpty()) {
|
|
||||||
log.warn("No files found in directory: {}", rootPath);
|
|
||||||
} else log.debug("Found {} files in {}", files.size(), rootPath);
|
|
||||||
files.forEach(file -> {
|
|
||||||
if (cancelled) {
|
|
||||||
log.debug("Cancelled scanning file {}", file);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.debug("Handle file {}", file);
|
|
||||||
listener.onFileFound(file);
|
|
||||||
|
|
||||||
// 进度更新
|
|
||||||
if (listener instanceof ProgressAwareListener progressListener) {
|
|
||||||
int current = processed.incrementAndGet();
|
|
||||||
progressListener.onProgressUpdate(
|
|
||||||
current,
|
|
||||||
totalFiles != null ? totalFiles.intValue() : -1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("Error Handle file {}", file, e);
|
|
||||||
listener.onError(file, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!cancelled) {
|
|
||||||
log.debug("Finished scanning files in {}", rootPath);
|
|
||||||
listener.onScanComplete();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
listener.onError(rootPath, e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Unexpected error in scan thread", e);
|
|
||||||
listener.onError(rootPath, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
log.debug("Task submitted to thread pool");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
cancelled = true;
|
|
||||||
forkJoinPool.shutdownNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package top.r3944realms.docchecktoolrefactored.io.scanner;
|
package top.r3944realms.docchecktoolrefactored.io.scanner;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -26,17 +27,24 @@ public class RobustParallelScanner implements FileScanner, AutoCloseable {
|
||||||
this.forkJoinPool = new ForkJoinPool(parallelism);
|
this.forkJoinPool = new ForkJoinPool(parallelism);
|
||||||
this.maxDepth = maxDepth; // 防止无限递归
|
this.maxDepth = maxDepth; // 防止无限递归
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scan(Path rootPath, FileScanListener listener) {
|
public void scan(Path rootPath, FileScanListener listener, long timeout) {
|
||||||
scanInternal(rootPath, listener, null);
|
scanInternal(rootPath, listener, null, timeout);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void scanWithProgress(Path rootPath, ProgressAwareListener listener) {
|
public void scan(Path rootPath, FileScanListener listener) {
|
||||||
|
scan(rootPath, listener, 30);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void scanWithProgress(Path rootPath, ProgressAwareListener listener, long timeout) {
|
||||||
// 预扫描阶段:计算总文件数
|
// 预扫描阶段:计算总文件数
|
||||||
AtomicLong totalFiles = new AtomicLong(0);
|
AtomicLong totalFiles = new AtomicLong(0);
|
||||||
countFiles(rootPath, totalFiles);
|
countFiles(rootPath, totalFiles);
|
||||||
scanInternal(rootPath, listener, totalFiles);
|
scanInternal(rootPath, listener, totalFiles, timeout);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void scanWithProgress(Path rootPath, ProgressAwareListener listener) {
|
||||||
|
scanWithProgress(rootPath, listener, 30);
|
||||||
}
|
}
|
||||||
private void countFiles(Path dir, AtomicLong counter) {
|
private void countFiles(Path dir, AtomicLong counter) {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
|
|
@ -52,10 +60,13 @@ public class RobustParallelScanner implements FileScanner, AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warn("Failed to pre-scan: {}", dir, e);
|
log.warn(LoggerHelper.TRACE_MARKER, "Failed to pre-scan: {}", dir, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void scanInternal(Path rootPath, FileScanListener listener, AtomicLong totalFiles) {
|
private void scanInternal(Path rootPath, FileScanListener listener, AtomicLong totalFiles) {
|
||||||
|
scanInternal(rootPath, listener, totalFiles, 30);
|
||||||
|
}
|
||||||
|
private void scanInternal(Path rootPath, FileScanListener listener, AtomicLong totalFiles, long timeout) {
|
||||||
try {
|
try {
|
||||||
validateDirectory(rootPath);
|
validateDirectory(rootPath);
|
||||||
|
|
||||||
|
|
@ -70,9 +81,9 @@ public class RobustParallelScanner implements FileScanner, AutoCloseable {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
listener.onError(rootPath, e);
|
listener.onError(rootPath, e);
|
||||||
}
|
}
|
||||||
}).get(30, TimeUnit.SECONDS);
|
}).get(timeout, TimeUnit.SECONDS);
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
log.error("Scan timeout: {}", rootPath, e);
|
log.error(LoggerHelper.TRACE_MARKER, "Scan timeout: {}", rootPath, e);
|
||||||
forkJoinPool.shutdownNow();
|
forkJoinPool.shutdownNow();
|
||||||
listener.onError(rootPath, new TimeoutException("扫描超时30秒"));
|
listener.onError(rootPath, new TimeoutException("扫描超时30秒"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
@ -38,10 +39,10 @@ public class LoginStageController implements Initializable {
|
||||||
String password = passwordField.getText();
|
String password = passwordField.getText();
|
||||||
|
|
||||||
if ("admin".equals(username) && "admin".equals(password)) {
|
if ("admin".equals(username) && "admin".equals(password)) {
|
||||||
log.info("{} Login successful", username);
|
log.info(LoggerHelper.DEBUG_MARKER, "{} Login successful", username);
|
||||||
SceneManager.switchMainView();
|
SceneManager.switchMainView();
|
||||||
} else {
|
} else {
|
||||||
log.info("Invalid username or password");
|
log.info(LoggerHelper.DEBUG_MARKER, "Invalid username or password");
|
||||||
DialogUtil.showErrorDialog("错误", null, "用户名或密码错误!");
|
DialogUtil.showErrorDialog("错误", null, "用户名或密码错误!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,25 @@
|
||||||
package top.r3944realms.docchecktoolrefactored.ui;
|
package top.r3944realms.docchecktoolrefactored.ui;
|
||||||
|
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Menu;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.System;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.core.Setting;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type Main stage controller.
|
* The type Main stage controller.
|
||||||
*/
|
*/
|
||||||
public class MainStageController {
|
public class MainStageController {
|
||||||
|
|
||||||
|
@FXML public Button nextB;
|
||||||
|
@FXML public Button prevB;
|
||||||
@FXML private Tab step1T;
|
@FXML private Tab step1T;
|
||||||
@FXML private Tab step2T;
|
@FXML private Tab step2T;
|
||||||
@FXML private Tab step3T;
|
@FXML private Tab step3T;
|
||||||
|
|
@ -16,11 +27,110 @@ public class MainStageController {
|
||||||
@FXML private Tab step5T;
|
@FXML private Tab step5T;
|
||||||
@FXML private Tab step6T;
|
@FXML private Tab step6T;
|
||||||
@FXML private Tab step7T;
|
@FXML private Tab step7T;
|
||||||
|
@FXML private TabPane tabPane;
|
||||||
@FXML private Menu helpM;
|
@FXML private Menu helpM;
|
||||||
|
@FXML private MenuItem aboutSoftwareMI;
|
||||||
|
@FXML private MenuItem helpDocMI;
|
||||||
|
@FXML private MenuItem exitMI;
|
||||||
|
@FXML private MenuItem logoutMI;
|
||||||
|
@FXML private MenuItem settingMI;
|
||||||
|
|
||||||
|
private List<Tab> tabs;
|
||||||
|
private int currentIndex = 0;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
// 初始化所有Tab的集合
|
||||||
|
tabs = new ArrayList<>();
|
||||||
|
tabs.add(step1T);
|
||||||
|
tabs.add(step2T);
|
||||||
|
tabs.add(step3T);
|
||||||
|
tabs.add(step4T);
|
||||||
|
tabs.add(step5T);
|
||||||
|
tabs.add(step6T);
|
||||||
|
tabs.add(step7T);
|
||||||
|
|
||||||
|
// 初始状态设置
|
||||||
|
updateButtonStates();
|
||||||
|
updateStepButtonsVisibility(); // 根据配置初始可见性
|
||||||
|
// 监听Tab切换事件
|
||||||
|
tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
|
||||||
|
currentIndex = tabs.indexOf(newTab);
|
||||||
|
updateButtonStates();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新按钮状态
|
||||||
|
*/
|
||||||
|
private void updateButtonStates() {
|
||||||
|
prevB.setDisable(currentIndex == 0);
|
||||||
|
nextB.setDisable(currentIndex == tabs.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* 切换到下一个Tab
|
||||||
|
* @param actionEvent 事件
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
void onNext(ActionEvent actionEvent) {
|
||||||
|
if (currentIndex < tabs.size() - 1) {
|
||||||
|
tabPane.getSelectionModel().select(tabs.get(currentIndex + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换到上一个Tab
|
||||||
|
* @param actionEvent 事件
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
void onPrev(ActionEvent actionEvent) {
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
tabPane.getSelectionModel().select(tabs.get(currentIndex - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理键盘快捷键
|
||||||
|
* @param keyEvent 键盘事件
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
void handleKeyPressed(KeyEvent keyEvent) {
|
||||||
|
// Ctrl+> 或 Ctrl+. 切换到下一个Tab
|
||||||
|
if (new KeyCodeCombination(KeyCode.PERIOD, KeyCombination.CONTROL_DOWN).match(keyEvent) ||
|
||||||
|
new KeyCodeCombination(KeyCode.GREATER, KeyCombination.CONTROL_DOWN).match(keyEvent)) {
|
||||||
|
onNext(null);
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
// Ctrl+< 或 Ctrl+, 切换到上一个Tab
|
||||||
|
else if (new KeyCodeCombination(KeyCode.COMMA, KeyCombination.CONTROL_DOWN).match(keyEvent) ||
|
||||||
|
new KeyCodeCombination(KeyCode.LESS, KeyCombination.CONTROL_DOWN).match(keyEvent)) {
|
||||||
|
onPrev(null);
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML void onLogout(ActionEvent actionEvent) {
|
||||||
|
SceneManager.switchLoginView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML void onExit(ActionEvent actionEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML void onOpenSetting(ActionEvent actionEvent) {
|
||||||
|
SceneManager.openSettingView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML void onOpenHelpDoc(ActionEvent actionEvent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML void onAbout(ActionEvent actionEvent) {
|
||||||
|
}
|
||||||
|
public void updateStepButtonsVisibility() {
|
||||||
|
Setting setting = System.getSetting();
|
||||||
|
boolean visible = setting.isEnableStep(); // 由enableStep控制
|
||||||
|
nextB.setVisible(visible);
|
||||||
|
prevB.setVisible(visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
@ -13,6 +14,7 @@ import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.r3944realms.docchecktoolrefactored.Main;
|
import top.r3944realms.docchecktoolrefactored.Main;
|
||||||
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -29,7 +31,8 @@ public class SceneManager {
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private static Stage primaryStage;
|
private static Stage primaryStage;
|
||||||
|
@Getter
|
||||||
|
private static MainStageController mainController;
|
||||||
@Getter
|
@Getter
|
||||||
private static final List<Stage> openStages = new ArrayList<>();
|
private static final List<Stage> openStages = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -46,14 +49,59 @@ public class SceneManager {
|
||||||
* Switch login view.
|
* Switch login view.
|
||||||
*/
|
*/
|
||||||
public static void switchLoginView() {
|
public static void switchLoginView() {
|
||||||
SceneManager.loadView("/fxml/login-view.fxml","淮阴区数字化档案检查验收系统 - 登录", 400, 300);
|
SceneManager.loadView("/fxml/login-view.fxml","数字化验收工具 - 登录", 400, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch main view.
|
* Switch main view.
|
||||||
*/
|
*/
|
||||||
public static void switchMainView() {
|
public static void switchMainView() {
|
||||||
SceneManager.loadView("/fxml/main-view.fxml", "淮阴区数字化档案检查验收系统 - 主界面", 1200, 900);
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(Main.class.getResource("/fxml/main-view.fxml")));
|
||||||
|
Parent root = loader.load();
|
||||||
|
mainController = loader.getController(); // 保存控制器引用
|
||||||
|
Scene newScene = new Scene(root, 1200, 900);
|
||||||
|
|
||||||
|
applyFadeTransition(root);
|
||||||
|
|
||||||
|
primaryStage.setScene(newScene);
|
||||||
|
primaryStage.centerOnScreen();
|
||||||
|
primaryStage.setTitle("数字化验收工具 - 主界面");
|
||||||
|
|
||||||
|
// 设置窗口关闭确认
|
||||||
|
primaryStage.setOnCloseRequest(event -> {
|
||||||
|
if (!DialogUtil.showExitConfirmation(primaryStage)) {
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(LoggerHelper.TRACE_MARKER, "Failed to load main view", e);
|
||||||
|
DialogUtil.showErrorDialog("错误", "加载主界面失败", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 打开设置窗口
|
||||||
|
*/
|
||||||
|
public static void openSettingView() {
|
||||||
|
try {
|
||||||
|
Parent root = FXMLLoader.load(Objects.requireNonNull(Main.class.getResource("/fxml/setting-view.fxml")));
|
||||||
|
Stage settingStage = new Stage();
|
||||||
|
settingStage.setTitle("数字化验收工具 - 设置");
|
||||||
|
Scene scene = new Scene(root, 300, 206);
|
||||||
|
settingStage.setScene(scene); // 默认大小可调
|
||||||
|
settingStage.initOwner(primaryStage); // 设置父窗口
|
||||||
|
settingStage.initModality(Modality.WINDOW_MODAL);
|
||||||
|
settingStage.setResizable(false);
|
||||||
|
settingStage.centerOnScreen();
|
||||||
|
|
||||||
|
settingStage.show();
|
||||||
|
openStages.add(settingStage);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(LoggerHelper.TRACE_MARKER, "Failed to open setting view: {}", e.getMessage(), e);
|
||||||
|
DialogUtil.showErrorDialog("错误", "加载设置窗口失败", "无法加载设置窗口: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -100,9 +148,8 @@ public class SceneManager {
|
||||||
event.consume();
|
event.consume();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to load view: {}", fxmlPath, e);
|
log.error(LoggerHelper.TRACE_MARKER, "Failed to load view: {}", fxmlPath, e);
|
||||||
DialogUtil.showErrorDialog("错误", "加载视图失败", "无法加载视图: " + e.getMessage());
|
DialogUtil.showErrorDialog("错误", "加载视图失败", "无法加载视图: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
package top.r3944realms.docchecktoolrefactored.ui;
|
||||||
|
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.Spinner;
|
||||||
|
import javafx.scene.input.MouseDragEvent;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.System;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.core.Setting;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.ui.extend.LongSpinnerValueFactory;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class SettingDialogController implements Initializable {
|
||||||
|
|
||||||
|
@FXML private CheckBox enableStepCB;
|
||||||
|
@FXML private Button resetB, saveB, cancelB;
|
||||||
|
@FXML private Spinner<Long> scanTotalTimeOutS, scanSingleTimeOutS;
|
||||||
|
|
||||||
|
private Setting setting;
|
||||||
|
|
||||||
|
// Spinner 范围常量
|
||||||
|
private static final int SINGLE_MIN = 1;
|
||||||
|
private static final int SINGLE_MAX = 3600;
|
||||||
|
private static final int TOTAL_MIN = 1;
|
||||||
|
private static final int TOTAL_MAX = 86400;
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
|
// 获取当前配置
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 添加焦点离开时校验
|
||||||
|
addSpinnerValidation(scanSingleTimeOutS, SINGLE_MIN, SINGLE_MAX);
|
||||||
|
addSpinnerValidation(scanTotalTimeOutS, TOTAL_MIN, TOTAL_MAX);
|
||||||
|
enableStepCB.setSelected(setting.isEnableStep());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存修改 */
|
||||||
|
@FXML
|
||||||
|
void onSave(ActionEvent actionEvent) {
|
||||||
|
// 更新配置对象
|
||||||
|
setting.setSingleTimeout(scanSingleTimeOutS.getValue());
|
||||||
|
setting.setTotalTimeout(scanTotalTimeOutS.getValue());
|
||||||
|
setting.setEnableStep(enableStepCB.isSelected());
|
||||||
|
// 保存到配置文件
|
||||||
|
System.saveSettingsNow();
|
||||||
|
// 通知主界面刷新按钮状态
|
||||||
|
MainStageController mainController = SceneManager.getMainController();
|
||||||
|
if(mainController != null){
|
||||||
|
mainController.updateStepButtonsVisibility();
|
||||||
|
}
|
||||||
|
closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置为默认值 */
|
||||||
|
@FXML
|
||||||
|
void onReset(ActionEvent actionEvent) {
|
||||||
|
scanSingleTimeOutS.getValueFactory().setValue(30L); // 默认单次超时
|
||||||
|
scanTotalTimeOutS.getValueFactory().setValue(300L); // 默认总超时
|
||||||
|
enableStepCB.setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消修改 */
|
||||||
|
@FXML
|
||||||
|
void onCancel(ActionEvent actionEvent) {
|
||||||
|
closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void onCheckOne(MouseDragEvent mouseDragEvent) {
|
||||||
|
validateSpinnerValue(scanSingleTimeOutS, 30, 3600);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void onCheckTwo(MouseDragEvent mouseDragEvent) {
|
||||||
|
validateSpinnerValue(scanTotalTimeOutS, 60, 3600 * 24);
|
||||||
|
}
|
||||||
|
@FXML
|
||||||
|
void onSettingThree(ActionEvent actionEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 给 Spinner 添加离开焦点校验 */
|
||||||
|
private void addSpinnerValidation(Spinner<Long> spinner, long min, long max) {
|
||||||
|
spinner.getEditor().focusedProperty().addListener((obs, oldVal, newVal) -> {
|
||||||
|
if (!newVal) { // 焦点离开时触发
|
||||||
|
validateSpinnerValue(spinner, min, max);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验 Spinner 的值是否在范围内,如果超出则纠正并提示 */
|
||||||
|
private void validateSpinnerValue(Spinner<Long> spinner, long min, long max) {
|
||||||
|
try {
|
||||||
|
long value = Integer.parseInt(spinner.getEditor().getText());
|
||||||
|
if (value < min || value > max) {
|
||||||
|
// 超出范围则纠正
|
||||||
|
long corrected = Math.max(min, Math.min(value, max));
|
||||||
|
spinner.getValueFactory().setValue(corrected);
|
||||||
|
|
||||||
|
// 弹出提示
|
||||||
|
DialogUtil.showWarningDialog("输入警告", "输入的值超出范围,已自动调整为 " + corrected);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 非数字输入恢复原来的值
|
||||||
|
spinner.getEditor().setText(spinner.getValue().toString());
|
||||||
|
DialogUtil.showWarningDialog("输入警告","输入非法,已恢复为原值 " + spinner.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** 关闭对话框 */
|
||||||
|
private void closeDialog() {
|
||||||
|
Stage stage = (Stage) cancelB.getScene().getWindow();
|
||||||
|
stage.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package top.r3944realms.docchecktoolrefactored.ui.extend;
|
||||||
|
|
||||||
|
import javafx.scene.control.SpinnerValueFactory;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
public class LongSpinnerValueFactory extends SpinnerValueFactory<Long> {
|
||||||
|
|
||||||
|
public LongSpinnerValueFactory(long min, long max, long initialValue) {
|
||||||
|
setMin(min);
|
||||||
|
setMax(max);
|
||||||
|
setValue(initialValue);
|
||||||
|
|
||||||
|
setConverter(new javafx.util.StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(Long object) {
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long fromString(String string) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(string);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private long min;
|
||||||
|
private long max;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decrement(int steps) {
|
||||||
|
long newValue = getValue() - steps;
|
||||||
|
if (newValue < min) newValue = min;
|
||||||
|
setValue(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void increment(int steps) {
|
||||||
|
long newValue = getValue() + steps;
|
||||||
|
if (newValue > max) newValue = max;
|
||||||
|
setValue(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package top.r3944realms.docchecktoolrefactored.ui.module;
|
package top.r3944realms.docchecktoolrefactored.ui.module;
|
||||||
|
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
|
@ -8,11 +9,11 @@ import javafx.scene.control.TextField;
|
||||||
import javafx.stage.DirectoryChooser;
|
import javafx.stage.DirectoryChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.DuplicateFinder;
|
import top.r3944realms.docchecktoolrefactored.System;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.FileHashCalculator;
|
import top.r3944realms.docchecktoolrefactored.ui.SceneManager;
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
|
||||||
import top.r3944realms.docchecktoolrefactored.ui.task.DuplicateDocumentDetectionTask;
|
import top.r3944realms.docchecktoolrefactored.ui.task.DuplicateDocumentDetectionTask;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.ui.utils.ProgressBarUtil;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|
@ -21,22 +22,26 @@ import java.io.File;
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DuplicateDocumentPaneController {
|
public class DuplicateDocumentPaneController {
|
||||||
|
|
||||||
@FXML private TextArea result1B;
|
@FXML private TextArea result1B;
|
||||||
@FXML private TextField loadFolder1TF;
|
@FXML private TextField loadFolder1TF;
|
||||||
@FXML private Button selectLoadFolder1B;
|
@FXML private Button selectLoadFolder1B;
|
||||||
@FXML private Button start1B;
|
@FXML private Button start1B;
|
||||||
|
@FXML private Button cancel1B;
|
||||||
|
private final ProgressBarUtil progressBarUtil = new ProgressBarUtil();
|
||||||
|
private DuplicateDocumentDetectionTask currentTask; // 保存任务引用
|
||||||
/**
|
/**
|
||||||
* On select folder.
|
* On select folder.
|
||||||
*
|
*
|
||||||
* @param actionEvent the action event
|
* @param actionEvent the action event
|
||||||
*/
|
*/
|
||||||
@FXML void onSelectFolder(ActionEvent actionEvent) {
|
@FXML void onSelectFolder(ActionEvent actionEvent) {
|
||||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
DirectoryChooser directoryChooser = System.getDirectoryChooser();
|
||||||
directoryChooser.setTitle("选择要检查的文件夹");
|
directoryChooser.setTitle("选择要检查的文件夹");
|
||||||
File selectedFolder = directoryChooser.showDialog(new Stage());
|
File selectedFolder = directoryChooser.showDialog(new Stage());
|
||||||
if (selectedFolder != null) {
|
if (selectedFolder != null) {
|
||||||
loadFolder1TF.setText(selectedFolder.getAbsolutePath());
|
loadFolder1TF.setText(selectedFolder.getAbsolutePath());
|
||||||
|
System.setLastModifiedFile(selectedFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,39 +51,84 @@ public class DuplicateDocumentPaneController {
|
||||||
* @param actionEvent the action event
|
* @param actionEvent the action event
|
||||||
*/
|
*/
|
||||||
@FXML void onStart(ActionEvent actionEvent) {
|
@FXML void onStart(ActionEvent actionEvent) {
|
||||||
log.info("用户点击了开始查重按钮");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了开始查重按钮");
|
||||||
String folderPath = loadFolder1TF.getText();
|
String folderPath = loadFolder1TF.getText();
|
||||||
if (folderPath == null || folderPath.trim().isEmpty()) {
|
if (folderPath == null || folderPath.trim().isEmpty()) {
|
||||||
log.warn("未选择文件夹,无法进行查重");
|
log.warn(LoggerHelper.DEBUG_MARKER, "未选择文件夹,无法进行查重");
|
||||||
result1B.setText("请选择要检查的文件夹。");
|
result1B.setText("请选择要检查的文件夹。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 禁用开始按钮避免重复点击
|
||||||
|
start1B.setDisable(true);
|
||||||
|
cancel1B.setDisable(false);
|
||||||
|
// 显示进度条窗口
|
||||||
|
progressBarUtil.showProgress(SceneManager.getPrimaryStage(), "重复文件检测", "正在初始化扫描...");
|
||||||
|
|
||||||
// 创建并启动后台任务
|
// 创建并启动后台任务
|
||||||
DuplicateDocumentDetectionTask task = new DuplicateDocumentDetectionTask(folderPath);
|
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() : "处理中...");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
task.progressProperty().addListener(progressChangeListener);
|
||||||
|
|
||||||
// 绑定任务的消息到结果文本区域
|
// 绑定任务的消息到结果文本区域
|
||||||
task.messageProperty().addListener((observable, oldValue, newValue) -> {
|
ChangeListener<String> messageChangeListener = (observable, oldValue, newValue) -> {
|
||||||
result1B.setText(newValue);
|
result1B.setText(newValue);
|
||||||
});
|
};
|
||||||
|
task.messageProperty().addListener(messageChangeListener);
|
||||||
|
|
||||||
// 当任务完成时显示完整结果
|
// 当任务完成时显示完整结果
|
||||||
task.setOnSucceeded(e -> {
|
task.setOnSucceeded(e -> {
|
||||||
|
progressBarUtil.closeProgress();
|
||||||
result1B.setText(task.getValue());
|
result1B.setText(task.getValue());
|
||||||
log.info("查重任务完成,结果如下:{}", task.getValue());
|
start1B.setDisable(false);
|
||||||
|
cancel1B.setDisable(true);
|
||||||
|
log.info(LoggerHelper.RELEASE_MARKER, "查重任务完成,结果如下:{}", task.getValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理任务失败情况
|
// 处理任务失败情况
|
||||||
task.setOnFailed(e -> {
|
task.setOnFailed(e -> {
|
||||||
|
progressBarUtil.closeProgress();
|
||||||
Throwable exception = task.getException();
|
Throwable exception = task.getException();
|
||||||
result1B.setText("检测过程中发生错误: " + exception.getMessage());
|
result1B.setText("检测过程中发生错误: " + exception.getMessage());
|
||||||
log.error("error", exception);
|
start1B.setDisable(false);
|
||||||
log.info("查重任务失败,错误信息: {}", exception.getMessage());
|
cancel1B.setDisable(true);
|
||||||
|
log.error(LoggerHelper.RELEASE_MARKER, "查重任务失败", exception);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 处理任务取消情况
|
||||||
|
task.setOnCancelled(e -> {
|
||||||
|
progressBarUtil.closeProgress();
|
||||||
|
result1B.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();
|
||||||
|
}
|
||||||
|
});
|
||||||
// 在新线程中执行任务
|
// 在新线程中执行任务
|
||||||
Thread thread = new Thread(task);
|
Thread thread = new Thread(task);
|
||||||
thread.setDaemon(true);
|
thread.setDaemon(true);
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onCancel(ActionEvent actionEvent) {
|
||||||
|
if (currentTask != null && currentTask.isRunning()) {
|
||||||
|
currentTask.cancel(); // 触发 setOnCancelled
|
||||||
|
} else {
|
||||||
|
log.warn(LoggerHelper.DEBUG_MARKER, "没有正在运行的任务可取消");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ import javafx.scene.control.TextArea;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.System;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.AddressFileComparator;
|
import top.r3944realms.docchecktoolrefactored.core.AddressFileComparator;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.AddressFileGenerator;
|
import top.r3944realms.docchecktoolrefactored.core.AddressFileGenerator;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
import top.r3944realms.docchecktoolrefactored.core.LogicalAddressFileGenerator;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.PhysicalAddressFileGenerator;
|
import top.r3944realms.docchecktoolrefactored.core.PhysicalAddressFileGenerator;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
@ -49,8 +51,8 @@ public class PathCheckPaneController implements Initializable {
|
||||||
* @param actionEvent the action event
|
* @param actionEvent the action event
|
||||||
*/
|
*/
|
||||||
@FXML void onSelectLC(ActionEvent actionEvent) {
|
@FXML void onSelectLC(ActionEvent actionEvent) {
|
||||||
log.info("用户点击了选择目录文件按钮");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了选择目录文件按钮");
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = System.getFileChooser();
|
||||||
fileChooser.setTitle("选择目录文件");
|
fileChooser.setTitle("选择目录文件");
|
||||||
|
|
||||||
// 设置文件过滤器,只允许DBF、XML、xlsx、xls格式
|
// 设置文件过滤器,只允许DBF、XML、xlsx、xls格式
|
||||||
|
|
@ -60,7 +62,7 @@ public class PathCheckPaneController implements Initializable {
|
||||||
FileChooser.ExtensionFilter dbfFilter = new FileChooser.ExtensionFilter("DBF Files (*.dbf)", "*.dbf");
|
FileChooser.ExtensionFilter dbfFilter = new FileChooser.ExtensionFilter("DBF Files (*.dbf)", "*.dbf");
|
||||||
FileChooser.ExtensionFilter xmlFilter = new FileChooser.ExtensionFilter("XML Files (*.xml)", "*.xml");
|
FileChooser.ExtensionFilter xmlFilter = new FileChooser.ExtensionFilter("XML Files (*.xml)", "*.xml");
|
||||||
|
|
||||||
fileChooser.getExtensionFilters().addAll( xlsxFilter, xlsFilter,dbfFilter, xmlFilter);
|
fileChooser.getExtensionFilters().addAll(xlsxFilter, xlsFilter,dbfFilter, xmlFilter);
|
||||||
|
|
||||||
// 显示文件选择对话框
|
// 显示文件选择对话框
|
||||||
File selectedFile = fileChooser.showOpenDialog(selectLoadCatalog2B.getScene().getWindow());
|
File selectedFile = fileChooser.showOpenDialog(selectLoadCatalog2B.getScene().getWindow());
|
||||||
|
|
@ -68,9 +70,10 @@ public class PathCheckPaneController implements Initializable {
|
||||||
// 如果选择了文件,则将文件路径显示在loadCatalog2TF上
|
// 如果选择了文件,则将文件路径显示在loadCatalog2TF上
|
||||||
if (selectedFile != null) {
|
if (selectedFile != null) {
|
||||||
loadCatalog2TF.setText(selectedFile.getAbsolutePath());
|
loadCatalog2TF.setText(selectedFile.getAbsolutePath());
|
||||||
log.info("选择的目录文件路径为:{}", selectedFile.getAbsolutePath());
|
System.setLastModifiedFile(selectedFile);
|
||||||
|
log.info(LoggerHelper.DEBUG_MARKER, "选择的目录文件路径为:{}", selectedFile.getAbsolutePath());
|
||||||
}else{
|
}else{
|
||||||
log.warn("用户未选择任何文件夹");
|
log.warn(LoggerHelper.DEBUG_MARKER, "用户未选择任何文件夹");
|
||||||
result2TA.setText("未选择任何文件夹,请重新选择。");
|
result2TA.setText("未选择任何文件夹,请重新选择。");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +84,7 @@ public class PathCheckPaneController implements Initializable {
|
||||||
* @param actionEvent the action event
|
* @param actionEvent the action event
|
||||||
*/
|
*/
|
||||||
@FXML void onSelectJPGF(ActionEvent actionEvent) {
|
@FXML void onSelectJPGF(ActionEvent actionEvent) {
|
||||||
javafx.stage.DirectoryChooser directoryChooser = new javafx.stage.DirectoryChooser();
|
javafx.stage.DirectoryChooser directoryChooser = System.getDirectoryChooser();
|
||||||
// 正确获取当前选中的值
|
// 正确获取当前选中的值
|
||||||
Mode selectedMode = loadFolderType2CB.getValue();
|
Mode selectedMode = loadFolderType2CB.getValue();
|
||||||
if (selectedMode == Mode.PAGE_TYPE) {
|
if (selectedMode == Mode.PAGE_TYPE) {
|
||||||
|
|
@ -89,14 +92,15 @@ public class PathCheckPaneController implements Initializable {
|
||||||
} else if (selectedMode == Mode.FILE_TYPE) {
|
} else if (selectedMode == Mode.FILE_TYPE) {
|
||||||
directoryChooser.setTitle("选择文件级文件夹");
|
directoryChooser.setTitle("选择文件级文件夹");
|
||||||
}
|
}
|
||||||
log.info("用户选择的模式为:{}", selectedMode);
|
log.info(LoggerHelper.DEBUG_MARKER, "用户选择的模式为:{}", selectedMode);
|
||||||
|
|
||||||
File selectedDirectory = directoryChooser.showDialog(selectJPGFolder2B.getScene().getWindow());
|
File selectedDirectory = directoryChooser.showDialog(selectJPGFolder2B.getScene().getWindow());
|
||||||
|
|
||||||
if (selectedDirectory != null) {
|
if (selectedDirectory != null) {
|
||||||
loadJPGFolder2TF.setText(selectedDirectory.getAbsolutePath());
|
loadJPGFolder2TF.setText(selectedDirectory.getAbsolutePath());
|
||||||
log.info("选择的{}文件夹路径为:{}", selectedMode,selectedDirectory.getAbsolutePath());
|
log.info(LoggerHelper.DEBUG_MARKER, "选择的{}文件夹路径为:{}", selectedMode,selectedDirectory.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
System.setLastModifiedFile(selectedDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -105,14 +109,14 @@ public class PathCheckPaneController implements Initializable {
|
||||||
* @param actionEvent the action event
|
* @param actionEvent the action event
|
||||||
*/
|
*/
|
||||||
@FXML void onGenerateLA(ActionEvent actionEvent) {
|
@FXML void onGenerateLA(ActionEvent actionEvent) {
|
||||||
log.info("用户点击了生成逻辑地址文件按钮");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了生成逻辑地址文件按钮");
|
||||||
String filePath = loadCatalog2TF.getText();
|
String filePath = loadCatalog2TF.getText();
|
||||||
if (filePath.isEmpty()) {
|
if (filePath.isEmpty()) {
|
||||||
result2TA.setText("请先选择目录文件。");
|
result2TA.setText("请先选择目录文件。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = System.getFileChooser();
|
||||||
fileChooser.setTitle("选择保存逻辑地址文件的位置");
|
fileChooser.setTitle("选择保存逻辑地址文件的位置");
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
|
||||||
fileChooser.setInitialFileName("逻辑地址文件.csv");
|
fileChooser.setInitialFileName("逻辑地址文件.csv");
|
||||||
|
|
@ -120,10 +124,10 @@ public class PathCheckPaneController implements Initializable {
|
||||||
File outputFile = fileChooser.showSaveDialog(generateLogicalAddress2B.getScene().getWindow());
|
File outputFile = fileChooser.showSaveDialog(generateLogicalAddress2B.getScene().getWindow());
|
||||||
if (outputFile == null) {
|
if (outputFile == null) {
|
||||||
result2TA.setText("未选择保存位置");
|
result2TA.setText("未选择保存位置");
|
||||||
log.warn("用户未选择任何文件");
|
log.warn(LoggerHelper.DEBUG_MARKER, "用户未选择任何文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
System.setLastModifiedFile(outputFile);
|
||||||
// 正确处理文件扩展名 - 只有在没有.csv扩展名时才添加
|
// 正确处理文件扩展名 - 只有在没有.csv扩展名时才添加
|
||||||
final File finalOutputFile;
|
final File finalOutputFile;
|
||||||
if (!outputFile.getName().toLowerCase().endsWith(".csv")) {
|
if (!outputFile.getName().toLowerCase().endsWith(".csv")) {
|
||||||
|
|
@ -134,7 +138,7 @@ public class PathCheckPaneController implements Initializable {
|
||||||
|
|
||||||
// 保存生成的文件路径
|
// 保存生成的文件路径
|
||||||
logicalAddressFilePath = finalOutputFile.getAbsolutePath();
|
logicalAddressFilePath = finalOutputFile.getAbsolutePath();
|
||||||
log.info("选择的输出文件路径: {}", logicalAddressFilePath);
|
log.info(LoggerHelper.DEBUG_MARKER, "选择的输出文件路径: {}", logicalAddressFilePath);
|
||||||
// 创建后台任务来处理文件生成
|
// 创建后台任务来处理文件生成
|
||||||
Thread backgroundThread = new Thread(() -> {
|
Thread backgroundThread = new Thread(() -> {
|
||||||
// 获取当前选择的文件夹类型
|
// 获取当前选择的文件夹类型
|
||||||
|
|
@ -180,7 +184,7 @@ public class PathCheckPaneController implements Initializable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = System.getFileChooser();
|
||||||
fileChooser.setTitle("选择保存物理地址文件的位置");
|
fileChooser.setTitle("选择保存物理地址文件的位置");
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
|
||||||
fileChooser.setInitialFileName("物理地址文件.csv");
|
fileChooser.setInitialFileName("物理地址文件.csv");
|
||||||
|
|
@ -191,6 +195,7 @@ public class PathCheckPaneController implements Initializable {
|
||||||
result2TA.setText("未选择保存位置");
|
result2TA.setText("未选择保存位置");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
System.setLastModifiedFile(outputFile);
|
||||||
|
|
||||||
// 正确处理文件扩展名 - 只有在没有.csv扩展名时才添加
|
// 正确处理文件扩展名 - 只有在没有.csv扩展名时才添加
|
||||||
final File finalOutputFile;
|
final File finalOutputFile;
|
||||||
|
|
@ -240,7 +245,7 @@ public class PathCheckPaneController implements Initializable {
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
void onStart(ActionEvent actionEvent) {
|
void onStart(ActionEvent actionEvent) {
|
||||||
log.info("用户点击了开始对比按钮");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户点击了开始对比按钮");
|
||||||
|
|
||||||
// 检查是否已生成两个文件
|
// 检查是否已生成两个文件
|
||||||
if (logicalAddressFilePath == null || physicalAddressFilePath == null) {
|
if (logicalAddressFilePath == null || physicalAddressFilePath == null) {
|
||||||
|
|
@ -248,8 +253,8 @@ public class PathCheckPaneController implements Initializable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("逻辑地址文件路径为:{}", logicalAddressFilePath);
|
log.info(LoggerHelper.DEBUG_MARKER, "逻辑地址文件路径为:{}", logicalAddressFilePath);
|
||||||
log.info("物理地址文件路径为:{}", physicalAddressFilePath);
|
log.info(LoggerHelper.DEBUG_MARKER, "物理地址文件路径为:{}", physicalAddressFilePath);
|
||||||
|
|
||||||
// 使用新创建的核心类进行文件比较
|
// 使用新创建的核心类进行文件比较
|
||||||
AddressFileComparator comparator = new AddressFileComparator();
|
AddressFileComparator comparator = new AddressFileComparator();
|
||||||
|
|
@ -323,13 +328,23 @@ public class PathCheckPaneController implements Initializable {
|
||||||
*/
|
*/
|
||||||
enum Mode {
|
enum Mode {
|
||||||
/**
|
/**
|
||||||
* Jpg mode.
|
* Jpg mode. 文件以JPG
|
||||||
*/
|
*/
|
||||||
PAGE_TYPE("jpg"),
|
PAGE_TYPE("jpg") {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "页面级";
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Pdf mode.
|
* Pdf mode. 文件以PDF
|
||||||
*/
|
*/
|
||||||
FILE_TYPE("pdf");
|
FILE_TYPE("pdf") {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "文件级";
|
||||||
|
}
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* The Id.
|
* The Id.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package top.r3944realms.docchecktoolrefactored.ui.module;
|
||||||
|
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Dialog;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.ui.utils.DialogUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type Project info pane controller.
|
* The type Project info pane controller.
|
||||||
|
|
@ -15,12 +17,13 @@ public class ProjectInfoPaneController {
|
||||||
@FXML private TextField fileYearTF;
|
@FXML private TextField fileYearTF;
|
||||||
@FXML
|
@FXML
|
||||||
void onReset(ActionEvent event) {
|
void onReset(ActionEvent event) {
|
||||||
// 清空所有文本字段
|
if (DialogUtil.showConfirmationDialog("确认","是否清除(该操作不可逆)", "")){// 清空所有文本字段
|
||||||
projectNameTF.clear();
|
projectNameTF.clear();
|
||||||
fileYearTF.clear();
|
fileYearTF.clear();
|
||||||
fileCategoriesTF.clear();
|
fileCategoriesTF.clear();
|
||||||
totalCatalogNumberTF.clear();
|
totalCatalogNumberTF.clear();
|
||||||
AcceptanceTimeTF.clear();
|
AcceptanceTimeTF.clear();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// StorageCarrierPaneController.java
|
|
||||||
package top.r3944realms.docchecktoolrefactored.ui.module;
|
package top.r3944realms.docchecktoolrefactored.ui.module;
|
||||||
|
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
|
@ -11,8 +10,10 @@ import javafx.stage.DirectoryChooser;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.System;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.HashFileGenerator;
|
import top.r3944realms.docchecktoolrefactored.core.HashFileGenerator;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.MD5HashCalculator;
|
import top.r3944realms.docchecktoolrefactored.core.MD5HashCalculator;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.util.LoggerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -52,12 +53,13 @@ public class StorageCarrierPaneController {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void onSelectLD(ActionEvent event) {
|
void onSelectLD(ActionEvent event) {
|
||||||
log.info("用户点击选择文件夹按钮");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户点击选择文件夹按钮");
|
||||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
DirectoryChooser directoryChooser = System.getDirectoryChooser();
|
||||||
directoryChooser.setTitle("选择要检查的文件夹(页面级文件夹和文件级文件夹等不包括目录文件夹)");
|
directoryChooser.setTitle("选择要检查的文件夹(页面级文件夹和文件级文件夹等不包括目录文件夹)");
|
||||||
|
|
||||||
File selectedFolder = directoryChooser.showDialog(new Stage());
|
File selectedFolder = directoryChooser.showDialog(new Stage());
|
||||||
if (selectedFolder != null) {
|
if (selectedFolder != null) {
|
||||||
|
System.setLastModifiedFile(selectedFolder);
|
||||||
String currentText = loadDigitalOutcomes.getText();
|
String currentText = loadDigitalOutcomes.getText();
|
||||||
String folderPath = selectedFolder.getAbsolutePath();
|
String folderPath = selectedFolder.getAbsolutePath();
|
||||||
|
|
||||||
|
|
@ -79,18 +81,18 @@ public class StorageCarrierPaneController {
|
||||||
loadDigitalOutcomes.setText(currentText + File.pathSeparator + folderPath);
|
loadDigitalOutcomes.setText(currentText + File.pathSeparator + folderPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("用户选择了文件夹: {}", selectedFolder.getAbsolutePath());
|
log.info(LoggerHelper.DEBUG_MARKER, "用户选择了文件夹: {}", selectedFolder.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
log.info("用户取消了文件夹选择");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户取消了文件夹选择");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void onClearSelectedFolders(ActionEvent event) {
|
void onClearSelectedFolders(ActionEvent event) {
|
||||||
log.info("用户点击清除已选择文件夹按钮");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户点击清除已选择文件夹按钮");
|
||||||
loadDigitalOutcomes.setText("");
|
loadDigitalOutcomes.setText("");
|
||||||
result7TA.setText("已清除所有已选择的文件夹");
|
result7TA.setText("已清除所有已选择的文件夹");
|
||||||
log.info("已清除所有已选择的文件夹");
|
log.info(LoggerHelper.DEBUG_MARKER, "已清除所有已选择的文件夹");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -98,54 +100,56 @@ public class StorageCarrierPaneController {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void onSelectLC(ActionEvent event) {
|
void onSelectLC(ActionEvent event) {
|
||||||
log.info("用户点击选择RAR文件按钮");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户点击选择RAR文件按钮");
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = System.getFileChooser();
|
||||||
fileChooser.setTitle("选择一个 .rar 文件");
|
fileChooser.setTitle("选择一个 .rar 文件");
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("RAR Files", "*.rar"));
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("RAR Files", "*.rar"));
|
||||||
File selectedFile = fileChooser.showOpenDialog(new Stage());
|
File selectedFile = fileChooser.showOpenDialog(new Stage());
|
||||||
if (selectedFile != null) {
|
if (selectedFile != null) {
|
||||||
|
System.setLastModifiedFile(selectedFile);
|
||||||
loadCompressedFile.setText(selectedFile.getAbsolutePath());
|
loadCompressedFile.setText(selectedFile.getAbsolutePath());
|
||||||
log.info("用户选择了RAR文件: {}", selectedFile.getAbsolutePath());
|
log.info(LoggerHelper.DEBUG_MARKER, "用户选择了RAR文件: {}", selectedFile.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
log.info("用户取消了RAR文件选择");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户取消了RAR文件选择");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void onCaculateHash(ActionEvent event) {
|
void onCaculateHash(ActionEvent event) {
|
||||||
log.info("开始计算RAR文件的MD5哈希值");
|
log.info(LoggerHelper.DEBUG_MARKER, "开始计算RAR文件的MD5哈希值");
|
||||||
String filePath = loadCompressedFile.getText();
|
String filePath = loadCompressedFile.getText();
|
||||||
if (filePath == null || filePath.isEmpty()) {
|
if (filePath == null || filePath.isEmpty()) {
|
||||||
log.warn("未选择RAR文件,无法计算哈希值");
|
log.warn(LoggerHelper.DEBUG_MARKER, "未选择RAR文件,无法计算哈希值");
|
||||||
result7TA.setText("请先选择一个 .rar 文件");
|
result7TA.setText("请先选择一个 .rar 文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File file = new File(filePath);
|
File file = new File(filePath);
|
||||||
if (!file.exists() || !file.isFile() || !filePath.endsWith(".rar")) {
|
if (!file.exists() || !file.isFile() || !filePath.endsWith(".rar")) {
|
||||||
log.warn("选择的文件无效或不是RAR文件: {}", filePath);
|
log.warn(LoggerHelper.DEBUG_MARKER, "选择的文件无效或不是RAR文件: {}", filePath);
|
||||||
result7TA.setText("所选文件不存在或不是一个有效的 .rar 文件");
|
result7TA.setText("所选文件不存在或不是一个有效的 .rar 文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("开始计算文件哈希值: {}", filePath);
|
log.info(LoggerHelper.DEBUG_MARKER, "开始计算文件哈希值: {}", filePath);
|
||||||
MD5HashCalculator hashCalculator = new MD5HashCalculator();
|
MD5HashCalculator hashCalculator = new MD5HashCalculator();
|
||||||
String hashResult = hashCalculator.calculateHash(file.toPath());
|
String hashResult = hashCalculator.calculateHash(file.toPath());
|
||||||
result7TA.setText("计算结果:\n" + hashResult);
|
result7TA.setText("计算结果:\n" + hashResult);
|
||||||
log.info("文件哈希值计算完成: {}", hashResult);
|
log.info(LoggerHelper.DEBUG_MARKER, "文件哈希值计算完成: {}", hashResult);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("计算文件哈希值时出错: {}", filePath, e);
|
log.error(LoggerHelper.DEBUG_MARKER, "计算文件哈希值时出错: {}", filePath, e);
|
||||||
result7TA.setText("计算哈希值时出错: " + e.getMessage());
|
result7TA.setText("计算哈希值时出错: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void onGenerateHF(ActionEvent event) {
|
void onGenerateHF(ActionEvent event) {
|
||||||
log.info("开始生成哈希列表文件");
|
log.info(LoggerHelper.DEBUG_MARKER, "开始生成哈希列表文件");
|
||||||
String folderPathsText = loadDigitalOutcomes.getText();
|
String folderPathsText = loadDigitalOutcomes.getText();
|
||||||
if (folderPathsText == null || folderPathsText.isEmpty()) {
|
if (folderPathsText == null || folderPathsText.isEmpty()) {
|
||||||
log.warn("未选择文件夹,无法生成哈希列表文件");
|
log.warn(LoggerHelper.DEBUG_MARKER, "未选择文件夹,无法生成哈希列表文件");
|
||||||
result7TA.setText("请先选择一个文件夹");
|
result7TA.setText("请先选择一个文件夹");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -159,13 +163,13 @@ public class StorageCarrierPaneController {
|
||||||
if (folder.exists() && folder.isDirectory()) {
|
if (folder.exists() && folder.isDirectory()) {
|
||||||
folders.add(folder);
|
folders.add(folder);
|
||||||
} else {
|
} else {
|
||||||
log.warn("选择的路径无效或不是文件夹: {}", path);
|
log.warn(LoggerHelper.DEBUG_MARKER, "选择的路径无效或不是文件夹: {}", path);
|
||||||
result7TA.setText("所选路径不存在或不是一个有效的文件夹: " + path);
|
result7TA.setText("所选路径不存在或不是一个有效的文件夹: " + path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = System.getFileChooser();
|
||||||
fileChooser.setTitle("选择保存哈希列表文件的位置");
|
fileChooser.setTitle("选择保存哈希列表文件的位置");
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", "*.csv"));
|
||||||
|
|
||||||
|
|
@ -176,7 +180,7 @@ public class StorageCarrierPaneController {
|
||||||
File outputFile = fileChooser.showSaveDialog(selectLoadDigitalOutcomes7B.getScene().getWindow());
|
File outputFile = fileChooser.showSaveDialog(selectLoadDigitalOutcomes7B.getScene().getWindow());
|
||||||
|
|
||||||
if (outputFile == null) {
|
if (outputFile == null) {
|
||||||
log.info("用户取消了文件保存操作");
|
log.info(LoggerHelper.DEBUG_MARKER, "用户取消了文件保存操作");
|
||||||
result7TA.setText("未选择保存位置");
|
result7TA.setText("未选择保存位置");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -189,13 +193,13 @@ public class StorageCarrierPaneController {
|
||||||
finalOutputFile = outputFile;
|
finalOutputFile = outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("选择的输出文件路径: {}", finalOutputFile.getAbsolutePath());
|
log.info(LoggerHelper.DEBUG_MARKER, "选择的输出文件路径: {}", finalOutputFile.getAbsolutePath());
|
||||||
|
|
||||||
// 创建后台任务
|
// 创建后台任务
|
||||||
Task<String> task = new Task<String>() {
|
Task<String> task = new Task<>() {
|
||||||
@Override
|
@Override
|
||||||
protected String call() throws Exception {
|
protected String call() throws Exception {
|
||||||
log.info("开始执行哈希文件生成任务");
|
log.info(LoggerHelper.DEBUG_MARKER, "开始执行哈希文件生成任务");
|
||||||
updateMessage("开始生成哈希文件...");
|
updateMessage("开始生成哈希文件...");
|
||||||
|
|
||||||
HashFileGenerator generator = new HashFileGenerator();
|
HashFileGenerator generator = new HashFileGenerator();
|
||||||
|
|
@ -209,11 +213,11 @@ public class StorageCarrierPaneController {
|
||||||
updateProgress(current, total);
|
updateProgress(current, total);
|
||||||
updateMessage("处理文件: " + current + "/" + total);
|
updateMessage("处理文件: " + current + "/" + total);
|
||||||
if (current % 500 == 0 || current == total) { // 每500个文件或完成时记录一次日志
|
if (current % 500 == 0 || current == total) { // 每500个文件或完成时记录一次日志
|
||||||
log.info("处理进度: {}/{}", current, total);
|
log.info(LoggerHelper.DEBUG_MARKER, "处理进度: {}/{}", current, total);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("哈希文件生成任务完成,输出文件: {}", finalOutputFile.getAbsolutePath());
|
log.info(LoggerHelper.DEBUG_MARKER, "哈希文件生成任务完成,输出文件: {}", finalOutputFile.getAbsolutePath());
|
||||||
return "哈希列表文件已生成: " + finalOutputFile.getAbsolutePath();
|
return "哈希列表文件已生成: " + finalOutputFile.getAbsolutePath();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -225,7 +229,7 @@ public class StorageCarrierPaneController {
|
||||||
|
|
||||||
// 任务成功完成
|
// 任务成功完成
|
||||||
task.setOnSucceeded(e -> {
|
task.setOnSucceeded(e -> {
|
||||||
log.info("哈希文件生成任务成功完成");
|
log.info(LoggerHelper.DEBUG_MARKER, "哈希文件生成任务成功完成");
|
||||||
result7TA.setText(task.getValue());
|
result7TA.setText(task.getValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -233,13 +237,13 @@ public class StorageCarrierPaneController {
|
||||||
task.setOnFailed(e -> {
|
task.setOnFailed(e -> {
|
||||||
Throwable exception = task.getException();
|
Throwable exception = task.getException();
|
||||||
String errorMsg = "生成哈希文件时出错: " + (exception != null ? exception.getMessage() : "未知错误");
|
String errorMsg = "生成哈希文件时出错: " + (exception != null ? exception.getMessage() : "未知错误");
|
||||||
log.error("哈希文件生成任务失败", exception);
|
log.error(LoggerHelper.RELEASE_MARKER, "哈希文件生成任务失败", exception);
|
||||||
result7TA.setText(errorMsg);
|
result7TA.setText(errorMsg);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 任务取消处理
|
// 任务取消处理
|
||||||
task.setOnCancelled(e -> {
|
task.setOnCancelled(e -> {
|
||||||
log.info("哈希文件生成任务被用户取消");
|
log.info(LoggerHelper.DEBUG_MARKER, "哈希文件生成任务被用户取消");
|
||||||
result7TA.setText("哈希文件生成操作已取消");
|
result7TA.setText("哈希文件生成操作已取消");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,23 @@ package top.r3944realms.docchecktoolrefactored.ui.task;
|
||||||
|
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.System;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.core.DuplicateFinder;
|
||||||
import top.r3944realms.docchecktoolrefactored.core.MD5HashCalculator;
|
import top.r3944realms.docchecktoolrefactored.core.MD5HashCalculator;
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.FileScanner;
|
import top.r3944realms.docchecktoolrefactored.core.ScanningException;
|
||||||
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
import top.r3944realms.docchecktoolrefactored.io.scanner.RobustParallelScanner;
|
||||||
|
import top.r3944realms.docchecktoolrefactored.model.DuplicateGroup;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DuplicateDocumentDetectionTask extends Task<String> {
|
public class DuplicateDocumentDetectionTask extends Task<String>{
|
||||||
private final String folderPath;
|
private final String folderPath;
|
||||||
private final MD5HashCalculator hashCalculator;
|
private final MD5HashCalculator hashCalculator;
|
||||||
private volatile RobustParallelScanner scanner;
|
private volatile RobustParallelScanner scanner;
|
||||||
|
|
@ -33,137 +28,166 @@ public class DuplicateDocumentDetectionTask extends Task<String> {
|
||||||
this.hashCalculator = new MD5HashCalculator();
|
this.hashCalculator = new MD5HashCalculator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String call() throws Exception {
|
protected String call() throws Exception {
|
||||||
updateMessage("正在初始化扫描...");
|
updateMessage("正在初始化扫描...");
|
||||||
|
|
||||||
|
|
||||||
Path rootPath = Paths.get(folderPath);
|
Path rootPath = Paths.get(folderPath);
|
||||||
if (!Files.exists(rootPath) || !Files.isDirectory(rootPath)) {
|
if (!Files.exists(rootPath) || !Files.isDirectory(rootPath)) {
|
||||||
throw new IllegalArgumentException("指定路径不是有效目录: " + folderPath);
|
throw new IllegalArgumentException("指定路径不是有效目录: " + folderPath);
|
||||||
}
|
}
|
||||||
|
// 创建带进度更新的扫描器
|
||||||
// 使用 RobustParallelScanner 和 MD5HashCalculator 进行并行扫描和哈希计算
|
|
||||||
Map<String, List<Path>> hashToFileMap = new ConcurrentHashMap<>();
|
|
||||||
AtomicInteger processed = new AtomicInteger(0);
|
|
||||||
AtomicReference<Exception> errorRef = new AtomicReference<>(null);
|
|
||||||
AtomicBoolean scanCompleted = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
// 使用 CountDownLatch 等待扫描完成
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
// 创建扫描器
|
|
||||||
scanner = new RobustParallelScanner(10);
|
scanner = new RobustParallelScanner(10);
|
||||||
|
|
||||||
// 异步启动扫描任务
|
// 创建带有进度监听的 DuplicateFinder
|
||||||
Thread scanThread = new Thread(() -> {
|
DuplicateFinder duplicateFinder = new DuplicateFinder(scanner, hashCalculator, true)
|
||||||
|
.applySetting(System.getSetting());
|
||||||
|
|
||||||
|
// 用于统计文件总数
|
||||||
|
AtomicInteger totalFiles = new AtomicInteger(0);
|
||||||
|
// 使用 RobustParallelScanner 和 MD5HashCalculator 进行并行扫描和哈希计算
|
||||||
|
// 设置进度回调
|
||||||
|
duplicateFinder.setProgressCallback(new DuplicateFinder.ProgressCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPhaseStarted(DuplicateFinder.Phase phase) {
|
||||||
|
switch (phase) {
|
||||||
|
case GROUP_BY_SIZE:
|
||||||
|
updateMessage("正在按文件大小分组...");
|
||||||
|
break;
|
||||||
|
case CALCULATE_HASH:
|
||||||
|
updateMessage("正在计算可能重复文件的哈希值...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPhaseProgress(DuplicateFinder.Phase phase, int current, int total) {
|
||||||
|
if (total > 0) {
|
||||||
|
updateProgress(current, total);
|
||||||
|
switch (phase) {
|
||||||
|
case GROUP_BY_SIZE:
|
||||||
|
totalFiles.set(total);
|
||||||
|
updateMessage(String.format("正在按文件大小分组: %d/%d", current, total));
|
||||||
|
break;
|
||||||
|
case CALCULATE_HASH:
|
||||||
|
updateMessage(String.format("正在计算哈希值: %d/%d", current, total));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPhaseCompleted(DuplicateFinder.Phase phase) {
|
||||||
|
switch (phase) {
|
||||||
|
case GROUP_BY_SIZE:
|
||||||
|
updateMessage("文件大小分组完成");
|
||||||
|
break;
|
||||||
|
case CALCULATE_HASH:
|
||||||
|
updateMessage("哈希值计算完成");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AtomicReference<List<DuplicateGroup>> resultRef = new AtomicReference<>();
|
||||||
|
List<Exception> errors = new CopyOnWriteArrayList<>();
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
// 在单独线程中执行查找
|
||||||
|
Thread findThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
scanner.scanWithProgress(rootPath, new FileScanner.ProgressAwareListener() {
|
List<DuplicateGroup> duplicates = duplicateFinder.findDuplicates(rootPath);
|
||||||
@Override
|
resultRef.set(duplicates);
|
||||||
public void onFileFound(Path file) {
|
|
||||||
if (isCancelled()) {
|
|
||||||
scanner.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
String hash = hashCalculator.calculatePartialHash(file);
|
|
||||||
hashToFileMap.computeIfAbsent(hash, k -> new ArrayList<>()).add(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// 记录无法计算哈希的文件,但不中断整个过程
|
|
||||||
updateMessage("警告: 无法处理文件 " + file.toString() + " - " + e.getMessage());
|
|
||||||
}
|
|
||||||
processed.incrementAndGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Path path, Exception e) {
|
|
||||||
// 记录错误但不中断扫描过程
|
|
||||||
errorRef.set(e);
|
|
||||||
updateMessage("扫描错误: " + path.toString() + " - " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onScanComplete() {
|
|
||||||
// 扫描完成
|
|
||||||
scanCompleted.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgressUpdate(int current, int total) {
|
|
||||||
if (isCancelled()) {
|
|
||||||
scanner.cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
updateProgress(current, total);
|
|
||||||
updateMessage("正在处理文件: " + current + "/" + total);
|
|
||||||
if (current % 500 == 0 || current == total) { // 每500个文件或完成时记录一次日志
|
|
||||||
log.info("处理进度: {}/{}", current, total);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
errorRef.set(e);
|
errors.add(e);
|
||||||
|
} finally {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scanThread.setDaemon(true);
|
findThread.setDaemon(true);
|
||||||
scanThread.start();
|
findThread.start();
|
||||||
|
|
||||||
// 等待扫描完成,设置更长的超时时间(例如5分钟)
|
// 等待扫描完成,设置超时时间(例如5分钟)
|
||||||
if (!latch.await(5*60, TimeUnit.MINUTES)) {
|
long totalTimeout = System.getSetting().getTotalTimeout();
|
||||||
|
if (!latch.await(totalTimeout, TimeUnit.SECONDS)) {
|
||||||
scanner.cancel();
|
scanner.cancel();
|
||||||
throw new TimeoutException("扫描超时(5分钟)");
|
throw new TimeoutException(String.format("扫描超时(%d秒)", totalTimeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否被取消
|
// 检查是否被取消
|
||||||
if (isCancelled()) {
|
long start = java.lang.System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
boolean finished = false;
|
||||||
|
while (!finished) {
|
||||||
|
// 每 200ms 等待一次 latch,避免忙等待
|
||||||
|
finished = latch.await(200, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// 检查是否被取消
|
||||||
|
if (isCancelled()) {
|
||||||
|
scanner.cancel();
|
||||||
|
return "操作已被取消";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否超时
|
||||||
|
if (java.lang.System.currentTimeMillis() - start > totalTimeout * 1000L) {
|
||||||
|
scanner.cancel();
|
||||||
|
throw new TimeoutException(String.format("扫描超时(%d秒)", totalTimeout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
scanner.cancel();
|
scanner.cancel();
|
||||||
return "操作已被取消";
|
Thread.currentThread().interrupt();
|
||||||
|
return "操作被中断";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有错误且扫描未完成,抛出异常
|
// 检查是否有错误
|
||||||
if (errorRef.get() != null && !scanCompleted.get()) {
|
if (!errors.isEmpty()) {
|
||||||
throw errorRef.get();
|
throw new ScanningException(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分析重复文件并构建结果
|
List<DuplicateGroup> duplicateGroups = resultRef.get();
|
||||||
updateMessage("正在分析重复文件...");
|
|
||||||
|
// 构建最终结果
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
result.append("重复文件检测结果:\n");
|
|
||||||
|
|
||||||
if (errorRef.get() != null) {
|
|
||||||
result.append("警告: 扫描过程中发生错误 - ").append(errorRef.get().getMessage()).append("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.append("总共处理 ").append(processed.get()).append(" 个文件\n");
|
// 计算总文件数(所有组中的文件数)
|
||||||
|
int totalDuplicateFiles = duplicateGroups.stream()
|
||||||
|
.mapToInt(group -> group.fileMetas().size())
|
||||||
|
.sum();
|
||||||
|
|
||||||
List<Map.Entry<String, List<Path>>> duplicateGroups = hashToFileMap.entrySet().stream()
|
int totalGroups = duplicateGroups.size();
|
||||||
.filter(entry -> entry.getValue().size() > 1)
|
|
||||||
.collect(Collectors.toList());
|
result.append(String.format("总共扫描文件数: %d\n", totalFiles.get()));
|
||||||
|
result.append(String.format("发现重复文件组数: %d\n", totalGroups));
|
||||||
|
result.append(String.format("重复文件总数: %d\n", totalDuplicateFiles));
|
||||||
|
|
||||||
if (!duplicateGroups.isEmpty()) {
|
if (!duplicateGroups.isEmpty()) {
|
||||||
result.append("有 ").append(duplicateGroups.size()).append(" 组重复文件\n\n");
|
result.append("\n详细重复文件信息:\n");
|
||||||
|
result.append("----------------------------------------\n");
|
||||||
|
|
||||||
int groupIndex = 1;
|
int groupIndex = 1;
|
||||||
for (Map.Entry<String, List<Path>> entry : duplicateGroups) {
|
for (DuplicateGroup group : duplicateGroups) {
|
||||||
result.append("第 ").append(groupIndex).append(" 组\t");
|
result.append(String.format("第 %d 组 (哈希值: %s, 大小: %d 字节)\n",
|
||||||
result.append("哈希值: ").append(entry.getKey()).append("\n");
|
groupIndex, group.hash(), group.size()));
|
||||||
|
|
||||||
int fileIndex = 1;
|
int fileIndex = 1;
|
||||||
for (Path file : entry.getValue()) {
|
for (var file : group.fileMetas()) {
|
||||||
result.append("文件名").append(fileIndex).append(": ").append(file.getFileName()).append("\t\t");
|
Path filePath = file.getPath();
|
||||||
result.append("文件路径").append(fileIndex).append(": ").append(file.toAbsolutePath()).append("\n");
|
result.append(String.format(" 文件%d: %s\n", fileIndex, filePath.toAbsolutePath()));
|
||||||
fileIndex++;
|
fileIndex++;
|
||||||
}
|
}
|
||||||
result.append("\n");
|
result.append("\n");
|
||||||
groupIndex++;
|
groupIndex++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.append("没有重复文件\n");
|
result.append("\n没有发现重复文件\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMessage("检测完成!");
|
result.append("检测完成!\n");
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package top.r3944realms.docchecktoolrefactored.ui.utils;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonBar;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
|
@ -18,14 +19,7 @@ public class DialogUtil {
|
||||||
* @return the boolean
|
* @return the boolean
|
||||||
*/
|
*/
|
||||||
public static boolean showExitConfirmation(Window owner) {
|
public static boolean showExitConfirmation(Window owner) {
|
||||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
return showConfirmationDialog("确认退出", "您确定要退出程序吗?", "请确认您的操作");
|
||||||
alert.initOwner(owner);
|
|
||||||
alert.setTitle("确认退出");
|
|
||||||
alert.setHeaderText("您确定要退出程序吗?");
|
|
||||||
alert.setContentText("请确认您的操作");
|
|
||||||
|
|
||||||
Optional<ButtonType> result = alert.showAndWait();
|
|
||||||
return result.isPresent() && result.get() == ButtonType.OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,8 +35,11 @@ public class DialogUtil {
|
||||||
alert.setTitle(title);
|
alert.setTitle(title);
|
||||||
alert.setHeaderText(header);
|
alert.setHeaderText(header);
|
||||||
alert.setContentText(content);
|
alert.setContentText(content);
|
||||||
|
ButtonType yesButton = new ButtonType("Yes", ButtonBar.ButtonData.YES);
|
||||||
|
ButtonType noButton = new ButtonType("No", ButtonBar.ButtonData.NO);
|
||||||
|
alert.getButtonTypes().setAll(yesButton, noButton);
|
||||||
Optional<ButtonType> result = alert.showAndWait();
|
Optional<ButtonType> result = alert.showAndWait();
|
||||||
return result.isPresent() && result.get() == ButtonType.OK;
|
return result.isPresent() && result.get() == yesButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package top.r3944realms.docchecktoolrefactored.ui.utils;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import javafx.stage.StageStyle;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进度条窗口工具类,支持取消按钮
|
||||||
|
*/
|
||||||
|
public class ProgressBarUtil {
|
||||||
|
|
||||||
|
private Stage progressStage;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private Label messageLabel;
|
||||||
|
private Button cancelButton;
|
||||||
|
private final AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||||
|
private Runnable onCancelCallback;
|
||||||
|
/**
|
||||||
|
* 显示进度条窗口
|
||||||
|
* @param ownerStage 父窗口
|
||||||
|
* @param title 窗口标题
|
||||||
|
* @param initialMessage 初始消息
|
||||||
|
*/
|
||||||
|
public void showProgress(Stage ownerStage, String title, String initialMessage) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
progressStage = new Stage();
|
||||||
|
progressStage.initOwner(ownerStage);
|
||||||
|
progressStage.initStyle(StageStyle.UTILITY);
|
||||||
|
progressStage.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
progressStage.setTitle(title);
|
||||||
|
progressStage.setResizable(false);
|
||||||
|
|
||||||
|
// 创建进度条
|
||||||
|
progressBar = new ProgressBar();
|
||||||
|
progressBar.setPrefWidth(300);
|
||||||
|
progressBar.setProgress(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) {
|
||||||
|
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;");
|
||||||
|
|
||||||
|
Scene scene = new Scene(root);
|
||||||
|
progressStage.setScene(scene);
|
||||||
|
progressStage.sizeToScene();
|
||||||
|
progressStage.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新进度和消息
|
||||||
|
* @param progress 进度值 (0.0 - 1.0)
|
||||||
|
* @param message 要显示的消息
|
||||||
|
*/
|
||||||
|
public void updateProgress(double progress, String message) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (progressBar != null) {
|
||||||
|
progressBar.setProgress(progress);
|
||||||
|
}
|
||||||
|
if (messageLabel != null) {
|
||||||
|
messageLabel.setText(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭进度条窗口
|
||||||
|
*/
|
||||||
|
public void closeProgress() {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (progressStage != null) {
|
||||||
|
progressStage.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已取消
|
||||||
|
*/
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return cancelled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速显示一个进度条窗口并执行任务
|
||||||
|
* @param ownerStage 父窗口
|
||||||
|
* @param title 窗口标题
|
||||||
|
* @param initialMessage 初始消息
|
||||||
|
* @param task 要执行的任务(可检查 isCancelled() 中途退出)
|
||||||
|
*/
|
||||||
|
public static void showAndExecute(Stage ownerStage, String title, String initialMessage, CancellableTask task) {
|
||||||
|
ProgressBarUtil progressBarUtil = new ProgressBarUtil();
|
||||||
|
progressBarUtil.showProgress(ownerStage, title, initialMessage);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
task.run(progressBarUtil);
|
||||||
|
} finally {
|
||||||
|
progressBarUtil.closeProgress();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface CancellableTask {
|
||||||
|
void run(ProgressBarUtil 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ public class FileUtil {
|
||||||
Path path = Paths.get(filePath).normalize();
|
Path path = Paths.get(filePath).normalize();
|
||||||
File file = path.toFile();
|
File file = path.toFile();
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
log.error("文件不存在: {}", filePath);
|
log.error(LoggerHelper.TRACE_MARKER, "文件不存在: {}", filePath);
|
||||||
throw new NoSuchFileException("文件不存在: " + filePath);
|
throw new NoSuchFileException("文件不存在: " + filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ public class FileUtil {
|
||||||
File file = path.toFile();
|
File file = path.toFile();
|
||||||
|
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
log.error("文件不存在: {}", filePath);
|
log.error(LoggerHelper.TRACE_MARKER, "文件不存在: {}", filePath);
|
||||||
throw new NoSuchFileException("文件不存在: " + filePath);
|
throw new NoSuchFileException("文件不存在: " + filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ public class FileUtil {
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (!supportedExtensions.contains(fileExtension)) {
|
if (!supportedExtensions.contains(fileExtension)) {
|
||||||
log.error("不支持的文件格式: {}", fileExtension);
|
log.error(LoggerHelper.TRACE_MARKER, "不支持的文件格式: {}", fileExtension);
|
||||||
throw new IllegalArgumentException("不支持的文件格式,预期: "
|
throw new IllegalArgumentException("不支持的文件格式,预期: "
|
||||||
+ supportedExtensions + ",实际: " + fileExtension);
|
+ supportedExtensions + ",实际: " + fileExtension);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package top.r3944realms.docchecktoolrefactored.util;
|
||||||
|
|
||||||
|
import org.slf4j.Marker;
|
||||||
|
import org.slf4j.MarkerFactory;
|
||||||
|
|
||||||
|
public class LoggerHelper {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
/* 默认Tab形状容器 */
|
/* 默认Tab形状容器 */
|
||||||
.tab .tab-container {
|
.tab .tab-container {
|
||||||
-fx-background-color: #13b72b;
|
-fx-background-color: #7BB0D9;
|
||||||
-fx-shape: "M0,5 L65,5 L75,15 L65,25 L0,25 L10,15 Z";
|
-fx-shape: "M0,5 L65,5 L75,15 L65,25 L0,25 L10,15 Z";
|
||||||
-fx-background-insets: 0;
|
-fx-background-insets: 0;
|
||||||
-fx-border-width: 0; /* 确保无边框 */
|
-fx-border-width: 0; /* 确保无边框 */
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:selected .tab-container {
|
.tab:selected .tab-container {
|
||||||
-fx-background-color: #9b0e0e; /* 只改变背景色 */
|
-fx-background-color: #0063b0; /* 只改变背景色 */
|
||||||
-fx-effect: null; /* 移除所有效果 */
|
-fx-effect: null; /* 移除所有效果 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
</padding>
|
</padding>
|
||||||
|
|
||||||
<top>
|
<top>
|
||||||
<Label alignment="CENTER" text="淮阴区数字化档案检查验收系统" textAlignment="CENTER" textFill="rgb(66,133,244)">
|
<Label alignment="CENTER" text="数字化验收工具" textAlignment="CENTER" textFill="rgb(66,133,244)" BorderPane.alignment="TOP_CENTER">
|
||||||
<font>
|
<font>
|
||||||
<Font name="Microsoft YaHei" size="24.0" />
|
<Font name="Microsoft YaHei" size="24.0" />
|
||||||
</font>
|
</font>
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,43 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Menu?>
|
||||||
|
<?import javafx.scene.control.MenuBar?>
|
||||||
|
<?import javafx.scene.control.MenuItem?>
|
||||||
|
<?import javafx.scene.control.SeparatorMenuItem?>
|
||||||
|
<?import javafx.scene.control.Tab?>
|
||||||
|
<?import javafx.scene.control.TabPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="1000.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.MainStageController">
|
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="1000.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.MainStageController">
|
||||||
<children>
|
<children>
|
||||||
<MenuBar prefWidth="2558.0" VBox.vgrow="ALWAYS">
|
<MenuBar prefWidth="2558.0" VBox.vgrow="ALWAYS">
|
||||||
<menus>
|
<menus>
|
||||||
<Menu mnemonicParsing="false" text="文件">
|
<Menu mnemonicParsing="false" text="文件">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem mnemonicParsing="false" text="关闭" />
|
<MenuItem fx:id="settingMI" mnemonicParsing="false" onAction="#onOpenSetting" text="设置" />
|
||||||
</items>
|
<SeparatorMenuItem mnemonicParsing="false" />
|
||||||
</Menu>
|
<MenuItem fx:id="logoutMI" mnemonicParsing="false" onAction="#onLogout" text="登出" />
|
||||||
<Menu mnemonicParsing="false" text="编辑">
|
<SeparatorMenuItem mnemonicParsing="false" />
|
||||||
<items>
|
<MenuItem fx:id="exitMI" mnemonicParsing="false" onAction="#onExit" text="退出" />
|
||||||
<MenuItem mnemonicParsing="false" text="未完成" />
|
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Menu fx:id="helpM" mnemonicParsing="false" text="帮助">
|
<Menu fx:id="helpM" mnemonicParsing="false" text="帮助">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem mnemonicParsing="false" text="关于" />
|
<MenuItem fx:id="helpDocMI" mnemonicParsing="false" onAction="#onOpenHelpDoc" text="帮助文档" />
|
||||||
|
<SeparatorMenuItem mnemonicParsing="false" />
|
||||||
|
<MenuItem fx:id="aboutSoftwareMI" mnemonicParsing="false" onAction="#onAbout" text="关于软件" />
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
</menus>
|
</menus>
|
||||||
</MenuBar>
|
</MenuBar>
|
||||||
<!-- 导入项目信息面板 -->
|
<!-- 导入项目信息面板 -->
|
||||||
<fx:include source="module/project-info-pane.fxml" VBox.vgrow="ALWAYS"/>
|
<fx:include source="module/project-info-pane.fxml" VBox.vgrow="ALWAYS" />
|
||||||
<!-- 导入项目内容面板 -->
|
<!-- 导入项目内容面板 -->
|
||||||
<TabPane stylesheets="@../css/custom-tab.css" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
<TabPane fx:id="tabPane" onKeyPressed="#handleKeyPressed" stylesheets="@../css/custom-tab.css" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
||||||
<tabs>
|
<tabs>
|
||||||
<Tab id="startTab" fx:id="step1T" text="1. 查重复文件">
|
<Tab id="startTab" fx:id="step1T" text="1. 查重复文件">
|
||||||
<content>
|
<content>
|
||||||
|
|
@ -66,5 +76,34 @@
|
||||||
</Tab>
|
</Tab>
|
||||||
</tabs>
|
</tabs>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
<HBox nodeOrientation="RIGHT_TO_LEFT" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="nextB" mnemonicParsing="false" onAction="#onNext" prefHeight="100.0" prefWidth="500.0" text="下一步">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="20.0" left="40.0" right="40.0" top="20.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="19.0" />
|
||||||
|
</font>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="prevB" mnemonicParsing="false" onAction="#onPrev" prefHeight="100.0" prefWidth="500.0" text="上一步">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="20.0" left="40.0" right="40.0" top="20.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="19.0" />
|
||||||
|
</font>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<?import javafx.scene.layout.RowConstraints?>
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.DuplicateDocumentPaneController">
|
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.DuplicateDocumentPaneController">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="288.0" minWidth="0.0" percentWidth="0.0" prefWidth="82.0" />
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="288.0" minWidth="0.0" percentWidth="0.0" prefWidth="82.0" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1263.9999633789064" minWidth="0.0" prefWidth="745.0" />
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1263.9999633789064" minWidth="0.0" prefWidth="745.0" />
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<RowConstraints maxHeight="592.6666666666666" prefHeight="581.3333536783855" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="592.6666666666666" prefHeight="581.3333536783855" vgrow="SOMETIMES" />
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<TextArea fx:id="result1B" editable="false" GridPane.columnSpan="3" GridPane.rowIndex="3">
|
<TextArea fx:id="result1B" editable="false" prefHeight="414.0" prefWidth="683.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
<Insets bottom="2.0" left="10.0" right="2.0" top="2.0" />
|
<Insets bottom="2.0" left="10.0" right="2.0" top="2.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</Button>
|
</Button>
|
||||||
<Label text="反馈结果:" GridPane.rowIndex="2">
|
<Label text="结果反馈:" GridPane.rowIndex="2">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets left="10.0" />
|
<Insets left="10.0" />
|
||||||
</GridPane.margin></Label>
|
</GridPane.margin></Label>
|
||||||
|
|
@ -71,6 +71,11 @@
|
||||||
<Insets left="10.0" />
|
<Insets left="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</Label>
|
</Label>
|
||||||
|
<Button fx:id="cancel1B" alignment="CENTER" mnemonicParsing="false" onAction="#onCancel" prefHeight="52.0" prefWidth="117.0" text="取消检查" GridPane.columnIndex="3" GridPane.rowIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Button>
|
||||||
</children>
|
</children>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<?import javafx.scene.layout.RowConstraints?>
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.PathCheckPaneController">
|
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.PathCheckPaneController">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="940.0" minWidth="0.0" percentWidth="0.0" prefWidth="104.0" />
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="940.0" minWidth="0.0" percentWidth="0.0" prefWidth="104.0" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="940.0" minWidth="10.0" percentWidth="0.0" prefWidth="104.0" />
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="940.0" minWidth="10.0" percentWidth="0.0" prefWidth="104.0" />
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.*?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.TextArea?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.text.*?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<AnchorPane prefHeight="8000.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17">
|
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<children>
|
<children>
|
||||||
<TextArea editable="false" text="工作内容: 对照《元数据检查登记表》(附件4)检查并登记数字化项目信息、技术环境及技术参数的完整性等情况。" wrapText="true" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
|
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" scrollLeft="1.0" text="工作内容: 对照《元数据检查登记表》(附件4)检查并登记数字化项目信息、技术环境及技术参数的完整性等情况。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
<font>
|
<font>
|
||||||
<Font size="18.0" />
|
<Font size="18.0" />
|
||||||
</font></TextArea>
|
</font></TextArea>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17">
|
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<children>
|
<children>
|
||||||
<TextArea editable="false" prefHeight="200.0" prefWidth="200.0" text="工作内容: ①检查档案管理系统或电子目录的挂接准确率(要求100%) ②逐件验证数字化成果与目录的关联性 ③结果填入《挂接检查登记表》(附件5)。" wrapText="true" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
|
<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">
|
||||||
<font>
|
<font>
|
||||||
<Font size="18.0" />
|
<Font size="18.0" />
|
||||||
</font></TextArea>
|
</font></TextArea>
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
|
<AnchorPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<children>
|
<children>
|
||||||
<TextArea editable="false" prefHeight="200.0" prefWidth="200.0" text="工作内容: 对照《工作记录检查登记表》(附件6)检查数字化工作台帐的规范性及与成果的一致性,并在表格中登记检查情况。" wrapText="true" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
|
<TextArea editable="false" prefHeight="800.0" prefWidth="1000.0" text="工作内容: 对照《工作记录检查登记表》(附件6)检查数字化工作台帐的规范性及与成果的一致性,并在表格中登记检查情况。" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||||
<font>
|
<font>
|
||||||
<Font size="18.0" />
|
<Font size="18.0" />
|
||||||
</font></TextArea>
|
</font></TextArea>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<?import javafx.scene.layout.RowConstraints?>
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.StorageCarrierPaneController">
|
<GridPane prefHeight="800.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.module.StorageCarrierPaneController">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="226.33331298828125" minWidth="10.0" percentWidth="10.0" prefWidth="108.33333333333334" />
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="226.33331298828125" minWidth="10.0" percentWidth="10.0" prefWidth="108.33333333333334" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="559.3333511352539" minWidth="10.0" percentWidth="40.0" prefWidth="373.00002034505206" />
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="559.3333511352539" minWidth="10.0" percentWidth="40.0" prefWidth="373.00002034505206" />
|
||||||
|
|
@ -18,11 +18,10 @@
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="500.0" minWidth="10.0" percentWidth="25.0" prefWidth="400.0" />
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="500.0" minWidth="10.0" percentWidth="25.0" prefWidth="400.0" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
<RowConstraints maxHeight="151.33334350585938" percentHeight="7.0" prefHeight="55.00001017252603" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="151.33334350585938" percentHeight="7.0" prefHeight="55.00001017252603" vgrow="NEVER" />
|
||||||
<RowConstraints maxHeight="407.0" percentHeight="7.0" prefHeight="73.33333841959634" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="508.33333333333326" percentHeight="7.0" prefHeight="73.99999491373697" vgrow="NEVER" />
|
||||||
<RowConstraints maxHeight="508.33333333333326" percentHeight="7.0" prefHeight="73.99999491373697" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="592.6666666666666" percentHeight="7.0" prefHeight="581.3333536783855" vgrow="NEVER" />
|
||||||
<RowConstraints maxHeight="591.6666666666667" percentHeight="7.0" prefHeight="43.33332316080731" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="1.7976931348623157E308" prefHeight="581.3333536783855" vgrow="SOMETIMES" />
|
||||||
<RowConstraints maxHeight="602.0000152587891" minHeight="10.0" prefHeight="534.6666615804037" vgrow="SOMETIMES" />
|
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<Label text="载入数字化成果:">
|
<Label text="载入数字化成果:">
|
||||||
|
|
@ -33,7 +32,7 @@
|
||||||
<Insets left="5.0" />
|
<Insets left="5.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</Label>
|
</Label>
|
||||||
<Label text="将档案目录、哈希值列表文件和检测过程文件打包制成打包制成“数字化验收检测包.rar”压缩包" GridPane.columnSpan="4" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
|
<Label text="将档案目录、哈希值列表文件和检测过程文件打包制成打包制成“数字化验收检测包.rar”压缩包" GridPane.columnSpan="5" GridPane.halignment="CENTER" GridPane.rowSpan="2" GridPane.valignment="CENTER">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
@ -41,7 +40,7 @@
|
||||||
<Font name="System Bold" size="14.0" />
|
<Font name="System Bold" size="14.0" />
|
||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<Label text="反馈结果:" GridPane.rowIndex="3">
|
<Label text="结果反馈:" GridPane.rowIndex="2">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
@ -49,7 +48,7 @@
|
||||||
<Insets left="5.0" />
|
<Insets left="5.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</Label>
|
</Label>
|
||||||
<Label text="载入压缩包:" GridPane.rowIndex="2">
|
<Label text="载入压缩包:" GridPane.rowIndex="1">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
@ -65,7 +64,7 @@
|
||||||
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</TextField>
|
</TextField>
|
||||||
<TextField fx:id="loadCompressedFile" GridPane.columnIndex="1" GridPane.rowIndex="2">
|
<TextField fx:id="loadCompressedFile" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
|
|
@ -73,7 +72,7 @@
|
||||||
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</TextField>
|
</TextField>
|
||||||
<TextArea fx:id="result7TA" editable="false" GridPane.columnSpan="3" GridPane.hgrow="ALWAYS" GridPane.rowIndex="4">
|
<TextArea fx:id="result7TA" editable="false" prefWidth="400.0" GridPane.columnSpan="3" GridPane.rowIndex="3">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
|
|
@ -103,7 +102,7 @@
|
||||||
<Font size="14.0" />
|
<Font size="14.0" />
|
||||||
</font>
|
</font>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="selectLoadCompressedFile7B" mnemonicParsing="false" onAction="#onSelectLC" text="选择文件" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
|
<Button fx:id="selectLoadCompressedFile7B" mnemonicParsing="false" onAction="#onSelectLC" text="选择文件" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
|
|
@ -114,7 +113,7 @@
|
||||||
<Font size="14.0" />
|
<Font size="14.0" />
|
||||||
</font>
|
</font>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="caculateHash7B" mnemonicParsing="false" onAction="#onCaculateHash" text="计算哈希值" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="CENTER">
|
<Button fx:id="caculateHash7B" mnemonicParsing="false" onAction="#onCaculateHash" text="计算哈希值" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="CENTER">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
|
|
@ -125,7 +124,7 @@
|
||||||
<Font size="14.0" />
|
<Font size="14.0" />
|
||||||
</font>
|
</font>
|
||||||
</Button>
|
</Button>
|
||||||
<TextArea editable="false" prefWidth="400.0" text="①对照《存储载体检查登记表》(附件7)检查并记录存储载体的类型/数量/内容/可读性情况。 ②将档案目录、哈希值列表文件和检测过程文件打包制成打包制成“数字化验收检测包.rar”压缩包 ③计算并验证压缩包的MD5或哈希值" wrapText="true" GridPane.columnIndex="3" GridPane.rowIndex="4">
|
<TextArea editable="false" maxWidth="1.7976931348623157E308" prefWidth="400.0" text="①对照《存储载体检查登记表》(附件7)检查并记录存储载体的类型/数量/内容/可读性情况。 ②将数字化成果(包括单页、多页文件及目录)打包生成"数字化验收检测包.rar"(含目录、哈希值列表、检测文件) ③计算并验证压缩包的MD5或哈希值" wrapText="true" GridPane.columnIndex="3" GridPane.rowIndex="3">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
|
|
@ -136,7 +135,7 @@
|
||||||
<Font size="14.0" />
|
<Font size="14.0" />
|
||||||
</font>
|
</font>
|
||||||
</TextArea>
|
</TextArea>
|
||||||
<Label text="工作内容:" GridPane.columnIndex="3" GridPane.rowIndex="3">
|
<Label text="工作内容:" GridPane.columnIndex="3" GridPane.rowIndex="2">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets left="10.0" />
|
<Insets left="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
|
|
|
||||||
83
src/main/resources/fxml/setting-view.fxml
Normal file
83
src/main/resources/fxml/setting-view.fxml
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Spinner?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<VBox xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.r3944realms.docchecktoolrefactored.ui.SettingDialogController">
|
||||||
|
<children>
|
||||||
|
<GridPane>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="138.0" minWidth="10.0" prefWidth="133.0" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="109.0" minWidth="10.0" prefWidth="94.0" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="73.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
<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">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Spinner>
|
||||||
|
<Label alignment="CENTER" contentDisplay="CENTER" prefHeight="28.0" prefWidth="332.0" text="设置" GridPane.columnSpan="3">
|
||||||
|
<font>
|
||||||
|
<Font size="21.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label text="秒" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||||
|
<Label text="秒" GridPane.columnIndex="2" GridPane.rowIndex="2" />
|
||||||
|
<CheckBox fx:id="enableStepCB" mnemonicParsing="false" onAction="#onSettingThree" text="启用步骤辅助" GridPane.columnSpan="3" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER" />
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
|
<HBox alignment="CENTER">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="resetB" mnemonicParsing="false" onAction="#onReset" text="恢复默认值" />
|
||||||
|
</children>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="CENTER" nodeOrientation="RIGHT_TO_LEFT">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="cancelB" mnemonicParsing="false" onAction="#onCancel" prefWidth="100.0" text="取消">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="saveB" mnemonicParsing="false" onAction="#onSave" prefWidth="100.0" text="保存">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
BIN
src/main/resources/img/icon.jpg
Normal file
BIN
src/main/resources/img/icon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
Loading…
Reference in New Issue
Block a user