news 2026/9/13 13:31:33

Sa-Token 缓存层扩展实战:实现 SaTokenDao 接口对接 Redis、MongoDB 等持久化中间件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sa-Token 缓存层扩展实战:实现 SaTokenDao 接口对接 Redis、MongoDB 等持久化中间件

Sa-Token 缓存层扩展实战:实现 SaTokenDao 接口对接 Redis、MongoDB 等持久化中间件

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

对于权限认证框架而言,最容易碰到的扩展点就是数据存储方式:默认实现把 Token、Session 都放在 JVM 内存中,重启即丢、无法多节点共享。Sa-Token 将所有数据持久化操作抽象到了SaTokenDao接口,开发者只需实现该接口(或基于框架提供的次级接口做少量实现),即可将会话数据对接到 Redis、MongoDB、Caffeine 等任意存储介质上。读完本文,你将掌握SaTokenDao的完整方法契约、timeout 取值语义、框架"跟随式"次级接口的设计,以及官方 Redis 集成包与自定义 MongoDB 实现的源码级细节,并知道如何选型官方已提供的 9 类集成方案。

一、为什么需要缓存层扩展:默认内存方案的边界

Sa-Token 的登录态由两类数据构成:Token 对应的字符串数据,以及SaSession会话对象。默认情况下它们存储在内存缓存中——读写速度最快,且避免了序列化与反序列化的性能消耗,但存在两个固有限制:

  1. 系统重启后数据丢失:所有登录态、Session 数据清零,用户被迫重新登录;
  2. 无法在分布式环境中共享数据:多节点部署时,A 节点登录的会话 B 节点无法识别。

为此,Sa-Token 把全部持久化操作收敛到一个接口中。对接新的存储介质,只需要提供该接口的一个实现,无需改动任何业务代码。接口定义见 SaTokenDao.java,其 Javadoc 明确指出:"此接口的不同实现类可将数据存储至不同位置,如:内存Map、Redis 等等。如果你要自定义数据存储策略,也需通过实现此接口来完成。"

二、SaTokenDao 接口全解:方法契约与超时语义

SaTokenDao定义了四组方法,覆盖了会话数据的全生命周期操作:

2.1 字符串读写(最底层的六件套)

方法语义
String get(String key)获取 value,如无返空
void set(String key, String value, long timeout)写入 value 并设定存活时间(单位:秒)
void update(String key, String value)更新 value,过期时间不变
void delete(String key)删除 value
long getTimeout(String key)获取 value 的剩余存活时间(单位:秒)
void updateTimeout(String key, long timeout)修改剩余存活时间(单位:秒)

2.2 对象读写

getObject(key)getObject(key, classType)setObject(key, object, timeout)updateObjectdeleteObjectgetObjectTimeoutupdateObjectTimeout,方法与字符串组一一对应,但操作的是可序列化的对象(例如SaSession)。

2.3 SaSession 读写

getSessionsetSessionupdateSessiondeleteSessiongetSessionTimeoutupdateSessionTimeout。这组方法在接口层面有默认实现,复用对象读写(详见下文"跟随式"次级接口)。

2.4 会话管理与生命周期

  • List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType):按前缀+关键字搜索 key 集合,用于踢人下线、按条件查询会话等场景。size传 -1 表示从start处一直取到末尾;
  • default void init()/default void destroy():实例被装载/卸载时的生命周期钩子,内存型实现用它来启停过期数据的定时清理线程。

2.5 timeout 参数的取值语义

接口的 Javadoc 与 Redis 官方实现的注释共同定义了 timeout 的完整取值规则,实现时务必严格遵守:

取值含义
timeout > 0限时存储,单位为秒
timeout == SaTokenDao.NEVER_EXPIRE(即-1永久存储
timeout == 0<= SaTokenDao.NOT_VALUE_EXPIRE(即≤ -2不存储(直接 return)

另外,getTimeout对不存在的 key 应返回NOT_VALUE_EXPIRE(-2),实现方可据此区分"key 不存在"与"永不过期"(-1)。这两个常量定义于 SaTokenDao.java。

三、"跟随式"次级接口:实现时到底要写几个方法

完整实现SaTokenDao需要写 20 个方法,工作量大且容易出错。为此框架在dao.auto包中提供了三层继承结构的次级接口,全部采用 default 方法"向下折叠",让实现方只写真正需要持久化的那一层:

  1. SaTokenDaoByObjectFollowString:Object 读写跟随 String 读写。接口注释明确标注"推荐中间件型缓存实现 implements 此接口"。它借助SaManager.getSaSerializerTemplate()完成 String↔Object 的 JSON 序列化转换,实现方只需实现字符串组 6 个方法 +searchData
  2. SaTokenDaoByStringFollowObject:String 读写跟随 Object 读写,注释标注"推荐内存型缓存实现 implements 此接口"。内存缓存直接存对象即可,省去序列化开销,实现方只需实现对象组;
  3. SaTokenDaoBySessionFollowObject:SaSession 读写跟随 Object 读写getSession默认委托给getObject(sessionId, SaStrategy.instance.sessionClassType)——会话类的类型是可被策略(Strategy)替换的,这是 Sa-Token 可定制性设计的体现。

从源码结构看,继承链为SaTokenDao → SaTokenDaoBySessionFollowObject → (ByStringFollowObject | ByObjectFollowString),两者最终都实现了完整的SaTokenDao契约。选择原则很简单:往中间件(Redis、MongoDB 等)里写的数据天然是字符串,就实现SaTokenDaoByObjectFollowString;纯内存缓存直接存 Java 对象,就实现SaTokenDaoByStringFollowObject

四、默认内存实现与官方集成包全家福

默认实现 SaTokenDaoDefaultImpl 位于 core 核心包中,基于SaTimedCache(底层为两个ConcurrentHashMap包装)存储字符串与对象,实现了SaTokenDaoByStringFollowObjectinit()时启动定时刷新线程清理过期数据,destroy()时结束该线程。正如类注释所述,它是"内存缓存,系统重启后数据丢失"。

框架已提供的集成包包括(对应源码均位于sa-token-plugin/目录):

  • 默认方式:储存在内存中,位于 core 核心包(SaTokenDaoDefaultImpl.java);
  • sa-token-redis-template:Redis Template 集成包,可用环境为 SpringBoot2、SpringBoot3、SpringBoot4(SaTokenDaoForRedisTemplate.java);
  • sa-token-redis-template-jdk-serializer:Redis 集成包,使用 JDK 默认序列化方式;
  • sa-token-hutool-timed-cache:集成 hutool 框架的 Timed-Cache 缓存方案(基于内存);
  • sa-token-caffeine:集成 Caffeine 缓存方案(基于内存,SaTokenDaoForCaffeine.java);
  • sa-token-redisson:集成 Redisson 客户端;
  • sa-token-redisson-spring-boot-starter:集成 Redisson 客户端的 SpringBoot 自动配置包;
  • sa-token-alone-redisson:独立 Redisson 连接,权限缓存与业务缓存分离;
  • sa-token-redisx:Redisx 集成包。

以 Caffeine 方案为例,SaTokenDaoForCaffeine 与默认实现结构一致:仍实现SaTokenDaoByStringFollowObject,只是把底层的 Map 包装替换为SaMapPackageForCaffeine,可见内存型方案之间"换底不换形"的扩展模式。

注意(Redisson 升级兼容)SaTokenDaoForRedisson默认使用StringCodec。从旧版本升级时,请先清空 Redis 中的 Sa-Token 缓存,或构造时传入Kryo5Codec以兼容旧数据,详见 集成 Redis - 集成 Redisson。

五、源码深读:Redis 实现的关键细节

SaTokenDaoForRedisTemplate 是理解"如何实现一个中间件型 SaTokenDao"的最佳范本,其实现细节值得逐一拆解:

1. 只实现字符串层。类签名为implements SaTokenDaoByObjectFollowString, SaTokenDao——全部对象/Session 读写由 default 方法折叠到get/set/update/delete/getTimeout/updateTimeout+searchData这 7 个方法上。

2. 惰性初始化init(RedisConnectionFactory)通过@Autowired注入,内部用isInit标记防止重复初始化,构建StringRedisTemplate后调用afterPropertiesSet()

3. timeout 语义的严格落地set方法开头即判断if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) return;,命中NEVER_EXPIRE(-1)时执行无过期时间的set,否则按秒设置 TTL——与接口契约完全一致。

4. update 的 KEEPTTL 实现update要求"值更新但过期时间不变",官方实现采用 Redis 6.0+ 的SET key value XX KEEPTTL(见 setStringAndKeepTTL 方法),且XX选项保证仅当 key 存在时才覆写。前提:Redis 版本必须 ≥ 6.0。类尾部注释还贴心地给出了低于 6.0 的兼容写法(先读 TTL 再带 TTL 重写)。

5. updateTimeout 的"永久化"处理。传入 -1 时,若当前已为永久则不作处理;否则读取现值后重新set一次以去除 TTL。这一"先查后写"的逻辑在 MongoDB 参考实现中被原样复用,是各实现必须处理的边界。

6. 基于 SCAN 的 searchDatasearchData使用ScanOptions(count=1000)游标遍历代替阻塞式的KEYS,先对prefix + "*" + keyword + "*"整体做wrapKey包装再执行 scan,最后交给SaFoxUtil.searchList完成分页与排序——这是生产环境可用的扫描式搜索范本。

7. wrapKey 扩展点wrapKey(String key)默认原样返回,注释说明"需要给 Redis 键加统一前缀时,可重写此方法",方便多系统共用一个 Redis 时做键隔离。

该实现的正确性由单元测试覆盖:SaTokenDaoForRedisTemplateTest 与 SaTokenDaoForRedisTemplateSearchDataTest;Caffeine 方案同样配有 SaTokenDaoForCaffeineTest。

六、自定义实现实战:将 Session 持久化到 MongoDB

对于官方未提供集成包的介质(如 MongoDB),可以完全自定义SaTokenDao实现。仓库文档 集成 MongoDB 参考一(另见 集成 MongoDB 参考二)给出了完整示例,先决条件为 Spring Boot 3 + Spring Data MongoDB,引入spring-boot-starter-data-mongodb依赖即可。

第一步:定义数据包装类,利用 MongoDB TTL 索引实现自动清理过期数据:

@Document("saTokenMongo") // 你也可以自定义集合名称 public class SaTokenMongoData { @Id private String id; // token @Indexed(unique = true) private String key; // sa-token 的 session private SaSession session; // sa-token 的 token string private String string; // 给 expireAt 添加 @Indexed(expireAfterSeconds = 0),过期时 MongoDB 自动删除 @SuppressWarnings("removal") @Indexed(expireAfterSeconds = 0) private LocalDateTime expireAt; // 忽略 getter setter }

第二步:实现 SaTokenDao。示例类SaTokenMongoDao仿照官方 Redis 集成实现,核心方法要点如下(完整代码见上述文档):

@Component public class SaTokenMongoDao implements SaTokenDao { private final MongoTemplate mongoTemplate; public SaTokenMongoDao(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } private Query keyQuery(String key) { return Query.query(Criteria.where("key").is(key)); } @Override public String get(String key) { return Optional.ofNullable(mongoTemplate.findOne(keyQuery(key), SaTokenMongoData.class)) .map(SaTokenMongoData::getString).orElse(null); } // timeout == NEVER_EXPIRE 时 expireAt 置 null,MongoDB 不会删除该记录 LocalDateTime getExpireAtFromTimeout(long timeout) { return timeout == SaTokenDao.NEVER_EXPIRE ? null : LocalDateTime.now().plusSeconds(timeout); } @Override public void set(String key, String value, long timeout) { if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) { return; } mongoTemplate.upsert( keyQuery(key), Update.update("string", value).set("expireAt", getExpireAtFromTimeout(timeout)), SaTokenMongoData.class ); } @Override public void updateTimeout(String key, long timeout) { // 与 Redis 实现相同的"永久化"边界处理: // 已永久则不处理;否则读取现值重新 set 一次 if (timeout == SaTokenDao.NEVER_EXPIRE) { long expire = getTimeout(key); if (expire == SaTokenDao.NEVER_EXPIRE) { // 已经被设置为永久,不作任何处理 } else { this.set(key, this.get(key), timeout); } return; } mongoTemplate.upsert( keyQuery(key), Update.update("expireAt", getExpireAtFromTimeout(timeout)), SaTokenMongoData.class ); } // 其余 getTimeout / searchData(正则匹配 + skip/limit 分页)等方法见完整文档 }

该示例验证了SaTokenDao契约的可移植性:timeout 三值语义(>0 限时 / -1 永久 / 0 或 ≤-2 不存储)、update的"不改过期时间"约定、updateTimeout(-1)的永久化边界,在 Redis、MongoDB 等不同介质上的落地方式高度一致——这正是接口抽象带来的工程红利。

七、选型建议与版本注意事项

结合仓库实际情况,给出选型决策路径:

  1. 想省心:直接使用sa-token-redis-templatesa-token-redisson-spring-boot-starter,依赖 + 自动装配即可,详细步骤见 集成 Redis;
  2. 权限缓存与业务缓存要隔离:选sa-token-alone-redisson(独立 Redisson 连接);
  3. 单机/演示环境、不想依赖外部中间件:默认内存实现或sa-token-caffeinesa-token-hutool-timed-cache(均基于内存,重启丢数据,适合非多节点场景);
  4. 非 Redis 介质:参照第五、六节的模式自定义实现,优先选择正确的次级接口(中间件型选SaTokenDaoByObjectFollowString,内存型选SaTokenDaoByStringFollowObject)。

版本与环境的适用前提需注意:

  • sa-token-redis-template/sa-token-redisson自 v1.46.0 起使用了 Redis 6.0+ 的SET KEEPTTL特性,Redis 服务低于 6.0 会报ERR syntax error(兼容写法见 SaTokenDaoForRedisTemplate 尾部注释);
  • Redisson 方案升级时的 Codec 兼容问题(StringCodecvsKryo5Codec)见第一节"注意";
  • 往 Session 存自定义实体类后从 Redis 读回若报"无法反序列化的类型",需要先配置 JSON 全局类型白名单。

八、小结

SaTokenDao是 Sa-Token 面向数据存储的核心扩展点:接口以"字符串 + 对象 + Session + 搜索 + 生命周期"五组方法定义了完整契约,timeout 采用>0 / -1 / 0或≤-2三值语义;三层"跟随式"次级接口让中间件型实现只需编写 7 个方法;官方已提供内存、RedisTemplate、Redisson、Caffeine、Hutool Timed-Cache 等 9 类集成方案,自定义 MongoDB 等介质的实现路径也可由 Redis 范本与官方参考文档直接推导。理解并掌握这一扩展点,即可让 Sa-Token 的会话存储适配任意基础设施,兼顾重启不丢失与分布式一致性。

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 13:28:07

Zulip 自动化测试体系完全指南:从 test-all 到单测隔离策略

Zulip 自动化测试体系完全指南&#xff1a;从 test-all 到单测隔离策略 【免费下载链接】zulip Zulip server and web application. Open-source team chat that helps teams stay productive and focused. 项目地址: https://gitcode.com/GitHub_Trending/zu/zulip Zul…

作者头像 李华
网站建设 2026/9/13 13:26:44

gpt-image-2生产级图像生成实战:API调参、提示词工程与资源清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 13:26:24

Bokeh 命令行子命令框架解析:bokeh.command.subcommand 设计与实战

Bokeh 命令行子命令框架解析&#xff1a;bokeh.command.subcommand 设计与实战 【免费下载链接】bokeh Interactive Data Visualization in the browser, from Python 项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh bokeh.command.subcommand 是 Bokeh 命令行…

作者头像 李华
网站建设 2026/9/13 13:25:57

BokehJS 纯 JavaScript 开发指南:模型、Plotting 与 Charts 接口详解

BokehJS 纯 JavaScript 开发指南&#xff1a;模型、Plotting 与 Charts 接口详解 【免费下载链接】bokeh Interactive Data Visualization in the browser, from Python 项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh 导读 BokehJS 是 Bokeh 的客户端运行时…

作者头像 李华
网站建设 2026/9/13 13:25:47

GitHub项目可视化工具:静态代码分析与架构解构

1. 项目概述&#xff1a;GitHub项目透明化工具的价值与意义在开源社区摸爬滚打多年&#xff0c;我见过太多开发者面对高Star项目时的困惑&#xff1a;代码结构复杂、文档缺失、核心逻辑难以快速把握。最近发现一个名为GitDiagram的工具&#xff08;非官方命名&#xff0c;根据功…

作者头像 李华
网站建设 2026/9/13 13:24:01

MicroPython固件集成TFLM与ulab:ESP32-S3手势识别部署实践

简介&#xff1a;一份为微控制器量身定制的MicroPython固件工程&#xff0c;面向嵌入式开发者与AI边缘计算爱好者&#xff0c;目标是在ESP32等MCU上集成TensorFlow Lite与ulab&#xff0c;让开发者直接用Python开展轻量级神经网络实验。工程基于USER_C_MODULES机制扩展&#xf…

作者头像 李华