auth更新

This commit is contained in:
2025-12-09 18:43:56 +08:00
parent b59babed0a
commit 23afb90cbe
5 changed files with 604 additions and 147 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
/**

View File

@@ -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;
}
}

View File

@@ -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
return null;
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());
}
}
@Override
public ResultDomain<LoginDomain> logout(HttpServletRequest arg0) {
// TODO Auto-generated method stub
return null;
/**
* 构建完整的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;
}
}
@Override
public ResultDomain<LoginDomain> refreshToken(HttpServletRequest arg0) {
// TODO Auto-generated method stub
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> 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> 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;
}
}

View File

@@ -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()));