ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Sa-Token 会话查询实战指南:终端列表、会话检索与性能边界

2026/9/14 3:27:22 拓冰建站 浏览量
Sa-Token 会话查询实战指南:终端列表、会话检索与性能边界 Sa-Token 会话查询实战指南终端列表、会话检索与性能边界【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token导读本文聚焦 Sa-Token 提供的会话查询能力既可以在已知loginId时获取该账号的全部已登录终端设备明细也可以在不确定账号的情况下按关键字、分页遍历系统内所有 Token 与会话。通过本文你将掌握StpUtil.getTerminalListByLoginId、StpUtil.searchTokenValue、StpUtil.searchSessionId、StpUtil.searchTokenSessionId四个核心 API 的用法与参数语义理解其底层基于 SaTokenDao 的遍历式实现原理并了解单机 / Redis 两种存储模式下的性能边界与适用场景。一、会话查询能解决什么问题在权限认证框架中登录状态通常沉淀为两类数据Token一次登录凭证与会话 Session一个账号的数据容器。实际业务中我们经常需要回答以下问题某账号当前在哪些设备登录分别是什么设备类型、什么 Token、什么时间登录的系统当前一共有多少在线 Token多少已登录账号如何在不知道账号的前提下按关键字/分页检索出所有在线会话用于后台管理大屏或运营统计Sa-Token 的会话查询能力正是为上述场景设计的。它分为两个层次单账号终端查询以loginId为入口精确到设备级与全局会话检索以关键字为入口扫描整个存储。二、单账号会话查询getTerminalListByLoginId2.1 基本用法StpUtil.getTerminalListByLoginId(loginId)返回指定账号已登录的终端信息列表每个元素是一个SaTerminalInfo对象public static void main(String[] args) { System.out.println(账号 10001 登录设备信息); ListSaTerminalInfo terminalList StpUtil.getTerminalListByLoginId(10001); for (SaTerminalInfo ter : terminalList) { System.out.println(登录index ter.getIndex() , 设备type ter.getDeviceType() , token ter.getTokenValue() , 登录time ter.getCreateTime()); } }控制台打印结果账号 10001 登录设备信息 登录index1, 设备typePC, tokena8fbb46f-e043-459a-a875-0a2874911be8, 登录time1742354951192 登录index2, 设备typeAPP, token882b6c9c-bdf9-4e8f-a42b-6e17d2fe0e34, 登录time1742354960950 登录index3, 设备typeWEB, tokendacac78c-0983-4819-ab8b-07e7603597fc, 登录time1742354962848从输出可以看出账号 10001 在 PC、APP、WEB 三个设备依次登录index从 1 开始递增每个终端持有独立的 Token 与登录时间13 位时间戳。2.2 SaTerminalInfo 字段说明一个SaTerminalInfo对象代表一个终端信息其字段定义在 SaTerminalInfo.java自 1.41.0 版本引入实现SaJsonType, Serializable对外取值方法如下terminal.getIndex(); // 登录会话索引值 (该账号第几个登录的设备, 从 1 开始) terminal.getDeviceType(); // 所属设备类型例如PC、WEB、HD、MOBILE、APP terminal.getTokenValue(); // 此次登录的token值 terminal.getCreateTime(); // 登录时间, 13位时间戳 terminal.getDeviceId(); // 设备id, 设备唯一标识 terminal.getExtra(key); // 此次登录的额外自定义参数字段与含义对应关系方法含义说明getIndex()登录会话索引值该账号第几个登录的设备从 1 开始计数getDeviceType()设备类型典型取值PC、WEB、HD、MOBILE、APPgetTokenValue()本次登录的 Token 值可用于后续踢人下线等操作getCreateTime()登录时间13 位毫秒时间戳构造对象时由System.currentTimeMillis()写入getDeviceId()设备唯一标识例如kQwIOrYvnXmSDkwEiFngrKidMcdrgKorXmSDkwEiFngrKidMgetExtra(key)登录时的自定义扩展参数需登录时通过setTerminalExtra预置从源码结构看SaTerminalInfo注释中还预留了deviceName浏览器名称、systemName操作系统、loginIp、address地理位置等字段的扩展空间属于未来规划能力当前版本未实现使用时应以现有字段为准。2.3 按设备类型过滤getTerminalListByLoginId还有一个重载方法可在查询时按设备类型过滤。其实现位于 StpLogic.javapublic ListSaTerminalInfo getTerminalListByLoginId(Object loginId) { return getTerminalListByLoginId(loginId, null); } public ListSaTerminalInfo getTerminalListByLoginId(Object loginId, String deviceType) { // 如果该账号的 Account-Session 为 null说明此账号尚没有客户端在登录此时返回空集合 SaSession session getSessionByLoginId(loginId, false); if(session null) { return new ArrayList(); } // 按照设备类型进行筛选 return session.getTerminalListByDeviceType(deviceType); }要点解读底层是先根据loginId取Account-Session会话不存在时直接返回空集合不会抛异常deviceType传null表示不限设备类型返回全部终端SaSession.getTerminalListByDeviceTypeSaSession.java内部通过terminalListCopy()拷贝副本后按设备类型过滤返回的是副本不会暴露内部可变列表安全性良好。示例查询账号 10001 在APP设备上的所有终端ListSaTerminalInfo list StpUtil.getTerminalListByLoginId(10001, APP);2.4 延伸单终端信息查询除列表查询外StpLogic 还提供了针对单个终端的查询 API底层均复用getTerminalInfoByTokenStpLogic.java内部依次校验 Token 是否为空、登录 id 是否有效、Account-Session 是否存在、Token 是否被冻结最后在终端列表中按 Token 值精确匹配SaTerminalInfo info StpUtil.getTerminalInfo(); // 当前会话的终端信息 SaTerminalInfo info2 StpUtil.getTerminalInfoByToken(tokenValue); // 指定 token 的终端信息 String deviceType StpUtil.getLoginDeviceType(); // 当前会话设备类型 String deviceId StpUtil.getLoginDeviceId(); // 当前会话设备 id三、终端自定义扩展参数ExtraSaTerminalInfo支持携带自定义扩展数据用于在登录时挂载业务需要的上下文信息如登录渠道、客户端版本号等。扩展数据只允许在登录前设定登录后不建议更改。在登录时通过SaLoginParameter指定StpUtil.login(10001, new SaLoginParameter().setTerminalExtra(key, value));查询时取回Object value terminal.getExtra(key);从源码看SaLoginParameterSaLoginParameter.java除了terminalExtraDatasetTerminalExtra(key, value)外还包含deviceType默认取SaTokenConsts.DEFAULT_LOGIN_DEVICE_TYPE与deviceId等字段因此登录时也可以显式指定设备类型与设备唯一标识让会话查询的结果更具业务语义StpUtil.login(10001, new SaLoginParameter() .setDevice(APP) // 指定本次登录的设备类型 .setDeviceId(device-xxx-001) // 指定设备唯一标识 .setTerminalExtra(channel, huawei-store) // 自定义扩展参数 );对应的存储端SaTerminalInfo.setExtra内部使用LinkedHashMap维护扩展数据SaTerminalInfo.java保证插入顺序稳定。四、全局会话检索search 系列 API当不知道具体账号需要遍历系统内全部会话时使用以下三个 API// 查询所有已登录的 Token StpUtil.searchTokenValue(String keyword, int start, int size, boolean sortType); // 查询所有 Account-Session 会话 StpUtil.searchSessionId(String keyword, int start, int size, boolean sortType); // 查询所有 Token-Session 会话 StpUtil.searchTokenSessionId(String keyword, int start, int size, boolean sortType);4.1 参数详解参数含义keyword查询关键字只有包含这个字符串的 token 值才会被查询出来start数据开始处索引分页起点size要获取的数据条数值为 -1 代表一直获取到末尾sortType排序方式true正序先登录的在前false反序后登录的在前三个方法的返回类型均为ListString。4.2 简单样例// 查询 value 包括 1000 的所有 token结果集从第 0 条开始返回 10 条 ListString tokenList StpUtil.searchTokenValue(1000, 0, 10, true); for (String token : tokenList) { System.out.println(token); }同样的分页参数也适用于searchSessionId、searchTokenSessionId。其中keyword传空字符串表示不过滤关键字、匹配全部。4.3 三种检索的存储前缀从 StpLogic.java 的实现可以看到三个方法最终都委托给数据访问层public ListString searchTokenValue(String keyword, int start, int size, boolean sortType) { return getSaTokenDao().searchData(splicingKeyTokenValue(), (keyword null ? : keyword), start, size, sortType); } public ListString searchSessionId(String keyword, int start, int size, boolean sortType) { return getSaTokenDao().searchData(splicingKeySession(), (keyword null ? : keyword), start, size, sortType); } public ListString searchTokenSessionId(String keyword, int start, int size, boolean sortType) { return getSaTokenDao().searchData(splicingKeyTokenSession(), (keyword null ? : keyword), start, size, sortType); }区别只在于扫描的存储前缀不同splicingKeyTokenValue()→ Token 存储区检索对象是一次次登录产生的 TokensplicingKeySession()→ Account-Session 存储区检索对象是一个个已登录账号的会话 idsplicingKeyTokenSession()→ Token-Session 存储区检索对象是随 Token 关联的会话 id。五、searchTokenValue 与 searchSessionId 的区别这是最容易混淆的两个 API两者的核心区别在于StpUtil.searchTokenValue查询的是登录产生的所有 TokenStpUtil.searchSessionId查询的是所有已登录账号的会话 id。当一个账号允许多端同时登录时Token 数量会显著多于会话数量。举个例子项目配置如下sa-token: # 允许同一账号在多个设备一起登录 is-concurrent: true # 同一账号每次登录产生不同的token is-share: false假设此时账号 A 在 电脑、手机、平板 依次登录共 3 次登录账号 B 在 电脑、手机 依次登录共 2 次登录那么StpUtil.searchTokenValue将返回一共5 个TokenStpUtil.searchSessionId将返回一共2 个SessionId。可见Token 粒度 一次登录会话粒度 一个账号。若配置is-share: true同账号同端复用 Token则同一设备多次登录不会新增 TokenToken 数量将进一步收敛。六、遍历系统所有已登录会话的完整示例综合上述 API若要遍历系统所有已登录的会话每个账号在哪些设备在线代码大致如下// 获取所有已登录的会话id ListString sessionIdList StpUtil.searchSessionId(, 0, -1, false); for (String sessionId : sessionIdList) { // 根据会话id查询对应的 SaSession 对象此处一个 SaSession 对象即代表一个登录的账号 SaSession session StpUtil.getSessionBySessionId(sessionId); // 查询这个账号都在哪些设备登录了依据上面的示例账号A 的 SaTerminalInfo 数量是 3账号B 的 SaTerminalInfo 数量是 2 ListSaTerminalInfo terminalList session.terminalListCopy(); System.out.println(会话id sessionId 共在 terminalList.size() 设备登录); }代码要点searchSessionId(, 0, -1, false)size -1表示一直取到末尾false表示反序后登录的在前StpUtil.getSessionBySessionId(sessionId)根据会话 id 拿到SaSession对象每个SaSession对应一个登录账号session.terminalListCopy()返回终端列表的拷贝副本SaSession.java避免并发修改内部列表SaSession还提供了getTerminalListByDeviceType(deviceType)、getTokenValueListByDeviceType(deviceType)、forEachTerminalList(function)等配套方法便于按设备过滤或批量遍历。七、底层实现原理委托 SaTokenDao 遍历会话检索的底层统一收敛到数据访问层接口SaTokenDao.searchDataSaTokenDao.javaListString searchData(String prefix, String keyword, int start, int size, boolean sortType);不同存储实现各自提供searchData的具体逻辑存储实现类文件实现方式默认内存实现SaTokenDaoDefaultImpl.java对timedCache.keySet()调用SaFoxUtil.searchList遍历过滤RedisSpring Data RedisSaTokenDaoForRedisTemplate.java遍历 Redis 中指定前缀的 KeyRedissonSaTokenDaoForRedisson.java遍历 Redisson 缓存 KeyCaffeineSaTokenDaoForCaffeine.java遍历 Caffeine 缓存的 KeyHutool TimedCacheSaTokenDaoForHutoolTimedCache.java遍历 Hutool 定时缓存 KeyRedisXSaTokenDaoForRedisx.java遍历 RedisX 缓存 Key可以推断SaFoxUtil.searchList内部会对 Key 集合做包含匹配keyword、分页截取start/size与排序sortType。因此会话查询本质是一次全量遍历 过滤而非走索引的精确查询——这也是性能与数据量强相关的根本原因。八、性能与注意事项8.1 遍历成本参考由于会话查询底层采用了遍历方式获取数据当数据量过大时此操作将会比较耗时。这里提供一份参考数据百万会话量级下取出 10 条 Token 的平均耗时单机模式下百万会话取出 10 条 Token 平均耗时0.255sRedis 模式下百万会话取出 10 条 Token 平均耗时3.322s。请根据业务实际水平合理调用 API在线会话规模大时应严格控制调用频率配合分页参数使用避免高频全量扫描对存储层造成压力。8.2 统计延迟说明::: warning 注意 基于活跃 Token 的统计方式会比实际情况略有延迟如果需要精确统计实时在线用户信息需要采用 WebSocket。 :::也就是说会话查询得到的是当前存储在缓存中的登录凭证快照Token 过期清理、异地互踢is-concurrent: false时的顶号下线等机制可能导致统计结果与实时在线人数之间存在时间差。若业务要求精确的实时在线指标如在线人数看板、强制下线推送应结合 WebSocket 心跳维护一份实时连接状态而非依赖本 API 的统计结果。九、可运行的完整示例工程官方 Demo 仓库中提供了可直接运行的会话查询示例SearchSessionController.java位于sa-token-demo/sa-token-demo-case模块测试步骤如下启动示例工程默认端口 8081先登录 5 个账号http://localhost:8081/search/login?userId10001name张三age18 http://localhost:8081/search/login?userId10002name李四age20 http://localhost:8081/search/login?userId10003name王五age22 http://localhost:8081/search/login?userId10004name赵六age24 http://localhost:8081/search/login?userId10005name冯七age26登录接口内部执行StpUtil.login(userId)并将SysUser对象存入SaSession。分页查询全部会话http://localhost:8081/search/getList?start0size10查询接口的核心逻辑与上文遍历所有已登录会话一致先用StpUtil.searchSessionId(, start, size, false)分页拿到会话 id 列表再逐个通过StpUtil.getSessionBySessionId(sessionId)还原为SaSession集合返回可作为后台管理接口的参考实现。十、小结Sa-Token 的会话查询能力覆盖了从单账号设备明细到全量会话检索两个粒度单账号终端查询StpUtil.getTerminalListByLoginId(loginId[, deviceType])返回SaTerminalInfo列表含索引、设备类型、Token、登录时间、设备 id 与自定义扩展参数全量检索searchTokenValue/searchSessionId/searchTokenSessionId通过关键字 分页 排序参数遍历存储层分别对应 Token、Account-Session、Token-Session 三类数据实现原理统一委托SaTokenDao.searchData做前缀匹配的全量遍历单机内存与 Redis 等存储实现各有对应的searchData逻辑性能边界遍历式查询与数据量线性相关百万会话级别下存在明显耗时单机约 0.255s、Redis 约 3.322s 取 10 条且统计存在延迟精确实时在线指标应另用 WebSocket 方案。合理组合上述 API即可低成本实现在线设备管理会话大屏统计后台会话检索等常见运维与运营功能。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考