diff --git a/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/vo/SysUserVO.java b/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/vo/SysUserVO.java index e851e92f..fdcd944b 100644 --- a/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/vo/SysUserVO.java +++ b/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/vo/SysUserVO.java @@ -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; } } diff --git a/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java index 3da1a77e..d8a41dd9 100644 --- a/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java +++ b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/controller/AuthController.java @@ -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 验证码 + * @description 获取验证码(统一接口,支持多种类型) + * @param loginParam 登录参数(包含验证码类型) + * @return ResultDomain 验证码结果 * @author yslg - * @since 2025-09-28 + * @since 2025-12-09 */ - @GetMapping("/captcha") - public ResultDomain getCaptcha() { - // TODO: 实现验证码生成逻辑 - // 生成验证码会话ID,用于验证时匹配 - String captchaId = IDUtils.generateID(); - String captchaData = captchaId + ":captcha-placeholder"; // 格式: ID:验证码内容 - - return ResultDomain.success("验证码获取成功", captchaData); + @PostMapping("/captcha") + public ResultDomain getCaptcha(@RequestBody LoginParam loginParam) { + return authService.getCaptcha(loginParam); } /** * @description 刷新令牌 - * @param token 原令牌 - * @return ResultDomain 新令牌 + * @param request HTTP请求 + * @return ResultDomain 新的登录信息 * @author yslg - * @since 2025-09-28 + * @since 2025-12-09 */ @PostMapping("/refresh") - public ResultDomain refreshToken(@RequestHeader("Authorization") String token) { - // TODO: 实现令牌刷新逻辑 - // 为新令牌生成唯一ID - String newTokenId = IDUtils.generateID(); - String newToken = "new-token-" + newTokenId; // 临时占位符 - - return ResultDomain.success("令牌刷新成功", newToken); + public ResultDomain refreshToken(HttpServletRequest request) { + return authService.refreshToken(request); } /** @@ -121,116 +105,37 @@ public class AuthController { /** * @description 发送邮箱验证码 * @param requestBody 包含email字段的请求体 - * @return ResultDomain 发送结果 + * @return ResultDomain 发送结果 * @author yslg - * @since 2025-11-03 + * @since 2025-12-09 */ @PostMapping("/send-email-code") - public ResultDomain> sendEmailCode(@RequestBody Map requestBody) { + public ResultDomain sendEmailCode(@RequestBody Map 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 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 发送结果 + * @return ResultDomain 发送结果 * @author yslg - * @since 2025-11-03 + * @since 2025-12-09 */ @PostMapping("/send-sms-code") - public ResultDomain> sendSmsCode(@RequestBody Map requestBody) { + public ResultDomain sendSmsCode(@RequestBody Map 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 data = Map.of( - "sessionId", sessionId, - "message", "验证码已发送" - ); - - logger.info("短信验证码已发送,手机号: {}, sessionId: {}", phone, sessionId); - return ResultDomain.success("验证码已发送", data); - } else { - return ResultDomain.failure("验证码发送失败,请稍后重试"); - } + return authService.getCaptcha(loginParam); } /** diff --git a/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/enums/UserStatus.java b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/enums/UserStatus.java new file mode 100644 index 00000000..56e0547e --- /dev/null +++ b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/enums/UserStatus.java @@ -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; + } +} diff --git a/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java index cd6876ef..c6d969f5 100644 --- a/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java +++ b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java @@ -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 getCaptcha(LoginParam arg0) { - // TODO Auto-generated method stub - return null; + public ResultDomain 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 getLoginByToken(String arg0) { - // TODO Auto-generated method stub - return null; + public ResultDomain 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 isTokenValid(String arg0) { - // TODO Auto-generated method stub - return null; + public ResultDomain 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 login(LoginParam arg0, HttpServletRequest arg1) { - // TODO Auto-generated method stub - return null; + public ResultDomain 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 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 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 userRoles = new ArrayList<>(); + List userDepts = new ArrayList<>(); + + ResultDomain 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 userPermissions = new ArrayList<>(); + List userViews = new ArrayList<>(); + + ResultDomain 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 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 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 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 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; + } + } diff --git a/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysUserServiceImpl.java b/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysUserServiceImpl.java index 612fcdd9..d0a56ad5 100644 --- a/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysUserServiceImpl.java +++ b/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysUserServiceImpl.java @@ -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()));