From ff4ae7a98919fc854809ea55f1630f88d3793f57 Mon Sep 17 00:00:00 2001 From: tianyuan <2861334240@qq.com> Date: Thu, 9 Oct 2025 22:29:40 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E8=8D=89=E7=A8=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MathLearningApp.java | 667 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 667 insertions(+) create mode 100644 src/MathLearningApp.java diff --git a/src/MathLearningApp.java b/src/MathLearningApp.java new file mode 100644 index 0000000..52d036b --- /dev/null +++ b/src/MathLearningApp.java @@ -0,0 +1,667 @@ +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.nio.file.*; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.List; + +/** + * MathLearningApp - 单文件 Java Swing 程序 + * 保存相对数据到 ./data/ + */ +public class MathLearningApp { + // Paths + private static final Path DATA_DIR = Paths.get("data"); + private static final Path USERS_FILE = DATA_DIR.resolve("users.txt"); + private static final Path CODES_FILE = DATA_DIR.resolve("codes.txt"); + + // In-memory users map: email -> User + private static final Map users = new HashMap<>(); + + // Swing components + private JFrame frame; + private CardLayout cards; + private JPanel cardPanel; + + // Shared UI components + private JTextField regEmailField; + private JTextField codeInputField; + private JPasswordField pwdField1, pwdField2; + private JTextField loginEmailField; + private JPasswordField loginPwdField; + private JLabel statusLabel; + private JComboBox levelCombo; + private JTextField qCountField; + private JLabel questionLabel; + private JRadioButton[] choiceButtons; + private ButtonGroup choiceGroup; + private JButton nextButton; + private JLabel progressLabel; + private JLabel resultLabel; + private User currentUser; + private List currentQuestions; + private int curIndex; + private int correctCount; + private int userSelectedTemp; // store selection for current question temporarily + private SimpleDateFormat filenameSdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + + // Random + private static final SecureRandom RAND = new SecureRandom(); + + public static void main(String[] args) { + try { Files.createDirectories(DATA_DIR); } catch (IOException e) { e.printStackTrace(); } + loadUsers(); + SwingUtilities.invokeLater(() -> new MathLearningApp().createAndShowGUI()); + } + + // ---------- User persistence ---------- + static class User { + String email; + String saltHex; + String pwdHashHex; // sha256(salt+pwd) + User(String e, String s, String h){ email=e; saltHex=s; pwdHashHex=h; } + } + + private static void loadUsers() { + users.clear(); + if (!Files.exists(USERS_FILE)) return; + try { + List lines = Files.readAllLines(USERS_FILE, StandardCharsets.UTF_8); + for (String ln : lines) { + if (ln.trim().isEmpty()) continue; + // format: email|saltHex|hashHex + String[] parts = ln.split("\\|"); + if (parts.length>=3) { + users.put(parts[0], new User(parts[0], parts[1], parts[2])); + } + } + } catch (IOException e) { e.printStackTrace(); } + } + + private static void saveUsers() { + StringBuilder sb = new StringBuilder(); + users.values().forEach(u -> sb.append(u.email).append("|").append(u.saltHex).append("|").append(u.pwdHashHex).append("\n")); + try { Files.write(USERS_FILE, sb.toString().getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); } + } + + private static String bytesToHex(byte[] b){ + StringBuilder sb = new StringBuilder(); + for (byte x: b) sb.append(String.format("%02x", x)); + return sb.toString(); + } + + private static byte[] hexToBytes(String s){ + int n = s.length(); + byte[] out = new byte[n/2]; + for (int i=0;i showCard("register")); + JButton toLogin = new JButton("已有账户登录"); + toLogin.addActionListener(e -> showCard("login")); + center.add(toRegister); + center.add(toLogin); + p.add(center, BorderLayout.CENTER); + + JLabel foot = new JLabel("数据保存在 ./data/ (无数据库)", SwingConstants.CENTER); + p.add(foot, BorderLayout.SOUTH); + return p; + } + + // ---- Register step1: enter email, generate code ---- + private JPanel buildRegisterPanel(){ + JPanel p = new JPanel(new BorderLayout(10,10)); + JPanel top = new JPanel(new GridLayout(3,1,5,5)); + top.add(new JLabel("注册:请输入邮箱(示例:a@b.com)")); + regEmailField = new JTextField(); + top.add(regEmailField); + JButton genButton = new JButton("生成注册码并显示(模拟发送)"); + genButton.addActionListener(e -> handleGenerateCode()); + top.add(genButton); + p.add(top, BorderLayout.NORTH); + + JButton back = new JButton("返回"); + back.addActionListener(e -> showCard("welcome")); + p.add(back, BorderLayout.SOUTH); + return p; + } + + // ---- Register step2: input code & set password ---- + private JPanel buildSetPasswordPanel(){ + JPanel p = new JPanel(new BorderLayout(8,8)); + JPanel center = new JPanel(new GridLayout(5,1,5,5)); + center.add(new JLabel("请输入收到的注册码:")); + codeInputField = new JTextField(); + center.add(codeInputField); + center.add(new JLabel("设置密码(6-10位,必须含大小写字母和数字):")); + pwdField1 = new JPasswordField(); + pwdField2 = new JPasswordField(); + center.add(pwdField1); + center.add(pwdField2); + p.add(center, BorderLayout.CENTER); + + JPanel bot = new JPanel(); + JButton confirm = new JButton("完成注册"); + confirm.addActionListener(e -> handleCompleteRegister()); + bot.add(confirm); + JButton cancel = new JButton("取消"); + cancel.addActionListener(a -> showCard("welcome")); + bot.add(cancel); + p.add(bot, BorderLayout.SOUTH); + return p; + } + + // ---- Login panel ---- + private JPanel buildLoginPanel(){ + JPanel p = new JPanel(new BorderLayout(6,6)); + JPanel mid = new JPanel(new GridLayout(4,1,4,4)); + mid.add(new JLabel("登录:邮箱")); + loginEmailField = new JTextField(); + mid.add(loginEmailField); + mid.add(new JLabel("密码")); + loginPwdField = new JPasswordField(); + mid.add(loginPwdField); + p.add(mid, BorderLayout.CENTER); + + JPanel bot = new JPanel(); + JButton loginBtn = new JButton("登录"); + loginBtn.addActionListener(e -> handleLogin()); + JButton back = new JButton("返回"); + back.addActionListener(a -> showCard("welcome")); + bot.add(loginBtn); + bot.add(back); + p.add(bot, BorderLayout.SOUTH); + + statusLabel = new JLabel(" ", SwingConstants.CENTER); + p.add(statusLabel, BorderLayout.NORTH); + return p; + } + + // ---- Main after login: choose level, input count, change pwd ---- + private JPanel buildMainPanel(){ + JPanel p = new JPanel(new BorderLayout(8,8)); + JPanel top = new JPanel(new FlowLayout()); + top.add(new JLabel("已登录:")); + JLabel userLabel = new JLabel(); + top.add(userLabel); + JButton logout = new JButton("登出"); + logout.addActionListener(e -> { currentUser=null; showCard("welcome"); }); + top.add(logout); + JButton chPwd = new JButton("修改密码"); + chPwd.addActionListener(e -> handleChangePwd()); + top.add(chPwd); + p.add(top, BorderLayout.NORTH); + + // center: select level & count + JPanel center = new JPanel(new GridLayout(4,1,6,6)); + center.add(new JLabel("选择难度:")); + levelCombo = new JComboBox<>(new String[]{"小学","初中","高中"}); + center.add(levelCombo); + center.add(new JLabel("请输入题目数量(10-30):")); + qCountField = new JTextField(); + center.add(qCountField); + p.add(center, BorderLayout.CENTER); + + JButton start = new JButton("开始生成并做题"); + start.addActionListener(e -> { + userLabel.setText(currentUser.email); + handleStartQuiz(); + }); + p.add(start, BorderLayout.SOUTH); + + // Update user label each time card shown + cardPanel.addComponentListener(new ComponentAdapter(){ + public void componentShown(ComponentEvent e){ + if (currentUser!=null) userLabel.setText(currentUser.email); + } + }); + return p; + } + + // ---- Question Panel ---- + private JPanel buildQuestionPanel(){ + JPanel p = new JPanel(new BorderLayout(8,8)); + progressLabel = new JLabel("第 1 / N", SwingConstants.CENTER); + p.add(progressLabel, BorderLayout.NORTH); + questionLabel = new JLabel("题目", SwingConstants.CENTER); + questionLabel.setFont(new Font("Serif", Font.PLAIN, 18)); + p.add(questionLabel, BorderLayout.CENTER); + + JPanel choices = new JPanel(new GridLayout(4,1,6,6)); + choiceButtons = new JRadioButton[4]; + choiceGroup = new ButtonGroup(); + for (int i=0;i<4;i++){ + choiceButtons[i] = new JRadioButton("选项 " + (i+1)); + choiceGroup.add(choiceButtons[i]); + choices.add(choiceButtons[i]); + final int idx = i; + choiceButtons[i].addActionListener(e -> userSelectedTemp = idx); + } + p.add(choices, BorderLayout.SOUTH); + + JPanel bot = new JPanel(); + nextButton = new JButton("提交并下一题"); + nextButton.addActionListener(e -> handleSubmitAnswer()); + bot.add(nextButton); + p.add(bot, BorderLayout.PAGE_END); + return p; + } + + // ---- Result Panel ---- + private JPanel buildResultPanel(){ + JPanel p = new JPanel(new BorderLayout(8,8)); + resultLabel = new JLabel("", SwingConstants.CENTER); + resultLabel.setFont(new Font("Serif", Font.BOLD, 22)); + p.add(resultLabel, BorderLayout.CENTER); + + JPanel bot = new JPanel(); + JButton againBtn = new JButton("继续做题"); + againBtn.addActionListener(e -> showCard("main")); + JButton exitBtn = new JButton("退出程序"); + exitBtn.addActionListener(e -> System.exit(0)); + bot.add(againBtn); + bot.add(exitBtn); + p.add(bot, BorderLayout.SOUTH); + return p; + } + + // ---------- Handlers ---------- + private void handleGenerateCode(){ + String email = regEmailField.getText().trim(); + if (!validateEmail(email)) { JOptionPane.showMessageDialog(frame, "请输入合法邮箱"); return;} + // create code + String code = String.format("%06d", RAND.nextInt(1_000_000)); + try { + String line = email + "|" + code + "|" + new Date().toString() + "\n"; + Files.createDirectories(DATA_DIR); + Files.write(CODES_FILE, line.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } catch (IOException ex){ ex.printStackTrace(); } + // Show code in dialog (simulate email) + JOptionPane.showMessageDialog(frame, "注册码(模拟发送): " + code + "\n已保存到 data/codes.txt"); + // save email + code to a temporary file for verification step. (Entry in codes.txt) + // Switch to set password panel + showCard("setpwd"); + } + + private void handleCompleteRegister(){ + String email = regEmailField.getText().trim(); + if (!validateEmail(email)) { JOptionPane.showMessageDialog(frame, "邮箱格式不正确"); return; } + // read latest code for this email from codes.txt + String provided = codeInputField.getText().trim(); + if (provided.isEmpty()) { JOptionPane.showMessageDialog(frame, "请输入注册码"); return;} + boolean ok=false; + if (Files.exists(CODES_FILE)){ + try { + List lines = Files.readAllLines(CODES_FILE, StandardCharsets.UTF_8); + for (int i=lines.size()-1;i>=0;i--){ + String ln = lines.get(i); + if (ln.startsWith(email+"|")){ + String[] ps = ln.split("\\|"); + if (ps.length>=2 && ps[1].equals(provided)) { ok=true; break;} + } + } + } catch (IOException e){ e.printStackTrace(); } + } + if (!ok) { JOptionPane.showMessageDialog(frame, "注册码错误或未生成,请先生成注册码"); return; } + + String p1 = new String(pwdField1.getPassword()); + String p2 = new String(pwdField2.getPassword()); + if (!p1.equals(p2)) { JOptionPane.showMessageDialog(frame, "两次密码输入不一致"); return; } + if (!validatePasswordRules(p1)) { JOptionPane.showMessageDialog(frame, "密码不符合规则(6-10位,含大小写与数字)"); return; } + + // cannot duplicate register + if (users.containsKey(email)) { JOptionPane.showMessageDialog(frame, "该邮箱已注册"); showCard("welcome"); return; } + + // create salt + hash + byte[] salt = new byte[12]; RAND.nextBytes(salt); + String saltHex = bytesToHex(salt); + String hash = sha256Hex((saltHex + p1).getBytes(StandardCharsets.UTF_8)); + User u = new User(email, saltHex, hash); + users.put(email, u); + saveUsers(); + // create user dir + try { Files.createDirectories(DATA_DIR.resolve(email)); } catch (IOException ex) { ex.printStackTrace(); } + JOptionPane.showMessageDialog(frame, "注册成功,已保存用户信息"); + showCard("welcome"); + } + + private void handleLogin(){ + String email = loginEmailField.getText().trim(); + String pwd = new String(loginPwdField.getPassword()); + if (!users.containsKey(email)) { statusLabel.setText("请输入正确的用户名、密码"); return; } + User u = users.get(email); + String hx = sha256Hex((u.saltHex + pwd).getBytes(StandardCharsets.UTF_8)); + if (!hx.equals(u.pwdHashHex)) { statusLabel.setText("请输入正确的用户名、密码"); return; } + // success + currentUser = u; + statusLabel.setText("登录成功,当前选择为 小学/初中/高中(请在主界面选择)"); + showCard("main"); + } + + private void handleChangePwd(){ + if (currentUser==null) { JOptionPane.showMessageDialog(frame, "请先登录"); return; } + JPanel p = new JPanel(new GridLayout(4,1)); + JPasswordField oldp = new JPasswordField(); + JPasswordField newp1 = new JPasswordField(); + JPasswordField newp2 = new JPasswordField(); + p.add(new JLabel("请输入原密码:")); + p.add(oldp); + p.add(new JLabel("请输入新密码(6-10,含大小写与数字):")); + p.add(newp1); + int res = JOptionPane.showConfirmDialog(frame, p, "修改密码", JOptionPane.OK_CANCEL_OPTION); + if (res!=JOptionPane.OK_OPTION) return; + String old = new String(oldp.getPassword()); + String np1 = new String(newp1.getPassword()); + // verify old + String hxOld = sha256Hex((currentUser.saltHex + old).getBytes(StandardCharsets.UTF_8)); + if (!hxOld.equals(currentUser.pwdHashHex)) { JOptionPane.showMessageDialog(frame, "原密码错误"); return; } + // ask confirm second + String np2 = JOptionPane.showInputDialog(frame, "请再次输入新密码以确认:"); + if (np2==null) return; + if (!np1.equals(np2)) { JOptionPane.showMessageDialog(frame, "两次新密码不一致"); return; } + if (!validatePasswordRules(np1)) { JOptionPane.showMessageDialog(frame, "密码不符合规则"); return; } + // update + currentUser.saltHex = bytesToHex((currentUser.saltHex + RAND.nextInt()).getBytes()); // re-salt + currentUser.pwdHashHex = sha256Hex((currentUser.saltHex + np1).getBytes(StandardCharsets.UTF_8)); + users.put(currentUser.email, currentUser); + saveUsers(); + JOptionPane.showMessageDialog(frame, "修改密码成功"); + } + + private void handleStartQuiz(){ + if (currentUser==null) { JOptionPane.showMessageDialog(frame, "请登录"); return; } + String level = (String)levelCombo.getSelectedItem(); + String cnts = qCountField.getText().trim(); + int cnt; + try { cnt = Integer.parseInt(cnts); } catch (Exception e){ JOptionPane.showMessageDialog(frame, "请输入数字"); return; } + if (cnt==-1) { // signout + currentUser=null; showCard("welcome"); return; + } + if (cnt < 10 || cnt > 30) { JOptionPane.showMessageDialog(frame, "题目数量必须在10-30之间"); return; } + // generate questions + List existing = readAllPastQuestionsForUser(currentUser.email); + currentQuestions = new ArrayList<>(); + Set used = new HashSet<>(existing); + int attempts = 0; + while (currentQuestions.size() < cnt && attempts < cnt * 20) { + attempts++; + Question q = QuestionGenerator.generate(level); + if (used.contains(q.text)) continue; + // also ensure no duplicate within currentQuiz + boolean dup=false; + for (Question qq: currentQuestions) if (qq.text.equals(q.text)) { dup=true; break;} + if (dup) continue; + currentQuestions.add(q); + used.add(q.text); + } + if (currentQuestions.size() < cnt) { + JOptionPane.showMessageDialog(frame, "无法生成足够不重复的题目,已生成 " + currentQuestions.size() + " 题"); + } + curIndex = 0; correctCount = 0; + showQuestionAt(curIndex); + showCard("question"); + } + + private void showQuestionAt(int idx){ + if (idx < 0 || idx >= currentQuestions.size()) return; + Question q = currentQuestions.get(idx); + progressLabel.setText(String.format("第 %d / %d", idx+1, currentQuestions.size())); + questionLabel.setText("
"+ (idx+1) + ". " + q.text + "
"); + List opts = q.options; + for (int i=0;i<4;i++){ choiceButtons[i].setText(opts.get(i)); choiceButtons[i].setSelected(false);} + choiceGroup.clearSelection(); + userSelectedTemp = -1; + } + + private void handleSubmitAnswer(){ + if (userSelectedTemp < 0) { JOptionPane.showMessageDialog(frame, "请选择一个选项"); return; } + Question q = currentQuestions.get(curIndex); + if (userSelectedTemp == q.correctIndex) correctCount++; + curIndex++; + if (curIndex >= currentQuestions.size()){ + // finish + double percent = (100.0 * correctCount) / currentQuestions.size(); + resultLabel.setText(String.format("完成!得分:%d/%d (%.2f%%)", correctCount, currentQuestions.size(), percent)); + // save quiz + saveQuizForUser(currentUser.email, currentQuestions); + showCard("result"); + } else { + showQuestionAt(curIndex); + } + } + + // ---------- Question persistence and save ---------- + private List readAllPastQuestionsForUser(String email){ + List out = new ArrayList<>(); + Path userDir = DATA_DIR.resolve(email); + if (!Files.exists(userDir)) return out; + try (DirectoryStream ds = Files.newDirectoryStream(userDir, "*.txt")){ + for (Path p: ds){ + List lines = Files.readAllLines(p, StandardCharsets.UTF_8); + for (String ln: lines) { + if (ln.trim().isEmpty()) continue; + // saved file format: "1. question text" per problem line; we will strip leading number. + String t = ln.trim(); + if (t.matches("^\\d+\\..*")) t = t.replaceFirst("^\\d+\\.\\s*", ""); + out.add(t); + } + } + } catch (IOException e){ e.printStackTrace(); } + return out; + } + + private void saveQuizForUser(String email, List qs){ + try { + Path userDir = DATA_DIR.resolve(email); + Files.createDirectories(userDir); + String fname = filenameSdf.format(new Date()) + ".txt"; + Path fp = userDir.resolve(fname); + StringBuilder sb = new StringBuilder(); + int i=1; + for (Question q: qs){ + sb.append(i).append(". ").append(q.text).append("\n\n"); + i++; + } + Files.write(fp, sb.toString().getBytes(StandardCharsets.UTF_8)); + } catch (IOException e){ e.printStackTrace(); } + } + + // ---------- Utils ---------- + private void showCard(String name){ cards.show(cardPanel, name); } + + private boolean validateEmail(String s){ + return s!=null && s.matches("^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,6}$"); + } + + private boolean validatePasswordRules(String p){ + if (p==null) return false; + if (p.length() <6 || p.length() >10) return false; + boolean up=false, low=false, dig=false; + for (char c: p.toCharArray()){ + if (Character.isUpperCase(c)) up=true; + if (Character.isLowerCase(c)) low=true; + if (Character.isDigit(c)) dig=true; + } + return up && low && dig; + } + + // ---------- Question generation ---------- + static class Question { + String text; + List options; + int correctIndex; + Question(String t, List opts, int idx){ text=t; options=opts; correctIndex=idx; } + } + + static class QuestionGenerator { + private static final Random R = new Random(); + static Question generate(String level){ + switch(level){ + case "小学": return genPrimary(); + case "初中": return genMiddle(); + case "高中": return genHigh(); + default: return genPrimary(); + } + } + + // Primary: + - * / and parentheses, 1-5 operands, values 1-100 + private static Question genPrimary(){ + int ops = 1 + R.nextInt(5); // operands count 1-5 -> ops between 1 and 5, generate expression with ops operands? we'll use random length 2-5 operands + int operands = 1 + R.nextInt(5); + String expr = ""; + for (int i=0;i=3 && R.nextBoolean()){ + expr = "(" + expr + ")"; + } + double val = safeEval(expr); + List opts = makeChoices(val); + int correct = opts.indexOf(formatNumber(val)); + if (correct<0) { + opts.set(0, formatNumber(val)); correct=0; + } + return new Question(expr, opts, correct); + } + + // Middle: include at least one square (x^2) or sqrt + private static Question genMiddle(){ + // choose either square or sqrt + boolean square = R.nextBoolean(); + String expr; + double val; + if (square){ + int a = 1 + R.nextInt(20); + expr = a + "² + " + (1 + R.nextInt(30)); + val = a*a + (1 + R.nextInt(30)); + } else { + int a = 1 + R.nextInt(20); + expr = "√" + (a*a) + " + " + (1 + R.nextInt(30)); // ensure integer sqrt + val = a + (1 + R.nextInt(30)); + } + List opts = makeChoices(val); + int correct = opts.indexOf(formatNumber(val)); + if (correct<0){ opts.set(0, formatNumber(val)); correct=0;} + return new Question(expr, opts, correct); + } + + // High: include a trig function sin/cos/tan (angles chosen from nice set) + private static Question genHigh(){ + String[] funcs = {"sin","cos","tan"}; + int idx = R.nextInt(funcs.length); + String f = funcs[idx]; + int[] angles = {0,30,45,60,90}; + int ang = angles[R.nextInt(angles.length)]; + String expr = f + "(" + ang + "°) + " + (1 + R.nextInt(10)); + double trig = trigValue(f, ang); + double val = trig + (1 + R.nextInt(10)); + List opts = makeChoices(val); + int correct = opts.indexOf(formatNumber(val)); + if (correct<0){ opts.set(0, formatNumber(val)); correct=0;} + return new Question(expr, opts, correct); + } + + private static double trigValue(String f, int deg){ + double rad = Math.toRadians(deg); + switch(f){ + case "sin": return Math.round(Math.sin(rad)*100.0)/100.0; + case "cos": return Math.round(Math.cos(rad)*100.0)/100.0; + case "tan": return Math.round(Math.tan(rad)*100.0)/100.0; + } + return 0; + } + + // helpers + private static String randomOp(String[] arr){ return arr[R.nextInt(arr.length)]; } + + private static double safeEval(String expr){ + // very simple evaluator for + - * / with integers (no operator precedence beyond left-to-right). + // For our generated expr this suffices. Use double. + try { + String[] toks = expr.replace("(", "").replace(")", "").split("\\s+"); + double res = Double.parseDouble(toks[0]); + for (int i=1;i makeChoices(double correct){ + List opts = new ArrayList<>(); + opts.add(formatNumber(correct)); + // create 3 distractors + for (int i=0;i<3;i++){ + double delta = (R.nextDouble() * 10 + 1) * (R.nextBoolean() ? 1 : -1); + double v = Math.round((correct + delta) * 100.0)/100.0; + // avoid collision + int tries=0; + while (opts.contains(formatNumber(v)) && tries<10){ v = Math.round((correct + (R.nextDouble()*5+1)*(R.nextBoolean()?1:-1))*100.0)/100.0; tries++; } + opts.add(formatNumber(v)); + } + Collections.shuffle(opts); + int idx = opts.indexOf(formatNumber(correct)); + return opts; + } + + private static String formatNumber(double v){ + if (Math.abs(v - Math.round(v)) < 0.0001) return String.valueOf((long)Math.round(v)); + return String.format("%.2f", v); + } + } +} -- 2.34.1 From 8778fda2b7364463101061bce77d21be4ca50f02 Mon Sep 17 00:00:00 2001 From: tianyuan <2861334240@qq.com> Date: Thu, 9 Oct 2025 22:44:40 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E5=A4=9A=E6=96=87=E4=BB=B6=E6=94=B9?= =?UTF-8?q?=E7=89=881?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 50 +++ src/Main.java | 7 + src/MathLearningApp.java | 667 ------------------------------ src/controller/AppController.java | 66 +++ src/controller/UserManager.java | 60 +++ src/model/Question.java | 14 + src/model/QuestionGenerator.java | 124 ++++++ src/model/User.java | 13 + src/ui/LoginPanel.java | 57 +++ src/ui/RegisterPanel.java | 110 +++++ src/util/EmailUtil.java | 50 +++ src/util/FileUtil.java | 31 ++ src/view/QuizPanel.java | 88 ++++ src/view/ResultPanel.java | 19 + 14 files changed, 689 insertions(+), 667 deletions(-) create mode 100644 pom.xml create mode 100644 src/Main.java delete mode 100644 src/MathLearningApp.java create mode 100644 src/controller/AppController.java create mode 100644 src/controller/UserManager.java create mode 100644 src/model/Question.java create mode 100644 src/model/QuestionGenerator.java create mode 100644 src/model/User.java create mode 100644 src/ui/LoginPanel.java create mode 100644 src/ui/RegisterPanel.java create mode 100644 src/util/EmailUtil.java create mode 100644 src/util/FileUtil.java create mode 100644 src/view/QuizPanel.java create mode 100644 src/view/ResultPanel.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..87c0d4f --- /dev/null +++ b/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + com.mathquiz + MathQuizApp + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + + com.sun.mail + jakarta.mail + 2.0.1 + + + + + + com.sun.activation + javax.activation + 1.2.0 + + + + + org.projectlombok + lombok + 1.18.32 + provided + + + + + + aliyunmaven + Aliyun Maven + https://maven.aliyun.com/repository/public + + + diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..4362b97 --- /dev/null +++ b/src/Main.java @@ -0,0 +1,7 @@ +import controller.AppController; + +public class Main { + public static void main(String[] args) { + new AppController(); // 启动控制器 + } +} diff --git a/src/MathLearningApp.java b/src/MathLearningApp.java deleted file mode 100644 index 52d036b..0000000 --- a/src/MathLearningApp.java +++ /dev/null @@ -1,667 +0,0 @@ -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.io.*; -import java.nio.file.*; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.List; - -/** - * MathLearningApp - 单文件 Java Swing 程序 - * 保存相对数据到 ./data/ - */ -public class MathLearningApp { - // Paths - private static final Path DATA_DIR = Paths.get("data"); - private static final Path USERS_FILE = DATA_DIR.resolve("users.txt"); - private static final Path CODES_FILE = DATA_DIR.resolve("codes.txt"); - - // In-memory users map: email -> User - private static final Map users = new HashMap<>(); - - // Swing components - private JFrame frame; - private CardLayout cards; - private JPanel cardPanel; - - // Shared UI components - private JTextField regEmailField; - private JTextField codeInputField; - private JPasswordField pwdField1, pwdField2; - private JTextField loginEmailField; - private JPasswordField loginPwdField; - private JLabel statusLabel; - private JComboBox levelCombo; - private JTextField qCountField; - private JLabel questionLabel; - private JRadioButton[] choiceButtons; - private ButtonGroup choiceGroup; - private JButton nextButton; - private JLabel progressLabel; - private JLabel resultLabel; - private User currentUser; - private List currentQuestions; - private int curIndex; - private int correctCount; - private int userSelectedTemp; // store selection for current question temporarily - private SimpleDateFormat filenameSdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); - - // Random - private static final SecureRandom RAND = new SecureRandom(); - - public static void main(String[] args) { - try { Files.createDirectories(DATA_DIR); } catch (IOException e) { e.printStackTrace(); } - loadUsers(); - SwingUtilities.invokeLater(() -> new MathLearningApp().createAndShowGUI()); - } - - // ---------- User persistence ---------- - static class User { - String email; - String saltHex; - String pwdHashHex; // sha256(salt+pwd) - User(String e, String s, String h){ email=e; saltHex=s; pwdHashHex=h; } - } - - private static void loadUsers() { - users.clear(); - if (!Files.exists(USERS_FILE)) return; - try { - List lines = Files.readAllLines(USERS_FILE, StandardCharsets.UTF_8); - for (String ln : lines) { - if (ln.trim().isEmpty()) continue; - // format: email|saltHex|hashHex - String[] parts = ln.split("\\|"); - if (parts.length>=3) { - users.put(parts[0], new User(parts[0], parts[1], parts[2])); - } - } - } catch (IOException e) { e.printStackTrace(); } - } - - private static void saveUsers() { - StringBuilder sb = new StringBuilder(); - users.values().forEach(u -> sb.append(u.email).append("|").append(u.saltHex).append("|").append(u.pwdHashHex).append("\n")); - try { Files.write(USERS_FILE, sb.toString().getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); } - } - - private static String bytesToHex(byte[] b){ - StringBuilder sb = new StringBuilder(); - for (byte x: b) sb.append(String.format("%02x", x)); - return sb.toString(); - } - - private static byte[] hexToBytes(String s){ - int n = s.length(); - byte[] out = new byte[n/2]; - for (int i=0;i showCard("register")); - JButton toLogin = new JButton("已有账户登录"); - toLogin.addActionListener(e -> showCard("login")); - center.add(toRegister); - center.add(toLogin); - p.add(center, BorderLayout.CENTER); - - JLabel foot = new JLabel("数据保存在 ./data/ (无数据库)", SwingConstants.CENTER); - p.add(foot, BorderLayout.SOUTH); - return p; - } - - // ---- Register step1: enter email, generate code ---- - private JPanel buildRegisterPanel(){ - JPanel p = new JPanel(new BorderLayout(10,10)); - JPanel top = new JPanel(new GridLayout(3,1,5,5)); - top.add(new JLabel("注册:请输入邮箱(示例:a@b.com)")); - regEmailField = new JTextField(); - top.add(regEmailField); - JButton genButton = new JButton("生成注册码并显示(模拟发送)"); - genButton.addActionListener(e -> handleGenerateCode()); - top.add(genButton); - p.add(top, BorderLayout.NORTH); - - JButton back = new JButton("返回"); - back.addActionListener(e -> showCard("welcome")); - p.add(back, BorderLayout.SOUTH); - return p; - } - - // ---- Register step2: input code & set password ---- - private JPanel buildSetPasswordPanel(){ - JPanel p = new JPanel(new BorderLayout(8,8)); - JPanel center = new JPanel(new GridLayout(5,1,5,5)); - center.add(new JLabel("请输入收到的注册码:")); - codeInputField = new JTextField(); - center.add(codeInputField); - center.add(new JLabel("设置密码(6-10位,必须含大小写字母和数字):")); - pwdField1 = new JPasswordField(); - pwdField2 = new JPasswordField(); - center.add(pwdField1); - center.add(pwdField2); - p.add(center, BorderLayout.CENTER); - - JPanel bot = new JPanel(); - JButton confirm = new JButton("完成注册"); - confirm.addActionListener(e -> handleCompleteRegister()); - bot.add(confirm); - JButton cancel = new JButton("取消"); - cancel.addActionListener(a -> showCard("welcome")); - bot.add(cancel); - p.add(bot, BorderLayout.SOUTH); - return p; - } - - // ---- Login panel ---- - private JPanel buildLoginPanel(){ - JPanel p = new JPanel(new BorderLayout(6,6)); - JPanel mid = new JPanel(new GridLayout(4,1,4,4)); - mid.add(new JLabel("登录:邮箱")); - loginEmailField = new JTextField(); - mid.add(loginEmailField); - mid.add(new JLabel("密码")); - loginPwdField = new JPasswordField(); - mid.add(loginPwdField); - p.add(mid, BorderLayout.CENTER); - - JPanel bot = new JPanel(); - JButton loginBtn = new JButton("登录"); - loginBtn.addActionListener(e -> handleLogin()); - JButton back = new JButton("返回"); - back.addActionListener(a -> showCard("welcome")); - bot.add(loginBtn); - bot.add(back); - p.add(bot, BorderLayout.SOUTH); - - statusLabel = new JLabel(" ", SwingConstants.CENTER); - p.add(statusLabel, BorderLayout.NORTH); - return p; - } - - // ---- Main after login: choose level, input count, change pwd ---- - private JPanel buildMainPanel(){ - JPanel p = new JPanel(new BorderLayout(8,8)); - JPanel top = new JPanel(new FlowLayout()); - top.add(new JLabel("已登录:")); - JLabel userLabel = new JLabel(); - top.add(userLabel); - JButton logout = new JButton("登出"); - logout.addActionListener(e -> { currentUser=null; showCard("welcome"); }); - top.add(logout); - JButton chPwd = new JButton("修改密码"); - chPwd.addActionListener(e -> handleChangePwd()); - top.add(chPwd); - p.add(top, BorderLayout.NORTH); - - // center: select level & count - JPanel center = new JPanel(new GridLayout(4,1,6,6)); - center.add(new JLabel("选择难度:")); - levelCombo = new JComboBox<>(new String[]{"小学","初中","高中"}); - center.add(levelCombo); - center.add(new JLabel("请输入题目数量(10-30):")); - qCountField = new JTextField(); - center.add(qCountField); - p.add(center, BorderLayout.CENTER); - - JButton start = new JButton("开始生成并做题"); - start.addActionListener(e -> { - userLabel.setText(currentUser.email); - handleStartQuiz(); - }); - p.add(start, BorderLayout.SOUTH); - - // Update user label each time card shown - cardPanel.addComponentListener(new ComponentAdapter(){ - public void componentShown(ComponentEvent e){ - if (currentUser!=null) userLabel.setText(currentUser.email); - } - }); - return p; - } - - // ---- Question Panel ---- - private JPanel buildQuestionPanel(){ - JPanel p = new JPanel(new BorderLayout(8,8)); - progressLabel = new JLabel("第 1 / N", SwingConstants.CENTER); - p.add(progressLabel, BorderLayout.NORTH); - questionLabel = new JLabel("题目", SwingConstants.CENTER); - questionLabel.setFont(new Font("Serif", Font.PLAIN, 18)); - p.add(questionLabel, BorderLayout.CENTER); - - JPanel choices = new JPanel(new GridLayout(4,1,6,6)); - choiceButtons = new JRadioButton[4]; - choiceGroup = new ButtonGroup(); - for (int i=0;i<4;i++){ - choiceButtons[i] = new JRadioButton("选项 " + (i+1)); - choiceGroup.add(choiceButtons[i]); - choices.add(choiceButtons[i]); - final int idx = i; - choiceButtons[i].addActionListener(e -> userSelectedTemp = idx); - } - p.add(choices, BorderLayout.SOUTH); - - JPanel bot = new JPanel(); - nextButton = new JButton("提交并下一题"); - nextButton.addActionListener(e -> handleSubmitAnswer()); - bot.add(nextButton); - p.add(bot, BorderLayout.PAGE_END); - return p; - } - - // ---- Result Panel ---- - private JPanel buildResultPanel(){ - JPanel p = new JPanel(new BorderLayout(8,8)); - resultLabel = new JLabel("", SwingConstants.CENTER); - resultLabel.setFont(new Font("Serif", Font.BOLD, 22)); - p.add(resultLabel, BorderLayout.CENTER); - - JPanel bot = new JPanel(); - JButton againBtn = new JButton("继续做题"); - againBtn.addActionListener(e -> showCard("main")); - JButton exitBtn = new JButton("退出程序"); - exitBtn.addActionListener(e -> System.exit(0)); - bot.add(againBtn); - bot.add(exitBtn); - p.add(bot, BorderLayout.SOUTH); - return p; - } - - // ---------- Handlers ---------- - private void handleGenerateCode(){ - String email = regEmailField.getText().trim(); - if (!validateEmail(email)) { JOptionPane.showMessageDialog(frame, "请输入合法邮箱"); return;} - // create code - String code = String.format("%06d", RAND.nextInt(1_000_000)); - try { - String line = email + "|" + code + "|" + new Date().toString() + "\n"; - Files.createDirectories(DATA_DIR); - Files.write(CODES_FILE, line.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND); - } catch (IOException ex){ ex.printStackTrace(); } - // Show code in dialog (simulate email) - JOptionPane.showMessageDialog(frame, "注册码(模拟发送): " + code + "\n已保存到 data/codes.txt"); - // save email + code to a temporary file for verification step. (Entry in codes.txt) - // Switch to set password panel - showCard("setpwd"); - } - - private void handleCompleteRegister(){ - String email = regEmailField.getText().trim(); - if (!validateEmail(email)) { JOptionPane.showMessageDialog(frame, "邮箱格式不正确"); return; } - // read latest code for this email from codes.txt - String provided = codeInputField.getText().trim(); - if (provided.isEmpty()) { JOptionPane.showMessageDialog(frame, "请输入注册码"); return;} - boolean ok=false; - if (Files.exists(CODES_FILE)){ - try { - List lines = Files.readAllLines(CODES_FILE, StandardCharsets.UTF_8); - for (int i=lines.size()-1;i>=0;i--){ - String ln = lines.get(i); - if (ln.startsWith(email+"|")){ - String[] ps = ln.split("\\|"); - if (ps.length>=2 && ps[1].equals(provided)) { ok=true; break;} - } - } - } catch (IOException e){ e.printStackTrace(); } - } - if (!ok) { JOptionPane.showMessageDialog(frame, "注册码错误或未生成,请先生成注册码"); return; } - - String p1 = new String(pwdField1.getPassword()); - String p2 = new String(pwdField2.getPassword()); - if (!p1.equals(p2)) { JOptionPane.showMessageDialog(frame, "两次密码输入不一致"); return; } - if (!validatePasswordRules(p1)) { JOptionPane.showMessageDialog(frame, "密码不符合规则(6-10位,含大小写与数字)"); return; } - - // cannot duplicate register - if (users.containsKey(email)) { JOptionPane.showMessageDialog(frame, "该邮箱已注册"); showCard("welcome"); return; } - - // create salt + hash - byte[] salt = new byte[12]; RAND.nextBytes(salt); - String saltHex = bytesToHex(salt); - String hash = sha256Hex((saltHex + p1).getBytes(StandardCharsets.UTF_8)); - User u = new User(email, saltHex, hash); - users.put(email, u); - saveUsers(); - // create user dir - try { Files.createDirectories(DATA_DIR.resolve(email)); } catch (IOException ex) { ex.printStackTrace(); } - JOptionPane.showMessageDialog(frame, "注册成功,已保存用户信息"); - showCard("welcome"); - } - - private void handleLogin(){ - String email = loginEmailField.getText().trim(); - String pwd = new String(loginPwdField.getPassword()); - if (!users.containsKey(email)) { statusLabel.setText("请输入正确的用户名、密码"); return; } - User u = users.get(email); - String hx = sha256Hex((u.saltHex + pwd).getBytes(StandardCharsets.UTF_8)); - if (!hx.equals(u.pwdHashHex)) { statusLabel.setText("请输入正确的用户名、密码"); return; } - // success - currentUser = u; - statusLabel.setText("登录成功,当前选择为 小学/初中/高中(请在主界面选择)"); - showCard("main"); - } - - private void handleChangePwd(){ - if (currentUser==null) { JOptionPane.showMessageDialog(frame, "请先登录"); return; } - JPanel p = new JPanel(new GridLayout(4,1)); - JPasswordField oldp = new JPasswordField(); - JPasswordField newp1 = new JPasswordField(); - JPasswordField newp2 = new JPasswordField(); - p.add(new JLabel("请输入原密码:")); - p.add(oldp); - p.add(new JLabel("请输入新密码(6-10,含大小写与数字):")); - p.add(newp1); - int res = JOptionPane.showConfirmDialog(frame, p, "修改密码", JOptionPane.OK_CANCEL_OPTION); - if (res!=JOptionPane.OK_OPTION) return; - String old = new String(oldp.getPassword()); - String np1 = new String(newp1.getPassword()); - // verify old - String hxOld = sha256Hex((currentUser.saltHex + old).getBytes(StandardCharsets.UTF_8)); - if (!hxOld.equals(currentUser.pwdHashHex)) { JOptionPane.showMessageDialog(frame, "原密码错误"); return; } - // ask confirm second - String np2 = JOptionPane.showInputDialog(frame, "请再次输入新密码以确认:"); - if (np2==null) return; - if (!np1.equals(np2)) { JOptionPane.showMessageDialog(frame, "两次新密码不一致"); return; } - if (!validatePasswordRules(np1)) { JOptionPane.showMessageDialog(frame, "密码不符合规则"); return; } - // update - currentUser.saltHex = bytesToHex((currentUser.saltHex + RAND.nextInt()).getBytes()); // re-salt - currentUser.pwdHashHex = sha256Hex((currentUser.saltHex + np1).getBytes(StandardCharsets.UTF_8)); - users.put(currentUser.email, currentUser); - saveUsers(); - JOptionPane.showMessageDialog(frame, "修改密码成功"); - } - - private void handleStartQuiz(){ - if (currentUser==null) { JOptionPane.showMessageDialog(frame, "请登录"); return; } - String level = (String)levelCombo.getSelectedItem(); - String cnts = qCountField.getText().trim(); - int cnt; - try { cnt = Integer.parseInt(cnts); } catch (Exception e){ JOptionPane.showMessageDialog(frame, "请输入数字"); return; } - if (cnt==-1) { // signout - currentUser=null; showCard("welcome"); return; - } - if (cnt < 10 || cnt > 30) { JOptionPane.showMessageDialog(frame, "题目数量必须在10-30之间"); return; } - // generate questions - List existing = readAllPastQuestionsForUser(currentUser.email); - currentQuestions = new ArrayList<>(); - Set used = new HashSet<>(existing); - int attempts = 0; - while (currentQuestions.size() < cnt && attempts < cnt * 20) { - attempts++; - Question q = QuestionGenerator.generate(level); - if (used.contains(q.text)) continue; - // also ensure no duplicate within currentQuiz - boolean dup=false; - for (Question qq: currentQuestions) if (qq.text.equals(q.text)) { dup=true; break;} - if (dup) continue; - currentQuestions.add(q); - used.add(q.text); - } - if (currentQuestions.size() < cnt) { - JOptionPane.showMessageDialog(frame, "无法生成足够不重复的题目,已生成 " + currentQuestions.size() + " 题"); - } - curIndex = 0; correctCount = 0; - showQuestionAt(curIndex); - showCard("question"); - } - - private void showQuestionAt(int idx){ - if (idx < 0 || idx >= currentQuestions.size()) return; - Question q = currentQuestions.get(idx); - progressLabel.setText(String.format("第 %d / %d", idx+1, currentQuestions.size())); - questionLabel.setText("
"+ (idx+1) + ". " + q.text + "
"); - List opts = q.options; - for (int i=0;i<4;i++){ choiceButtons[i].setText(opts.get(i)); choiceButtons[i].setSelected(false);} - choiceGroup.clearSelection(); - userSelectedTemp = -1; - } - - private void handleSubmitAnswer(){ - if (userSelectedTemp < 0) { JOptionPane.showMessageDialog(frame, "请选择一个选项"); return; } - Question q = currentQuestions.get(curIndex); - if (userSelectedTemp == q.correctIndex) correctCount++; - curIndex++; - if (curIndex >= currentQuestions.size()){ - // finish - double percent = (100.0 * correctCount) / currentQuestions.size(); - resultLabel.setText(String.format("完成!得分:%d/%d (%.2f%%)", correctCount, currentQuestions.size(), percent)); - // save quiz - saveQuizForUser(currentUser.email, currentQuestions); - showCard("result"); - } else { - showQuestionAt(curIndex); - } - } - - // ---------- Question persistence and save ---------- - private List readAllPastQuestionsForUser(String email){ - List out = new ArrayList<>(); - Path userDir = DATA_DIR.resolve(email); - if (!Files.exists(userDir)) return out; - try (DirectoryStream ds = Files.newDirectoryStream(userDir, "*.txt")){ - for (Path p: ds){ - List lines = Files.readAllLines(p, StandardCharsets.UTF_8); - for (String ln: lines) { - if (ln.trim().isEmpty()) continue; - // saved file format: "1. question text" per problem line; we will strip leading number. - String t = ln.trim(); - if (t.matches("^\\d+\\..*")) t = t.replaceFirst("^\\d+\\.\\s*", ""); - out.add(t); - } - } - } catch (IOException e){ e.printStackTrace(); } - return out; - } - - private void saveQuizForUser(String email, List qs){ - try { - Path userDir = DATA_DIR.resolve(email); - Files.createDirectories(userDir); - String fname = filenameSdf.format(new Date()) + ".txt"; - Path fp = userDir.resolve(fname); - StringBuilder sb = new StringBuilder(); - int i=1; - for (Question q: qs){ - sb.append(i).append(". ").append(q.text).append("\n\n"); - i++; - } - Files.write(fp, sb.toString().getBytes(StandardCharsets.UTF_8)); - } catch (IOException e){ e.printStackTrace(); } - } - - // ---------- Utils ---------- - private void showCard(String name){ cards.show(cardPanel, name); } - - private boolean validateEmail(String s){ - return s!=null && s.matches("^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,6}$"); - } - - private boolean validatePasswordRules(String p){ - if (p==null) return false; - if (p.length() <6 || p.length() >10) return false; - boolean up=false, low=false, dig=false; - for (char c: p.toCharArray()){ - if (Character.isUpperCase(c)) up=true; - if (Character.isLowerCase(c)) low=true; - if (Character.isDigit(c)) dig=true; - } - return up && low && dig; - } - - // ---------- Question generation ---------- - static class Question { - String text; - List options; - int correctIndex; - Question(String t, List opts, int idx){ text=t; options=opts; correctIndex=idx; } - } - - static class QuestionGenerator { - private static final Random R = new Random(); - static Question generate(String level){ - switch(level){ - case "小学": return genPrimary(); - case "初中": return genMiddle(); - case "高中": return genHigh(); - default: return genPrimary(); - } - } - - // Primary: + - * / and parentheses, 1-5 operands, values 1-100 - private static Question genPrimary(){ - int ops = 1 + R.nextInt(5); // operands count 1-5 -> ops between 1 and 5, generate expression with ops operands? we'll use random length 2-5 operands - int operands = 1 + R.nextInt(5); - String expr = ""; - for (int i=0;i=3 && R.nextBoolean()){ - expr = "(" + expr + ")"; - } - double val = safeEval(expr); - List opts = makeChoices(val); - int correct = opts.indexOf(formatNumber(val)); - if (correct<0) { - opts.set(0, formatNumber(val)); correct=0; - } - return new Question(expr, opts, correct); - } - - // Middle: include at least one square (x^2) or sqrt - private static Question genMiddle(){ - // choose either square or sqrt - boolean square = R.nextBoolean(); - String expr; - double val; - if (square){ - int a = 1 + R.nextInt(20); - expr = a + "² + " + (1 + R.nextInt(30)); - val = a*a + (1 + R.nextInt(30)); - } else { - int a = 1 + R.nextInt(20); - expr = "√" + (a*a) + " + " + (1 + R.nextInt(30)); // ensure integer sqrt - val = a + (1 + R.nextInt(30)); - } - List opts = makeChoices(val); - int correct = opts.indexOf(formatNumber(val)); - if (correct<0){ opts.set(0, formatNumber(val)); correct=0;} - return new Question(expr, opts, correct); - } - - // High: include a trig function sin/cos/tan (angles chosen from nice set) - private static Question genHigh(){ - String[] funcs = {"sin","cos","tan"}; - int idx = R.nextInt(funcs.length); - String f = funcs[idx]; - int[] angles = {0,30,45,60,90}; - int ang = angles[R.nextInt(angles.length)]; - String expr = f + "(" + ang + "°) + " + (1 + R.nextInt(10)); - double trig = trigValue(f, ang); - double val = trig + (1 + R.nextInt(10)); - List opts = makeChoices(val); - int correct = opts.indexOf(formatNumber(val)); - if (correct<0){ opts.set(0, formatNumber(val)); correct=0;} - return new Question(expr, opts, correct); - } - - private static double trigValue(String f, int deg){ - double rad = Math.toRadians(deg); - switch(f){ - case "sin": return Math.round(Math.sin(rad)*100.0)/100.0; - case "cos": return Math.round(Math.cos(rad)*100.0)/100.0; - case "tan": return Math.round(Math.tan(rad)*100.0)/100.0; - } - return 0; - } - - // helpers - private static String randomOp(String[] arr){ return arr[R.nextInt(arr.length)]; } - - private static double safeEval(String expr){ - // very simple evaluator for + - * / with integers (no operator precedence beyond left-to-right). - // For our generated expr this suffices. Use double. - try { - String[] toks = expr.replace("(", "").replace(")", "").split("\\s+"); - double res = Double.parseDouble(toks[0]); - for (int i=1;i makeChoices(double correct){ - List opts = new ArrayList<>(); - opts.add(formatNumber(correct)); - // create 3 distractors - for (int i=0;i<3;i++){ - double delta = (R.nextDouble() * 10 + 1) * (R.nextBoolean() ? 1 : -1); - double v = Math.round((correct + delta) * 100.0)/100.0; - // avoid collision - int tries=0; - while (opts.contains(formatNumber(v)) && tries<10){ v = Math.round((correct + (R.nextDouble()*5+1)*(R.nextBoolean()?1:-1))*100.0)/100.0; tries++; } - opts.add(formatNumber(v)); - } - Collections.shuffle(opts); - int idx = opts.indexOf(formatNumber(correct)); - return opts; - } - - private static String formatNumber(double v){ - if (Math.abs(v - Math.round(v)) < 0.0001) return String.valueOf((long)Math.round(v)); - return String.format("%.2f", v); - } - } -} diff --git a/src/controller/AppController.java b/src/controller/AppController.java new file mode 100644 index 0000000..4851d4d --- /dev/null +++ b/src/controller/AppController.java @@ -0,0 +1,66 @@ +package controller; + +import ui.LoginPanel; +import ui.RegisterPanel; +import model.UserManager; + +import javax.swing.*; +import java.awt.*; + +/** + * 控制器:负责界面切换与核心逻辑 + */ +public class AppController { + private JFrame frame; + private UserManager userManager; + + public AppController() { + userManager = new UserManager(); + + frame = new JFrame("学生数学学习系统"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setSize(450, 400); + frame.setLocationRelativeTo(null); + frame.setLayout(new BorderLayout()); + + showLoginPanel(); + frame.setVisible(true); + } + + /** 显示登录界面 */ + public void showLoginPanel() { + frame.getContentPane().removeAll(); + frame.add(new LoginPanel(this)); + frame.revalidate(); + frame.repaint(); + } + + /** 显示注册界面 */ + public void showRegisterPanel() { + frame.getContentPane().removeAll(); + frame.add(new RegisterPanel(this)); + frame.revalidate(); + frame.repaint(); + } + + /** 登录逻辑 */ + public void handleLogin(String email, String password) { + if (userManager.checkLogin(email, password)) { + JOptionPane.showMessageDialog(frame, "登录成功!"); + // TODO: 跳转到题目选择界面(QuizPanel) + } else { + JOptionPane.showMessageDialog(frame, "邮箱或密码错误!"); + } + } + + /** 注册逻辑 */ + public void handleRegister(String email, String password) { + if (userManager.isUserExist(email)) { + JOptionPane.showMessageDialog(frame, "该邮箱已注册!"); + return; + } + userManager.addUser(email, password); + JOptionPane.showMessageDialog(frame, "注册成功!请登录。"); + showLoginPanel(); + } +} diff --git a/src/controller/UserManager.java b/src/controller/UserManager.java new file mode 100644 index 0000000..37032eb --- /dev/null +++ b/src/controller/UserManager.java @@ -0,0 +1,60 @@ +package controller; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +/** + * 用户信息管理(本地文件,无数据库) + */ +public class UserManager { + private static final String USER_FILE = "users.txt"; + private Map users = new HashMap<>(); + + public UserManager() { + loadUsers(); + } + + /** 检查登录 */ + public boolean checkLogin(String email, String password) { + return users.containsKey(email) && users.get(email).equals(password); + } + + /** 判断用户是否存在 */ + public boolean isUserExist(String email) { + return users.containsKey(email); + } + + /** 添加用户 */ + public void addUser(String email, String password) { + users.put(email, password); + saveUsers(); + } + + /** 加载用户文件 */ + private void loadUsers() { + File file = new File(USER_FILE); + if (!file.exists()) return; + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + String line; + while ((line = br.readLine()) != null) { + String[] parts = line.split(","); + if (parts.length == 2) + users.put(parts[0], parts[1]); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** 保存用户信息 */ + private void saveUsers() { + try (PrintWriter pw = new PrintWriter(new FileWriter(USER_FILE))) { + for (Map.Entry entry : users.entrySet()) { + pw.println(entry.getKey() + "," + entry.getValue()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/model/Question.java b/src/model/Question.java new file mode 100644 index 0000000..5b5bc47 --- /dev/null +++ b/src/model/Question.java @@ -0,0 +1,14 @@ +package model; + +public class Question { + private String text; + private String userAnswer; + + public Question(String text) { + this.text = text; + } + + public String getText() { return text; } + public String getUserAnswer() { return userAnswer; } + public void setUserAnswer(String ans) { this.userAnswer = ans; } +} diff --git a/src/model/QuestionGenerator.java b/src/model/QuestionGenerator.java new file mode 100644 index 0000000..22719f4 --- /dev/null +++ b/src/model/QuestionGenerator.java @@ -0,0 +1,124 @@ +package model; + +import java.util.*; + +/** + * 随机题目生成器 + * 小学:仅 + - * / () + * 初中:含平方、根号 + * 高中:含三角函数 + */ +public class QuestionGenerator { + private static final Random rand = new Random(); + + public static List generateQuestions(String level, int count) { + Set uniqueQuestions = new HashSet<>(); + List list = new ArrayList<>(); + + while (list.size() < count) { + String q; + switch (level) { + case "小学": + q = genPrimary(); + break; + case "初中": + q = genMiddle(); + break; + default: + q = genHigh(); + } + if (!uniqueQuestions.contains(q)) { + uniqueQuestions.add(q); + list.add(new Question(q, getOptions(q))); + } + } + return list; + } + + // 小学难度 + private static String genPrimary() { + int a = rand.nextInt(50) + 1; + int b = rand.nextInt(50) + 1; + char op = "+-*/".charAt(rand.nextInt(4)); + return a + " " + op + " " + b; + } + + // 初中难度 + private static String genMiddle() { + int a = rand.nextInt(20) + 1; + int b = rand.nextInt(5) + 1; + String[] ops = {a + "^2", "√" + (a * b), "(" + a + "+" + b + ")^2"}; + return ops[rand.nextInt(ops.length)]; + } + + // 高中难度 + private static String genHigh() { + String[] funcs = {"sin", "cos", "tan"}; + int angle = (rand.nextInt(12) + 1) * 15; + return funcs[rand.nextInt(3)] + "(" + angle + "°)"; + } + + // 模拟生成四个选项(正确答案随机放置) + private static String[] getOptions(String question) { + double correct = calculate(question); + String[] opts = new String[4]; + int correctPos = rand.nextInt(4); + for (int i = 0; i < 4; i++) { + opts[i] = (i == correctPos) + ? String.format("%.2f", correct) + : String.format("%.2f", correct + rand.nextDouble() * 5 - 2.5); + } + return opts; + } + + private static double calculate(String q) { + try { + if (q.contains("sin")) { + int angle = Integer.parseInt(q.replaceAll("\\D", "")); + return Math.sin(Math.toRadians(angle)); + } + if (q.contains("cos")) { + int angle = Integer.parseInt(q.replaceAll("\\D", "")); + return Math.cos(Math.toRadians(angle)); + } + if (q.contains("tan")) { + int angle = Integer.parseInt(q.replaceAll("\\D", "")); + return Math.tan(Math.toRadians(angle)); + } + if (q.contains("√")) { + int num = Integer.parseInt(q.replaceAll("\\D", "")); + return Math.sqrt(num); + } + if (q.contains("^2")) { + int num = Integer.parseInt(q.replaceAll("\\D", "")); + return num * num; + } + // 简单算术 + String[] tokens = q.split(" "); + double a = Double.parseDouble(tokens[0]); + double b = Double.parseDouble(tokens[2]); + return switch (tokens[1]) { + case "+" -> a + b; + case "-" -> a - b; + case "*" -> a * b; + case "/" -> b != 0 ? a / b : 0; + default -> 0; + }; + } catch (Exception e) { + return 0; + } + } + + // Question 内部类 + public static class Question { + public String questionText; + public String[] options; + public String correctAnswer; + + public Question(String text, String[] options) { + this.questionText = text; + this.options = options; + this.correctAnswer = options[0]; // 默认第一个是正确答案(计算时设置) + } + } +} diff --git a/src/model/User.java b/src/model/User.java new file mode 100644 index 0000000..c9ca7c6 --- /dev/null +++ b/src/model/User.java @@ -0,0 +1,13 @@ +package model; + +public class User { + private String email; + private String password; + + public User(String email, String password) { + this.email = email; + this.password = password; + } + public String getEmail() { return email; } + public String getPassword() { return password; } +} diff --git a/src/ui/LoginPanel.java b/src/ui/LoginPanel.java new file mode 100644 index 0000000..b77d690 --- /dev/null +++ b/src/ui/LoginPanel.java @@ -0,0 +1,57 @@ +package ui; + +import controller.AppController; + +import javax.swing.*; +import java.awt.*; + +/** + * 登录界面 + */ +public class LoginPanel extends JPanel { + private JTextField emailField; + private JPasswordField passwordField; + + public LoginPanel(AppController controller) { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(8, 8, 8, 8); + gbc.fill = GridBagConstraints.HORIZONTAL; + + JLabel title = new JLabel("学生数学学习系统", JLabel.CENTER); + title.setFont(new Font("微软雅黑", Font.BOLD, 22)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(title, gbc); + + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 1; + add(new JLabel("邮箱:"), gbc); + + gbc.gridx = 1; + emailField = new JTextField(20); + add(emailField, gbc); + + gbc.gridx = 0; gbc.gridy = 2; + add(new JLabel("密码:"), gbc); + + gbc.gridx = 1; + passwordField = new JPasswordField(20); + add(passwordField, gbc); + + JButton loginButton = new JButton("登录"); + gbc.gridx = 0; gbc.gridy = 3; + add(loginButton, gbc); + + JButton registerButton = new JButton("注册"); + gbc.gridx = 1; + add(registerButton, gbc); + + loginButton.addActionListener(e -> { + String email = emailField.getText().trim(); + String password = new String(passwordField.getPassword()); + controller.handleLogin(email, password); + }); + + registerButton.addActionListener(e -> controller.showRegisterPanel()); + } +} diff --git a/src/ui/RegisterPanel.java b/src/ui/RegisterPanel.java new file mode 100644 index 0000000..c94a50f --- /dev/null +++ b/src/ui/RegisterPanel.java @@ -0,0 +1,110 @@ +package ui; + +import controller.AppController; +import util.EmailUtil; + +import javax.swing.*; +import java.awt.*; + +/** + * 注册界面 + */ +public class RegisterPanel extends JPanel { + private JTextField emailField; + private JTextField codeField; + private JPasswordField passwordField; + private JPasswordField confirmField; + + private String sentCode = null; + + public RegisterPanel(AppController controller) { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(8, 8, 8, 8); + gbc.fill = GridBagConstraints.HORIZONTAL; + + JLabel title = new JLabel("用户注册", JLabel.CENTER); + title.setFont(new Font("微软雅黑", Font.BOLD, 22)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(title, gbc); + + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 1; + add(new JLabel("邮箱:"), gbc); + + gbc.gridx = 1; + emailField = new JTextField(20); + add(emailField, gbc); + + gbc.gridx = 0; gbc.gridy = 2; + add(new JLabel("验证码:"), gbc); + + gbc.gridx = 1; + codeField = new JTextField(10); + add(codeField, gbc); + + gbc.gridx = 0; gbc.gridy = 3; + add(new JLabel("密码:"), gbc); + + gbc.gridx = 1; + passwordField = new JPasswordField(20); + add(passwordField, gbc); + + gbc.gridx = 0; gbc.gridy = 4; + add(new JLabel("确认密码:"), gbc); + + gbc.gridx = 1; + confirmField = new JPasswordField(20); + add(confirmField, gbc); + + JButton sendCodeButton = new JButton("发送验证码"); + gbc.gridx = 0; gbc.gridy = 5; + add(sendCodeButton, gbc); + + JButton registerButton = new JButton("注册"); + gbc.gridx = 1; + add(registerButton, gbc); + + JButton backButton = new JButton("返回登录"); + gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 2; + add(backButton, gbc); + + sendCodeButton.addActionListener(e -> { + String email = emailField.getText().trim(); + if (!email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) { + JOptionPane.showMessageDialog(this, "邮箱格式不正确!"); + return; + } + sentCode = EmailUtil.sendVerificationCode(email); + JOptionPane.showMessageDialog(this, "验证码已发送(调试模式控制台查看)!"); + }); + + registerButton.addActionListener(e -> { + String email = emailField.getText().trim(); + String code = codeField.getText().trim(); + String password = new String(passwordField.getPassword()); + String confirm = new String(confirmField.getPassword()); + + if (sentCode == null) { + JOptionPane.showMessageDialog(this, "请先获取验证码!"); + return; + } + if (!code.equals(sentCode)) { + JOptionPane.showMessageDialog(this, "验证码错误!"); + return; + } + if (!password.equals(confirm)) { + JOptionPane.showMessageDialog(this, "两次密码不一致!"); + return; + } + if (!password.matches("(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[A-Za-z\\d]{6,10}")) { + JOptionPane.showMessageDialog(this, "密码需包含大小写字母与数字,长度6-10位!"); + return; + } + + controller.handleRegister(email, password); + }); + + backButton.addActionListener(e -> controller.showLoginPanel()); + } +} diff --git a/src/util/EmailUtil.java b/src/util/EmailUtil.java new file mode 100644 index 0000000..e4ba146 --- /dev/null +++ b/src/util/EmailUtil.java @@ -0,0 +1,50 @@ +package util; + +import jakarta.mail.*; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import java.util.Properties; +import java.util.Random; + +/** + * 邮件发送工具(支持 QQ、163 邮箱) + * 使用时请修改 senderEmail / senderAuthCode + */ +public class EmailUtil { + + // 修改为你的发件邮箱和授权码(⚠️不是密码) + private static final String senderEmail = "你的邮箱@qq.com"; + private static final String senderAuthCode = "你的授权码"; + private static final String smtpHost = "smtp.qq.com"; // 163 改为 smtp.163.com + + public static String sendVerificationCode(String toEmail) { + String code = String.valueOf(100000 + new Random().nextInt(900000)); + + Properties props = new Properties(); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.host", smtpHost); + props.put("mail.smtp.port", "587"); + props.put("mail.smtp.starttls.enable", "true"); + + Session session = Session.getInstance(props, new Authenticator() { + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(senderEmail, senderAuthCode); + } + }); + + try { + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(senderEmail)); + message.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail)); + message.setSubject("【数学学习系统】注册验证码"); + message.setText("您的验证码是:" + code + "\n(有效期5分钟)"); + + Transport.send(message); + System.out.println("✅ 邮件验证码已发送到:" + toEmail); + } catch (Exception e) { + System.err.println("⚠️ 邮件发送失败:" + e.getMessage()); + } + + return code; + } +} diff --git a/src/util/FileUtil.java b/src/util/FileUtil.java new file mode 100644 index 0000000..fc721f9 --- /dev/null +++ b/src/util/FileUtil.java @@ -0,0 +1,31 @@ +package util; + +import model.User; +import java.io.*; +import java.util.*; + +public class FileUtil { + public static Map loadUsers(String fileName) { + Map users = new HashMap<>(); + try (BufferedReader br = new BufferedReader(new FileReader(fileName))) { + String line; + while ((line = br.readLine()) != null) { + String[] parts = line.split(","); + users.put(parts[0], new User(parts[0], parts[1])); + } + } catch (IOException e) { + // 首次运行时文件可能不存在 + } + return users; + } + + public static void saveUsers(Map users, String fileName) { + try (PrintWriter pw = new PrintWriter(new FileWriter(fileName))) { + for (User u : users.values()) { + pw.println(u.getEmail() + "," + u.getPassword()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/view/QuizPanel.java b/src/view/QuizPanel.java new file mode 100644 index 0000000..377a479 --- /dev/null +++ b/src/view/QuizPanel.java @@ -0,0 +1,88 @@ +package view; + +import controller.AppController; +import util.QuestionGenerator; +import util.QuestionGenerator.Question; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class QuizPanel extends JPanel { + private List questions; + private int currentIndex = 0; + private int correctCount = 0; + private ButtonGroup group; + private JLabel questionLabel; + private JRadioButton[] options; + private JButton nextButton; + private AppController controller; + + public QuizPanel(AppController controller, String level, int count) { + this.controller = controller; + this.questions = QuestionGenerator.generateQuestions(level, count); + setLayout(new BorderLayout()); + + questionLabel = new JLabel("", JLabel.CENTER); + questionLabel.setFont(new Font("微软雅黑", Font.BOLD, 18)); + add(questionLabel, BorderLayout.NORTH); + + JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 5, 5)); + group = new ButtonGroup(); + options = new JRadioButton[4]; + for (int i = 0; i < 4; i++) { + options[i] = new JRadioButton(); + group.add(options[i]); + optionsPanel.add(options[i]); + } + add(optionsPanel, BorderLayout.CENTER); + + nextButton = new JButton("下一题"); + add(nextButton, BorderLayout.SOUTH); + + nextButton.addActionListener(e -> checkAndNext()); + showQuestion(); + } + + private void showQuestion() { + if (currentIndex >= questions.size()) { + showResult(); + return; + } + Question q = questions.get(currentIndex); + questionLabel.setText("第 " + (currentIndex + 1) + " 题:" + q.questionText); + for (int i = 0; i < 4; i++) { + options[i].setText(q.options[i]); + options[i].setSelected(false); + } + } + + private void checkAndNext() { + for (JRadioButton r : options) { + if (r.isSelected() && r.getText().equals(questions.get(currentIndex).options[0])) { + correctCount++; + } + } + currentIndex++; + showQuestion(); + } + + private void showResult() { + removeAll(); + setLayout(new GridLayout(3, 1, 10, 10)); + JLabel result = new JLabel("你的得分:" + (100 * correctCount / questions.size()) + " 分", JLabel.CENTER); + result.setFont(new Font("微软雅黑", Font.BOLD, 22)); + add(result); + + JButton retry = new JButton("再做一份"); + JButton exit = new JButton("退出"); + add(retry); + add(exit); + + retry.addActionListener(e -> controller.showLoginPanel()); + exit.addActionListener(e -> System.exit(0)); + + revalidate(); + repaint(); + } +} diff --git a/src/view/ResultPanel.java b/src/view/ResultPanel.java new file mode 100644 index 0000000..4168b08 --- /dev/null +++ b/src/view/ResultPanel.java @@ -0,0 +1,19 @@ +package view; + +import controller.AppController; +import model.User; +import javax.swing.*; +import java.awt.*; + +public class ResultPanel extends JPanel { + public ResultPanel(AppController controller, User user, int score) { + setLayout(new BorderLayout()); + JLabel lbl = new JLabel("用户:" + user.getEmail() + ",得分:" + score + " 分", JLabel.CENTER); + lbl.setFont(new Font("微软雅黑", Font.BOLD, 20)); + add(lbl, BorderLayout.CENTER); + + JButton btnBack = new JButton("返回登录"); + btnBack.addActionListener(e -> controller.showLoginPanel()); + add(btnBack, BorderLayout.SOUTH); + } +} -- 2.34.1 From 078139b2e9cb2ad077b054e269410a8d2fa5545b Mon Sep 17 00:00:00 2001 From: tianyuan <2861334240@qq.com> Date: Fri, 10 Oct 2025 15:21:17 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 50 ------------------------------- src/controller/AppController.java | 2 +- src/view/QuizPanel.java | 4 +-- 3 files changed, 3 insertions(+), 53 deletions(-) delete mode 100644 pom.xml diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 87c0d4f..0000000 --- a/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - com.mathquiz - MathQuizApp - 1.0-SNAPSHOT - - - 17 - 17 - UTF-8 - - - - - - com.sun.mail - jakarta.mail - 2.0.1 - - - - - - com.sun.activation - javax.activation - 1.2.0 - - - - - org.projectlombok - lombok - 1.18.32 - provided - - - - - - aliyunmaven - Aliyun Maven - https://maven.aliyun.com/repository/public - - - diff --git a/src/controller/AppController.java b/src/controller/AppController.java index 4851d4d..3a618d8 100644 --- a/src/controller/AppController.java +++ b/src/controller/AppController.java @@ -2,7 +2,7 @@ package controller; import ui.LoginPanel; import ui.RegisterPanel; -import model.UserManager; + import javax.swing.*; import java.awt.*; diff --git a/src/view/QuizPanel.java b/src/view/QuizPanel.java index 377a479..e4609ae 100644 --- a/src/view/QuizPanel.java +++ b/src/view/QuizPanel.java @@ -1,8 +1,8 @@ package view; import controller.AppController; -import util.QuestionGenerator; -import util.QuestionGenerator.Question; +import model.QuestionGenerator; +import model.QuestionGenerator.Question; import javax.swing.*; import java.awt.*; -- 2.34.1 From d1bb1599145bd2459f55b01544e5f7260fe8c05b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=AB=9E=E7=94=B1?= <1193626695@qq.com> Date: Sat, 11 Oct 2025 00:08:53 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E5=8A=A0=E9=80=89=E5=AD=A6=E5=8E=86?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.java | 52 ++++++++++++++++++++++++++-- src/ui/DifficultySelectionPanel.java | 39 +++++++++++++++++++++ src/util/EmailUtil.java | 4 +-- 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 src/ui/DifficultySelectionPanel.java diff --git a/src/controller/AppController.java b/src/controller/AppController.java index 3a618d8..681253b 100644 --- a/src/controller/AppController.java +++ b/src/controller/AppController.java @@ -2,7 +2,8 @@ package controller; import ui.LoginPanel; import ui.RegisterPanel; - +import ui.DifficultySelectionPanel; // 添加难度选择面板 +import view.QuizPanel; // 保持原有导入 import javax.swing.*; import java.awt.*; @@ -43,11 +44,28 @@ public class AppController { frame.repaint(); } + /** 显示难度选择界面 */ + public void showDifficultySelectionPanel() { + frame.getContentPane().removeAll(); + frame.add(new DifficultySelectionPanel(this)); + frame.revalidate(); + frame.repaint(); + } + + /** 显示题目选择界面(QuizPanel) */ + public void showQuizPanel(String level, int count) { + frame.getContentPane().removeAll(); + frame.add(new QuizPanel(this, level, count)); + frame.revalidate(); + frame.repaint(); + } + /** 登录逻辑 */ public void handleLogin(String email, String password) { if (userManager.checkLogin(email, password)) { JOptionPane.showMessageDialog(frame, "登录成功!"); - // TODO: 跳转到题目选择界面(QuizPanel) + // 跳转到难度选择界面 + showDifficultySelectionPanel(); } else { JOptionPane.showMessageDialog(frame, "邮箱或密码错误!"); } @@ -63,4 +81,32 @@ public class AppController { JOptionPane.showMessageDialog(frame, "注册成功!请登录。"); showLoginPanel(); } -} + + /** 在难度选择界面处理难度选择 */ + public void handleDifficultySelection(String level) { + // 弹出输入框要求输入题目数量 + String countInput = JOptionPane.showInputDialog( + frame, + "请输入需要生成的题目数量 (1-50):", + "题目数量", + JOptionPane.INFORMATION_MESSAGE + ); + + if (countInput == null) { + // 用户取消输入 + return; + } + + try { + int count = Integer.parseInt(countInput); + if (count < 1 || count > 50) { + JOptionPane.showMessageDialog(frame, "题目数量必须在1-50之间!"); + return; + } + // 跳转到题目界面 + showQuizPanel(level, count); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(frame, "请输入有效的数字!"); + } + } +} \ No newline at end of file diff --git a/src/ui/DifficultySelectionPanel.java b/src/ui/DifficultySelectionPanel.java new file mode 100644 index 0000000..f4f146f --- /dev/null +++ b/src/ui/DifficultySelectionPanel.java @@ -0,0 +1,39 @@ +package ui; + +import controller.AppController; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class DifficultySelectionPanel extends JPanel { + private AppController controller; + + public DifficultySelectionPanel(AppController controller) { + this.controller = controller; + setLayout(new GridLayout(3, 1, 10, 10)); + setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 创建难度按钮 + JButton elementaryBtn = createDifficultyButton("小学", "小学"); + JButton middleBtn = createDifficultyButton("初中", "初中"); + JButton highBtn = createDifficultyButton("高中", "高中"); + + add(elementaryBtn); + add(middleBtn); + add(highBtn); + } + + private JButton createDifficultyButton(String text, String level) { + JButton btn = new JButton(text); + btn.setFont(new Font("微软雅黑", Font.BOLD, 16)); + btn.setPreferredSize(new Dimension(200, 40)); + btn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + controller.handleDifficultySelection(level); + } + }); + return btn; + } +} \ No newline at end of file diff --git a/src/util/EmailUtil.java b/src/util/EmailUtil.java index e4ba146..c1d536c 100644 --- a/src/util/EmailUtil.java +++ b/src/util/EmailUtil.java @@ -13,8 +13,8 @@ import java.util.Random; public class EmailUtil { // 修改为你的发件邮箱和授权码(⚠️不是密码) - private static final String senderEmail = "你的邮箱@qq.com"; - private static final String senderAuthCode = "你的授权码"; + private static final String senderEmail = "1193626695@qq.com"; + private static final String senderAuthCode = "ldxzjbnsirnsbacj"; private static final String smtpHost = "smtp.qq.com"; // 163 改为 smtp.163.com public static String sendVerificationCode(String toEmail) { -- 2.34.1 From 2d786c379d9056d6e788a54e05063fdd2fa816f9 Mon Sep 17 00:00:00 2001 From: tianyuan <2861334240@qq.com> Date: Sat, 11 Oct 2025 11:41:52 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E5=90=88=E5=B9=B6=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/EmailUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/EmailUtil.java b/src/util/EmailUtil.java index e4ba146..af2082d 100644 --- a/src/util/EmailUtil.java +++ b/src/util/EmailUtil.java @@ -13,8 +13,8 @@ import java.util.Random; public class EmailUtil { // 修改为你的发件邮箱和授权码(⚠️不是密码) - private static final String senderEmail = "你的邮箱@qq.com"; - private static final String senderAuthCode = "你的授权码"; + private static final String senderEmail = "2861334240@qq.com"; + private static final String senderAuthCode = "mdouxlqwjamhdcdh"; private static final String smtpHost = "smtp.qq.com"; // 163 改为 smtp.163.com public static String sendVerificationCode(String toEmail) { -- 2.34.1 From ef1717bf396d830b750158a36ba7d640fa736528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=AB=9E=E7=94=B1?= <1193626695@qq.com> Date: Sat, 11 Oct 2025 16:59:27 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=A1=E5=88=86?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/QuestionGenerator.java | 16 ++++++++++++++-- src/view/QuizPanel.java | 10 ++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/model/QuestionGenerator.java b/src/model/QuestionGenerator.java index 22719f4..c37d3bc 100644 --- a/src/model/QuestionGenerator.java +++ b/src/model/QuestionGenerator.java @@ -29,7 +29,10 @@ public class QuestionGenerator { } if (!uniqueQuestions.contains(q)) { uniqueQuestions.add(q); - list.add(new Question(q, getOptions(q))); + double correct = calculate(q); + String[] opts = getOptions(q); + // 使用新构造函数传递正确答案 + list.add(new Question(q, opts, String.format("%.2f", correct))); } } return list; @@ -110,6 +113,7 @@ public class QuestionGenerator { } // Question 内部类 + // 修改Question内部类,添加新的构造函数 public static class Question { public String questionText; public String[] options; @@ -118,7 +122,15 @@ public class QuestionGenerator { public Question(String text, String[] options) { this.questionText = text; this.options = options; - this.correctAnswer = options[0]; // 默认第一个是正确答案(计算时设置) + // 旧的错误实现:this.correctAnswer = options[0]; + // 改为使用新构造函数 + } + + // 新增构造函数,正确设置正确答案 + public Question(String text, String[] options, String correctAnswer) { + this.questionText = text; + this.options = options; + this.correctAnswer = correctAnswer; } } } diff --git a/src/view/QuizPanel.java b/src/view/QuizPanel.java index e4609ae..7fdb9fc 100644 --- a/src/view/QuizPanel.java +++ b/src/view/QuizPanel.java @@ -58,8 +58,10 @@ public class QuizPanel extends JPanel { } private void checkAndNext() { + // 从Question对象中获取正确答案,而不是假设options[0]是正确答案 + String correctAnswer = questions.get(currentIndex).correctAnswer; for (JRadioButton r : options) { - if (r.isSelected() && r.getText().equals(questions.get(currentIndex).options[0])) { + if (r.isSelected() && r.getText().equals(correctAnswer)) { correctCount++; } } @@ -70,10 +72,14 @@ public class QuizPanel extends JPanel { private void showResult() { removeAll(); setLayout(new GridLayout(3, 1, 10, 10)); - JLabel result = new JLabel("你的得分:" + (100 * correctCount / questions.size()) + " 分", JLabel.CENTER); + // 添加除以0的检查 + int total = questions.size(); + int score = total > 0 ? (100 * correctCount / total) : 0; + JLabel result = new JLabel("你的得分:" + score + " 分", JLabel.CENTER); result.setFont(new Font("微软雅黑", Font.BOLD, 22)); add(result); + // 其余代码保持不变 JButton retry = new JButton("再做一份"); JButton exit = new JButton("退出"); add(retry); -- 2.34.1 From d95daacbd667868b9753d7addd6c6f1c27dad8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=AB=9E=E7=94=B1?= <1193626695@qq.com> Date: Sat, 11 Oct 2025 17:37:00 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.java | 25 +++++++----------- src/controller/UserManager.java | 44 ++++++++++++++++++++----------- src/model/User.java | 15 +++++++++-- src/ui/RegisterPanel.java | 20 +++++++++----- 4 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/controller/AppController.java b/src/controller/AppController.java index 681253b..d9ec606 100644 --- a/src/controller/AppController.java +++ b/src/controller/AppController.java @@ -2,8 +2,9 @@ package controller; import ui.LoginPanel; import ui.RegisterPanel; -import ui.DifficultySelectionPanel; // 添加难度选择面板 -import view.QuizPanel; // 保持原有导入 +import ui.DifficultySelectionPanel; +import model.User; +import view.QuizPanel; import javax.swing.*; import java.awt.*; @@ -28,7 +29,6 @@ public class AppController { frame.setVisible(true); } - /** 显示登录界面 */ public void showLoginPanel() { frame.getContentPane().removeAll(); frame.add(new LoginPanel(this)); @@ -36,7 +36,6 @@ public class AppController { frame.repaint(); } - /** 显示注册界面 */ public void showRegisterPanel() { frame.getContentPane().removeAll(); frame.add(new RegisterPanel(this)); @@ -44,7 +43,6 @@ public class AppController { frame.repaint(); } - /** 显示难度选择界面 */ public void showDifficultySelectionPanel() { frame.getContentPane().removeAll(); frame.add(new DifficultySelectionPanel(this)); @@ -52,7 +50,6 @@ public class AppController { frame.repaint(); } - /** 显示题目选择界面(QuizPanel) */ public void showQuizPanel(String level, int count) { frame.getContentPane().removeAll(); frame.add(new QuizPanel(this, level, count)); @@ -60,31 +57,31 @@ public class AppController { frame.repaint(); } - /** 登录逻辑 */ public void handleLogin(String email, String password) { if (userManager.checkLogin(email, password)) { JOptionPane.showMessageDialog(frame, "登录成功!"); - // 跳转到难度选择界面 + // 获取用户并显示注册的用户名 + User user = userManager.getUserByEmail(email); + String displayName = user != null ? user.getUsername() : email; + JOptionPane.showMessageDialog(frame, "欢迎回来," + displayName + "!"); showDifficultySelectionPanel(); } else { JOptionPane.showMessageDialog(frame, "邮箱或密码错误!"); } } - /** 注册逻辑 */ - public void handleRegister(String email, String password) { + public void handleRegister(String email, String password, String username) { if (userManager.isUserExist(email)) { JOptionPane.showMessageDialog(frame, "该邮箱已注册!"); return; } - userManager.addUser(email, password); + // 传递 username 参数 + userManager.addUser(email, password, username); JOptionPane.showMessageDialog(frame, "注册成功!请登录。"); showLoginPanel(); } - /** 在难度选择界面处理难度选择 */ public void handleDifficultySelection(String level) { - // 弹出输入框要求输入题目数量 String countInput = JOptionPane.showInputDialog( frame, "请输入需要生成的题目数量 (1-50):", @@ -93,7 +90,6 @@ public class AppController { ); if (countInput == null) { - // 用户取消输入 return; } @@ -103,7 +99,6 @@ public class AppController { JOptionPane.showMessageDialog(frame, "题目数量必须在1-50之间!"); return; } - // 跳转到题目界面 showQuizPanel(level, count); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(frame, "请输入有效的数字!"); diff --git a/src/controller/UserManager.java b/src/controller/UserManager.java index 37032eb..c0d819d 100644 --- a/src/controller/UserManager.java +++ b/src/controller/UserManager.java @@ -1,37 +1,41 @@ package controller; +import model.User; + import java.io.*; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * 用户信息管理(本地文件,无数据库) */ public class UserManager { private static final String USER_FILE = "users.txt"; - private Map users = new HashMap<>(); + private Map users = new HashMap<>(); // 修改为存储 User 对象 public UserManager() { loadUsers(); } - /** 检查登录 */ public boolean checkLogin(String email, String password) { - return users.containsKey(email) && users.get(email).equals(password); + User user = users.get(email); + return user != null && user.getPassword().equals(password); } - /** 判断用户是否存在 */ public boolean isUserExist(String email) { return users.containsKey(email); } - /** 添加用户 */ - public void addUser(String email, String password) { - users.put(email, password); + // 修改 addUser 方法,接收 username 参数 + public void addUser(String email, String password, String username) { + users.put(email, new User(email, password, username)); saveUsers(); } - /** 加载用户文件 */ + // 添加获取用户的方法 + public User getUserByEmail(String email) { + return users.get(email); + } + private void loadUsers() { File file = new File(USER_FILE); if (!file.exists()) return; @@ -39,22 +43,30 @@ public class UserManager { String line; while ((line = br.readLine()) != null) { String[] parts = line.split(","); - if (parts.length == 2) - users.put(parts[0], parts[1]); + if (parts.length >= 2) { + String email = parts[0]; + String password = parts[1]; + String username = email; // 默认用户名为邮箱 + if (parts.length >= 3) { + username = parts[2]; + } + users.put(email, new User(email, password, username)); + } } } catch (IOException e) { e.printStackTrace(); } } - /** 保存用户信息 */ private void saveUsers() { try (PrintWriter pw = new PrintWriter(new FileWriter(USER_FILE))) { - for (Map.Entry entry : users.entrySet()) { - pw.println(entry.getKey() + "," + entry.getValue()); + for (Map.Entry entry : users.entrySet()) { + User user = entry.getValue(); + // 保存 email, password, username 三列 + pw.println(user.getEmail() + "," + user.getPassword() + "," + user.getUsername()); } } catch (IOException e) { e.printStackTrace(); } } -} +} \ No newline at end of file diff --git a/src/model/User.java b/src/model/User.java index c9ca7c6..0560c64 100644 --- a/src/model/User.java +++ b/src/model/User.java @@ -1,13 +1,24 @@ + package model; public class User { private String email; private String password; + private String username; - public User(String email, String password) { + // 修改构造函数,添加 username 参数 + public User(String email, String password, String username) { this.email = email; this.password = password; + this.username = username; + } + + // 保留原有无参数构造函数,用于旧数据兼容 + public User(String email, String password) { + this(email, password, email); // 默认用户名为邮箱 } + public String getEmail() { return email; } public String getPassword() { return password; } -} + public String getUsername() { return username; } +} \ No newline at end of file diff --git a/src/ui/RegisterPanel.java b/src/ui/RegisterPanel.java index c94a50f..553b26f 100644 --- a/src/ui/RegisterPanel.java +++ b/src/ui/RegisterPanel.java @@ -14,6 +14,7 @@ public class RegisterPanel extends JPanel { private JTextField codeField; private JPasswordField passwordField; private JPasswordField confirmField; + private JTextField usernameField; // 新增用户名输入框 private String sentCode = null; @@ -37,20 +38,26 @@ public class RegisterPanel extends JPanel { add(emailField, gbc); gbc.gridx = 0; gbc.gridy = 2; + add(new JLabel("用户名:"), gbc); // 修改标签 + gbc.gridx = 1; + usernameField = new JTextField(20); // 新增用户名输入框 + add(usernameField, gbc); + + gbc.gridx = 0; gbc.gridy = 3; add(new JLabel("验证码:"), gbc); gbc.gridx = 1; codeField = new JTextField(10); add(codeField, gbc); - gbc.gridx = 0; gbc.gridy = 3; + gbc.gridx = 0; gbc.gridy = 4; add(new JLabel("密码:"), gbc); gbc.gridx = 1; passwordField = new JPasswordField(20); add(passwordField, gbc); - gbc.gridx = 0; gbc.gridy = 4; + gbc.gridx = 0; gbc.gridy = 5; add(new JLabel("确认密码:"), gbc); gbc.gridx = 1; @@ -58,7 +65,7 @@ public class RegisterPanel extends JPanel { add(confirmField, gbc); JButton sendCodeButton = new JButton("发送验证码"); - gbc.gridx = 0; gbc.gridy = 5; + gbc.gridx = 0; gbc.gridy = 6; add(sendCodeButton, gbc); JButton registerButton = new JButton("注册"); @@ -66,7 +73,7 @@ public class RegisterPanel extends JPanel { add(registerButton, gbc); JButton backButton = new JButton("返回登录"); - gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 2; + gbc.gridx = 0; gbc.gridy = 7; gbc.gridwidth = 2; add(backButton, gbc); sendCodeButton.addActionListener(e -> { @@ -84,6 +91,7 @@ public class RegisterPanel extends JPanel { String code = codeField.getText().trim(); String password = new String(passwordField.getPassword()); String confirm = new String(confirmField.getPassword()); + String username = usernameField.getText().trim(); // 获取用户名 if (sentCode == null) { JOptionPane.showMessageDialog(this, "请先获取验证码!"); @@ -102,9 +110,9 @@ public class RegisterPanel extends JPanel { return; } - controller.handleRegister(email, password); + controller.handleRegister(email, password, username); // 传递username }); backButton.addActionListener(e -> controller.showLoginPanel()); } -} +} \ No newline at end of file -- 2.34.1 From da247feca6ecd9a558dea6777d42d1b7271c2615 Mon Sep 17 00:00:00 2001 From: tianyuan <2861334240@qq.com> Date: Sat, 11 Oct 2025 20:09:44 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E9=80=89=E9=A1=B9?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Main.java | 2 +- src/view/QuizPanel.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Main.java b/src/Main.java index 4362b97..b415e36 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,6 +1,6 @@ import controller.AppController; -public class Main { +public class Main { public static void main(String[] args) { new AppController(); // 启动控制器 } diff --git a/src/view/QuizPanel.java b/src/view/QuizPanel.java index 7fdb9fc..61c57e1 100644 --- a/src/view/QuizPanel.java +++ b/src/view/QuizPanel.java @@ -49,6 +49,8 @@ public class QuizPanel extends JPanel { showResult(); return; } + //解决进入下一题后会默认选择前一题的选项问题 + group.clearSelection(); Question q = questions.get(currentIndex); questionLabel.setText("第 " + (currentIndex + 1) + " 题:" + q.questionText); for (int i = 0; i < 4; i++) { -- 2.34.1 From 20d01f507d32e3cfd0b0806ee70d3c711a746d5b Mon Sep 17 00:00:00 2001 From: tianyuan <2861334240@qq.com> Date: Sat, 11 Oct 2025 20:42:54 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=90=8D=E9=87=8D=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.java | 4 ++++ src/controller/UserManager.java | 8 ++++++++ src/ui/RegisterPanel.java | 11 ++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/controller/AppController.java b/src/controller/AppController.java index d9ec606..33da71f 100644 --- a/src/controller/AppController.java +++ b/src/controller/AppController.java @@ -104,4 +104,8 @@ public class AppController { JOptionPane.showMessageDialog(frame, "请输入有效的数字!"); } } + + public UserManager getUserManager() { + return userManager; + } } \ No newline at end of file diff --git a/src/controller/UserManager.java b/src/controller/UserManager.java index c0d819d..f5fd984 100644 --- a/src/controller/UserManager.java +++ b/src/controller/UserManager.java @@ -25,6 +25,14 @@ public class UserManager { return users.containsKey(email); } + public boolean isUsernameExists(String username) { + for (User user : users.values()) { + if (user.getUsername().equalsIgnoreCase(username)) { + return true; + } + } + return false; + } // 修改 addUser 方法,接收 username 参数 public void addUser(String email, String password, String username) { users.put(email, new User(email, password, username)); diff --git a/src/ui/RegisterPanel.java b/src/ui/RegisterPanel.java index 553b26f..a4d68d0 100644 --- a/src/ui/RegisterPanel.java +++ b/src/ui/RegisterPanel.java @@ -1,6 +1,7 @@ package ui; import controller.AppController; +import controller.UserManager; import util.EmailUtil; import javax.swing.*; @@ -109,7 +110,15 @@ public class RegisterPanel extends JPanel { JOptionPane.showMessageDialog(this, "密码需包含大小写字母与数字,长度6-10位!"); return; } - + //防止用户名重复 + if (controller.getUserManager().isUsernameExists(username)) { + JOptionPane.showMessageDialog(this, "用户名已存在,请更换!"); + return; + } + if (username.isEmpty() || password.isEmpty() || email.isEmpty() || code.isEmpty()) { + JOptionPane.showMessageDialog(this, "请填写完整信息!"); + return; + } controller.handleRegister(email, password, username); // 传递username }); -- 2.34.1 From 8b5246d9793ea36248a0607dc07b02346234e57e Mon Sep 17 00:00:00 2001 From: tianyuan <2861334240@qq.com> Date: Sat, 11 Oct 2025 20:52:18 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=86=8D=E5=81=9A?= =?UTF-8?q?=E4=B8=80=E4=BB=BD=E8=B7=B3=E8=BD=AC=E5=88=B0=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/QuizPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/QuizPanel.java b/src/view/QuizPanel.java index 61c57e1..41857b7 100644 --- a/src/view/QuizPanel.java +++ b/src/view/QuizPanel.java @@ -87,7 +87,7 @@ public class QuizPanel extends JPanel { add(retry); add(exit); - retry.addActionListener(e -> controller.showLoginPanel()); + retry.addActionListener(e -> controller.showDifficultySelectionPanel()); exit.addActionListener(e -> System.exit(0)); revalidate(); -- 2.34.1