diff --git a/http-client.env.json b/http-client.env.json index ebe70d1e..f1d7f632 100644 --- a/http-client.env.json +++ b/http-client.env.json @@ -1,7 +1,8 @@ { "local": { "baseUrl": "http://127.0.0.1:9200/orion-api", - "token": "Bearer YQJ3IpwJJv5HujIWY6ZTNDgUxXRY6aDt" + "token": "Bearer YQJ3IpwJJv5HujIWY6ZTNDgUxXRY6aDt", + "timestamp": 1689577685914 }, "gateway": { "baseUrl": "http://127.0.0.1:9200/orion-api", diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorCode.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorCode.java index 83d148a2..b2445c31 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorCode.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorCode.java @@ -46,11 +46,11 @@ public enum ErrorCode implements CodeInfo { // -------------------- 自定义 - 业务 -------------------- - OTHER_DEVICE_LOGIN(700, "该账号于 {} 已在其他设备登录 {}({})"), + USER_DISABLED(700, "当前用户已禁用"), - USER_DISABLED(701, "当前用户已禁用"), + USER_LOCKED(701, "当前用户已被锁定"), - USER_LOCKED(702, "当前用户已被锁定"), + OTHER_DEVICE_LOGIN(702, "该账号于 {} 已在其他设备登录 {}({})"), // -------------------- 自定义 - 通用 -------------------- diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/security/LoginUser.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/security/LoginUser.java index df8e1fc5..8249ce41 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/security/LoginUser.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/security/LoginUser.java @@ -31,6 +31,9 @@ public class LoginUser { @Schema(description = "头像地址") private String avatar; + @Schema(description = "登录时间戳") + private Long timestamp; + @Schema(description = "角色") private List roles; diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisStrings.java b/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisStrings.java index b5409032..de2ff412 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisStrings.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-redis/src/main/java/com/orion/ops/framework/redis/core/utils/RedisStrings.java @@ -7,6 +7,7 @@ import com.orion.lang.define.cache.CacheKeyDefine; import com.orion.lang.utils.Strings; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -83,7 +84,7 @@ public class RedisStrings extends RedisUtils { * @param keys keys * @return cache */ - public static List getJsonList(List keys) { + public static List getJsonList(Collection keys) { List values = redisTemplate.opsForValue().multiGet(keys); if (values == null) { return new ArrayList<>(); @@ -101,7 +102,7 @@ public class RedisStrings extends RedisUtils { * @param T * @return cache */ - public static List getJsonList(List keys, CacheKeyDefine define) { + public static List getJsonList(Collection keys, CacheKeyDefine define) { return getJsonList(keys, (Class) define.getType()); } @@ -113,7 +114,7 @@ public class RedisStrings extends RedisUtils { * @param T * @return cache */ - public static List getJsonList(List keys, Class type) { + public static List getJsonList(Collection keys, Class type) { List values = redisTemplate.opsForValue().multiGet(keys); if (values == null) { return new ArrayList<>(); @@ -182,7 +183,7 @@ public class RedisStrings extends RedisUtils { * @param keys keys * @return cache */ - public static List getJsonArrayList(List keys) { + public static List getJsonArrayList(Collection keys) { List values = redisTemplate.opsForValue().multiGet(keys); if (values == null) { return new ArrayList<>(); @@ -200,7 +201,7 @@ public class RedisStrings extends RedisUtils { * @param T * @return cache */ - public static List> getJsonArrayList(List keys, CacheKeyDefine define) { + public static List> getJsonArrayList(Collection keys, CacheKeyDefine define) { return getJsonArrayList(keys, (Class) define.getType()); } @@ -212,7 +213,7 @@ public class RedisStrings extends RedisUtils { * @param T * @return cache */ - public static List> getJsonArrayList(List keys, Class type) { + public static List> getJsonArrayList(Collection keys, Class type) { List values = redisTemplate.opsForValue().multiGet(keys); if (values == null) { return new ArrayList<>(); diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-security/src/main/java/com/orion/ops/framework/security/core/utils/SecurityUtils.java b/orion-ops-framework/orion-ops-spring-boot-starter-security/src/main/java/com/orion/ops/framework/security/core/utils/SecurityUtils.java index b3b4f1f9..b3cd670e 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-security/src/main/java/com/orion/ops/framework/security/core/utils/SecurityUtils.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-security/src/main/java/com/orion/ops/framework/security/core/utils/SecurityUtils.java @@ -88,6 +88,16 @@ public class SecurityUtils { return loginUser != null ? loginUser.getUsername() : null; } + /** + * 获取当前 timestamp + * + * @return timestamp + */ + public static Long getLoginTimestamp() { + LoginUser loginUser = getLoginUser(); + return loginUser != null ? loginUser.getTimestamp() : null; + } + /** * 设置当前用户 * diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.http b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.http index 6f7f1538..e6f62019 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.http +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.http @@ -30,4 +30,19 @@ Authorization: {{token}} GET {{baseUrl}}/infra/mine/login-history Authorization: {{token}} + +### 获取当前用户会话列表 +GET {{baseUrl}}/infra/mine/user-session +Authorization: {{token}} + + +### 下线当前用户会话 +PUT {{baseUrl}}/infra/mine/offline-session +Content-Type: application/json +Authorization: {{token}} + +{ + "timestamp": 1698774195296 +} + ### \ No newline at end of file diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.java index 0338e4c4..3691735a 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/MineController.java @@ -6,10 +6,12 @@ import com.orion.ops.framework.log.core.annotation.IgnoreLog; import com.orion.ops.framework.log.core.enums.IgnoreLogMode; import com.orion.ops.framework.web.core.annotation.RestWrapper; import com.orion.ops.module.infra.define.operator.AuthenticationOperatorType; +import com.orion.ops.module.infra.entity.request.user.OfflineUserSessionRequest; import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest; import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest; import com.orion.ops.module.infra.entity.vo.LoginHistoryVO; import com.orion.ops.module.infra.entity.vo.SystemUserVO; +import com.orion.ops.module.infra.entity.vo.UserSessionVO; import com.orion.ops.module.infra.service.MineService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -67,4 +69,22 @@ public class MineController { return mineService.getCurrentLoginHistory(); } + @IgnoreLog(IgnoreLogMode.RET) + @GetMapping("/user-session") + @Operation(summary = "获取当前用户会话列表") + public List getCurrentUserSessionList() { + return mineService.getCurrentUserSessionList(); + } + + @IgnoreLog(IgnoreLogMode.RET) + @PutMapping("/offline-session") + @Operation(summary = "下线当前用户会话") + public HttpWrapper offlineCurrentUserSession(@Validated @RequestBody OfflineUserSessionRequest request) { + mineService.offlineCurrentUserSession(request); + return HttpWrapper.ok(); + } + + // fixme 全部用户接口进行 设置缓存 + // fixme 操作日志 + } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/SystemUserController.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/SystemUserController.java index 18812203..e3c6ce67 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/SystemUserController.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/controller/SystemUserController.java @@ -11,6 +11,7 @@ import com.orion.ops.framework.web.core.annotation.RestWrapper; import com.orion.ops.module.infra.define.operator.SystemUserOperatorType; import com.orion.ops.module.infra.entity.request.user.*; import com.orion.ops.module.infra.entity.vo.SystemUserVO; +import com.orion.ops.module.infra.entity.vo.UserSessionVO; import com.orion.ops.module.infra.service.SystemUserRoleService; import com.orion.ops.module.infra.service.SystemUserService; import io.swagger.v3.oas.annotations.Operation; @@ -137,5 +138,22 @@ public class SystemUserController { return systemUserService.deleteSystemUserById(id); } + // fixme 权限配置 + @IgnoreLog(IgnoreLogMode.RET) + @GetMapping("/user-session") + @Operation(summary = "获取用户会话列表") + public List getUserSessionList(@RequestParam("id") Long id) { + return systemUserService.getUserSessionList(id); + } + + // fixme 权限配置 + @IgnoreLog(IgnoreLogMode.RET) + @PutMapping("/offline-session") + @Operation(summary = "下线用户会话") + public HttpWrapper offlineUserSession(@Validated @RequestBody OfflineUserSessionRequest request) { + systemUserService.offlineUserSession(request); + return HttpWrapper.ok(); + } + } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenDTO.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenDTO.java index 233ef8ee..8f441d1f 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenDTO.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenDTO.java @@ -38,41 +38,11 @@ public class LoginTokenDTO { /** * 原始登录身份 */ - private Identity origin; + private LoginTokenIdentityDTO origin; /** * 覆盖登录身份 */ - private Identity override; - - /** - * 身份信息 - */ - @Data - @NoArgsConstructor - @AllArgsConstructor - public static class Identity { - - /** - * 原始登录时间 - */ - private Long loginTime; - - /** - * 当前设备登录地址 - */ - private String address; - - /** - * 当前设备登录地址 - */ - private String location; - - /** - * 当前设备 userAgent - */ - private String userAgent; - - } + private LoginTokenIdentityDTO override; } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenIdentityDTO.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenIdentityDTO.java new file mode 100644 index 00000000..fd3b0b4e --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/dto/LoginTokenIdentityDTO.java @@ -0,0 +1,39 @@ +package com.orion.ops.module.infra.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 身份信息 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/11/1 1:01 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginTokenIdentityDTO { + + /** + * 原始登录时间 + */ + private Long loginTime; + + /** + * 当前设备登录地址 + */ + private String address; + + /** + * 当前设备登录地址 + */ + private String location; + + /** + * 当前设备 userAgent + */ + private String userAgent; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/user/OfflineUserSessionRequest.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/user/OfflineUserSessionRequest.java new file mode 100644 index 00000000..c276777b --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/user/OfflineUserSessionRequest.java @@ -0,0 +1,26 @@ +package com.orion.ops.module.infra.entity.request.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 用户下线请求 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/7/17 12:19 + */ +@Data +@Schema(name = "OfflineUserSessionRequest", description = "用户下线请求") +public class OfflineUserSessionRequest { + + @Schema(description = "userId") + private Long userId; + + @NotNull + @Schema(description = "时间戳") + private Long timestamp; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/user/SystemUserUpdateRequest.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/user/SystemUserUpdateRequest.java index be72d83f..2b0dc7aa 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/user/SystemUserUpdateRequest.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/request/user/SystemUserUpdateRequest.java @@ -7,7 +7,6 @@ import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.io.Serializable; @@ -25,7 +24,6 @@ import java.io.Serializable; @Schema(name = "SystemUserUpdateRequest", description = "用户 更新请求对象") public class SystemUserUpdateRequest implements Serializable { - @NotNull @Schema(description = "id") private Long id; diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/UserSessionVO.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/UserSessionVO.java new file mode 100644 index 00000000..7ff8b398 --- /dev/null +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/entity/vo/UserSessionVO.java @@ -0,0 +1,43 @@ +package com.orion.ops.module.infra.entity.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +/** + * 用户 会话响应对象 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023-7-13 18:42 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "UserSessionVO", description = "用户 会话响应对象") +public class UserSessionVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "是否为当前会话") + private Boolean current; + + @Schema(description = "请求ip") + private String address; + + @Schema(description = "请求地址") + private String location; + + @Schema(description = "userAgent") + private String userAgent; + + @Schema(description = "登录时间") + private Date loginTime; + +} diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/framework/service/impl/SecurityFrameworkServiceImpl.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/framework/service/impl/SecurityFrameworkServiceImpl.java index 069b21e0..ed637ecb 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/framework/service/impl/SecurityFrameworkServiceImpl.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/framework/service/impl/SecurityFrameworkServiceImpl.java @@ -3,8 +3,11 @@ package com.orion.ops.module.infra.framework.service.impl; import com.orion.lang.utils.time.Dates; import com.orion.ops.framework.common.constant.ErrorCode; import com.orion.ops.framework.common.security.LoginUser; +import com.orion.ops.framework.redis.core.utils.RedisUtils; import com.orion.ops.framework.security.core.service.SecurityFrameworkService; +import com.orion.ops.module.infra.define.cache.UserCacheKeyDefine; import com.orion.ops.module.infra.entity.dto.LoginTokenDTO; +import com.orion.ops.module.infra.entity.dto.LoginTokenIdentityDTO; import com.orion.ops.module.infra.enums.LoginTokenStatusEnum; import com.orion.ops.module.infra.enums.UserStatusEnum; import com.orion.ops.module.infra.service.AuthenticationService; @@ -55,12 +58,21 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { if (tokenInfo == null) { return null; } - // 检查 token 状态 - this.checkTokenStatus(tokenInfo); + try { + // 检查 token 状态 + this.checkTokenStatus(tokenInfo); + } catch (Exception e) { + // token 失效则删除 + // fixme test + RedisUtils.delete(UserCacheKeyDefine.LOGIN_TOKEN.format(tokenInfo.getId(), tokenInfo.getOrigin().getLoginTime())); + throw e; + } // 获取登录信息 LoginUser user = authenticationService.getLoginUser(tokenInfo.getId()); // 检查用户状态 UserStatusEnum.checkUserStatus(user.getStatus()); + // 设置登录时间戳 + user.setTimestamp(tokenInfo.getOrigin().getLoginTime()); return user; } @@ -77,7 +89,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { } // 其他设备登录 if (LoginTokenStatusEnum.OTHER_DEVICE.getStatus().equals(tokenStatus)) { - LoginTokenDTO.Identity override = loginToken.getOverride(); + LoginTokenIdentityDTO override = loginToken.getOverride(); throw ErrorCode.OTHER_DEVICE_LOGIN.exception( Dates.format(new Date(override.getLoginTime()), Dates.MD_HM), override.getAddress(), diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/MineService.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/MineService.java index 2569ba80..449a31d1 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/MineService.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/MineService.java @@ -1,9 +1,11 @@ package com.orion.ops.module.infra.service; +import com.orion.ops.module.infra.entity.request.user.OfflineUserSessionRequest; import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest; import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest; import com.orion.ops.module.infra.entity.vo.LoginHistoryVO; import com.orion.ops.module.infra.entity.vo.SystemUserVO; +import com.orion.ops.module.infra.entity.vo.UserSessionVO; import java.util.List; @@ -45,4 +47,18 @@ public interface MineService { */ List getCurrentLoginHistory(); + /** + * 获取当前用户会话列表 + * + * @return 回话列表 + */ + List getCurrentUserSessionList(); + + /** + * 下线当前用户会话 + * + * @param request request + */ + void offlineCurrentUserSession(OfflineUserSessionRequest request); + } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/SystemUserService.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/SystemUserService.java index 7ad0e6f7..8c8b6649 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/SystemUserService.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/SystemUserService.java @@ -3,6 +3,7 @@ package com.orion.ops.module.infra.service; import com.orion.lang.define.wrapper.DataGrid; import com.orion.ops.module.infra.entity.request.user.*; import com.orion.ops.module.infra.entity.vo.SystemUserVO; +import com.orion.ops.module.infra.entity.vo.UserSessionVO; import java.util.List; @@ -84,4 +85,19 @@ public interface SystemUserService { */ void resetPassword(UserResetPasswordRequest request); + /** + * 获取用户会话列表 + * + * @param userId userId + * @return 回话列表 + */ + List getUserSessionList(Long userId); + + /** + * 下线用户会话 + * + * @param request request + */ + void offlineUserSession(OfflineUserSessionRequest request); + } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java index 2c257eef..e017c0ad 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/AuthenticationServiceImpl.java @@ -23,13 +23,13 @@ import com.orion.ops.module.infra.define.cache.UserCacheKeyDefine; import com.orion.ops.module.infra.entity.domain.SystemRoleDO; import com.orion.ops.module.infra.entity.domain.SystemUserDO; import com.orion.ops.module.infra.entity.dto.LoginTokenDTO; +import com.orion.ops.module.infra.entity.dto.LoginTokenIdentityDTO; import com.orion.ops.module.infra.entity.request.user.UserLoginRequest; import com.orion.ops.module.infra.entity.vo.UserLoginVO; import com.orion.ops.module.infra.enums.LoginTokenStatusEnum; import com.orion.ops.module.infra.enums.UserStatusEnum; import com.orion.ops.module.infra.service.AuthenticationService; import com.orion.ops.module.infra.service.PermissionService; -import com.orion.ops.module.infra.service.SystemUserService; import com.orion.web.servlet.web.Servlets; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -331,7 +331,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { for (LoginTokenDTO loginTokenInfo : loginTokenInfoList) { String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getOrigin().getLoginTime()); loginTokenInfo.setStatus(LoginTokenStatusEnum.OTHER_DEVICE.getStatus()); - loginTokenInfo.setOverride(new LoginTokenDTO.Identity(loginTime, remoteAddr, location, userAgent)); + loginTokenInfo.setOverride(new LoginTokenIdentityDTO(loginTime, remoteAddr, location, userAgent)); RedisStrings.setJson(deviceLoginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginTokenInfo); } } @@ -364,7 +364,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { .id(id) .status(LoginTokenStatusEnum.OK.getStatus()) .refreshCount(0) - .origin(new LoginTokenDTO.Identity(loginTime, remoteAddr, location, userAgent)) + .origin(new LoginTokenIdentityDTO(loginTime, remoteAddr, location, userAgent)) .build(); RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue); // 生成 refreshToken diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/MineServiceImpl.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/MineServiceImpl.java index 197efad3..73e84e60 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/MineServiceImpl.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/MineServiceImpl.java @@ -6,11 +6,13 @@ import com.orion.ops.framework.common.utils.Valid; import com.orion.ops.framework.security.core.utils.SecurityUtils; import com.orion.ops.module.infra.dao.SystemUserDAO; import com.orion.ops.module.infra.entity.domain.SystemUserDO; +import com.orion.ops.module.infra.entity.request.user.OfflineUserSessionRequest; import com.orion.ops.module.infra.entity.request.user.SystemUserUpdateRequest; import com.orion.ops.module.infra.entity.request.user.UserResetPasswordRequest; import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest; import com.orion.ops.module.infra.entity.vo.LoginHistoryVO; import com.orion.ops.module.infra.entity.vo.SystemUserVO; +import com.orion.ops.module.infra.entity.vo.UserSessionVO; import com.orion.ops.module.infra.service.MineService; import com.orion.ops.module.infra.service.OperatorLogService; import com.orion.ops.module.infra.service.SystemUserService; @@ -73,4 +75,15 @@ public class MineServiceImpl implements MineService { return operatorLogService.getLoginHistory(username); } + @Override + public List getCurrentUserSessionList() { + return systemUserService.getUserSessionList(SecurityUtils.getLoginUserId()); + } + + @Override + public void offlineCurrentUserSession(OfflineUserSessionRequest request) { + request.setUserId(SecurityUtils.getLoginUserId()); + systemUserService.offlineUserSession(request); + } + } diff --git a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java index 45b32fd2..4973cc36 100644 --- a/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java +++ b/orion-ops-module-infra/orion-ops-module-infra-service/src/main/java/com/orion/ops/module/infra/service/impl/SystemUserServiceImpl.java @@ -20,8 +20,10 @@ import com.orion.ops.module.infra.dao.SystemUserRoleDAO; import com.orion.ops.module.infra.define.cache.TipsCacheKeyDefine; import com.orion.ops.module.infra.define.cache.UserCacheKeyDefine; import com.orion.ops.module.infra.entity.domain.SystemUserDO; +import com.orion.ops.module.infra.entity.dto.LoginTokenDTO; import com.orion.ops.module.infra.entity.request.user.*; import com.orion.ops.module.infra.entity.vo.SystemUserVO; +import com.orion.ops.module.infra.entity.vo.UserSessionVO; import com.orion.ops.module.infra.enums.UserStatusEnum; import com.orion.ops.module.infra.service.AuthenticationService; import com.orion.ops.module.infra.service.FavoriteService; @@ -34,8 +36,11 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.util.Comparator; +import java.util.Date; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * 用户 服务实现类 @@ -242,6 +247,42 @@ public class SystemUserServiceImpl implements SystemUserService { } } + @Override + public List getUserSessionList(Long userId) { + // 扫描缓存 + Set keys = RedisStrings.scanKeys(UserCacheKeyDefine.LOGIN_TOKEN.format(userId, "*")); + if (Lists.isEmpty(keys)) { + return Lists.empty(); + } + // 查询缓存 + List tokens = RedisStrings.getJsonList(keys, UserCacheKeyDefine.LOGIN_TOKEN); + if (Lists.isEmpty(tokens)) { + return Lists.empty(); + } + // 返回 + return tokens.stream() + .map(LoginTokenDTO::getOrigin) + .map(s -> UserSessionVO.builder() + .current(s.getLoginTime().equals(SecurityUtils.getLoginTimestamp())) + .address(s.getAddress()) + .location(s.getLocation()) + .userAgent(s.getUserAgent()) + .loginTime(new Date(s.getLoginTime())) + .build()) + .sorted(Comparator.comparing(UserSessionVO::getLoginTime).reversed()) + .collect(Collectors.toList()); + } + + @Override + public void offlineUserSession(OfflineUserSessionRequest request) { + Long userId = Valid.notNull(request.getUserId()); + Long timestamp = request.getTimestamp(); + RedisStrings.delete( + UserCacheKeyDefine.LOGIN_TOKEN.format(userId, timestamp), + UserCacheKeyDefine.LOGIN_REFRESH.format(userId, request.getTimestamp()) + ); + } + /** * 检查用户名否存在 *