diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..4ea72a9 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml new file mode 100644 index 0000000..7ef04e2 --- /dev/null +++ b/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..8648f94 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml new file mode 100644 index 0000000..7f1aa7e --- /dev/null +++ b/.idea/libraries/lib.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..31e1ebc --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..831f033 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/data/sqf/2025-10-02-11-29-36.txt b/data/sqf/2025-10-02-11-29-36.txt deleted file mode 100644 index 0f1fc7c..0000000 --- a/data/sqf/2025-10-02-11-29-36.txt +++ /dev/null @@ -1,20 +0,0 @@ -1. 37 + 35 - -2. 82 * 28 - -3. (16 / 56) + 42 - -4. 6 * 52 + 35 - -5. (99 + 26) + 37 - -6. 40 * 63 - -7. 52 - 61 - 16 - 70 * 51 - -8. 65 + 83 - -9. 84 / 64 - -10. 24 - 47 + 21 + 1 - 33 - diff --git a/data/users.cfg b/data/users.cfg index 7842a5d..a790fcf 100644 --- a/data/users.cfg +++ b/data/users.cfg @@ -1,10 +1,12 @@ 王五3 123 HIGH wangwu3@example.com 王五2 123 HIGH wangwu2@example.com +123 Aa123456 PRIMARY 2571400460@qq.com 王五1 123 HIGH wangwu1@example.com +scb Aa123456 PRIMARY 3110858122@qq.com 张三3 123 PRIMARY zhangsan3@example.com 张三1 123 PRIMARY zhangsan1@example.com 张三2 123 PRIMARY zhangsan2@example.com 李四3 123 MIDDLE lishi3@example.com -sqf Aa123456 PRIMARY sqf090815@hnu.edu.cn +sqf Qq123456 PRIMARY sqf090815@hnu.edu.cn 李四1 123 MIDDLE lishi1@example.com 李四2 123 MIDDLE lishi2@example.com diff --git a/mathStudyApp.iml b/mathStudyApp.iml index 8a266dd..fb8e866 100644 --- a/mathStudyApp.iml +++ b/mathStudyApp.iml @@ -8,7 +8,5 @@ - - \ No newline at end of file diff --git a/out/production/mathStudyApp/Main.class b/out/production/mathStudyApp/Main.class index 2880209..50bd932 100644 Binary files a/out/production/mathStudyApp/Main.class and b/out/production/mathStudyApp/Main.class differ diff --git a/out/production/mathStudyApp/controller/FunctionController.class b/out/production/mathStudyApp/controller/FunctionController.class index 099f702..621af2e 100644 Binary files a/out/production/mathStudyApp/controller/FunctionController.class and b/out/production/mathStudyApp/controller/FunctionController.class differ diff --git a/out/production/mathStudyApp/model/Create.class b/out/production/mathStudyApp/model/Create.class index d271ca1..722d227 100644 Binary files a/out/production/mathStudyApp/model/Create.class and b/out/production/mathStudyApp/model/Create.class differ diff --git a/out/production/mathStudyApp/model/ExpressionEvaluator.class b/out/production/mathStudyApp/model/ExpressionEvaluator.class index f896f4a..ab78d34 100644 Binary files a/out/production/mathStudyApp/model/ExpressionEvaluator.class and b/out/production/mathStudyApp/model/ExpressionEvaluator.class differ diff --git a/out/production/mathStudyApp/model/Generator$1.class b/out/production/mathStudyApp/model/Generator$1.class index d64e0b3..2f8516d 100644 Binary files a/out/production/mathStudyApp/model/Generator$1.class and b/out/production/mathStudyApp/model/Generator$1.class differ diff --git a/out/production/mathStudyApp/model/Generator.class b/out/production/mathStudyApp/model/Generator.class index c58172c..58ca828 100644 Binary files a/out/production/mathStudyApp/model/Generator.class and b/out/production/mathStudyApp/model/Generator.class differ diff --git a/out/production/mathStudyApp/model/LanguageSwitch$1.class b/out/production/mathStudyApp/model/LanguageSwitch$1.class index 3891474..f9b2a2b 100644 Binary files a/out/production/mathStudyApp/model/LanguageSwitch$1.class and b/out/production/mathStudyApp/model/LanguageSwitch$1.class differ diff --git a/out/production/mathStudyApp/model/LanguageSwitch.class b/out/production/mathStudyApp/model/LanguageSwitch.class index b337ced..c3c3ca6 100644 Binary files a/out/production/mathStudyApp/model/LanguageSwitch.class and b/out/production/mathStudyApp/model/LanguageSwitch.class differ diff --git a/out/production/mathStudyApp/model/LoadFile.class b/out/production/mathStudyApp/model/LoadFile.class index b4789e2..41b059e 100644 Binary files a/out/production/mathStudyApp/model/LoadFile.class and b/out/production/mathStudyApp/model/LoadFile.class differ diff --git a/out/production/mathStudyApp/model/Login$Account.class b/out/production/mathStudyApp/model/Login$Account.class index f9a637f..aa24861 100644 Binary files a/out/production/mathStudyApp/model/Login$Account.class and b/out/production/mathStudyApp/model/Login$Account.class differ diff --git a/out/production/mathStudyApp/model/Login$Level.class b/out/production/mathStudyApp/model/Login$Level.class index 2ad13ea..a35c914 100644 Binary files a/out/production/mathStudyApp/model/Login$Level.class and b/out/production/mathStudyApp/model/Login$Level.class differ diff --git a/out/production/mathStudyApp/model/Login.class b/out/production/mathStudyApp/model/Login.class index 790a1f9..e914c85 100644 Binary files a/out/production/mathStudyApp/model/Login.class and b/out/production/mathStudyApp/model/Login.class differ diff --git a/out/production/mathStudyApp/model/Paper.class b/out/production/mathStudyApp/model/Paper.class index 8e5f952..ee32776 100644 Binary files a/out/production/mathStudyApp/model/Paper.class and b/out/production/mathStudyApp/model/Paper.class differ diff --git a/out/production/mathStudyApp/model/Save.class b/out/production/mathStudyApp/model/Save.class index c21ca61..e08888b 100644 Binary files a/out/production/mathStudyApp/model/Save.class and b/out/production/mathStudyApp/model/Save.class differ diff --git a/out/production/mathStudyApp/view/ExamFrame.class b/out/production/mathStudyApp/view/ExamFrame.class index dadb467..e37f665 100644 Binary files a/out/production/mathStudyApp/view/ExamFrame.class and b/out/production/mathStudyApp/view/ExamFrame.class differ diff --git a/out/production/mathStudyApp/view/ExamSetupFrame.class b/out/production/mathStudyApp/view/ExamSetupFrame.class index c710d40..2850903 100644 Binary files a/out/production/mathStudyApp/view/ExamSetupFrame.class and b/out/production/mathStudyApp/view/ExamSetupFrame.class differ diff --git a/out/production/mathStudyApp/view/LoginFrame.class b/out/production/mathStudyApp/view/LoginFrame.class index 02d6b24..b8a1070 100644 Binary files a/out/production/mathStudyApp/view/LoginFrame.class and b/out/production/mathStudyApp/view/LoginFrame.class differ diff --git a/out/production/mathStudyApp/view/MainMenuFrame$ChangePasswordDialog.class b/out/production/mathStudyApp/view/MainMenuFrame$ChangePasswordDialog.class index f72d97f..1147586 100644 Binary files a/out/production/mathStudyApp/view/MainMenuFrame$ChangePasswordDialog.class and b/out/production/mathStudyApp/view/MainMenuFrame$ChangePasswordDialog.class differ diff --git a/out/production/mathStudyApp/view/MainMenuFrame.class b/out/production/mathStudyApp/view/MainMenuFrame.class index 4f406ae..de0d464 100644 Binary files a/out/production/mathStudyApp/view/MainMenuFrame.class and b/out/production/mathStudyApp/view/MainMenuFrame.class differ diff --git a/out/production/mathStudyApp/view/RegisterFrame$1.class b/out/production/mathStudyApp/view/RegisterFrame$1.class index dc8496c..9dd1b2a 100644 Binary files a/out/production/mathStudyApp/view/RegisterFrame$1.class and b/out/production/mathStudyApp/view/RegisterFrame$1.class differ diff --git a/out/production/mathStudyApp/view/RegisterFrame.class b/out/production/mathStudyApp/view/RegisterFrame.class index 65dca4f..eecd276 100644 Binary files a/out/production/mathStudyApp/view/RegisterFrame.class and b/out/production/mathStudyApp/view/RegisterFrame.class differ diff --git a/out/production/mathStudyApp/view/ResultFrame.class b/out/production/mathStudyApp/view/ResultFrame.class index 569f425..bc6b282 100644 Binary files a/out/production/mathStudyApp/view/ResultFrame.class and b/out/production/mathStudyApp/view/ResultFrame.class differ diff --git a/src/Main.java b/src/Main.java index cdc27b5..07d6d26 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,4 +1,5 @@ import javax.swing.SwingUtilities; + import view.LoginFrame; /** diff --git a/src/controller/AssignController.java b/src/controller/AssignController.java deleted file mode 100644 index b31851c..0000000 --- a/src/controller/AssignController.java +++ /dev/null @@ -1,18 +0,0 @@ -package controller; - -import model.Login; -import model.LanguageSwitch; -import view.ExamSetupFrame; - -import javax.swing.JOptionPane; - -/** - * 登录后流程控制:负责从登录跳转到出题设置 - */ -public class AssignController { - public void loginSuccess(Login.Account user) { - JOptionPane.showMessageDialog(null, - "登录成功。当前选择为 " + LanguageSwitch.levelToChinese(user.level) + " 出题"); - new ExamSetupFrame(user); - } -} diff --git a/src/controller/FunctionController.java b/src/controller/FunctionController.java index 21f9192..ef07cf9 100644 --- a/src/controller/FunctionController.java +++ b/src/controller/FunctionController.java @@ -1,16 +1,17 @@ package controller; +import javax.swing.JOptionPane; + import model.Create; import model.Login; import model.Paper; import view.ExamFrame; -import javax.swing.JOptionPane; - /** * 题目生成控制器:接收题数与用户信息,调用 Create 生成 Paper 并打开 ExamFrame */ public class FunctionController { + private Login.Account user; public FunctionController(Login.Account user) { diff --git a/src/model/Create.java b/src/model/Create.java index 85cf2b7..d27a77a 100644 --- a/src/model/Create.java +++ b/src/model/Create.java @@ -1,44 +1,23 @@ package model; -import java.util.*; -import javax.script.*; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import javax.script.ScriptException; /** * 负责生成试卷并创建选择题选项(四选一) */ public class Create { - // 把数学表达式转换为 JS 可执行表达式的简单转换器 - private static String toJsExpr(String expr) { - String s = expr; - s = s.replaceAll("\\^2", ".pow2"); // 临时占位 - s = s.replaceAll("sqrt\\(", "Math.sqrt("); - s = s.replaceAll("sin\\(", "Math.sin(Math.toRadians("); - s = s.replaceAll("cos\\(", "Math.cos(Math.toRadians("); - s = s.replaceAll("tan\\(", "Math.tan(Math.toRadians("); - // 修复 sin(Math.toRadians(x) 末尾多一层 ) -> we need to close paren - // Simple approach: for each "Math.sin(Math.toRadians(", there will be following number or parenthesis, we close later with "))" - // Replace our .pow2 placeholder into Math.pow(x,2) - // Handle pow2: we earlier converted x^2 -> x.pow2, so replace (\d+|...) .pow2 to Math.pow(that,2) - // For simplicity, replace pattern "([0-9)]+)\\.pow2" with "Math.pow($1,2)" — approximate - s = s.replaceAll("(\\d+)\\.pow2", "Math.pow($1,2)"); - // If there are parentheses before .pow2: ") .pow2" unlikely; accept some limitations. - // For sin/cos/tan we changed 'sin(' -> 'Math.sin(Math.toRadians(' so must add two closing ) after the argument. - // To keep it simple, replace "Math.sin(Math.toRadians(" -> "Math.sin(Math.toRadians(" and later we will ensure parentheses in evaluation by appending "))" when needed. - // We'll also replace any leftover "^2" directly - s = s.replaceAll("\\^2", ""); - // Finally, replace any multiple spaces - s = s.replaceAll("\\s+", ""); - return s; - } - - // 评估表达式数值(double),若失败抛异常 // 评估表达式数值(double),若失败抛异常 private static double evalExpression(String expr) throws ScriptException { return ExpressionEvaluator.evaluate(expr); } - /** * 生成试卷:返回 Paper 对象(questions, options, correctIndex) */ @@ -78,7 +57,6 @@ public class Create { // 判断是否为整数答案 boolean isIntAnswer = Math.abs(correctVal - Math.round(correctVal)) < 1e-6; - // 产生三个干扰值 Set optsSet = new LinkedHashSet<>(); optsSet.add(correctStr); @@ -90,7 +68,9 @@ public class Create { if (isIntAnswer) { // 整数题:干扰项也是整数(偏差 ±1~±10) delta = r.nextInt(10) + 1; - if (r.nextBoolean()) delta = -delta; + if (r.nextBoolean()) { + delta = -delta; + } } else { // 小数题:随机偏差比例 delta = (Math.abs(correctVal) < 1e-6) @@ -122,7 +102,9 @@ public class Create { try { // 为保存,我们保存原题干列表(文本) java.util.List raw = new ArrayList<>(); - for (String s : paper) raw.add(s); + for (String s : paper) { + raw.add(s); + } Save.savePaper(user.username, raw); } catch (RuntimeException re) { // 抛给 UI 处理(但仍返回 Paper) @@ -143,13 +125,17 @@ public class Create { } private static int findIndex(String[] arr, String target) { - for (int i = 0; i < arr.length; i++) if (arr[i].equals(target)) return i; + for (int i = 0; i < arr.length; i++) { + if (arr[i].equals(target)) { + return i; + } + } return 0; } private static String formatNumber(double v) { if (Math.abs(v - Math.round(v)) < 1e-6) { - return String.valueOf((long)Math.round(v)); + return String.valueOf((long) Math.round(v)); } else { return String.format("%.3f", v); } diff --git a/src/model/ExpressionEvaluator.java b/src/model/ExpressionEvaluator.java index db5a20a..0fc214d 100644 --- a/src/model/ExpressionEvaluator.java +++ b/src/model/ExpressionEvaluator.java @@ -1,9 +1,17 @@ package model; -import java.util.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.StringTokenizer; + public class ExpressionEvaluator { private static final Map PRECEDENCE = new HashMap<>(); + static { PRECEDENCE.put("+", 1); PRECEDENCE.put("-", 1); @@ -26,12 +34,12 @@ public class ExpressionEvaluator { private static List toRPN(String expr) { List output = new ArrayList<>(); Stack stack = new Stack<>(); - - // 正则拆分 token(数字、函数、符号、括号) StringTokenizer tokenizer = new StringTokenizer(expr, "+-*/^() ", true); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken().trim(); - if (token.isEmpty()) continue; + if (token.isEmpty()) { + continue; + } if (token.matches("[0-9.]+")) { // 数字 output.add(token); @@ -52,7 +60,6 @@ public class ExpressionEvaluator { if (!stack.isEmpty() && stack.peek().equals("(")) { stack.pop(); } - // 如果栈顶是函数,弹出函数 if (!stack.isEmpty() && stack.peek().matches("[a-zA-Z]+")) { output.add(stack.pop()); } @@ -76,20 +83,39 @@ public class ExpressionEvaluator { double b = stack.pop(); double a = stack.pop(); switch (token) { - case "+": stack.push(a + b); break; - case "-": stack.push(a - b); break; - case "*": stack.push(a * b); break; - case "/": stack.push(a / b); break; - case "^": stack.push(Math.pow(a, b)); break; + case "+": + stack.push(a + b); + break; + case "-": + stack.push(a - b); + break; + case "*": + stack.push(a * b); + break; + case "/": + stack.push(a / b); + break; + case "^": + stack.push(Math.pow(a, b)); + break; } } else { // 函数 double a = stack.pop(); switch (token.toLowerCase()) { - case "sin": stack.push(Math.sin(Math.toRadians(a))); break; - case "cos": stack.push(Math.cos(Math.toRadians(a))); break; - case "tan": stack.push(Math.tan(Math.toRadians(a))); break; - case "sqrt": stack.push(Math.sqrt(a)); break; - default: throw new RuntimeException("未知函数: " + token); + case "sin": + stack.push(Math.sin(Math.toRadians(a))); + break; + case "cos": + stack.push(Math.cos(Math.toRadians(a))); + break; + case "tan": + stack.push(Math.tan(Math.toRadians(a))); + break; + case "sqrt": + stack.push(Math.sqrt(a)); + break; + default: + throw new RuntimeException("未知函数: " + token); } } } diff --git a/src/model/Generator.java b/src/model/Generator.java index b26584a..da2f306 100644 --- a/src/model/Generator.java +++ b/src/model/Generator.java @@ -1,6 +1,11 @@ package model; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; import java.util.stream.Collectors; /** @@ -31,7 +36,8 @@ public class Generator extends QuestionGenerator { } } if (generated.size() < n) { - System.out.println("注意:无法生成足够的不重复题目,已生成 " + generated.size() + " 道题(请求 " + n + " 道)"); + System.out.println( + "注意:无法生成足够的不重复题目,已生成 " + generated.size() + " 道题(请求 " + n + " 道)"); } return new ArrayList<>(generated); } @@ -59,7 +65,8 @@ public class Generator extends QuestionGenerator { boolean useParens = RAND.nextBoolean(); if (useParens && operands >= 3 && RAND.nextBoolean()) { sb.append("("); - sb.append(randInt(1, 100)).append(" ").append(randomChoice(ops)).append(" ").append(randInt(1, 100)); + sb.append(randInt(1, 100)).append(" ").append(randomChoice(ops)).append(" ") + .append(randInt(1, 100)); sb.append(")"); for (int i = 2; i < operands; i++) { sb.append(" ").append(randomChoice(ops)).append(" ").append(randInt(1, 100)); @@ -75,8 +82,11 @@ public class Generator extends QuestionGenerator { public String genMiddle(int operands) { String expr = genPrimary(operands); - if (RAND.nextBoolean()) expr = applySquare(expr); - else expr = applySqrt(expr); + if (RAND.nextBoolean()) { + expr = applySquare(expr); + } else { + expr = applySqrt(expr); + } return expr; } @@ -88,7 +98,9 @@ public class Generator extends QuestionGenerator { public String applySquare(String expr) { List spans = findNumberSpans(expr); - if (spans.isEmpty()) return expr + "^2"; + if (spans.isEmpty()) { + return expr + "^2"; + } int[] s = spans.get(RAND.nextInt(spans.size())); String before = expr.substring(0, s[0]); String num = expr.substring(s[0], s[1]); @@ -98,7 +110,9 @@ public class Generator extends QuestionGenerator { public String applySqrt(String expr) { List spans = findNumberSpans(expr); - if (spans.isEmpty()) return "sqrt(" + expr + ")"; + if (spans.isEmpty()) { + return "sqrt(" + expr + ")"; + } int[] s = spans.get(RAND.nextInt(spans.size())); String before = expr.substring(0, s[0]); String num = expr.substring(s[0], s[1]); @@ -109,7 +123,9 @@ public class Generator extends QuestionGenerator { public String applyTrig(String expr) { List spans = findNumberSpans(expr); String func = randomChoice(Arrays.asList("sin", "cos", "tan")); - if (spans.isEmpty()) return func + "(" + expr + ")"; + if (spans.isEmpty()) { + return func + "(" + expr + ")"; + } int[] s = spans.get(RAND.nextInt(spans.size())); String before = expr.substring(0, s[0]); String num = expr.substring(s[0], s[1]); @@ -124,10 +140,14 @@ public class Generator extends QuestionGenerator { while (i < n) { if (Character.isDigit(chs[i])) { int j = i; - while (j < n && (Character.isDigit(chs[j]))) j++; + while (j < n && (Character.isDigit(chs[j]))) { + j++; + } spans.add(new int[]{i, j}); i = j; - } else i++; + } else { + i++; + } } return spans; } diff --git a/src/model/LanguageSwitch.java b/src/model/LanguageSwitch.java index 50bb180..a3c0501 100644 --- a/src/model/LanguageSwitch.java +++ b/src/model/LanguageSwitch.java @@ -1,6 +1,7 @@ package model; public class LanguageSwitch { + public static String levelToChinese(Login.Level l) { return switch (l) { case PRIMARY -> "小学"; diff --git a/src/model/LoadFile.java b/src/model/LoadFile.java index be1c88e..cb8a7a5 100644 --- a/src/model/LoadFile.java +++ b/src/model/LoadFile.java @@ -1,12 +1,16 @@ package model; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.nio.file.*; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; + /** * 读取用户文件夹下已有题目的所有题目文本(用于查重) */ @@ -24,7 +28,9 @@ public class LoadFile { StringBuilder cur = new StringBuilder(); for (String line : lines) { if (line.matches("^\\s*\\d+\\..*")) { - if (!cur.isEmpty()) all.add(cur.toString().trim()); + if (!cur.isEmpty()) { + all.add(cur.toString().trim()); + } cur.setLength(0); cur.append(line.replaceFirst("^\\s*\\d+\\.", "").trim()); } else { @@ -34,16 +40,21 @@ public class LoadFile { cur.setLength(0); } } else { - if (!cur.isEmpty()) cur.append(" "); + if (!cur.isEmpty()) { + cur.append(" "); + } cur.append(line.trim()); } } } - if (!cur.isEmpty()) all.add(cur.toString().trim()); + if (!cur.isEmpty()) { + all.add(cur.toString().trim()); + } } } catch (IOException e) { throw new RuntimeException("读取题目文件失败:" + e.getMessage(), e); } - return all.stream().map(String::trim).filter(s -> !s.isEmpty()).distinct().collect(Collectors.toList()); + return all.stream().map(String::trim).filter(s -> !s.isEmpty()).distinct() + .collect(Collectors.toList()); } } diff --git a/src/model/Login.java b/src/model/Login.java index 25a0269..54192f2 100644 --- a/src/model/Login.java +++ b/src/model/Login.java @@ -1,10 +1,14 @@ package model; -import java.util.Map; -import java.util.HashMap; -import java.io.*; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Serializable; import java.nio.charset.StandardCharsets; -import java.nio.file.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; /** @@ -61,17 +65,30 @@ public class Login { } // 注册(GUI 调用),若用户名已存在返回 false + // 注册(GUI 调用),若用户名或邮箱已存在返回 false public static synchronized boolean register(String username, String password, Level level, String email) { + // 检查用户名是否重复 if (accounts.containsKey(username)) { return false; } + + // 检查邮箱是否重复 + for (Account existing : accounts.values()) { + if (existing.email != null && existing.email.equalsIgnoreCase(email)) { + // 邮箱重复,不区分大小写 + return false; + } + } + + // 都不重复,则注册成功 Account acc = new Account(username, password, level, email); accounts.put(username, acc); persistToFile(); return true; } + // 修改密码(需提供原密码),返回是否成功 public static synchronized boolean changePassword(String username, String oldPwd, String newPwd) { Account acc = accounts.get(username); diff --git a/src/model/Paper.java b/src/model/Paper.java index dc09a27..f2e73a4 100644 --- a/src/model/Paper.java +++ b/src/model/Paper.java @@ -6,6 +6,7 @@ import java.util.List; * 试卷封装:每一道题为字符串(题干),每题有四个选项(字符串),且标记正确选项索引 */ public class Paper { + public final List questions; public final List options; // 每题 options[i] 长度为4 public final int[] correctIndex; // 每题正确选项下标 0..3 diff --git a/src/model/Save.java b/src/model/Save.java index 23f2a9a..8b5cd7d 100644 --- a/src/model/Save.java +++ b/src/model/Save.java @@ -2,17 +2,22 @@ package model; import java.io.BufferedWriter; import java.io.IOException; -import java.nio.file.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; -import java.nio.charset.StandardCharsets; public class Save { + public static String savePaper(String username, List paper) { Path userDir = Paths.get("data", username); try { - if (!Files.exists(userDir)) Files.createDirectories(userDir); + if (!Files.exists(userDir)) { + Files.createDirectories(userDir); + } } catch (IOException e) { throw new RuntimeException("无法创建用户文件夹:" + e.getMessage(), e); } diff --git a/src/view/ExamFrame.java b/src/view/ExamFrame.java index 424b579..eda873e 100644 --- a/src/view/ExamFrame.java +++ b/src/view/ExamFrame.java @@ -1,7 +1,20 @@ package view; -import javax.swing.*; -import java.awt.*; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.ButtonGroup; +import javax.swing.BorderFactory; +import javax.swing.SwingConstants; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridLayout; + import model.Paper; import model.Login; @@ -9,6 +22,7 @@ import model.Login; * 答题界面(选择题,四选一) */ public class ExamFrame extends JFrame { + private Paper paper; private Login.Account user; private int index = 0; @@ -35,12 +49,12 @@ public class ExamFrame extends JFrame { qLabel = new JLabel("", SwingConstants.LEFT); qLabel.setFont(new Font("Serif", Font.PLAIN, 16)); JPanel top = new JPanel(new BorderLayout()); - top.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + top.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); top.add(qLabel, BorderLayout.CENTER); - centerPanel = new JPanel(new GridLayout(4,1,6,6)); + centerPanel = new JPanel(new GridLayout(4, 1, 6, 6)); group = new ButtonGroup(); - for (int i=0;i<4;i++){ + for (int i = 0; i < 4; i++) { radioBtns[i] = new JRadioButton(); group.add(radioBtns[i]); centerPanel.add(radioBtns[i]); @@ -63,33 +77,43 @@ public class ExamFrame extends JFrame { setVisible(true); } - /** 显示一道题 */ + /** + * 显示一道题 + */ private void showQuestion() { if (index >= paper.size()) { showSubmitPage(); return; } String q = paper.questions.get(index); - qLabel.setText("第 " + (index+1) + " 题: " + q + ""); + qLabel.setText("第 " + (index + 1) + " 题: " + q + ""); String[] opts = paper.options.get(index); - for (int i=0;i<4;i++) { + for (int i = 0; i < 4; i++) { radioBtns[i].setText(opts[i]); } // 清除上一次选择 group.clearSelection(); } - /** 提交当前题 */ + /** + * 提交当前题 + */ private void submitAnswer() { int selected = -1; - for (int i=0;i<4;i++) if (radioBtns[i].isSelected()) selected = i; + for (int i = 0; i < 4; i++) { + if (radioBtns[i].isSelected()) { + selected = i; + } + } if (selected == -1) { JOptionPane.showMessageDialog(this, "请选择一个选项后再提交"); return; } // 判断对错 - if (selected == paper.correctIndex[index]) score++; + if (selected == paper.correctIndex[index]) { + score++; + } index++; if (index < paper.size()) { @@ -100,7 +124,9 @@ public class ExamFrame extends JFrame { } } - /** 做完所有题目后显示确认提交界面 */ + /** + * 做完所有题目后显示确认提交界面 + */ private void showSubmitPage() { // 移除中间题目区域和按钮 getContentPane().remove(centerPanel); @@ -134,10 +160,12 @@ public class ExamFrame extends JFrame { repaint(); } - /** 完成考试 → 进入成绩界面 */ + /** + * 完成考试 → 进入成绩界面 + */ private void finishExam() { int total = paper.size(); - double percent = total==0 ? 0 : (100.0 * score / total); + double percent = total == 0 ? 0 : (100.0 * score / total); dispose(); new ResultFrame(user, score, total, percent); } diff --git a/src/view/ExamSetupFrame.java b/src/view/ExamSetupFrame.java index a67fcf4..0da4e57 100644 --- a/src/view/ExamSetupFrame.java +++ b/src/view/ExamSetupFrame.java @@ -1,7 +1,16 @@ package view; -import javax.swing.*; -import java.awt.*; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.BorderFactory; + +import java.awt.GridLayout; + import controller.FunctionController; import model.Login; import model.LanguageSwitch; @@ -10,6 +19,7 @@ import model.LanguageSwitch; * 出题设置:选择/切换难度(显示当前账户默认难度),输入题数(10-30) */ public class ExamSetupFrame extends JFrame { + private JTextField numberField; private JComboBox levelBox; private FunctionController controller; @@ -24,10 +34,10 @@ public class ExamSetupFrame extends JFrame { setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - JPanel p = new JPanel(new GridLayout(4,2,8,8)); - p.setBorder(BorderFactory.createEmptyBorder(8,8,8,8)); + JPanel p = new JPanel(new GridLayout(4, 2, 8, 8)); + p.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); p.add(new JLabel("当前难度:")); - levelBox = new JComboBox<>(new String[]{"小学","初中","高中"}); + levelBox = new JComboBox<>(new String[]{"小学", "初中", "高中"}); levelBox.setSelectedItem(LanguageSwitch.levelToChinese(user.level)); p.add(levelBox); @@ -56,7 +66,10 @@ public class ExamSetupFrame extends JFrame { } }); - backBtn.addActionListener(e -> { dispose(); new LoginFrame(); }); + backBtn.addActionListener(e -> { + dispose(); + new LoginFrame(); + }); setVisible(true); } diff --git a/src/view/LoginFrame.java b/src/view/LoginFrame.java index 01cea47..bda2dbb 100644 --- a/src/view/LoginFrame.java +++ b/src/view/LoginFrame.java @@ -1,9 +1,17 @@ package view; -import javax.swing.*; -import java.awt.*; +import java.awt.GridLayout; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.JPasswordField; +import javax.swing.BorderFactory; + import model.Login; -import controller.AssignController; /** * 登录窗口 @@ -20,7 +28,7 @@ public class LoginFrame extends JFrame { setLocationRelativeTo(null); JPanel p = new JPanel(new GridLayout(4, 2, 8, 8)); - p.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); p.add(new JLabel("用户名:")); usernameField = new JTextField(); p.add(usernameField); @@ -44,7 +52,8 @@ public class LoginFrame extends JFrame { String u = usernameField.getText().trim(); String pwd = new String(passwordField.getPassword()).trim(); if (u.isEmpty() || pwd.isEmpty()) { - JOptionPane.showMessageDialog(this, "请输入用户名和密码", "提示", JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(this, "请输入用户名和密码", "提示", + JOptionPane.INFORMATION_MESSAGE); return; } Login.Account acc = Login.login(u, pwd); @@ -52,7 +61,8 @@ public class LoginFrame extends JFrame { dispose(); new MainMenuFrame(acc); // 登录后先进入主菜单 } else { - JOptionPane.showMessageDialog(this, "用户名或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, "用户名或密码错误", "登录失败", + JOptionPane.ERROR_MESSAGE); } }); diff --git a/src/view/MainMenuFrame.java b/src/view/MainMenuFrame.java index f80f3ac..7fdc377 100644 --- a/src/view/MainMenuFrame.java +++ b/src/view/MainMenuFrame.java @@ -1,56 +1,94 @@ package view; -import javax.swing.*; -import java.awt.*; +import java.awt.GridLayout; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.BorderFactory; + import model.Login; + /** - * 登录成功后的主菜单(可修改密码、开始出题、登出) - * 现在由 ExamSetupFrame 直接替代,但保留此类可拓展 + * 登录成功后的主菜单(可修改密码、开始出题、登出) 现在由 ExamSetupFrame 直接替代,但保留此类可拓展 */ public class MainMenuFrame extends JFrame { + public MainMenuFrame(Login.Account user) { setTitle("主菜单 - " + user.username); - setSize(400,200); + setSize(400, 200); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - JPanel p = new JPanel(new GridLayout(3,1,10,10)); + JPanel p = new JPanel(new GridLayout(3, 1, 10, 10)); JButton changePwd = new JButton("修改密码"); JButton start = new JButton("出题设置"); JButton logout = new JButton("退出登录"); - p.add(start); p.add(changePwd); p.add(logout); + p.add(start); + p.add(changePwd); + p.add(logout); add(p); - start.addActionListener(e -> { new ExamSetupFrame(user); dispose(); }); + start.addActionListener(e -> { + new ExamSetupFrame(user); + dispose(); + }); changePwd.addActionListener(e -> new ChangePasswordDialog(this, user)); - logout.addActionListener(e -> { dispose(); new LoginFrame(); }); + logout.addActionListener(e -> { + dispose(); + new LoginFrame(); + }); setVisible(true); } // 内部类:修改密码对话框 static class ChangePasswordDialog extends JDialog { + public ChangePasswordDialog(JFrame owner, Login.Account user) { super(owner, "修改密码", true); - setSize(350,200); + setSize(350, 200); setLocationRelativeTo(owner); - JPanel p = new JPanel(new GridLayout(4,2,6,6)); - p.setBorder(BorderFactory.createEmptyBorder(6,6,6,6)); - p.add(new JLabel("旧密码:")); JPasswordField oldp = new JPasswordField(); p.add(oldp); - p.add(new JLabel("新密码:")); JPasswordField newp = new JPasswordField(); p.add(newp); - p.add(new JLabel("再次输入新密码:")); JPasswordField newp2 = new JPasswordField(); p.add(newp2); - JButton ok = new JButton("确定"); JButton cancel = new JButton("取消"); p.add(ok); p.add(cancel); + JPanel p = new JPanel(new GridLayout(4, 2, 6, 6)); + p.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); + p.add(new JLabel("旧密码:")); + JPasswordField oldp = new JPasswordField(); + p.add(oldp); + p.add(new JLabel("新密码:")); + JPasswordField newp = new JPasswordField(); + p.add(newp); + p.add(new JLabel("再次输入新密码:")); + JPasswordField newp2 = new JPasswordField(); + p.add(newp2); + JButton ok = new JButton("确定"); + JButton cancel = new JButton("取消"); + p.add(ok); + p.add(cancel); add(p); ok.addActionListener(a -> { String oldPwd = new String(oldp.getPassword()).trim(); String np = new String(newp.getPassword()).trim(); String np2 = new String(newp2.getPassword()).trim(); - if (!np.equals(np2)) { JOptionPane.showMessageDialog(this, "两次密码不一致"); return; } - if (!Login.validatePasswordRules(np)) { JOptionPane.showMessageDialog(this, "密码不满足规则"); return; } + if (!np.equals(np2)) { + JOptionPane.showMessageDialog(this, "两次密码不一致"); + return; + } + if (!Login.validatePasswordRules(np)) { + JOptionPane.showMessageDialog(this, "密码不满足规则"); + return; + } boolean okr = Login.changePassword(user.username, oldPwd, np); - if (okr) { JOptionPane.showMessageDialog(this, "修改成功"); dispose(); } - else JOptionPane.showMessageDialog(this, "旧密码错误"); + if (okr) { + JOptionPane.showMessageDialog(this, "修改成功"); + dispose(); + } else { + JOptionPane.showMessageDialog(this, "旧密码错误"); + } }); cancel.addActionListener(a -> dispose()); setVisible(true); diff --git a/src/view/RegisterFrame.java b/src/view/RegisterFrame.java index a9627c6..a08c1a6 100644 --- a/src/view/RegisterFrame.java +++ b/src/view/RegisterFrame.java @@ -1,11 +1,28 @@ package view; -import javax.swing.*; -import java.awt.*; +import java.awt.GridLayout; import java.util.Properties; import java.util.Random; -import jakarta.mail.*; -import jakarta.mail.internet.*; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.BorderFactory; + +import jakarta.mail.Authenticator; +import jakarta.mail.Message; +import jakarta.mail.PasswordAuthentication; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; + import model.Login; import model.LanguageSwitch; @@ -13,6 +30,7 @@ import model.LanguageSwitch; * 注册界面(使用 QQ 邮箱发送验证码) */ public class RegisterFrame extends JDialog { + private JTextField usernameField; private JTextField emailField; private JComboBox levelBox; @@ -106,7 +124,7 @@ public class RegisterFrame extends JDialog { Login.Level lv = LanguageSwitch.chineseToLevel(levelStr); boolean ok = Login.register(u, pwd, lv, email); if (!ok) { - JOptionPane.showMessageDialog(this, "用户名已存在,请换一个用户名"); + JOptionPane.showMessageDialog(this, "用户名或邮箱已存在,请换一个用户名"); return; } JOptionPane.showMessageDialog(this, "注册成功,请用新用户登录"); @@ -126,7 +144,6 @@ public class RegisterFrame extends JDialog { props.put("mail.smtp.auth", "true"); props.put("mail.smtp.ssl.enable", "true"); // ← 开启 SSL - Session session = Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(FROM_EMAIL, AUTH_CODE); diff --git a/src/view/ResultFrame.java b/src/view/ResultFrame.java index 1e424d2..a5186c4 100644 --- a/src/view/ResultFrame.java +++ b/src/view/ResultFrame.java @@ -1,21 +1,29 @@ package view; -import javax.swing.*; -import java.awt.*; +import java.awt.FlowLayout; +import java.awt.GridLayout; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + import model.Login; /** * 显示成绩并提供“退出或继续做题”的选择 */ public class ResultFrame extends JFrame { + public ResultFrame(Login.Account user, int score, int total, double percent) { setTitle("成绩 - " + user.username); - setSize(360,220); + setSize(360, 220); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - JPanel p = new JPanel(new GridLayout(5,1,6,6)); - p.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + JPanel p = new JPanel(new GridLayout(5, 1, 6, 6)); + p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); p.add(new JLabel("用户名: " + user.username)); p.add(new JLabel("得分: " + score + " / " + total)); p.add(new JLabel(String.format("百分比: %.2f%%", percent)));