diff --git a/README.md b/README.md index a929c809..851b81ae 100644 --- a/README.md +++ b/README.md @@ -251,10 +251,11 @@ continew-admin │ ├─ views # 页面模板 │ │ ├─ login # 登录模块 │ │ ├─ monitor # 系统监控模块 - │ │ │ └─ log # 日志管理 - │ │ │ ├─ login # 登录日志 - │ │ │ ├─ operation # 操作日志 - │ │ │ └─ system # 系统日志 + │ │ │ ├─ log # 日志管理 + │ │ │ │ ├─ login # 登录日志 + │ │ │ │ ├─ operation # 操作日志 + │ │ │ │ └─ system # 系统日志 + │ │ │ └─ online # 在线用户 │ │ └─ system # 系统管理模块 │ │ └─ user # 用户模块 │ │ └─ center # 个人中心 diff --git a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/handler/GlobalExceptionHandler.java b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/handler/GlobalExceptionHandler.java index 749a091d..b7da3af1 100644 --- a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/handler/GlobalExceptionHandler.java +++ b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/handler/GlobalExceptionHandler.java @@ -169,7 +169,20 @@ public class GlobalExceptionHandler { @ExceptionHandler(NotLoginException.class) public R handleNotLoginException(NotLoginException e, HttpServletRequest request) { log.error("请求地址'{}',认证失败,无法访问系统资源", request.getRequestURI(), e); - String errorMsg = "登录状态已过期,请重新登录"; + + String errorMsg; + switch (e.getType()) { + case NotLoginException.KICK_OUT: + errorMsg = "您已被踢下线"; + break; + case NotLoginException.BE_REPLACED_MESSAGE: + errorMsg = "您已被顶下线"; + break; + default: + errorMsg = "登录状态已过期,请重新登录"; + break; + } + LogContextHolder.setErrorMsg(errorMsg); return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg); } diff --git a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java index 801b931d..368d0842 100644 --- a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java +++ b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/dto/LoginUser.java @@ -83,4 +83,29 @@ public class LoginUser implements Serializable { * 创建时间 */ private LocalDateTime createTime; + + /** + * 令牌 + */ + private String token; + + /** + * 登录 IP + */ + private String clientIp; + + /** + * 登录地点 + */ + private String location; + + /** + * 浏览器 + */ + private String browser; + + /** + * 登录时间 + */ + private LocalDateTime loginTime; } diff --git a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/query/PageQuery.java b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/query/PageQuery.java index 61fed926..a8643987 100644 --- a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/query/PageQuery.java +++ b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/query/PageQuery.java @@ -78,6 +78,10 @@ public class PageQuery implements Serializable { this.size = DEFAULT_SIZE; } + public int getPage() { + return page < 0 ? DEFAULT_PAGE : page; + } + /** * 解析排序条件为 Spring 分页排序实体 * diff --git a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/vo/PageInfo.java b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/vo/PageInfo.java index b8ae4f58..88f4a90a 100644 --- a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/vo/PageInfo.java +++ b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/model/vo/PageInfo.java @@ -16,6 +16,7 @@ package top.charles7c.cnadmin.common.model.vo; +import java.util.ArrayList; import java.util.List; import lombok.Data; @@ -26,6 +27,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import com.baomidou.mybatisplus.core.metadata.IPage; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; /** * 分页信息 @@ -93,4 +95,37 @@ public class PageInfo { pageInfo.setTotal(pageInfo.getTotal()); return pageInfo; } + + /** + * 基于列表数据构建分页信息 + * + * @param page + * 页码 + * @param size + * 每页记录数 + * @param list + * 列表数据 + * @param + * 列表数据类型 + * @return 分页信息 + */ + public static PageInfo build(int page, int size, List list) { + PageInfo pageInfo = new PageInfo<>(); + if (CollUtil.isEmpty(list)) { + return pageInfo; + } + + pageInfo.setTotal(list.size()); + // 对列表数据进行分页 + int fromIndex = (page - 1) * size; + int toIndex = page * size + size; + if (fromIndex > list.size()) { + pageInfo.setList(new ArrayList<>()); + } else if (toIndex >= list.size()) { + pageInfo.setList(list.subList(fromIndex, list.size())); + } else { + pageInfo.setList(list.subList(fromIndex, toIndex)); + } + return pageInfo; + } } diff --git a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/helper/LoginHelper.java b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/helper/LoginHelper.java index 62655f93..38adc865 100644 --- a/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/helper/LoginHelper.java +++ b/continew-admin-common/src/main/java/top/charles7c/cnadmin/common/util/helper/LoginHelper.java @@ -16,15 +16,24 @@ package top.charles7c.cnadmin.common.util.helper; +import java.time.LocalDateTime; + +import javax.servlet.http.HttpServletRequest; + import lombok.AccessLevel; import lombok.NoArgsConstructor; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.extra.servlet.ServletUtil; import top.charles7c.cnadmin.common.consts.CacheConstants; +import top.charles7c.cnadmin.common.model.dto.LogContext; import top.charles7c.cnadmin.common.model.dto.LoginUser; import top.charles7c.cnadmin.common.util.ExceptionUtils; +import top.charles7c.cnadmin.common.util.IpUtils; +import top.charles7c.cnadmin.common.util.ServletUtils; +import top.charles7c.cnadmin.common.util.holder.LogContextHolder; /** * 登录助手 @@ -42,8 +51,22 @@ public class LoginHelper { * 登录用户信息 */ public static void login(LoginUser loginUser) { - SaHolder.getStorage().set(CacheConstants.LOGIN_USER_CACHE_KEY, loginUser); + if (loginUser == null) { + return; + } + + // 记录登录信息 + HttpServletRequest request = ServletUtils.getRequest(); + loginUser.setClientIp(ServletUtil.getClientIP(request)); + loginUser.setLocation(IpUtils.getCityInfo(loginUser.getClientIp())); + loginUser.setBrowser(ServletUtils.getBrowser(request)); + LogContext logContext = LogContextHolder.get(); + loginUser.setLoginTime(logContext != null ? logContext.getCreateTime() : LocalDateTime.now()); + + // 登录保存用户信息 StpUtil.login(loginUser.getUserId()); + loginUser.setToken(StpUtil.getTokenValue()); + SaHolder.getStorage().set(CacheConstants.LOGIN_USER_CACHE_KEY, loginUser); StpUtil.getTokenSession().set(CacheConstants.LOGIN_USER_CACHE_KEY, loginUser); } diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/entity/SysLog.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/entity/SysLog.java index 022d48df..8c27d263 100644 --- a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/entity/SysLog.java +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/entity/SysLog.java @@ -39,7 +39,7 @@ public class SysLog implements Serializable { private static final long serialVersionUID = 1L; /** - * 日志ID + * 日志 ID */ @TableId private Long logId; diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/query/OnlineUserQuery.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/query/OnlineUserQuery.java new file mode 100644 index 00000000..60164218 --- /dev/null +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/query/OnlineUserQuery.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.charles7c.cnadmin.monitor.model.query; + +import java.util.Date; +import java.util.List; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +import org.springdoc.api.annotations.ParameterObject; +import org.springframework.format.annotation.DateTimeFormat; + +/** + * 在线用户查询条件 + * + * @author Charles7c + * @since 2023/1/20 23:07 + */ +@Data +@ParameterObject +@Schema(description = "在线用户查询条件") +public class OnlineUserQuery { + + /** + * 用户昵称 + */ + @Schema(description = "用户昵称") + private String nickname; + + /** + * 登录时间 + */ + @Schema(description = "登录时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private List loginTime; +} diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/LoginLogVO.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/LoginLogVO.java index afbab97a..a9e6e615 100644 --- a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/LoginLogVO.java +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/LoginLogVO.java @@ -38,9 +38,9 @@ public class LoginLogVO extends LogVO implements Serializable { private static final long serialVersionUID = 1L; /** - * 日志ID + * 日志 ID */ - @Schema(description = "日志ID") + @Schema(description = "日志 ID") private Long logId; /** @@ -56,9 +56,9 @@ public class LoginLogVO extends LogVO implements Serializable { private LogStatusEnum status; /** - * 登录IP + * 登录 IP */ - @Schema(description = "登录IP") + @Schema(description = "登录 IP") private String clientIp; /** diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/OnlineUserVO.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/OnlineUserVO.java new file mode 100644 index 00000000..24b92a38 --- /dev/null +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/OnlineUserVO.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.charles7c.cnadmin.monitor.model.vo; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import lombok.Data; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 在线用户信息 + * + * @author Charles7c + * @since 2023/1/20 21:54 + */ +@Data +@Schema(description = "在线用户信息") +public class OnlineUserVO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 令牌 + */ + @Schema(description = "令牌") + private String token; + + /** + * 用户名 + */ + @Schema(description = "用户名") + private String username; + + /** + * 昵称 + */ + @Schema(description = "昵称") + private String nickname; + + /** + * 登录 IP + */ + @Schema(description = "登录 IP") + private String clientIp; + + /** + * 登录地点 + */ + @Schema(description = "登录地点") + private String location; + + /** + * 浏览器 + */ + @Schema(description = "浏览器") + private String browser; + + /** + * 登录时间 + */ + @Schema(description = "登录时间") + private LocalDateTime loginTime; +} diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/OperationLogVO.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/OperationLogVO.java index 6b8f8719..0b0dbf4a 100644 --- a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/OperationLogVO.java +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/OperationLogVO.java @@ -38,9 +38,9 @@ public class OperationLogVO extends LogVO implements Serializable { private static final long serialVersionUID = 1L; /** - * 日志ID + * 日志 ID */ - @Schema(description = "日志ID") + @Schema(description = "日志 ID") private Long logId; /** diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogDetailVO.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogDetailVO.java index 7164828b..2c0285e8 100644 --- a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogDetailVO.java +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogDetailVO.java @@ -36,9 +36,9 @@ public class SystemLogDetailVO extends LogVO implements Serializable { private static final long serialVersionUID = 1L; /** - * 日志ID + * 日志 ID */ - @Schema(description = "日志ID") + @Schema(description = "日志 ID") private Long logId; /** diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogVO.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogVO.java index 5df0b05e..8a6c11ef 100644 --- a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogVO.java +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/model/vo/SystemLogVO.java @@ -36,9 +36,9 @@ public class SystemLogVO extends LogVO implements Serializable { private static final long serialVersionUID = 1L; /** - * 日志ID + * 日志 ID */ - @Schema(description = "日志ID") + @Schema(description = "日志 ID") private Long logId; /** diff --git a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/service/LogService.java b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/service/LogService.java index 7d0bf7df..84f3feea 100644 --- a/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/service/LogService.java +++ b/continew-admin-monitor/src/main/java/top/charles7c/cnadmin/monitor/service/LogService.java @@ -71,7 +71,7 @@ public interface LogService { * 查看系统日志详情 * * @param logId - * 日志ID + * 日志 ID * @return 系统日志详情 */ SystemLogDetailVO detail(Long logId); diff --git a/continew-admin-ui/src/api/monitor/online.ts b/continew-admin-ui/src/api/monitor/online.ts new file mode 100644 index 00000000..77501928 --- /dev/null +++ b/continew-admin-ui/src/api/monitor/online.ts @@ -0,0 +1,35 @@ +import axios from 'axios'; +import qs from 'query-string'; + +export interface OnlineUserRecord { + token: string; + username: string; + nickname: string; + clientIp: string; + location: string; + browser: string; + loginTime: string; +} + +export interface OnlineUserParams extends Partial { + page: number; + size: number; + sort: Array; +} +export interface OnlineUserListRes { + list: OnlineUserRecord[]; + total: number; +} + +export function queryOnlineUserList(params: OnlineUserParams) { + return axios.get('/monitor/online/user', { + params, + paramsSerializer: (obj) => { + return qs.stringify(obj); + }, + }); +} + +export function kickout(token: string) { + return axios.delete(`/monitor/online/user/${token}`); +} \ No newline at end of file diff --git a/continew-admin-ui/src/locale/en-US.ts b/continew-admin-ui/src/locale/en-US.ts index 2491c3ce..d33820e0 100644 --- a/continew-admin-ui/src/locale/en-US.ts +++ b/continew-admin-ui/src/locale/en-US.ts @@ -8,6 +8,7 @@ import localeMonitor from '@/views/dashboard/monitor/locale/en-US'; import localeDataAnalysis from '@/views/visualization/data-analysis/locale/en-US'; import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/en-US'; +import localeOnlineUser from '@/views/monitor/online/locale/en-US'; import localeLoginLog from '@/views/monitor/log/login/locale/en-US'; import localeOperationLog from '@/views/monitor/log/operation/locale/en-US'; import localeSystemLog from '@/views/monitor/log/system/locale/en-US'; @@ -57,6 +58,7 @@ export default { ...localeDataAnalysis, ...localeMultiDAnalysis, + ...localeOnlineUser, ...localeLoginLog, ...localeOperationLog, ...localeSystemLog, diff --git a/continew-admin-ui/src/locale/zh-CN.ts b/continew-admin-ui/src/locale/zh-CN.ts index 27fce54a..c200ef44 100644 --- a/continew-admin-ui/src/locale/zh-CN.ts +++ b/continew-admin-ui/src/locale/zh-CN.ts @@ -8,6 +8,7 @@ import localeMonitor from '@/views/dashboard/monitor/locale/zh-CN'; import localeDataAnalysis from '@/views/visualization/data-analysis/locale/zh-CN'; import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/zh-CN'; +import localeOnlineUser from '@/views/monitor/online/locale/zh-CN'; import localeLoginLog from '@/views/monitor/log/login/locale/zh-CN'; import localeOperationLog from '@/views/monitor/log/operation/locale/zh-CN'; import localeSystemLog from '@/views/monitor/log/system/locale/zh-CN'; @@ -57,6 +58,7 @@ export default { ...localeDataAnalysis, ...localeMultiDAnalysis, + ...localeOnlineUser, ...localeLoginLog, ...localeOperationLog, ...localeSystemLog, diff --git a/continew-admin-ui/src/router/routes/modules/monitor.ts b/continew-admin-ui/src/router/routes/modules/monitor.ts index 9ad616ff..0bd5bd1b 100644 --- a/continew-admin-ui/src/router/routes/modules/monitor.ts +++ b/continew-admin-ui/src/router/routes/modules/monitor.ts @@ -12,6 +12,16 @@ const Monitor: AppRouteRecordRaw = { order: 2, }, children: [ + { + path: '/online', + name: 'OnlineUser', + component: () => import('@/views/monitor/online/index.vue'), + meta: { + locale: 'menu.online.user.list', + requiresAuth: true, + roles: ['*'], + }, + }, { path: 'log/login', name: 'LoginLog', diff --git a/continew-admin-ui/src/views/monitor/log/login/index.vue b/continew-admin-ui/src/views/monitor/log/login/index.vue index f68dede2..3eee355b 100644 --- a/continew-admin-ui/src/views/monitor/log/login/index.vue +++ b/continew-admin-ui/src/views/monitor/log/login/index.vue @@ -4,15 +4,8 @@ - - + + - + diff --git a/continew-admin-ui/src/views/monitor/log/operation/index.vue b/continew-admin-ui/src/views/monitor/log/operation/index.vue index bb55b582..afef8e9f 100644 --- a/continew-admin-ui/src/views/monitor/log/operation/index.vue +++ b/continew-admin-ui/src/views/monitor/log/operation/index.vue @@ -4,15 +4,8 @@ - - + + - + - + diff --git a/continew-admin-ui/src/views/monitor/online/index.vue b/continew-admin-ui/src/views/monitor/online/index.vue new file mode 100644 index 00000000..5e0e43c7 --- /dev/null +++ b/continew-admin-ui/src/views/monitor/online/index.vue @@ -0,0 +1,202 @@ + + + + + + + diff --git a/continew-admin-ui/src/views/monitor/online/locale/en-US.ts b/continew-admin-ui/src/views/monitor/online/locale/en-US.ts new file mode 100644 index 00000000..ea23c972 --- /dev/null +++ b/continew-admin-ui/src/views/monitor/online/locale/en-US.ts @@ -0,0 +1,3 @@ +export default { + 'menu.online.user.list': 'Online user', +}; diff --git a/continew-admin-ui/src/views/monitor/online/locale/zh-CN.ts b/continew-admin-ui/src/views/monitor/online/locale/zh-CN.ts new file mode 100644 index 00000000..bab3974c --- /dev/null +++ b/continew-admin-ui/src/views/monitor/online/locale/zh-CN.ts @@ -0,0 +1,3 @@ +export default { + 'menu.online.user.list': '在线用户', +}; diff --git a/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/monitor/OnlineUserController.java b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/monitor/OnlineUserController.java new file mode 100644 index 00000000..5a4282b0 --- /dev/null +++ b/continew-admin-webapi/src/main/java/top/charles7c/cnadmin/webapi/controller/monitor/OnlineUserController.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.charles7c.cnadmin.webapi.controller.monitor; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import lombok.RequiredArgsConstructor; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; + +import top.charles7c.cnadmin.common.consts.CacheConstants; +import top.charles7c.cnadmin.common.model.dto.LoginUser; +import top.charles7c.cnadmin.common.model.query.PageQuery; +import top.charles7c.cnadmin.common.model.vo.PageInfo; +import top.charles7c.cnadmin.common.model.vo.R; +import top.charles7c.cnadmin.common.util.validate.ValidationUtils; +import top.charles7c.cnadmin.monitor.model.query.OnlineUserQuery; +import top.charles7c.cnadmin.monitor.model.vo.*; + +/** + * 在线用户 API + * + * @author Charles7c + * @since 2023/1/20 21:51 + */ +@Tag(name = "在线用户 API") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/monitor/online/user", produces = MediaType.APPLICATION_JSON_VALUE) +public class OnlineUserController { + + @Operation(summary = "分页查询在线用户列表") + @GetMapping + public R> list(@Validated OnlineUserQuery query, @Validated PageQuery pageQuery) { + List loginUserList = new ArrayList<>(); + List tokenKeyList = StpUtil.searchTokenValue("", 0, -1, false); + for (String tokenKey : tokenKeyList) { + String token = StrUtil.subAfter(tokenKey, ":", true); + // 忽略已过期或失效 token + if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < SaTokenDao.NEVER_EXPIRE) { + continue; + } + + // 获取 Token Session + SaSession saSession = StpUtil.getTokenSessionByToken(token); + LoginUser loginUser = saSession.get(CacheConstants.LOGIN_USER_CACHE_KEY, new LoginUser()); + + // 检查是否符合查询条件 + if (Boolean.TRUE.equals(checkQuery(query, loginUser))) { + loginUserList.add(loginUser); + } + } + + // 构建分页数据 + List onlineUserList = BeanUtil.copyToList(loginUserList, OnlineUserVO.class); + CollUtil.sort(onlineUserList, Comparator.comparing(OnlineUserVO::getLoginTime).reversed()); + PageInfo pageInfo = PageInfo.build(pageQuery.getPage(), pageQuery.getSize(), onlineUserList); + return R.ok(pageInfo); + } + + /** + * 检查是否符合查询条件 + * + * @param query + * 查询条件 + * @param loginUser + * 登录用户信息 + * @return 是否符合查询条件 + */ + private boolean checkQuery(OnlineUserQuery query, LoginUser loginUser) { + boolean flag1 = true; + String nickname = query.getNickname(); + if (StrUtil.isNotBlank(nickname)) { + flag1 = loginUser.getUsername().contains(nickname) || loginUser.getNickname().contains(nickname); + } + + boolean flag2 = true; + List loginTime = query.getLoginTime(); + if (CollUtil.isNotEmpty(loginTime)) { + flag2 = + DateUtil.isIn(DateUtil.date(loginUser.getLoginTime()).toJdkDate(), loginTime.get(0), loginTime.get(1)); + } + return flag1 && flag2; + } + + @Operation(summary = "强退在线用户") + @DeleteMapping("/{token}") + public R kickout(@PathVariable String token) { + String currentToken = StpUtil.getTokenValue(); + ValidationUtils.throwIfEqual(token, currentToken, "不能强退当前登录"); + + StpUtil.kickoutByTokenValue(token); + return R.ok("强退成功"); + } +}