yesitis 5 months ago committed by Gitea
parent b1cabc0ceb
commit 663b823d28

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