diff --git a/exam-module-exam/exam-module-exam-biz/src/main/resources/mapper/exam/EducationPaperTaskMapper.xml b/exam-module-exam/exam-module-exam-biz/src/main/resources/mapper/exam/EducationPaperTaskMapper.xml
index 921fe7a7..e4a1472f 100644
--- a/exam-module-exam/exam-module-exam-biz/src/main/resources/mapper/exam/EducationPaperTaskMapper.xml
+++ b/exam-module-exam/exam-module-exam-biz/src/main/resources/mapper/exam/EducationPaperTaskMapper.xml
@@ -25,8 +25,6 @@
-
-
@@ -64,6 +62,7 @@
SELECT sp_id, parent_id, ancestors, sp_name, order_num
FROM knowledge_points
WHERE FIND_IN_SET(#{id}, ancestors)
+ and del_flag='0'
ORDER BY order_num;
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Browser/BrowserComtroller.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Browser/BrowserComtroller.java
new file mode 100644
index 00000000..1ba0adca
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Browser/BrowserComtroller.java
@@ -0,0 +1,29 @@
+package pc.exam.pp.module.judgement.controller.admin.Browser;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import pc.exam.pp.framework.common.pojo.CommonResult;
+import pc.exam.pp.module.judgement.controller.service.browser.IBrowserServerice;
+
+import java.io.IOException;
+
+@RestController
+@RequestMapping("/tool/Browser")
+
+public class BrowserComtroller {
+
+ @Autowired
+ private IBrowserServerice browserServerice;
+ /**
+ * 浏览器判分
+ * @return 得分
+ */
+ @PostMapping("/run_judgement")
+ public CommonResult run_C_code() throws IOException {
+ return CommonResult.success(browserServerice.Judgement());
+ }
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Mysql/MysqlController.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Mysql/MysqlController.java
new file mode 100644
index 00000000..39add505
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/Mysql/MysqlController.java
@@ -0,0 +1,28 @@
+package pc.exam.pp.module.judgement.controller.admin.Mysql;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import pc.exam.pp.framework.common.pojo.CommonResult;
+import pc.exam.pp.module.judgement.controller.service.mysql.IMysqlServerice;
+
+@RestController
+@RequestMapping("/tool/Mysql")
+public class MysqlController {
+
+
+
+ @Autowired
+ private IMysqlServerice mysqlServerice;
+
+ /**
+ * Mysql判分
+ * @return 得分
+ */
+ @PostMapping("/run_judgement")
+ public CommonResult run_C_code() {
+ return CommonResult.success(mysqlServerice.Judgement());
+ }
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/WindowFile/WindowsFileController.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/WindowFile/WindowsFileController.java
new file mode 100644
index 00000000..f700a551
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/admin/WindowFile/WindowsFileController.java
@@ -0,0 +1,44 @@
+package pc.exam.pp.module.judgement.controller.admin.WindowFile;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import pc.exam.pp.framework.common.pojo.CommonResult;
+import pc.exam.pp.module.judgement.controller.service.file.IFileServerice;
+
+import java.io.IOException;
+
+/**
+ * 文件操作题判分
+ * rwb
+ */
+
+@RestController
+@RequestMapping("/tool/File")
+public class WindowsFileController {
+
+ @Autowired
+ private IFileServerice fileServerice;
+ /**
+ * 得出文件操作考点
+ * @return 得分
+ */
+// @PostMapping("/get_filePoint")
+ //todo 后期需要素材文件和答案文件的 父目录 如:D:/exam/3/shucai,D:/exam/3/win
+ public CommonResult get_file_point() throws IOException {
+ return CommonResult.success(fileServerice.get_file_point());
+ }
+
+
+ /**
+ * 文件操作题判分
+ * @return 判分
+ */
+ @PostMapping("/run_file")
+ public CommonResult run_file_point() throws IOException {
+ //"权值得分比重"
+ return CommonResult.success(fileServerice.run_file_point());
+ }
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/browser/BrowserServericeImpl.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/browser/BrowserServericeImpl.java
new file mode 100644
index 00000000..e5a8291d
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/browser/BrowserServericeImpl.java
@@ -0,0 +1,108 @@
+package pc.exam.pp.module.judgement.controller.service.browser;
+
+import org.springframework.stereotype.Service;
+import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
+import pc.exam.pp.module.judgement.controller.utils.brower.BookmarkChecker;
+import pc.exam.pp.module.judgement.controller.utils.brower.BookmarkDeleter;
+import pc.exam.pp.module.judgement.controller.utils.file.GetDifferencesBetweenFolders;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class BrowserServericeImpl implements IBrowserServerice {
+ //考生试题文件夹
+ static final String BASE_DIR = "D:/exam/4/";
+ //谷歌浏览器
+ String chromeBookmarkPath = System.getenv("LOCALAPPDATA") + "\\Google\\Chrome\\User Data\\Default\\Bookmarks";
+ @Override
+ public String Judgement() throws IOException {
+ //根据题目,查找考点
+
+ //得到该试题的得分点
+ //Net得分点 文件类型 文件名 考点权值
+ // 添加到文件夹 1
+ // 添加到收藏夹 2
+ List answerList=new ArrayList<>();
+ answerList.add(new ExamQuestionAnswer("","","","","1", "文件名1", "1", "1"));
+ answerList.add(new ExamQuestionAnswer("","","","","1", "文件名2", "1", "2"));
+ answerList.add(new ExamQuestionAnswer("","","","","2", "百度一下,你就知道", "1", "3"));
+ //判断如果类型为1,为添加到文件夹的html,后缀加 .html
+ for (ExamQuestionAnswer answer : answerList) {
+ if ("1".equals(answer.getContent())) {
+ String fileName = answer.getContentIn();
+ if (!fileName.endsWith(".html")) {
+ answer.setContentIn(fileName + ".html");
+ }
+ }
+ }
+
+
+
+ //分为两点,1:文件夹-- 去考生文件夹去找 文件名
+ // 2:收藏夹--去浏览器去找 根据值 文件名 查找
+ // 找到后 —————— +权值 ,根据文件名删除
+
+
+
+ //根据路径,得到考生答题文件集合
+ Map stuFiles = GetDifferencesBetweenFolders.listFilesAndFoldersWithAttributes(Paths.get(BASE_DIR));
+
+ // 对比学生提交内容与试题得分点
+ //这里指挥判断存在文件夹的得分点
+ Integer studentScore = compareStuAndTestFiles(answerList, stuFiles);
+
+ //判断收藏夹得分点
+ for (ExamQuestionAnswer examQuestionAnswer : answerList) {
+ int currentScore = Integer.parseInt(examQuestionAnswer.getScoreRate()); // 当前得分
+
+ if ( examQuestionAnswer.getContent().equals("2")){
+ String bookmarkNameToDelete = examQuestionAnswer.getContentIn();
+ //检查收藏夹是否有书签
+ if (BookmarkChecker.bookmarkExists(chromeBookmarkPath, bookmarkNameToDelete)) {
+ //如果有 +权值
+ studentScore += currentScore;
+ System.out.println("📌 发现书签,准备删除...");
+ //删除此书签
+ BookmarkDeleter.deleteBookmarkByName(chromeBookmarkPath, bookmarkNameToDelete);
+ }
+ }
+
+ }
+ // 计算试题总分
+ int totalScore = answerList.stream()
+ .mapToInt(a -> Integer.parseInt(a.getScoreRate()))
+ .sum();
+ // 计算最终得分比例(保留两位小数)
+ double scoreRatio = totalScore == 0 ? 0 : (double) studentScore / totalScore;
+ String formattedScoreRatio = String.format("%.2f", scoreRatio);
+
+ return formattedScoreRatio;
+ }
+
+
+ // 对比学生提交内容与试题得分点
+ static Integer compareStuAndTestFiles(List answerList, Map stuFiles) {
+ int totalScore = 0; // 记录总得分
+ for (ExamQuestionAnswer answer : answerList) {
+ String filePath = answer.getContentIn(); // 试题文件路径
+ int currentScore = Integer.parseInt(answer.getScoreRate()); // 当前得分
+ boolean isCorrect = false;
+ // 如果学生提交中存在该文件,则得分
+ isCorrect = stuFiles.containsKey(filePath);
+ // 如果正确,则累加总分
+ if (isCorrect) {
+ totalScore += currentScore;
+ System.out.println(answer.getContentIn() + " -> 得分权值:" + answer.getScoreRate()+"-> 是否得分:"+isCorrect);
+ }else {
+ System.out.println(answer.getContentIn() + " -> 得分权值:" + answer.getScoreRate()+"-> 是否得分:"+isCorrect);
+
+ }
+ }
+ //返回累加的得分点
+ return totalScore;
+ }
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/browser/IBrowserServerice.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/browser/IBrowserServerice.java
new file mode 100644
index 00000000..eb6b260a
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/browser/IBrowserServerice.java
@@ -0,0 +1,8 @@
+package pc.exam.pp.module.judgement.controller.service.browser;
+
+import java.io.IOException;
+
+public interface IBrowserServerice {
+ String Judgement() throws IOException;
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/file/FileServericeImpl.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/file/FileServericeImpl.java
new file mode 100644
index 00000000..a0c3fa85
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/file/FileServericeImpl.java
@@ -0,0 +1,126 @@
+package pc.exam.pp.module.judgement.controller.service.file;
+
+import org.springframework.stereotype.Service;
+import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
+import pc.exam.pp.module.judgement.controller.utils.file.GetDifferencesBetweenFolders;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+
+@Service
+public class FileServericeImpl implements IFileServerice {
+ static final String BASE_DIR = "D:/exam/3/";
+ @Override
+ public List get_file_point() throws IOException {
+ List answerList = new ArrayList<>();
+ AtomicInteger sortCounter = new AtomicInteger(1); // 计数器
+ String path1 = BASE_DIR + "shucai";
+ String path2 = BASE_DIR + "win";
+
+ // 获取 shucai 和 win 文件夹的差异
+ Map differences = GetDifferencesBetweenFolders.getDifferencesBetweenFolders(path1, path2);
+
+ List formattedDifferences = differences.entrySet().stream()
+ .map(entry -> {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ ExamQuestionAnswer answer = new ExamQuestionAnswer();
+ answer.setContent(key); // 设置文件路径
+ answer.setScoreRate("1");
+ //这里设置answer的排序,按照循环次数来
+ answer.setSort(String.valueOf(sortCounter.getAndIncrement())); // 按顺序设置排序值
+ if (value.startsWith("仅存在于 "+path1)) {
+ answer.setContentIn("考察删除");
+ } else if (value.startsWith("仅存在于 "+path2)) {
+ answer.setContentIn("考察名称");
+ } else if (value.startsWith("属性不同")) {
+ answer.setContentIn("考察属性");
+// answer.setContent(key + " -> " + value.split(" vs ")[1]); // 设置属性信息
+ }
+ return answer;
+ })
+ .sorted(Comparator.comparingInt(answer -> Integer.parseInt(answer.getSort()))) // 按 sort 排序
+ .collect(Collectors.toList());
+ answerList.addAll(formattedDifferences);
+
+ return answerList;
+ }
+
+ @Override
+ public String run_file_point() throws IOException {
+ //todo 得到学生的考题答案
+ List answerList=new ArrayList<>();
+ answerList.add(new ExamQuestionAnswer("","","","","HGACYL\\RLQM.MEM", "考察删除", "1", "1"));
+ answerList.add(new ExamQuestionAnswer("","","","","TING\\XYU\\AUTOE.BAT", "考察删除", "1", "2"));
+ answerList.add(new ExamQuestionAnswer("","","","","AHEWL\\KMENS", "考察名称", "1", "3"));
+ answerList.add(new ExamQuestionAnswer("","","","","EDZK\\RONGHE.COM", "考察名称", "1", "4"));
+ answerList.add(new ExamQuestionAnswer("","","","","HGACYL\\PLAY.MEM", "考察名称", "1", "5"));
+ answerList.add(new ExamQuestionAnswer("","","","","WUE\\PB6.txt", "考察名称", "1", "6"));
+ String stuPath = BASE_DIR + "stu";
+ // 获取 stu 文件夹的内容
+ Map stuFiles = GetDifferencesBetweenFolders.listFilesAndFoldersWithAttributes(Paths.get( stuPath));
+
+ // 输出学生提交的内容
+ System.out.println("\n=== 学生提交内容(stu 目录) ===");
+ stuFiles.forEach((key, value) -> System.out.println(key + " -> " + value));
+ System.out.println("\n=== 学生提交内容得分点 ===");
+ // 对比学生提交内容与试题得分点
+ Integer studentScore = compareStuAndTestFiles(answerList, stuFiles);
+
+ //获取answerList里的所有sorcerate,和integer相除得到一个小于等于1的数
+ // 计算试题总分
+ int totalScore = answerList.stream()
+ .mapToInt(a -> Integer.parseInt(a.getScoreRate()))
+ .sum();
+ // 计算最终得分比例(保留两位小数)
+ double scoreRatio = totalScore == 0 ? 0 : (double) studentScore / totalScore;
+ String formattedScoreRatio = String.format("%.2f", scoreRatio);
+
+ return formattedScoreRatio;
+ }
+
+
+ // 对比学生提交内容与试题得分点
+ static Integer compareStuAndTestFiles(List answerList, Map stuFiles) {
+ int totalScore = 0; // 记录总得分
+ for (ExamQuestionAnswer answer : answerList) {
+ String filePath = answer.getContent(); // 试题文件路径
+ String checkType = answer.getContentIn(); // 考察类型(考察删除 / 考察名称 / 考察属性)
+ int currentScore = Integer.parseInt(answer.getScoreRate()); // 当前得分
+ boolean isCorrect = false;
+
+ if ("考察删除".equals(checkType)) {
+ // 如果学生提交中不存在该文件,则得分
+ isCorrect = !stuFiles.containsKey(filePath);
+ } else if ("考察名称".equals(checkType)) {
+ // 如果学生提交中存在该文件,则得分
+ isCorrect = stuFiles.containsKey(filePath);
+ } else if ("考察属性".equals(checkType)) {
+ // 如果学生提交中存在该文件,且属性匹配,则得分
+ if (stuFiles.containsKey(filePath)) {
+ String studentAttrs = stuFiles.get(filePath); // 学生提交的属性
+ String expectedAttrs = answer.getScoreRate(); // 试题中期望的属性
+ isCorrect = studentAttrs.equals(expectedAttrs);
+ }
+ }
+ // 如果正确,则累加总分
+ if (isCorrect) {
+ totalScore += currentScore;
+ System.out.println(answer.getContent() + " -> " + answer.getContentIn() + " -> 得分权值:" + answer.getScoreRate()+"-> 是否得分:"+isCorrect);
+ }else {
+ System.out.println(answer.getContent() + " -> " + answer.getContentIn() + " -> 得分权值:" + answer.getScoreRate()+"-> 是否得分:"+isCorrect);
+
+ }
+ }
+
+ //返回累加的总分
+ return totalScore;
+ }
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/file/IFileServerice.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/file/IFileServerice.java
new file mode 100644
index 00000000..89068a13
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/file/IFileServerice.java
@@ -0,0 +1,17 @@
+package pc.exam.pp.module.judgement.controller.service.file;
+
+
+import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface IFileServerice {
+
+
+ List get_file_point() throws IOException;
+
+ String run_file_point() throws IOException;
+
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/mysql/IMysqlServerice.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/mysql/IMysqlServerice.java
new file mode 100644
index 00000000..868d380e
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/mysql/IMysqlServerice.java
@@ -0,0 +1,8 @@
+package pc.exam.pp.module.judgement.controller.service.mysql;
+
+
+public interface IMysqlServerice {
+ int Judgement();
+
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/mysql/MysqlServericeImpl.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/mysql/MysqlServericeImpl.java
new file mode 100644
index 00000000..17808542
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/service/mysql/MysqlServericeImpl.java
@@ -0,0 +1,280 @@
+package pc.exam.pp.module.judgement.controller.service.mysql;
+
+import org.springframework.stereotype.Service;
+import pc.exam.pp.module.judgement.controller.utils.Mysql.MySQLExporterUtil;
+import pc.exam.pp.module.judgement.controller.utils.Mysql.SQLComparatorUtil;
+import pc.exam.pp.module.judgement.controller.utils.Mysql.SqlFileProcessor;
+
+import java.awt.*;
+import java.io.*;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.*;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class MysqlServericeImpl implements IMysqlServerice {
+ List backupFiles;
+ String databaseName;
+ @Override
+ public int Judgement() {
+ backupFiles = new ArrayList<>(); // 用于存储生成的备份文件路径
+ //todo
+ //根据考生考试的试卷id,来获取答案文件路径、 原始 数据库SQL 文件路径
+ // 文件路径
+ String filePath = "C:\\Users\\20373\\Desktop\\202504211120_mysql\\71\\MYS_002_114\\结果素材\\结果素材\\Teacher\\答案.txt"; // 答案文件路径
+
+ String answerPath = "C:\\Users\\20373\\Desktop\\202504211120_mysql\\71\\MYS_002_114\\考试素材\\考试素材\\Student\\db\\db_hrm.sql"; // 原始 SQL 文件路径
+
+ // 生成随机数据库名,临时使用
+ databaseName = "db_" + UUID.randomUUID().toString().replace("-", "").substring(0, 6);
+
+ // 连接到 MySQL 默认数据库
+ String url = "jdbc:mysql://localhost:3306/mysql?useSSL=false&serverTimezone=Asia/Shanghai";
+ String user = "root";
+ String password = "123";
+ int similarityRatio = 0;
+ try {
+ // **连接到 MySQL 默认数据库,创建新的数据库**
+ try (Connection conn = DriverManager.getConnection(url, user, password);
+ Statement stmt = conn.createStatement()) {
+ String createDbSql = "CREATE DATABASE " + databaseName;
+ stmt.executeUpdate(createDbSql);
+ System.out.println("已创建数据库:" + databaseName);
+ }
+
+ // **建立连接到新创建的数据库**
+ String newDbUrl = "jdbc:mysql://localhost:3306/" + databaseName + "?useSSL=false&serverTimezone=Asia/Shanghai";
+
+ // 解析 SQL 文件
+ Map> sqlMap = SqlFileProcessor.processSqlFile(filePath);
+ //把select语句和其他语句分开
+ List selectStatements = sqlMap.getOrDefault("SELECT", new ArrayList<>());
+ List otherStatements = sqlMap.getOrDefault("OTHER", new ArrayList<>());
+
+ // **通过命令行执行 SQL 文件**
+ boolean sqlFileExecuted = executeSqlFileUsingCommandLine(answerPath, databaseName, user, password);
+
+
+ // 执行完 SQL 后,进行数据库导出
+ MySQLExporterUtil exporter = new MySQLExporterUtil(databaseName, user, password, "backup_0");
+ exporter.export();
+
+
+ // 如果 SQL 文件执行成功,执行其他 SQL 语句
+ if (sqlFileExecuted) {
+ executeOtherSqlStatements(otherStatements, newDbUrl, user, password);
+ }
+ //todo 后面改成试题数据库,这里是完整正确答案数据库
+ //执行完所有的sql语句后,把结果数据库导出来
+ MySQLExporterUtil exporterDataBase = new MySQLExporterUtil(databaseName, user, password, databaseName);
+ exporterDataBase.export();
+ // 导出的结果数据库文件路径
+ String exportedDatabaseFilePath = "./" + databaseName + ".sql";
+ //String exportedDatabaseFilePath = "C:\\Users\\20373\\Desktop\\exam-master\\exam-api\\"+"db_bfd3b5bc"+".sql";
+
+ // 计算与第一个文件的比较结果
+ similarityRatio = compareBackupFiles(exportedDatabaseFilePath, backupFiles);
+
+// // 输出匹配比例
+ System.out.println("匹配比例个数: " + similarityRatio);
+
+ //删除临时创建的数据库databaseName
+ try (Connection conn = DriverManager.getConnection(newDbUrl, user, password);
+ Statement stmt = conn.createStatement()) {
+ String dropDbSql = "DROP DATABASE " + databaseName;
+ stmt.executeUpdate(dropDbSql);
+ System.out.println("已删除数据库:" + databaseName);
+ }
+ //比较select语句正确个数
+ //后期直接根据考生文件路径和试卷答案路径,来比对select文件
+ String filepath1 = "C:\\Users\\20373\\Desktop\\1.txt";
+ String filepath2 = "C:\\Users\\20373\\Desktop\\2.txt";
+
+ boolean isEquivalent = SQLComparatorUtil.compareSQLResults(filepath1, filepath2);
+ if (isEquivalent) {
+ ++similarityRatio;
+ }
+
+
+ } catch (IOException | SQLException e) {
+ e.printStackTrace();
+ }
+ return similarityRatio;
+ }
+
+ /**
+ * 通过命令行执行 SQL 文件
+ */
+ private static boolean executeSqlFileUsingCommandLine(String filePath, String databaseName, String user, String password) throws IOException {
+ // 构建 MySQL 命令
+ String command = String.format("mysql -u %s -p%s %s < %s", user, password, databaseName, filePath);
+
+ // 使用 ProcessBuilder 执行命令
+ ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", command);
+ processBuilder.inheritIO(); // 将输入输出重定向到当前控制台
+ Process process = processBuilder.start();
+
+ try {
+ // 等待命令执行完成
+ int exitCode = process.waitFor();
+ if (exitCode == 0) {
+ System.out.println("SQL 文件执行成功:" + filePath);
+ return true; // 返回 true 表示执行成功
+ } else {
+ System.err.println("SQL 文件执行失败:" + filePath);
+ return false; // 返回 false 表示执行失败
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 执行其他 SQL 语句
+ */
+ private void executeOtherSqlStatements(List otherStatements, String dbUrl, String user, String password) throws SQLException {
+ int fileName = 1; // 初始化 fileName
+ String outputPath = "./"; // 备份文件存放路径
+
+ backupFiles.add(outputPath+ "backup_" + 0 + ".sql");
+ try (Connection conn = DriverManager.getConnection(dbUrl, user, password);
+ Statement stmt = conn.createStatement()) {
+
+ for (String sql : otherStatements) {
+ String trimmedSql = sql.trim();
+ if (!trimmedSql.isEmpty() && !trimmedSql.startsWith("--")) { // 忽略空行和注释
+ try {
+ stmt.executeUpdate(trimmedSql); // 执行 SQL 语句
+ System.out.println("成功执行 SQL: " + trimmedSql);
+
+ // 执行完 SQL 后,进行数据库导出
+ String backupFile = outputPath + "backup_" + fileName + ".sql";
+ MySQLExporterUtil exporter = new MySQLExporterUtil(databaseName, user, password, "backup_" + fileName);
+ exporter.export();
+
+ // 将备份文件路径添加到备份文件列表中
+ backupFiles.add(backupFile);
+ fileName++; // 每执行一次 SQL 语句,fileName 递增
+ } catch (SQLException e) {
+ System.err.println("执行 SQL 失败: " + trimmedSql);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // 所有 SQL 执行完后,反向顺序进行备份文件比较
+ for (int i = backupFiles.size() -1; i > 0; i--) {
+ String currentBackupFile = backupFiles.get(i);
+ String previousBackupFile = backupFiles.get(i - 1);
+ filterBackupFile(previousBackupFile, currentBackupFile); // 过滤并保留差异部分
+ }
+
+ }
+ System.out.println("所有 SQL 语句执行完成");
+ }
+
+
+ /**
+ * 过滤 `backup_X.sql`,只保留与 `backup_0.sql` 不同的 SQL 语句(按 `;` 进行断句)
+ */
+ public static void filterBackupFile(String baseFile, String newFile) {
+ try {
+ Set baseSqlSet = readSqlStatements(baseFile);
+ List newSqlList = readSqlStatements(newFile).stream()
+ .filter(sql -> !baseSqlSet.contains(sql)) // 只保留 `backup_0.sql` 中没有的 SQL
+ .collect(Collectors.toList());
+
+ // 重新写入 `backup_X.sql`,仅包含不同的 SQL 语句
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(newFile))) {
+ for (String sql : newSqlList) {
+ writer.write(sql + ";");
+ writer.newLine();
+ }
+ }
+ System.out.println("已过滤 " + newFile + ",仅保留与 " + baseFile + " 不同的 SQL 语句");
+ } catch (IOException e) {
+ System.err.println("过滤文件失败:" + newFile);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 比较备份文件与导出的数据库备份之间的差异,计算匹配比例
+ */
+ public static int compareBackupFiles(String exportedDatabaseFilePath, List backupFiles) {
+ // 读取导出的数据库备份文件并按 SQL 语句进行分割
+ Set exportedSqlStatements = readSqlStatements(exportedDatabaseFilePath);
+
+ // 用于存储符合条件的文件数量
+ int matchedCount = 0;
+
+ // 遍历其他备份文件进行比较
+ for (int i = 1; i < backupFiles.size(); i++) {
+ String currentFilePath = backupFiles.get(i);
+
+ // 读取当前备份文件并按 SQL 语句进行分割
+ Set currentSqlStatements = readSqlStatements(currentFilePath);
+
+ // 输出当前备份文件中的所有 SQL 语句
+ System.out.println("文件 " + currentFilePath + " 中的 SQL 语句:");
+ for (String sql : currentSqlStatements) {
+ System.out.println(sql);
+ }
+
+ // 判断当前备份文件中的每条 SQL 语句是否都在导出的数据库备份中
+ boolean allMatched = true; // 假设所有 SQL 语句都匹配
+
+ for (String currentSql : currentSqlStatements) {
+ if (!exportedSqlStatements.contains(currentSql)) {
+ // 如果有任何一条 SQL 语句不在导出的数据库备份中,则设置 allMatched 为 false
+ allMatched = false;
+ System.out.println("未匹配的 SQL 语句: " + currentSql); // 输出未匹配的 SQL 语句
+ }
+ }
+
+ // 如果所有 SQL 语句都匹配,则计数器增加
+ if (allMatched) {
+ matchedCount++;
+ }
+
+ // 输出当前文件的匹配率
+
+ System.out.println("文件 " + currentFilePath + " 与导出数据库的匹配结果: " + allMatched);
+ }
+
+ // 返回匹配的文件数量
+ return matchedCount;
+ }
+
+
+ /**
+ * 读取 SQL 文件并按 `;` 进行分割,返回去重后的 SQL 语句集合
+ */
+ private static Set readSqlStatements(String filePath) {
+ StringBuilder content = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ String trimmedLine = line.trim();
+ if (!trimmedLine.isEmpty() && !trimmedLine.startsWith("--")) { // 忽略空行和注释
+ content.append(trimmedLine).append(" "); // 以空格拼接,防止换行影响 SQL 语句完整性
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("读取文件失败:" + filePath);
+ e.printStackTrace();
+ }
+
+ // 按 `;` 进行分割,并去重
+ return Arrays.stream(content.toString().split(";"))
+ .map(String::trim)
+ .filter(sql -> !sql.isEmpty())
+ .collect(Collectors.toSet());
+ }
+
+ }
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/MySQLExporterUtil.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/MySQLExporterUtil.java
new file mode 100644
index 00000000..18d32196
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/MySQLExporterUtil.java
@@ -0,0 +1,123 @@
+package pc.exam.pp.module.judgement.controller.utils.Mysql;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.sql.*;
+
+
+public class MySQLExporterUtil {
+ private final String databaseName;
+ private final String jdbcUrl;
+ private final String user;
+ private final String password;
+ private final String outputFile;
+
+ public MySQLExporterUtil(String databaseName, String user, String password, String fileName) {
+ this.databaseName = databaseName;
+ this.jdbcUrl = "jdbc:mysql://localhost:3306/" + databaseName + "?useSSL=false&serverTimezone=Asia/Shanghai";
+ this.user = user;
+ this.password = password;
+ this.outputFile = fileName + ".sql";
+ }
+
+ public void export() {
+ File file = new File("");
+ File parentDir = file.getParentFile();
+ if (parentDir != null && !parentDir.exists()) {
+ parentDir.mkdirs(); // 自动创建父级目录
+ }
+ try (Connection conn = DriverManager.getConnection(jdbcUrl, user, password);
+ FileWriter writer = new FileWriter(outputFile)) {
+
+ switchDatabase(conn);
+ exportTables(conn, writer);
+ exportViews(conn, writer);
+
+ System.out.println("导出成功!文件路径: " + outputFile);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void switchDatabase(Connection conn) throws SQLException {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute("USE " + databaseName);
+ }
+ }
+
+ private void exportTables(Connection conn, FileWriter writer) throws SQLException, IOException {
+ DatabaseMetaData meta = conn.getMetaData();
+ try (ResultSet tables = meta.getTables(databaseName, null, "%", new String[]{"TABLE"})) {
+ while (tables.next()) {
+ String tableName = tables.getString("TABLE_NAME");
+
+ writer.write(getCreateTableSQL(conn, tableName) + ";\n\n");
+ exportTableData(conn, tableName, writer);
+ }
+ }
+ }
+
+ private String getCreateTableSQL(Connection conn, String tableName) throws SQLException {
+ try (Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SHOW CREATE TABLE " + tableName)) {
+
+ // 获取 CREATE TABLE 语句
+ String createTableSQL = rs.next() ? rs.getString("Create Table") : "";
+
+ // 移除 AUTO_INCREMENT 部分
+ createTableSQL = createTableSQL.replaceAll("AUTO_INCREMENT=\\d+", "");
+
+ // 移除 ENGINE, CHARSET 和 ROW_FORMAT 部分
+ createTableSQL = createTableSQL.replaceAll("ENGINE=[^ ]+|DEFAULT CHARSET=[^ ]+|ROW_FORMAT=[^ ]+", "");
+
+ // 清理多余的空格
+ createTableSQL = createTableSQL.trim();
+
+ return createTableSQL;
+ }
+ }
+
+
+
+ private void exportTableData(Connection conn, String tableName, FileWriter writer) throws SQLException, IOException {
+ String query = "SELECT * FROM " + tableName;
+ try (Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(query)) {
+ ResultSetMetaData metaData = rs.getMetaData();
+ int columnCount = metaData.getColumnCount();
+
+ while (rs.next()) {
+ StringBuilder sb = new StringBuilder("INSERT INTO " + tableName + " (");
+ for (int i = 1; i <= columnCount; i++) {
+ sb.append(metaData.getColumnName(i)).append(i < columnCount ? ", " : ") VALUES (");
+ }
+ for (int i = 1; i <= columnCount; i++) {
+ sb.append(rs.getString(i) == null ? "NULL" : "'" + rs.getString(i).replace("'", "''") + "'")
+ .append(i < columnCount ? ", " : ");\n");
+ }
+ writer.write(sb.toString());
+ }
+ }
+ }
+
+ private void exportViews(Connection conn, FileWriter writer) throws SQLException, IOException {
+ DatabaseMetaData meta = conn.getMetaData();
+ try (ResultSet views = meta.getTables(databaseName, null, "%", new String[]{"VIEW"})) {
+ while (views.next()) {
+ String viewName = views.getString("TABLE_NAME");
+
+ writer.write(getCreateViewSQL(conn, viewName) + ";\n\n");
+ }
+ }
+ }
+
+ private String getCreateViewSQL(Connection conn, String viewName) throws SQLException {
+ try (Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SHOW CREATE VIEW " + viewName)) {
+ return rs.next() ? rs.getString("Create View") : "";
+ }
+ }
+
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/SQLComparatorUtil.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/SQLComparatorUtil.java
new file mode 100644
index 00000000..eb142a11
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/SQLComparatorUtil.java
@@ -0,0 +1,149 @@
+package pc.exam.pp.module.judgement.controller.utils.Mysql;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * SQL 查询结果比较工具类
+ */
+public class SQLComparatorUtil {
+
+ /**
+ * 比较两个 SQL 文件中的查询结果
+ *
+ * @param filepath1 SQL 文件 1 的路径
+ * @param filepath2 SQL 文件 2 的路径
+ * @return 是否等价
+ */
+ public static boolean compareSQLResults(String filepath1, String filepath2) throws IOException, SQLException {
+ // 读取 SQL 语句
+ String sql1 = readSQLFromFile(filepath1);
+ String sql2 = readSQLFromFile(filepath2);
+
+ System.out.println("SQL 1: " + sql1);
+ System.out.println("SQL 2: " + sql2);
+
+ // 执行 SQL 查询
+ List> result1 = executeQuery(sql1);
+ List> result2 = executeQuery(sql2);
+
+ // 打印查询结果
+ System.out.println("执行 SQL 1 的查询结果:");
+ printResult(result1);
+
+ System.out.println("\n执行 SQL 2 的查询结果:");
+ printResult(result2);
+
+ // 比较查询结果
+ return compareResults(result1, result2);
+ }
+
+ /**
+ * 读取 SQL 文件中的内容
+ */
+ private static String readSQLFromFile(String filePath) throws IOException {
+ StringBuilder sql = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sql.append(line).append(" ");
+ }
+ }
+ return sql.toString().trim();
+ }
+
+ /**
+ * 执行 SQL 查询并返回结果
+ */
+ private static List> executeQuery(String sql) throws SQLException {
+ List> result = new ArrayList<>();
+ try (Connection conn = DriverManager.getConnection(
+ "jdbc:mysql://localhost:3306/test2?useSSL=false&serverTimezone=Asia/Shanghai",
+ "root", "123");
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(sql)) {
+
+ // 获取列数和列名
+ ResultSetMetaData metaData = rs.getMetaData();
+ int columnCount = metaData.getColumnCount();
+
+ // 获取列名
+ List columnNames = new ArrayList<>();
+ for (int i = 1; i <= columnCount; i++) {
+ columnNames.add(metaData.getColumnLabel(i));
+ }
+ result.add(columnNames); // 将列名添加为结果的第一行
+
+ // 遍历结果集
+ while (rs.next()) {
+ List row = new ArrayList<>();
+ for (int i = 1; i <= columnCount; i++) {
+ row.add(rs.getString(i));
+ }
+ result.add(row);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 打印查询结果
+ */
+ private static void printResult(List> result) {
+ if (result.isEmpty()) {
+ System.out.println("查询结果为空");
+ } else {
+ for (int i = 0; i < result.size(); i++) {
+ List row = result.get(i);
+ if (i == 0) { // 第一次打印是列名
+ System.out.println("列名:");
+ }
+ for (String value : row) {
+ System.out.print(value + "\t");
+ }
+ System.out.println();
+ }
+ }
+ }
+
+ /**
+ * 比较两个 SQL 查询的结果
+ */
+ private static boolean compareResults(List> result1, List> result2) {
+ if (result1.isEmpty() || result2.isEmpty()) {
+ return result1.isEmpty() && result2.isEmpty();
+ }
+
+ // 获取列名并比较(顺序无关)
+ Set columnSet1 = new HashSet<>(result1.get(0));
+ Set columnSet2 = new HashSet<>(result2.get(0));
+ if (!columnSet1.equals(columnSet2)) {
+ return false;
+ }
+
+ // 获取数据行(去除列名)
+ List> rows1 = result1.subList(1, result1.size());
+ List> rows2 = result2.subList(1, result2.size());
+
+ // 使用 Set 存储每行数据并比较
+ Set> rowSet1 = new HashSet<>();
+ for (List row : rows1) {
+ rowSet1.add(new HashSet<>(row));
+ }
+
+ Set> rowSet2 = new HashSet<>();
+ for (List row : rows2) {
+ rowSet2.add(new HashSet<>(row));
+ }
+
+ // 比较行数据(无顺序)
+ return rowSet1.equals(rowSet2);
+ }
+
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/SqlFileProcessor.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/SqlFileProcessor.java
new file mode 100644
index 00000000..2ea14655
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/Mysql/SqlFileProcessor.java
@@ -0,0 +1,73 @@
+package pc.exam.pp.module.judgement.controller.utils.Mysql;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 工具类:把答案文件按照 select语句和其他语句取出
+ */
+public class SqlFileProcessor {
+
+ public static Map> processSqlFile(String filePath) throws IOException {
+ List sqlStatements = readSQLFromFile(filePath);
+ List selectStatements = new ArrayList<>();
+ List otherStatements = new ArrayList<>();
+
+ for (String sql : sqlStatements) {
+ if (sql.trim().toUpperCase().startsWith("SELECT")) {
+ selectStatements.add(sql);
+ } else {
+ otherStatements.add(sql);
+ }
+ }
+
+ Map> result = new HashMap<>();
+ result.put("SELECT", selectStatements);
+ result.put("OTHER", otherStatements);
+
+ return result;
+ }
+
+ private static List readSQLFromFile(String filePath) throws IOException {
+ List sqlStatements = new ArrayList<>();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"))) {
+ StringBuilder sqlStatement = new StringBuilder();
+ String line;
+ boolean insideBlockComment = false;
+
+ while ((line = reader.readLine()) != null) {
+ line = line.trim();
+
+ if (line.contains("/*")) {
+ insideBlockComment = true;
+ line = line.split("/\\*")[0].trim();
+ }
+ if (line.contains("*/")) {
+ insideBlockComment = false;
+ line = line.split("\\*/")[1].trim();
+ }
+
+ if (line.startsWith("--") || line.isEmpty() || insideBlockComment) {
+ continue;
+ }
+
+ sqlStatement.append(line).append(" ");
+ if (line.endsWith(";")) {
+ sqlStatements.add(sqlStatement.toString().trim());
+ sqlStatement.setLength(0);
+ }
+ }
+
+ if (sqlStatement.length() > 0) {
+ sqlStatements.add(sqlStatement.toString().trim());
+ }
+ }
+ return sqlStatements;
+ }
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/brower/BookmarkChecker.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/brower/BookmarkChecker.java
new file mode 100644
index 00000000..d899b5b3
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/brower/BookmarkChecker.java
@@ -0,0 +1,77 @@
+package pc.exam.pp.module.judgement.controller.utils.brower;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import java.io.FileReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * 检查书签是否存在工具类
+ */
+public class BookmarkChecker {
+
+ /**
+ * 检查书签是否存在
+ *
+ * @param bookmarkFile 书签文件路径
+ * @param bookmarkName 要检查的书签名称
+ * @return 是否存在该书签
+ */
+ public static boolean bookmarkExists(String bookmarkFile, String bookmarkName) {
+ Path path = Paths.get(bookmarkFile);
+ if (!Files.exists(path)) {
+ System.out.println("❌ 没有找到书签文件!");
+ return false;
+ }
+
+ JsonObject root;
+ try (FileReader reader = new FileReader(bookmarkFile)) {
+ root = JsonParser.parseReader(reader).getAsJsonObject();
+ } catch (Exception e) {
+ System.err.println("❌ 读取书签文件失败:" + e.getMessage());
+ return false;
+ }
+
+ JsonObject roots = root.getAsJsonObject("roots");
+
+ if (roots.has("bookmark_bar")) {
+ if (searchBookmark(roots.getAsJsonObject("bookmark_bar"), bookmarkName)) {
+ return true;
+ }
+ }
+ if (roots.has("other")) {
+ if (searchBookmark(roots.getAsJsonObject("other"), bookmarkName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // 递归查找书签
+ private static boolean searchBookmark(JsonObject node, String nameToFind) {
+ if (!node.has("children")) return false;
+
+ JsonArray children = node.getAsJsonArray("children");
+ for (JsonElement element : children) {
+ JsonObject child = element.getAsJsonObject();
+ String name = child.get("name").getAsString();
+ String type = child.get("type").getAsString();
+
+ if ("url".equals(type) && name.equals(nameToFind)) {
+ return true;
+ } else if ("folder".equals(type) && child.has("children")) {
+ if (searchBookmark(child, nameToFind)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
+
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/brower/BookmarkDeleter.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/brower/BookmarkDeleter.java
new file mode 100644
index 00000000..534d37b1
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/brower/BookmarkDeleter.java
@@ -0,0 +1,88 @@
+package pc.exam.pp.module.judgement.controller.utils.brower;
+
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * 删除指定书签工具类
+ */
+public class BookmarkDeleter {
+
+ /**
+ * 删除指定书签
+ *
+ * @param bookmarkFile 书签文件路径
+ * @param bookmarkNameToDelete 要删除的书签名称
+ * @return 是否删除成功
+ */
+ public static boolean deleteBookmarkByName(String bookmarkFile, String bookmarkNameToDelete) {
+ Path path = Paths.get(bookmarkFile);
+ if (!Files.exists(path)) {
+ System.out.println("❌ 没有找到书签文件!");
+ return false;
+ }
+
+ JsonObject root;
+ try (FileReader reader = new FileReader(bookmarkFile)) {
+ root = JsonParser.parseReader(reader).getAsJsonObject();
+ } catch (Exception e) {
+ System.err.println("❌ 读取书签文件失败:" + e.getMessage());
+ return false;
+ }
+
+ JsonObject roots = root.getAsJsonObject("roots");
+ boolean deleted = false;
+
+ if (roots.has("bookmark_bar")) {
+ deleted |= deleteUrlBookmark(roots.getAsJsonObject("bookmark_bar"), bookmarkNameToDelete);
+ }
+ if (roots.has("other")) {
+ deleted |= deleteUrlBookmark(roots.getAsJsonObject("other"), bookmarkNameToDelete);
+ }
+
+ if (deleted) {
+ try (FileWriter writer = new FileWriter(bookmarkFile)) {
+ new GsonBuilder().setPrettyPrinting().create().toJson(root, writer);
+ } catch (Exception e) {
+ System.err.println("❌ 写入书签文件失败:" + e.getMessage());
+ return false;
+ }
+ System.out.println("✅ 成功删除书签: " + bookmarkNameToDelete);
+ } else {
+ System.out.println("⚠️ 未找到该书签: " + bookmarkNameToDelete);
+ }
+
+ return deleted;
+ }
+
+ // 私有方法:只删除普通网页书签(type = url),支持递归
+ private static boolean deleteUrlBookmark(JsonObject node, String nameToDelete) {
+ if (!node.has("children")) return false;
+
+ JsonArray children = node.getAsJsonArray("children");
+ boolean deleted = false;
+
+ for (int i = children.size() - 1; i >= 0; i--) {
+ JsonObject child = children.get(i).getAsJsonObject();
+ String name = child.get("name").getAsString();
+ String type = child.get("type").getAsString();
+
+ if ("url".equals(type) && name.equals(nameToDelete)) {
+ children.remove(i);
+ deleted = true;
+ } else if ("folder".equals(type) && child.has("children")) {
+ deleted |= deleteUrlBookmark(child, nameToDelete); // 递归
+ }
+ }
+
+ return deleted;
+ }
+}
diff --git a/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/file/GetDifferencesBetweenFolders.java b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/file/GetDifferencesBetweenFolders.java
new file mode 100644
index 00000000..fd354ae8
--- /dev/null
+++ b/exam-module-judgement/exam-module-judgement-biz/src/main/java/pc/exam/pp/module/judgement/controller/utils/file/GetDifferencesBetweenFolders.java
@@ -0,0 +1,91 @@
+package pc.exam.pp.module.judgement.controller.utils.file;
+
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 获取文件夹差异工具类
+ */
+public class GetDifferencesBetweenFolders {
+
+ // 获取两个文件夹的差异
+ public static Map getDifferencesBetweenFolders( String folder1, String folder2) throws IOException {
+
+ Path path1 = Paths.get(folder1);
+ Path path2 = Paths.get( folder2);
+
+ Map files1 = listFilesAndFoldersWithAttributes(path1);
+ Map files2 = listFilesAndFoldersWithAttributes(path2);
+
+ Map differences = new LinkedHashMap<>();
+
+ // 找出仅存在于 folder1 的文件
+ for (String file : files1.keySet()) {
+ if (!files2.containsKey(file)) {
+ differences.put(file, "仅存在于 " + folder1);
+ } else {
+ String file1Attributes = files1.get(file);
+ String file2Attributes = files2.get(file);
+ if (!file1Attributes.equals(file2Attributes)) {
+ differences.put(file, "属性不同: " + file1Attributes + " vs " + file2Attributes);
+ }
+ }
+ }
+
+ // 找出仅存在于 folder2 的文件
+ for (String file : files2.keySet()) {
+ if (!files1.containsKey(file)) {
+ differences.put(file, "仅存在于 " + folder2);
+ }
+ }
+
+ return differences;
+ }
+
+
+
+
+
+ // 列出文件夹下的所有文件及其属性
+ public static Map listFilesAndFoldersWithAttributes(Path folder) throws IOException {
+ if (!Files.exists(folder)) return Collections.emptyMap();
+ try (Stream stream = Files.walk(folder)) {
+ return stream.collect(Collectors.toMap(
+ path -> folder.relativize(path).toString(),
+ GetDifferencesBetweenFolders::getFileAttributes,
+ (a, b) -> a,
+ LinkedHashMap::new
+ ));
+ }
+ }
+
+ // 在 getFileAttributes 方法中,只返回文件的属性,如果是文件夹则返回特殊标识
+ static String getFileAttributes(Path path) {
+ try {
+ if (Files.isDirectory(path)) {
+ return "文件夹"; // 特别标记为文件夹
+ } else {
+ BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
+ boolean isHidden = Files.isHidden(path);
+ boolean isReadable = Files.isReadable(path);
+ boolean isWritable = Files.isWritable(path);
+ return String.format("大小: %dB, 隐藏: %b, 可读: %b, 可写: %b",
+ attrs.size(), isHidden, isReadable, isWritable);
+ }
+ } catch (IOException e) {
+ return "无法获取属性";
+ }
+ }
+
+
+
+}