diff --git a/README.md b/README.md
index fb64fd58..be65b772 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-Sa-Token v1.30.0.RC
+Sa-Token v1.30.0
一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!
@@ -28,64 +28,6 @@
**Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:**`登录认证`**、**`权限认证`**、**`Session会话`**、**`单点登录`**、**`OAuth2.0`**、**`微服务网关鉴权`**
等一系列权限相关问题。
-Sa-Token 的 API 设计非常简单,有多简单呢?以登录认证为例,你只需要:
-
-``` java
-// 在登录时写入当前会话的账号id
-StpUtil.login(10001);
-
-// 然后在需要校验登录处调用以下方法:
-// 如果当前会话未登录,这句代码会抛出 `NotLoginException` 异常
-StpUtil.checkLogin();
-```
-
-至此,我们已经借助 Sa-Token 完成登录认证!
-
-此时的你小脑袋可能飘满了问号,就这么简单?自定义 Realm 呢?全局过滤器呢?我不用写各种配置文件吗?
-
-没错,在 Sa-Token 中,登录认证就是如此简单,不需要任何的复杂前置工作,只需这一行简单的API调用,就可以完成会话登录认证!
-
-当你受够 Shiro、SpringSecurity 等框架的三拜九叩之后,你就会明白,相对于这些传统老牌框架,Sa-Token 的 API 设计是多么的简单、优雅!
-
-权限认证示例(只有具备 `user:add` 权限的会话才可以进入请求)
-``` java
-@SaCheckPermission("user:add")
-@RequestMapping("/user/insert")
-public String insert(SysUser user) {
- // ...
- return "用户增加";
-}
-```
-
-将某个账号踢下线(待到对方再次访问系统时会抛出`NotLoginException`异常)
-``` java
-// 将账号id为 10001 的会话踢下线
-StpUtil.kickout(10001);
-```
-
-在 Sa-Token 中,绝大多数功能都可以 **一行代码** 完成:
-``` java
-StpUtil.login(10001); // 标记当前会话登录的账号id
-StpUtil.getLoginId(); // 获取当前会话登录的账号id
-StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
-StpUtil.logout(); // 当前会话注销登录
-StpUtil.kickout(10001); // 将账号为10001的会话踢下线
-StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
-StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
-StpUtil.getSession(); // 获取当前账号id的Session
-StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
-StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
-StpUtil.login(10001, "PC"); // 指定设备类型登录,常用于“同端互斥登录”
-StpUtil.kickout(10001, "PC"); // 指定账号指定设备类型踢下线 (不同端不受影响)
-StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
-StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
-StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
-```
-
-即使不运行测试,相信您也能意会到绝大多数 API 的用法。
-
-
-## Sa-Token 功能一览
- **登录认证** —— 单端登录、多端登录、同端互斥登录、七天内免登录
- **权限认证** —— 权限认证、角色认证、会话二级认证
@@ -119,7 +61,7 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
## Sa-Token-SSO 单点登录
-网上的单点登录教程大多以CAS流程为主,其实对于不同的系统架构,实现单点登录的步骤也大为不同,Sa-Token由简入难将其划分为三种模式:
+Sa-Token-SSO 由简入难划分为三种模式,解决不同架构下的 SSO 接入问题:
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- |
@@ -131,8 +73,6 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com`
2. 后端同Redis:就是指多个系统可以连接同一个Redis。PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis)
3. 如果既无法做到前端同域,也无法做到后端同Redis,那么只能走模式三,Http请求获取会话(Sa-Token对SSO提供了完整的封装,你只需要按照示例从文档上复制几段代码便可以轻松集成)
-4. 技术选型一定要根据系统架构对症下药,切不可胡乱选择
-
## Sa-Token-OAuth2.0 授权登录
Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749) 编写,通过Sa-OAuth2你可以非常轻松的实现系统的OAuth2.0授权认证
@@ -154,12 +94,6 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)

-## Star 趋势
-[](https://giteye.net/chart/77YQZ6UK)
-
-[](https://starchart.cc/dromara/sa-token)
-
-
## 使用Sa-Token的开源项目
- [[ sa-plus ]](https://gitee.com/click33/sa-plus):一个基于 SpringBoot 架构的快速开发框架,内置代码生成器
@@ -201,11 +135,6 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)
- [[ TLog ]](https://gitee.com/dromara/TLog):一个轻量级的分布式日志标记追踪神器
-## 贡献者名单
-感谢每一个为 Sa-Token 贡献代码的小伙伴
-
-[](https://giteye.net/chart/CGZ7GT8E)
-
## 交流群
QQ交流群:1群:1002350610 (已满) 、
diff --git a/pom.xml b/pom.xml
index 73074ff6..d982dc28 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,7 @@
- 1.30.0.RC
+ 1.30.0
1.8
utf-8
utf-8
diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenAction.java b/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenAction.java
deleted file mode 100644
index 31a4833e..00000000
--- a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenAction.java
+++ /dev/null
@@ -1,54 +0,0 @@
-//package cn.dev33.satoken.action;
-//
-//import java.lang.reflect.AnnotatedElement;
-//import java.lang.reflect.Method;
-//import java.util.List;
-//
-//import cn.dev33.satoken.session.SaSession;
-//
-///**
-// * v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy
-// * Sa-Token 逻辑代理接口
-// * 此接口将会代理框架内部的一些关键性逻辑,方便开发者进行按需重写
-// * @author kong
-// *
-// */
-//@Deprecated
-//public interface SaTokenAction {
-//
-// /**
-// * 创建一个Token
-// * @param loginId 账号id
-// * @param loginType 账号类型
-// * @return token
-// */
-// public String createToken(Object loginId, String loginType);
-//
-// /**
-// * 创建一个Session
-// * @param sessionId Session的Id
-// * @return 创建后的Session
-// */
-// public SaSession createSession(String sessionId);
-//
-// /**
-// * 判断:集合中是否包含指定元素(模糊匹配)
-// * @param list 集合
-// * @param element 元素
-// * @return 是否包含
-// */
-// public boolean hasElement(List list, String element);
-//
-// /**
-// * 对一个Method对象进行注解检查(注解鉴权内部实现)
-// * @param method Method对象
-// */
-// public void checkMethodAnnotation(Method method);
-//
-// /**
-// * 从指定元素校验注解
-// * @param target /
-// */
-// public void validateAnnotation(AnnotatedElement target);
-//
-//}
diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenActionDefaultImpl.java b/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenActionDefaultImpl.java
deleted file mode 100644
index 8aa217c9..00000000
--- a/sa-token-core/src/main/java/cn/dev33/satoken/action/SaTokenActionDefaultImpl.java
+++ /dev/null
@@ -1,150 +0,0 @@
-//package cn.dev33.satoken.action;
-//
-//import java.lang.reflect.AnnotatedElement;
-//import java.lang.reflect.Method;
-//import java.util.List;
-//import java.util.UUID;
-//
-//import cn.dev33.satoken.SaManager;
-//import cn.dev33.satoken.annotation.SaCheckBasic;
-//import cn.dev33.satoken.annotation.SaCheckLogin;
-//import cn.dev33.satoken.annotation.SaCheckPermission;
-//import cn.dev33.satoken.annotation.SaCheckRole;
-//import cn.dev33.satoken.annotation.SaCheckSafe;
-//import cn.dev33.satoken.basic.SaBasicUtil;
-//import cn.dev33.satoken.session.SaSession;
-//import cn.dev33.satoken.strategy.SaStrategy;
-//import cn.dev33.satoken.util.SaFoxUtil;
-//import cn.dev33.satoken.util.SaTokenConsts;
-//
-///**
-// * v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy
-// * Sa-Token 逻辑代理接口 [默认实现类]
-// * @author kong
-// *
-// */
-//@Deprecated
-//public class SaTokenActionDefaultImpl implements SaTokenAction {
-//
-// /**
-// * 创建一个Token
-// */
-// @Override
-// public String createToken(Object loginId, String loginType) {
-// // 根据配置的tokenStyle生成不同风格的token
-// String tokenStyle = SaManager.getConfig().getTokenStyle();
-// // uuid
-// if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) {
-// return UUID.randomUUID().toString();
-// }
-// // 简单uuid (不带下划线)
-// if(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID.equals(tokenStyle)) {
-// return UUID.randomUUID().toString().replaceAll("-", "");
-// }
-// // 32位随机字符串
-// if(SaTokenConsts.TOKEN_STYLE_RANDOM_32.equals(tokenStyle)) {
-// return SaFoxUtil.getRandomString(32);
-// }
-// // 64位随机字符串
-// if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {
-// return SaFoxUtil.getRandomString(64);
-// }
-// // 128位随机字符串
-// if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {
-// return SaFoxUtil.getRandomString(128);
-// }
-// // tik风格 (2_14_16)
-// if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) {
-// return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__";
-// }
-// // 默认,还是uuid
-// return UUID.randomUUID().toString();
-// }
-//
-// /**
-// * 创建一个Session
-// */
-// @Override
-// public SaSession createSession(String sessionId) {
-// return new SaSession(sessionId);
-// }
-//
-// /**
-// * 判断:集合中是否包含指定元素(模糊匹配)
-// */
-// @Override
-// public boolean hasElement(List list, String element) {
-//
-// // 空集合直接返回false
-// if(list == null || list.size() == 0) {
-// return false;
-// }
-//
-// // 先尝试一下简单匹配,如果可以匹配成功则无需继续模糊匹配
-// if (list.contains(element)) {
-// return true;
-// }
-//
-// // 开始模糊匹配
-// for (String patt : list) {
-// if(SaFoxUtil.vagueMatch(patt, element)) {
-// return true;
-// }
-// }
-//
-// // 走出for循环说明没有一个元素可以匹配成功
-// return false;
-// }
-//
-// /**
-// * 对一个Method对象进行注解检查(注解鉴权内部实现)
-// */
-// @Override
-// public void checkMethodAnnotation(Method method) {
-//
-// // 先校验 Method 所属 Class 上的注解
-// validateAnnotation(method.getDeclaringClass());
-//
-// // 再校验 Method 上的注解
-// validateAnnotation(method);
-// }
-//
-// /**
-// * 从指定元素校验注解
-// * @param target see note
-// */
-// public void validateAnnotation(AnnotatedElement target) {
-//
-// // 校验 @SaCheckLogin 注解
-// SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
-// if(checkLogin != null) {
-// SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
-// }
-//
-// // 校验 @SaCheckRole 注解
-// SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
-// if(checkRole != null) {
-// SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
-// }
-//
-// // 校验 @SaCheckPermission 注解
-// SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
-// if(checkPermission != null) {
-// SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
-// }
-//
-// // 校验 @SaCheckSafe 注解
-// SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
-// if(checkSafe != null) {
-// SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
-// }
-//
-// // 校验 @SaCheckBasic 注解
-// SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
-// if(checkBasic != null) {
-// SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
-// }
-//
-// }
-//
-//}
diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java
index a1a67078..88005237 100644
--- a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java
+++ b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaExceptionCode.java
@@ -8,7 +8,7 @@ package cn.dev33.satoken.exception;
*/
public class SaExceptionCode {
- /** 代表未指定异常细分状态码 */
+ /** 代表这个异常在抛出时未指定异常细分状态码 */
public static final int CODE_UNDEFINED = -1;
}
diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java
index c9d6d946..7eace922 100644
--- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java
+++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java
@@ -13,7 +13,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
- public static final String VERSION_NO = "v1.30.0.RC";
+ public static final String VERSION_NO = "v1.30.0";
/**
* Sa-Token 开源地址 Gitee
diff --git a/sa-token-demo/sa-token-demo-alone-redis/pom.xml b/sa-token-demo/sa-token-demo-alone-redis/pom.xml
index aca5ab64..6886e9d2 100644
--- a/sa-token-demo/sa-token-demo-alone-redis/pom.xml
+++ b/sa-token-demo/sa-token-demo-alone-redis/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml b/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml
index ebba72f6..8a941bc5 100644
--- a/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml
+++ b/sa-token-demo/sa-token-demo-dubbo-consumer/pom.xml
@@ -16,7 +16,7 @@
1.8
3.1.1
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml b/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml
index 9ba7e59d..c9292252 100644
--- a/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml
+++ b/sa-token-demo/sa-token-demo-dubbo-provider/pom.xml
@@ -16,7 +16,7 @@
1.8
3.1.1
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-jwt/pom.xml b/sa-token-demo/sa-token-demo-jwt/pom.xml
index 489e645d..6e348ac0 100644
--- a/sa-token-demo/sa-token-demo-jwt/pom.xml
+++ b/sa-token-demo/sa-token-demo-jwt/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-oauth2-client/pom.xml b/sa-token-demo/sa-token-demo-oauth2-client/pom.xml
index f3280320..2617edcc 100644
--- a/sa-token-demo/sa-token-demo-oauth2-client/pom.xml
+++ b/sa-token-demo/sa-token-demo-oauth2-client/pom.xml
@@ -17,7 +17,7 @@
1.8
3.1.1
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-oauth2-server/pom.xml b/sa-token-demo/sa-token-demo-oauth2-server/pom.xml
index ec4488a8..ff5ce390 100644
--- a/sa-token-demo/sa-token-demo-oauth2-server/pom.xml
+++ b/sa-token-demo/sa-token-demo-oauth2-server/pom.xml
@@ -17,7 +17,7 @@
1.8
3.1.1
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-quick-login/pom.xml b/sa-token-demo/sa-token-demo-quick-login/pom.xml
index 9f951b93..cf0f7218 100644
--- a/sa-token-demo/sa-token-demo-quick-login/pom.xml
+++ b/sa-token-demo/sa-token-demo-quick-login/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-solon/pom.xml b/sa-token-demo/sa-token-demo-solon/pom.xml
index 09ff61eb..53b55bbe 100644
--- a/sa-token-demo/sa-token-demo-solon/pom.xml
+++ b/sa-token-demo/sa-token-demo-solon/pom.xml
@@ -9,7 +9,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/.gitignore b/sa-token-demo/sa-token-demo-springboot-redis/.gitignore
new file mode 100644
index 00000000..99a6e767
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/.gitignore
@@ -0,0 +1,12 @@
+target/
+
+node_modules/
+bin/
+.settings/
+unpackage/
+.classpath
+.project
+
+.idea/
+
+.factorypath
\ No newline at end of file
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/pom.xml b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml
new file mode 100644
index 00000000..0dc8aa5e
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/pom.xml
@@ -0,0 +1,72 @@
+
+ 4.0.0
+ cn.dev33
+ sa-token-demo-springboot-redis
+ 0.0.1-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.12
+
+
+
+
+
+
+ 1.30.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot-starter
+ ${sa-token-version}
+
+
+
+
+
+
+
+ cn.dev33
+ sa-token-dao-redis-jackson
+ ${sa-token-version}
+
+
+
+
+ org.apache.commons
+ commons-pool2
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java
new file mode 100644
index 00000000..4f5e4467
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java
@@ -0,0 +1,21 @@
+package com.pj;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+import cn.dev33.satoken.SaManager;
+
+/**
+ * Sa-Token整合SpringBoot 示例,整合redis
+ * @author kong
+ *
+ */
+@SpringBootApplication
+public class SaTokenDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SaTokenDemoApplication.class, args);
+ System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java
new file mode 100644
index 00000000..26b7cd5d
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java
@@ -0,0 +1,57 @@
+package com.pj.current;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import com.pj.util.AjaxJson;
+
+import cn.dev33.satoken.exception.DisableLoginException;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.dev33.satoken.exception.NotRoleException;
+
+/**
+ * 全局异常处理
+ */
+@RestControllerAdvice
+public class GlobalException {
+
+ // 全局异常拦截(拦截项目中的所有异常)
+ @ExceptionHandler
+ public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
+ throws Exception {
+
+ // 打印堆栈,以供调试
+ System.out.println("全局异常---------------");
+ e.printStackTrace();
+
+ // 不同异常返回不同状态码
+ AjaxJson aj = null;
+ if (e instanceof NotLoginException) { // 如果是未登录异常
+ NotLoginException ee = (NotLoginException) e;
+ aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
+ }
+ else if(e instanceof NotRoleException) { // 如果是角色异常
+ NotRoleException ee = (NotRoleException) e;
+ aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
+ }
+ else if(e instanceof NotPermissionException) { // 如果是权限异常
+ NotPermissionException ee = (NotPermissionException) e;
+ aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
+ }
+ else if(e instanceof DisableLoginException) { // 如果是被封禁异常
+ DisableLoginException ee = (DisableLoginException) e;
+ aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
+ }
+ else { // 普通异常, 输出:500 + 异常信息
+ aj = AjaxJson.getError(e.getMessage());
+ }
+
+ // 返回给前端
+ return aj;
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/NotFoundHandle.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/NotFoundHandle.java
new file mode 100644
index 00000000..1e82e95a
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/NotFoundHandle.java
@@ -0,0 +1,27 @@
+package com.pj.current;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.dev33.satoken.util.SaResult;
+
+/**
+ * 处理 404
+ * @author kong
+ */
+@RestController
+public class NotFoundHandle implements ErrorController {
+
+ @RequestMapping("/error")
+ public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ response.setStatus(200);
+ return SaResult.get(404, "not found", null);
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java
new file mode 100644
index 00000000..5f3212e7
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java
@@ -0,0 +1,85 @@
+package com.pj.satoken;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.pj.util.AjaxJson;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.filter.SaServletFilter;
+import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
+import cn.dev33.satoken.strategy.SaStrategy;
+
+
+/**
+ * [Sa-Token 权限认证] 配置类
+ * @author kong
+ *
+ */
+@Configuration
+public class SaTokenConfigure implements WebMvcConfigurer {
+
+ /**
+ * 注册Sa-Token 的拦截器,打开注解式鉴权功能
+ */
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 注册注解拦截器
+ registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
+ }
+
+ /**
+ * 注册 [Sa-Token 全局过滤器]
+ */
+ @Bean
+ public SaServletFilter getSaServletFilter() {
+ return new SaServletFilter()
+
+ // 指定 [拦截路由] 与 [放行路由]
+ .addInclude("/**")// .addExclude("/favicon.ico")
+
+ // 认证函数: 每次请求执行
+ .setAuth(obj -> {
+ // System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
+
+ })
+
+ // 异常处理函数:每次认证函数发生异常时执行此函数
+ .setError(e -> {
+ System.out.println("---------- sa全局异常 ");
+ return AjaxJson.getError(e.getMessage());
+ })
+
+ // 前置函数:在每次认证函数之前执行
+ .setBeforeAuth(r -> {
+ // ---------- 设置一些安全响应头 ----------
+ SaHolder.getResponse()
+ // 服务器名称
+ .setServer("sa-server")
+ // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
+ .setHeader("X-Frame-Options", "SAMEORIGIN")
+ // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
+ .setHeader("X-XSS-Protection", "1; mode=block")
+ // 禁用浏览器内容嗅探
+ .setHeader("X-Content-Type-Options", "nosniff")
+ ;
+ })
+ ;
+ }
+
+ /**
+ * 重写 Sa-Token 框架内部算法策略
+ */
+ @Autowired
+ public void rewriteSaStrategy() {
+ // 重写Sa-Token的注解处理器,增加注解合并功能
+ SaStrategy.me.getAnnotation = (element, annotationClass) -> {
+ return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
+ };
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/StpInterfaceImpl.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/StpInterfaceImpl.java
new file mode 100644
index 00000000..b6cc79f7
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/StpInterfaceImpl.java
@@ -0,0 +1,44 @@
+package com.pj.satoken;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import cn.dev33.satoken.stp.StpInterface;
+
+/**
+ * 自定义权限验证接口扩展
+ */
+@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
+public class StpInterfaceImpl implements StpInterface {
+
+ /**
+ * 返回一个账号所拥有的权限码集合
+ */
+ @Override
+ public List getPermissionList(Object loginId, String loginType) {
+ // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
+ List list = new ArrayList();
+ list.add("101");
+ list.add("user-add");
+ list.add("user-delete");
+ list.add("user-update");
+ list.add("user-get");
+ list.add("article-get");
+ return list;
+ }
+
+ /**
+ * 返回一个账号所拥有的角色标识集合
+ */
+ @Override
+ public List getRoleList(Object loginId, String loginType) {
+ // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
+ List list = new ArrayList();
+ list.add("admin");
+ list.add("super-admin");
+ return list;
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckLogin.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckLogin.java
new file mode 100644
index 00000000..74014898
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckLogin.java
@@ -0,0 +1,21 @@
+package com.pj.satoken.at;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+
+/**
+ * 登录认证(User版):只有登录之后才能进入该方法
+ * 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ * @author kong
+ *
+ */
+@SaCheckLogin(type = StpUserUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaUserCheckLogin {
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckPermission.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckPermission.java
new file mode 100644
index 00000000..38312116
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckPermission.java
@@ -0,0 +1,38 @@
+package com.pj.satoken.at;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaMode;
+
+/**
+ * 权限认证(User版):必须具有指定权限才能进入该方法
+ *
可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ * @author kong
+ *
+ */
+@SaCheckPermission(type = StpUserUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaUserCheckPermission {
+
+ /**
+ * 需要校验的权限码
+ * @return 需要校验的权限码
+ */
+ @AliasFor(annotation = SaCheckPermission.class)
+ String [] value() default {};
+
+ /**
+ * 验证模式:AND | OR,默认AND
+ * @return 验证模式
+ */
+ @AliasFor(annotation = SaCheckPermission.class)
+ SaMode mode() default SaMode.AND;
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckRole.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckRole.java
new file mode 100644
index 00000000..c9b44f32
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/SaUserCheckRole.java
@@ -0,0 +1,38 @@
+package com.pj.satoken.at;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+
+/**
+ * 角色认证(User版):必须具有指定角色标识才能进入该方法
+ *
可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ * @author kong
+ *
+ */
+@SaCheckRole(type = StpUserUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaUserCheckRole {
+
+ /**
+ * 需要校验的角色标识
+ * @return 需要校验的角色标识
+ */
+ @AliasFor(annotation = SaCheckRole.class)
+ String [] value() default {};
+
+ /**
+ * 验证模式:AND | OR,默认AND
+ * @return 验证模式
+ */
+ @AliasFor(annotation = SaCheckRole.class)
+ SaMode mode() default SaMode.AND;
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/StpUserUtil.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/StpUserUtil.java
new file mode 100644
index 00000000..939b5fe8
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/at/StpUserUtil.java
@@ -0,0 +1,912 @@
+package com.pj.satoken.at;
+
+import java.util.List;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.fun.SaFunction;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.SaTokenInfo;
+import cn.dev33.satoken.stp.StpLogic;
+import cn.dev33.satoken.stp.StpUtil;
+
+/**
+ * Sa-Token 权限认证工具类 (user版)
+ * @author kong
+ */
+public class StpUserUtil {
+
+ /**
+ * 账号类型标识
+ */
+ public static final String TYPE = "user";
+
+ /**
+ * 底层的 StpLogic 对象
+ */
+ public static StpLogic stpLogic = new StpLogic(TYPE);
+
+ /**
+ * 获取当前 StpLogic 的账号类型
+ * @return See Note
+ */
+ public static String getLoginType(){
+ return stpLogic.getLoginType();
+ }
+
+ /**
+ * 重置 StpLogic 对象
+ * @param stpLogic /
+ */
+ public static void setStpLogic(StpLogic stpLogic) {
+ StpUtil.stpLogic = stpLogic;
+ // 防止自定义 stpLogic 被覆盖
+ SaManager.putStpLogic(stpLogic);
+ }
+
+
+ // =================== 获取token 相关 ===================
+
+ /**
+ * 返回token名称
+ * @return 此StpLogic的token名称
+ */
+ public static String getTokenName() {
+ return stpLogic.getTokenName();
+ }
+
+ /**
+ * 在当前会话写入当前TokenValue
+ * @param tokenValue token值
+ */
+ public static void setTokenValue(String tokenValue){
+ stpLogic.setTokenValue(tokenValue);
+ }
+
+ /**
+ * 在当前会话写入当前TokenValue
+ * @param tokenValue token值
+ * @param cookieTimeout Cookie存活时间(秒)
+ */
+ public static void setTokenValue(String tokenValue, int cookieTimeout){
+ stpLogic.setTokenValue(tokenValue, cookieTimeout);
+ }
+
+ /**
+ * 获取当前TokenValue
+ * @return 当前tokenValue
+ */
+ public static String getTokenValue() {
+ return stpLogic.getTokenValue();
+ }
+
+ /**
+ * 获取当前TokenValue (不裁剪前缀)
+ * @return /
+ */
+ public static String getTokenValueNotCut(){
+ return stpLogic.getTokenValueNotCut();
+ }
+
+ /**
+ * 获取当前会话的Token信息
+ * @return token信息
+ */
+ public static SaTokenInfo getTokenInfo() {
+ return stpLogic.getTokenInfo();
+ }
+
+
+ // =================== 登录相关操作 ===================
+
+ // --- 登录
+
+ /**
+ * 会话登录
+ * @param id 账号id,建议的类型:(long | int | String)
+ */
+ public static void login(Object id) {
+ stpLogic.login(id);
+ }
+
+ /**
+ * 会话登录,并指定登录设备类型
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @param device 设备类型
+ */
+ public static void login(Object id, String device) {
+ stpLogic.login(id, device);
+ }
+
+ /**
+ * 会话登录,并指定是否 [记住我]
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @param isLastingCookie 是否为持久Cookie
+ */
+ public static void login(Object id, boolean isLastingCookie) {
+ stpLogic.login(id, isLastingCookie);
+ }
+
+ /**
+ * 会话登录,并指定所有登录参数Model
+ * @param id 登录id,建议的类型:(long | int | String)
+ * @param loginModel 此次登录的参数Model
+ */
+ public static void login(Object id, SaLoginModel loginModel) {
+ stpLogic.login(id, loginModel);
+ }
+
+ /**
+ * 创建指定账号id的登录会话
+ * @param id 登录id,建议的类型:(long | int | String)
+ * @return 返回会话令牌
+ */
+ public static String createLoginSession(Object id) {
+ return stpLogic.createLoginSession(id);
+ }
+
+ /**
+ * 创建指定账号id的登录会话
+ * @param id 登录id,建议的类型:(long | int | String)
+ * @param loginModel 此次登录的参数Model
+ * @return 返回会话令牌
+ */
+ public static String createLoginSession(Object id, SaLoginModel loginModel) {
+ return stpLogic.createLoginSession(id, loginModel);
+ }
+
+ // --- 注销
+
+ /**
+ * 会话注销
+ */
+ public static void logout() {
+ stpLogic.logout();
+ }
+
+ /**
+ * 会话注销,根据账号id
+ * @param loginId 账号id
+ */
+ public static void logout(Object loginId) {
+ stpLogic.logout(loginId);
+ }
+
+ /**
+ * 会话注销,根据账号id 和 设备类型
+ *
+ * @param loginId 账号id
+ * @param device 设备类型 (填null代表注销所有设备类型)
+ */
+ public static void logout(Object loginId, String device) {
+ stpLogic.logout(loginId, device);
+ }
+
+ /**
+ * 会话注销,根据指定 Token
+ *
+ * @param tokenValue 指定token
+ */
+ public static void logoutByTokenValue(String tokenValue) {
+ stpLogic.logoutByTokenValue(tokenValue);
+ }
+
+ /**
+ * 踢人下线,根据账号id
+ *
当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5
+ *
+ * @param loginId 账号id
+ */
+ public static void kickout(Object loginId) {
+ stpLogic.kickout(loginId);
+ }
+
+ /**
+ * 踢人下线,根据账号id 和 设备类型
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5
+ *
+ * @param loginId 账号id
+ * @param device 设备类型 (填null代表踢出所有设备类型)
+ */
+ public static void kickout(Object loginId, String device) {
+ stpLogic.kickout(loginId, device);
+ }
+
+ /**
+ * 踢人下线,根据指定 Token
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5
+ *
+ * @param tokenValue 指定token
+ */
+ public static void kickoutByTokenValue(String tokenValue) {
+ stpLogic.kickoutByTokenValue(tokenValue);
+ }
+
+ /**
+ * 顶人下线,根据账号id 和 设备类型
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4
+ *
+ * @param loginId 账号id
+ * @param device 设备类型 (填null代表顶替所有设备类型)
+ */
+ public static void replaced(Object loginId, String device) {
+ stpLogic.replaced(loginId, device);
+ }
+
+
+ // 查询相关
+
+ /**
+ * 当前会话是否已经登录
+ * @return 是否已登录
+ */
+ public static boolean isLogin() {
+ return stpLogic.isLogin();
+ }
+
+ /**
+ * 检验当前会话是否已经登录,如未登录,则抛出异常
+ */
+ public static void checkLogin() {
+ stpLogic.checkLogin();
+ }
+
+ /**
+ * 获取当前会话账号id, 如果未登录,则抛出异常
+ * @return 账号id
+ */
+ public static Object getLoginId() {
+ return stpLogic.getLoginId();
+ }
+
+ /**
+ * 获取当前会话账号id, 如果未登录,则返回默认值
+ * @param 返回类型
+ * @param defaultValue 默认值
+ * @return 登录id
+ */
+ public static T getLoginId(T defaultValue) {
+ return stpLogic.getLoginId(defaultValue);
+ }
+
+ /**
+ * 获取当前会话账号id, 如果未登录,则返回null
+ * @return 账号id
+ */
+ public static Object getLoginIdDefaultNull() {
+ return stpLogic.getLoginIdDefaultNull();
+ }
+
+ /**
+ * 获取当前会话账号id, 并转换为String类型
+ * @return 账号id
+ */
+ public static String getLoginIdAsString() {
+ return stpLogic.getLoginIdAsString();
+ }
+
+ /**
+ * 获取当前会话账号id, 并转换为int类型
+ * @return 账号id
+ */
+ public static int getLoginIdAsInt() {
+ return stpLogic.getLoginIdAsInt();
+ }
+
+ /**
+ * 获取当前会话账号id, 并转换为long类型
+ * @return 账号id
+ */
+ public static long getLoginIdAsLong() {
+ return stpLogic.getLoginIdAsLong();
+ }
+
+ /**
+ * 获取指定Token对应的账号id,如果未登录,则返回 null
+ * @param tokenValue token
+ * @return 账号id
+ */
+ public static Object getLoginIdByToken(String tokenValue) {
+ return stpLogic.getLoginIdByToken(tokenValue);
+ }
+
+ /**
+ * 获取Token扩展信息(只在jwt模式下有效)
+ * @param key 键值
+ * @return 对应的扩展数据
+ */
+ public static Object getExtra(String key) {
+ return stpLogic.getExtra(key);
+ }
+
+
+ // =================== User-Session 相关 ===================
+
+ /**
+ * 获取指定账号id的Session, 如果Session尚未创建,isCreate=是否新建并返回
+ * @param loginId 账号id
+ * @param isCreate 是否新建
+ * @return Session对象
+ */
+ public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
+ return stpLogic.getSessionByLoginId(loginId, isCreate);
+ }
+
+ /**
+ * 获取指定key的Session, 如果Session尚未创建,则返回null
+ * @param sessionId SessionId
+ * @return Session对象
+ */
+ public static SaSession getSessionBySessionId(String sessionId) {
+ return stpLogic.getSessionBySessionId(sessionId);
+ }
+
+ /**
+ * 获取指定账号id的Session,如果Session尚未创建,则新建并返回
+ * @param loginId 账号id
+ * @return Session对象
+ */
+ public static SaSession getSessionByLoginId(Object loginId) {
+ return stpLogic.getSessionByLoginId(loginId);
+ }
+
+ /**
+ * 获取当前会话的Session, 如果Session尚未创建,isCreate=是否新建并返回
+ * @param isCreate 是否新建
+ * @return Session对象
+ */
+ public static SaSession getSession(boolean isCreate) {
+ return stpLogic.getSession(isCreate);
+ }
+
+ /**
+ * 获取当前会话的Session,如果Session尚未创建,则新建并返回
+ * @return Session对象
+ */
+ public static SaSession getSession() {
+ return stpLogic.getSession();
+ }
+
+
+ // =================== Token-Session 相关 ===================
+
+ /**
+ * 获取指定Token-Session,如果Session尚未创建,则新建并返回
+ * @param tokenValue Token值
+ * @return Session对象
+ */
+ public static SaSession getTokenSessionByToken(String tokenValue) {
+ return stpLogic.getTokenSessionByToken(tokenValue);
+ }
+
+ /**
+ * 获取当前Token-Session,如果Session尚未创建,则新建并返回
+ * @return Session对象
+ */
+ public static SaSession getTokenSession() {
+ return stpLogic.getTokenSession();
+ }
+
+
+ // =================== [临时有效期] 验证相关 ===================
+
+ /**
+ * 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
+ */
+ public static void checkActivityTimeout() {
+ stpLogic.checkActivityTimeout();
+ }
+
+ /**
+ * 续签当前token:(将 [最后操作时间] 更新为当前时间戳)
+ * 请注意: 即使token已经 [临时过期] 也可续签成功,
+ * 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可
+ */
+ public static void updateLastActivityToNow() {
+ stpLogic.updateLastActivityToNow();
+ }
+
+
+ // =================== 过期时间相关 ===================
+
+ /**
+ * 获取当前登录者的 token 剩余有效时间 (单位: 秒)
+ * @return token剩余有效时间
+ */
+ public static long getTokenTimeout() {
+ return stpLogic.getTokenTimeout();
+ }
+
+ /**
+ * 获取当前登录者的 User-Session 剩余有效时间 (单位: 秒)
+ * @return token剩余有效时间
+ */
+ public static long getSessionTimeout() {
+ return stpLogic.getSessionTimeout();
+ }
+
+ /**
+ * 获取当前 Token-Session 剩余有效时间 (单位: 秒)
+ * @return token剩余有效时间
+ */
+ public static long getTokenSessionTimeout() {
+ return stpLogic.getTokenSessionTimeout();
+ }
+
+ /**
+ * 获取当前 token [临时过期] 剩余有效时间 (单位: 秒)
+ * @return token [临时过期] 剩余有效时间
+ */
+ public static long getTokenActivityTimeout() {
+ return stpLogic.getTokenActivityTimeout();
+ }
+
+ /**
+ * 对当前 Token 的 timeout 值进行续期
+ * @param timeout 要修改成为的有效时间 (单位: 秒)
+ */
+ public static void renewTimeout(long timeout) {
+ stpLogic.renewTimeout(timeout);
+ }
+
+ /**
+ * 对指定 Token 的 timeout 值进行续期
+ * @param tokenValue 指定token
+ * @param timeout 要修改成为的有效时间 (单位: 秒)
+ */
+ public static void renewTimeout(String tokenValue, long timeout) {
+ stpLogic.renewTimeout(tokenValue, timeout);
+ }
+
+ // =================== 角色验证操作 ===================
+
+ /**
+ * 获取:当前账号的角色集合
+ * @return /
+ */
+ public static List getRoleList() {
+ return stpLogic.getRoleList();
+ }
+
+ /**
+ * 获取:指定账号的角色集合
+ * @param loginId 指定账号id
+ * @return /
+ */
+ public static List getRoleList(Object loginId) {
+ return stpLogic.getRoleList(loginId);
+ }
+
+ /**
+ * 判断:当前账号是否拥有指定角色, 返回true或false
+ * @param role 角色标识
+ * @return 是否含有指定角色标识
+ */
+ public static boolean hasRole(String role) {
+ return stpLogic.hasRole(role);
+ }
+
+ /**
+ * 判断:指定账号是否含有指定角色标识, 返回true或false
+ * @param loginId 账号id
+ * @param role 角色标识
+ * @return 是否含有指定角色标识
+ */
+ public static boolean hasRole(Object loginId, String role) {
+ return stpLogic.hasRole(loginId, role);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
+ * @param roleArray 角色标识数组
+ * @return true或false
+ */
+ public static boolean hasRoleAnd(String... roleArray){
+ return stpLogic.hasRoleAnd(roleArray);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
+ * @param roleArray 角色标识数组
+ * @return true或false
+ */
+ public static boolean hasRoleOr(String... roleArray){
+ return stpLogic.hasRoleOr(roleArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
+ * @param role 角色标识
+ */
+ public static void checkRole(String role) {
+ stpLogic.checkRole(role);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
+ * @param roleArray 角色标识数组
+ */
+ public static void checkRoleAnd(String... roleArray){
+ stpLogic.checkRoleAnd(roleArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
+ * @param roleArray 角色标识数组
+ */
+ public static void checkRoleOr(String... roleArray){
+ stpLogic.checkRoleOr(roleArray);
+ }
+
+
+ // =================== 权限验证操作 ===================
+
+ /**
+ * 获取:当前账号的权限码集合
+ * @return /
+ */
+ public static List getPermissionList() {
+ return stpLogic.getPermissionList();
+ }
+
+ /**
+ * 获取:指定账号的权限码集合
+ * @param loginId 指定账号id
+ * @return /
+ */
+ public static List getPermissionList(Object loginId) {
+ return stpLogic.getPermissionList(loginId);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限, 返回true或false
+ * @param permission 权限码
+ * @return 是否含有指定权限
+ */
+ public static boolean hasPermission(String permission) {
+ return stpLogic.hasPermission(permission);
+ }
+
+ /**
+ * 判断:指定账号id是否含有指定权限, 返回true或false
+ * @param loginId 账号id
+ * @param permission 权限码
+ * @return 是否含有指定权限
+ */
+ public static boolean hasPermission(Object loginId, String permission) {
+ return stpLogic.hasPermission(loginId, permission);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限, [指定多个,必须全部具有]
+ * @param permissionArray 权限码数组
+ * @return true 或 false
+ */
+ public static boolean hasPermissionAnd(String... permissionArray){
+ return stpLogic.hasPermissionAnd(permissionArray);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
+ * @param permissionArray 权限码数组
+ * @return true 或 false
+ */
+ public static boolean hasPermissionOr(String... permissionArray){
+ return stpLogic.hasPermissionOr(permissionArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
+ * @param permission 权限码
+ */
+ public static void checkPermission(String permission) {
+ stpLogic.checkPermission(permission);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
+ * @param permissionArray 权限码数组
+ */
+ public static void checkPermissionAnd(String... permissionArray) {
+ stpLogic.checkPermissionAnd(permissionArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
+ * @param permissionArray 权限码数组
+ */
+ public static void checkPermissionOr(String... permissionArray) {
+ stpLogic.checkPermissionOr(permissionArray);
+ }
+
+
+ // =================== id 反查token 相关操作 ===================
+
+ /**
+ * 获取指定账号id的tokenValue
+ * 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
+ * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
+ * @param loginId 账号id
+ * @return token值
+ */
+ public static String getTokenValueByLoginId(Object loginId) {
+ return stpLogic.getTokenValueByLoginId(loginId);
+ }
+
+ /**
+ * 获取指定账号id指定设备类型端的tokenValue
+ *
在配置为允许并发登录时,此方法只会返回队列的最后一个token,
+ * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
+ * @param loginId 账号id
+ * @param device 设备类型
+ * @return token值
+ */
+ public static String getTokenValueByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenValueByLoginId(loginId, device);
+ }
+
+ /**
+ * 获取指定账号id的tokenValue集合
+ * @param loginId 账号id
+ * @return 此loginId的所有相关token
+ */
+ public static List getTokenValueListByLoginId(Object loginId) {
+ return stpLogic.getTokenValueListByLoginId(loginId);
+ }
+
+ /**
+ * 获取指定账号id指定设备类型端的tokenValue 集合
+ * @param loginId 账号id
+ * @param device 设备类型
+ * @return 此loginId的所有相关token
+ */
+ public static List getTokenValueListByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenValueListByLoginId(loginId, device);
+ }
+
+ /**
+ * 返回当前会话的登录设备类型
+ * @return 当前令牌的登录设备类型
+ */
+ public static String getLoginDevice() {
+ return stpLogic.getLoginDevice();
+ }
+
+
+ // =================== 会话管理 ===================
+
+ /**
+ * 根据条件查询Token
+ * @param keyword 关键字
+ * @param start 开始处索引 (-1代表查询所有)
+ * @param size 获取数量
+ * @return token集合
+ */
+ public static List 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 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 searchTokenSessionId(String keyword, int start, int size) {
+ return stpLogic.searchTokenSessionId(keyword, start, size);
+ }
+
+
+ // ------------------- 账号封禁 -------------------
+
+ /**
+ * 封禁指定账号
+ * 此方法不会直接将此账号id踢下线,而是在对方再次登录时抛出`DisableLoginException`异常
+ * @param loginId 指定账号id
+ * @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁)
+ */
+ public static void disable(Object loginId, long disableTime) {
+ stpLogic.disable(loginId, disableTime);
+ }
+
+ /**
+ * 指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
+ * @param loginId 账号id
+ * @return see note
+ */
+ public static boolean isDisable(Object loginId) {
+ return stpLogic.isDisable(loginId);
+ }
+
+ /**
+ * 获取指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
+ * @param loginId 账号id
+ * @return see note
+ */
+ public static long getDisableTime(Object loginId) {
+ return stpLogic.getDisableTime(loginId);
+ }
+
+ /**
+ * 解封指定账号
+ * @param loginId 账号id
+ */
+ public static void untieDisable(Object loginId) {
+ stpLogic.untieDisable(loginId);
+ }
+
+
+ // =================== 身份切换 ===================
+
+ /**
+ * 临时切换身份为指定账号id
+ * @param loginId 指定loginId
+ */
+ public static void switchTo(Object loginId) {
+ stpLogic.switchTo(loginId);
+ }
+
+ /**
+ * 结束临时切换身份
+ */
+ public static void endSwitch() {
+ stpLogic.endSwitch();
+ }
+
+ /**
+ * 当前是否正处于[身份临时切换]中
+ * @return 是否正处于[身份临时切换]中
+ */
+ public static boolean isSwitch() {
+ return stpLogic.isSwitch();
+ }
+
+ /**
+ * 在一个代码段里方法内,临时切换身份为指定账号id
+ * @param loginId 指定账号id
+ * @param function 要执行的方法
+ */
+ public static void switchTo(Object loginId, SaFunction function) {
+ stpLogic.switchTo(loginId, function);
+ }
+
+
+ // ------------------- 二级认证 -------------------
+
+ /**
+ * 在当前会话 开启二级认证
+ * @param safeTime 维持时间 (单位: 秒)
+ */
+ public static void openSafe(long safeTime) {
+ stpLogic.openSafe(safeTime);
+ }
+
+ /**
+ * 当前会话 是否处于二级认证时间内
+ * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
+ */
+ public static boolean isSafe() {
+ return stpLogic.isSafe();
+ }
+
+ /**
+ * 检查当前会话是否已通过二级认证,如未通过则抛出异常
+ */
+ public static void checkSafe() {
+ stpLogic.checkSafe();
+ }
+
+ /**
+ * 获取当前会话的二级认证剩余有效时间 (单位: 秒, 返回-2代表尚未通过二级认证)
+ * @return 剩余有效时间
+ */
+ public static long getSafeTime() {
+ return stpLogic.getSafeTime();
+ }
+
+ /**
+ * 在当前会话 结束二级认证
+ */
+ public static void closeSafe() {
+ stpLogic.closeSafe();
+ }
+
+
+ // =================== 历史API,兼容旧版本 ===================
+
+ /**
+ *
本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.getLoginType() ,使用方式保持不变
+ *
+ * 获取当前StpLogin的loginKey
+ * @return 当前StpLogin的loginKey
+ */
+ @Deprecated
+ public static String getLoginKey(){
+ return stpLogic.getLoginType();
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId) {
+ stpLogic.login(loginId);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id, 并指定登录设备类型
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ * @param device 设备类型
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId, String device) {
+ stpLogic.login(loginId, device);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id, 并指定登录设备类型
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ * @param isLastingCookie 是否为持久Cookie
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId, boolean isLastingCookie) {
+ stpLogic.login(loginId, isLastingCookie);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.login() ,使用方式保持不变
+ *
+ * 在当前会话上登录id, 并指定所有登录参数Model
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ * @param loginModel 此次登录的参数Model
+ */
+ @Deprecated
+ public static void setLoginId(Object loginId, SaLoginModel loginModel) {
+ stpLogic.login(loginId, loginModel);
+ }
+
+ /**
+ * 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变
+ *
+ * 会话注销,根据账号id (踢人下线)
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
+ * @param loginId 账号id
+ */
+ @Deprecated
+ public static void logoutByLoginId(Object loginId) {
+ stpLogic.kickout(loginId);
+ }
+
+ /**
+ *
本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变
+ *
+ * 会话注销,根据账号id and 设备类型 (踢人下线)
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
+ * @param loginId 账号id
+ * @param device 设备类型 (填null代表注销所有设备类型)
+ */
+ @Deprecated
+ public static void logoutByLoginId(Object loginId, String device) {
+ stpLogic.kickout(loginId, device);
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/AtController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/AtController.java
new file mode 100644
index 00000000..dc7c6f95
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/AtController.java
@@ -0,0 +1,80 @@
+package com.pj.test;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.dev33.satoken.annotation.SaCheckBasic;
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaCheckSafe;
+import cn.dev33.satoken.annotation.SaMode;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.util.SaResult;
+
+/**
+ * 注解鉴权测试
+ * @author kong
+ *
+ */
+@RestController
+@RequestMapping("/at/")
+public class AtController {
+
+ // 登录认证,登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin
+ @SaCheckLogin
+ @RequestMapping("checkLogin")
+ public SaResult checkLogin() {
+ return SaResult.ok();
+ }
+
+ // 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission
+ @SaCheckPermission("user-add")
+ @RequestMapping("checkPermission")
+ public SaResult checkPermission() {
+ return SaResult.ok();
+ }
+
+ // 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd
+ @SaCheckPermission({"user-add", "user-delete", "user-update"})
+ @RequestMapping("checkPermissionAnd")
+ public SaResult checkPermissionAnd() {
+ return SaResult.ok();
+ }
+
+ // 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr
+ @SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
+ @RequestMapping("checkPermissionOr")
+ public SaResult checkPermissionOr() {
+ return SaResult.ok();
+ }
+
+ // 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole
+ @SaCheckRole("admin")
+ @RequestMapping("checkRole")
+ public SaResult checkRole() {
+ return SaResult.ok();
+ }
+
+ // 完成二级认证 ---- http://localhost:8081/at/openSafe
+ @RequestMapping("openSafe")
+ public SaResult openSafe() {
+ StpUtil.openSafe(200); // 打开二级认证,有效期为200秒
+ return SaResult.ok();
+ }
+
+ // 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe
+ @SaCheckSafe
+ @RequestMapping("checkSafe")
+ public SaResult checkSafe() {
+ return SaResult.ok();
+ }
+
+ // 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
+ @SaCheckBasic(account = "sa:123456")
+ @RequestMapping("checkBasic")
+ public SaResult checkBasic() {
+ return SaResult.ok();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/LoginController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/LoginController.java
new file mode 100644
index 00000000..ea94cfa7
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/LoginController.java
@@ -0,0 +1,48 @@
+package com.pj.test;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.util.SaResult;
+
+/**
+ * 登录测试
+ * @author kong
+ *
+ */
+@RestController
+@RequestMapping("/acc/")
+public class LoginController {
+
+ // 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
+ @RequestMapping("doLogin")
+ public SaResult doLogin(String name, String pwd) {
+ // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
+ if("zhang".equals(name) && "123456".equals(pwd)) {
+ StpUtil.login(10001);
+ return SaResult.ok("登录成功");
+ }
+ return SaResult.error("登录失败");
+ }
+
+ // 查询登录状态 ---- http://localhost:8081/acc/isLogin
+ @RequestMapping("isLogin")
+ public SaResult isLogin() {
+ return SaResult.ok("是否登录:" + StpUtil.isLogin());
+ }
+
+ // 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
+ @RequestMapping("tokenInfo")
+ public SaResult tokenInfo() {
+ return SaResult.data(StpUtil.getTokenInfo());
+ }
+
+ // 测试注销 ---- http://localhost:8081/acc/logout
+ @RequestMapping("logout")
+ public SaResult logout() {
+ StpUtil.logout();
+ return SaResult.ok();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/StressTestController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/StressTestController.java
new file mode 100644
index 00000000..5c0b3ce1
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/StressTestController.java
@@ -0,0 +1,61 @@
+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);
+
+ int count = 10; // 循环多少轮
+ int loginCount = 10000; // 每轮循环多少次
+
+ // 循环10次 取平均时间
+ List list = new ArrayList<>();
+ for (int i = 1; i <= count; i++) {
+ System.out.println("\n---------------------第" + i + "轮---------------------");
+ Ttime t = new Ttime().start();
+ // 每次登录的次数
+ for (int j = 1; j <= loginCount; j++) {
+ StpUtil.login("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();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java
new file mode 100644
index 00000000..0712eb2d
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java
@@ -0,0 +1,251 @@
+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;
+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;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+import cn.dev33.satoken.session.SaSessionCustomUtil;
+import cn.dev33.satoken.stp.SaTokenInfo;
+import cn.dev33.satoken.stp.StpUtil;
+
+/**
+ * 测试专用Controller
+ * @author kong
+ *
+ */
+@RestController
+@RequestMapping("/test/")
+public class TestController {
+
+ // 测试登录接口, 浏览器访问: http://localhost:8081/test/login
+ @RequestMapping("login")
+ public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
+ System.out.println("======================= 进入方法,测试登录接口 ========================= ");
+ System.out.println("当前会话的token:" + StpUtil.getTokenValue());
+ System.out.println("当前是否登录:" + StpUtil.isLogin());
+ System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
+
+ StpUtil.login(id); // 在当前会话登录此账号
+ System.out.println("登录成功");
+ System.out.println("当前是否登录:" + StpUtil.isLogin());
+ System.out.println("当前登录账号:" + StpUtil.getLoginId());
+// System.out.println("当前登录账号并转为int:" + StpUtil.getLoginIdAsInt());
+ System.out.println("当前登录设备:" + StpUtil.getLoginDevice());
+// System.out.println("当前token信息:" + StpUtil.getTokenInfo());
+
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试退出登录 , 浏览器访问: http://localhost:8081/test/logout
+ @RequestMapping("logout")
+ public AjaxJson logout() {
+ StpUtil.logout();
+// StpUtil.logoutByLoginId(10001);
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试角色接口, 浏览器访问: http://localhost:8081/test/testRole
+ @RequestMapping("testRole")
+ public AjaxJson testRole() {
+ System.out.println("======================= 进入方法,测试角色接口 ========================= ");
+
+ System.out.println("是否具有角色标识 user " + StpUtil.hasRole("user"));
+ System.out.println("是否具有角色标识 admin " + StpUtil.hasRole("admin"));
+
+ System.out.println("没有admin权限就抛出异常");
+ StpUtil.checkRole("admin");
+
+ System.out.println("在【admin、user】中只要拥有一个就不会抛出异常");
+ StpUtil.checkRoleOr("admin", "user");
+
+ System.out.println("在【admin、user】中必须全部拥有才不会抛出异常");
+ StpUtil.checkRoleAnd("admin", "user");
+
+ System.out.println("角色测试通过");
+
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试权限接口, 浏览器访问: http://localhost:8081/test/testJur
+ @RequestMapping("testJur")
+ public AjaxJson testJur() {
+ System.out.println("======================= 进入方法,测试权限接口 ========================= ");
+
+ System.out.println("是否具有权限101" + StpUtil.hasPermission("101"));
+ System.out.println("是否具有权限user-add" + StpUtil.hasPermission("user-add"));
+ System.out.println("是否具有权限article-get" + StpUtil.hasPermission("article-get"));
+
+ System.out.println("没有user-add权限就抛出异常");
+ StpUtil.checkPermission("user-add");
+
+ System.out.println("在【101、102】中只要拥有一个就不会抛出异常");
+ StpUtil.checkPermissionOr("101", "102");
+
+ System.out.println("在【101、102】中必须全部拥有才不会抛出异常");
+ StpUtil.checkPermissionAnd("101", "102");
+
+ System.out.println("权限测试通过");
+
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试会话session接口, 浏览器访问: http://localhost:8081/test/session
+ @RequestMapping("session")
+ public AjaxJson session() throws JsonProcessingException {
+ System.out.println("======================= 进入方法,测试会话session接口 ========================= ");
+ System.out.println("当前是否登录:" + StpUtil.isLogin());
+ System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
+ System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
+ System.out.println("测试取值name:" + StpUtil.getSession().get("name"));
+ StpUtil.getSession().set("name", new Date()); // 写入一个值
+ System.out.println("测试取值name:" + StpUtil.getSession().get("name"));
+ System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession()));
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试自定义session接口, 浏览器访问: http://localhost:8081/test/session2
+ @RequestMapping("session2")
+ public AjaxJson session2() {
+ System.out.println("======================= 进入方法,测试自定义session接口 ========================= ");
+ // 自定义session就是无需登录也可以使用 的session :比如拿用户的手机号当做 key, 来获取 session
+ System.out.println("自定义 session的id为:" + SaSessionCustomUtil.getSessionById("1895544896").getId());
+ System.out.println("测试取值name:" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
+ SaSessionCustomUtil.getSessionById("1895544896").set("name", "张三"); // 写入值
+ System.out.println("测试取值name:" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
+ System.out.println("测试取值name:" + SaSessionCustomUtil.getSessionById("1895544896").get("name"));
+ return AjaxJson.getSuccess();
+ }
+
+ // ----------
+ // 测试token专属session, 浏览器访问: http://localhost:8081/test/getTokenSession
+ @RequestMapping("getTokenSession")
+ public AjaxJson getTokenSession() {
+ System.out.println("======================= 进入方法,测试会话session接口 ========================= ");
+ System.out.println("当前是否登录:" + StpUtil.isLogin());
+ System.out.println("当前token专属session: " + StpUtil.getTokenSession().getId());
+
+ System.out.println("测试取值name:" + StpUtil.getTokenSession().get("name"));
+ StpUtil.getTokenSession().set("name", "张三"); // 写入一个值
+ System.out.println("测试取值name:" + StpUtil.getTokenSession().get("name"));
+
+ return AjaxJson.getSuccess();
+ }
+
+ // 打印当前token信息, 浏览器访问: http://localhost:8081/test/tokenInfo
+ @RequestMapping("tokenInfo")
+ public AjaxJson tokenInfo() {
+ System.out.println("======================= 进入方法,打印当前token信息 ========================= ");
+ SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
+ System.out.println(tokenInfo);
+ return AjaxJson.getSuccessData(tokenInfo);
+ }
+
+ // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atCheck
+ @SaCheckLogin // 注解式鉴权:当前会话必须登录才能通过
+ @SaCheckRole("super-admin") // 注解式鉴权:当前会话必须具有指定角色标识才能通过
+ @SaCheckPermission("user-add") // 注解式鉴权:当前会话必须具有指定权限才能通过
+ @RequestMapping("atCheck")
+ public AjaxJson atCheck() {
+ System.out.println("======================= 进入方法,测试注解鉴权接口 ========================= ");
+ System.out.println("只有通过注解鉴权,才能进入此方法");
+// StpUtil.checkActivityTimeout();
+// StpUtil.updateLastActivityToNow();
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atJurOr
+ @RequestMapping("atJurOr")
+ @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR) // 注解式鉴权:只要具有其中一个权限即可通过校验
+ public AjaxJson atJurOr() {
+ return AjaxJson.getSuccessData("用户信息");
+ }
+
+ // [活动时间] 续签: http://localhost:8081/test/rene
+ @RequestMapping("rene")
+ public AjaxJson rene() {
+ StpUtil.checkActivityTimeout();
+ StpUtil.updateLastActivityToNow();
+ return AjaxJson.getSuccess("续签成功");
+ }
+
+ // 测试踢人下线 浏览器访问: http://localhost:8081/test/kickOut
+ @RequestMapping("kickOut")
+ public AjaxJson kickOut() {
+ // 先登录上
+ StpUtil.login(10001);
+ // 踢下线
+ StpUtil.kickout(10001);
+ // 再尝试获取
+ StpUtil.getLoginId();
+ // 返回
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试登录接口, 按照设备登录, 浏览器访问: http://localhost:8081/test/login2
+ @RequestMapping("login2")
+ public AjaxJson login2(@RequestParam(defaultValue="10001") String id, @RequestParam(defaultValue="PC") String device) {
+ StpUtil.login(id, device);
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试身份临时切换: http://localhost:8081/test/switchTo
+ @RequestMapping("switchTo")
+ public AjaxJson switchTo() {
+ System.out.println("当前会话身份:" + StpUtil.getLoginIdDefaultNull());
+ System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch());
+ StpUtil.switchTo(10044, () -> {
+ System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch());
+ System.out.println("当前会话身份已被切换为:" + StpUtil.getLoginId());
+ });
+ System.out.println("是否正在身份临时切换中: " + StpUtil.isSwitch());
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试会话治理 浏览器访问: http://localhost:8081/test/search
+ @RequestMapping("search")
+ public AjaxJson search() {
+ System.out.println("--------------");
+ Ttime t = new Ttime().start();
+ List tokenValue = StpUtil.searchTokenValue("8feb8265f773", 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/loginByDevice
+ @RequestMapping("loginByDevice")
+ public AjaxJson loginByDevice() {
+ System.out.println("--------------");
+ StpUtil.login(10001, "PC");
+ return AjaxJson.getSuccessData("登录成功");
+ }
+
+ // 测试 浏览器访问: http://localhost:8081/test/test
+ @RequestMapping("test")
+ public AjaxJson test() {
+ System.out.println("------------进来了");
+ return AjaxJson.getSuccess();
+ }
+
+ // 测试 浏览器访问: http://localhost:8081/test/test2
+ @RequestMapping("test2")
+ public AjaxJson test2() {
+ return AjaxJson.getSuccess();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/AjaxJson.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/AjaxJson.java
new file mode 100644
index 00000000..768d0578
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/AjaxJson.java
@@ -0,0 +1,162 @@
+package com.pj.util;
+
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * ajax请求返回Json格式数据的封装
+ */
+public class AjaxJson implements Serializable{
+
+ private static final long serialVersionUID = 1L; // 序列化版本号
+
+ public static final int CODE_SUCCESS = 200; // 成功状态码
+ public static final int CODE_ERROR = 500; // 错误状态码
+ public static final int CODE_WARNING = 501; // 警告状态码
+ public static final int CODE_NOT_JUR = 403; // 无权限状态码
+ public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
+ public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
+
+ public int code; // 状态码
+ public String msg; // 描述信息
+ public Object data; // 携带对象
+ public Long dataCount; // 数据总数,用于分页
+
+ /**
+ * 返回code
+ * @return
+ */
+ public int getCode() {
+ return this.code;
+ }
+
+ /**
+ * 给msg赋值,连缀风格
+ */
+ public AjaxJson setMsg(String msg) {
+ this.msg = msg;
+ return this;
+ }
+ public String getMsg() {
+ return this.msg;
+ }
+
+ /**
+ * 给data赋值,连缀风格
+ */
+ public AjaxJson setData(Object data) {
+ this.data = data;
+ return this;
+ }
+
+ /**
+ * 将data还原为指定类型并返回
+ */
+ @SuppressWarnings("unchecked")
+ public T getData(Class cs) {
+ return (T) data;
+ }
+
+ // ============================ 构建 ==================================
+
+ public AjaxJson(int code, String msg, Object data, Long dataCount) {
+ this.code = code;
+ this.msg = msg;
+ this.data = data;
+ this.dataCount = dataCount;
+ }
+
+ // 返回成功
+ public static AjaxJson getSuccess() {
+ return new AjaxJson(CODE_SUCCESS, "ok", null, null);
+ }
+ public static AjaxJson getSuccess(String msg) {
+ return new AjaxJson(CODE_SUCCESS, msg, null, null);
+ }
+ public static AjaxJson getSuccess(String msg, Object data) {
+ return new AjaxJson(CODE_SUCCESS, msg, data, null);
+ }
+ public static AjaxJson getSuccessData(Object data) {
+ return new AjaxJson(CODE_SUCCESS, "ok", data, null);
+ }
+ public static AjaxJson getSuccessArray(Object... data) {
+ return new AjaxJson(CODE_SUCCESS, "ok", data, null);
+ }
+
+ // 返回失败
+ public static AjaxJson getError() {
+ return new AjaxJson(CODE_ERROR, "error", null, null);
+ }
+ public static AjaxJson getError(String msg) {
+ return new AjaxJson(CODE_ERROR, msg, null, null);
+ }
+
+ // 返回警告
+ public static AjaxJson getWarning() {
+ return new AjaxJson(CODE_ERROR, "warning", null, null);
+ }
+ public static AjaxJson getWarning(String msg) {
+ return new AjaxJson(CODE_WARNING, msg, null, null);
+ }
+
+ // 返回未登录
+ public static AjaxJson getNotLogin() {
+ return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
+ }
+
+ // 返回没有权限的
+ public static AjaxJson getNotJur(String msg) {
+ return new AjaxJson(CODE_NOT_JUR, msg, null, null);
+ }
+
+ // 返回一个自定义状态码的
+ public static AjaxJson get(int code, String msg){
+ return new AjaxJson(code, msg, null, null);
+ }
+
+ // 返回分页和数据的
+ public static AjaxJson getPageData(Long dataCount, Object data){
+ return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
+ }
+
+ // 返回,根据受影响行数的(大于0=ok,小于0=error)
+ public static AjaxJson getByLine(int line){
+ if(line > 0){
+ return getSuccess("ok", line);
+ }
+ return getError("error").setData(line);
+ }
+
+ // 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
+ public static AjaxJson getByBoolean(boolean b){
+ return b ? getSuccess("ok") : getError("error");
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @SuppressWarnings("rawtypes")
+ @Override
+ public String toString() {
+ String data_string = null;
+ if(data == null){
+
+ } else if(data instanceof List){
+ data_string = "List(length=" + ((List)data).size() + ")";
+ } else {
+ data_string = data.toString();
+ }
+ return "{"
+ + "\"code\": " + this.getCode()
+ + ", \"msg\": \"" + this.getMsg() + "\""
+ + ", \"data\": " + data_string
+ + ", \"dataCount\": " + dataCount
+ + "}";
+ }
+
+
+
+
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/Ttime.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/Ttime.java
new file mode 100644
index 00000000..cb17dcaa
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/util/Ttime.java
@@ -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
+ }
+
+
+
+}
diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml
new file mode 100644
index 00000000..cb63067a
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml
@@ -0,0 +1,49 @@
+# 端口
+server:
+ port: 8081
+
+# sa-token配置
+sa-token:
+ # token名称 (同时也是cookie名称)
+ token-name: satoken
+ # token有效期,单位s 默认30天, -1代表永不过期
+ timeout: 2592000
+ # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
+ activity-timeout: -1
+ # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+ is-concurrent: true
+ # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+ is-share: true
+ # token风格
+ token-style: uuid
+ # 是否输出操作日志
+ is-log: false
+
+spring:
+ # redis配置
+ 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
+
+
+
+
+
\ No newline at end of file
diff --git a/sa-token-demo/sa-token-demo-springboot/pom.xml b/sa-token-demo/sa-token-demo-springboot/pom.xml
index 5c443d13..afaf8993 100644
--- a/sa-token-demo/sa-token-demo-springboot/pom.xml
+++ b/sa-token-demo/sa-token-demo-springboot/pom.xml
@@ -17,7 +17,7 @@
- 1.30.0.RC
+ 1.30.0
@@ -39,26 +39,6 @@
${sa-token-version}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sa-token-demo/sa-token-demo-sso-server/pom.xml b/sa-token-demo/sa-token-demo-sso-server/pom.xml
index af57f8b0..18cdd8d9 100644
--- a/sa-token-demo/sa-token-demo-sso-server/pom.xml
+++ b/sa-token-demo/sa-token-demo-sso-server/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-sso1-client/pom.xml b/sa-token-demo/sa-token-demo-sso1-client/pom.xml
index f1a74e8d..c21a3263 100644
--- a/sa-token-demo/sa-token-demo-sso1-client/pom.xml
+++ b/sa-token-demo/sa-token-demo-sso1-client/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-sso2-client/pom.xml b/sa-token-demo/sa-token-demo-sso2-client/pom.xml
index dd655d54..da44a372 100644
--- a/sa-token-demo/sa-token-demo-sso2-client/pom.xml
+++ b/sa-token-demo/sa-token-demo-sso2-client/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-sso3-client/pom.xml b/sa-token-demo/sa-token-demo-sso3-client/pom.xml
index ee90d827..9af2f62c 100644
--- a/sa-token-demo/sa-token-demo-sso3-client/pom.xml
+++ b/sa-token-demo/sa-token-demo-sso3-client/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml
index ec496107..3227b68d 100644
--- a/sa-token-demo/sa-token-demo-thymeleaf/pom.xml
+++ b/sa-token-demo/sa-token-demo-thymeleaf/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-webflux/pom.xml b/sa-token-demo/sa-token-demo-webflux/pom.xml
index 2eac97eb..d71464d7 100644
--- a/sa-token-demo/sa-token-demo-webflux/pom.xml
+++ b/sa-token-demo/sa-token-demo-webflux/pom.xml
@@ -16,7 +16,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml
index 08f862bc..e0a4b2a7 100644
--- a/sa-token-demo/sa-token-demo-websocket-spring/pom.xml
+++ b/sa-token-demo/sa-token-demo-websocket-spring/pom.xml
@@ -17,7 +17,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-demo/sa-token-demo-websocket/pom.xml b/sa-token-demo/sa-token-demo-websocket/pom.xml
index 5ddbb0b5..b18f7b89 100644
--- a/sa-token-demo/sa-token-demo-websocket/pom.xml
+++ b/sa-token-demo/sa-token-demo-websocket/pom.xml
@@ -17,7 +17,7 @@
- 1.30.0.RC
+ 1.30.0
diff --git a/sa-token-doc/doc/README.md b/sa-token-doc/doc/README.md
index 98dd7a87..b9225ef2 100644
--- a/sa-token-doc/doc/README.md
+++ b/sa-token-doc/doc/README.md
@@ -1,7 +1,7 @@
-Sa-Token v1.30.0.RC
+Sa-Token v1.30.0
一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!
@@ -122,10 +122,6 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
-[](https://giteye.net/chart/77YQZ6UK)
-
-
-
[](https://starchart.cc/dromara/sa-token)
如果 Sa-Token 帮助到了您,希望您可以为其点上一个 `star`:
@@ -133,19 +129,10 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
[GitHub](https://github.com/dromara/sa-token)
-
-
## 使用Sa-Token的开源项目
参考:[Sa-Token 生态](/more/link)
-## 贡献者名单
-感谢每一个为 Sa-Token 贡献代码的小伙伴
-
-
-
-[](https://giteye.net/chart/CGZ7GT8E)
-
## 交流群
QQ交流群:1群:1002350610 (已满) 、
2群:614714762 [点击加入](https://jq.qq.com/?_wv=1027&k=b759RZrL)
diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md
index a2ebd112..1a19d128 100644
--- a/sa-token-doc/doc/_sidebar.md
+++ b/sa-token-doc/doc/_sidebar.md
@@ -85,6 +85,7 @@
- [Token有效期详解](/fun/token-timeout)
- [Session模型详解](/fun/session-model)
- [TokenInfo参数详解](/fun/token-info)
+ - [异常细分状态码](/fun/exception-code)
- [解决反向代理 uri 丢失的问题](/fun/curr-domain)
- [参考:把权限放在缓存里](/fun/jur-cache)
- [解决跨域问题](/fun/cors-filter)
diff --git a/sa-token-doc/doc/fun/exception-code.md b/sa-token-doc/doc/fun/exception-code.md
new file mode 100644
index 00000000..250169dc
--- /dev/null
+++ b/sa-token-doc/doc/fun/exception-code.md
@@ -0,0 +1,107 @@
+# 异常细分状态码
+
+---
+
+### 获取异常细分状态码
+
+Sa-Token 中的基础异常类是 `SaTokenException`,在此基础上,又针对一些特定场景划分出诸如 `NotLoginException`、`NotPermissionException` 等。
+
+但是框架中异常抛出点远远多于异常种类的划分,比如在 SSO 插件中,[ redirect 重定向地址无效 ] 和 [ ticket 参数值无效 ] 都会导致 SSO 授权的失败,
+但是它们抛出的异常都是 `SaSsoException`,如果你需要对这两种异常情形做出不同的处理,仅仅判断异常的 ClassType 显然不够。
+
+为了解决上述需求,Sa-Token 对每个异常抛出点都会指定一个特定的 code 值,就像这样:
+
+``` java
+if(SaFoxUtil.isUrl(url) == false) {
+ throw new SaSsoException("无效redirect:" + url).setCode(SaSsoExceptionCode.CODE_20001);
+}
+```
+
+就像是打上一个特定的标记,不同异常情形标记的 code 码值也会不同,这就为你精细化异常处理提供了前提。
+
+要在捕获异常时获取这个 code 码也非常简单:
+
+``` java
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ @ExceptionHandler(SaTokenException.class)
+ public SaResult handlerSaTokenException(SaTokenException e) {
+
+ // 根据不同异常细分状态码返回不同的提示
+ if(e.getCode() == 20001) {
+ return SaResult.error("redirect 重定向 url 是一个无效地址");
+ }
+ if(e.getCode() == 20002) {
+ return SaResult.error("redirect 重定向 url 不在 allowUrl 允许的范围内");
+ }
+ if(e.getCode() == 20004) {
+ return SaResult.error("提供的 ticket 是无效的");
+ }
+ // 更多 code 码判断 ...
+
+ // 默认的提示
+ return SaResult.error("服务器繁忙,请稍后重试...");
+ }
+}
+```
+
+SaToken 中的所有异常都是继承于 `SaTokenException` 的,也就是说,所有异常你都可以通过 `e.getCode()` 的方式获取对应的异常细分状态码。
+
+
+
+
+
+### 异常细分状态码-参照表
+
+!> 目前仅对 sso 插件和 jwt 插件做了异常状态码细分,后续版本升级会支持更多模块
+
+**sa-token-code 核心包:**
+
+| code码值 | 含义 |
+| :-------- | :-------- |
+| -1 | 代表这个异常在抛出时未指定异常细分状态码 |
+
+
+**sa-token-sso 单点登录相关:**
+
+| code码值 | 含义 |
+| :-------- | :-------- |
+| 20001 | `redirect` 重定向 url 是一个无效地址 |
+| 20002 | `redirect` 重定向 url 不在 allowUrl 允许的范围内 |
+| 20003 | 接口调用方提供的 `secretkey` 秘钥无效 |
+| 20004 | 提供的 `ticket` 是无效的 |
+| 20005 | 在模式三下,sso-client 调用 sso-server 端 校验ticket接口 时,得到的响应是校验失败 |
+| 20006 | 在模式三下,sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 |
+| 20007 | http 请求调用 提供的 `timestamp` 与当前时间的差距超出允许的范围 |
+| 20008 | http 请求调用 提供的 `sign` 无效 |
+| 20009 | 本地系统没有配置 `secretkey` 字段 |
+
+
+
+**sa-token-jwt 插件相关:**
+
+| code码值 | 含义 |
+| :-------- | :-------- |
+| 40101 | 对 jwt 字符串解析失败 |
+| 40102 | 此 jwt 的签名无效 |
+| 40103 | 此 jwt 的 `loginType` 字段不符合预期 |
+| 40104 | 此 jwt 已超时 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sa-token-doc/doc/index.html b/sa-token-doc/doc/index.html
index 07b9130f..aa11b204 100644
--- a/sa-token-doc/doc/index.html
+++ b/sa-token-doc/doc/index.html
@@ -20,7 +20,7 @@
Sa-Token
-
v1.30.0.RC
+
v1.30.0
@@ -114,7 +114,7 @@