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