tokenSignList = session.getTokenSignList();
+ for (TokenSign tokenSign : tokenSignList) {
+ if(tokenSign.getDevice().equals(device)) {
+ dao.updateValue(getKeyTokenValue(tokenSign.getValue()), NotLoginException.BE_REPLACED); // 1. 将此token 标记为已顶替
+ clearLastActivity(tokenSign.getValue()); // 2. 清理掉[token-最后操作时间]
+ session.removeTokenSign(tokenSign.getValue()); // 3. 清理账号session上的token签名记录
+ }
}
}
}
@@ -176,7 +191,7 @@ public class StpLogic {
dao.updateSessionTimeout(session.getId(), config.getTimeout());
}
// 在session上记录token签名
- session.addTokenSign(new TokenSign(tokenValue, SaTokenConsts.DEFAULT_LOGIN_DEVICE));
+ session.addTokenSign(new TokenSign(tokenValue, device));
// ------ 4. 持久化其它数据
dao.setValue(getKeyTokenValue(tokenValue), String.valueOf(loginId), config.getTimeout()); // token -> uid
@@ -187,6 +202,7 @@ public class StpLogic {
}
}
+
/**
* 当前会话注销登录
*/
@@ -203,6 +219,7 @@ public class StpLogic {
logoutByTokenValue(tokenValue);
}
+
/**
* 指定token的会话注销登录
* @param tokenValue 指定token
@@ -229,12 +246,23 @@ public class StpLogic {
session.logoutByTokenSignCountToZero();
}
+
/**
* 指定loginId的会话注销登录(踢人下线)
* 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
*/
public void logoutByLoginId(Object loginId) {
+ logoutByLoginId(loginId, null);
+ }
+
+ /**
+ * 指定loginId指定设备的会话注销登录(踢人下线)
+ *
当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
+ * @param loginId 账号id
+ * @param device 设备标识 (填null代表所有注销设备)
+ */
+ public void logoutByLoginId(Object loginId, String device) {
// 先获取这个账号的[id-session], 如果为null,则不执行任何操作
SaSession session = getSessionByLoginId(loginId);
if(session == null) {
@@ -244,14 +272,16 @@ public class StpLogic {
// 循环token签名列表,开始删除相关信息
List tokenSignList = session.getTokenSignList();
for (TokenSign tokenSign : tokenSignList) {
- // 1. 获取token
- String tokenValue = tokenSign.getValue();
- // 2. 清理掉[token-最后操作时间]
- clearLastActivity(tokenValue);
- // 3. 标记:已被踢下线
- SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记:已被踢下线
- // 4. 清理账号session上的token签名
- session.removeTokenSign(tokenValue);
+ if(device == null || tokenSign.getDevice().equals(device)) {
+ // 1. 获取token
+ String tokenValue = tokenSign.getValue();
+ // 2. 清理掉[token-最后操作时间]
+ clearLastActivity(tokenValue);
+ // 3. 标记:已被踢下线
+ SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记:已被踢下线
+ // 4. 清理账号session上的token签名
+ session.removeTokenSign(tokenValue);
+ }
}
// 尝试注销session
session.logoutByTokenSignCountToZero();
@@ -269,6 +299,7 @@ public class StpLogic {
return getLoginIdDefaultNull() != null;
}
+
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
@@ -276,6 +307,7 @@ public class StpLogic {
getLoginId();
}
+
/**
* 获取当前会话账号id, 如果未登录,则抛出异常
* @return 账号id
@@ -310,7 +342,8 @@ public class StpLogic {
return loginId;
}
- /**
+
+ /**
* 获取当前会话登录id, 如果未登录,则返回默认值
* @param 返回类型
* @param defaultValue 默认值
@@ -336,7 +369,8 @@ public class StpLogic {
return (T)loginId;
}
- /**
+
+ /**
* 获取当前会话登录id, 如果未登录,则返回null
* @return 账号id
*/
@@ -359,6 +393,7 @@ public class StpLogic {
return loginId;
}
+
/**
* 获取当前会话登录id, 并转换为String
* @return 账号id
@@ -367,7 +402,8 @@ public class StpLogic {
return String.valueOf(getLoginId());
}
- /**
+
+ /**
* 获取当前会话登录id, 并转换为int
* @return 账号id
*/
@@ -379,7 +415,8 @@ public class StpLogic {
return Integer.valueOf(String.valueOf(getLoginId()));
}
- /**
+
+ /**
* 获取当前会话登录id, 并转换为long
* @return 账号id
*/
@@ -391,6 +428,7 @@ public class StpLogic {
return Long.valueOf(String.valueOf(getLoginId()));
}
+
/**
* 获取指定token对应的登录id,如果未登录,则返回 null
* @param tokenValue token
@@ -424,6 +462,7 @@ public class StpLogic {
return session;
}
+
/**
* 获取指定loginId的session, 如果session尚未创建,isCreate=是否新建并返回
* @param loginId 账号id
@@ -434,6 +473,7 @@ public class StpLogic {
return getSessionBySessionId(getKeySession(loginId), isCreate);
}
+
/**
* 获取指定loginId的session,如果session尚未创建,则新建并返回
* @param loginId 账号id
@@ -443,6 +483,7 @@ public class StpLogic {
return getSessionByLoginId(loginId, true);
}
+
/**
* 获取当前会话的session, 如果session尚未创建,isCreate=是否新建并返回
* @param isCreate 是否新建
@@ -452,6 +493,7 @@ public class StpLogic {
return getSessionByLoginId(getLoginId(), isCreate);
}
+
/**
* 获取当前会话的session,如果session尚未创建,则新建并返回
* @return 当前会话的session
@@ -463,6 +505,7 @@ public class StpLogic {
// =================== token专属session ===================
+
/**
* 获取指定token的专属session,如果session尚未创建,isCreate代表是否新建并返回
* @param tokenValue token值
@@ -473,6 +516,7 @@ public class StpLogic {
return getSessionBySessionId(getKeyTokenSession(tokenValue), isCreate);
}
+
/**
* 获取指定token的专属session,如果session尚未创建,则新建并返回
* @param tokenValue token值
@@ -482,6 +526,7 @@ public class StpLogic {
return getSessionBySessionId(getKeyTokenSession(tokenValue), true);
}
+
/**
* 获取当前token的专属-session,如果session尚未创建,isCreate代表是否新建并返回
* @param isCreate 是否新建
@@ -502,6 +547,7 @@ public class StpLogic {
return getSessionBySessionId(getKeyTokenSession(getTokenValue()), isCreate);
}
+
/**
* 获取当前token的专属-session,如果session尚未创建,则新建并返回
* @return session会话
@@ -513,7 +559,8 @@ public class StpLogic {
// =================== [临时过期] 验证相关 ===================
- /**
+
+ /**
* 写入指定token的 [最后操作时间] 为当前时间戳
* @param tokenValue 指定token
*/
@@ -526,6 +573,7 @@ public class StpLogic {
SaTokenManager.getSaTokenDao().setValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout());
}
+
/**
* 清除指定token的 [最后操作时间]
* @param tokenValue 指定token
@@ -541,6 +589,7 @@ public class StpLogic {
SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY);
}
+
/**
* 检查指定token 是否已经[临时过期],如果已经过期则抛出异常
* @param tokenValue 指定token
@@ -572,6 +621,7 @@ public class StpLogic {
request.setAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true);
}
+
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
*/
@@ -579,6 +629,7 @@ public class StpLogic {
checkActivityTimeout(getTokenValue());
}
+
/**
* 续签指定token:(将 [最后操作时间] 更新为当前时间戳)
* @param tokenValue 指定token
@@ -591,6 +642,7 @@ public class StpLogic {
SaTokenManager.getSaTokenDao().updateValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()));
}
+
/**
* 续签当前token:(将 [最后操作时间] 更新为当前时间戳)
* 请注意: 即时token已经 [临时过期] 也可续签成功,
@@ -827,7 +879,6 @@ public class StpLogic {
// =================== id 反查token 相关操作 ===================
-
/**
* 获取指定loginId的tokenValue
*
在配置为允许并发登录时,此方法只会返回队列的最后一个token,
@@ -836,16 +887,38 @@ public class StpLogic {
* @return token值
*/
public String getTokenValueByLoginId(Object loginId) {
- List tokenValueList = getTokenValueListByLoginId(loginId);
+ return getTokenValueByLoginId(loginId, SaTokenConsts.DEFAULT_LOGIN_DEVICE);
+ }
+
+ /**
+ * 获取指定loginId指定设备端的tokenValue
+ * 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
+ * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId
+ * @param loginId 账号id
+ * @param device 设备标识
+ * @return token值
+ */
+ public String getTokenValueByLoginId(Object loginId, String device) {
+ List tokenValueList = getTokenValueListByLoginId(loginId, device);
return tokenValueList.size() == 0 ? null : tokenValueList.get(tokenValueList.size() - 1);
}
/**
- * 获取指定loginId的tokenValue
+ * 获取指定loginId的tokenValue集合
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
public List getTokenValueListByLoginId(Object loginId) {
+ return getTokenValueListByLoginId(loginId, SaTokenConsts.DEFAULT_LOGIN_DEVICE);
+ }
+
+ /**
+ * 获取指定loginId指定设备端的tokenValue 集合
+ * @param loginId 账号id
+ * @param device 设备标识
+ * @return 此loginId的所有相关token
+ */
+ public List getTokenValueListByLoginId(Object loginId, String device) {
// 如果session为null的话直接返回空集合
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
@@ -855,11 +928,42 @@ public class StpLogic {
List tokenSignList = session.getTokenSignList();
List tokenValueList = new ArrayList<>();
for (TokenSign tokenSign : tokenSignList) {
- tokenValueList.add(tokenSign.getValue());
+ if(tokenSign.getDevice().equals(device)) {
+ tokenValueList.add(tokenSign.getValue());
+ }
}
return tokenValueList;
}
+ /**
+ * 返回当前token的登录设备
+ * @return 当前令牌的登录设备
+ */
+ public String getLoginDevice() {
+ // 如果没有token,直接返回
+ String tokenValue = getTokenValue();
+ if(tokenValue == null) {
+ return null;
+ }
+ // 如果还未登录,直接返回 null
+ if(isLogin() == false) {
+ return null;
+ }
+ // 如果session为null的话直接返回 null
+ SaSession session = getSession(false);
+ if(session == null) {
+ return null;
+ }
+ // 遍历解析
+ List tokenSignList = session.getTokenSignList();
+ for (TokenSign tokenSign : tokenSignList) {
+ if(tokenSign.getValue().equals(tokenValue)) {
+ return tokenSign.getDevice();
+ }
+ }
+ return null;
+ }
+
// =================== 返回相应key ===================
@@ -870,6 +974,7 @@ public class StpLogic {
public String getKeyTokenName() {
return getConfig().getTokenName();
}
+
/**
* 获取key: tokenValue 持久化 token-id
* @param tokenValue token值
@@ -878,6 +983,7 @@ public class StpLogic {
public String getKeyTokenValue(String tokenValue) {
return getConfig().getTokenName() + ":" + loginKey + ":token:" + tokenValue;
}
+
/**
* 获取key: session 持久化
* @param loginId 账号id
@@ -886,6 +992,7 @@ public class StpLogic {
public String getKeySession(Object loginId) {
return getConfig().getTokenName() + ":" + loginKey + ":session:" + loginId;
}
+
/**
* 获取key: tokenValue的专属session
* @param tokenValue token值
@@ -894,6 +1001,7 @@ public class StpLogic {
public String getKeyTokenSession(String tokenValue) {
return getConfig().getTokenName() + ":" + loginKey + ":token-session:" + tokenValue;
}
+
/**
* 获取key: 指定token的最后操作时间 持久化
* @param tokenValue token值
diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java
index 2d98ec82..786f3ad8 100644
--- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java
+++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java
@@ -61,6 +61,15 @@ public class StpUtil {
stpLogic.setLoginId(loginId);
}
+ /**
+ * 在当前会话上登录id
+ * @param loginId 登录id,建议的类型:(long | int | String)
+ * @param device 设备标识
+ */
+ public static void setLoginId(Object loginId, String device) {
+ stpLogic.setLoginId(loginId, device);
+ }
+
/**
* 当前会话注销登录
*/
@@ -85,6 +94,16 @@ public class StpUtil {
stpLogic.logoutByLoginId(loginId);
}
+ /**
+ * 指定loginId指定设备的会话注销登录(踢人下线)
+ * 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
+ * @param loginId 账号id
+ * @param device 设备标识
+ */
+ public static void logoutByLoginId(Object loginId, String device) {
+ stpLogic.logoutByLoginId(loginId, device);
+ }
+
// 查询相关
@@ -381,15 +400,44 @@ public class StpUtil {
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
+
+ /**
+ * 获取指定loginId指定设备端的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);
+ }
/**
- * 获取指定loginId的tokenValue
+ * 获取指定loginId的tokenValue集合
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
public static List getTokenValueListByLoginId(Object loginId) {
return stpLogic.getTokenValueListByLoginId(loginId);
}
+
+ /**
+ * 获取指定loginId指定设备端的tokenValue集合
+ * @param loginId 账号id
+ * @param device 设备标识
+ * @return 此loginId的所有相关token
+ */
+ public static List getTokenValueListByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenValueListByLoginId(loginId, device);
+ }
+ /**
+ * 返回当前token的登录设备
+ * @return 当前令牌的登录设备
+ */
+ public static String getLoginDevice() {
+ return stpLogic.getLoginDevice();
+ }
}
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 10e8decd..a5704f65 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
@@ -10,7 +10,7 @@ public class SaTokenConsts {
/**
* sa-token 版本号
*/
- public static final String VERSION_NO = "v1.8.0";
+ public static final String VERSION_NO = "v1.9.0";
/**
* sa-token 开源地址
diff --git a/sa-token-dao-redis-jackson/pom.xml b/sa-token-dao-redis-jackson/pom.xml
index 293c235a..5adde229 100644
--- a/sa-token-dao-redis-jackson/pom.xml
+++ b/sa-token-dao-redis-jackson/pom.xml
@@ -7,7 +7,7 @@
cn.dev33
sa-token-parent
- 1.8.0
+ 1.9.0
jar
@@ -20,13 +20,13 @@
cn.dev33
sa-token-spring-boot-starter
- 1.8.0
+ 1.9.0
org.springframework.boot
spring-boot-starter-data-redis
- 2.3.7.RELEASE
+ 2.3.3.RELEASE
diff --git a/sa-token-dao-redis/pom.xml b/sa-token-dao-redis/pom.xml
index 9e66756a..9d5f072b 100644
--- a/sa-token-dao-redis/pom.xml
+++ b/sa-token-dao-redis/pom.xml
@@ -7,7 +7,7 @@
cn.dev33
sa-token-parent
- 1.8.0
+ 1.9.0
jar
@@ -20,13 +20,13 @@
cn.dev33
sa-token-spring-boot-starter
- 1.8.0
+ 1.9.0
org.springframework.boot
spring-boot-starter-data-redis
- 2.3.7.RELEASE
+ 2.3.3.RELEASE
diff --git a/sa-token-demo-springboot/pom.xml b/sa-token-demo-springboot/pom.xml
index bc599554..09bb81d1 100644
--- a/sa-token-demo-springboot/pom.xml
+++ b/sa-token-demo-springboot/pom.xml
@@ -29,21 +29,21 @@
cn.dev33
sa-token-spring-boot-starter
- 1.8.0
+ 1.9.0
diff --git a/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenAction.java b/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenAction.java
deleted file mode 100644
index 7327f1d4..00000000
--- a/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenAction.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.pj.satoken;
-
-import org.springframework.stereotype.Component;
-
-import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
-
-/**
- * 继承sa-token行为Bean默认实现, 重写部分逻辑
- * @author kong
- *
- */
-@Component
-public class MySaTokenAction extends SaTokenActionDefaultImpl {
-
- // 重写token生成策略
-// @Override
-// public String createToken(Object loginId, String loginKey) {
-// return SaTokenInsideUtil.getRandomString(60);
-// }
-
-}
diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java
index 096ac1c4..a45f1f7a 100644
--- a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java
+++ b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java
@@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
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;
@@ -38,7 +39,8 @@ public class TestController {
System.out.println("登录成功");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号:" + StpUtil.getLoginId());
- System.out.println("当前登录账号:" + StpUtil.getLoginIdAsInt()); // 获取登录id并转为int
+// System.out.println("当前登录账号并转为int:" + StpUtil.getLoginIdAsInt());
+ System.out.println("当前登录设备:" + StpUtil.getLoginDevice());
// System.out.println("当前token信息:" + StpUtil.getTokenInfo());
return AjaxJson.getSuccess();
@@ -151,13 +153,14 @@ public class TestController {
// 测试注解式鉴权, 浏览器访问: 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();
+// StpUtil.checkActivityTimeout();
+// StpUtil.updateLastActivityToNow();
return AjaxJson.getSuccess();
}
@@ -192,10 +195,18 @@ public class TestController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
- StpUtil.getTokenSession().logout();
+// StpUtil.getTokenSession().logout();
+ StpUtil.logoutByLoginId(10001);
return AjaxJson.getSuccess();
}
+
+ // 测试登录接口, 按照设备登录, 浏览器访问: http://localhost:8081/test/login2
+ @RequestMapping("login2")
+ public AjaxJson login2(@RequestParam(defaultValue="10001") String id, @RequestParam(defaultValue="PC") String device) {
+ StpUtil.setLoginId(id, device);
+ return AjaxJson.getSuccess();
+ }
}
diff --git a/sa-token-doc/doc/README.md b/sa-token-doc/doc/README.md
index c30805e6..3e5e92db 100644
--- a/sa-token-doc/doc/README.md
+++ b/sa-token-doc/doc/README.md
@@ -1,11 +1,11 @@
-sa-token v1.8.0
+sa-token v1.9.0
一个JavaWeb轻量级权限认证框架,功能全面,上手简单
-
+
@@ -16,7 +16,7 @@
---
-## 😘 在线资料
+## 在线资料
- [官网首页:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
@@ -27,8 +27,8 @@
- [开源不易,求鼓励,点个star吧](https://github.com/click33/sa-token)
-## ⭐ sa-token是什么?
-**sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:**
+## sa-token是什么?
+sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:
``` java
// 在登录时写入当前会话的账号id
@@ -39,16 +39,16 @@ StpUtil.checkLogin();
```
-**没有复杂的封装!不要任何的配置!只需这两行简单的调用,即可轻松完成系统登录鉴权!**
+没有复杂的封装!不要任何的配置!只需这两行简单的调用,即可轻松完成系统登录鉴权!
-## 🔥 框架设计思想
+## 框架设计思想
与其它权限认证框架相比,`sa-token`尽力保证两点:
- 上手简单:能自动化的配置全部自动化,不让你费脑子
- 功能强大:能涵盖的功能全部涵盖,不让你用个框架还要自己给框架打各种补丁
-**如果上面的示例能够证明`sa-token`的简单,那么以下API则可以证明`sa-token`的强大**
+如果上面的示例能够证明`sa-token`的简单,那么以下API则可以证明`sa-token`的强大
``` java
StpUtil.setLoginId(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
@@ -60,13 +60,15 @@ StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
+StpUtil.setLoginId(10001, "PC"); // 指定设备标识登录
+StpUtil.logoutByLoginId(10001, "PC"); // 指定设备标识进行强制注销 (不同端不受影响)
```
-**sa-token的API众多,请恕此处无法为您逐一展示,更多示例请戳官方在线文档**
+sa-token的API众多,请恕此处无法为您逐一展示,更多示例请戳官方在线文档
-## 💦️️ 涵盖功能
+## 涵盖功能
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 拦截违规调用,不同角色不同授权
- **Session会话** —— 专业的数据缓存中心
@@ -78,11 +80,12 @@ StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
- **花式token生成** —— 内置六种token风格,还可自定义token生成策略
- **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签
+- **同端互斥登录** —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
- **组件自动注入** —— 零配置与Spring等框架集成
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
-## 🔨 贡献代码
+## 贡献代码
sa-token欢迎大家贡献代码,为框架添砖加瓦
1. 在github上fork一份到自己的仓库
2. clone自己的仓库到本地电脑
@@ -91,7 +94,7 @@ sa-token欢迎大家贡献代码,为框架添砖加瓦
5. 等待合并
-## 🌱 建议贡献的地方
+## 建议贡献的地方
- 修复源码现有bug,或增加新的实用功能
- 完善在线文档,或者修复现有错误之处
- 更多demo示例:比如SSM版搭建步骤
@@ -99,11 +102,11 @@ sa-token欢迎大家贡献代码,为框架添砖加瓦
- 如果更新实用功能,可在文档友情链接处留下自己的推广链接
-## 🚀 友情链接
+## 友情链接
[**[ okhttps ]** 一个轻量级http通信框架,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
-## 😎 交流群
+## 交流群
QQ交流群:[1002350610 点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM) ,欢迎你的加入
diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md
index f0db84c1..e76e2dce 100644
--- a/sa-token-doc/doc/_sidebar.md
+++ b/sa-token-doc/doc/_sidebar.md
@@ -14,6 +14,7 @@
- [无Cookie模式(前后台分离)](/use/not-cookie)
- [模拟他人](/use/mock-person)
- [多账号验证](/use/many-account)
+ - [同端互斥登录](/use/mutex-login)
- [注解式鉴权](/use/at-check)
- [花式token](/use/token-style)
- [框架配置](/use/config)
diff --git a/sa-token-doc/doc/index.html b/sa-token-doc/doc/index.html
index 511f2746..4dbf9360 100644
--- a/sa-token-doc/doc/index.html
+++ b/sa-token-doc/doc/index.html
@@ -2,13 +2,13 @@
- sa-token
+ sa-token 官方文档
-
-
+
+
@@ -22,6 +22,7 @@