首次提交

陈映江 5 months ago committed by Gitea
parent 1bd7d65151
commit 12ca414c26

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,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,8 @@
[ {
"username" : "小鱼",
"email" : "1280556515@qq.com",
"passwordHash" : "$2a$12$J6oCgotwddD.tOspkOnMlOBcB9C7RcMNmR0MvaCuAutnIneXSrHm6",
"registrationDate" : [ 2025, 10, 8, 21, 0, 37, 863490500 ],
"verificationCode" : "658919",
"verified" : true
} ]

@ -0,0 +1,50 @@
<?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>
<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>
</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,41 @@
package mathlearning.model;
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;
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();
}
// 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; }
}

@ -0,0 +1,57 @@
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) {
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 = "尊敬的用户:\n\n" +
"您的注册验证码是:" + verificationCode + "\n\n" +
"该验证码有效期为10分钟。\n\n" +
"如果您没有注册本软件,请忽略此邮件。\n\n" +
"数学学习软件团队";
message.setText(emailContent);
Transport.send(message);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}

@ -0,0 +1,175 @@
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 boolean changePassword(String email, String oldPassword, String newPassword) {
User user = users.get(email);
if (user == null) {
return false;
}
// 验证旧密码
BCrypt.Result result = BCrypt.verifyer().verify(oldPassword.toCharArray(), user.getPasswordHash());
if (!result.verified) {
return false;
}
// 验证新密码格式
if (!validatePassword(newPassword)) {
return false;
}
// 更新密码
String newPasswordHash = BCrypt.withDefaults().hashToString(12, newPassword.toCharArray());
user.setPasswordHash(newPasswordHash);
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 isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
}

@ -0,0 +1,111 @@
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("邮箱:");
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());
buttonPanel.add(loginButton);
buttonPanel.add(registerButton);
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 openMainFrame(String email) {
// 这里先简单显示一个消息,后续可以扩展为主界面
JOptionPane.showMessageDialog(this,
"欢迎 " + email + "!\n登录成功主界面功能待实现。",
"登录成功", JOptionPane.INFORMATION_MESSAGE);
}
}

@ -0,0 +1,230 @@
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
);
initializeUI();
}
private void initializeUI() {
setTitle("数学学习软件 - 注册");
setDefaultCloseOperation(JFrame.EXIT_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 nameLabel = new JLabel("用户名:");
nameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
nameField = new JTextField();
JLabel emailLabel = new JLabel("邮箱:");
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(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);
mainPanel.add(formPanel, 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);
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();
}
}
Loading…
Cancel
Save