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]);
+ }
+}