You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
7.5 KiB
227 lines
7.5 KiB
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 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 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 isValidEmail(String email) {
|
|
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
|
|
}
|
|
|
|
public User getUser(String email ) {
|
|
User user = users.get(email);
|
|
return user;
|
|
}
|
|
} |