auth更新
This commit is contained in:
@@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
|
||||
import org.xyzh.common.vo.BaseVO;
|
||||
|
||||
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -103,8 +104,31 @@ public class SysUserVO extends BaseVO {
|
||||
return dto;
|
||||
}
|
||||
|
||||
public static TbSysUserDTO toFilter(SysUserVO vo) {
|
||||
return toDTO(vo);
|
||||
/**
|
||||
* 将 SysUserVO 转换为 TbSysUserInfoDTO
|
||||
*/
|
||||
public static TbSysUserInfoDTO toUserInfoDTO(SysUserVO userVO) {
|
||||
TbSysUserInfoDTO userInfoDTO = new TbSysUserInfoDTO();
|
||||
userInfoDTO.setUserId(userVO.getUserId());
|
||||
userInfoDTO.setUsername(userVO.getUsername());
|
||||
userInfoDTO.setAvatar(userVO.getAvatar());
|
||||
userInfoDTO.setGender(userVO.getGender());
|
||||
userInfoDTO.setLevel(userVO.getLevel());
|
||||
userInfoDTO.setIdCard(userVO.getIdCard());
|
||||
userInfoDTO.setAddress(userVO.getAddress());
|
||||
|
||||
// 继承自 BaseDTO 的字段
|
||||
userInfoDTO.setOptsn(userVO.getOptsn());
|
||||
userInfoDTO.setCreator(userVO.getCreator());
|
||||
userInfoDTO.setUpdater(userVO.getUpdater());
|
||||
userInfoDTO.setDeptPath(userVO.getDeptPath());
|
||||
userInfoDTO.setRemark(userVO.getRemark());
|
||||
userInfoDTO.setCreateTime(userVO.getCreateTime());
|
||||
userInfoDTO.setUpdateTime(userVO.getUpdateTime());
|
||||
userInfoDTO.setDeleteTime(userVO.getDeleteTime());
|
||||
userInfoDTO.setDeleted(userVO.getDeleted());
|
||||
|
||||
return userInfoDTO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
package org.xyzh.auth.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.xyzh.api.auth.service.AuthService;
|
||||
import org.xyzh.api.message.service.MessageService;
|
||||
import org.xyzh.api.system.service.SysUserService;
|
||||
import org.xyzh.api.system.vo.SysUserVO;
|
||||
import org.xyzh.auth.utils.CapcatUtils;
|
||||
import org.xyzh.common.core.domain.LoginParam;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.common.utils.validation.method.PhoneValidateMethod;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -48,8 +43,6 @@ public class AuthController {
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
/**
|
||||
* @description 用户登录
|
||||
* @param loginParam 登录参数
|
||||
@@ -75,36 +68,27 @@ public class AuthController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取验证码
|
||||
* @return ResultDomain<String> 验证码
|
||||
* @description 获取验证码(统一接口,支持多种类型)
|
||||
* @param loginParam 登录参数(包含验证码类型)
|
||||
* @return ResultDomain<LoginDomain> 验证码结果
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
* @since 2025-12-09
|
||||
*/
|
||||
@GetMapping("/captcha")
|
||||
public ResultDomain<String> getCaptcha() {
|
||||
// TODO: 实现验证码生成逻辑
|
||||
// 生成验证码会话ID,用于验证时匹配
|
||||
String captchaId = IDUtils.generateID();
|
||||
String captchaData = captchaId + ":captcha-placeholder"; // 格式: ID:验证码内容
|
||||
|
||||
return ResultDomain.success("验证码获取成功", captchaData);
|
||||
@PostMapping("/captcha")
|
||||
public ResultDomain<LoginDomain> getCaptcha(@RequestBody LoginParam loginParam) {
|
||||
return authService.getCaptcha(loginParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 刷新令牌
|
||||
* @param token 原令牌
|
||||
* @return ResultDomain<String> 新令牌
|
||||
* @param request HTTP请求
|
||||
* @return ResultDomain<LoginDomain> 新的登录信息
|
||||
* @author yslg
|
||||
* @since 2025-09-28
|
||||
* @since 2025-12-09
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
public ResultDomain<String> refreshToken(@RequestHeader("Authorization") String token) {
|
||||
// TODO: 实现令牌刷新逻辑
|
||||
// 为新令牌生成唯一ID
|
||||
String newTokenId = IDUtils.generateID();
|
||||
String newToken = "new-token-" + newTokenId; // 临时占位符
|
||||
|
||||
return ResultDomain.success("令牌刷新成功", newToken);
|
||||
public ResultDomain<LoginDomain> refreshToken(HttpServletRequest request) {
|
||||
return authService.refreshToken(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,116 +105,37 @@ public class AuthController {
|
||||
/**
|
||||
* @description 发送邮箱验证码
|
||||
* @param requestBody 包含email字段的请求体
|
||||
* @return ResultDomain<Boolean> 发送结果
|
||||
* @return ResultDomain<LoginDomain> 发送结果
|
||||
* @author yslg
|
||||
* @since 2025-11-03
|
||||
* @since 2025-12-09
|
||||
*/
|
||||
@PostMapping("/send-email-code")
|
||||
public ResultDomain<Map<String, String>> sendEmailCode(@RequestBody Map<String, String> requestBody) {
|
||||
public ResultDomain<LoginDomain> sendEmailCode(@RequestBody Map<String, String> requestBody) {
|
||||
String email = requestBody.get("email");
|
||||
|
||||
// 验证邮箱格式
|
||||
if (email == null || email.trim().isEmpty()) {
|
||||
return ResultDomain.failure("邮箱不能为空");
|
||||
}
|
||||
LoginParam loginParam = new LoginParam();
|
||||
loginParam.setEmail(email);
|
||||
loginParam.setLoginType("email");
|
||||
|
||||
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
|
||||
if (!email.matches(emailRegex)) {
|
||||
return ResultDomain.failure("邮箱格式不正确");
|
||||
}
|
||||
|
||||
// 检查是否频繁发送(60秒内只能发送一次)
|
||||
String rateLimitKey = "email:code:ratelimit:" + email;
|
||||
if (redisService.hasKey(rateLimitKey)) {
|
||||
return ResultDomain.failure("验证码已发送,请勿重复发送");
|
||||
}
|
||||
|
||||
// 生成会话ID(用于绑定验证码和用户)
|
||||
String sessionId = IDUtils.generateID();
|
||||
|
||||
// 生成6位数字验证码
|
||||
String code = CapcatUtils.generateVerificationCode();
|
||||
|
||||
// 发送邮件
|
||||
boolean success = messageService.sendEmailVerificationCode(email, code).getSuccess();
|
||||
|
||||
if (success) {
|
||||
// 将验证码存储到Redis,绑定sessionId,有效期5分钟
|
||||
String codeKey = "email:code:" + sessionId;
|
||||
String codeValue = email + ":" + code; // 格式:邮箱:验证码
|
||||
redisService.set(codeKey, codeValue, 5, TimeUnit.MINUTES);
|
||||
|
||||
// 设置5分钟的发送频率限制
|
||||
redisService.set(rateLimitKey, "1", 5, TimeUnit.MINUTES);
|
||||
|
||||
// 返回sessionId给前端
|
||||
Map<String, String> data = Map.of(
|
||||
"sessionId", sessionId,
|
||||
"message", "验证码已发送到邮箱"
|
||||
);
|
||||
|
||||
logger.info("邮箱验证码已发送,邮箱: {}, sessionId: {}", email, sessionId);
|
||||
return ResultDomain.success("验证码已发送到邮箱", data);
|
||||
} else {
|
||||
return ResultDomain.failure("验证码发送失败,请稍后重试");
|
||||
}
|
||||
return authService.getCaptcha(loginParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 发送手机验证码
|
||||
* @param requestBody 包含phone字段的请求体
|
||||
* @return ResultDomain<Boolean> 发送结果
|
||||
* @return ResultDomain<LoginDomain> 发送结果
|
||||
* @author yslg
|
||||
* @since 2025-11-03
|
||||
* @since 2025-12-09
|
||||
*/
|
||||
@PostMapping("/send-sms-code")
|
||||
public ResultDomain<Map<String, String>> sendSmsCode(@RequestBody Map<String, String> requestBody) {
|
||||
public ResultDomain<LoginDomain> sendSmsCode(@RequestBody Map<String, String> requestBody) {
|
||||
String phone = requestBody.get("phone");
|
||||
|
||||
// 验证手机号格式
|
||||
if (phone == null || phone.trim().isEmpty()) {
|
||||
return ResultDomain.failure("手机号不能为空");
|
||||
}
|
||||
PhoneValidateMethod validateMethod = new PhoneValidateMethod();
|
||||
if (!validateMethod.validate(phone)) {
|
||||
return ResultDomain.failure("手机号格式不正确");
|
||||
}
|
||||
LoginParam loginParam = new LoginParam();
|
||||
loginParam.setPhone(phone);
|
||||
loginParam.setLoginType("sms");
|
||||
|
||||
// 检查是否频繁发送(60秒内只能发送一次)
|
||||
String rateLimitKey = "sms:code:ratelimit:" + phone;
|
||||
if (redisService.hasKey(rateLimitKey)) {
|
||||
return ResultDomain.failure("验证码已发送,请勿重复发送");
|
||||
}
|
||||
|
||||
// 生成会话ID(用于绑定验证码和用户)
|
||||
String sessionId = IDUtils.generateID();
|
||||
|
||||
// 生成6位数字验证码
|
||||
String code = CapcatUtils.generateVerificationCode();
|
||||
|
||||
// 发送短信
|
||||
boolean success = messageService.sendPhoneVerificationCode(phone, code).getSuccess();
|
||||
|
||||
if (success) {
|
||||
// 将验证码存储到Redis,绑定sessionId,有效期5分钟
|
||||
String codeKey = "sms:code:" + sessionId;
|
||||
String codeValue = phone + ":" + code; // 格式:手机号:验证码
|
||||
redisService.set(codeKey, codeValue, 5, TimeUnit.MINUTES);
|
||||
|
||||
// 设置5分钟的发送频率限制
|
||||
redisService.set(rateLimitKey, "1", 5, TimeUnit.MINUTES);
|
||||
|
||||
// 返回sessionId给前端
|
||||
Map<String, String> data = Map.of(
|
||||
"sessionId", sessionId,
|
||||
"message", "验证码已发送"
|
||||
);
|
||||
|
||||
logger.info("短信验证码已发送,手机号: {}, sessionId: {}", phone, sessionId);
|
||||
return ResultDomain.success("验证码已发送", data);
|
||||
} else {
|
||||
return ResultDomain.failure("验证码发送失败,请稍后重试");
|
||||
}
|
||||
return authService.getCaptcha(loginParam);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.xyzh.auth.enums;
|
||||
|
||||
/**
|
||||
* @description 用户状态枚举
|
||||
* @filename UserStatus.java
|
||||
* @author yslg
|
||||
* @copyright yslg
|
||||
* @since 2025-12-09
|
||||
*/
|
||||
public enum UserStatus {
|
||||
|
||||
NORMAL("0", "正常"),
|
||||
DISABLED("1", "禁用"),
|
||||
LOCKED("2", "锁定"),
|
||||
EXPIRED("3", "过期");
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
|
||||
UserStatus(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态码获取用户状态
|
||||
* @param code 状态码
|
||||
* @return UserStatus
|
||||
*/
|
||||
public static UserStatus fromCode(String code) {
|
||||
if (code == null) {
|
||||
return DISABLED;
|
||||
}
|
||||
for (UserStatus status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return DISABLED; // 默认返回禁用状态
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为正常状态
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isNormal() {
|
||||
return this == NORMAL;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,38 @@
|
||||
package org.xyzh.auth.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.xyzh.api.auth.service.AuthService;
|
||||
import org.xyzh.api.system.service.SysUserService;
|
||||
import org.xyzh.api.system.service.ModulePermissionService;
|
||||
import org.xyzh.api.system.vo.SysUserVO;
|
||||
import org.xyzh.api.system.vo.PermissionVO;
|
||||
import org.xyzh.api.system.vo.UserDeptRoleVO;
|
||||
import org.xyzh.auth.strategy.LoginStrategyFactory;
|
||||
import org.xyzh.auth.strategy.LoginStrategy;
|
||||
import org.xyzh.auth.enums.UserStatus;
|
||||
import org.xyzh.auth.utils.CapcatUtils;
|
||||
import org.xyzh.common.auth.utils.JwtTokenUtil;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.core.domain.LoginParam;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.dto.sys.TbSysUserDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysUserRoleDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysDeptDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
|
||||
import org.xyzh.common.dto.sys.TbSysViewDTO;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @description AuthServiceImpl.java文件描述 认证服务实现类
|
||||
@@ -27,42 +51,489 @@ public class AuthServiceImpl implements AuthService{
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||
private SysUserService userService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false)
|
||||
private ModulePermissionService modulePermissionService;
|
||||
|
||||
@Autowired
|
||||
private LoginStrategyFactory loginStrategyFactory;
|
||||
|
||||
@Override
|
||||
public ResultDomain<LoginDomain> getCaptcha(LoginParam arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
public ResultDomain<LoginDomain> getCaptcha(LoginParam loginParam) {
|
||||
try {
|
||||
String captchaType = loginParam.getLoginType(); // "image", "sms", "email"
|
||||
|
||||
switch (captchaType) {
|
||||
case "image":
|
||||
// 生成图片验证码
|
||||
String captchaId = IDUtils.generateID();
|
||||
String captchaCode = CapcatUtils.generateVerificationCode();
|
||||
|
||||
// 存储验证码到Redis,有效期5分钟
|
||||
String captchaKey = "captcha:image:" + captchaId;
|
||||
redisService.set(captchaKey, captchaCode, 5, TimeUnit.MINUTES);
|
||||
|
||||
LoginDomain captchaResult = new LoginDomain();
|
||||
captchaResult.setToken(captchaId); // 使用token字段存储captchaId
|
||||
captchaResult.setLoginType("image");
|
||||
|
||||
return ResultDomain.success("图片验证码生成成功", captchaResult);
|
||||
|
||||
case "sms":
|
||||
if (loginParam.getPhone() == null || loginParam.getPhone().trim().isEmpty()) {
|
||||
return ResultDomain.failure("手机号不能为空");
|
||||
}
|
||||
|
||||
String smsSessionId = IDUtils.generateID();
|
||||
String smsCode = CapcatUtils.generateVerificationCode();
|
||||
|
||||
// 存储短信验证码到Redis,有效期5分钟
|
||||
String smsKey = "captcha:sms:" + smsSessionId;
|
||||
String smsValue = loginParam.getPhone() + ":" + smsCode;
|
||||
redisService.set(smsKey, smsValue, 5, TimeUnit.MINUTES);
|
||||
|
||||
LoginDomain smsResult = new LoginDomain();
|
||||
smsResult.setToken(smsSessionId);
|
||||
smsResult.setLoginType("sms");
|
||||
|
||||
// 这里应该调用短信服务发送验证码,暂时先返回
|
||||
logger.info("短信验证码已生成:phone={}, code={}", loginParam.getPhone(), smsCode);
|
||||
|
||||
return ResultDomain.success("短信验证码发送成功", smsResult);
|
||||
|
||||
case "email":
|
||||
if (loginParam.getEmail() == null || loginParam.getEmail().trim().isEmpty()) {
|
||||
return ResultDomain.failure("邮箱不能为空");
|
||||
}
|
||||
|
||||
String emailSessionId = IDUtils.generateID();
|
||||
String emailCode = CapcatUtils.generateVerificationCode();
|
||||
|
||||
// 存储邮箱验证码到Redis,有效期5分钟
|
||||
String emailKey = "captcha:email:" + emailSessionId;
|
||||
String emailValue = loginParam.getEmail() + ":" + emailCode;
|
||||
redisService.set(emailKey, emailValue, 5, TimeUnit.MINUTES);
|
||||
|
||||
LoginDomain emailResult = new LoginDomain();
|
||||
emailResult.setToken(emailSessionId);
|
||||
emailResult.setLoginType("email");
|
||||
|
||||
// 这里应该调用邮件服务发送验证码,暂时先返回
|
||||
logger.info("邮箱验证码已生成:email={}, code={}", loginParam.getEmail(), emailCode);
|
||||
|
||||
return ResultDomain.success("邮箱验证码发送成功", emailResult);
|
||||
|
||||
default:
|
||||
return ResultDomain.failure("不支持的验证码类型");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("验证码生成失败", e);
|
||||
return ResultDomain.failure("验证码生成失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<LoginDomain> getLoginByToken(String arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
public ResultDomain<LoginDomain> getLoginByToken(String token) {
|
||||
try {
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
return ResultDomain.failure("Token不能为空");
|
||||
}
|
||||
|
||||
// 1. 验证Token格式和是否过期
|
||||
if (jwtTokenUtil.isTokenExpired(token)) {
|
||||
return ResultDomain.failure("Token已过期");
|
||||
}
|
||||
|
||||
// 2. 从Redis获取登录信息
|
||||
String loginKey = "login:token:" + token;
|
||||
String loginJson = (String) redisService.get(loginKey);
|
||||
if (loginJson == null) {
|
||||
return ResultDomain.failure("登录信息已失效");
|
||||
}
|
||||
|
||||
LoginDomain loginDomain = JSON.parseObject(loginJson, LoginDomain.class);
|
||||
|
||||
// 3. 验证Token中的用户ID与Redis中的是否匹配
|
||||
if (loginDomain.getUser() != null) {
|
||||
String tokenUserId = jwtTokenUtil.getUserIdFromToken(token);
|
||||
if (!loginDomain.getUser().getUserId().equals(tokenUserId)) {
|
||||
return ResultDomain.failure("Token验证失败");
|
||||
}
|
||||
}
|
||||
|
||||
return ResultDomain.success("获取登录信息成功", loginDomain);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("获取登录信息失败", e);
|
||||
return ResultDomain.failure("获取登录信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<Boolean> isTokenValid(String arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
public ResultDomain<Boolean> isTokenValid(String token) {
|
||||
try {
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
return ResultDomain.success("Token验证", false);
|
||||
}
|
||||
|
||||
// 1. 检查Token格式和是否过期
|
||||
if (jwtTokenUtil.isTokenExpired(token)) {
|
||||
return ResultDomain.success("Token验证", false);
|
||||
}
|
||||
|
||||
// 2. 检查Redis中是否存在登录信息
|
||||
String loginKey = "login:token:" + token;
|
||||
boolean exists = redisService.hasKey(loginKey);
|
||||
|
||||
return ResultDomain.success("Token验证", exists);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Token验证失败", e);
|
||||
return ResultDomain.success("Token验证", false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<LoginDomain> login(LoginParam arg0, HttpServletRequest arg1) {
|
||||
// TODO Auto-generated method stub
|
||||
public ResultDomain<LoginDomain> login(LoginParam loginParam, HttpServletRequest request) {
|
||||
String loginType = loginParam.getLoginType();
|
||||
String loginAttempt = IDUtils.generateID(); // 生成登录尝试ID
|
||||
|
||||
try {
|
||||
logger.info("用户登录请求:loginType={}, username={}, attempt={}", loginType, loginParam.getUsername(), loginAttempt);
|
||||
|
||||
// 1. 获取对应的登录策略
|
||||
LoginStrategy strategy = loginStrategyFactory.getStrategy(loginType);
|
||||
|
||||
// 2. 验证登录参数
|
||||
if (!strategy.validate(loginParam)) {
|
||||
logLoginAttempt(loginParam, null, false, loginAttempt, "登录参数不正确");
|
||||
return ResultDomain.failure("登录参数不正确");
|
||||
}
|
||||
|
||||
// 3. 查找用户
|
||||
SysUserVO user = strategy.findUser(loginParam);
|
||||
if (user == null) {
|
||||
logLoginAttempt(loginParam, null, false, loginAttempt, "用户不存在");
|
||||
return ResultDomain.failure("用户不存在");
|
||||
}
|
||||
|
||||
// 4. 检查用户状态
|
||||
UserStatus userStatus = UserStatus.fromCode(user.getStatus());
|
||||
if (!userStatus.isNormal()) {
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "用户状态异常: " + userStatus.getName());
|
||||
return ResultDomain.failure("用户状态异常: " + userStatus.getName());
|
||||
}
|
||||
|
||||
// 5. 验证凭据
|
||||
if ("password".equals(loginType)) {
|
||||
if (!strategy.verifyCredential(loginParam.getPassword(), user.getPassword())) {
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "密码错误");
|
||||
return ResultDomain.failure("密码错误");
|
||||
}
|
||||
} else if ("email".equals(loginType) || "phone".equals(loginType)) {
|
||||
if (!strategy.verifyCaptchaWithSession(loginParam)) {
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "验证码验证失败");
|
||||
return ResultDomain.failure("验证码错误或已过期");
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 获取用户详细信息
|
||||
ResultDomain<SysUserVO> userInfoResult = userService.getUserInfo(user.getUserId());
|
||||
if (!userInfoResult.getSuccess() || userInfoResult.getData() == null) {
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "获取用户信息失败");
|
||||
return ResultDomain.failure("获取用户信息失败");
|
||||
}
|
||||
|
||||
SysUserVO userInfo = userInfoResult.getData();
|
||||
|
||||
// 7. 构建完整的登录域对象
|
||||
LoginDomain loginDomain = buildLoginDomain(userInfo, loginType, request);
|
||||
if (loginDomain == null) {
|
||||
logLoginAttempt(loginParam, user, false, loginAttempt, "构建登录信息失败");
|
||||
return ResultDomain.failure("构建登录信息失败");
|
||||
}
|
||||
|
||||
// 8. 生成 JWT Token
|
||||
String token = jwtTokenUtil.generateToken(loginDomain);
|
||||
loginDomain.setToken(token);
|
||||
|
||||
// 9. 将登录信息存储到 Redis
|
||||
String loginKey = "login:token:" + token;
|
||||
redisService.set(loginKey, JSON.toJSONString(loginDomain), 24, TimeUnit.HOURS);
|
||||
|
||||
// 10. 存储用户登录状态
|
||||
String userLoginKey = "login:user:" + user.getUserId();
|
||||
redisService.set(userLoginKey, token, 24, TimeUnit.HOURS);
|
||||
|
||||
// 11. 记录成功日志
|
||||
logLoginAttempt(loginParam, user, true, loginAttempt, "登录成功");
|
||||
logger.info("用户登录成功:userId={}, username={}, loginType={}, attempt={}", user.getUserId(), userInfo.getUsername(), loginType, loginAttempt);
|
||||
|
||||
return ResultDomain.success("登录成功", loginDomain);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("用户登录失败:attempt={}", loginAttempt, e);
|
||||
logLoginAttempt(loginParam, null, false, loginAttempt, "系统异常: " + e.getMessage());
|
||||
return ResultDomain.failure("登录失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整的LoginDomain对象
|
||||
* @param userInfo 用户信息
|
||||
* @param loginType 登录类型
|
||||
* @param request HTTP请求
|
||||
* @return LoginDomain 登录域对象
|
||||
*/
|
||||
public LoginDomain buildLoginDomain(SysUserVO userInfo, String loginType, HttpServletRequest request) {
|
||||
try {
|
||||
// 1. 转换为 DTO 对象
|
||||
TbSysUserDTO userDTO = SysUserVO.toDTO(userInfo);
|
||||
TbSysUserInfoDTO userInfoDTO = SysUserVO.toUserInfoDTO(userInfo);
|
||||
|
||||
// 2. 获取用户角色和部门信息
|
||||
List<TbSysUserRoleDTO> userRoles = new ArrayList<>();
|
||||
List<TbSysDeptDTO> userDepts = new ArrayList<>();
|
||||
|
||||
ResultDomain<UserDeptRoleVO> userDeptRoleResult = userService.getUserWithDeptRole(userInfo.getUserId());
|
||||
if (userDeptRoleResult.getSuccess() && userDeptRoleResult.getDataList() != null) {
|
||||
for (UserDeptRoleVO deptRole : userDeptRoleResult.getDataList()) {
|
||||
// 添加用户角色
|
||||
if (deptRole.getRoleId() != null) {
|
||||
TbSysUserRoleDTO userRole = new TbSysUserRoleDTO();
|
||||
userRole.setUserId(deptRole.getUserId());
|
||||
userRole.setRoleId(deptRole.getRoleId());
|
||||
userRole.setCreateTime(deptRole.getCreateTime());
|
||||
userRole.setUpdateTime(deptRole.getUpdateTime());
|
||||
userRoles.add(userRole);
|
||||
}
|
||||
|
||||
// 添加用户部门
|
||||
if (deptRole.getDeptId() != null) {
|
||||
TbSysDeptDTO userDept = new TbSysDeptDTO();
|
||||
userDept.setDeptId(deptRole.getDeptId());
|
||||
userDept.setName(deptRole.getDeptName());
|
||||
userDept.setParentId(deptRole.getParentId());
|
||||
userDept.setDescription(deptRole.getDeptDescription());
|
||||
userDepts.add(userDept);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 获取用户权限信息
|
||||
List<TbSysPermissionDTO> userPermissions = new ArrayList<>();
|
||||
List<TbSysViewDTO> userViews = new ArrayList<>();
|
||||
|
||||
ResultDomain<PermissionVO> permissionsResult = modulePermissionService.getUserPermissions(userInfo.getUserId());
|
||||
if (permissionsResult.getSuccess() && permissionsResult.getDataList() != null) {
|
||||
for (PermissionVO permission : permissionsResult.getDataList()) {
|
||||
// 添加权限信息
|
||||
if (permission.getPermissionId() != null) {
|
||||
TbSysPermissionDTO permissionDTO = PermissionVO.toPermissionDTO(permission);
|
||||
if (permissionDTO != null) {
|
||||
userPermissions.add(permissionDTO);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加视图信息
|
||||
if (permission.getViewId() != null) {
|
||||
TbSysViewDTO viewDTO = PermissionVO.toViewDTO(permission);
|
||||
if (viewDTO != null) {
|
||||
userViews.add(viewDTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 创建登录域对象
|
||||
LoginDomain loginDomain = new LoginDomain();
|
||||
loginDomain.setUser(userDTO);
|
||||
loginDomain.setUserInfo(userInfoDTO);
|
||||
loginDomain.setUserRoles(userRoles);
|
||||
loginDomain.setUserDepts(userDepts);
|
||||
loginDomain.setUserPermissions(userPermissions);
|
||||
loginDomain.setUserViews(userViews);
|
||||
loginDomain.setLoginType(loginType);
|
||||
loginDomain.setIpAddress(getClientIP(request));
|
||||
|
||||
return loginDomain;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("构建LoginDomain失败:userId={}", userInfo.getUserId(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录尝试日志
|
||||
* @param loginParam 登录参数
|
||||
* @param user 用户信息(可能为null)
|
||||
* @param success 是否成功
|
||||
* @param attemptId 尝试ID
|
||||
* @param message 消息
|
||||
*/
|
||||
private void logLoginAttempt(LoginParam loginParam, SysUserVO user, boolean success, String attemptId, String message) {
|
||||
try {
|
||||
String userId = user != null ? user.getUserId() : "unknown";
|
||||
String username = loginParam.getUsername() != null ? loginParam.getUsername() :
|
||||
(loginParam.getPhone() != null ? loginParam.getPhone() :
|
||||
(loginParam.getEmail() != null ? loginParam.getEmail() : "unknown"));
|
||||
|
||||
if (success) {
|
||||
logger.info("登录成功 - 用户ID: {}, 用户名: {}, 登录类型: {}, 尝试ID: {}, 消息: {}",
|
||||
userId, username, loginParam.getLoginType(), attemptId, message);
|
||||
} else {
|
||||
logger.warn("登录失败 - 用户名: {}, 登录类型: {}, 尝试ID: {}, 原因: {}",
|
||||
username, loginParam.getLoginType(), attemptId, message);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("记录登录日志失败:attemptId={}", attemptId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<LoginDomain> logout(HttpServletRequest arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
public ResultDomain<LoginDomain> refreshToken(HttpServletRequest request) {
|
||||
try {
|
||||
String token = extractTokenFromRequest(request);
|
||||
if (token == null) {
|
||||
return ResultDomain.failure("Token不能为空");
|
||||
}
|
||||
|
||||
// 1. 验证当前Token是否有效
|
||||
if (jwtTokenUtil.isTokenExpired(token)) {
|
||||
return ResultDomain.failure("Token已过期");
|
||||
}
|
||||
|
||||
// 2. 从Redis获取登录信息
|
||||
String loginKey = "login:token:" + token;
|
||||
String loginJson = (String) redisService.get(loginKey);
|
||||
if (loginJson == null) {
|
||||
return ResultDomain.failure("登录信息已失效");
|
||||
}
|
||||
|
||||
LoginDomain oldLoginDomain = JSON.parseObject(loginJson, LoginDomain.class);
|
||||
|
||||
// 3. 获取用户最新信息
|
||||
ResultDomain<SysUserVO> userInfoResult = userService.getUserInfo(oldLoginDomain.getUser().getUserId());
|
||||
if (!userInfoResult.getSuccess() || userInfoResult.getData() == null) {
|
||||
return ResultDomain.failure("获取用户信息失败");
|
||||
}
|
||||
|
||||
SysUserVO userInfo = userInfoResult.getData();
|
||||
|
||||
// 4. 重新构建LoginDomain
|
||||
LoginDomain newLoginDomain = buildLoginDomain(userInfo, oldLoginDomain.getLoginType(), request);
|
||||
if (newLoginDomain == null) {
|
||||
return ResultDomain.failure("构建登录信息失败");
|
||||
}
|
||||
|
||||
// 5. 生成新Token
|
||||
String newToken = jwtTokenUtil.generateToken(newLoginDomain);
|
||||
newLoginDomain.setToken(newToken);
|
||||
|
||||
// 6. 删除旧的Token信息
|
||||
redisService.delete(loginKey);
|
||||
|
||||
// 7. 存储新的登录信息
|
||||
String newLoginKey = "login:token:" + newToken;
|
||||
redisService.set(newLoginKey, JSON.toJSONString(newLoginDomain), 24, TimeUnit.HOURS);
|
||||
|
||||
// 8. 更新用户登录状态
|
||||
String userLoginKey = "login:user:" + userInfo.getUserId();
|
||||
redisService.set(userLoginKey, newToken, 24, TimeUnit.HOURS);
|
||||
|
||||
logger.info("Token刷新成功:userId={}, oldToken={}, newToken={}",
|
||||
userInfo.getUserId(),
|
||||
token.substring(0, Math.min(10, token.length())) + "...",
|
||||
newToken.substring(0, Math.min(10, newToken.length())) + "...");
|
||||
|
||||
return ResultDomain.success("Token刷新成功", newLoginDomain);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Token刷新失败", e);
|
||||
return ResultDomain.failure("Token刷新失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<LoginDomain> refreshToken(HttpServletRequest arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
public ResultDomain<LoginDomain> logout(HttpServletRequest request) {
|
||||
try {
|
||||
String token = extractTokenFromRequest(request);
|
||||
if (token == null) {
|
||||
return ResultDomain.failure("Token不能为空");
|
||||
}
|
||||
|
||||
// 1. 从Redis获取登录信息
|
||||
String loginKey = "login:token:" + token;
|
||||
String loginJson = (String) redisService.get(loginKey);
|
||||
if (loginJson == null) {
|
||||
return ResultDomain.success("登出成功", (LoginDomain) null); // 已经过期的token,直接返回成功
|
||||
}
|
||||
|
||||
LoginDomain loginDomain = JSON.parseObject(loginJson, LoginDomain.class);
|
||||
|
||||
// 2. 删除Redis中的登录信息
|
||||
redisService.delete(loginKey);
|
||||
|
||||
// 3. 删除用户登录状态
|
||||
if (loginDomain.getUser() != null) {
|
||||
String userLoginKey = "login:user:" + loginDomain.getUser().getUserId();
|
||||
redisService.delete(userLoginKey);
|
||||
}
|
||||
|
||||
logger.info("用户登出成功:userId={}, token={}",
|
||||
loginDomain.getUser() != null ? loginDomain.getUser().getUserId() : "unknown",
|
||||
token.substring(0, Math.min(10, token.length())) + "...");
|
||||
|
||||
return ResultDomain.success("登出成功", (LoginDomain)null);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("用户登出失败", e);
|
||||
return ResultDomain.failure("登出失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取Token
|
||||
*/
|
||||
private String extractTokenFromRequest(HttpServletRequest request) {
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
return authHeader.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
*/
|
||||
private String getClientIP(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
// 如果是多个IP,取第一个
|
||||
if (ip != null && ip.contains(",")) {
|
||||
ip = ip.split(",")[0].trim();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ public class SysUserServiceImpl implements SysUserService {
|
||||
return ResultDomain.failure(MSG_PAGE_PARAM_REQUIRED);
|
||||
}
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
TbSysUserDTO filter = SysUserVO.toFilter(pageRequest.getFilter());
|
||||
TbSysUserDTO filter = SysUserVO.toDTO(pageRequest.getFilter());
|
||||
int total = userMapper.getUserCount(filter);
|
||||
pageParam.setTotal(total);
|
||||
pageParam.setTotalPages(pageParam.getPageSize() == 0 ? 0 : (int) Math.ceil((double) total / pageParam.getPageSize()));
|
||||
|
||||
Reference in New Issue
Block a user