diff --git a/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java b/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java index 33d7f27e..939d2fc8 100644 --- a/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java +++ b/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java @@ -25,10 +25,13 @@ */ package org.springblade.auth.handler; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springblade.common.constant.TenantConstant; import org.springblade.core.launch.props.BladeProperties; +import org.springblade.core.log.logger.BladeLogger; import org.springblade.core.oauth2.exception.ExceptionCode; import org.springblade.core.oauth2.handler.AbstractAuthorizationHandler; import org.springblade.core.oauth2.props.OAuth2Properties; @@ -42,6 +45,10 @@ import org.springblade.core.tool.utils.DesUtil; import org.springblade.core.tool.utils.SM2Util; import org.springblade.system.cache.SysCache; import org.springblade.system.pojo.entity.Tenant; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import java.util.Date; import java.util.List; @@ -59,6 +66,8 @@ public class BladeAuthorizationHandler extends AbstractAuthorizationHandler { private final BladeTenantProperties tenantProperties; private final OAuth2Properties oAuth2Properties; private final BladeLockHandler lockHandler; + @Resource + private RedisTemplate redisTemplate; /** * 自定义弱密码列表 @@ -134,10 +143,36 @@ public class BladeAuthorizationHandler extends AbstractAuthorizationHandler { public void authSuccessful(OAuth2User user, OAuth2Request request) { // 处理认证成功,清空错误次数 lockHandler.handleAuthSuccess(user.getTenantId(), user.getAccount()); - + // 更新 Redis 登录统计 + updateUserLoginStats(user); log.info("用户:{},认证成功", user.getAccount()); } - + /** + * 更新用户登录统计信息到 Redis + */ + private void updateUserLoginStats(OAuth2User user) { + // 获取用户ID + String userId = user.getUserId(); + // 获取用户名 + String username = user.getName(); + String key = "user:login:" + userId; + // 获取登录IP + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + String loginIp = request.getRemoteAddr(); + // 获取部门ID(假设 user 对象提供 getDeptId 方法) + String deptId = user.getDeptId(); + // 获取 Redis 操作对象 + HashOperations hashOps = redisTemplate.opsForHash(); + // 1. 更新用户名(可选,若用户名可能变更) + hashOps.put(key, "username", username); + // 2. 存储登录IP + hashOps.put(key, "loginIp", loginIp); + // 3. 存储部门ID + hashOps.put(key, "deptId", deptId); + // 4. 更新最后一次登录时间 + long lastLoginTime = System.currentTimeMillis(); + hashOps.put(key, "lastLoginTime", lastLoginTime); + } /** * 认证失败回调 * diff --git a/blade-ops/blade-log/src/main/java/org/springblade/core/log/controller/LogApiController.java b/blade-ops/blade-log/src/main/java/org/springblade/core/log/controller/LogApiController.java index 5ba1d79f..0e09ccb6 100644 --- a/blade-ops/blade-log/src/main/java/org/springblade/core/log/controller/LogApiController.java +++ b/blade-ops/blade-log/src/main/java/org/springblade/core/log/controller/LogApiController.java @@ -30,6 +30,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import io.swagger.v3.oas.annotations.Parameter; import lombok.AllArgsConstructor; import org.springblade.core.log.model.LogApi; +import org.springblade.core.log.pojo.dto.UserLoginStatsDTO; import org.springblade.core.log.pojo.vo.LogApiVO; import org.springblade.core.log.service.ILogApiService; import org.springblade.core.log.wrapper.LogApiWrapper; @@ -38,12 +39,13 @@ import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.IsAdmin; import org.springblade.core.tenant.annotation.NonDS; import org.springblade.core.tool.api.R; +import org.springframework.data.redis.core.HashOperations; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.Map; +import java.util.*; /** * 控制器 @@ -77,4 +79,15 @@ public class LogApiController { return R.data(LogApiWrapper.build().pageVO(pages)); } + @IsAdmin + @GetMapping("/listUserLoginStatus") + public R> getUserLoginStats( + @RequestParam(required = false) String username, + @RequestParam(required = false) String deptId + ) { + // 获取所有 user:login:* 键 + List result = logService.getUserLoginStats(username, deptId); + return R.data(result); + } + } diff --git a/blade-ops/blade-log/src/main/java/org/springblade/core/log/pojo/dto/UserLoginStatsDTO.java b/blade-ops/blade-log/src/main/java/org/springblade/core/log/pojo/dto/UserLoginStatsDTO.java new file mode 100644 index 00000000..279eab47 --- /dev/null +++ b/blade-ops/blade-log/src/main/java/org/springblade/core/log/pojo/dto/UserLoginStatsDTO.java @@ -0,0 +1,39 @@ +package org.springblade.core.log.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author 石玖洲 + * @Description + * @create 2026-02-27 14:15 + */ +@Data +@AllArgsConstructor +public class UserLoginStatsDTO { + + /** + * 用户ID + */ + private String userId; + + /** + * 用户名 + */ + private String username; + + /** + * 登录IP + */ + private String loginIp; + + /** + * 部门ID + */ + private String deptId; + + /** + * 最后一次登录时间 + */ + private Long lastLoginTime; +} diff --git a/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/ILogApiService.java b/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/ILogApiService.java index 08cf39a8..95d79b4c 100644 --- a/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/ILogApiService.java +++ b/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/ILogApiService.java @@ -27,6 +27,9 @@ package org.springblade.core.log.service; import com.baomidou.mybatisplus.extension.service.IService; import org.springblade.core.log.model.LogApi; +import org.springblade.core.log.pojo.dto.UserLoginStatsDTO; + +import java.util.List; /** * 服务类 @@ -35,4 +38,11 @@ import org.springblade.core.log.model.LogApi; */ public interface ILogApiService extends IService { + /** + * 获取用户登录状态 + * @param username 用户名 + * @param deptId 部门ID + * @return 用户登录状态列表 + */ + List getUserLoginStats(String username, String deptId); } diff --git a/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/impl/LogApiServiceImpl.java b/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/impl/LogApiServiceImpl.java index c3c0de68..407f230e 100644 --- a/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/impl/LogApiServiceImpl.java +++ b/blade-ops/blade-log/src/main/java/org/springblade/core/log/service/impl/LogApiServiceImpl.java @@ -26,11 +26,19 @@ package org.springblade.core.log.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.annotation.Resource; +import org.springblade.core.log.controller.LogApiController; import org.springblade.core.log.mapper.LogApiMapper; import org.springblade.core.log.model.LogApi; +import org.springblade.core.log.pojo.dto.UserLoginStatsDTO; import org.springblade.core.log.service.ILogApiService; +import org.springblade.core.tool.api.R; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import java.util.*; + /** * 服务实现类 * @@ -39,5 +47,39 @@ import org.springframework.stereotype.Service; @Service public class LogApiServiceImpl extends ServiceImpl implements ILogApiService { + @Resource + private RedisTemplate redisTemplate; + @Override + public List getUserLoginStats(String username, String deptId) { + Set keys = redisTemplate.keys("user:login:*"); + if (keys == null || keys.isEmpty()) { + new ArrayList<>(); + } + // 获取用户登录信息(带筛选) + List result = new ArrayList<>(); + for (String key : Objects.requireNonNull(keys)) { + HashOperations hashOps = redisTemplate.opsForHash(); + String userId = key.replace("user:login:", ""); + String currentUsername = (String) hashOps.get(key, "username"); + String currentDeptId = (String) hashOps.get(key, "deptId"); + // 过滤条件:根据 username 和 deptId 筛选 + boolean matchUsername = username == null || currentUsername != null && currentUsername.contains(username); + boolean matchDept = deptId == null || currentDeptId != null && currentDeptId.equals(deptId); + if (matchUsername && matchDept) { + String loginIp = (String) hashOps.get(key, "loginIp"); + Long lastLoginTime = (Long) hashOps.get(key, "lastLoginTime"); + if (currentUsername != null && loginIp != null && lastLoginTime != null) { + result.add(new UserLoginStatsDTO( + userId, + currentUsername, + loginIp, + currentDeptId, + lastLoginTime + )); + } + } + } + return result; + } }