diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java b/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java index 07d72c1c..2c56c70b 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java @@ -5,6 +5,8 @@ import java.util.Map; import cn.dev33.satoken.action.SaTokenAction; import cn.dev33.satoken.action.SaTokenActionDefaultImpl; +import cn.dev33.satoken.aop.SaTokenListener; +import cn.dev33.satoken.aop.SaTokenListenerDefaultImpl; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.config.SaTokenConfigFactory; import cn.dev33.satoken.context.SaTokenContext; @@ -124,6 +126,24 @@ public class SaTokenManager { return saTokenContext; } + /** + * 监听器 Bean + */ + private static SaTokenListener saTokenListener; + public static void setSaTokenListener(SaTokenListener saTokenListener) { + SaTokenManager.saTokenListener = saTokenListener; + } + public static SaTokenListener getSaTokenListener() { + if (saTokenListener == null) { + synchronized (SaTokenManager.class) { + if (saTokenListener == null) { + setSaTokenListener(new SaTokenListenerDefaultImpl()); + } + } + } + return saTokenListener; + } + /** * StpLogic集合, 记录框架所有成功初始化的StpLogic */ diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/aop/SaTokenListener.java b/sa-token-core/src/main/java/cn/dev33/satoken/aop/SaTokenListener.java new file mode 100644 index 00000000..d4410215 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/aop/SaTokenListener.java @@ -0,0 +1,74 @@ +package cn.dev33.satoken.aop; + +import cn.dev33.satoken.stp.SaLoginModel; + +/** + * Sa-Token的监听器 + *
你可以通过实现此接口在用户登陆、退出等关键性操作时进行一些AOP操作 + * @author kong + * + */ +public interface SaTokenListener { + + /** + * 每次登录时触发 + * @param loginKey 账号类别 + * @param loginId 账号id + * @param loginModel 登录参数 + */ + public void doLogin(String loginKey, Object loginId, SaLoginModel loginModel); + + /** + * 每次注销时触发 + * @param loginKey 账号类别 + * @param loginId 账号id + * @param tokenValue token值 + */ + public void doLogout(String loginKey, Object loginId, String tokenValue); + + /** + * 每次被踢下线时触发 + * @param loginKey 账号类别 + * @param loginId 账号id + * @param tokenValue token值 + * @param device 设备标识 + */ + public void doLogoutByLoginId(String loginKey, Object loginId, String tokenValue, String device); + + /** + * 每次被顶下线时触发 + * @param loginKey 账号类别 + * @param loginId 账号id + * @param tokenValue token值 + * @param device 设备标识 + */ + public void doReplaced(String loginKey, Object loginId, String tokenValue, String device); + + /** + * 每次被封禁时触发 + * @param loginKey 账号类别 + * @param loginId 账号id + * @param disableTime 封禁时长,单位: 秒 + */ + public void doDisable(String loginKey, Object loginId, long disableTime); + + /** + * 每次被解封时触发 + * @param loginKey 账号类别 + * @param loginId 账号id + */ + public void doUntieDisable(String loginKey, Object loginId); + + /** + * 每次创建Session时触发 + * @param id SessionId + */ + public void doCreateSession(String id); + + /** + * 每次注销Session时触发 + * @param id SessionId + */ + public void doLogoutSession(String id); + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/aop/SaTokenListenerDefaultImpl.java b/sa-token-core/src/main/java/cn/dev33/satoken/aop/SaTokenListenerDefaultImpl.java new file mode 100644 index 00000000..7f9f408b --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/aop/SaTokenListenerDefaultImpl.java @@ -0,0 +1,96 @@ +package cn.dev33.satoken.aop; + +import java.util.Date; + +import cn.dev33.satoken.SaTokenManager; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.util.SaTokenInsideUtil; + +/** + * Sa-Token 监听器的默认实现:log打印 + * @author kong + * + */ +public class SaTokenListenerDefaultImpl implements SaTokenListener { + + /** + * 每次登录时触发 + */ + @Override + public void doLogin(String loginKey, Object loginId, SaLoginModel loginModel) { + println("账号[" + loginId + "]登录成功"); + } + + /** + * 每次注销时触发 + */ + @Override + public void doLogout(String loginKey, Object loginId, String tokenValue) { + println("账号[" + loginId + "]注销成功"); + } + + /** + * 每次被踢下线时触发 + */ + @Override + public void doLogoutByLoginId(String loginKey, Object loginId, String tokenValue, String device) { + println("账号[" + loginId + "]被踢下线 (终端: " + device + ")"); + } + + /** + * 每次被顶下线时触发 + */ + @Override + public void doReplaced(String loginKey, Object loginId, String tokenValue, String device) { + println("账号[" + loginId + "]被顶下线 (终端: " + device + ")"); + } + + /** + * 每次被封禁时触发 + */ + @Override + public void doDisable(String loginKey, Object loginId, long disableTime) { + Date date = new Date(System.currentTimeMillis() + disableTime * 1000); + println("账号[" + loginId + "]被封禁 (解封时间: " + SaTokenInsideUtil.formatDate(date) + ")"); + } + + /** + * 每次被解封时触发 + */ + @Override + public void doUntieDisable(String loginKey, Object loginId) { + println("账号[" + loginId + "]被解除封禁"); + } + + /** + * 每次创建Session时触发 + */ + @Override + public void doCreateSession(String id) { + println("Session[" + id + "]创建成功"); + } + + /** + * 每次注销Session时触发 + */ + @Override + public void doLogoutSession(String id) { + println("Session[" + id + "]注销成功"); + } + + /** + * 日志输出的前缀 + */ + public static final String LOG_PREFIX = "SaLog -->: "; + + /** + * 打印指定字符串 + * @param str 字符串 + */ + public void println(String str) { + if(SaTokenManager.getConfig().getIsLog()) { + System.out.println(LOG_PREFIX + str); + } + } + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java index 684fcf04..3a07f24d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java @@ -58,7 +58,9 @@ public class SaTokenConfig { /** 是否在初始化配置时打印版本字符画 */ private Boolean isV = true; - + /** 是否打印操作日志 */ + private Boolean isLog = false; + /** * @return token名称 (同时也是cookie名称) @@ -304,10 +306,23 @@ public class SaTokenConfig { return this; } - + /** + * @return 是否打印操作日志 + */ + public Boolean getIsLog() { + return isLog; + } /** - * toString + * @param isLog 是否打印操作日志 + */ + public SaTokenConfig setIsLog(Boolean isLog) { + this.isLog = isLog; + return this; + } + + /** + * toString() */ @Override public String toString() { @@ -316,8 +331,10 @@ public class SaTokenConfig { + isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle=" + tokenStyle + ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin=" + tokenSessionCheckLogin + ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain - + ", tokenPrefix=" + tokenPrefix + ", isV=" + isV + "]"; + + ", tokenPrefix=" + tokenPrefix + ", isV=" + isV + ", isLog=" + isLog + "]"; } + + diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java index f8d0bd1c..2bf219fc 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java @@ -34,6 +34,7 @@ public class SaSession implements Serializable { * 构建一个Session对象 */ public SaSession() { + this(null); } /** @@ -43,6 +44,8 @@ public class SaSession implements Serializable { public SaSession(String id) { this.id = id; this.createTime = System.currentTimeMillis(); + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doCreateSession(id); } /** @@ -249,6 +252,8 @@ public class SaSession implements Serializable { /** 注销Session (从持久库删除) */ public void logout() { SaTokenManager.getSaTokenDao().deleteSession(this.id); + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doLogoutSession(id); } /** 当Session上的tokenSign数量为零时,注销会话 */ diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 36e236d5..2eede31f 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -245,6 +245,8 @@ public class StpLogic { clearLastActivity(tokenSign.getValue()); // 3. 清理user-session上的token签名记录 session.removeTokenSign(tokenSign.getValue()); + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doReplaced(loginKey, loginId, tokenSign.getValue(), tokenSign.getDevice()); } } } @@ -273,6 +275,9 @@ public class StpLogic { // 在当前会话写入当前tokenValue setTokenValue(tokenValue, loginModel.getCookieTimeout()); + + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doLogin(loginKey, loginId, loginModel); } /** @@ -306,6 +311,9 @@ public class StpLogic { } SaTokenManager.getSaTokenDao().delete(splicingKeyTokenValue(tokenValue)); + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doLogout(loginKey, loginId, tokenValue); + // 3. 尝试清理账号session上的token签名 (如果为null或已被标记为异常, 那么无需继续执行 ) SaSession session = getSessionByLoginId(loginId, false); if(session == null) { @@ -333,8 +341,8 @@ public class StpLogic { * @param device 设备标识 (填null代表所有注销设备) */ public void logoutByLoginId(Object loginId, String device) { - // 1. 先获取这个账号的[id-session], 如果为null,则不执行任何操作 - SaSession session = getSessionByLoginId(loginId); + // 1. 先获取这个账号的[user-session], 如果为null,则不执行任何操作 + SaSession session = getSessionByLoginId(loginId, false); if(session == null) { return; } @@ -351,6 +359,8 @@ public class StpLogic { SaTokenManager.getSaTokenDao().update(splicingKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 4. 清理账号session上的token签名 session.removeTokenSign(tokenValue); + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doLogoutByLoginId(loginKey, loginId, tokenValue, tokenSign.getDevice()); } } // 3. 尝试注销session @@ -364,7 +374,11 @@ public class StpLogic { * @param disableTime 封禁时间, 单位: 秒 (-1=永久封禁) */ public void disable(Object loginId, long disableTime) { + // 标注为已被封禁 SaTokenManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime); + + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doDisable(loginKey, loginId, disableTime); } /** @@ -391,6 +405,9 @@ public class StpLogic { */ public void untieDisable(Object loginId) { SaTokenManager.getSaTokenDao().delete(splicingKeyDisable(loginId)); + + // $$ 通知监听器 + SaTokenManager.getSaTokenListener().doUntieDisable(loginKey, loginId); } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java index a6eef6a7..248c6c64 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java @@ -1,7 +1,9 @@ package cn.dev33.satoken.util; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Random; @@ -63,6 +65,15 @@ public class SaTokenInsideUtil { return System.currentTimeMillis() + "" + new Random().nextInt(Integer.MAX_VALUE); } + /** + * 将日期格式化 + * @param date 日期 + * @return 格式化后的时间 + */ + public static String formatDate(Date date){ + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + } + /** * 从集合里查询数据 * 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 7699f9f9..c6ec0838 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 @@ -241,6 +241,8 @@ public class TestController { @RequestMapping("test") public AjaxJson test() { System.out.println("进来了"); + StpUtil.disable(10001, 10002); + StpUtil.untieDisable(10001); return AjaxJson.getSuccess("访问成功"); } diff --git a/sa-token-demo-springboot/src/main/resources/application.yml b/sa-token-demo-springboot/src/main/resources/application.yml index 0ee1952f..5f211b33 100644 --- a/sa-token-demo-springboot/src/main/resources/application.yml +++ b/sa-token-demo-springboot/src/main/resources/application.yml @@ -17,6 +17,8 @@ spring: is-share: true # token风格 token-style: uuid + # 是否输出操作日志 + is-log: false # redis配置 diff --git a/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenSpringAutowired.java b/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenSpringAutowired.java index eaae7218..992d3624 100644 --- a/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenSpringAutowired.java +++ b/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaTokenSpringAutowired.java @@ -8,6 +8,7 @@ import org.springframework.util.PathMatcher; import cn.dev33.satoken.SaTokenManager; import cn.dev33.satoken.action.SaTokenAction; +import cn.dev33.satoken.aop.SaTokenListener; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.context.SaTokenContext; import cn.dev33.satoken.context.SaTokenContextForThreadLocal; @@ -102,6 +103,15 @@ public class SaTokenSpringAutowired { SaTokenManager.setSaTokenContext(saTokenContext); } + /** + * 注入监听器Bean + * + * @param saTokenListener saTokenListener对象 + */ + @Autowired(required = false) + public void setSaTokenListener(SaTokenListener saTokenListener) { + SaTokenManager.setSaTokenListener(saTokenListener); + } /** * 利用自动匹配特性,获取SpringMVC框架内部使用的路由匹配器 * diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenSpringAutowired.java b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenSpringAutowired.java index bafef81e..c45fb688 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenSpringAutowired.java +++ b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenSpringAutowired.java @@ -8,6 +8,7 @@ import org.springframework.util.PathMatcher; import cn.dev33.satoken.SaTokenManager; import cn.dev33.satoken.action.SaTokenAction; +import cn.dev33.satoken.aop.SaTokenListener; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.context.SaTokenContext; import cn.dev33.satoken.dao.SaTokenDao; @@ -93,6 +94,16 @@ public class SaTokenSpringAutowired { SaTokenManager.setSaTokenContext(saTokenContext); } + /** + * 注入监听器Bean + * + * @param saTokenListener saTokenListener对象 + */ + @Autowired(required = false) + public void setSaTokenListener(SaTokenListener saTokenListener) { + SaTokenManager.setSaTokenListener(saTokenListener); + } + /** * 利用自动匹配特性,获取SpringMVC框架内部使用的路由匹配器 *