diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..4f2c764 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +UserService.java \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..001e756 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ 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/users.json b/data/users.json new file mode 100644 index 0000000..721399b --- /dev/null +++ b/data/users.json @@ -0,0 +1,17 @@ +[ { + "username" : "LXR", + "email" : "2759462569@qq.com", + "passwordHash" : "$2a$12$AiupZKqpZMXvstX6XYNGUuBDZOMdzqXy/ADaYzurUYatM293Lbxii", + "registrationDate" : [ 2025, 10, 11, 19, 40, 59, 177105400 ], + "verificationCode" : "371978", + "verified" : true, + "type" : "高中" +}, { + "username" : "小鱼", + "email" : "1280556515@qq.com", + "passwordHash" : "$2a$12$dbNwBK6NBj7mXU6YzNMAweTMhD9NOxsjPGzW2SfIM.QvGdWt7Lyvy", + "registrationDate" : [ 2025, 10, 10, 11, 7, 5, 853200500 ], + "verificationCode" : "688201", + "verified" : true, + "type" : "高中" +} ] \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6f1c887 --- /dev/null +++ b/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + com.mathlearning + math-learning-app + 1.0.0 + + + 11 + 11 + UTF-8 + + + + + + com.sun.mail + javax.mail + 1.6.2 + + + + + com.fasterxml.jackson.core + jackson-core + 2.15.2 + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.15.2 + + + + + at.favre.lib + bcrypt + 0.9.0 + + + \ No newline at end of file diff --git a/src/main/java/mathlearning/App.java b/src/main/java/mathlearning/App.java new file mode 100644 index 0000000..0e2ff2d --- /dev/null +++ b/src/main/java/mathlearning/App.java @@ -0,0 +1,24 @@ +package mathlearning; + +import mathlearning.service.UserService; +import mathlearning.ui.LoginFrame; + +import javax.swing.*; + +public class App { + public static void main(String[] args) { + // 设置UI风格 + try { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + + // 启动应用程序 + SwingUtilities.invokeLater(() -> { + UserService userService = new UserService(); + LoginFrame loginFrame = new LoginFrame(userService); + loginFrame.setVisible(true); + }); + } +} diff --git a/src/main/java/mathlearning/model/User.java b/src/main/java/mathlearning/model/User.java new file mode 100644 index 0000000..cbf61cd --- /dev/null +++ b/src/main/java/mathlearning/model/User.java @@ -0,0 +1,48 @@ +package mathlearning.model; + +import com.fasterxml.jackson.databind.BeanProperty; + +import java.time.LocalDateTime; + +public class User { + private String username; + private String email; + private String passwordHash; + private LocalDateTime registrationDate; + private String verificationCode; + private boolean verified; + private String type; + + public User( ) {} + + public User(String email, String passwordHash, String verificationCode) { + this.email = email; + this.passwordHash = passwordHash; + this.verificationCode = verificationCode; + this.verified = false; + this.registrationDate = LocalDateTime.now(); + this.type = null; + } + + // Getters and setters + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public String getPasswordHash() { return passwordHash; } + public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; } + + public String getVerificationCode() { return verificationCode; } + public void setVerificationCode(String verificationCode) { this.verificationCode = verificationCode; } + + public boolean isVerified() { return verified; } + public void setVerified(boolean verified) { this.verified = verified; } + + public LocalDateTime getRegistrationDate() { return registrationDate; } + public void setRegistrationDate(LocalDateTime registrationDate) { this.registrationDate = registrationDate; } + + public String getUsername() { return username; } + public void setUsername(String username) {this.username = username; } + + public String getType() { return type; } + public void setType(String type) { this.type = type; } +} diff --git a/src/main/java/mathlearning/service/EmailService.java b/src/main/java/mathlearning/service/EmailService.java new file mode 100644 index 0000000..cc9dcec --- /dev/null +++ b/src/main/java/mathlearning/service/EmailService.java @@ -0,0 +1,72 @@ +package mathlearning.service; + +import javax.mail.*; +import javax.mail.internet.*; +import java.util.Properties; + +public class EmailService { + private final String host; + private final String port; + private final String username; + private final String password; + private final boolean auth; + + public EmailService(String host, String port, String username, String password, boolean auth) { + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.auth = auth; + } + + public boolean sendVerificationCode(String toEmail, String verificationCode, int flag) { + try { + Properties props = new Properties(); + props.put("mail.smtp.host", host); + props.put("mail.smtp.port", port); + props.put("mail.smtp.auth", auth); + props.put("mail.smtp.starttls.enable", "true"); + + Session session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(username)); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); + message.setSubject("数学学习软件 - 注册验证码"); + String emailContent=""; + + if (flag == 1) emailContent = registerContent(verificationCode); + else if (flag == 2) emailContent = resetPasswordContent(verificationCode); + + + message.setText(emailContent); + + Transport.send(message); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public String registerContent(String verificationCode) { + return "尊敬的用户:\n\n" + + "您的注册验证码是:" + verificationCode + "\n\n" + + "该验证码有效期为10分钟。\n\n" + + "如果您没有注册本软件,请忽略此邮件。\n\n" + + "数学学习软件团队"; + } + + public String resetPasswordContent(String verificationCode) { + return "尊敬的用户:\n\n" + + "您的重置验证码是:" + verificationCode + "\n\n" + + "该验证码有效期为10分钟。\n\n" + + "如果您没有重置密码,请忽略此邮件。\n\n" + + "数学学习软件团队"; + } +} diff --git a/src/main/java/mathlearning/service/MultipleChoiceGenerator/ComputingUnit.java b/src/main/java/mathlearning/service/MultipleChoiceGenerator/ComputingUnit.java new file mode 100644 index 0000000..33c4a37 --- /dev/null +++ b/src/main/java/mathlearning/service/MultipleChoiceGenerator/ComputingUnit.java @@ -0,0 +1,190 @@ + +package mathlearning.service.MultipleChoiceGenerator; + + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +public class ComputingUnit { + String expression; + Stack numbers = new Stack<>(); + Stack operators = new Stack<>(); + + ComputingUnit(String Question){ + expression = Question; + } + + private void SplitAndChange(){ + + String[] split = expression.split(" "); + split = separateParentheses(split); + for(int i = 0 ; i < split.length ; i++){ + if (split[i].charAt(0) == '√') { + String StrNum = split[i].substring(1); + double num = Math.sqrt(Double.parseDouble(StrNum)); + split[i] = String.valueOf(num); + } else if (split[i].charAt(split[i].length()-1) == '²') { + String StrNum = split[i].substring(0, split[i].length()-1); + double result = Math.pow(Double.parseDouble(StrNum), 2); + split[i] = String.valueOf(result); + } else if ((split[i].charAt(0) == 's' || split[i].charAt(0) == 'c' + || split[i].charAt(0) == 't' )) { + int index = split[i].indexOf(')'); + int index2 = split[i].indexOf('('); + String StrNum = split[i].substring(index2 + 1, index); + double num = Double.parseDouble(SquOrRoot(StrNum)); + double result = getFunc(split[i].charAt(0), num); + split[i] = String.valueOf(result); + } + } + this.expression = String.join(" ", split); + } + + public String[] separateParentheses(String[] originalArray) { + List result = new ArrayList<>(); + + for (String item : originalArray) { + if (item.startsWith("(") && item.length() > 1) { + // 如果字符串以(开头且长度大于1,分离出( + result.add("("); + result.add(item.substring(1).trim()); + } else if (item.endsWith(")") && item.length() > 1 && + (item.charAt(0) != 's' && item.charAt(0) != 'c' && item.charAt(0) != 't')) { + // 如果字符串以)结尾且长度大于1,分离出) + result.add(item.substring(0, item.length() - 1).trim()); + result.add(")"); + } else if ((item.charAt(0) == 's' || item.charAt(0) == 'c' || item.charAt(0) == 't') && + (item.endsWith(")") && item.charAt(item.length()-2) == ')')) { + result.add(item.substring(0, item.length() - 1).trim()); + result.add(")"); + } else { + // 其他情况保持不变 + result.add(item); + } + } + + return result.toArray(new String[0]); + } + + + private String SquOrRoot(String StrNum){ + if(StrNum.charAt(0) == '√'){ + return String.valueOf(Math.sqrt(Double.parseDouble(StrNum.substring(1)))); + } else if (StrNum.charAt(StrNum.length()-1) == '²') { + return String.valueOf(Math.pow(Double.parseDouble(StrNum.substring(0, StrNum.length()-1)), 2)); + }else { + return StrNum; + } + } + + private double getFunc(char func, double num){ + switch (func) { + case 's': + return Math.sin(num); + case 'c': + return Math.cos(num); + case 't': + return Math.tan(num); + default: + return 0; + } + } + + private void priority(String operator){ + if(operators.isEmpty() || operators.peek().equals("(")){ + operators.push(operator); + }else{ + if((operators.peek().equals("+")|| operators.peek().equals("-")) && + (operator.equals("×") || operator.equals("÷"))){ + operators.push(operator); + }else{ + CulAndPushOperator(operator); + } + } + } + + private void CulAndPushOperator(String operator){ + String num1 = numbers.pop(); + String num2 = numbers.pop(); + String operator1 = operators.pop(); + operators.push(operator); + String result = SingleCul(num2, operator1, num1); + numbers.push(result); + } + + private String Compute(){ + String[] spilt = expression.split(" "); + for (int i = 0; i < spilt.length; i++){ + if(spilt[i].equals("+") || spilt[i].equals("-") || + spilt[i].equals("×") || spilt[i].equals("÷") || + spilt[i].equals("(") ){//处理运算符 + if( spilt[i].equals("(")){ + operators.push(spilt[i]); + }else{ + priority(spilt[i]); + } + }else if(spilt[i].equals(")")){ + String tempResult = numbers.pop(); + while (!operators.peek().equals("(")){ + String operator = operators.pop(); + String num1 = numbers.pop(); + tempResult = SingleCul(num1, operator, tempResult); + } + if(operators.peek().equals("(")){ + operators.pop(); + } + numbers.push(tempResult); + } else { + numbers.push(spilt[i]); + } + } + return CulWithoutPriority(); + } + + private String CulWithoutPriority(){ + if(numbers.isEmpty()){ + return "0"; + } + String result = numbers.pop(); + while (!operators.isEmpty() && !numbers.isEmpty()){ + String num1 = numbers.pop(); + String operator = operators.pop(); + result = SingleCul(num1, operator, result); + } + return result; + } + + public String getAnswer(){ + SplitAndChange(); + return Compute(); + } + + private String SingleCul(String nowResult, String operator, String num){ + // 使用 trim() 去除首尾空格 + // 使用 split("\\s+") 按空格分割,只取第一个元素(数字) + String cleanNowResult = nowResult.trim().split("\\s+")[0]; + String cleanNum = num.trim().split("\\s+")[0]; + + // 现在可以安全地解析数字了 + Double result = Double.parseDouble(cleanNowResult); + switch (operator) { + case "+": + result += Double.parseDouble(cleanNum); + break; + case "-": + result -= Double.parseDouble(cleanNum); + break; + case "×": + result *= Double.parseDouble(cleanNum); + break; + case "÷": + result /= Double.parseDouble(cleanNum); + break; + } + return String.valueOf(result); + } + + +} + diff --git a/src/main/java/mathlearning/service/MultipleChoiceGenerator/MultipleChoiceGenerator.java b/src/main/java/mathlearning/service/MultipleChoiceGenerator/MultipleChoiceGenerator.java new file mode 100644 index 0000000..f844f1a --- /dev/null +++ b/src/main/java/mathlearning/service/MultipleChoiceGenerator/MultipleChoiceGenerator.java @@ -0,0 +1,101 @@ +package mathlearning.service.MultipleChoiceGenerator; +import mathlearning.model.User; +import mathlearning.service.QuestionGenerator.*; + +import java.util.*; + +public class MultipleChoiceGenerator { + private static QuestionGenerator QuestionGenerator = new PrimaryGenerator(); + String[] QuestionList ; + String[] AnswerList ; + String[] ChoiceList ; + String[] CorrectAnswerNo; + + public MultipleChoiceGenerator(int count, User nowUser){// 如此声明MultipleChoiceGenerator实例,再调用下面三个接口 + this.QuestionList = new String[count]; + this.ChoiceList = new String[count]; + this.CorrectAnswerNo = new String[count]; + this.QuestionList = SetQuestionList(count, nowUser); + SetChoiceList(); + } + + public String[] GetQuestionList(){ + return this.QuestionList; + } + + public String[] GetChoiceList(){ + return this.ChoiceList; + } + + public String[] GetAnswerList(){ + return this.AnswerList; + } + + public String[] GetCorrectAnswerNo(){ + return this.CorrectAnswerNo; + } + + private void SetChoiceList(){ + for(int i = 0 ; i < this.AnswerList.length ; i++){ + Random random = new Random(); + String[] choiceNo = {"A","B","C","D"}; + String[] choices = new String[4]; + double correctChoice = Double.parseDouble(this.AnswerList[i]); + int position = random.nextInt(4); + choices[position] = choiceNo[position] + String.format("%.2f", correctChoice); + + + for(int j = 0 ; j < 4 ; j++){ + if(j != position){ + double choice = correctChoice; + double offset = random.nextInt(41) - 20; + choice += offset; + choices[j] =choiceNo[j] +"." + String.format("%.2f", choice); + } + else{ + CorrectAnswerNo[i] = choiceNo[j]; + } + } + + this.ChoiceList[i] = String.join(" ", choices); + } + } + + private String[] SetQuestionList(int count, User nowUser){ + String[] Questions= new String[count]; + if(nowUser.getType().equals("小学") ) { + QuestionGenerator = new PrimaryGenerator(); + } + else if( nowUser.getType().equals("初中") ) { + QuestionGenerator = new middleGenerator(); + } + else if( nowUser.getType().equals("高中") ) { + QuestionGenerator = new highGenerator(); + } + + List questions = new ArrayList<>(); + Set generatedQuestions = new HashSet<>(); + + for (int i = 0; i < count; i++) { + String question; + do { + question = QuestionGenerator.generateQuestion(); + } while ( generatedQuestions.contains(question )); + generatedQuestions.add(question); + questions.add(question); + } + + for(int i = 0 ; i< count ; i++){ + Questions[i] = questions.get(i); + } + this.AnswerList = new String[count]; + for(int i = 0 ; i< count ; i++){ + ComputingUnit computingUnit = new ComputingUnit(Questions[i]); + this.AnswerList[i] = computingUnit.getAnswer(); + } + return Questions; + } + + + +} \ No newline at end of file diff --git a/src/main/java/mathlearning/service/MultipleChoiceGenerator/MultipleChoiceGeneratorTest.java b/src/main/java/mathlearning/service/MultipleChoiceGenerator/MultipleChoiceGeneratorTest.java new file mode 100644 index 0000000..260dd42 --- /dev/null +++ b/src/main/java/mathlearning/service/MultipleChoiceGenerator/MultipleChoiceGeneratorTest.java @@ -0,0 +1,37 @@ +package mathlearning.service.MultipleChoiceGenerator; + +import mathlearning.model.User; + +public class MultipleChoiceGeneratorTest { + public static void main(String[] args) { + // 创建一个模拟用户 + User testUser = new User(); + testUser.setType("小学"); // 可以分别测试"小学"、"初中"、"高中" + + // 测试生成10道题目 + int questionCount = 10; + MultipleChoiceGenerator generator = new MultipleChoiceGenerator(questionCount, testUser); + + // 获取生成的题目、答案和选项 + String[] questions = generator.GetQuestionList(); + String[] answers = generator.GetAnswerList(); + String[] choices = generator.GetChoiceList(); + + // 输出测试结果 + System.out.println("=== 数学题目生成测试 ==="); + System.out.println("用户类型: " + testUser.getType()); + System.out.println("生成题目数量: " + questionCount); + System.out.println(); + + for (int i = 0; i < questions.length; i++) { + System.out.println("题目 " + (i + 1) + ": " + questions[i]); + System.out.println("答案: " + answers[i]); + System.out.println("选项:"); + String[] choiceArray = choices[i].split("\n"); + for (int j = 0; j < choiceArray.length; j++) { + System.out.println(" " + (char)('A' + j) + ". " + choiceArray[j]); + } + System.out.println("----------------------------------------"); + } + } +} diff --git a/src/main/java/mathlearning/service/QuestionGenerator/PrimaryGenerator.java b/src/main/java/mathlearning/service/QuestionGenerator/PrimaryGenerator.java new file mode 100644 index 0000000..d4f73fc --- /dev/null +++ b/src/main/java/mathlearning/service/QuestionGenerator/PrimaryGenerator.java @@ -0,0 +1,22 @@ +package mathlearning.service.QuestionGenerator; + +public class PrimaryGenerator extends QuestionGenerator{ + public PrimaryGenerator() { + super("小学"); + } + + @Override + public String generateQuestion() { + + int operandCount = random.nextInt(4) + 2; + + // 生成操作数 + int[] operands = new int[operandCount]; + for (int i = 0; i < operandCount; i++) { + operands[i] = random.nextInt(100) + 1; // 1-100 + } + + String question = preForOper(operands); + return addParen(question); + } +} diff --git a/src/main/java/mathlearning/service/QuestionGenerator/QuestionGenerator.java b/src/main/java/mathlearning/service/QuestionGenerator/QuestionGenerator.java new file mode 100644 index 0000000..b41d315 --- /dev/null +++ b/src/main/java/mathlearning/service/QuestionGenerator/QuestionGenerator.java @@ -0,0 +1,142 @@ +package mathlearning.service.QuestionGenerator; + +import java.util.Random; + +public abstract class QuestionGenerator{ + protected Random random = new Random(); + public abstract String generateQuestion() ; + protected String type; + + public String getType() { + return type; + } + + QuestionGenerator() { + type = "无"; + } + + QuestionGenerator(String Type) { + type = Type; + } + + protected String preForOper(int[] operands) { + StringBuilder question = new StringBuilder(); + String[] operators = {"+", "-", "×", "÷"}; + question.append(operands[0]); + + for (int i = 1; i < operands.length; i++) { + String op = operators[ random.nextInt (operators.length)]; + question.append(" ").append(op).append(" ").append(operands[i]); + } + return question.toString(); + + } + + protected boolean Check_num(String expression) { + if(!(expression.equals("+") || expression.equals("-") || expression.equals("×") || expression.equals("÷"))) { + return true; + } + else{ + return false; + } + } + + protected String addParen(String expression) { + String[] parts = expression.split(" "); + StringBuilder result = new StringBuilder(); + boolean r_paren_needed = false; + + for (int i = 0; i < parts.length; i++) { + if(Check_num ( parts [i]) ) { + if( !r_paren_needed ) { + if(i <= parts.length -3 ) + { + if( random.nextBoolean() ) + { result.append("(");r_paren_needed = true;} + } + result.append(parts[i]); + } else { + result.append( parts [i]); + if( !random.nextBoolean()) { + result.append(")");r_paren_needed = false; + } + } + } else { + result.append( parts [i] ); + } + if( i < parts.length -1 ) { + result.append(" "); + } + } + + if( r_paren_needed ){ + result.append(")");r_paren_needed = false; + } + return result.toString(); + } + + protected String add_squs(String expression) { + String[] parts = expression.split(" "); + StringBuilder result = new StringBuilder(); + boolean has_squs = false; + + for (int i = 0; i < parts.length; i++) { + if( Check_num( parts [i] )) { + double Thres = 0.3; + if( !has_squs){ + Thres = 0.7; + } + if ( random.nextDouble() < Thres ||(i == parts.length -1 && !has_squs)) { + if ( random.nextBoolean() ) { + result.append(parts[i]); + result.append("²"); + has_squs = true; + } else { + result.append("√"); + result.append(parts[i]); + has_squs = true; + } + } else { + result.append(parts[i]); + } + } else { + result.append(parts[i]); + } + if( i < parts.length -1 ) { + result.append(" "); + } + } + return result.toString(); + } + + protected String add_sins(String expression) { + String[] parts = expression.split(" "); + StringBuilder result = new StringBuilder(); + String[] functions = {"sin", "cos", "tan"}; + boolean has_func = false; + + for (int i = 0; i < parts.length; i++) { + double Thres = 0.4; + if(!has_func){Thres = 0.8;} + if(Check_num(parts[i])) + { + if ( random.nextDouble() < Thres ||(i == parts.length-1 && !has_func) ) { + String func = functions[random.nextInt(functions.length)]; + result.append(func).append("(").append(parts[i]).append(")"); + } else { + result.append(parts[i]); + } + } else { + result.append(parts[i]); + } + if( i < parts.length-1 ) { + result.append(" "); + } + + } + + return result.toString(); + } +} + + diff --git a/src/main/java/mathlearning/service/QuestionGenerator/highGenerator.java b/src/main/java/mathlearning/service/QuestionGenerator/highGenerator.java new file mode 100644 index 0000000..bfc17fc --- /dev/null +++ b/src/main/java/mathlearning/service/QuestionGenerator/highGenerator.java @@ -0,0 +1,24 @@ +package mathlearning.service.QuestionGenerator; + +public class highGenerator extends QuestionGenerator{ + + public highGenerator() { + super("高中"); + } + + @Override + public String generateQuestion() { + int operandCount = random.nextInt(4) + 2; + + // 生成操作数 + int[] operands = new int[ operandCount ]; + for (int i = 0; i < operandCount; i++) { + operands[i] = random.nextInt(100) + 1; // 1-100 + } + + String question = preForOper( operands ); + question = add_squs( question ); + question = add_sins( question ); + return addParen( question ); + } +} diff --git a/src/main/java/mathlearning/service/QuestionGenerator/middleGenerator.java b/src/main/java/mathlearning/service/QuestionGenerator/middleGenerator.java new file mode 100644 index 0000000..b1d3b61 --- /dev/null +++ b/src/main/java/mathlearning/service/QuestionGenerator/middleGenerator.java @@ -0,0 +1,23 @@ +package mathlearning.service.QuestionGenerator; + +public class middleGenerator extends QuestionGenerator{ + + public middleGenerator() { + super("初中"); + } + + @Override + public String generateQuestion() { + int operandCount = random.nextInt(4) + 2; + + // 生成操作数 + int[] operands = new int [ operandCount ]; + for (int i = 0; i < operandCount; i++) { + operands[i] = random.nextInt(100) + 1; // 1-100 + } + + String question = preForOper(operands); + question = add_squs(question); + return addParen(question); + } +} \ No newline at end of file diff --git a/src/main/java/mathlearning/service/UserService.java b/src/main/java/mathlearning/service/UserService.java new file mode 100644 index 0000000..930e3b1 --- /dev/null +++ b/src/main/java/mathlearning/service/UserService.java @@ -0,0 +1,251 @@ +package mathlearning.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import mathlearning.model.User; +import at.favre.lib.crypto.bcrypt.BCrypt; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class UserService { + private static final String USERS_FILE = "data/users.json"; + private static final long VERIFICATION_CODE_EXPIRY_MINUTES = 10; + + private Map users; + private Map verificationCodeTimestamps; + private ObjectMapper objectMapper; + + public UserService() { + this.users = new ConcurrentHashMap<>(); + this.verificationCodeTimestamps = new ConcurrentHashMap<>(); + this.objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + loadUsers(); + } + + private void loadUsers() { + try { + File file = new File(USERS_FILE); + if (file.exists() && file.length() > 0) { + User[] userArray = objectMapper.readValue(file, User[].class); + for (User user : userArray) { + users.put(user.getEmail(), user); + } + System.out.println("成功加载 " + userArray.length + " 个用户"); + } else { + saveUsers(); + System.out.println("创建新的用户数据文件"); + } + } catch (IOException e) { + System.err.println("加载用户数据失败: " + e.getMessage()); + backupAndRecreateFile(); + } + } + + private void backupAndRecreateFile() { + try { + File file = new File(USERS_FILE); + if (file.exists()) { + File backup = new File(USERS_FILE + ".bak." + System.currentTimeMillis()); + file.renameTo(backup); + System.err.println("损坏的文件已备份为: " + backup.getName()); + } + saveUsers(); + System.out.println("已重新创建用户数据文件"); + } catch (Exception e) { + System.err.println("备份文件失败: " + e.getMessage()); + } + } + + private void saveUsers() { + try { + File file = new File(USERS_FILE); + file.getParentFile().mkdirs(); + objectMapper.writerWithDefaultPrettyPrinter().writeValue(file, users.values().toArray(new User[0])); + } catch (IOException e) { + System.err.println("保存用户数据失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + public boolean registerUser(String email, String verificationCode) { + if (users.containsKey(email)) { + return false; // 用户已存在 + } + + // 生成密码占位符,实际密码在验证后设置 + String tempPasswordHash = BCrypt.withDefaults().hashToString(12, "temp".toCharArray()); + User user = new User(email, tempPasswordHash, verificationCode); + users.put(email, user); + verificationCodeTimestamps.put(email, LocalDateTime.now()); + saveUsers(); + return true; + } + + public boolean verifyUser(String username, String email, String verificationCode, String password) { + User user = users.get(email); + if (user == null || !user.getVerificationCode().equals(verificationCode)) { + return false; + } + + // 检查验证码是否过期 + LocalDateTime codeTime = verificationCodeTimestamps.get(email); + if (codeTime == null || codeTime.plusMinutes(VERIFICATION_CODE_EXPIRY_MINUTES).isBefore(LocalDateTime.now())) { + return false; + } + + // 验证密码格式 + if (!validatePassword(password)) { + return false; + } + + // 设置实际密码 + String passwordHash = BCrypt.withDefaults().hashToString(12, password.toCharArray()); + user.setPasswordHash(passwordHash); + user.setUsername(username); + user.setVerified(true); + verificationCodeTimestamps.remove(email); + saveUsers(); + return true; + } + + public boolean login(String email, String password) { + User user = users.get(email); + if (user == null || !user.isVerified()) { + return false; + } + + BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), user.getPasswordHash()); + return result.verified; + } + + public int changePassword(String email, String oldPassword, String newPassword) { + User user = users.get(email); + if (user == null) { + return 1; + } + + // 验证旧密码 + BCrypt.Result result = BCrypt.verifyer().verify(oldPassword.toCharArray(), user.getPasswordHash()); + if (!result.verified) { + return 2; + } + + // 验证新密码格式 + if (!validatePassword(newPassword)) { + return 3; + } + + if (oldPassword.equals(newPassword)) { + return 4; + } + + // 更新密码 + String newPasswordHash = BCrypt.withDefaults().hashToString(12, newPassword.toCharArray()); + user.setPasswordHash(newPasswordHash); + saveUsers(); + return 5; + } + + public boolean setPasswordResetCode(String email, String verificationCode) { + User user = users.get(email); + if (user == null) { + return false; // 用户不存在 + } + + user.setVerificationCode(verificationCode); + verificationCodeTimestamps.put(email, LocalDateTime.now()); + saveUsers(); + return true; + } + + public boolean resetPasswordWithCode(String email, String verificationCode, String newPassword) { + User user = users.get(email); + if (user == null) { + return false; + } + + // 验证验证码 + if (!user.getVerificationCode().equals(verificationCode)) { + return false; + } + + // 检查验证码是否过期 + LocalDateTime codeTime = verificationCodeTimestamps.get(email); + if (codeTime == null || codeTime.plusMinutes(VERIFICATION_CODE_EXPIRY_MINUTES).isBefore(LocalDateTime.now())) { + return false; + } + + // 验证新密码格式 + if (!validatePassword(newPassword)) { + return false; + } + + // 更新密码 + String newPasswordHash = BCrypt.withDefaults().hashToString(12, newPassword.toCharArray()); + user.setPasswordHash(newPasswordHash); + verificationCodeTimestamps.remove(email); // 清除验证码 + saveUsers(); + return true; + } + + public boolean updateUserType(String email, String type) { + User user = users.get(email); + if (user == null) { + return false; + } + user.setType(type); + saveUsers(); + return true; + } + + public boolean validatePassword(String password) { + if (password.length() < 6 || password.length() > 10) { + return false; + } + + boolean hasUpper = false; + boolean hasLower = false; + boolean hasDigit = false; + + for (char c : password.toCharArray()) { + if (Character.isUpperCase(c)) hasUpper = true; + if (Character.isLowerCase(c)) hasLower = true; + if (Character.isDigit(c)) hasDigit = true; + } + + return hasUpper && hasLower && hasDigit; + } + + public boolean userExists(String email) { + return users.containsKey(email); + } + + public boolean isUserExistsAndVerified(String email) { + User user = users.get(email); + return user != null && user.isVerified(); + } + + public boolean updateUsername(String email, String newUsername) { + User user = users.get(email); + if (user == null) { + return false; + } + user.setUsername(newUsername); + saveUsers(); + return true; + } + + public boolean isValidEmail(String email) { + return email.matches("^[A-Za-z0-9+_.-]+@(.+)$"); + } + + public User getUser(String email ) { + User user = users.get(email); + return user; + } +} \ No newline at end of file diff --git a/src/main/java/mathlearning/ui/ChangeCodeFrame.java b/src/main/java/mathlearning/ui/ChangeCodeFrame.java new file mode 100644 index 0000000..d799553 --- /dev/null +++ b/src/main/java/mathlearning/ui/ChangeCodeFrame.java @@ -0,0 +1,228 @@ +package mathlearning.ui; + +import mathlearning.service.EmailService; +import mathlearning.service.UserService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ChangeCodeFrame extends JFrame { + private UserService userService; + private EmailService emailService; + private LoginFrame loginFrame; + + private JTextField emailField; + private JButton sendCodeButton; + private JTextField codeField; + private JPasswordField passwordField; + private JPasswordField confirmPasswordField; + private JButton registerButton; + private JButton backButton; + + private String verificationCode; + + public ChangeCodeFrame(UserService userService, LoginFrame loginFrame) { + this.userService = userService; + this.loginFrame = loginFrame; + + // 配置邮箱服务(需要替换为真实的邮箱配置) + this.emailService = new EmailService( + "smtp.qq.com", + "587", + "2793415226@qq.com", // 替换为你的QQ邮箱 + "rmiomlakglpjddhb", // 替换为你的授权码 + true + ); + + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent windowEvent) { + loginFrame.setVisible(true); + } + }); + + initializeUI(); + } + + private void initializeUI() { + setTitle("数学学习软件 - 忘记密码"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(450, 400); + setLocationRelativeTo(null); + setResizable(false); + + // 主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("重置密码", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + mainPanel.add(titleLabel, BorderLayout.NORTH); + + //表单面板 + JPanel formPanel = new JPanel(new GridLayout(5, 2, 10, 10)); + + JLabel emailLabel = new JLabel("QQ邮箱:"); + emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + emailField = new JTextField(); + + JLabel codeLabel = new JLabel("验证码:"); + codeLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + + JPanel codePanel = new JPanel(new BorderLayout()); + codeField = new JTextField(); + sendCodeButton = new JButton("发送验证码"); + sendCodeButton.addActionListener(new SendCodeListener()); + + codePanel.add(codeField, BorderLayout.CENTER); + codePanel.add(sendCodeButton, BorderLayout.EAST); + + JLabel passwordLabel = new JLabel("新密码(大小写字母+数字):"); + passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + passwordField = new JPasswordField(); + + JLabel confirmPasswordLabel = new JLabel("确认密码:"); + confirmPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + confirmPasswordField = new JPasswordField(); + + formPanel.add(emailLabel); + formPanel.add(emailField); + formPanel.add(codeLabel); + formPanel.add(codePanel); + formPanel.add(passwordLabel); + formPanel.add(passwordField); + formPanel.add(confirmPasswordLabel); + formPanel.add(confirmPasswordField); + + mainPanel.add(formPanel, BorderLayout.CENTER); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout()); + + registerButton = new JButton("更改密码"); + registerButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + registerButton.addActionListener(new ChangeCodeFrame.ChangeCodeButtonListener()); + + backButton = new JButton("返回登录"); + backButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + backButton.addActionListener(e -> goBackToLogin()); + + buttonPanel.add(registerButton); + buttonPanel.add(backButton); + + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + } + + private class SendCodeListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String email = emailField.getText().trim(); + + if (email.isEmpty()) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "请输入邮箱地址", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (!userService.isValidEmail(email)) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "邮箱格式不正确", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 检查用户是否存在且已验证 + if (!userService.isUserExistsAndVerified(email)) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "该邮箱未注册或未验证", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 生成6位验证码 + verificationCode = String.valueOf((int)((Math.random() * 9 + 1) * 100000)); + + // 发送验证码邮件 + boolean sent = emailService.sendVerificationCode(email, verificationCode, 2); + + if (sent) { + // 在服务中保存验证码 + boolean codeSaved = userService.setPasswordResetCode(email, verificationCode); + + if (codeSaved) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "验证码已发送到您的邮箱,请查收", "成功", JOptionPane.INFORMATION_MESSAGE); + + // 禁用发送按钮60秒 + sendCodeButton.setEnabled(false); + new Thread(() -> { + try { + for (int i = 60; i > 0; i--) { + final int current = i; + SwingUtilities.invokeLater(() -> + sendCodeButton.setText(current + "秒后重发")); + Thread.sleep(1000); + } + SwingUtilities.invokeLater(() -> { + sendCodeButton.setText("发送验证码"); + sendCodeButton.setEnabled(true); + }); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + }).start(); + } else { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "保存验证码失败", "错误", JOptionPane.ERROR_MESSAGE); + } + } else { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "验证码发送失败,请检查邮箱地址或网络连接", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private class ChangeCodeButtonListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String email = emailField.getText().trim(); + String code = codeField.getText().trim(); + String password = new String(passwordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + // 验证输入 + if (email.isEmpty() || code.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "请填写所有字段", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + if (!password.equals(confirmPassword)) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "两次输入的密码不一致", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + if (!userService.validatePassword(password)) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "密码必须为6-10位,且包含大小写字母和数字", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (userService.resetPasswordWithCode(email, code, password)) { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "密码重置成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + goBackToLogin(); + } else { + JOptionPane.showMessageDialog(ChangeCodeFrame.this, + "重置失败,请检查验证码是否正确或是否已过期", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void goBackToLogin() { + loginFrame.setVisible(true); + this.dispose(); + } +} diff --git a/src/main/java/mathlearning/ui/ChangePasswordFrame.java b/src/main/java/mathlearning/ui/ChangePasswordFrame.java new file mode 100644 index 0000000..f79431b --- /dev/null +++ b/src/main/java/mathlearning/ui/ChangePasswordFrame.java @@ -0,0 +1,125 @@ +package mathlearning.ui; + +import mathlearning.model.User; +import mathlearning.service.UserService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ChangePasswordFrame extends JFrame { + private User user; + private UserService userService; + private ProfileFrame profileFrame; + private JPasswordField oldPasswordField; + private JPasswordField newPasswordField; + private JPasswordField confirmPasswordField; + + public ChangePasswordFrame(User user, UserService userService, ProfileFrame profileFrame) { + this.user = user; + this.userService = userService; + this.profileFrame = profileFrame; + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent windowEvent) { + profileFrame.setVisible(true); + } + }); + InitUI(); + } + + private void InitUI() { + setTitle("修改密码"); + setSize(400, 300); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setLocationRelativeTo(null); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("修改密码", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + mainPanel.add(titleLabel, BorderLayout.NORTH); + + //表单 + JPanel infoPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + JLabel oldPasswordLabel = new JLabel("请输入旧密码:"); + oldPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + oldPasswordField = new JPasswordField(); + infoPanel.add(oldPasswordLabel); + infoPanel.add(oldPasswordField); + JLabel newPasswordLabel = new JLabel("请输入新密码:"); + newPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + newPasswordField = new JPasswordField(); + infoPanel.add(newPasswordLabel); + infoPanel.add(newPasswordField); + JLabel confirmPasswordLabel = new JLabel("请再次输入新密码:"); + confirmPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + confirmPasswordField = new JPasswordField(); + infoPanel.add(confirmPasswordLabel); + infoPanel.add(confirmPasswordField); + JLabel infoLabel = new JLabel("密码需要包含大小写字母以及数字,6-10位。"); + infoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + JPanel centerPanel = new JPanel(new BorderLayout()); + centerPanel.add(infoPanel, BorderLayout.CENTER); + centerPanel.add(infoLabel, BorderLayout.SOUTH); + mainPanel.add(centerPanel, BorderLayout.CENTER); + + //按钮 + JPanel buttonPanel = new JPanel(new FlowLayout()); + JButton changeButton = new JButton("修改"); + changeButton.addActionListener(new ChangeButtonListener()); + JButton cancelButton = new JButton("取消并返回"); + cancelButton.addActionListener(e -> returnToProfileFrame()); + + buttonPanel.add(changeButton); + buttonPanel.add(cancelButton); + + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + } + + private void returnToProfileFrame() { + profileFrame.setVisible(true); + dispose(); + } + + private class ChangeButtonListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String oldPassword = new String(oldPasswordField.getPassword()); + String newPassword = new String(newPasswordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + if (!newPassword.equals(confirmPassword)) { + JOptionPane.showMessageDialog(ChangePasswordFrame.this, "两次输入的密码不相同!", "错误", JOptionPane.ERROR_MESSAGE ); + return; + } + + int changed = userService.changePassword(user.getEmail(), oldPassword, newPassword); + + if (changed == 1) { + JOptionPane.showMessageDialog(ChangePasswordFrame.this, "修改失败!用户账户异常!", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + else if (changed == 2) { + JOptionPane.showMessageDialog(ChangePasswordFrame.this, "修改失败!旧密码输入有误!", "错误", JOptionPane.ERROR_MESSAGE); + return; + } else if (changed == 3) { + JOptionPane.showMessageDialog(ChangePasswordFrame.this, "修改失败!新密码的格式有误!密码必须为6-10位,且包含大小写字母和数字", "错误", JOptionPane.ERROR_MESSAGE); + return; + } else if (changed == 4) { + JOptionPane.showMessageDialog(ChangePasswordFrame.this, "修改失败!旧密码与新密码一致!", "错误", JOptionPane.ERROR_MESSAGE); + return; + } else { + JOptionPane.showMessageDialog(ChangePasswordFrame.this, "修改成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + } + returnToProfileFrame(); + } + } +} diff --git a/src/main/java/mathlearning/ui/LoginFrame.java b/src/main/java/mathlearning/ui/LoginFrame.java new file mode 100644 index 0000000..e116abb --- /dev/null +++ b/src/main/java/mathlearning/ui/LoginFrame.java @@ -0,0 +1,168 @@ +package mathlearning.ui; + +import mathlearning.model.User; +import mathlearning.service.UserService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class LoginFrame extends JFrame{ + private UserService userService; + private JTextField emailField; + private JPasswordField passwordField; + + public LoginFrame(UserService userService) { + this.userService = userService; + initializeUI(); + } + + private void initializeUI() { + setTitle("数学学习软件 - 登录"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(400, 300); + setLocationRelativeTo(null); + setResizable(false); + + // 主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("用户登录", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + mainPanel.add(titleLabel, BorderLayout.NORTH); + + // 表单面板 + JPanel formPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + + JLabel emailLabel = new JLabel("QQ邮箱:"); + emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + emailField = new JTextField(); + + JLabel passwordLabel = new JLabel("密码:"); + passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + passwordField = new JPasswordField(); + + formPanel.add(emailLabel); + formPanel.add(emailField); + formPanel.add(passwordLabel); + formPanel.add(passwordField); + + mainPanel.add(formPanel, BorderLayout.CENTER); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout()); + + JButton loginButton = new JButton("登录"); + loginButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + loginButton.addActionListener(new LoginButtonListener()); + + JButton registerButton = new JButton("注册"); + registerButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + registerButton.addActionListener(e -> openRegisterFrame()); + + JButton changeCodeButton = new JButton("忘记密码?"); + changeCodeButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + changeCodeButton.addActionListener(e -> openChangeCodeFrame()); + + buttonPanel.add(loginButton); + buttonPanel.add(registerButton); + buttonPanel.add(changeCodeButton); + + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + } + + private class LoginButtonListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String email = emailField.getText().trim(); + String password = new String(passwordField.getPassword()); + + if (email.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(LoginFrame.this, + "请输入邮箱和密码", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (userService.login(email, password)) { + JOptionPane.showMessageDialog(LoginFrame.this, + "登录成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + // 这里可以打开主界面 + openMainFrame(email); + } else { + JOptionPane.showMessageDialog(LoginFrame.this, + "邮箱或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void openRegisterFrame() { + RegisterFrame registerFrame = new RegisterFrame(userService, this); + registerFrame.setVisible(true); + this.setVisible(false); + } + + private void openChangeCodeFrame() { + ChangeCodeFrame changeCodeFrame = new ChangeCodeFrame(userService, this); + changeCodeFrame.setVisible(true); + this.setVisible(false); + } + + private void openMainFrame(String email) { + User user = userService.getUser(email); + // 如果用户类型为空,让用户选择类型 + if (user.getType() == null || user.getType().isEmpty()) { + String[] types = {"小学", "初中", "高中"}; + String selectedType = (String) JOptionPane.showInputDialog( + this, + "欢迎 " + user.getUsername() + "!\n请选择您的教育阶段:", + "选择教育阶段", + JOptionPane.QUESTION_MESSAGE, + null, + types, + types[0] // 默认选择第一个 + ); + + // 如果用户选择了类型(没有点击取消) + if (selectedType != null) { + // 更新用户类型 + boolean updated = userService.updateUserType(email, selectedType); + if (updated) { + // 更新本地user对象 + user.setType(selectedType); + JOptionPane.showMessageDialog(this, + "登录成功!\n教育阶段:" + selectedType, + "登录成功", JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(this, + "登录成功!\n但教育阶段设置失败", + "登录成功", JOptionPane.WARNING_MESSAGE); + } + } else { + // 如果用户取消选择,可以设置默认类型或者保持为空 + userService.updateUserType(email, "小学"); + user.setType("小学"); + JOptionPane.showMessageDialog(this, + "登录成功!\n已为您选择默认教育阶段:小学", + "登录成功", JOptionPane.INFORMATION_MESSAGE); + } + } else { + // 如果已经有类型,直接显示欢迎信息 + JOptionPane.showMessageDialog(this, + "欢迎 " + user.getUsername() + "!\n登录成功。\n教育阶段:" + user.getType(), + "登录成功", JOptionPane.INFORMATION_MESSAGE); + } + + openMainApplicationWindow(user, userService); + } + + private void openMainApplicationWindow(User user, UserService userService) { + MainFrame mainFrame = new MainFrame(user, userService); + mainFrame.setVisible(true); + dispose(); + } +} diff --git a/src/main/java/mathlearning/ui/MainFrame.java b/src/main/java/mathlearning/ui/MainFrame.java new file mode 100644 index 0000000..18b6a0f --- /dev/null +++ b/src/main/java/mathlearning/ui/MainFrame.java @@ -0,0 +1,175 @@ +package mathlearning.ui; + +import com.sun.tools.javac.Main; +import mathlearning.model.User; +import mathlearning.service.UserService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class MainFrame extends JFrame { + private User currentUser; + private UserService userService; + private JLabel welcomeLabel; + private JLabel typeLabel; + private JButton changeTypeButton; + private JPanel mainPanel; + + public MainFrame(User user, UserService userService) { + this.currentUser = user; + this.userService = userService; + InitializeUI(); + } + + private void InitializeUI() { + setTitle("数学学习软件 - 菜单"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(500, 400); + setLocationRelativeTo(null); + setResizable(false); + + // 主面板 + JPanel titlePanel = new JPanel(new BorderLayout()); + mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 欢迎用户与type信息 + welcomeLabel = new JLabel("欢迎"+currentUser.getUsername()+"!", JLabel.CENTER); + welcomeLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + + typeLabel = new JLabel("当前选择的测试题为" + currentUser.getType() + "难度", JLabel.CENTER); + typeLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + typeLabel.setForeground(Color.BLUE); + + titlePanel.add(welcomeLabel, BorderLayout.NORTH); + titlePanel.add(typeLabel, BorderLayout.CENTER); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // 功能按钮区域 + JPanel centerButtonPanel = new JPanel(new GridLayout(2, 1, 15, 15)); + centerButtonPanel.setBorder(BorderFactory.createEmptyBorder(30, 50, 30, 50)); + // 切换教育阶段按钮 + changeTypeButton = new JButton("切换教育阶段"); + changeTypeButton.setFont(new Font("微软雅黑", Font.BOLD, 18)); + changeTypeButton.setBackground(new Color(70, 130, 180)); + changeTypeButton.setForeground(Color.WHITE); + changeTypeButton.setPreferredSize(new Dimension(200, 60)); + changeTypeButton.addActionListener(new ChangeTypeListener()); + + // 自我测试按钮 + JButton selfTestButton = new JButton("自我测试"); + selfTestButton.setFont(new Font("微软雅黑", Font.BOLD, 18)); + selfTestButton.setBackground(new Color(34, 139, 34)); + selfTestButton.setForeground(Color.WHITE); + selfTestButton.setPreferredSize(new Dimension(200, 60)); + selfTestButton.addActionListener(e -> openSelfTestFrame()); + + centerButtonPanel.add(changeTypeButton); + centerButtonPanel.add(selfTestButton); + mainPanel.add(centerButtonPanel, BorderLayout.CENTER); + + // 底部按钮面板 - 两个较小按钮 + JPanel bottomButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + + // 退出登录按钮 + JButton logoutButton = new JButton("退出登录"); + logoutButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + logoutButton.setBackground(Color.CYAN); + logoutButton.setForeground(Color.WHITE); + logoutButton.addActionListener(new LogoutListener()); + + // 个人资料按钮 + JButton profileButton = new JButton("个人资料"); + profileButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + profileButton.setBackground(new Color(100, 149, 237)); + profileButton.setForeground(Color.WHITE); + profileButton.addActionListener(new ProfileListener()); + + bottomButtonPanel.add(logoutButton); + bottomButtonPanel.add(profileButton); + mainPanel.add(bottomButtonPanel, BorderLayout.SOUTH); + + add(mainPanel); + } + + private class ChangeTypeListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String[] types = {"小学", "初中", "高中" }; + String selectedType = (String) JOptionPane.showInputDialog(MainFrame.this, "请选择需要切换的难度:", "选择难度", JOptionPane.QUESTION_MESSAGE, null, types, currentUser.getType()); + if (selectedType != null) { + boolean updated = userService.updateUserType(currentUser.getEmail(), selectedType); + + if (updated) { + currentUser.setType(selectedType); + JOptionPane.showMessageDialog(MainFrame.this, "当前难度已更改为" + currentUser.getType() , "切换成功", JOptionPane.INFORMATION_MESSAGE); + updateTypeDisplay(); + } else { + JOptionPane.showMessageDialog(MainFrame.this, "切换失败", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + } + + private void updateTypeDisplay() { + typeLabel.setText("当前选择的测试题为" + currentUser.getType() + "难度"); + } + + private class LogoutListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + int result = JOptionPane.showConfirmDialog(MainFrame.this, "确定要退出登陆吗?", "退出登录", JOptionPane.YES_NO_OPTION); + + if (result == JOptionPane.YES_OPTION) {// 为按钮添加边框和圆角效果 + changeTypeButton.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(50, 100, 150), 1), + BorderFactory.createEmptyBorder(10, 20, 10, 20))); + changeTypeButton.setFocusPainted(false); // 去除焦点边框 + + // 为主面板添加渐变背景 + mainPanel.setBackground(new Color(245, 245, 245)); + + LoginFrame loginFrame = new LoginFrame(userService); + loginFrame.setVisible(true); + dispose(); + } + } + } + + private class ProfileListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + ProfileFrame profileFrame = new ProfileFrame(currentUser, userService); + profileFrame.setVisible(true); + dispose(); + } + } + + private void openSelfTestFrame() { + String input = JOptionPane.showInputDialog(MainFrame.this, "请输入题目数量:(10-30)", "题目数量", JOptionPane.QUESTION_MESSAGE); + if (input == null) { + return; + } + + try { + int num = Integer.parseInt(input.trim()); + if (num < 10 || num > 30) { + JOptionPane.showMessageDialog(MainFrame.this, "题目数量应该在10-30之间!", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + SelTestFrame selTestFrame = new SelTestFrame(this, currentUser, userService, num); + selTestFrame.setVisible(true); + this.setVisible(false); + + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(MainFrame.this, "请输入数字!", "错误", JOptionPane.ERROR_MESSAGE); + } + + } + + public void reTryTest() { + openSelfTestFrame(); + } +} diff --git a/src/main/java/mathlearning/ui/ProfileFrame.java b/src/main/java/mathlearning/ui/ProfileFrame.java new file mode 100644 index 0000000..8f2b3cf --- /dev/null +++ b/src/main/java/mathlearning/ui/ProfileFrame.java @@ -0,0 +1,130 @@ +package mathlearning.ui; + +import mathlearning.model.User; +import mathlearning.service.UserService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ProfileFrame extends JFrame { + private User user; + private UserService userService; + private JPanel mainPanel; + private JLabel usernameValue; + + public ProfileFrame(User user, UserService userService) { + this.user = user; + this.userService = userService; + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent windowEvent) { + MainFrame mainFrame = new MainFrame(user, userService); + mainFrame.setVisible(true); + } + }); + InitUI(); + } + + private void InitUI() { + setTitle("个人资料"); + setSize(400, 300); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setLocationRelativeTo(null); + setResizable(false); + + // 创建主面板 + mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + //标题 + JLabel titleLabel = new JLabel("个人资料", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + mainPanel.add(titleLabel, BorderLayout.NORTH); + + //个人信息(表单) + JPanel infoPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + JLabel usernameLabel = new JLabel("用户名:"); + usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + usernameValue = new JLabel(user.getUsername()); + usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + infoPanel.add(usernameLabel); + infoPanel.add(usernameValue); + JLabel mailLabel = new JLabel("QQ邮箱:"); + mailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + JLabel mailValue = new JLabel(user.getEmail()); + infoPanel.add(mailLabel); + infoPanel.add(mailValue); + mainPanel.add(infoPanel, BorderLayout.CENTER); + + //三个按钮 + JPanel buttonPanel = new JPanel(new FlowLayout()); + JButton returnToMainButton = new JButton("返回"); + returnToMainButton.addActionListener(new returnToMainButtonListener()); + + JButton changePasswordButton = new JButton("修改密码"); + changePasswordButton.addActionListener(e -> openChangePasswordFrame()); + + JButton changeUsernameButton = new JButton("更改用户名"); + changeUsernameButton.addActionListener(new ChangeUsernameButtonListener()); + + buttonPanel.add(returnToMainButton); + buttonPanel.add(changePasswordButton); + buttonPanel.add(changeUsernameButton); + + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + } + + private class returnToMainButtonListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + MainFrame mainFrame = new MainFrame(user, userService); + mainFrame.setVisible(true); + dispose(); + } + } + + private void openChangePasswordFrame() { + ChangePasswordFrame changePasswordFrame = new ChangePasswordFrame(user, userService, this); + changePasswordFrame.setVisible(true); + this.setVisible(false); + } + + private class ChangeUsernameButtonListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e ) { + String newUsername = JOptionPane.showInputDialog(ProfileFrame.this, "请输入您的新用户名:", "修改用户名", JOptionPane.QUESTION_MESSAGE); + + if (newUsername == null ) { + return; + } + + newUsername = newUsername.trim(); + if (newUsername.isEmpty()) { + JOptionPane.showMessageDialog( ProfileFrame.this, "用户名不能为空!", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + if (newUsername.equals(user.getUsername())) { + JOptionPane.showMessageDialog( ProfileFrame.this, "新用户名与原用户名相同!", "提示", JOptionPane.ERROR_MESSAGE); + return; + } + int confirm = JOptionPane.showConfirmDialog(ProfileFrame.this,"确定要修改用户名为\"" + newUsername + "\"吗?", "修改用户名", JOptionPane.YES_NO_OPTION); + if (confirm == JOptionPane.YES_OPTION) { + String oldUsername = user.getUsername(); + + boolean updated = userService.updateUsername(user.getEmail(), newUsername); + if (updated) { + usernameValue.setText(newUsername); + JOptionPane.showMessageDialog(ProfileFrame.this, "修改成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + } + else { + user.setUsername(oldUsername); + JOptionPane.showMessageDialog(ProfileFrame.this, "修改失败!", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + } +} diff --git a/src/main/java/mathlearning/ui/RegisterFrame.java b/src/main/java/mathlearning/ui/RegisterFrame.java new file mode 100644 index 0000000..ef7997e --- /dev/null +++ b/src/main/java/mathlearning/ui/RegisterFrame.java @@ -0,0 +1,244 @@ +package mathlearning.ui; + +import mathlearning.service.EmailService; +import mathlearning.service.UserService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class RegisterFrame extends JFrame{ + private UserService userService; + private EmailService emailService; + private LoginFrame loginFrame; + + private JTextField nameField; + private JTextField emailField; + private JButton sendCodeButton; + private JTextField codeField; + private JPasswordField passwordField; + private JPasswordField confirmPasswordField; + private JButton registerButton; + private JButton backButton; + + private String verificationCode; + + public RegisterFrame(UserService userService, LoginFrame loginFrame) { + this.userService = userService; + this.loginFrame = loginFrame; + + // 配置邮箱服务(需要替换为真实的邮箱配置) + this.emailService = new EmailService( + "smtp.qq.com", + "587", + "2793415226@qq.com", // 替换为你的QQ邮箱 + "rmiomlakglpjddhb", // 替换为你的授权码 + true + ); + + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent windowEvent) { + loginFrame.setVisible(true); + } + }); + + initializeUI(); + } + + private void initializeUI() { + setTitle("数学学习软件 - 注册"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(450, 400); + setLocationRelativeTo(null); + setResizable(false); + + // 主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + mainPanel.add(titleLabel, BorderLayout.NORTH); + + // 表单面板 + JPanel formPanel = new JPanel(new GridLayout(6, 2, 10, 10)); + + JLabel nameLabel = new JLabel("用户名:"); + nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + nameField = new JTextField(); + + JLabel emailLabel = new JLabel("QQ邮箱:"); + emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + emailField = new JTextField(); + + JLabel codeLabel = new JLabel("验证码:"); + codeLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + + JPanel codePanel = new JPanel(new BorderLayout()); + codeField = new JTextField(); + sendCodeButton = new JButton("发送验证码"); + sendCodeButton.addActionListener(new SendCodeListener()); + + codePanel.add(codeField, BorderLayout.CENTER); + codePanel.add(sendCodeButton, BorderLayout.EAST); + + JLabel passwordLabel = new JLabel("密码:"); + passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + passwordField = new JPasswordField(); + + JLabel confirmPasswordLabel = new JLabel("确认密码:"); + confirmPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + confirmPasswordField = new JPasswordField(); + + JLabel infoLabel = new JLabel("提示:密码需要包含大小写字母以及数字,6-10位。"); + infoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + + formPanel.add(nameLabel); + formPanel.add(nameField); + formPanel.add(emailLabel); + formPanel.add(emailField); + formPanel.add(codeLabel); + formPanel.add(codePanel); + formPanel.add(passwordLabel); + formPanel.add(passwordField); + formPanel.add(confirmPasswordLabel); + formPanel.add(confirmPasswordField); + + JPanel centerPanel = new JPanel(new BorderLayout()); + centerPanel.add(formPanel, BorderLayout.CENTER); + centerPanel.add(infoLabel, BorderLayout.SOUTH); + + mainPanel.add(centerPanel, BorderLayout.CENTER); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout()); + + registerButton = new JButton("注册"); + registerButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + registerButton.addActionListener(new RegisterButtonListener()); + + backButton = new JButton("返回登录"); + backButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + backButton.addActionListener(e -> goBackToLogin()); + + buttonPanel.add(registerButton); + buttonPanel.add(backButton); + + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + } + + private class SendCodeListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String email = emailField.getText().trim(); + + if (email.isEmpty()) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "请输入邮箱地址", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (!userService.isValidEmail(email)) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "邮箱格式不正确", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (userService.userExists(email)) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "该邮箱已注册", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 生成6位验证码 + verificationCode = String.valueOf((int)((Math.random() * 9 + 1) * 100000)); + + // 发送验证码邮件 + boolean sent = emailService.sendVerificationCode(email, verificationCode, 1); + + if (sent) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "验证码已发送到您的邮箱,请查收", "成功", JOptionPane.INFORMATION_MESSAGE); + + // 禁用发送按钮60秒 + sendCodeButton.setEnabled(false); + new Thread(() -> { + try { + for (int i = 60; i > 0; i--) { + final int current = i; + SwingUtilities.invokeLater(() -> + sendCodeButton.setText(current + "秒后重发")); + Thread.sleep(1000); + } + SwingUtilities.invokeLater(() -> { + sendCodeButton.setText("发送验证码"); + sendCodeButton.setEnabled(true); + }); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + }).start(); + + } else { + JOptionPane.showMessageDialog(RegisterFrame.this, + "验证码发送失败,请检查邮箱地址或网络连接", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private class RegisterButtonListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String username = nameField.getText().trim(); + String email = emailField.getText().trim(); + String code = codeField.getText().trim(); + String password = new String(passwordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + // 验证输入 + if (email.isEmpty() || code.isEmpty() || password.isEmpty() || username.isEmpty()) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "请填写所有字段", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (!password.equals(confirmPassword)) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "两次输入的密码不一致", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (!userService.validatePassword(password)) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "密码必须为6-10位,且包含大小写字母和数字", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 先注册用户(临时状态) + if (userService.registerUser(email, verificationCode)) { + // 验证用户并设置密码 + if (userService.verifyUser(username, email, code, password)) { + JOptionPane.showMessageDialog(RegisterFrame.this, + "注册成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + goBackToLogin(); + } else { + JOptionPane.showMessageDialog(RegisterFrame.this, + "验证码错误或已过期", "错误", JOptionPane.ERROR_MESSAGE); + } + } else { + JOptionPane.showMessageDialog(RegisterFrame.this, + "注册失败,用户可能已存在", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void goBackToLogin() { + loginFrame.setVisible(true); + this.dispose(); + } +} diff --git a/src/main/java/mathlearning/ui/SelTestFrame.java b/src/main/java/mathlearning/ui/SelTestFrame.java new file mode 100644 index 0000000..327e3d7 --- /dev/null +++ b/src/main/java/mathlearning/ui/SelTestFrame.java @@ -0,0 +1,324 @@ +package mathlearning.ui; + +import mathlearning.model.User; +import mathlearning.service.MultipleChoiceGenerator.MultipleChoiceGenerator; +import mathlearning.service.UserService; + +import javax.swing.*; +import java.awt.*; + +public class SelTestFrame extends JFrame { + private MainFrame mainFrame; + private User user; + private UserService userService; + private MultipleChoiceGenerator multipleChoiceGenerator; + + private int questionNum; + private int currentQuestionDex; + private String[] answers; + private String[] myAnswers; + + private JLabel titleLabel; + private JButton optionA; + private JButton optionB; + private JButton optionC; + private JButton optionD; + private JButton prevButton; + private JButton nextButton; + private JButton submitButton; + private JLabel question; + private JLabel choice; + + public SelTestFrame(MainFrame mainFrame, User user, UserService userService, int num) { + this.mainFrame = mainFrame; + this.user = user; + this.userService = userService; + this.questionNum = num; + this.currentQuestionDex = 0; + this.myAnswers = new String[questionNum]; + this.multipleChoiceGenerator = new MultipleChoiceGenerator(num, user); + this.answers = multipleChoiceGenerator.GetCorrectAnswerNo(); + InitUI(); + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent windowEvent) { + handleCloseOperation(); + } + }); + } + + private void InitUI() { + setTitle("答题界面"); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + setSize(800, 600); + setLocationRelativeTo(null); + + //主面板 + JPanel mainPanel = new JPanel(new BorderLayout()); + + //上 + titleLabel = new JLabel("当前为:第" + (currentQuestionDex + 1) + "题"); + + //左侧面板 + JPanel leftPanel = createLeftPanel(); + + //中 + JPanel centerPanel = centerPanel(); + + //右 + JPanel rightPanel = createRightPanel(); + + //底 + JPanel bottomPanel = createButtonPanel(); + + mainPanel.add(titleLabel, BorderLayout.NORTH); + mainPanel.add(leftPanel, BorderLayout.WEST); + mainPanel.add(centerPanel, BorderLayout.CENTER); + mainPanel.add(rightPanel, BorderLayout.EAST); + mainPanel.add(bottomPanel, BorderLayout.SOUTH); + + add(mainPanel); + + updateNavigationButtons(); + } + + private JPanel createLeftPanel() { + JPanel panel = new JPanel(new BorderLayout()); + + JLabel usernameLabel = new JLabel("用户:" + user.getUsername()); + usernameLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + usernameLabel.setForeground(Color.BLUE); + panel.add(usernameLabel, BorderLayout.NORTH); + + JList questionList = new JList<>(); + String[] questions = new String[questionNum + 1]; + for (int i = 0; i < questionNum; i++) { + questions[i] = "题目" + (i + 1); + } + questionList.setListData(questions); + panel.add(questionList, BorderLayout.CENTER); + + return panel; + } + + private JPanel centerPanel() { + JPanel panel = new JPanel(new GridLayout(6, 1)); + + JLabel questionLabel = new JLabel("题目:"); + questionLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + question = new JLabel(multipleChoiceGenerator.GetQuestionList()[currentQuestionDex]); + question.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + panel.add(questionLabel); + panel.add(question); + + JLabel choiceLabel = new JLabel("选项:"); + choiceLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + choice = new JLabel(multipleChoiceGenerator.GetChoiceList()[currentQuestionDex]); + choiceLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + panel.add(choiceLabel); + panel.add(choice); + + return panel; + } + + private JPanel createRightPanel() { + JPanel panel = new JPanel(new BorderLayout()); + + JLabel titleLabel = new JLabel("作答区域:"); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + panel.add(titleLabel, BorderLayout.NORTH); + + JPanel buttonPanel = new JPanel(new GridLayout(4, 1)); + + optionA = new JButton("A"); + optionA.setBackground(Color.LIGHT_GRAY); + optionA.setForeground(Color.BLACK); + optionA.addActionListener(e -> saveAnswer("A")); + optionB = new JButton("B"); + optionB.setBackground(Color.LIGHT_GRAY); + optionB.setForeground(Color.BLACK); + optionB.addActionListener(e -> saveAnswer("B")); + optionC = new JButton("C"); + optionC.setBackground(Color.LIGHT_GRAY); + optionC.setForeground(Color.BLACK); + optionC.addActionListener(e -> saveAnswer("C")); + optionD = new JButton("D"); + optionD.setBackground(Color.LIGHT_GRAY); + optionD.setForeground(Color.BLACK); + optionD.addActionListener(e -> saveAnswer("D")); + buttonPanel.add(optionA); + buttonPanel.add(optionB); + buttonPanel.add(optionC); + buttonPanel.add(optionD); + panel.add(buttonPanel, BorderLayout.CENTER); + + return panel; + } + + private JPanel createButtonPanel() { + JPanel panel = new JPanel(new FlowLayout()); + + prevButton = new JButton("上一题"); + prevButton.addActionListener(e -> goToPrevQuestion()); + + nextButton = new JButton("下一题"); + nextButton.addActionListener(e -> goToNextQuestion()); + + submitButton = new JButton("提交"); + submitButton.addActionListener(e -> goToSubmit()); + + panel.add(prevButton); + panel.add(nextButton); + panel.add(submitButton); + return panel; + } + + private void saveAnswer(String answer) { + resetButtonColors(); + + switch (answer) { + case "A": + optionA.setBackground(Color.GREEN); + myAnswers[currentQuestionDex] = answer; + break; + case "B": + optionB.setBackground(Color.GREEN); + myAnswers[currentQuestionDex] = answer; + break; + case "C": + optionC.setBackground(Color.GREEN); + myAnswers[currentQuestionDex] = answer; + break; + case "D": + optionD.setBackground(Color.GREEN); + myAnswers[currentQuestionDex] = answer; + break; + } + + } + + private void resetButtonColors() { + optionA.setBackground(Color.LIGHT_GRAY); + optionB.setBackground(Color.LIGHT_GRAY); + optionC.setBackground(Color.LIGHT_GRAY); + optionD.setBackground(Color.LIGHT_GRAY); + } + + private void goToPrevQuestion() { + if (currentQuestionDex > 0) { + currentQuestionDex--; + updateTitleLabel(); + updateQuestion(); + if (myAnswers[currentQuestionDex] == null) { + resetButtonColors(); + } + else { + saveAnswer(myAnswers[currentQuestionDex]); + } + + updateNavigationButtons(); + } + } + + private void goToNextQuestion() { + if (currentQuestionDex < questionNum - 1) { + currentQuestionDex++; + updateTitleLabel(); + updateQuestion(); + if (myAnswers[currentQuestionDex] == null) { + resetButtonColors(); + } else { + saveAnswer(myAnswers[currentQuestionDex]); + } + + updateNavigationButtons(); + } + } + + private void goToSubmit() { + int confirm = JOptionPane.showConfirmDialog(SelTestFrame.this, "你确定要提交试卷吗?", "提示", JOptionPane.YES_NO_OPTION); + if (confirm == JOptionPane.YES_OPTION) { + int correctCount = 0; + correctCount = getScore(answers, myAnswers); + + double accuracy = Math.round((double) correctCount / myAnswers.length * 10000) / 100.0; + double score = Math.round((double) correctCount * 100 / myAnswers.length * 10) / 10.0; + + // 弹出分数结果 + String[] options = {"返回主菜单", "继续做题"}; + int choice = JOptionPane.showOptionDialog( + SelTestFrame.this, + "您答对了" + correctCount + "道题,正确率为" + String.format("%.2f", accuracy) + "\n您的得分是: " + score + "分", + "测试结果", + JOptionPane.YES_NO_OPTION, + JOptionPane.INFORMATION_MESSAGE, + null, + options, + options[0] + ); + + if (choice == 0) { + mainFrame.setVisible(true); + dispose(); + } else if (choice == 1) { + dispose(); + mainFrame.setVisible(true); + mainFrame.reTryTest(); + } + } + } + + private void updateNavigationButtons() { + if (currentQuestionDex == 0) { + prevButton.setEnabled(false); + prevButton.setVisible(false); + } + else { + prevButton.setEnabled(true); + prevButton.setVisible(true); + } + + if (currentQuestionDex == questionNum - 1) { + nextButton.setEnabled(false); + nextButton.setVisible(false); + submitButton.setEnabled(true); + submitButton.setVisible(true); + } else { + nextButton.setEnabled(true); + nextButton.setVisible(true); + submitButton.setEnabled(false); + submitButton.setVisible(false); + } + } + + private void updateTitleLabel() { + titleLabel.setText("当前为:第" + (currentQuestionDex + 1) + "题"); + } + + private void handleCloseOperation() { + int result = JOptionPane.showConfirmDialog(SelTestFrame.this, "确定要中途退出答题吗?当前答题进度将会丢失!", "确认退出", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (result == JOptionPane.YES_OPTION) { + mainFrame.setVisible(true); + dispose(); + } + } + + private int getScore(String[] answers, String[] myAnswers) { + if (answers == null || myAnswers == null){ + return 0; + } + int score = 0; + for (int i = 0; i < answers.length; i++) { + if (answers[i] != null && answers[i].equals(myAnswers[i])){ + score++; + } + } + return score; + } + + private void updateQuestion() { + question.setText(multipleChoiceGenerator.GetQuestionList()[currentQuestionDex]); + choice.setText(multipleChoiceGenerator.GetChoiceList()[currentQuestionDex]); + } +}