提交 f57cfdb9 编写于 作者: lakernote's avatar lakernote

(新增)[整体](在线用户管理)

上级 5de93ffb
......@@ -5,11 +5,14 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
*
* @author laker
*/
@SpringBootApplication
public class EasyAdminApplication {
public static void main(String[] args) {
SpringApplication.run(EasyAdminApplication.class, args);
System.out.println("Gitee:https://gitee.com/lakernote/easy-admin");
System.out.println("GitHub:https://github.com/lakernote/easy-admin");
}
}
package com.laker.admin.framework;
import cn.hutool.core.util.PageUtil;
import lombok.Data;
import java.util.List;
/**
* 内存分页
*/
@Data
public class PageDtoUtil {
//当前页
private int pageNum;
//页大小
private int pageSize;
//总页数
private int pages;
//数据总数
private int total;
//分页结果
private List<?> pageList;
/**
* @return
* @description 内存分页,hutool工具类相关
* @Param
* @author tzh
* @date
*/
public static PageDtoUtil getPageDto(List<?> list, int pageNum, int pageSize) {
PageDtoUtil pageDtoUtil = new PageDtoUtil();
//总页数
int pages = PageUtil.totalPage(list.size(), pageSize);
//开始位置和结束位置
int[] startEnd = PageUtil.transToStartEnd(pageNum - 1, pageSize);
//分页后的结果集
List<?> pageList = null;
if (startEnd[1] < list.size()) {
pageList = list.subList(startEnd[0], startEnd[1]);
} else {
pageList = list.subList(startEnd[0], list.size());
}
pageDtoUtil.setPageSize(pageSize);//页大小
pageDtoUtil.setPageNum(pageNum);//当前页码
pageDtoUtil.setPages(pages);//总页数
pageDtoUtil.setTotal(list.size());//数据总数
pageDtoUtil.setPageList(pageList);//分页结果集
return pageDtoUtil;
}
}
\ No newline at end of file
package com.laker.admin.framework.ext.satoken;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import com.laker.admin.module.sys.service.ISysUserService;
import com.laker.admin.utils.IP2CityUtil;
import com.laker.admin.utils.http.HttpServletRequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 自定义侦听器的实现
*/
@Component
@Slf4j
public class MySaTokenListener implements SaTokenListener {
public static final List<OnlineUser> ONLINE_USERS = new CopyOnWriteArrayList<>();
@Autowired
ISysUserService sysUserService;
/**
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, SaLoginModel loginModel) {
UserAgent requestUserAgent = HttpServletRequestUtil.getRequestUserAgent();
String cityInfo = IP2CityUtil.getCityInfo(HttpServletRequestUtil.getRemoteIP());
String[] split = cityInfo.split("\\|");
ONLINE_USERS.add(OnlineUser.builder()
.ip(HttpServletRequestUtil.getRemoteIP())
.city(StrUtil.format("{}.{}.{}", split[0], split[2], split[3]))
.loginTime(new Date())
.os(requestUserAgent.getOs().getName())
.userId((Long) loginId)
.tokenValue(StpUtil.getTokenValue())
.nickName(sysUserService.getById((Long) loginId).getNickName())
.browser(requestUserAgent.getBrowser().getName()).build());
log.info("user doLogin,useId:{},token:{}",loginId,StpUtil.getTokenValue());
}
/**
* 每次注销时触发
*/
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
// ...
ONLINE_USERS.removeIf(onlineUser ->
onlineUser.getTokenValue().equals(tokenValue)
);
log.info("user doLogout,useId:{},token:{}",loginId,tokenValue);
}
/**
* 每次被踢下线时触发
*/
@Override
public void doLogoutByLoginId(String loginType, Object loginId, String tokenValue, String device) {
// ...
ONLINE_USERS.removeIf(onlineUser ->
onlineUser.getTokenValue().equals(tokenValue)
);
log.info("user doLogoutByLoginId,useId:{},token:{}",loginId,tokenValue);
}
/**
* 每次被顶下线时触发
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue, String device) {
ONLINE_USERS.removeIf(onlineUser ->
onlineUser.getTokenValue().equals(tokenValue)
);
log.info("user doReplaced,useId:{},token:{}",loginId,tokenValue);
}
/**
* 每次被封禁时触发
*/
@Override
public void doDisable(String loginType, Object loginId, long disableTime) {
// ...
}
/**
* 每次被解封时触发
*/
@Override
public void doUntieDisable(String loginType, Object loginId) {
// ...
}
/**
* 每次创建Session时触发
*/
@Override
public void doCreateSession(String id) {
// ...
}
/**
* 每次注销Session时触发
*/
@Override
public void doLogoutSession(String id) {
// ...
}
}
package com.laker.admin.framework.ext.satoken;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
public class OnlineUser {
private String loginId;
private Long userId;
private String nickName;
private String ip;
private String os;
private String city;
private String browser;
private String tokenValue;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date loginTime;
}
package com.laker.admin.module.sys.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
......@@ -9,10 +10,13 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.laker.admin.framework.EasyAdminConstants;
import com.laker.admin.framework.PageDtoUtil;
import com.laker.admin.framework.PageResponse;
import com.laker.admin.framework.Response;
import com.laker.admin.framework.aop.Metrics;
import com.laker.admin.framework.cache.ICache;
import com.laker.admin.framework.ext.mybatis.UserInfoAndPowers;
import com.laker.admin.framework.ext.satoken.MySaTokenListener;
import com.laker.admin.module.sys.entity.SysRole;
import com.laker.admin.module.sys.entity.SysUser;
import com.laker.admin.module.sys.entity.SysUserRole;
......@@ -24,11 +28,9 @@ import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
......@@ -101,6 +103,31 @@ public class LoginController {
return Response.ok(sysUserService.getById(StpUtil.getLoginIdAsLong()));
}
@GetMapping("/api/v1/onlineUsers")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "获取在线用户信息")
public PageResponse onlineUsers(@RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false, defaultValue = "10") int limit) {
List<String> sessionIds = StpUtil.searchTokenValue(null, -1, 1000);
log.info("当前用户:{}", Arrays.toString(sessionIds.toArray()));
PageDtoUtil pageDto = PageDtoUtil.getPageDto(MySaTokenListener.ONLINE_USERS, page, limit);
log.warn("stp用户数:{},memory用户数:{}", sessionIds.size(), pageDto.getTotal());
return PageResponse.ok(pageDto.getPageList(), (long) pageDto.getTotal());
}
@GetMapping("/api/v1/kickOffline")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "踢人下线")
@SaCheckPermission("online.user.kick")
public Response kickOffline(String token) {
log.info("踢人下线,token:{}", token);
if (StpUtil.getTokenValue().equals(token)) {
return Response.error("500", "不能踢自己啊老弟");
}
StpUtil.logoutByTokenValue(token);
return Response.ok();
}
@GetMapping("/api/v1/loginOut")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "登出")
......
......@@ -41,15 +41,17 @@ sa-token:
# token名称 (同时也是cookie名称)
token-name: LakerToken
# token有效期,单位s 默认30天, -1代表永不过期
# timeout: 1800
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
# activity-timeout: 3000
# timeout: 1800
# token临时有效期 [指定时间内无操作就视为token过期] (单位: 秒), 默认-1 代表不限制
activity-timeout: 1800
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: simple-uuid
# 是否打印操作日志
is-log: false
video:
url: http://1.1.1.1
admin:
......
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>日志</title>
<link href="../../component/pear/css/pear.css" rel="stylesheet"/>
</head>
<body class="pear-container">
<div class="layui-card">
<div class="layui-card-body">
<form class="layui-form" action="">
<div class="layui-form-item">
<label class="layui-form-label">关键字</label>
<div class="layui-input-inline">
<input type="text" name="keyWord" placeholder="" class="layui-input">
</div>
<button class="pear-btn pear-btn-md pear-btn-primary" lay-submit lay-filter="dict-type-query">
<i class="layui-icon layui-icon-search"></i>
查询
</button>
<button type="reset" class="pear-btn pear-btn-md">
<i class="layui-icon layui-icon-refresh"></i>
重置
</button>
</div>
</form>
</div>
</div>
<div class="layui-card">
<div class="layui-card-body">
<table id="table" lay-filter="table-filter"></table>
</div>
</div>
</body>
<script type="text/html" id="record-toolbar">
<button class="pear-btn pear-btn-danger pear-btn-sm" lay-event="kick">
<i class="layui-icon layui-icon-delete">踢人</i>
</button>
</script>
<script src="../../component/layui/layui.js"></script>
<script src="../../component/pear/pear.js"></script>
<script>
layui.use(['easyAdmin', 'table'], function () {
let easyAdmin = layui.easyAdmin;
let table = layui.table;
let cols = [
[
{
title: '用户',
field: 'nickName',
align: 'center'
},
{
title: '城市',
field: 'city',
align: 'center'
},
{
title: '操作系统',
field: 'os',
align: 'center'
},
{
title: '浏览器',
field: 'browser',
align: 'center'
},
{
title: '请求IP',
field: 'ip',
align: 'center'
},
{
title: '访问时间',
field: 'loginTime',
align: 'center'
}
,
{
title: '操作',
field: 'loginTime',
align: 'center',
templet: '#record-toolbar'
}
]
]
;
let module = "log";
easyAdmin.tableRender({
url: "/api/v1/onlineUsers",
cols: cols,
toolbar: null,
defaultToolbar: null
});
table.on('tool(table-filter)', function (obj) {
if (obj.event === 'kick') {
easyAdmin.httpGet("/api/v1/kickOffline?token=" + obj.data.tokenValue, function (result) {
if (result.success) {
layer.msg("踢人成功", {
icon: 1,
area: ['100px', '65px'],
time: 1000
}, function () {
table.reload('table');
});
} else {
layer.msg(result.msg, {
icon: 2,
area: ['300px', '65px'],
time: 2000
});
}
});
}
});
easyAdmin.FormQuery();
})
</script>
</html>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册