sa-token-demo-alone-redis
sa-token-demo-alone-redis-cluster
+ sa-token-demo-alone-redis-sb4
sa-token-demo-apikey
sa-token-demo-async
sa-token-demo-beetl
diff --git a/sa-token-demo/sa-token-demo-alone-redis-sb4/pom.xml b/sa-token-demo/sa-token-demo-alone-redis-sb4/pom.xml
new file mode 100644
index 00000000..e157da08
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-alone-redis-sb4/pom.xml
@@ -0,0 +1,78 @@
+
+ 4.0.0
+ cn.dev33
+ sa-token-demo-alone-redis-sb4
+ 0.0.1-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.3
+
+
+
+
+
+ 1.45.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc
+
+
+ org.springframework.boot
+ spring-boot-starter-aspectj
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot4-starter
+ ${sa-token.version}
+
+
+
+
+ cn.dev33
+ sa-token-redis-template
+ ${sa-token.version}
+
+
+
+
+ cn.dev33
+ sa-token-redis-template-jdk-serializer
+ ${sa-token.version}
+ test
+
+
+
+
+ org.apache.commons
+ commons-pool2
+
+
+
+
+ cn.dev33
+ sa-token-alone-redis-by-spring-boot4
+ ${sa-token.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
diff --git a/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/java/com/pj/SaTokenAloneRedisSb4Application.java b/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/java/com/pj/SaTokenAloneRedisSb4Application.java
new file mode 100644
index 00000000..eedf1503
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/java/com/pj/SaTokenAloneRedisSb4Application.java
@@ -0,0 +1,20 @@
+package com.pj;
+
+import cn.dev33.satoken.SaManager;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Sa-Token 整合 SpringBoot4 示例,整合 alone-redis 插件(权限缓存与业务缓存分离)
+ *
+ * @author click33
+ */
+@SpringBootApplication
+public class SaTokenAloneRedisSb4Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SaTokenAloneRedisSb4Application.class, args);
+ System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/java/com/pj/test/TestController.java
new file mode 100644
index 00000000..240cd2eb
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/java/com/pj/test/TestController.java
@@ -0,0 +1,40 @@
+package com.pj.test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.util.SaResult;
+
+/**
+ * 测试专用Controller,演示 alone-redis 权限缓存与业务缓存分离
+ *
+ * @author click33
+ */
+@RestController
+@RequestMapping("/test/")
+public class TestController {
+
+ @Autowired
+ StringRedisTemplate stringRedisTemplate;
+
+ // 测试Sa-Token缓存,浏览器访问: http://localhost:8083/test/login
+ @RequestMapping("login")
+ public SaResult login(@RequestParam(defaultValue = "10001") String id) {
+ System.out.println("--------------- 测试Sa-Token缓存");
+ StpUtil.login(id);
+ return SaResult.ok();
+ }
+
+ // 测试业务缓存,浏览器访问: http://localhost:8083/test/test
+ @RequestMapping("test")
+ public SaResult test() {
+ System.out.println("--------------- 测试业务缓存");
+ stringRedisTemplate.opsForValue().set("hello", "Hello World");
+ return SaResult.ok();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/resources/application.yml
new file mode 100644
index 00000000..d700d19d
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-alone-redis-sb4/src/main/resources/application.yml
@@ -0,0 +1,59 @@
+# 端口
+server:
+ port: 8083
+
+# Sa-Token配置
+sa-token:
+ # Token名称 (同时也是cookie名称)
+ token-name: satoken
+ # Token有效期,单位s 默认30天, -1代表永不过期
+ timeout: 2592000
+ # Token风格
+ token-style: uuid
+ # 配置Sa-Token单独使用的Redis连接
+ alone-redis:
+ # Redis数据库索引(默认为0)
+ database: 2
+ # Redis服务器地址
+ host: 127.0.0.1
+ # Redis服务器连接端口
+ port: 6379
+ # Redis服务器连接密码(默认为空)
+ # password:
+ # 连接超时时间(毫秒)
+ timeout: 10s
+ lettuce:
+ pool:
+ # 连接池最大连接数
+ max-active: 200
+ # 连接池最大阻塞等待时间(使用负值表示没有限制)
+ max-wait: -1ms
+ # 连接池中的最大空闲连接
+ max-idle: 10
+ # 连接池中的最小空闲连接
+ min-idle: 0
+
+# 配置业务使用的Redis连接
+spring:
+ data:
+ redis:
+ # Redis数据库索引(默认为0)
+ database: 0
+ # Redis服务器地址
+ host: 127.0.0.1
+ # Redis服务器连接端口
+ port: 6379
+ # Redis服务器连接密码(默认为空)
+ password:
+ # 连接超时时间
+ timeout: 10s
+ lettuce:
+ pool:
+ # 连接池最大连接数
+ max-active: 200
+ # 连接池最大阻塞等待时间(使用负值表示没有限制)
+ max-wait: -1ms
+ # 连接池中的最大空闲连接
+ max-idle: 10
+ # 连接池中的最小空闲连接
+ min-idle: 0
diff --git a/sa-token-doc/plugin/alone-redis.md b/sa-token-doc/plugin/alone-redis.md
index 1de2f524..b0c3b4b7 100644
--- a/sa-token-doc/plugin/alone-redis.md
+++ b/sa-token-doc/plugin/alone-redis.md
@@ -16,6 +16,12 @@ Sa-Token默认的Redis集成方式会把权限数据和业务缓存放在一起
### 1、首先引入Alone-Redis依赖
+
+!--
+> [!NOTE| label:Spring Boot 4 用户]
+> 若使用 Spring Boot 4.x,请引入 `sa-token-alone-redis-by-spring-boot4` 替代 `sa-token-alone-redis`。
+-->
+
``` xml
diff --git a/sa-token-plugin/pom.xml b/sa-token-plugin/pom.xml
index 16be841c..b18a6628 100644
--- a/sa-token-plugin/pom.xml
+++ b/sa-token-plugin/pom.xml
@@ -48,6 +48,7 @@
sa-token-redis-template-jdk-serializer
sa-token-redis-jackson
sa-token-alone-redis
+ sa-token-alone-redis-by-spring-boot4
sa-token-spring-aop
sa-token-spring-el
sa-token-grpc
diff --git a/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/pom.xml b/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/pom.xml
new file mode 100644
index 00000000..1c237768
--- /dev/null
+++ b/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/pom.xml
@@ -0,0 +1,56 @@
+
+
+ 4.0.0
+
+
+ cn.dev33
+ sa-token-plugin
+ ${revision}
+ ../pom.xml
+
+ jar
+
+ sa-token-alone-redis-by-spring-boot4
+ sa-token-alone-redis-by-spring-boot4
+ sa-token-alone-redis for Spring Boot 4
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ cn.dev33
+ sa-token-redis-template
+ true
+
+
+ cn.dev33
+ sa-token-redis-template-jdk-serializer
+ true
+
+
+
+ org.apache.commons
+ commons-pool2
+ true
+
+
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot4-dependencies
+ ${revision}
+ pom
+ import
+
+
+
+
+
diff --git a/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/src/main/java/cn/dev33/satoken/dao/alone/SaAloneRedisInject.java b/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/src/main/java/cn/dev33/satoken/dao/alone/SaAloneRedisInject.java
new file mode 100644
index 00000000..d6590824
--- /dev/null
+++ b/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/src/main/java/cn/dev33/satoken/dao/alone/SaAloneRedisInject.java
@@ -0,0 +1,228 @@
+/*
+ * 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.alone;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
+import cn.dev33.satoken.dao.SaTokenDaoForRedisTemplate;
+import cn.dev33.satoken.exception.SaTokenException;
+import jakarta.annotation.PostConstruct;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.data.redis.connection.*;
+import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 为 SaToken 单独设置 Redis 连接信息,使权限缓存与业务缓存分离 (springboot4 版本专用)
+ *
+ *
+ * 使用方式:在引入 sa-token redis 集成相关包的前提下,继续引入当前依赖
+ * 注意事项:目前本依赖仅对以下插件有 Redis 分离效果:
+ * sa-token-redis-template
+ * sa-token-redis-template-jdk-serializer
+ *
+ *
+ *
+ * @author click33
+ * @since 1.21.0
+ */
+@Configuration
+public class SaAloneRedisInject {
+
+ /**
+ * 配置信息的前缀
+ */
+ public static final String ALONE_PREFIX = "sa-token.alone-redis";
+
+ /**
+ * Sa-Token 持久层接口
+ */
+ private final SaTokenDao saTokenDao;
+ private final Environment environment;
+
+ public SaAloneRedisInject(SaTokenDao saTokenDao, Environment environment) {
+ this.saTokenDao = saTokenDao;
+ this.environment = environment;
+ }
+
+ /**
+ * 开始注入
+ */
+ @PostConstruct
+ public void init() {
+ try {
+ // 如果 saTokenDao 为空或者为默认实现,则不进行任何操作
+ if(saTokenDao == null || saTokenDao instanceof SaTokenDaoDefaultImpl) {
+ return;
+ }
+
+ // ------------------- 开始注入
+
+ // 获取cfg对象,解析开发者配置的 sa-token.alone-redis 相关信息
+ DataRedisProperties cfg = Binder.get(environment).bind(ALONE_PREFIX, DataRedisProperties.class).get();
+
+ // 1. Redis配置
+ RedisConfiguration redisAloneConfig;
+ String pattern = environment.getProperty(ALONE_PREFIX + ".pattern", "single");
+ if (pattern.equals("single")) {
+ // 单体模式
+ RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
+ redisConfig.setHostName(cfg.getHost());
+ redisConfig.setPort(cfg.getPort());
+ redisConfig.setDatabase(cfg.getDatabase());
+ redisConfig.setPassword(RedisPassword.of(cfg.getPassword()));
+ redisConfig.setUsername(cfg.getUsername());
+ redisAloneConfig = redisConfig;
+
+ } else if (pattern.equals("cluster")){
+ // 普通集群模式
+ RedisClusterConfiguration redisClusterConfig = new RedisClusterConfiguration();
+ redisClusterConfig.setUsername(cfg.getUsername());
+ redisClusterConfig.setPassword(RedisPassword.of(cfg.getPassword()));
+
+ DataRedisProperties.Cluster cluster = cfg.getCluster();
+ if (cluster == null || cluster.getNodes() == null) {
+ throw new SaTokenException("Alone-Redis 集群模式需要配置 cluster.nodes");
+ }
+ List serverList = cluster.getNodes().stream().map(node -> {
+ String[] ipAndPort = node.split(":");
+ return new RedisNode(ipAndPort[0].trim(), Integer.parseInt(ipAndPort[1]));
+ }).collect(Collectors.toList());
+ redisClusterConfig.setClusterNodes(serverList);
+ if (cluster.getMaxRedirects() != null) {
+ redisClusterConfig.setMaxRedirects(cluster.getMaxRedirects());
+ }
+ redisAloneConfig = redisClusterConfig;
+ } else if (pattern.equals("sentinel")) {
+ // 哨兵集群模式
+ RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
+ redisSentinelConfiguration.setDatabase(cfg.getDatabase());
+ redisSentinelConfiguration.setUsername(cfg.getUsername());
+ redisSentinelConfiguration.setPassword(RedisPassword.of(cfg.getPassword()));
+
+ DataRedisProperties.Sentinel sentinel = cfg.getSentinel();
+ if (sentinel == null || sentinel.getNodes() == null) {
+ throw new SaTokenException("Alone-Redis 哨兵模式需要配置 sentinel.nodes");
+ }
+ redisSentinelConfiguration.setMaster(sentinel.getMaster());
+ redisSentinelConfiguration.setSentinelPassword(sentinel.getPassword());
+ List serverList = sentinel.getNodes().stream().map(node -> {
+ String[] ipAndPort = node.split(":");
+ return new RedisNode(ipAndPort[0].trim(), Integer.parseInt(ipAndPort[1]));
+ }).collect(Collectors.toList());
+ redisSentinelConfiguration.setSentinels(serverList);
+
+ redisAloneConfig = redisSentinelConfiguration;
+ } else if (pattern.equals("socket")) {
+ // socket 连接单体 Redis
+ RedisSocketConfiguration redisSocketConfiguration = new RedisSocketConfiguration();
+ redisSocketConfiguration.setDatabase(cfg.getDatabase());
+ redisSocketConfiguration.setUsername(cfg.getUsername());
+ redisSocketConfiguration.setPassword(RedisPassword.of(cfg.getPassword()));
+ String socket = environment.getProperty(ALONE_PREFIX + ".socket", "");
+ redisSocketConfiguration.setSocket(socket);
+
+ redisAloneConfig = redisSocketConfiguration;
+ } else if (pattern.equals("aws")) {
+ // AWS ElastiCache
+ // AWS Redis 远程主机地址: String hoseName = "****.***.****.****.cache.amazonaws.com";
+ String hostName = cfg.getHost();
+ int port = cfg.getPort();
+ RedisStaticMasterReplicaConfiguration redisStaticMasterReplicaConfiguration = new RedisStaticMasterReplicaConfiguration(hostName, port);
+ redisStaticMasterReplicaConfiguration.setDatabase(cfg.getDatabase());
+ redisStaticMasterReplicaConfiguration.setUsername(cfg.getUsername());
+ redisStaticMasterReplicaConfiguration.setPassword(RedisPassword.of(cfg.getPassword()));
+
+ redisAloneConfig = redisStaticMasterReplicaConfiguration;
+ } else {
+ // 模式无法识别
+ throw new SaTokenException("SaToken 无法识别 Alone-Redis 配置的模式: " + pattern);
+ }
+
+ // 2. 连接池配置
+ GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
+ // pool配置
+ DataRedisProperties.Lettuce lettuce = cfg.getLettuce();
+ if(lettuce.getPool() != null) {
+ DataRedisProperties.Pool pool = cfg.getLettuce().getPool();
+ // 连接池最大连接数
+ poolConfig.setMaxTotal(pool.getMaxActive());
+ // 连接池中的最大空闲连接
+ poolConfig.setMaxIdle(pool.getMaxIdle());
+ // 连接池中的最小空闲连接
+ poolConfig.setMinIdle(pool.getMinIdle());
+ // 连接池最大阻塞等待时间(使用负值表示没有限制)
+ poolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
+ }
+ LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
+ // timeout
+ if(cfg.getTimeout() != null) {
+ builder.commandTimeout(cfg.getTimeout());
+ }
+ // shutdownTimeout
+ builder.shutdownTimeout(lettuce.getShutdownTimeout());
+ // 创建Factory对象
+ LettuceClientConfiguration clientConfig = builder.poolConfig(poolConfig).build();
+ LettuceConnectionFactory factory = new LettuceConnectionFactory(redisAloneConfig, clientConfig);
+ factory.afterPropertiesSet();
+
+ // 3. 开始初始化 SaTokenDao ,此处需要依次判断开发者引入的是哪个 redis 库
+
+ // 如果开发者引入的是:sa-token-redis-template-jdk-serializer 或 sa-token-redis-template
+ try {
+ Class.forName("cn.dev33.satoken.dao.SaTokenDaoForRedisTemplateUseJdkSerializer");
+ SaTokenDaoForRedisTemplate dao = (SaTokenDaoForRedisTemplate) saTokenDao;
+ dao.isInit = false;
+ dao.init(factory);
+ return;
+ } catch (ClassNotFoundException ignored) {
+ }
+ try {
+ Class.forName("cn.dev33.satoken.dao.SaTokenDaoForRedisTemplate");
+ SaTokenDaoForRedisTemplate dao = (SaTokenDaoForRedisTemplate) saTokenDao;
+ dao.isInit = false;
+ dao.init(factory);
+ return;
+ } catch (ClassNotFoundException ignored) {
+ }
+
+ // 至此,说明开发者一个 redis 插件也没引入,或者引入的 redis 插件不在 sa-token-alone-redis 的支持范围内
+ throw new SaTokenException("未引入 sa-token-redis-xxx 相关插件,或引入的插件不在 Alone-Redis 支持范围内");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 骗过编辑器,增加配置文件代码提示
+ * @return 配置对象
+ */
+ @ConfigurationProperties(prefix = ALONE_PREFIX)
+ public DataRedisProperties getSaAloneRedisConfig() {
+ return new DataRedisProperties();
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000..74cac595
--- /dev/null
+++ b/sa-token-plugin/sa-token-alone-redis-by-spring-boot4/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.dev33.satoken.dao.alone.SaAloneRedisInject
diff --git a/sa-token-special-dependencies/sa-token-spring-boot4-dependencies/pom.xml b/sa-token-special-dependencies/sa-token-spring-boot4-dependencies/pom.xml
index 767a5335..de9e494a 100644
--- a/sa-token-special-dependencies/sa-token-spring-boot4-dependencies/pom.xml
+++ b/sa-token-special-dependencies/sa-token-spring-boot4-dependencies/pom.xml
@@ -56,6 +56,21 @@
${springboot4.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+ ${springboot4.version}
+
+
+
+
+ org.apache.commons
+ commons-pool2
+ 2.12.1
+ true
+
+