v1.10.0新特性:查询所有会话

This commit is contained in:
shengzhang
2021-01-07 22:03:44 +08:00
parent a1ec710efd
commit 22826dac86
15 changed files with 418 additions and 43 deletions

View File

@@ -1,5 +1,7 @@
package cn.dev33.satoken.dao;
import java.util.List;
import cn.dev33.satoken.session.SaSession;
/**
@@ -8,7 +10,6 @@ import cn.dev33.satoken.session.SaSession;
*/
public interface SaTokenDao {
/** 常量表示一个key永不过期 (在一个key被标注为永远不过期时返回此值) */
public static final Long NEVER_EXPIRE = -1L;
@@ -16,6 +17,7 @@ public interface SaTokenDao {
public static final Long NOT_VALUE_EXPIRE = -2L;
// --------------------- token相关 ---------------------
/**
* 根据key获取value如果没有则返回空
@@ -60,6 +62,8 @@ public interface SaTokenDao {
public void updateTimeout(String key, long timeout);
// --------------------- Session相关 ---------------------
/**
* 根据指定key的Session如果没有则返回空
* @param sessionId 键名称
@@ -101,7 +105,17 @@ public interface SaTokenDao {
public void updateSessionTimeout(String sessionId, long timeout);
// --------------------- 会话管理 ---------------------
/**
* 搜索数据
* @param prefix 前缀
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return sessionId集合
*/
public List<String> searchData(String prefix, String keyword, int start, int size);
}

View File

@@ -2,6 +2,7 @@ package cn.dev33.satoken.dao;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.ConcurrentHashMap;
@@ -10,6 +11,7 @@ import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTaskUtil;
import cn.dev33.satoken.util.SaTaskUtil.FunctionRunClass;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token持久层默认的实现类 , 基于内存Map
@@ -207,6 +209,14 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
// --------------------- 会话管理
@Override
public List<String> searchData(String prefix, String keyword, int start, int size) {
return SaTokenInsideUtil.searchList(expireMap.keySet(), prefix, keyword, start, size);
}

View File

@@ -202,7 +202,6 @@ public class StpLogic {
}
}
/**
* 当前会话注销登录
*/
@@ -219,7 +218,6 @@ public class StpLogic {
logoutByTokenValue(tokenValue);
}
/**
* 指定token的会话注销登录
* @param tokenValue 指定token
@@ -246,7 +244,6 @@ public class StpLogic {
session.logoutByTokenSignCountToZero();
}
/**
* 指定loginId的会话注销登录踢人下线
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-2
@@ -287,7 +284,6 @@ public class StpLogic {
session.logoutByTokenSignCountToZero();
}
// 查询相关
/**
@@ -299,7 +295,6 @@ public class StpLogic {
return getLoginIdDefaultNull() != null;
}
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
@@ -307,7 +302,6 @@ public class StpLogic {
getLoginId();
}
/**
* 获取当前会话账号id, 如果未登录,则抛出异常
* @return 账号id
@@ -342,7 +336,6 @@ public class StpLogic {
return loginId;
}
/**
* 获取当前会话登录id, 如果未登录,则返回默认值
* @param <T> 返回类型
@@ -369,7 +362,6 @@ public class StpLogic {
return (T)loginId;
}
/**
* 获取当前会话登录id, 如果未登录则返回null
* @return 账号id
@@ -393,7 +385,6 @@ public class StpLogic {
return loginId;
}
/**
* 获取当前会话登录id, 并转换为String
* @return 账号id
@@ -402,7 +393,6 @@ public class StpLogic {
return String.valueOf(getLoginId());
}
/**
* 获取当前会话登录id, 并转换为int
* @return 账号id
@@ -415,7 +405,6 @@ public class StpLogic {
return Integer.valueOf(String.valueOf(getLoginId()));
}
/**
* 获取当前会话登录id, 并转换为long
* @return 账号id
@@ -428,7 +417,6 @@ public class StpLogic {
return Long.valueOf(String.valueOf(getLoginId()));
}
/**
* 获取指定token对应的登录id如果未登录则返回 null
* @param tokenValue token
@@ -462,6 +450,14 @@ public class StpLogic {
return session;
}
/**
* 获取指定key的session, 如果session尚未创建则返回null
* @param sessionId sessionId
* @return session对象
*/
public SaSession getSessionBySessionId(String sessionId) {
return getSessionBySessionId(sessionId, false);
}
/**
* 获取指定loginId的session, 如果session尚未创建isCreate=是否新建并返回
@@ -473,7 +469,6 @@ public class StpLogic {
return getSessionBySessionId(getKeySession(loginId), isCreate);
}
/**
* 获取指定loginId的session如果session尚未创建则新建并返回
* @param loginId 账号id
@@ -483,7 +478,6 @@ public class StpLogic {
return getSessionByLoginId(loginId, true);
}
/**
* 获取当前会话的session, 如果session尚未创建isCreate=是否新建并返回
* @param isCreate 是否新建
@@ -493,7 +487,6 @@ public class StpLogic {
return getSessionByLoginId(getLoginId(), isCreate);
}
/**
* 获取当前会话的session如果session尚未创建则新建并返回
* @return 当前会话的session
@@ -505,7 +498,6 @@ public class StpLogic {
// =================== token专属session ===================
/**
* 获取指定token的专属session如果session尚未创建isCreate代表是否新建并返回
* @param tokenValue token值
@@ -516,7 +508,6 @@ public class StpLogic {
return getSessionBySessionId(getKeyTokenSession(tokenValue), isCreate);
}
/**
* 获取指定token的专属session如果session尚未创建则新建并返回
* @param tokenValue token值
@@ -526,7 +517,6 @@ public class StpLogic {
return getSessionBySessionId(getKeyTokenSession(tokenValue), true);
}
/**
* 获取当前token的专属-session如果session尚未创建isCreate代表是否新建并返回
* @param isCreate 是否新建
@@ -547,7 +537,6 @@ public class StpLogic {
return getSessionBySessionId(getKeyTokenSession(getTokenValue()), isCreate);
}
/**
* 获取当前token的专属-session如果session尚未创建则新建并返回
* @return session会话
@@ -559,7 +548,6 @@ public class StpLogic {
// =================== [临时过期] 验证相关 ===================
/**
* 写入指定token的 [最后操作时间] 为当前时间戳
* @param tokenValue 指定token
@@ -573,7 +561,6 @@ public class StpLogic {
SaTokenManager.getSaTokenDao().setValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout());
}
/**
* 清除指定token的 [最后操作时间]
* @param tokenValue 指定token
@@ -589,7 +576,6 @@ public class StpLogic {
SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY);
}
/**
* 检查指定token 是否已经[临时过期],如果已经过期则抛出异常
* @param tokenValue 指定token
@@ -621,7 +607,6 @@ public class StpLogic {
request.setAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true);
}
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
*/
@@ -629,7 +614,6 @@ public class StpLogic {
checkActivityTimeout(getTokenValue());
}
/**
* 续签指定token(将 [最后操作时间] 更新为当前时间戳)
* @param tokenValue 指定token
@@ -642,7 +626,6 @@ public class StpLogic {
SaTokenManager.getSaTokenDao().updateValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()));
}
/**
* 续签当前token(将 [最后操作时间] 更新为当前时间戳)
* <h1>请注意: 即时token已经 [临时过期] 也可续签成功,
@@ -653,7 +636,6 @@ public class StpLogic {
}
// =================== 过期时间相关 ===================
/**
@@ -965,6 +947,42 @@ public class StpLogic {
}
// =================== 会话管理 ===================
/**
* 根据条件查询token
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return token集合
*/
public List<String> searchTokenValue(String keyword, int start, int size) {
return SaTokenManager.getSaTokenDao().searchData(getKeyTokenValue(""), keyword, start, size);
}
/**
* 根据条件查询SessionId
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return sessionId集合
*/
public List<String> searchSessionId(String keyword, int start, int size) {
return SaTokenManager.getSaTokenDao().searchData(getKeySession(""), keyword, start, size);
}
/**
* 根据条件查询token专属Session的Id
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return sessionId集合
*/
public List<String> searchTokenSessionId(String keyword, int start, int size) {
return SaTokenManager.getSaTokenDao().searchData(getKeyTokenSession(""), keyword, start, size);
}
// =================== 返回相应key ===================
/**

View File

@@ -194,6 +194,15 @@ public class StpUtil {
return stpLogic.getSessionByLoginId(loginId, isCreate);
}
/**
* 获取指定key的session, 如果session尚未创建则返回null
* @param sessionId sessionId
* @return session对象
*/
public static SaSession getSessionBySessionId(String sessionId) {
return stpLogic.getSessionBySessionId(sessionId);
}
/**
* 获取指定loginId的session如果session尚未创建则新建并返回
* @param loginId 账号id
@@ -440,4 +449,41 @@ public class StpUtil {
return stpLogic.getLoginDevice();
}
// =================== 会话管理 ===================
/**
* 根据条件查询token
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return token集合
*/
public static List<String> searchTokenValue(String keyword, int start, int size) {
return stpLogic.searchTokenValue(keyword, start, size);
}
/**
* 根据条件查询SessionId
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return sessionId集合
*/
public static List<String> searchSessionId(String keyword, int start, int size) {
return stpLogic.searchSessionId(keyword, start, size);
}
/**
* 根据条件查询token专属Session的Id
* @param keyword 关键字
* @param start 开始处索引 (-1代表查询所有)
* @param size 获取数量
* @return sessionId集合
*/
public static List<String> searchTokenSessionId(String keyword, int start, int size) {
return stpLogic.searchTokenSessionId(keyword, start, size);
}
}

View File

@@ -1,9 +1,13 @@
package cn.dev33.satoken.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* sa-token 工具类
* sa-token 内部代码工具类
* @author kong
*
*/
@@ -49,4 +53,58 @@ public class SaTokenInsideUtil {
}
/**
* 从集合里查询数据
* @param dataList 数据集合
* @param prefix 前缀
* @param keyword 关键字
* @param start 起始位置 (-1代表查询所有)
* @param size 获取条数
* @return 符合条件的新数据集合
*/
public static List<String> searchList(Collection<String> dataList, String prefix, String keyword, int start, int size) {
if(prefix == null) {
prefix = "";
}
if(keyword == null) {
keyword = "";
}
// 挑选出所有符合条件的
List<String> list = new ArrayList<String>();
Iterator<String> keys = dataList.iterator();
while (keys.hasNext()) {
String key = keys.next();
if(key.startsWith(prefix) && key.indexOf(keyword) > -1) {
list.add(key);
}
}
// 取指定段数据
return searchList(list, start, size);
}
/**
* 从集合里查询数据
* @param list 数据集合
* @param start 起始位置 (-1代表查询所有)
* @param size 获取条数
* @return 符合条件的新数据集合
*/
public static List<String> searchList(List<String> list, int start, int size) {
// 取指定段数据
if(start < 0) {
return list;
}
int end = start + size;
List<String> list2 = new ArrayList<String>();
for (int i = start; i < end; i++) {
if(i >= list.size()) {
return list2;
}
list2.add(list.get(i));
}
return list2;
}
}

View File

@@ -1,6 +1,9 @@
package cn.dev33.satoken.dao;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +18,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token持久层的实现类, 基于redis (to jackson)
@@ -182,4 +186,14 @@ public class SaTokenDaoRedisJackson implements SaTokenDao {
sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS);
}
/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size) {
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<String>(keys);
return SaTokenInsideUtil.searchList(list, start, size);
}
}

View File

@@ -1,5 +1,8 @@
package cn.dev33.satoken.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
@@ -11,6 +14,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token持久层的实现类, 基于redis
@@ -163,4 +167,17 @@ public class SaTokenDaoRedis implements SaTokenDao {
sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS);
}
/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size) {
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<String>(keys);
return SaTokenInsideUtil.searchList(list, start, size);
}
}

View File

@@ -9,6 +9,8 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;

View File

@@ -0,0 +1,62 @@
package com.pj.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pj.util.AjaxJson;
import com.pj.util.Ttime;
import cn.dev33.satoken.stp.StpUtil;
/**
* 压力测试
* @author kong
*
*/
@RestController
@RequestMapping("/s-test/")
public class StressTestController {
// 测试 浏览器访问: http://localhost:8081/s-test/login
// 测试前,请先将 is-read-cookie 配置为 false
@RequestMapping("login")
public AjaxJson login() {
// StpUtil.getTokenSession().logout();
// StpUtil.logoutByLoginId(10001);
// 循环10次 取平均时间
List<Double> list = new ArrayList<>();
for (int i = 10; i <= 1; i++) {
System.out.println("\n---------------------第" + i + "轮---------------------");
Ttime t = new Ttime().start();
// 每次登录的次数
for (int j = 1; j <= 10000; j++) {
StpUtil.setLoginId("1000" + j, "PC-" + j);
if(j % 1000 == 0) {
System.out.println("已登录:" + j);
}
}
t.end();
list.add((t.returnMs() + 0.0) / 1000);
System.out.println("" + i + "" + "用时:" + t.toString());
}
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
System.out.println("\n---------------------测试结果---------------------");
System.out.println(list.size() + "次测试: " + list);
double ss = 0;
for (int i = 0; i < list.size(); i++) {
ss += list.get(i);
}
System.out.println("平均用时: " + ss / list.size());
return AjaxJson.getSuccess();
}
}

View File

@@ -1,6 +1,7 @@
package com.pj.test;
import java.util.Date;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -8,6 +9,8 @@ import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.util.AjaxJson;
import com.pj.util.Ttime;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
@@ -192,16 +195,6 @@ public class TestController {
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
// StpUtil.getTokenSession().logout();
StpUtil.logoutByLoginId(10001);
return AjaxJson.getSuccess();
}
// 测试登录接口, 按照设备登录, 浏览器访问: http://localhost:8081/test/login2
@RequestMapping("login2")
public AjaxJson login2(@RequestParam(defaultValue="10001") String id, @RequestParam(defaultValue="PC") String device) {
@@ -209,4 +202,30 @@ public class TestController {
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/searchSession
// 测试前,请先将 is-read-cookie 配置为 false
@RequestMapping("searchSession")
public AjaxJson searchSession() {
System.out.println("--------------");
Ttime t = new Ttime().start();
List<String> tokenValue = StpUtil.searchTokenValue("", 0, 10);
for (String v : tokenValue) {
// SaSession session = StpUtil.getSessionBySessionId(sid);
System.out.println(v);
}
System.out.println("用时:" + t.end().toString());
return AjaxJson.getSuccess();
}
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
StpUtil.getTokenSession().logout();
StpUtil.logoutByLoginId(10001);
return AjaxJson.getSuccess();
}
}

View File

@@ -1,4 +1,4 @@
package com.pj.test;
package com.pj.util;
import java.io.Serializable;
import java.util.List;

View File

@@ -0,0 +1,63 @@
package com.pj.util;
/**
* 用于测试用时
* @author kong
*
*/
public class Ttime {
private long start=0; //开始时间
private long end=0; //结束时间
public static Ttime t = new Ttime(); //static快捷使用
/**
* 开始计时
* @return
*/
public Ttime start() {
start=System.currentTimeMillis();
return this;
}
/**
* 结束计时
*/
public Ttime end() {
end=System.currentTimeMillis();
return this;
}
/**
* 返回所用毫秒数
*/
public long returnMs() {
return end-start;
}
/**
* 格式化输出结果
*/
public void outTime() {
System.out.println(this.toString());
}
/**
* 结束并格式化输出结果
*/
public void endOutTime() {
this.end().outTime();
}
@Override
public String toString() {
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为0.01s
}
}

View File

@@ -18,11 +18,14 @@ spring:
# token风格
token-style: uuid
# 是否从cookie里读取token
# is-read-cookie: false
# redis配置
redis:
# Redis数据库索引默认为0
database: 1
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
@@ -30,7 +33,7 @@ spring:
# Redis服务器连接密码默认为空
password:
# 连接超时时间(毫秒)
timeout: 1000ms
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数

View File

@@ -18,6 +18,7 @@
- [注解式鉴权](/use/at-check)
- [花式token](/use/token-style)
- [框架配置](/use/config)
- [会话治理](/use/search-session)
- **其它**
- [常见问题](/more/common-questions)

View File

@@ -0,0 +1,48 @@
# 会话治理
尽管框架将大部分操作提供了简易的封装,但在一些特殊场景下,我们仍需要绕过框架,直达数据底层进行一些操作 <br>
sa-token提供以下API助你直接操作会话列表
---
## 具体API
#### 查询所有token
``` java
StpUtil.searchTokenValue(String keyword, int start, int size);
```
#### 查询所有账号Session会话
``` java
StpUtil.searchSessionId(String keyword, int start, int size);
```
#### 查询所有令牌Session会话
``` java
StpUtil.searchTokenSessionId(String keyword, int start, int size);
```
#### 参数详解:
- `keyword`: 查询关键字只有包括这个字符串的token值才会被查询出来
- `start`: 数据开始处索引, 值为-1时代表一次性取出所有数据
- `size`: 要获取的数据条数
使用示例:
``` java
StpUtil.searchTokenValue("1000", 0, 10); // 查询value包括1000的所有token结果集从第0条开始返回10条
```
<br/>
#### 注意事项:
由于会话查询底层采用了遍历方式获取数据,当数据量过大时此操作将会比较耗时,有多耗时呢?这里提供一份参考数据:
- 单机模式下百万会话取出10条token平均耗时 `0.255s`
- Redis模式下百万会话取出10条token平均耗时 `3.322s`
请根据业务实际水平合理调用API