|
|
|
|
@ -4,6 +4,7 @@ import com.google.gson.Gson;
|
|
|
|
|
import com.google.gson.GsonBuilder;
|
|
|
|
|
import com.google.gson.reflect.TypeToken;
|
|
|
|
|
import com.mathgenerator.model.User;
|
|
|
|
|
import com.mathgenerator.util.ValidationUtils;
|
|
|
|
|
import org.apache.commons.mail.Email;
|
|
|
|
|
import org.apache.commons.mail.EmailException;
|
|
|
|
|
import org.apache.commons.mail.SimpleEmail;
|
|
|
|
|
@ -21,12 +22,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 用户服务类。
|
|
|
|
|
* <p>
|
|
|
|
|
* 负责处理所有与用户账户相关的业务逻辑,包括用户的注册、登录、密码修改、
|
|
|
|
|
* 信息查询以及发送验证码等功能。该类通过读写 JSON 文件来持久化用户数据。
|
|
|
|
|
*/
|
|
|
|
|
public class UserService {
|
|
|
|
|
private static final Path USER_FILE_PATH = Paths.get("users.json");
|
|
|
|
|
private static final Pattern PASSWORD_PATTERN =
|
|
|
|
|
@ -34,25 +29,52 @@ public class UserService {
|
|
|
|
|
private Map<String, User> userDatabase;
|
|
|
|
|
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造一个新的 UserService 实例。
|
|
|
|
|
* 在构造时会自动从 {@code users.json} 文件加载用户数据。
|
|
|
|
|
*/
|
|
|
|
|
public UserService() {
|
|
|
|
|
this.userDatabase = loadUsersFromFile();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 新增方法:检查用户名或邮箱是否已被占用 ---
|
|
|
|
|
/**
|
|
|
|
|
* 从 {@code users.json} 文件加载用户数据到内存中的 Map。
|
|
|
|
|
* 如果文件不存在或为空,则返回一个空的 Map。
|
|
|
|
|
*
|
|
|
|
|
* @return 一个包含所有用户数据的 {@code ConcurrentHashMap}。
|
|
|
|
|
* 检查指定的用户名或邮箱是否已经被注册。
|
|
|
|
|
* @param username 要检查的用户名。
|
|
|
|
|
* @param email 要检查的邮箱地址。
|
|
|
|
|
* @return 如果用户名或邮箱中任意一个已被占用,则返回 true。
|
|
|
|
|
*/
|
|
|
|
|
public boolean isUsernameOrEmailTaken(String username, String email) {
|
|
|
|
|
if (userDatabase.containsKey(username)) {
|
|
|
|
|
return true; // 用户名已存在
|
|
|
|
|
}
|
|
|
|
|
return userDatabase.values().stream()
|
|
|
|
|
.anyMatch(u -> email.equals(u.email())); // 邮箱已存在
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 核心修改:创建一个全新的、原子化的注册方法 ---
|
|
|
|
|
/**
|
|
|
|
|
* 在用户设置密码的最后一步,创建并保存一个完整的新用户。
|
|
|
|
|
* @param username 用户名。
|
|
|
|
|
* @param email 邮箱地址。
|
|
|
|
|
* @param password 经过验证的密码。
|
|
|
|
|
* @return 如果成功创建用户,返回 true。
|
|
|
|
|
*/
|
|
|
|
|
public boolean createUserWithPassword(String username, String email, String password) {
|
|
|
|
|
// 双重检查,确保在并发场景下数据的一致性
|
|
|
|
|
if (isUsernameOrEmailTaken(username, email)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
User newUser = new User(username, email, password);
|
|
|
|
|
userDatabase.put(username, newUser);
|
|
|
|
|
saveUsers();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 旧的 register 和 setPassword 方法已被上面的新方法取代,可以删除 ---
|
|
|
|
|
|
|
|
|
|
// ... (文件中的其他方法,如 login, sendVerificationCode, changePassword 等保持不变) ...
|
|
|
|
|
|
|
|
|
|
private Map<String, User> loadUsersFromFile() {
|
|
|
|
|
if (!Files.exists(USER_FILE_PATH)) {
|
|
|
|
|
return new ConcurrentHashMap<>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try (FileReader reader = new FileReader(USER_FILE_PATH.toFile())) {
|
|
|
|
|
Type type = new TypeToken<Map<String, User>>() {}.getType();
|
|
|
|
|
Map<String, User> loadedUsers = gson.fromJson(reader, type);
|
|
|
|
|
@ -63,9 +85,6 @@ public class UserService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将内存中的用户数据保存到 {@code users.json} 文件中。
|
|
|
|
|
*/
|
|
|
|
|
private void saveUsers() {
|
|
|
|
|
try (FileWriter writer = new FileWriter(USER_FILE_PATH.toFile())) {
|
|
|
|
|
gson.toJson(this.userDatabase, writer);
|
|
|
|
|
@ -74,37 +93,24 @@ public class UserService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据用户名查找用户。
|
|
|
|
|
*
|
|
|
|
|
* @param username 要查找的用户名。
|
|
|
|
|
* @return 一个包含 {@link User} 对象的 {@code Optional},如果找不到则为空。
|
|
|
|
|
*/
|
|
|
|
|
public Optional<User> findUserByUsername(String username) {
|
|
|
|
|
return Optional.ofNullable(this.userDatabase.get(username));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证用户名和密码,执行登录操作。
|
|
|
|
|
*
|
|
|
|
|
* @param username 用户的用户名。
|
|
|
|
|
* @param password 用户的密码。
|
|
|
|
|
* @return 如果登录成功,返回一个包含 {@link User} 对象的 {@code Optional};否则返回空的 {@code Optional}。
|
|
|
|
|
*/
|
|
|
|
|
public Optional<User> login(String username, String password) {
|
|
|
|
|
return findUserByUsername(username)
|
|
|
|
|
.filter(user -> user.password().equals(password));
|
|
|
|
|
public Optional<User> login(String identifier, String password) {
|
|
|
|
|
Optional<User> userOptional;
|
|
|
|
|
if (identifier.contains("@") && ValidationUtils.isEmailValid(identifier)) {
|
|
|
|
|
userOptional = this.userDatabase.values().stream()
|
|
|
|
|
.filter(user -> identifier.equals(user.email()))
|
|
|
|
|
.findFirst();
|
|
|
|
|
} else {
|
|
|
|
|
userOptional = findUserByUsername(identifier);
|
|
|
|
|
}
|
|
|
|
|
return userOptional.filter(user -> user.password() != null && user.password().equals(password));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 向指定的邮箱地址发送一个6位数的随机验证码。
|
|
|
|
|
*
|
|
|
|
|
* @param email 接收验证码的目标邮箱地址。
|
|
|
|
|
* @return 如果邮件发送成功,则返回生成的6位验证码字符串;如果失败,则返回 {@code null}。
|
|
|
|
|
*/
|
|
|
|
|
public String sendVerificationCode(String email) {
|
|
|
|
|
String code = String.format("%06d", ThreadLocalRandom.current().nextInt(100000, 1000000));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Email mail = new SimpleEmail();
|
|
|
|
|
mail.setHostName(EmailConfig.getHost());
|
|
|
|
|
@ -125,66 +131,10 @@ public class UserService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 注册一个新用户,该用户初始时没有密码。
|
|
|
|
|
*
|
|
|
|
|
* @param username 新用户的用户名。
|
|
|
|
|
* @param email 新用户的邮箱地址。
|
|
|
|
|
* @return 如果注册成功,返回 {@code true};如果用户名或邮箱已存在,则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public boolean register(String username, String email) {
|
|
|
|
|
if (userDatabase.containsKey(username)) {
|
|
|
|
|
return false; // 用户名已存在
|
|
|
|
|
}
|
|
|
|
|
if (userDatabase.values().stream().anyMatch(u -> email.equals(u.email()))) {
|
|
|
|
|
return false; // 邮箱已存在
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
User newUser = new User(username, email, null);
|
|
|
|
|
userDatabase.put(username, newUser);
|
|
|
|
|
saveUsers();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 为指定用户设置其初始密码。
|
|
|
|
|
* 此方法只在用户当前密码为 {@code null} 时才允许操作。
|
|
|
|
|
*
|
|
|
|
|
* @param username 要设置密码的用户名。
|
|
|
|
|
* @param password 要设置的新密码。
|
|
|
|
|
* @return 如果密码设置成功,返回 {@code true};如果用户不存在或已有密码,则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public boolean setPassword(String username, String password) {
|
|
|
|
|
return findUserByUsername(username)
|
|
|
|
|
.map(user -> {
|
|
|
|
|
if (user.password() == null) {
|
|
|
|
|
User updatedUser = new User(user.username(), user.email(), password);
|
|
|
|
|
userDatabase.put(username, updatedUser);
|
|
|
|
|
saveUsers();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}).orElse(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证给定的密码字符串是否符合预设的复杂度要求。
|
|
|
|
|
*
|
|
|
|
|
* @param password 待验证的密码。
|
|
|
|
|
* @return 如果密码有效,返回 {@code true};否则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public static boolean isPasswordValid(String password) {
|
|
|
|
|
return password != null && PASSWORD_PATTERN.matcher(password).matches();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 修改指定用户的密码。
|
|
|
|
|
*
|
|
|
|
|
* @param username 要修改密码的用户名。
|
|
|
|
|
* @param oldPassword 用户的当前密码,用于验证。
|
|
|
|
|
* @param newPassword 用户的新密码。
|
|
|
|
|
* @return 如果旧密码正确且新密码设置成功,返回 {@code true};否则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public boolean changePassword(String username, String oldPassword, String newPassword) {
|
|
|
|
|
return findUserByUsername(username)
|
|
|
|
|
.filter(user -> user.password().equals(oldPassword))
|
|
|
|
|
|