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+_.-]+@(.+)$");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue