【新增】C语言程序设计,选择题判分

This commit is contained in:
任维炳
2025-04-25 14:14:13 +08:00
parent 6635acb723
commit ae78971c9a
6 changed files with 580 additions and 0 deletions

View File

@@ -127,6 +127,12 @@
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.10</version> <version>2.10</version>
</dependency> </dependency>
<dependency>
<groupId>pc.exam.gg</groupId>
<artifactId>exam-module-exam-biz</artifactId>
<version>2.4.2-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -0,0 +1,29 @@
package pc.exam.pp.module.judgement.service.c_programming;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestion;
/**
* 判分逻辑集合
*
* @author rwb
*/
public interface JudgementService {
/**
* 程序设计判分
* @param examQuestion 程序设计题内容
* @param path 文件路径
* @param score 分数
* @return 返回判分
*/
public double ProgrammingC(double score, String path, ExamQuestion examQuestion);
/**
* 出题时测试运行C语言代码
* @param code 源码
* @return 运行结果
*/
public String run_test_result(String code);
}

View File

@@ -0,0 +1,207 @@
package pc.exam.pp.module.judgement.service.c_programming;
import org.springframework.stereotype.Service;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestion;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionKeyword;
import pc.exam.pp.module.exam.utils.file.LogFileUtils;
import pc.exam.pp.module.judgement.utils.JudgementCUtils;
import java.util.*;
/**
* 判分逻辑集合
*
* @author rwb
*/
@Service
public class JudgementServiceImpl implements JudgementService
{
/**
* 程序设计判分
* @param examQuestion 程序设计题内容
* @param path 文件路径
* @param score 分数
* @return 返回判分
*/
public double ProgrammingC(double score, String path, ExamQuestion examQuestion) {
// 关键字比对,超过权重-测试用例/运行(测试用例全对,直接满分-不全对)-结果,不超过权重只给关键字几个的分
// 获取该题有多少分
// TODO 测试分数15该程序设计题为15分
// double score = 15;
// 总分
double totalScore = 0;
// 测试用例结果分数
double compile_score = 0;
// 关键字分数
double key_score = 0;
// 关键字分数
double result_score = 0;
// 先获取题的组成部分
// 是否需要程序编译 (0:true;1:false)
boolean is_pass = Objects.equals(examQuestion.getQuestionScores().getIsPass(), "0");
// 是否需要程序结果 (0:true;1:false)
boolean is_result = Objects.equals(examQuestion.getQuestionScores().getIsResult(), "0");
// 是否需要关键字 (0:true;1:false)
boolean is_keyword = Objects.equals(examQuestion.getQuestionScores().getIsKeyword(), "0");
// 是否需要测试用例 (0:true;1:false)
boolean is_compile = Objects.equals(examQuestion.getQuestionScores().getIsCompile(), "0");
// 占比百分比
double is_pass_score = Integer.parseInt(examQuestion.getQuestionScores().getIsPassScore()) / 100.0;
double is_result_score = Integer.parseInt(examQuestion.getQuestionScores().getIsResultScore()) / 100.0;
double is_keyword_score = Integer.parseInt(examQuestion.getQuestionScores().getIsKeywordScore()) / 100.0;
double is_compile_score = Integer.parseInt(examQuestion.getQuestionScores().getIsCompileScore()) / 100.0;
// 关键字分数占比
double keyword_score = score * is_keyword_score;
// 编译分数分数占比
double pass_score = score * is_pass_score;
// 试卷其中有C语言的试题
String path_c = path + "/" + examQuestion.getQuBankName();
// 创建log文件txt用于记录
LogFileUtils.createFile(path_c + "/log.txt");
String code = JudgementCUtils.readFile(path_c, examQuestion.getQuId()+".txt");
LogFileUtils.writeLine("✅ 系统开始读取文件:" + code);
if (code == null) {
// 如果没有读到源码
LogFileUtils.writeLine("❌ 系统没有读取到文件。");
LogFileUtils.close();
// 该题不得分直接算成0分
return 0;
}
int true_number = 0;
// 关键字分数
if (is_keyword) {
// 总权重值
int weight = 0;
List<Map<String, Object>> key_list = new ArrayList<>();
// 进行关键字权重比对进行判断
for (ExamQuestionKeyword examQuestionKeyword : examQuestion.getQuestionKeywords()) {
boolean keyword_run = code.contains(examQuestionKeyword.getKeyword());
// 计算权值
Map<String, Object> item = new HashMap<>();
item.put("success", keyword_run);
item.put("score_rate", examQuestionKeyword.getScoreRate());
LogFileUtils.writeLine("✅ 关键字比对:" + examQuestionKeyword.getKeyword() + "--" + keyword_run);
weight += Integer.parseInt(examQuestionKeyword.getScoreRate());
key_list.add(item);
}
// 所有的关键字比对完成之后进行盘端关键字的分数
// 先获取关键字应该分数占比
double one_keyword_score = keyword_score / weight;
// 根据权重进行给分
for (Map<String, Object> item : key_list) {
// 判断首先等于true 的情况下
if ((boolean)item.get("success")) {
// 每个选项分值 = 总分 / 总权重
true_number += 1;
key_score += one_keyword_score * Integer.parseInt((String) item.get("score_rate"));
LogFileUtils.writeLine("✅ 关键字得分:" + key_score);
}
}
}
// 程序需要在运行测试用例,及满足关键字的情况下进行给出运行得分
// 关键字临界得分值
int todo_key_percentage = Integer.parseInt(examQuestion.getQuestionScores().getKeywordCutoff());
if (key_score > keyword_score * ((double) todo_key_percentage / 100) ) {
// 编译代码运行
if (is_pass) {
// 如果使用程序编译,进行程序编译
LogFileUtils.writeLine("✅ 正在使用-std=c99进行编译...");
// 使用C99 运行并得出结果
String code_return = JudgementCUtils.run_code(code,null,"-std=c99", "编译通过运行");
if (!code_return.contains("error")) {
// 编译没有报错,加上编译分数
totalScore += pass_score;
LogFileUtils.writeLine("✅ 编译通过得分:" + pass_score);
} else {
LogFileUtils.writeLine("❌ 编译未通过。");
}
}
// 进行判断测试用例
// 测试用例
// 判断是否要程序结果 ,需要程序结果的,就需要测试用例
if (is_compile) {
// 先运行程序,再将测试用例进行比对
// 运行完成后在判断是否需要进行关键字比对
boolean run_code = false;
List<Boolean> runList = new ArrayList<>();
LogFileUtils.writeLine("✅ 使用测试用例进行判分...");
for (ExamQuestionAnswer examQuestionAnswer : examQuestion.getAnswerList()) {
// 使用C99 运行并得出结果
String code_return = JudgementCUtils.run_code(code, examQuestionAnswer.getContentIn(),"-std=c99",null);
String actual = code_return.trim();
String expected = examQuestionAnswer.getContent().trim();
if (actual.equals(expected)) {
// 判断测试用例结果是否正确
runList.add(true);
// 获取测试用例临界值,并进行判断
// if (runList.size() >= Integer.parseInt(examQuestion.getQuestionScores().getCompileCutoff()) && !runList.contains(false)) {
// // 测试用例得分
// compile_score += (double) (score * is_compile_score);
// // 结果得分
// result_score += (double) (score * is_result_score);
// LogFileUtils.writeLine("✅ 测试用例得分:" + compile_score);
// break;
// }
}
}
// 记录存在多少个测试用例,并且同时记录正确测试用例个数
int test_case_number = examQuestion.getAnswerList().size();
int true_test_case_number = runList.size();
// 判断正确关系
// 1、如果完全相等说明完全正确直接给满分
if (test_case_number == true_test_case_number) {
// 满分,该题多少分就是多少分
LogFileUtils.writeLine("✅ 测试用例全部正确:"+ score);
LogFileUtils.close();
return score;
} else if (test_case_number > true_test_case_number) {
// 2、测试用例没有完全正确对多少个就是多少分
// 公式:测试用例总分数 / 测试用例数量 * 正确测试用例数量
compile_score += (double) ((score * is_compile_score) / test_case_number) * true_test_case_number;
LogFileUtils.writeLine("✅ 测试用例数量:"+ test_case_number + ",正确数量:" + true_test_case_number + ",得分:" + compile_score);
}
}
// 总分 = 总分 + 测试用例得分
totalScore += compile_score;
if (compile_score > 0) {
// 如果测试用例正确有得分的
// 结果
if (is_result) {
// 总分 = 总分 + 结果得分
totalScore += result_score;
LogFileUtils.writeLine("✅ 结果得分:" + result_score);
}
}
totalScore += key_score;
LogFileUtils.close();
return totalScore;
} else {
// 关键字对几个给几分,没有达到临界值的情况下
totalScore += key_score;
LogFileUtils.writeLine("❌ 关键字没有达到临界值,正确数量:"+ true_number);
LogFileUtils.close();
return totalScore;
}
}
/**
*
* @param code 源码
* @return 运行结果
*/
@Override
public String run_test_result(String code){
// 使用C99 运行并得出结果
return JudgementCUtils.run_test_code(code,"-std=c99");
}
}

View File

@@ -0,0 +1,22 @@
package pc.exam.pp.module.judgement.service.choice;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestion;
/**
* 判分逻辑集合(选择题)
*
* @author rwb
*/
public interface JudgementChoiceService {
/**
*
* @param score 题分
* @param examQuestion 选择题内容(学生答题)
* @param originalExamQuestion 原始题(判断答案)
* @return 得分
*/
public double ProgrammingChoice(double score, ExamQuestion examQuestion, ExamQuestion originalExamQuestion);
}

View File

@@ -0,0 +1,54 @@
package pc.exam.pp.module.judgement.service.choice;
import org.springframework.stereotype.Service;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestion;
import pc.exam.pp.module.exam.dal.dataobject.ExamQuestionAnswer;
/**
* 判分逻辑集合
*
* @author rwb
*/
@Service
public class JudgementChoiceServiceImpl implements JudgementChoiceService
{
/**
* 选择题判分
* @param examQuestion 选择题内容(学生答题)
* @param originalExamQuestion 原始题(判断答案)
* @param score 分数
* @return 返回判分
*/
public double ProgrammingChoice(double score, ExamQuestion examQuestion, ExamQuestion originalExamQuestion) {
// 总分重置
double totalScore = 0;
String right_id = null;
String stu_right_id = null;
// 获取选择题的标准答案
// 找到对应正确题的答案ID
for (ExamQuestionAnswer examQuestionAnswer : originalExamQuestion.getAnswerList()) {
// 判断正确答案
if ("0".equals(examQuestionAnswer.getIsRight())) {
// 正确答案
right_id = examQuestionAnswer.getAnswerId();
break;
}
}
// 获取学生的做题答案,进行判断是否一致
for (ExamQuestionAnswer examQuestionAnswer : examQuestion.getAnswerList()) {
// 判断正确答案
if ("0".equals(examQuestionAnswer.getIsRight())) {
// 正确答案
stu_right_id = examQuestionAnswer.getAnswerId();
break;
}
}
// 判断
if (stu_right_id.equals(right_id)) {
totalScore += score;
}
// 该题得分返回
return totalScore;
}
}

View File

@@ -0,0 +1,262 @@
package pc.exam.pp.module.judgement.utils;
import javax.swing.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Stream;
/**
* C语言 相关工具类
*
* @author pengchen
*/
public class JudgementCUtils
{
/**
* 判断程序是否安装GCC 编译环境
* @return true or false
*/
public static boolean isGCCInstalled() {
try {
ProcessBuilder builder = new ProcessBuilder("gcc", "--version");
Process process = builder.start();
process.waitFor();
return process.exitValue() == 0;
} catch (Exception e) {
return false;
}
}
/**
* 安装GCC编译环境
* @param os 操作系统
* @return 安装话术
*/
public static String installGCC(String os) {
try {
ProcessBuilder builder;
if (os.equals("windows")) {
builder = new ProcessBuilder("cmd.exe", "/c", "choco install mingw");
} else {
builder = new ProcessBuilder("bash", "-c", "sudo apt-get install gcc -y");
}
builder.redirectErrorStream(true);
Process process = builder.start();
process.waitFor();
JOptionPane.showMessageDialog(null, "GCC installation completed for " + os);
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "Installation failed: " + ex.getMessage());
throw new RuntimeException("Installation failed: " + ex.getMessage());
}
return "GCC installation completed for " + os;
}
/**
* 运行C语言代码
* @param code C语言代码
* @param input 测试用例插入值(如有)
* @param standard 编译版本 C99
* @return 运行结果
*/
public static String run_code(String code, String input, String standard, String text) {
try {
boolean hasInput = code.contains("scanf") || code.contains("fgets") || code.contains("getchar");
// 写入 C 源码到文件
File file = new File("program.c");
try (FileWriter writer = new FileWriter(file)) {
writer.write(code);
}
// 编译代码
ProcessBuilder compileBuilder = new ProcessBuilder("gcc", standard, "program.c", "-o", "program.out");
compileBuilder.redirectErrorStream(true);
Process compileProcess = compileBuilder.start();
StringBuilder compileOutput = new StringBuilder();
try (BufferedReader compileReader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()))) {
String line;
while ((line = compileReader.readLine()) != null) {
compileOutput.append(line).append("\n");
}
}
int compileResult = compileProcess.waitFor();
String outputLower = compileOutput.toString().toLowerCase();
if (outputLower.contains("error:") || !Files.exists(Paths.get("program.out"))) {
// LogFileUtils.writeLine("❌ 编译失败:");
// LogFileUtils.writeLine(compileOutput.toString());
return "编译失败,输出:\n" + compileOutput.toString();
}
// 运行程序
ProcessBuilder runBuilder = new ProcessBuilder("./program.out");
runBuilder.redirectErrorStream(true);
Process runProcess = runBuilder.start();
// 输入注入(如有)
if (hasInput && input != null) {
try (BufferedWriter inputWriter = new BufferedWriter(new OutputStreamWriter(runProcess.getOutputStream()))) {
inputWriter.write(input);
inputWriter.newLine();
inputWriter.flush();
}
}
// 使用线程池处理带超时的输出读取
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(runProcess.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
return output.toString();
});
String output;
try {
output = future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// LogFileUtils.writeLine("⏰ 程序超时,强制终止进程!");
runProcess.destroy();
future.cancel(true);
output = "程序运行超时(超过 5 秒)";
} finally {
executor.shutdownNow();
}
// if (text != null) {
// LogFileUtils.writeLine("✅ 程序运行完成,输出:" + text);
// } else {
// LogFileUtils.writeLine("✅ 程序运行完成,输出:" + output.trim());
// }
return output;
} catch (Exception ex) {
ex.printStackTrace();
// LogFileUtils.writeLine("❌ 运行 C 代码时出错:" + ex.getMessage());
return "运行 C 代码时出错:" + ex.getMessage();
}
}
/**
* 运行C语言代码,出题测试运行
* @param code C语言代码
* @param standard 编译版本 C99
* @return 运行结果
*/
public static String run_test_code(String code, String standard) {
try {
// 写入 C 语言源码到文件
File file = new File("program.c");
try (FileWriter writer = new FileWriter(file)) {
writer.write(code);
}
// 编译 C 代码
ProcessBuilder compileBuilder = new ProcessBuilder("gcc", standard, "program.c", "-o", "program.out");
compileBuilder.redirectErrorStream(true);
Process compileProcess = compileBuilder.start();
StringBuilder compileOutput = new StringBuilder();
try (BufferedReader compileReader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()))) {
String line;
while ((line = compileReader.readLine()) != null) {
compileOutput.append(line).append("\n");
}
}
int compileResult = compileProcess.waitFor();
// 检查编译是否成功
String outputLower = compileOutput.toString().toLowerCase();
if (outputLower.contains("error:") || !Files.exists(Paths.get("program.out"))) {
return "编译失败,输出:\n" + compileOutput.toString();
}
// 运行程序并添加超时机制
ProcessBuilder runBuilder = new ProcessBuilder("./program.out");
runBuilder.redirectErrorStream(true);
Process runProcess = runBuilder.start();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(runProcess.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
}
return output.toString();
});
String output;
try {
output = future.get(5, TimeUnit.SECONDS); // 设置 5 秒超时
} catch (TimeoutException e) {
runProcess.destroy();
future.cancel(true);
output = "程序运行超时(超过 5 秒)";
} finally {
executor.shutdownNow();
}
return output;
} catch (Exception ex) {
ex.printStackTrace();
return "运行 C 代码时出错:" + ex.getMessage();
}
}
/**
* 读取文件代码
* @param filePath 文件路径
* @return 文件内容
*/
public static String readFile(String filePath, String fileName) {
// 创建一个 Path 对象,指向包含 C 语言文件的文件夹
Path folderPath = Paths.get(filePath);
// 判断路径是否存在
if (!Files.exists(folderPath)) {
// LogFileUtils.writeLine("❗ 文件夹路径不存在: " + folderPath.toString());
return null; // 或者 return null或者 throw new RuntimeException(...),看你的函数定义
}
// 使用 StringBuilder 来累积所有读取到的代码内容
StringBuilder codeBuilder = new StringBuilder();
try (Stream<Path> paths = Files.walk(folderPath)) {
paths.filter(Files::isRegularFile)
.filter(path -> path.toString().toLowerCase().endsWith(fileName))
.forEach(path -> {
// LogFileUtils.writeLine("📄 文件: " + path);
try {
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
for (String line : lines) {
codeBuilder.append("\n").append(line);
System.out.println(" " + line);
}
} catch (IOException e) {
// LogFileUtils.writeLine("❗ 无法读取文件: " + path);
}
});
} catch (IOException e) {
return null;
}
return codeBuilder.toString();
}
}