diff --git a/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/SysUserServiceApi.java b/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/SysUserServiceApi.java
index 31d1425eb..91ac24567 100644
--- a/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/SysUserServiceApi.java
+++ b/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/SysUserServiceApi.java
@@ -24,6 +24,7 @@
*/
package cn.stylefeng.roses.kernel.sys.api;
+import cn.stylefeng.roses.kernel.sys.api.pojo.user.OnlineUserItem;
import cn.stylefeng.roses.kernel.sys.api.pojo.user.SimpleUserDTO;
import cn.stylefeng.roses.kernel.sys.api.pojo.user.UserValidateDTO;
@@ -94,4 +95,26 @@ public interface SysUserServiceApi {
*/
Boolean userExist(Long userId);
+ /**
+ * 获取用户的账号和姓名信息
+ *
+ * 一般用在获取在线用户列表
+ *
+ * @author fengshuonan
+ * @since 2023/7/2 13:22
+ */
+ OnlineUserItem getUserNameAccountInfo(Long userId);
+
+ /**
+ * 获取用户账号和姓名信息
+ *
+ * 一般用在获取在线用户列表
+ *
+ * @param onlineUserItems 查询条件,在此用户id列表中查询
+ * @param searchText 查询条件,查询账号或姓名包含此字符串的结果
+ * @author fengshuonan
+ * @since 2023/7/2 13:36
+ */
+ List getUserNameAccountInfoListByCondition(List onlineUserItems, String searchText);
+
}
diff --git a/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/exception/enums/UserExceptionEnum.java b/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/exception/enums/UserExceptionEnum.java
index 6ed2e368e..4c37fe432 100644
--- a/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/exception/enums/UserExceptionEnum.java
+++ b/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/exception/enums/UserExceptionEnum.java
@@ -26,7 +26,12 @@ public enum UserExceptionEnum implements AbstractExceptionEnum {
/**
* 无法操作,只有超级管理员可以重置密码!
*/
- RESET_PASSWORD_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + "10003", "无法操作,只有超级管理员可以重置密码!");
+ RESET_PASSWORD_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + "10003", "无法操作,只有超级管理员可以重置密码!"),
+
+ /**
+ * 无法操作,只有超级管理员可以踢下线用户
+ */
+ KICK_OFF_ERROR(RuleConstants.USER_OPERATION_ERROR_TYPE_CODE + "10004", "无法操作,只有超级管理员可以踢下线用户");
/**
* 错误编码
diff --git a/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/pojo/user/OnlineUserItem.java b/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/pojo/user/OnlineUserItem.java
new file mode 100644
index 000000000..033851ae5
--- /dev/null
+++ b/kernel-s-sys/sys-api/src/main/java/cn/stylefeng/roses/kernel/sys/api/pojo/user/OnlineUserItem.java
@@ -0,0 +1,72 @@
+package cn.stylefeng.roses.kernel.sys.api.pojo.user;
+
+import cn.stylefeng.roses.kernel.rule.annotation.ChineseDescription;
+import cn.stylefeng.roses.kernel.rule.pojo.request.BaseRequest;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotBlank;
+import java.util.Date;
+
+/**
+ * 用在响应获取在线用户列表
+ *
+ * @author fengshuonan
+ * @since 2023/7/2 11:30
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class OnlineUserItem extends BaseRequest {
+
+ /**
+ * 用户主键id
+ */
+ @ChineseDescription("用户主键id")
+ private Long userId;
+
+ /**
+ * 用户token
+ */
+ @ChineseDescription("用户token")
+ @NotBlank(message = "用户token不能为空", groups = offlineUser.class)
+ private String token;
+
+ /**
+ * 真实姓名
+ */
+ @ChineseDescription("真实姓名")
+ private String realName;
+
+ /**
+ * 账号
+ */
+ @ChineseDescription("账号")
+ private String account;
+
+ /**
+ * 登录时的ip
+ */
+ @ChineseDescription("登录时的ip")
+ private String loginIp;
+
+ /**
+ * 登录时间
+ */
+ @ChineseDescription("登录时间")
+ private Date loginTime;
+
+ /**
+ * 参数校验分组:踢下线某个token
+ */
+ public @interface offlineUser {
+ }
+
+ public OnlineUserItem() {
+ }
+
+ public OnlineUserItem(Long userId, String realName, String account) {
+ this.userId = userId;
+ this.realName = realName;
+ this.account = account;
+ }
+}
diff --git a/kernel-s-sys/sys-business-hr/src/main/java/cn/stylefeng/roses/kernel/sys/modular/user/service/impl/SysUserServiceImpl.java b/kernel-s-sys/sys-business-hr/src/main/java/cn/stylefeng/roses/kernel/sys/modular/user/service/impl/SysUserServiceImpl.java
index 7bd760f34..e792ebe53 100644
--- a/kernel-s-sys/sys-business-hr/src/main/java/cn/stylefeng/roses/kernel/sys/modular/user/service/impl/SysUserServiceImpl.java
+++ b/kernel-s-sys/sys-business-hr/src/main/java/cn/stylefeng/roses/kernel/sys/modular/user/service/impl/SysUserServiceImpl.java
@@ -21,6 +21,7 @@ import cn.stylefeng.roses.kernel.sys.api.constants.SysConstants;
import cn.stylefeng.roses.kernel.sys.api.enums.user.UserStatusEnum;
import cn.stylefeng.roses.kernel.sys.api.exception.enums.UserExceptionEnum;
import cn.stylefeng.roses.kernel.sys.api.expander.SysConfigExpander;
+import cn.stylefeng.roses.kernel.sys.api.pojo.user.OnlineUserItem;
import cn.stylefeng.roses.kernel.sys.api.pojo.user.SimpleUserDTO;
import cn.stylefeng.roses.kernel.sys.api.pojo.user.UserOrgDTO;
import cn.stylefeng.roses.kernel.sys.api.pojo.user.UserValidateDTO;
@@ -41,10 +42,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.stream.Collectors;
/**
@@ -438,6 +436,57 @@ public class SysUserServiceImpl extends ServiceImpl impl
return count > 0;
}
+ @Override
+ public OnlineUserItem getUserNameAccountInfo(Long userId) {
+
+ if (userId == null) {
+ return new OnlineUserItem();
+ }
+
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(SysUser::getUserId, userId);
+ wrapper.select(SysUser::getRealName, SysUser::getAccount);
+ SysUser sysUser = this.getOne(wrapper, false);
+
+ return new OnlineUserItem(userId, sysUser.getRealName(), sysUser.getAccount());
+ }
+
+ @Override
+ public List getUserNameAccountInfoListByCondition(List onlineUserItems, String searchText) {
+
+ if (ObjectUtil.isEmpty(onlineUserItems) || ObjectUtil.isEmpty(searchText)) {
+ return new ArrayList<>();
+ }
+
+ // 在线用户列表的id集合
+ Set userIdList = onlineUserItems.stream().map(OnlineUserItem::getUserId).collect(Collectors.toSet());
+
+ // 在这些id集合和查询条件中筛选符合条件的用户,并组装上他们的姓名和账号
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.in(SysUser::getUserId, userIdList);
+ wrapper.nested(wrap -> {
+ wrap.like(SysUser::getRealName, searchText).or().like(SysUser::getAccount, searchText);
+ });
+ wrapper.select(SysUser::getUserId, SysUser::getRealName, SysUser::getAccount);
+ List sysUserList = this.list(wrapper);
+
+ List resultList = new ArrayList<>();
+ if (ObjectUtil.isEmpty(sysUserList)) {
+ return resultList;
+ }
+
+ Map userMap = sysUserList.stream().collect(Collectors.toMap(SysUser::getUserId, item -> item));
+
+ // 从在线用户中查找包含这些key的元素
+ for (OnlineUserItem onlineUserItem : onlineUserItems) {
+ if (userMap.containsKey(onlineUserItem.getUserId())) {
+ resultList.add(onlineUserItem);
+ }
+ }
+
+ return resultList;
+ }
+
/**
* 获取信息
*
diff --git a/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/controller/OnlineUserController.java b/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/controller/OnlineUserController.java
new file mode 100644
index 000000000..0ad426988
--- /dev/null
+++ b/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/controller/OnlineUserController.java
@@ -0,0 +1,58 @@
+package cn.stylefeng.roses.kernel.sys.modular.login.controller;
+
+import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData;
+import cn.stylefeng.roses.kernel.rule.pojo.response.SuccessResponseData;
+import cn.stylefeng.roses.kernel.scanner.api.annotation.ApiResource;
+import cn.stylefeng.roses.kernel.scanner.api.annotation.GetResource;
+import cn.stylefeng.roses.kernel.sys.api.pojo.user.OnlineUserItem;
+import cn.stylefeng.roses.kernel.sys.modular.login.pojo.OnlineUserResult;
+import cn.stylefeng.roses.kernel.sys.modular.login.service.OnlineUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * 获取用户在线信息的接口
+ *
+ * @author fengshuonan
+ * @since 2023/7/2 11:25
+ */
+@RestController
+@Slf4j
+@ApiResource(name = "获取用户在线信息的接口")
+public class OnlineUserController {
+
+ @Resource
+ private OnlineUserService onlineUserService;
+
+ /**
+ * 获取在线用户列表
+ *
+ * @param searchText 查询条件,可以根据用户姓名和账号进行查询
+ * @author fengshuonan
+ * @since 2023/7/2 11:26
+ */
+ @GetResource(name = "获取在线用户列表", path = "/getOnlineUserList")
+ public ResponseData getOnlineUserList(@RequestParam(value = "searchText", required = false) String searchText) {
+ OnlineUserResult result = onlineUserService.getOnlineUserList(searchText);
+ return new SuccessResponseData<>(result);
+ }
+
+ /**
+ * 踢掉在线的某个人token
+ *
+ * @param onlineUserInfo 请求参数
+ * @author fengshuonan
+ * @since 2023/7/2 11:26
+ */
+ @GetResource(name = "获取在线用户列表", path = "/offlineUser")
+ public ResponseData> offlineUser(@RequestBody @Validated(OnlineUserItem.offlineUser.class) OnlineUserItem onlineUserInfo) {
+ onlineUserService.offlineUser(onlineUserInfo);
+ return new SuccessResponseData<>();
+ }
+
+}
diff --git a/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/pojo/OnlineUserResult.java b/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/pojo/OnlineUserResult.java
new file mode 100644
index 000000000..71d65c7a8
--- /dev/null
+++ b/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/pojo/OnlineUserResult.java
@@ -0,0 +1,29 @@
+package cn.stylefeng.roses.kernel.sys.modular.login.pojo;
+
+import cn.stylefeng.roses.kernel.rule.annotation.ChineseDescription;
+import cn.stylefeng.roses.kernel.sys.api.pojo.user.OnlineUserItem;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 在线用户的返回结果
+ *
+ * @author fengshuonan
+ * @since 2023/7/2 12:29
+ */
+@Data
+public class OnlineUserResult {
+
+ /**
+ * 总的在线用户数
+ */
+ private Integer totalUserCount = 0;
+
+ /**
+ * 用户在线列表人数
+ */
+ @ChineseDescription("用户在线列表人数")
+ private List onlineUserList;
+
+}
diff --git a/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/service/OnlineUserService.java b/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/service/OnlineUserService.java
new file mode 100644
index 000000000..b96256b66
--- /dev/null
+++ b/kernel-s-sys/sys-business-permission/src/main/java/cn/stylefeng/roses/kernel/sys/modular/login/service/OnlineUserService.java
@@ -0,0 +1,104 @@
+package cn.stylefeng.roses.kernel.sys.modular.login.service;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.stylefeng.roses.kernel.auth.api.SessionManagerApi;
+import cn.stylefeng.roses.kernel.auth.api.context.LoginContext;
+import cn.stylefeng.roses.kernel.auth.api.pojo.login.LoginUser;
+import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
+import cn.stylefeng.roses.kernel.sys.api.SysUserServiceApi;
+import cn.stylefeng.roses.kernel.sys.api.exception.enums.UserExceptionEnum;
+import cn.stylefeng.roses.kernel.sys.api.pojo.user.OnlineUserItem;
+import cn.stylefeng.roses.kernel.sys.modular.login.pojo.OnlineUserResult;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 获取用户在线登录信息业务
+ *
+ * @author fengshuonan
+ * @since 2023/7/2 11:25
+ */
+@Service
+public class OnlineUserService {
+
+ @Resource
+ private SessionManagerApi sessionManagerApi;
+
+ @Resource
+ private SysUserServiceApi sysUserServiceApi;
+
+ /**
+ * 获取当前在线用户列表
+ *
+ * 可以根据用户名称和账号进行查询
+ *
+ * @author fengshuonan
+ * @since 2023/7/2 12:09
+ */
+ public OnlineUserResult getOnlineUserList(String searchText) {
+
+ OnlineUserResult onlineUserResult = new OnlineUserResult();
+
+ List loginUsers = sessionManagerApi.onlineUserList();
+ if (ObjectUtil.isEmpty(loginUsers)) {
+ return new OnlineUserResult();
+ }
+
+ // 返回总的在线人数
+ onlineUserResult.setTotalUserCount(loginUsers.size());
+
+ // 转化为用户的在线信息
+ List onlineUserInfos = new ArrayList<>();
+ for (LoginUser loginUser : loginUsers) {
+ OnlineUserItem onlineUserInfo = new OnlineUserItem();
+ onlineUserInfo.setUserId(loginUser.getUserId());
+ onlineUserInfo.setToken(loginUser.getToken());
+ onlineUserInfos.add(onlineUserInfo);
+ }
+
+ // 如果没传查询条件,只返回前10条
+ if (StrUtil.isBlank(searchText)) {
+ if (onlineUserInfos.size() > 10) {
+ onlineUserInfos = onlineUserInfos.subList(0, 9);
+ }
+
+ // 用户信息补充姓名和账号返回
+ for (OnlineUserItem onlineUserInfo : onlineUserInfos) {
+ OnlineUserItem userNameAccountInfo = sysUserServiceApi.getUserNameAccountInfo(onlineUserInfo.getUserId());
+ onlineUserInfo.setAccount(userNameAccountInfo.getAccount());
+ onlineUserInfo.setRealName(userNameAccountInfo.getRealName());
+ }
+
+ onlineUserResult.setOnlineUserList(onlineUserInfos);
+ return onlineUserResult;
+ }
+
+ // 如果传递了查询条件,则从在线用户id和指定查询条件中筛选出来结果
+ else {
+ List resultUserList = sysUserServiceApi.getUserNameAccountInfoListByCondition(onlineUserInfos, searchText);
+ onlineUserResult.setOnlineUserList(resultUserList);
+ return onlineUserResult;
+ }
+ }
+
+ /**
+ * 踢下线某个用户,根据参数中的token
+ *
+ * @author fengshuonan
+ * @since 2023/7/2 12:23
+ */
+ public void offlineUser(OnlineUserItem onlineUserInfo) throws ServiceException {
+
+ if (!LoginContext.me().getSuperAdminFlag()) {
+ throw new ServiceException(UserExceptionEnum.KICK_OFF_ERROR);
+ }
+
+ sessionManagerApi.removeSession(onlineUserInfo.getToken());
+
+ }
+
+}