feat: 实现邮箱验证码和邮箱绑定功能

- 添加邮件发送服务实现(EmailService/EmailServiceImpl)
- 新增发送验证码、绑定邮箱、解绑邮箱接口
- 用户实体新增邮箱相关字段(userEmail/userEmailVerified)
- 添加邮件配置和JavaMailSender Bean
- 放行邮箱验证码接口(/v1/user/email/send-code)
- 新增ContextHolderUtils工具类用于获取当前用户上下文
- 完善Swagger文档注解

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-09 23:53:19 +08:00
parent fc72acf490
commit 47a468096d
15 changed files with 485 additions and 71 deletions

View File

@@ -67,5 +67,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- Spring Security (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,87 @@
package cn.meowrain.aioj.backend.framework.core.utils;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* Security Context 工具类
* 用于获取当前登录用户信息
*/
@Slf4j
@UtilityClass
public class ContextHolderUtils {
/**
* 获取当前登录用户ID
* @return 用户ID
* @throws IllegalStateException 如果用户未登录
*/
public static Long getCurrentUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
throw new IllegalStateException("用户未登录");
}
String userId = authentication.getName();
try {
return Long.valueOf(userId);
}
catch (NumberFormatException e) {
log.error("解析用户ID失败: {}", userId, e);
throw new IllegalStateException("无效的用户ID");
}
}
/**
* 获取当前登录用户ID可选返回
* @return 用户ID未登录返回 null
*/
public static Long getCurrentUserIdOrNull() {
try {
return getCurrentUserId();
}
catch (Exception e) {
return null;
}
}
/**
* 获取当前登录用户角色
* @return 用户角色,如 "USER", "ADMIN", "BAN"
*/
public static String getCurrentUserRole() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
throw new IllegalStateException("用户未登录");
}
return authentication.getAuthorities()
.stream()
.findFirst()
.map(authority -> authority.getAuthority().replace("ROLE_", ""))
.orElse("USER");
}
/**
* 判断当前用户是否已登录
* @return true-已登录, false-未登录
*/
public static boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && authentication.isAuthenticated()
&& !"anonymousUser".equals(authentication.getName());
}
/**
* 判断当前用户是否为管理员
* @return true-是管理员, false-不是管理员
*/
public static boolean isAdmin() {
return "ADMIN".equals(getCurrentUserRole());
}
}