diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java b/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java index a02f6e9a..3753485a 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java @@ -14,6 +14,9 @@ import cn.dev33.satoken.error.SaErrorCode; import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.json.SaJsonTemplate; import cn.dev33.satoken.json.SaJsonTemplateDefaultImpl; +import cn.dev33.satoken.log.SaLog; +import cn.dev33.satoken.log.SaLogForConsole; +import cn.dev33.satoken.log.input.SaLogInput; import cn.dev33.satoken.same.SaSameTemplate; import cn.dev33.satoken.sign.SaSignTemplate; import cn.dev33.satoken.sign.SaSignTemplateDefaultImpl; @@ -37,18 +40,27 @@ public class SaManager { */ public volatile static SaTokenConfig config; public static void setConfig(SaTokenConfig config) { - SaManager.config = config; + setConfigMethod(config); + + // 打印 banner if(config.getIsPrint()) { SaFoxUtil.printSaToken(); } + + // ## 发送日志 + SaManager.getLogInput().registerConfig(config); + // 调用一次StpUtil中的方法,保证其可以尽早的初始化 StpLogic StpUtil.getLoginType(); } + private static void setConfigMethod(SaTokenConfig config) { + SaManager.config = config; + } public static SaTokenConfig getConfig() { if (config == null) { synchronized (SaManager.class) { if (config == null) { - setConfig(SaTokenConfigFactory.createConfig()); + setConfigMethod(SaTokenConfigFactory.createConfig()); } } } @@ -60,6 +72,10 @@ public class SaManager { */ private volatile static SaTokenDao saTokenDao; public static void setSaTokenDao(SaTokenDao saTokenDao) { + setSaTokenDaoMethod(saTokenDao); + SaManager.getLogInput().registerComponent("SaTokenDao", saTokenDao); + } + private static void setSaTokenDaoMethod(SaTokenDao saTokenDao) { if((SaManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) { ((SaTokenDaoDefaultImpl)SaManager.saTokenDao).endRefreshThread(); } @@ -69,7 +85,7 @@ public class SaManager { if (saTokenDao == null) { synchronized (SaManager.class) { if (saTokenDao == null) { - setSaTokenDao(new SaTokenDaoDefaultImpl()); + setSaTokenDaoMethod(new SaTokenDaoDefaultImpl()); } } } @@ -82,12 +98,13 @@ public class SaManager { private volatile static StpInterface stpInterface; public static void setStpInterface(StpInterface stpInterface) { SaManager.stpInterface = stpInterface; + SaManager.getLogInput().registerComponent("StpInterface", stpInterface); } public static StpInterface getStpInterface() { if (stpInterface == null) { synchronized (SaManager.class) { if (stpInterface == null) { - setStpInterface(new StpInterfaceDefaultImpl()); + SaManager.stpInterface = new StpInterfaceDefaultImpl(); } } } @@ -100,6 +117,7 @@ public class SaManager { private volatile static SaTokenContext saTokenContext; public static void setSaTokenContext(SaTokenContext saTokenContext) { SaManager.saTokenContext = saTokenContext; + SaManager.getLogInput().registerComponent("SaTokenContext", saTokenContext); } public static SaTokenContext getSaTokenContext() { return saTokenContext; @@ -109,11 +127,12 @@ public class SaManager { * 二级Context */ private volatile static SaTokenSecondContext saTokenSecondContext; - public static SaTokenSecondContext getSaTokenSecondContext() { - return saTokenSecondContext; - } public static void setSaTokenSecondContext(SaTokenSecondContext saTokenSecondContext) { SaManager.saTokenSecondContext = saTokenSecondContext; + SaManager.getLogInput().registerComponent("SaTokenSecondContext", saTokenSecondContext); + } + public static SaTokenSecondContext getSaTokenSecondContext() { + return saTokenSecondContext; } /** @@ -146,12 +165,13 @@ public class SaManager { private volatile static SaTempInterface saTemp; public static void setSaTemp(SaTempInterface saTemp) { SaManager.saTemp = saTemp; + SaManager.getLogInput().registerComponent("SaTempInterface", saTemp); } public static SaTempInterface getSaTemp() { if (saTemp == null) { synchronized (SaManager.class) { if (saTemp == null) { - setSaTemp(new SaTempDefaultImpl()); + SaManager.saTemp = new SaTempDefaultImpl(); } } } @@ -164,12 +184,13 @@ public class SaManager { private volatile static SaJsonTemplate saJsonTemplate; public static void setSaJsonTemplate(SaJsonTemplate saJsonTemplate) { SaManager.saJsonTemplate = saJsonTemplate; + SaManager.getLogInput().registerComponent("SaJsonTemplate", saJsonTemplate); } public static SaJsonTemplate getSaJsonTemplate() { if (saJsonTemplate == null) { synchronized (SaManager.class) { if (saJsonTemplate == null) { - setSaJsonTemplate(new SaJsonTemplateDefaultImpl()); + SaManager.saJsonTemplate = new SaJsonTemplateDefaultImpl(); } } } @@ -182,12 +203,13 @@ public class SaManager { private volatile static SaSignTemplate saSignTemplate; public static void setSaSignTemplate(SaSignTemplate saSignTemplate) { SaManager.saSignTemplate = saSignTemplate; + SaManager.getLogInput().registerComponent("SaSignTemplate", saSignTemplate); } public static SaSignTemplate getSaSignTemplate() { if (saSignTemplate == null) { synchronized (SaManager.class) { if (saSignTemplate == null) { - setSaSignTemplate(new SaSignTemplateDefaultImpl()); + SaManager.saSignTemplate = new SaSignTemplateDefaultImpl(); } } } @@ -200,18 +222,50 @@ public class SaManager { private volatile static SaSameTemplate saSameTemplate; public static void setSaSameTemplate(SaSameTemplate saSameTemplate) { SaManager.saSameTemplate = saSameTemplate; + SaManager.getLogInput().registerComponent("SaSameTemplate", saSameTemplate); } public static SaSameTemplate getSaSameTemplate() { if (saSameTemplate == null) { synchronized (SaManager.class) { if (saSameTemplate == null) { - setSaSameTemplate(new SaSameTemplate()); + SaManager.saSameTemplate = new SaSameTemplate(); } } } return saSameTemplate; } + /** + * 日志接收器 + */ + private volatile static SaLogInput logInput; + public static void setLogInput(SaLogInput logInput) { + SaManager.logInput = logInput; + SaManager.getLogInput().registerComponent("SaLogInput", logInput); + } + public static SaLogInput getLogInput() { + if (logInput == null) { + synchronized (SaManager.class) { + if (logInput == null) { + SaManager.logInput = new SaLogInput(); + } + } + } + return logInput; + } + + /** + * 日志输出器 + */ + public volatile static SaLog log = new SaLogForConsole(); + public static void setLog(SaLog log) { + SaManager.log = log; + SaManager.getLogInput().registerComponent("SaLog", log); + } + public static SaLog getLog() { + return SaManager.log; + } + /** * StpLogic集合, 记录框架所有成功初始化的StpLogic */ 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 6ecd1ea6..be77d923 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 @@ -2,6 +2,8 @@ package cn.dev33.satoken.config; import java.io.Serializable; +import cn.dev33.satoken.util.SaFoxUtil; + /** * Sa-Token 配置类 Model *

@@ -70,6 +72,13 @@ public class SaTokenConfig implements Serializable { /** 是否打印操作日志 */ private Boolean isLog = false; + /** 日志等级(trace、debug、info、warn、error、fatal) */ + private String logLevel = "trace"; + + /** 日志等级 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal) */ + private int logLevelInt = 1; + + /** * jwt秘钥 (只有集成 jwt 模块时此参数才会生效) */ @@ -374,6 +383,40 @@ public class SaTokenConfig implements Serializable { return this; } + /** + * @return 日志等级(trace、debug、info、warn、error、fatal) + */ + public String getLogLevel() { + return logLevel; + } + + /** + * @param logLevel 日志等级(trace、debug、info、warn、error、fatal) + * @return 对象自身 + */ + public SaTokenConfig setLogLevel(String logLevel) { + this.logLevel = logLevel; + this.logLevelInt = SaFoxUtil.translateLogLevelToInt(logLevel); + return this; + } + + /** + * @return 日志等级 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal) + */ + public int getLogLevelInt() { + return logLevelInt; + } + + /** + * @param logLevelInt 日志等级 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal) + * @return 对象自身 + */ + public SaTokenConfig setLogLeveInt(int logLevelInt) { + this.logLevelInt = logLevelInt; + this.logLevel = SaFoxUtil.translateLogLevelToString(logLevelInt); + return this; + } + /** * @return jwt秘钥 (只有集成 jwt 模块时此参数才会生效) */ @@ -490,6 +533,8 @@ public class SaTokenConfig implements Serializable { + ", tokenPrefix=" + tokenPrefix + ", isPrint=" + isPrint + ", isLog=" + isLog + + ", logLevel=" + logLevel + + ", logLevelInt=" + logLevelInt + ", jwtSecretKey=" + jwtSecretKey + ", basic=" + basic + ", currDomain=" + currDomain diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java index c81e5f90..9bfc7bae 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java @@ -21,7 +21,7 @@ public class SaTokenEventCenter { static { // 默认添加控制台日志侦听器 - listenerList.add(new SaTokenListenerForConsolePrint()); + listenerList.add(new SaTokenListenerForLog()); } /** @@ -198,7 +198,32 @@ public class SaTokenEventCenter { listener.doUntieDisable(loginType, loginId, service); } } - + + /** + * 每次打开二级认证时触发 + * @param loginType 账号类别 + * @param tokenValue token值 + * @param service 指定服务 + * @param safeTime 认证时间,单位:秒 + */ + public static void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + for (SaTokenListener listener : listenerList) { + listener.doOpenSafe(loginType, tokenValue, service, safeTime); + } + } + + /** + * 每次关闭二级认证时触发 + * @param loginType 账号类别 + * @param service 指定服务 + * @param tokenValue token值 + */ + public static void doCloseSafe(String loginType, String tokenValue, String service) { + for (SaTokenListener listener : listenerList) { + listener.doCloseSafe(loginType, tokenValue, service); + } + } + /** * 每次创建Session时触发 * @param id SessionId diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java index 1ef18fe7..839d3d57 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java @@ -60,7 +60,24 @@ public interface SaTokenListener { * @param service 指定服务 */ public void doUntieDisable(String loginType, Object loginId, String service); - + + /** + * 每次打开二级认证时触发 + * @param loginType 账号类别 + * @param tokenValue token值 + * @param service 指定服务 + * @param safeTime 认证时间,单位:秒 + */ + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime); + + /** + * 每次关闭二级认证时触发 + * @param loginType 账号类别 + * @param tokenValue token值 + * @param service 指定服务 + */ + public void doCloseSafe(String loginType, String tokenValue, String service); + /** * 每次创建Session时触发 * @param id SessionId diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForConsolePrint.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForConsolePrint.java deleted file mode 100644 index 4a7362a3..00000000 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForConsolePrint.java +++ /dev/null @@ -1,107 +0,0 @@ -package cn.dev33.satoken.listener; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.stp.SaLoginModel; -import cn.dev33.satoken.util.SaFoxUtil; - -/** - * Sa-Token 侦听器实现:控制台 log 打印 - * @author kong - * - */ -public class SaTokenListenerForConsolePrint implements SaTokenListener { - - /** - * 每次登录时触发 - */ - @Override - public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { - println("账号[" + loginId + "]登录成功"); - } - - /** - * 每次注销时触发 - */ - @Override - public void doLogout(String loginType, Object loginId, String tokenValue) { - println("账号[" + loginId + "]注销成功 (Token=" + tokenValue + ")"); - } - - /** - * 每次被踢下线时触发 - */ - @Override - public void doKickout(String loginType, Object loginId, String tokenValue) { - println("账号[" + loginId + "]被踢下线 (Token=" + tokenValue + ")"); - } - - /** - * 每次被顶下线时触发 - */ - @Override - public void doReplaced(String loginType, Object loginId, String tokenValue) { - println("账号[" + loginId + "]被顶下线 (Token=" + tokenValue + ")"); - } - - /** - * 每次被封禁时触发 - */ - @Override - public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) { - Instant instant = Instant.ofEpochMilli(System.currentTimeMillis() + disableTime * 1000); - ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()); - println("账号[" + loginId + "] " + service + " 服务被封禁,封禁等级=" + level + " (解封时间: " + SaFoxUtil.formatDate(zonedDateTime) + ")"); - } - - /** - * 每次被解封时触发 - */ - @Override - public void doUntieDisable(String loginType, Object loginId, String service) { - println("账号[" + loginId + "] " + service + " 服务被解除封禁"); - } - - /** - * 每次创建Session时触发 - */ - @Override - public void doCreateSession(String id) { - println("Session[" + id + "]创建成功"); - } - - /** - * 每次注销Session时触发 - */ - @Override - public void doLogoutSession(String id) { - println("Session[" + id + "]注销成功"); - } - - /** - * 每次Token续期时触发 - */ - @Override - public void doRenewTimeout(String tokenValue, Object loginId, long timeout) { - println("帐号[" + loginId + "],Token=" + tokenValue + " 续期timeout成功!"); - } - - /** - * 日志输出的前缀 - */ - public static final String LOG_PREFIX = "SaLog -->: "; - - /** - * 打印指定字符串 - * @param str 字符串 - */ - public void println(String str) { - if(SaManager.getConfig().getIsLog()) { - System.out.println(LOG_PREFIX + str); - } - } - -} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java new file mode 100644 index 00000000..75afa853 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java @@ -0,0 +1,103 @@ +package cn.dev33.satoken.listener; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.stp.SaLoginModel; + +/** + * Sa-Token 侦听器实现:Log 打印 + * + * @author kong + * @since 2022-11-2 + */ +public class SaTokenListenerForLog implements SaTokenListener { + + /** + * 每次登录时触发 + */ + @Override + public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { + SaManager.getLogInput().doLogin(loginType, loginId, tokenValue, loginModel); + } + + /** + * 每次注销时触发 + */ + @Override + public void doLogout(String loginType, Object loginId, String tokenValue) { + SaManager.getLogInput().doLogout(loginType, loginId, tokenValue); + } + + /** + * 每次被踢下线时触发 + */ + @Override + public void doKickout(String loginType, Object loginId, String tokenValue) { + SaManager.getLogInput().doKickout(loginType, loginId, tokenValue); + } + + /** + * 每次被顶下线时触发 + */ + @Override + public void doReplaced(String loginType, Object loginId, String tokenValue) { + SaManager.getLogInput().doReplaced(loginType, loginId, tokenValue); + } + + /** + * 每次被封禁时触发 + */ + @Override + public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) { + SaManager.getLogInput().doDisable(loginType, loginId, service, level, disableTime); + } + + /** + * 每次被解封时触发 + */ + @Override + public void doUntieDisable(String loginType, Object loginId, String service) { + SaManager.getLogInput().doUntieDisable(loginType, loginId, service); + } + + /** + * 每次打开二级认证时触发 + */ + @Override + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + SaManager.getLogInput().doOpenSafe(loginType, tokenValue, service, safeTime); + } + + /** + * 每次关闭二级认证时触发 + */ + @Override + public void doCloseSafe(String loginType, String tokenValue, String service) { + SaManager.getLogInput().doCloseSafe(loginType, tokenValue, service); + } + + /** + * 每次创建Session时触发 + */ + @Override + public void doCreateSession(String id) { + SaManager.getLogInput().doCreateSession(id); + } + + /** + * 每次注销Session时触发 + */ + @Override + public void doLogoutSession(String id) { + SaManager.getLogInput().doLogoutSession(id); + } + + /** + * 每次Token续期时触发 + */ + @Override + public void doRenewTimeout(String tokenValue, Object loginId, long timeout) { + SaManager.getLogInput().doRenewTimeout(tokenValue, loginId, timeout); + } + + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForSimple.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForSimple.java index 78eba257..21cb87b4 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForSimple.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForSimple.java @@ -40,6 +40,16 @@ public class SaTokenListenerForSimple implements SaTokenListener { @Override public void doUntieDisable(String loginType, Object loginId, String service) { + } + + @Override + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + + } + + @Override + public void doCloseSafe(String loginType, String tokenValue, String service) { + } @Override @@ -57,4 +67,5 @@ public class SaTokenListenerForSimple implements SaTokenListener { } + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/log/SaLog.java b/sa-token-core/src/main/java/cn/dev33/satoken/log/SaLog.java new file mode 100644 index 00000000..769bcef8 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/log/SaLog.java @@ -0,0 +1,53 @@ +package cn.dev33.satoken.log; + +/** + * Sa-Token 日志输出接口 + * + * @author kong + * @since 2022-11-1 + */ +public interface SaLog { + + /** + * 输出 trace 日志 + * @param str 日志内容 + * @param args 参数列表 + */ + public void trace(String str, Object ...args); + + /** + * 输出 debug 日志 + * @param str 日志内容 + * @param args 参数列表 + */ + public void debug(String str, Object ...args); + + /** + * 输出 info 日志 + * @param str 日志内容 + * @param args 参数列表 + */ + public void info(String str, Object ...args); + + /** + * 输出 warn 日志 + * @param str 日志内容 + * @param args 参数列表 + */ + public void warn(String str, Object ...args); + + /** + * 输出 error 日志 + * @param str 日志内容 + * @param args 参数列表 + */ + public void error(String str, Object ...args); + + /** + * 输出 fatal 日志 + * @param str 日志内容 + * @param args 参数列表 + */ + public void fatal(String str, Object ...args); + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/log/SaLogForConsole.java b/sa-token-core/src/main/java/cn/dev33/satoken/log/SaLogForConsole.java new file mode 100644 index 00000000..837a92e9 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/log/SaLogForConsole.java @@ -0,0 +1,73 @@ +package cn.dev33.satoken.log; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.util.StrFormatter; + +/** + * Sa-Token 日志实现类 [控制台打印] + * + * @author kong + * @since 2022-11-1 + */ +public class SaLogForConsole implements SaLog { + + /** + * 日志输出的前缀 + */ + public static String LOG_PREFIX = "SaLog -->: "; + + /** + * 日志等级 + */ + public static final int trace = 1; + public static final int debug = 2; + public static final int info = 3; + public static final int warn = 4; + public static final int error = 5; + public static final int fatal = 6; + + @Override + public void trace(String str, Object... args) { + println(trace, str, args); + } + + @Override + public void debug(String str, Object... args) { + println(debug, str, args); + } + + @Override + public void info(String str, Object... args) { + println(info, str, args); + } + + @Override + public void warn(String str, Object... args) { + println(warn, str, args); + } + + @Override + public void error(String str, Object... args) { + println(error, str, args); + } + + @Override + public void fatal(String str, Object... args) { + println(fatal, str, args); + } + + /** + * 打印日志到控制台 + * @param level 日志等级 + * @param str 字符串 + * @param args 参数列表 + */ + public void println(int level, String str, Object... args) { + SaTokenConfig config = SaManager.getConfig(); + if(config.getIsLog() && level >= config.getLogLevelInt()) { + System.out.println(LOG_PREFIX + StrFormatter.format(str, args)); + } + } + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/log/input/SaLogInput.java b/sa-token-core/src/main/java/cn/dev33/satoken/log/input/SaLogInput.java new file mode 100644 index 00000000..87acded0 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/log/input/SaLogInput.java @@ -0,0 +1,160 @@ +package cn.dev33.satoken.log.input; + +import static cn.dev33.satoken.SaManager.log; + +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.util.SaFoxUtil; + +/** + * Sa-Token 日志接受器 + * + * @author kong + * @since 2022-11-1 + */ +public class SaLogInput { + + /** + * 账号登录 + * @param loginType 账号类别 + * @param loginId 账号id + * @param tokenValue 本次登录产生的 token 值 + * @param loginModel 登录参数 + */ + public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) { + log.info("账号 {} 登录成功 (loginType={}), 会话凭证Token={}", loginId, loginType, tokenValue); + } + + /** + * 每次注销时触发 + * @param loginType 账号类别 + * @param loginId 账号id + * @param tokenValue token值 + */ + public void doLogout(String loginType, Object loginId, String tokenValue) { + log.info("账号 {} 注销登录 (loginType={}), 会话凭证Token={}", loginId, loginType, tokenValue); + } + + /** + * 每次被踢下线时触发 + * @param loginType 账号类别 + * @param loginId 账号id + * @param tokenValue token值 + */ + public void doKickout(String loginType, Object loginId, String tokenValue) { + log.info("账号 {} 被踢下线 (loginType={}), 会话凭证Token={}", loginId, loginType, tokenValue); + } + + /** + * 每次被顶下线时触发 + * @param loginType 账号类别 + * @param loginId 账号id + * @param tokenValue token值 + */ + public void doReplaced(String loginType, Object loginId, String tokenValue) { + log.info("账号 {} 被顶下线 (loginType={}), 会话凭证Token={}", loginId, loginType, tokenValue); + } + + /** + * 每次被封禁时触发 + * @param loginType 账号类别 + * @param loginId 账号id + * @param service 指定服务 + * @param level 封禁等级 + * @param disableTime 封禁时长,单位: 秒 + */ + public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) { + log.info("账号 {} [{}服务] 被封禁 (loginType={}), 封禁等级={}, 解封时间为 {}", loginId, loginType, service, level, SaFoxUtil.formatAfterDate(disableTime * 1000)); + } + + /** + * 每次被解封时触发 + * @param loginType 账号类别 + * @param loginId 账号id + * @param service 指定服务 + */ + public void doUntieDisable(String loginType, Object loginId, String service) { + log.info("账号 {} [{}服务] 解封成功 (loginType={})", loginId, service, loginType); + } + + /** + * 每次打开二级认证时触发 + * @param loginType 账号类别 + * @param tokenValue token值 + * @param service 指定服务 + * @param safeTime 认证时间,单位:秒 + */ + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + log.info("Token 二级认证成功, 业务标识={}, 有效期={}秒, Token值={}", service, safeTime, tokenValue); + } + + /** + * 每次关闭二级认证时触发 + * @param loginType 账号类别 + * @param tokenValue token值 + * @param service 指定服务 + */ + public void doCloseSafe(String loginType, String tokenValue, String service) { + log.info("Token 二级认证关闭, 业务标识={}, Token值={}", service, tokenValue); + } + + /** + * 每次创建Session时触发 + * @param id SessionId + */ + public void doCreateSession(String id) { + log.info("SaSession [{}] 创建成功", id); + } + + /** + * 每次注销Session时触发 + * @param id SessionId + */ + public void doLogoutSession(String id) { + log.info("SaSession [{}] 注销成功", id); + } + + /** + * 每次Token续期时触发 + * + * @param tokenValue token 值 + * @param loginId 账号id + * @param timeout 续期时间 + */ + public void doRenewTimeout(String tokenValue, Object loginId, long timeout) { + log.info("Token 续期成功, {} 秒后到期, 帐号={}, Token值={} ", timeout, loginId, tokenValue); + } + + + /** + * 全局组件载入 + * @param comtName 组件名称 + * @param comtObj 组件对象 + */ + public void registerComponent(String comtName, Object comtObj) { + String canonicalName = comtObj == null ? null : comtObj.getClass().getCanonicalName(); + log.info("全局组件 {} 载入成功: {}", comtName, canonicalName); + } + + /** + * StpLogic 对象替换 + * @param stpLogic / + */ + public void replaceStpLogic(StpLogic stpLogic) { + if(stpLogic != null) { + log.info("会话组件 StpLogic(type={}) 重置成功: {}", stpLogic.getLoginType(), stpLogic.getClass()); + } + } + + /** + * 载入全局配置 + * @param stpLogic / + */ + public void registerConfig(SaTokenConfig config) { + if(config != null) { + log.info("全局配置 {} ", config); + } + } + +} 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 f2687bb6..e3f07f47 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 @@ -375,7 +375,7 @@ public class StpLogic { // 写入 [token-last-activity] setLastActivityToNow(tokenValue); - // $$ 发布事件:账号xxx 登录成功 + // $$ 发布事件:账号 xxx 登录成功 SaTokenEventCenter.doLogin(loginType, id, tokenValue, loginModel); // 检查此账号会话数量是否超出最大值 @@ -551,7 +551,7 @@ public class StpLogic { // $$ 发布事件:某某Token注销下线了 SaTokenEventCenter.doLogout(loginType, loginId, tokenValue); - + // 4. 清理User-Session上的token签名 & 尝试注销User-Session SaSession session = getSessionByLoginId(loginId, false); if(session != null) { @@ -2018,8 +2018,13 @@ public class StpLogic { public void openSafe(String service, long safeTime) { // 开启二级认证前必须处于登录状态 checkLogin(); + // 写入key - getSaTokenDao().set(splicingKeySafe(getTokenValueNotNull(), service), SaTokenConsts.SAFE_AUTH_SAVE_VALUE, safeTime); + String tokenValue = getTokenValueNotNull(); + getSaTokenDao().set(splicingKeySafe(tokenValue, service), SaTokenConsts.SAFE_AUTH_SAVE_VALUE, safeTime); + + // $$ 发布事件 + SaTokenEventCenter.doOpenSafe(loginType, tokenValue, service, safeTime); } /** @@ -2118,6 +2123,9 @@ public class StpLogic { // 删除 key getSaTokenDao().delete(splicingKeySafe(tokenValue, service)); + + // $$ 发布事件 + SaTokenEventCenter.doCloseSafe(loginType, tokenValue, service); } 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 d787b6d4..2008ce0a 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 @@ -36,6 +36,7 @@ public class StpUtil { * 重置 StpLogic 对象 *
1、更改此账户的 StpLogic 对象 *
2、put 到全局 StpLogic 集合中 + *
3、发送日志 * * @param newStpLogic / */ @@ -46,6 +47,9 @@ public class StpUtil { // 添加到全局 StpLogic 集合中 // 以便可以通过 SaManager.getStpLogic(type) 的方式来全局获取到这个 StpLogic SaManager.putStpLogic(newStpLogic); + + // ## 发送日志 + SaManager.getLogInput().replaceStpLogic(stpLogic); } /** diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java index a9bfe20f..0d4e8e42 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java @@ -4,9 +4,12 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -84,7 +87,16 @@ public class SaFoxUtil { public static boolean isNotEmpty(Object str) { return isEmpty(str) == false; } - + + /** + * 指定数组是否为null或者空数组 + * @param array / + * @return / + */ + public static boolean isEmpty(T[] array) { + return array == null || array.length == 0; + } + /** * 比较两个对象是否相等 * @param a 第一个对象 @@ -121,7 +133,18 @@ public class SaFoxUtil { public static String formatDate(ZonedDateTime zonedDateTime) { return zonedDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } - + + /** + * 指定毫秒后的时间(格式化 :yyyy-MM-dd HH:mm:ss) + * @param ms 指定毫秒后 + * @return 格式化后的时间 + */ + public static String formatAfterDate(long ms) { + Instant instant = Instant.ofEpochMilli(System.currentTimeMillis() + ms); + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()); + return formatDate(zonedDateTime); + } + /** * 从集合里查询数据 * @@ -552,7 +575,31 @@ public class SaFoxUtil { return list; } + public static List logLevelList = Arrays.asList("", "trace", "debug", "info", "warn", "error", "fatal"); - + /** + * 将日志等级从 String 格式转化为 int 格式 + * @param level / + * @return / + */ + public static int translateLogLevelToInt(String level) { + int levelInt = logLevelList.indexOf(level); + if(levelInt <= 0 || levelInt >= logLevelList.size()) { + levelInt = 1; + } + return levelInt; + } + + /** + * 将日志等级从 String 格式转化为 int 格式 + * @param level / + * @return / + */ + public static String translateLogLevelToString(int level) { + if(level <= 0 || level >= logLevelList.size()) { + level = 1; + } + return logLevelList.get(level); + } } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/StrFormatter.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/StrFormatter.java new file mode 100644 index 00000000..b02a4d8b --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/StrFormatter.java @@ -0,0 +1,103 @@ +package cn.dev33.satoken.util; + +/** + * 字符串格式化工具 + *

+ * 本工具类 copy 自 Hutool: + * https://github.com/dromara/hutool/blob/v5-master/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java + *

+ * + * @author Looly + */ +public class StrFormatter { + + /** + * 占位符 + */ + public static String EMPTY_JSON = "{}"; + + public static char C_BACKSLASH = '\\'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") =》 this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(String strPattern, Object... argArray) { + return formatWith(strPattern, EMPTY_JSON, argArray); + } + + /** + * 格式化字符串
+ * 此方法只是简单将指定占位符 按照顺序替换为参数
+ * 如果想输出占位符使用 \\转义即可,如果想输出占位符之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "{}", "a", "b") =》 this is a for b
+ * 转义{}: format("this is \\{} for {}", "{}", "a", "b") =》 this is {} for a
+ * 转义\: format("this is \\\\{} for {}", "{}", "a", "b") =》 this is \a for b
+ * + * @param strPattern 字符串模板 + * @param placeHolder 占位符,例如{} + * @param argArray 参数列表 + * @return 结果 + * @since 5.7.14 + */ + public static String formatWith(String strPattern, String placeHolder, Object... argArray) { + if (SaFoxUtil.isEmpty(strPattern) || SaFoxUtil.isEmpty(placeHolder) || SaFoxUtil.isEmpty(argArray)) { + return strPattern; + } + final int strPatternLength = strPattern.length(); + final int placeHolderLength = placeHolder.length(); + + // 初始化定义好的长度以获得更好的性能 + final StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0;// 记录已经处理到的位置 + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) { + delimIndex = strPattern.indexOf(placeHolder, handledPosition); + if (delimIndex == -1) {// 剩余部分无占位符 + if (handledPosition == 0) { // 不带占位符的模板直接返回 + return strPattern; + } + // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + + // 转义符 + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) {// 转义符 + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) {// 双转义符 + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(String.valueOf(argArray[argIndex])); + handledPosition = delimIndex + placeHolderLength; + } else { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(placeHolder.charAt(0)); + handledPosition = delimIndex + 1; + } + } else {// 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(String.valueOf(argArray[argIndex])); + handledPosition = delimIndex + placeHolderLength; + } + } + + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPatternLength); + + return sbuf.toString(); + } + +} diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/DisableController.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/DisableController.java index 5006a0c8..cd017e9a 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/DisableController.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/DisableController.java @@ -22,6 +22,8 @@ public class DisableController { 2、注销登录 ---- http://localhost:8081/disable/logout 3、禁用账号 ---- http://localhost:8081/disable/disable?userId=10001 4、再次访问登录接口,登录失败 ---- http://localhost:8081/disable/login?userId=10001 + 5、解封账号 ---- http://localhost:8081/disable/untieDisable?userId=10001 + 6、再次访问登录接口,登录成功 ---- http://localhost:8081/disable/login?userId=10001 */ // 会话登录接口 ---- http://localhost:8081/disable/login?userId=10001 @@ -41,7 +43,7 @@ public class DisableController { return SaResult.ok("账号退出成功"); } - // 封禁指定账号 ---- http://localhost:8081/disable/disable + // 封禁指定账号 ---- http://localhost:8081/disable/disable?userId=10001 @RequestMapping("disable") public SaResult disable(long userId) { /* @@ -53,4 +55,11 @@ public class DisableController { return SaResult.ok("账号 " + userId + " 封禁成功"); } + // 解封指定账号 ---- http://localhost:8081/disable/untieDisable?userId=10001 + @RequestMapping("untieDisable") + public SaResult untieDisable(long userId) { + StpUtil.untieDisable(userId); + return SaResult.ok("账号 " + userId + " 解封成功"); + } + } diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java index e8bad9b0..34caa912 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/cases/up/SafeAuthController.java @@ -61,6 +61,13 @@ public class SafeAuthController { return SaResult.error("二级认证失败"); } + // 手动关闭二级认证 ---- http://localhost:8081/safe/closeSafe + @RequestMapping("closeSafe") + public SaResult closeSafe() { + StpUtil.closeSafe(); + return SaResult.ok(); + } + // ------------------ 指定业务类型进行二级认证 diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/MySaTokenListener.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/MySaTokenListener.java index 3dd4f66f..dd4bd66d 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/MySaTokenListener.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/MySaTokenListener.java @@ -47,7 +47,19 @@ public class MySaTokenListener implements SaTokenListener { public void doUntieDisable(String loginType, Object loginId, String service) { System.out.println("---------- 自定义侦听器实现 doUntieDisable"); } + + /** 每次打开二级认证时触发 */ + @Override + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + System.out.println("---------- 自定义侦听器实现 doOpenSafe"); + } + /** 每次关闭二级认证时触发 */ + @Override + public void doCloseSafe(String loginType, String tokenValue, String service) { + System.out.println("---------- 自定义侦听器实现 doCloseSafe"); + } + /** 每次创建Session时触发 */ @Override public void doCreateSession(String id) { @@ -65,5 +77,6 @@ public class MySaTokenListener implements SaTokenListener { public void doRenewTimeout(String tokenValue, Object loginId, long timeout) { System.out.println("---------- 自定义侦听器实现 doRenewTimeout"); } - + + } diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java index 26da4900..9ef97a41 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -31,6 +31,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { // 注册 Sa-Token 拦截器打开注解鉴权功能 registry.addInterceptor(new SaInterceptor(handle -> { + // SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue()); // 指定一条 match 规则 SaRouter @@ -75,6 +76,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { // 认证函数: 每次请求执行 .setAuth(obj -> { // System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath()); + // SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue()); // 权限校验 -- 不同模块认证不同权限 // 这里你可以写和拦截器鉴权同样的代码,不同点在于: diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java index 752b0715..3dcb0d32 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/StpUserUtil.java @@ -42,6 +42,7 @@ public class StpUserUtil { * 重置 StpLogic 对象 *
1、更改此账户的 StpLogic 对象 *
2、put 到全局 StpLogic 集合中 + *
3、发送日志 * * @param newStpLogic / */ @@ -52,6 +53,9 @@ public class StpUserUtil { // 添加到全局 StpLogic 集合中 // 以便可以通过 SaManager.getStpLogic(type) 的方式来全局获取到这个 StpLogic SaManager.putStpLogic(newStpLogic); + + // ## 发送日志 + SaManager.getLogInput().replaceStpLogic(stpLogic); } /** diff --git a/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java b/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java index 752b0715..3dcb0d32 100644 --- a/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java +++ b/sa-token-demo/sa-token-demo-test/src/main/java/com/pj/satoken/StpUserUtil.java @@ -42,6 +42,7 @@ public class StpUserUtil { * 重置 StpLogic 对象 *
1、更改此账户的 StpLogic 对象 *
2、put 到全局 StpLogic 集合中 + *
3、发送日志 * * @param newStpLogic / */ @@ -52,6 +53,9 @@ public class StpUserUtil { // 添加到全局 StpLogic 集合中 // 以便可以通过 SaManager.getStpLogic(type) 的方式来全局获取到这个 StpLogic SaManager.putStpLogic(newStpLogic); + + // ## 发送日志 + SaManager.getLogInput().replaceStpLogic(stpLogic); } /** diff --git a/sa-token-doc/up/global-listener.md b/sa-token-doc/up/global-listener.md index 7564c9b0..639d4a2f 100644 --- a/sa-token-doc/up/global-listener.md +++ b/sa-token-doc/up/global-listener.md @@ -67,6 +67,18 @@ public class MySaTokenListener implements SaTokenListener { System.out.println("---------- 自定义侦听器实现 doUntieDisable"); } + /** 每次二级认证时触发 */ + @Override + public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) { + System.out.println("---------- 自定义侦听器实现 doOpenSafe"); + } + + /** 每次退出二级认证时触发 */ + @Override + public void doCloseSafe(String loginType, String tokenValue, String service) { + System.out.println("---------- 自定义侦听器实现 doCloseSafe"); + } + /** 每次创建Session时触发 */ @Override public void doCreateSession(String id) {