|
|
|
|
@ -1,10 +1,13 @@
|
|
|
|
|
package com.mathgenerator.service;
|
|
|
|
|
|
|
|
|
|
import com.google.gson.Gson;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
import com.google.gson.GsonBuilder;
|
|
|
|
|
import com.google.gson.reflect.TypeToken;
|
|
|
|
|
import com.mathgenerator.model.User;
|
|
|
|
|
import org.apache.commons.mail.Email;
|
|
|
|
|
import org.apache.commons.mail.EmailException;
|
|
|
|
|
import org.apache.commons.mail.SimpleEmail;
|
|
|
|
|
|
|
|
|
|
import java.io.FileReader;
|
|
|
|
|
import java.io.FileWriter;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
@ -17,25 +20,35 @@ import java.util.Optional;
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
import org.apache.commons.mail.Email;
|
|
|
|
|
import org.apache.commons.mail.EmailException;
|
|
|
|
|
import org.apache.commons.mail.SimpleEmail;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 用户服务类。
|
|
|
|
|
* <p>
|
|
|
|
|
* 负责处理所有与用户账户相关的业务逻辑,包括用户的注册、登录、密码修改、
|
|
|
|
|
* 信息查询以及发送验证码等功能。该类通过读写 JSON 文件来持久化用户数据。
|
|
|
|
|
*/
|
|
|
|
|
public class UserService {
|
|
|
|
|
private static final Path USER_FILE_PATH = Paths.get("users.json");
|
|
|
|
|
// 密码策略: 6-10位, 必须包含大小写字母和数字
|
|
|
|
|
private static final Pattern PASSWORD_PATTERN =
|
|
|
|
|
Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$");
|
|
|
|
|
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}。
|
|
|
|
|
*/
|
|
|
|
|
private Map<String, User> loadUsersFromFile() {
|
|
|
|
|
// 如果文件不存在,直接返回一个空的Map,不再创建默认用户
|
|
|
|
|
if (!Files.exists(USER_FILE_PATH)) {
|
|
|
|
|
return new ConcurrentHashMap<>();
|
|
|
|
|
}
|
|
|
|
|
@ -43,7 +56,6 @@ public class UserService {
|
|
|
|
|
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);
|
|
|
|
|
// 如果文件为空或格式错误,也返回一个空的Map
|
|
|
|
|
return loadedUsers != null ? new ConcurrentHashMap<>(loadedUsers) : new ConcurrentHashMap<>();
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
System.err.println("错误:加载用户文件失败 - " + e.getMessage());
|
|
|
|
|
@ -51,6 +63,9 @@ public class UserService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将内存中的用户数据保存到 {@code users.json} 文件中。
|
|
|
|
|
*/
|
|
|
|
|
private void saveUsers() {
|
|
|
|
|
try (FileWriter writer = new FileWriter(USER_FILE_PATH.toFile())) {
|
|
|
|
|
gson.toJson(this.userDatabase, writer);
|
|
|
|
|
@ -59,70 +74,72 @@ 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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* (已更新) 发送真实的邮件验证码。
|
|
|
|
|
* @param email 用户的邮箱
|
|
|
|
|
* @return 成功发送则返回生成的6位验证码, 失败则返回null
|
|
|
|
|
* 向指定的邮箱地址发送一个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();
|
|
|
|
|
|
|
|
|
|
// 1. 设置SMTP服务器信息
|
|
|
|
|
mail.setHostName(EmailConfig.getHost());
|
|
|
|
|
mail.setSmtpPort(EmailConfig.getPort());
|
|
|
|
|
mail.setAuthentication(EmailConfig.getUsername(), EmailConfig.getPassword());
|
|
|
|
|
mail.setSSLOnConnect(true); // 开启SSL加密
|
|
|
|
|
|
|
|
|
|
// 2. 设置邮件内容
|
|
|
|
|
mail.setFrom(EmailConfig.getUsername()); // 发件人
|
|
|
|
|
mail.setSubject("【数学学习软件】您的注册验证码"); // 邮件主题
|
|
|
|
|
mail.setMsg("您好!\n\n感谢您注册数学学习软件。您的验证码是:" + code + "\n\n请在5分钟内使用。"); // 邮件正文
|
|
|
|
|
mail.addTo(email); // 收件人
|
|
|
|
|
|
|
|
|
|
// 3. 发送邮件
|
|
|
|
|
mail.setSSLOnConnect(true);
|
|
|
|
|
mail.setFrom(EmailConfig.getUsername());
|
|
|
|
|
mail.setSubject("【数学学习软件】您的注册验证码");
|
|
|
|
|
mail.setMsg("您好!\n\n感谢您注册数学学习软件。您的验证码是:" + code + "\n\n请在5分钟内使用。");
|
|
|
|
|
mail.addTo(email);
|
|
|
|
|
mail.send();
|
|
|
|
|
|
|
|
|
|
System.out.println("验证码邮件已成功发送至: " + email);
|
|
|
|
|
return code;
|
|
|
|
|
|
|
|
|
|
} catch (EmailException e) {
|
|
|
|
|
System.err.println("错误:发送验证码邮件失败!请检查您的 config.properties 配置或网络连接。");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return null; // 发送失败
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* (已修正) 注册一个没有初始密码的新用户。
|
|
|
|
|
* @param username 新用户的用户名
|
|
|
|
|
* @param email 新用户的邮箱
|
|
|
|
|
* @return 注册成功返回true, 如果用户名或邮箱已存在则返回false。
|
|
|
|
|
* 注册一个新用户,该用户初始时没有密码。
|
|
|
|
|
*
|
|
|
|
|
* @param username 新用户的用户名。
|
|
|
|
|
* @param email 新用户的邮箱地址。
|
|
|
|
|
* @return 如果注册成功,返回 {@code true};如果用户名或邮箱已存在,则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public boolean register(String username, String email) {
|
|
|
|
|
if (userDatabase.containsKey(username)) {
|
|
|
|
|
return false; // 用户名已存在
|
|
|
|
|
}
|
|
|
|
|
// 检查数据库中已存在的用户的email是否与新email相同
|
|
|
|
|
// 使用 email.equals(u.email()) 可以安全地处理 u.email() 为 null 的情况
|
|
|
|
|
if (userDatabase.values().stream()
|
|
|
|
|
.anyMatch(u -> email.equals(u.email()))) {
|
|
|
|
|
if (userDatabase.values().stream().anyMatch(u -> email.equals(u.email()))) {
|
|
|
|
|
return false; // 邮箱已存在
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 核心修正在这里 ---
|
|
|
|
|
// 创建用户时,密码字段设为 null,表示该用户处于“待设置密码”状态
|
|
|
|
|
User newUser = new User(username, email, null);
|
|
|
|
|
userDatabase.put(username, newUser);
|
|
|
|
|
saveUsers();
|
|
|
|
|
@ -130,36 +147,43 @@ public class UserService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* (新增) 为指定用户设置初始密码。
|
|
|
|
|
* @param username 要设置密码的用户名
|
|
|
|
|
* @param password 要设置的新密码
|
|
|
|
|
* @return 成功设置返回 true, 如果用户不存在则返回 false
|
|
|
|
|
* 为指定用户设置其初始密码。
|
|
|
|
|
* 此方法只在用户当前密码为 {@code null} 时才允许操作。
|
|
|
|
|
*
|
|
|
|
|
* @param username 要设置密码的用户名。
|
|
|
|
|
* @param password 要设置的新密码。
|
|
|
|
|
* @return 如果密码设置成功,返回 {@code true};如果用户不存在或已有密码,则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public boolean setPassword(String username, String password) {
|
|
|
|
|
return findUserByUsername(username)
|
|
|
|
|
.map(user -> {
|
|
|
|
|
// 只有当用户当前密码为 null 时才允许设置
|
|
|
|
|
if (user.password() == null) {
|
|
|
|
|
User updatedUser = new User(user.username(), user.email(), password);
|
|
|
|
|
userDatabase.put(username, updatedUser);
|
|
|
|
|
saveUsers();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false; // 用户已经有密码,不能通过此方法设置
|
|
|
|
|
return false;
|
|
|
|
|
}).orElse(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证密码是否符合复杂度要求。
|
|
|
|
|
* @param password 待验证的密码
|
|
|
|
|
* @return true如果符合要求
|
|
|
|
|
* 验证给定的密码字符串是否符合预设的复杂度要求。
|
|
|
|
|
*
|
|
|
|
|
* @param password 待验证的密码。
|
|
|
|
|
* @return 如果密码有效,返回 {@code true};否则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public static boolean isPasswordValid(String password) {
|
|
|
|
|
return password != null && PASSWORD_PATTERN.matcher(password).matches();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 修改密码。
|
|
|
|
|
* @return 成功返回true
|
|
|
|
|
* 修改指定用户的密码。
|
|
|
|
|
*
|
|
|
|
|
* @param username 要修改密码的用户名。
|
|
|
|
|
* @param oldPassword 用户的当前密码,用于验证。
|
|
|
|
|
* @param newPassword 用户的新密码。
|
|
|
|
|
* @return 如果旧密码正确且新密码设置成功,返回 {@code true};否则返回 {@code false}。
|
|
|
|
|
*/
|
|
|
|
|
public boolean changePassword(String username, String oldPassword, String newPassword) {
|
|
|
|
|
return findUserByUsername(username)
|
|
|
|
|
|