Accept Merge Request #142: (hyc -> master)
Merge Request: 【修改】mysql出题后端 Created By: @华允传 Accepted By: @华允传 URL: https://g-iswv8783.coding.net/p/education/d/pengchen-exam-java/git/merge/142
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package pc.exam.pp.module.judgement.controller.service.getpoints;
|
||||
|
||||
import com.alibaba.excel.write.metadata.RowData;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -41,7 +42,10 @@ import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -183,16 +187,590 @@ public class ExamGetPointsServiceImpl implements ExamGetPointsService{
|
||||
|
||||
@Override
|
||||
public List<String> get_mysql_point(PointsVo pointsVo) throws IOException {
|
||||
|
||||
// 获取平台文件参数
|
||||
ConfigDO config = configService.getConfigByKey("file_down_path");
|
||||
String sthPath = ZipUtil.downloadStudentFile(pointsVo.getShucaiPath(), config.getValue());
|
||||
String answerPath = ZipUtil.downloadStudentFile(pointsVo.getAnswerPath(), config.getValue());
|
||||
File zipStu = new File(sthPath);
|
||||
File zipAnswer = new File(answerPath);
|
||||
String stuFilePath = ZipUtil.unzipToNamedFolder(sthPath);
|
||||
String answerFilePath = ZipUtil.unzipToNamedFolder(answerPath);
|
||||
File zipStuFile = new File(stuFilePath);
|
||||
File zipAnswerFile = new File(answerFilePath);
|
||||
// 读取文件内容
|
||||
String stuSqlContent = readFileContentAutoCharset(getFirstMydFilePath(stuFilePath));
|
||||
String answerSqlContent = readFileContentAutoCharset(getFirstMydFilePath(answerFilePath));
|
||||
|
||||
String sthPath = ZipUtil.downloadStudentFile(pointsVo.getAnswerPath(), config.getValue());
|
||||
List<String> sqlStatements = readSQLFromFile(sthPath);
|
||||
File zip_file = new File(sthPath);
|
||||
zip_file.delete();
|
||||
return sqlStatements;
|
||||
// 解析SQL语句
|
||||
Map<String, TableInfo> stuTables = parseCreateTableStatements(stuSqlContent);
|
||||
Map<String, TableInfo> answerTables = parseCreateTableStatements(answerSqlContent);
|
||||
|
||||
Map<String, List<RowData>> stuInserts = parseInsertStatements(stuSqlContent);
|
||||
Map<String, List<RowData>> answerInserts = parseInsertStatements(answerSqlContent);
|
||||
|
||||
List<String> resultSqls = new ArrayList<>();
|
||||
// ✅ 直接提取视图语句
|
||||
List<String> answerViews = extractCreateViewStatements(answerSqlContent);
|
||||
resultSqls.addAll(answerViews);
|
||||
// 1. 处理CREATE TABLE和ALTER TABLE差异
|
||||
// 你之前的代码段里:处理CREATE TABLE和ALTER TABLE差异的地方
|
||||
for (String tableName : answerTables.keySet()) {
|
||||
TableInfo answerTable = answerTables.get(tableName);
|
||||
TableInfo stuTable = stuTables.get(tableName);
|
||||
|
||||
if (stuTable == null) {
|
||||
// 学生表没有,直接返回完整答案建表语句
|
||||
resultSqls.add(answerTable.originalCreateSql);
|
||||
} else {
|
||||
// 学生表有,调用compareTableStructures对比并返回对应的SQL
|
||||
List<String> diffSqls = compareTableStructures(stuTable, answerTable);
|
||||
resultSqls.addAll(diffSqls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 2. 处理INSERT差异
|
||||
for (String tableName : answerInserts.keySet()) {
|
||||
List<RowData> stuRows = stuInserts.getOrDefault(tableName, Collections.emptyList());
|
||||
List<RowData> answerRows = answerInserts.get(tableName);
|
||||
|
||||
// 建立主键索引(假设主键已通过create table解析得到,下面简化处理)
|
||||
List<String> primaryKeys = answerTables.containsKey(tableName) ? answerTables.get(tableName).primaryKeys : Collections.emptyList();
|
||||
|
||||
TableInfo stuTableInfo = stuTables.get(tableName);
|
||||
if (stuTableInfo == null) {
|
||||
// 如果学生表不存在,使用答案表结构
|
||||
stuTableInfo = answerTables.get(tableName);
|
||||
if (stuTableInfo == null) {
|
||||
throw new IllegalStateException("找不到表结构: " + tableName);
|
||||
}
|
||||
}
|
||||
|
||||
// 假设你已经有这个多条合并的map函数和合并函数
|
||||
Map<String, List<RowData>> stuRowsMapMulti = mapRowsByPrimaryKeyMulti(stuRows, primaryKeys, stuTableInfo);
|
||||
Map<String, List<RowData>> answerRowsMapMulti = mapRowsByPrimaryKeyMulti(answerRows, primaryKeys, stuTableInfo);
|
||||
|
||||
// 答案有,学生没有,合并生成一条 insert 语句
|
||||
List<RowData> insertRows = new ArrayList<>();
|
||||
if (stuRowsMapMulti == null || stuRowsMapMulti.isEmpty()) {
|
||||
// 学生表完全为空,把答案表全加进去
|
||||
for (List<RowData> rows : answerRowsMapMulti.values()) {
|
||||
insertRows.addAll(rows);
|
||||
}
|
||||
} else {
|
||||
// 否则按主键逐条比较
|
||||
for (String pk : answerRowsMapMulti.keySet()) {
|
||||
if (!stuRowsMapMulti.containsKey(pk)) {
|
||||
insertRows.addAll(answerRowsMapMulti.get(pk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量合并生成一条 insert
|
||||
if (!insertRows.isEmpty()) {
|
||||
TableInfo tableInfo = stuTables.get(tableName);
|
||||
if (tableInfo == null) {
|
||||
tableInfo = answerTables.get(tableName);
|
||||
}
|
||||
|
||||
if (tableInfo != null) {
|
||||
List<String> columns = new ArrayList<>(tableInfo.columns.keySet());
|
||||
String batchInsertSql = buildBatchInsert(tableName, columns, insertRows);
|
||||
resultSqls.add(batchInsertSql);
|
||||
}
|
||||
}
|
||||
// 学生有,答案没有,返回删除语句
|
||||
for (String pk : stuRowsMapMulti.keySet()) {
|
||||
if (!answerRowsMapMulti.containsKey(pk)) {
|
||||
List<RowData> stuRowsList = stuRowsMapMulti.get(pk);
|
||||
RowData mergedStuRow = mergeRowData(stuRowsList);
|
||||
String deleteSql = generateDeleteSql(tableName, primaryKeys, mergedStuRow, stuTables.get(tableName));
|
||||
resultSqls.add(deleteSql);
|
||||
}
|
||||
}
|
||||
|
||||
// 两边都有主键,内容不同,返回update语句
|
||||
for (String pk : answerRowsMapMulti.keySet()) {
|
||||
if (stuRowsMapMulti.containsKey(pk)) {
|
||||
List<RowData> stuRowsList = stuRowsMapMulti.get(pk);
|
||||
List<RowData> answerRowsList = answerRowsMapMulti.get(pk);
|
||||
|
||||
RowData mergedStuRow = mergeRowData(stuRowsList);
|
||||
RowData mergedAnswerRow = mergeRowData(answerRowsList);
|
||||
|
||||
if (!mergedStuRow.equalsContent(mergedAnswerRow)) {
|
||||
String updateSql = generateUpdateSql(tableName, primaryKeys, mergedAnswerRow, mergedStuRow, stuTables.get(tableName));
|
||||
if (!updateSql.isEmpty()) {
|
||||
resultSqls.add(updateSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3. 从answerFilePath目录下读取所有.sql文件内容,加入resultSqls
|
||||
Path answerDir = Paths.get(answerFilePath);
|
||||
if (Files.exists(answerDir) && Files.isDirectory(answerDir)) {
|
||||
try (Stream<Path> paths = Files.walk(answerDir)) {
|
||||
paths.filter(p -> Files.isRegularFile(p) && p.toString().toLowerCase().endsWith(".sql"))
|
||||
.forEach(sqlFile -> {
|
||||
try {
|
||||
String sql = Files.readString(sqlFile);
|
||||
if (sql != null && !sql.trim().isEmpty()) {
|
||||
resultSqls.add(sql.trim());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 读取某文件失败,记录日志或忽略
|
||||
System.err.println("读取SQL文件失败:" + sqlFile + ",原因:" + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
zipStu.delete();
|
||||
zipAnswer.delete();
|
||||
deleteFolder(zipStuFile);
|
||||
deleteFolder(zipAnswerFile);
|
||||
return resultSqls;
|
||||
}
|
||||
public static class TableInfo {
|
||||
String tableName;
|
||||
String originalCreateSql;
|
||||
LinkedHashMap<String, ColumnInfo> columns = new LinkedHashMap<>();
|
||||
List<String> primaryKeys = new ArrayList<>();
|
||||
}
|
||||
public static Set<Integer> diffFields(List<String> values1, List<String> values2) {
|
||||
Set<Integer> diffIndexes = new HashSet<>();
|
||||
int size = Math.min(values1.size(), values2.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!Objects.equals(values1.get(i), values2.get(i))) {
|
||||
diffIndexes.add(i);
|
||||
}
|
||||
}
|
||||
return diffIndexes;
|
||||
}
|
||||
|
||||
|
||||
public static class ColumnInfo {
|
||||
String name;
|
||||
String typeDefinition;
|
||||
boolean isPrimaryKey;
|
||||
}
|
||||
/**
|
||||
* 获取目录下第一个 .myd 文件绝对路径
|
||||
*/
|
||||
private String getFirstMydFilePath(String dirPath) {
|
||||
File dir = new File(dirPath, "db");
|
||||
if (dir.exists() && dir.isDirectory()) {
|
||||
File[] files = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".myd"));
|
||||
if (files != null && files.length > 0) {
|
||||
return files[0].getAbsolutePath();
|
||||
}
|
||||
}
|
||||
return dirPath; // fallback
|
||||
}
|
||||
|
||||
private Map<String, TableInfo> parseCreateTableStatements(String sqlContent) {
|
||||
Map<String, TableInfo> tableMap = new HashMap<>();
|
||||
|
||||
// 提取所有 CREATE TABLE ... 语句
|
||||
Pattern createTablePattern = Pattern.compile(
|
||||
"CREATE TABLE\\s+`?(\\w+)`?\\s*\\((.*?)\\)\\s*ENGINE\\s*=.*?;",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL
|
||||
);
|
||||
|
||||
|
||||
Matcher matcher = createTablePattern.matcher(sqlContent);
|
||||
|
||||
while (matcher.find()) {
|
||||
String tableName = matcher.group(1);
|
||||
String columnsSection = matcher.group(2);
|
||||
String originalSql = matcher.group(0);
|
||||
|
||||
TableInfo tableInfo = new TableInfo();
|
||||
tableInfo.tableName = tableName;
|
||||
tableInfo.originalCreateSql = originalSql;
|
||||
|
||||
// 按行解析字段和主键
|
||||
String[] lines = columnsSection.split(",\\s*\\n|,\\s*`"); // 支持多行或紧凑字段
|
||||
for (String line : lines) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
if (line.toUpperCase().startsWith("PRIMARY KEY")) {
|
||||
// 提取主键
|
||||
Pattern pkPattern = Pattern.compile("\\(([^)]+)\\)");
|
||||
Matcher pkMatcher = pkPattern.matcher(line);
|
||||
if (pkMatcher.find()) {
|
||||
String pkCols = pkMatcher.group(1);
|
||||
for (String pk : pkCols.replace("`", "").split(",")) {
|
||||
tableInfo.primaryKeys.add(pk.trim());
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("`")) {
|
||||
// 普通字段
|
||||
String[] parts = line.split("\\s+", 3);
|
||||
String columnName = parts[0].replace("`", "");
|
||||
String typeDefinition = parts.length > 1 ? parts[1] : "VARCHAR(255)";
|
||||
|
||||
ColumnInfo columnInfo = new ColumnInfo();
|
||||
columnInfo.name = columnName;
|
||||
columnInfo.typeDefinition = typeDefinition;
|
||||
columnInfo.isPrimaryKey = tableInfo.primaryKeys.contains(columnName);
|
||||
|
||||
tableInfo.columns.put(columnName, columnInfo);
|
||||
}
|
||||
}
|
||||
|
||||
tableMap.put(tableName, tableInfo);
|
||||
}
|
||||
|
||||
return tableMap;
|
||||
}
|
||||
private List<String> extractCreateViewStatements(String sqlContent) {
|
||||
List<String> views = new ArrayList<>();
|
||||
Pattern createViewPattern = Pattern.compile(
|
||||
"CREATE\\s+(?:ALGORITHM\\s*=\\s*.*?\\s+)?VIEW\\s+`?(\\w+)`?\\s+AS\\s+.*?;",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL
|
||||
);
|
||||
|
||||
Matcher viewMatcher = createViewPattern.matcher(sqlContent);
|
||||
while (viewMatcher.find()) {
|
||||
views.add(viewMatcher.group(0).trim());
|
||||
}
|
||||
return views;
|
||||
}
|
||||
|
||||
private Map<String, List<RowData>> parseInsertStatements(String sqlContent) {
|
||||
Map<String, List<RowData>> tableInserts = new HashMap<>();
|
||||
|
||||
// 只支持标准 INSERT INTO `table` VALUES (...) 语法
|
||||
Pattern insertPattern = Pattern.compile(
|
||||
"INSERT INTO\\s+`?(\\w+)`?\\s+VALUES\\s*(\\(.*?\\));",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL
|
||||
);
|
||||
Matcher matcher = insertPattern.matcher(sqlContent);
|
||||
|
||||
while (matcher.find()) {
|
||||
String tableName = matcher.group(1);
|
||||
String valuesSection = matcher.group(2);
|
||||
String originalSql = matcher.group(0);
|
||||
|
||||
// 拆分 values
|
||||
List<String> valuesList = parseValuesSection(valuesSection);
|
||||
|
||||
RowData rowData = new RowData(valuesList, originalSql);
|
||||
tableInserts.computeIfAbsent(tableName, k -> new ArrayList<>()).add(rowData);
|
||||
}
|
||||
|
||||
return tableInserts;
|
||||
}
|
||||
// 多条同主键数据按字段合并,非空优先
|
||||
public RowData mergeRowData(List<RowData> rows) {
|
||||
if (rows == null || rows.isEmpty()) return null;
|
||||
int size = rows.get(0).values.size();
|
||||
List<String> mergedValues = new ArrayList<>(Collections.nCopies(size, null));
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (RowData row : rows) {
|
||||
String val = row.values.get(i);
|
||||
if (val != null && !val.trim().isEmpty()) {
|
||||
mergedValues.set(i, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new RowData(mergedValues);
|
||||
}
|
||||
|
||||
// 按主键分组,返回 Map<String, List<RowData>>
|
||||
public static Map<String, List<RowData>> mapRowsByPrimaryKeyMulti(List<RowData> rows, List<String> primaryKeys, TableInfo table) {
|
||||
Map<String, List<RowData>> map = new HashMap<>();
|
||||
List<String> columnsInOrder = new ArrayList<>(table.columns.keySet());
|
||||
for (RowData row : rows) {
|
||||
StringBuilder keySb = new StringBuilder();
|
||||
for (String pk : primaryKeys) {
|
||||
int idx = columnsInOrder.indexOf(pk);
|
||||
if (idx >= 0 && idx < row.values.size()) {
|
||||
keySb.append(row.values.get(idx)).append("|");
|
||||
}
|
||||
}
|
||||
String key = keySb.toString();
|
||||
map.computeIfAbsent(key, k -> new ArrayList<>()).add(row);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单解析 VALUES 部分,按逗号拆分,去除引号、空格等
|
||||
*/
|
||||
private List<String> parseValuesSection(String valuesSection) {
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
String content = valuesSection.trim();
|
||||
if (content.startsWith("(") && content.endsWith(")")) {
|
||||
content = content.substring(1, content.length() - 1);
|
||||
}
|
||||
|
||||
// 粗略拆分(假设值中不包含逗号;实际情况复杂时用 SQL 解析器)
|
||||
String[] parts = content.split(",(?=(?:[^']*'[^']*')*[^']*$)"); // 支持逗号分隔且忽略单引号包裹的逗号
|
||||
for (String part : parts) {
|
||||
values.add(part.trim());
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public List<String> compareTableStructures(TableInfo stuTable, TableInfo answerTable) {
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
// 先判断答案表是否有学生表没有的字段
|
||||
boolean hasExtraColumn = false;
|
||||
for (String col : answerTable.columns.keySet()) {
|
||||
if (!stuTable.columns.containsKey(col)) {
|
||||
hasExtraColumn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasExtraColumn) {
|
||||
// 返回答案完整建表语句,替代学生建表
|
||||
results.add(answerTable.originalCreateSql);
|
||||
} else {
|
||||
// 否则对比字段属性,生成ALTER语句
|
||||
results.addAll(generateAlterTableSql(stuTable, answerTable));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static Map<String, RowData> mapRowsByPrimaryKey(List<RowData> rows, List<String> primaryKeys, TableInfo table) {
|
||||
Map<String, RowData> map = new HashMap<>();
|
||||
|
||||
// 获取表的字段名按顺序组成的列表
|
||||
List<String> columnsInOrder = new ArrayList<>(table.columns.keySet());
|
||||
|
||||
for (RowData row : rows) {
|
||||
StringBuilder keySb = new StringBuilder();
|
||||
for (String pk : primaryKeys) {
|
||||
int idx = columnsInOrder.indexOf(pk); // 用列名定位索引
|
||||
if (idx >= 0 && idx < row.values.size()) {
|
||||
keySb.append(row.values.get(idx)).append("|");
|
||||
}
|
||||
}
|
||||
map.put(keySb.toString(), row);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// 生成 ALTER TABLE 语句
|
||||
public List<String> generateAlterTableSql(TableInfo stuTable, TableInfo answerTable) {
|
||||
List<String> alters = new ArrayList<>();
|
||||
String tableName = stuTable.tableName;
|
||||
|
||||
// 遍历字段,对比字段类型或约束差异,生成 ALTER MODIFY COLUMN 语句
|
||||
for (String colName : answerTable.columns.keySet()) {
|
||||
if (stuTable.columns.containsKey(colName)) {
|
||||
ColumnInfo stuCol = stuTable.columns.get(colName);
|
||||
ColumnInfo ansCol = answerTable.columns.get(colName);
|
||||
|
||||
// 简单比较字段类型定义是否一致(大小写忽略)
|
||||
if (!stuCol.typeDefinition.equalsIgnoreCase(ansCol.typeDefinition) ||
|
||||
stuCol.isPrimaryKey != ansCol.isPrimaryKey) { // 可以加更多属性判断
|
||||
alters.add(String.format("ALTER TABLE `%s` MODIFY COLUMN `%s` %s;", tableName, colName, ansCol.typeDefinition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果学生表字段比答案表多,删除多余字段
|
||||
for (String stuColName : stuTable.columns.keySet()) {
|
||||
if (!answerTable.columns.containsKey(stuColName)) {
|
||||
alters.add(String.format("ALTER TABLE `%s` DROP COLUMN `%s`;", tableName, stuColName));
|
||||
}
|
||||
}
|
||||
|
||||
return alters;
|
||||
}
|
||||
public static String buildInsertWithColumns(String tableName, List<String> columns, List<String> values) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("INSERT INTO `").append(tableName).append("` (");
|
||||
sb.append(columns.stream().map(col -> "`" + col + "`").collect(Collectors.joining(", ")));
|
||||
sb.append(") VALUES (");
|
||||
sb.append(values.stream().map(val -> formatSqlValue(val)).collect(Collectors.joining(", ")));
|
||||
sb.append(");");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String formatSqlValue(String val) {
|
||||
if (val == null || val.equalsIgnoreCase("null")) {
|
||||
return "NULL";
|
||||
}
|
||||
if (val.matches("^-?\\d+(\\.\\d+)?$")) { // 如果是数字,不加引号
|
||||
return val;
|
||||
}
|
||||
// 去除原始的引号(避免二次加引号)
|
||||
if ((val.startsWith("'") && val.endsWith("'")) || (val.startsWith("\"") && val.endsWith("\""))) {
|
||||
val = val.substring(1, val.length() - 1);
|
||||
}
|
||||
|
||||
val = val.replace("'", "''"); // SQL标准转义
|
||||
return "'" + val + "'"; // 添加单层引号
|
||||
}
|
||||
|
||||
public static String buildBatchInsert(String tableName, List<String> columns, List<RowData> rows) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("INSERT INTO `").append(tableName).append("` (");
|
||||
sb.append(columns.stream().map(col -> "`" + col + "`").collect(Collectors.joining(", ")));
|
||||
sb.append(") VALUES ");
|
||||
|
||||
List<String> valueTuples = new ArrayList<>();
|
||||
for (RowData row : rows) {
|
||||
List<String> formattedValues = new ArrayList<>();
|
||||
for (Object value : row.values) {
|
||||
if (value == null) {
|
||||
formattedValues.add("NULL");
|
||||
} else {
|
||||
String valueStr = value.toString();
|
||||
// 去掉首尾引号(如果有)
|
||||
if ((valueStr.startsWith("'") && valueStr.endsWith("'")) ||
|
||||
(valueStr.startsWith("\"") && valueStr.endsWith("\""))) {
|
||||
valueStr = valueStr.substring(1, valueStr.length() - 1);
|
||||
}
|
||||
// 转义单引号
|
||||
valueStr = valueStr.replace("'", "''");
|
||||
formattedValues.add("'" + valueStr + "'");
|
||||
}
|
||||
}
|
||||
valueTuples.add("(" + String.join(", ", formattedValues) + ")");
|
||||
}
|
||||
|
||||
sb.append(String.join(", ", valueTuples));
|
||||
sb.append(";");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static String generateDeleteSql(String tableName, List<String> primaryKeys, RowData stuRow, TableInfo tableInfo) {
|
||||
StringBuilder where = new StringBuilder();
|
||||
List<String> columnsInOrder = new ArrayList<>(tableInfo.columns.keySet());
|
||||
|
||||
for (int i = 0; i < primaryKeys.size(); i++) {
|
||||
String pk = primaryKeys.get(i);
|
||||
int idx = columnsInOrder.indexOf(pk);
|
||||
String val = stuRow.values.get(idx);
|
||||
|
||||
if (i > 0) where.append(" AND ");
|
||||
where.append(String.format("`%s` = %s", pk, val));
|
||||
}
|
||||
return String.format("DELETE FROM `%s` WHERE %s;", tableName, where.toString());
|
||||
}
|
||||
|
||||
|
||||
// 生成 UPDATE 语句,更新全部非主键字段
|
||||
public static String generateUpdateSql(String tableName, List<String> primaryKeys, RowData answerRow, RowData stuRow, TableInfo tableInfo) {
|
||||
StringBuilder setClause = new StringBuilder();
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
|
||||
List<String> columnsInOrder = new ArrayList<>(tableInfo.columns.keySet());
|
||||
|
||||
Set<Integer> diffIndexes = diffFields(stuRow.values, answerRow.values);
|
||||
|
||||
|
||||
for (int i = 0; i < columnsInOrder.size(); i++) {
|
||||
String colName = columnsInOrder.get(i);
|
||||
String value = answerRow.values.get(i);
|
||||
|
||||
if (primaryKeys.contains(colName)) {
|
||||
// 拼接WHERE
|
||||
if (whereClause.length() > 0) whereClause.append(" AND ");
|
||||
whereClause.append(String.format("`%s` = %s", colName, value));
|
||||
} else if (diffIndexes.contains(i)) {
|
||||
// 拼接SET,只包含变动字段
|
||||
if (setClause.length() > 0) setClause.append(", ");
|
||||
setClause.append(String.format("`%s` = %s", colName, value));
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有字段变动,返回空字符串或null
|
||||
if (setClause.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.format("UPDATE `%s` SET %s WHERE %s;", tableName, setClause.toString(), whereClause.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public class RowData {
|
||||
List<String> values; // 每一列的值,按列顺序存储
|
||||
String originalInsertSql; // 原始 INSERT 语句
|
||||
|
||||
public RowData() {
|
||||
}
|
||||
|
||||
public RowData(List<String> values, String originalInsertSql) {
|
||||
this.values = values;
|
||||
this.originalInsertSql = originalInsertSql;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public RowData(List<String> values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个 RowData 的字段值,返回值不同的字段索引列表
|
||||
*/
|
||||
public List<Integer> diffFields(RowData other) {
|
||||
List<Integer> diffIndexes = new ArrayList<>();
|
||||
int size = Math.min(this.values.size(), other.values.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
String v1 = this.values.get(i);
|
||||
String v2 = other.values.get(i);
|
||||
if (v1 == null && v2 == null) {
|
||||
continue;
|
||||
}
|
||||
if (v1 == null || v2 == null || !v1.equals(v2)) {
|
||||
diffIndexes.add(i);
|
||||
}
|
||||
}
|
||||
// 如果两者长度不同,可以考虑多出来的字段也算不同
|
||||
if (this.values.size() != other.values.size()) {
|
||||
for (int i = size; i < Math.max(this.values.size(), other.values.size()); i++) {
|
||||
diffIndexes.add(i);
|
||||
}
|
||||
}
|
||||
return diffIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 你原有的 equalsContent 可以保留,用于全字段对比
|
||||
*/
|
||||
public boolean equalsContent(RowData other) {
|
||||
if (this.values.size() != other.values.size()) {
|
||||
return false;
|
||||
}
|
||||
return this.values.equals(other.values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 自动识别 UTF-8 or GBK
|
||||
private String readFileContentAutoCharset(String filePath) throws IOException {
|
||||
try {
|
||||
// 优先尝试 UTF-8
|
||||
return Files.readString(Paths.get(filePath), StandardCharsets.UTF_8);
|
||||
} catch (MalformedInputException e) {
|
||||
// 如果UTF-8失败,再尝试GBK
|
||||
System.out.println("文件不是UTF-8编码,尝试使用GBK读取: " + filePath);
|
||||
return Files.readString(Paths.get(filePath), Charset.forName("GBK"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static void deleteFolder(File folder) {
|
||||
if (folder.isDirectory()) {
|
||||
@@ -205,31 +783,88 @@ public class ExamGetPointsServiceImpl implements ExamGetPointsService{
|
||||
}
|
||||
folder.delete(); // 删除空文件夹或文件
|
||||
}
|
||||
private static List<String> readSQLFromFile(String filePath) throws IOException {
|
||||
private List<String> compareSqlFiles(String stuFilePath, String answerFilePath) throws IOException {
|
||||
System.out.println("学生文件:" + stuFilePath);
|
||||
System.out.println("答案文件:" + answerFilePath);
|
||||
|
||||
// 读取两个文件的SQL语句块集合
|
||||
Set<String> stuSqlSet = readAllSqlFromFile(stuFilePath);
|
||||
Set<String> answerSqlSet = readAllSqlFromFile(answerFilePath);
|
||||
|
||||
List<String> lines = readFileAutoCharset(filePath);
|
||||
List<String> sqlList = parseSql(lines);
|
||||
System.out.println("学生SQL语句:");
|
||||
System.out.println(stuSqlSet);
|
||||
System.out.println("答案SQL语句:");
|
||||
System.out.println(answerSqlSet);
|
||||
|
||||
for (String sql : sqlList) {
|
||||
System.out.println(sql);
|
||||
System.out.println("-------");
|
||||
}
|
||||
// 差集:学生文件中有而标准答案没有的SQL语句
|
||||
Set<String> diff = new LinkedHashSet<>(stuSqlSet);
|
||||
diff.removeAll(answerSqlSet);
|
||||
|
||||
return sqlList;
|
||||
return new ArrayList<>(diff);
|
||||
}
|
||||
|
||||
// 自动识别 UTF-8 or GBK
|
||||
private static List<String> readFileAutoCharset(String filePath) throws IOException {
|
||||
try {
|
||||
// 优先尝试 UTF-8
|
||||
return Files.readAllLines(Paths.get(filePath), Charset.forName("UTF-8"));
|
||||
} catch (MalformedInputException e) {
|
||||
// 如果UTF-8失败,再尝试GBK
|
||||
System.out.println("文件不是UTF-8编码,尝试使用GBK编码读取...");
|
||||
return Files.readAllLines(Paths.get(filePath), Charset.forName("GBK"));
|
||||
}
|
||||
private Set<String> readAllSqlFromFile(String filePath) throws IOException {
|
||||
Set<String> sqlSet = new LinkedHashSet<>();
|
||||
|
||||
String content = readFileContentAutoCharset(filePath);
|
||||
sqlSet.addAll(extractSqlStatements(content)); // 提取完整SQL块
|
||||
|
||||
return sqlSet;
|
||||
}
|
||||
|
||||
private List<String> extractSqlStatements(String fileContent) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
// 清理注释
|
||||
fileContent = fileContent.replaceAll("(?s)/\\*.*?\\*/", "");
|
||||
fileContent = fileContent.replaceAll("(?m)--.*?$", "");
|
||||
|
||||
// 合并多行,方便用正则提取
|
||||
String normalizedContent = fileContent.replaceAll("\\r?\\n", "\n");
|
||||
|
||||
// 提取 CREATE TABLE ... ;(包括多行)
|
||||
Matcher matcher = Pattern.compile("(?i)(CREATE TABLE[\\s\\S]*?;)").matcher(normalizedContent);
|
||||
while (matcher.find()) {
|
||||
String statement = matcher.group(1).trim();
|
||||
result.add(standardizeTableSql(statement)); // 标准化后加入
|
||||
}
|
||||
|
||||
// 提取其他SQL语句
|
||||
String[] otherStatements = normalizedContent.split(";");
|
||||
for (String stmt : otherStatements) {
|
||||
String statement = stmt.trim();
|
||||
if (statement.isEmpty()) continue;
|
||||
|
||||
String upper = statement.length() >= 50 ? statement.substring(0, 50).toUpperCase() : statement.toUpperCase();
|
||||
|
||||
if (isCoreSqlBlock(upper) && !statement.startsWith("CREATE TABLE")) {
|
||||
result.add(statement + ";");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String standardizeTableSql(String sql) {
|
||||
// 去掉多余空白
|
||||
sql = sql.replaceAll("\\s+", " ");
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
private boolean isCoreSqlBlock(String sqlStart) {
|
||||
return sqlStart.startsWith("CREATE TABLE")
|
||||
|| sqlStart.startsWith("CREATE OR REPLACE VIEW")
|
||||
|| sqlStart.startsWith("CREATE VIEW")
|
||||
|| sqlStart.startsWith("INSERT INTO")
|
||||
|| sqlStart.startsWith("UPDATE")
|
||||
|| sqlStart.startsWith("DELETE FROM")
|
||||
|| sqlStart.startsWith("ALTER TABLE"); // 可选:识别ALTER修改语句
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 解析SQL:去掉注释,只保留SQL语句
|
||||
private static List<String> parseSql(List<String> lines) {
|
||||
List<String> sqlList = new ArrayList<>();
|
||||
|
Reference in New Issue
Block a user