init
This commit is contained in:
parent
f27bd6055d
commit
17f557625b
@ -0,0 +1,17 @@
|
||||
package top.biwin.xinayu.common.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 邮箱+邮箱验证码时的图形验证请求对象
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22 12:54
|
||||
*/
|
||||
@Data
|
||||
public class LoginCaptchaRequest {
|
||||
|
||||
@JsonProperty("session_id")
|
||||
private String sessionId;
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package top.biwin.xinayu.common.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@ -29,5 +30,6 @@ public class LoginRequest {
|
||||
/**
|
||||
* 邮箱验证码(用于邮箱验证码登录)
|
||||
*/
|
||||
@JsonProperty("verification_code")
|
||||
private String verificationCode;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package top.biwin.xinayu.common.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@ -15,4 +16,12 @@ public class SendCodeRequest {
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 图形验证码ID
|
||||
*/
|
||||
@JsonProperty("session_id")
|
||||
private String sessionId;
|
||||
|
||||
private String type;
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package top.biwin.xinayu.common.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22 13:00
|
||||
*/
|
||||
@Data
|
||||
public class VerifyLoginCaptchaRequest {
|
||||
@JsonProperty("session_id")
|
||||
private String sessionId;
|
||||
@JsonProperty("captcha_code")
|
||||
private String captchaCode;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package top.biwin.xinayu.common.dto.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22 13:01
|
||||
*/
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BaseResponse {
|
||||
private String message;
|
||||
private Boolean success;
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package top.biwin.xinayu.common.dto.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 图形验证码响应 DTO
|
||||
* 返回给客户端的验证码信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CaptchaResponse {
|
||||
/**
|
||||
* 验证码唯一标识(UUID)
|
||||
*/
|
||||
private String captchaId;
|
||||
|
||||
/**
|
||||
* 验证码图片(Base64编码)
|
||||
* 格式:data:image/png;base64,iVBORw0KGgo...
|
||||
*/
|
||||
@JsonProperty("captcha_image")
|
||||
private String captchaImage;
|
||||
|
||||
/**
|
||||
* 验证码有效期(秒)
|
||||
*/
|
||||
private Integer expiresIn;
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package top.biwin.xinayu.common.dto.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22 21:52
|
||||
*/
|
||||
@Data
|
||||
public class CookieDetailsResponse {
|
||||
private Long id;
|
||||
private String cookie;
|
||||
private Boolean enabled;
|
||||
@JsonProperty("auto_confirm")
|
||||
private Integer autoConfirm;
|
||||
private String remark;
|
||||
@JsonProperty("pause_duration")
|
||||
private Integer pauseDuration;
|
||||
private String username;
|
||||
@JsonProperty("login_password")
|
||||
private String loginPassword;
|
||||
@JsonProperty("show_browser")
|
||||
private Boolean showBrowser;
|
||||
}
|
||||
@ -2,6 +2,7 @@ package top.biwin.xinayu.common.dto.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ -13,55 +14,63 @@ import lombok.NoArgsConstructor;
|
||||
* @since 2026-01-21
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoginResponse {
|
||||
/**
|
||||
* 访问令牌(短期有效,用于访问受保护资源)
|
||||
*/
|
||||
private String accessToken;
|
||||
/**
|
||||
* 访问令牌(短期有效,用于访问受保护资源)
|
||||
*/
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* 刷新令牌(长期有效,用于获取新的访问令牌)
|
||||
*/
|
||||
private String refreshToken;
|
||||
/**
|
||||
* 刷新令牌(长期有效,用于获取新的访问令牌)
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* 访问令牌过期时间(秒)
|
||||
*/
|
||||
private Long expiresIn;
|
||||
/**
|
||||
* 访问令牌过期时间(秒)
|
||||
*/
|
||||
private Long expiresIn;
|
||||
|
||||
/**
|
||||
* 令牌类型(固定为 "Bearer")
|
||||
*/
|
||||
private String tokenType = "Bearer";
|
||||
/**
|
||||
* 令牌类型(固定为 "Bearer")
|
||||
*/
|
||||
private String tokenType = "Bearer";
|
||||
|
||||
@JsonProperty("is_admin")
|
||||
private Boolean isAdmin;
|
||||
@JsonProperty("is_admin")
|
||||
private Boolean isAdmin;
|
||||
|
||||
@JsonProperty("user_id")
|
||||
private Long userId;
|
||||
@JsonProperty("user_id")
|
||||
private Long userId;
|
||||
|
||||
private String username;
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String email;
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 账号是否激活
|
||||
*/
|
||||
@JsonProperty("is_active")
|
||||
private Boolean isActive;
|
||||
/**
|
||||
* 账号是否激活
|
||||
*/
|
||||
@JsonProperty("is_active")
|
||||
private Boolean isActive;
|
||||
|
||||
/**
|
||||
* 是否登陆成功
|
||||
*/
|
||||
private Boolean success;
|
||||
/**
|
||||
* 用户角色
|
||||
* SUPER_ADMIN: 超级管理员
|
||||
* ADMIN: 普通管理员
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String message;
|
||||
/**
|
||||
* 是否登陆成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String message;
|
||||
}
|
||||
|
||||
@ -2,7 +2,9 @@ package top.biwin.xinayu.common.dto.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
/**
|
||||
* 发送验证码响应 DTO
|
||||
@ -11,22 +13,15 @@ import lombok.NoArgsConstructor;
|
||||
* @author wangli
|
||||
* @since 2026-01-21
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SendCodeResponse {
|
||||
/**
|
||||
* 是否发送成功
|
||||
*/
|
||||
private Boolean success;
|
||||
public class SendCodeResponse extends BaseResponse {
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 验证码过期时间(秒)
|
||||
*/
|
||||
private Integer expiresIn;
|
||||
/**
|
||||
* 验证码过期时间(秒)
|
||||
*/
|
||||
private Integer expiresIn;
|
||||
}
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
package top.biwin.xinayu.common.enums;
|
||||
|
||||
/**
|
||||
* 用户角色枚举
|
||||
* 定义系统中的用户角色类型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
public enum UserRole {
|
||||
|
||||
/**
|
||||
* 超级管理员
|
||||
* 拥有系统所有权限,可以管理所有用户和系统配置
|
||||
*/
|
||||
SUPER_ADMIN("超级管理员", "ROLE_SUPER_ADMIN"),
|
||||
|
||||
/**
|
||||
* 普通管理员
|
||||
* 拥有基本的管理权限,但受到一定限制
|
||||
*/
|
||||
ADMIN("普通管理员", "ROLE_ADMIN");
|
||||
|
||||
/**
|
||||
* 角色显示名称
|
||||
*/
|
||||
private final String displayName;
|
||||
|
||||
/**
|
||||
* Spring Security 角色标识
|
||||
* 遵循 Spring Security 规范,以 "ROLE_" 开头
|
||||
*/
|
||||
private final String authority;
|
||||
|
||||
UserRole(String displayName, String authority) {
|
||||
this.displayName = displayName;
|
||||
this.authority = authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色显示名称
|
||||
*
|
||||
* @return 显示名称
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Spring Security 权限标识
|
||||
*
|
||||
* @return 权限标识(如 "ROLE_SUPER_ADMIN")
|
||||
*/
|
||||
public String getAuthority() {
|
||||
return authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为超级管理员
|
||||
*
|
||||
* @return true 如果是超级管理员
|
||||
*/
|
||||
public boolean isSuperAdmin() {
|
||||
return this == SUPER_ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符串转换为角色枚举
|
||||
*
|
||||
* @param role 角色字符串
|
||||
* @return UserRole 枚举值
|
||||
*/
|
||||
public static UserRole fromString(String role) {
|
||||
if (role == null) {
|
||||
return ADMIN; // 默认为普通管理员
|
||||
}
|
||||
try {
|
||||
return UserRole.valueOf(role.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ADMIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,41 +1,49 @@
|
||||
package top.biwin.xianyu.core.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
import top.biwin.xinayu.common.enums.UserRole;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 管理员用户实体
|
||||
* 存储系统管理员的基本信息和角色权限
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-21 00:00
|
||||
* @since 2026-01-21
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "admin_user")
|
||||
public class AdminUser {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
@Column(nullable = false, unique = true, length = 100)
|
||||
private String email;
|
||||
|
||||
@Column(name = "password_hash", nullable = false)
|
||||
@Column(name = "password_hash", nullable = false, length = 255)
|
||||
private String passwordHash;
|
||||
|
||||
/**
|
||||
* 用户角色
|
||||
* SUPER_ADMIN: 超级管理员,拥有所有权限
|
||||
* ADMIN: 普通管理员,基本权限
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "role", nullable = false, length = 20)
|
||||
@ColumnDefault("'ADMIN'")
|
||||
private UserRole role = UserRole.ADMIN;
|
||||
|
||||
@Column(name = "is_active")
|
||||
@ColumnDefault("true")
|
||||
private Boolean isActive = true;
|
||||
@ -47,4 +55,13 @@ public class AdminUser {
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 检查当前用户是否为超级管理员
|
||||
*
|
||||
* @return true 如果是超级管理员
|
||||
*/
|
||||
public boolean isSuperAdmin() {
|
||||
return role != null && role.isSuperAdmin();
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,50 +22,49 @@ import java.time.LocalDateTime;
|
||||
@Entity
|
||||
@Table(name = "goofish_account")
|
||||
public class GoofishAccount {
|
||||
@Id
|
||||
private Long id;
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, length = 10000) // cookies can be long
|
||||
private String cookie;
|
||||
|
||||
@Column(nullable = false, length = 10000) // cookies can be long
|
||||
private String value;
|
||||
@Column(name = "user_id", nullable = false)
|
||||
@JsonProperty("user_id")
|
||||
private Long userId;
|
||||
|
||||
@Column(name = "user_id", nullable = false)
|
||||
@JsonProperty("user_id")
|
||||
private Long userId;
|
||||
@Column(name = "auto_confirm")
|
||||
@ColumnDefault("1")
|
||||
@JsonProperty("auto_confirm")
|
||||
private Integer autoConfirm = 1;
|
||||
|
||||
@Column(name = "auto_confirm")
|
||||
@ColumnDefault("1")
|
||||
@JsonProperty("auto_confirm")
|
||||
private Integer autoConfirm = 1;
|
||||
@ColumnDefault("''")
|
||||
private String remark;
|
||||
|
||||
@ColumnDefault("''")
|
||||
private String remark;
|
||||
@Column(name = "pause_duration")
|
||||
@ColumnDefault("10")
|
||||
@JsonProperty("pause_duration")
|
||||
private Integer pauseDuration = 10;
|
||||
|
||||
@Column(name = "pause_duration")
|
||||
@ColumnDefault("10")
|
||||
@JsonProperty("pause_duration")
|
||||
private Integer pauseDuration = 10;
|
||||
@ColumnDefault("''")
|
||||
private String username;
|
||||
|
||||
@ColumnDefault("''")
|
||||
private String username;
|
||||
@ColumnDefault("''")
|
||||
private String password;
|
||||
|
||||
@ColumnDefault("''")
|
||||
private String password;
|
||||
@Column(name = "show_browser")
|
||||
@ColumnDefault("0")
|
||||
@JsonProperty("show_browser")
|
||||
private Integer showBrowser = 0;
|
||||
|
||||
@Column(name = "show_browser")
|
||||
@ColumnDefault("0")
|
||||
@JsonProperty("show_browser")
|
||||
private Integer showBrowser = 0;
|
||||
@Column(name = "enabled")
|
||||
@ColumnDefault("true")
|
||||
private Boolean enabled = true;
|
||||
|
||||
@Column(name = "enabled")
|
||||
@ColumnDefault("true")
|
||||
private Boolean enabled = true;
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
package top.biwin.xianyu.core.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import top.biwin.xianyu.core.entity.GoofishAccount;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface GoofishAccountRepository extends JpaRepository<GoofishAccount, Long> {
|
||||
|
||||
Optional<GoofishAccount> findByUserId(Long UserId);
|
||||
}
|
||||
@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
@ -32,12 +33,14 @@ import top.biwin.xinayu.server.security.JwtAuthenticationFilter;
|
||||
* - 禁用 CSRF(REST API 场景)
|
||||
* - 白名单:/auth/** 接口
|
||||
* - 其他所有接口需要认证
|
||||
* - 启用方法级安全(支持 @PreAuthorize 等注解)
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-21
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig {
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@ -4,18 +4,16 @@ import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.biwin.xinayu.common.dto.request.LoginRequest;
|
||||
import top.biwin.xinayu.common.dto.request.RefreshRequest;
|
||||
import top.biwin.xinayu.common.dto.request.SendCodeRequest;
|
||||
import top.biwin.xinayu.common.dto.response.LoginResponse;
|
||||
import top.biwin.xinayu.common.dto.response.RefreshResponse;
|
||||
import top.biwin.xinayu.common.dto.response.SendCodeResponse;
|
||||
import top.biwin.xinayu.server.security.JwtUtil;
|
||||
import top.biwin.xinayu.server.service.AuthService;
|
||||
import top.biwin.xinayu.server.service.EmailVerificationService;
|
||||
import top.biwin.xianyu.core.entity.AdminUser;
|
||||
import top.biwin.xianyu.core.repository.AdminUserRepository;
|
||||
import top.biwin.xinayu.common.dto.request.*;
|
||||
import top.biwin.xinayu.common.dto.response.*;
|
||||
import top.biwin.xinayu.server.security.JwtUtil;
|
||||
import top.biwin.xinayu.server.service.AuthService;
|
||||
import top.biwin.xinayu.server.service.CaptchaService;
|
||||
import top.biwin.xinayu.server.service.EmailVerificationService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -24,11 +22,11 @@ import java.util.Optional;
|
||||
/**
|
||||
* 认证控制器
|
||||
* 提供登录、刷新令牌等认证相关的 REST API
|
||||
*
|
||||
* <p>
|
||||
* 接口列表:
|
||||
* - POST /auth/login - 用户登录
|
||||
* - POST /auth/refresh - 刷新访问令牌
|
||||
*
|
||||
* <p>
|
||||
* 注意:这些接口在 SecurityConfig 中已配置为白名单,无需认证即可访问
|
||||
*
|
||||
* @author wangli
|
||||
@ -38,200 +36,231 @@ import java.util.Optional;
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
@Autowired
|
||||
private EmailVerificationService emailVerificationService;
|
||||
@Autowired
|
||||
private EmailVerificationService emailVerificationService;
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private AdminUserRepository adminUserRepository;
|
||||
@Autowired
|
||||
private AdminUserRepository adminUserRepository;
|
||||
|
||||
/**
|
||||
* 用户登录接口
|
||||
*
|
||||
* 请求示例:
|
||||
* POST /auth/login
|
||||
* {
|
||||
* "username": "admin",
|
||||
* "password": "password123"
|
||||
* }
|
||||
*
|
||||
* 响应示例:
|
||||
* {
|
||||
* "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
* "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
* "expiresIn": 900,
|
||||
* "tokenType": "Bearer"
|
||||
* }
|
||||
*
|
||||
* 错误响应:
|
||||
* - 401 Unauthorized: 用户名或密码错误
|
||||
* - 500 Internal Server Error: 服务器内部错误
|
||||
*
|
||||
* @param request 登录请求(包含 username 和 password)
|
||||
* @return ResponseEntity<LoginResponse> 登录响应
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||
LoginResponse response = authService.login(request);
|
||||
return ResponseEntity.ok(response);
|
||||
@Autowired
|
||||
private CaptchaService captchaService;
|
||||
|
||||
/**
|
||||
* 用户登录接口
|
||||
* <p>
|
||||
* 请求示例:
|
||||
* POST /auth/login
|
||||
* {
|
||||
* "username": "admin",
|
||||
* "password": "password123"
|
||||
* }
|
||||
* <p>
|
||||
* 响应示例:
|
||||
* {
|
||||
* "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
* "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
* "expiresIn": 900,
|
||||
* "tokenType": "Bearer"
|
||||
* }
|
||||
* <p>
|
||||
* 错误响应:
|
||||
* - 401 Unauthorized: 用户名或密码错误
|
||||
* - 500 Internal Server Error: 服务器内部错误
|
||||
*
|
||||
* @param request 登录请求(包含 username 和 password)
|
||||
* @return ResponseEntity<LoginResponse> 登录响应
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||
LoginResponse response = authService.login(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新访问令牌接口
|
||||
* <p>
|
||||
* 请求示例:
|
||||
* POST /auth/refresh
|
||||
* {
|
||||
* "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
* }
|
||||
* <p>
|
||||
* 响应示例:
|
||||
* {
|
||||
* "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
* "expiresIn": 900,
|
||||
* "tokenType": "Bearer"
|
||||
* }
|
||||
* <p>
|
||||
* 错误响应:
|
||||
* - 401 Unauthorized: 刷新令牌无效或已过期
|
||||
* - 500 Internal Server Error: 服务器内部错误
|
||||
*
|
||||
* @param request 刷新令牌请求(包含 refreshToken)
|
||||
* @return ResponseEntity<RefreshResponse> 刷新令牌响应
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<RefreshResponse> refresh(@RequestBody RefreshRequest request) {
|
||||
RefreshResponse response = authService.refresh(request.getRefreshToken());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码接口
|
||||
* <p>
|
||||
*
|
||||
* @param request 发送验证码请求(包含 email、captchaId、captchaCode)
|
||||
* @return ResponseEntity<SendCodeResponse> 发送结果响应
|
||||
*/
|
||||
@PostMapping("/send-verification-code")
|
||||
public ResponseEntity<SendCodeResponse> sendVerificationCode(@RequestBody SendCodeRequest request) {
|
||||
Optional<AdminUser> optUser = adminUserRepository.findByEmail(request.getEmail());
|
||||
|
||||
SendCodeResponse response = SendCodeResponse.builder().build();
|
||||
if (optUser.isEmpty()) {
|
||||
response.setSuccess(false);
|
||||
response.setMessage("该邮箱未注册账户");
|
||||
} else {
|
||||
// 验证通过,发送邮箱验证码
|
||||
emailVerificationService.generateAndSendCode(request.getEmail());
|
||||
response.setSuccess(true);
|
||||
response.setMessage("验证码已发送到您的邮箱,请注意查收");
|
||||
response.setExpiresIn(300);
|
||||
}
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成图形验证码接口
|
||||
* <p>
|
||||
* 生成4位字母数字混合验证码,5分钟有效
|
||||
* <p>
|
||||
* 请求示例:
|
||||
* GET /auth/generate-captcha
|
||||
* <p>
|
||||
* 响应示例:
|
||||
* {
|
||||
* "captchaId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||||
* "captchaImage": "data:image/png;base64,iVBORw0KGgo...",
|
||||
* "expiresIn": 300
|
||||
* }
|
||||
*
|
||||
* @return ResponseEntity<CaptchaResponse> 验证码响应
|
||||
*/
|
||||
@PostMapping("/generate-captcha")
|
||||
public ResponseEntity<CaptchaResponse> generateCaptcha(@RequestBody LoginCaptchaRequest request) {
|
||||
CaptchaResponse response = captchaService.generateCaptcha(request.getSessionId());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证图形验证码
|
||||
* 对应 Python: /verify-captcha
|
||||
*/
|
||||
@PostMapping("/verify-captcha")
|
||||
public ResponseEntity<BaseResponse> verifyCaptcha(@RequestBody VerifyLoginCaptchaRequest request) {
|
||||
|
||||
// 先验证图形验证码
|
||||
if (!captchaService.verifyCaptcha(request.getSessionId(), request.getCaptchaCode())) {
|
||||
throw new BadCredentialsException("图形验证码错误或已过期");
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新访问令牌接口
|
||||
*
|
||||
* 请求示例:
|
||||
* POST /auth/refresh
|
||||
* {
|
||||
* "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
* }
|
||||
*
|
||||
* 响应示例:
|
||||
* {
|
||||
* "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
* "expiresIn": 900,
|
||||
* "tokenType": "Bearer"
|
||||
* }
|
||||
*
|
||||
* 错误响应:
|
||||
* - 401 Unauthorized: 刷新令牌无效或已过期
|
||||
* - 500 Internal Server Error: 服务器内部错误
|
||||
*
|
||||
* @param request 刷新令牌请求(包含 refreshToken)
|
||||
* @return ResponseEntity<RefreshResponse> 刷新令牌响应
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<RefreshResponse> refresh(@RequestBody RefreshRequest request) {
|
||||
RefreshResponse response = authService.refresh(request.getRefreshToken());
|
||||
return ResponseEntity.ok(response);
|
||||
return ResponseEntity.ok(new BaseResponse("图形验证码验证成功", true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Token 接口
|
||||
* 检查 Access Token 是否有效,并返回用户信息
|
||||
* <p>
|
||||
* 请求示例:
|
||||
* GET /auth/verify
|
||||
* Headers: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
* <p>
|
||||
* 响应示例(Token 有效):
|
||||
* {
|
||||
* "authenticated": true,
|
||||
* "user_id": 1,
|
||||
* "username": "admin",
|
||||
* "email": "admin@example.com",
|
||||
* "is_admin": true
|
||||
* }
|
||||
* <p>
|
||||
* 响应示例(Token 无效):
|
||||
* {
|
||||
* "authenticated": false
|
||||
* }
|
||||
*
|
||||
* @param request HTTP 请求对象
|
||||
* @return 验证结果和用户信息
|
||||
*/
|
||||
@GetMapping("/verify")
|
||||
public ResponseEntity<Map<String, Object>> verify(HttpServletRequest request) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
// 从请求头中提取 Token
|
||||
String token = getTokenFromRequest(request);
|
||||
|
||||
if (token == null) {
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "缺少 Authorization 头");
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码接口
|
||||
*
|
||||
* 请求示例:
|
||||
* POST /auth/send-code
|
||||
* {
|
||||
* "email": "user@example.com"
|
||||
* }
|
||||
*
|
||||
* 响应示例:
|
||||
* {
|
||||
* "success": true,
|
||||
* "message": "验证码已发送到您的邮箱",
|
||||
* "expiresIn": 300
|
||||
* }
|
||||
*
|
||||
* 错误响应:
|
||||
* - 400 Bad Request: 邮箱格式错误
|
||||
* - 429 Too Many Requests: 发送过于频繁
|
||||
* - 500 Internal Server Error: 邮件发送失败
|
||||
*
|
||||
* @param request 发送验证码请求(包含 email)
|
||||
* @return ResponseEntity<SendCodeResponse> 发送结果响应
|
||||
*/
|
||||
@PostMapping("/send-code")
|
||||
public ResponseEntity<SendCodeResponse> sendCode(@RequestBody SendCodeRequest request) {
|
||||
emailVerificationService.generateAndSendCode(request.getEmail());
|
||||
SendCodeResponse response = new SendCodeResponse(
|
||||
true,
|
||||
"验证码已发送到您的邮箱,请注意查收",
|
||||
300
|
||||
);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
try {
|
||||
// 使用 JwtUtil 验证 Token 是否有效(检查签名和过期时间)
|
||||
if (jwtUtil.validateToken(token)) {
|
||||
// Token 有效,提取用户名
|
||||
String username = jwtUtil.getUsernameFromToken(token);
|
||||
|
||||
/**
|
||||
* 验证 Token 接口
|
||||
* 检查 Access Token 是否有效,并返回用户信息
|
||||
*
|
||||
* 请求示例:
|
||||
* GET /auth/verify
|
||||
* Headers: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
*
|
||||
* 响应示例(Token 有效):
|
||||
* {
|
||||
* "authenticated": true,
|
||||
* "user_id": 1,
|
||||
* "username": "admin",
|
||||
* "email": "admin@example.com",
|
||||
* "is_admin": true
|
||||
* }
|
||||
*
|
||||
* 响应示例(Token 无效):
|
||||
* {
|
||||
* "authenticated": false
|
||||
* }
|
||||
*
|
||||
* @param request HTTP 请求对象
|
||||
* @return 验证结果和用户信息
|
||||
*/
|
||||
@GetMapping("/verify")
|
||||
public ResponseEntity<Map<String, Object>> verify(HttpServletRequest request) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
// 从请求头中提取 Token
|
||||
String token = getTokenFromRequest(request);
|
||||
|
||||
if (token == null) {
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "缺少 Authorization 头");
|
||||
return ResponseEntity.ok(response);
|
||||
// 从数据库查询用户信息
|
||||
Optional<AdminUser> userOpt = adminUserRepository.findByUsername(username);
|
||||
|
||||
if (userOpt.isPresent()) {
|
||||
AdminUser user = userOpt.get();
|
||||
|
||||
// 返回用户信息
|
||||
response.put("authenticated", true);
|
||||
response.put("user_id", user.getId());
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("is_admin", true); // admin_user 表的用户都是管理员
|
||||
response.put("is_active", user.getIsActive());
|
||||
} else {
|
||||
// Token 有效但用户不存在(可能已被删除)
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "用户不存在");
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用 JwtUtil 验证 Token 是否有效(检查签名和过期时间)
|
||||
if (jwtUtil.validateToken(token)) {
|
||||
// Token 有效,提取用户名
|
||||
String username = jwtUtil.getUsernameFromToken(token);
|
||||
|
||||
// 从数据库查询用户信息
|
||||
Optional<AdminUser> userOpt = adminUserRepository.findByUsername(username);
|
||||
|
||||
if (userOpt.isPresent()) {
|
||||
AdminUser user = userOpt.get();
|
||||
|
||||
// 返回用户信息
|
||||
response.put("authenticated", true);
|
||||
response.put("user_id", user.getId());
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("is_admin", true); // admin_user 表的用户都是管理员
|
||||
response.put("is_active", user.getIsActive());
|
||||
} else {
|
||||
// Token 有效但用户不存在(可能已被删除)
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "用户不存在");
|
||||
}
|
||||
} else {
|
||||
// Token 无效或已过期
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "Token 无效或已过期");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Token 解析失败
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "Token 格式错误");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
} else {
|
||||
// Token 无效或已过期
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "Token 无效或已过期");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Token 解析失败
|
||||
response.put("authenticated", false);
|
||||
response.put("message", "Token 格式错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中提取 Token
|
||||
*
|
||||
* @param request HTTP 请求对象
|
||||
* @return Token 字符串,如果不存在则返回 null
|
||||
*/
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String bearer = request.getHeader("Authorization");
|
||||
if (StrUtil.isNotBlank(bearer) && bearer.startsWith("Bearer ")) {
|
||||
return bearer.substring(7);
|
||||
}
|
||||
return null;
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中提取 Token
|
||||
*
|
||||
* @param request HTTP 请求对象
|
||||
* @return Token 字符串,如果不存在则返回 null
|
||||
*/
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String bearer = request.getHeader("Authorization");
|
||||
if (StrUtil.isNotBlank(bearer) && bearer.startsWith("Bearer ")) {
|
||||
return bearer.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
package top.biwin.xinayu.server.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.biwin.xianyu.core.entity.GoofishAccount;
|
||||
import top.biwin.xianyu.core.repository.GoofishAccountRepository;
|
||||
import top.biwin.xinayu.common.dto.response.CookieDetailsResponse;
|
||||
import top.biwin.xinayu.server.util.CurrentUserUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22 22:34
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/goofish")
|
||||
public class GoofishController {
|
||||
@Autowired
|
||||
private GoofishAccountRepository goofishAccountRepository;
|
||||
|
||||
/**
|
||||
* 获取所有Cookie的详细信息(包括值和状态)
|
||||
* 对应Python的 get_cookies_details 接口
|
||||
*/
|
||||
@GetMapping("/details")
|
||||
public ResponseEntity<List<CookieDetailsResponse>> getAllCookiesDetails() {
|
||||
List<GoofishAccount> goofishAccounts = new ArrayList<>();
|
||||
if (CurrentUserUtil.isSuperAdmin()) {
|
||||
goofishAccounts.addAll(goofishAccountRepository.findAll());
|
||||
} else {
|
||||
goofishAccountRepository.findByUserId(CurrentUserUtil.getCurrentUserId())
|
||||
.ifPresent(goofishAccounts::add);
|
||||
}
|
||||
|
||||
// 构建详细信息响应
|
||||
return ResponseEntity.ok(goofishAccounts.stream().map(account -> {
|
||||
CookieDetailsResponse response = new CookieDetailsResponse();
|
||||
response.setId(account.getId());
|
||||
response.setCookie(account.getCookie());
|
||||
response.setEnabled(account.getEnabled());
|
||||
response.setAutoConfirm(account.getAutoConfirm());
|
||||
response.setRemark(account.getRemark() != null ? account.getRemark() : "");
|
||||
response.setPauseDuration(account.getPauseDuration() != null ? account.getPauseDuration() : 10);
|
||||
response.setUsername(account.getUsername());
|
||||
response.setLoginPassword(account.getPassword());
|
||||
response.setShowBrowser(account.getShowBrowser() == 1);
|
||||
return response;
|
||||
}).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
package top.biwin.xinayu.server.controller.demo;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.biwin.xianyu.core.entity.AdminUser;
|
||||
import top.biwin.xinayu.common.enums.UserRole;
|
||||
import top.biwin.xinayu.server.util.CurrentUserUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CurrentUserUtil 使用示例控制器
|
||||
* 演示如何使用 CurrentUserUtil 的各种方法
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/current-user")
|
||||
public class CurrentUserDemoController {
|
||||
|
||||
/**
|
||||
* 示例1:获取完整的 AdminUser 对象
|
||||
*/
|
||||
@GetMapping("/full-info")
|
||||
public ResponseEntity<Map<String, Object>> getFullUserInfo() {
|
||||
AdminUser user = CurrentUserUtil.getCurrentUser();
|
||||
|
||||
if (user == null) {
|
||||
return ResponseEntity.ok(Map.of("message", "未登录"));
|
||||
}
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("id", user.getId());
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("role", user.getRole().name());
|
||||
response.put("roleDisplay", user.getRole().getDisplayName());
|
||||
response.put("isActive", user.getIsActive());
|
||||
response.put("isSuperAdmin", user.isSuperAdmin());
|
||||
response.put("createdAt", user.getCreatedAt());
|
||||
response.put("updatedAt", user.getUpdatedAt());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2:使用便捷方法获取用户信息
|
||||
*/
|
||||
@GetMapping("/quick-info")
|
||||
public ResponseEntity<Map<String, Object>> getQuickInfo() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
// 使用便捷方法
|
||||
response.put("userId", CurrentUserUtil.getCurrentUserId());
|
||||
response.put("username", CurrentUserUtil.getCurrentUsername());
|
||||
response.put("email", CurrentUserUtil.getCurrentUserEmail());
|
||||
response.put("role", CurrentUserUtil.getCurrentUserRole());
|
||||
response.put("isSuperAdmin", CurrentUserUtil.isSuperAdmin());
|
||||
response.put("isAuthenticated", CurrentUserUtil.isAuthenticated());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3:基于用户信息的业务逻辑
|
||||
*/
|
||||
@GetMapping("/personalized-greeting")
|
||||
public ResponseEntity<Map<String, Object>> personalizedGreeting() {
|
||||
AdminUser user = CurrentUserUtil.getCurrentUser();
|
||||
|
||||
if (user == null) {
|
||||
return ResponseEntity.ok(Map.of("greeting", "欢迎访客!"));
|
||||
}
|
||||
|
||||
String greeting;
|
||||
UserRole role = user.getRole();
|
||||
|
||||
if (role == UserRole.SUPER_ADMIN) {
|
||||
greeting = String.format("尊敬的超级管理员 %s,欢迎回来!您拥有系统最高权限。",
|
||||
user.getUsername());
|
||||
} else {
|
||||
greeting = String.format("您好,%s!欢迎登录后台管理系统。",
|
||||
user.getUsername());
|
||||
}
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("greeting", greeting);
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("accountAge", java.time.Duration.between(
|
||||
user.getCreatedAt(),
|
||||
java.time.LocalDateTime.now()
|
||||
).toDays() + " 天");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4:对比所有获取用户信息的方法
|
||||
*/
|
||||
@GetMapping("/method-comparison")
|
||||
public ResponseEntity<Map<String, Object>> methodComparison() {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
// 方法1:获取完整对象
|
||||
AdminUser user = CurrentUserUtil.getCurrentUser();
|
||||
if (user != null) {
|
||||
response.put("method1_fullObject", Map.of(
|
||||
"description", "通过 getCurrentUser() 获取完整对象",
|
||||
"result", Map.of(
|
||||
"id", user.getId(),
|
||||
"username", user.getUsername(),
|
||||
"email", user.getEmail(),
|
||||
"role", user.getRole().name()
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
// 方法2:使用便捷方法
|
||||
response.put("method2_quickAccess", Map.of(
|
||||
"description", "使用便捷方法快速访问",
|
||||
"result", Map.of(
|
||||
"userId", CurrentUserUtil.getCurrentUserId(),
|
||||
"username", CurrentUserUtil.getCurrentUsername(),
|
||||
"email", CurrentUserUtil.getCurrentUserEmail(),
|
||||
"role", CurrentUserUtil.getCurrentUserRole()
|
||||
)
|
||||
));
|
||||
|
||||
// 方法3:角色判断
|
||||
response.put("method3_roleCheck", Map.of(
|
||||
"description", "使用角色判断方法",
|
||||
"result", Map.of(
|
||||
"isSuperAdmin", CurrentUserUtil.isSuperAdmin(),
|
||||
"hasAdminRole", CurrentUserUtil.hasRole("ROLE_ADMIN"),
|
||||
"hasSuperAdminRole", CurrentUserUtil.hasRole("ROLE_SUPER_ADMIN")
|
||||
)
|
||||
));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
package top.biwin.xinayu.server.controller.demo;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.biwin.xinayu.server.security.RequireSuperAdmin;
|
||||
import top.biwin.xinayu.server.util.CurrentUserUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 角色权限演示控制器
|
||||
* 展示如何使用角色权限控制接口访问
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/demo")
|
||||
public class RolePermissionDemoController {
|
||||
|
||||
/**
|
||||
* 示例1:所有管理员都可以访问
|
||||
* 任何已登录的用户(ADMIN 或 SUPER_ADMIN)都可以访问
|
||||
*/
|
||||
@GetMapping("/admin-only")
|
||||
public ResponseEntity<Map<String, Object>> adminOnly() {
|
||||
String username = CurrentUserUtil.getCurrentUsername();
|
||||
boolean isSuperAdmin = CurrentUserUtil.isSuperAdmin();
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("message", "欢迎,管理员!");
|
||||
response.put("username", username);
|
||||
response.put("isSuperAdmin", isSuperAdmin);
|
||||
response.put("accessLevel", "ADMIN");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2:只有超级管理员可以访问(使用自定义注解)
|
||||
* 使用 @RequireSuperAdmin 注解限制只有超级管理员能访问
|
||||
*/
|
||||
@RequireSuperAdmin
|
||||
@GetMapping("/super-admin-only")
|
||||
public ResponseEntity<Map<String, Object>> superAdminOnly() {
|
||||
String username = CurrentUserUtil.getCurrentUsername();
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("message", "欢迎,超级管理员!您拥有最高权限。");
|
||||
response.put("username", username);
|
||||
response.put("accessLevel", "SUPER_ADMIN");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3:只有超级管理员可以访问(使用 @PreAuthorize 注解)
|
||||
* 直接使用 Spring Security 的 @PreAuthorize 注解
|
||||
*/
|
||||
@PreAuthorize("hasRole('SUPER_ADMIN')")
|
||||
@GetMapping("/super-admin-settings")
|
||||
public ResponseEntity<Map<String, Object>> superAdminSettings() {
|
||||
String username = CurrentUserUtil.getCurrentUsername();
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("message", "系统高级设置");
|
||||
response.put("username", username);
|
||||
response.put("settings", Map.of(
|
||||
"maxUsers", 1000,
|
||||
"enableDebugMode", true,
|
||||
"systemVersion", "1.0.0"
|
||||
));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4:检查当前用户角色
|
||||
* 返回当前用户的详细角色信息
|
||||
*/
|
||||
@GetMapping("/check-role")
|
||||
public ResponseEntity<Map<String, Object>> checkRole() {
|
||||
String username = CurrentUserUtil.getCurrentUsername();
|
||||
boolean isSuperAdmin = CurrentUserUtil.isSuperAdmin();
|
||||
boolean hasAdminRole = CurrentUserUtil.hasRole("ROLE_ADMIN");
|
||||
boolean hasSuperAdminRole = CurrentUserUtil.hasRole("ROLE_SUPER_ADMIN");
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("username", username);
|
||||
response.put("isSuperAdmin", isSuperAdmin);
|
||||
response.put("hasAdminRole", hasAdminRole);
|
||||
response.put("hasSuperAdminRole", hasSuperAdminRole);
|
||||
response.put("hasAnyAdminRole", CurrentUserUtil.hasAnyRole("ROLE_ADMIN", "ROLE_SUPER_ADMIN"));
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5:根据角色返回不同内容
|
||||
* 超级管理员可以看到敏感信息,普通管理员只能看到基本信息
|
||||
*/
|
||||
@GetMapping("/dashboard")
|
||||
public ResponseEntity<Map<String, Object>> dashboard() {
|
||||
String username = CurrentUserUtil.getCurrentUsername();
|
||||
boolean isSuperAdmin = CurrentUserUtil.isSuperAdmin();
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("username", username);
|
||||
response.put("role", isSuperAdmin ? "SUPER_ADMIN" : "ADMIN");
|
||||
|
||||
// 基本统计(所有管理员可见)
|
||||
Map<String, Object> basicStats = new HashMap<>();
|
||||
basicStats.put("totalUsers", 150);
|
||||
basicStats.put("activeUsers", 120);
|
||||
response.put("basicStats", basicStats);
|
||||
|
||||
// 敏感数据(只有超级管理员可见)
|
||||
if (isSuperAdmin) {
|
||||
Map<String, Object> sensitiveData = new HashMap<>();
|
||||
sensitiveData.put("totalRevenue", 1250000);
|
||||
sensitiveData.put("systemErrors", 5);
|
||||
sensitiveData.put("securityAlerts", 2);
|
||||
response.put("sensitiveData", sensitiveData);
|
||||
response.put("message", "您拥有完整的系统访问权限");
|
||||
} else {
|
||||
response.put("message", "基础仪表板数据");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,167 @@
|
||||
package top.biwin.xinayu.server.controller.demo;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.biwin.xianyu.core.entity.AdminUser;
|
||||
import top.biwin.xianyu.core.repository.AdminUserRepository;
|
||||
import top.biwin.xinayu.server.util.CurrentUserUtil;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户信息控制器示例
|
||||
* 演示如何在 Controller 中获取当前登录用户信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserInfoController {
|
||||
|
||||
@Autowired
|
||||
private AdminUserRepository adminUserRepository;
|
||||
|
||||
/**
|
||||
* 方式1:使用 CurrentUserUtil 工具类(推荐)
|
||||
* <p>
|
||||
* 优点:简单直接,代码简洁
|
||||
* 适用场景:Service 层、Controller 层通用
|
||||
*/
|
||||
@GetMapping("/current/method1")
|
||||
public ResponseEntity<Map<String, Object>> getCurrentUserMethod1() {
|
||||
// 获取当前登录用户名
|
||||
String username = CurrentUserUtil.getCurrentUsername();
|
||||
|
||||
if (username == null) {
|
||||
return ResponseEntity.ok(Map.of("message", "未登录"));
|
||||
}
|
||||
|
||||
// 根据用户名查询完整用户信息
|
||||
Optional<AdminUser> userOpt = adminUserRepository.findByUsername(username);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
return ResponseEntity.ok(Map.of("message", "用户不存在"));
|
||||
}
|
||||
|
||||
AdminUser user = userOpt.get();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("userId", user.getId());
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("isActive", user.getIsActive());
|
||||
response.put("method", "CurrentUserUtil");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方式2:使用 @AuthenticationPrincipal 注解(推荐)
|
||||
* <p>
|
||||
* 优点:Spring 自动注入,类型安全
|
||||
* 适用场景:Controller 层
|
||||
*/
|
||||
@GetMapping("/current/method2")
|
||||
public ResponseEntity<Map<String, Object>> getCurrentUserMethod2(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
|
||||
if (userDetails == null) {
|
||||
return ResponseEntity.ok(Map.of("message", "未登录"));
|
||||
}
|
||||
|
||||
String username = userDetails.getUsername();
|
||||
|
||||
// 根据用户名查询完整用户信息
|
||||
Optional<AdminUser> userOpt = adminUserRepository.findByUsername(username);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
return ResponseEntity.ok(Map.of("message", "用户不存在"));
|
||||
}
|
||||
|
||||
AdminUser user = userOpt.get();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("userId", user.getId());
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("isActive", user.getIsActive());
|
||||
response.put("method", "@AuthenticationPrincipal");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方式3:使用 Authentication 参数
|
||||
* <p>
|
||||
* 优点:可以获取更多认证信息(如权限列表)
|
||||
* 适用场景:需要权限信息的场景
|
||||
*/
|
||||
@GetMapping("/current/method3")
|
||||
public ResponseEntity<Map<String, Object>> getCurrentUserMethod3(Authentication authentication) {
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return ResponseEntity.ok(Map.of("message", "未登录"));
|
||||
}
|
||||
|
||||
String username = authentication.getName();
|
||||
|
||||
// 根据用户名查询完整用户信息
|
||||
Optional<AdminUser> userOpt = adminUserRepository.findByUsername(username);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
return ResponseEntity.ok(Map.of("message", "用户不存在"));
|
||||
}
|
||||
|
||||
AdminUser user = userOpt.get();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("userId", user.getId());
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("isActive", user.getIsActive());
|
||||
response.put("authorities", authentication.getAuthorities());
|
||||
response.put("method", "Authentication");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 方式4:使用 Principal 参数
|
||||
* <p>
|
||||
* 优点:轻量级,只包含用户名
|
||||
* 适用场景:仅需要用户名的场景
|
||||
*/
|
||||
@GetMapping("/current/method4")
|
||||
public ResponseEntity<Map<String, Object>> getCurrentUserMethod4(Principal principal) {
|
||||
|
||||
if (principal == null) {
|
||||
return ResponseEntity.ok(Map.of("message", "未登录"));
|
||||
}
|
||||
|
||||
String username = principal.getName();
|
||||
|
||||
// 根据用户名查询完整用户信息
|
||||
Optional<AdminUser> userOpt = adminUserRepository.findByUsername(username);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
return ResponseEntity.ok(Map.of("message", "用户不存在"));
|
||||
}
|
||||
|
||||
AdminUser user = userOpt.get();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("userId", user.getId());
|
||||
response.put("username", user.getUsername());
|
||||
response.put("email", user.getEmail());
|
||||
response.put("isActive", user.getIsActive());
|
||||
response.put("method", "Principal");
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import top.biwin.xinayu.common.dto.response.BaseResponse;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
@ -36,74 +37,6 @@ import java.util.Map;
|
||||
@RestControllerAdvice
|
||||
public class AuthenticationExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理认证失败异常
|
||||
* <p>
|
||||
* 触发场景:
|
||||
* - 用户名或密码错误
|
||||
* - JWT 令牌无效或过期
|
||||
*
|
||||
* @param ex 异常对象
|
||||
* @return ResponseEntity<Map<String, Object>> 错误响应
|
||||
*/
|
||||
@ExceptionHandler(BadCredentialsException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleBadCredentialsException(BadCredentialsException ex) {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("timestamp", LocalDateTime.now());
|
||||
errorResponse.put("status", HttpStatus.UNAUTHORIZED.value());
|
||||
errorResponse.put("error", "Unauthorized");
|
||||
errorResponse.put("message", ex.getMessage());
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户不存在异常
|
||||
* <p>
|
||||
* 触发场景:
|
||||
* - 根据用户名查询用户时,用户不存在
|
||||
*
|
||||
* @param ex 异常对象
|
||||
* @return ResponseEntity<Map<String, Object>> 错误响应
|
||||
*/
|
||||
@ExceptionHandler(UsernameNotFoundException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleUsernameNotFoundException(UsernameNotFoundException ex) {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("timestamp", LocalDateTime.now());
|
||||
errorResponse.put("status", HttpStatus.UNAUTHORIZED.value());
|
||||
errorResponse.put("error", "Unauthorized");
|
||||
errorResponse.put("message", ex.getMessage());
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运行时异常
|
||||
* <p>
|
||||
* 触发场景:
|
||||
* - 用户被禁用
|
||||
* - 业务逻辑异常
|
||||
*
|
||||
* @param ex 异常对象
|
||||
* @return ResponseEntity<Map<String, Object>> 错误响应
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex) {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("timestamp", LocalDateTime.now());
|
||||
errorResponse.put("status", HttpStatus.BAD_REQUEST.value());
|
||||
errorResponse.put("error", "Bad Request");
|
||||
errorResponse.put("message", ex.getMessage());
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.BAD_REQUEST)
|
||||
.body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理未知异常
|
||||
* <p>
|
||||
@ -111,18 +44,13 @@ public class AuthenticationExceptionHandler {
|
||||
* - 其他未被捕获的异常
|
||||
*
|
||||
* @param ex 异常对象
|
||||
* @return ResponseEntity<Map<String, Object>> 错误响应
|
||||
* @return ResponseEntity<BaseResponse> 错误响应
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Map<String, Object>> handleException(Exception ex) {
|
||||
Map<String, Object> errorResponse = new HashMap<>();
|
||||
errorResponse.put("timestamp", LocalDateTime.now());
|
||||
errorResponse.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
errorResponse.put("error", "Internal Server Error");
|
||||
errorResponse.put("message", "服务器内部错误: " + ex.getMessage());
|
||||
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(errorResponse);
|
||||
public ResponseEntity<BaseResponse> handleException(Exception ex) {
|
||||
return ResponseEntity.ok(BaseResponse.builder()
|
||||
.success(false)
|
||||
.message(ex.getMessage())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package top.biwin.xinayu.server.security;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 超级管理员权限注解
|
||||
* 使用此注解标记的方法或类只能由超级管理员访问
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* @RequireSuperAdmin
|
||||
* @GetMapping("/admin/users")
|
||||
* public List<User> getAllUsers() {
|
||||
* // 只有超级管理员可以访问
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@PreAuthorize("hasRole('SUPER_ADMIN')")
|
||||
public @interface RequireSuperAdmin {
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package top.biwin.xinayu.server.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
@ -8,19 +9,19 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import top.biwin.xianyu.core.entity.AdminUser;
|
||||
import top.biwin.xianyu.core.repository.AdminUserRepository;
|
||||
import top.biwin.xinayu.common.enums.UserRole;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* AdminUser 用户详情服务
|
||||
* 实现 Spring Security 的 UserDetailsService 接口
|
||||
* <p>
|
||||
* ⭐️ 这是主人实现自定义用户查询逻辑的核心位置!
|
||||
* <p>
|
||||
* 主要职责:
|
||||
* 1. 根据用户名从数据库查询 AdminUser
|
||||
* 2. 检查用户是否存在和是否激活
|
||||
* 3. 将 AdminUser 转换为 Spring Security 的 UserDetails
|
||||
* 4. 为用户分配对应的角色权限
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-21
|
||||
@ -28,34 +29,42 @@ import java.util.ArrayList;
|
||||
@Service
|
||||
public class AdminUserDetailsService implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private AdminUserRepository adminUserRepository;
|
||||
@Autowired
|
||||
private AdminUserRepository adminUserRepository;
|
||||
|
||||
/**
|
||||
* 根据用户名加载用户信息
|
||||
* <p>
|
||||
* ⭐️ 主人的自定义逻辑实现位置 #1
|
||||
* <p>
|
||||
* 💡 扩展提示:
|
||||
* - 可以在这里添加登录失败次数限制(需要在 AdminUser 实体中添加 failedLoginAttempts 字段)
|
||||
* - 可以添加 IP 白名单验证
|
||||
* - 可以添加账号锁定逻辑
|
||||
* - 如果后续需要角色权限,可以在 AdminUser 中添加 roles 字段,并在这里设置 authorities
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return UserDetails Spring Security 的用户详情对象
|
||||
* @throws UsernameNotFoundException 用户不存在时抛出
|
||||
*/
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
AdminUser adminUser = adminUserRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
|
||||
if (!adminUser.getIsActive()) { throw new RuntimeException("用户已被禁用: " + username); }
|
||||
return User.builder()
|
||||
.username(adminUser.getUsername())
|
||||
.password(adminUser.getPasswordHash())
|
||||
.authorities(new ArrayList<>())
|
||||
.build();
|
||||
/**
|
||||
* 根据用户名加载用户信息
|
||||
* <p>
|
||||
* 加载用户信息并设置角色权限:
|
||||
* - SUPER_ADMIN -> ROLE_SUPER_ADMIN
|
||||
* - ADMIN -> ROLE_ADMIN
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return UserDetails Spring Security 的用户详情对象,包含角色权限
|
||||
* @throws UsernameNotFoundException 用户不存在时抛出
|
||||
*/
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
// 1. 从数据库查询用户
|
||||
AdminUser adminUser = adminUserRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
|
||||
|
||||
}
|
||||
// 2. 检查用户是否被禁用
|
||||
if (!adminUser.getIsActive()) {
|
||||
throw new RuntimeException("用户已被禁用: " + username);
|
||||
}
|
||||
|
||||
// 3. 获取用户角色,如果为空则默认为 ADMIN
|
||||
UserRole role = adminUser.getRole() != null ? adminUser.getRole() : UserRole.ADMIN;
|
||||
|
||||
// 4. 将角色转换为 Spring Security 的 GrantedAuthority
|
||||
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getAuthority());
|
||||
|
||||
// 5. 构建并返回 UserDetails 对象
|
||||
return User.builder()
|
||||
.username(adminUser.getUsername())
|
||||
.password(adminUser.getPasswordHash())
|
||||
.authorities(Collections.singletonList(authority))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package top.biwin.xinayu.server.service;
|
||||
|
||||
import cn.hutool.log.Log;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@ -10,15 +9,13 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.biwin.xianyu.core.entity.AdminUser;
|
||||
import top.biwin.xianyu.core.repository.AdminUserRepository;
|
||||
import top.biwin.xinayu.common.dto.request.LoginRequest;
|
||||
import top.biwin.xinayu.common.dto.response.LoginResponse;
|
||||
import top.biwin.xinayu.common.dto.response.RefreshResponse;
|
||||
import top.biwin.xianyu.core.entity.AdminUser;
|
||||
import top.biwin.xianyu.core.repository.AdminUserRepository;
|
||||
import top.biwin.xinayu.server.security.JwtUtil;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 认证服务
|
||||
* 提供登录、刷新令牌等认证相关功能
|
||||
@ -69,30 +66,33 @@ public class AuthService {
|
||||
|
||||
// TODO: 主人,这里判断使用哪种登录方式!
|
||||
// 根据 request 中的字段判断登录类型,然后调用对应的认证方法
|
||||
|
||||
|
||||
if (StringUtils.hasText(request.getUsername()) && StringUtils.hasText(request.getPassword())) {
|
||||
// 方式1:用户名 + 密码登录
|
||||
user = authenticateByUsername(request.getUsername(), request.getPassword());
|
||||
log.info("✅ 用户名登录成功: {}", request.getUsername());
|
||||
|
||||
|
||||
} else if (StringUtils.hasText(request.getEmail()) && StringUtils.hasText(request.getPassword())) {
|
||||
// 方式2:邮箱 + 密码登录
|
||||
user = authenticateByEmail(request.getEmail(), request.getPassword());
|
||||
log.info("✅ 邮箱密码登录成功: {}", request.getEmail());
|
||||
|
||||
|
||||
} else if (StringUtils.hasText(request.getEmail()) && StringUtils.hasText(request.getVerificationCode())) {
|
||||
// 方式3:邮箱 + 验证码登录
|
||||
user = authenticateByEmailCode(request.getEmail(), request.getVerificationCode());
|
||||
log.info("✅ 邮箱验证码登录成功: {}", request.getEmail());
|
||||
|
||||
|
||||
} else {
|
||||
throw new BadCredentialsException("无效的登录参数组合");
|
||||
return LoginResponse.builder()
|
||||
.success(false)
|
||||
.message("无效的登录参数组合")
|
||||
.build();
|
||||
}
|
||||
|
||||
// TODO: 主人,这里生成 JWT Token 并构建响应!
|
||||
// 1. 使用 user.getUsername() 生成 accessToken 和 refreshToken
|
||||
// 2. 调用 buildLoginResponse() 方法构建包含完整用户信息的响应
|
||||
|
||||
|
||||
// 生成 JWT Token
|
||||
String accessToken = jwtUtil.generateAccessToken(user.getUsername());
|
||||
String refreshToken = jwtUtil.generateRefreshToken(user.getUsername());
|
||||
@ -115,7 +115,7 @@ public class AuthService {
|
||||
// 1. 创建 UsernamePasswordAuthenticationToken
|
||||
// 2. 调用 authenticationManager.authenticate(authToken)
|
||||
// 3. 认证成功后,从数据库查询用户信息并返回
|
||||
|
||||
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(username, password);
|
||||
Authentication authentication = authenticationManager.authenticate(authToken);
|
||||
@ -128,7 +128,7 @@ public class AuthService {
|
||||
/**
|
||||
* 邮箱 + 密码认证
|
||||
*
|
||||
* @param email 邮箱
|
||||
* @param email 邮箱
|
||||
* @param password 密码
|
||||
* @return AdminUser 用户实体
|
||||
*/
|
||||
@ -141,7 +141,7 @@ public class AuthService {
|
||||
// 4. 如果密码错误,抛出 BadCredentialsException
|
||||
// 5. 检查账号是否激活
|
||||
// 6. 返回用户实体
|
||||
|
||||
|
||||
AdminUser user = adminUserRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new BadCredentialsException("邮箱或密码错误"));
|
||||
|
||||
@ -162,7 +162,7 @@ public class AuthService {
|
||||
* 邮箱 + 验证码认证
|
||||
*
|
||||
* @param email 邮箱
|
||||
* @param code 验证码
|
||||
* @param code 验证码
|
||||
* @return AdminUser 用户实体
|
||||
*/
|
||||
private AdminUser authenticateByEmailCode(String email, String code) {
|
||||
@ -173,7 +173,7 @@ public class AuthService {
|
||||
// 3. 通过邮箱查询用户
|
||||
// 4. 检查账号是否激活
|
||||
// 5. 返回用户实体
|
||||
|
||||
|
||||
// 验证验证码
|
||||
if (!emailVerificationService.verifyCode(email, code)) {
|
||||
throw new BadCredentialsException("验证码无效或已过期");
|
||||
@ -194,31 +194,36 @@ public class AuthService {
|
||||
/**
|
||||
* 构建登录响应(包含完整用户信息)
|
||||
*
|
||||
* @param user 用户实体
|
||||
* @param accessToken 访问令牌
|
||||
* @param user 用户实体
|
||||
* @param accessToken 访问令牌
|
||||
* @param refreshToken 刷新令牌
|
||||
* @param expiresIn 过期时间
|
||||
* @param expiresIn 过期时间
|
||||
* @return LoginResponse
|
||||
*/
|
||||
private LoginResponse buildLoginResponse(AdminUser user, String accessToken,
|
||||
private LoginResponse buildLoginResponse(AdminUser user, String accessToken,
|
||||
String refreshToken, Long expiresIn) {
|
||||
// TODO: 主人,这里构建包含完整用户信息的响应!
|
||||
// 将 user 实体的信息映射到 LoginResponse 的各个字段
|
||||
|
||||
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setAccessToken(accessToken);
|
||||
response.setRefreshToken(refreshToken);
|
||||
response.setExpiresIn(expiresIn);
|
||||
response.setTokenType("Bearer");
|
||||
|
||||
|
||||
// 设置用户信息
|
||||
response.setUserId(user.getId());
|
||||
response.setUsername(user.getUsername());
|
||||
response.setEmail(user.getEmail());
|
||||
response.setIsActive(user.getIsActive());
|
||||
response.setIsAdmin(true); // 所有 admin_user 表的用户都是管理员
|
||||
response.setSuccess(true);
|
||||
|
||||
// 设置角色信息
|
||||
String roleName = user.getRole() != null ? user.getRole().name() : "ADMIN";
|
||||
response.setRole(roleName);
|
||||
|
||||
response.setSuccess(true);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,193 @@
|
||||
package top.biwin.xinayu.server.service;
|
||||
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.LineCaptcha;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import top.biwin.xinayu.common.dto.response.CaptchaResponse;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 图形验证码服务
|
||||
* 负责生成、存储和验证图形验证码
|
||||
* <p>
|
||||
* 使用 Hutool 的 CaptchaUtil 生成验证码图片
|
||||
* 使用 ConcurrentHashMap 在内存中存储验证码
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CaptchaService {
|
||||
|
||||
/**
|
||||
* 验证码存储(内存)
|
||||
* Key: captchaId, Value: CaptchaData
|
||||
*/
|
||||
private final Map<String, CaptchaData> captchaStore = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 验证码有效期(分钟)
|
||||
*/
|
||||
private static final int CAPTCHA_EXPIRE_MINUTES = 5;
|
||||
|
||||
/**
|
||||
* 验证码长度
|
||||
*/
|
||||
private static final int CAPTCHA_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* 生成图形验证码
|
||||
* <p>
|
||||
* 生成4位字母数字混合验证码,包含干扰线
|
||||
* 验证码5分钟后过期
|
||||
*
|
||||
* @return CaptchaResponse 包含验证码ID和Base64图片
|
||||
*/
|
||||
public CaptchaResponse generateCaptcha(String sessionId) {
|
||||
// TODO: 主人,这里使用 Hutool 生成验证码!
|
||||
// 步骤:
|
||||
// 1. 使用 CaptchaUtil.createLineCaptcha(宽, 高, 验证码长度, 干扰线数量)
|
||||
// 2. 生成唯一的 captchaId(使用 UUID)
|
||||
// 3. 获取验证码文本:captcha.getCode()
|
||||
// 4. 存储验证码到内存,设置过期时间
|
||||
// 5. 获取Base64图片:captcha.getImageBase64Data()
|
||||
// 6. 返回 CaptchaResponse
|
||||
|
||||
// 创建线性验证码(宽120,高40,4位验证码,50条干扰线)
|
||||
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(120, 40, CAPTCHA_LENGTH, 50);
|
||||
|
||||
// 获取验证码文本
|
||||
String code = captcha.getCode();
|
||||
|
||||
// 计算过期时间
|
||||
LocalDateTime expiresAt = LocalDateTime.now().plusMinutes(CAPTCHA_EXPIRE_MINUTES);
|
||||
|
||||
// 存储验证码
|
||||
CaptchaData data = new CaptchaData(code, expiresAt);
|
||||
captchaStore.put(sessionId, data);
|
||||
|
||||
// 获取Base64图片(包含 data:image/png;base64, 前缀)
|
||||
String base64Image = captcha.getImageBase64Data();
|
||||
|
||||
log.info("✅ 生成图形验证码,ID: {}, 过期时间: {}", sessionId, expiresAt);
|
||||
|
||||
return new CaptchaResponse(sessionId, base64Image, CAPTCHA_EXPIRE_MINUTES * 60, true, "图形验证码生成成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证图形验证码
|
||||
* <p>
|
||||
* 验证成功后立即删除验证码(一次性使用)
|
||||
* 验证码不区分大小写
|
||||
*
|
||||
* @param captchaId 验证码ID
|
||||
* @param userInput 用户输入的验证码
|
||||
* @return 是否验证成功
|
||||
*/
|
||||
public boolean verifyCaptcha(String captchaId, String userInput) {
|
||||
// TODO: 主人,这里验证验证码是否正确!
|
||||
// 步骤:
|
||||
// 1. 从 captchaStore 获取验证码数据
|
||||
// 2. 检查是否存在
|
||||
// 3. 检查是否过期
|
||||
// 4. 比较验证码(不区分大小写)
|
||||
// 5. 验证成功后立即删除(一次性使用)
|
||||
|
||||
if (captchaId == null || userInput == null) {
|
||||
log.warn("⚠️ 验证码参数为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取存储的验证码数据
|
||||
CaptchaData data = captchaStore.get(captchaId);
|
||||
|
||||
if (data == null) {
|
||||
log.warn("⚠️ 验证码不存在,ID: {}", captchaId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (data.getExpiresAt().isBefore(LocalDateTime.now())) {
|
||||
captchaStore.remove(captchaId);
|
||||
log.warn("⚠️ 验证码已过期,ID: {}", captchaId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证码比较(不区分大小写)
|
||||
boolean isValid = data.getCode().equalsIgnoreCase(userInput);
|
||||
|
||||
if (isValid) {
|
||||
// 验证成功,立即删除(一次性使用)
|
||||
captchaStore.remove(captchaId);
|
||||
log.info("✅ 验证码验证成功,ID: {}", captchaId);
|
||||
} else {
|
||||
log.warn("⚠️ 验证码错误,ID: {}, 期望: {}, 输入: {}",
|
||||
captchaId, data.getCode(), userInput);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时清理过期验证码
|
||||
* 每分钟执行一次,防止内存泄漏
|
||||
*/
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void cleanExpiredCaptcha() {
|
||||
// TODO: 主人,这里清理过期的验证码!
|
||||
// 遍历 captchaStore,删除过期的验证码
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int removedCount = 0;
|
||||
|
||||
// 使用 Iterator 安全删除
|
||||
captchaStore.entrySet().removeIf(entry -> {
|
||||
if (entry.getValue().getExpiresAt().isBefore(now)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (removedCount > 0) {
|
||||
log.info("🧹 清理过期验证码,数量: {}", removedCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前验证码数量(用于监控)
|
||||
*
|
||||
* @return 验证码数量
|
||||
*/
|
||||
public int getCaptchaCount() {
|
||||
return captchaStore.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码数据内部类
|
||||
*/
|
||||
@Data
|
||||
private static class CaptchaData {
|
||||
/**
|
||||
* 验证码文本
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private final LocalDateTime expiresAt;
|
||||
|
||||
public CaptchaData(String code, LocalDateTime expiresAt) {
|
||||
this.code = code;
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,14 +105,20 @@ public class EmailService {
|
||||
// 动态创建 MailSender
|
||||
JavaMailSender mailSender = createMailSender();
|
||||
|
||||
// 获取发件人显示名(如果配置了)
|
||||
String smtpFrom = getSettingValue("smtp_from");
|
||||
// 获取发件人邮箱(必须是真实邮箱地址)
|
||||
String smtpUser = getSettingValue("smtp_user");
|
||||
String fromAddress = StringUtils.hasText(smtpFrom) ? smtpFrom : smtpUser;
|
||||
String smtpFrom = getSettingValue("smtp_from");
|
||||
|
||||
// 创建邮件消息
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(fromAddress);
|
||||
|
||||
// 设置发件人:如果有显示名,使用 "显示名 <邮箱>" 格式,否则只用邮箱
|
||||
if (StringUtils.hasText(smtpFrom)) {
|
||||
message.setFrom(smtpFrom + " <" + smtpUser + ">");
|
||||
} else {
|
||||
message.setFrom(smtpUser);
|
||||
}
|
||||
|
||||
message.setTo(toEmail);
|
||||
message.setSubject("【闲鱼自由】邮箱验证码");
|
||||
message.setText(
|
||||
@ -146,14 +152,20 @@ public class EmailService {
|
||||
// 动态创建 MailSender
|
||||
JavaMailSender mailSender = createMailSender();
|
||||
|
||||
// 获取发件人显示名
|
||||
String smtpFrom = getSettingValue("smtp_from");
|
||||
// 获取发件人邮箱(必须是真实邮箱地址)
|
||||
String smtpUser = getSettingValue("smtp_user");
|
||||
String fromAddress = StringUtils.hasText(smtpFrom) ? smtpFrom : smtpUser;
|
||||
String smtpFrom = getSettingValue("smtp_from");
|
||||
|
||||
// 创建邮件消息
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(fromAddress);
|
||||
|
||||
// 设置发件人:如果有显示名,使用 "显示名 <邮箱>" 格式,否则只用邮箱
|
||||
if (StringUtils.hasText(smtpFrom)) {
|
||||
message.setFrom(smtpFrom + " <" + smtpUser + ">");
|
||||
} else {
|
||||
message.setFrom(smtpUser);
|
||||
}
|
||||
|
||||
message.setTo(toEmail);
|
||||
message.setSubject(subject);
|
||||
message.setText(content);
|
||||
|
||||
@ -0,0 +1,272 @@
|
||||
package top.biwin.xinayu.server.util;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* 当前用户工具类
|
||||
* 提供简便的方法获取当前登录用户信息
|
||||
* <p>
|
||||
* 使用场景:
|
||||
* - 在 Service 层获取当前用户
|
||||
* - 在 Controller 层获取当前用户
|
||||
* - 记录操作日志时获取操作人
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
public class CurrentUserUtil {
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的用户名
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* String username = CurrentUserUtil.getCurrentUsername();
|
||||
* </pre>
|
||||
*
|
||||
* @return 用户名,如果未登录则返回 null
|
||||
*/
|
||||
public static String getCurrentUsername() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
|
||||
if (principal instanceof UserDetails) {
|
||||
return ((UserDetails) principal).getUsername();
|
||||
} else if (principal instanceof String) {
|
||||
return (String) principal;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的 UserDetails
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* UserDetails userDetails = CurrentUserUtil.getCurrentUserDetails();
|
||||
* if (userDetails != null) {
|
||||
* String username = userDetails.getUsername();
|
||||
* Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return UserDetails 对象,如果未登录则返回 null
|
||||
*/
|
||||
public static UserDetails getCurrentUserDetails() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
|
||||
if (principal instanceof UserDetails) {
|
||||
return (UserDetails) principal;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的 Authentication 对象
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* Authentication auth = CurrentUserUtil.getCurrentAuthentication();
|
||||
* if (auth != null) {
|
||||
* String username = auth.getName();
|
||||
* Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return Authentication 对象,如果未登录则返回 null
|
||||
*/
|
||||
public static Authentication getCurrentAuthentication() {
|
||||
return SecurityContextHolder.getContext().getAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否已登录
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* if (CurrentUserUtil.isAuthenticated()) {
|
||||
* // 用户已登录
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return true 如果用户已登录,false 否则
|
||||
*/
|
||||
public static boolean isAuthenticated() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication != null
|
||||
&& authentication.isAuthenticated()
|
||||
&& !(authentication.getPrincipal() instanceof String && "anonymousUser".equals(authentication.getPrincipal()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否为超级管理员
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* if (CurrentUserUtil.isSuperAdmin()) {
|
||||
* // 执行超管专属操作
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return true 如果当前用户是超级管理员,false 否则
|
||||
*/
|
||||
public static boolean isSuperAdmin() {
|
||||
Authentication authentication = getCurrentAuthentication();
|
||||
if (authentication == null || authentication.getAuthorities() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return authentication.getAuthorities().stream()
|
||||
.anyMatch(authority -> "ROLE_SUPER_ADMIN".equals(authority.getAuthority()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否拥有指定角色
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* if (CurrentUserUtil.hasRole("ROLE_ADMIN")) {
|
||||
* // 用户拥有 ADMIN 角色
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param role 角色名称(格式:ROLE_XXX)
|
||||
* @return true 如果拥有指定角色,false 否则
|
||||
*/
|
||||
public static boolean hasRole(String role) {
|
||||
Authentication authentication = getCurrentAuthentication();
|
||||
if (authentication == null || authentication.getAuthorities() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return authentication.getAuthorities().stream()
|
||||
.anyMatch(authority -> role.equals(authority.getAuthority()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否拥有任意一个指定角色
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* if (CurrentUserUtil.hasAnyRole("ROLE_SUPER_ADMIN", "ROLE_ADMIN")) {
|
||||
* // 用户拥有其中任意一个角色
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param roles 角色名称数组
|
||||
* @return true 如果拥有任意一个角色,false 否则
|
||||
*/
|
||||
public static boolean hasAnyRole(String... roles) {
|
||||
Authentication authentication = getCurrentAuthentication();
|
||||
if (authentication == null || authentication.getAuthorities() == null || roles == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String role : roles) {
|
||||
if (authentication.getAuthorities().stream()
|
||||
.anyMatch(authority -> role.equals(authority.getAuthority()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的完整 AdminUser 对象
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* AdminUser user = CurrentUserUtil.getCurrentUser();
|
||||
* if (user != null) {
|
||||
* Long userId = user.getId();
|
||||
* String email = user.getEmail();
|
||||
* UserRole role = user.getRole();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return AdminUser 对象,如果未登录或用户不存在则返回 null
|
||||
*/
|
||||
public static top.biwin.xianyu.core.entity.AdminUser getCurrentUser() {
|
||||
// 1. 获取当前用户名
|
||||
String username = getCurrentUsername();
|
||||
if (username == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 通过 SpringContextHolder 获取 AdminUserRepository
|
||||
try {
|
||||
top.biwin.xianyu.core.repository.AdminUserRepository repository =
|
||||
SpringContextHolder.getBean(top.biwin.xianyu.core.repository.AdminUserRepository.class);
|
||||
|
||||
// 3. 查询并返回 AdminUser 对象
|
||||
return repository.findByUsername(username).orElse(null);
|
||||
} catch (Exception e) {
|
||||
// 如果获取失败(例如 ApplicationContext 未初始化),返回 null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的 ID
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* Long userId = CurrentUserUtil.getCurrentUserId();
|
||||
* </pre>
|
||||
*
|
||||
* @return 用户 ID,如果未登录则返回 null
|
||||
*/
|
||||
public static Long getCurrentUserId() {
|
||||
top.biwin.xianyu.core.entity.AdminUser user = getCurrentUser();
|
||||
return user != null ? user.getId() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的邮箱
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* String email = CurrentUserUtil.getCurrentUserEmail();
|
||||
* </pre>
|
||||
*
|
||||
* @return 用户邮箱,如果未登录则返回 null
|
||||
*/
|
||||
public static String getCurrentUserEmail() {
|
||||
top.biwin.xianyu.core.entity.AdminUser user = getCurrentUser();
|
||||
return user != null ? user.getEmail() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的角色
|
||||
* <p>
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* UserRole role = CurrentUserUtil.getCurrentUserRole();
|
||||
* if (role == UserRole.SUPER_ADMIN) {
|
||||
* // 执行超管操作
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @return 用户角色枚举,如果未登录则返回 null
|
||||
*/
|
||||
public static top.biwin.xinayu.common.enums.UserRole getCurrentUserRole() {
|
||||
top.biwin.xianyu.core.entity.AdminUser user = getCurrentUser();
|
||||
return user != null ? user.getRole() : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package top.biwin.xinayu.server.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring Context 持有者
|
||||
* 用于在非 Spring 管理的类中获取 Spring Bean
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-22
|
||||
*/
|
||||
@Component
|
||||
public class SpringContextHolder implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
applicationContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApplicationContext
|
||||
*
|
||||
* @return ApplicationContext
|
||||
*/
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型获取 Bean
|
||||
*
|
||||
* @param clazz Bean 类型
|
||||
* @param <T> 泛型类型
|
||||
* @return Bean 实例
|
||||
*/
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
if (applicationContext == null) {
|
||||
throw new IllegalStateException("ApplicationContext 未初始化");
|
||||
}
|
||||
return applicationContext.getBean(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称获取 Bean
|
||||
*
|
||||
* @param name Bean 名称
|
||||
* @return Bean 实例
|
||||
*/
|
||||
public static Object getBean(String name) {
|
||||
if (applicationContext == null) {
|
||||
throw new IllegalStateException("ApplicationContext 未初始化");
|
||||
}
|
||||
return applicationContext.getBean(name);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user