diff --git a/README.md b/README.md
index bb1bfc243bf95e49679c19075e0ebf546c0ec800..a5b85eabfef2227ec49550a2bcddae6fb1613d57 100644
--- a/README.md
+++ b/README.md
@@ -11,12 +11,13 @@ Sa-Token 三方插件合集,希望本仓库可以最大程度汇集社区的
#### 插件列表
目前已开发的插件列表:
-| 插件 | 作者 | 介绍 | 是否已发布 | 使用文档 |
-| :-------- | :-------- | :-------- | :-------- | :-------- |
-| sa-token-three-example-plugin | 孔明 | 为第三方插件开发时提供的示例工程 | 未发布 | [详情](sa-token-three-example-plugin/README.md) |
-| sa-token-three-redis-jackson-add-prefix | RockMan | 为 Sa-Token 在 Redis 中的 key 添加上指定前缀 | 未发布 | [详情](sa-token-three-redis-jackson-add-prefix/README.md) |
-| sa-token-three-custom-check-permission | RockMan | 自定义 Sa-Token 的鉴权逻辑,不再一次性返回整个权限码集合给框架判断,而是根据自定义验证规则返回 true 或 false 给框架 | 未发布 | [详情](sa-token-three-custom-check-permission/README.md) |
-| sa-token-three-token-prefix-compatible-cookie | 就剩一个桃 | 让 sa-token 在打开前缀模式时,Cookie 鉴权依然生效 | 未发布 | [详情](sa-token-three-token-prefix-compatible-cookie/README.md) |
+| 插件 | 作者 | 介绍 | 是否已发布 | 使用文档 |
+| :-------- |:---------|:-----------------------------------------------------------------------| :-------- |:---------------------------------------------------------------|
+| sa-token-three-example-plugin | 孔明 | 为第三方插件开发时提供的示例工程 | 未发布 | [详情](sa-token-three-example-plugin/README.md) |
+| sa-token-three-redis-jackson-add-prefix | RockMan | 为 Sa-Token 在 Redis 中的 key 添加上指定前缀 | 未发布 | [详情](sa-token-three-redis-jackson-add-prefix/README.md) |
+| sa-token-three-custom-check-permission | RockMan | 自定义 Sa-Token 的鉴权逻辑,不再一次性返回整个权限码集合给框架判断,而是根据自定义验证规则返回 true 或 false 给框架 | 未发布 | [详情](sa-token-three-custom-check-permission/README.md) |
+| sa-token-three-token-prefix-compatible-cookie | 就剩一个桃 | 让 sa-token 在打开前缀模式时,Cookie 鉴权依然生效 | 未发布 | [详情](sa-token-three-token-prefix-compatible-cookie/README.md) |
+| sa-token-three-jdbc-fastjson2 | moon69 | 数据库持久化插件 | 未发布 | [详情](sa-token-three-jdbc-fastjson2/README.md) |
#### 使用方式
diff --git a/pom.xml b/pom.xml
index 186446e50549ef883ac89242e04dc9db3a451836..6a45e430e731d292d9e40b6af7e21769ca33ca8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,7 @@
sa-token-three-redis-jackson-add-prefix
sa-token-three-custom-check-permission
sa-token-three-token-prefix-compatible-cookie
+ sa-token-three-jdbc-fastjson2
diff --git a/sa-token-three-jdbc-fastjson2/README.md b/sa-token-three-jdbc-fastjson2/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..86524f0d4c1a088a2810fc02ac57f093b8fc2a87
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/README.md
@@ -0,0 +1,46 @@
+## sa-token-three-jdbc-fastjson2
+
+### 插件介绍
+Sa-Token 数据库持久化插件。
+
+### 使用方式
+1. 引入插件:
+``` xml
+
+ cn.dev33
+ sa-token-three-jdbc-fastjson2
+ ${sa-token.version}
+
+```
+
+2. 在 application.yml 配置数据源:
+``` yaml
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/test
+ username: test
+ password: test
+```
+
+3. 在启动类配置注解:
+``` java
+@MapperScan(basePackages = "cn.dev33.satoken.dao.mapper")
+```
+
+4. 在数据库新增表结构,以下为 MySQL 的表结构:
+``` mysql
+# 本插件使用 mybatis-plus 作为 ORM 框架, mybatis-plus 支持的数据库本插件应该都支持
+# token_value 的长度可根据实际需求进行修改
+
+CREATE TABLE `sa_token_data` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `token_key` varchar(255) NOT NULL COMMENT '键',
+ `token_value` varchar(1000) NOT NULL COMMENT '值',
+ `expire_time` bigint NOT NULL COMMENT '过期时间戳',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_key` (`token_key`)
+) ENGINE=InnoDB;
+```
+
+### 联系方式
+使用时如遇问题,请在 sa-token-three-plugin 中提交 issue 咨询
\ No newline at end of file
diff --git a/sa-token-three-jdbc-fastjson2/pom.xml b/sa-token-three-jdbc-fastjson2/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1210b1a51367ba974002844463447b6929853331
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/pom.xml
@@ -0,0 +1,37 @@
+
+
+
+ sa-token-three-plugin
+ cn.dev33
+ ${revision}
+ ../pom.xml
+
+ 4.0.0
+
+ sa-token-three-jdbc-fastjson2
+
+
+ 3.5.3.2
+
+
+
+
+
+ cn.dev33
+ sa-token-core
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ ${mybatis-plus.version}
+
+
+
\ No newline at end of file
diff --git a/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/SaSessionForJdbcCustomized.java b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/SaSessionForJdbcCustomized.java
new file mode 100644
index 0000000000000000000000000000000000000000..77c5f932f9bbc3f324437d99d04295b302b1cdda
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/SaSessionForJdbcCustomized.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * 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 cn.dev33.satoken.dao;
+
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.util.SaFoxUtil;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.annotation.JSONField;
+
+
+/**
+ * 定制版 SaSession
+ *
+ * @author moon69
+ * @since 1.37.0
+ */
+public class SaSessionForJdbcCustomized extends SaSession {
+
+ private static final long serialVersionUID = 4855910561516650589L;
+
+ public SaSessionForJdbcCustomized() {
+ super();
+ }
+
+ public SaSessionForJdbcCustomized(String id) {
+ super(id);
+ }
+
+ @Override
+ public T getModel(String key, Class cs) {
+ if (SaFoxUtil.isBasicType(cs)) {
+ return SaFoxUtil.getValueByType(get(key), cs);
+ }
+
+ return JSON.parseObject(getString(key), cs);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T getModel(String key, Class cs, Object defaultValue) {
+ Object value = get(key);
+ if (valueIsNull(value)) {
+ return (T) defaultValue;
+ }
+
+ if (SaFoxUtil.isBasicType(cs)) {
+ return SaFoxUtil.getValueByType(get(key), cs);
+ }
+
+ return JSON.parseObject(getString(key), cs);
+ }
+
+ /**
+ * 忽略 timeout 字段的序列化
+ */
+ @Override
+ @JSONField(serialize = false)
+ public long getTimeout() {
+ return super.getTimeout();
+ }
+}
diff --git a/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/SaTokenDaoJdbcImpl.java b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/SaTokenDaoJdbcImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6deb92b0d4adc56a4e7452f23da826b2a6b4a111
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/SaTokenDaoJdbcImpl.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * 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 cn.dev33.satoken.dao;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.dao.entity.SaTokenData;
+import cn.dev33.satoken.dao.mapper.SaTokenDataMapper;
+import cn.dev33.satoken.strategy.SaStrategy;
+import cn.dev33.satoken.util.SaFoxUtil;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * Sa-Token 持久层实现 [ 数据库存储、FastJson2序列化 ]
+ *
+ * @author moon69
+ * @since 1.37.0
+ */
+@Component
+public class SaTokenDaoJdbcImpl implements SaTokenDao {
+
+ private SaTokenDataMapper saTokenDataMapper;
+
+ public SaTokenDaoJdbcImpl(SaTokenDataMapper saTokenDataMapper) {
+ this.saTokenDataMapper = saTokenDataMapper;
+
+ // 重写 SaSession 生成策略
+ SaStrategy.instance.createSession = SaSessionForJdbcCustomized::new;
+ }
+
+ // --------------------- 字符串读写 ---------------------
+
+ @Override
+ public String get(String key) {
+ SaTokenData saTokenData = saTokenDataMapper.findByTokenKey(key);
+ if (clearKey(saTokenData)) {
+ return null;
+ }
+
+ return saTokenData.getTokenValue();
+ }
+
+ @Override
+ public void set(String key, String value, long timeout) {
+ if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
+ return;
+ }
+
+ SaTokenData saTokenData = saTokenDataMapper.findByTokenKey(key);
+ Long expireTime = (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000);
+ // 不存在则新增,存在则更新
+ if (saTokenData == null) {
+ saTokenData = new SaTokenData();
+ saTokenData.setTokenKey(key);
+ saTokenData.setTokenValue(value);
+ saTokenData.setExpireTime(expireTime);
+ saTokenDataMapper.insert(saTokenData);
+ } else {
+ saTokenData.setTokenValue(value);
+ saTokenData.setExpireTime(expireTime);
+ saTokenDataMapper.updateById(saTokenData);
+ }
+ }
+
+ @Override
+ public void update(String key, String value) {
+ SaTokenData saTokenData = saTokenDataMapper.findByTokenKey(key);
+ if (getKeyTimeout(saTokenData) == SaTokenDao.NOT_VALUE_EXPIRE) {
+ return;
+ }
+
+ saTokenData.setTokenValue(value);
+ saTokenDataMapper.updateById(saTokenData);
+ }
+
+ @Override
+ public void delete(String key) {
+ saTokenDataMapper.deleteByTokenKey(key);
+ }
+
+ @Override
+ public long getTimeout(String key) {
+ return getKeyTimeout(key);
+ }
+
+ @Override
+ public void updateTimeout(String key, long timeout) {
+ saTokenDataMapper.updateExpireTime(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
+ }
+
+ // --------------------- 对象读写 ---------------------
+
+ @Override
+ public Object getObject(String key) {
+ SaTokenData saTokenData = saTokenDataMapper.findByTokenKey(key);
+ if (clearKey(saTokenData)) {
+ return null;
+ }
+
+ return JSON.parseObject(saTokenData.getTokenValue(), Object.class, JSONReader.Feature.SupportAutoType);
+ }
+
+ @Override
+ public void setObject(String key, Object object, long timeout) {
+ if (timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
+ return;
+ }
+
+ SaTokenData saTokenData = saTokenDataMapper.findByTokenKey(key);
+ Long expireTime = (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000);
+ // 不存在则新增,存在则更新
+ if (saTokenData == null) {
+ saTokenData = new SaTokenData();
+ saTokenData.setTokenKey(key);
+ saTokenData.setTokenValue(JSON.toJSONString(object, JSONWriter.Feature.WriteClassName));
+ saTokenData.setExpireTime(expireTime);
+ saTokenDataMapper.insert(saTokenData);
+ } else {
+ saTokenData.setTokenValue(JSON.toJSONString(object, JSONWriter.Feature.WriteClassName));
+ saTokenData.setExpireTime(expireTime);
+ saTokenDataMapper.updateById(saTokenData);
+ }
+ }
+
+ @Override
+ public void updateObject(String key, Object object) {
+ SaTokenData saTokenData = saTokenDataMapper.findByTokenKey(key);
+ if (getKeyTimeout(saTokenData) == SaTokenDao.NOT_VALUE_EXPIRE) {
+ return;
+ }
+
+ saTokenData.setTokenValue(JSON.toJSONString(object, JSONWriter.Feature.WriteClassName));
+ saTokenDataMapper.updateById(saTokenData);
+ }
+
+ @Override
+ public void deleteObject(String key) {
+ saTokenDataMapper.deleteByTokenKey(key);
+ }
+
+ @Override
+ public long getObjectTimeout(String key) {
+ return getKeyTimeout(key);
+ }
+
+ @Override
+ public void updateObjectTimeout(String key, long timeout) {
+ saTokenDataMapper.updateExpireTime(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));
+ }
+
+ // --------------------- 会话管理 ---------------------
+
+ @Override
+ public List searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+ List keyList = saTokenDataMapper.findKeyList(prefix, keyword);
+ return SaFoxUtil.searchList(keyList, start, size, sortType);
+ }
+
+ // --------------------- 数据过期 ---------------------
+
+ /**
+ * @param saTokenData saToken数据
+ * @return true-数据过期被清理 false-数据存活
+ */
+ public boolean clearKey(SaTokenData saTokenData) {
+ // 不存在则当作已被清理
+ if (saTokenData == null) {
+ return true;
+ }
+
+ // 删除过期数据
+ Long expireTime = saTokenData.getExpireTime();
+ if (expireTime != SaTokenDao.NEVER_EXPIRE && expireTime < System.currentTimeMillis()) {
+ saTokenDataMapper.deleteById(saTokenData);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取指定 key 的剩余存活时间 (单位:秒)
+ *
+ * @param key 指定 key
+ * @return 这个 key 的剩余存活时间
+ */
+ public long getKeyTimeout(String key) {
+ SaTokenData saTokenData = saTokenDataMapper.findByTokenKey(key);
+ return getKeyTimeout(saTokenData);
+ }
+
+ /**
+ * 获取指定 key 的剩余存活时间 (单位:秒)
+ *
+ * @param saTokenData saToken数据
+ * @return 这个 key 的剩余存活时间
+ */
+ public long getKeyTimeout(SaTokenData saTokenData) {
+ // 由于数据过期检测属于惰性扫描,很可能此时这个 key 已经是过期状态了,所以这里需要先检查一下
+ if (clearKey(saTokenData)) {
+ return SaTokenDao.NOT_VALUE_EXPIRE;
+ }
+
+ // 如果 expire 被标注为永不过期,则返回 NEVER_EXPIRE
+ if (saTokenData.getExpireTime() == SaTokenDao.NEVER_EXPIRE) {
+ return SaTokenDao.NEVER_EXPIRE;
+ }
+
+ // 计算剩余时间并返回 (过期时间戳 - 当前时间戳) / 1000 转秒
+ long timeout = (saTokenData.getExpireTime() - System.currentTimeMillis()) / 1000;
+
+ // 小于零时,视为不存在
+ if (timeout < 0) {
+ saTokenDataMapper.deleteByTokenKey(saTokenData.getTokenKey());
+ return SaTokenDao.NOT_VALUE_EXPIRE;
+ }
+
+ return timeout;
+ }
+
+ // --------------------- 定时清理过期数据 ---------------------
+
+ /**
+ * 执行数据清理的线程引用
+ */
+ public Thread refreshThread;
+
+ /**
+ * 是否继续执行数据清理的线程标记
+ */
+ public volatile boolean refreshFlag;
+
+ /**
+ * 初始化定时任务,定时清理过期数据
+ */
+ public void initRefreshThread() {
+ // 如果开发者配置了 <=0 的值,则不启动定时清理
+ if (SaManager.getConfig().getDataRefreshPeriod() <= 0) {
+ return;
+ }
+
+ refreshFlag = true;
+ refreshThread = new Thread(() -> {
+ for (; ; ) {
+ try {
+ try {
+ // 如果已经被标记为结束
+ if (!refreshFlag) {
+ return;
+ }
+
+ // 执行清理
+ refreshDataMap();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // 休眠N秒
+ int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
+ if (dataRefreshPeriod <= 0) {
+ dataRefreshPeriod = 1;
+ }
+ Thread.sleep(dataRefreshPeriod * 1000L);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ refreshThread.start();
+ }
+
+ /**
+ * 清理所有已经过期的 key
+ */
+ public void refreshDataMap() {
+ saTokenDataMapper.deleteByExpireTime(0L, System.currentTimeMillis());
+ }
+
+ @Override
+ public void init() {
+ // 定时清理过期数据
+ initRefreshThread();
+ }
+
+ @Override
+ public void destroy() {
+ // 不再定时清理过期数据
+ refreshFlag = false;
+ }
+}
diff --git a/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/entity/SaTokenData.java b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/entity/SaTokenData.java
new file mode 100644
index 0000000000000000000000000000000000000000..554090d7dcb032dbeb3420a0c778172e9444bc2e
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/entity/SaTokenData.java
@@ -0,0 +1,54 @@
+package cn.dev33.satoken.dao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+/**
+ * sa_token_data 实体类
+ *
+ * @author moon69
+ * @since 1.37.0
+ */
+public class SaTokenData {
+
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ private String tokenKey;
+
+ private String tokenValue;
+
+ private Long expireTime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTokenKey() {
+ return tokenKey;
+ }
+
+ public void setTokenKey(String tokenKey) {
+ this.tokenKey = tokenKey;
+ }
+
+ public String getTokenValue() {
+ return tokenValue;
+ }
+
+ public void setTokenValue(String tokenValue) {
+ this.tokenValue = tokenValue;
+ }
+
+ public Long getExpireTime() {
+ return expireTime;
+ }
+
+ public void setExpireTime(Long expireTime) {
+ this.expireTime = expireTime;
+ }
+}
diff --git a/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/mapper/SaTokenDataMapper.java b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/mapper/SaTokenDataMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..1509922f9813efe09cc2f917640c47153c3ae83f
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/src/main/java/cn/dev33/satoken/dao/mapper/SaTokenDataMapper.java
@@ -0,0 +1,86 @@
+package cn.dev33.satoken.dao.mapper;
+
+import cn.dev33.satoken.dao.entity.SaTokenData;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * sa_token_data Mapper
+ *
+ * @author moon69
+ * @since 1.37.0
+ */
+public interface SaTokenDataMapper extends BaseMapper {
+
+ /**
+ * select * from sa_token_data where token_key = tokenKey
+ *
+ * @param tokenKey 键
+ * @return SaTokenData
+ */
+ default SaTokenData findByTokenKey(String tokenKey) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper()
+ .eq(SaTokenData::getTokenKey, tokenKey);
+ return this.selectOne(wrapper);
+ }
+
+ /**
+ * select token_key from where token_key like 'prefix%' and token_key like '%keyword%'
+ *
+ * @param prefix 前缀
+ * @param keyword 关键字
+ * @return 查询到的数据集合
+ */
+ default List findKeyList(String prefix, String keyword) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper()
+ .select(SaTokenData::getTokenKey)
+ .likeRight(SaTokenData::getTokenKey, prefix)
+ .like(SaTokenData::getTokenKey, keyword);
+ return this.selectList(wrapper)
+ .stream()
+ .map(SaTokenData::getTokenKey)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * delete from sa_token_data where token_key = tokenKey
+ *
+ * @param tokenKey 键
+ */
+ default void deleteByTokenKey(String tokenKey) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper()
+ .eq(SaTokenData::getTokenKey, tokenKey);
+ this.delete(wrapper);
+ }
+
+ /**
+ * delete from sa_token_data where expire_time > leftTime and expire_time < rightTime
+ *
+ * @param leftTime 大于的时间戳
+ * @param rightTime 小于的时间戳
+ */
+ default void deleteByExpireTime(Long leftTime, Long rightTime) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper()
+ .gt(SaTokenData::getExpireTime, leftTime)
+ .lt(SaTokenData::getExpireTime, rightTime);
+ this.delete(wrapper);
+ }
+
+ /**
+ * update sa_token_data set expire_time = expireTime where token_key = tokenKey
+ *
+ * @param tokenKey 键
+ * @param expireTime 过期时间
+ */
+ default void updateExpireTime(String tokenKey, Long expireTime) {
+ LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper()
+ .set(SaTokenData::getExpireTime, expireTime)
+ .eq(SaTokenData::getTokenKey, tokenKey);
+ this.update(null, wrapper);
+ }
+
+}
diff --git a/sa-token-three-jdbc-fastjson2/src/main/resources/META-INF/spring.factories b/sa-token-three-jdbc-fastjson2/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000000000000000000000000000000000..828b9962c0e0ba1bbd1f84ec242e57e404887459
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoJdbcImpl
\ No newline at end of file
diff --git a/sa-token-three-jdbc-fastjson2/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/sa-token-three-jdbc-fastjson2/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000000000000000000000000000000000..16d02c951af3d9343567844002866474eee25787
--- /dev/null
+++ b/sa-token-three-jdbc-fastjson2/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.dev33.satoken.dao.SaTokenDaoJdbcImpl
\ No newline at end of file