Merge pull request '最终版1.5' (#9) from develop into main

hnu202326010217 5 months ago
commit 3141d0accd

38
.gitignore vendored

@ -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

3
.idea/.gitignore vendored

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

@ -0,0 +1 @@
UserService.java

@ -0,0 +1,27 @@
<component name="ArtifactManager">
<artifact type="jar" name="MathLearningApp:jar">
<output-path>$PROJECT_DIR$/target</output-path>
<root id="archive" name="MathLearningApp.jar">
<element id="directory" name="META-INF">
<element id="file-copy" path="$PROJECT_DIR$/out/META-INF/MANIFEST.MF" />
</element>
<element id="module-output" name="MathLearningApp" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/sun/mail/javax.mail/1.6.2/javax.mail-1.6.2.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/at/favre/lib/bcrypt/0.9.0/bcrypt-0.9.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/at/favre/lib/bytes/1.3.0/bytes-1.3.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.15.2/jackson-datatype-jsr310-2.15.2.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-core/2.15.2/jackson-core-2.15.2.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/javax/activation/activation/1.1/activation-1.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-databind/2.15.2/jackson-databind-2.15.2.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-annotations/2.15.2/jackson-annotations-2.15.2.jar" path-in-jar="/" />
<element id="library" level="project" name="Maven: com.sun.mail:javax.mail:1.6.2" />
<element id="library" level="project" name="Maven: javax.activation:activation:1.1" />
<element id="library" level="project" name="Maven: com.fasterxml.jackson.core:jackson-core:2.15.2" />
<element id="library" level="project" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.15.2" />
<element id="library" level="project" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.15.2" />
<element id="library" level="project" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2" />
<element id="library" level="project" name="Maven: at.favre.lib:bcrypt:0.9.0" />
<element id="library" level="project" name="Maven: at.favre.lib:bytes:1.3.0" />
</root>
</artifact>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -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" : "高中"
} ]

@ -0,0 +1,85 @@
## 功能特性
### 1. 用户管理系统
- 用户注册与邮箱验证
- 用户登录与密码管理
- 个人资料管理(用户名修改、密码修改)
- 用户教育阶段设置(小学/初中/高中)
### 2. 数学题目生成系统
- 支持三种教育阶段的题目生成:
- 小学:基础四则运算(+、-、×、÷)
- 初中:包含平方、开方运算
- 高中包含三角函数sin、cos、tan
- 自动生成带括号的表达式
- 智能选项生成(正确答案+干扰项)
### 3. 答题测试系统
- 自适应题目数量10-30题可选
- 图形化答题界面
- 实时答题记录
- 自动评分与结果展示
## 技术特点
### 核心技术栈
- Java Swing图形用户界面
- Jackson用户数据序列化
- BCrypt密码加密存储
- JavaMail邮件验证码发送
### 设计模式
- MVC架构模式
- 抽象工厂模式(题目生成器)
- 单例模式(用户服务)
## 使用说明
### 系统要求
- Java 8或更高版本
- 网络连接(用于邮件验证)
### 安装与运行
1. 克隆或下载项目源代码
2. 使用IDE导入项目
3. 配置邮件服务参数(在[RegisterFrame.java](file://D:\filee\软导\java\结对\project\MathLearningApp\git\src\main\java\mathlearning\ui\RegisterFrame.java)和[ChangeCodeFrame.java](file://D:\filee\软导\java\结对\project\MathLearningApp\git\src\main\java\mathlearning\ui\ChangeCodeFrame.java)中)
4. 编译并运行[App.java](file://D:\filee\软导\java\结对\project\MathLearningApp\git\src\main\java\mathlearning\App.java)
5. 运行可执行文件:终端输入: java -jar math-learning-app-1.0.0.jar
### 使用流程
1. **注册账户**:输入用户名、邮箱,获取验证码并设置密码
2. **登录系统**:使用注册邮箱和密码登录
3. **选择教育阶段**:首次登录需选择小学/初中/高中
4. **开始测试**:在主界面选择题目数量开始答题
5. **查看结果**:提交后自动评分并显示结果
## 项目亮点
1. **安全性**
- 密码BCrypt加密存储
- 邮箱验证机制
- 验证码时效控制10分钟
2. **智能化**
- 根据教育阶段自适应题目难度
- 智能选项生成算法
- 表达式计算引擎
3. **用户体验**
- 友好的图形界面
- 实时反馈机制
- 完善的错误处理
## 注意事项
1. 邮件服务配置:
- 需要在[RegisterFrame.java](file://D:\filee\软导\java\结对\project\MathLearningApp\git\src\main\java\mathlearning\ui\RegisterFrame.java)和[ChangeCodeFrame.java](file://D:\filee\软导\java\结对\project\MathLearningApp\git\src\main\java\mathlearning\ui\ChangeCodeFrame.java)中配置SMTP参数
- 默认使用QQ邮箱SMTP服务
2. 数据存储:
- 用户数据保存在`data/users.json`文件中
- 首次运行会自动创建数据文件
3. 密码要求:
- 长度6-10位
- 必须包含大小写字母和数字

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mathlearning</groupId>
<artifactId>math-learning-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- JavaMail for email verification -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<!-- JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Password hashing -->
<dependency>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编译器插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- Assembly Plugin 用于创建包含所有依赖的可执行JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<archive>
<manifest>
<!-- 重要:根据你的实际主类修改这里 -->
<mainClass>mathlearning.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId> <!-- 可选生成不带分类器的JAR -->
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -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);
});
}
}

@ -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; }
}

@ -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" +
"数学学习软件团队";
}
}

@ -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 <String> numbers = new Stack<>();
Stack <String> 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<String> 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);
}
}

@ -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<String> questions = new ArrayList<>();
Set<String> 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;
}
}

@ -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("----------------------------------------");
}
}
}

@ -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);
}
}

@ -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();
}
}

@ -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 );
}
}

@ -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);
}
}

@ -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<String, User> users;
private Map<String, LocalDateTime> 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;
}
}

@ -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();
}
}

@ -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();
}
}
}

@ -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();
}
}

@ -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();
}
}

@ -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);
}
}
}
}
}

@ -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();
}
}

@ -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<String> 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]);
}
}
Loading…
Cancel
Save