From 39f8c0bd3592797f3fec0df297787fad108a4f3c Mon Sep 17 00:00:00 2001 From: trydofor Date: Thu, 22 Jun 2023 11:45:03 +0800 Subject: [PATCH 01/48] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20dep.=20unif?= =?UTF-8?q?y=20add-opens=20=F0=9F=90=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- observe/scripts/wings-starter.sh | 27 +++++----- observe/scripts/wings-testing.sh | 3 ++ pom.xml | 54 ++++++++++--------- .../JooqJournalDiffConverterTest.java | 2 +- 5 files changed, 47 insertions(+), 41 deletions(-) diff --git a/observe/docs b/observe/docs index 701714f44..398149827 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 701714f44eefa48b976313b1720775c84703dd42 +Subproject commit 3981498272a9630c6d1181c8a5e87f22de316d3c diff --git a/observe/scripts/wings-starter.sh b/observe/scripts/wings-starter.sh index 5a5b88622..ee39b5a83 100755 --- a/observe/scripts/wings-starter.sh +++ b/observe/scripts/wings-starter.sh @@ -1,5 +1,5 @@ #!/bin/bash -THIS_VERSION=2023-06-16 +THIS_VERSION=2023-06-22 ################ modify the following params ################ WORK_DIR='' # 脚本生成文件,日志的目录,默认空(脚本位置) TAIL_LOG='log' # 默认tail的日志,"log|out|new|ask" @@ -30,18 +30,19 @@ JDK8_ARG=' # 可延时求值 # shellcheck disable=SC2016 JDK9_ARG=' ---add-modules java.se ---add-exports java.base/jdk.internal.ref=ALL-UNNAMED ---add-opens java.base/java.lang=ALL-UNNAMED ---add-opens java.base/java.lang.invoke=ALL-UNNAMED ---add-opens java.base/java.util=ALL-UNNAMED ---add-opens java.base/java.io=ALL-UNNAMED ---add-opens java.base/java.nio=ALL-UNNAMED ---add-opens java.base/sun.nio.ch=ALL-UNNAMED ---add-opens java.management/sun.management=ALL-UNNAMED ---add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED ---add-opens java.base/sun.security.x509=ALL-UNNAMED ---add-opens jdk.unsupported/sun.misc=ALL-UNNAMED +--add-modules=java.se +--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED +--add-opens=java.base/java.io=ALL-UNNAMED +--add-opens=java.base/java.lang.invoke=ALL-UNNAMED +--add-opens=java.base/java.lang=ALL-UNNAMED +--add-opens=java.base/java.net=ALL-UNNAMED +--add-opens=java.base/java.nio=ALL-UNNAMED +--add-opens=java.base/java.util=ALL-UNNAMED +--add-opens=java.base/sun.nio.ch=ALL-UNNAMED +--add-opens=java.base/sun.security.x509=ALL-UNNAMED +--add-opens=java.management/sun.management=ALL-UNNAMED +--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED +--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED -Xlog:gc*=info:file=${BOOT_TKN}.gc:time,tid,tags:filecount=5,filesize=100m ' # 可延时求值 diff --git a/observe/scripts/wings-testing.sh b/observe/scripts/wings-testing.sh index 2e5aaa6dc..c73188c5b 100755 --- a/observe/scripts/wings-testing.sh +++ b/observe/scripts/wings-testing.sh @@ -66,6 +66,9 @@ for arg in "$@"; do fi done +echo -e "\033[32m ==== mvn version ==== \033[0m" +mvn --version || exit + ## install [[ "$*" =~ .*init.* ]] && mvn -U clean install -Dmaven.test.skip=true diff --git a/pom.xml b/pom.xml index e30c94fbe..615497240 100644 --- a/pom.xml +++ b/pom.xml @@ -38,12 +38,12 @@ false true - 2.19.1 + 2.20.0 24.0.1 1.5.5.Final - 31.1-jre - 2.12.0 + 32.0.1-jre + 2.13.0 2.4.5-SNAPSHOT 1.4.1-SNAPSHOT @@ -53,34 +53,35 @@ ${fastjson2.version} 5.5.0 2.14.2 - 1.73 + 1.74 1.16.5 - 2.8.7 + 2.8.8 2.1.0 3.0.4 - 6.19.0 + 6.23.0 2.9.0 1.3.0 3.0.1 {} - --add-modules java.se - --add-opens java.base/java.io=ALL-UNNAMED - --add-opens java.base/java.nio=ALL-UNNAMED - --add-opens java.base/java.lang=ALL-UNNAMED - --add-opens java.base/java.lang.invoke=ALL-UNNAMED - --add-opens java.base/java.util=ALL-UNNAMED - --add-opens java.base/sun.nio.ch=ALL-UNNAMED - --add-opens java.base/jdk.internal.ref=ALL-UNNAMED - --add-opens java.base/sun.security.x509=ALL-UNNAMED - --add-opens java.management/sun.management=ALL-UNNAMED - --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED - --add-opens jdk.unsupported/sun.misc=ALL-UNNAMED - --add-opens jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED - --add-opens jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + --add-modules=java.se + --add-opens=java.base/java.io=ALL-UNNAMED + --add-opens=java.base/java.lang.invoke=ALL-UNNAMED + --add-opens=java.base/java.lang=ALL-UNNAMED + --add-opens=java.base/java.net=ALL-UNNAMED + --add-opens=java.base/java.nio=ALL-UNNAMED + --add-opens=java.base/java.util=ALL-UNNAMED + --add-opens=java.base/jdk.internal.ref=ALL-UNNAMED + --add-opens=java.base/sun.nio.ch=ALL-UNNAMED + --add-opens=java.base/sun.security.x509=ALL-UNNAMED + --add-opens=java.management/sun.management=ALL-UNNAMED + --add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED + --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED @@ -530,22 +531,23 @@ ${java.version} ${java.version} + true - -parameters --add-modules=java.se --add-exports=java.base/java.io=ALL-UNNAMED - --add-exports=java.base/java.nio=ALL-UNNAMED - --add-exports=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/java.lang.invoke=ALL-UNNAMED + --add-exports=java.base/java.lang=ALL-UNNAMED + --add-exports=java.base/java.net=ALL-UNNAMED + --add-exports=java.base/java.nio=ALL-UNNAMED --add-exports=java.base/java.util=ALL-UNNAMED - --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=java.base/jdk.internal.ref=ALL-UNNAMED + --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.management/sun.management=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=jdk.management/com.sun.management.internal=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED - --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED - --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED diff --git a/wings/warlock/src/test/java/pro/fessional/wings/warlock/database/jooq/converter/JooqJournalDiffConverterTest.java b/wings/warlock/src/test/java/pro/fessional/wings/warlock/database/jooq/converter/JooqJournalDiffConverterTest.java index e168d5f58..dd9ab371d 100644 --- a/wings/warlock/src/test/java/pro/fessional/wings/warlock/database/jooq/converter/JooqJournalDiffConverterTest.java +++ b/wings/warlock/src/test/java/pro/fessional/wings/warlock/database/jooq/converter/JooqJournalDiffConverterTest.java @@ -27,7 +27,7 @@ void to() { final String s = c.to(d0); log.warn("JournalDiff JSON ={}", s); // 类型丢失 - d0.setValue1(List.of("10086", "trydofor", new BigDecimal("3.14"), "2022-10-24 12:34:56")); + d0.setValue1(List.of("10086", "trydofor", "3.14", "2022-10-24 12:34:56")); final JournalDiff d1 = c.from(s); Assertions.assertEquals(d0, d1); } From f7bedde5e14394bfc0931a43a41af25cb2139ff1 Mon Sep 17 00:00:00 2001 From: trydofor Date: Sun, 25 Jun 2023 22:13:53 +0800 Subject: [PATCH 02/48] =?UTF-8?q?=F0=9F=93=8C=20boot=20307;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 615497240..943a376de 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.0.6 + 3.0.7 @@ -21,7 +21,7 @@ - 3.0.6 + 3.0.7 300-SNAPSHOT From 36cd4b9518e7c64e3f838015d095eab10d217ad0 Mon Sep 17 00:00:00 2001 From: trydofor Date: Mon, 26 Jun 2023 09:59:32 +0800 Subject: [PATCH 03/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20i18n=20zh=5FCN=20to?= =?UTF-8?q?=20zh;=20add=20authn=20error;=20more=20test=20and=20docs=20#103?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- .../wings-i18n/base-validator.properties | 1 - .../wings-i18n/base-validator.properties | 1 - ...roperties => base-validator_zh.properties} | 0 .../silencer/i18n/MessageModuleTest.java | 67 ++++++++++++++ .../wings/silencer/i18n/MessagePrintTest.java | 36 -------- .../spring/boot/WingsSilencerI18nTest.java | 42 --------- ...roperties => base-validator_zh.properties} | 0 .../base-validator_zh_TW.properties | 1 + .../slardar/enums/errcode/AuthnErrorEnum.java | 36 ++++++++ .../security/bind/WingsBindAuthProvider.java | 18 ++-- .../wings-i18n/authn-error.properties | 7 ++ .../wings-i18n/authn-error_zh.properties | 6 ++ .../controller/TestLocaleController.java | 31 +++++++ .../webmvc/I18nLocaleResolverTest.java | 92 +++++++++++++++++++ 15 files changed, 249 insertions(+), 91 deletions(-) delete mode 100644 wings/faceless-jooq/src/test/resources/wings-i18n/base-validator.properties delete mode 100644 wings/faceless/src/test/resources/wings-i18n/base-validator.properties rename wings/silencer-test/src/main/resources/wings-i18n/{base-validator_zh_CN.properties => base-validator_zh.properties} (100%) create mode 100644 wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessageModuleTest.java delete mode 100644 wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessagePrintTest.java delete mode 100644 wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerI18nTest.java rename wings/silencer/src/test/resources/wings-i18n/{base-validator_zh_CN.properties => base-validator_zh.properties} (100%) create mode 100644 wings/silencer/src/test/resources/wings-i18n/base-validator_zh_TW.properties create mode 100644 wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java create mode 100644 wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties create mode 100644 wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties create mode 100644 wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestLocaleController.java create mode 100644 wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/I18nLocaleResolverTest.java diff --git a/observe/docs b/observe/docs index 398149827..5d553abd2 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 3981498272a9630c6d1181c8a5e87f22de316d3c +Subproject commit 5d553abd264ce577a1695bcf5ba9b417acadcdcf diff --git a/wings/faceless-jooq/src/test/resources/wings-i18n/base-validator.properties b/wings/faceless-jooq/src/test/resources/wings-i18n/base-validator.properties deleted file mode 100644 index 1fc3e7d16..000000000 --- a/wings/faceless-jooq/src/test/resources/wings-i18n/base-validator.properties +++ /dev/null @@ -1 +0,0 @@ -base.not-empty={0} can not be empty test \ No newline at end of file diff --git a/wings/faceless/src/test/resources/wings-i18n/base-validator.properties b/wings/faceless/src/test/resources/wings-i18n/base-validator.properties deleted file mode 100644 index 1fc3e7d16..000000000 --- a/wings/faceless/src/test/resources/wings-i18n/base-validator.properties +++ /dev/null @@ -1 +0,0 @@ -base.not-empty={0} can not be empty test \ No newline at end of file diff --git a/wings/silencer-test/src/main/resources/wings-i18n/base-validator_zh_CN.properties b/wings/silencer-test/src/main/resources/wings-i18n/base-validator_zh.properties similarity index 100% rename from wings/silencer-test/src/main/resources/wings-i18n/base-validator_zh_CN.properties rename to wings/silencer-test/src/main/resources/wings-i18n/base-validator_zh.properties diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessageModuleTest.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessageModuleTest.java new file mode 100644 index 000000000..cef0a3db7 --- /dev/null +++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessageModuleTest.java @@ -0,0 +1,67 @@ +package pro.fessional.wings.silencer.i18n; + +import lombok.Setter; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.MessageSource; +import pro.fessional.mirana.data.Arr; + +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author trydofor + * @since 2019-06-10 + */ +@SpringBootTest(properties = "wings.silencer.i18n.locale=en_US") +public class MessageModuleTest { + + @Setter(onMethod_ = {@Autowired}) + private MessageSource messageSource; + + @Setter(onMethod_ = {@Value("${wings.test.module}")}) + private String module; + + @Test + void testModule() { + assertEquals("沉默术士", module); + assertEquals(Locale.forLanguageTag("en-US"), Locale.getDefault()); + } + + @Test + void testMessageStandard() { + // use lang + String cn = messageSource.getMessage("base.not-empty", Arr.of("姓名"), Locale.CHINA); + // use default + String en = messageSource.getMessage("base.not-empty", Arr.of("name"), Locale.US); + // use lang and region + String tw = messageSource.getMessage("base.not-empty", Arr.of("姓名"), Locale.TAIWAN); + // use default + String jp = messageSource.getMessage("base.not-empty", Arr.of("name"), Locale.JAPAN); + + assertEquals("姓名 不能为空", cn); + assertEquals("name can not be empty", en); + assertEquals("姓名 不能為空", tw); + assertEquals("name can not be empty", jp); + } + + @Test + void testMessagePartial() { + // use lang + String zh = messageSource.getMessage("base.not-empty", Arr.of("姓名"), Locale.forLanguageTag("zh")); + // use default + String en = messageSource.getMessage("base.not-empty", Arr.of("name"), Locale.forLanguageTag("en")); + // use lang and region + String tw = messageSource.getMessage("base.not-empty", Arr.of("姓名"), Locale.forLanguageTag("zh-TW")); + // use default + String jp = messageSource.getMessage("base.not-empty", Arr.of("name"), Locale.forLanguageTag("jp")); + + assertEquals("姓名 不能为空", zh); + assertEquals("name can not be empty", en); + assertEquals("姓名 不能為空", tw); + assertEquals("name can not be empty", jp); + } +} diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessagePrintTest.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessagePrintTest.java deleted file mode 100644 index cd6ab9ee4..000000000 --- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/i18n/MessagePrintTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package pro.fessional.wings.silencer.i18n; - -import lombok.Setter; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.MessageSource; -import pro.fessional.mirana.data.Arr; - -import java.util.Locale; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @author trydofor - * @since 2019-06-10 - */ -@SpringBootTest -public class MessagePrintTest { - - @Setter(onMethod_ = {@Autowired}) - private MessageSource messageSource; - - @Setter(onMethod_ = {@Value("${wings.test.module}")}) - private String module; - - @Test - void print() { - String cn = messageSource.getMessage("base.not-empty", Arr.of("姓名"), Locale.CHINA); - String en = messageSource.getMessage("base.not-empty", Arr.of("name"), Locale.US); - assertEquals("姓名 不能为空", cn); - assertEquals("name can not be empty", en); - assertEquals("沉默术士", module); - } -} diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerI18nTest.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerI18nTest.java deleted file mode 100644 index a67d0d485..000000000 --- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerI18nTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package pro.fessional.wings.silencer.spring.boot; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.MessageSource; - -import java.util.Locale; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @author trydofor - * @since 2019-06-25 - */ - -@SpringBootTest -public class WingsSilencerI18nTest { - - private MessageSource messageSource; - private String module; - - @Autowired - public void setMessageSource(MessageSource messageSource) { - this.messageSource = messageSource; - } - - @Value("${wings.test.module}") - public void setModule(String module) { - this.module = module; - } - - @Test - public void cnEn() { - assertEquals("沉默术士", module); - String cn = messageSource.getMessage("base.not-empty", new Object[]{"姓名"}, Locale.CHINA); - String en = messageSource.getMessage("base.not-empty", new Object[]{"name"}, Locale.US); - assertEquals("姓名 不能为空", cn); - assertEquals("name can not be empty", en); - } -} diff --git a/wings/silencer/src/test/resources/wings-i18n/base-validator_zh_CN.properties b/wings/silencer/src/test/resources/wings-i18n/base-validator_zh.properties similarity index 100% rename from wings/silencer/src/test/resources/wings-i18n/base-validator_zh_CN.properties rename to wings/silencer/src/test/resources/wings-i18n/base-validator_zh.properties diff --git a/wings/silencer/src/test/resources/wings-i18n/base-validator_zh_TW.properties b/wings/silencer/src/test/resources/wings-i18n/base-validator_zh_TW.properties new file mode 100644 index 000000000..9ce659e85 --- /dev/null +++ b/wings/silencer/src/test/resources/wings-i18n/base-validator_zh_TW.properties @@ -0,0 +1 @@ +base.not-empty={0} 不能為空 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java new file mode 100644 index 000000000..f21dd786a --- /dev/null +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java @@ -0,0 +1,36 @@ +package pro.fessional.wings.slardar.enums.errcode; + +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; +import pro.fessional.mirana.data.CodeEnum; + +/** + * @author trydofor + * @see AbstractUserDetailsAuthenticationProvider + * @since 2021-03-25 + */ +@RequiredArgsConstructor +public enum AuthnErrorEnum implements CodeEnum { + + OnlySupports("AbstractUserDetailsAuthenticationProvider.onlySupports", "仅支持账号密码方式登录"), + BadCredentials("AbstractUserDetailsAuthenticationProvider.badCredentials", "密码错误"), + Locked("AbstractUserDetailsAuthenticationProvider.locked", "账号已锁定"), + Disabled("AbstractUserDetailsAuthenticationProvider.disabled", "账号已禁用"), + Expired("AbstractUserDetailsAuthenticationProvider.expired", "账号已过期"), + CredentialsExpired("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "密码已过期"), + ; + + private final String code; + private final String hint; + + @Override + public @NotNull String getCode() { + return code; + } + + @Override + public @NotNull String getHint() { + return hint; + } +} diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthProvider.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthProvider.java index 3fce98b59..67006e252 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthProvider.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthProvider.java @@ -17,6 +17,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.util.Assert; import pro.fessional.mirana.bits.MdHelp; +import pro.fessional.mirana.code.RandCode; +import pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum; import pro.fessional.wings.slardar.security.PasssaltEncoder; import pro.fessional.wings.slardar.security.PasswordHelper; import pro.fessional.wings.slardar.security.WingsAuthCheckService; @@ -38,9 +40,6 @@ public class WingsBindAuthProvider extends AbstractUserDetailsAuthenticationProv private final static Log log = LogFactory.getLog(WingsBindAuthProvider.class); - private static final String BadCredentialsCode = "AbstractUserDetailsAuthenticationProvider.badCredentials"; - private static final String USER_NOT_FOUND_PASSWORD = "TimingAttackProtectionUserNotFoundPassword"; - private volatile String userNotFoundEncodedPassword = null; private boolean onlyWingsBindAuthnToken = false; @@ -68,7 +67,7 @@ protected void additionalAuthenticationChecks(UserDetails userDetails, UsernameP if (wingsAuthCheckService != null && isWingsUserDetails && authentication instanceof WingsBindAuthToken) { if (!wingsAuthCheckService.check((WingsUserDetails) userDetails, (WingsBindAuthToken) authentication)) { log.debug("Failed to post check userDetails and authentication"); - throw new BadCredentialsException(messages.getMessage(BadCredentialsCode, "Bad credentials")); + throw new BadCredentialsException(messages.getMessage(AuthnErrorEnum.BadCredentials.getCode(), "Bad credentials")); } } } @@ -151,30 +150,30 @@ protected UserDetails buildUserDetails(String username, WingsUserDetailsService protected void checkPassword(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) { if (authentication.getCredentials() == null) { log.debug("Failed to authenticate since no credentials provided"); - throw new BadCredentialsException(messages.getMessage(BadCredentialsCode, "Bad credentials")); + throw new BadCredentialsException(messages.getMessage(AuthnErrorEnum.BadCredentials.getCode(), "Bad credentials")); } String presentedPassword = presentPassword(userDetails, authentication); if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { log.debug("Failed to authenticate since password does not match stored value"); - throw new BadCredentialsException(messages.getMessage(BadCredentialsCode, "Bad credentials")); + throw new BadCredentialsException(messages.getMessage(AuthnErrorEnum.BadCredentials.getCode(), "Bad credentials")); } } protected String presentPassword(UserDetails details, Authentication auth) { String presentedPassword = auth.getCredentials().toString(); // 加盐处理 - if (passsaltEncoder != null && details instanceof WingsUserDetails) { + if (passsaltEncoder != null && details instanceof WingsUserDetails dtl) { PasswordHelper helper = new PasswordHelper(passwordEncoder, passsaltEncoder); - presentedPassword = helper.salt(presentedPassword, ((WingsUserDetails) details).getPasssalt()); + presentedPassword = helper.salt(presentedPassword, dtl.getPasssalt()); } return presentedPassword; } protected void prepareTimingAttackProtection() { if (userNotFoundEncodedPassword == null) { - userNotFoundEncodedPassword = passwordEncoder.encode(USER_NOT_FOUND_PASSWORD); + userNotFoundEncodedPassword = passwordEncoder.encode(RandCode.strong(20)); } } @@ -187,7 +186,6 @@ protected void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken a // setter & getter - public PasssaltEncoder getPasssaltEncoder() { return this.passsaltEncoder; } diff --git a/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties new file mode 100644 index 000000000..dccfc1d14 --- /dev/null +++ b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties @@ -0,0 +1,7 @@ +# AbstractUserDetailsAuthenticationProvider +AbstractUserDetailsAuthenticationProvider.onlySupports=Only UsernamePasswordAuthenticationToken is supported +AbstractUserDetailsAuthenticationProvider.badCredentials=Bad credentials +AbstractUserDetailsAuthenticationProvider.locked=User account is locked +AbstractUserDetailsAuthenticationProvider.disabled=User is disabled +AbstractUserDetailsAuthenticationProvider.expired=User account has expired +AbstractUserDetailsAuthenticationProvider.credentialsExpired=User credentials have expired diff --git a/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties new file mode 100644 index 000000000..20956c3c6 --- /dev/null +++ b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties @@ -0,0 +1,6 @@ +AbstractUserDetailsAuthenticationProvider.onlySupports=仅支持账号密码方式登录 +AbstractUserDetailsAuthenticationProvider.badCredentials=密码错误 +AbstractUserDetailsAuthenticationProvider.locked=账号已锁定 +AbstractUserDetailsAuthenticationProvider.disabled=账号已禁用 +AbstractUserDetailsAuthenticationProvider.expired=账号已过期 +AbstractUserDetailsAuthenticationProvider.credentialsExpired=密码已过期 diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestLocaleController.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestLocaleController.java new file mode 100644 index 000000000..a32392d0f --- /dev/null +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestLocaleController.java @@ -0,0 +1,31 @@ +package pro.fessional.wings.slardar.controller; + +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import pro.fessional.wings.slardar.context.TerminalContext; +import pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum; +import pro.fessional.wings.slardar.servlet.resolver.WingsLocaleResolver; + +import java.util.Locale; + +/** + * @author trydofor + * @see WingsLocaleResolver + * @since 2021-02-01 + */ +@RestController +public class TestLocaleController { + + @Setter(onMethod_ = {@Autowired}) + private MessageSource messageSource; + + @GetMapping("/test/i18n-message.json") + public String login() { + final Locale lang = TerminalContext.currentLocale(); + final String msg = messageSource.getMessage(AuthnErrorEnum.BadCredentials.getCode(), null, lang); + return msg + "|" + lang.toLanguageTag(); + } +} diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/I18nLocaleResolverTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/I18nLocaleResolverTest.java new file mode 100644 index 000000000..33c6016b0 --- /dev/null +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/I18nLocaleResolverTest.java @@ -0,0 +1,92 @@ +package pro.fessional.wings.slardar.webmvc; + +import jakarta.servlet.http.Cookie; +import lombok.Setter; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import pro.fessional.wings.slardar.servlet.resolver.WingsLocaleResolver; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; + +/** + * @author trydofor + * @see WingsLocaleResolver + * @since 2023-06-25 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "wings.silencer.i18n.locale=en_US") +@AutoConfigureMockMvc +public class I18nLocaleResolverTest { + + @Setter(onMethod_ = {@Autowired}) + private MockMvc mockMvc; + + @Test + public void testHeader() throws Exception { + testHeader("en", "Bad credentials|en"); + testHeader("en_US", "Bad credentials|en-US"); + testHeader("zh_TW", "密码错误|zh-TW"); + testHeader("zh-TW", "密码错误|zh-TW"); + testHeader("zh", "密码错误|zh"); + testHeader("zh-CN", "密码错误|zh-CN"); + testHeader("zh_CN", "密码错误|zh-CN"); + } + + private void testHeader(String lang, String output) throws Exception { + final MockHttpServletRequestBuilder builder = get("/test/i18n-message.json") + .header("Accept-Language", lang) + .cookie(new Cookie("Wings-Locale", lang)) + .contentType(MediaType.APPLICATION_JSON); + mockMvc.perform(builder) + .andDo(print()) + .andExpect(content().string(output)); + } + + @Test + public void testCookie() throws Exception { + testCookie("en", "Bad credentials|en"); + testCookie("en_US", "Bad credentials|en-US"); + testCookie("zh_TW", "密码错误|zh-TW"); + testCookie("zh-TW", "密码错误|zh-TW"); + testCookie("zh", "密码错误|zh"); + testCookie("zh-CN", "密码错误|zh-CN"); + testCookie("zh_CN", "密码错误|zh-CN"); + } + + private void testCookie(String lang, String output) throws Exception { + final MockHttpServletRequestBuilder builder = get("/test/i18n-message.json") + .cookie(new Cookie("Wings-Locale", lang)) + .contentType(MediaType.APPLICATION_JSON); + mockMvc.perform(builder) + .andDo(print()) + .andExpect(content().string(output)); + } + + @Test + public void testParam() throws Exception { + testParam("en", "Bad credentials|en"); + testParam("en_US", "Bad credentials|en-US"); + testParam("zh_TW", "密码错误|zh-TW"); + testParam("zh-TW", "密码错误|zh-TW"); + testParam("zh", "密码错误|zh"); + testParam("zh-CN", "密码错误|zh-CN"); + testParam("zh_CN", "密码错误|zh-CN"); + } + + private void testParam(String lang, String output) throws Exception { + final MockHttpServletRequestBuilder builder = get("/test/i18n-message.json?locale=" + lang) + .header("Accept-Language", "ja") + .cookie(new Cookie("Wings-Locale", "ko")) + .contentType(MediaType.APPLICATION_JSON); + mockMvc.perform(builder) + .andDo(print()) + .andExpect(content().string(output)); + } +} From f38c1057b18bbc513e05a40beaca39163185db5f Mon Sep 17 00:00:00 2001 From: trydofor Date: Tue, 27 Jun 2023 09:12:28 +0800 Subject: [PATCH 04/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20init=20logger=20twea?= =?UTF-8?q?k=20early?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- observe/scripts/wings-testing.sh | 61 ++++++++++--------- .../spring/consts/OrderedSilencerConst.java | 1 - .../bean/SilencerTweakConfiguration.java | 58 ++++++++---------- .../wings/silencer/tweak/TweakLogger.java | 5 ++ .../WingsSilencerCurseApplication.java | 1 + .../spring/ApplicationEventLogger.java | 21 +++++++ .../spring/bean/SpringOrderConfiguration.java | 19 +++--- .../test/resources/META-INF/spring.factories | 1 + .../message/CombinableMessageSource.java | 23 ++++--- .../bean/SilencerRunnerConfiguration.java | 2 +- .../spring/boot/DeferredLogFactory.java | 4 +- .../spring/boot/WingsAutoConfigProcessor.java | 20 +++--- .../spring/boot/WingsSpringBeanScanner.java | 7 +-- .../service/watching/WatchingService.java | 4 +- 15 files changed, 129 insertions(+), 100 deletions(-) create mode 100644 wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/ApplicationEventLogger.java create mode 100644 wings/silencer-curse/src/test/resources/META-INF/spring.factories diff --git a/observe/docs b/observe/docs index 5d553abd2..9ff43746d 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 5d553abd264ce577a1695bcf5ba9b417acadcdcf +Subproject commit 9ff43746d85af20c4d8ba002ecedcbc62cb0072b diff --git a/observe/scripts/wings-testing.sh b/observe/scripts/wings-testing.sh index c73188c5b..944ad627d 100755 --- a/observe/scripts/wings-testing.sh +++ b/observe/scripts/wings-testing.sh @@ -24,41 +24,41 @@ cd $(dirname "$this_file") cd ../.. # to wings project dir module_auto="wings/silencer\ - ,wings/silencer-curse\ - ,wings/faceless\ - ,wings/faceless-awesome\ - ,wings/faceless-flywave\ - ,wings/faceless-jooq\ - ,wings/faceless-shard\ - ,wings/slardar\ - ,wings/slardar-hazel-session\ - ,wings/slardar-sprint\ - ,wings/slardar-webmvc\ - ,wings/warlock\ - ,wings/warlock-awesome\ - ,wings/warlock-bond\ - ,wings/warlock-shadow\ - ,radiant/tiny-mail\ - ,radiant/tiny-task" +,wings/silencer-curse\ +,wings/faceless\ +,wings/faceless-awesome\ +,wings/faceless-flywave\ +,wings/faceless-jooq\ +,wings/faceless-shard\ +,wings/slardar\ +,wings/slardar-hazel-session\ +,wings/slardar-sprint\ +,wings/slardar-webmvc\ +,wings/warlock\ +,wings/warlock-awesome\ +,wings/warlock-bond\ +,wings/warlock-shadow\ +,radiant/tiny-mail\ +,radiant/tiny-task" module_h2db="wings/faceless-awesome\ - ,wings/faceless-jooq\ - ,wings/slardar\ - ,wings/slardar-hazel-session\ - ,wings/slardar-sprint\ - ,wings/slardar-webmvc\ - ,wings/warlock\ - ,wings/warlock-awesome\ - ,wings/warlock-bond\ - ,wings/warlock-shadow" +,wings/faceless-jooq\ +,wings/slardar\ +,wings/slardar-hazel-session\ +,wings/slardar-sprint\ +,wings/slardar-webmvc\ +,wings/warlock\ +,wings/warlock-awesome\ +,wings/warlock-bond\ +,wings/warlock-shadow" ## ############## -echo "usage: $0 [init] [auto=*] [h2db=*]" +echo "usage: $0 [init] [auto=p1,...,pn] [h2db=p1,...,pn]" for arg in "$@"; do - # 使用等号分割参数,取得 arg 和 value + # split arguments then get arg and value IFS='=' read -r -a parts <<<"$arg" - # 检查参数名是否匹配 + # match arg type if [[ "${parts[0]}" == "auto" && "${parts[1]}" != "" ]]; then module_auto=${parts[1]} elif [[ "${parts[0]}" == "h2db" && "${parts[1]}" != "" ]]; then @@ -66,9 +66,14 @@ for arg in "$@"; do fi done +echo "auto=$module_auto" +echo "h2db=$module_h2db" echo -e "\033[32m ==== mvn version ==== \033[0m" mvn --version || exit +# shellcheck disable=SC2034 +MAVEN_OPTS="-Xmx2048m" + ## install [[ "$*" =~ .*init.* ]] && mvn -U clean install -Dmaven.test.skip=true diff --git a/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedSilencerConst.java b/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedSilencerConst.java index 58085995b..bdafbba83 100644 --- a/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedSilencerConst.java +++ b/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedSilencerConst.java @@ -13,5 +13,4 @@ public interface OrderedSilencerConst extends WingsBeanOrdered { int TweakConfiguration = Lv5Supervisor; // int RunnerInspectCommandLine = Lv5Supervisor + PriorityD; - int RunnerLogbackTweak = Lv5Supervisor; } diff --git a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerTweakConfiguration.java b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerTweakConfiguration.java index 29099a59a..da89cb804 100644 --- a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerTweakConfiguration.java +++ b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerTweakConfiguration.java @@ -1,7 +1,6 @@ package pro.fessional.wings.silencer.spring.bean; import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.core.spi.FilterReply; import com.alibaba.ttl.TransmittableThreadLocal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -10,16 +9,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureOrder; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerGroups; import org.springframework.boot.logging.LoggingSystem; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import pro.fessional.mirana.evil.ThreadLocalAttention; import pro.fessional.mirana.pain.CodeException; import pro.fessional.mirana.time.ThreadNow; -import pro.fessional.wings.silencer.runner.CommandLineRunnerOrdered; import pro.fessional.wings.silencer.spring.prop.SilencerTweakProp; import pro.fessional.wings.silencer.tweak.TweakLogger; import pro.fessional.wings.spring.consts.OrderedSilencerConst; @@ -55,36 +51,34 @@ public void autowireThreadClockTweak(SilencerTweakProp prop) throws ThreadLocalA } } + @Autowired @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") - @Bean - @ConditionalOnClass(FilterReply.class) - public CommandLineRunnerOrdered runnerLogbackTweak(SilencerTweakProp prop, - LoggingSystem system, - LoggerGroups groups, - @Value("${debug:false}") boolean debug, - @Value("${trace:false}") boolean trace + public void autowireLogbackTweak(SilencerTweakProp prop, + LoggingSystem loggingSystem, + LoggerGroups loggerGroups, + @Value("${debug:false}") boolean debug, + @Value("${trace:false}") boolean trace ) { - log.info("SilencerCurse spring-runs runnerLogbackTweak, init TtlMDC"); + log.info("SilencerCurse spring-auto autowireLogbackTweak, init TtlMDC"); TtlMDCAdapter.initMdc();// 尽早初始化 - return new CommandLineRunnerOrdered(OrderedSilencerConst.RunnerLogbackTweak, ignored -> { - if (prop.isMdcThreshold()) { - log.info("SilencerCurse spring-conf runnerLogbackTweak WingsMdcThresholdFilter"); - LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); - lc.getTurboFilterList().add(0, TweakLogger.MdcThresholdFilter); - } - // 尽晚初始化 - final LogLevel core; - if (debug) { - core = LogLevel.DEBUG; - } - else if (trace) { - core = LogLevel.TRACE; - } - else { - core = null; - } - log.info("SilencerCurse spring-conf runnerLogbackTweak TweakLogger, coreLevel=" + core); - TweakLogger.initGlobal(system, groups, core); - }); + + if (prop.isMdcThreshold()) { + log.info("SilencerCurse spring-conf autowireLogbackTweak WingsMdcThresholdFilter"); + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + lc.getTurboFilterList().add(0, TweakLogger.MdcThresholdFilter); + } + // 尽晚初始化 + final LogLevel core; + if (debug) { + core = LogLevel.DEBUG; + } + else if (trace) { + core = LogLevel.TRACE; + } + else { + core = null; + } + log.info("SilencerCurse spring-conf autowireLogbackTweak TweakLogger, coreLevel=" + core); + TweakLogger.initGlobal(loggingSystem, loggerGroups, core); } } diff --git a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/tweak/TweakLogger.java b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/tweak/TweakLogger.java index eb57b1bdf..f4b07b3e7 100644 --- a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/tweak/TweakLogger.java +++ b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/tweak/TweakLogger.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.MDC; import org.slf4j.Marker; +import org.springframework.boot.context.logging.LoggingApplicationListener; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggerConfiguration; import org.springframework.boot.logging.LoggerGroup; @@ -26,6 +27,7 @@ * Logging * * @author trydofor + * @see LoggingApplicationListener * @since 2022-10-27 */ public class TweakLogger { @@ -138,6 +140,9 @@ public static void resetGlobal() { @NotNull public static LogLevel globalLevel(@NotNull String name) { + if (loggingSystem == null) { + throw new IllegalStateException("must initLogging first"); + } return GlobalLevel.computeIfAbsent(name, k -> { if (loggerGroups != null) { LoggerGroup group = loggerGroups.get(name); diff --git a/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/WingsSilencerCurseApplication.java b/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/WingsSilencerCurseApplication.java index c7d0352d7..a34e6ecbf 100644 --- a/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/WingsSilencerCurseApplication.java +++ b/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/WingsSilencerCurseApplication.java @@ -19,6 +19,7 @@ public InnerFace innerFace() { return new InnerFace() {}; } + public static void main(String[] args) { SpringApplication.run(WingsSilencerCurseApplication.class, args); } diff --git a/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/ApplicationEventLogger.java b/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/ApplicationEventLogger.java new file mode 100644 index 000000000..a76b20800 --- /dev/null +++ b/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/ApplicationEventLogger.java @@ -0,0 +1,21 @@ +package pro.fessional.wings.silencer.spring; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import pro.fessional.wings.silencer.spring.bean.SpringOrderConfiguration; + +/** + * @author trydofor + * @since 2023-06-26 + */ +public class ApplicationEventLogger implements ApplicationListener { + + private static final Log log = LogFactory.getLog(SpringOrderConfiguration.class); + + @Override + public void onApplicationEvent(ApplicationEvent event) { + log.info(">>>>> " + event.getClass().getSimpleName() + "(spring.factories) timestamp=" + event.getTimestamp()); + } +} diff --git a/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/bean/SpringOrderConfiguration.java b/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/bean/SpringOrderConfiguration.java index 0344f9e7d..366c86f55 100644 --- a/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/bean/SpringOrderConfiguration.java +++ b/wings/silencer-curse/src/test/java/pro/fessional/wings/silencer/spring/bean/SpringOrderConfiguration.java @@ -6,8 +6,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.EventListener; @@ -15,6 +14,9 @@ import pro.fessional.wings.silencer.spring.prop.SilencerEnabledProp; /** + * ApplicationEnvironmentPreparedEvent(spring.factories) + * ApplicationContextInitializedEvent(spring.factories) + * ApplicationPreparedEvent(spring.factories) * constructor * testAutowired1 * postConstruct1 @@ -22,10 +24,12 @@ * afterPropertiesSet * testBean1 * testBean2 - * testApplicationStartedEvent + * ContextRefreshedEvent(spring.factories) + * ApplicationStartedEvent * CommandLineRunner1 * CommandLineRunner2 * ApplicationReadyEvent + * ContextClosedEvent(spring.factories) * * @author trydofor * @since 2022-11-02 @@ -71,14 +75,9 @@ public CommandLineRunner testBean2(SilencerEnabledProp prop) { return ignored -> log.info(">>>>> CommandLineRunner2 AutoLog=" + prop.isAutoLog()); } - @EventListener(ApplicationStartedEvent.class) - public void testApplicationStartedEvent() { - log.info(">>>>> testApplicationStartedEvent 事件参数"); - } - @EventListener - public void testApplicationReadyEvent(ApplicationReadyEvent event) { - log.info(">>>>> ApplicationReadyEvent timestamp=" + event.getTimestamp()); + public void testApplicationReadyEvent(SpringApplicationEvent event) { + log.info(">>>>> " + event.getClass().getSimpleName() + " timestamp=" + event.getTimestamp()); } @Override diff --git a/wings/silencer-curse/src/test/resources/META-INF/spring.factories b/wings/silencer-curse/src/test/resources/META-INF/spring.factories new file mode 100644 index 000000000..9f67524c8 --- /dev/null +++ b/wings/silencer-curse/src/test/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.context.ApplicationListener=pro.fessional.wings.silencer.spring.ApplicationEventLogger diff --git a/wings/silencer/src/main/java/pro/fessional/wings/silencer/message/CombinableMessageSource.java b/wings/silencer/src/main/java/pro/fessional/wings/silencer/message/CombinableMessageSource.java index dddf41df5..366db3557 100644 --- a/wings/silencer/src/main/java/pro/fessional/wings/silencer/message/CombinableMessageSource.java +++ b/wings/silencer/src/main/java/pro/fessional/wings/silencer/message/CombinableMessageSource.java @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap; /** - * 自己先处理,然后[兄弟连]处理,然后父类 + * Handles itself first, then handles [brother] it, then the parent * * @author trydofor * @since 2019-09-15 @@ -52,7 +52,8 @@ public void removeMessage(String code, Locale locale) { if (locale == null) { codeLocaleString.remove(code); codeLocaleFormat.remove(code); - } else { + } + else { remove(codeLocaleString, code, locale); remove(codeLocaleFormat, code, locale); } @@ -100,18 +101,19 @@ public void setParentMessageSource(@Nullable MessageSource parent) { final int size = orderedBrotherSources.size(); if (size == 0) { super.setParentMessageSource(parent); - } else { + } + else { orderedBrotherSources.get(size - 1).source.setParentMessageSource(parent); } } } /** - * 按order顺序,组合其他messageSource,序号小的优先解析message。 - * 当order==Integer.MIN_VALUE时,表示移除该message + * Combine other messageSource in order, and the one with the smaller order number will be resolved first. + * If order equals Integer.MIN_VALUE, it means that the message will be removed. * - * @param messageSource 其他messageSource - * @param order 顺序,#Integer.MIN_VALUE时表示移除 + * @param messageSource other messageSource + * @param order order Integer#MIN_VALUE means remove */ public void addMessages(HierarchicalMessageSource messageSource, int order) { Assert.notNull(messageSource, "messageSource must not be null"); @@ -120,7 +122,8 @@ public void addMessages(HierarchicalMessageSource messageSource, int order) { MessageSource thatParent; if (size == 0) { thatParent = getParentMessageSource(); - } else { + } + else { thatParent = orderedBrotherSources.get(size - 1).source.getParentMessageSource(); } @@ -141,9 +144,9 @@ public void addMessages(HierarchicalMessageSource messageSource, int order) { } /** - * 移除组合的messageSource + * remove messageSource * - * @param messageSource 被移除对象 + * @param messageSource to be removed */ public void removeMessages(HierarchicalMessageSource messageSource) { addMessages(messageSource, Integer.MIN_VALUE); diff --git a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerRunnerConfiguration.java b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerRunnerConfiguration.java index bf2d61b20..500c3529d 100644 --- a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerRunnerConfiguration.java +++ b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerRunnerConfiguration.java @@ -20,7 +20,7 @@ import java.util.Map; /** - * 当前在SpringApplication#callRunners中,applicationRunner先于commandLineRunner执行 + * applicationRunner are executed before commandLineRunner in SpringApplication#callRunners * * @author trydofor * @link Application Events and Listeners diff --git a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/DeferredLogFactory.java b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/DeferredLogFactory.java index 9cbf9b501..57363a115 100644 --- a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/DeferredLogFactory.java +++ b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/DeferredLogFactory.java @@ -8,7 +8,9 @@ import java.util.concurrent.ConcurrentHashMap; /** - * 日志系统未初始化之前记录日志信息,当ApplicationPreparedEvent时定向到日志系统。 + * Logging information is recorded before the logging system is initialized, + * and redirected to the logging system when ApplicationPreparedEvent. + * * @author trydofor * @since 2019-05-30 */ diff --git a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsAutoConfigProcessor.java b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsAutoConfigProcessor.java index af766532c..8ac5cd4c8 100644 --- a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsAutoConfigProcessor.java +++ b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsAutoConfigProcessor.java @@ -48,9 +48,9 @@ import java.util.stream.Collectors; /** - * 自动加载配置路径中的 /wings-conf/*.{yml,yaml,properties}配置。 + * Automatically load the configuration that matches `/wings-conf/*.{yml,yaml,properties}` *
- * 参考资料docs.spring.io
+ * docs.spring.io
  *  - #boot-features-application-events-and-listeners
  *  - #boot-features-external-config
  *  - #howto-change-the-location-of-external-properties
@@ -317,7 +317,7 @@ private Set parsePromoProp(Collection res, String promo) {
         return prop;
     }
 
-    // 移除非活动profile,basename相同,application-{profile}由spring自身管理
+    // Remove the inactive profile, with the same basename, application-{profile} is managed by spring itself
     private List profileBlockSort(LinkedHashSet confResources,
                                                 HashMap blockList,
                                                 String[] activeProfs) {
@@ -337,7 +337,7 @@ private List profileBlockSort(LinkedHashSet confReso
             }
             else {
                 HashSet prof = new HashSet<>(Arrays.asList(activeProfs));
-                // 移除所有非活动,empty以低优先级加载
+                // Remove all inactivity, empty is loaded with low priority
                 final Set actProf = new HashSet<>();
                 for (ConfResource cr : profiledConf) {
                     if (cr.profile.isEmpty() || prof.contains(cr.profile)) {
@@ -354,7 +354,7 @@ private List profileBlockSort(LinkedHashSet confReso
             }
         }
 
-        // 按名字分组,排序
+        // group by name, and sort
         LinkedHashMap> groups = new LinkedHashMap<>(confResources.size());
         Function> newList = ignored -> new ArrayList<>();
         for (ConfResource cr : confResources) {
@@ -392,7 +392,7 @@ private List profileBlockSort(LinkedHashSet confReso
         return sortedConf;
     }
 
-    // 按路径优先级扫描
+    // Scan by path priority
     private LinkedHashSet scanWingsResource(MutablePropertySources sources, PathMatchingResourcePatternResolver resolver, AutoConf autoConf) {
         LinkedHashSet sortedPath = new LinkedHashSet<>();
         for (PropertySource next : sources) {
@@ -418,7 +418,7 @@ private LinkedHashSet scanWingsResource(MutablePropertySources sou
 
         final LinkedHashSet confResources = new LinkedHashSet<>();
         for (String path : sortedPath) {
-            // 5. `classpath:/`会被以`classpath*:/`扫描
+            // 5. `classpath:/` is scanned as `classpath*:/`
             if (path.startsWith("classpath:")) {
                 path = path.replace("classpath:", "classpath*:");
             }
@@ -426,14 +426,14 @@ else if (path.startsWith("file:") || path.startsWith("classpath*:")) {
                 // skip
             }
             else {
-                // 6. 任何非`classpath:`,`classpath*:`的,都以`file:`扫描
+                // 6. any non-`classpath:`,`classpath*:` will be scanned as `file:`
                 path = "file:" + path;
             }
 
             log.info("🦁 Wings scan classpath, path=" + path);
-            //  7. 以`/`结尾的当做目录,否则作为文件
+            //  7. ending with `/` as dir, otherwirse as file
             if (path.endsWith("/") || path.endsWith("\\")) {
-                // 8. 从以上路径,优先加载`application.*`,次之`wings-conf/**/*.*`
+                // 8. From the above path, `application.*` is loaded first, then `wings-conf/**/*.*`
                 for (String auto : autoConf.onces) {
                     putConfIfValid(false, confResources, resolver, path + auto, autoConf);
                 }
diff --git a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsSpringBeanScanner.java b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsSpringBeanScanner.java
index 39339cd2b..4fd658eaa 100644
--- a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsSpringBeanScanner.java
+++ b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/boot/WingsSpringBeanScanner.java
@@ -18,7 +18,7 @@
 import java.util.Map;
 
 /**
- * 自动加载配置路径中的/spring/bean/下的*.class配置
+ * Automatically load the configuration that matches `**/spring/bean/**/*.class`
  *
  * @author trydofor
  * @since 2019-07-11
@@ -107,9 +107,8 @@ private void guessClassPackage(Map map, String path, ClassLoader
 
         // common path
         int off = ps - 1;
-        int p3 = ps;
-        while (off > 0 && p3 > 0) {
-            p3 = path.lastIndexOf('/', off);
+        while (off > 0) {
+            int p3 = path.lastIndexOf('/', off);
             if (p3 > 0 && packageName(map, loader, path, p3 + 1, ps, px)) {
                 return;
             }
diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/watching/WatchingService.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/watching/WatchingService.java
index 2c94db3fc..0c28fdda3 100644
--- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/watching/WatchingService.java
+++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/watching/WatchingService.java
@@ -48,7 +48,7 @@ public void errorFetch() {
         final WinConfRuntimeTable t = winConfRuntimeDao.getTable();
         winConfRuntimeDao.ctx()
                          .selectFrom(t)
-                         .where("aaa=bbb")
+                         .where("ignore_this_error=true")
                          .fetch();
     }
 
@@ -68,7 +68,7 @@ public void asyncWatch() {
             log.warn("AsyncWatch={}", WatchOwner);
             asyncLatch.countDown();
             asyncLatch = new CountDownLatch(1);
-            try (Watch w0 = stopWatch.start("AsyncWatch.fetchLatch")) {
+            try (Watch ignored = stopWatch.start("AsyncWatch.fetchLatch")) {
                 fetchLatch.await(); // 等待sql执行,交叉时间线
             }
             catch (InterruptedException e) {

From 24678bf9ece125a4adff6f59c1310e81e265f265 Mon Sep 17 00:00:00 2001
From: trydofor 
Date: Wed, 28 Jun 2023 14:12:44 +0800
Subject: [PATCH 05/48] =?UTF-8?q?=F0=9F=94=96=20disclaimer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 readme-zh.md | 19 ++++++++++---------
 readme.md    | 23 ++++++++++++++---------
 2 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/readme-zh.md b/readme-zh.md
index e75bbc843..9467e3517 100644
--- a/readme-zh.md
+++ b/readme-zh.md
@@ -23,15 +23,15 @@
 
 ## 1.关联文档
 
-* Official Doc - 
-* NotBad Review - 
-* Doc GitHub - 
-* [Gitee](https://gitee.com/trydofor) is the mirror site 
+* 官方手册 - 
+* 代码审查 - 
+* 文档项目 - 
+* 镜像站点 - 
 
 ## 2.常用命令
 
 ```bash
-# ① 国内镜像,成功后进入项目目录
+# ① 获取源码,成功后进入项目目录
 git clone --depth 1 https://github.com/\
 trydofor/pro.fessional.wings.git
 # ② 安装依赖,可跳过,支持java8编译
@@ -59,8 +59,9 @@ mvn package install
 
 ## 4.免责声明
 
-WingsBoot及其submodule项目,均以Apache2授权。但本人,
+WingsBoot及其submodule项目,均以[Apache2]授权。请注意,
 
-* 不对滥用技术或手册造成的任何损失负责。
-* 没有义务提供咨询,答疑,开发等服务。
-* 可付费咨询,404 CNY/H
+* 项目是基于现有技术,资源和团队实践的自愿贡献,没有任何明示或暗示的保证或条件。
+* 项目的开发者已经尽力确保代码的质量和功能性,但不保证完全没有缺陷或错误。
+* 在使用项目时,你应该自行评估其适用性,并承担使用该项目的所有风险。
+* 在任何情况下,项目的开发者都不对因使用该项目而导致的任何损失、损害或其他责任承担责任。
diff --git a/readme.md b/readme.md
index 4d1e919c1..f505ce9dd 100644
--- a/readme.md
+++ b/readme.md
@@ -11,7 +11,7 @@
 * [![Jooq-3.17](https://img.shields.io/badge/jooq-3.17-cyan)](https://www.jooq.org/download/)  The main type-safe SqlMapping 🏅 [Apache2]
 * [![Mysql-8](https://img.shields.io/badge/mysql-8.0-blue)](https://dev.mysql.com/downloads/mysql/) Main business database, 8 recommended, 5.7 compatible 💡 [GPLv2]
 * [![H2Database-2.1](https://img.shields.io/badge/h2db-2.1-blue)](https://h2database.com/html/main.html) Standalone database for offline and disconnected operations [MPL2] or [EPL1]
-* [![Hazelcast-5.1](https://img.shields.io/badge/hazelcast-5.1-violet)](https://hazelcast.org/imdg/) IMDG,Distributed caching, messaging, streaming, etc. [Apache2]
+* [![Hazelcast-5.1](https://img.shields.io/badge/hazelcast-5.1-violet)](https://hazelcast.org/imdg/) IMDG, Distributed caching, messaging, streaming, etc. [Apache2]
 * [![ServiceComb-2.8](https://img.shields.io/badge/servicecomb-2.8-violet)](https://servicecomb.apache.org) more engineering and compact miscroservice solution [Apache2]
 * [![ShardingSphere-5.3](https://img.shields.io/badge/shardingsphere-5.3-violet)](https://shardingsphere.apache.org) Database RW splitting, data sharding and elastic scaling [Apache2]
 
@@ -24,9 +24,9 @@
 ## 1.Related Documents
 
 * Official Doc - 
-* NotBad Review - 
+* NotBad Code Review - 
 * Doc GitHub - 
-* [Gitee](https://gitee.com/trydofor) is the mirror site
+* Mirror Site - 
 
 ## 2.Useful commands
 
@@ -34,7 +34,7 @@
 # ① get source code
 git clone --depth 1 https://github.com/\
 trydofor/pro.fessional.wings.git
-# ② install dependency useing java8
+# ② install dependency using java8
 # sdk use java 8.0.352-tem
 git submodule update --remote --init
 (cd observe/meepo && mvn package install)
@@ -60,10 +60,15 @@ basically synchronized push.
 We will keep contributor and footprint as much as possible,
 such as @author comments, code comments, commit information, etc.
 
-## 4.Disclaimers
+## 4.Disclaimer
 
-WingsBoot and its submodule project are licensed under [Apache2]. but I am,
+WingsBoot and its submodule projects are licensed under [Apache2]. Please note that,
 
-* No responsibility for any damages caused by misuse of the code or doc.
-* No obligation to provide consulting, development, etc.
-* Non-free Consulting is available, 59 USD/H
+* The projects are voluntary contributions based on existing technologies, resources and team practices,
+  without any express or implied warranties or conditions.
+* The developers of the projects have made efforts to ensure the quality and functionality of the code,
+  but do not guarantee that the projects are completely free of defects or bugs.
+* When using the projects, you must make your own evaluation of their suitability and
+  assume all risks associated with their use.
+* Under no circumstances will the developers of the projects be liable for any loss, damages,
+  or other liabilities arising from the use of the projects.

From e2ab2e29f640c74fd19104dd75b44a00720f4110 Mon Sep 17 00:00:00 2001
From: trydofor 
Date: Mon, 3 Jul 2023 14:45:04 +0800
Subject: [PATCH 06/48] =?UTF-8?q?=E2=9C=A8=20DingTalk=20Notice=20markdown?=
 =?UTF-8?q?=20message=20add=20title=20#107?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../monitor/report/DingTalkReportTest.java    |  4 +-
 .../wings/slardar/notice/DingTalkConf.java    |  6 ++-
 .../wings/slardar/notice/DingTalkNotice.java  | 44 +++++++++----------
 3 files changed, 28 insertions(+), 26 deletions(-)

diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/monitor/report/DingTalkReportTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/monitor/report/DingTalkReportTest.java
index 7bbc87e00..f599089d2 100644
--- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/monitor/report/DingTalkReportTest.java
+++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/monitor/report/DingTalkReportTest.java
@@ -49,6 +49,8 @@ void postReport() {
     void postNotice() {
         final DingTalkConf conf = dingTalkNotice.provideConfig("monitor", true);
         conf.setNoticeMobiles(Map.of("a9", "155XXXX1992"));
-        dingTalkNotice.post(conf, "测试标题", "##测试正文\n\n- **列表** 正常");
+        dingTalkNotice.post(conf, "MARKDOWN标题", "## 测试正文\n\n- **列表** 正常");
+        conf.setMsgType(DingTalkConf.MsgText);
+        dingTalkNotice.post(conf, "文本标题", "## 测试正文\n\n- **列表** 正常");
     }
 }
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkConf.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkConf.java
index 77c08f923..1afaf835d 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkConf.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkConf.java
@@ -18,6 +18,10 @@
  */
 @Data
 public class DingTalkConf {
+
+    public static final String MsgText = "text";
+    public static final String MsgMarkdown = "markdown";
+
     /**
      * template of DingTalk webhook URL.
      */
@@ -43,7 +47,7 @@ public class DingTalkConf {
     /**
      * message type, support `text`|`markdown`
      */
-    private String msgType = "markdown";
+    private String msgType = MsgMarkdown;
 
     /**
      * notified person and his phone number, non-member's phone number will be desensitized.
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkNotice.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkNotice.java
index 20fa5c9d9..5e0a2d41c 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkNotice.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/notice/DingTalkNotice.java
@@ -30,6 +30,8 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME;
+import static pro.fessional.wings.slardar.notice.DingTalkConf.MsgMarkdown;
+import static pro.fessional.wings.slardar.notice.DingTalkConf.MsgText;
 
 /**
  * custom-robot-access
@@ -131,7 +133,7 @@ public boolean send(DingTalkConf config, String subject, String content) {
         }
 
         final String message;
-        if ("markdown".equalsIgnoreCase(config.getMsgType())) {
+        if (MsgMarkdown.equalsIgnoreCase(config.getMsgType())) {
             message = buildMarkdown(config, subject, content);
         }
         else {
@@ -183,12 +185,9 @@ public void afterPropertiesSet() {
      * }
      */
     public String buildText(DingTalkConf conf, String subject, String content) {
-        if (subject == null) subject = Null.Str;
-        if (content == null) content = Null.Str;
-        final String message = subject.isEmpty() ? content : subject + "\r\n" + content;
         return JsonTemplate.obj(t -> t
-                .putVal("msgtype", "text")
-                .putObj("text", o -> o.putVal("content", buildContent(conf, message)))
+                .putVal("msgtype", MsgText)
+                .putObj("text", o -> o.putVal("content", buildContent(conf, content, subject)))
                 .putObj("at", o -> buildNotice(conf, o))
         );
     }
@@ -207,7 +206,7 @@ public String buildText(DingTalkConf conf, String subject, String content) {
     @SuppressWarnings("JavadocLinkAsPlainText")
     public String buildMarkdown(DingTalkConf conf, String subject, String content) {
         return JsonTemplate.obj(t -> t
-                .putVal("msgtype", "markdown")
+                .putVal("msgtype", MsgMarkdown)
                 .putObj("markdown", o -> o
                         .putVal("title", subject != null ? subject : "untitled")
                         .putVal("text", buildContent(conf, content, subject)))
@@ -215,32 +214,29 @@ public String buildMarkdown(DingTalkConf conf, String subject, String content) {
         );
     }
 
-    private String buildContent(DingTalkConf conf, String main, String... kws) {
+    private String buildContent(DingTalkConf conf, String main, String title) {
         if (main == null) main = Null.Str;
 
-        StringBuilder sb = new StringBuilder();
+        StringBuilder buff = new StringBuilder();
+        // title
+        if (title != null && !main.contains(title)) {
+            buff.append("# ").append(title).append("\n\n")
+                .append(main);
+        }
+
+        // key word
         final String kw = conf.getNoticeKeyword();
-        if (kw != null && !kw.isEmpty()) {
-            boolean ng = !main.contains(kw);
-            if (ng && kws != null) {
-                for (String s : kws) {
-                    if (s != null && s.contains(kw)) {
-                        ng = false;
-                        break;
-                    }
-                }
-            }
-            if (ng) {
-                sb.append(kw);
-            }
+        if (kw != null && !kw.isEmpty() && !main.contains(kw)) {
+            buff.append('\n').append(kw);
         }
+        // notice
         for (String mb : conf.getNoticeMobiles().values()) {
             if (!main.contains(mb)) {
-                sb.append(" @").append(mb);
+                buff.append(" @").append(mb);
             }
         }
 
-        return sb.length() == 0 ? main : main + "\n\n" + sb;
+        return buff.length() == 0 ? main : buff.toString();
     }
 
     private void buildNotice(DingTalkConf conf, JsonTemplate.Obj obj) {

From 5b761973e16c7349242cb17e1ac3e82e3bfcb5dc Mon Sep 17 00:00:00 2001
From: trydofor 
Date: Wed, 5 Jul 2023 17:28:27 +0800
Subject: [PATCH 07/48] =?UTF-8?q?=F0=9F=92=A5=20CudEvent=20auto/manually?=
 =?UTF-8?q?=20trigger=20#99?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../wings-conf/spring-datasource.properties   |   2 +-
 observe/docs                                  |   2 +-
 observe/mirana                                |   2 +-
 pom.xml                                       |   2 +-
 .../spring/consts/OrderedWarlockConst.java    |   1 -
 .../spring-datasource-79.properties           |   2 +-
 .../database/WingsTableCudHandler.java        |  70 +++++--
 .../jooq/listener/TableCudListener.java       | 147 ++++++++-----
 .../bean/FacelessJooqConfiguration.java       |  16 +-
 .../spring/prop/FacelessJooqCudProp.java      |  44 +++-
 .../wings-conf/wings-jooq-cud-79.properties   |   4 +-
 .../jooq/JooqTableCudListenerTest.java        |   2 +-
 .../service/WingsTableCudHandlerTest.java     |   8 +-
 .../slardar/event/HazelcastSyncPublisher.java |  15 +-
 .../extra-conf/hazelcast-server-webmvc.xml    |   2 +-
 .../spring-datasource-99@h2.properties        |   3 +-
 .../service/conf/RuntimeConfService.java      |  10 +-
 .../conf/impl/RuntimeConfServiceImpl.java     |  93 ++++++---
 .../service/conf/RuntimeConfCacheTest.java    |  47 +++++
 .../service/conf/RuntimeConfServiceTest.java  |  43 +++-
 .../perm/impl/WarlockPermServiceImpl.java     | 177 ++++++++++------
 .../perm/impl/WarlockRoleServiceImpl.java     | 194 +++++++++++-------
 .../bean/WarlockBondAutoRunConfiguration.java |  41 ----
 .../bean/WarlockBondBeanConfiguration.java    |  35 +++-
 .../wings-warlock-cud-77.properties           |  11 +-
 .../service/perm/WarlockPermServiceTest.java  |  34 +++
 .../perm/WarlockRoleServiceCacheTest.java     |  36 ++++
 .../service/auth/WarlockAuthnService.java     |  14 +-
 .../auth/impl/ComboWarlockAuthnService.java   |  31 ---
 .../wings/warlock/caching/CacheConst.java     |   7 -
 .../warlock/caching/CacheEventHelper.java     |  28 ++-
 .../warlock/event/cache/TableChangeEvent.java |  12 ++
 .../event/impl/WingsTableCudHandlerImpl.java  |  36 +++-
 33 files changed, 786 insertions(+), 385 deletions(-)
 create mode 100644 wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfCacheTest.java
 delete mode 100644 wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondAutoRunConfiguration.java
 create mode 100644 wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermServiceTest.java
 create mode 100644 wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockRoleServiceCacheTest.java

diff --git a/example/winx-common/src/main/resources/wings-conf/spring-datasource.properties b/example/winx-common/src/main/resources/wings-conf/spring-datasource.properties
index 46903d4d3..340765910 100644
--- a/example/winx-common/src/main/resources/wings-conf/spring-datasource.properties
+++ b/example/winx-common/src/main/resources/wings-conf/spring-datasource.properties
@@ -5,7 +5,7 @@ spring.datasource.url=jdbc:mysql://localhost:3306/wings_example\
 
 #spring.datasource.url=jdbc:h2:./wings-example\
 #;USER=trydofor;PASSWORD=moilioncircle\
-#;MODE=MySQL;CASE_INSENSITIVE_IDENTIFIERS=TRUE\
+#;MODE=MySQL;CASE_INSENSITIVE_IDENTIFIERS=TRUE;IGNORECASE=TRUE\
 #;AUTO_RECONNECT=TRUE;AUTO_SERVER=TRUE
 
 spring.datasource.username=trydofor
diff --git a/observe/docs b/observe/docs
index 9ff43746d..198a29731 160000
--- a/observe/docs
+++ b/observe/docs
@@ -1 +1 @@
-Subproject commit 9ff43746d85af20c4d8ba002ecedcbc62cb0072b
+Subproject commit 198a2973171c06446fceb8344d7b5fecfddd79da
diff --git a/observe/mirana b/observe/mirana
index 18d8a826f..5ea95bd0d 160000
--- a/observe/mirana
+++ b/observe/mirana
@@ -1 +1 @@
-Subproject commit 18d8a826f26730f41b955283699cf8de74ccc33c
+Subproject commit 5ea95bd0d8b851a011919aff7143a22ff15e2e00
diff --git a/pom.xml b/pom.xml
index 943a376de..7b7bbe349 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,7 +45,7 @@
         32.0.1-jre 
         2.13.0 
         
-        2.4.5-SNAPSHOT 
+        2.6.0-SNAPSHOT 
         1.4.1-SNAPSHOT 
         5.3.2 
         2.3.3 
diff --git a/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedWarlockConst.java b/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedWarlockConst.java
index 2b4ec4c59..7ff12b171 100644
--- a/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedWarlockConst.java
+++ b/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/OrderedWarlockConst.java
@@ -14,7 +14,6 @@ public interface OrderedWarlockConst extends WingsBeanOrdered {
     int LockBeanConfiguration = Lv4Application + PriorityD;
     int TableChangeConfiguration = Lv4Application + PriorityD;
     int WatchingConfiguration = Lv4Application + PriorityD;
-    int BondAutoRunConfiguration = Lv4Application + PriorityD;
     int BondBeanConfiguration = Lv4Application + PriorityD;
     int JustAuthConfiguration = Lv4Application + PriorityD;
     int UnionAuthConfiguration = Lv4Application + PriorityD;
diff --git a/wings/batrider-test/src/test/resources/wings-conf/spring-datasource-79.properties b/wings/batrider-test/src/test/resources/wings-conf/spring-datasource-79.properties
index 8c8d92ce1..247f937a6 100644
--- a/wings/batrider-test/src/test/resources/wings-conf/spring-datasource-79.properties
+++ b/wings/batrider-test/src/test/resources/wings-conf/spring-datasource-79.properties
@@ -1,4 +1,4 @@
 spring.datasource.url=jdbc:h2:mem:wings-batrider\
 ;USER=${spring.datasource.username};PASSWORD=${spring.datasource.password}\
-;MODE=MySQL;CASE_INSENSITIVE_IDENTIFIERS=TRUE\
+;MODE=MySQL;CASE_INSENSITIVE_IDENTIFIERS=TRUE;IGNORECASE=TRUE\
 ;AUTO_RECONNECT=TRUE;AUTO_SERVER=TRUE
diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/WingsTableCudHandler.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/WingsTableCudHandler.java
index 959838519..13d8e0da4 100644
--- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/WingsTableCudHandler.java
+++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/WingsTableCudHandler.java
@@ -1,13 +1,20 @@
 package pro.fessional.wings.faceless.database;
 
 import org.jetbrains.annotations.NotNull;
+import org.jooq.Table;
 
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /**
- * 数据表CUD处理,可以是CUD或者Other
+ * 
  *
  * @author trydofor
  * @since 2021-06-12
@@ -22,30 +29,63 @@ enum Cud {
     }
 
     /**
-     * 处理表变更,建议轻任务或异步
+     * Auto Handler
+     */
+    interface Auto {
+        boolean accept(@NotNull Class source, @NotNull Cud cud, @NotNull String table);
+    }
+
+    /**
+     * Handle table changes, light task or async are recommended,
      *
-     * @param cud   类型
-     * @param table 表名
-     * @param field 关联字段和值(或值集合)
+     * @param source event source
+     * @param cud    type
+     * @param table  case-sensitive table name
+     * @param field  field and its changes
+     */
+    void handle(@NotNull Class source, @NotNull Cud cud, @NotNull String table, @NotNull Supplier>> field);
+
+    /**
+     * @see #handle(Class, Cud, String, Supplier)
+     */
+    default void handle(@NotNull Class source, @NotNull Cud cud, @NotNull Table table, @NotNull Supplier>> field) {
+        handle(source, cud, table.getName(), field);
+    }
+
+    default void handle(@NotNull Class source, @NotNull Cud cud, @NotNull String table, @NotNull Map> field) {
+        handle(source, cud, table, () -> field);
+    }
+
+    /**
+     * @see #handle(Class, Cud, Table, Map)
      */
-    void handle(@NotNull Cud cud, @NotNull String table, @NotNull Map> field);
+    default void handle(@NotNull Class source, @NotNull Cud cud, @NotNull Table table, @NotNull Map> field) {
+        handle(source, cud, table.getName(), () -> field);
+    }
 
     /**
-     * 处理表变更,建议轻任务或异步,关联键为empty
+     * Handle table changes, light task or async are recommended. empty field as default.
      *
-     * @param cud   类型
-     * @param table 表名
+     * @param source event source
+     * @param cud    type
+     * @param table  case-sensitive table name
+     */
+    default void handle(@NotNull Class source, @NotNull Cud cud, @NotNull String table) {
+        handle(source, cud, table, Collections::emptyMap);
+    }
+
+    /**
+     * @see #handle(Class, Cud, String)
      */
-    default void handle(@NotNull Cud cud, @NotNull String table) {
-        handle(cud, table, Collections.emptyMap());
+    default void handle(@NotNull Class source, @NotNull Cud cud, @NotNull Table table) {
+        handle(source, cud, table.getName(), Collections::emptyMap);
     }
 
     /**
-     * 处理表变更,建议轻任务或异步,类型为Unsure
+     * Register an auto handler to handle table changes instead of doing it manually.
      *
-     * @param table 表名
+     * @param auto the handler to register
      */
-    default void handle(@NotNull String table) {
-        handle(Cud.Unsure, table, Collections.emptyMap());
+    default void register(@NotNull Auto auto) {
     }
 }
diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/TableCudListener.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/TableCudListener.java
index 51e36bb65..c108183f7 100644
--- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/TableCudListener.java
+++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/TableCudListener.java
@@ -3,6 +3,7 @@
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jooq.Clause;
 import org.jooq.Configuration;
@@ -16,6 +17,7 @@
 import org.jooq.impl.QOM;
 import org.jooq.impl.TableImpl;
 import pro.fessional.mirana.data.Null;
+import pro.fessional.mirana.pain.DebugException;
 import pro.fessional.wings.faceless.database.WingsTableCudHandler;
 
 import java.util.ArrayList;
@@ -27,26 +29,33 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 import static pro.fessional.wings.faceless.database.WingsTableCudHandler.Cud;
 
 /**
- * 仅支持jooq的单表insert,update,delete。

- * 不支持merge和replace。不支持batch执行(无法获得bind值)

- * 仅支持eq,le,ge,in的where条件。

- * 注意:visit可能触发多次,任何需要render的地方,如日志debug,toString等

+ *

+ * Only support for single table insert,update,delete in jooq.
+ * No support for merge and replace.
+ * No support for batch execution (cannot get bind values)
+ * Only support where conditions for eq,le,ge,in.
+ * INSERT_ON_DUPLICATE_KEY_UPDATE as UPDATE
+ * 
* * @author trydofor * @since 2021-01-14 */ @SuppressWarnings("removal") @Slf4j -public class TableCudListener implements VisitListener { +public class TableCudListener implements VisitListener, WingsTableCudHandler.Auto { + /** + * for debug only + */ public static boolean WarnVisit = false; @Setter @Getter - private boolean insert = true; + private boolean create = true; @Setter @Getter private boolean update = true; @Setter @Getter @@ -63,8 +72,9 @@ public enum ContextKey { EXECUTING_FIELD_MAP, // Map> EXECUTING_INSERT_IDX, EXECUTING_INSERT_CNT, + EXECUTING_INSERT_UPD, EXECUTING_WHERE_KEY, - EXECUTING_WHERE_CMP, // String 固定值 Null, WHERE_EQ, WHERE_IN + EXECUTING_WHERE_CMP, // String fixed Null, WHERE_EQ, WHERE_IN } private static final String WHERE_EQ = "="; @@ -77,7 +87,7 @@ public void clauseStart(VisitContext context) { final Clause clause = context.clause(); if (clause == Clause.INSERT || clause == Clause.UPDATE || clause == Clause.DELETE) { log.warn(">>> clauseStart Clause=" + clause + ", Query=" + clz - , new RuntimeException("debug for call stack")); + , new DebugException("debug for call stack")); } else { log.warn(">>> clauseStart Clause={}, Query={}", clause, clz); @@ -89,7 +99,8 @@ public void clauseStart(VisitContext context) { final Clause clause = context.clause(); final Cud cud; - if (insert && clause == Clause.INSERT) { + if (create && clause == Clause.INSERT) { + // on duplicate key update cud = Cud.Create; } else if (update && clause == Clause.UPDATE) { @@ -104,7 +115,8 @@ else if (delete && clause == Clause.DELETE) { for (Map.Entry ent : context.data().entrySet()) { final Object key = ent.getKey(); - if (key instanceof Enum && "DATA_COUNT_BIND_VALUES".equals(((Enum) key).name())) { + // org.jooq.impl.Tools.BooleanDataKey#DATA_COUNT_BIND_VALUES; + if (key instanceof Enum em && "DATA_COUNT_BIND_VALUES".equals(em.name())) { if (WarnVisit) { log.warn(">>> got DATA_COUNT_BIND_VALUES"); } @@ -133,7 +145,7 @@ public void clauseEnd(VisitContext context) { if (handlers.isEmpty() || tableField.isEmpty()) return; - final Cud cud = (Cud) context.data(ContextKey.EXECUTING_VISIT_CUD); + Cud cud = (Cud) context.data(ContextKey.EXECUTING_VISIT_CUD); if (cud == null) return; final Clause clause = context.clause(); @@ -143,26 +155,34 @@ public void clauseEnd(VisitContext context) { if (context.renderContext() == null) return; - final String table = (String) context.data(ContextKey.EXECUTING_TABLE_STR); - if (table == null) { + final String tbl = (String) context.data(ContextKey.EXECUTING_TABLE_STR); + if (tbl == null) { log.warn("find CUD without table, may be unsupported, sql={}", context.renderContext()); return; } - Map> field = (Map>) context.data(ContextKey.EXECUTING_FIELD_MAP); - if (field == null) field = Collections.emptyMap(); + final Map> field = (Map>) context.data(ContextKey.EXECUTING_FIELD_MAP); + + final Object upd = context.data(ContextKey.EXECUTING_INSERT_UPD); + if (upd == Boolean.TRUE) { + log.debug("find INSERT_ON_DUPLICATE_KEY_UPDATE, set CUD to update"); + cud = Cud.Update; + } + + log.debug("handle CUD={}, table={}, filed={}", cud, tbl, field); + final Class src = this.getClass(); + final Supplier>> sup = field == null ? Collections::emptyMap : () -> field; - log.debug("handle CUD={}, table={}, filed={}", cud, table, field); for (WingsTableCudHandler hd : handlers) { try { - hd.handle(cud, table, field); + hd.handle(src, cud, tbl, sup); } catch (Exception e) { StringBuilder msg = new StringBuilder(); msg.append("failed to handle cud=").append(cud); - msg.append(", table=").append(table); + msg.append(", table=").append(tbl); msg.append(", handle=").append(hd.getClass()); - if (!field.isEmpty()) { + if (field != null && !field.isEmpty()) { msg.append(", field="); for (Map.Entry> en : field.entrySet()) { msg.append(',').append(en.getKey()).append(':').append(en.getValue()); @@ -229,24 +249,29 @@ private void handleUpdate(VisitContext context) { else if (clause == Clause.UPDATE_SET && query instanceof final Map updSet) { final Set fds = (Set) context.data(ContextKey.EXECUTING_FIELD_KEY); if (fds == null) { - log.debug("should not be here, update-table without key"); + log.warn("should not be here, update-table without key"); + return; + } + if (fds.isEmpty()) { + log.debug("skip careless field in update"); return; } - final Map> map = (Map>) context.data(ContextKey.EXECUTING_FIELD_MAP); - if (map == null) { - log.debug("should not be here, update-table without map"); + final Map> field = (Map>) context.data(ContextKey.EXECUTING_FIELD_MAP); + if (field == null) { + log.warn("should not be here, update-table without field"); return; } + // handle set for (Map.Entry en : updSet.entrySet()) { final Object ky = en.getKey(); final Object vl = en.getValue(); if (ky instanceof TableField && (vl == null || vl instanceof Param)) { final String fd = ((TableField) ky).getName(); if (fds.contains(fd)) { - final List set = map.computeIfAbsent(fd, k -> new ArrayList<>()); - set.add(vl == null ? null : ((Param) vl).getValue()); + final List lst = field.computeIfAbsent(fd, k -> new ArrayList<>()); + lst.add(vl == null ? null : ((Param) vl).getValue()); log.debug("handle update-field, name={}", fd); } else { @@ -266,7 +291,7 @@ else if (clause == Clause.UPDATE_WHERE && query instanceof Keyword) { @SuppressWarnings({"unchecked", "UnstableApiUsage"}) private void handleWhere(VisitContext context, Clause clause, QueryPart query) { - if (clause == Clause.FIELD_REFERENCE && query instanceof TableField) { + if (clause == Clause.FIELD_REFERENCE && query instanceof TableField field) { if (context.data(ContextKey.EXECUTING_WHERE_CMP) == null) { log.debug("skip where without where-clause"); return; @@ -274,20 +299,22 @@ private void handleWhere(VisitContext context, Clause clause, QueryPart query) { final Set fds = (Set) context.data(ContextKey.EXECUTING_FIELD_KEY); if (fds == null) { - log.debug("should not be here, table without key"); + log.warn("should not be here, table without key"); return; } - final String fd = ((TableField) query).getName(); + + final String fd = field.getName(); if (fds.contains(fd)) { log.debug("handle where-field={}", fd); context.data(ContextKey.EXECUTING_WHERE_KEY, fd); } else { log.debug("skip careless where-field={}", fd); + // remove the old key context.data(ContextKey.EXECUTING_WHERE_KEY, null); } } - // 3.14为query instanceof Keyword + // 3.14 use query instanceof Keyword // else if ((clause == Clause.CONDITION_COMPARISON || clause == Clause.CONDITION_IN) && query instanceof Keyword) { // if (context.data(ContextKey.EXECUTING_WHERE_KEY) == null) { // log.debug("skip comparison without where-key or careless"); @@ -307,7 +334,7 @@ private void handleWhere(VisitContext context, Clause clause, QueryPart query) { // log.debug("skip comparison. key={}", cmp); // } // } - // 3.16 使用QOM + // 3.16 use QOM else if ((clause == Clause.CONDITION_COMPARISON || clause == Clause.CONDITION_IN)) { if (query instanceof QOM.Eq || query instanceof QOM.Ge || query instanceof QOM.Le) { log.debug("handle comparison. key={}", query); @@ -318,7 +345,7 @@ else if (query instanceof QOM.In || query instanceof QOM.InList) { context.data(ContextKey.EXECUTING_WHERE_CMP, WHERE_IN); } } - else if (clause == Clause.FIELD_VALUE && query instanceof Param) { + else if (clause == Clause.FIELD_VALUE && query instanceof Param param) { final String fd = (String) context.data(ContextKey.EXECUTING_WHERE_KEY); if (fd == null) { log.debug("skip where-field without where-key or careless"); @@ -334,8 +361,8 @@ else if (clause == Clause.FIELD_VALUE && query instanceof Param) { final Object cmp = context.data(ContextKey.EXECUTING_WHERE_CMP); if (cmp == WHERE_EQ || cmp == WHERE_IN) { log.debug("handle where-value key={}", cmp); - final List set = map.computeIfAbsent(fd, k -> new ArrayList<>()); - set.add(((Param) query).getValue()); + final List lst = map.computeIfAbsent(fd, k -> new ArrayList<>()); + lst.add(param.getValue()); } } } @@ -369,10 +396,14 @@ private void handleInsert(VisitContext context) { handleTable(context, (TableImpl) query); } // QueryPartCollectionView - else if (clause == Clause.INSERT_INSERT_INTO && query instanceof final Collection col) { + else if (clause == Clause.INSERT_INSERT_INTO && query instanceof Collection col) { final Set fds = (Set) context.data(ContextKey.EXECUTING_FIELD_KEY); if (fds == null) { - log.debug("should not be here, insert-table without key"); + log.warn("should not be here, insert-table without key"); + return; + } + if (fds.isEmpty()) { + log.debug("skip careless field in insert"); return; } @@ -390,37 +421,59 @@ else if (clause == Clause.INSERT_INSERT_INTO && query instanceof final Collectio } if (cnt > 0) { log.debug("handle insert-fields. count={}", cnt); - context.data(ContextKey.EXECUTING_INSERT_CNT, new AtomicInteger(0)); context.data(ContextKey.EXECUTING_INSERT_IDX, idx); + context.data(ContextKey.EXECUTING_INSERT_CNT, new AtomicInteger(0)); } } - else if (clause == Clause.FIELD_VALUE && query instanceof Param) { - final AtomicInteger cnt = (AtomicInteger) context.data(ContextKey.EXECUTING_INSERT_CNT); - if (cnt == null) { - log.debug("should not be here, insert-fields without cnt"); - return; - } + else if (clause == Clause.FIELD_VALUE && query instanceof Param param) { final Map idx = (Map) context.data(ContextKey.EXECUTING_INSERT_IDX); if (idx == null) { log.debug("skip careless insert-fields without index"); return; } + + final AtomicInteger cnt = (AtomicInteger) context.data(ContextKey.EXECUTING_INSERT_CNT); + if (cnt == null) { + log.warn("should not be here, insert-fields without cnt"); + return; + } + final String name = idx.get(cnt.incrementAndGet()); if (name == null) { log.debug("skip careless insert-field not in index"); } else { - final Map> map = (Map>) context.data(ContextKey.EXECUTING_FIELD_MAP); - if (map == null) { - log.debug("should not be here, insert-field without map"); + final Map> field = (Map>) context.data(ContextKey.EXECUTING_FIELD_MAP); + if (field == null) { + log.warn("should not be here, insert-field without field"); } else { - final List set = map.computeIfAbsent(name, k -> new ArrayList<>()); - set.add(((Param) query).getValue()); + final List lst = field.computeIfAbsent(name, k -> new ArrayList<>()); + lst.add(param.getValue()); log.debug("handle insert-field={} with value", name); } } } + else if (clause == Clause.INSERT_ON_DUPLICATE_KEY_UPDATE && query instanceof Keyword) { + context.data(ContextKey.EXECUTING_INSERT_UPD, Boolean.TRUE); + } + } + + @Override + public boolean accept(@NotNull Class source, @NotNull Cud cud, @NotNull String table) { + // this class or no handler + if (source == this.getClass() || handlers.isEmpty()) return false; + // careless table + final Set fld = tableField.get(table); + if (fld == null) return false; + + // cud type matching + if (create && (cud == Cud.Create || cud == Cud.Unsure)) return true; + if (update && (cud == Cud.Update || cud == Cud.Unsure)) return true; + if (delete && (cud == Cud.Delete || cud == Cud.Unsure)) return true; + + // default + return false; } @Nullable diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessJooqConfiguration.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessJooqConfiguration.java index ff084caa4..432e4b99a 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessJooqConfiguration.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessJooqConfiguration.java @@ -69,13 +69,17 @@ public VisitListenerProvider jooqAutoQualifyFieldListener() { @Bean @ConditionalOnProperty(name = FacelessJooqEnabledProp.Key$listenTableCud, havingValue = "true") @Order(OrderedFacelessConst.JooqTableCudListener) - public VisitListenerProvider jooqTableCudListener(ObjectProvider handlers, FacelessJooqCudProp prop) { - final List hdl = handlers.orderedStream().collect(Collectors.toList()); - final String names = hdl.stream().map(it -> it.getClass().getName()).collect(Collectors.joining(",")); - log.info("FacelessJooq spring-bean jooqTableCudListener with handler=" + names); + public VisitListenerProvider jooqTableCudListener(FacelessJooqCudProp prop, List handlers) { final TableCudListener listener = new TableCudListener(); - listener.setHandlers(hdl); - listener.setInsert(prop.isInsert()); + + final String names = handlers.stream().map(it -> it.getClass().getName()).collect(Collectors.joining(",")); + log.info("FacelessJooq spring-bean jooqTableCudListener with handler=" + names); + for (WingsTableCudHandler handler : handlers) { + handler.register(listener); + } + + listener.setHandlers(handlers); + listener.setCreate(prop.isCreate()); listener.setUpdate(prop.isUpdate()); listener.setDelete(prop.isDelete()); listener.setTableField(prop.getTable()); diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/prop/FacelessJooqCudProp.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/prop/FacelessJooqCudProp.java index ca15e1775..aca6bae8b 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/prop/FacelessJooqCudProp.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/spring/prop/FacelessJooqCudProp.java @@ -1,12 +1,18 @@ package pro.fessional.wings.faceless.spring.prop; import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import static pro.fessional.wings.silencer.spring.help.CommonPropHelper.DisabledValue; + /** * CUD listener settings for jooq. * spring-wings-enabled-79.properties @@ -17,17 +23,18 @@ */ @Data @ConfigurationProperties(FacelessJooqCudProp.Key) +@Slf4j public class FacelessJooqCudProp { public static final String Key = "wings.faceless.jooq.cud"; /** - * Whether to listen to insert + * Whether to listen to create * - * @see #Key$insert + * @see #Key$create */ - private boolean insert = true; - public static final String Key$insert = Key + ".insert"; + private boolean create = true; + public static final String Key$create = Key + ".create"; /** * Whether to listen to update @@ -46,14 +53,41 @@ public class FacelessJooqCudProp { public static final String Key$delete = Key + ".delete"; /** - * Listening tables and their fields. + * Listening tables and their fields. `empty` means no fields are recorded, `-` means this table is ignored. * CUD listens to tables and fields, both tables and fields are case-sensitive. * + * @see pro.fessional.wings.silencer.spring.help.CommonPropHelper#DisabledValue * @see #Key$table */ private Map> table = Collections.emptyMap(); public static final String Key$table = Key + ".table"; + public void setTable(@NotNull Map> table) { + try { + final Iterator>> it = table.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry> en = it.next(); + if (en.getValue().contains(DisabledValue)) { + log.info("remove disable value for table={}", en.getKey()); + it.remove(); + } + } + this.table = table; + } + catch (Exception e) { + Map> temp = new LinkedHashMap<>(); + for (Map.Entry> en : table.entrySet()) { + if (en.getValue().contains(DisabledValue)) { + log.info("remove disable value for table={}", en.getKey()); + } + else { + temp.put(en.getKey(), en.getValue()); + } + } + this.table = temp; + } + } + /** * default fields to be ignored by JournalDiff. * Tables are case-sensitive, fields are case-insensitive, `default` means all tables, otherwise specific tables. diff --git a/wings/faceless-jooq/src/main/resources/wings-conf/wings-jooq-cud-79.properties b/wings/faceless-jooq/src/main/resources/wings-conf/wings-jooq-cud-79.properties index 0fd1a3aa5..2d80bff84 100644 --- a/wings/faceless-jooq/src/main/resources/wings-conf/wings-jooq-cud-79.properties +++ b/wings/faceless-jooq/src/main/resources/wings-conf/wings-jooq-cud-79.properties @@ -1,5 +1,5 @@ -## Whether to listen to insert -wings.faceless.jooq.cud.insert=true +## Whether to listen to create +wings.faceless.jooq.cud.create=true ## Whether to listen to update wings.faceless.jooq.cud.update=true diff --git a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqTableCudListenerTest.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqTableCudListenerTest.java index ef5d8ae13..01d143548 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqTableCudListenerTest.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqTableCudListenerTest.java @@ -105,7 +105,7 @@ public void test1Create() { "insert ignore into"); testcaseNotice("单个插入 replace"); - assertCud(false, Cud.Create, singletonList(singletonList(301L)), () -> testDao.insertInto(pojo, false), + assertCud(false, Cud.Update, singletonList(singletonList(301L)), () -> testDao.insertInto(pojo, false), "duplicate key update"); final TstShardingTable t = testDao.getTable(); diff --git a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/service/WingsTableCudHandlerTest.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/service/WingsTableCudHandlerTest.java index 4c18943e2..00dd97f4d 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/service/WingsTableCudHandlerTest.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/service/WingsTableCudHandlerTest.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -29,20 +30,21 @@ public void reset() { } @Override - public void handle(@NotNull Cud c, @NotNull String t, @NotNull Map> f) { + public void handle(@NotNull Class s, @NotNull Cud c, @NotNull String t, @NotNull Supplier>> field) { + final Map> f = field.get(); this.cud.add(c); this.table.add(t); this.field.add(f); if (f.isEmpty()) { - log.info("cud={}, table={}", c, t); + log.info("src={}, cud={}, table={}", s, c, t); } else { final List strs = f.entrySet() .stream() .map(e -> e.getKey() + ":" + e.getValue()) .collect(Collectors.toList()); - log.info("Handle cud=" + c + ", table=" + t + ", field=" + String.join(",", strs)); + log.info("Handle src={}, cud={}, table={}, field={}", s, c, t, String.join(",", strs)); } } } diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/event/HazelcastSyncPublisher.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/event/HazelcastSyncPublisher.java index a1f8969fd..cbe6bb659 100644 --- a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/event/HazelcastSyncPublisher.java +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/event/HazelcastSyncPublisher.java @@ -4,22 +4,23 @@ import com.hazelcast.topic.ITopic; import com.hazelcast.topic.Message; import com.hazelcast.topic.MessageListener; +import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.context.ApplicationEventPublisher; import java.util.UUID; /** - * ApplicationEventPublisher辅助类。一般用于非事务Event处理,主要功能: - * ①异步发布。 - * ②IDE提示导航。 - * ③hazelcast的topic(#HazelcastTopic)按SpringEvent模式。 + * ApplicationEventPublisher is a helper. Generally used for non-transactional Event processing, with the following main functions: + * ① Asynchronous publishing. + * ② IDE prompt navigation. + * ③ Hazelcast topic (#HazelcastTopic) in the SpringEvent pattern. * * @author trydofor * @see #HazelcastTopic * @since 2021-06-07 */ - +@Slf4j public class HazelcastSyncPublisher implements ApplicationEventPublisher, MessageListener { public static final String HazelcastTopic = "SlardarApplicationEvent"; @@ -45,6 +46,8 @@ public void publishEvent(@NotNull Object event) { @Override public void onMessage(Message message) { - publisher.publishEvent(message.getMessageObject()); + final Object event = message.getMessageObject(); + log.debug("publish event from hazelcast topic, event={}", event); + publisher.publishEvent(event); } } diff --git a/wings/slardar-hazel-session/src/main/resources/extra-conf/hazelcast-server-webmvc.xml b/wings/slardar-hazel-session/src/main/resources/extra-conf/hazelcast-server-webmvc.xml index 2773f1757..a737c8243 100644 --- a/wings/slardar-hazel-session/src/main/resources/extra-conf/hazelcast-server-webmvc.xml +++ b/wings/slardar-hazel-session/src/main/resources/extra-conf/hazelcast-server-webmvc.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-5.1.xsd"> - + diff --git a/wings/testing-database/src/main/resources/wings-conf/spring-datasource-99@h2.properties b/wings/testing-database/src/main/resources/wings-conf/spring-datasource-99@h2.properties index 5c1c2e809..b00bdaa16 100644 --- a/wings/testing-database/src/main/resources/wings-conf/spring-datasource-99@h2.properties +++ b/wings/testing-database/src/main/resources/wings-conf/spring-datasource-99@h2.properties @@ -1,6 +1,7 @@ ## for h2 database compatible testing +## http://www.h2database.com/html/features.html#compatibility spring.datasource.url=jdbc:h2:./${testing.dbname}\ ;USER=${spring.datasource.username};PASSWORD=${spring.datasource.password}\ -;MODE=MySQL;CASE_INSENSITIVE_IDENTIFIERS=TRUE\ +;MODE=MySQL;CASE_INSENSITIVE_IDENTIFIERS=TRUE;IGNORECASE=TRUE\ ;AUTO_RECONNECT=TRUE;AUTO_SERVER=TRUE diff --git a/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/RuntimeConfService.java b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/RuntimeConfService.java index dbf8ebaa3..6c7501d6d 100644 --- a/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/RuntimeConfService.java +++ b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/RuntimeConfService.java @@ -170,14 +170,14 @@ default boolean newObject(Enum key, Object value, String comment, String hand * @param value 初始值 * @param comment 注释 */ - void newObject(String key, Object value, String comment); + boolean newObject(String key, Object value, String comment); - default void newObject(Class key, Object value, String comment) { - newObject(key.getName(), value, comment); + default boolean newObject(Class key, Object value, String comment) { + return newObject(key.getName(), value, comment); } - default void newObject(Enum key, Object value, String comment) { - newObject(EnumConvertor.enum2Str(key), value, comment); + default boolean newObject(Enum key, Object value, String comment) { + return newObject(EnumConvertor.enum2Str(key), value, comment); } } diff --git a/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/impl/RuntimeConfServiceImpl.java b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/impl/RuntimeConfServiceImpl.java index c00677299..79ede674f 100644 --- a/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/impl/RuntimeConfServiceImpl.java +++ b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/conf/impl/RuntimeConfServiceImpl.java @@ -8,10 +8,14 @@ import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; +import pro.fessional.mirana.best.AssertArgs; import pro.fessional.mirana.data.Null; +import pro.fessional.wings.faceless.database.WingsTableCudHandler; +import pro.fessional.wings.faceless.database.WingsTableCudHandler.Cud; import pro.fessional.wings.warlock.caching.CacheEventHelper; import pro.fessional.wings.warlock.database.autogen.tables.WinConfRuntimeTable; import pro.fessional.wings.warlock.database.autogen.tables.daos.WinConfRuntimeDao; @@ -20,10 +24,12 @@ import pro.fessional.wings.warlock.service.conf.RuntimeConfService; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; -import static pro.fessional.wings.warlock.caching.CacheConst.RuntimeConfService.CacheResolver; +import static pro.fessional.wings.warlock.caching.CacheConst.RuntimeConfService.CacheManager; import static pro.fessional.wings.warlock.caching.CacheConst.RuntimeConfService.CacheName; import static pro.fessional.wings.warlock.caching.CacheConst.RuntimeConfService.EventTables; import static pro.fessional.wings.warlock.event.cache.TableChangeEvent.DELETE; @@ -34,7 +40,7 @@ * @since 2022-03-09 */ @Slf4j -@CacheConfig(cacheNames = CacheName, cacheResolver = CacheResolver) +@CacheConfig(cacheNames = CacheName, cacheManager = CacheManager) public class RuntimeConfServiceImpl implements RuntimeConfService { public static final String PropHandler = "prop"; @@ -42,7 +48,10 @@ public class RuntimeConfServiceImpl implements RuntimeConfService { public static final String KryoHandler = "kryo"; @Setter(onMethod_ = {@Autowired}) - private WinConfRuntimeDao winConfRuntimeDao; + protected WinConfRuntimeDao winConfRuntimeDao; + + @Setter(onMethod_ = {@Autowired}) + protected WingsTableCudHandler wingsTableCudHandler; private final Map handlerMap = new LinkedHashMap<>(); @@ -50,25 +59,9 @@ public void addHandler(String type, ConversionService handler) { handlerMap.put(type, handler); } - @SuppressWarnings("unchecked") @Override - @Cacheable public T getObject(String key, TypeDescriptor type) { - if(winConfRuntimeDao.notTableExist()) return null; - - final WinConfRuntimeTable t = winConfRuntimeDao.getTable(); - final Record2 r2 = winConfRuntimeDao - .ctx() - .select(t.Current, t.Handler) - .from(t) - .where(t.Key.eq(key)) - .fetchOne(); - if (r2 != null) { - ConversionService service = handlerMap.get(r2.value2()); - final Object obj = service.convert(r2.value1(), TypeDescriptor.valueOf(String.class), type); - return (T) obj; - } - return null; + return selfLazy.getObjectCache(key, type); } @Override @@ -78,13 +71,23 @@ public void setObject(String key, Object value) { final String handler = winConfRuntimeDao.fetchOne(String.class, t, t.Key.eq(key), t.Handler); ConversionService service = handlerMap.get(handler); final String str = service.convert(value, String.class); - winConfRuntimeDao + AssertArgs.notNull(str, "can not covert value to string, key={}", key); + final int rc = winConfRuntimeDao .ctx() .update(t) .set(t.Current, str) .set(t.Previous, t.Current) .where(t.Key.eq(key)) .execute(); + + if (rc > 0) { + wingsTableCudHandler.handle(this.getClass(), Cud.Update, t, () -> { + Map> field = new HashMap<>(); + field.put(t.Key.getName(), List.of(key)); + field.put(t.Current.getName(), List.of(str)); + return field; + }); + } } @Override @@ -94,6 +97,7 @@ public boolean newObject(String key, Object value, String comment, String handle if (service == null || !service.canConvert(value.getClass(), String.class)) return false; final String str = service.convert(value, String.class); + AssertArgs.notNull(str, "can not covert value to string, key={}", key); final WinConfRuntime pojo = new WinConfRuntime(); pojo.setKey(key); pojo.setCurrent(str); @@ -104,28 +108,65 @@ public boolean newObject(String key, Object value, String comment, String handle final int rc = winConfRuntimeDao.insertInto(pojo, false); log.debug("rc={}, key={}, han={}, val={}", rc, key, handler, str); + + if (rc > 0) { + Cud type = rc == 1 ? Cud.Create : Cud.Update; + final WinConfRuntimeTable t = winConfRuntimeDao.getTable(); + wingsTableCudHandler.handle(this.getClass(), type, t, () -> { + Map> field = new HashMap<>(); + field.put(t.Key.getName(), List.of(key)); + field.put(t.Current.getName(), List.of(str)); + return field; + }); + } + return rc >= 1; } @Override - public void newObject(String key, Object value, String comment) { + public boolean newObject(String key, Object value, String comment) { for (String handler : new ArrayList<>(handlerMap.keySet())) { if (newObject(key, value, comment, handler)) { - return; + return true; } } + return false; + } + + // cache self-invoke + @Setter(onMethod_ = {@Autowired, @Lazy}) + protected RuntimeConfServiceImpl selfLazy; + + @Cacheable + @SuppressWarnings("unchecked") + public T getObjectCache(String key, TypeDescriptor type) { + if (winConfRuntimeDao.notTableExist()) return null; + + final WinConfRuntimeTable t = winConfRuntimeDao.getTable(); + final Record2 r2 = winConfRuntimeDao + .ctx() + .select(t.Current, t.Handler) + .from(t) + .where(t.Key.eq(key)) + .fetchOne(); + + if (r2 != null) { + ConversionService service = handlerMap.get(r2.value2()); + final Object obj = service.convert(r2.value1(), TypeDescriptor.valueOf(String.class), type); + return (T) obj; + } + return null; } @EventListener @CacheEvict(allEntries = true, condition = "#result") public boolean evictAllConfCache(TableChangeEvent event) { - final String tb = CacheEventHelper.fire(event, EventTables, DELETE | UPDATE); + final String tb = CacheEventHelper.receiveTable(event, EventTables, DELETE | UPDATE); if (tb != null) { - log.info("evictAllConfCache by {}, {}", tb, event == null ? -1 : event.getChange()); + log.info("evictAllConfCache by {}, {}", tb, event); return true; } return false; } - } diff --git a/wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfCacheTest.java b/wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfCacheTest.java new file mode 100644 index 000000000..8dd86899b --- /dev/null +++ b/wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfCacheTest.java @@ -0,0 +1,47 @@ +package pro.fessional.wings.warlock.service.conf; + +import lombok.Setter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import pro.fessional.mirana.time.Sleep; +import pro.fessional.wings.silencer.modulate.RunMode; + +import java.util.List; + +/** + * 需要先初始化数据库 Warlock1SchemaCreator#init0Schema + * + * @author trydofor + * @since 2022-03-09 + */ +@SpringBootTest(properties = { + "wings.faceless.jooq.cud.table[win_conf_runtime]=-", + "logging.level.root=debug"}) +class RuntimeConfCacheTest { + + @Setter(onMethod_ = {@Autowired}) + private RuntimeConfService runtimeConfService; + + @Test + void testCache() { + final List arm = List.of(RunMode.Develop, RunMode.Local); + final String key = "RuntimeConfCacheTest.testCache"; + runtimeConfService.newObject(key, arm, "test RunMode"); + final List arm1 = runtimeConfService.getList(key, RunMode.class); + final List arm2 = runtimeConfService.getList(key, RunMode.class); + + runtimeConfService.setObject(key, arm); + Sleep.ignoreInterrupt(2_000); + // check log TableChangeEvent(source=[pro.fessional.wings.warlock.service.conf.impl.RuntimeConfServiceImpl] + + final List rm1 = runtimeConfService.getList(key, RunMode.class); + final List rm2 = runtimeConfService.getList(key, RunMode.class); + + Assertions.assertEquals(arm, arm1); + Assertions.assertSame(arm1, arm2); + Assertions.assertNotSame(arm1, rm1); + Assertions.assertSame(rm1, rm2); + } +} diff --git a/wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfServiceTest.java b/wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfServiceTest.java index 636e1782c..86fb2c422 100644 --- a/wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfServiceTest.java +++ b/wings/warlock-awesome/src/test/java/pro/fessional/wings/warlock/service/conf/RuntimeConfServiceTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.CacheManager; +import pro.fessional.mirana.time.Sleep; import pro.fessional.wings.silencer.modulate.RunMode; import pro.fessional.wings.slardar.cache.WingsCacheHelper; import pro.fessional.wings.warlock.caching.CacheConst; @@ -27,7 +28,9 @@ * @author trydofor * @since 2022-03-09 */ -@SpringBootTest +@SpringBootTest(properties = { + "wings.faceless.jooq.cud.table[win_conf_runtime]=key,current,handler", + "logging.level.root=debug"}) class RuntimeConfServiceTest { @Setter(onMethod_ = {@Autowired}) @@ -50,13 +53,13 @@ void testSimple() { Assertions.assertTrue(names.contains(CacheConst.RuntimeConfService.CacheManager)); Assertions.assertTrue(names.contains(CacheConst.RuntimeConfService.CacheResolver)); - final Map> cas = WingsCacheHelper.getCacheMeta(RuntimeConfServiceImpl.class, "getObject"); - final Set v = cas.get(CacheConst.RuntimeConfService.CacheResolver); + final Map> cas = WingsCacheHelper.getCacheMeta(RuntimeConfServiceImpl.class, "getObjectCache"); + final Set v = cas.get(CacheConst.RuntimeConfService.CacheManager); Assertions.assertNotNull(v); Assertions.assertTrue(v.contains(CacheConst.RuntimeConfService.CacheName)); } - void assertSimple(Class clz, T obj) { + private void assertSimple(Class clz, T obj) { runtimeConfService.newObject(clz, obj, "test " + clz.getSimpleName()); final T obj1 = runtimeConfService.getSimple(clz, clz); Assertions.assertEquals(obj, obj1); @@ -88,6 +91,7 @@ public static class Dto { void testJson() { Dto dto = new Dto(); runtimeConfService.newObject(Dto.class, dto, "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Sleep.ignoreInterrupt(1000); final Dto dto1 = runtimeConfService.getSimple(Dto.class, Dto.class); Assertions.assertEquals(dto, dto1); } @@ -97,6 +101,7 @@ void testKryo() { Dto dto = new Dto(); dto.setLdt(LocalDateTime.now()); runtimeConfService.newObject(Dto.class, dto, "test dto", RuntimeConfServiceImpl.KryoHandler); + Sleep.ignoreInterrupt(1000); final Dto dto1 = runtimeConfService.getSimple(Dto.class, Dto.class); Assertions.assertEquals(dto, dto1); } @@ -104,12 +109,34 @@ void testKryo() { @Test void testMode() { final List arm = List.of(RunMode.Develop, RunMode.Local); - runtimeConfService.newObject(RunMode.class, arm, "test RunMode"); - final List arm1 = runtimeConfService.getEnums(RunMode.class); + final String key = "RuntimeConfServiceTest.testMode"; + runtimeConfService.newObject(key, arm, "test RunMode"); + final List arm1 = runtimeConfService.getList(key, RunMode.class); Assertions.assertEquals(arm, arm1); - runtimeConfService.setObject(RunMode.class, RunMode.Develop); - final RunMode rm1 = runtimeConfService.getEnum(RunMode.class); + runtimeConfService.setObject(key, RunMode.Develop); + final RunMode rm1 = runtimeConfService.getSimple(key, RunMode.class); Assertions.assertEquals(RunMode.Develop, rm1); } + + @Test + void testCache() { + final List arm = List.of(RunMode.Develop, RunMode.Local); + final String key = "RuntimeConfCacheTest.testCache"; + runtimeConfService.newObject(key, arm, "test RunMode"); + final List arm1 = runtimeConfService.getList(key, RunMode.class); + final List arm2 = runtimeConfService.getList(key, RunMode.class); + + runtimeConfService.setObject(key, arm); + Sleep.ignoreInterrupt(2_000); + // check log TableChangeEvent(source=[pro.fessional.wings.warlock.service.event.impl.WingsTableCudHandlerImpl] + + final List rm1 = runtimeConfService.getList(key, RunMode.class); + final List rm2 = runtimeConfService.getList(key, RunMode.class); + + Assertions.assertEquals(arm, arm1); + Assertions.assertSame(arm1, arm2); + Assertions.assertNotSame(arm1, rm1); + Assertions.assertSame(rm1, rm2); + } } diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockPermServiceImpl.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockPermServiceImpl.java index 576fe5fb9..2d7e382cf 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockPermServiceImpl.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockPermServiceImpl.java @@ -1,5 +1,6 @@ package pro.fessional.wings.warlock.service.perm.impl; +import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -9,6 +10,8 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.event.EventListener; +import pro.fessional.wings.faceless.database.WingsTableCudHandler; +import pro.fessional.wings.faceless.database.WingsTableCudHandler.Cud; import pro.fessional.wings.faceless.service.journal.JournalService; import pro.fessional.wings.faceless.service.lightid.LightIdService; import pro.fessional.wings.warlock.caching.CacheEventHelper; @@ -21,11 +24,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import static pro.fessional.wings.warlock.caching.CacheConst.WarlockPermService.CacheManager; import static pro.fessional.wings.warlock.caching.CacheConst.WarlockPermService.CacheName; -import static pro.fessional.wings.warlock.caching.CacheConst.WarlockPermService.CacheResolver; import static pro.fessional.wings.warlock.caching.CacheConst.WarlockPermService.EventTables; import static pro.fessional.wings.warlock.service.grant.PermGrantHelper.unitePermit; @@ -34,87 +38,128 @@ * @since 2021-03-07 */ @Slf4j -@CacheConfig(cacheNames = CacheName, cacheResolver = CacheResolver) +@RequiredArgsConstructor public class WarlockPermServiceImpl implements WarlockPermService { - @Setter(onMethod_ = {@Autowired}) - protected WinPermEntryDao winPermEntryDao; + protected final Caching warlockPermServiceCaching; - @Setter(onMethod_ = {@Autowired}) - protected LightIdService lightIdService; + @Override + public Map loadPermAll() { + return warlockPermServiceCaching.loadPermAll(); + } - @Setter(onMethod_ = {@Autowired}) - protected JournalService journalService; + @Override + public void create(@NotNull String scopes, @NotNull Collection acts) { + warlockPermServiceCaching.create(scopes, acts); + } @Override - @Cacheable - public Map loadPermAll() { - if (winPermEntryDao.notTableExist()) return Collections.emptyMap(); - - final WinPermEntryTable t = winPermEntryDao.getTable(); - - final Map all = winPermEntryDao - .ctx() - .select(t.Id, t.Scopes, t.Action) - .from(t) - .where(t.getOnlyLive()) - .fetch() - .intoMap(Record3::value1, it -> unitePermit(it.value2(), it.value3())); - log.info("loadPermAll size={}", all.size()); - return all; + public void modify(long permId, @NotNull String remark) { + warlockPermServiceCaching.modify(permId, remark); } /** - * 异步清理缓存,event可以为null - * - * @param event 可以为null + * @author trydofor + * @since 2021-03-07 */ - @EventListener - @CacheEvict(allEntries = true, condition = "#result") - public boolean evictPermAllCache(TableChangeEvent event) { - final String tb = CacheEventHelper.fire(event, EventTables); - if (tb != null) { - log.info("evictPermAllCache by {}, {}", tb, event == null ? -1 : event.getChange()); - return true; - } + @Slf4j + @CacheConfig(cacheNames = CacheName, cacheManager = CacheManager) + public static class Caching { - return false; - } + @Setter(onMethod_ = {@Autowired}) + protected WinPermEntryDao winPermEntryDao; - @Override - public void create(@NotNull String scopes, @NotNull Collection acts) { - if (acts.isEmpty()) return; + @Setter(onMethod_ = {@Autowired}) + protected LightIdService lightIdService; + + @Setter(onMethod_ = {@Autowired}) + protected JournalService journalService; + + @Setter(onMethod_ = {@Autowired}) + protected WingsTableCudHandler wingsTableCudHandler; + + @Cacheable + public Map loadPermAll() { + if (winPermEntryDao.notTableExist()) return Collections.emptyMap(); - journalService.commit(Jane.Create, scopes, commit -> { - List pos = new ArrayList<>(acts.size()); final WinPermEntryTable t = winPermEntryDao.getTable(); - for (Act act : acts) { - WinPermEntry po = new WinPermEntry(); - po.setId(lightIdService.getId(t)); - po.setScopes(scopes); - po.setAction(act.getAction()); - po.setRemark(act.getRemark()); - commit.create(po); - pos.add(po); + + final Map all = winPermEntryDao + .ctx() + .select(t.Id, t.Scopes, t.Action) + .from(t) + .where(t.getOnlyLive()) + .fetch() + .intoMap(Record3::value1, it -> unitePermit(it.value2(), it.value3())); + log.info("loadPermAll size={}", all.size()); + return all; + } + + /** + * 异步清理缓存,event可以为null + * + * @param event 可以为null + */ + @EventListener + @CacheEvict(allEntries = true, condition = "#result") + public boolean evictPermAllCache(TableChangeEvent event) { + final String tb = CacheEventHelper.receiveTable(event, EventTables); + if (tb != null) { + log.info("evictPermAllCache by {}, {}", tb, event); + return true; } - log.info("insert perm scope={}, action count={}", scopes, acts.size()); - winPermEntryDao.insert(pos); - }); - } - @Override - public void modify(long permId, @NotNull String remark) { - journalService.commit(Jane.Modify, permId, commit -> { + return false; + } + + public void create(@NotNull String scopes, @NotNull Collection acts) { + if (acts.isEmpty()) return; + final WinPermEntryTable t = winPermEntryDao.getTable(); - final int rc = winPermEntryDao - .ctx() - .update(t) - .set(t.CommitId, commit.getCommitId()) - .set(t.ModifyDt, commit.getCommitDt()) - .set(t.Remark, remark) - .where(t.Id.eq(permId)) - .execute(); - log.info("modify perm remark. permId={}, affect={}", permId, rc); - }); + List pos = new ArrayList<>(acts.size()); + journalService.commit(Jane.Create, scopes, commit -> { + for (Act act : acts) { + WinPermEntry po = new WinPermEntry(); + po.setId(lightIdService.getId(t)); + po.setScopes(scopes); + po.setAction(act.getAction()); + po.setRemark(act.getRemark()); + commit.create(po); + pos.add(po); + } + log.info("insert perm scope={}, action count={}", scopes, acts.size()); + winPermEntryDao.insert(pos); + }); + + wingsTableCudHandler.handle(this.getClass(), Cud.Create, t, () -> { + Map> field = new HashMap<>(); + field.put(t.Id.getName(), pos.stream().map(WinPermEntry::getId).toList()); + return field; + }); + } + + public void modify(long permId, @NotNull String remark) { + final WinPermEntryTable t = winPermEntryDao.getTable(); + int rct = journalService.submit(Jane.Modify, permId, commit -> { + final int rc = winPermEntryDao + .ctx() + .update(t) + .set(t.CommitId, commit.getCommitId()) + .set(t.ModifyDt, commit.getCommitDt()) + .set(t.Remark, remark) + .where(t.Id.eq(permId)) + .execute(); + log.info("modify perm remark. permId={}, affect={}", permId, rc); + return rc; + }); + + if (rct > 0) { + wingsTableCudHandler.handle(this.getClass(), Cud.Update, t, () -> { + Map> field = new HashMap<>(); + field.put(t.Id.getName(), List.of(permId)); + return field; + }); + } + } } } diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockRoleServiceImpl.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockRoleServiceImpl.java index b7e78db8b..12d465b2a 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockRoleServiceImpl.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockRoleServiceImpl.java @@ -1,5 +1,6 @@ package pro.fessional.wings.warlock.service.perm.impl; +import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -12,6 +13,8 @@ import org.springframework.util.StringUtils; import pro.fessional.mirana.data.Null; import pro.fessional.mirana.pain.CodeException; +import pro.fessional.wings.faceless.database.WingsTableCudHandler; +import pro.fessional.wings.faceless.database.WingsTableCudHandler.Cud; import pro.fessional.wings.faceless.service.journal.JournalService; import pro.fessional.wings.faceless.service.lightid.LightIdService; import pro.fessional.wings.warlock.caching.CacheEventHelper; @@ -24,10 +27,12 @@ import pro.fessional.wings.warlock.service.perm.WarlockRoleService; import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import static pro.fessional.wings.warlock.caching.CacheConst.WarlockRoleService.CacheManager; import static pro.fessional.wings.warlock.caching.CacheConst.WarlockRoleService.CacheName; -import static pro.fessional.wings.warlock.caching.CacheConst.WarlockRoleService.CacheResolver; import static pro.fessional.wings.warlock.caching.CacheConst.WarlockRoleService.EventTables; /** @@ -35,96 +40,139 @@ * @since 2021-03-07 */ @Slf4j -@CacheConfig(cacheNames = CacheName, cacheResolver = CacheResolver) +@RequiredArgsConstructor public class WarlockRoleServiceImpl implements WarlockRoleService { - @Setter(onMethod_ = {@Autowired}) - protected WinRoleEntryDao winRoleEntryDao; + protected final Caching warlockRoleServiceCaching; - @Setter(onMethod_ = {@Autowired}) - protected LightIdService lightIdService; - - @Setter(onMethod_ = {@Autowired}) - protected JournalService journalService; + @Override + public Map loadRoleAll() { + return warlockRoleServiceCaching.loadRoleAll(); + } - @Setter(onMethod_ = {@Autowired}) - protected WarlockPermNormalizer permNormalizer; + @Override + public long create(@NotNull String name, String remark) { + return warlockRoleServiceCaching.create(name, remark); + } @Override - @Cacheable - public Map loadRoleAll() { - if (winRoleEntryDao.notTableExist()) return Collections.emptyMap(); - - final WinRoleEntryTable t = winRoleEntryDao.getTable(); - - final Map all = winRoleEntryDao - .ctx() - .select(t.Id, t.Name) - .from(t) - .where(t.getOnlyLive()) - .fetch() - .intoMap(Record2::value1, it -> permNormalizer.role(it.value2())); - log.info("loadRoleAll size={}", all.size()); - return all; + public void modify(long roleId, String remark) { + warlockRoleServiceCaching.modify(roleId, remark); } /** - * 异步清理缓存,event可以为null - * - * @param event 可以为null + * @author trydofor + * @since 2021-03-07 */ - @EventListener - @CacheEvict(allEntries = true, condition = "#result") - public boolean evictRoleAllCache(TableChangeEvent event) { - final String tb = CacheEventHelper.fire(event, EventTables); - if (tb != null) { - log.info("evictRoleAllCache by {}, {}", tb, event == null ? -1 : event.getChange()); - return true; - } + @Slf4j + @CacheConfig(cacheNames = CacheName, cacheManager = CacheManager) + public static class Caching { - return false; - } + @Setter(onMethod_ = {@Autowired}) + protected WinRoleEntryDao winRoleEntryDao; - @Override - public long create(@NotNull String name, String remark) { - if (!StringUtils.hasText(name)) { - throw new CodeException(CommonErrorEnum.AssertEmpty1, "role.name"); - } + @Setter(onMethod_ = {@Autowired}) + protected LightIdService lightIdService; + + @Setter(onMethod_ = {@Autowired}) + protected JournalService journalService; + + @Setter(onMethod_ = {@Autowired}) + protected WarlockPermNormalizer permNormalizer; + + @Setter(onMethod_ = {@Autowired}) + protected WingsTableCudHandler wingsTableCudHandler; + + @Cacheable + public Map loadRoleAll() { + if (winRoleEntryDao.notTableExist()) return Collections.emptyMap(); - return journalService.submit(Jane.Create, name, remark, commit -> { final WinRoleEntryTable t = winRoleEntryDao.getTable(); - long id = lightIdService.getId(t); - WinRoleEntry po = new WinRoleEntry(); - po.setId(id); - po.setName(name); - po.setRemark(Null.notNull(remark)); - commit.create(po); - - try { - winRoleEntryDao.insert(po); + + final Map all = winRoleEntryDao + .ctx() + .select(t.Id, t.Name) + .from(t) + .where(t.getOnlyLive()) + .fetch() + .intoMap(Record2::value1, it -> permNormalizer.role(it.value2())); + log.info("loadRoleAll size={}", all.size()); + return all; + } + + /** + * 异步清理缓存,event可以为null + * + * @param event 可以为null + */ + @EventListener + @CacheEvict(allEntries = true, condition = "#result") + public boolean evictRoleAllCache(TableChangeEvent event) { + final String tb = CacheEventHelper.receiveTable(event, EventTables); + if (tb != null) { + log.info("evictRoleAllCache by {}, {}", tb, event); + return true; } - catch (Exception e) { - log.error("failed to insert role entry. name=" + name + ", remark=" + remark, e); - throw new CodeException(e, CommonErrorEnum.AssertState2, "role.name", name); + + return false; + } + + public long create(@NotNull String name, String remark) { + if (!StringUtils.hasText(name)) { + throw new CodeException(CommonErrorEnum.AssertEmpty1, "role.name"); } - return id; - }); - } + final WinRoleEntryTable t = winRoleEntryDao.getTable(); + final Long rid = journalService.submit(Jane.Create, name, remark, commit -> { + long id = lightIdService.getId(t); + WinRoleEntry po = new WinRoleEntry(); + po.setId(id); + po.setName(name); + po.setRemark(Null.notNull(remark)); + commit.create(po); + + try { + winRoleEntryDao.insert(po); + } + catch (Exception e) { + log.error("failed to insert role entry. name=" + name + ", remark=" + remark, e); + throw new CodeException(e, CommonErrorEnum.AssertState2, "role.name", name); + } + + return id; + }); + + wingsTableCudHandler.handle(this.getClass(), Cud.Create, t, () -> { + Map> field = new HashMap<>(); + field.put(t.Id.getName(), List.of(rid)); + return field; + }); + + return rid; + } - @Override - public void modify(long roleId, String remark) { - journalService.commit(Jane.Modify, roleId, remark, commit -> { + public void modify(long roleId, String remark) { final WinRoleEntryTable t = winRoleEntryDao.getTable(); - final int rc = winRoleEntryDao - .ctx() - .update(t) - .set(t.CommitId, commit.getCommitId()) - .set(t.ModifyDt, commit.getCommitDt()) - .set(t.Remark, remark) - .where(t.Id.eq(roleId)) - .execute(); - log.info("modify perm remark. roleId={}, affect={}", roleId, rc); - }); + int rct = journalService.submit(Jane.Modify, roleId, remark, commit -> { + final int rc = winRoleEntryDao + .ctx() + .update(t) + .set(t.CommitId, commit.getCommitId()) + .set(t.ModifyDt, commit.getCommitDt()) + .set(t.Remark, remark) + .where(t.Id.eq(roleId)) + .execute(); + log.info("modify role remark. roleId={}, affect={}", roleId, rc); + return rc; + }); + + if (rct > 0) { + wingsTableCudHandler.handle(this.getClass(), Cud.Update, t, () -> { + Map> field = new HashMap<>(); + field.put(t.Id.getName(), List.of(roleId)); + return field; + }); + } + } } } diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondAutoRunConfiguration.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondAutoRunConfiguration.java deleted file mode 100644 index 46ebef009..000000000 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondAutoRunConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -package pro.fessional.wings.warlock.spring.bean; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.boot.autoconfigure.AutoConfigureOrder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import pro.fessional.wings.silencer.runner.ApplicationStartedEventRunner; -import pro.fessional.wings.spring.consts.OrderedWarlockConst; -import pro.fessional.wings.warlock.caching.CacheConst; -import pro.fessional.wings.warlock.database.autogen.tables.WinRoleEntryTable; -import pro.fessional.wings.warlock.database.autogen.tables.WinUserAuthnTable; -import pro.fessional.wings.warlock.database.autogen.tables.WinUserBasisTable; - -/** - * @author trydofor - * @since 2019-12-01 - */ -@Configuration(proxyBeanMethods = false) -@AutoConfigureOrder(OrderedWarlockConst.BondAutoRunConfiguration) -public class WarlockBondAutoRunConfiguration { - - private final static Log log = LogFactory.getLog(WarlockBondAutoRunConfiguration.class); - - @Bean // 静态注入,执行一次即可 - public ApplicationStartedEventRunner runnerRegisterCacheConst() { - log.info("WarlockBond spring-runs runnerRegisterCacheConst"); - return new ApplicationStartedEventRunner(OrderedWarlockConst.RunnerRegisterCacheConst, ignored -> { - CacheConst.WarlockAuthnService.EventTables.add(WinUserBasisTable.WinUserBasis.getName()); - CacheConst.WarlockAuthnService.EventTables.add(WinUserAuthnTable.WinUserAuthn.getName()); - log.info("WarlockBond conf WarlockAuthnService.EventTables"); - - CacheConst.WarlockPermService.EventTables.add(WinUserBasisTable.WinUserBasis.getName()); - CacheConst.WarlockPermService.EventTables.add(WinUserAuthnTable.WinUserAuthn.getName()); - log.info("WarlockBond conf WarlockPermService.EventTables"); - - CacheConst.WarlockRoleService.EventTables.add(WinRoleEntryTable.WinRoleEntry.getName()); - log.info("WarlockBond conf WinRoleEntryTable.EventTables"); - }); - } -} diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java index 3f251f4ae..2e7495ab4 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java @@ -2,6 +2,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -9,6 +10,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import pro.fessional.wings.spring.consts.OrderedWarlockConst; +import pro.fessional.wings.warlock.caching.CacheConst; +import pro.fessional.wings.warlock.database.autogen.tables.WinPermEntryTable; +import pro.fessional.wings.warlock.database.autogen.tables.WinRoleEntryTable; import pro.fessional.wings.warlock.service.auth.impl.DefaultDaoAuthnCombo; import pro.fessional.wings.warlock.service.grant.WarlockGrantService; import pro.fessional.wings.warlock.service.grant.impl.WarlockGrantServiceImpl; @@ -37,6 +41,14 @@ public class WarlockBondBeanConfiguration { private final static Log log = LogFactory.getLog(WarlockBondBeanConfiguration.class); + @Autowired + public void autoRegisterCacheConst() { + CacheConst.WarlockPermService.EventTables.add(WinPermEntryTable.WinPermEntry.getName()); + log.info("WarlockBond spring-conf WarlockPermService.EventTables"); + + CacheConst.WarlockRoleService.EventTables.add(WinRoleEntryTable.WinRoleEntry.getName()); + log.info("WarlockBond spring-conf WinRoleEntryTable.EventTables"); + } ///////// AuthZ & AuthN ///////// @Bean @@ -54,18 +66,33 @@ public WarlockGrantService warlockGrantService() { return new WarlockGrantServiceImpl(); } + + @Bean + @ConditionalOnMissingBean(WarlockPermServiceImpl.Caching.class) + public WarlockPermServiceImpl.Caching warlockPermServiceCaching() { + log.info("WarlockBond spring-bean warlockPermServiceCaching"); + return new WarlockPermServiceImpl.Caching(); + } + @Bean @ConditionalOnMissingBean(WarlockPermService.class) - public WarlockPermService warlockPermService() { + public WarlockPermService warlockPermService(WarlockPermServiceImpl.Caching caching) { log.info("WarlockBond spring-bean warlockPermService"); - return new WarlockPermServiceImpl(); + return new WarlockPermServiceImpl(caching); + } + + @Bean + @ConditionalOnMissingBean(WarlockRoleServiceImpl.Caching.class) + public WarlockRoleServiceImpl.Caching warlockRoleServiceCaching() { + log.info("WarlockBond spring-bean warlockRoleServiceCaching"); + return new WarlockRoleServiceImpl.Caching(); } @Bean @ConditionalOnMissingBean(WarlockRoleService.class) - public WarlockRoleService warlockRoleService() { + public WarlockRoleService warlockRoleService(WarlockRoleServiceImpl.Caching caching) { log.info("WarlockBond spring-bean warlockRoleService"); - return new WarlockRoleServiceImpl(); + return new WarlockRoleServiceImpl(caching); } @Bean diff --git a/wings/warlock-bond/src/main/resources/wings-conf/wings-warlock-cud-77.properties b/wings/warlock-bond/src/main/resources/wings-conf/wings-warlock-cud-77.properties index fa7c48407..56e6115d0 100644 --- a/wings/warlock-bond/src/main/resources/wings-conf/wings-warlock-cud-77.properties +++ b/wings/warlock-bond/src/main/resources/wings-conf/wings-warlock-cud-77.properties @@ -1,9 +1,4 @@ ## CUD listens to tables and fields, both tables and fields are case-sensitive. -wings.faceless.jooq.cud.table[win_perm_entry]=id -wings.faceless.jooq.cud.table[win_role_entry]=id -wings.faceless.jooq.cud.table[win_role_grant]=refer_role -wings.faceless.jooq.cud.table[win_user_grant]=refer_user -wings.faceless.jooq.cud.table[win_conf_runtime]=key - -wings.faceless.jooq.cud.table[win_user_authn]=user_id,username,auth_type -wings.faceless.jooq.cud.table[win_user_basis]=id +wings.faceless.jooq.cud.table[win_perm_entry]= +wings.faceless.jooq.cud.table[win_role_entry]= +wings.faceless.jooq.cud.table[win_conf_runtime]=key,current,handler diff --git a/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermServiceTest.java b/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermServiceTest.java new file mode 100644 index 000000000..c56f053a0 --- /dev/null +++ b/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermServiceTest.java @@ -0,0 +1,34 @@ +package pro.fessional.wings.warlock.service.perm; + +import lombok.Setter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import pro.fessional.mirana.time.Sleep; + +import java.util.Map; + +/** + * @author trydofor + * @since 2023-07-03 + */ +@SpringBootTest(properties = {"logging.level.root=debug"}) +class WarlockPermServiceTest { + + @Setter(onMethod_ = {@Autowired}) + protected WarlockPermService warlockPermService; + + @Test + void loadPermAll() { + final Map a1 = warlockPermService.loadPermAll(); + final Map a2 = warlockPermService.loadPermAll(); + Assertions.assertSame(a1, a2); + + warlockPermService.modify(1, "super user"); + Sleep.ignoreInterrupt(2_000); + + final Map a3 = warlockPermService.loadPermAll(); + Assertions.assertNotSame(a1, a3); + } +} diff --git a/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockRoleServiceCacheTest.java b/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockRoleServiceCacheTest.java new file mode 100644 index 000000000..36ecce77f --- /dev/null +++ b/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockRoleServiceCacheTest.java @@ -0,0 +1,36 @@ +package pro.fessional.wings.warlock.service.perm; + +import lombok.Setter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import pro.fessional.mirana.time.Sleep; + +import java.util.Map; + +/** + * @author trydofor + * @since 2023-07-03 + */ +@SpringBootTest(properties = { + "wings.faceless.jooq.cud.table[win_role_entry]=-", + "logging.level.root=debug"}) +class WarlockRoleServiceCacheTest { + + @Setter(onMethod_ = {@Autowired}) + protected WarlockRoleService warlockRoleService; + + @Test + void loadRoleAll() { + final Map a1 = warlockRoleService.loadRoleAll(); + final Map a2 = warlockRoleService.loadRoleAll(); + Assertions.assertSame(a1, a2); + + warlockRoleService.modify(1,"super user"); + Sleep.ignoreInterrupt(2_000); + + final Map a3 = warlockRoleService.loadRoleAll(); + Assertions.assertNotSame(a1, a3); + } +} diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java index bc7f18a91..88ff64e32 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java @@ -17,7 +17,7 @@ import java.util.Locale; /** - * 验证 + * Authentication (AuthN) * * @author trydofor * @since 2021-02-23 @@ -43,7 +43,7 @@ class Details { private String passsalt; private LocalDateTime expiredDt; - public boolean isUninit(){ + public boolean isUninit() { return EmptySugar.asEmptyValue(expiredDt); } } @@ -61,12 +61,12 @@ enum Jane { Details load(@NotNull Enum authType, long userId); /** - * 自动创建用户 + * create the user automatically * - * @param authType 类型 - * @param username 登录用户名 - * @param details 用户和验证信息 - * @return 如果成功,返回用户信息 + * @param authType authn type + * @param username username to login + * @param details user and auth info + * @return user details if success */ @Nullable Details register(@NotNull Enum authType, String username, WingsAuthDetails details); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java index c212123b6..8cc75c956 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java @@ -4,36 +4,23 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheConfig; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.event.EventListener; import org.springframework.core.Ordered; import org.springframework.transaction.annotation.Transactional; import pro.fessional.mirana.time.ThreadNow; import pro.fessional.wings.slardar.context.Now; import pro.fessional.wings.slardar.security.WingsAuthDetails; import pro.fessional.wings.slardar.security.impl.DefaultWingsUserDetails; -import pro.fessional.wings.warlock.caching.CacheEventHelper; -import pro.fessional.wings.warlock.event.cache.TableChangeEvent; import pro.fessional.wings.warlock.service.auth.WarlockAuthnService; import pro.fessional.wings.warlock.service.auth.help.AuthnDetailsMapper; import java.util.Collections; import java.util.List; -import static pro.fessional.wings.warlock.caching.CacheConst.WarlockAuthnService.CacheName; -import static pro.fessional.wings.warlock.caching.CacheConst.WarlockAuthnService.CacheResolver; -import static pro.fessional.wings.warlock.caching.CacheConst.WarlockAuthnService.EventTables; -import static pro.fessional.wings.warlock.event.cache.TableChangeEvent.DELETE; -import static pro.fessional.wings.warlock.event.cache.TableChangeEvent.UPDATE; - /** * @author trydofor * @since 2021-02-23 */ @Slf4j -@CacheConfig(cacheNames = CacheName, cacheResolver = CacheResolver) public class ComboWarlockAuthnService implements WarlockAuthnService { @Setter(onMethod_ = {@Autowired(required = false)}) @@ -43,7 +30,6 @@ public class ComboWarlockAuthnService implements WarlockAuthnService { private List authAutoRegs = Collections.emptyList(); @Override - @Cacheable public Details load(@NotNull Enum authType, String username) { Details dtl = null; for (Combo cmb : combos) { @@ -54,7 +40,6 @@ public Details load(@NotNull Enum authType, String username) { } @Override - @Cacheable public Details load(@NotNull Enum authType, long userId) { Details dtl = null; for (Combo cmb : combos) { @@ -64,22 +49,6 @@ public Details load(@NotNull Enum authType, long userId) { return dtl; } - /** - * 异步清理缓存,event可以为null - * - * @param event 可以为null - */ - @EventListener - @CacheEvict(allEntries = true, condition = "#result") - public boolean evictAllAuthnCache(TableChangeEvent event) { - final String tb = CacheEventHelper.fire(event, EventTables, DELETE | UPDATE); - if (tb != null) { - log.info("evictAllAuthnCache by {}, {}", tb, event == null ? -1 : event.getChange()); - return true; - } - return false; - } - @Override public void auth(DefaultWingsUserDetails userDetails, Details details) { if (userDetails == null || details == null) return; diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheConst.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheConst.java index b772480b3..4d4693283 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheConst.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheConst.java @@ -22,13 +22,6 @@ interface RuntimeConfService { Set EventTables = new HashSet<>(singletonList("win_conf_runtime")); } - interface WarlockAuthnService { - String CacheName = WingsCache.Level.Service + "WarlockAuthnService" + WingsCache.Extend; - String CacheManager = WingsCache.Manager.Memory; - String CacheResolver = WingsCache.Resolver.Memory; - Set EventTables = new HashSet<>(); - } - interface WarlockPermService { String CacheName = WingsCache.Level.Service + "WarlockPermService" + WingsCache.Extend; String CacheManager = WingsCache.Manager.Memory; diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheEventHelper.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheEventHelper.java index bfaed6cba..4b3675173 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheEventHelper.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/caching/CacheEventHelper.java @@ -12,22 +12,38 @@ */ public class CacheEventHelper { - public static String fire(TableChangeEvent event, Collection tables) { - if (event == null) return "NULL"; + + /** + * Check if the table name of event is in the tables collection + * + * @param event Get the table name + * @param tables Check if the table name is in the tables collection + * @return table name + */ + public static String receiveTable(TableChangeEvent event, Collection tables) { + if (event == null) return null; final String tb = event.getTable(); - if (EqualsUtil.inNoCase(tb, tables)) { + if (EqualsUtil.inCaseless(tb, tables)) { return tb; } return null; } - public static String fire(TableChangeEvent event, Collection tables, int change) { - if (event == null) return "NULL"; + /** + * Check if the table name of event is in the tables collection, and the change type must match. + * + * @param event Get the table name + * @param tables Check if the table name is in the tables collection + * @param change change type {@link TableChangeEvent#DELETE} + * @return table name + */ + public static String receiveTable(TableChangeEvent event, Collection tables, int change) { + if (event == null) return null; final String tb = event.getTable(); - if (event.hasChange(change) && EqualsUtil.inNoCase(tb, tables)) { + if (event.hasChange(change) && EqualsUtil.inCaseless(tb, tables)) { return tb; } diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/cache/TableChangeEvent.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/cache/TableChangeEvent.java index 65827cf92..1cd9df3ce 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/cache/TableChangeEvent.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/cache/TableChangeEvent.java @@ -19,9 +19,21 @@ public class TableChangeEvent implements WarlockMetadataEvent { public static final int UPDATE = 1 << 2; public static final int DELETE = 1 << 3; + /** + * event source, something like publisher chain + */ private List source = new LinkedList<>(); + /** + * change type {@link #INSERT} {@link #DELETE} {@link #UPDATE} + */ private int change = 0; + /** + * the table name + */ private String table; + /** + * the filed(column) and its values. VALUES of INSERT; SET and WHERE of UPDATE; WHERE of DELETE; + */ private Map> field = Collections.emptyMap(); public boolean isInsert() { diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/event/impl/WingsTableCudHandlerImpl.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/event/impl/WingsTableCudHandlerImpl.java index 1c5399494..0530486eb 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/event/impl/WingsTableCudHandlerImpl.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/event/impl/WingsTableCudHandlerImpl.java @@ -1,5 +1,6 @@ package pro.fessional.wings.warlock.service.event.impl; +import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -7,34 +8,49 @@ import pro.fessional.wings.faceless.database.WingsTableCudHandler; import pro.fessional.wings.warlock.service.event.TableChangePublisher; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; /** * @author trydofor * @since 2021-06-19 */ -@Slf4j -public class WingsTableCudHandlerImpl implements WingsTableCudHandler { +@Slf4j public class WingsTableCudHandlerImpl implements WingsTableCudHandler { - @Setter(onMethod_ = {@Autowired}) - protected TableChangePublisher tableChangePublisher; + @Setter(onMethod_ = {@Autowired}) protected TableChangePublisher tableChangePublisher; + + @Getter protected final LinkedHashMap, Auto> autoMap = new LinkedHashMap<>(); + + @Override + public void register(@NotNull Auto auto) { + autoMap.put(auto.getClass(), auto); + } @Override - public void handle(@NotNull Cud cud, @NotNull String table, @NotNull Map> field) { - log.debug("handle CUD={}, table={}, field={}", cud, table, field); + public void handle(@NotNull Class source, @NotNull Cud cud, @NotNull String table, @NotNull Supplier>> field) { + for (Auto auto : autoMap.values()) { + if (auto.accept(source, cud, table)) { + log.debug("skip handle, source={}, cud={}, table={}", source, cud, table); + return; + } + } + + final Map> fld = field.get(); + log.debug("handle, source={}, cud={}, table={}, field={}", source, cud, table, fld); if (cud == Cud.Create) { - tableChangePublisher.publishInsert(WingsTableCudHandlerImpl.class, table, field); + tableChangePublisher.publishInsert(source, table, fld); } else if (cud == Cud.Update) { - tableChangePublisher.publishUpdate(WingsTableCudHandlerImpl.class, table, field); + tableChangePublisher.publishUpdate(source, table, fld); } else if (cud == Cud.Delete) { - tableChangePublisher.publishDelete(WingsTableCudHandlerImpl.class, table, field); + tableChangePublisher.publishDelete(source, table, fld); } else { - tableChangePublisher.publishAllCud(WingsTableCudHandlerImpl.class, table, field); + tableChangePublisher.publishAllCud(source, table, fld); } } } From 2ac0f8a6e1dfb0bedc5335d215678c856c2f285b Mon Sep 17 00:00:00 2001 From: trydofor Date: Thu, 6 Jul 2023 15:09:52 +0800 Subject: [PATCH 08/48] =?UTF-8?q?=F0=9F=90=9B=20expose=20SpringSessionBack?= =?UTF-8?q?edSessionRegistry=20#106?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- .../session/HazelcastSessionHelper.java | 34 +++++- .../bean/HazelcastSessionConfiguration.java | 11 +- .../session/WingsSessionInformation.java | 49 -------- .../slardar/session/WingsSessionRegistry.java | 109 ------------------ .../httprest/okhttp/OkHttpClientHelper.java | 107 ++++++++++------- .../controller/user/AuthedUserController.java | 3 + .../controller/api/OkHttpTokenizeTest.java | 22 ++-- .../controller/auth/TestLoginController.java | 4 + .../controller/other/TestOtherController.java | 18 +++ .../warlock/other/Param1ControllerTest.java | 5 +- .../warlock/other/WarlockWatchingTest.java | 6 +- .../warlock/security/AccessDeny302Test.java | 6 +- .../warlock/security/AccessDeny401Test.java | 8 +- .../warlock/security/GuestSessionTest.java | 48 ++++++++ .../wings/warlock/security/MemLoginTest.java | 44 +++++-- 16 files changed, 227 insertions(+), 249 deletions(-) delete mode 100644 wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionInformation.java delete mode 100644 wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionRegistry.java create mode 100644 wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/GuestSessionTest.java diff --git a/observe/docs b/observe/docs index 198a29731..99796242d 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 198a2973171c06446fceb8344d7b5fecfddd79da +Subproject commit 99796242d4b75e275bb71e65ea25a23405a3fc0f diff --git a/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/session/HazelcastSessionHelper.java b/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/session/HazelcastSessionHelper.java index c6ed861e6..ced99cab1 100644 --- a/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/session/HazelcastSessionHelper.java +++ b/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/session/HazelcastSessionHelper.java @@ -3,11 +3,18 @@ import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; import com.hazelcast.query.Predicates; +import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; +import org.springframework.context.ApplicationListener; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.session.MapSession; +import org.springframework.session.Session; import org.springframework.session.SessionRepository; +import org.springframework.session.events.SessionCreatedEvent; +import pro.fessional.wings.slardar.context.SecurityContextUtil; import pro.fessional.wings.slardar.security.DefaultUserId; +import pro.fessional.wings.slardar.security.WingsUserDetails; import java.util.ArrayList; import java.util.Collection; @@ -19,12 +26,13 @@ * @see HttpSessionSecurityContextRepository#SPRING_SECURITY_CONTEXT_KEY * @since 2022-02-24 */ -public class HazelcastSessionHelper implements WingsSessionHelper { +@Slf4j +public class HazelcastSessionHelper implements WingsSessionHelper, ApplicationListener { - private final SessionRepository hazelcastRepository; + private final SessionRepository hazelcastRepository; private final IMap hazelcastSessionMap; - public HazelcastSessionHelper(SessionRepository repository, HazelcastInstance instance, String sessionMap) { + public HazelcastSessionHelper(SessionRepository repository, HazelcastInstance instance, String sessionMap) { this.hazelcastRepository = repository; this.hazelcastSessionMap = instance.getMap(sessionMap); } @@ -47,4 +55,24 @@ public boolean dropSession(String sessionId) { hazelcastRepository.deleteById(sessionId); return true; } + + @Override + public void onApplicationEvent(@NotNull SessionCreatedEvent event) { + final Session session = event.getSession(); + final SecurityContext ctx = getSecurityContext(session); + if (ctx == null) return; + final WingsUserDetails dtl = SecurityContextUtil.getUserDetails(ctx.getAuthentication()); + if (dtl == null) return; + + Session backend = hazelcastRepository.findById(session.getId()); + if (backend == null) { + log.warn("Could not find Session with id={} to set UserId", session.getId()); + } + else { + final long userId = dtl.getUserId(); + log.debug("set Attribute UserIdKey to session, userId={}", userId); + backend.setAttribute(WingsSessionHelper.UserIdKey, userId); + hazelcastRepository.save(backend); + } + } } diff --git a/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastSessionConfiguration.java b/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastSessionConfiguration.java index f35d40df8..ff29dbd84 100644 --- a/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastSessionConfiguration.java +++ b/wings/slardar-hazel-session/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastSessionConfiguration.java @@ -12,10 +12,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.core.session.SessionRegistry; import org.springframework.session.FindByIndexNameSessionRepository; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; +import org.springframework.session.Session; +import org.springframework.session.security.SpringSessionBackedSessionRegistry; import pro.fessional.wings.slardar.session.HazelcastSessionHelper; import pro.fessional.wings.slardar.session.WingsSessionHelper; -import pro.fessional.wings.slardar.session.WingsSessionRegistry; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; /** * @author trydofor @@ -30,14 +31,14 @@ public class HazelcastSessionConfiguration { @Bean @ConditionalOnMissingBean(SessionRegistry.class) - public SessionRegistry sessionRegistry(FindByIndexNameSessionRepository repository, WingsSessionHelper helper) { + public SessionRegistry sessionRegistry(FindByIndexNameSessionRepository repository) { log.info("SlardarHazelSession spring-bean sessionRegistry"); - return new WingsSessionRegistry<>(repository, helper); + return new SpringSessionBackedSessionRegistry<>(repository); } @Bean public WingsSessionHelper wingsSessionHelper( - FindByIndexNameSessionRepository sessionRepository, + FindByIndexNameSessionRepository sessionRepository, HazelcastInstance hzInstance, @Value("${spring.session.hazelcast.map-name:spring:session:sessions}") String mapName) { diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionInformation.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionInformation.java deleted file mode 100644 index 8f7b494ef..000000000 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionInformation.java +++ /dev/null @@ -1,49 +0,0 @@ -package pro.fessional.wings.slardar.session; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.session.Session; -import org.springframework.session.SessionRepository; - -import java.util.Date; - -import static org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME; -import static pro.fessional.wings.slardar.session.WingsSessionHelper.ExpiredKey; - -/** - * @author trydofor - * @since 2022-04-21 - */ -@Slf4j -@Getter -public class WingsSessionInformation extends SessionInformation { - - private final SessionRepository sessionRepository; - private final S session; - - public WingsSessionInformation(S session, SessionRepository repository) { - super(session.getAttribute(PRINCIPAL_NAME_INDEX_NAME), session.getId(), Date.from(session.getLastAccessedTime())); - this.session = session; - this.sessionRepository = repository; - final Boolean expired = session.getAttribute(ExpiredKey); - if (Boolean.TRUE.equals(expired)) { - super.expireNow(); - } - } - - @Override - public void expireNow() { - final String sid = getSessionId(); - log.debug("Expiring session {} for user '{}', presumably because maximum allowed concurrent sessions was exceeded", sid, getPrincipal()); - super.expireNow(); - S session = sessionRepository.findById(sid); - if (session != null) { - session.setAttribute(ExpiredKey, Boolean.TRUE); - sessionRepository.save(session); - } - else { - log.info("Could not find Session with id {} to mark as expired", sid); - } - } -} diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionRegistry.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionRegistry.java deleted file mode 100644 index ee4011a43..000000000 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionRegistry.java +++ /dev/null @@ -1,109 +0,0 @@ -package pro.fessional.wings.slardar.session; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.context.ApplicationListener; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.session.FindByIndexNameSessionRepository; -import org.springframework.session.Session; -import org.springframework.session.events.SessionCreatedEvent; -import org.springframework.session.security.SpringSessionBackedSessionRegistry; -import pro.fessional.wings.slardar.context.SecurityContextUtil; -import pro.fessional.wings.slardar.security.DefaultUserId; -import pro.fessional.wings.slardar.security.WingsUserDetails; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * @author trydofor - * @see SpringSessionBackedSessionRegistry - * @since 2022-02-24 - */ -@Slf4j -public class WingsSessionRegistry implements SessionRegistry, ApplicationListener { - - private final WingsSessionHelper wingsSessionHelper; - private final FindByIndexNameSessionRepository sessionRepository; - - public WingsSessionRegistry(FindByIndexNameSessionRepository repository, WingsSessionHelper helper) { - this.sessionRepository = repository; - this.wingsSessionHelper = helper; - } - - @Override - public List getAllSessions(Object principal, boolean includeExpiredSessions) { - final String name; - long userId = DefaultUserId.Guest; - if (principal instanceof final WingsUserDetails dt) { - name = dt.getUsername(); - userId = dt.getUserId(); - } - else if (principal instanceof AbstractAuthenticationToken) { - name = ((AbstractAuthenticationToken) principal).getName(); - } - else { - name = principal == null ? "" : principal.toString(); - } - - final Collection sessions = sessionRepository.findByPrincipalName(name).values(); - List infos = new ArrayList<>(); - - for (S ss : sessions) { - if (includeExpiredSessions || !wingsSessionHelper.isExpired(ss)) { - // 默认情况principal和userId一对一,实际也支持principal和userId一对多 - if (userId == DefaultUserId.Guest || userId == wingsSessionHelper.getUserId(ss)) { - infos.add(new WingsSessionInformation<>(ss, sessionRepository)); - } - } - } - - return infos; - } - - @Override - public List getAllPrincipals() { - throw new UnsupportedOperationException("no need"); - } - - @Override - public SessionInformation getSessionInformation(String sessionId) { - S ss = sessionRepository.findById(sessionId); - return ss == null ? null : new WingsSessionInformation<>(ss, sessionRepository); - } - - @Override - public void refreshLastRequest(String sessionId) { - } - - @Override - public void registerNewSession(String sessionId, Object principal) { - } - - @Override - public void removeSessionInformation(String sessionId) { - } - - // - @Override - public void onApplicationEvent(@NotNull SessionCreatedEvent event) { - final Session session = event.getSession(); - final SecurityContext ctx = wingsSessionHelper.getSecurityContext(session); - if (ctx == null) return; - final WingsUserDetails dtl = SecurityContextUtil.getUserDetails(ctx.getAuthentication()); - if (dtl == null) return; - - S backend = sessionRepository.findById(session.getId()); - if (backend == null) { - log.warn("Could not find Session with id={} to set UserId", session.getId()); - } - else { - backend.setAttribute(WingsSessionHelper.UserIdKey, dtl.getUserId()); - sessionRepository.save(backend); - } - } -} diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientHelper.java index 4eb7eb6d1..a143f3bd3 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientHelper.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientHelper.java @@ -1,6 +1,9 @@ package pro.fessional.wings.slardar.httprest.okhttp; import okhttp3.Call; +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -18,10 +21,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.function.BiFunction; /** - * 保持,谁用response谁关闭,采用 try-close模式 + * Who uses it, who should close it, in a try-close pattern. * * @author trydofor * @since 2020-06-02 @@ -34,7 +38,7 @@ private static final class DefaultClientHolder { } /** - * 静态全局的默认初始化的 + * global static client */ @NotNull public static OkHttpClient staticClient() { @@ -44,7 +48,7 @@ public static OkHttpClient staticClient() { protected static OkHttpClient SpringClient; /** - * 注入的Spring Bean + * inject Spring Configured client */ @NotNull public static OkHttpClient springClient() { @@ -55,61 +59,61 @@ public static OkHttpClient springClient() { public static final RequestBody EMPTY = RequestBody.create("", OkHttpMediaType.ALL_VALUE); @NotNull - public static MultipartBody.Builder postFile(String key, File file) { + public static MultipartBody.Builder postFile(@NotNull String key, @NotNull File file) { return new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart(key, file.getName(), RequestBody.create(file, OkHttpMediaType.MULTIPART_FORM_DATA_VALUE)); } @NotNull - public static MultipartBody.Builder postFile(String key, byte[] file, String fileName) { + public static MultipartBody.Builder postFile(@NotNull String key, byte @NotNull [] file, @NotNull String fileName) { return new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart(key, fileName, RequestBody.create(file, OkHttpMediaType.MULTIPART_FORM_DATA_VALUE)); } @NotNull - public static MultipartBody.Builder postFile(String key, InputStream file, String fileName) { + public static MultipartBody.Builder postFile(@NotNull String key, @NotNull InputStream file, @NotNull String fileName) { return postFile(key, InputStreams.readBytes(file), fileName); } @NotNull - public static String postFile(OkHttpClient client, String url, String key, File file) { + public static String postFile(@NotNull OkHttpClient client, @NotNull String url, @NotNull String key, @NotNull File file) { return postFile((Call.Factory) client, url, postFile(key, file).build()); } @NotNull - public static String postFile(Call.Factory callFactory, String url, String key, File file) { + public static String postFile(@NotNull Call.Factory callFactory, @NotNull String url, @NotNull String key, @NotNull File file) { return postFile(callFactory, url, postFile(key, file).build()); } @NotNull - public static String postFile(OkHttpClient client, String url, String key, byte[] file, String fileName) { + public static String postFile(@NotNull OkHttpClient client, @NotNull String url, @NotNull String key, byte @NotNull [] file, @NotNull String fileName) { return postFile((Call.Factory) client, url, postFile(key, file, fileName).build()); } @NotNull - public static String postFile(Call.Factory callFactory, String url, String key, byte[] file, String fileName) { + public static String postFile(@NotNull Call.Factory callFactory, @NotNull String url, @NotNull String key, byte @NotNull [] file, @NotNull String fileName) { return postFile(callFactory, url, postFile(key, file, fileName).build()); } @NotNull - public static String postFile(OkHttpClient client, String url, String key, InputStream file, String fileName) { + public static String postFile(@NotNull OkHttpClient client, @NotNull String url, @NotNull String key, @NotNull InputStream file, @NotNull String fileName) { return postFile((Call.Factory) client, url, postFile(key, file, fileName).build()); } @NotNull - public static String postFile(Call.Factory callFactory, String url, String key, InputStream file, String fileName) { + public static String postFile(@NotNull Call.Factory callFactory, @NotNull String url, @NotNull String key, @NotNull InputStream file, @NotNull String fileName) { return postFile(callFactory, url, postFile(key, file, fileName).build()); } @NotNull - public static String postFile(OkHttpClient client, String url, MultipartBody body) { + public static String postFile(@NotNull OkHttpClient client, @NotNull String url, @NotNull MultipartBody body) { return postFile((Call.Factory) client, url, body); } @NotNull - public static String postFile(Call.Factory callFactory, String url, MultipartBody body) { + public static String postFile(@NotNull Call.Factory callFactory, @NotNull String url, @NotNull MultipartBody body) { Request.Builder builder = new Request.Builder() .url(url) .post(body); @@ -122,17 +126,17 @@ public static String postFile(Call.Factory callFactory, String url, MultipartBod } @NotNull - public static String postJson(OkHttpClient client, String url, CharSequence json) { + public static String postJson(@NotNull OkHttpClient client, @NotNull String url, @Nullable CharSequence json) { return executeJson(client, url, json, "POST"); } @NotNull - public static String postJson(Call.Factory callFactory, String url, CharSequence json) { + public static String postJson(@NotNull Call.Factory callFactory, @NotNull String url, @Nullable CharSequence json) { return executeJson(callFactory, url, json, "POST"); } @Nullable - public static ResponseBody extract(Response response) { + public static ResponseBody extract(@Nullable Response response) { if (response != null && response.isSuccessful()) { return response.body(); } @@ -140,27 +144,27 @@ public static ResponseBody extract(Response response) { } @NotNull - public static String extractString(Response response) throws IOException { + public static String extractString(@Nullable Response response) throws IOException { ResponseBody body = extract(response); return extractString(body); } @Nullable @Contract("_,false->!null") - public static String extractString(Response response, boolean nullWhenThrow) { + public static String extractString(@Nullable Response response, boolean nullWhenThrow) { ResponseBody body = extract(response); return extractString(body, nullWhenThrow); } @NotNull - public static String extractString(ResponseBody body) throws IOException { + public static String extractString(@Nullable ResponseBody body) throws IOException { if (body == null) return Null.Str; return body.string(); } @Nullable @Contract("_,false->!null") - public static String extractString(ResponseBody body, boolean nullWhenThrow) { + public static String extractString(@Nullable ResponseBody body, boolean nullWhenThrow) { try { return extractString(body); } @@ -174,15 +178,15 @@ public static String extractString(ResponseBody body, boolean nullWhenThrow) { } } - public static byte @NotNull [] download(OkHttpClient client, String url) { + public static byte @NotNull [] download(@NotNull OkHttpClient client, @NotNull String url) { return download((Call.Factory) client, url, "GET"); } - public static byte @NotNull [] download(Call.Factory callFactory, String url) { + public static byte @NotNull [] download(@NotNull Call.Factory callFactory, @NotNull String url) { return download(callFactory, url, "GET"); } - public static byte @NotNull [] download(OkHttpClient client, String url, String method) { + public static byte @NotNull [] download(@NotNull OkHttpClient client, @NotNull String url, @NotNull String method) { return download((Call.Factory) client, url, method); } @@ -192,11 +196,11 @@ public static String extractString(ResponseBody body, boolean nullWhenThrow) { * @see HttpMethod#requiresRequestBody(String) */ @Nullable - public static RequestBody emptyBody(String method) { + public static RequestBody emptyBody(@NotNull String method) { return HttpMethod.requiresRequestBody(method) ? EMPTY : null; } - public static byte @NotNull [] download(Call.Factory callFactory, String url, String method) { + public static byte @NotNull [] download(@NotNull Call.Factory callFactory, @NotNull String url, @NotNull String method) { Request.Builder builder = new Request.Builder().url(url); builder.method(method, emptyBody(method)); @@ -212,12 +216,12 @@ public static RequestBody emptyBody(String method) { } @NotNull - public static String getText(OkHttpClient client, String url) { + public static String getText(@NotNull OkHttpClient client, @NotNull String url) { return getText((Call.Factory) client, url); } @NotNull - public static String getText(Call.Factory callFactory, String url) { + public static String getText(@NotNull Call.Factory callFactory, @NotNull String url) { Request.Builder builder = new okhttp3.Request.Builder() .url(url) .get(); @@ -230,12 +234,12 @@ public static String getText(Call.Factory callFactory, String url) { } @Contract("_,_,false->!null") - public static String executeString(OkHttpClient client, Request request, boolean nullWhenThrow) { + public static String executeString(@NotNull OkHttpClient client, @NotNull Request request, boolean nullWhenThrow) { return executeString((Call.Factory) client, request, nullWhenThrow); } @Contract("_,_,false->!null") - public static String executeString(Call.Factory callFactory, Request request, boolean nullWhenThrow) { + public static String executeString(@NotNull Call.Factory callFactory, @NotNull Request request, boolean nullWhenThrow) { try (Response response = execute(callFactory, request)) { return extractString(response); @@ -250,11 +254,11 @@ public static String executeString(Call.Factory callFactory, Request request, bo } } - public static String executeJson(OkHttpClient client, String url, CharSequence json, String method) { + public static String executeJson(@NotNull OkHttpClient client, @NotNull String url, @Nullable CharSequence json, @NotNull String method) { return executeJson((Call.Factory) client, url, json, method); } - public static String executeJson(Call.Factory callFactory, String url, CharSequence json, String method) { + public static String executeJson(@NotNull Call.Factory callFactory, @NotNull String url, @Nullable CharSequence json, @NotNull String method) { okhttp3.RequestBody body = json == null ? null : RequestBody.create(json.toString(), OkHttpMediaType.APPLICATION_JSON_VALUE); Request.Builder builder = new okhttp3.Request.Builder() .url(url) @@ -268,22 +272,22 @@ public static String executeJson(Call.Factory callFactory, String url, CharSeque } @NotNull - public static Response execute(OkHttpClient client, Request request) throws IOException { + public static Response execute(@NotNull OkHttpClient client, @NotNull Request request) throws IOException { return execute((Call.Factory) client, request); } @NotNull - public static Response execute(Call.Factory callFactory, Request request) throws IOException { + public static Response execute(@NotNull Call.Factory callFactory, @NotNull Request request) throws IOException { return callFactory.newCall(request).execute(); } @Contract("_,_,false->!null") - public static Response execute(OkHttpClient client, Request request, boolean nullWhenThrow) { + public static Response execute(@NotNull OkHttpClient client, @NotNull Request request, boolean nullWhenThrow) { return execute((Call.Factory) client, request, nullWhenThrow); } @Contract("_,_,false->!null") - public static Response execute(Call.Factory callFactory, Request request, boolean nullWhenThrow) { + public static Response execute(@NotNull Call.Factory callFactory, @NotNull Request request, boolean nullWhenThrow) { try { return execute(callFactory, request); } @@ -298,12 +302,12 @@ public static Response execute(Call.Factory callFactory, Request request, boolea } @Nullable - public static T execute(OkHttpClient client, Request request, BiFunction fun) { + public static T execute(@NotNull OkHttpClient client, @NotNull Request request, @NotNull BiFunction fun) { return execute((Call.Factory) client, request, fun); } @Nullable - public static T execute(Call.Factory callFactory, Request request, BiFunction fun) { + public static T execute(@NotNull Call.Factory callFactory, @NotNull Request request, @NotNull BiFunction fun) { try (final Response res = execute(callFactory, request)) { return fun.apply(res, null); } @@ -313,12 +317,12 @@ public static T execute(Call.Factory callFactory, Request request, BiFunctio } @NotNull - public static Response execute(OkHttpClient client, Request.Builder builder) throws IOException { + public static Response execute(@NotNull OkHttpClient client, @NotNull Request.Builder builder) throws IOException { return execute((Call.Factory) client, builder); } @NotNull - public static Response execute(Call.Factory callFactory, Request.Builder builder) throws IOException { + public static Response execute(@NotNull Call.Factory callFactory, @NotNull Request.Builder builder) throws IOException { if (callFactory instanceof OkHttpBuildableClient) { return ((OkHttpBuildableClient) callFactory).newCall(builder).execute(); } @@ -328,12 +332,12 @@ public static Response execute(Call.Factory callFactory, Request.Builder builder } @Contract("_,_,false->!null") - public static Response execute(OkHttpClient client, Request.Builder builder, boolean nullWhenThrow) { + public static Response execute(@NotNull OkHttpClient client, @NotNull Request.Builder builder, boolean nullWhenThrow) { return execute((Call.Factory) client, builder, nullWhenThrow); } @Contract("_,_,false->!null") - public static Response execute(Call.Factory callFactory, Request.Builder builder, boolean nullWhenThrow) { + public static Response execute(@NotNull Call.Factory callFactory, @NotNull Request.Builder builder, boolean nullWhenThrow) { try { return execute(callFactory, builder); } @@ -348,11 +352,11 @@ public static Response execute(Call.Factory callFactory, Request.Builder builder } @Nullable - public static T execute(OkHttpClient client, Request.Builder builder, BiFunction fun) { + public static T execute(@NotNull OkHttpClient client, @NotNull Request.Builder builder, @NotNull BiFunction fun) { return execute((Call.Factory) client, builder, fun); } - public static T execute(Call.Factory callFactory, Request.Builder builder, BiFunction fun) { + public static T execute(@NotNull Call.Factory callFactory, @NotNull Request.Builder builder, @NotNull BiFunction fun) { try (final Response res = execute(callFactory, builder)) { return fun.apply(res, null); } @@ -361,4 +365,19 @@ public static T execute(Call.Factory callFactory, Request.Builder builder, B } } + public static void clearCookie(@NotNull OkHttpClient client, @NotNull HttpUrl url) { + final CookieJar cookieJar = client.cookieJar(); + final List list = cookieJar.loadForRequest(url).stream().map(it -> { + final Cookie.Builder builder = new Cookie.Builder() + .name(it.name()) + .path(it.path()) + .domain(it.domain()) + .value(it.value()) + .expiresAt(0); + if(it.secure()) builder.secure(); + if(it.httpOnly()) builder.httpOnly(); + return builder.build(); + }).toList(); + cookieJar.saveFromResponse(url, list); + } } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/user/AuthedUserController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/user/AuthedUserController.java index 436993d97..dd72aabd8 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/user/AuthedUserController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/user/AuthedUserController.java @@ -57,6 +57,8 @@ public class AuthedUserController { public static class Dto { @Schema(description = "昵称", example = "trydofor") private String nickname; + @Schema(description = "用户名", example = "trydofor") + private String username; @Schema(description = "语言,参考java.util.Locale", example = "zh-CN") private String locale; @Schema(description = "时区,参考java.time.ZoneId", example = "Asia/Shanghai") @@ -102,6 +104,7 @@ private void fillDetail(WingsUserDetails wd, Dto dto) { dto.setAuthtype(at.name()); } dto.setNickname(wd.getNickname()); + dto.setUsername(wd.getUsername()); dto.setLocale(wd.getLocale().toLanguageTag()); final ZoneId zid = wd.getZoneId(); dto.setZoneid(zid.getId()); diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/OkHttpTokenizeTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/OkHttpTokenizeTest.java index 896f4e00b..56b3756ec 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/OkHttpTokenizeTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/OkHttpTokenizeTest.java @@ -33,8 +33,8 @@ class OkHttpTokenizeTest { @Setter(onMethod_ = {@Autowired}) private SlardarSessionProp slardarSessionProp; - @Setter(onMethod_ = {@Value("${local.server.port}")}) - private int apiPort; + @Setter(onMethod_ = {@Value("http://localhost:${local.server.port}")}) + private String host; @Setter(onMethod_ = {@Autowired}) private OkHttpClient okHttpClient; @@ -49,13 +49,13 @@ public void testOauthAuthorizationCode() { tokenize.setClientSecret(secret); tokenize.setValAccessToken(OkHttpTokenizeOauth.AuthorizationCode); tokenize.setScopes("api1 api2"); - tokenize.setRedirectUri("http://localhost:" + apiPort); - tokenize.setAuthorizeUrl("http://localhost:" + apiPort + urlmapProp.getOauthAuthorize()); - tokenize.setAccessTokenUrl("http://localhost:" + apiPort + urlmapProp.getOauthAccessToken()); + tokenize.setRedirectUri(host); + tokenize.setAuthorizeUrl(host + urlmapProp.getOauthAuthorize()); + tokenize.setAccessTokenUrl(host + urlmapProp.getOauthAccessToken()); OkHttpTokenClient oauthClient = new OkHttpTokenClient(okHttpClient, tokenize); okhttp3.Request request = new okhttp3.Request.Builder() - .url("http://localhost:" + apiPort + "/api/oauth.json") + .url(host + "/api/oauth.json") .post(OkHttpClientHelper.EMPTY) .build(); @@ -69,12 +69,12 @@ public void testOauthClientCredentials() { OkHttpTokenizeOauth tokenize = new OkHttpTokenizeOauth(); tokenize.setClientId(client); tokenize.setClientSecret(secret); - tokenize.setAuthorizeUrl("http://localhost:" + apiPort + urlmapProp.getOauthAuthorize()); - tokenize.setAccessTokenUrl("http://localhost:" + apiPort + urlmapProp.getOauthAccessToken()); + tokenize.setAuthorizeUrl(host + urlmapProp.getOauthAuthorize()); + tokenize.setAccessTokenUrl(host + urlmapProp.getOauthAccessToken()); OkHttpTokenClient oauthClient = new OkHttpTokenClient(okHttpClient, tokenize); okhttp3.Request request = new okhttp3.Request.Builder() - .url("http://localhost:" + apiPort + "/api/oauth.json") + .url(host + "/api/oauth.json") .post(OkHttpClientHelper.EMPTY) .build(); @@ -86,7 +86,7 @@ public void testOauthClientCredentials() { @Test public void testFormLogin() { OkHttpTokenizeLogin tokenize = new OkHttpTokenizeLogin(); - tokenize.setLoginUrl("http://localhost:" + apiPort + "/auth/username/login.json"); + tokenize.setLoginUrl(host + "/auth/username/login.json"); tokenize.setKeyUsername("username"); tokenize.setKeyPassword("password"); tokenize.setUsername("trydofor"); @@ -95,7 +95,7 @@ public void testFormLogin() { OkHttpTokenClient oauthClient = new OkHttpTokenClient(okHttpClient, tokenize); okhttp3.Request request = new okhttp3.Request.Builder() - .url("http://localhost:" + apiPort + "/api/login.json") + .url(host + "/api/login.json") .post(OkHttpClientHelper.EMPTY) .build(); diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/auth/TestLoginController.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/auth/TestLoginController.java index cfcd5faf5..36861c751 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/auth/TestLoginController.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/auth/TestLoginController.java @@ -6,8 +6,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -72,6 +74,8 @@ public Set listAllHold() { */ @GetMapping("/auth/current-principal.json") public R currentPrincipal(@Parameter(hidden = true) @AuthenticationPrincipal WingsUserDetails principal) { + final Authentication authn = SecurityContextHolder.getContext().getAuthentication(); + log.info("current Authentication={}", authn); return R.okData(principal); } diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/other/TestOtherController.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/other/TestOtherController.java index 479ead582..53649448b 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/other/TestOtherController.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/other/TestOtherController.java @@ -1,5 +1,7 @@ package pro.fessional.wings.warlock.controller.other; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; @@ -7,6 +9,8 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -26,6 +30,20 @@ @Slf4j public class TestOtherController { + @RequestMapping("/test/guest-session.json") + public String guestSession(HttpServletRequest request) { + final HttpSession session = request.getSession(true); + final Authentication authn = SecurityContextHolder.getContext().getAuthentication(); + log.info("guest Authentication={}", authn); + return session.getId(); + } + + @RequestMapping("/user/guest-401.json") + public String guest401(HttpServletRequest request) { + final HttpSession session = request.getSession(true); + return session.getId(); + } + @RequestMapping("/test/code-exception.json") public String codeException() { throw new CodeException(false, CommonErrorEnum.AssertEmpty1, "test"); diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Param1ControllerTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Param1ControllerTest.java index f4300c537..9ee5a4092 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Param1ControllerTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Param1ControllerTest.java @@ -25,8 +25,8 @@ @Slf4j public class Param1ControllerTest { - @Setter(onMethod_ = {@Value("${local.server.port}")}) - private int port; + @Setter(onMethod_ = {@Value("http://localhost:${local.server.port}")}) + private String host; @Setter(onMethod_ = {@Autowired}) private OkHttpClient okHttpClient; @@ -95,7 +95,6 @@ private void testObj(Object obj, String... acc) throws JsonProcessingException { } private void testMvc(String uri, String body, String... acc) { - final String host = "http://localhost:" + port; final String str1 = OkHttpClientHelper.postJson(okHttpClient, host + uri, body); boolean ok = false; for (String s : acc) { diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/WarlockWatchingTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/WarlockWatchingTest.java index 88cbd495b..28a463ddf 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/WarlockWatchingTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/WarlockWatchingTest.java @@ -29,20 +29,18 @@ @Slf4j public class WarlockWatchingTest { - @Setter(onMethod_ = {@Value("${local.server.port}")}) - private int port; + @Setter(onMethod_ = {@Value("http://localhost:${local.server.port}")}) + private String host; @Setter(onMethod_ = {@Autowired}) private OkHttpClient okHttpClient; - /** * 查看日志输出 */ @Test public void testWatching() { final StopWatch.Watch watch = Watches.acquire("testWatching"); - final String host = "http://localhost:" + port; final Request.Builder body = new Request.Builder().url(host + "/test/watching.json"); final Response r1 = OkHttpClientHelper.execute(okHttpClient, body, false); Assertions.assertEquals(200, r1.code()); diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny302Test.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny302Test.java index 0d6561728..b2c54b15b 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny302Test.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny302Test.java @@ -21,14 +21,12 @@ @Slf4j class AccessDeny302Test { - @Setter(onMethod_ = {@Value("${local.server.port}")}) - private int port; + @Setter(onMethod_ = {@Value("http://localhost:${local.server.port}")}) + private String host; @Test public void test302() { RestTemplate tmpl = new RestTemplate(); - final String host = "http://localhost:" + port; - RequestEntity entity = RequestEntity .post(host + "/user/authed-user.json") .accept(MediaType.TEXT_HTML) diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny401Test.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny401Test.java index 1ad03411e..6d41ff9d0 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny401Test.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/AccessDeny401Test.java @@ -24,8 +24,8 @@ @Slf4j class AccessDeny401Test { - @Setter(onMethod_ = {@Value("${local.server.port}")}) - private int port; + @Setter(onMethod_ = {@Value("http://localhost:${local.server.port}")}) + private String host; final RestTemplate tmpl = new RestTemplateBuilder() .errorHandler(NopErrorHandler) @@ -33,7 +33,7 @@ class AccessDeny401Test { @Test public void test401Form() { - final String url = "http://localhost:" + port + "/user/authed-user.json"; + final String url = host + "/user/authed-user.json"; RequestEntity entity = RequestEntity .post(url) .accept(MediaType.TEXT_HTML) @@ -47,7 +47,7 @@ public void test401Form() { } @Test public void test401Basic() { - final String url = "http://localhost:" + port + "/user/authed-user.json"; + final String url = host + "/user/authed-user.json"; RequestEntity entity = RequestEntity .post(url) .accept(MediaType.APPLICATION_JSON) diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/GuestSessionTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/GuestSessionTest.java new file mode 100644 index 000000000..4821a93f2 --- /dev/null +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/GuestSessionTest.java @@ -0,0 +1,48 @@ +package pro.fessional.wings.warlock.security; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import pro.fessional.wings.slardar.httprest.okhttp.OkHttpClientHelper; + +/** + * Principal required + * + * @author trydofor + * @since 2021-03-09 + */ + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Slf4j +class GuestSessionTest { + + @Setter(onMethod_ = {@Value("http://localhost:${local.server.port}")}) + private String host; + + @Setter(onMethod_ = {@Autowired}) + private OkHttpClient okHttpClient; + + @Test + public void guestSession() { + final Response r1 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/test/guest-session.json"), false); + String s1 = OkHttpClientHelper.extractString(r1, false); + + final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/test/guest-session.json"), false); + String s2 = OkHttpClientHelper.extractString(r2, false); + Assertions.assertEquals(s1, s2); + } + + @Test + public void guest401() { + try (final Response r1 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/user/guest-401.json"), false)) { + Assertions.assertEquals(401, r1.code()); + } + } +} diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/MemLoginTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/MemLoginTest.java index 5172f3aeb..2cb665724 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/MemLoginTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/MemLoginTest.java @@ -4,11 +4,15 @@ import com.alibaba.fastjson2.TypeReference; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; @@ -26,33 +30,34 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class MemLoginTest { - @Setter(onMethod_ = {@Value("${local.server.port}")}) - private int port; + @Setter(onMethod_ = {@Value("http://localhost:${local.server.port}")}) + private String host; @Setter(onMethod_ = {@Autowired}) private OkHttpClient okHttpClient; @Test + @Order(1) public void testUsernameLogin() { - final String host = "http://localhost:" + port; - + OkHttpClientHelper.clearCookie(okHttpClient, HttpUrl.get(host)); final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder() .url(host + "/auth/username/login.json?username=trydofor&password=moMxVKXxA8Pe9XX9"), false); String login = OkHttpClientHelper.extractString(r2, false); - log.warn("get login res = " + login); + log.info("get login res = " + login); Assertions.assertTrue(login.contains("true")); final Response r3 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder() .url(host + "/auth/list-auth.json"), false); String au3 = OkHttpClientHelper.extractString(r3, false); - log.warn("UsernameLogin auth3={}", au3); + log.info("UsernameLogin auth3={}", au3); final Response r4 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder() .url(host + "/auth/list-hold.json"), false); String au4 = OkHttpClientHelper.extractString(r4, false); - log.warn("UsernameLogin auth4={}", au4); + log.info("UsernameLogin auth4={}", au4); final TypeReference> setRef = new TypeReference<>() {}; final Set st3 = JSON.parseObject(au3, setRef, FastJsonHelper.DefaultReader()); @@ -70,29 +75,44 @@ public void testUsernameLogin() { } @Test + @Order(2) public void testEmailLogin() { - final String host = "http://localhost:" + port; - + OkHttpClientHelper.clearCookie(okHttpClient, HttpUrl.get(host)); final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder() .url(host + "/auth/email/login.json?username=trydofor@qq.com&password=3bvlPy7oQbds28c1"), false); String login = OkHttpClientHelper.extractString(r2, false); - log.warn("get login res = " + login); + log.info("get login res = " + login); Assertions.assertTrue(login.contains("true")); final Response r3 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder() .url(host + "/auth/list-auth.json"), false); String au3 = OkHttpClientHelper.extractString(r3, false); - log.warn("EmailLogin auth3={}", au3); + log.info("EmailLogin auth3={}", au3); final Response r4 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder() .url(host + "/auth/list-hold.json"), false); String au4 = OkHttpClientHelper.extractString(r4, false); - log.warn("EmailLogin auth4={}", au4); + log.info("EmailLogin auth4={}", au4); final Set st3 = JSON.parseObject(au3, new TypeReference>() {}, FastJsonHelper.DefaultReader()); final Set st4 = JSON.parseObject(au4, new TypeReference>() {}, FastJsonHelper.DefaultReader()); Assertions.assertEquals(st3, st4); Assertions.assertTrue(st3.contains("email-perm")); + + // + final Response r5 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder() + .url(host + "/auth/current-principal.json"), false); + String au5 = OkHttpClientHelper.extractString(r5, false); + log.info("current-principal={}", au5); + } + + @Test + @Order(3) + public void testListSession() { + final String r4 = OkHttpClientHelper.postJson(okHttpClient, host + "/user/list-session.json", ""); + log.info("list-session auth4={}", r4); + Assertions.assertTrue(r4.contains("\"username\":\"trydofor@qq.com\"")); + Assertions.assertTrue(r4.contains("\"username\":\"trydofor\"")); } } From 8939f34f2b6e3962efa7935da8a6481f5a03e6a4 Mon Sep 17 00:00:00 2001 From: trydofor Date: Wed, 12 Jul 2023 10:11:27 +0800 Subject: [PATCH 09/48] =?UTF-8?q?=E2=9C=A8=20authn=20failed=20handling=20#?= =?UTF-8?q?104?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- observe/mirana | 2 +- .../slardar/enums/errcode/AuthnErrorEnum.java | 15 ++- .../impl/ComboWingsAuthDetailsSource.java | 24 +++- .../servlet/resolver/WingsLocaleResolver.java | 7 +- .../bean/SlardarLocaleConfiguration.java | 6 +- .../bean/SlardarTerminalConfiguration.java | 3 +- .../wings-i18n/authn-error.properties | 13 ++- .../wings-i18n/authn-error_zh.properties | 13 ++- .../wings/slardar/cache/WingsCache.java | 53 ++++++--- .../slardar/context/LocaleZoneIdUtil.java | 30 +++-- .../slardar/context/TerminalContext.java | 38 ++++++- .../bean/SlardarCacheConfiguration.java | 4 +- .../slardar/spring/prop/SlardarCacheProp.java | 3 +- .../slardar/autodto/AutoDtoHelperTest.java | 6 +- .../admin/AdminAuthnController.java | 21 +++- .../auth/impl/DefaultDaoAuthnCombo.java | 49 ++++++--- .../impl/WarlockUserAuthnServiceImpl.java | 31 +++++- .../bean/WarlockBondBeanConfiguration.java | 10 ++ .../warlock/security/NoncePermLoginTest.java | 14 ++- .../errorhandle/DefaultExceptionResolver.java | 84 ++++++++------ .../event/auth/WarlockMaxFailedEvent.java | 15 --- ...nternalAuthenticationServiceException.java | 31 ++++++ .../security/handler/LoginFailureHandler.java | 103 +++++++++++++----- .../justauth/JustAuthRequestBuilder.java | 1 + .../listener/WarlockFailedLoginListener.java | 22 +++- .../listener/WarlockSuccessLoginListener.java | 1 + .../service/auth/WarlockAuthnService.java | 2 +- .../service/auth/WarlockDangerService.java | 33 ++++++ .../auth/impl/ComboWarlockAuthnService.java | 6 +- .../auth/impl/WarlockDangerServiceImpl.java | 46 ++++++++ .../service/user/WarlockUserAuthnService.java | 3 +- .../service/user/WarlockUserLoginService.java | 2 + .../impl/WarlockUserAuthnServiceDummy.java | 2 +- .../bean/WarlockOtherBeanConfiguration.java | 5 +- .../WarlockSecurityBeanConfiguration.java | 6 +- .../spring/prop/WarlockDangerProp.java | 53 +++++++++ .../wings-warlock-danger-77.properties | 11 ++ 38 files changed, 592 insertions(+), 178 deletions(-) delete mode 100644 wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/event/auth/WarlockMaxFailedEvent.java create mode 100644 wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/error/FailureWaitingInternalAuthenticationServiceException.java create mode 100644 wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockDangerService.java create mode 100644 wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/WarlockDangerServiceImpl.java create mode 100644 wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockDangerProp.java create mode 100644 wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-danger-77.properties diff --git a/observe/docs b/observe/docs index 99796242d..6056500e4 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 99796242d4b75e275bb71e65ea25a23405a3fc0f +Subproject commit 6056500e4ff07af97003a8c0fb5b6b4800fc230a diff --git a/observe/mirana b/observe/mirana index 5ea95bd0d..aa8f8364e 160000 --- a/observe/mirana +++ b/observe/mirana @@ -1 +1 @@ -Subproject commit 5ea95bd0d8b851a011919aff7143a22ff15e2e00 +Subproject commit aa8f8364e006d8ce60015013ad8ea55c50b1fad5 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java index f21dd786a..d5825d16c 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java @@ -2,23 +2,26 @@ import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; +import org.springframework.security.authentication.AccountStatusUserDetailsChecker; import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; import pro.fessional.mirana.data.CodeEnum; /** * @author trydofor * @see AbstractUserDetailsAuthenticationProvider + * @see AccountStatusUserDetailsChecker * @since 2021-03-25 */ @RequiredArgsConstructor public enum AuthnErrorEnum implements CodeEnum { - OnlySupports("AbstractUserDetailsAuthenticationProvider.onlySupports", "仅支持账号密码方式登录"), - BadCredentials("AbstractUserDetailsAuthenticationProvider.badCredentials", "密码错误"), - Locked("AbstractUserDetailsAuthenticationProvider.locked", "账号已锁定"), - Disabled("AbstractUserDetailsAuthenticationProvider.disabled", "账号已禁用"), - Expired("AbstractUserDetailsAuthenticationProvider.expired", "账号已过期"), - CredentialsExpired("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "密码已过期"), + OnlySupports("error.authn.onlySupports", "仅支持账号密码方式登录"), + BadCredentials("error.authn.badCredentials", "密码错误"), + Locked("error.authn.locked", "账号已锁定"), + Disabled("error.authn.disabled", "账号已禁用"), + Expired("error.authn.expired", "账号已过期"), + CredentialsExpired("error.authn.credentialsExpired", "密码已过期"), + FailureWaiting("error.authn.failureWaiting", "密码错误,请{0}秒后重试"), ; private final String code; diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthDetailsSource.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthDetailsSource.java index 9443676ae..dd4f89cb6 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthDetailsSource.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthDetailsSource.java @@ -4,7 +4,9 @@ import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.Ordered; +import org.springframework.web.servlet.LocaleResolver; import pro.fessional.mirana.func.Dcl; import pro.fessional.wings.slardar.security.WingsAuthDetails; import pro.fessional.wings.slardar.security.WingsAuthDetailsSource; @@ -17,27 +19,40 @@ import java.util.Comparator; import java.util.Enumeration; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; /** + * build auth details and set locale + * * @author trydofor * @since 2021-02-17 */ public class ComboWingsAuthDetailsSource implements WingsAuthDetailsSource { - private final List> combos = new ArrayList<>(); - private final Dcl dclCombos = Dcl.of(() -> combos.sort(Comparator.comparingInt(Combo::getOrder))); + protected final List> combos = new ArrayList<>(); + protected final Dcl dclCombos = Dcl.of(() -> combos.sort(Comparator.comparingInt(Combo::getOrder))); @Setter @Getter - private Set ignoredMetaKey = Collections.emptySet(); + protected Set ignoredMetaKey = Collections.emptySet(); @Setter @Getter - private WingsRemoteResolver wingsRemoteResolver = null; + protected WingsRemoteResolver wingsRemoteResolver = null; + + @Setter @Getter + protected LocaleResolver localeResolver; @Override public WingsAuthDetails buildDetails(@NotNull Enum authType, @NotNull HttpServletRequest request) { dclCombos.runIfDirty(); + + // set correct locale for exception + if (localeResolver != null) { + final Locale locale = localeResolver.resolveLocale(request); + LocaleContextHolder.setLocale(locale); + } + WingsAuthDetails detail = null; for (Combo cb : combos) { detail = cb.buildDetails(authType, request); @@ -67,6 +82,7 @@ public final void addAll(Collection> source) { protected void buildMetaData(@NotNull Enum authType, @NotNull HttpServletRequest request, @NotNull WingsAuthDetails details) { final Map meta = details.getMetaData(); final String zone = WingsAuthHelper.getAuthZoneAttribute(request); + meta.put(WingsAuthHelper.AuthType, authType.name()); if (zone != null) { meta.putIfAbsent(WingsAuthHelper.AuthZone, zone); } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsLocaleResolver.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsLocaleResolver.java index f37292027..28606b05a 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsLocaleResolver.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsLocaleResolver.java @@ -28,12 +28,13 @@ import static pro.fessional.wings.slardar.context.TerminalAttribute.ZoneIdByUid; /** - * 按以下优先顺序获得用户语言和时区设置。 - * ① request中被设置的`WINGS.I18N_CONTEXT` + * get current Locale and ZoneId in the following order: + * ① request `WINGS.I18N_CONTEXT` * ② query string `locale`, `zoneid` * ③ http header `Accept-Language`,`Zone-Id` * ④ cookie `WINGS_LOCALE`, `WINGS_ZONEID` - * ⑤ 登录用户的SecurityContext中获得wings设置 + * ⑤ login user's SecurityContext to get Wings settings + * ⑥ system default value * * @author trydofor * @since 2019-06-30 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarLocaleConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarLocaleConfiguration.java index 12da2db96..5fbe9d544 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarLocaleConfiguration.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarLocaleConfiguration.java @@ -8,10 +8,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.LocaleResolver; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; import pro.fessional.wings.slardar.servlet.resolver.WingsLocaleResolver; import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp; import pro.fessional.wings.slardar.spring.prop.SlardarLocaleProp; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; + +import static org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME; /** * @author trydofor @@ -24,7 +26,7 @@ public class SlardarLocaleConfiguration { private final Log log = LogFactory.getLog(SlardarLocaleConfiguration.class); - @Bean + @Bean(LOCALE_RESOLVER_BEAN_NAME) @ConditionalOnClass(LocaleResolver.class) public WingsLocaleResolver wingsLocaleResolver(SlardarLocaleProp conf) { log.info("SlardarWebmvc spring-bean wingsLocaleResolver"); diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java index 705cc6c7e..4305407a6 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java @@ -11,7 +11,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.i18n.TimeZoneAwareLocaleContext; import org.springframework.security.core.GrantedAuthority; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; import pro.fessional.wings.slardar.constants.SlardarServletConst; import pro.fessional.wings.slardar.context.SecurityContextUtil; import pro.fessional.wings.slardar.context.TerminalContext; @@ -21,6 +20,7 @@ import pro.fessional.wings.slardar.servlet.resolver.WingsRemoteResolver; import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp; import pro.fessional.wings.slardar.spring.prop.SlardarTerminalProp; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; import java.util.ArrayList; import java.util.Map; @@ -77,6 +77,7 @@ public TerminalInterceptor.TerminalBuilder securityTerminalBuilder(WingsLocaleRe .timeZone(details.getZoneId()) .user(details.getUserId()) .authType(details.getAuthType()) + .username(details.getUsername()) .authPerm(details.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toSet())); diff --git a/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties index dccfc1d14..0c1ba10a8 100644 --- a/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties +++ b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties @@ -1,7 +1,8 @@ # AbstractUserDetailsAuthenticationProvider -AbstractUserDetailsAuthenticationProvider.onlySupports=Only UsernamePasswordAuthenticationToken is supported -AbstractUserDetailsAuthenticationProvider.badCredentials=Bad credentials -AbstractUserDetailsAuthenticationProvider.locked=User account is locked -AbstractUserDetailsAuthenticationProvider.disabled=User is disabled -AbstractUserDetailsAuthenticationProvider.expired=User account has expired -AbstractUserDetailsAuthenticationProvider.credentialsExpired=User credentials have expired +error.authn.onlySupports=Only UsernamePasswordAuthenticationToken is supported +error.authn.badCredentials=Bad credentials +error.authn.locked=User account is locked +error.authn.disabled=User is disabled +error.authn.expired=User account has expired +error.authn.credentialsExpired=User credentials have expired +error.authn.failureWaiting=Bad credentials, retry after {0}s diff --git a/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties index 20956c3c6..a2a277d6e 100644 --- a/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties +++ b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties @@ -1,6 +1,7 @@ -AbstractUserDetailsAuthenticationProvider.onlySupports=仅支持账号密码方式登录 -AbstractUserDetailsAuthenticationProvider.badCredentials=密码错误 -AbstractUserDetailsAuthenticationProvider.locked=账号已锁定 -AbstractUserDetailsAuthenticationProvider.disabled=账号已禁用 -AbstractUserDetailsAuthenticationProvider.expired=账号已过期 -AbstractUserDetailsAuthenticationProvider.credentialsExpired=密码已过期 +error.authn.onlySupports=仅支持账号密码方式登录 +error.authn.badCredentials=密码错误 +error.authn.locked=账号已锁定 +error.authn.disabled=账号已禁用 +error.authn.expired=账号已过期 +error.authn.credentialsExpired=密码已过期 +error.authn.failureWaiting=密码错误,请{0}秒后重试 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/WingsCache.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/WingsCache.java index d38402bbb..feb2ed934 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/WingsCache.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/WingsCache.java @@ -6,8 +6,6 @@ import java.util.Set; /** - * 不使用enum或interface,可继承使用 - * * @author trydofor * @since 2020-07-30 */ @@ -19,61 +17,82 @@ public interface WingsCache { String Joiner = "~"; /** - * 以此结尾表示扩展为实现类 + * suffix can be expanded to its implement qualified name */ String Extend = "!"; + /** + * use wildcard like `program~*` to match cache name prefix. + */ + String Wildcard = "*"; + class Manager { /** - * 内存缓存,默认 cache2k + * cache in current jvm, default cache2k */ public static final String Memory = "MemoryCacheManager"; /** - * 外部服务缓存,默认hazelcast,可选用redis + * cache in standalone server, default hazelcast. (or redis) */ public static final String Server = "ServerCacheManager"; } class Resolver { - public static final String _Suffix = "Resolver"; + public static final String Suffix = "Resolver"; /** - * 内存缓存,默认 cache2k + * cache in current jvm, default cache2k */ - public static final String Memory = Manager.Memory + _Suffix; + public static final String Memory = Manager.Memory + Suffix; /** - * 外部服务缓存,默认hazelcast,可选用redis + * cache in standalone server, default hazelcast. (or redis) */ - public static final String Server = Manager.Server + _Suffix; + public static final String Server = Manager.Server + Suffix; } class Level { /** - * 程序级,程序或服务运行期间 + * Program level, during program or service operation */ public static final String Forever = "program" + Joiner; /** - * 通常,1天 + * General, 1 day */ public static final String General = "general" + Joiner; /** - * 服务级,1小时 + * Service, 1 hour */ public static final String Service = "service" + Joiner; /** - * 会话级,10分钟 + * Session, 10 minutes */ public static final String Session = "session" + Joiner; public static String join(String... part) { return String.join(Joiner, part); } + + public static String forever(String... part) { + return Forever + String.join(Joiner, part); + } + + public static String general(String... part) { + return General + String.join(Joiner, part); + } + + public static String service(String... part) { + return Service + String.join(Joiner, part); + } + + public static String session(String... part) { + return Session + String.join(Joiner, part); + } } interface State { /** - * 获得 缓存的name及size + * get name and size of cache * * @return name-size */ @@ -81,9 +100,9 @@ interface State { Map statsCacheSize(); /** - * 获得缓存keys + * get keys in cache * - * @param name 名字 + * @param name cache name * @return keys */ @NotNull diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/LocaleZoneIdUtil.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/LocaleZoneIdUtil.java index 2f0e1449b..65018368e 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/LocaleZoneIdUtil.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/LocaleZoneIdUtil.java @@ -13,35 +13,33 @@ public class LocaleZoneIdUtil { /** - * 获得当前线程的ZoneId,优先级为TerminalContext,LocaleContextHolder + * Get the ZoneId of the current thread with priority logined TerminalContext, LocaleContextHolder */ public static final Supplier ZoneIdNullable = () -> { - final ZoneId zid; if (TerminalContext.isActive()) { - zid = TerminalContext.get(false).getZoneId(); + final TerminalContext.Context ctx = TerminalContext.get(false); + if (!ctx.isNull()) { + return ctx.getZoneId(); + } } - else { - zid = LocaleContextHolder.getTimeZone().toZoneId(); - } - return zid; + return LocaleContextHolder.getTimeZone().toZoneId(); }; /** - * 获得当前线程的Locale,优先级为TerminalContext,LocaleContextHolder + * Get the Locale of the current thread with priority logined TerminalContext, LocaleContextHolder */ public static final Supplier LocaleNullable = () -> { - final Locale lcl; if (TerminalContext.isActive()) { - lcl = TerminalContext.get(false).getLocale(); - } - else { - lcl = LocaleContextHolder.getLocale(); + final TerminalContext.Context ctx = TerminalContext.get(false); + if (!ctx.isNull()) { + return ctx.getLocale(); + } } - return lcl; + return LocaleContextHolder.getLocale(); }; /** - * 获得当前线程的ZoneId,优先级为TerminalContext,LocaleContextHolder,systemDefault + * Get the ZoneId of the current thread with priority logined TerminalContext, LocaleContextHolder, systemDefault */ public static final Supplier ZoneIdNonnull = () -> { final ZoneId zid = ZoneIdNullable.get(); @@ -49,7 +47,7 @@ public class LocaleZoneIdUtil { }; /** - * 获得当前线程的Locale,优先级为TerminalContext,LocaleContextHolder,systemDefault + * Get the Locale of the current thread with priority logined TerminalContext, LocaleContextHolder, systemDefault */ public static final Supplier LocaleNonnull = () -> { final Locale lcl = LocaleNullable.get(); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/TerminalContext.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/TerminalContext.java index 15188025a..a54b76eab 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/TerminalContext.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/TerminalContext.java @@ -32,7 +32,14 @@ */ public class TerminalContext { - public static final Context Null = new Context(DefaultUserId.Null, null, null, null, null, null); + public static final Context Null = new Context( + DefaultUserId.Null, + Locale.getDefault(), + TimeZone.getDefault(), + Collections.emptyMap(), + pro.fessional.mirana.data.Null.Enm, + pro.fessional.mirana.data.Null.Str, + Collections.emptySet()); /** * no leak, for static and Interceptor clean @@ -169,8 +176,8 @@ public static Context get() { public static Context get(boolean onlyLogin) { Context ctx = TerminalContext.ContextLocal.get(); if (ctx == null) ctx = Null; - if (onlyLogin && ctx.isGuest()) { - throw new IllegalStateException("must login user"); + if (onlyLogin && !ctx.asLogin()) { + throw new IllegalStateException("must login user or guest"); } return ctx; } @@ -188,7 +195,6 @@ public static void login(Context ctx) { ContextLocal.set(ctx); fireContextChange(false, ctx); } - Active = true; } public static void logout() { @@ -225,16 +231,18 @@ public static class Context { private final Locale locale; private final TimeZone timeZone; private final Enum authType; + private final String username; private final Set authPerm; private final Map, Object> terminal; public Context(long userId, Locale locale, TimeZone timeZone, Map, - Object> params, Enum authType, Set authPerm) { + Object> params, Enum authType, String username, Set authPerm) { this.userId = userId; this.locale = locale != null ? locale : DefaultLocale; this.timeZone = timeZone != null ? timeZone : DefaultTimeZone; this.terminal = params != null ? params : Collections.emptyMap(); this.authType = authType != null ? authType : pro.fessional.mirana.data.Null.Enm; + this.username = username != null ? username : pro.fessional.mirana.data.Null.Str; this.authPerm = authPerm != null ? authPerm : Collections.emptySet(); } @@ -252,6 +260,13 @@ public boolean isGuest() { return userId == DefaultUserId.Guest; } + /** + *
userId > DefaultUserId#Guest 
+ */ + public boolean isLogin() { + return userId > DefaultUserId.Guest; + } + /** *
userId >= DefaultUserId#Guest
*/ @@ -283,6 +298,11 @@ public Enum getAuthType() { return authType; } + @NotNull + public String getUsername() { + return username; + } + @NotNull public Set getAuthPerm() { return authPerm; @@ -348,6 +368,7 @@ public static class Builder { private Locale locale; private TimeZone timeZone; private Enum authType; + private String username; private final Set authPerm = new HashSet<>(); private final Map, Object> terminal = new HashMap<>(); @@ -392,6 +413,11 @@ public Builder authType(Enum at) { return this; } + public Builder username(String un) { + username = un; + return this; + } + public Builder authPerm(String pm) { authPerm.add(pm); return this; @@ -441,7 +467,7 @@ public Context build() { if (userId == Null.userId) { throw new IllegalArgumentException("invalid userid"); } - return new Context(userId, locale, timeZone, terminal, authType, authPerm); + return new Context(userId, locale, timeZone, terminal, authType, username, authPerm); } } } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfiguration.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfiguration.java index aa94c8841..03490a504 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfiguration.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfiguration.java @@ -111,13 +111,13 @@ public static class SlardarCachingConfigurerSupport implements CachingConfigurer public CacheManager cacheManager() { final Map resolverMap = new HashMap<>(managers); for (Map.Entry en : resolvers.entrySet()) { - log.info("Slardar find cacheResolver bean=" + en.getKey() + ""); + log.info("Slardar find cacheResolver bean=" + en.getKey()); resolverMap.put(en.getKey(), en.getValue().getCacheManager()); } // 动态注册Bean,cacheResolver for (Map.Entry en : managers.entrySet()) { - final String key = en.getKey() + WingsCache.Resolver._Suffix; + final String key = en.getKey() + WingsCache.Resolver.Suffix; final CacheManager cm = en.getValue(); resolverMap.put(key, cm); if (beanFactory.containsBean(key)) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarCacheProp.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarCacheProp.java index f961eeda7..3eacec2ce 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarCacheProp.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarCacheProp.java @@ -8,6 +8,7 @@ import java.util.Map; import static pro.fessional.wings.slardar.cache.WingsCache.Joiner; +import static pro.fessional.wings.slardar.cache.WingsCache.Wildcard; /** * LRU (Least Recently Used) default, unit is second, 0=infinitely @@ -104,7 +105,7 @@ public static class Conf { // ///////////////// public static String wildcard(String level) { - return WingsCache.Level.join(level, "*"); + return WingsCache.Level.join(level, Wildcard); } public static boolean inLevel(String name, String level) { diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/autodto/AutoDtoHelperTest.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/autodto/AutoDtoHelperTest.java index e2d580812..6bed93a6e 100644 --- a/wings/slardar/src/test/java/pro/fessional/wings/slardar/autodto/AutoDtoHelperTest.java +++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/autodto/AutoDtoHelperTest.java @@ -40,12 +40,13 @@ class AutoDtoHelperTest { */ @Test void autoRequest() { + TerminalContext.initActive(true); TerminalContext.Builder builder = new TerminalContext.Builder() .locale(Locale.CHINA) .timeZone(ZONE_JP) .terminal(TerminalAddr, "localhost") .terminal(TerminalAgent, "SpringTest") - .user(-1); + .user(1); TerminalContext.login(builder.build()); I18nItem it = initI18nItem(ZONE_JP); @@ -87,12 +88,13 @@ void autoRequest() { */ @Test void autoResponse() { + TerminalContext.initActive(true); TerminalContext.Builder builder = new TerminalContext.Builder() .locale(Locale.US) .timeZone(ZONE_JP) .terminal(TerminalAddr, "localhost") .terminal(TerminalAgent, "SpringTest") - .user(-1); + .user(1); TerminalContext.login(builder.build()); I18nItem it = initI18nItem(ZONE_CN); diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminAuthnController.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminAuthnController.java index 07210d566..c028101eb 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminAuthnController.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminAuthnController.java @@ -11,10 +11,13 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import pro.fessional.mirana.data.R; +import pro.fessional.wings.slardar.security.WingsAuthTypeParser; import pro.fessional.wings.warlock.service.user.WarlockUserAuthnService; import pro.fessional.wings.warlock.spring.prop.WarlockEnabledProp; import pro.fessional.wings.warlock.spring.prop.WarlockUrlmapProp; +import java.util.List; + /** * @author trydofor * @since 2022-10-31 @@ -27,10 +30,14 @@ public class AdminAuthnController { @Setter(onMethod_ = {@Autowired}) protected WarlockUserAuthnService warlockUserAuthnService; + @Setter(onMethod_ = {@Autowired}) + protected WingsAuthTypeParser wingsAuthTypeParser; + @Data public static class Ins { private long userId; private boolean danger; + private List authType; } @Operation(summary = "set/unset user danger status and failed count", description = """ @@ -39,6 +46,7 @@ public static class Ins { ## Params * @param userId - the user * @param danger - set danger or unset + * @param authType - auth type to reset ## Returns * @return {401} 权限不够时 * @return {200} 直接访问或redirect时 @@ -46,7 +54,18 @@ public static class Ins { @PostMapping(value = "${" + WarlockUrlmapProp.Key$adminAuthnDanger + "}") @ResponseBody public R adminAuthnDanger(@RequestBody Ins ins) { - warlockUserAuthnService.dander(ins.getUserId(), ins.isDanger()); + final List tps = ins.getAuthType(); + if (tps == null || tps.isEmpty()) { + warlockUserAuthnService.dander(ins.getUserId(), ins.isDanger()); + } + else { + Enum[] types = new Enum[tps.size()]; + int idx = 0; + for (String tp : tps) { + types[idx++] = wingsAuthTypeParser.parse(tp); + } + warlockUserAuthnService.dander(ins.getUserId(), ins.isDanger(), types); + } return R.OK; } } diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java index d70f88859..6759f01ca 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultDaoAuthnCombo.java @@ -7,9 +7,12 @@ import org.jetbrains.annotations.NotNull; import org.jooq.Condition; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; import pro.fessional.wings.faceless.service.journal.JournalService; import pro.fessional.wings.slardar.context.GlobalAttributeHolder; -import pro.fessional.wings.slardar.event.EventPublishHelper; +import pro.fessional.wings.slardar.context.TerminalContext; +import pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum; import pro.fessional.wings.slardar.security.WingsAuthTypeParser; import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.warlock.constants.WarlockGlobalAttribute; @@ -17,10 +20,14 @@ import pro.fessional.wings.warlock.database.autogen.tables.WinUserBasisTable; import pro.fessional.wings.warlock.database.autogen.tables.daos.WinUserAuthnDao; import pro.fessional.wings.warlock.database.autogen.tables.daos.WinUserBasisDao; -import pro.fessional.wings.warlock.event.auth.WarlockMaxFailedEvent; +import pro.fessional.wings.warlock.security.error.FailureWaitingInternalAuthenticationServiceException; import pro.fessional.wings.warlock.service.auth.WarlockAuthnService; +import pro.fessional.wings.warlock.service.auth.WarlockDangerService; import pro.fessional.wings.warlock.service.user.WarlockUserAuthnService; import pro.fessional.wings.warlock.service.user.WarlockUserLoginService; +import pro.fessional.wings.warlock.spring.prop.WarlockDangerProp; + +import java.util.Locale; /** * @author trydofor @@ -50,8 +57,25 @@ public class DefaultDaoAuthnCombo implements ComboWarlockAuthnService.Combo { @Setter(onMethod_ = {@Autowired}) protected JournalService journalService; + @Setter(onMethod_ = {@Autowired}) + protected MessageSource messageSource; + + @Setter(onMethod_ = {@Autowired}) + protected WarlockDangerProp warlockDangerProp; + + @Setter(onMethod_ = {@Autowired}) + protected WarlockDangerService warlockDangerService; + @Override public WarlockAuthnService.Details load(@NotNull Enum authType, String username) { + final int block = warlockDangerService.check(authType, username); + if (block > 0) { + final Locale locale = LocaleContextHolder.getLocale(); + final String code = AuthnErrorEnum.FailureWaiting.getCode(); + final String message = messageSource.getMessage(code, new Object[]{block}, locale); + throw new FailureWaitingInternalAuthenticationServiceException(block, code, message); + } + if (winUserBasisDao.notTableExist() || winUserAuthnDao.notTableExist()) return null; final WinUserBasisTable user = winUserBasisDao.getAlias(); @@ -104,13 +128,16 @@ public void onSuccess(@NotNull Enum authType, long userId, String details) { .set(ta.FailedCnt, 0) .set(ta.CommitId, commit.getCommitId()) .set(ta.ModifyDt, commit.getCommitDt()) - .where(ta.UserId.eq(userId)) + .where(ta.UserId.eq(userId).and(ta.AuthType.eq(at))) .execute(); }); + // + final String username = TerminalContext.get().getUsername(); + warlockDangerService.allow(authType, username); } @Override - public void onFailure(@NotNull Enum authType, String username) { + public void onFailure(@NotNull Enum authType, String username, String details) { if (username == null || username.isEmpty() || winUserAuthnDao.notTableExist()) return; final String at = wingsAuthTypeParser.parse(authType); @@ -132,14 +159,8 @@ public void onFailure(@NotNull Enum authType, String username) { final long aid = auth.value4(); final int max = auth.value3(); - // - if (cnt > max - 3) { - WarlockMaxFailedEvent evt = new WarlockMaxFailedEvent(); - evt.setCurrent(cnt); - evt.setMaximum(max); - evt.setUserId(uid); - EventPublishHelper.SyncSpring.publishEvent(evt); - } + final int second = (int) (warlockDangerProp.getRetryStep().toSeconds() * cnt); + warlockDangerService.block(authType, username, second); if (cnt > max) { log.info("ignore login failure by reach max-count={}, auth-type={}, username={}", max, at, username); @@ -148,7 +169,7 @@ public void onFailure(@NotNull Enum authType, String username) { journalService.commit(WarlockAuthnService.Jane.Failure, uid, "failed login auth-id=" + aid, commit -> { // 锁账号 - if (cnt >= max) { + if (warlockDangerProp.isMaxFailure() && cnt >= max) { log.info("danger user by reach max-count={}, auth-type={}, username={}", max, at, username); warlockUserAuthnService.dander(uid, true); } @@ -165,7 +186,7 @@ public void onFailure(@NotNull Enum authType, String username) { WarlockUserLoginService.Auth la = new WarlockUserLoginService.Auth(); la.setAuthType(authType); la.setUserId(uid); - la.setDetails(""); + la.setDetails(details); la.setFailed(true); warlockUserLoginService.auth(la); }); diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceImpl.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceImpl.java index f6aaa76b6..30a5a22e4 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceImpl.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceImpl.java @@ -5,6 +5,8 @@ import org.jetbrains.annotations.NotNull; import org.jooq.Condition; import org.jooq.Field; +import org.jooq.Record2; +import org.jooq.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; @@ -25,10 +27,12 @@ import pro.fessional.wings.warlock.database.autogen.tables.pojos.WinUserAuthn; import pro.fessional.wings.warlock.enums.autogen.UserStatus; import pro.fessional.wings.warlock.enums.errcode.CommonErrorEnum; +import pro.fessional.wings.warlock.service.auth.WarlockDangerService; import pro.fessional.wings.warlock.service.user.WarlockUserAuthnService; import pro.fessional.wings.warlock.spring.prop.WarlockSecurityProp; import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -65,6 +69,9 @@ public class WarlockUserAuthnServiceImpl implements WarlockUserAuthnService { @Setter(onMethod_ = {@Autowired}) protected WarlockSecurityProp warlockSecurityProp; + @Setter(onMethod_ = {@Autowired}) + protected WarlockDangerService warlockDangerService; + @Override @Transactional public long create(long userId, @NotNull Authn authn) { @@ -199,7 +206,7 @@ public void renew(long userId, @NotNull Renew renew) { } @Override - public void dander(long userId, boolean danger) { + public void dander(long userId, boolean danger, @NotNull Enum... authType) { if (winUserBasisDao.notTableExist()) return; journalService.commit(Jane.Danger, userId, danger, commit -> { @@ -215,14 +222,34 @@ public void dander(long userId, boolean danger) { if (!danger && !winUserAuthnDao.notTableExist()) { final WinUserAuthnTable ta = winUserAuthnDao.getTable(); + Condition cond = ta.UserId.eq(userId); + if (authType.length != 0) { + List ats = new ArrayList<>(authType.length); + for (Enum en : authType) { + ats.add(wingsAuthTypeParser.parse(en)); + } + cond = cond.and(ta.AuthType.in(ats)); + } winUserAuthnDao .ctx() .update(ta) .set(ta.FailedCnt, 0) .set(ta.CommitId, commit.getCommitId()) .set(ta.ModifyDt, commit.getCommitDt()) - .where(ta.UserId.eq(userId)) + .where(cond) .execute(); + + // allow + final Result> r2 = winUserAuthnDao + .ctx() + .select(ta.AuthType, ta.Username) + .from(ta) + .where(cond) + .fetch(); + + for (Record2 r : r2) { + warlockDangerService.allow(wingsAuthTypeParser.parse(r.value1()), r.value2()); + } } }); } diff --git a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java index 2e7495ab4..586196260 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockBondBeanConfiguration.java @@ -13,7 +13,9 @@ import pro.fessional.wings.warlock.caching.CacheConst; import pro.fessional.wings.warlock.database.autogen.tables.WinPermEntryTable; import pro.fessional.wings.warlock.database.autogen.tables.WinRoleEntryTable; +import pro.fessional.wings.warlock.service.auth.WarlockDangerService; import pro.fessional.wings.warlock.service.auth.impl.DefaultDaoAuthnCombo; +import pro.fessional.wings.warlock.service.auth.impl.WarlockDangerServiceImpl; import pro.fessional.wings.warlock.service.grant.WarlockGrantService; import pro.fessional.wings.warlock.service.grant.impl.WarlockGrantServiceImpl; import pro.fessional.wings.warlock.service.perm.WarlockPermService; @@ -26,6 +28,7 @@ import pro.fessional.wings.warlock.service.user.impl.WarlockUserAuthnServiceImpl; import pro.fessional.wings.warlock.service.user.impl.WarlockUserBasisServiceImpl; import pro.fessional.wings.warlock.service.user.impl.WarlockUserLoginServiceImpl; +import pro.fessional.wings.warlock.spring.prop.WarlockDangerProp; import pro.fessional.wings.warlock.spring.prop.WarlockEnabledProp; @@ -51,6 +54,13 @@ public void autoRegisterCacheConst() { } ///////// AuthZ & AuthN ///////// + @Bean + @ConditionalOnMissingBean(WarlockDangerService.class) + public WarlockDangerService warlockDangerService(WarlockDangerProp warlockDangerProp) { + log.info("WarlockBond spring-bean warlockDangerService"); + return new WarlockDangerServiceImpl(warlockDangerProp.getCacheSize(), (int) warlockDangerProp.getCacheTtl().toSeconds()); + } + @Bean @ConditionalOnMissingBean(DefaultDaoAuthnCombo.class) public DefaultDaoAuthnCombo defaultDaoAuthnCombo() { diff --git a/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/security/NoncePermLoginTest.java b/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/security/NoncePermLoginTest.java index 1896d1dae..435ebd4eb 100644 --- a/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/security/NoncePermLoginTest.java +++ b/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/security/NoncePermLoginTest.java @@ -13,6 +13,8 @@ import org.springframework.test.annotation.DirtiesContext; import pro.fessional.wings.slardar.httprest.okhttp.OkHttpClientHelper; +import java.util.Locale; + import static pro.fessional.wings.warlock.security.NoncePermLoginTest.DangerUrl; /** @@ -59,12 +61,14 @@ public void testRootLogin() { @Test public void testDanger() { + log.warn("current locale = {}", Locale.getDefault()); OkHttpClientHelper.postJson(okHttpClient, host + DangerUrl, "{\"userId\":1,\"danger\":true}"); { - final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=root&password=BAD"), false); + final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=root&password=BAD&locale=zh"), false); String login = OkHttpClientHelper.extractString(r2, false); log.warn("testDanger-a get login res = " + login); Assertions.assertTrue(login.contains("false"), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(login.contains("账号已锁定"), "确认i18n设置"); } OkHttpClientHelper.postJson(okHttpClient, host + DangerUrl, "{\"userId\":1,\"danger\":false}"); @@ -81,10 +85,16 @@ public void testDanger() { } for (int i = 0; i < 6; i++) { - final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=root&password=BAD"), false); + final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=root&password=BAD&locale=en"), false); String login = OkHttpClientHelper.extractString(r2, false); log.warn("testDanger-c " + i + " get login res = " + login); + Assertions.assertTrue(login.contains("false"), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + if (i > 2) { + Assertions.assertTrue(login.contains("error.authn.failureWaiting"), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(login.contains("retry after"), "确认i18n设置"); + } } + OkHttpClientHelper.postJson(okHttpClient, host + DangerUrl, "{\"userId\":1,\"danger\":false}"); { final Response r1 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/console-nonce.json?username=root"), false); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java index 9beea24aa..61858e657 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java @@ -1,12 +1,16 @@ package pro.fessional.wings.warlock.errorhandle; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.context.MessageSource; import org.springframework.core.annotation.Order; +import pro.fessional.mirana.best.DummyBlock; +import pro.fessional.mirana.data.DataResult; import pro.fessional.mirana.data.Null; +import pro.fessional.mirana.data.R; import pro.fessional.mirana.pain.CodeException; import pro.fessional.mirana.pain.HttpStatusException; -import pro.fessional.mirana.pain.MessageException; import pro.fessional.mirana.text.JsonTemplate; import pro.fessional.wings.slardar.context.LocaleZoneIdUtil; import pro.fessional.wings.slardar.webmvc.SimpleExceptionResolver; @@ -28,51 +32,61 @@ public class DefaultExceptionResolver extends SimpleExceptionResolver { protected final MessageSource messageSource; + protected final ObjectMapper objectMapper; - public DefaultExceptionResolver(SimpleResponse defaultResponse, MessageSource messageSource) { + public DefaultExceptionResolver(SimpleResponse defaultResponse, MessageSource messageSource, ObjectMapper objectMapper) { super(defaultResponse); this.messageSource = messageSource; + this.objectMapper = objectMapper; } @Override - protected SimpleResponse resolve(Exception ex) { - CodeException cex = null; - HttpStatusException hse = null; - Throwable tmp = ex; - for (; tmp != null; tmp = tmp.getCause()) { - if (tmp instanceof HttpStatusException he) { - hse = he; - cex = he; - break; - } else if (tmp instanceof CodeException ce) { - cex = ce; - break; + protected SimpleResponse resolve(Exception exception) { + try { + Throwable tmp = exception; + for (; tmp != null; tmp = tmp.getCause()) { + if (tmp instanceof HttpStatusException ex) { + return handle(ex); + } + else if (tmp instanceof CodeException ex) { + return handle(ex); + } + else if (tmp instanceof DataResult ex) { + return handle(ex); + } } } - - if (cex == null) { - log.error("uncaught exception, response default", ex); - return defaultResponse; + catch (Exception e) { + DummyBlock.ignore(e); } - if (!(cex instanceof MessageException)) { - log.warn("caught code exception", ex); - } + log.error("unhandled exception, response default", exception); + return defaultResponse; + } - try { - final String code = cex.getCode(); - final String msg = resolveMessage(cex); - final String body = JsonTemplate.obj(obj -> { - obj.putVal("success", false); - obj.putVal("code", code); - obj.putVal("message", msg); - }); - final int sts = hse == null ? defaultResponse.getHttpStatus() : hse.getStatus(); - return new SimpleResponse(sts, defaultResponse.getContentType(), body); - } catch (Exception e) { - log.error("uncaught exception, response default", ex); - return defaultResponse; - } + @SneakyThrows + protected SimpleResponse handle(DataResult dre) { + final R ng = R.ng(dre.getMessage(), dre.getCode(), dre.getData()); + final String body = objectMapper.writeValueAsString(ng); + return new SimpleResponse(defaultResponse.getHttpStatus(), defaultResponse.getContentType(), body); + } + + protected SimpleResponse handle(HttpStatusException cex) { + final String body = JsonTemplate.obj(obj -> { + obj.putVal("success", false); + obj.putVal("code", cex.getCode()); + obj.putVal("message", resolveMessage(cex)); + }); + return new SimpleResponse(cex.getStatus(), defaultResponse.getContentType(), body); + } + + protected SimpleResponse handle(CodeException cex) { + final String body = JsonTemplate.obj(obj -> { + obj.putVal("success", false); + obj.putVal("code", cex.getCode()); + obj.putVal("message", resolveMessage(cex)); + }); + return new SimpleResponse(defaultResponse.getHttpStatus(), defaultResponse.getContentType(), body); } protected String resolveMessage(CodeException ce) { diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/event/auth/WarlockMaxFailedEvent.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/event/auth/WarlockMaxFailedEvent.java deleted file mode 100644 index fd1053697..000000000 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/event/auth/WarlockMaxFailedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package pro.fessional.wings.warlock.event.auth; - -import lombok.Data; -import pro.fessional.wings.warlock.event.WarlockMetadataEvent; - -/** - * @author trydofor - * @since 2021-02-26 - */ -@Data -public class WarlockMaxFailedEvent implements WarlockMetadataEvent { - private int current; - private int maximum; - private long userId; -} diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/error/FailureWaitingInternalAuthenticationServiceException.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/error/FailureWaitingInternalAuthenticationServiceException.java new file mode 100644 index 000000000..476c3d05b --- /dev/null +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/error/FailureWaitingInternalAuthenticationServiceException.java @@ -0,0 +1,31 @@ +package pro.fessional.wings.warlock.security.error; + +import lombok.Getter; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.core.Authentication; +import pro.fessional.mirana.data.DataResult; + +/** + * @author trydofor + * @see ProviderManager#authenticate(Authentication) + * @since 2023-07-10 + */ +@Getter +public class FailureWaitingInternalAuthenticationServiceException extends InternalAuthenticationServiceException implements DataResult { + + private final String code; + private final Integer data; + + public FailureWaitingInternalAuthenticationServiceException(int wait, String code, String message, Throwable cause) { + super(message, cause); + this.code = code; + this.data = wait; + } + + public FailureWaitingInternalAuthenticationServiceException(int wait, String code, String message) { + super(message); + this.code = code; + this.data = wait; + } +} diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/LoginFailureHandler.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/LoginFailureHandler.java index 1dead42eb..c9f964525 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/LoginFailureHandler.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/LoginFailureHandler.java @@ -1,19 +1,35 @@ package pro.fessional.wings.warlock.security.handler; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.Setter; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.security.authentication.AccountExpiredException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import pro.fessional.mirana.best.DummyBlock; +import pro.fessional.mirana.data.DataResult; +import pro.fessional.mirana.data.Null; +import pro.fessional.mirana.data.R; import pro.fessional.mirana.text.StringTemplate; -import pro.fessional.wings.slardar.context.RequestContextUtil; import pro.fessional.wings.slardar.servlet.response.ResponseHelper; -import pro.fessional.wings.warlock.event.auth.WarlockMaxFailedEvent; import pro.fessional.wings.warlock.spring.prop.WarlockSecurityProp; +import static pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum.BadCredentials; +import static pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum.CredentialsExpired; +import static pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum.Disabled; +import static pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum.Expired; +import static pro.fessional.wings.slardar.enums.errcode.AuthnErrorEnum.Locked; + /** * @author trydofor * @since 2021-02-17 @@ -21,40 +37,77 @@ @Slf4j public class LoginFailureHandler implements AuthenticationFailureHandler { - public final static String eventKey = "wings.WarlockMaxFailedEvent.Key"; - @Setter(onMethod_ = {@Autowired}) protected WarlockSecurityProp warlockSecurityProp; + @Setter(onMethod_ = {@Autowired}) + protected ObjectMapper objectMapper; + + @Setter(onMethod_ = {@Autowired}) + protected MessageSource messageSource; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) { - - final String msg; - final Object atr = request.getAttribute(eventKey); - if (atr instanceof WarlockMaxFailedEvent evt) { - int lft = evt.getMaximum() - evt.getCurrent(); - - msg = lft > 0 ? "login failed, " + lft + " times left" : "login failed, and locked"; + String body = null; + try { + if (exception instanceof DataResult dre) { + body = handle(dre); + } } - else { - msg = "login failed"; + catch (Exception e) { + DummyBlock.ignore(e); } - // TODO 更优化的提示信息 - log.debug(msg, exception); + if (body == null) { + body = handle(exception); + } + + ResponseHelper.writeBodyUtf8(response, body); + } - final String mess = StringTemplate.dyn(warlockSecurityProp.getLoginFailureBody()) - .bindStr("{message}", msg) - .toString(); - ResponseHelper.writeBodyUtf8(response, mess); + @SneakyThrows + protected String handle(DataResult dre) { + final R ng = R.ng(dre.getMessage(), dre.getCode(), dre.getData()); + return objectMapper.writeValueAsString(ng); } - @EventListener - public void listenWarlockMaxFailedEvent(WarlockMaxFailedEvent event) { - final HttpServletRequest request = RequestContextUtil.getRequest(); - if (request != null) { - request.setAttribute(eventKey, event); + @SneakyThrows + protected String handle(AuthenticationException exception) { + final String msg; + final String code; + if (exception instanceof BadCredentialsException) { + code = BadCredentials.getCode(); + msg = messageSource.getMessage(code, Null.StrArr, LocaleContextHolder.getLocale()); + } + else if (exception instanceof LockedException) { + code = Locked.getCode(); + msg = messageSource.getMessage(code, Null.StrArr, LocaleContextHolder.getLocale()); + } + else if (exception instanceof DisabledException) { + code = Disabled.getCode(); + msg = messageSource.getMessage(code, Null.StrArr, LocaleContextHolder.getLocale()); + } + else if (exception instanceof AccountExpiredException) { + code = Expired.getCode(); + msg = messageSource.getMessage(code, Null.StrArr, LocaleContextHolder.getLocale()); + } + else if (exception instanceof CredentialsExpiredException) { + code = CredentialsExpired.getCode(); + msg = messageSource.getMessage(code, Null.StrArr, LocaleContextHolder.getLocale()); + } + else { + code = null; + msg = exception.getMessage(); + } + + if (code == null) { + return StringTemplate.dyn(warlockSecurityProp.getLoginFailureBody()) + .bindStr("{message}", msg) + .toString(); + } + else { + final R ng = R.ng(msg, code); + return objectMapper.writeValueAsString(ng); } } } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/JustAuthRequestBuilder.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/JustAuthRequestBuilder.java index d77c707d8..c81eaa7d2 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/JustAuthRequestBuilder.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/JustAuthRequestBuilder.java @@ -93,6 +93,7 @@ public DefaultWingsAuthDetails buildDetails(@NotNull Enum authType, @NotNull if (data instanceof AuthUser) { final DefaultWingsAuthDetails detail = new DefaultWingsAuthDetails(data); final Map meta = detail.getMetaData(); + meta.put(WingsAuthHelper.AuthType, authType.name()); meta.put(WingsAuthHelper.AuthZone, authStateBuilder.parseAuthZone(request)); meta.put(WingsAuthHelper.AuthAddr, remoteResolver.resolveRemoteIp(request)); meta.put(WingsAuthHelper.AuthAgent, remoteResolver.resolveAgentInfo(request)); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockFailedLoginListener.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockFailedLoginListener.java index 2e478ceff..cd5e57ec1 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockFailedLoginListener.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockFailedLoginListener.java @@ -1,13 +1,19 @@ package pro.fessional.wings.warlock.security.listener; +import com.alibaba.fastjson2.JSON; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; +import org.springframework.security.core.Authentication; +import pro.fessional.wings.slardar.fastjson.FastJsonHelper; +import pro.fessional.wings.slardar.security.WingsAuthDetails; import pro.fessional.wings.slardar.security.bind.WingsBindAuthToken; import pro.fessional.wings.warlock.service.auth.WarlockAuthnService; +import java.util.Map; + /** * @author trydofor * @since 2021-02-24 @@ -25,6 +31,20 @@ public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) { log.info("skip non-wings-source, type={}", source.getClass().getName()); return; } - warlockAuthnService.onFailure(src.getAuthType(), src.getName()); + + final Authentication authn = event.getAuthentication(); + if(authn == null) return; + + final Object dtl = authn.getDetails(); + final String details; + if (dtl instanceof WingsAuthDetails authDetails) { + final Map meta = authDetails.getMetaData(); + details = JSON.toJSONString(meta, FastJsonHelper.DefaultWriter()); + }else{ + details = dtl.toString(); + } + + + warlockAuthnService.onFailure(src.getAuthType(), src.getName(), details); } } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockSuccessLoginListener.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockSuccessLoginListener.java index f227a7b7c..01790b63c 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockSuccessLoginListener.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/listener/WarlockSuccessLoginListener.java @@ -67,6 +67,7 @@ public void onApplicationEvent(AuthenticationSuccessEvent event) { .terminal(TerminalAgent, meta.get(WingsAuthHelper.AuthAgent)) .user(userId) .authType(authType) + .username(ud.getUsername()) .authPerm(ud.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toSet())); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java index 88ff64e32..a84f66e0a 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthnService.java @@ -75,5 +75,5 @@ enum Jane { void onSuccess(@NotNull Enum authType, long userId, String details); - void onFailure(@NotNull Enum authType, String username); + void onFailure(@NotNull Enum authType, String username, String details); } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockDangerService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockDangerService.java new file mode 100644 index 000000000..b75064883 --- /dev/null +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockDangerService.java @@ -0,0 +1,33 @@ +package pro.fessional.wings.warlock.service.auth; + +/** + * @author trydofor + * @since 2023-07-10 + */ +public interface WarlockDangerService { + /** + * block this login + * + * @param authType authn type + * @param username username + * @param seconds username + */ + void block(Enum authType, String username, int seconds); + + /** + * check blocking second + * + * @param authType authn type + * @param username username + * @return zero or negative mean allow + */ + int check(Enum authType, String username); + + /** + * allow this login + * + * @param authType authn type + * @param username username + */ + void allow(Enum authType, String username); +} diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java index 8cc75c956..116b952d0 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthnService.java @@ -99,10 +99,10 @@ public void onSuccess(@NotNull Enum authType, long userId, String details) { } @Override - public void onFailure(@NotNull Enum authType, String username) { + public void onFailure(@NotNull Enum authType, String username, String details) { final long bgn = ThreadNow.millis(); for (Combo cmb : combos) { - cmb.onFailure(authType, username); + cmb.onFailure(authType, username, details); } // timing attack final long cost = ThreadNow.millis() - bgn; @@ -145,7 +145,7 @@ public interface Combo extends Ordered { void onSuccess(@NotNull Enum authType, long userId, String details); - void onFailure(@NotNull Enum authType, String username); + void onFailure(@NotNull Enum authType, String username, String details); } // ///// diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/WarlockDangerServiceImpl.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/WarlockDangerServiceImpl.java new file mode 100644 index 000000000..f0bffc9dd --- /dev/null +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/WarlockDangerServiceImpl.java @@ -0,0 +1,46 @@ +package pro.fessional.wings.warlock.service.auth.impl; + +import lombok.Data; +import org.cache2k.Cache; +import pro.fessional.wings.slardar.cache.cache2k.WingsCache2k; +import pro.fessional.wings.slardar.context.Now; +import pro.fessional.wings.warlock.service.auth.WarlockDangerService; + +/** + * @author trydofor + * @since 2023-07-10 + */ +public class WarlockDangerServiceImpl implements WarlockDangerService { + + @Data + private static class Ck { + private final Enum authType; + private final String username; + } + + + protected final Cache cache; + + public WarlockDangerServiceImpl(int size, int ttl) { + cache = WingsCache2k.builder(this.getClass(), "WarlockDangerService", + size, ttl, ttl, Ck.class, Long.class).build(); + + } + + @Override + public void block(Enum authType, String username, int seconds) { + cache.put(new Ck(authType, username), Now.millis() + seconds * 1000L); + } + + @Override + public int check(Enum authType, String username) { + final Long block = cache.get(new Ck(authType, username)); + if (block == null) return -1; + return (int) ((block - Now.millis()) / 1000); + } + + @Override + public void allow(Enum authType, String username) { + cache.remove(new Ck(authType, username)); + } +} diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserAuthnService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserAuthnService.java index c35392db1..ee8570145 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserAuthnService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserAuthnService.java @@ -75,8 +75,9 @@ class Renew { * * @param userId user * @param danger danger or not + * @param authType auth type to reset */ - void dander(long userId, boolean danger); + void dander(long userId, boolean danger, @NotNull Enum... authType); /** * disable auth by type diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserLoginService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserLoginService.java index 0aa62bcde..ccf9c84b1 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserLoginService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserLoginService.java @@ -18,6 +18,7 @@ public interface WarlockUserLoginService { @Data class Item { private String authType; + private String username; private String loginIp; private LocalDateTime loginDt; private String terminal; @@ -38,6 +39,7 @@ class Item { @Data class Auth { private Enum authType; + private String username; private long userId; private String details; private boolean failed; diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceDummy.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceDummy.java index f48911293..1847f3542 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceDummy.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceDummy.java @@ -27,7 +27,7 @@ public void renew(long userId, @NotNull Renew renew) { } @Override - public void dander(long userId, boolean danger) { + public void dander(long userId, boolean danger, @NotNull Enum... authType) { throw new UnsupportedOperationException("Dummy Service"); } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockOtherBeanConfiguration.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockOtherBeanConfiguration.java index 50c819156..043e0d099 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockOtherBeanConfiguration.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockOtherBeanConfiguration.java @@ -1,5 +1,6 @@ package pro.fessional.wings.warlock.spring.bean; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.AutoConfigureOrder; @@ -41,9 +42,9 @@ public static class BindingErrorConfig { @Bean(name = defaultExceptionResolver) @ConditionalOnMissingBean(name = defaultExceptionResolver) @ConditionalOnProperty(name = WarlockEnabledProp.Key$defaultExceptionHandler, havingValue = "true") - public HandlerExceptionResolver defaultExceptionResolver(WarlockErrorProp prop, MessageSource messageSource) { + public HandlerExceptionResolver defaultExceptionResolver(WarlockErrorProp prop, MessageSource messageSource, ObjectMapper objectMapper) { log.info("WarlockShadow spring-bean " + defaultExceptionResolver); - return new DefaultExceptionResolver(prop.getDefaultException(), messageSource); + return new DefaultExceptionResolver(prop.getDefaultException(), messageSource, objectMapper); } @Bean diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java index 72c6e6662..e67b7f44c 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java @@ -19,6 +19,7 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.util.AntPathMatcher; +import org.springframework.web.servlet.LocaleResolver; import pro.fessional.mirana.bits.Aes; import pro.fessional.wings.silencer.spring.help.CommonPropHelper; import pro.fessional.wings.slardar.cache.WingsCache; @@ -382,7 +383,9 @@ public MemoryTypedAuthzCombo memoryTypedAuthzCombo( @Bean @ConditionalOnMissingBean(WingsAuthDetailsSource.class) public WingsAuthDetailsSource wingsAuthDetailsSource(ObjectProvider> combos, - ObjectProvider rrs) { + ObjectProvider rrs, + ObjectProvider lrp + ) { log.info("WarlockShadow spring-bean wingsAuthDetailsSource"); final ComboWingsAuthDetailsSource uds = new ComboWingsAuthDetailsSource(); @@ -396,6 +399,7 @@ public WingsAuthDetailsSource wingsAuthDetailsSource(ObjectProvider Date: Thu, 13 Jul 2023 10:34:32 +0800 Subject: [PATCH 10/48] =?UTF-8?q?=E2=9C=A8=20report=20issue=20via=20silenc?= =?UTF-8?q?er=20#109?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- readme-zh.md | 10 ++-- readme.md | 10 ++-- wings/batrider/pom.xml | 4 ++ wings/faceless/pom.xml | 4 ++ wings/silencer/pom.xml | 4 ++ .../main/java/pro/fessional/wings/WhoAmI.java | 51 ++++++++++++++++++- wings/slardar/pom.xml | 4 ++ wings/warlock/pom.xml | 4 ++ 9 files changed, 83 insertions(+), 10 deletions(-) diff --git a/observe/docs b/observe/docs index 6056500e4..92284c55b 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 6056500e4ff07af97003a8c0fb5b6b4800fc230a +Subproject commit 92284c55bcc8a560592610b82b7c6301ee05fe1a diff --git a/readme-zh.md b/readme-zh.md index 9467e3517..464ef9860 100644 --- a/readme-zh.md +++ b/readme-zh.md @@ -31,17 +31,19 @@ ## 2.常用命令 ```bash -# ① 获取源码,成功后进入项目目录 +## ① 获取源码,成功后进入项目目录 git clone --depth 1 https://github.com/\ trydofor/pro.fessional.wings.git -# ② 安装依赖,可跳过,支持java8编译 -# sdk use java 8.0.352-tem +## ② 安装依赖,可跳过,支持java8编译 +sdk use java 8.0.352-tem git submodule update --remote --init (cd observe/meepo && mvn package install) (cd observe/mirana && mvn package install) -# ③ 安装wings,java-17 +## ③ 安装wings,java-17 sdk use java 17.0.6-tem mvn package install +## ④ 报告Issue +java -jar silencer-*-SNAPSHOT.jar ``` ## 3.用爱发电 diff --git a/readme.md b/readme.md index f505ce9dd..f5b75ca68 100644 --- a/readme.md +++ b/readme.md @@ -31,17 +31,19 @@ ## 2.Useful commands ```bash -# ① get source code +## ① get source code git clone --depth 1 https://github.com/\ trydofor/pro.fessional.wings.git -# ② install dependency using java8 -# sdk use java 8.0.352-tem +## ② install dependency using java8 +sdk use java 8.0.352-tem git submodule update --remote --init (cd observe/meepo && mvn package install) (cd observe/mirana && mvn package install) -# ③ install wings using java-17 +## ③ install wings using java-17 sdk use java 17.0.6-tem mvn package install +## ④ report issue +java -jar silencer-*-SNAPSHOT.jar ``` ## 3.Powered by Love diff --git a/wings/batrider/pom.xml b/wings/batrider/pom.xml index cbbff5462..ba1f03394 100644 --- a/wings/batrider/pom.xml +++ b/wings/batrider/pom.xml @@ -16,6 +16,10 @@ Wings::Batrider Basic settings of Servicecomb + + pro.fessional.wings.WhoAmI + + pro.fessional.wings diff --git a/wings/faceless/pom.xml b/wings/faceless/pom.xml index 8d5033791..95571602d 100644 --- a/wings/faceless/pom.xml +++ b/wings/faceless/pom.xml @@ -16,6 +16,10 @@ Wings::Faceless basic database features and definitions, e.g. locks/types/enum/i18n + + pro.fessional.wings.WhoAmI + + diff --git a/wings/silencer/pom.xml b/wings/silencer/pom.xml index 792684a6d..312b0c48c 100644 --- a/wings/silencer/pom.xml +++ b/wings/silencer/pom.xml @@ -16,6 +16,10 @@ Wings::Silencer Zero-Intrusion and Auto-Config SpringBoot's Bean/I18n in Cascading Style + + pro.fessional.wings.WhoAmI + + pro.fessional diff --git a/wings/silencer/src/main/java/pro/fessional/wings/WhoAmI.java b/wings/silencer/src/main/java/pro/fessional/wings/WhoAmI.java index 775570686..c6d7726bd 100644 --- a/wings/silencer/src/main/java/pro/fessional/wings/WhoAmI.java +++ b/wings/silencer/src/main/java/pro/fessional/wings/WhoAmI.java @@ -1,11 +1,60 @@ package pro.fessional.wings; +import java.awt.*; +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + /** * @author trydofor * @since 2019-07-02 */ public class WhoAmI { - public static void main(String[] args) { + public static void main(String[] args) throws Exception { System.out.println("Silencer: Enemies should be seen and not heard."); + + final StringBuilder buff = new StringBuilder(); + buff.append("## Environment\n"); + buff.append("\n * java = ").append(System.getProperty("java.version")); + buff.append("\n * os = ").append(System.getProperty("os.name")); + buff.append(" ").append(System.getProperty("os.version")); + buff.append(" ").append(System.getProperty("os.arch")); + + try (InputStream git = WhoAmI.class.getResourceAsStream("/git.properties")) { + if (git != null) { + final Properties info = new Properties(); + info.load(git); + buff.append("\n * version = ").append(info.getProperty("git.build.version")); + buff.append("\n * time = ").append(info.getProperty("git.commit.time")); + buff.append("\n * branch = ").append(info.getProperty("git.branch")); + buff.append("\n * commit = ").append(info.getProperty("git.commit.id.full")); + } + } + + System.out.println("_ _ _ _"); + System.out.println(buff); + System.out.println("_ _ _ _"); + + buff.append("\n\n## Expected Behavior\n"); + buff.append("\n\n## Current Behavior\n"); + buff.append("\n\n## Reproduce Steps\n"); + buff.append("\n\n## Detailed Description\n"); + buff.append("\n\n## Possible Solution\n"); + + String url = "https://github.com/trydofor/pro.fessional.wings/issues/new?body=" + + URLEncoder.encode(buff.toString(), StandardCharsets.UTF_8) + .replace("+", "%20") + .replace("*", "%2A"); + + System.out.println(url); + try { + Desktop desktop = Desktop.getDesktop(); + desktop.browse(new URI(url)); + } + catch (Exception e) { + System.err.println("Failed to open browser, Please open the URL manually."); + } } } diff --git a/wings/slardar/pom.xml b/wings/slardar/pom.xml index c1c987765..994f20366 100644 --- a/wings/slardar/pom.xml +++ b/wings/slardar/pom.xml @@ -16,6 +16,10 @@ Wings::Slardar Caching, Event, Serialization/Json and other basic feathers + + pro.fessional.wings.WhoAmI + + pro.fessional.wings diff --git a/wings/warlock/pom.xml b/wings/warlock/pom.xml index f091fda53..d76d45f9e 100644 --- a/wings/warlock/pom.xml +++ b/wings/warlock/pom.xml @@ -16,6 +16,10 @@ Wings::Warlock Integration of Database(FacelessJooq) and WebMvc(Slardar) + + pro.fessional.wings.WhoAmI + + pro.fessional.wings From a449b42d15b73140439a560f27ecb6020b4a8851 Mon Sep 17 00:00:00 2001 From: trydofor Date: Fri, 14 Jul 2023 09:37:14 +0800 Subject: [PATCH 11/48] =?UTF-8?q?=E2=9C=A8=20change=20MapStruct=20naming?= =?UTF-8?q?=20#110?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- observe/docs | 2 +- .../faceless/sample/JooqMostSelectSample.java | 161 ++++++++---------- .../wings/slardar/concur/DebounceTest.java | 2 +- .../controller/TestJsonController.java | 47 +++-- .../service/auth/help/AuthnDetailsMapper.java | 68 +++++--- wings/wings-idea-live.xml | 17 +- 6 files changed, 166 insertions(+), 131 deletions(-) diff --git a/observe/docs b/observe/docs index 92284c55b..9d8c726f5 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 92284c55bcc8a560592610b82b7c6301ee05fe1a +Subproject commit 9d8c726f571872bc496210858c53cc01f0672694 diff --git a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqMostSelectSample.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqMostSelectSample.java index 7f939274a..3401ea95c 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqMostSelectSample.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqMostSelectSample.java @@ -4,19 +4,10 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import lombok.val; -import org.jooq.Condition; -import org.jooq.DSLContext; -import org.jooq.DataType; -import org.jooq.DatePart; -import org.jooq.Field; -import org.jooq.Param; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jooq.Record; -import org.jooq.Record1; -import org.jooq.Record2; -import org.jooq.Row2; -import org.jooq.SelectConditionStep; -import org.jooq.SelectOrderByStep; -import org.jooq.TableOnConditionStep; +import org.jooq.*; import org.jooq.impl.DSL; import org.jooq.impl.SQLDataType; import org.junit.jupiter.api.Assertions; @@ -46,11 +37,7 @@ import pro.fessional.wings.faceless.util.FlywaveRevisionScanner; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static org.jooq.Operator.AND; import static org.jooq.Operator.OR; @@ -115,9 +102,9 @@ public void test1SelectOnDemand() { testcaseNotice("分组Pojo到Map"); Map> grps = ctx.selectFrom(t) - .where(c) - .fetch() - .intoGroups(t.Id, dao.mapper()); + .where(c) + .fetch() + .intoGroups(t.Id, dao.mapper()); testcaseNotice("多个字段到2维数组"); Object[][] arrs = ctx.select(t.Id, t.LoginInfo) @@ -143,37 +130,27 @@ public static class DiffName { } /** - * Record2 to DiffName mapper, auto generate by `wgmp` live template + * auto generated by `wgmp` live template */ @Mapper public interface Record2ToDiffName { + @Mapping(target = "uid", expression = "java(source.value1())") + @Mapping(target = "str", expression = "java(source.value2())") + void mapping(@Nullable Record2 source, @NotNull @MappingTarget DiffName target); + Record2ToDiffName INSTANCE = Mappers.getMapper(Record2ToDiffName.class); - /** - * create new DiffName by Record2 - * - * @param a Record2 - * @return DiffName - */ - static DiffName into(Record2 a) { - return into(a, new DiffName()); + @NotNull + static DiffName into(@Nullable Record2 source) { + final DiffName target = new DiffName(); + INSTANCE.mapping(source, target); + return target; } - /** - * build DiffName with Record2 - * - * @param a Record2 - * @param b DiffName - */ - static DiffName into(Record2 a, DiffName b) { - INSTANCE._into(a, b); - return b; + static void into(@Nullable Record2 source, @NotNull DiffName target) { + INSTANCE.mapping(source, target); } - - @Mapping(target = "uid", expression = "java(a.value1())") - @Mapping(target = "str", expression = "java(a.value2())") - void _into(Record2 a, @MappingTarget DiffName b); } @Test @@ -449,56 +426,56 @@ public void test5PaginateJooq() { Map> order = new HashMap<>(); order.put("d", t1.Id); PageResult pr1 = PageJooqHelper.use(dao, page) - .count() - .from(t1) - .where(t1.Id.ge(1L)) - .order(order) - .fetch(t1.Id, t1.CommitId) - .into(TstSharding.class); + .count() + .from(t1) + .where(t1.Id.ge(1L)) + .order(order) + .fetch(t1.Id, t1.CommitId) + .into(TstSharding.class); PageResult pr2 = PageJooqHelper.use(dao.ctx(), page) - .count() - .from(t1) - .where(t1.Id.ge(1L)) - .order(order) - .fetch(t1.Id, t1.CommitId) - .into(it -> { - TstSharding po = new TstSharding(); - po.setId(it.get(t1.Id)); - po.setCommitId(it.get(t1.CommitId)); - return po; - }); + .count() + .from(t1) + .where(t1.Id.ge(1L)) + .order(order) + .fetch(t1.Id, t1.CommitId) + .into(it -> { + TstSharding po = new TstSharding(); + po.setId(it.get(t1.Id)); + po.setCommitId(it.get(t1.CommitId)); + return po; + }); testcaseNotice("使用helperJooq简化", "缓存的total,使页面不执行count操作", "select * from `tst_sharding` limit ?"); PageResult pr3 = PageJooqHelper.use(dao, page, 10) - .count() - .from(t) - .whereTrue() - .orderNone() - .fetch() - .into(TstSharding.class); + .count() + .from(t) + .whereTrue() + .orderNone() + .fetch() + .into(TstSharding.class); // testcaseNotice("使用helperJooq包装", "select count(*) as `c` from (select `t1`.* from `tst_sharding` as `t1` where `t1`.`id` >= ?) as `q`", "select `t1`.* from `tst_sharding` as `t1` where `t1`.`id` >= ? order by `id` asc limit ?"); val qry4 = dsl.select(t1.asterisk()).from(t1).where(t1.Id.ge(1L)); PageResult pr4 = PageJooqHelper.use(dao, page) - .wrap(qry4, order) - .fetch() - .into(TstSharding.class); + .wrap(qry4, order) + .fetch() + .into(TstSharding.class); val qry5 = dsl.select(t1.Id, t1.CommitId).from(t1).where(t1.Id.ge(1L)); PageResult pr5 = PageJooqHelper.use(dao, page) - .wrap(qry5, order) - .fetch() - .into(it -> { - TstSharding po = new TstSharding(); - po.setId(it.get(t1.Id)); - po.setCommitId(it.get(t1.CommitId)); - return po; - }); + .wrap(qry5, order) + .fetch() + .into(it -> { + TstSharding po = new TstSharding(); + po.setId(it.get(t1.Id)); + po.setCommitId(it.get(t1.CommitId)); + return po; + }); ///////////////////// // 包装count @@ -520,12 +497,12 @@ public void test5PaginateJooq() { .fetchOptionalInto(Integer.class) .orElse(0); List lst1 = dsl.select() - .from(t) - .where(t.Id.gt(1L)) - .orderBy(t.Id.asc()) - .limit(0, 10) - .fetch() - .into(TstSharding.class); + .from(t) + .where(t.Id.gt(1L)) + .orderBy(t.Id.asc()) + .limit(0, 10) + .fetch() + .into(TstSharding.class); log.info("cnt1={}", cnt1); log.info("lst1={}", lst1.size()); @@ -562,10 +539,10 @@ public void test5PaginateJdbc() { Map order = new HashMap<>(); order.put("d", "t1.Id"); PageResult pr1 = PageJooqHelper.use(jdbcTemplate, page) - .wrap("select `t1`.* from `tst_sharding` as `t1` where `t1`.`id` >= ?") - .order(order) - .bind(1L) - .fetchInto(TstSharding.class, WingsEnumConverters.Id2Language); + .wrap("select `t1`.* from `tst_sharding` as `t1` where `t1`.`id` >= ?") + .order(order) + .bind(1L) + .fetchInto(TstSharding.class, WingsEnumConverters.Id2Language); log.info("pr1={}", pr1.getData().size()); @@ -574,12 +551,12 @@ public void test5PaginateJdbc() { "SELECT id,login_info,other_info from `tst_sharding` where id >= ? order by id limit 5"); PageResult pr2 = PageJooqHelper.use(jdbcTemplate, page) - .count("count(*)") - .fromWhere("from `tst_sharding` where id >= ?") - .order("id") - .bind(1L) - .fetch("id,login_info,other_info") - .into(TstSharding.class, WingsEnumConverters.Id2Language); + .count("count(*)") + .fromWhere("from `tst_sharding` where id >= ?") + .order("id") + .bind(1L) + .fetch("id,login_info,other_info") + .into(TstSharding.class, WingsEnumConverters.Id2Language); log.info("pr2={}", pr2.getData().size()); } diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/concur/DebounceTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/concur/DebounceTest.java index c90cf8775..02047d7a5 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/concur/DebounceTest.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/concur/DebounceTest.java @@ -70,7 +70,7 @@ private void debounce(boolean reuse, String url) throws InterruptedException { log.info(">>r2>>" + r2.getBody()); if (reuse) { assertEquals(HttpStatus.OK, r2.getStatusCode()); - assertEquals(r1.getBody(), r2.getBody()); + assertEquals(r1.getBody(), r2.getBody(),"may be waiting more than 600ms"); } else { assertEquals(202, r2.getStatusCode().value()); diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestJsonController.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestJsonController.java index 6c315bc9d..949a7c739 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestJsonController.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestJsonController.java @@ -5,6 +5,9 @@ import com.fasterxml.jackson.annotation.JsonView; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; import org.mapstruct.factory.Mappers; @@ -38,23 +41,40 @@ public BigDecimal getDec() { } /** - * DateFmt to DateMmm mapper, auto generate by `wgmp` live template + * auto generated by `wgmp` live template */ @Mapper - public interface DecToSub { + public interface DecToSubMapper { - DecToSub INSTANCE = Mappers.getMapper(DecToSub.class); - static Sub into(Dec a) { - return into(a, new Sub()); + void mapping(Dec source, @MappingTarget Sub target); + + @InheritInverseConfiguration + void mapping(Sub source, @MappingTarget Dec target); + + DecToSubMapper INSTANCE = Mappers.getMapper(DecToSubMapper.class); + + @NotNull + static Sub into(@Nullable Dec source) { + final Sub target = new Sub(); + INSTANCE.mapping(source, target); + return target; + } + + static void into(@Nullable Dec source, @NotNull Sub target) { + INSTANCE.mapping(source, target); } - static Sub into(Dec a, Sub b) { - INSTANCE._into(a, b); - return b; + @NotNull + static Dec into(@Nullable Sub source) { + final Dec target = new Dec(); + INSTANCE.mapping(source, target); + return target; } - void _into(Dec a, @MappingTarget Sub b); + static void into(@Nullable Sub source, @NotNull Dec target) { + INSTANCE.mapping(source, target); + } } // 自己控制,分作不同的view @@ -63,8 +83,11 @@ public static class Vi { private BigDecimal dec = new BigDecimal("12345.67"); // - public interface Pub {} - public interface Api {} + public interface Pub { + } + + public interface Api { + } @JsonView(Pub.class) public BigDecimal getDec() { @@ -88,7 +111,7 @@ public R jsonDec() { @GetMapping("/test/json-sub.json") public R jsonSub() { // {"success":true,"data":{"dec":"12,345.67","str":"string"}} - return R.okData(DecToSub.into(new Dec())); + return R.okData(DecToSubMapper.into(new Dec())); } @GetMapping("/test/json-api.json") diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/help/AuthnDetailsMapper.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/help/AuthnDetailsMapper.java index 643f830b0..ff1071faf 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/help/AuthnDetailsMapper.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/help/AuthnDetailsMapper.java @@ -1,5 +1,8 @@ package pro.fessional.wings.warlock.service.auth.help; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; @@ -8,7 +11,7 @@ import pro.fessional.wings.warlock.service.auth.WarlockAuthnService; /** - * Details to DefaultWingsUserDetails mapper, auto generate by `wgmp` live template + * auto generated by `wgmp` live template * * @author trydofor * @since 2021-02-23 @@ -16,34 +19,61 @@ @Mapper public interface AuthnDetailsMapper { + @Mapping(target = "preAuthed", ignore = true) + @Mapping(target = "enabled", ignore = true) + @Mapping(target = "credentialsNonExpired", ignore = true) + @Mapping(target = "authorities", ignore = true) + @Mapping(target = "accountNonLocked", ignore = true) + @Mapping(target = "accountNonExpired", ignore = true) + void mapping(WarlockAuthnService.Details source, @MappingTarget DefaultWingsUserDetails target); + @InheritInverseConfiguration + void mapping(DefaultWingsUserDetails source, @MappingTarget WarlockAuthnService.Details target); + AuthnDetailsMapper INSTANCE = Mappers.getMapper(AuthnDetailsMapper.class); /** - * create new DefaultWingsUserDetails by Details + * create new DefaultWingsUserDetails from the source Details * - * @param a Details - * @return DefaultWingsUserDetails + * @param source the source from + * @return new target */ - static DefaultWingsUserDetails into(WarlockAuthnService.Details a) { - return into(a, new DefaultWingsUserDetails()); + @NotNull + static DefaultWingsUserDetails into(@Nullable WarlockAuthnService.Details source) { + final DefaultWingsUserDetails target = new DefaultWingsUserDetails(); + INSTANCE.mapping(source, target); + return target; } /** - * build DefaultWingsUserDetails with Details + * build the target DefaultWingsUserDetails from the source Details * - * @param a Details - * @param b DefaultWingsUserDetails + * @param source the source from + * @param target the target into */ - static DefaultWingsUserDetails into(WarlockAuthnService.Details a, DefaultWingsUserDetails b) { - INSTANCE._into(a, b); - return b; + static void into(@Nullable WarlockAuthnService.Details source, @NotNull DefaultWingsUserDetails target) { + INSTANCE.mapping(source, target); } - @Mapping(target = "preAuthed", ignore = true) - @Mapping(target = "enabled", ignore = true) - @Mapping(target = "credentialsNonExpired", ignore = true) - @Mapping(target = "authorities", ignore = true) - @Mapping(target = "accountNonLocked", ignore = true) - @Mapping(target = "accountNonExpired", ignore = true) - void _into(WarlockAuthnService.Details a, @MappingTarget DefaultWingsUserDetails b); + /** + * create new DefaultWingsUserDetails from the source Details + * + * @param source the source from + * @return new target + */ + @NotNull + static WarlockAuthnService.Details into(@Nullable DefaultWingsUserDetails source) { + final WarlockAuthnService.Details target = new WarlockAuthnService.Details(); + INSTANCE.mapping(source, target); + return target; + } + + /** + * build the target DefaultWingsUserDetails from the source Details + * + * @param source the source from + * @param target the target into + */ + static void into(@Nullable DefaultWingsUserDetails source, @NotNull WarlockAuthnService.Details target) { + INSTANCE.mapping(source, target); + } } diff --git a/wings/wings-idea-live.xml b/wings/wings-idea-live.xml index cb31f5bf9..a56e9027f 100644 --- a/wings/wings-idea-live.xml +++ b/wings/wings-idea-live.xml @@ -9,13 +9,11 @@