diff --git a/example/winx-admin/src/main/java/com/moilioncircle/wings/admin/controller/AdminController.java b/example/winx-admin/src/main/java/com/moilioncircle/wings/admin/controller/AdminController.java index 823d9b934..f221d52d7 100644 --- a/example/winx-admin/src/main/java/com/moilioncircle/wings/admin/controller/AdminController.java +++ b/example/winx-admin/src/main/java/com/moilioncircle/wings/admin/controller/AdminController.java @@ -22,9 +22,9 @@ public class AdminController { private WarlockUserAuthnService authnService; @PostMapping("/admin/disable-root.json") - public R rootNeverLogin() { + public R rootNeverLogin() { authnService.disable(DefaultUserId.Root, WarlockAuthType.USERNAME); log.info("disable root"); - return R.ok(); + return R.OK; } } diff --git a/example/winx-admin/src/test/resources/wings-conf/demo-admin.properties b/example/winx-admin/src/test/resources/wings-conf/demo-admin.properties index 2229112ae..9e9b4fd83 100644 --- a/example/winx-admin/src/test/resources/wings-conf/demo-admin.properties +++ b/example/winx-admin/src/test/resources/wings-conf/demo-admin.properties @@ -1,7 +1,7 @@ -############## BGN: 仅过测试使用,发布是需要移除 ############## +############## BGN: For testing only, to be removed before release ############## wings.warlock.security.mem-user[winx-admin].user-id=99 wings.warlock.security.mem-user[winx-admin].username=winx-admin@fessional.pro wings.warlock.security.mem-user[winx-admin].password={noop-md5}Make-CnDota-Great-Again! wings.warlock.security.mem-auth[winx-admin].user-id=99 wings.warlock.security.mem-auth[winx-admin].auth-perm=ROLE_ADMIN -############## END: 仅过测试使用,发布是需要移除 ############## +############## END: For testing only, to be removed before release ############## diff --git a/example/winx-api/src/main/java/com/moilioncircle/wings/api/contractor/HelloContractor.java b/example/winx-api/src/main/java/com/moilioncircle/wings/api/contractor/HelloContractor.java index 64a7dc269..d1ee4e71d 100644 --- a/example/winx-api/src/main/java/com/moilioncircle/wings/api/contractor/HelloContractor.java +++ b/example/winx-api/src/main/java/com/moilioncircle/wings/api/contractor/HelloContractor.java @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.ResponseBody; /** - * Rpc风格,接口定义SchemaId,basePath指定SchemaId + * Rpc style, interface define SchemaId , basePath specify SchemaId * * @author trydofor * @since 2022-08-04 diff --git a/example/winx-common/src/main/java/com/moilioncircle/wings/common/spring/prop/CommonEnabledProp.java b/example/winx-common/src/main/java/com/moilioncircle/wings/common/spring/prop/CommonEnabledProp.java index 1d742b6a9..b93c79755 100644 --- a/example/winx-common/src/main/java/com/moilioncircle/wings/common/spring/prop/CommonEnabledProp.java +++ b/example/winx-common/src/main/java/com/moilioncircle/wings/common/spring/prop/CommonEnabledProp.java @@ -17,7 +17,7 @@ public class CommonEnabledProp { public static final String Key = "spring.winx.common.enabled"; /** - * 是否默认配置 fedex + * Whether to config fedex * * @see #Key$fedex */ 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/example/winx-common/src/main/resources/wings-conf/spring-demo-enabled.properties b/example/winx-common/src/main/resources/wings-conf/spring-demo-enabled.properties index 9534cc1f6..98c2538ca 100644 --- a/example/winx-common/src/main/resources/wings-conf/spring-demo-enabled.properties +++ b/example/winx-common/src/main/resources/wings-conf/spring-demo-enabled.properties @@ -1,2 +1,2 @@ -# 是否默认配置 fedex +## Whether to config fedex spring.winx.common.enabled.fedex=true diff --git a/example/winx-common/src/main/resources/wings-conf/spring-sentry.properties b/example/winx-common/src/main/resources/wings-conf/spring-sentry.properties index 1316a8370..d9eadb06e 100644 --- a/example/winx-common/src/main/resources/wings-conf/spring-sentry.properties +++ b/example/winx-common/src/main/resources/wings-conf/spring-sentry.properties @@ -1,4 +1,4 @@ -# 不同于 sentry.properties,此配置有 sentry starter使用 +## Unlike sentry.properties, this config is used by the Sentry Starter. sentry.dsn=${SENTRY_DSN:} # Set traces-sample-rate to 1.0 to capture 100% of transactions for performance monitoring. # We recommend adjusting this value in production. diff --git a/example/winx-common/src/main/resources/wings-conf/wings-boot-admin.properties b/example/winx-common/src/main/resources/wings-conf/wings-boot-admin.properties index 4cbc47123..21acbcb6c 100644 --- a/example/winx-common/src/main/resources/wings-conf/wings-boot-admin.properties +++ b/example/winx-common/src/main/resources/wings-conf/wings-boot-admin.properties @@ -1,4 +1,4 @@ -# 默认不监控邮件,以免应用无法启动 +## Mail is not monitored by default in case the application fails to start management.health.mail.enabled=false management.metrics.tags.application=wings-winx @@ -27,6 +27,6 @@ wings.warlock.security.mem-user[boot-admin-client].user-id=79 wings.warlock.security.mem-user[boot-admin-client].username=${spring.boot.admin.client.instance.metadata.user.name} wings.warlock.security.mem-user[boot-admin-client].password={basic}${spring.boot.admin.client.instance.metadata.user.password} -# 内存用户权限,key授权说明,重复时覆盖,建议以类型和用途 +## Memory user permissions, key is auth info, overwrite if duplicated, recommend to use type and purpose. wings.warlock.security.mem-auth[boot-admin-client].user-id=79 wings.warlock.security.mem-auth[boot-admin-client].auth-perm=ROLE_ACTUATOR diff --git a/example/winx-common/src/main/resources/wings-conf/wings-hazelcast.properties b/example/winx-common/src/main/resources/wings-conf/wings-hazelcast.properties index b9785ecd1..cd2d29226 100644 --- a/example/winx-common/src/main/resources/wings-conf/wings-hazelcast.properties +++ b/example/winx-common/src/main/resources/wings-conf/wings-hazelcast.properties @@ -1,2 +1,3 @@ -# 自行修改集群名字,因社区版无安全设置,仅通过集群名便可加入,因此建议使用密码强度的名字,如32字符随机数,避开扫描。 +## Modify the cluster name on your own, as the community version has no security settings and joining is only based on the cluster name. +## Therefore, it is recommended to use a strong and password-like name, such as a 32-character random string, to avoid scanning. wings.slardar.hazelcast.cluster-name=wings-example-${git.commit.id.full:NEED_SAFE_HASH} diff --git a/example/winx-common/src/main/resources/wings-conf/wings-watching.properties b/example/winx-common/src/main/resources/wings-conf/wings-watching.properties index 7feee21d3..59df46672 100644 --- a/example/winx-common/src/main/resources/wings-conf/wings-watching.properties +++ b/example/winx-common/src/main/resources/wings-conf/wings-watching.properties @@ -1,6 +1,6 @@ -# 总开关 +## Whether enable watching spring.wings.warlock.enabled.watching=true -# -1表示关闭,0表示全开,毫秒值 +## -1 means disable, 0 means enable, otherwise number in millis #wings.warlock.watching.jooq-threshold=-1 #wings.warlock.watching.service-threshold=-1 #wings.warlock.watching.controller-threshold=-1 diff --git a/example/winx-devops/src/main/java/com/moilioncircle/wings/devops/init/WingsInitProjectUtil.java b/example/winx-devops/src/main/java/com/moilioncircle/wings/devops/init/WingsInitProjectUtil.java index fae824def..1d16efb0b 100644 --- a/example/winx-devops/src/main/java/com/moilioncircle/wings/devops/init/WingsInitProjectUtil.java +++ b/example/winx-devops/src/main/java/com/moilioncircle/wings/devops/init/WingsInitProjectUtil.java @@ -40,11 +40,11 @@ public static void initProject(Info info, Consumer message) throws IOExc String dstAbsPath = info.dstDir.getAbsolutePath(); if (dstAbsPath.contains(srcAbsPath) || srcAbsPath.contains(dstAbsPath)) { - throw new IOException("新工程路径和wings-example有重合,重选"); + throw new IOException("Path overlaps with wings-example, please choose a different one."); } if (!info.srcDir.exists()) { - message.accept("创建新工程目录"); + message.accept("Create new project dir."); info.srcDir.mkdirs(); } @@ -113,7 +113,7 @@ private static void makeWings(File root, String code, String pkg, Consumer exc, Consumer message) throws IOException { final String path = src.getAbsolutePath(); - // 忽略 + // ignore if (exc.test(path)) { return; } @@ -163,14 +163,14 @@ else if (path.endsWith(".java") || } if (bytes.length > 0) { - message.accept("写入 " + dstName); + message.accept("Write to " + dstName); FileOutputStream fos = new FileOutputStream(dstFile); fos.write(bytes); fos.flush(); fos.close(); } else { - message.accept("新建 " + dstName); + message.accept("Create New " + dstName); dstFile.createNewFile(); } } diff --git a/example/winx-devops/src/main/resources/application.properties b/example/winx-devops/src/main/resources/application.properties index 238b69e6d..f57403385 100644 --- a/example/winx-devops/src/main/resources/application.properties +++ b/example/winx-devops/src/main/resources/application.properties @@ -2,14 +2,14 @@ server.port=8093 spring.application.name=winx-devops #debug=true -# disable CONDITIONS EVALUATION REPORT +## disable CONDITIONS EVALUATION REPORT logging.level.org.springframework.boot.autoconfigure=INFO #logging.file.name=winx-devops.log #wings.warlock.security.session-maximum=10 -# IDEA的默认启动参数,增加 `-Dspring.jmx.enabled=true`,此参数无效 -# 需要手动 Disable JMX agent +## add `-Dspring.jmx.enabled=true` to IDEA start args +## Need manually Disable JMX agent spring.jmx.enabled=false wings.silencer.inspect.properties=true diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/init/WingsInitProjectSwing.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/init/WingsInitProjectSwing.java index d6c4ecce5..8a2951e9e 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/init/WingsInitProjectSwing.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/init/WingsInitProjectSwing.java @@ -275,8 +275,8 @@ private static void showWings(String[] args) { project.initWings(args); project.pack(); - project.setLocationRelativeTo(null); // 居中 - // 焦点 + project.setLocationRelativeTo(null); // center + // focus project.setState(Frame.NORMAL); project.toFront(); project.requestFocus(); @@ -295,8 +295,8 @@ private void initWings(String[] args) { txtWingsVer.setText(args[2]); } - btnWingsPath.addActionListener(evt -> chooseDir("wings-example工程目录", txtWingsPath)); - btnProjectPath.addActionListener(evt -> chooseDir("新建工程目录", txtProjectPath)); + btnWingsPath.addActionListener(evt -> chooseDir("wings-example project dir", txtWingsPath)); + btnProjectPath.addActionListener(evt -> chooseDir("new project dir", txtProjectPath)); btnGenerate.addActionListener(evt -> generatePrj()); FocusAdapter txtFocus = new FocusAdapter() { @@ -316,7 +316,7 @@ public void focusGained(FocusEvent e) { private void chooseDir(String name, JTextField field) { JFileChooser jfc = new JFileChooser(); jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - jfc.showDialog(new JLabel(), "选择" + name); + jfc.showDialog(new JLabel(), "Select" + name); File dir = jfc.getSelectedFile(); if (dir != null) { field.setText(dir.getAbsolutePath()); @@ -327,7 +327,7 @@ private void generatePrj() { try { pgbGenerate.setStringPainted(true); - pgbGenerate.setString("嗖一下,就完事"); + pgbGenerate.setString("Done, so easy :)"); pgbGenerate.setIndeterminate(true); WingsInitProjectUtil.Info info = new WingsInitProjectUtil.Info(); @@ -339,14 +339,14 @@ private void generatePrj() { info.dstPackage = txtPackage.getText().trim(); info.version = txtWingsVer.getText().trim(); - WingsInitProjectUtil.initProject(info, it -> message("复制:" + it)); - message("完成了 ¯\\_(ツ)_/¯"); - pgbGenerate.setString("完成了,¯\\_(ツ)_/¯"); + WingsInitProjectUtil.initProject(info, it -> message("Copy:" + it)); + message("Done ¯\\_(\"/)_/¯"); + pgbGenerate.setString("Done ¯\\_(\"/)_/¯"); } catch (Exception e) { e.printStackTrace(); - message("出错了 " + e.getMessage()); - pgbGenerate.setString("出错了!"); + message("Error " + e.getMessage()); + pgbGenerate.setString("Error"); pgbGenerate.setIndeterminate(false); } } diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops0ProjectConstant.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops0ProjectConstant.java index c08c928f7..8cc98eccb 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops0ProjectConstant.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops0ProjectConstant.java @@ -1,9 +1,11 @@ package com.moilioncircle.wings.devops.project; /** - * 注意在IDEA默认情况下,Main函数与TestCase启动的Workdir不同 - * - Main是根工程路径,目标中设置 - * - TestCase是当前工程,$MODULE_WORKING_DIR$ + *
+ * Note that by default in IDEA, the Workdir launched by `main` and TestCase are different.
+ * - `main` refers to the root project path, which can be set in configuration.
+ * - TestCase refers to the current project and uses $MODULE_WORKING_DIR$.
+ * 
* * @author trydofor * @since 2021-02-22 @@ -17,7 +19,7 @@ public interface Devops0ProjectConstant { String JDBC_USER = "trydofor"; String JDBC_PASS = "moilioncircle"; - // 需要设置 Working Directory=$MODULE_WORKING_DIR$ + // Need set Working Directory=$MODULE_WORKING_DIR$ String JAVA_MAIN = "../winx-codegen/src/main/java/"; String PKG_ENUM = "com.moilioncircle.wings.codegen.enums"; diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops1SchemaManagerTest.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops1SchemaManagerTest.java index c13ae5bb1..ffbeb88da 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops1SchemaManagerTest.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops1SchemaManagerTest.java @@ -16,7 +16,7 @@ * @author trydofor * @since 2021-02-22 */ -@Disabled("数据库版本") +@Disabled("Database Version") @SpringBootTest(properties = { "spring.datasource.url=" + Devops0ProjectConstant.JDBC_URL, "spring.datasource.username=" + Devops0ProjectConstant.JDBC_USER, diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops2CodeGeneratorTest.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops2CodeGeneratorTest.java index 37a897410..5afa44453 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops2CodeGeneratorTest.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops2CodeGeneratorTest.java @@ -19,7 +19,7 @@ * @author trydofor * @since 2021-02-22 */ -@Disabled("生成代码") +@Disabled("Code Gen") public class Devops2CodeGeneratorTest { @Test diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops5JournalManagerTest.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops5JournalManagerTest.java index 12f7564a5..b096483df 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops5JournalManagerTest.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops5JournalManagerTest.java @@ -13,7 +13,7 @@ import java.util.List; /** - * ⑤ 使用wings的flywave,生成trigger和跟踪表 + * Generate trigger and trace table by wings flywave * * @author trydofor * @since 2019-12-26 @@ -24,7 +24,7 @@ "spring.datasource.username=" + Devops0ProjectConstant.JDBC_USER, "spring.datasource.password=" + Devops0ProjectConstant.JDBC_PASS, }) -@Disabled("日志表管理") +@Disabled("Journal Manage") @Slf4j public class Devops5JournalManagerTest { diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops6ShardingManagerTest.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops6ShardingManagerTest.java index a4ec939c4..820077233 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops6ShardingManagerTest.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops6ShardingManagerTest.java @@ -8,8 +8,6 @@ import pro.fessional.wings.faceless.flywave.SchemaShardingManager; /** - * ⑥ 使用wings的flywave,生成trigger和跟踪表 - * * @author trydofor * @since 2019-12-26 */ @@ -19,7 +17,7 @@ "spring.datasource.username=" + Devops0ProjectConstant.JDBC_USER, "spring.datasource.password=" + Devops0ProjectConstant.JDBC_PASS, }) -@Disabled("分表分库") +@Disabled("Sharding table") public class Devops6ShardingManagerTest { @Setter(onMethod_ = {@Autowired}) @@ -30,7 +28,7 @@ public void test1SplitTable() { schemaShardingManager.publishShard("win_user", 2); } - // 需要 sharding config + // need sharding config // @Test // public void test2MoveDate() { // schemaShardingManager.shardingData("win_user", true); diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7EnumsDumperTest.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7EnumsDumperTest.java index 24dcbd07a..50d4f2091 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7EnumsDumperTest.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7EnumsDumperTest.java @@ -12,14 +12,12 @@ import java.util.Map; /** - * ⑥ 使用wings的flywave,生成trigger和跟踪表 - * * @author trydofor * @since 2019-12-26 */ @SpringBootTest -@Disabled("枚举类导出") +@Disabled("Dump Enums") public class Devops7EnumsDumperTest { @Setter(onMethod_ = {@Autowired}) diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7RequestMapperTest.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7RequestMapperTest.java index f6fe56127..725972ce0 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7RequestMapperTest.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7RequestMapperTest.java @@ -17,7 +17,7 @@ */ @SpringBootTest -@Disabled("查看Web映射关系") +@Disabled("List Web RequestMapping") @Slf4j public class Devops7RequestMapperTest { diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7SchemaDumperTest.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7SchemaDumperTest.java index 466b6349a..171327ad2 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7SchemaDumperTest.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops7SchemaDumperTest.java @@ -19,7 +19,7 @@ @SpringBootTest -@Disabled("数据库导出") +@Disabled("Dump database") @Slf4j public class Devops7SchemaDumperTest { @@ -32,22 +32,22 @@ public class Devops7SchemaDumperTest { @Test public void dumpSchema() { Function1, List> ddl = SchemaFulldumpManager.groupedTable(false, - "-- ==================== Basement-4(B4/10#):基础 =======================", - "sys_schema_version", // 101/表结构版本 - "sys_schema_journal", // 102/数据触发器 - "sys_light_sequence", // 103/序号生成器 - "sys_commit_journal", // 104/数据变更集 - "sys_constant_enum", // 105/常量枚举:自动生成enum类 - "sys_standard_i18n", // 106/标准多国语 - "-- ==================== Floor-1(F1/12#-13#):用户 =======================", - "win_user_basis", // 120/用户基本表 - "win_user_authn", // 121/用户验证表 - "win_user_login", // 122/用户登录表 - "win_perm_entry", // 130/权限条目表 - "win_role_entry", // 131/角色条目表 - "win_role_grant", // 134/角色权限映射表 - "win_user_grant", // 135/角色权限映射表 - "-- ==================== Floor-10(F11/90#):辅助 =======================" + "-- ==================== Basement-4(B4/10#): Base =======================", + "sys_schema_version", // 101/schema version + "sys_schema_journal", // 102/schema journal + "sys_light_sequence", // 103/sequence + "sys_commit_journal", // 104/data changeset + "sys_constant_enum", // 105/enum const: auto gen enum + "sys_standard_i18n", // 106/i18n message + "-- ==================== Floor-1(F1/12#-13#): User =======================", + "win_user_basis", // 120/user basis + "win_user_authn", // 121/user authn + "win_user_login", // 122/user login + "win_perm_entry", // 130/perm entry + "win_role_entry", // 131/role entry + "win_role_grant", // 134/grant to role + "win_user_grant", // 135/grant to user + "-- ==================== Floor-10(F11/90#): Help =======================" ); String root = Devops0ProjectConstant.DUMP_PATH + Devops0ProjectConstant.DUMP_TYPE; log.info("===== dump ddl to " + root); @@ -62,14 +62,14 @@ public void dumpRecord() { "sys_light_.*", "sys_constant_.*", "sys_standard_.*", - "win_user_basis", // 120/用户基本表 - "win_user_authn", // 121/用户验证表 - "win_user_login", // 122/用户登录表 - "win_perm_entry", // 130/权限条目表 - "win_role_entry", // 131/角色条目表 - "win_role_grant", // 134/角色权限映射表 - "win_user_grant" // 135/角色权限映射表 - ); + "win_user_basis", + "win_user_authn", + "win_user_login", + "win_perm_entry", + "win_role_entry", + "win_role_grant", + "win_user_grant" + ); String root = Devops0ProjectConstant.DUMP_PATH + Devops0ProjectConstant.DUMP_TYPE; log.info("===== dump rec to " + root); diff --git a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops9GitStatisticsMain.java b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops9GitStatisticsMain.java index bbdaf253d..00eb5ef75 100644 --- a/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops9GitStatisticsMain.java +++ b/example/winx-devops/src/test/java/com/moilioncircle/wings/devops/project/Devops9GitStatisticsMain.java @@ -8,7 +8,7 @@ import java.util.Map; /** - * 统计git代码,按年月和作者 + * Statistics Git code, grouping it by year, month, and author. * * @author trydofor * @since 2019-12-26 diff --git a/observe/docs b/observe/docs index 701714f44..0d29dde2e 160000 --- a/observe/docs +++ b/observe/docs @@ -1 +1 @@ -Subproject commit 701714f44eefa48b976313b1720775c84703dd42 +Subproject commit 0d29dde2e8620ad35278f2210954a3fbcfa0a99e diff --git a/observe/meepo b/observe/meepo index bafb7e782..dbf710284 160000 --- a/observe/meepo +++ b/observe/meepo @@ -1 +1 @@ -Subproject commit bafb7e782d8b6d65a1aad22ba7fdf215e9f76368 +Subproject commit dbf710284af733f33effb1472ee9deb69cfb5ebd diff --git a/observe/mirana b/observe/mirana index 18d8a826f..e9215f349 160000 --- a/observe/mirana +++ b/observe/mirana @@ -1 +1 @@ -Subproject commit 18d8a826f26730f41b955283699cf8de74ccc33c +Subproject commit e9215f3499436a623dd08b8a22e48232234ad217 diff --git a/observe/scripts/wings-docker.sh b/observe/scripts/wings-docker.sh index 479a544a7..343ba2f86 100755 --- a/observe/scripts/wings-docker.sh +++ b/observe/scripts/wings-docker.sh @@ -1,7 +1,7 @@ #!/bin/bash THIS_VERSION=2022-01-22 -TEMP_DIR="../../example/winx-devops/target" # 避免复制,建议在同一硬盘分区 +TEMP_DIR="../../example/winx-devops/target" # To avoid copy, recommend the same partition on the hard disk. BOOT_JAR="../../example/winx-devops/target/winx-devops-*-SNAPSHOT.jar" BOOT_ENV="./wings-starter.env" BOOT_BSH="./wings-starter.sh" diff --git a/observe/scripts/wings-git-stat.sh b/observe/scripts/wings-git-stat.sh index b1149a482..96b9baf76 100755 --- a/observe/scripts/wings-git-stat.sh +++ b/observe/scripts/wings-git-stat.sh @@ -2,7 +2,7 @@ THIS_VERSION=2021-12-21 echo -e "\033[37;42;1mScript-Version $THIS_VERSION \033[0m" -# 统计提交行数 +# Statistics the commit lines echo "name : $(git config --get user.name)" echo "email : $(git config --get user.email)" echo "remote : $(git config --get remote.origin.url)" diff --git a/observe/scripts/wings-init-project.sh b/observe/scripts/wings-init-project.sh index dcf716ca0..f10184815 100755 --- a/observe/scripts/wings-init-project.sh +++ b/observe/scripts/wings-init-project.sh @@ -1,7 +1,7 @@ #!/bin/bash THIS_VERSION=2021-12-21 -# 相对于 wings-example +# relative to wings-example JAVA_ROOT="winx-devops/src/*/java" CLAZ_ROOT="winx-devops/target/test-classes" JAVA_FILE="com/moilioncircle/wings/devops/init/*.java" diff --git a/observe/scripts/wings-mysql-dump.sh b/observe/scripts/wings-mysql-dump.sh index 77122a1f2..8168568e5 100755 --- a/observe/scripts/wings-mysql-dump.sh +++ b/observe/scripts/wings-mysql-dump.sh @@ -4,17 +4,17 @@ THIS_VERSION=2023-04-14 cat << EOF ################################################# # Version $THIS_VERSION # test on Mac and Lin -# 通过 mysqldump 生成 'db-ts' 开头的以下文件, -- {db-ts}-main.sql 主表 -- {db-ts}-logs.sql log表 -- {db-ts}-tbl.log dump的表及结果信息 -- {db-ts}-tip.txt scp及restore手册 +# Generate the following files starting with 'db-ts' using mysqldump. +- {db-ts}-main.sql main table +- {db-ts}-logs.sql trac table +- {db-ts}-tbl.log log of dump table +- {db-ts}-tip.txt tip of scp and restore # Usage $0 cnf [db] [opt] -- cnf - 配置文件,参考'--defaults-extra-file' -- db - 需要dump的database,空时显示所有db -- opt - dump参数,如 '--no-data' -# option 详细参考client和mysqldump段 +- cnf - config file, see '--defaults-extra-file' +- db - database to dump, empty means all +- opt - dump args, e.g. '--no-data' +# option details in client/mysqldump help - https://dev.mysql.com/doc/refman/8.0/en/option-files.html ################################################# EOF diff --git a/observe/scripts/wings-mysql-user.env b/observe/scripts/wings-mysql-user.env index f2665f8d9..1c78a2a38 100755 --- a/observe/scripts/wings-mysql-user.env +++ b/observe/scripts/wings-mysql-user.env @@ -1,23 +1,24 @@ execute=false -# 用户名前缀 +## prefix of username user_pre=wings -# 默认的名字分隔符 +## default name separator name_pre=_ -# 授权db,空格分隔。其中`_`和`%`是通配符,可`\`转义 + +## the database to grant, separated by spaces. The `_` and `%` are wildcards and can be escaped with `\`. grant_db='wings%' -# passwd 空为忽略 +## passwd, empty means ignore it pass_raw=$(passwd24) pass_app=$(passwd24) pass_dev=$(passwd24) pass_dba=$(passwd24) -# host 默认,% +## host, default `%` host_raw=% host_app=% host_dev=% host_dba=% -#对dba用户增加其他DB的SELECT权限 +## Grant SELECT privileges on other databases to the DBA user. more_dba='sys mysql' diff --git a/observe/scripts/wings-mysql-user.sh b/observe/scripts/wings-mysql-user.sh index e429b4ca0..27b54f195 100755 --- a/observe/scripts/wings-mysql-user.sh +++ b/observe/scripts/wings-mysql-user.sh @@ -4,7 +4,7 @@ THIS_VERSION=2023-04-14 cat < 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 @@ -37,50 +37,50 @@ false false true - - 2.19.1 + 24.0.1 1.5.5.Final - 31.1-jre - 2.12.0 + 32.1.1-jre + 2.13.0 - 2.4.5-SNAPSHOT + 2.6.0-SNAPSHOT 1.4.1-SNAPSHOT 5.3.2 2.3.3 - 2.0.34 + 2.0.36 ${fastjson2.version} 5.5.0 - 2.14.2 - 1.73 + 2.14.3 + 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 @@ -373,7 +373,7 @@ ${fastjson2.version} - + com.alibaba fastjson ${fastjson.version} @@ -437,11 +437,6 @@ annotations ${annotations.version} - - com.google.errorprone - error_prone_annotations - ${errorprone.version} - org.mapstruct mapstruct @@ -530,22 +525,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/radiant/devs-mvndep/pom.xml b/radiant/devs-mvndep/pom.xml index 88ae271d8..140924318 100644 --- a/radiant/devs-mvndep/pom.xml +++ b/radiant/devs-mvndep/pom.xml @@ -87,7 +87,7 @@ test - + com.alibaba fastjson ${fastjson.version} @@ -144,11 +144,6 @@ ${annotations.version} test - - com.google.errorprone - error_prone_annotations - ${errorprone.version} - org.mapstruct mapstruct-processor diff --git a/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/codegen/AutogenDependencyTest.java b/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/codegen/AutogenDependencyTest.java index afd902df0..ff0645f76 100644 --- a/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/codegen/AutogenDependencyTest.java +++ b/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/codegen/AutogenDependencyTest.java @@ -29,7 +29,7 @@ * @author trydofor * @since 2023-01-23 */ -@Disabled("自动生成代码,手动初始化") +@Disabled("Automatic code generation, manual initialization") @SpringBootTest @TestMethodOrder(MethodOrderer.MethodName.class) public class AutogenDependencyTest { diff --git a/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/database/WarlockSchemaTest.java b/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/database/WarlockSchemaTest.java index af296afba..b677fccca 100644 --- a/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/database/WarlockSchemaTest.java +++ b/radiant/devs-mvndep/src/test/java/pro/fessional/wings/devs/database/WarlockSchemaTest.java @@ -13,7 +13,7 @@ * @author trydofor * @since 2021-02-22 */ -@Disabled("初始化数据库,已有devs统一管理") +@Disabled("Init database") @SpringBootTest public class WarlockSchemaTest { diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailListController.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailListController.java index f130e9094..8fbc151e5 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailListController.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailListController.java @@ -28,60 +28,60 @@ public class MailListController { @Setter(onMethod_ = {@Autowired}) protected TinyMailListService tinyMailListService; - @Operation(summary = "获取全部邮件的简要信息,默认倒序") + @Operation(summary = "list summary of all messages, in reverse order by default.") @PostMapping(value = "${" + TinyMailUrlmapProp.Key$listAll + "}") @ResponseBody public PageResult listAll(PageQuery pq) { return tinyMailListService.listAll(pq); } - @Operation(summary = "获取失败邮件的简要信息,默认倒序") + @Operation(summary = "list summary of failed emails, in reverse order by default.") @PostMapping(value = "${" + TinyMailUrlmapProp.Key$listFailed + "}") @ResponseBody public PageResult listFailed(PageQuery pq) { return tinyMailListService.listFailed(pq); } - @Operation(summary = "获取未成功邮件的简要信息,默认倒序") + @Operation(summary = "list summary of unsuccessful emails, in reverse order by default.") @PostMapping(value = "${" + TinyMailUrlmapProp.Key$listUndone + "}") @ResponseBody public PageResult listUndone(PageQuery pq) { return tinyMailListService.listUndone(pq); } - @Operation(summary = "根据Biz-Mark获取邮件的简要信息,默认倒序") + @Operation(summary = "find summary of the email by Biz-Mark, in reverse order by default.") @PostMapping(value = "${" + TinyMailUrlmapProp.Key$byBizmark + "}") @ResponseBody public PageResult byBizMark(@RequestBody Q q, PageQuery pq) { return tinyMailListService.listByBizMark(q.getQ(), pq); } - @Operation(summary = "根据正则比较收件人to/cc/bcc获取邮件的简要信息,默认倒序") + @Operation(summary = "find summary of the email by RegExp of to/cc/bcc, reverse order by default.") @PostMapping(value = "${" + TinyMailUrlmapProp.Key$byRecipient + "}") @ResponseBody public PageResult byRecipient(@RequestBody Q q, PageQuery pq) { return tinyMailListService.listByRecipient(q.getQ(), pq); } - @Operation(summary = "根据收件人from获取邮件的简要信息,默认倒序") + @Operation(summary = "find summary of the email by from, in reverse order by default.") @PostMapping(value = "${" + TinyMailUrlmapProp.Key$bySender + "}") @ResponseBody public PageResult bySender(@RequestBody Q q, PageQuery pq) { return tinyMailListService.listBySender(q.getQ(), pq); } - @Operation(summary = "根据正则比较邮件标题获取邮件的简要信息,默认倒序") + @Operation(summary = "find summary of the email by RegExp of subject, reverse order by default.") @PostMapping(value = "${" + TinyMailUrlmapProp.Key$bySubject + "}") @ResponseBody public PageResult bySubject(@RequestBody Q q, PageQuery pq) { return tinyMailListService.listBySubject(q.getQ(), pq); } - @Operation(summary = "获取邮件详情", description = """ + @Operation(summary = "get mail detail", description = """ # Usage - 获取邮件详情。 + get mail detail ## Params - * @param id - 必填,Mailid + * @param id - required, Mailid """) @PostMapping(value = "${" + TinyMailUrlmapProp.Key$loadDetail + "}") @ResponseBody diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailSendController.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailSendController.java index 4140b3a7f..0f8276877 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailSendController.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/controller/MailSendController.java @@ -26,7 +26,16 @@ public class MailSendController { @Setter(onMethod_ = {@Autowired}) protected TinyMailService tinyMailService; - @Operation(summary = "新建或编辑邮件,并同步立即或异步定时,-1为失败,0为同步,否则为异步") + @Operation(summary = "Create mail and send it sync or async", description = """ + # Usage + Create the mail, and auto send it in sync or async way. + ## Params + * @param - request body + ## Returns + * @return {200 | Result(-1)} failure + * @return {200 | Result(0)} sync send + * @return {200 | Result(mills)} async send at mills time + """) @PostMapping(value = "${" + TinyMailUrlmapProp.Key$sendMail + "}") @ResponseBody public R sendMail(@RequestBody TinyMailPlain mail) { @@ -34,7 +43,15 @@ public R sendMail(@RequestBody TinyMailPlain mail) { return R.okData(ms); } - @Operation(summary = "仅新建或编辑邮件,但并不发送") + + @Operation(summary = "Save the mail only, do not send", description = """ + # Usage + Save the new mail, return the id. + ## Params + * @param - request body + ## Returns + * @return {200 | Result(id)} mail id + """) @PostMapping(value = "${" + TinyMailUrlmapProp.Key$sendSave + "}") @ResponseBody public R sendSave(@RequestBody TinyMailPlain mail) { @@ -42,7 +59,13 @@ public R sendSave(@RequestBody TinyMailPlain mail) { return R.okData(id); } - @Operation(summary = "同步扫需要描补发的邮件,并异步发送,返回补发的件数") + + @Operation(summary = "sync scan and resend mail async", description = """ + # Usage + sync scan the mail to resend, return the count, and send them async + ## Returns + * @return {200 | Result(count)} mail cou t + """) @PostMapping(value = "${" + TinyMailUrlmapProp.Key$sendScan + "}") @ResponseBody public R sendScan() { @@ -53,20 +76,28 @@ public R sendScan() { @Data public static class Ins { /** - * 邮件id + * Mail id */ private long id; /** - * 若失败,是否异步重试 + * Whether retry async if fail */ private boolean retry; /** - * 是否检查发送条件,否则为强制发送 + * Whether to check the send condition, otherwise force send */ private boolean check; } - @Operation(summary = "同步重试失败的邮件,发送成功或失败,或异常") + + @Operation(summary = "Sync resend failed mail", description = """ + # Usage + Sync resend the failed email, return success/fail, or throw exception + ## Params + * @param - request body + ## Returns + * @return {200 | Result(bool)} success or not + """) @PostMapping(value = "${" + TinyMailUrlmapProp.Key$sendRetry + "}") @ResponseBody public R sendRetry(@RequestBody Ins mail) { diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailSenderManager.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailSenderManager.java index 9c4b55847..413a53dfb 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailSenderManager.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailSenderManager.java @@ -38,7 +38,7 @@ import static org.apache.commons.lang3.StringUtils.isNotEmpty; /** - * 支持force替换,DayLimitException + * Support dryrun, replacement and DayLimitException * * @author trydofor * @since 2023-01-03 @@ -59,14 +59,14 @@ public class MailSenderManager { protected final ConcurrentHashMap mailHostIdle = new ConcurrentHashMap<>(); /** - * remove host waiting by host + * Remove host from waiting */ public void removeHostWait(String host) { mailHostWait.remove(host); } /** - * remove cached sender by its config.name + * Remove cached sender by its config.name * * @param name config.name */ @@ -75,7 +75,7 @@ public void removeCachingSender(String name) { } /** - * 列出当前等待的host + * List all hosts waiting for frequency limit */ public Map listHostWait() { return new HashMap<>(mailHostWait); @@ -101,9 +101,9 @@ public void singleSend(@NotNull TinyMailMessage message, @Nullable MimeMessagePr } /** - * 支持dryrun,一次链接验证,发送一个邮件,MailException会被转为MailWaitException + * Supports dryrun, send one mail per connect login, MailException is wrapped to MailWaitException * - * @throws MailWaitException 需要处理waitMillis及cause非null时的原始异常 + * @throws MailWaitException need handle the waitMillis and non-null cause (original exception) */ @SneakyThrows public void singleSend(@NotNull TinyMailMessage message, long maxWait, @Nullable MimeMessagePrepareHelper preparer) { @@ -154,7 +154,7 @@ public List batchSend(Collection message } /** - * 支持dryrun,一次链接验证,发送批量邮件,需要单个处理结果 + * Supports dryrun, batch send mails per connect login, need to handle the batch result */ public List batchSend(Collection messages, long maxWait, @Nullable MimeMessagePrepareHelper preparer) { if (messages.isEmpty()) return Collections.emptyList(); @@ -220,7 +220,7 @@ public List batchSend(Collection message now = ThreadNow.millis(); final Wait wt = dealErrorWait(me, now); if (wt != null) { - // 一个sender应该只有一个host,因以按sender分组 + // group by sender. one sender should have only one host for (String host : hosts) { mailHostWait.put(host, wt.wait); } diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailWaitException.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailWaitException.java index c82f85937..d90362d49 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailWaitException.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/MailWaitException.java @@ -10,18 +10,18 @@ public class MailWaitException extends MailException { /** - * 等待的epoch毫秒数 + * Epoch mills to wait */ @Getter private final long waitEpoch; /** - * 是否为host级别的等待 + * Whether it is a host-level wait */ @Getter private final boolean hostLevel; /** - * 相比于等待,更建议停止发送 + * Whether to stop sending than to wait */ @Getter private final boolean stopRetry; diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/TinyMailMessage.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/TinyMailMessage.java index 22d5cee46..7ec90bf13 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/TinyMailMessage.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/sender/TinyMailMessage.java @@ -19,26 +19,26 @@ public class TinyMailMessage extends TinyMailConfig { /** - * 邮件的业务ID + * Business id of the mail */ protected Long bizId; /** - * 邮件的业务标记 + * Business mark of the mail */ protected String bizMark; /** - * 邮件标题 + * Mail subject */ protected String subject; /** - * 邮件正文 + * Mail content */ protected String content; /** - * 邮件附件 + * Mail attachments and its names */ protected Map attachment = null; @@ -61,7 +61,7 @@ public String toMainString() { } /** - * 全部使用that值 + * Use all `that` values */ public void adopt(TinyMailMessage that) { if (that == null) return; @@ -74,7 +74,7 @@ public void adopt(TinyMailMessage that) { } /** - * this值无效时,使用that值 + * Use `that` value if `this` is invalid */ public void merge(TinyMailMessage that) { if (that == null) return; diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMail.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMail.java index 2f36f26e1..7b5d1a5bb 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMail.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMail.java @@ -13,15 +13,15 @@ @Data public class TinyMail { /** - * 配置的名字,空时使用默认值 + * Config name, use default if empty */ protected String conf; /** - * 默认发件人,空时使用默认值 + * Mail from, use default if empty */ protected String from; /** - * 默认收件人,仅null时使用默认值 + * Mail to, use default if null */ protected String[] to; @@ -30,7 +30,7 @@ public void setTo(String... to) { } /** - * 默认抄送,仅null时使用默认值 + * Mail cc, use default if null */ protected String[] cc; @@ -39,7 +39,7 @@ public void setCc(String... cc) { } /** - * 默认暗送,仅null时使用默认值 + * Mail bcc, use default if null */ protected String[] bcc; @@ -48,56 +48,57 @@ public void setBcc(String... bcc) { } /** - * 默认回复,空时使用默认值 + * Mail reply, use default if empty */ protected String reply; /** - * 默认是否发送html邮件(text/html),否则纯文本(text/plain),仅null时使用默认值 + * Whether to send html mail (text/html), otherwise text mail(text/plain). + * use default if null */ protected Boolean html; /** - * 邮件标题,空时使用默认值 + * Mail subject, use default if empty */ protected String subject; /** - * 邮件正文,空时使用默认值 + * Mail content, use default if empty */ protected String content; /** - * 邮件附件,仅null时使用默认值 + * Mail attachment, use default if null */ protected Map attachment = null; /** - * 标记关键词,空格分隔业务key,仅null时使用默认值 + * Business keyword ot mark, space seperated, use default if null */ protected String mark; /** - * 邮件定时发送时间(系统时区) + * Schedule to send mail (system time zone) */ protected LocalDateTime date; /** - * 最大失败次数,默认为系统配置 + * Max count of fail, defaults to system configuration */ protected Integer maxFail = 0; /** - * 最大成功次数,默认为系统配置 + * Max count of done (successfully send), defaults to system configuration */ protected Integer maxDone = 0; /** - * 引用类型,标记key1,key2用途 + * Reference Type to Tag key1, key2 usage */ private Integer refType; /** - * 引用key1,一般为主键 + * Reference key1, Generally the PK */ private Long refKey1; /** - * 引用key2,一般为符合类型 + * Reference key2, Generally the composite type */ private String refKey2; diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailListService.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailListService.java index de3700686..d1a1d9dfe 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailListService.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailListService.java @@ -10,42 +10,42 @@ public interface TinyMailListService { /** - * 获取全部邮件的简要信息,默认倒序 + * list summary of all messages, in reverse order by default. */ PageResult listAll(PageQuery pq); /** - * 获取失败邮件的简要信息,默认倒序 + * list summary of failed emails, in reverse order by default. */ PageResult listFailed(PageQuery pq); /** - * 获取未成功邮件的简要信息,默认倒序 + * list summary of unsuccessful emails, in reverse order by default. */ PageResult listUndone(PageQuery pq); /** - * 根据Biz-Mark获取邮件的简要信息,默认倒序 + * find summary of the email by Biz-Mark, in reverse order by default. */ PageResult listByBizMark(String mark, PageQuery pq); /** - * 根据正则比较收件人to/cc/bcc获取邮件的简要信息,默认倒序 + * find summary of the email by RegExp of to/cc/bcc, reverse order by default. */ PageResult listByRecipient(String mailRegex, PageQuery pq); /** - * 根据收件人from获取邮件的简要信息,默认倒序 + * find summary of the email by from, in reverse order by default. */ PageResult listBySender(String mail, PageQuery pq); /** - * 根据正则比较邮件标题获取邮件的简要信息,默认倒序 + * find summary of the email by RegExp of subject, reverse order by default. */ PageResult listBySubject(String subjRegex, PageQuery pq); /** - * 获取邮件详情 + * get mail detail */ TinyMailPlain loadDetail(long id); } diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailService.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailService.java index c11c3e381..d002bf5d0 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailService.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/TinyMailService.java @@ -8,7 +8,7 @@ import java.time.LocalDateTime; /** - * 可选择同步或异步发送邮件,首先存入database,保证邮件一定发送。 + * Send mail sync or async, save to database first to ensure mail must be sent. * * @author trydofor * @since 2022-12-29 @@ -16,22 +16,27 @@ public interface TinyMailService { /** - * 同步发送,发送成功或失败,或异常。失败时,可进行异步retry + * Sync send, return success or not, or throw exception. + * If not success, async retry */ boolean send(@NotNull TinyMail message, boolean retry); /** - * 同步发送 fire and forget,不会抛出异常。失败时,可进行异步retry + * Sync send, fire and forget, no exception throw. + * If not success, async retry */ boolean post(@NotNull TinyMail message, boolean retry); /** - * 异步发送,忽略异常,自动进行批量处理。失败时,可进行异步retry,返回预计发送时间,-1为失败 + * Async, no exception throw. auto batch send. + * Return the estimated sending time, `-1` for failure + * If not success, async retry. */ long emit(@NotNull TinyMail message, boolean retry); /** - * 同步发送,发送成功或失败,或异常。失败时,可进行异步retry + * Sync send, return success or not, or throw exception. + * If not success, async retry */ default boolean send(@NotNull TinyMailPlain message) { final long id = save(message); @@ -39,7 +44,8 @@ default boolean send(@NotNull TinyMailPlain message) { } /** - * 同步发送 fire and forget,不会抛出异常。失败时,可进行异步retry + * Sync send, fire and forget, no exception throw. + * If not success, async retry */ default boolean post(@NotNull TinyMailPlain message) { final long id = save(message); @@ -47,7 +53,9 @@ default boolean post(@NotNull TinyMailPlain message) { } /** - * 异步发送,忽略异常,自动进行批量处理。失败时,可进行异步retry,返回预计发送时间,-1为失败 + * Async, no exception throw. auto batch send. + * Return the estimated sending time, `-1` for failure + * If not success, async retry. */ default long emit(@NotNull TinyMailPlain message) { final long id = save(message); @@ -55,33 +63,39 @@ default long emit(@NotNull TinyMailPlain message) { } /** - * 同步发送,发送成功或失败,或异常。失败时,可进行异步retry,发送前是否check状态 + * Sync send, fire and forget, no exception throw. + * If not success, async retry, whether to check state before sending */ boolean send(long id, boolean retry, boolean check); /** - * 同步发送 fire and forget,不会抛出异常。失败时,可进行异步retry,发送前是否check状态 + * Sync send, fire and forget, no exception throw. + * If not success, async retry, whether to check state before sending */ boolean post(long id, boolean retry, boolean check); /** - * 异步发送,忽略异常,自动进行批量处理。失败时,可进行异步retry,发送前是否check状态,返回预计发送时间,-1为失败 + * Async, no exception throw. auto batch send. + * Return the estimated sending time, `-1` for failure + * If not success, async retry, whether to check state before sending */ long emit(long id, boolean retry, boolean check); /** - * 新建(id为空)或编辑一个邮件,返回id + * Create(id is empty) or edit a mail, return the id */ long save(@NotNull TinyMailPlain message); /** - * 同步扫描补发的邮件,返回补发的件数 + * Sync scan the mail to resend, return the count, and send them async */ int scan(); /** - * 自动发送,根据时间自行决定同步还是异步发送,-1为发送失败,0为同步发送,否则为异步发送时间 + * Create the mail, and auto send it in sync or async way. + * `-1` means failure, `0` means sync send, + * otherwise means async send at estimated sending time */ default long auto(@NotNull TinyMail message, boolean retry) { final LocalDateTime md = message.getDate(); @@ -95,7 +109,9 @@ default long auto(@NotNull TinyMail message, boolean retry) { } /** - * 自动发送,根据时间自行决定同步还是异步发送,-1为发送失败,0为同步发送,否则为异步发送时间 + * Create the mail, and auto send it in sync or async way. + * `-1` means failure, `0` means sync send, + * otherwise means async send at estimated sending time */ default long auto(@NotNull TinyMailPlain message) { final long id = save(message); diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/impl/TinyMailServiceImpl.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/impl/TinyMailServiceImpl.java index 0f9e957ff..7cccb334d 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/impl/TinyMailServiceImpl.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/service/impl/TinyMailServiceImpl.java @@ -35,8 +35,12 @@ import pro.fessional.wings.tiny.mail.database.autogen.tables.WinMailSenderTable; import pro.fessional.wings.tiny.mail.database.autogen.tables.daos.WinMailSenderDao; import pro.fessional.wings.tiny.mail.database.autogen.tables.pojos.WinMailSender; -import pro.fessional.wings.tiny.mail.sender.*; +import pro.fessional.wings.tiny.mail.sender.MailConfigProvider; +import pro.fessional.wings.tiny.mail.sender.MailSenderManager; import pro.fessional.wings.tiny.mail.sender.MailSenderManager.BatchResult; +import pro.fessional.wings.tiny.mail.sender.MailWaitException; +import pro.fessional.wings.tiny.mail.sender.TinyMailConfig; +import pro.fessional.wings.tiny.mail.sender.TinyMailMessage; import pro.fessional.wings.tiny.mail.service.TinyMail; import pro.fessional.wings.tiny.mail.service.TinyMailPlain; import pro.fessional.wings.tiny.mail.service.TinyMailService; @@ -44,7 +48,13 @@ import java.time.Instant; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; @@ -172,10 +182,10 @@ public long save(@NotNull TinyMailPlain message) { final LocalDateTime md = message.getDate(); if (isNew) { id = lightIdService.getId(winMailSenderDao.getTable()); - // 乐观锁 + // Optimist Lock po.setNextLock(0); po.setNextSend(md != null ? md : ThreadNow.localDateTime()); - // 检查结束 + // Count the result po.setSumSend(0); po.setSumFail(0); po.setSumDone(0); @@ -329,11 +339,11 @@ private WinMailSender saveMailSender(@NotNull TinyMailConfig config, @NotNull Ti Null.notNull(msg.getRefKey1(), po::setRefKey1); Null.notNull(msg.getRefKey2(), po::setRefKey2); - // 乐观锁 + // Optimist lock po.setNextLock(0); po.setNextSend(md != null ? md : ThreadNow.localDateTime()); - // 检查结束 + // Count the result po.setSumSend(0); po.setSumFail(0); po.setSumDone(0); diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailConfigProp.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailConfigProp.java index 0f542c551..bc0d9b8bb 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailConfigProp.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailConfigProp.java @@ -25,7 +25,7 @@ public class TinyMailConfigProp extends LinkedHashMap im private MailProperties springMail; /** - * 默认属性 + * Default Config */ @Getter private TinyMailConfig Default; diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailEnabledProp.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailEnabledProp.java index 39b5f2f9b..a6ff24709 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailEnabledProp.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailEnabledProp.java @@ -17,7 +17,7 @@ public class TinyMailEnabledProp { public static final String Key = "spring.wings.tiny.mail.enabled"; /** - * 是否启动自动配置 + * whether to enable auto config * * @see #Key$autoconf */ @@ -25,7 +25,7 @@ public class TinyMailEnabledProp { public static final String Key$autoconf = Key + ".autoconf"; /** - * 是否干跑,仅记录日志不真正执行任务 + * whether to dry run, log only without actually send * * @see #Key$dryrun */ @@ -33,7 +33,7 @@ public class TinyMailEnabledProp { public static final String Key$dryrun = Key + ".dryrun"; /** - * 是否开启 MailListController + * whether to enable MailListController * * @see #Key$controllerList */ @@ -41,7 +41,7 @@ public class TinyMailEnabledProp { public static final String Key$controllerList = Key + ".controller-list"; /** - * 是否开启 MailSendController + * whether to enable MailSendController * * @see #Key$controllerSend */ diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailSenderProp.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailSenderProp.java index aa15ca03b..38bda3983 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailSenderProp.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailSenderProp.java @@ -18,7 +18,7 @@ public class TinyMailSenderProp { public static final String Key = "wings.tiny.mail.sender"; /** - * biz-id的Header + * biz-id Header to locate mail by business, default mail id. * * @see #Key$bizId */ @@ -26,7 +26,7 @@ public class TinyMailSenderProp { public static final String Key$bizId = Key + ".biz-id"; /** - * biz-mark的Header + * biz-mark Header to locate data by business, eg. orderNumber. * * @see #Key$bizMark */ @@ -34,7 +34,7 @@ public class TinyMailSenderProp { public static final String Key$bizMark = Key + ".biz-mark"; /** - * 发送失败 MailSendException 时,等待多少时间,默认5分钟 + * how much time to wait if MailSendException, default 5 minutes. * * @see #Key$errSend */ @@ -42,7 +42,7 @@ public class TinyMailSenderProp { public static final String Key$errSend = Key + ".err-send"; /** - * 认证失败 MailAuthenticationException 时,等待多少时间,默认1小时 + * how much time to wait if MailAuthenticationException, default 1 hour. * * @see #Key$errAuth */ @@ -50,7 +50,9 @@ public class TinyMailSenderProp { public static final String Key$errAuth = Key + ".err-auth"; /** - * 包括以下异常信息时,对此host进行多少秒的等待。秒为key,以小数部分仅用来区分key,负数为建议停止重发 + * how many seconds to wait for the host if it contains the + * following exception message. seconds is the key, the fraction is only used to make + * key unique, negative number means stop resending. * * @see #Key$errHost */ @@ -58,15 +60,17 @@ public class TinyMailSenderProp { public static final String Key$errHost = Key + ".err-host"; /** - * 包括以下异常信息时,对此邮件的重发进行多少秒的等待。秒为key,以小数部分仅用来区分key,负数为建议停止重发 - * 如 `501001.001`的意义为,501为错误号,001为host编号,.001为区别位 + * how many seconds to wait to resend this email if it contains the + * following exception message. seconds is the key, the fraction is only used to make key unique, + * negative number means stop resending. + * * @see #Key$errMail */ private Map errMail = Collections.emptyMap(); public static final String Key$errMail = Key + ".err-mail"; /** - * 同一邮件host每次登录的间隔,避免限频,默认无视 + * interval of each login of the same mailhost, avoid limit frequency, 0 is ignored. * * @see #Key$perIdle */ @@ -74,7 +78,8 @@ public class TinyMailSenderProp { public static final String Key$perIdle = Key + ".per-idle"; /** - * 同一邮件host最多等待时间,小于时等待,否则抛出MailWaitException,默认无视 + * max wait time for the same mailhost, if less then wait, + * otherwise throw MailWaitException, 0 is ignored. * * @see #Key$maxIdle */ @@ -82,7 +87,7 @@ public class TinyMailSenderProp { public static final String Key$maxIdle = Key + ".max-idle"; /** - * 强制替换真实的to + * force to replace the real "to", string arrays, comma separated. * * @see #Key$forceTo */ @@ -90,7 +95,7 @@ public class TinyMailSenderProp { public static final String Key$forceTo = Key + ".force-to"; /** - * 强制替换真实的cc + * force to replace the real "cc", string arrays, comma separated. * * @see #Key$forceCc */ @@ -98,7 +103,7 @@ public class TinyMailSenderProp { public static final String Key$forceCc = Key + ".force-cc"; /** - * 强制替换真实的bcc + * force to replace the real "bcc", string arrays, comma separated. * * @see #Key$forceBcc */ @@ -106,6 +111,8 @@ public class TinyMailSenderProp { public static final String Key$forceBcc = Key + ".force-bcc"; /** + * force to add prefix to the real subject. + * * @see #Key$forcePrefix */ private String forcePrefix = ""; diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailServiceProp.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailServiceProp.java index c16915fc4..8c7c8467f 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailServiceProp.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailServiceProp.java @@ -15,7 +15,7 @@ public class TinyMailServiceProp { public static final String Key = "wings.tiny.mail.service"; /** - * 同一邮件最大失败次数 + * max failures for the same email. * * @see #Key$maxFail */ @@ -23,7 +23,7 @@ public class TinyMailServiceProp { public static final String Key$maxFail = Key + ".max-fail"; /** - * 同一邮件最大成功次数 + * max success for the same email. * * @see #Key$maxDone */ @@ -31,7 +31,7 @@ public class TinyMailServiceProp { public static final String Key$maxDone = Key + ".max-done"; /** - * 超过多少时间的邮件不需要发送,默认1天 + * the email does not need to be sent anymore as it has been a certain amount of time. default 1 day. * * @see #Key$maxNext */ @@ -39,7 +39,7 @@ public class TinyMailServiceProp { public static final String Key$maxNext = Key + ".max-next"; /** - * 失败后多久进行重试,默认1分钟 + * how soon to retry after failure, default 1 minute. * * @see #Key$tryNext */ @@ -47,7 +47,7 @@ public class TinyMailServiceProp { public static final String Key$tryNext = Key + ".try-next"; /** - * 批量发送时,一次发的最大件数 + * max number of bulk emails sent at one time. * * @see #Key$batchSize */ @@ -55,7 +55,7 @@ public class TinyMailServiceProp { public static final String Key$batchSize = Key + ".batch-size"; /** - * 超过此容量时,以Warn记录日志 + * if this capacity is exceeded, log it as Warn. * * @see #Key$warnSize */ @@ -63,7 +63,7 @@ public class TinyMailServiceProp { public static final String Key$warnSize = Key + ".warn-size"; /** - * 启动后多少秒,扫描未发送的邮件,-1为不扫描 + * how long after start, scan for unsent mail, `0` for no scan. * * @see #Key$bootScan */ @@ -71,7 +71,7 @@ public class TinyMailServiceProp { public static final String Key$bootScan = Key + ".boot-scan"; /** - * 是否仅发送本app的邮件 + * whether to send emails from this app only. * * @see #Key$onlyApp */ @@ -79,7 +79,7 @@ public class TinyMailServiceProp { public static final String Key$onlyApp = Key + ".only-app"; /** - * 是否仅发送本RumMode的邮件 + * whether to send emails from this RumMode only. * * @see #Key$onlyRun */ diff --git a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailUrlmapProp.java b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailUrlmapProp.java index 0d48ecce5..91c9a365b 100644 --- a/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailUrlmapProp.java +++ b/radiant/tiny-mail/src/main/java/pro/fessional/wings/tiny/mail/spring/prop/TinyMailUrlmapProp.java @@ -13,7 +13,7 @@ public class TinyMailUrlmapProp { public static final String Key = "wings.tiny.mail.urlmap"; /** - * 获取全部邮件的简要信息,默认倒序 + * list summary of all messages, in reverse order by default. * * @see #Key$listAll */ @@ -21,7 +21,7 @@ public class TinyMailUrlmapProp { public static final String Key$listAll = Key + ".list-all"; /** - * 获取失败邮件的简要信息,默认倒序 + * list summary of failed emails, in reverse order by default. * * @see #Key$listFailed */ @@ -29,7 +29,7 @@ public class TinyMailUrlmapProp { public static final String Key$listFailed = Key + ".list-failed"; /** - * 获取未成功邮件的简要信息,默认倒序 + * list summary of unsuccessful emails, in reverse order by default. * * @see #Key$listUndone */ @@ -37,7 +37,7 @@ public class TinyMailUrlmapProp { public static final String Key$listUndone = Key + ".list-undone"; /** - * 根据Biz-Mark获取邮件的简要信息,默认倒序 + * find summary of the email by Biz-Mark, in reverse order by default. * * @see #Key$byBizmark */ @@ -45,7 +45,7 @@ public class TinyMailUrlmapProp { public static final String Key$byBizmark = Key + ".by-bizmark"; /** - * 根据正则比较收件人to/cc/bcc获取邮件的简要信息,默认倒序 + * find summary of the email by RegExp of to/cc/bcc, reverse order by default. * * @see #Key$byRecipient */ @@ -53,7 +53,7 @@ public class TinyMailUrlmapProp { public static final String Key$byRecipient = Key + ".by-recipient"; /** - * 根据收件人from获取邮件的简要信息,默认倒序 + * find summary of the email by from, in reverse order by default. * * @see #Key$bySender */ @@ -61,7 +61,7 @@ public class TinyMailUrlmapProp { public static final String Key$bySender = Key + ".by-sender"; /** - * 根据正则比较邮件标题获取邮件的简要信息,默认倒序 + * find summary of the email by RegExp of subject, reverse order by default. * * @see #Key$bySubject */ @@ -69,7 +69,7 @@ public class TinyMailUrlmapProp { public static final String Key$bySubject = Key + ".by-subject"; /** - * 获取邮件详情 + * get mail detail. * * @see #Key$loadDetail */ @@ -77,7 +77,7 @@ public class TinyMailUrlmapProp { public static final String Key$loadDetail = Key + ".load-detail"; /** - * 新建或编辑邮件,并同步立即或异步定时发送 + * create or save an email, and send it immediately or asynchronously * * @see #Key$sendMail */ @@ -85,7 +85,7 @@ public class TinyMailUrlmapProp { public static final String Key$sendMail = Key + ".send-mail"; /** - * 仅新建或编辑邮件,但并不发送 + * only save messages, but do not send them. * * @see #Key$sendSave */ @@ -93,7 +93,7 @@ public class TinyMailUrlmapProp { public static final String Key$sendSave = Key + ".send-save"; /** - * 同步重试失败的邮件,发送成功或失败,或异常 + * sync retry failed emails, send success or failure, or exceptions * * @see #Key$sendRetry */ @@ -101,7 +101,8 @@ public class TinyMailUrlmapProp { public static final String Key$sendRetry = Key + ".send-retry"; /** - * 同步扫需要描补发的邮件,并异步发送,返回补发的件数 + * sync scan the emails that need to resend, + * and send them async, return the number of resend emails. * * @see #Key$sendScan */ diff --git a/radiant/tiny-mail/src/main/resources/wings-conf/spring-wings-enabled-79.properties b/radiant/tiny-mail/src/main/resources/wings-conf/spring-wings-enabled-79.properties index e4bf61256..32952cff7 100644 --- a/radiant/tiny-mail/src/main/resources/wings-conf/spring-wings-enabled-79.properties +++ b/radiant/tiny-mail/src/main/resources/wings-conf/spring-wings-enabled-79.properties @@ -1,10 +1,11 @@ -# 是否启动自动配置 +## whether to enable auto config spring.wings.tiny.mail.enabled.autoconf=true -# 是否干跑,仅记录日志不真正执行任务 + +## whether to dry run, log only without actually send spring.wings.tiny.mail.enabled.dryrun=false -# 是否开启 MailListController +## whether to enable MailListController spring.wings.tiny.mail.enabled.controller-list=true -# 是否开启 MailSendController +## whether to enable MailSendController spring.wings.tiny.mail.enabled.controller-send=true diff --git a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-config-79.properties b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-config-79.properties index a21f7ec10..db57626a6 100644 --- a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-config-79.properties +++ b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-config-79.properties @@ -1,22 +1,22 @@ -# 配置多个发送账号,key同spring mail配置 +## Configure multiple sender accounts with the same key as spring mail. -# 默认发送项,TinyMailConfig 类型,以spring.mail为default, -# 对于properties的值,仅存在且无效时,使用为default值 +## Default send item, TinyMailConfig type, with spring.mail as default. +## For properties, the default value is used only if it exists and is invalid. -# 发件人,字符串 +## Mail from, string wings.tiny.mail.config.default.from=${QQ_MAIL_USER:} -# 收件人,字符串数组,逗号分隔 +## Mail to, string array, comma seperated wings.tiny.mail.config.default.to=${QQ_MAIL_USER:} -# 抄送,字符串数组,逗号分隔 +## Mail cc, string array, comma seperated wings.tiny.mail.config.default.cc= -# 暗送,字符串数组,逗号分隔 +## Mail bcc, string array, comma seperated wings.tiny.mail.config.default.bcc= -# 回复,字符串 +## Mail reply, string wings.tiny.mail.config.default.reply= -# 是否发送html邮件(text/html),否则纯文本(text/plain) +## Whether to send html mail (text/html), otherwise text mail(text/plain). wings.tiny.mail.config.default.html=true -## gmail # 推荐使用app专用密码,不要使用登录密码 +## gmail # Recommended to use app-specific passwords, not login passwords #wings.tiny.mail.config.gmail.host=smtp.gmail.com #wings.tiny.mail.config.gmail.username=${GMAIL_USER:} #wings.tiny.mail.config.gmail.password=${GMAIL_PASS:} diff --git a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-sender-79.properties b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-sender-79.properties index 783dfa59f..817a247b9 100644 --- a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-sender-79.properties +++ b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-sender-79.properties @@ -1,37 +1,36 @@ -# biz-id的Header +## biz-id Header to locate mail by business, default mail id. wings.tiny.mail.sender.biz-id=X-Biz-Id -# biz-mark的Header +## biz-mark Header to locate data by business, eg. orderNumber. wings.tiny.mail.sender.biz-mark=X-Biz-Mark -# 发送失败 MailSendException 时,默认等待多少时间,默认5分钟 +## how much time to wait if MailSendException, default 5 minutes. wings.tiny.mail.sender.err-send=5m -# 认证失败 MailAuthenticationException 时,默认等待多少时间,默认1小时 +## how much time to wait if MailAuthenticationException, default 1 hour. wings.tiny.mail.sender.err-auth=1h -# 付费邮箱,也有限频或限流,各供应商存在差异 -## 腾讯邮箱对相同的发件人有一定的频率限制: -## 1、超过每分钟发信量限制,被禁止发信若干分钟。 -## 2、超过每小时发信量限制,被禁止发信若干小时。 -## 3、超过每日发信量限制,本日内禁止再发信。 -## 4、以上频率限制数值属于腾讯邮箱保密数据,恕不公开。 -# 包括以下异常信息时,对此host进行多少秒的等待。秒为key,以小数部分仅用来区分key,负数为建议停止发送 +## how many seconds to wait for the host if it contains the +## following exception message. seconds is the key, the fraction is only used to make +## key unique, negative number means stop resending. wings.tiny.mail.sender.err-host[3600.001]=frequency limited -# 包括以下异常信息时,对此邮件的重发进行多少秒的等待。秒为key,以小数部分仅用来区分key,负数为建议停止发送 -# 501为错误号,001为host编号,.001为区别位 +## how many seconds to wait to resend this email if it contains the +## following exception message. seconds is the key, the fraction is only used to make key unique, +## negative number means stop resending. +## `501` is error number, `001` is host number, `.001` is the unique bit. wings.tiny.mail.sender.err-mail[-501001.001]=from address must be same as authorization user wings.tiny.mail.sender.err-mail[-502001.001]=502 Invalid input from -# 同一邮件host每次登录的间隔,避免限频,0为无视 +## interval of each login of the same mailhost, avoid limit frequency, 0 is ignored. #wings.tiny.mail.sender.per-idle[smtp.qq.com]=500ms -# 同一邮件host最多等待时间,小于时等待,否则抛出MailWaitException,0为无视 +## max wait time for the same mailhost, if less then wait, +## otherwise throw MailWaitException, 0 is ignored. #wings.tiny.mail.sender.max-idle[smtp.qq.com]=3s -# 强制替换真实的to,字符串数组,逗号分隔 +## force to replace the real "to", string arrays, comma separated. wings.tiny.mail.sender.force-to= -# 强制替换真实的cc,字符串数组,逗号分隔 +## force to replace the real "cc", string arrays, comma separated. wings.tiny.mail.sender.force-cc= -# 强制替换真实的bcc,字符串数组,逗号分隔 +## force to replace the real "bcc", string arrays, comma separated. wings.tiny.mail.sender.force-bcc= -# 强制增加真实的subject前缀 +## force to add prefix to the real subject. wings.tiny.mail.sender.force-prefix= diff --git a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-service-79.properties b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-service-79.properties index 91ef97ab6..ec095542d 100644 --- a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-service-79.properties +++ b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-service-79.properties @@ -1,18 +1,18 @@ -# 同一邮件最大失败次数 +## max failures for the same email. wings.tiny.mail.service.max-fail=3 -# 同一邮件最大成功次数 +## max success for the same email. wings.tiny.mail.service.max-done=1 -# 超过多少时间的邮件不需要发送,默认1天 +## the email does not need to be sent anymore as it has been a certain amount of time. default 1 day. wings.tiny.mail.service.max-next=1d -# 失败后多久进行重试,默认1分钟 +## how soon to retry after failure, default 1 minute. wings.tiny.mail.service.try-next=1m -# 批量发送时,一次发的最大件数 +## max number of bulk emails sent at one time. wings.tiny.mail.service.batch-size=10 -# 超过此容量时,以Warn记录日志 +## if this capacity is exceeded, log it as Warn. wings.tiny.mail.service.warn-size=50 -# 启动后多少秒,扫描未发送的邮件,0为不扫描 +## how long after start, scan for unsent mail, `0` for no scan. wings.tiny.mail.service.boot-scan=60s -# 是否仅发送本app的邮件 +## whether to send emails from this app only. wings.tiny.mail.service.only-app=false -# 是否仅发送本RumMode的邮件 +## whether to send emails from this RumMode only. wings.tiny.mail.service.only-run=true diff --git a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-urlmap-79.properties b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-urlmap-79.properties index e2087455c..322ba3b03 100644 --- a/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-urlmap-79.properties +++ b/radiant/tiny-mail/src/main/resources/wings-conf/wings-tinymail-urlmap-79.properties @@ -1,35 +1,36 @@ -# 获取全部邮件的简要信息,默认倒序 +## list summary of all messages, in reverse order by default. wings.tiny.mail.urlmap.list-all=/admin/mail/list-all.json -# 获取失败邮件的简要信息,默认倒序 +## list summary of failed emails, in reverse order by default. wings.tiny.mail.urlmap.list-failed=/admin/mail/list-failed.json -# 获取未成功邮件的简要信息,默认倒序 +## list summary of unsuccessful emails, in reverse order by default. wings.tiny.mail.urlmap.list-undone=/admin/mail/list-undone.json -# 根据Biz-Mark获取邮件的简要信息,默认倒序 +## find summary of the email by Biz-Mark, in reverse order by default. wings.tiny.mail.urlmap.by-bizmark=/admin/mail/by-bizmark.json -# 根据正则比较收件人to/cc/bcc获取邮件的简要信息,默认倒序 +## find summary of the email by RegExp of to/cc/bcc, reverse order by default. wings.tiny.mail.urlmap.by-recipient=/admin/mail/by-recipient.json -# 根据收件人from获取邮件的简要信息,默认倒序 +## find summary of the email by from, in reverse order by default. wings.tiny.mail.urlmap.by-sender=/admin/mail/by-sender.json -# 根据正则比较邮件标题获取邮件的简要信息,默认倒序 +## find summary of the email by RegExp of subject, reverse order by default. wings.tiny.mail.urlmap.by-subject=/admin/mail/by-subject.json -# 获取邮件详情 +# get mail detail. wings.tiny.mail.urlmap.load-detail=/admin/mail/load-detail.json -# 新建或编辑邮件,并同步立即或异步定时发送,-1为失败,0为同步,否则为异步 +## create or save an email, and send it immediately or asynchronously wings.tiny.mail.urlmap.send-mail=/admin/mail/send-mail.json -# 仅新建或编辑邮件,但并不发送 +## only save messages, but do not send them. wings.tiny.mail.urlmap.send-save=/admin/mail/send-save.json -# 同步重试失败的邮件,发送成功或失败,或异常 +## sync retry failed emails, send success or failure, or exceptions wings.tiny.mail.urlmap.send-retry=/admin/mail/send-retry.json -# 同步扫需要描补发的邮件,并异步发送,返回补发的件数 +## sync scan the emails that need to resend, +## and send them async, return the number of resend emails. wings.tiny.mail.urlmap.send-scan=/admin/mail/send-scan.json diff --git a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailNoticeTest.java b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailNoticeTest.java index 6aa5ba1c2..0aa22349c 100644 --- a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailNoticeTest.java +++ b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailNoticeTest.java @@ -47,7 +47,7 @@ public void testPost() { } @Test - @Disabled("统计耗时") + @Disabled("Statistics time cost") public void testDefault() { final StopWatch stopWatch = new StopWatch(); try (final StopWatch.Watch ignored = stopWatch.start("emit")) { diff --git a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailSenderManagerTest.java b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailSenderManagerTest.java index 60157e509..be0744b65 100644 --- a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailSenderManagerTest.java +++ b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/sender/MailSenderManagerTest.java @@ -18,7 +18,7 @@ @SpringBootTest(properties = { "wings.tiny.mail.service.boot-scan=0", }) -@Disabled("批量发邮件,手动执行") +@Disabled("Batch send mails, manual") @Slf4j public class MailSenderManagerTest { @@ -31,7 +31,7 @@ public class MailSenderManagerTest { /** *
-     * single连续发送10个,javax.mail.AuthenticationFailedException: 535 Login Fail. Please enter your authorization code to login
+     * send 10 mails, javax.mail.AuthenticationFailedException: 535 Login Fail. Please enter your authorization code to login
      * 1-single vs batch-5
      * +--s--ms------ns-+---%-+--------+---------------
      * |  5,403,675,738 | 100 | thread | task and timing
diff --git a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/service/TinyMailServiceTest.java b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/service/TinyMailServiceTest.java
index b570f7a23..402fcba8a 100644
--- a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/service/TinyMailServiceTest.java
+++ b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/mail/service/TinyMailServiceTest.java
@@ -15,7 +15,7 @@
 @SpringBootTest(properties = {
         "wings.tiny.mail.service.boot-scan=0",
 })
-@Disabled("邮件测试,手动执行")
+@Disabled("Mail test, manual")
 class TinyMailServiceTest {
 
     @Setter(onMethod_ = {@Autowired})
diff --git a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/project/TinyMailCodeGenTest.java b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/project/TinyMailCodeGenTest.java
index fad3305c5..aeed789d2 100644
--- a/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/project/TinyMailCodeGenTest.java
+++ b/radiant/tiny-mail/src/test/java/pro/fessional/wings/tiny/project/TinyMailCodeGenTest.java
@@ -20,7 +20,7 @@
         "spring.wings.faceless.flywave.enabled.checker=false",
         "wings.tiny.mail.service.boot-scan=0",
 })
-@Disabled("生成代码,已有devs统一管理")
+@Disabled("Code gen, managed by devops")
 public class TinyMailCodeGenTest {
     @Setter(onMethod_ = {@Autowired})
     private SchemaRevisionManager schemaRevisionManager;
diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskConfController.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskConfController.java
index 3fc908b4d..d6e1a6dde 100644
--- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskConfController.java
+++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskConfController.java
@@ -38,7 +38,7 @@ public static class In1 {
         private boolean enable;
     }
 
-    @Operation(summary = "启动或禁用任务")
+    @Operation(summary = "enable or disable a task.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskEnable + "}")
     @ResponseBody
     public R taskEnable(@RequestBody In1 ins) {
@@ -52,7 +52,7 @@ public static class In2 extends TaskerProp {
         private long id;
     }
 
-    @Operation(summary = "更新任务配置")
+    @Operation(summary = "update the task config.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskPropSave + "}")
     @ResponseBody
     public R taskPropSave(@RequestBody In2 ins) {
@@ -60,7 +60,7 @@ public R taskPropSave(@RequestBody In2 ins) {
         return R.okData(ok);
     }
 
-    @Operation(summary = "任务载入属性")
+    @Operation(summary = "load the task config.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskPropLoad + "}")
     @ResponseBody
     public R taskPropLoad(@RequestBody Q.Id ins) {
@@ -68,7 +68,7 @@ public R taskPropLoad(@RequestBody Q.Id ins) {
         return R.okData(pp);
     }
 
-    @Operation(summary = "任务配置属性")
+    @Operation(summary = "show the prop of task conf.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskPropConf + "}")
     @ResponseBody
     public R taskPropConf(@RequestBody Q.Id ins) {
@@ -76,7 +76,7 @@ public R taskPropConf(@RequestBody Q.Id ins) {
         return R.okData(pp);
     }
 
-    @Operation(summary = "任务配置属性")
+    @Operation(summary = "show the diff of task conf.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskPropDiff + "}")
     @ResponseBody
     public R>> taskPropDiff(@RequestBody Q.Id ins) {
diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskExecController.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskExecController.java
index 7f8aa6a7a..dc4b4c9ba 100644
--- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskExecController.java
+++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskExecController.java
@@ -26,7 +26,7 @@ public class TaskExecController {
     protected TinyTaskExecService tinyTaskExecService;
 
 
-    @Operation(summary = "取消任务")
+    @Operation(summary = "cancel a task.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskCancel + "}")
     @ResponseBody
     public R taskCancel(@RequestBody Q.Id ins) {
@@ -34,7 +34,7 @@ public R taskCancel(@RequestBody Q.Id ins) {
         return R.okData(cancel);
     }
 
-    @Operation(summary = "启动任务")
+    @Operation(summary = "start a task.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskLaunch + "}")
     @ResponseBody
     public R taskLaunch(@RequestBody Q.Id ins) {
@@ -42,7 +42,7 @@ public R taskLaunch(@RequestBody Q.Id ins) {
         return R.okData(cancel);
     }
 
-    @Operation(summary = "强制执行任务")
+    @Operation(summary = "force to start a task.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskForce + "}")
     @ResponseBody
     public R taskForce(@RequestBody Q.Id ins) {
diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskListController.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskListController.java
index 3197315f1..94b826c06 100644
--- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskListController.java
+++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/controller/TaskListController.java
@@ -27,25 +27,25 @@ public class TaskListController {
     @Setter(onMethod_ = {@Autowired})
     protected TinyTaskListService tinyTaskListService;
 
-    @Operation(summary = "列出当前运行中的任务")
+    @Operation(summary = "list of running tasks.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskRunning + "}")
     @ResponseBody
     public PageResult taskRunning(PageQuery pq) {
         return tinyTaskListService.listRunning(pq);
     }
 
-    @Operation(summary = "列出已定义的任务")
+    @Operation(summary = "list of defined tasks.")
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskDefined + "}")
     @ResponseBody
     public PageResult taskDefined(PageQuery pq) {
         return tinyTaskListService.listDefined(pq);
     }
 
-    @Operation(summary = "列出任务的结果", description = """
+    @Operation(summary = "list of task results.", description = """
             # Usage
-            列出任务的结果。
+            list of task results.
             ## Params
-            * @param id - 必填,任务id
+            * @param id - required, task id
             """)
     @PostMapping(value = "${" + TinyTaskUrlmapProp.Key$taskResult + "}")
     @ResponseBody
diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/TinyTasker.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/TinyTasker.java
index 0b8d9402a..aeca3df51 100644
--- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/TinyTasker.java
+++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/TinyTasker.java
@@ -9,15 +9,18 @@
 
 /**
  * 
- * 除value外,其他属性推荐使用配置文件!
- * 标记任务Bean的执行方法。注意事项如下,
- * 无参数的方法,可被自动注册和执行,
- * 有参数的方法,只能手动注册并执行。
- * 不可使用多态,仅通过方法名定位(简化及可读性),不使用参数列表
- * 若有参数,则其必须是具体类型,无状态的,可被序列化的(默认json)
+ * A config file is recommended for all properties except `value`!
  *
- * annotation优先级低于property,高于default。
- * 合并后的配置,最终写入database,以id管理task
+ * Marks the execution method of the Task Bean. Note the following.
+ * - Methods without parameters can be registered and executed automatically.
+ * - Methods with parameters can only be registered and executed manually.
+ * - Polymorphism is not allowed, methods are located by name only
+ *   (for simplicity and readability), and parameter lists are not used.
+ * - If there is a parameter, it must be of a specific type, stateless,
+ *   and serializable (json by default).
+ *
+ * Annotation has a lower priority than property and a higher priority than default.
+ * The merged config is eventually saved to database, which manages the task with the id
  * 
* * @author trydofor @@ -29,36 +32,44 @@ public @interface TinyTasker { /** - * propkey,配置的属性名,默认为Class#method, - * 其属性类型为Map[String,TaskerConf],位于前缀默认为wings.tiny.task.define下 - * 如 wings.tiny.task.define[pro.fessional.wings.tiny.task.schedule.TaskerTest#test].enable=false + * `propkey`, the key of the property, which defaults to `Class#method`. + * Its property type is Map[String,TaskerConf], located under the prefix `wings.tiny.task.define` + * e.g. `wings.tiny.task.define[pro.fessional.wings.tiny.task.schedule.TaskerTest#test].enable=false` */ String value() default ""; /** - * timingZone,调度时区的ZoneId格式,默认系统时区,null及空时使用Default配置 + * timingZone, scheduling timezone in ZoneId format, default system time zone. + * Use the `Default` config if null or empty. */ String zone() default ""; /** - * timingCron,调度表达式内容,最高优先级,受timingType影响,默认spring cron格式(秒分时日月周),不会使用Default配置 + * timingCron, scheduling expression, highest priority, affected by timingType, + * default spring cron format (seconds minutes hours days months weeks). + * Not use the `Default` config */ String cron() default ""; /** - * timingIdle,固定空闲相连(秒),优先级次于timingCron,相当于fixedDelay,结束到开始,0为无效,不会使用Default配置 + * timingIdle, fixed idle between (seconds), + * priority second to timingCron,equivalent to fixedDelay. + * end to start, 0 is invalid, Not use the `Default` config + * */ int idle() default 0; /** - * timingRate,固定频率开始(秒),优先级次于timingIdle,相当于fixedRate,开始到开始,0为无效,不会使用Default配置 + * timingRate, fixed frequency start (seconds), + * priority second to timingIdle, equivalent to fixedRate. + * start to start, 0 is invalid, Not use the `Default` config */ int rate() default 0; /** - * 对SpringBean增加此标记类,可被Wings在启动时自动配置 + * Adding to a SpringBean can be auto config by Wings at startup. */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/conf/TaskerProp.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/conf/TaskerProp.java index c59526949..f70bb55f4 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/conf/TaskerProp.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/conf/TaskerProp.java @@ -4,7 +4,7 @@ import lombok.NoArgsConstructor; /** - * TinyTask的配置项,一个taskerBean只能存在一条 + * TinyTask Config, A taskerBean can have only one * * @author trydofor * @since 2022-12-09 @@ -13,40 +13,44 @@ @NoArgsConstructor public class TaskerProp { /** - * 是否可以注册及执行,不会使用Default配置 + * whether to register and execute, not use Default config. */ protected boolean enabled = true; /** - * 是否可以自动注册并启动,不会使用Default配置 + * whether to auto register and start, not use Default config. */ protected boolean autorun = true; /** - * 版本号,版本高的配置覆盖版本低的,不会使用Default配置 + * version number, higher version config overrides lower one, not use Default config. */ protected int version = 0; /** - * 由TinyTasker注解的Bean,格式为Class#method,默认自动识别,不会使用Default配置 + * Beans annotated by TinyTasker, formatted as Class#method, + * automatically recognized by default, not use Default config. */ protected String taskerBean = null; /** - * 任务的参数,对象数组的json格式,默认null或空无参数,不会使用Default配置 + * Parameters of the task, object array in json format, + * default null or no parameters, not use Default config. */ protected String taskerPara = null; /** - * 任务名字,用于通知和日志,可读性好一些,默认为'[全类名#方法名]',不会使用Default配置 + * Task name, used for notice and log, better readability, + * default is `[shortClassName#method]`, not use Default config. */ protected String taskerName = null; /** - * 是否为轻任务,执行快,秒级完成,不会使用Default配置 + * Whether it is a light task, fast execution, completed in seconds, not use Default config. */ protected boolean taskerFast = true; /** - * 所属程序,逗号分隔,推荐一个,使用spring.application.name,空时使用Default配置 + * The app it belongs to, comma separated, + * use Default config if null or empty. */ protected String taskerApps = null; @@ -55,7 +59,8 @@ public boolean notTaskerApps() { } /** - * 执行模式,RunMode(product|test|develop|local),逗号分隔忽略大小写,空表示所有。空时使用Default配置 + * RunMode(product|test|develop|local), Comma separated, ignore case, default all, + * use Default config if null or empty. * * @see pro.fessional.wings.silencer.modulate.RunMode */ @@ -66,7 +71,8 @@ public boolean notTaskerRuns() { } /** - * 通知Bean,SmallNotice类型,格式为Class,默认无通知。空时使用Default配置 + * Notice bean, SmallNotice type, fullpath of Class, no notice by default. + * use Default config if null or empty. */ protected String noticeBean = ""; @@ -76,11 +82,11 @@ public boolean notNoticeBean() { /** *
-     * 通知的时机,exec|fail|done,逗号分隔忽略大小写,默认fail。空时使用Default配置
-     * 时机大概表述为:exec;try{run...;done}catch{fail}
-     * exec - 初始任务
-     * done - 执行成功
-     * fail - 执行失败
+     * Timing of notice, exec|fail|done|feed, comma separated ignoring case, default fail.
+     * use Default config if null or empty.
+     *
+     * * timing is roughly expressed: exec;try{run...;done}catch{fail}
+     * * exec - init task; done - success; fail - failed; feed - non-empty return.
      * 
*/ protected String noticeWhen = ""; @@ -90,7 +96,7 @@ public boolean notNoticeWhen() { } /** - * 通知Bean的配置文件名字,默认自动,空时使用Default配置 + * The config name of the notice bean, automatic by default. use Default config if empty. */ protected String noticeConf = ""; @@ -99,7 +105,7 @@ public boolean notNoticeConf() { } /** - * 调度时区的ZoneId格式,默认系统时区,空时使用Default配置,null为自动替换 + * timezone of scheduling , default system timezone, use Default config if null or empty. */ protected String timingZone = null; @@ -108,7 +114,8 @@ public boolean notTimingZone() { } /** - * 调度表达式类型,影响timingCron的解析方式,默认为spring的cron格式,空时使用Default配置 + * scheduling expression type, affects how timingCron is parsed, + * defaults to spring cron format, use Default config if null or empty. */ protected String timingType = ""; @@ -117,7 +124,8 @@ public boolean notTimingType() { } /** - * 调度表达式内容,最高优先级,受timingType影响,默认spring cron格式(秒分时日月周),不会使用Default配置 + * Scheduling expression content, highest priority, affected by timingType, + * default spring cron format (second minute hour day month week), not use Default config. * * @see org.springframework.scheduling.annotation.Scheduled * @see org.springframework.scheduling.support.CronTrigger @@ -129,7 +137,8 @@ public boolean hasTimingCron() { } /** - * 固定空闲相连(秒),优先级次于timingCron,相当于fixedDelay,结束到开始,0为无效,不会使用Default配置 + * Fixed idle interval (seconds), lower priority than timingCron, + * equal to fixedDelay, end to start, 0 means disable, not use Default config. * * @see org.springframework.scheduling.annotation.Scheduled * @see org.springframework.scheduling.support.PeriodicTrigger @@ -141,7 +150,8 @@ public boolean hasTimingIdle() { } /** - * 固定频率开始(秒),优先级次于timingIdle,相当于fixedRate,开始到开始,0为无效,不会使用Default配置 + * Fixed frequency interval (seconds), lower priority than timingIdle, + * equal to fixedRate, start to start, 0 means disable, not use Default config. * * @see org.springframework.scheduling.annotation.Scheduled * @see org.springframework.scheduling.support.PeriodicTrigger @@ -153,48 +163,55 @@ public boolean hasTimingRate() { } /** - * 是否 hasTimingCron或hasTimingIdle或hasTimingRate + * Whether no hasTimingCron, hasTimingIdle or hasTimingRate */ public boolean notTimingPlan() { return !hasTimingCron() && !hasTimingIdle() && !hasTimingRate(); } /** - * 错过调度(misfire)多少秒内,需要补救执行,0表示不补救,不会使用Default配置 + * Within how many seconds of a misfire, execution is required, + * 0 means no execution. not use Default config. */ protected int timingMiss = 0; /** - * 心跳间隔秒数,last_exec距今超过2个心态任务task异常,默认自动。取rate或idle最大值,cron需要自行指定,不会使用Default配置 + * the interval seconds of heartbeat, if the task's last_exec is more + * than 2 heartbeats away from now, it is considered as an exception. default auto to + * take rate or idle maximum, cron needs to specify it by itself, not use Default config. */ protected int timingBeat = 0; /** - * 调度开始的日期时间,timingZone时区,yyyy-MM-dd HH:mm:ss,0表示无效,不会使用Default配置 + * schedule start datetime at timingZone, in yyyy-MM-dd HH:mm:ss format, + * 0 means disable, not use Default config. */ protected String duringFrom = ""; /** - * 调度结束的日期时间,timingZone时区,yyyy-MM-dd HH:mm:ss,0表示无效,不会使用Default配置 + * schedule stop datetime at timingZone, in yyyy-MM-dd HH:mm:ss format, + * 0 means disable, not use Default config. */ protected String duringStop = ""; /** - * 总计初始执行多少次后,结束调度,不会使用Default配置 + * stop schedule after how many total executions, not use Default config. */ protected int duringExec = 0; /** - * 连续失败多少次后,结束调度,不会使用Default配置 + * stop schedule after how many consecutive failures, not use Default config. */ protected int duringFail = 0; /** - * 总计成功执行多少次后,结束调度,不会使用Default配置 + * stop schedule after how many successful executions, not use Default config. */ protected int duringDone = 0; /** - * 每应用每启动时重新计数,总计成功执行多少次后,结束调度,默认无效,不会使用Default配置 + * recount each time the app is started, and stop schedule after how many + * successful executions, disable by default, not use Default config. */ protected int duringBoot = 0; /** - * 执行结果保存的天数,负数为不保存,0为使用Default配置 + * how many days to save the execution results, default 60 days, + * 0 means not save, use Default configuration if null. */ protected int resultKeep = 0; diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/NoticeExec.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/NoticeExec.java index 529fed430..22016f0b4 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/NoticeExec.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/NoticeExec.java @@ -31,21 +31,21 @@ public NoticeExec(@NotNull SmallNotice beanObject) { } /** - * 格式为name:Class,优先匹配name,然后Class + * The format is name:Class, which matches name first, then Class. */ public boolean accept(String token) { return TaskerHelper.acceptToken(beanClass, null, token); } /** - * 判断Bean是否一致 + * Whether the bean is accepted */ public boolean accept(SmallNotice bean) { return TaskerHelper.acceptBean(beanClass, beanObject, bean); } /** - * 组合decode的配置,post一个notice + * Post a notice with combined the config */ public void postNotice(String config, String subject, String content) { try { diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/TaskerExec.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/TaskerExec.java index 0b4c23d0f..219901acb 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/TaskerExec.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/schedule/exec/TaskerExec.java @@ -67,14 +67,14 @@ public String encodePara(Object para) { } /** - * 格式为name:Class,优先匹配name,然后Class + * The format is name:Class, which matches name first, then Class. */ public boolean accept(String token) { return TaskerHelper.acceptToken(beanClass, beanMethod, token); } /** - * 判断Bean是否一致 + * Whether the bean is accepted */ public boolean accept(Object bean) { return TaskerHelper.acceptBean(beanClass, beanObject, bean); diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskBeatService.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskBeatService.java index 9bdf8b808..d0bc5168d 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskBeatService.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskBeatService.java @@ -7,12 +7,12 @@ public interface TinyTaskBeatService { /** - * 清理task历史result + * Clean the history result of task */ int cleanResult(); /** - * 检查任务健康状态,非空时发生通知 + * Check task health, should notice if return non-empty */ String checkHealth(); } diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskConfService.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskConfService.java index ab4fc5d3a..27d88b2a6 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskConfService.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskConfService.java @@ -12,8 +12,9 @@ import java.util.Set; /** - * 配置tasker的属性,数据库与配置文件比较,version大的优先,同version时,数据库优先。 - * 此外,提供default配置,非独立属性,可以继承default配置。 + * Configure the properties of Tasker, compare the database and configuration files, + * giving priority to the larger version. When the versions are the same, prioritize the database. + * Additionally, provide default configurations that can be inherited for non-independent properties. * * @author trydofor * @since 2022-12-11 @@ -22,17 +23,18 @@ public interface TinyTaskConfService { /** *
-     * 配置TinyTasker标记的指定方法且enabled,并返回taskId
-     * 若property不存在,则报错。
-     * 若database不存在,则存入。
-     * 若database存在,version大的优先,若property大,则存入database,否则无操作。
+     * Configure the TinyTasker annotated and enabled method , return the taskId.
+     * - throw exception if property not exist
+     * - save to database if not exist in database
+     * - save to database, if exist in database, but higher version
+     * - otherwise no operation
      * 
*/ Conf config(@NotNull Object bean, @NotNull Method method, @Nullable Object para); /** - * 配置TinyTasker标记的所有方法,并返回taskId和有效的配置。 - * 若autorun但para不正确的,则报错。 + * Configures all TinyTasker annotated methods, and returns the taskId and its config. + * Throw exception if autorun but para is incorrect. * * @see #config(Object, Method, Object) */ @@ -40,13 +42,13 @@ public interface TinyTaskConfService { Set config(@NotNull Object bean); /** - * 从数据库获取配置 + * Load properties from database by taskId */ @Contract("_,true->!null") TaskerProp database(long id, boolean nonnull); /** - * 从配置文件获取配置,根据配置的key获取 + * Load properties from config file, get value by key */ @Contract("_,true->!null") TaskerProp property(long id, boolean nonnull); @@ -55,12 +57,12 @@ public interface TinyTaskConfService { LinkedHashMap> diffProp(long id); /** - * 启用或禁用task + * Enable or disable the task */ boolean enable(long id, boolean enabled); /** - * 保存配置到数据库 + * Save properties to database */ boolean replace(long id, TaskerProp prop); diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskExecService.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskExecService.java index 67d4f9481..2ea097eac 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskExecService.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskExecService.java @@ -3,7 +3,7 @@ import java.util.Set; /** - * 执行和取消任务,全局管理,同一个id只能启动一次 + * Launch and cancel tasks, global management, same id can only be started once * * @author trydofor * @since 2022-12-16 @@ -11,23 +11,23 @@ public interface TinyTaskExecService { /** - * 调度一个任务 + * launch a task */ boolean launch(long id); /** - * 强制执行一个任务,不记入调度 + * Force launch a task without scheduling */ boolean force(long id); /** - * 取消一个任务,若任务不存在视为成功。 - * 应用重启或再次launch时,任务恢复 + * Cancel a task. If the task does not exist, consider it as successful. + * When the application restarts or relaunches, the task should be restored. */ boolean cancel(long id); /** - * 获取经执行中任务id + * Get all running tasks */ Set running(); } diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskService.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskService.java index 37a551df8..60db98ef4 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskService.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/TinyTaskService.java @@ -16,9 +16,9 @@ /** *
- * 基于 ThreadPoolTaskScheduler 和 Database 的任务。
- * execute方法仅为执行,不做通知及database
- * schedule方法会自动通知和database,通过id管理任务
+ * Task based on ThreadPoolTaskScheduler and Database.
+ * `execute` is only for execution, not involve notice or databases
+ * `schedule` auto trigger notice and database, task is managed by id
  * 
* * @author trydofor @@ -27,13 +27,13 @@ public interface TinyTaskService { /** - * 获取内部的ThreadPoolTaskScheduler + * Get the internal ThreadPoolTaskScheduler */ @NotNull ThreadPoolTaskScheduler referScheduler(boolean fast); /** - * 获取内部的ScheduledExecutorService + * Get the internal ScheduledExecutorService */ @NotNull default ScheduledExecutorService referExecutor(boolean fast) { @@ -41,7 +41,8 @@ default ScheduledExecutorService referExecutor(boolean fast) { } /** - * 异步立即执行一个任务,fast为轻任务,执行快,秒级完成 + * Async execute a task immediately. + * `fast` means a lightweight that executes quickly and completes within seconds. * * @see ThreadPoolTaskScheduler#execute(Runnable) */ @@ -50,7 +51,8 @@ default void execute(boolean fast, @NotNull Runnable task) { } /** - * 在delayMs毫秒(ThreadNow)后,异步执行一个任务,fast为轻任务,执行快,秒级完成 + * Async execute a task after `delayMs` millis (ThreadNow). + * `fast` means a lightweight that executes quickly and completes within seconds. * * @see ThreadPoolTaskScheduler#schedule(Runnable, Instant) */ @@ -59,7 +61,8 @@ default ScheduledFuture execute(boolean fast, long delayMs, @NotNull Runnable } /** - * 在指定时间(fastTime构建,不考虑时区),异步执行一个任务,fast为轻任务,执行快,秒级完成 + * Async execute a task at specified time (`fastTime` without timezone), + * `fast` means a lightweight that executes quickly and completes within seconds. * * @see ThreadPoolTaskScheduler#schedule(Runnable, Instant) */ @@ -68,8 +71,9 @@ default ScheduledFuture execute(boolean fast, Instant startTime, @NotNull Run } /** - * 指定trigger,异步执行一个任务,fast为轻任务,执行快,秒级完成 - * errorHandler不同于其他方法,不识别DelegatingErrorHandlingRunnable + * Async execute a task by specified trigger, + * `fast` means a lightweight that executes quickly and completes within seconds. + * The `errorHandler` is different from other methods and does not recognize `DelegatingErrorHandlingRunnable`. * * @see ThreadPoolTaskScheduler#schedule(Runnable, Trigger) */ @@ -78,7 +82,8 @@ default ScheduledFuture execute(boolean fast, Trigger trigger, @NotNull Runna } /** - * 把taskerBean内所有TinyTask标注的方法,作为任务初始并执行。 + * Take all the methods annotated with TinyTask inside the taskerBean, + * initialize them as tasks, and execute them. * * @see ThreadPoolTaskScheduler#schedule(Runnable, Trigger) */ @@ -86,15 +91,17 @@ default ScheduledFuture execute(boolean fast, Trigger trigger, @NotNull Runna Set schedule(@NotNull Object taskerBean); /** - * 把TaskerBean中TinyTask标注的方法,作为任务初始并执行,返回TaskId,-1为未启动 + * Take all the methods annotated with TinyTask inside the taskerBean, + * initialize them as tasks, and execute them. taskId == `-1` means not start * * @see ThreadPoolTaskScheduler#schedule(Runnable, Trigger) */ Task schedule(@NotNull Object taskerBean, @NotNull Method taskerCall, @Nullable Object taskerPara); /** - * 把TaskerBean中TinyTask标注的方法,作为任务初始并执行,返回TaskId,-1为未启动 - * 通过对象引用的lambda方式获取,格式如 Lam.ref(taskerBean::method) + * Take all the methods annotated with TinyTask inside the taskerBean, + * initialize them as tasks, and execute them. taskId == `-1` means not start, + * `lambdaRefer` is lambda ref, get by `Lam.ref(taskerBean::method)` * * @see ThreadPoolTaskScheduler#schedule(Runnable, Trigger) */ diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/impl/TinyTaskExecServiceImpl.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/impl/TinyTaskExecServiceImpl.java index 9fed6cad6..dbaa60a16 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/impl/TinyTaskExecServiceImpl.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/service/impl/TinyTaskExecServiceImpl.java @@ -284,7 +284,7 @@ private boolean relaunch(long id) { log.error("failed to save result, id=" + id, e); } - if (canRelaunch(id, doneTms, failTms, td)) { // 被取消 + if (canRelaunch(id, doneTms, failTms, td)) { // canceled relaunch(id); } } @@ -475,7 +475,7 @@ private boolean canRelaunch(long id, long doneTms, long failTms, WinTaskDefine t return false; } - if (Cancel.containsKey(id)) { // 被取消 + if (Cancel.containsKey(id)) { // canceled log.info("remove task for canceled, id={}", id); return false; } @@ -502,7 +502,7 @@ private long calcNextExec(WinTaskDefine td) { final Long id = td.getId(); final long timingMiss = td.getTimingMiss() * 1000L; - // 规划中,但执行结束前程序killed + // Planned, program waw killed before execution ends final long nextMs = DateLocaling.sysEpoch(td.getNextExec()); if (nextMs + timingMiss >= now) { log.info("launch misfire task, id={}", id); diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskDefineProp.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskDefineProp.java index f43941041..9de3cba4f 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskDefineProp.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskDefineProp.java @@ -18,7 +18,7 @@ public class TinyTaskDefineProp extends LinkedHashMap implem public static final String Key = "wings.tiny.task.define"; /** - * 默认属性 + * Default Config * * @see #Key$default */ diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskEnabledProp.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskEnabledProp.java index 302ac2696..2ed0a8a01 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskEnabledProp.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskEnabledProp.java @@ -17,7 +17,7 @@ public class TinyTaskEnabledProp { public static final String Key = "spring.wings.tiny.task.enabled"; /** - * 是否启动自动配置 + * whether to enable auto config. * * @see #Key$autoconf */ @@ -26,7 +26,7 @@ public class TinyTaskEnabledProp { /** - * 是否允许自动注册TinyTask.Auto + * whether to auto register TinyTask.Auto. * * @see #Key$autorun */ @@ -34,7 +34,7 @@ public class TinyTaskEnabledProp { public static final String Key$autorun = Key + ".autorun"; /** - * 是否干跑,仅记录日志不真正执行任务 + * whether to dry run, log only without realy exec the task. * * @see #Key$dryrun */ @@ -42,7 +42,7 @@ public class TinyTaskEnabledProp { public static final String Key$dryrun = Key + ".dryrun"; /** - * 是否开启 TaskConfController + * whether to enable TaskConfController. * * @see #Key$controllerConf */ @@ -50,7 +50,7 @@ public class TinyTaskEnabledProp { public static final String Key$controllerConf = Key + ".controller-conf"; /** - * 是否开启 TaskExecController + * whether to enable TaskExecController. * * @see #Key$controllerExec */ @@ -58,7 +58,7 @@ public class TinyTaskEnabledProp { public static final String Key$controllerExec = Key + ".controller-exec"; /** - * 是否开启 TaskListController + * whether to enable TaskListController. * * @see #Key$controllerList */ diff --git a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskUrlmapProp.java b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskUrlmapProp.java index cbe3b35e4..236d4a9ab 100644 --- a/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskUrlmapProp.java +++ b/radiant/tiny-task/src/main/java/pro/fessional/wings/tiny/task/spring/prop/TinyTaskUrlmapProp.java @@ -13,66 +13,88 @@ public class TinyTaskUrlmapProp { public static final String Key = "wings.tiny.task.urlmap"; /** + * list of running tasks. + * * @see #Key$taskRunning */ private String taskRunning = ""; public static final String Key$taskRunning = Key + ".task-running"; /** + * list of defined tasks. + * * @see #Key$taskDefined */ private String taskDefined = ""; public static final String Key$taskDefined = Key + ".task-defined"; /** + * list of task results. + * * @see #Key$taskResult */ private String taskResult = ""; public static final String Key$taskResult = Key + ".task-result"; /** + * cancel a task. + * * @see #Key$taskCancel */ private String taskCancel = ""; public static final String Key$taskCancel = Key + ".task-cancel"; /** + * start a task. + * * @see #Key$taskLaunch */ private String taskLaunch = ""; public static final String Key$taskLaunch = Key + ".task-launch"; /** + * force to start a task. + * * @see #Key$taskForce */ private String taskForce = ""; public static final String Key$taskForce = Key + ".task-force"; /** + * enable or disable a task. + * * @see #Key$taskEnable */ private String taskEnable = ""; public static final String Key$taskEnable = Key + ".task-enable"; /** + * update the task config. + * * @see #Key$taskPropSave */ private String taskPropSave = ""; public static final String Key$taskPropSave = Key + ".task-prop-save"; /** + * load the task config. + * * @see #Key$taskPropLoad */ private String taskPropLoad = ""; public static final String Key$taskPropLoad = Key + ".task-prop-load"; /** + * show the prop of task conf. + * * @see #Key$taskPropConf */ private String taskPropConf = ""; public static final String Key$taskPropConf = Key + ".task-prop-conf"; /** + * show the diff of task conf. + * * @see #Key$taskPropDiff */ private String taskPropDiff = ""; diff --git a/radiant/tiny-task/src/main/resources/wings-conf/spring-wings-enabled-79.properties b/radiant/tiny-task/src/main/resources/wings-conf/spring-wings-enabled-79.properties index 043370b25..f02f39317 100644 --- a/radiant/tiny-task/src/main/resources/wings-conf/spring-wings-enabled-79.properties +++ b/radiant/tiny-task/src/main/resources/wings-conf/spring-wings-enabled-79.properties @@ -1,12 +1,12 @@ -# 是否启动自动配置 +## whether to enable auto config. spring.wings.tiny.task.enabled.autoconf=true -# 是否允许自动注册TinyTask.Auto +## whether to auto register TinyTask.Auto. spring.wings.tiny.task.enabled.autorun=true -# 是否干跑,仅记录日志不真正执行任务 +## whether to dry run, log only without realy exec the task. spring.wings.tiny.task.enabled.dryrun=false -# 是否开启 TaskConfController +## whether to enable TaskConfController. spring.wings.tiny.task.enabled.controller-conf=true -# 是否开启 TaskExecController +## whether to enable TaskExecController. spring.wings.tiny.task.enabled.controller-exec=true -# 是否开启 TaskListController +## whether to enable TaskListController. spring.wings.tiny.task.enabled.controller-list=true diff --git a/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-beat-79.properties b/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-beat-79.properties index 92b9d5153..1094c02c3 100644 --- a/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-beat-79.properties +++ b/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-beat-79.properties @@ -1,5 +1,5 @@ -# 系统时间02:01:00清理result +# Clean the result at system timezone 02:01:00 wings.tiny.task.define[TinyTaskCleanResult].timing-cron=0 1 2 * * * -# 每300秒,检查一下健康状态 +## Check health status every 300 seconds of idle time. wings.tiny.task.define[TinyTaskCheckHealth].timing-idle=300 wings.tiny.task.define[TinyTaskCheckHealth].notice-when=fail,feed diff --git a/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-define-79.properties b/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-define-79.properties index 4aad5a1a2..f7ebe7e75 100644 --- a/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-define-79.properties +++ b/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-define-79.properties @@ -1,60 +1,97 @@ -# 是否可以注册及执行,不会使用Default配置 +## whether to register and execute, not use Default config. #wings.tiny.task.define[default].enabled=true -# 是否可以自动注册并启动,不会使用Default配置 + +## whether to auto register and start, not use Default config. #wings.tiny.task.define[default].autorun=true -# 版本号,版本高的配置覆盖版本低的,不会使用Default配置 + +## version number, higher version config overrides lower one, not use Default config. #wings.tiny.task.define[default].version=0 -# 由TinyTasker注解的Bean,格式为Class#method,默认自动识别,不会使用Default配置 +## Beans annotated by TinyTasker, formatted as Class#method, +## automatically recognized by default, not use Default config. #wings.tiny.task.define[default].tasker-bean= -# 任务的参数,对象数组的json格式,默认null或空无参数,不会使用Default配置 + +## Parameters of the task, object array in json format, +## default null or no parameters, not use Default config. #wings.tiny.task.define[default].tasker-para= -# 任务名字,用于通知和日志,可读性好一些,默认为'[短类名#方法名]',不会使用Default配置 + +## Task name, used for notice and log, better readability, +## default is `[shortClassName#method]`, not use Default config. #wings.tiny.task.define[default].tasker-name= -# 是否为轻任务,执行快,秒级完成,不会使用Default配置 +## Whether it is a light task, fast execution, completed in seconds, not use Default config. #wings.tiny.task.define[default].task-fast=true -# 所属程序,逗号分隔,默认使用spring.application.name,null及空时使用Default配置 + +## The app it belongs to, comma separated, +## use Default config if null or empty. wings.tiny.task.define[default].tasker-apps=${spring.application.name} -# 执行模式,RunMode(product|test|develop|local),逗号分隔忽略大小写,默认所有,null及空时使用Default配置 + +## RunMode(product|test|develop|local), Comma separated, ignore case, default all, +## use Default config if null or empty. wings.tiny.task.define[default].tasker-runs= -# 通知Bean,SmallNotice类型,格式为Class,默认无通知。null及空时使用Default配置 +## Notice bean, SmallNotice type, fullpath of Class, no notice by default. +## use Default config if null or empty. wings.tiny.task.define[default].notice-bean=pro.fessional.wings.slardar.notice.DingTalkNotice -# 通知的时机,exec|fail|done|feed,逗号分隔忽略大小写,默认fail。null及空时使用Default配置 -# 时机大概表述为:exec;try{run...;done}catch{fail} -# exec - 初始任务;done - 执行成功;fail - 执行失败;feed - 方法返回非空 + +## Timing of notice, exec|fail|done|feed, comma separated ignoring case, default fail. +## use Default config if null or empty. +## * timing is roughly expressed: exec;try{run...;done}catch{fail} +## * exec - init task; done - success; fail - failed; feed - non-empty return. wings.tiny.task.define[default].notice-when=fail -# 通知Bean的配置文件名字,默认自动,空时使用Default配置 + +## The config name of the notice bean, automatic by default. use Default config if empty. wings.tiny.task.define[default].notice-conf= -# 调度时区的ZoneId格式,默认系统时区,null及空时使用Default配置 +## timezone of scheduling , default system timezone, use Default config if null or empty. wings.tiny.task.define[default].timing-zone= -# 调度表达式类型,影响timingCron的解析方式,默认为spring cron格式,null及空时使用Default配置 + +## scheduling expression type, affects how timingCron is parsed, +## defaults to spring cron format, use Default config if null or empty. wings.tiny.task.define[default].timing-type=cron -# 调度表达式内容,最高优先级,受timingType影响,默认spring cron格式(秒分时日月周),不会使用Default配置 + +## Scheduling expression content, highest priority, affected by timingType, +## default spring cron format (second minute hour day month week), not use Default config. #wings.tiny.task.define[default].timing-cron= -# 固定空闲相连(秒),优先级次于timingCron,相当于fixedDelay,结束到开始,0为无效,不会使用Default配置 + +## Fixed idle interval (seconds), lower priority than timingCron, +## equal to fixedDelay, end to start, 0 means disable, not use Default config. #wings.tiny.task.define[default].timing-idle=0 -# 固定频率开始(秒),优先级次于timingIdle,相当于fixedRate,开始到开始,0为无效,不会使用Default配置 + +## Fixed frequency interval (seconds), lower priority than timingIdle, +## equal to fixedRate, start to start, 0 means disable, not use Default config. #wings.tiny.task.define[default].timing-rate=0 -# 错过调度(misfire)多少秒内,需要补救执行,0表示不补救,不会使用Default配置 + +## Within how many seconds of a misfire, execution is required, +## 0 means no execution. not use Default config. #wings.tiny.task.define[default].timing-miss=0 -# 心跳间隔秒数,last_exec距今超过2个心态任务task异常,默认自动。取rate或idle最大值,cron需要自行指定,不会使用Default配置 + +## the interval seconds of heartbeat, if the task's last_exec is more +## than 2 heartbeats away from now, it is considered as an exception. default auto to +## take rate or idle maximum, cron needs to specify it by itself, not use Default config. #wings.tiny.task.define[default].timing-beat=0 -# 调度开始的日期时间,timingZone时区,yyyy-MM-dd HH:mm:ss,0表示无效,不会使用Default配置 +## schedule start datetime at timingZone, in yyyy-MM-dd HH:mm:ss format, +## 0 means disable, not use Default config. #wings.tiny.task.define[default].during-from= -# 调度结束的日期时间,timingZone时区,yyyy-MM-dd HH:mm:ss,0表示无效,不会使用Default配置 + +## schedule stop datetime at timingZone, in yyyy-MM-dd HH:mm:ss format, +## 0 means disable, not use Default config. #wings.tiny.task.define[default].during-stop= -# 总计初始执行多少次后,结束调度,不会使用Default配置 + +## stop schedule after how many total executions, not use Default config. #wings.tiny.task.define[default].during-exec=0 -# 连续失败多少次后,结束调度,不会使用Default配置 + +## stop schedule after how many consecutive failures, not use Default config. #wings.tiny.task.define[default].during-fail=0 -# 总计成功执行多少次后,结束调度,不会使用Default配置 + +## stop schedule after how many successful executions, not use Default config. #wings.tiny.task.define[default].during-done=0 -# 每次启动时开始计数,总计成功执行多少次后,结束调度,默认无效,不会使用Default配置 + +## recount each time the app is started, and stop schedule after how many +## successful executions, disable by default, not use Default config. #wings.tiny.task.define[default].during-boot=0 -# 执行结果保存的天数,0为不保存,默认60天,0为不保存,null时使用Default配置 +## how many days to save the execution results, default 60 days, +## 0 means not save, use Default configuration if null. wings.tiny.task.define[default].result-keep=60 diff --git a/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-urlmap-79.properties b/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-urlmap-79.properties index 1f0a77e6a..ada4495ca 100644 --- a/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-urlmap-79.properties +++ b/radiant/tiny-task/src/main/resources/wings-conf/wings-tinytask-urlmap-79.properties @@ -1,32 +1,32 @@ -# 运行中的任务列表 +## list of running tasks. wings.tiny.task.urlmap.task-running=/admin/task/task-running.json -# 定义的任务列表 +## list of defined tasks. wings.tiny.task.urlmap.task-defined=/admin/task/task-defined.json -# 任务历史列表 +## list of task results. wings.tiny.task.urlmap.task-result=/admin/task/task-result.json -# 取消一个任务 +## cancel a task. wings.tiny.task.urlmap.task-cancel=/admin/task/task-cancel.json -# 启动任务 +## start a task. wings.tiny.task.urlmap.task-launch=/admin/task/task-launch.json -# 强制执行任务 +## force to start a task. wings.tiny.task.urlmap.task-force=/admin/task/task-force.json -# 启动或禁用任务 +## enable or disable a task. wings.tiny.task.urlmap.task-enable=/admin/task/task-enable.json -# 更新任务配置 +## update the task config. wings.tiny.task.urlmap.task-prop-save=/admin/task/task-prop-save.json -# 任务载入属性 +## load the task config. wings.tiny.task.urlmap.task-prop-load=/admin/task/task-prop-load.json -# 任务配置属性 +## show the prop of task conf. wings.tiny.task.urlmap.task-prop-conf=/admin/task/task-prop-conf.json -# 任务属性差异 +## show the diff of task conf. wings.tiny.task.urlmap.task-prop-diff=/admin/task/task-prop-diff.json diff --git a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/project/TinyTaskCodeGenTest.java b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/project/TinyTaskCodeGenTest.java index 620b8acd6..8cfd6fa89 100644 --- a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/project/TinyTaskCodeGenTest.java +++ b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/project/TinyTaskCodeGenTest.java @@ -19,7 +19,7 @@ "spring.wings.faceless.flywave.enabled.checker=false", "spring.wings.tiny.task.enabled.autorun=false", }) -@Disabled("生成代码,已有devs统一管理") +@Disabled("Code gen, managed by devs") public class TinyTaskCodeGenTest { @Setter(onMethod_ = {@Autowired}) diff --git a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/other/ExecutorServiceTest.java b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/other/ExecutorServiceTest.java index 85ca1d15f..3360b0422 100644 --- a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/other/ExecutorServiceTest.java +++ b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/other/ExecutorServiceTest.java @@ -16,7 +16,7 @@ */ @SpringBootTest @Slf4j -@Disabled("模拟批处理,人工观察") +@Disabled("Simulate batch, check manually") class ExecutorServiceTest { @Test diff --git a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TestServiceAuto.java b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TestServiceAuto.java index a27448372..310244483 100644 --- a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TestServiceAuto.java +++ b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TestServiceAuto.java @@ -18,19 +18,19 @@ public String strStr(String msg) throws Exception { return msg; } - @TinyTasker(rate = 60)// 使用prop值,30 + @TinyTasker(rate = 60)// use prop value, 30 public void strVoid(String msg) throws Exception { log.info("TestServiceAuto.strVoid {}", msg); Thread.sleep(1500); } - @TinyTasker(value = "voidStrAuto", idle = 60) // 使用注解值 + @TinyTasker(value = "voidStrAuto", idle = 60) // use annotation value public void voidStr(String msg) throws Exception { log.info("TestServiceAuto.voidStr {}", msg); Thread.sleep(1500); } - @TinyTasker(value = "voidVoidAuto", cron = "0 * * * * *") // 使用prop enabled=false + @TinyTasker(value = "voidVoidAuto", cron = "0 * * * * *") // use prop value, enabled=false public void voidVoid() throws Exception { log.info("TestServiceAuto.voidVoid"); Thread.sleep(1500); diff --git a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TinyTaskServiceTest.java b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TinyTaskServiceTest.java index d3178a269..1d2766eba 100644 --- a/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TinyTaskServiceTest.java +++ b/radiant/tiny-task/src/test/java/pro/fessional/wings/tiny/task/service/TinyTaskServiceTest.java @@ -54,7 +54,7 @@ void schedule() { } @Test - @Disabled("模拟慢处理,sleep 180s") + @Disabled("Simulate slow process,sleep 180s") void sleep180() throws InterruptedException { Thread.sleep(180 * 1000L); } diff --git a/readme-zh.md b/readme-zh.md index e75bbc843..22f21ca8a 100644 --- a/readme-zh.md +++ b/readme-zh.md @@ -11,7 +11,7 @@ * [![Jooq-3.17](https://img.shields.io/badge/jooq-3.17-cyan)](https://www.jooq.org/download/) 主要的强类型SqlMapping 🏅 [Apache2] * [![Mysql-8](https://img.shields.io/badge/mysql-8.0-blue)](https://dev.mysql.com/downloads/mysql/) 主要的业务数据库,推荐8,兼容5.7 💡 [GPLv2] * [![H2Database-2.1](https://img.shields.io/badge/h2db-2.1-blue)](https://h2database.com/html/main.html) 单机数据库,以离线及断线业务 [MPL2]或[EPL1] -* [![Hazelcast-5.1](https://img.shields.io/badge/hazelcast-5.1-violet)](https://hazelcast.org/imdg/) IMDG,分布式缓存,消息,流等 [Apache2] +* [![Hazelcast-5.1](https://img.shields.io/badge/hazelcast-5.1-violet)](https://docs.hazelcast.com/hazelcast/) 分布式缓存,消息,流等 [Apache2] * [![ServiceComb-2.8](https://img.shields.io/badge/servicecomb-2.8-violet)](https://servicecomb.apache.org) 更工程化和紧凑的微服务方案 [Apache2] * [![ShardingSphere-5.3](https://img.shields.io/badge/shardingsphere-5.3-violet)](https://shardingsphere.apache.org) 数据库的分表分片弹性伸缩方案 [Apache2] @@ -23,25 +23,27 @@ ## 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编译 -# 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.用爱发电 @@ -59,8 +61,9 @@ mvn package install ## 4.免责声明 -WingsBoot及其submodule项目,均以Apache2授权。但本人, +WingsBoot及其submodule项目,均以[Apache2]授权。请注意, -* 不对滥用技术或手册造成的任何损失负责。 -* 没有义务提供咨询,答疑,开发等服务。 -* 可付费咨询,404 CNY/H +* 项目是基于现有技术,资源和团队实践的自愿贡献,没有任何明示或暗示的保证或条件。 +* 项目的开发者已经尽力确保代码的质量和功能性,但不保证完全没有缺陷或错误。 +* 在使用项目时,你应该自行评估其适用性,并承担使用该项目的所有风险。 +* 在任何情况下,项目的开发者都不对因使用该项目而导致的任何损失、损害或其他责任承担责任。 diff --git a/readme.md b/readme.md index 4d1e919c1..4c679d4a8 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://docs.hazelcast.com/hazelcast/) 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,24 +24,26 @@ ## 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 ```bash -# ① get source code +## (1) get source code git clone --depth 1 https://github.com/\ trydofor/pro.fessional.wings.git -# ② install dependency useing java8 -# sdk use java 8.0.352-tem +## (2) 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 +## (3) install wings using java-17 sdk use java 17.0.6-tem mvn package install +## (4) report issue +java -jar silencer-*-SNAPSHOT.jar ``` ## 3.Powered by Love @@ -60,10 +62,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. 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/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/aegis/src/main/java/pro/fessional/wings/spring/consts/WingsBeanOrdered.java b/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/WingsBeanOrdered.java index 9de49cbf5..202f6726a 100644 --- a/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/WingsBeanOrdered.java +++ b/wings/aegis/src/main/java/pro/fessional/wings/spring/consts/WingsBeanOrdered.java @@ -10,49 +10,49 @@ public interface WingsBeanOrdered { /** - * 层级单位,默认1M + * Level Unit, 1M default */ int Unit = 1_000_000; /** - * 配置层,-90Unit + * Config Level, -90Unit */ int Lv1Config = -90 * Unit; /** - * 资源层,-70Unit + * Resource Level, -70Unit */ int Lv2Resource = -70 * Unit; /** - * 服务层,-50Unit + * Service Level, -50Unit */ int Lv3Service = -50 * Unit; /** - * 应用层,-30Unit + * Application Level, -30Unit */ int Lv4Application = -30 * Unit; /** - * 监控层,-10Unit + * Supervisor Level, -10Unit */ int Lv5Supervisor = -10 * Unit; /** - * 优先级 A = 2Unit + * Priority A = 2Unit */ int PriorityA = 2 * Unit; /** - * 优先级 B = 4Unit + * Priority B = 4Unit */ int PriorityB = 4 * Unit; /** - * 优先级 C = 6Unit + * Priority C = 6Unit */ int PriorityC = 6 * Unit; /** - * 优先级 D = 8Unit + * Priority D = 8Unit */ int PriorityD = 8 * Unit; } diff --git a/wings/batrider-test/src/test/java/pro/fessional/wings/batrider/contractor/BatriderContractor.java b/wings/batrider-test/src/test/java/pro/fessional/wings/batrider/contractor/BatriderContractor.java index 2fe4e0518..90139a358 100644 --- a/wings/batrider-test/src/test/java/pro/fessional/wings/batrider/contractor/BatriderContractor.java +++ b/wings/batrider-test/src/test/java/pro/fessional/wings/batrider/contractor/BatriderContractor.java @@ -9,7 +9,7 @@ import pro.fessional.wings.batrider.contract.HelloContract; /** - * Mvc风格,自定义SchemaId,basePath不带SchemaId + * Mvc Style, Customize SchemaId, basePath without SchemaId * * @author trydofor * @since 2022-08-04 diff --git a/wings/batrider-test/src/test/resources/application.properties b/wings/batrider-test/src/test/resources/application.properties index 88d0e2c92..9b37bcdee 100644 --- a/wings/batrider-test/src/test/resources/application.properties +++ b/wings/batrider-test/src/test/resources/application.properties @@ -5,5 +5,5 @@ debug=true servicecomb.service.application=servicecomb-demo servicecomb.service.name=batrider -## 移除验证,以便http访问 +## delete auth to let http request servicecomb.handler.chain.Provider.default= 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/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/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderEnabledProp.java b/wings/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderEnabledProp.java index a56d776a3..42dc6cbd0 100644 --- a/wings/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderEnabledProp.java +++ b/wings/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderEnabledProp.java @@ -17,7 +17,7 @@ public class BatriderEnabledProp { public static final String Key = "spring.wings.batrider.enabled"; /** - * 是否启动自动配置 + * Whether to enable auto config. * * @see #Key$autoconf */ diff --git a/wings/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderHandlerProp.java b/wings/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderHandlerProp.java index 1dcb59ffa..cc842c6de 100644 --- a/wings/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderHandlerProp.java +++ b/wings/batrider/src/main/java/pro/fessional/wings/batrider/spring/prop/BatriderHandlerProp.java @@ -16,7 +16,7 @@ public class BatriderHandlerProp { public static final String Key = "wings.batrider.handler"; /** - * 不需要验证的schemaId + * SchemaId that can skip auth * * @see #Key$authSkipSchema */ diff --git a/wings/faceless-autogen/src/main/java/pro/fessional/wings/faceless/enums/autogen/StandardLanguage.java b/wings/faceless-autogen/src/main/java/pro/fessional/wings/faceless/enums/autogen/StandardLanguage.java index 8184f7c37..5448a7aae 100644 --- a/wings/faceless-autogen/src/main/java/pro/fessional/wings/faceless/enums/autogen/StandardLanguage.java +++ b/wings/faceless-autogen/src/main/java/pro/fessional/wings/faceless/enums/autogen/StandardLanguage.java @@ -9,7 +9,7 @@ import java.util.Locale; /** - * language + "_" + country,使用`_`分隔,zh_CN,在解析中,也支持zh-CN + * language + `_` + country, `_` delimited, `zh_CN`. For parsing, `zh-CN` is also supported. * * @author trydofor * @see Locale#toString() diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantEnumGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantEnumGenerator.java index 5c2463e5a..6bb2a57cb 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantEnumGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantEnumGenerator.java @@ -28,7 +28,7 @@ import java.util.stream.Collectors; /** - * 名字转换中,`-`变成`_`,ascii中非java字符,用狮子替换 + * In name conversion, `-` becomes `_`, non-java characters are replaced with #deerChar. * * @author trydofor * @since 2019-09-24 @@ -38,7 +38,7 @@ public class ConstantEnumGenerator { private static final Logger log = LoggerFactory.getLogger(ConstantEnumGenerator.class); /** - * 对java中非合法命名的字符进行替换,设置为空,以忽略非命名字符 + * Replacement for non-java characters, empty means ignore */ @SuppressWarnings("CanBeFinal") public static String deerChar = "_"; @@ -131,12 +131,11 @@ public void generate(Collection pos) { * * @param src ./src/main/java/ * @param pkg pro.fessional.wings.faceless.enums.constant - * @param pojos 对象数据 - * @param filter 过滤type组,true为包含,false为排除 + * @param pojos data objects + * @param filter filter by type, true for include, false for exclude * @see ConstantEnumTemplate */ public static void generate(File src, String pkg, Collection pojos, Map filter) { - // 初始 Set nowFiles = new HashSet<>(); File dst = new File(src, pkg.replace('.', '/')); diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantNaviGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantNaviGenerator.java index 9471f0422..23eabd316 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantNaviGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/codegen/ConstantNaviGenerator.java @@ -20,7 +20,7 @@ import java.util.TreeSet; /** - * 生成导航类 + * Generate Navigation Class * * @author trydofor * @since 2019-09-24 @@ -47,7 +47,7 @@ public void generate(String javaName, String prefixCode, Collection list = new ArrayList<>(entries); - // 初始 + // File dst = new File(targetDir, packageName.replace('.', '/')); dst.mkdirs(); diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/ConstantEnumTemplate.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/ConstantEnumTemplate.java index c2d2b9cfe..d79a4c3c4 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/ConstantEnumTemplate.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/ConstantEnumTemplate.java @@ -22,9 +22,9 @@ public enum ConstantEnumTemplate implements ConstantEnum, StandardI18nEnum { // RNA:USE /SUPER/enum.name/* // RNA:USE /1020100/enum.id/ // RNA:USE /standard_language/enum.code/fd - // RNA:USE /标准语言/enum.hint/ - // RNA:USE /模板路径/enum.info/ - SUPER(1020100, "standard_language", "标准语言", "模板路径"), + // RNA:USE /Standard Language/enum.hint/ + // RNA:USE /Template Path/enum.info/ + SUPER(1020100, "standard_language", "Standard Language", "Template Path"), // RNA:DONE enum ; // RNA:EACH /1/enum-items/enum diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardLanguageTemplate.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardLanguageTemplate.java index 610aa0bb0..11e3b0ee8 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardLanguageTemplate.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardLanguageTemplate.java @@ -13,7 +13,7 @@ // RNA:USE /2019-09-17/now.date/ /** - * language + "_" + country,使用`_`分隔,zh_CN,在解析中,也支持zh-CN + * language + `_` + country, `_` delimited, `zh_CN`. For parsing, `zh-CN` is also supported. * * @author trydofor * @see Locale#toString() @@ -26,9 +26,9 @@ public enum StandardLanguageTemplate implements StandardLanguageEnum { // RNA:USE /SUPER/enum.name/* // RNA:USE /1020100/enum.id/ // RNA:USE /standard_language/enum.code/fd - // RNA:USE /标准语言/enum.hint/ - // RNA:USE /模板路径/enum.info/ - SUPER(1020100, "standard_language", "标准语言", "模板路径"), + // RNA:USE /Standard Language/enum.hint/ + // RNA:USE /Template Path/enum.info/ + SUPER(1020100, "standard_language", "Standard Language", "Template Path"), // RNA:DONE enum ; // RNA:EACH /1/enum-items/enum diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardTimezoneTemplate.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardTimezoneTemplate.java index e9d3f0646..b2088d1f5 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardTimezoneTemplate.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/enums/templet/StandardTimezoneTemplate.java @@ -26,9 +26,9 @@ public enum StandardTimezoneTemplate implements StandardTimezoneEnum { // RNA:USE /SUPER/enum.name/* // RNA:USE /1020100/enum.id/ // RNA:USE /standard_language/enum.code/fd - // RNA:USE /标准语言/enum.hint/ - // RNA:USE /模板路径/enum.info/ - SUPER(1020100, "standard_language", "标准语言", "模板路径"), + // RNA:USE /Standard Language/enum.hint/ + // RNA:USE /Template Path/enum.info/ + SUPER(1020100, "standard_language", "Standard Language", "Template Path"), // RNA:DONE enum ; // RNA:EACH /1/enum-items/enum diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java index 54fe58298..2a06bde2b 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsCodeGenerator.java @@ -40,7 +40,7 @@ import java.util.stream.Collectors; /** - * 对 jooq-codegen-3.17.0.xsd 的包装 + * Wrapper for jooq-codegen-3.17.0.xsd * * @author trydofor * @since 2019-05-31 @@ -51,11 +51,11 @@ public class WingsCodeGenerator { private static final Logger log = LoggerFactory.getLogger(WingsCodeGenerator.class); /** - * 生成 Jooq 代码 + * Generate Jooq Code * - * @param conf 配置文件,建议使用 #Builder 生产。 - * @param incremental 是否增量生成,即不删除本次中不存在的文件。 - * @param suffix 为DefaultCatalog,DefaultSchema和Global对象增加后缀,以区分增量生成。 + * @param conf Configuration, recommended to use #Builder. + * @param incremental Whether to generate incrementally. That is, not to delete files that do not exist in this time. + * @param suffix Add suffixes to DefaultCatalog, DefaultSchema and Global to distinguish generation. */ @SuppressWarnings("resource") public static void generate(Configuration conf, boolean incremental, String suffix) { @@ -145,7 +145,7 @@ private static void safeCopy(String tmp, String src, String pkg, boolean inc) th } } - // 忽略注释,import排序和serialVersionUID + // Ignore comments, import sort and serialVersionUID // The table trydofor_20200515.jp_account. // The schema trydofor. // date = "2019-09-09T01:33:51.762Z", @@ -195,9 +195,7 @@ public Builder(Configuration conf) { } /** - * 增量生成,即不删除本次中不存在的文件 - * - * @param t 是否增量生成 + * Whether to generate incrementally. That is, not to delete files that do not exist in this time. */ public Builder incremental(boolean t) { incr = t; @@ -215,7 +213,7 @@ public Builder setLiveDataByMax(boolean asMax) { } /** - * 直接生成代码 + * Build Config and Generate Code */ public void buildAndGenerate() { generate(conf, incr, suffix); @@ -325,7 +323,7 @@ public Builder databaseName(String str) { } /** - * 注意,匹配的格式为QualifiedName,如db.table,详见 org.jooq.meta.AbstractDatabase#matches + * Note that the matches are in the format QualifiedName, e.g., db.table, as detailed in org.jooq.meta.AbstractDatabase#matches *

* replace configuration/generator/database/includes *

@@ -340,7 +338,7 @@ public Builder databaseName(String str) { *

* Whitespace is ignored and comments are possible unless overridden in getRegexFlags(). default COMMENTS CASE_INSENSITIVE * - * @param reg 正则 + * @param reg Regexp * @return Builder * @see Pattern#COMMENTS */ @@ -372,7 +370,7 @@ public Builder databaseIncludes(boolean append, String... reg) { * This is a Java regular expression. Use the pipe to separate several expressions. * Excludes match before includes, i.e. excludes have a higher priority. * - * @param reg 正则 + * @param reg Regexp * @return Builder * @see Pattern#COMMENTS */ @@ -454,11 +452,11 @@ public Builder forcedType(Class userType, Class> co } /** - * jooq中匹配 含`.`且<>或[]结尾,做 `new %s()`,否则做 %s - * 参考 JavaGenerator#converterTemplate + * Matching in jooq with `.` and ends in `<>` or `[]`, use `new %s()`, otherwise use `%s`. + * see JavaGenerator#converterTemplate * * @param ft ForcedType - * @param sortImport 以import代替全限定引用,仅wingsGenerator有效 + * @param sortImport Replacing fully qualified references with import is only valid for wingsGenerator. * @return this */ public Builder forcedType(ForcedType ft, String sortImport) { diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java index 288fe9e57..2a1929f02 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/jooqgen/WingsJavaGenerator.java @@ -39,9 +39,11 @@ import static pro.fessional.wings.faceless.database.helper.JournalJdbcHelper.COL_IS_DELETED; /** - * 升级jooq时,需要确认Override及反射方法是否兼容 - * 『🦁>>>』和『🦁<<<』间的代码不需要确认 - * 仅支持java,不支持kotlin及scala + *

+ * When upgrading jooq, you need to check the compatibility of Override and Reflection methods.
+ * The code between `🦁>>>` and `🦁<<<` doesn't need to be checked.
+ * Only java is supported, kotlin and scala are not.
+ * 
* * @author trydofor * @since 2019-05-31 @@ -83,7 +85,7 @@ public GeneratorStrategy getStrategy() { return proxyStrategy; } - @Override // 无需确认,可对比 + @Override // No confirmation required, comparable public void printSingletonInstance(JavaWriter out, Definition definition) { super.printSingletonInstance(out, definition); // 🦁>>> @@ -97,7 +99,7 @@ public void printSingletonInstance(JavaWriter out, Definition definition) { // 🦁<<< } - @Override // 无需确认,父方法empty + @Override // No confirmation needed, parent method is empty public void generateTableClassFooter(TableDefinition table, JavaWriter out) { // 🦁>>> // table is TableDefinition : SysCommitJournalTable, SysCommitJournal @@ -211,7 +213,7 @@ else if (colType.contains("int")) { out.println("}"); } - // 缩短import + // import Set import4Table = WingsCodeGenConf.getImport4Table(); if (!import4Table.isEmpty()) { StringBuilder java = reflectFieldSb(out); @@ -220,7 +222,7 @@ else if (colType.contains("int")) { boolean got = false; for (String imp : import4Table) { int p = imp.lastIndexOf('.'); - if (p > 0 && str.contains(imp)) { // 避免import无用,先判断 + if (p > 0 && str.contains(imp)) { // avoid useless import, check first String rep = imp.substring(p + 1); str = str.replace(imp, rep); qts.add(imp); @@ -241,7 +243,7 @@ else if (colType.contains("int")) { private final Pattern daoFetches = Pattern.compile("\n +/[* \n]+Fetch records", Pattern.MULTILINE); private final Pattern daoFetchMd = Pattern.compile("(fetch[^(]*)\\("); - @Override // 确认替换后代码,diff即可 + @Override // Confirm the replacement code and diff it public void generateDao(TableDefinition table, JavaWriter out) { super.generateDao(table, out); // 🦁>>> @@ -264,7 +266,7 @@ public void generateDao(TableDefinition table, JavaWriter out) { Matcher me = daoExtends.matcher(dao); dao = me.replaceFirst("public class $1Dao extends " + implClass.getSimpleName() + "<$1Table, "); - // 重置内容,正则替换 + // Reset the Content, Regexp Replacement java.setLength(0); if (implClass.equals(WingsJooqDaoJournalImpl.class)) { final Matcher md = daoFetches.matcher(dao); diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectAuthGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectAuthGenerator.java index 4369b92b4..692894ed9 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectAuthGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectAuthGenerator.java @@ -10,7 +10,7 @@ import java.util.List; /** - * idea中,main函数执行和spring执行,workdir不同 + * In IDEA, the execution of `main` is different from spring, workdir is different. * * @author trydofor * @since 2021-02-20 diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectEnumGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectEnumGenerator.java index 099bccbb9..c6adbf195 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectEnumGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectEnumGenerator.java @@ -12,7 +12,7 @@ import java.util.function.Consumer; /** - * idea中,main函数执行和spring执行,workdir不同 + * In IDEA, the execution of `main` is different from spring, workdir is different. * * @author trydofor * @since 2021-02-20 diff --git a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectJooqGenerator.java b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectJooqGenerator.java index 5294817cd..bc3022d72 100644 --- a/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectJooqGenerator.java +++ b/wings/faceless-codegen/src/main/java/pro/fessional/wings/faceless/project/ProjectJooqGenerator.java @@ -13,7 +13,7 @@ import java.util.function.Consumer; /** - * idea中,main函数执行和spring执行,workdir不同 + * In IDEA, the execution of `main` is different from spring, workdir is different. * * @author trydofor * @since 2021-02-20 diff --git a/wings/faceless-codegen/src/main/resources/wings-flywave/jooq-codegen-faceless.xml b/wings/faceless-codegen/src/main/resources/wings-flywave/jooq-codegen-faceless.xml index f0ccca1c5..202376c79 100644 --- a/wings/faceless-codegen/src/main/resources/wings-flywave/jooq-codegen-faceless.xml +++ b/wings/faceless-codegen/src/main/resources/wings-flywave/jooq-codegen-faceless.xml @@ -22,13 +22,13 @@ .* - spring.* # Spring 自动创建 - |.*__[a-z]* # 日志表 - |.*\$[a-z]* # 日志表 - |sys_commit_journal # jdbc处理 - |sys_light_sequence # jdbc处理 - |sys_schema_journal # jdbc处理 - |sys_schema_version # jdbc处理 + spring.* # Spring table + |.*__[a-z]* # journal table + |.*\$[a-z]* # journal table + |sys_commit_journal # jdbc handled + |sys_light_sequence # jdbc handled + |sys_schema_journal # jdbc handled + |sys_schema_version # jdbc handled @@ -80,7 +80,7 @@ true true true - + false false diff --git a/wings/faceless-codegen/src/main/resources/wings-tmpl/ConstantEnumTemplate.java b/wings/faceless-codegen/src/main/resources/wings-tmpl/ConstantEnumTemplate.java index c2d2b9cfe..d79a4c3c4 100644 --- a/wings/faceless-codegen/src/main/resources/wings-tmpl/ConstantEnumTemplate.java +++ b/wings/faceless-codegen/src/main/resources/wings-tmpl/ConstantEnumTemplate.java @@ -22,9 +22,9 @@ public enum ConstantEnumTemplate implements ConstantEnum, StandardI18nEnum { // RNA:USE /SUPER/enum.name/* // RNA:USE /1020100/enum.id/ // RNA:USE /standard_language/enum.code/fd - // RNA:USE /标准语言/enum.hint/ - // RNA:USE /模板路径/enum.info/ - SUPER(1020100, "standard_language", "标准语言", "模板路径"), + // RNA:USE /Standard Language/enum.hint/ + // RNA:USE /Template Path/enum.info/ + SUPER(1020100, "standard_language", "Standard Language", "Template Path"), // RNA:DONE enum ; // RNA:EACH /1/enum-items/enum diff --git a/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardLanguageTemplate.java b/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardLanguageTemplate.java index 610aa0bb0..11e3b0ee8 100644 --- a/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardLanguageTemplate.java +++ b/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardLanguageTemplate.java @@ -13,7 +13,7 @@ // RNA:USE /2019-09-17/now.date/ /** - * language + "_" + country,使用`_`分隔,zh_CN,在解析中,也支持zh-CN + * language + `_` + country, `_` delimited, `zh_CN`. For parsing, `zh-CN` is also supported. * * @author trydofor * @see Locale#toString() @@ -26,9 +26,9 @@ public enum StandardLanguageTemplate implements StandardLanguageEnum { // RNA:USE /SUPER/enum.name/* // RNA:USE /1020100/enum.id/ // RNA:USE /standard_language/enum.code/fd - // RNA:USE /标准语言/enum.hint/ - // RNA:USE /模板路径/enum.info/ - SUPER(1020100, "standard_language", "标准语言", "模板路径"), + // RNA:USE /Standard Language/enum.hint/ + // RNA:USE /Template Path/enum.info/ + SUPER(1020100, "standard_language", "Standard Language", "Template Path"), // RNA:DONE enum ; // RNA:EACH /1/enum-items/enum diff --git a/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardTimezoneTemplate.java b/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardTimezoneTemplate.java index e9d3f0646..b2088d1f5 100644 --- a/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardTimezoneTemplate.java +++ b/wings/faceless-codegen/src/main/resources/wings-tmpl/StandardTimezoneTemplate.java @@ -26,9 +26,9 @@ public enum StandardTimezoneTemplate implements StandardTimezoneEnum { // RNA:USE /SUPER/enum.name/* // RNA:USE /1020100/enum.id/ // RNA:USE /standard_language/enum.code/fd - // RNA:USE /标准语言/enum.hint/ - // RNA:USE /模板路径/enum.info/ - SUPER(1020100, "standard_language", "标准语言", "模板路径"), + // RNA:USE /Standard Language/enum.hint/ + // RNA:USE /Template Path/enum.info/ + SUPER(1020100, "standard_language", "Standard Language", "Template Path"), // RNA:DONE enum ; // RNA:EACH /1/enum-items/enum diff --git a/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/codegen/ConstantNaviGeneratorTest.java b/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/codegen/ConstantNaviGeneratorTest.java index f05929ccf..4c2ff4422 100644 --- a/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/codegen/ConstantNaviGeneratorTest.java +++ b/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/codegen/ConstantNaviGeneratorTest.java @@ -11,7 +11,7 @@ * @author trydofor * @since 2021-03-05 */ -@Disabled("权限树Mock,生成Constant,已有数据库数据代替") +@Disabled("Mock permission tree, generate Constant, use db instead") class ConstantNaviGeneratorTest { @Test diff --git a/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/enums/ConstantEnumGenSample.java b/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/enums/ConstantEnumGenSample.java index 764371799..5e2680c93 100644 --- a/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/enums/ConstantEnumGenSample.java +++ b/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/enums/ConstantEnumGenSample.java @@ -9,7 +9,7 @@ import java.util.List; /** - * 可以自己设置配置文件 + * Customize Config * * @author trydofor * @since 2020-06-10 diff --git a/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/sample/JooqCodeAutoGenSample.java b/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/sample/JooqCodeAutoGenSample.java index 94dc0de7b..7ae2e53e2 100644 --- a/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/sample/JooqCodeAutoGenSample.java +++ b/wings/faceless-codegen/src/test/java/pro/fessional/wings/faceless/sample/JooqCodeAutoGenSample.java @@ -4,15 +4,17 @@ import pro.fessional.wings.faceless.jooqgen.WingsCodeGenerator; /** - * 可由 FacelessAutogenTest 代替 + * FacelessAutogenTest instead * * @author trydofor * @since 2019-05-31 */ public class JooqCodeAutoGenSample { - // 注意路径,为工程顶级目录即可 - // 注意在目标工程中,应该注释掉.springRepository(false),使Dao自动加载 + /** + * Pay attention to the path, should be the project top-level directory. + * Note that in the target project, you should comment out `.springRepository(false)` to auto load the `Dao` + */ public static void main(String[] args) { // === Must Drop And Init === // WingsJooqDaoAliasImplTest#test0DropAndInit @@ -32,16 +34,16 @@ private static void genJooq() { .jdbcUser(user) .jdbcPassword(pass) .databaseSchema(database) - // 支持 pattern的注释写法 + // support Regexp comment .databaseIncludes("sys_constant_enum" + "|sys_standard_i18n" + "|tst_sharding") .databaseVersionProvider("SELECT MAX(revision) FROM sys_schema_version WHERE apply_dt > '1000-01-01'") .targetPackage("pro.fessional.wings.faceless.database.autogen") .targetDirectory("wings/faceless-jooq/src/test/java/") -// 不用spring自动注入 +// Disable spring auto scan // .springRepository(false) -// 使用enum类型 +// use enum type // .forcedType(new ForcedType() // .withUserType("pro.fessional.wings.faceless.enums.auto.StandardLanguage") // .withConverter("pro.fessional.wings.faceless.database.jooq.converter.JooqConsEnumConverter.of(StandardLanguage.class)") @@ -58,7 +60,7 @@ private static void genShard() { .jdbcUser(user) .jdbcPassword(pass) .databaseSchema(database) - // 支持 pattern的注释写法 + // support Regexp comment .databaseIncludes("tst_sharding") .databaseVersionProvider(null) .targetPackage("pro.fessional.wings.faceless.database.autogen") diff --git a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/flywave/RevisionRegister.java b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/flywave/RevisionRegister.java index 86edbc681..c996ae7b7 100644 --- a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/flywave/RevisionRegister.java +++ b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/flywave/RevisionRegister.java @@ -4,7 +4,7 @@ import pro.fessional.wings.faceless.util.FlywaveRevisionScanner; /** - * 标记已存在的版本 + * Mark the existed revision * * @author trydofor * @since 2021-03-17 @@ -12,33 +12,26 @@ public interface RevisionRegister { /** - * 版本 - * - * @return 版本 + * the revision number */ long revision(); /** - * 信息 - * - * @return 信息 + * description of this revision */ @NotNull String description(); /** - * flywave的路径,前后不必有`/` + * path of flywave, no `/` before and after * - * @return 路径 * @see FlywaveRevisionScanner#REVISION_PATH_FLYWAVE_HEAD */ @NotNull String flywave(); /** - * classpath路径 - * - * @return 路径 + * classpath of flywave */ @NotNull default String classpath() { diff --git a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/project/ProjectSchemaManager.java b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/project/ProjectSchemaManager.java index cc7f919a7..b9eac50f5 100644 --- a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/project/ProjectSchemaManager.java +++ b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/project/ProjectSchemaManager.java @@ -14,8 +14,7 @@ import java.util.function.Consumer; /** - * 按常见的版本管理场景提供便捷方式。 - *

+ * Provides a convenient version management of common scenarios. * spring.wings.faceless.flywave.enabled.module=true * * @author trydofor @@ -39,27 +38,28 @@ public ProjectSchemaManager(SchemaRevisionManager schemaRevisionManager, boolean } /** - * 适用于项目的线性升级,初始化warlock及项目第一版,以时间戳负值作为commitId,表示手动执行。 + * For linear upgrade or downgrade. using negative timestamp as commitId means manual execution. * - * @param revi publish的目标版本 - * @param customize 路径helper + * @param revision revision to publish to + * @param customize path helper + * @see #mergePublish(SortedMap, long, long) */ @SafeVarargs - public final void mergePublish(long revi, Consumer... customize) { + public final void mergePublish(long revision, Consumer... customize) { final Helper helper = FlywaveRevisionScanner.helper(); for (Consumer consumer : customize) { consumer.accept(helper); } - mergePublish(helper.scan(), -ThreadNow.millis(), revi); + mergePublish(helper.scan(), -ThreadNow.millis(), revision); } /** - * 适用于线性的升级或降级。①checkAndInitSql合并(插入或更新)脚本;②publishRevision到指定版本。 - * 注意:若降级且降级脚本被更新,可能导致降级失败,此时应该使用 forceDownThenMergePub + * For linear upgrade or downgrade. (1) checkAndInitSql merge (insert or update) script; (2) publishRevision to the specified version. + * Note: If you downgrade and the downgrade script is updated, it may cause the downgrade to fail, then you should use downThenMergePublish * - * @param sqls revi脚本 - * @param commitId cid - * @param revision 目标版本 + * @param sqls revision script + * @param commitId commit id + * @param revision revision to publish to */ public void mergePublish(SortedMap sqls, long commitId, long revision) { schemaRevisionManager.checkAndInitSql(sqls, commitId, true); @@ -76,11 +76,11 @@ public final void mergeForceApply(boolean isUpto, Consumer... customize) } /** - * 适用于断点发布,先合并脚本,然后发布未apply的合并进来的版本。 - * ①checkAndInitSql合并脚本;②forceApplyBreak合并进来的脚本。 + * To fix the breakpoint, first merge the script, then release the unapply merge-in version. + * (1) checkAndInitSql merge script; (2)forceApplyBreak merge-in script. * - * @param sqls 脚本 - * @param commitId cid + * @param sqls revision script + * @param commitId commit id */ public void mergeForceApply(SortedMap sqls, long commitId, boolean isUpto) { schemaRevisionManager.checkAndInitSql(sqls, commitId, true); @@ -91,12 +91,13 @@ public void mergeForceApply(SortedMap s /** - * 适用于降级脚本变化的重新发布,先排序版本,强制倒序降级,合并脚本,正序升级版本。 - * ①forceApplyBreak降级脚本;②checkAndInitSql合并;③publishRevision到指定版本 + * To republish changes in downgrade script, sort version first, + * force downgrade in reverse order, merge scripts, and upgrade version in ascending order. + * (1) forceApplyBreak downgrade script; (2) checkAndInitSql merge; (3) publishRevision to specified version * - * @param sqls 脚本 - * @param commitId cid - * @param revision 目标版本 + * @param sqls revision script + * @param commitId commit id + * @param revision revision to publish to */ public void downThenMergePublish(SortedMap sqls, long commitId, Collection revision) { final TreeSet tree = new TreeSet<>(revision); diff --git a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/spring/prop/FlywaveFitProp.java b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/spring/prop/FlywaveFitProp.java index 89d8910c4..30c8f97b9 100644 --- a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/spring/prop/FlywaveFitProp.java +++ b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/spring/prop/FlywaveFitProp.java @@ -27,7 +27,7 @@ public class FlywaveFitProp { public static final String Key$autoInit = Key + ".auto-init"; /** - * 具体依赖项目 + * Specific dependencies * * @see #Key$fit */ diff --git a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveInteractiveGui.java b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveInteractiveGui.java index 5423d44e3..59e9fcdea 100644 --- a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveInteractiveGui.java +++ b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveInteractiveGui.java @@ -65,9 +65,9 @@ public static BiConsumer logGui() { frame.add(scrollPang); frame.setSize(1200, 800); - // 居中 + // center frame.setLocationRelativeTo(null); - // 焦点 + // focus frame.setState(Frame.NORMAL); frame.toFront(); frame.requestFocus(); @@ -145,20 +145,20 @@ else if ("WARN".equalsIgnoreCase(key)) { hooked.add(fun); Runtime.getRuntime().addShutdownHook(new Thread(() -> { int res = showConfirmDialog(null, """ - 程序退出了,要看的赶紧看! - 重点关注ERROR内容,logger中更全 + The program exit, Please check ASAP! + Focus on ERROR, more in the logger. - [yes] 直接退出 - [no] 控制台<回车>退出""", + [yes] Exit + [no] show console, and press ENTER to exit""", "😺😸😹😻😼😽🙀😿😾😺", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (res != 0) { try { for (BiConsumer bc : hooked) { - bc.accept("WARN", "主程序已退出,在控制台(console)按<回车>退出"); + bc.accept("WARN", "The program exit, press ENTER to exit."); } - System.out.println("主程序已退出,为保留日志窗口,卡 in.read() 呢!"); - System.out.println("要在下面按<回车>才能退出,下面↓,下面↓"); + System.out.println("The program exit, waiting for in.read() to keep the log window"); + System.out.println("press ENTER to exit"); //noinspection ResultOfMethodCallIgnored System.in.read(); } diff --git a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveRevisionScanner.java b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveRevisionScanner.java index 4c9fc304a..f3c9bfb71 100644 --- a/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveRevisionScanner.java +++ b/wings/faceless-flywave/src/main/java/pro/fessional/wings/faceless/util/FlywaveRevisionScanner.java @@ -1,5 +1,6 @@ package pro.fessional.wings.faceless.util; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,7 +95,7 @@ private static String prefixPath(String prefix, String name) { } } - // sql 文件 + // sql file final int dot = sb.length() - REVISION_PATH_REVIFILE_EXTN.length() - 1; if (dot > 0) { final int lst = sb.length() - 1; @@ -104,7 +105,7 @@ private static String prefixPath(String prefix, String name) { } } - // 目录 + // dir sb.append(REVISION_PATH_REVIFILE_TAIL); return sb.toString(); } @@ -182,8 +183,10 @@ public static SortedMap scanBranch(String... name) { } /** + * scan revision-sql from RevisionRegister + * * @param path FlywaveRevisionRegister - * @return 按版本号升序排列的TreeMap + * @return TreeMap in ascending order by version number * @see PathMatchingResourcePatternResolver */ @NotNull @@ -196,13 +199,16 @@ public static SortedMap scan(@NotNull RevisionRegister... pat } /** - * 把指定或默认位置的*.sql文件都加载进来,并按文件名的字母顺序排序。 - * String path = "classpath*:/wings-flywave/master/"; // 全部类路径 - * String path = "classpath:/wings-flywave/master/"; // 当前类路径 - * String path = "file:src/main/resources/wings-flywave/master/"; // 具体文件 + *

+     * Load all `*.sql` files from the specified or default location and sorts them alphabetically by filename.
+     *
+     * String path = "classpath*:/wings-flywave/master/"; // all classpath, include dependence
+     * String path = "classpath:/wings-flywave/master/";  // current project classpath, exclude dependence.
+     * String path = "file:src/main/resources/wings-flywave/master/"; // file system
+     * 
* - * @param path 按Spring的格式写,classpath*:,classpath:等,默认[REVISIONSQL_PATH] - * @return 按版本号升序排列的TreeMap + * @param path in Spring's format, `classpath*:`, `classpath:`, etc. + * @return TreeMap in ascending order by revision number * @see PathMatchingResourcePatternResolver */ @NotNull @@ -220,8 +226,10 @@ public static SortedMap scan(@NotNull Collection path } /** - * @param result 排序map - * @param path 扫描路径 + * scan revision-sql to result + * + * @param result sorted map + * @param path path to scan * @see #scan(String...) */ public static void scan(SortedMap result, String path) { @@ -290,10 +298,10 @@ public static void scan(SortedMap result, String path) { } /** - * 读取所有降级脚本 + * Join all undo (downgrade) sql script by '\n' * - * @param sqls sqls - * @return sql + * @param sqls revision sqls + * @return sql undo sql */ @NotNull public static String undo(SortedMap sqls) { @@ -305,10 +313,10 @@ public static String undo(SortedMap sqls) { } /** - * 读取所有升级脚本 + * Join all upto (upgrade) sql script by '\n' * - * @param sqls sqls - * @return sql + * @param sqls revision sqls + * @return sql upto sql */ @NotNull public static String upto(SortedMap sqls) { @@ -320,9 +328,8 @@ public static String upto(SortedMap sqls) { } /** - * 可应用版本过滤器和重命名 + * New a helper to filter and handle revision * - * @return 构造器 * @see Helper */ public static Helper helper() { @@ -330,12 +337,12 @@ public static Helper helper() { } /** - * 执行以下步骤 - * ①scan全路径 - * ②replace版本 - * ③include过滤器 - * ④exclude过滤器 - * ⑤modifier调整 + * Perform the following steps, + * (1) scan all path + * (2) replace revision + * (3) include filter + * (4) exclude filter + * (5) modifier */ public static class Helper { private final LinkedHashMap, String> includes = new LinkedHashMap<>(); @@ -344,6 +351,7 @@ public static class Helper { private final LinkedHashMap, String> modifier = new LinkedHashMap<>(); private final LinkedHashSet paths = new LinkedHashSet<>(); + @Contract("_->this") public Helper path(RevisionRegister... path) { for (RevisionRegister s : path) { paths.add(s.classpath()); @@ -351,11 +359,13 @@ public Helper path(RevisionRegister... path) { return this; } + @Contract("_->this") public Helper path(String... path) { Collections.addAll(paths, path); return this; } + @Contract("_->this") public Helper flywave(String... path) { for (String s : path) { paths.add(FlywaveRevisionScanner.flywavePath(s)); @@ -363,11 +373,13 @@ public Helper flywave(String... path) { return this; } + @Contract("->this") public Helper master() { paths.add(REVISION_PATH_MASTER); return this; } + @Contract("_->this") public Helper master(String... path) { for (String s : path) { paths.add(FlywaveRevisionScanner.masterPath(s)); @@ -375,6 +387,7 @@ public Helper master(String... path) { return this; } + @Contract("_->this") public Helper branch(String... path) { for (String s : path) { paths.add(FlywaveRevisionScanner.branchPath(s)); @@ -382,6 +395,7 @@ public Helper branch(String... path) { return this; } + @Contract("_->this") public Helper feature(String... path) { for (String s : path) { paths.add(FlywaveRevisionScanner.featurePath(s)); @@ -389,6 +403,7 @@ public Helper feature(String... path) { return this; } + @Contract("_->this") public Helper somefix(String... path) { for (String s : path) { paths.add(FlywaveRevisionScanner.somefixPath(s)); @@ -396,6 +411,7 @@ public Helper somefix(String... path) { return this; } + @Contract("_->this") public Helper support(String... path) { for (String s : path) { paths.add(FlywaveRevisionScanner.supportPath(s)); @@ -404,24 +420,26 @@ public Helper support(String... path) { } /** - * 把from版替换为to版,新版不存在时创建。 + * Replace the revision from `from` to `to`, create if not exist. * - * @param from 旧版本 - * @param to 新版本 - * @return builder + * @param from old revision + * @param to new revision + * @return this */ + @Contract("_,_->this") public Helper replace(long from, long to) { return replace(from, to, false); } /** - * 把from版替换为to版,新版不存在时创建。同时使用fn处理from和to版 + * Replace the version from `from` to `to`, create if not exist. * - * @param from 旧版本 - * @param to 新版本 - * @param sql 同时replace undo和upto脚本中的版本号 - * @return builder + * @param from old revision + * @param to new revision + * @param sql whether to replace in the sql text + * @return this */ + @Contract("_,_,_->this") public Helper replace(long from, long to, boolean sql) { if (sql) { final Pattern op = Pattern.compile("\\b" + from + "\\b"); @@ -434,13 +452,15 @@ public Helper replace(long from, long to, boolean sql) { } /** - * 把from版替换为to版,新版不存在时创建。同时使用fn处理from和to版 + * Replace the version from `from` to `to`, create if not exist. + * And modify the sql text by `mod` * - * @param from 旧版本 - * @param to 新版本 - * @param mod 同时replace undo和upto脚本中的方法 - * @return builder + * @param from old revision + * @param to new revision + * @param mod modify the sql text + * @return this */ + @Contract("_,_,_->this") public Helper replace(long from, long to, Function mod) { if (from < 0 || to < 0) throw new IllegalArgumentException("revi must >0"); replaces.put(from, to); @@ -455,14 +475,15 @@ public Helper replace(long from, long to, Function mod) { } /** - * 调整RevisionSql内容 + * Modify the RevisionSql content * - * @param revi 声明版本 - * @param str 查找字符串 - * @param rpl 替换字符串 - * @return builder + * @param revi revision to modify + * @param str the string to find + * @param rpl the string to replace + * @return this * @see #modify(String, BiConsumer) */ + @Contract("_,_,_->this") public Helper modify(long revi, String str, String rpl) { return modify("replace " + str + " to " + rpl + " at revi=" + revi, revi, it -> { it.setUptoText(it.getUptoText().replace(str, rpl)); @@ -471,62 +492,68 @@ public Helper modify(long revi, String str, String rpl) { } /** - * 调整RevisionSql内容 + * Modify the RevisionSql content * - * @param revi 声明版本 - * @param mod 调整函数 Consumer(实际SQL) - * @return builder + * @param revi revision to modify + * @param mod Consumer of RevisionSql + * @return this * @see #modify(String, BiConsumer) */ + @Contract("_,_->this") public Helper modify(long revi, Consumer mod) { return modify("", revi, mod); } /** - * 调整RevisionSql内容 + * Modify the RevisionSql content * - * @param info 用途说明 - * @param revi 声明版本 - * @param mod 调整函数 Consumer(实际SQL) - * @return builder + * @param info info of modify + * @param revi revision to modify + * @param mod Consumer of RevisionSql + * @return this * @see #modify(String, BiConsumer) */ + @Contract("_,_,_->this") public Helper modify(String info, long revi, Consumer mod) { modifier.put((r, s) -> {if (r == revi) mod.accept(s);}, info); return this; } /** - * 调整RevisionSql内容 + * Modify the RevisionSql content * - * @param mod 调整函数 BiConsumer(声明版本, 实际SQL) - * @return builder + * @param mod BiConsumer of revision and RevisionSql + * @return this */ + @Contract("_->this") public Helper modify(BiConsumer mod) { return modify("", mod); } /** - * 调整RevisionSql内容 + * Modify the RevisionSql content * - * @param info 用途说明 - * @param mod 调整函数 BiConsumer(声明版本, 实际SQL) - * @return builder + * @param info info of modify + * @param mod BiConsumer of revision and RevisionSql + * @return this */ + @Contract("_,_->this") public Helper modify(String info, BiConsumer mod) { modifier.put(mod, info); return this; } - + @Contract("_->this") public Helper include(RevisionRegister revi) { return include(revi.description(), revi.revision()); } + @Contract("_->this") public Helper include(long... revi) { return include("", revi); } + @Contract("_,_->this") public Helper include(String info, long... revi) { final HashSet rvs = new HashSet<>(); for (Long l : revi) { @@ -535,24 +562,29 @@ public Helper include(String info, long... revi) { return include(info, rvs::contains); } + @Contract("_->this") public Helper include(Predicate inc) { return include("", inc); } + @Contract("_,_->this") public Helper include(String info, Predicate inc) { includes.put(inc, info); return this; } + @Contract("_->this") public Helper exclude(RevisionRegister revi) { return exclude(revi.description(), revi.revision()); } + @Contract("_->this") public Helper exclude(long... revi) { return exclude("", revi); } + @Contract("_,_->this") public Helper exclude(String info, long... revi) { final HashSet rvs = new HashSet<>(); for (Long l : revi) { @@ -561,10 +593,12 @@ public Helper exclude(String info, long... revi) { return exclude(info, rvs::contains); } + @Contract("_->this") public Helper exclude(Predicate exc) { return exclude("", exc); } + @Contract("_,_->this") public Helper exclude(String info, Predicate exc) { excludes.put(exc, info); return this; diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/InteractiveManager.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/InteractiveManager.kt index b3afac029..0990397bc 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/InteractiveManager.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/InteractiveManager.kt @@ -10,20 +10,20 @@ import java.util.function.Function interface InteractiveManager { /** - * 当执行undo的时候,是否控制台确认 - * @param ask ask类型 - * @param yes 是否确认,默认true - * @return 旧值 + * Whether to confirm before `undo` + * @param ask ask type + * @param yes whether to confirm, true by default + * @return old value */ fun needAsk(ask: T, yes: Boolean): Boolean? /** - * 用什么方式确认,传递message,返回继续or停止 + * How to confirm, pass message, return to continue or stop */ fun askWay(func: Function): Function? /** - * 用什么方式处理,运行时的 token, message + * How to log the runtime token and message */ fun logWay(func: BiConsumer): BiConsumer? } diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaDefinitionLoader.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaDefinitionLoader.kt index 1b9824e69..fd8407414 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaDefinitionLoader.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaDefinitionLoader.kt @@ -3,8 +3,8 @@ package pro.fessional.wings.faceless.flywave import javax.sql.DataSource /** - * 提供当前数据库的结构信息。 - * 用来做分表和触发器 + * Provides information about the table structure of the current database. Used for data sharding and triggers. + * * @author trydofor * @since 2019-06-12 */ @@ -17,67 +17,77 @@ interface SchemaDefinitionLoader { } /** - * 列出当前datasource中的所有表 - * @param dataSource 当前数据源 + * list all tables of current datasource + * + * @param dataSource current datasource */ fun showTables(dataSource: DataSource): List /** - * 列出,可以创建当前表(字段,约束,索引,触发器)的所有DDL - * 不应该不考虑外键约束,范式已经符合现代软件开发节奏。 - * @param dataSource 当前数据源 - * @param table 目标表名,不要包含`` ` `` + * List all DDLs that can create the current table with fields, constraints, indexes and triggers. + * Foreign Key should not be considered, some normalization is no longer good in modern software development. + * + * @param dataSource current datasource + * @param table the plain table name, do NOT include any quote. */ fun showFullDdl(dataSource: DataSource, table: String): List /** - * 检测两个表的是否骨架相同,包括字段,索引,触发器 - * @param dataSource 当前数据源 - * @param table 目标表名,不要包含`` ` `` - * @param other 其他表名,不要包含`` ` `` - * @return 不一致的信息,空表示完全一致 + * Whether two tables have the same skeleton, including field (NAME, TYPE, COMMENT, POSITION), index and trigger. + * + * @param dataSource current datasource + * @param table the plain table name, do NOT include any quote. + * @param other other plain table name, do NOT include any quote. + * @return diff info, empty mean the same */ fun diffBoneSame(dataSource: DataSource, table: String, other: String, types: Int = TYPE_TBL or TYPE_IDX or TYPE_TRG): String /** - * 检测两个表的是否完全相同,包括字段,索引,触发器 - * @param dataSource 当前数据源 - * @param table 目标表名,不要包含`` ` `` - * @param other 其他表名,不要包含`` ` `` - * @return 不一致的信息,空表示完全一致 + * Whether two tables have the same struct, including field (NAME, TYPE, COMMENT, POSITION, NULLABLE, DEFAULT), index and trigger. + * + * @param dataSource current datasource + * @param table the plain table name, do NOT include any quote. + * @param other other plain table name, do NOT include any quote. + * @return diff info, empty mean the same */ fun diffFullSame(dataSource: DataSource, table: String, other: String, types: Int = TYPE_TBL or TYPE_IDX or TYPE_TRG): String /** - * 至少列出当前表的`字段名`,`类型`,`注释`三项的DDL部分 - * 用来填充 `TABLE_BONE` 环境变量。各字段逗号分隔后,符合SQL语法(末行无逗号) + * A DDL section that lists at least the `Name`, `Type`, and `Comment` of field in the table. + * Used to populate the `TABLE_BONE` environment variable. Fields are comma-separated in SQL syntax + * (no comma at the end of the line) + * * ```sql * `LOGIN_INFO` text COMMENT 'login info', * `OTHER_INFO` text COMMENT 'other info' * ``` - * @param dataSource 当前数据源 - * @param table 目标表名。 + * + * @param dataSource current datasource + * @param table the plain table name, do NOT include any quote. */ fun showBoneCol(dataSource: DataSource, table: String): List /** - * 获得当前表的主键字段名 - * @param table 目标表名。 + * Get the field name of primary key in the table. + * + * @param table the plain table name, do NOT include any quote. */ fun showPkeyCol(dataSource: DataSource, table: String): List data class Trg(val name: String, val timing: String, val action: String, val event: String, val table: String) /** - * 获得当前表触发器的名字和`EVENT`内容 - * @param table 目标表名。 + * Get the name and `EVENT` content of trigger in the table. + * + * @param table the plain table name, do NOT include any quote. */ fun showBoneTrg(dataSource: DataSource, table: String): List /** - * 根据 Trg定义,创建trigger ddl - * @param trg 定义 - * @param drop 是drop还是create + * Create trigger DDL via trigger definition + * + * @param trg trigger definition + * @param drop drop or create */ fun makeDdlTrg(trg: Trg, drop: Boolean): String } diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManager.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManager.kt index b50242f94..ed3e5cda9 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManager.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManager.kt @@ -7,17 +7,18 @@ import java.io.File import java.util.LinkedList import javax.sql.DataSource + +typealias FilterSorter = (List) -> List + /** - * 进行表结构dump + * Dump table structure * * @author trydofor * @since 2019-12-15 */ -typealias FilterSorter = (List) -> List - class SchemaFulldumpManager( - private val sqlStatementParser: SqlStatementParser, - private val schemaDefinitionLoader: SchemaDefinitionLoader + private val sqlStatementParser: SqlStatementParser, + private val schemaDefinitionLoader: SchemaDefinitionLoader ) { private val log = LoggerFactory.getLogger(SchemaFulldumpManager::class.java) @@ -25,7 +26,7 @@ class SchemaFulldumpManager( const val prefix = "--" /** - * 满足正则(全匹配matches,忽略大小写)的会被移除,按ascii自燃顺序排序 + * Exclude Those that satisfy the RegExp (matches all, case-insensitive) and sorted in ascii order */ @JvmStatic fun excludeRegexp(vararg regex: String): FilterSorter { @@ -36,7 +37,7 @@ class SchemaFulldumpManager( } /** - * 不满足正则(全匹配matches,忽略大小写)的会被移除,按ascii自燃顺序排序 + * Include Those that satisfy the RegExp (matches all, case-insensitive) and sorted in ascii order */ @JvmStatic fun includeRegexp(vararg regex: String): FilterSorter { @@ -47,8 +48,9 @@ class SchemaFulldumpManager( } /** - * 按字符串相等(不区分大小写)过滤和排序。其中,`--` 开头表示分组分割线 - * @param only true表示只包匹配的,false把未匹配的放最后,按ascii自燃顺序排序 + * Filter and sort by string equality (case-insensitive). + * Where `--` at the beginning denotes a grouping divider. + * @param only sort by ascii order. `true` means include only the matched ones, `false` means include the unmatched ones but put them at last. */ @JvmStatic fun groupedTable(only: Boolean = true, vararg table: String): FilterSorter { @@ -79,8 +81,9 @@ class SchemaFulldumpManager( } /** - * 按正则(全匹配matches,忽略大小写)过滤和排序。其中,`--` 开头表示分组分割线 - * @param only true表示只包匹配的,false把未匹配的放最后,按ascii自燃顺序排序 + * Filter and sort by Regexp (matches all, case-insensitive) + * Where `--` at the beginning denotes a grouping divider. + * @param only sort by ascii order. `true` means include only the matched ones, `false` means include the unmatched ones but put them at last. */ @JvmStatic fun groupedRegexp(only: Boolean = true, vararg regexp: String): FilterSorter { @@ -131,9 +134,9 @@ class SchemaFulldumpManager( } data class SqlString( - val table: String, - val sqlType: SqlType, - val sqlText: String + val table: String, + val sqlType: SqlType, + val sqlText: String ) fun saveFile(path: String, sqls: List) = saveFile(File(path), sqls) @@ -148,9 +151,11 @@ class SchemaFulldumpManager( buf.write(sql.sqlText) buf.write("\n\n") } + SqlType.DdlTrigger -> { trgs.add(sql) } + else -> { buf.write("$prefix ${sql.table} ${sql.sqlType}") buf.write("\n") @@ -174,12 +179,12 @@ class SchemaFulldumpManager( } /** - * dump 指定数据库的DDL (table,index和trigger) - * 如果元素以`--`开头,表示注释 + * Dump DDL for the database (table, index and trigger), + * the element starting with `--` is a comment. * - * @param database 数据库 - * @param filterSorter 过滤并排序 - * @return table_name :sql的map + * @param database database + * @param filterSorter filter and sorter + * @return table and sql string */ fun dumpDdl(database: DataSource, filterSorter: FilterSorter = excludeShadow): List { @@ -205,9 +210,11 @@ class SchemaFulldumpManager( ddlTableReg.containsMatchIn(sql) -> { result.add(SqlString(table, SqlType.DdlTable, sql)) } + ddlTriggerReg.containsMatchIn(sql) -> { result.add(SqlString(table, SqlType.DdlTrigger, sql)) } + else -> { result.add(SqlString(table, SqlType.SqlUnknown, sql)) } @@ -219,12 +226,12 @@ class SchemaFulldumpManager( } /** - * dump指定数据库的数据,INSERT语句 - * 如果元素以`--`开头,表示注释 + * Dump data in insert statement for the database, + * the element starting with `--` is a comment. * - * @param database 数据库 - * @param filterSorter 过滤并排序 - * @return table_name:sql的map + * @param database database + * @param filterSorter filter and sorter + * @return table and sql string */ fun dumpRec(database: DataSource, filterSorter: FilterSorter = excludeShadow): List { diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManager.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManager.kt index c3c5e924e..1811608ef 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManager.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManager.kt @@ -18,8 +18,8 @@ import java.util.function.Function import javax.sql.DataSource /** - * 控制`$schemaJournalTable`中的`log_update`和`log_delete`, - * 进而实现自动的Trigger创建和删除。 + * Mange `log_update` and `log_delete` of `$schemaJournalTable` + * to auto create or delete Trigger. * * @author trydofor * @since 2019-06-13 @@ -68,48 +68,52 @@ class SchemaJournalManager( } /** - * 根据DDL模板应用跟踪表和触发器 - * 跟踪表,如果存在,没有数据,则重建。 - * 跟踪表,如果存在,且有数据,结构相同时,忽略,否则报错。 - * 触发器,如果触发器存在,删除重建。 - * 如果跟踪表和触发器都不存在,新建。 - * @param table 主表 - * @param enable 允许或禁止 - * @param commitId 提交ID,参见Journal + * Apply trace table and its Update Trigger based on DDL templates. + * Trace table, if it exists and has no data, recreate it. + * Trace table, if it exists and has data, ignore if the structure is the same, otherwise throw an error. + * Trigger, if trigger exists, delete and recreate it. + * If neither trace table nor trigger exists, create new one. + * + * @param table plain table + * @param enable enable or disable + * @param commitId commit id of Journal */ fun publishUpdate(table: String, enable: Boolean, commitId: Long) = publishJournal(table, enable, commitId, "update") /** - * 根据DDL模板应用跟踪表和触发器 - * 跟踪表,如果存在,没有数据,则重建。 - * 跟踪表,如果存在,且有数据,结构相同时,忽略,否则报错。 - * 触发器,如果触发器存在,删除重建。 - * 如果跟踪表和触发器都不存在,新建。 - * @param table 主表 - * @param enable 允许或禁止 - * @param commitId 提交ID,参见Journal + * Apply trace table and its Delete Trigger based on DDL templates. + * Trace table, if it exists and has no data, recreate it. + * Trace table, if it exists and has data, ignore if the structure is the same, otherwise throw an error. + * Trigger, if trigger exists, delete and recreate it. + * If neither trace table nor trigger exists, create new one. + * + * @param table plain table + * @param enable enable or disable + * @param commitId commit id of Journal */ fun publishDelete(table: String, enable: Boolean, commitId: Long) = publishJournal(table, enable, commitId, "delete") /** - * 根据DDL模板应用跟踪表和触发器 - * 跟踪表,如果存在,没有数据,则重建。 - * 跟踪表,如果存在,且有数据,结构相同时,忽略,否则报错。 - * 触发器,如果触发器存在,删除重建。 - * 如果跟踪表和触发器都不存在,新建。 - * @param table 主表 - * @param enable 允许或禁止 - * @param commitId 提交ID,参见Journal + * Apply trace table and its Insert Trigger based on DDL templates. + * Trace table, if it exists and has no data, recreate it. + * Trace table, if it exists and has data, ignore if the structure is the same, otherwise throw an error. + * Trigger, if trigger exists, delete and recreate it. + * If neither trace table nor trigger exists, create new one. + * + * @param table plain table + * @param enable enable or disable + * @param commitId commit id of Journal */ fun publishInsert(table: String, enable: Boolean, commitId: Long) = publishJournal(table, enable, commitId, "insert") /** - * 检查所有trigger,可以选择是否删除 - * @param table 主表 - * @param drop 是否询问删除,默认false + * Check all triggers, and can ask whether to delete. + * + * @param table plain table + * @param drop Whether to ask for drop, default false */ fun manageTriggers(table: String, drop: Boolean = false) { val here = "manageTriggers" @@ -130,11 +134,13 @@ class SchemaJournalManager( } /** - * 对比本地和数据库中的SQL。 - * 当不存在时,则把本地保存到数据库。 - * 当存在但内容不一致,已APPLY则log error,否则更新 - * @param table 主表 - * @param commitId 提交ID,参见Journal + * Compare the SQL between in local and in database. + * If it does not exist, then save local to database. + * If it exists but the contents are not the same and has been `APPLY` + * then log error, otherwise update it. + * + * @param table plain table + * @param commitId commit id of Journal */ fun checkAndInitDdl(table: String, commitId: Long) { val here = "checkAndInitDdl" @@ -443,7 +449,7 @@ class SchemaJournalManager( } } - // 跟踪表,删除存在的,非空的 + // trace table, delete existed and non-empty for ((_, tblRaw) in staffs) { val ddlTbl = mergeDdl(tmplTbl, model, tblRaw) val ddlTrg = mergeDdl(tmplTrg, model, tblRaw) @@ -454,11 +460,11 @@ class SchemaJournalManager( continue } - // 检查触发器 - val furTrg = parseTrgName(ddlTrg) // 新trigger名字 - var refTrc = false // 有引用 + // check trigger + val furTrg = parseTrgName(ddlTrg) // new trigger name + var refTrc = false // has ref for (trg in schemaDefinitionLoader.showBoneTrg(plainDs, tblRaw)) { - // 删除同名 + // delete same name if (trg.name.equals(furTrg, true)) { interactive.log(WARN, here, "drop trigger=${trg.name}, existed same name, table=$tblRaw, db=$plainName") if (interactive.needAsk(AskType.DropTrigger)) { @@ -466,7 +472,7 @@ class SchemaJournalManager( } tmpl.execute(schemaDefinitionLoader.makeDdlTrg(trg, true)) } else { - // 保留trigger使用的trac表 + // keep trace table used by trigger if (TemplateUtil.isBoundary(trg.event, curTac, false)) { interactive.log(INFO, here, "trigger=${trg.name}, with same trace-table=$curTac, db=$plainName") refTrc = true @@ -474,7 +480,7 @@ class SchemaJournalManager( } } - // 检查跟踪表 + // check trace table var newTrc = true if (tables.containsKey(curTac.lowercase())) { interactive.log(INFO, here, "existed trace-table=$curTac, table=$tblRaw, db=$plainName") @@ -494,7 +500,7 @@ class SchemaJournalManager( trgDdl[ddlTrg] = tblRaw } - // 检测已存在的,所有跟踪表应该结构一致 + // check existed, all trace tables have same structure. if (trcChk.isNotEmpty()) { val tmpTrc = "$table$tmpTkn" val tmpDdl = mergeDdl(tmplTbl, model, tmpTrc) @@ -525,7 +531,7 @@ class SchemaJournalManager( continue } } finally { - // 如创建,则删除 + // delete if created tmpl.execute("DROP TABLE IF EXISTS $safeTmp") interactive.log(INFO, here, "remove temp-trace-table=$tmpTbl, db=$plainName") } @@ -553,7 +559,7 @@ class SchemaJournalManager( interactive.log(INFO, here, "execute disable journal, plain-table=table=$table, db=$plainName") } - // 更新状态 + // update status val rst = tmpl.update(updateSql, commitId, table) if (rst != 1) { throw IllegalStateException("update journal $rst records, table=$table, db=$plainName") @@ -594,7 +600,7 @@ class SchemaJournalManager( .toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)) private fun parseTrgName(ddl: String): String { - return trgNameRegex.find(ddl)?.groupValues?.get(1) ?: Null.Str // 名字 + return trgNameRegex.find(ddl)?.groupValues?.get(1) ?: Null.Str } private fun parseTblName(ddl: String) = when (val st = sqlStatementParser.parseTypeAndTable(ddl)) { diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionManager.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionManager.kt index 647471731..b5ebad777 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionManager.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionManager.kt @@ -31,73 +31,84 @@ interface SchemaRevisionManager : InteractiveManager /** - * 获得所有真实数据源的版本状态,从低到高排序。null表示未初始化 + * Get the revision and its status of each datasource, + * sorted from lowest to highest. `null` means uninitialized. */ fun statusRevisions(): Map?> /** - * 指定数据库版本,可能级联升级或降级。 - * 如果存在起点终点不一致或有断点的时候,记录日志,跳过执行。 - * 断点,指不连续的APPLY状态,属于不正常或插入状态。 - * 升级时,起点必须为APPLY过的,到终点间都是未APPLY的。 - * 降级时,起点终点必须APPLY过,忽略中间未APPLY的断点。 - * 降级时,注意备份数据,可能会删除表。 - * @param revision 到此版本,即数据库是此版本 - * @param commitId 提交ID,参见Journal + * Publish the given revision to the database, possibly cascading upgrades or downgrades. + * If there is an inconsistency between the start and end points or a breakpoint, write to the log and skip the execution. + * + * A breakpoint, a discontinuous APPLY state, is an abnormal or inserting state. + * When upgrading, the start point must be `APPLY` and the end point must NOT be `APPLY`. + * When downgrading, the start point and the end points must be `APPLY`, ignoring the un-APPLY breakpoints in between. + * + * Be careful with backup data when downgrading, the data or table may be deleted. + * + * @param revision To this version, i.e. the database is this version + * @param commitId commit id of Journal */ fun publishRevision(revision: Long, commitId: Long) /** - * 强制执行一个断点脚本(仅该脚本,不会级联升级或降级),通常为不正常操作。 - * @param revision 到此版本,即数据库是此版本 - * @param commitId 提交ID,参见Journal - * @param isUpto 执行upto,还是undo,默认true - * @param dataSource 要执行的datasource名字,null时为全部执行 + * Force to run a breakpoint script (script only, no cascading upgrades or downgrades), + * usually to fix an abnormal operation. + * + * @param revision To this version, i.e. the database is this version + * @param commitId commit id of Journal + * @param isUpto upto or undo, default upto(true). + * @param dataSource name of datasource to execute, `null` means all */ fun forceApplyBreak(revision: Long, commitId: Long, isUpto: Boolean = true, dataSource: String? = null) /** - * 对比本地和数据库中的SQL。 - * 当未初始化时,执行 REVISION_1ST_SCHEMA 版 - * 当不存在时,则把本地保存到数据库。 - * 当存在但内容不一致,已APPLY则log error,否则更新 - * @param sqls 本地脚本 - * @param commitId 提交ID,参见Journal - * @param updateDiff 是否自动更新不一致的 sql,默认false + * Compare the SQL between in local and in database. + * If not initialized, run the `REVISION_1ST_SCHEMA` revision. + * If it does not exist, then save local to database. + * If it exists but the contents are not the same and has been `APPLY` + * then log error, otherwise update it. + * + * @param sqls sql in local + * @param commitId commit id of Journal + * @param updateDiff Whether to auto update inconsistent sql, default false. */ fun checkAndInitSql(sqls: SortedMap, commitId: Long, updateDiff: Boolean = false) /** - * 不一致时,强制把本地SQL插入或更新到管理表,一致时忽略。 - * @param revision 版本号 - * @param commitId 提交ID,参见Journal + * Force to insert/update the local SQL to the management table if inconsistent (do nothing if consistent) + * + * @param revision revision sql + * @param commitId commit id of Journal */ fun forceUpdateSql(revision: RevisionSql, commitId: Long) /** - * 不一致时,强制把本地SQL插入或更新到管理表,一致时忽略。 - * @param revision 版本号 - * @param upto 升级脚本 - * @param undo 降级脚本 - * @param commitId 提交ID,参见Journal + * Force to insert/update the local SQL to the management table if inconsistent (do nothing if consistent) + * @param revision revision + * @param upto upgrade sql text + * @param undo downgrade sql text + * @param commitId commit id of Journal */ fun forceUpdateSql(revision: Long, upto: String, undo: String, commitId: Long) /** - * 强制执行一个flywave语法的sql,不使用版本管理,无状态记录和检查 - * @param text sql文本 + * Force to execute a sql with flywave syntax, but no versioning, no stateful logging and checking. + * + * @param text sql text */ fun forceExecuteSql(text: String) /** - * 强制执行一个系列RevisionSql,不使用版本管理,无状态记录和检查 - * @param sqls 本地脚本 - * @param isUpto 执行upto,还是undo,默认true + * Force to execute the sqls with flywave syntax, but no versioning, no stateful logging and checking. + * + * @param sqls sql in local + * @param isUpto upto or undo, default true */ fun forceExecuteSql(sqls: SortedMap, isUpto: Boolean = true) } diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaShardingManager.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaShardingManager.kt index a2deb0a44..acac524fa 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaShardingManager.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SchemaShardingManager.kt @@ -20,7 +20,7 @@ import javax.sql.DataSource import kotlin.concurrent.thread /** - * clone主表实现自动分表,及初始化时的数据导入。 + * Clone the plain table structure to auto create shard table, and auto sharing data. * * @author trydofor * @since 2019-06-06 @@ -52,22 +52,23 @@ class SchemaShardingManager( } /** - * 检查并执行分表,分表为table_0, table_${number -1},共number个表。。 - * 分表脚标不连续或数量高于number时,显示警告。 - * 如果已存在分表,但都没有记录,则全部删除重建。 - * 如果都不存在,新建。 - * 否则,报错。需要人工介入 - * @param table 主表表名 - * @param number 分表数量,0表示不分表。 + * Check and shard table with table_0 to table_${NUMBER - 1}, total NUMBER tables. + * A warning is displayed if the footer is not consecutive or if the total is greater than the NUMBER. + * If shard table already exist, but none of them have records, delete them all and recreate. + * If none exist, create new ones. + * Otherwise, an error will throw. Manual intervention is required + * + * @param table plain table + * @param number count of sharding, `0` means no sharding. */ fun publishShard(table: String, number: Int) { val here = "publishShard" - interactive.log(INFO, here,"start publishShard table=$table, number=$number") + interactive.log(INFO, here, "start publishShard table=$table, number=$number") for ((plainName, plainDs) in plainDataSources) { - interactive.log(INFO, here,"ready publishShard table=$table, db=$plainName") + interactive.log(INFO, here, "ready publishShard table=$table, db=$plainName") val allTables = schemaDefinitionLoader.showTables(plainDs) - val shardAll = HashMap() // 可能存在不同的编号风格,key-val不能对调 + val shardAll = HashMap() // may different numbering styles, key-val can NOT swap val shardBgn = table.length + 1 for (tbl in allTables) { @@ -76,7 +77,7 @@ class SchemaShardingManager( } } - // 检查连续性 + // check consecutive val shardReb = HashMap() val shardNew = HashMap() for (i in 0 until number) { @@ -90,41 +91,41 @@ class SchemaShardingManager( } val tmpl = SimpleJdbcTemplate(plainDs, plainName) - // 多余的表 + // redundant table var hasError = false for ((tbl, _) in shardAll) { val cnt = tmpl.count("SELECT COUNT(1) FROM $tbl") val drop = "DROP TABLE " + sqlStatementParser.safeName(tbl) if (cnt == 0) { - interactive.log(INFO, here,"drop unused empty shard table=$table, db=$plainName") + interactive.log(INFO, here, "drop unused empty shard table=$table, db=$plainName") if (interactive.needAsk(AskType.DropTable)) { interactive.ask("continue?\ndrop unused empty shard table=$table") } tmpl.execute(drop) } else { hasError = true - interactive.log(ERROR, here,"ignore drop table with $cnt records, table=$table, db=$plainName, sql=$drop") + interactive.log(ERROR, here, "ignore drop table with $cnt records, table=$table, db=$plainName, sql=$drop") } } - // 重建的表 + // recreate table for ((idx, tbl) in shardReb) { val cnt = tmpl.count("SELECT COUNT(1) FROM $tbl") val canDrop = if (cnt == 0) { true } else { - // 检查全DDL(表结构,索引,触发器)是否一样 + // check full DDL (field detail, index, trigger) val diff = schemaDefinitionLoader.diffFullSame(plainDs, table, tbl) if (diff.isEmpty()) { true } else { hasError = true - interactive.log(ERROR, here,"ignore existed diff shard=$tbl, db=$plainName , diff=$diff") + interactive.log(ERROR, here, "ignore existed diff shard=$tbl, db=$plainName , diff=$diff") false } } if (canDrop) { val drop = "DROP TABLE " + sqlStatementParser.safeName(tbl) - interactive.log(INFO, here,"drop empty shard table then recreate it, table=$table, db=$plainName") + interactive.log(INFO, here, "drop empty shard table then recreate it, table=$table, db=$plainName") if (interactive.needAsk(AskType.DropTable)) { interactive.ask("continue?\ndrop empty shard table then recreate it, table=$table") } @@ -134,39 +135,42 @@ class SchemaShardingManager( } if (hasError) { - interactive.log(ERROR, here,"need manually handle above errors to continue, table=$table, db=$plainName") + interactive.log(ERROR, here, "need manually handle above errors to continue, table=$table, db=$plainName") if (interactive.needAsk(AskType.ManualCheck)) { interactive.ask("continue?\nskip above errors and continue next, table=$table") } continue } - // 新建的表 + // create new table val ddls = schemaDefinitionLoader.showFullDdl(plainDs, table).map { it to TemplateUtil.parse(it, table) } for ((_, tbl) in shardNew) { - interactive.log(INFO, here,"create shard table, table=$table, db=$plainName") + interactive.log(INFO, here, "create shard table, table=$table, db=$plainName") for ((ddl, idx) in ddls) { val sql = TemplateUtil.merge(ddl, idx, tbl) - interactive.log(INFO, here,"running db=$plainName, ddl=$sql") + interactive.log(INFO, here, "running db=$plainName, ddl=$sql") tmpl.execute(sql) } } } - interactive.log(INFO, here,"done publishShard table=$table, number=$number") + interactive.log(INFO, here, "done publishShard table=$table, number=$number") } /** - * 从原主表,按shard数据源,迁移数据,百万以上数据不建议使用。 - * 使用时,建议取消触发器,避免产生大量无用数据。 - * 执行过程中使用3线程:select, insert, delete。 - * 2阻塞队列:insert,delete。默认大小1024。 - * 执行没有事务。目标数据成功插入一条,则源数据删除该条。 - * 如果失败可能存在脏数据,需要人工加入,根据日志处理 - * @param table 主表表名 - * @param stopOnError 插入或删除失败时是否停止,默认不停止,只记录error。 + * Migrate small size data from the plain table to the shard datasource, + * it is not recommended to use it for more than one million data. + * When migrating data, it is recommended to disable triggers to avoid generating a lot of useless data. + * The execution process uses 3 threads: select, insert and delete. + * 2 blocking queue: insert, delete. default size 1024. + * Execute without transaction. + * If the target data is successfully inserted, then the source data is deleted. + * If it fails, there may be dirty data, you need to insert manually, according to the log processing + * + * @param table plain table + * @param stopOnError Whether to stop when insertion or deletion fails, the default is not to stop, only record error. */ fun shardingData(table: String, stopOnError: Boolean = false) { if (shardDataSource == null) { diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessor.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessor.kt index 371895094..234b7ceaa 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessor.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessor.kt @@ -26,20 +26,20 @@ class SqlSegmentProcessor( } class Segment( - val dbsType: DbsType, // 数据源类型 - val lineBgn: Int, // 开始行,含 - val lineEnd: Int, // 结束行,含 - val tblName: String, // 主表 - val tblIdx2: SortedMap>, // 要替换的启止坐标位置 - val sqlText: String, // SQL文本 - val errType: ErrType = ErrType.Stop, // 错误处理 - val askText: String, // 确认语句 - val tblRegx: Regex?, // 应用表正则 - val trgJour: Boolean, // 是否影响trigger - val dicName: Map = emptyMap() // 名字替换 + val dbsType: DbsType, // Datasource type + val lineBgn: Int, // begin line(include) + val lineEnd: Int, // end line(include) + val tblName: String, // plain table + val tblIdx2: SortedMap>, // start and stop indexes to be replaced + val sqlText: String, // SQL text + val errType: ErrType = ErrType.Stop, // error handle + val askText: String, // confirm text + val tblRegx: Regex?, // regexp of apply table + val trgJour: Boolean, // whether affects trigger + val dicName: Map = emptyMap() // name replacing ) { /** - * 筛选出可以被应用的table以及替换的关键词 + * Filter out tables that can be applied as well as replacement keywords */ fun applyTbl(tables: List?): Map> { if (tables.isNullOrEmpty() || tblIdx2.isEmpty() || tblName.isEmpty()) return emptyMap() @@ -55,9 +55,11 @@ class SqlSegmentProcessor( val tp = hasType(tblName, it) tp == TYPE_PLAIN || tp == TYPE_SHARD } + ptn.equals("log", true) -> tables.filter { hasType(tblName, it) == TYPE_TRACE } + else -> tables.filter { tblRegx.matches(it) } @@ -76,7 +78,7 @@ class SqlSegmentProcessor( } /** - * 是否是 Plain 数据源 + * Whether is plain datasource */ fun isPlain() = dbsType == DbsType.Plain } @@ -127,9 +129,10 @@ class SqlSegmentProcessor( } /** - * 解析SQL文本,变成可以执行和替换的SQL片段 - * @param statementParser 语法解析器 - * @param text SQL文本 + * Parse the SQL text to the sql segment that can be replaced and executed. + * + * @param statementParser simple statement parser + * @param text SQL text */ fun parse(statementParser: SqlStatementParser, text: String): List { if (text.isBlank()) { @@ -279,10 +282,12 @@ class SqlSegmentProcessor( trgJour = true if (dbsAnot == 0) dbsAnot = -1 } + is SqlStatementParser.SqlType.Shard -> { tblName = st.table if (dbsAnot == 0) dbsAnot = 1 } + SqlStatementParser.SqlType.Other -> log.warn("[parse] unsupported type, use shard datasource to run, sql=$sql") } @@ -315,9 +320,9 @@ class SqlSegmentProcessor( /** - * 使用新表名,合并sql片段。 - * @param segment 解析过的sql片段 - * @param newTbl 新表 + * Merge the segment with new table. + * @param segment parsed sql segment + * @param newTbl new table */ fun merge(segment: Segment, newTbl: Map) = TemplateUtil.merge(segment.sqlText, segment.tblIdx2, newTbl) @@ -329,27 +334,27 @@ class SqlSegmentProcessor( const val TYPE_SHARD = 2 /** - * 本表占位符,XXX + * Table placeholder, default `XXX` */ const val PLAIN_TABLE = "XXX" /** - * XXX_01形式的shard表达式 + * RegExp of shard table in `XXX_01` */ const val SHARD_LINE_SEQ = "${PLAIN_TABLE}_[0-9]+" /** - * XXX$log形式的trace表达式 + * RegExp of trace table in `XXX$log` */ const val TRACE_DOLLAR = "${PLAIN_TABLE}(_[0-9]+)?\\\$[a-z]*" /** - * XXX__log形式的trace表达式 + * RegExp of trace table in XXX__log */ const val TRACE_SU2_LINE = "${PLAIN_TABLE}(_[0-9]+)?__+[a-z]*" /** - * _log_XXX形式的trace表达式 + * RegExp of trace table in _log_XXX */ const val TRACE_PRE_LINE = "_+([a-z]+_+)?${PLAIN_TABLE}(_[0-9]+)?" @@ -357,8 +362,9 @@ class SqlSegmentProcessor( private var regTrace = TRACE_SU2_LINE.toRegex(RegexOption.IGNORE_CASE) /** - * 设置分表格式表达式,以`XXX`表示主表。 - * @param reg 正则,默认 `XXX_[0-9]+` + * Set the RegExp of shard table, `XXX` is placeholder of plain table + * @param reg Regexp of shard table + * @see PLAIN_TABLE */ @JvmStatic fun setShardFormat(reg: String) { @@ -367,8 +373,9 @@ class SqlSegmentProcessor( } /** - * 设置跟踪格式表达式,以`XXX`表示主表。 - * @param reg 正则,默认`XXX(_[0-9]+)?__+[a-z]*` + * Set the RegExp of trace table, `XXX` is placeholder of plain table + * @param reg Regexp of shard table + * @see PLAIN_TABLE */ @JvmStatic fun setTraceFormat(reg: String) { @@ -377,14 +384,16 @@ class SqlSegmentProcessor( } /** - * 判断两表关系,忽略大小写 - * @param table 主表 - * @param other 其他表 - * @return - * -1:没有关系 - * 0:自己 - * 1:trace表 - * 2:shard表 + * Determine the relationship type of `other` to `table` (case-insensitive) + * + * -1: no relation, + * 0: table itself, + * 1: other is trace, + * 2: other is shard, + * + * @param table plain table + * @param other other table + * @return the type */ fun hasType(table: String, other: String): Int { val pos = other.indexOf(table, 0, true) @@ -396,9 +405,11 @@ class SqlSegmentProcessor( regShard.matches(suf) -> { TYPE_SHARD } + regTrace.matches(suf) -> { TYPE_TRACE } + else -> { TYPE_OTHER } diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlStatementParser.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlStatementParser.kt index 7f94a2492..084939b9b 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlStatementParser.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/SqlStatementParser.kt @@ -1,7 +1,8 @@ package pro.fessional.wings.faceless.flywave /** - * 解析出主表和数据源类型,替换边界 + * Parsing out the plain table, datasource types and replacing boundaries + * * @author trydofor * @since 2019-06-11 */ @@ -14,25 +15,26 @@ interface SqlStatementParser { } /** - * 解析出数据源类型和主表名称 + * Parser the type of datasource and plain table name. */ fun parseTypeAndTable(sql: String): SqlType /** - * 对非标准名字(非ASCII命名),进行转义 - * @param str 名字 + * For non-standard names (e.g. non-ASCII, keyword), escape them to safe word. + * + * @param str name */ fun safeName(str: String): String /** - * 对sql类型,变成sql字面量 - * @param obj 值 + * Convert the value to the sql literal form + * @param obj value */ fun safeValue(obj: Any?): String /** - * 去掉转义字符,还原本名 - * @param str 名字 + * trim the name to plain style, e.g. remove the escape character, white char. + * @param str name */ fun trimName(str: String): String } diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/DefaultRevisionManager.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/DefaultRevisionManager.kt index 19ba6447d..fa933e48c 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/DefaultRevisionManager.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/DefaultRevisionManager.kt @@ -26,7 +26,7 @@ import javax.sql.DataSource /** - * 根据数据库中表(含分表)的名字,进行版本管理。 + * Manage database version based on the names of the tables (including shard tables). * * @author trydofor * @since 2019-06-05 @@ -62,8 +62,8 @@ class DefaultRevisionManager( } /** - * 增加一个识别drop语句的表达式 - * @param regexp 正则表达式 + * Add a RegExp to match drop statement. + * @param regexp RegExp */ fun addDropRegexp(regexp: String) { dropReg[regexp] = regexp.toRegex(RegexOption.IGNORE_CASE) @@ -123,10 +123,10 @@ class DefaultRevisionManager( } val isUptoSql = revision > plainRevi - val reviQuery = if (isUptoSql) { // 升级 + val reviQuery = if (isUptoSql) { interactive.log(INFO, here, "upgrade, db-revi=$plainRevi, to-revi=$revision, db=$plainName") selectUpto - } else { // 降级 + } else { interactive.log(INFO, here, "downgrade, db-revi=$plainRevi, to-revi=$revision") selectUndo } @@ -152,33 +152,33 @@ class DefaultRevisionManager( continue } - // 检测和处理边界 - if (isUptoSql) { // 版本从低到高,重点不一致,或不存在 + // check and handle boundary + if (isUptoSql) { // revision is from low to high, inconsistent and non-exist are important if (reviText.last.first != revision) { interactive.log(WARN, here, "skip the diff upgrade end point, db-revi=$plainRevi, to-revi=$revision, db=$plainName") continue } - // 检测apply情况,应该全都未APPLY + // check apply, should all NOT APPLY if (reviText.count { isUnapply(it.third) } != reviText.size) { interactive.log(WARN, here, "skip broken un-apply_dt upgrade, db-revi=$plainRevi, to-revi=$revision, db=$plainName") continue } - } else { // 版本从高到低 + } else { // revision is from high to low if (reviText.last.first != revision) { interactive.log(WARN, here, "skip the diff downgrade end point, db-revi=$plainRevi, to-revi=$revision, db=$plainName") continue } - // 检测apply情况 + // check apply if (reviText.count { isUnapply(it.third) } != 0) { interactive.log(WARN, here, "skip broken apply_dt-ed downgrade, db-revi=$plainRevi, to-revi=$revision, db=$plainName") continue } - // 去掉终点脚本,不需要执行 + // Remove the endpoint script, which does not need to be executed reviText.removeLast() } - // 检查部分执行 + // check partially executed val partUndo = LinkedList>() val partRedo = LinkedList>() plainTmpl.query( @@ -219,7 +219,7 @@ class DefaultRevisionManager( interactive.log(INFO, here, "done for revi=$revi, db=$plainName") } - // 后置检查 + // post check interactive.log(INFO, here, "post check revi=$revision, db=$plainName") val newRevi = getRevision(plainTmpl) if (revision != newRevi) { @@ -485,8 +485,7 @@ class DefaultRevisionManager( interactive.log(INFO, here, "ready force update revi=$revision, on db=$plainName") val tmpl = SimpleJdbcTemplate(plainDs, plainName) - // 不要使用msyql的REPLACE INTO,使用标准SQL - + // Don't use `REPLACE INTO` of msyql, use standard SQL val cnt = tmpl.count("SELECT COUNT(1) FROM $schemaVersionTable WHERE revision= ?", revision) if (cnt == 0) { val rst = tmpl.update(insertSql, revision, commitId, upto, undo) @@ -513,7 +512,7 @@ class DefaultRevisionManager( if (seg.sqlText.isBlank()) { continue } - // 不使用事务,出错时,根据日志进行回滚或数据清理 + // No transaction, should rollback data based on logs when something wrong if (seg.isPlain() || shardTmpl == null) { interactive.log(INFO, here, "use plain to run sql-line from ${seg.lineBgn} to ${seg.lineEnd}, db=$plainName") runSegment(plainTmpl, plainTbls, seg) @@ -551,7 +550,7 @@ class DefaultRevisionManager( askSegment(revi, "apply undo sqls") } - // 记录部分执行情况。 + // Record partial execute if (check) { interactive.log(INFO, here, "parse revi-sql, revi=$revi, isUpto=$isUpto, mark as '$runningMark'") plainTmpl.update("UPDATE $schemaVersionTable SET apply_dt='$runningMark', commit_id=? WHERE revision=?", commitId, revi) @@ -563,7 +562,7 @@ class DefaultRevisionManager( if (seg.sqlText.isBlank()) { continue } - // 不使用事务,出错时,根据日志进行回滚或数据清理 + // No transaction, should rollback data based on logs when something wrong if (seg.isPlain() || shardTmpl == null) { interactive.log(INFO, here, "use plain to run revi=$revi, sql-line from ${seg.lineBgn} to ${seg.lineEnd}, db=$plainName") runSegment(plainTmpl, plainTbls, seg, revi) @@ -573,7 +572,7 @@ class DefaultRevisionManager( } } - // update apply datetime,避免时区问题,使用SQL语法 + // update apply datetime, use sql literal to avoid timezone offset val applyDt = if (isUpto) { "NOW(3)" } else { @@ -591,7 +590,7 @@ class DefaultRevisionManager( interactive.log(WARN, here, "skip un-init-1st, revi={}, applyDt=$revi, db=$plainName") return } - // 执行了,必须一条,因为上面不会出现语法错误 + // Executed, must be one, as there will be no syntax error above if (cnt == 1) { interactive.log(INFO, here, "update revi=$revi, applyDt=$applyDt, db=$plainName") } else { @@ -640,6 +639,7 @@ class DefaultRevisionManager( last = r Status.Running } + isUnapply(d) -> Status.Future else -> { last = r @@ -669,7 +669,7 @@ class DefaultRevisionManager( val dbName = tmpl.name val tblApply = seg.applyTbl(tables) - val ask = if (seg.askText.isNotEmpty() && interactive.needAsk(AskType.Mark)) "强制确认:${seg.sqlText}" else Null.Str + val ask = if (seg.askText.isNotEmpty() && interactive.needAsk(AskType.Mark)) "Must confirm:${seg.sqlText}" else Null.Str askSegment(revi, ask, dangerous(seg.sqlText)) @@ -684,6 +684,7 @@ class DefaultRevisionManager( ErrType.Skip -> { // skip } + else -> { throw e } @@ -731,6 +732,7 @@ class DefaultRevisionManager( ErrType.Skip -> { // skip } + else -> { throw e } @@ -747,7 +749,7 @@ class DefaultRevisionManager( private fun isRunning(str: String?): Boolean { if (str.isNullOrEmpty()) return false - // 可能受到时区影响 + // may affect by timezone return str.startsWith(unapplyMark) && str.endsWith(runningFlag) } @@ -762,10 +764,10 @@ class DefaultRevisionManager( val tables = schemaDefinitionLoader.showTables(ds) if (tables.find { it.equals(schemaVersionTable, true) } != null) { log.error("exist $schemaVersionTable without any records, need manual fixed: drop empty table or insert records") - throw er // 存在 $schemaVersionTable 表,报出原异常 + throw er // throw, there is $schemaVersionTable table } } catch (e: Exception) { - // 报出原异常 + // throw the original exception throw er } } @@ -798,7 +800,7 @@ class DefaultRevisionManager( ask.append("\n") } - val pr = interactive.lastMessage.get() // 获取上一个信息,必须 interactive.log 之前 + val pr = interactive.lastMessage.get() // Get previous message, must be before interactive.log interactive.log(WARN, "askSegment", ask.toString()) if (pr != null) { diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MySqlStatementParser.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MySqlStatementParser.kt index a55d8de01..512bed891 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MySqlStatementParser.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MySqlStatementParser.kt @@ -28,7 +28,7 @@ class MySqlStatementParser : SqlStatementParser { private val ddlCreateTrigger = "^CREATE\\s+(?:DEFINER\\s*=\\s*\\S+\\s+)?TRIGGER\\s+(?:\\S+\\s+)*ON\\s+([^(\\s]+)".toPattern(options) private val ddlDropIndex = "^DROP\\s+INDEX\\s+\\S+\\s+ON\\s+([^(\\s]+)".toPattern(options) private val ddlDropTable = "^DROP\\s+(?:TEMPORARY\\s+)?TABLE\\s+(?:IF\\s+EXISTS\\s+)?([^(\\s]+)".toPattern(options) - private val ddlDropTrigger = "^DROP\\s+TRIGGER\\s+(?:IF\\s+EXISTS\\s+)?\\S+\\s".toPattern(options) // 没有table + private val ddlDropTrigger = "^DROP\\s+TRIGGER\\s+(?:IF\\s+EXISTS\\s+)?\\S+\\s".toPattern(options) // without table private val ddlTruncateTable = "^TRUNCATE\\s+(?:TABLE\\s+)?([^(\\s]+)".toPattern(options) private val dmlDelete = "^DELETE\\s+(?:\\S+\\s)*FROM\\s+([^(\\s]+)".toPattern(options) @@ -119,7 +119,7 @@ class MySqlStatementParser : SqlStatementParser { } } - // 备用方案,一般不会到达。 + // plain B, never here. log.warn("unmatched sql type, return Other. sql=[$sql]") return SqlStatementParser.SqlType.Other } diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MysqlDefinitionLoader.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MysqlDefinitionLoader.kt index 5c92e63d8..e4aad3c3a 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MysqlDefinitionLoader.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/impl/MysqlDefinitionLoader.kt @@ -107,7 +107,7 @@ class MysqlDefinitionLoader : SchemaDefinitionLoader { return diff.toString() } - // 对比列 + // diff column if (types and TYPE_TBL != 0) { val cols = if (bone) { "COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT, ORDINAL_POSITION" @@ -144,7 +144,7 @@ class MysqlDefinitionLoader : SchemaDefinitionLoader { } } - // 对比索引 + // diff index if (types and TYPE_IDX != 0) { tmpl.query( """ @@ -171,7 +171,7 @@ class MysqlDefinitionLoader : SchemaDefinitionLoader { } } - // 对比触发器 + // diff trigger if (types and TYPE_TRG != 0) { tmpl.query( """ @@ -206,7 +206,7 @@ class MysqlDefinitionLoader : SchemaDefinitionLoader { SimpleJdbcTemplate(dataSource).query("SHOW COLUMNS FROM $table") { val n = it.getString("FIELD") val t = it.getString("TYPE") - rst.add("`$n` $t COMMENT ''") // `EVENT_NAME` varchar(100) NOT NULL COMMENT '事件名称' + rst.add("`$n` $t COMMENT ''") // `EVENT_NAME` varchar(100) NOT NULL COMMENT 'event name' } return rst } @@ -229,7 +229,7 @@ class MysqlDefinitionLoader : SchemaDefinitionLoader { val n = it.getString("COLUMN_NAME") val t = it.getString("COLUMN_TYPE") val c = it.getString("COLUMN_COMMENT").replace("'", "\\'") - rst.add("`$n` $t COMMENT '$c'") // `EVENT_NAME` varchar(100) NOT NULL COMMENT '事件名称' + rst.add("`$n` $t COMMENT '$c'") // `EVENT_NAME` varchar(100) NOT NULL COMMENT 'event name' } return rst diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/SimpleJdbcTemplate.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/SimpleJdbcTemplate.kt index 43e28f8f2..8e947807f 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/SimpleJdbcTemplate.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/SimpleJdbcTemplate.kt @@ -6,7 +6,8 @@ import java.util.concurrent.ConcurrentHashMap import javax.sql.DataSource /** - * flywave 有可能分离出去,尽量与spring解藕 + * flywave may stand alone, uncouple with spring + * * @author trydofor * @since 2019-06-16 */ @@ -23,7 +24,7 @@ class SimpleJdbcTemplate(val dataSource: DataSource, val name: String = "unnamed } /** - * 处理每一条数据 + * handle each record of result * @param handler org.springframework.jdbc.core.RowCallbackHandler */ fun query(sql: String, vararg args: Any?, handler: (ResultSet) -> Unit) { diff --git a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtil.kt b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtil.kt index 66f8f3f61..18b17d3e7 100644 --- a/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtil.kt +++ b/wings/faceless-flywave/src/main/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtil.kt @@ -4,9 +4,9 @@ import java.util.SortedMap import java.util.TreeMap /** - * SQL模板解析和合并。 解析时, - * (1)忽略引号内文字,避免错误 - * (2)判断字符边界,避免截断 + * SQL template parsing and merging. When parsing. + * (1) Ignore text in quotes to avoid errors + * (2) Judge character boundaries to avoid truncation * * @author trydofor * @since 2019-06-20 @@ -15,12 +15,13 @@ object TemplateUtil { /** - * 直接完成 parse和merge - * @param txt 文本 - * @param sub 查找文本 - * @param rpl 替换文本 - * @param qto 引号字符,默认`'` - * @param bnd 是否检查边界,默认检查 + * Parse and merge are done directly + * + * @param txt text + * @param sub string to find + * @param rpl string to replace + * @param qto quotation mark character, default `'` + * @param bnd whether to check boundaries, default check */ fun replace(txt: String, sub: String, rpl: String, qto: String = "'", bnd: Boolean = true): String { val idx = parse(txt, sub, qto, bnd) @@ -29,11 +30,12 @@ object TemplateUtil { /** - * 直接完成 parse和merge - * @param txt 文本 - * @param rep 查找文本, 替换文本 - * @param qto 引号字符,默认`'` - * @param bnd 是否检查边界,默认检查 + * Parse and merge are done directly + * + * @param txt text + * @param rep map of find and replace + * @param qto quotation mark character, default `'` + * @param bnd whether to check boundaries, default check */ fun replace(txt: String, rep: Map, qto: String = "'", bnd: Boolean = true): String { val key = rep.keys.toList() @@ -42,11 +44,12 @@ object TemplateUtil { } /** - * 使用新表名,合并sql片段。 + * Merge the sql segment with new table + * * @see parse - * @param txt 目标文本 - * @param idx 解析过的坐标.`` - * @param tbl 新表 + * @param txt text + * @param idx parsed indexes `` + * @param tbl new table */ fun merge(txt: String, idx: SortedMap, tbl: String): String { if (idx.isEmpty()) return txt @@ -68,11 +71,12 @@ object TemplateUtil { } /** - * 处理多个token的模板 + * Merge template with multiple token + * * @see parse - * @param txt 目标文本 - * @param idx 解析过的坐标。`>` - * @param map 替换值。`` token区分大小写 + * @param txt text + * @param idx parsed indexes `>` + * @param map map of find and replace `` token is case-sensitive */ fun merge(txt: String, idx: SortedMap>, map: Map): String { if (idx.isEmpty()) return txt @@ -95,12 +99,14 @@ object TemplateUtil { } /** - * 忽略大小写解析多个token在文本中的开始和结束坐标。 - * 会处理多个token之间的包含关系,即两个坐标有交叉,舍去后面的,直到没有交叉。 - * @param txt 目标文本 - * @param tkn 单个token - * @param qto 引号字符,默认`'` - * @param bnd 是否检查边界,默认检查 + * Parse the start and end coordinates of multiple tokens in the text (case-insensitive). + * Containment relationships between multiple tokens are handled, i.e. + * two coordinates are crossed and the later one is rounded off until there is no crossing. + * + * @param txt text + * @param tkn single token + * @param qto quotation mark character, default `'` + * @param bnd whether to check boundaries, default check * @see maskQuote * @see isBoundary */ @@ -112,7 +118,7 @@ object TemplateUtil { val msk = maskQuote(txt, qto) val ix = TreeMap() for (tk in tkn) { - if(tk.isEmpty()) continue + if (tk.isEmpty()) continue parse(msk, tk, ix, bnd) for ((p1, p2) in ix) { idx[p1] = Pair(p2, tk) @@ -120,7 +126,7 @@ object TemplateUtil { ix.clear() } - // 处理交叉 + // handle crossing var e2 = -1 val iter = idx.entries.iterator() while (iter.hasNext()) { @@ -136,11 +142,12 @@ object TemplateUtil { } /** - * 忽略大小写解析单个token在文本中的开始和结束坐标。 - * @param txt 目标文本 - * @param tkn 单个token - * @param qto 引号字符,默认`'` - * @param bnd 是否检查边界,默认检查 + * Parse the start and end coordinates of single token in the text (case-insensitive). + * + * @param txt text + * @param tkn single token + * @param qto quotation mark character, default `'` + * @param bnd whether to check boundaries, default check * @see maskQuote * @see isBoundary */ @@ -181,9 +188,10 @@ object TemplateUtil { } /** - * 把引号内字符全部用等数量的空格替换掉,按char数量,不是byte - * @param txt 目标文本 - * @param qto 引号字符,默认`'` + * Replace all the chars inside the quotes with an equal number of spaces, by char, not byte. + * + * @param txt text + * @param qto quotation mark character, default `'` */ fun maskQuote(txt: String, qto: String = "'"): String { if (txt.isBlank() || qto.isBlank()) { @@ -218,10 +226,10 @@ object TemplateUtil { } /** - * 找到跟当前quote字符对应的结束位置。 - * 处理`\`转义情况。 - * @param txt 目标文本 - * @param idx 起始quote的位置 + * Find the end index corresponding to the current quote character. Handles the `\` escape case. + * + * @param txt text + * @param idx index of the start quote */ fun findQuoteEnd(txt: String, idx: Int): Int { if (idx < 0 || idx >= txt.length - 1) return -1 @@ -249,10 +257,12 @@ object TemplateUtil { } /** - * 是不是字符边界。连续的`[A-Z0-9_]`和非ASCII字符认为是连续的。 - * @param txt 目标文本 - * @param idx 起始位置 - * @param dollar `$`是否认为边界 + * Whether is a character boundary. + * Consecutive `[A-Z0-9_]` and non-ASCII characters are considered consecutive. + * + * @param txt text + * @param idx index of starting + * @param dollar whether `$` is the boundary */ fun isBoundary(txt: String, idx: Int, dollar: Boolean = true): Boolean { if (idx <= 0 || idx >= txt.length - 1) return true @@ -264,18 +274,19 @@ object TemplateUtil { c in '0'..'9' -> false c == '_' -> false c == '$' -> dollar - // 非ascii命名 + // non-ascii naming c.code > Byte.MAX_VALUE && idx > 0 && txt[idx - 1].code > Byte.MAX_VALUE -> false else -> true } } /** - * 忽略大小写检测sub字串是不是完整存在于文本txt。 + * Whether the sub-string is complete in the text (case-insensitive). + * * @see isBoundary - * @param txt 文本 - * @param sub 子串 - * @param dollar `$`是否认为边界 + * @param txt text + * @param sub substring + * @param dollar whether `$` is the boundary */ fun isBoundary(txt: String, sub: String, dollar: Boolean = true): Boolean { if (txt.isEmpty() || sub.isEmpty()) return false diff --git a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsInitDatabaseSample.java b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsInitDatabaseSample.java index dbc93e85b..74e4ec982 100644 --- a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsInitDatabaseSample.java +++ b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsInitDatabaseSample.java @@ -19,7 +19,7 @@ */ @SpringBootTest -@Disabled("初始化数据库,已有devs统一管理") +@Disabled("Init database, have handled by devs") @SuppressWarnings("NewClassNamingConvention") public class WingsInitDatabaseSample { @@ -28,12 +28,12 @@ public class WingsInitDatabaseSample { @Test public void init0601() { - // 初始 + // init val sqls = scan(REVISION_PATH_MASTER, WingsRevision.V01_19_0521_01_EnumI18n.classpath()); schemaRevisionManager.publishRevision(WingsRevision.V00_19_0512_01_Schema.revision(), 0); schemaRevisionManager.checkAndInitSql(sqls, 0, false); - // 升级 + // upgrade schemaRevisionManager.publishRevision(REVISION_TEST_V1, 0); } } diff --git a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaDumper.java b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaDumper.java index 63bb8c7a7..da453823c 100644 --- a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaDumper.java +++ b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaDumper.java @@ -20,7 +20,7 @@ @Slf4j @SpringBootTest -@Disabled("导出数据库表结构,备份数据时使用") +@Disabled("Dump schema structure, used to backup and restore") @SuppressWarnings("NewClassNamingConvention") public class WingsSchemaDumper { @@ -33,15 +33,15 @@ public class WingsSchemaDumper { @Test public void dump() { Function1, List> ddl = SchemaFulldumpManager.groupedTable(false, - "-- ==================== Basement-4(B4/10#):基础 =======================", - "sys_schema_version", // 101/表结构版本 - "sys_schema_journal", // 102/数据触发器 - "sys_light_sequence", // 103/序号生成器 - "sys_commit_journal", // 104/数据变更集 - "-- ==================== Basement-3(B3/15#):多语言,多时区,多货币 =======================", - "sys_constant_enum", // 105/常量枚举:自动生成enum类 - "sys_standard_i18n", // 106/标准多国语 - "-- ==================== Floor-10(F11/90#):辅助 =======================" + "-- ==================== Basement-4(B4/10#):basic =======================", + "sys_schema_version", // 101/table structure + "sys_schema_journal", // 102/data trigger + "sys_light_sequence", // 103/id and sequence + "sys_commit_journal", // 104/commit log + "-- ==================== Basement-3(B3/15#):multiple lang/time/money =======================", + "sys_constant_enum", // 105/enum const: auto code gen + "sys_standard_i18n", // 106/i18n message + "-- ==================== Floor-10(F11/90#):helper =======================" ); Function1, List> rec = SchemaFulldumpManager.includeRegexp( "sys_light_.*", diff --git a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaGenerator.java b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaGenerator.java index 4c11f92b1..f258e538d 100644 --- a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaGenerator.java +++ b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaGenerator.java @@ -13,7 +13,7 @@ import java.util.SortedMap; /** - * ① 使用wings的flywave管理数据库版本 + * Use flywave to manage database version * * @author trydofor * @since 2019-06-22 @@ -26,7 +26,7 @@ // "spring.shardingsphere.datasource.writer.username=trydofor", // "spring.shardingsphere.datasource.writer.password=moilioncircle", }) -@Disabled("初始化数据库,已有devs统一管理") +@Disabled("Init database, have handled by devs") @SuppressWarnings("NewClassNamingConvention") public class WingsSchemaGenerator { diff --git a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaJournal.java b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaJournal.java index 1751f6d71..73e8890a4 100644 --- a/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaJournal.java +++ b/wings/faceless-flywave/src/test/java/pro/fessional/wings/faceless/sample/WingsSchemaJournal.java @@ -17,7 +17,7 @@ */ @SpringBootTest -@Disabled("日志表个管理,数据维护使用") +@Disabled("Manage trace table, use to maintain data") @Slf4j @SuppressWarnings("NewClassNamingConvention") public class WingsSchemaJournal { diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManagerTest.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManagerTest.kt index e18f4b458..e4326d742 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManagerTest.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaFulldumpManagerTest.kt @@ -21,7 +21,7 @@ import javax.sql.DataSource @SpringBootTest @TestMethodOrder(MethodName::class) -@Disabled("导出表结构和数据,避免污染Git提交文件") +@Disabled("Export table structure and data to avoid polluting Git workspace") class SchemaFulldumpManagerTest { @Autowired @@ -58,7 +58,7 @@ class SchemaFulldumpManagerTest { ) val file = "$fold/schema.sql" schemaFulldumpManager.saveFile(file, dlls) - testcaseNotice("检查文件 $file") + testcaseNotice("Check File $file") } @Test @@ -72,6 +72,6 @@ class SchemaFulldumpManagerTest { ) val file = "$fold/record.sql" schemaFulldumpManager.saveFile(file, recs) - testcaseNotice("检查文件 $file") + testcaseNotice("Check File $file") } } diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManagerTest.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManagerTest.kt index aa73535cc..f7a7331be 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManagerTest.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaJournalManagerTest.kt @@ -19,7 +19,8 @@ import pro.fessional.wings.faceless.flywave.SchemaJournalManagerTest.Companion.T import pro.fessional.wings.faceless.util.FlywaveRevisionScanner /** - * 包括了分表,跟踪表的综合测试 + * Shard and trace table test + * * @author trydofor * @since 2019-06-20 */ @@ -137,7 +138,7 @@ class SchemaJournalManagerTest { val sqls = FlywaveRevisionScanner .helper() .master() - .modify("更名win_schema_version") { _, sql -> + .modify("rename win_schema_version") { _, sql -> if (sql.revision == WingsRevision.V00_19_0512_01_Schema.revision()) { sql.undoText = sql.undoText.replace("sys_schema_", schemaPrefix) sql.uptoText = sql.uptoText.replace("sys_schema_", schemaPrefix) @@ -145,7 +146,7 @@ class SchemaJournalManagerTest { } .scan() schemaRevisionManager.checkAndInitSql(sqls, 0, true) - breakpointDebug("清楚所有表,发布 REVISION_1ST_SCHEMA 版,新建 flywave 版本表") + breakpointDebug("drop all tables, publish REVISION_1ST_SCHEMA, create flywave tables") } @Test @@ -158,7 +159,7 @@ class SchemaJournalManagerTest { "${schemaPrefix}journal", "${schemaPrefix}version" ) - breakpointDebug("生成测试表💰,观察数据库所有表") + breakpointDebug("Create test table💰, check all tables in the database") schemaRevisionManager.publishRevision(REVISION_TEST_V1, 0) wingsTestHelper.assertSame( WingsTestHelper.Type.Table, @@ -170,7 +171,7 @@ class SchemaJournalManagerTest { "tst_sharding_postfix", "tst_normal_table", ) - testcaseNotice("可检查日志或debug观察,wing0和wing1表名") + testcaseNotice("Check the logs or debug to see the wing0 and wing1 table names") } @Test @@ -184,7 +185,7 @@ class SchemaJournalManagerTest { "tst_sharding_3", "tst_sharding_4" ) - breakpointDebug("分表测试表💰,观察数据库所有表") + breakpointDebug("Sharding test💰, check all tables in the database") shcemaShardingManager.publishShard("tst_sharding", 5) wingsTestHelper.assertHas( WingsTestHelper.Type.Table, "tst_sharding_0", @@ -193,7 +194,7 @@ class SchemaJournalManagerTest { "tst_sharding_3", "tst_sharding_4" ) - testcaseNotice("可检查日志或debug观察,wing_test,多出分表0-5") + testcaseNotice("Check logs or debug, wing_test, extra shard-table 0-5 will be created") } @Test @@ -203,7 +204,7 @@ class SchemaJournalManagerTest { return } - breakpointDebug("分表触发器💰,观察数据库所有表") + breakpointDebug("Trigger on shard💰, check all tables in the database") schemaJournalManager.publishInsert("tst_sharding", true, 0) wingsTestHelper.assertHas(WingsTestHelper.Type.Table, traceTable("tst_sharding")) wingsTestHelper.assertHas(WingsTestHelper.Type.Trigger, "ai__tst_sharding") @@ -212,17 +213,17 @@ class SchemaJournalManagerTest { """ INSERT INTO `tst_sharding_1` (`id`, `create_dt`, `modify_dt`, `delete_dt`, `commit_id`, `login_info`, `other_info`) - VALUES (1,NOW(3),NOW(3),'1000-01-01',0,'赵四','老张'); + VALUES (1,NOW(3),NOW(3),'1000-01-01',0,'Zhao4','OldZhang'); """ ) val del = jdbcTemplate.update("DELETE FROM `${traceTable("tst_sharding_1")}` WHERE id = 1") - assertEquals(1, del, "如果失败,单独运行整个类,消除分表干扰") - breakpointDebug("清楚数据🐵,因为trace表不会删除有数据表") + assertEquals(1, del, "If it fails, run the entire class individually to avoid shard table interference") + breakpointDebug("Clear data🐵, because trace table will NOT delete if its has data") schemaJournalManager.publishInsert("tst_sharding", false, 0) wingsTestHelper.assertNot(WingsTestHelper.Type.Table, traceTable("tst_sharding")) wingsTestHelper.assertNot(WingsTestHelper.Type.Trigger, "ai__tst_sharding") - testcaseNotice("检查日志和数据库变化,最好debug进行,wing0和wing1,同步更新表结构") + testcaseNotice("Check logs and database changes, best done by debug, wing0 and wing1, synchronized update table structure") } @Test @@ -232,23 +233,23 @@ class SchemaJournalManagerTest { return } - breakpointDebug("分表触发器💰,观察数据库所有表") + breakpointDebug("Trigger on shard💰, check all tables in the database") schemaJournalManager.publishUpdate("tst_sharding", true, 0) wingsTestHelper.assertHas(WingsTestHelper.Type.Table, traceTable("tst_sharding")) wingsTestHelper.assertHas(WingsTestHelper.Type.Trigger, "au__tst_sharding") - jdbcTemplate.execute("UPDATE `tst_sharding_1` SET login_info='赵思', commit_id=1 WHERE id = 1") - breakpointDebug("更新数据🐵,查询数据库各表及数据") + jdbcTemplate.execute("UPDATE `tst_sharding_1` SET login_info='ZhaoSi', commit_id=1 WHERE id = 1") + breakpointDebug("Update data🐵, select data and check table") val del = jdbcTemplate.update("DELETE FROM `${traceTable("tst_sharding_1")}` WHERE id = 1") - assertEquals(1, del, "如果失败,单独运行整个类,消除分表干扰") - breakpointDebug("清楚数据🐵,因为trace表不会删除有数据表") + assertEquals(1, del, "If it fails, run the entire class individually to avoid shard table interference") + breakpointDebug("Clear data🐵, because trace table will NOT delete if its has data") schemaJournalManager.publishUpdate("tst_sharding", false, 0) wingsTestHelper.assertNot(WingsTestHelper.Type.Table, traceTable("tst_sharding")) wingsTestHelper.assertNot(WingsTestHelper.Type.Trigger, "au__tst_sharding") - testcaseNotice("检查日志和数据库变化,最好debug进行,wing0和wing1,同步更新表结构") + testcaseNotice("Check logs and database changes, best done by debug, wing0 and wing1, synchronized update table structure") } @Test @@ -257,23 +258,23 @@ class SchemaJournalManagerTest { testcaseNotice("h2 database skip") return } - breakpointDebug("分表触发器💰,观察数据库所有表") + breakpointDebug("Trigger on shard💰, check all tables in the database") schemaJournalManager.publishDelete("tst_sharding", true, 0) wingsTestHelper.assertHas(WingsTestHelper.Type.Table, traceTable("tst_sharding")) wingsTestHelper.assertHas(WingsTestHelper.Type.Trigger, "bd__tst_sharding") jdbcTemplate.execute("DELETE FROM `tst_sharding_1` WHERE id = 1") - breakpointDebug("删除数据🐵,查询数据库各表及数据") + breakpointDebug("Delete data🐵, select data and check table") val del = jdbcTemplate.update("DELETE FROM `${traceTable("tst_sharding_1")}` WHERE id = 1") assertEquals(1, del) - breakpointDebug("清楚数据🐵,因为trace表不会删除有数据表") + breakpointDebug("Clear data🐵, because trace table will NOT delete if its has data") schemaJournalManager.publishDelete("tst_sharding", false, 0) wingsTestHelper.assertNot(WingsTestHelper.Type.Table, traceTable("tst_sharding")) wingsTestHelper.assertNot(WingsTestHelper.Type.Trigger, "bd__tst_sharding") - testcaseNotice("检查日志和数据库变化,最好debug进行,wing0和wing1,同步更新表结构") + testcaseNotice("Check logs and database changes, best done by debug, wing0 and wing1, synchronized update table structure") } @Test @@ -283,7 +284,7 @@ class SchemaJournalManagerTest { return } - breakpointDebug("分表触发器💰,观察数据库所有表") + breakpointDebug("Trigger on shard💰, check all tables in the database") schemaJournalManager.publishInsert("tst_sharding", true, 0) schemaJournalManager.publishUpdate("tst_sharding", true, 0) schemaJournalManager.publishDelete("tst_sharding", true, 0) @@ -296,17 +297,17 @@ class SchemaJournalManagerTest { """ INSERT INTO `tst_sharding_2` (`id`, `create_dt`, `modify_dt`, `delete_dt`, `commit_id`, `login_info`, `other_info`) - VALUES (1,NOW(3),NOW(3),'1000-01-01',0,'赵四','老张'); + VALUES (1,NOW(3),NOW(3),'1000-01-01',0,'Zhao4','OldZhang'); """ ) - jdbcTemplate.execute("UPDATE `tst_sharding_2` SET login_info='赵思', commit_id=1 WHERE id = 1") + jdbcTemplate.execute("UPDATE `tst_sharding_2` SET login_info='ZhaoSi', commit_id=1 WHERE id = 1") jdbcTemplate.execute("DELETE FROM `tst_sharding_2` WHERE id = 1") - breakpointDebug("删除数据🐵,查询数据库各表及数据") + breakpointDebug("Delete data🐵, select data and check table") val tps = jdbcTemplate.queryForList("SELECT _tp FROM `${traceTable("tst_sharding_2")}` WHERE id = 1 ORDER BY _id", String::class.java) assertEquals(listOf("C", "U", "D"), tps) - breakpointDebug("清楚数据🐵,因为trace表不会删除有数据表") + breakpointDebug("Clear data🐵, because trace table will NOT delete if its has data") schemaJournalManager.publishInsert("tst_sharding", false, 0) schemaJournalManager.publishUpdate("tst_sharding", false, 0) @@ -315,7 +316,7 @@ class SchemaJournalManagerTest { wingsTestHelper.assertNot(WingsTestHelper.Type.Trigger, "au__tst_sharding") wingsTestHelper.assertNot(WingsTestHelper.Type.Trigger, "bd__tst_sharding") - testcaseNotice("检查日志和数据库变化,最好debug进行,wing0和wing1,同步更新表结构") + testcaseNotice("Check logs and database changes, best done by debug, wing0 and wing1, synchronized update table structure") } @Test @@ -325,7 +326,7 @@ class SchemaJournalManagerTest { return } - breakpointDebug("分表触发器💰,观察数据库所有表") + breakpointDebug("Trigger on shard💰, check all tables in the database") schemaJournalManager.publishInsert("tst_sharding", true, 0) schemaJournalManager.publishUpdate("tst_sharding", true, 0) schemaJournalManager.publishDelete("tst_sharding", true, 0) @@ -371,7 +372,7 @@ class SchemaJournalManagerTest { assertSameColumn(traceTable("tst_sharding"), traceTable("tst_sharding_3")) assertSameColumn(traceTable("tst_sharding"), traceTable("tst_sharding_4")) - testcaseNotice("检查日志和数据库变化,最好debug进行,wing0和wing1,同步更新表结构") + testcaseNotice("Check logs and database changes, best done by debug, wing0 and wing1, synchronized update table structure") } diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionMangerTest.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionMangerTest.kt index 6b4e2a2b4..c679c8d4a 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionMangerTest.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SchemaRevisionMangerTest.kt @@ -12,7 +12,7 @@ import pro.fessional.wings.faceless.WingsTestHelper.breakpointDebug import pro.fessional.wings.faceless.util.FlywaveRevisionScanner /** - * 默认profile,有writer和reader数据源,但只使用writer + * Default profile, there are writer and reader datasource, use the writer only. * @author trydofor * @since 2019-06-05 */ @@ -43,7 +43,7 @@ open class SchemaRevisionMangerTest { .master() .replace(revi1Schema, revi1Schema + 1, true) .modify(revi1Schema + 1, "sys_schema_version", schemaVersion) -// .modify("更名win_schema_version") { _, sql -> +// .modify("rename win_schema_version") { _, sql -> // if (sql.revision == REVISION_1ST_SCHEMA) { // sql.undoText = sql.undoText.replace("sys_schema_version", schemaVersion) // sql.uptoText = sql.uptoText.replace("sys_schema_version", schemaVersion) @@ -55,13 +55,13 @@ open class SchemaRevisionMangerTest { @Test fun test1Publish520() { - breakpointDebug("发布REVISION_2ND_IDLOGS💰") + breakpointDebug("Publish to REVISION_2ND_IDLOGS💰") schemaRevisionManager.publishRevision(revi2IdLog, 0) } @Test fun test2CurrentRevi() { - breakpointDebug("查看当前版本💰") + breakpointDebug("Check current revision💰") val databaseVersion = schemaRevisionManager.currentRevision() for ((_, u) in databaseVersion) { assertEquals(revi2IdLog, u) @@ -70,7 +70,7 @@ open class SchemaRevisionMangerTest { @Test fun test2ReviLine() { - breakpointDebug("查看版本线状💰") + breakpointDebug("Check current revision line💰") val databaseVersion = schemaRevisionManager.statusRevisions() for ((d, u) in databaseVersion) { if (u == null) { @@ -86,11 +86,11 @@ open class SchemaRevisionMangerTest { @Test fun test3DownThenUp() { - breakpointDebug("降级到1st版本💰") + breakpointDebug("Downgrade to 1st💰") schemaRevisionManager.publishRevision(revi1Schema, -1) - breakpointDebug("升级到2st版本💰") + breakpointDebug("Upgrade to 2st💰") schemaRevisionManager.publishRevision(revi2IdLog, -1) - breakpointDebug("再次降级到1st版本💰") + breakpointDebug("Again downgrade to 1st💰") schemaRevisionManager.publishRevision(revi1Schema, -1) } @@ -98,21 +98,21 @@ open class SchemaRevisionMangerTest { @Test fun test4Force615() { - breakpointDebug("强制增加版本615💰,但未执行") + breakpointDebug("Force to add 615💰, but do NOT publish") schemaRevisionManager.forceUpdateSql( test3rdRevision, """ CREATE TABLE `test_temp`( - `SEQ_NAME` varchar(100) NOT NULL COMMENT '序列名' + `SEQ_NAME` varchar(100) NOT NULL COMMENT 'sequence name' ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='test_temp'; CREATE TABLE `test_temp_0`( - `SEQ_NAME` varchar(100) NOT NULL COMMENT '序列名' + `SEQ_NAME` varchar(100) NOT NULL COMMENT 'sequence name' ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='test_temp'; CREATE TABLE `test_temp_1`( - `SEQ_NAME` varchar(100) NOT NULL COMMENT '序列名' + `SEQ_NAME` varchar(100) NOT NULL COMMENT 'sequence name' ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='test_temp'; """.trimIndent(), @@ -124,11 +124,11 @@ open class SchemaRevisionMangerTest { @Test fun test5ForceBreak() { - breakpointDebug("发布615💰") + breakpointDebug("Publish 615💰") wingsTestHelper.assertNot(WingsTestHelper.Type.Table, "test_temp", "test_temp_0", "test_temp_1") schemaRevisionManager.forceApplyBreak(test3rdRevision, -3, true) wingsTestHelper.assertHas(WingsTestHelper.Type.Table, "test_temp", "test_temp_0", "test_temp_1") - breakpointDebug("取消615版💰") + breakpointDebug("Cancel 615💰") schemaRevisionManager.forceApplyBreak(test3rdRevision, -4, false) wingsTestHelper.assertNot(WingsTestHelper.Type.Table, "test_temp", "test_temp_0", "test_temp_1") } @@ -136,21 +136,21 @@ open class SchemaRevisionMangerTest { @Test fun test6Republish520() { - breakpointDebug("发布520💰") + breakpointDebug("Publish 520💰") schemaRevisionManager.publishRevision(revi2IdLog, 0) - breakpointDebug("降级520💰") + breakpointDebug("Downgrade 520💰") schemaRevisionManager.forceApplyBreak(revi2IdLog, 0, false) - breakpointDebug("重发520💰") + breakpointDebug("Re-publish 520💰") schemaRevisionManager.publishRevision(revi2IdLog, 0) } @Test fun test7ForceExecSql() { - breakpointDebug("强制执行Sql💰") + breakpointDebug("Force to execute the Sql💰") schemaRevisionManager.forceExecuteSql( """ CREATE TABLE `test_temp_x`( - `SEQ_NAME` varchar(100) NOT NULL COMMENT '序列名' + `SEQ_NAME` varchar(100) NOT NULL COMMENT 'sequence name' ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='test_temp'; @@ -162,29 +162,29 @@ open class SchemaRevisionMangerTest { @Test fun test8PublishBranch() { - breakpointDebug("扫描分支feature/01-enum-i18n💰") + breakpointDebug("scan branch feature/01-enum-i18n💰") val sqls = FlywaveRevisionScanner.scanBranch("feature/01-enum-i18n") schemaRevisionManager.checkAndInitSql(sqls, 0, true) - breakpointDebug("发布分支feature/01-enum-i18n💰") + breakpointDebug("publish branch feature/01-enum-i18n💰") schemaRevisionManager.publishRevision(WingsRevision.V01_19_0521_01_EnumI18n.revision(), 0) } @Test fun test9MaintainBreak() { - breakpointDebug("制作执行失败的断裂版本💰") + breakpointDebug("Prepare a breakpoint revision to mock a failure💰") schemaRevisionManager.forceExecuteSql( """ UPDATE `$schemaVersion` SET `apply_dt` = '1000-01-01 00:00:17' WHERE `revision` = '$REVISION_TEST_V1'; """.trimIndent() ) schemaRevisionManager.publishRevision(REVISION_TEST_V1, 0) - breakpointDebug("因断裂版本不能执行,看日志💰") + breakpointDebug("Can't execute due to broken version, see logs💰") schemaRevisionManager.forceExecuteSql( """ UPDATE `$schemaVersion` SET `apply_dt` = '1000-01-01 00:00:00' WHERE `revision` = '$REVISION_TEST_V1'; """.trimIndent() ) - breakpointDebug("修复断裂,降级版本💰") + breakpointDebug("Fix breakpoint, and downgrade💰") schemaRevisionManager.publishRevision(REVISION_TEST_V1, 0) } } diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentParserTest.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentParserTest.kt index 117b32b0f..0d3ec05f8 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentParserTest.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentParserTest.kt @@ -21,7 +21,7 @@ class SqlSegmentParserTest { lateinit var sqlStatementParser: SqlStatementParser @Test - @Disabled("遇到解析问题是,用于人工识别") + @Disabled("Use for debugging in case of parsing problems") fun test1ManualCheck() { val scan = FlywaveRevisionScanner.scanMaster() for ((k, v) in scan) { diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessorTest.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessorTest.kt index ca1cead8c..97d08ad4a 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessorTest.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/SqlSegmentProcessorTest.kt @@ -77,7 +77,7 @@ class SqlSegmentProcessorTest { @plain apply@ctr_clerk[_0-0]* error@skip ask@danger - // 其他注释 + // other comment @trigger */""","/*") assertNotNull(mt) @@ -92,7 +92,7 @@ class SqlSegmentProcessorTest { @Test fun parseCmd1() { - val mt = SqlSegmentProcessor.parseCmd("-- @plain apply@ctr_clerk[_0-0]* error@skip // 其他注释","--") + val mt = SqlSegmentProcessor.parseCmd("-- @plain apply@ctr_clerk[_0-0]* error@skip // other comment","--") assertNotNull(mt) mt!! assertEquals("", mt.tbl) @@ -105,7 +105,7 @@ class SqlSegmentProcessorTest { @Test fun parseCmd2() { - val mt = SqlSegmentProcessor.parseCmd("-- apply@ctr_clerk[_0-0]* error@skip // 其他注释","--") + val mt = SqlSegmentProcessor.parseCmd("-- apply@ctr_clerk[_0-0]* error@skip // other comment","--") assertNotNull(mt) mt!! assertEquals("", mt.tbl) @@ -117,7 +117,7 @@ class SqlSegmentProcessorTest { @Test fun parseCmd3() { - val mt = SqlSegmentProcessor.parseCmd("-- error@skip // 其他注释","--") + val mt = SqlSegmentProcessor.parseCmd("-- error@skip // other comment","--") assertNotNull(mt) mt!! assertEquals("", mt.tbl) @@ -129,7 +129,7 @@ class SqlSegmentProcessorTest { @Test fun parseCmd4() { - val mt = SqlSegmentProcessor.parseCmd("/* ask@danger // 其他注释 */","/*") + val mt = SqlSegmentProcessor.parseCmd("/* ask@danger // other comment */","/*") assertNotNull(mt) mt!! assertEquals("", mt.tbl) diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/WingsShardingTests.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/WingsShardingTests.kt index 24d53406b..27a7f395e 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/WingsShardingTests.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/WingsShardingTests.kt @@ -29,13 +29,13 @@ open class WingsShardingTests { val statement = datasource.connection.prepareStatement(""" create table `wg_order` ( - `id` bigint(20) not null comment '主键', - `create_dt` datetime(3) not null default now(3) comment '创建日时', - `modify_dt` datetime(3) not null default '1000-01-01' on update now(3) comment '修改日时', - `commit_id` bigint(20) not null comment '提交id', + `id` bigint(20) not null comment 'PK', + `create_dt` datetime(3) not null default now(3) comment 'created datetime', + `modify_dt` datetime(3) not null default '1000-01-01' on update now(3) comment 'modified datetime', + `commit_id` bigint(20) not null comment 'commit id', primary key (`id`) ) engine = innodb - default charset = utf8mb4 comment ='202/测试订单'; + default charset = utf8mb4 comment ='202/test order'; """.trimIndent()) val result = statement.executeUpdate() @@ -70,16 +70,16 @@ open class WingsShardingTests { val sts1 = datasource.connection.prepareStatement(""" create table `wg_order${"$"}log` ( - `id` bigint(20) not null comment '主键', - `create_dt` datetime(3) not null comment '创建日时', - `modify_dt` datetime(3) not null comment '修改日时', - `commit_id` bigint(20) not null comment '提交id', + `id` bigint(20) not null comment 'PK', + `create_dt` datetime(3) not null comment 'created datetime', + `modify_dt` datetime(3) not null comment 'modified datetime', + `commit_id` bigint(20) not null comment 'commit id', `_du` int(11) null, `_dt` datetime(3) default now(3), `_id` int(11) not null auto_increment, primary key (`_id`) ) engine = innodb - default charset = utf8mb4 comment ='测试订单'; + default charset = utf8mb4 comment ='test order'; """.trimIndent()) val rst1 = sts1.executeUpdate() diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtilTest.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtilTest.kt index e17559357..9426cf192 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtilTest.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/flywave/util/TemplateUtilTest.kt @@ -16,8 +16,8 @@ class TemplateUtilTest { val txt = """CREATE TABLE `SYS_LIGHT_SEQUENCE` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""" val tkn = "SYS_LIGHT_SEQUENCE" val idx = TemplateUtil.parse(txt, tkn, "'\"") - val mrg = TemplateUtil.merge(txt, idx, "中文表也是表") - assertEquals("""CREATE TABLE `中文表也是表` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""", mrg) + val mrg = TemplateUtil.merge(txt, idx, "SHARD_TABLE") + assertEquals("""CREATE TABLE `SHARD_TABLE` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""", mrg) } @Test @@ -25,8 +25,8 @@ class TemplateUtilTest { val txt = """CREATE TABLE `SYS_LIGHT_SEQUENCE_01` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""" val tkn = "SYS_LIGHT_SEQUENCE" val idx = TemplateUtil.parse(txt, tkn, "'\"",false) - val mrg = TemplateUtil.merge(txt, idx, "中文表也是表") - assertEquals("""CREATE TABLE `中文表也是表_01` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""", mrg) + val mrg = TemplateUtil.merge(txt, idx, "SHARD_TABLE") + assertEquals("""CREATE TABLE `SHARD_TABLE_01` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""", mrg) } @Test @@ -34,11 +34,11 @@ class TemplateUtilTest { val txt = """CREATE TABLE `SYS_LIGHT_SEQUENCE` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""" val tkn = listOf("SYS_LIGHT_SEQUENCE","LIGHT_SEQUENCE","SEQUENCE`") val idx = TemplateUtil.parse(txt, tkn, "'\"") - val map = mapOf("SYS_LIGHT_SEQUENCE" to "中文表也是表", + val map = mapOf("SYS_LIGHT_SEQUENCE" to "SHARD_TABLE", "LIGHT_SEQUENCE" to "XXX", "SEQUENCE`" to "ZZZ") val mrg = TemplateUtil.merge(txt, idx, map) - assertEquals("""CREATE TABLE `中文表也是表` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""", mrg) + assertEquals("""CREATE TABLE `SHARD_TABLE` "SYS_LIGHT_SEQUENCE is \" good" 'SYS_LIGHT_SEQUENCE \'is ''good'""", mrg) } @Test diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveInitDatabaseSample.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveInitDatabaseSample.kt index 7f8523c15..0f463ca64 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveInitDatabaseSample.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveInitDatabaseSample.kt @@ -14,7 +14,7 @@ import pro.fessional.wings.faceless.util.FlywaveRevisionScanner * @since 2019-06-22 */ @SpringBootTest -@Disabled("手动执行,以有SchemaRevisionMangerTest覆盖测试 ") +@Disabled("Manually, tested by SchemaRevisionMangerTest") class WingsFlywaveInitDatabaseSample { @Autowired diff --git a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveShardJournalSample.kt b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveShardJournalSample.kt index 66ade3533..31999b3b3 100644 --- a/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveShardJournalSample.kt +++ b/wings/faceless-flywave/src/test/kotlin/pro/fessional/wings/faceless/sample/WingsFlywaveShardJournalSample.kt @@ -17,7 +17,7 @@ import pro.fessional.wings.faceless.util.FlywaveRevisionScanner * @since 2019-06-22 */ @SpringBootTest(properties = ["debug = true"]) -@Disabled("手动执行,以有SchemaJournalManagerTest,SchemaShardingManagerTest覆盖测试 ") +@Disabled("Manually, tested by SchemaJournalManagerTest, SchemaShardingManagerTest") class WingsFlywaveShardJournalSample { @Autowired @@ -31,28 +31,28 @@ class WingsFlywaveShardJournalSample { @Test fun revisionShardJournal() { - // 初始 + // init val sqls = FlywaveRevisionScanner.scanMaster() schemaRevisionManager.checkAndInitSql(sqls, 0) - // 升级 + // upgrade schemaRevisionManager.publishRevision(WingsRevision.V01_19_0520_01_IdLog.revision(), 0) schemaRevisionManager.publishRevision(REVISION_TEST_V1, 0) - // 单库强升 + // force upgrade in master database only schemaRevisionManager.forceApplyBreak(REVISION_TEST_V2, 2, true, "master") - // 分表 + // sharding val table = "tst_sharding" schemaShardingManager.publishShard(table, 5) - // 需要sharding数据源,在shard中测试 + // need sharding datasource, tested in shard testcase // schemaShardingManager.shardingData(table, true) - // 跟踪 + // trace table schemaJournalManager.checkAndInitDdl(table, 0) - // 开启关闭 + // enable / disable schemaJournalManager.publishUpdate(table, false, 0) schemaJournalManager.publishUpdate(table, true, 0) schemaJournalManager.publishUpdate(table, false, 0) @@ -61,7 +61,7 @@ class WingsFlywaveShardJournalSample { schemaJournalManager.publishDelete(table, true, 0) schemaJournalManager.publishDelete(table, false, 0) - // 降级 + // downgrade schemaRevisionManager.publishRevision(WingsRevision.V00_19_0512_01_Schema.revision(), 0) } } diff --git a/wings/faceless-flywave/src/test/resources/wings-conf/wings-test-module.properties b/wings/faceless-flywave/src/test/resources/wings-conf/wings-test-module.properties deleted file mode 100644 index d44b54c3b..000000000 --- a/wings/faceless-flywave/src/test/resources/wings-conf/wings-test-module.properties +++ /dev/null @@ -1 +0,0 @@ -wings.test.module=虚空假面 \ No newline at end of file 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/WingsJooqDaoAliasImpl.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoAliasImpl.java
index 01d6f6f3a..deb6e5a23 100644
--- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoAliasImpl.java
+++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoAliasImpl.java
@@ -5,8 +5,27 @@
 import org.jetbrains.annotations.Contract;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.jooq.BatchBindStep;
+import org.jooq.Condition;
+import org.jooq.Configuration;
+import org.jooq.DSLContext;
+import org.jooq.Field;
+import org.jooq.InsertOnDuplicateSetMoreStep;
+import org.jooq.InsertOnDuplicateSetStep;
+import org.jooq.InsertReturningStep;
+import org.jooq.Loader;
+import org.jooq.LoaderOptionsStep;
+import org.jooq.OrderField;
+import org.jooq.QueryPart;
 import org.jooq.Record;
-import org.jooq.*;
+import org.jooq.RecordMapper;
+import org.jooq.Result;
+import org.jooq.SelectConditionStep;
+import org.jooq.SelectFieldOrAsterisk;
+import org.jooq.SelectSelectStep;
+import org.jooq.Table;
+import org.jooq.TableRecord;
+import org.jooq.UpdatableRecord;
 import org.jooq.impl.DAOImpl;
 import org.jooq.impl.DSL;
 import org.jooq.impl.TableImpl;
@@ -20,7 +39,14 @@
 import pro.fessional.wings.faceless.service.journal.JournalDiff;
 
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
@@ -29,14 +55,15 @@
 
 /**
  * 
- * 原则上,不希望Record携带的数据库信息扩散,因此建议Dao之外使用pojo
+ * In principle, the database information carried by Record should not be spread,
+ * so it's recommended to use Pojo instead of Record outside of Dao.
  *
- * 对于read方法,一律返回Pojo;对于write,同时支持 Record和Pojo。
- * 为了编码的便捷和减少数据拷贝,可以使用Record进行操作。
- * 批量处理中,一律使用了new Record,为了提升性能。
+ * For read method, it always returns Pojo; for write method, it supports both Record and Pojo.
+ * For the convenience of coding and to reduce data copying, you can use Record for operation.
+ * In batch processing, new Record is always used to improve performance.
  *
- * 注意,alias 用在多表查询,filed/condition和table需要同名,否则出现语法错误。
- * 即,不能是表名和字段不能一个是table,一个是alias的。
+ * Note that alias is used in multi-table query, filed/condition and table must have the same name,
+ * otherwise there will be a syntax error. I.e., fields that are in different alias from the table.
  * 
* * @param Table @@ -63,7 +90,7 @@ protected WingsJooqDaoAliasImpl(T table, Class

type, Configuration conf) { } /** - * -1:未检查 | 0:不存:1:存在 + * -1:Unchecked | 0:Not exist | 1:Exists * * @param type -1|0|1 */ @@ -72,9 +99,10 @@ public void setTableExist(@MagicConstant(intValues = {-1, 0, 1}) int type) { } /** - * 以 SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=? AND TABLE_SCHEMA=SCHEMA() 检查数据库中是否存在此表 + * Use `SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=? AND TABLE_SCHEMA=SCHEMA()` + * to check the table existence in the current database. * - * @return 存在与否 + * @return Whether not exist */ public boolean notTableExist() { if (tableExist < 0) { @@ -94,10 +122,11 @@ public boolean notTableExist() { } /** - * 相同表结构,构造一个新表名,有在分表,影子表的场景 + * Create a new table with the same table structure. + * Used in sharding table, shadow table scenario * - * @param name 新表名 - * @return 新表 + * @param name new table name + * @return new table * @see TableImpl#rename(String) */ @SuppressWarnings("unchecked") @@ -107,11 +136,7 @@ public T newTable(String name) { } /** - * 以当前表名为基础,增加前缀,后缀 - * - * @param prefix 前缀 - * @param postfix 后缀 - * @return 新表 + * Based on the current table name, add prefixes, suffixes */ @NotNull public T newTable(String prefix, String postfix) { @@ -131,9 +156,7 @@ public T getTable() { } /** - * 获得系统默认的table别名 - * - * @return 表 + * Get the system default table alias. */ @NotNull public T getAlias() { @@ -141,9 +164,9 @@ public T getAlias() { } /** - * 通过 mapping 构造一个 record + * Create new Record by object mapping. * - * @param obj 具有相同mapping规则 + * @param obj object with some mapping rules. * @return record */ @NotNull @@ -152,9 +175,9 @@ public R newRecord(Object obj) { } /** - * 把一组 po 构造为 record,可供batch系列使用 + * Create a list of records by pojo, usually used in batch. * - * @param pos po + * @param pos pojos * @return list of record */ @NotNull @@ -168,13 +191,15 @@ public List newRecord(Collection

pos) { ///////////////// batch ///////////////////// /** - * 一次性导入新记录,对重复记录忽略或更新。 - * ignore时,采用了先查询 from dual where exists select * where `id` = ? - * replace时,使用了 on duplicate key update + *

+     * Batch load records at once, and ignore/update on duplicate.
+     * ignore - check by `from dual where exists select * where `id` = ?` first,
+     * replace - use on duplicate key update statement
+     * 
* - * @param records 所有记录 - * @param ignoreOrReplace 唯一冲突时,忽略还是替换 - * @return 执行结果,使用 ModifyAssert判断 + * @param records all record + * @param ignoreOrReplace ignore or update on duplicate + * @return result, should use ModifyAssert to check * @see DSLContext#loadInto(Table) */ @NotNull @@ -201,25 +226,25 @@ public Loader batchLoad(Collection records, boolean ignoreOrReplace) { private void checkBatchMysql() { if (WingsJooqEnv.daoBatchMysql) { - throw new IllegalStateException("请使用#batchInsert(Collection, int, boolean),以使用insert ignore 和 replace into的mysql高效语法。避免使用from dual where exists 和 on duplicate key update"); + throw new IllegalStateException("Use #batchInsert(Collection, int, boolean) instead. `insert ignore` and `replace into` are more efficient mysql statements than `from dual where exists` and `on duplicate key update`"); } } /** - * 插入新记录,使用mysql的 insert ignore或 replace into。 - * 注意 jooq的mergeInto,不完美,必须都有值,而replace不会。 + * Insert Pojo, use mysql `insert ignore` or `replace into`, + * Note jooq mergeInto must all have values, and replace won't. * - * @param pojo 记录 - * @param ignoreOrReplace 唯一冲突时,忽略还是替换 - * @return 执行结果,使用 ModifyAssert判断 + * @param pojo pojo + * @param ignoreOrReplace ignore or update on duplicate + * @return result, should use ModifyAssert to check */ public int insertInto(P pojo, boolean ignoreOrReplace) { return insertInto(pojo, ignoreOrReplace, null); } /** - * 以ignoreOrReplace=false 插入,并获取diff + * Insert Pojo with ignoreOrReplace=false, and return the diff. * * @see #diffInsert(Object, boolean) */ @@ -229,7 +254,7 @@ public JournalDiff diffInsert(P pojo) { } /** - * 插入,并获取diff + * Insert Pojo and return the diff. * * @see #insertInto(Object, boolean) */ @@ -290,11 +315,11 @@ private int insertInto(P pojo, boolean ignoreOrReplace, JournalDiff diff) { } /** - * batchInsert record的语法糖 + * batchInsert syntax sugar * - * @param pos 记录 - * @param ignoreOrReplace 唯一冲突时,忽略还是替换 - * @return 执行结果,使用 ModifyAssert判断 + * @param pos pojo records + * @param ignoreOrReplace ignore or replace if DuplicateKey + * @return array of affected records, can use ModifyAssert to check */ public int @NotNull [] insertInto(Collection

pos, boolean ignoreOrReplace) { return batchInsert(newRecord(pos), 0, ignoreOrReplace); @@ -308,13 +333,12 @@ public int mergeInto(P pojo, Function[]> fun) { } /** - * 插入新记录,默认使用①insert into DuplicateKey update, - * 也可以②先select,在insert或update + * insert one record by insert into DuplicateKey update. * - * @param table 与 updateFields 同名表 - * @param pojo 记录 - * @param updateFields 唯一约束存在时更新的字段,确保不使用别名 - * @return 执行结果,使用 ModifyAssert判断 + * @param table table with the same name as updateFields + * @param pojo pojo record + * @param updateFields fields to update if Duplicate Key, should not use table alias + * @return affected records, can use ModifyAssert to check */ public int mergeInto(T table, P pojo, Field... updateFields) { Map, Object> map = new LinkedHashMap<>(); @@ -342,13 +366,13 @@ public int mergeInto(T table, P pojo, Field... updateFields) { } /** - * 先select,在insert或update + * Select first, then insert or update depending on whether record exists. * - * @param table 与 updateFields 同名表 - * @param records 所有记录 - * @param size 每批的数量,小于等于0时,表示不分批 - * @param updateFields 唯一约束存在时更新的字段 - * @return 执行结果,使用 ModifyAssert判断 + * @param table table with the same name as updateFields + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @param updateFields fields to update if Duplicate Key, should not use table alias + * @return array of affected records, can use ModifyAssert to check */ public int @NotNull [] batchMerge(T table, Collection records, int size, Field... updateFields) { if (records == null || records.isEmpty()) return Null.Ints; @@ -388,16 +412,16 @@ public int mergeInto(T table, P pojo, Field... updateFields) { } /** - * 当不使用db中的唯一约束时,使用此方法 - * 先根据keys进行分批select,再根据记录情况进行insert或update。 - * 字符串比较忽略大小写 + * Use this method if there are no unique constraints in the db. + * (1) batch SELECT based on KEYS first, (2) INSERT or UPDATE based on the records. + * String comparison ignores case * - * @param table 与 updateFields 同名表 - * @param keys 唯一索引字段 - * @param records 所有记录 - * @param size 每批的数量,小于等于0时,表示不分批 - * @param updateFields 唯一约束存在时更新的字段 - * @return 执行结果,使用 ModifyAssert判断 + * @param table table with the same name as updateFields + * @param keys keys of Duplicate Key + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @param updateFields fields to update if Duplicate Key, should not use table alias + * @return array of affected records, can use ModifyAssert to check */ public int @NotNull [] batchMerge(T table, Field[] keys, Collection records, int size, Field... updateFields) { return batchMerge(table, keys, caseIgnore, records, size, updateFields); @@ -413,16 +437,16 @@ public int mergeInto(T table, P pojo, Field... updateFields) { }; /** - * 当不使用db中的唯一约束时,使用此方法 - * 先根据keys进行分批select,再根据记录情况进行insert或update + * Use this method if there are no unique constraints in the db. + * (1) batch SELECT based on KEYS first, (2) INSERT or UPDATE based on the records. * - * @param table 与 updateFields 同名表 - * @param keys 唯一索引字段 - * @param equals 判断字段相等的方法 - * @param records 所有记录 - * @param size 每批的数量,小于等于0时,表示不分批 - * @param updateFields 唯一约束存在时更新的字段 - * @return 执行结果,使用 ModifyAssert判断 + * @param table table with the same name as updateFields + * @param keys keys of Duplicate Key + * @param equals predicate of equals + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @param updateFields fields to update if Duplicate Key, should not use table alias + * @return array of affected records, can use ModifyAssert to check */ @SuppressWarnings({"rawtypes", "unchecked"}) public int @NotNull [] batchMerge(T table, Field[] keys, BiPredicate equals, Collection records, int size, Field... updateFields) { @@ -507,13 +531,13 @@ public int mergeInto(T table, P pojo, Field... updateFields) { } /** - * 分配批量插入新记录,使用mysql的 insert ignore或 replace into。 - * 注意 jooq的mergeInto,不完美,必须都有值,而replace不会。 + * Batch insert records, use mysql's `insert ignore` or `replace into`. + * Note that jooq mergeInto is not perfect, requires both to have values, while `replace` does not. * - * @param records 所有记录 - * @param size 每批的数量,小于等于0时,表示不分批 - * @param ignoreOrReplace 唯一冲突时,忽略还是替换 - * @return 执行结果,使用 ModifyAssert判断 + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @param ignoreOrReplace ignore or replace if Duplicate Key + * @return array of affected records, can use ModifyAssert to check * @see DSLContext#mergeInto(Table) */ @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) @@ -566,11 +590,11 @@ public int mergeInto(T table, P pojo, Field... updateFields) { } /** - * 分配批量插入记录 + * Batch insert records * - * @param records 所有记录 - * @param size 每批的数量,小于等于0时,表示不分批 - * @return 执行结果,使用 ModifyAssert判断 + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @return array of affected records, can use ModifyAssert to check * @see DSLContext#batchInsert(TableRecord[]) */ public int @NotNull [] batchInsert(Collection records, int size) { @@ -580,11 +604,11 @@ public int mergeInto(T table, P pojo, Field... updateFields) { private final BiFunction, int[]> batchInsertExec = (dsl, rs) -> dsl.batchInsert(rs).execute(); /** - * 分配批量插入或更新记录 + * Batch store (insert/update) record. * - * @param records 所有记录 - * @param size 每批的数量,小于等于0时,表示不分批 - * @return 执行结果,使用 ModifyAssert判断 + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @return array of affected records, can use ModifyAssert to check * @see DSLContext#batchStore(UpdatableRecord[]) */ public int @NotNull [] batchStore(Collection records, int size) { @@ -594,14 +618,14 @@ public int mergeInto(T table, P pojo, Field... updateFields) { private final BiFunction, int[]> batchStoreExec = (dsl, rs) -> dsl.batchStore(rs).execute(); /** - * 分配批量更新数据 + * Batch update record. * - * @param table 与 updateFields 同名表 - * @param whereFields where条件 - * @param records 记录 - * @param size 批次大小 - * @param updateFields 更新字段 - * @return 执行结果,使用 ModifyAssert判断 + * @param table table with the same name as updateFields + * @param whereFields where condition fields + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @param updateFields fields to update + * @return array of affected records, can use ModifyAssert to check */ @SuppressWarnings({"rawtypes", "unchecked"}) public int @NotNull [] batchUpdate(T table, Field[] whereFields, Collection records, int size, Field... updateFields) { @@ -647,11 +671,11 @@ public int mergeInto(T table, P pojo, Field... updateFields) { } /** - * 分配批量更新记录 + * Batch update record. * - * @param records 所有记录 - * @param size 每批的数量,小于等于0时,表示不分批 - * @return 执行结果,使用 ModifyAssert判断 + * @param records collection of record + * @param size batch size, <=0 mean no batching + * @return array of affected records, can use ModifyAssert to check * @see DSLContext#batchUpdate(UpdatableRecord[]) */ @@ -1193,11 +1217,11 @@ public int delete(Function fun) { } /** - * 按条件删除 + * Delete by condition * - * @param table 表 - * @param cond 条件 - * @return 影响的数据条数 + * @param table the table + * @param cond where condition + * @return affected records */ public int delete(T table, Condition cond) { return ctx().delete(table) @@ -1206,7 +1230,7 @@ public int delete(T table, Condition cond) { } /** - * 删除一条记录,并获取Diff + * Delete a record and get the Diff */ @NotNull public JournalDiff diffDelete(T table, Condition cond) { @@ -1236,7 +1260,7 @@ public JournalDiff diffDelete(T table, Condition cond) { ///////////////// update ///////////////////// /** - * 更新记录,并获取Diff + * Update a record and get the Diff */ @NotNull public JournalDiff diffUpdate(T table, Map, ?> setter, Condition cond) { @@ -1286,11 +1310,11 @@ public int update(T table, Map setter, Condition cond) { * val ui = dao.update(setter, t.Id.eq(2L)) *

* - * @param table 同源表 - * @param setter 更新的字段-值 - * @param cond 更新条件 - * @param skipNull 忽略null值,true时需要map可编辑 - * @return 影响的数据条数 + * @param table table with the same name as condition/setter + * @param setter update key and value + * @param cond condition + * @param skipNull whether skip `null` values, true requires map to be editable. + * @return affected records * @see org.jooq.UpdateSetStep#set(Map) */ public int update(T table, Map setter, Condition cond, boolean skipNull) { @@ -1314,12 +1338,12 @@ public int update(T table, P pojo, Condition cond) { } /** - * 按对象和条件更新,null被忽略 + * Update record by pojo key and value, skip null. * - * @param table 同源表 - * @param pojo 对象 - * @param cond 条件 - * @return 更新数量 + * @param table table with the same name as condition + * @param pojo pojo + * @param cond condition + * @return affected records */ public int update(T table, P pojo, Condition cond, boolean skipNull) { DSLContext dsl = ctx(); @@ -1337,11 +1361,11 @@ public int update(T table, P pojo, Condition cond, boolean skipNull) { } /** - * 按对象和主键更新 + * Update record by pojo key and value, by PK * - * @param pojo 对象 - * @param skipNull null字段不被更新 - * @return 更新数量 + * @param pojo pojo + * @param skipNull whether skip `null` values + * @return affected records */ public int update(P pojo, boolean skipNull) { DSLContext dsl = ctx(); @@ -1352,17 +1376,17 @@ public int update(P pojo, boolean skipNull) { /** - * 按对象组更新 + * Update record by pojo key and value, by PK * - * @param objects 对象组 - * @param skipNull null字段不被更新 - * @return 更新数量 + * @param pojos pojos + * @param skipNull whether skip `null` values + * @return array of affected records */ - public int @NotNull [] update(Collection

objects, boolean skipNull) { - List records = new ArrayList<>(objects.size()); + public int @NotNull [] update(Collection

pojos, boolean skipNull) { + List records = new ArrayList<>(pojos.size()); DSLContext dsl = ctx(); - for (P object : objects) { - R record = dsl.newRecord(table, object); + for (P pojo : pojos) { + R record = dsl.newRecord(table, pojo); skipPkAndNull(record, skipNull); records.add(record); } @@ -1379,11 +1403,7 @@ public long count(Function fun) { } /** - * 按表count,要求table和cond中的字段必须同源 - * - * @param table 表 - * @param cond 条件 - * @return 结果 + * count table by condition, requires table with the same name as condition */ public long count(T table, Condition cond) { Long cnt = ctx().selectCount() diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoJournalImpl.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoJournalImpl.java index b3a57037e..66375266e 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoJournalImpl.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoJournalImpl.java @@ -25,14 +25,15 @@ /** *

- * 原则上,不希望Record携带的数据库信息扩散,因此建议Dao之外使用pojo
+ * In principle, the database information carried by Record should not be spread,
+ * so it's recommended to use Pojo instead of Record outside of Dao.
  *
- * 对于read方法,一律返回Pojo;对于write,同时支持 Record和Pojo。
- * 为了编码的便捷和减少数据拷贝,可以使用Record进行操作。
- * 批量处理中,一律使用了new Record,为了提升性能。
+ * For read method, it always returns Pojo; for write method, it supports both Record and Pojo.
+ * For the convenience of coding and to reduce data copying, you can use Record for operation.
+ * In batch processing, new Record is always used to improve performance.
  *
- * 注意,alias 用在多表查询,filed和table需要同源,否则出现语法错误。
- * 即,不能是表名和字段不能一个是table,一个是alias的。
+ * Note that alias is used in multi-table query, filed/condition and table must have the same name,
+ * otherwise there will be a syntax error. I.e., fields that are in different alias from the table.
  * 
* * @param Table @@ -475,12 +476,12 @@ public int delete(JournalService.Journal commit, Function fun) { } /** - * 按条件逻辑删除 + * Delete record by condition * * @param commit journal - * @param table 表 - * @param cond 条件 - * @return 影响的数据条数 + * @param table count table by condition, requires table with the same name as condition + * @param cond condition + * @return affected records */ public int delete(JournalService.Journal commit, T table, Condition cond) { return ctx() @@ -491,11 +492,11 @@ public int delete(JournalService.Journal commit, T table, Condition cond) { } /** - * 按id逻辑删除 + * Logic delete record by ids * * @param commit journal * @param ids ids - * @return 影响的数据条数 + * @return affected records */ @SafeVarargs public final int deleteById(JournalService.Journal commit, K... ids) { @@ -505,14 +506,14 @@ public final int deleteById(JournalService.Journal commit, K... ids) { private static final Record[] EMPTY_RECORD = {}; /** - * 按id逻辑删除 + * Logic delete record by ids * * @param commit journal * @param ids ids - * @return 影响的数据条数 + * @return affected records */ public int deleteById(JournalService.Journal commit, Collection ids) { - // 参考DAOImpl deleteById + // see DAOImpl deleteById final Condition cond; if (pkeys.length == 1) { @SuppressWarnings("unchecked") final Field pk = (Field) pkeys[0]; diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqEnv.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqEnv.java index 61a3fc4fa..a4ffcf56a 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqEnv.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqEnv.java @@ -5,14 +5,14 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * 全局的静态的wings jooq控制变量 + * Global static variable of wings jooq setting. * * @author trydofor * @since 2020-06-01 */ public class WingsJooqEnv { /** - * 控制dao中是否只支持mysql的高效 insert ignore和replace into + * Whether mysql's efficient `insert ignore` and `replace into` are supported in Dao. * spring.wings.faceless.jooq.enabled.batch-mysql=true */ public static volatile boolean daoBatchMysql = true; diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtil.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtil.java index a26dedb72..4bf05e2f2 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtil.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtil.java @@ -1,5 +1,6 @@ package pro.fessional.wings.faceless.database.jooq; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jooq.Condition; @@ -76,7 +77,7 @@ public static Field concatWs(String separator, Object... vals) { ///////////////// replace into ///////////////////// /** - * 不受CUD listener管理 + * CUD listener does not trigger */ public static RowCountQuery replaceInto(TableRecord record) { Table table = record.getTable(); @@ -85,7 +86,7 @@ public static RowCountQuery replaceInto(TableRecord record) { } /** - * 不受CUD listener管理 + * CUD listener does not trigger */ public static RowCountQuery replaceInto(Table table, Field... fields) { if (fields == null || fields.length == 0) { @@ -125,12 +126,12 @@ public static Condition condMatch(String against, Field... fields) { } /** - * 若 value.isEmpty,则返回NoCondition,否则返回eq NotNull + * Return NoCondition if value is null, otherwise return eq(NotNull) * - * @param filed 字段 - * @param value 数据 - * @param 类型 - * @return 条件 + * @param filed field + * @param value field value + * @param field type + * @return Condition */ @NotNull public static Condition condEqSkip(Field filed, Collection value) { @@ -138,13 +139,13 @@ public static Condition condEqSkip(Field filed, Collection value) { } /** - * 若 value.isEmpty,则返回NoCondition,否则返回eq NotNull + * Return NoCondition if filter is true, otherwise return eq(NotNull) * - * @param filed 字段 - * @param value 数据 - * @param filter 过滤 - * @param 类型 - * @return 条件 + * @param filed field + * @param value field value + * @param filter filter + * @param field type + * @return Condition */ @NotNull public static Condition condEqSkip(Field filed, Collection value, Predicate filter) { @@ -158,12 +159,12 @@ public static Condition condEqSkip(Field filed, Collection value, Pred } /** - * 若 value.isEmpty,则返回NoCondition,否则返回in + * Return NoCondition if value is null, otherwise return in(NotNull) * - * @param filed 字段 - * @param value 数据 - * @param 类型 - * @return 条件 + * @param filed field + * @param value field value + * @param field type + * @return Condition */ @NotNull public static Condition condInSkip(Field filed, Collection value) { @@ -171,13 +172,13 @@ public static Condition condInSkip(Field filed, Collection value) { } /** - * 若 value.isEmpty,则返回NoCondition,否则返回in + * Return NoCondition if filter is true, otherwise return in(NotNull) * - * @param filed 字段 - * @param value 数据 - * @param filter 过滤 - * @param 类型 - * @return 条件 + * @param filed field + * @param value field value + * @param filter filter + * @param field type + * @return Condition */ @NotNull public static Condition condInSkip(Field filed, Collection value, Predicate filter) { @@ -187,13 +188,13 @@ public static Condition condInSkip(Field filed, Collection value, Pred } /** - * 构造一个between的条件 + * Return a between condition * - * @param field 字段 - * @param lowerInclusive 小值,包含 - * @param upperInclusive 大值,包含 - * @param 类型 - * @return 条件 + * @param field field + * @param lowerInclusive lower (min) value (include) + * @param upperInclusive upper (max) value (include) + * @param field type + * @return Condition */ @NotNull public static Condition condRange(Field field, Z lowerInclusive, Z upperInclusive) { @@ -240,11 +241,12 @@ public static Condition condChain(Operator andOr, TableRecord record) { } /** - * 构造一个 and 级联的条件, type=1 and name='dog' + * Return a condition join by and/or with the record, e.g. type=1 and name='dog' * - * @param record 条件 - * @param ignoreNull 是否忽略null - * @return 条件 + * @param andOr operator (and/or) + * @param record table record + * @param ignoreNull whether to ignore null + * @return condition */ @NotNull public static Condition condChain(Operator andOr, TableRecord record, boolean ignoreNull) { @@ -285,14 +287,14 @@ public static Condition condChain(Operator andOr, Map fieldValue } /** - * 根据 map中的值,生成and条件,比如统一的用户数据隔离条件。 - * value是collection时翻译为f.in(v),否则为 f.eq(v) + * Return a condition join by and/or with the field map + * use `f.in(v)` if value is collection, otherwise `f.eq(v)` * - * @param andOr 链接操作 - * @param fieldValue 字段名和值 - * @param ignoreNull 是否忽略null - * @param alias 表名或别名 - * @return 条件 + * @param andOr operator (and/or) + * @param fieldValue filed name and value + * @param ignoreNull whether to ignore null + * @param alias table name or alias + * @return condition */ @NotNull public static Condition condChain(Operator andOr, Map fieldValue, boolean ignoreNull, TableImpl alias) { @@ -340,8 +342,8 @@ public static Condition condChain(Operator andOr, Map fieldValue * * @param field filed * @param ignoreNull filed - * @param value 值 - * @return 条件 + * @param value value + * @return condition */ @Nullable public static Condition condField(Field field, boolean ignoreNull, Object value) { @@ -398,18 +400,18 @@ public static List condField(TableRecord record, Field... inclu } /** - * 构造一个 and 级联的条件, type=1 and name='dog' + * Return a condition by record and specified fileds. eg. type=1 and name='dog' * - * @param record 条件 - * @param ignoreNull 是否忽略null - * @param includes 包含的字段,默认全包含 - * @return 条件 + * @param record record + * @param ignoreNull whether to ignore null + * @param includes included fields, all if empty + * @return condition */ @NotNull @SuppressWarnings("unchecked") public static List condField(TableRecord record, boolean ignoreNull, Field... includes) { Field[] fields = record.fields(); - // 按include赋值,其他为null + // handle include if not empty if (includes != null && includes.length > 0) { Field[] temp = new Field[fields.length]; for (Field fld : includes) { @@ -437,27 +439,26 @@ public static List condField(TableRecord record, boolean ignoreNul } /** - * 判断友好的链式条件builder - * - * @return builder + * Friendly chained condition builder */ + @NotNull public static CondBuilder condBuilder() { return new CondBuilder(); } /** - * 判断友好的链式条件builder - * - * @return builder + * Friendly chained condition builder */ + @NotNull public static CondBuilder condBuilder(Condition cond) { return new CondBuilder().and(cond); } /** + * Friendly chained condition builder *
      * (1=1) and ((2=2 or 3=3) or (4=4 and 5=5))
-     * 可以通过以下 grp-end,构造括号条件
+     * can be done by grp-end as follows
      * (1=1).and()
      * .grp()
      *    .grp(2=2).or(3=3).end()
@@ -465,7 +466,6 @@ public static CondBuilder condBuilder(Condition cond) {
      *    .grp(4=4).or(5=5).end()
      * .end()
      * 
- * 判断友好的链式条件builder */ public static class CondBuilder { @@ -475,7 +475,7 @@ public static class CondBuilder { /** * @see #and(Condition, boolean) */ - @NotNull + @Contract("->this") public CondBuilder and() { return cond(Operator.AND, null, true); } @@ -483,7 +483,7 @@ public CondBuilder and() { /** * @see #and(Condition, boolean) */ - @NotNull + @Contract("_->this") public CondBuilder and(Condition cond) { return cond(Operator.AND, cond, cond != null); } @@ -491,7 +491,7 @@ public CondBuilder and(Condition cond) { /** * @see #and(Condition, boolean) */ - @NotNull + @Contract("_,_->this") public CondBuilder andNotNull(Condition cond, Object... value) { final boolean vd = cond != null && Z.notNull(value) != null; return cond(Operator.AND, cond, vd); @@ -500,20 +500,16 @@ public CondBuilder andNotNull(Condition cond, Object... value) { /** * @see #and(Condition, boolean) */ - @NotNull + @Contract("_,_->this") public CondBuilder andNotEmpty(Condition cond, Collection value) { final boolean vd = cond != null && value != null && !value.isEmpty(); return cond(Operator.AND, cond, vd); } /** - * 当 valid且cond != null时,and cond - * - * @param cond 目标 - * @param valid 判定 - * @return builder + * and cond if valid and cond != null */ - @NotNull + @Contract("_,_->this") public CondBuilder and(Condition cond, boolean valid) { return cond(Operator.AND, cond, valid); } @@ -521,7 +517,7 @@ public CondBuilder and(Condition cond, boolean valid) { /** * @see #or(Condition, boolean) */ - @NotNull + @Contract("->this") public CondBuilder or() { return cond(Operator.OR, null, true); } @@ -529,7 +525,7 @@ public CondBuilder or() { /** * @see #or(Condition, boolean) */ - @NotNull + @Contract("_->this") public CondBuilder or(Condition cond) { return cond(Operator.OR, cond, cond != null); } @@ -537,7 +533,7 @@ public CondBuilder or(Condition cond) { /** * @see #and(Condition, boolean) */ - @NotNull + @Contract("_,_->this") public CondBuilder orNotNull(Condition cond, Object... value) { final boolean vd = cond != null && Z.notNull(value) != null; return cond(Operator.OR, cond, vd); @@ -546,20 +542,16 @@ public CondBuilder orNotNull(Condition cond, Object... value) { /** * @see #and(Condition, boolean) */ - @NotNull + @Contract("_,_->this") public CondBuilder orNotEmpty(Condition cond, Collection value) { final boolean vd = cond != null && value != null && !value.isEmpty(); return cond(Operator.OR, cond, vd); } /** - * 当 valid且cond != null时,or cond - * - * @param cond 目标 - * @param valid 判定 - * @return builder + * or cond if valid and cond != null */ - @NotNull + @Contract("_,_->this") public CondBuilder or(Condition cond, boolean valid) { return cond(Operator.OR, cond, valid); } @@ -567,7 +559,7 @@ public CondBuilder or(Condition cond, boolean valid) { /** * @see #grp(Condition, boolean) */ - @NotNull + @Contract("->this") public CondBuilder grp() { return grp(null, true); } @@ -575,17 +567,15 @@ public CondBuilder grp() { /** * @see #grp(Condition, boolean) */ - @NotNull + @Contract("_->this") public CondBuilder grp(Condition cond) { return grp(cond, true); } /** - * 开启一个括号条件组 (....) - * - * @return builder + * Open a bracketed condition group (....) */ - @NotNull + @Contract("_,_->this") public CondBuilder grp(Condition cond, boolean valid) { calcStack.add(BGN); if (valid && cond != null) calcStack.add(cond); @@ -593,14 +583,9 @@ public CondBuilder grp(Condition cond, boolean valid) { } /** - * 当 valid且cond != null时,and/or cond - * - * @param opr 操作 - * @param cond 目标 - * @param valid 判定 - * @return builder + * and/or `cond` if `valid` and `cond` != null */ - @NotNull + @Contract("_,_,_->this") public CondBuilder cond(Operator opr, Condition cond, boolean valid) { if (!valid || opr == null) return this; @@ -610,21 +595,21 @@ public CondBuilder cond(Operator opr, Condition cond, boolean valid) { else { for (int i = calcStack.size() - 1; i >= 0; i--) { Object obj = calcStack.get(i); - if (obj instanceof Condition) { // Condition -> 计算 + if (obj instanceof Condition) { // Condition -> calculate if (cond == null) { // only opr (group) calcStack.add(opr); } - else { // 如果错误,抛出异常 + else { // throw if error calcStack.set(i, eval((Condition) obj, opr, cond)); } break; } else if (obj instanceof Operator) { if (cond == null) { - break; // 忽略当前操作符 + break; // ignore } else { - // Operator -> 移除,找上一个 + // Operator -> remove, lookup parent calcStack.remove(i); } } @@ -639,11 +624,9 @@ else if (obj instanceof Operator) { } /** - * 结束上一个括号条件组,并求值。 - * - * @return 条件 + * End the last bracketed condition group and evaluate the value. */ - @NotNull + @Contract("->this") public CondBuilder end() { final int size = calcStack.size(); if (size <= 1) return this; @@ -665,8 +648,8 @@ public CondBuilder end() { else if (obj instanceof Operator) { op = (Operator) obj; } - else { // 括号 - if (grp < 0) { // 结束当前,继续求值 + else { // bracketed + if (grp < 0) { // finish, continue grp = cur; } else { @@ -684,12 +667,7 @@ else if (obj instanceof Operator) { } /** - * null友好的条件求值 - * - * @param h1 条件1 - * @param op 操作 - * @param h2 条件2 - * @return 条件 + * null-friendly condition evaluation */ @NotNull public Condition eval(Condition h1, Operator op, Condition h2) { @@ -699,6 +677,7 @@ public Condition eval(Condition h1, Operator op, Condition h2) { return condition(op, h1, h2); } + @NotNull public Condition build() { for (int i = calcStack.size(); i > 1 && calcStack.size() > 1; i--) { end(); diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJournalTable.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJournalTable.java index 4c18e47e2..09b769ffc 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJournalTable.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/WingsJournalTable.java @@ -34,9 +34,9 @@ default Condition getOnlyLive() { } /** - * 组合其他条件 and onlyDiedData + * Combine other condition with `and onlyDiedData` * - * @param cond 其他条件 + * @param cond other condition * @return Condition */ @NotNull @@ -45,9 +45,9 @@ default Condition onlyDied(Condition cond) { } /** - * 组合其他条件 and onlyLiveData + * Combine other condition with `and onlyLiveData` * - * @param cond 其他条件 + * @param cond other condition * @return Condition */ @NotNull diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/converter/JooqLocaleConverter.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/converter/JooqLocaleConverter.java index ab984e5eb..1c21be633 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/converter/JooqLocaleConverter.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/converter/JooqLocaleConverter.java @@ -6,7 +6,7 @@ import java.util.Locale; /** - * 统一成 en_US格式 + * Standard to en_US format * * @author trydofor * @since 2021-01-18 @@ -24,7 +24,7 @@ public Locale from(String str) { @Override public String to(Locale lcl) { - // FastJson使用sun.util.BaseLocale,为`_`分隔 + // FastJson use sun.util.BaseLocale, `_` delimited. String lt = lcl.getLanguage(); String ct = lcl.getCountry(); final int ln = lt.length(); diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/JournalDiffHelper.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/JournalDiffHelper.java index 4e083550d..43a111c74 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/JournalDiffHelper.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/JournalDiffHelper.java @@ -1,5 +1,6 @@ package pro.fessional.wings.faceless.database.jooq.helper; +import lombok.Getter; import org.jetbrains.annotations.NotNull; import org.jooq.Field; import org.jooq.Record; @@ -25,6 +26,7 @@ public class JournalDiffHelper { public static final String Default = "default"; + @Getter private static final Map> DefaultIgnore = new HashMap<>(); public static void putDefaultIgnore(String... field) { @@ -40,10 +42,6 @@ public static void putDefaultIgnore(Map> map) { DefaultIgnore.putAll(map); } - public static Map> getDefaultIgnore() { - return DefaultIgnore; - } - public static JournalDiff diffInsert(Table table, ResultQuery query, Runnable exec) { exec.run(); final Result rs2 = query.fetch(); @@ -84,14 +82,14 @@ public static JournalDiff diffDelete(Table table, ResultQuery query, Runna // //// /** - * 精简差分结果,去掉相同的数据,去掉忽略的字段 + * Refine the diff results by removing duplicate data and removing ignored fields */ public static void tidy(@NotNull JournalDiff diff, Field... ignore) { tidy(true, diff, ignore); } /** - * 精简差分结果,去掉相同的数据,去掉忽略的字段 + * Refine the diff results by removing duplicate data and removing ignored fields */ public static void tidy(boolean withDefault, @NotNull JournalDiff diff, Field... ignore) { if (!diff.isValid()) return; @@ -102,7 +100,7 @@ public static void tidy(boolean withDefault, @NotNull JournalDiff diff, Field final ArrayList vs2 = arrayList(diff.getValue2()); final Object delFlag = new Object(); - // remove same value,if update + // remove same value, if update if (diff.isUpdate()) { for (int i = 0, len = col.size(); i < len; i++) { boolean sm = true; @@ -161,7 +159,7 @@ public static void tidy(boolean withDefault, @NotNull JournalDiff diff, Field // //// /** - * 生成除了table之外的信息,包括记录数,字段和前后数据 + * Generate information about record count, field name, and data before and after the update. */ public static void help(@NotNull JournalDiff diff, Result before, Result after) { final ArrayList column; diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/PageJooqHelper.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/PageJooqHelper.java index 2229dbb87..f66c691b5 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/PageJooqHelper.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/helper/PageJooqHelper.java @@ -54,11 +54,12 @@ import java.util.Map; /** - * 提供基于jdbc和jooq的分页查询工具。 *
- * total < 0,DB执行count和select
- * total = 0,DB不count,不select
- * total > 0,DB不count,但select
+ * Pagination Util for jdbc and jooq.
+ *
+ * * total < 0 - run count, run select
+ * * total = 0 - no count, no select
+ * * total > 0 - no count, run select
  * 
* * @author trydofor @@ -73,13 +74,15 @@ public static , P, K> CountJooq use(DAOImpl Record * @param

pojo - * @param 主键 - * @return 结果 + * @param pk + * @return step */ @NotNull public static , P, K> CountJooq use(DAOImpl dao, PageQuery page, int total) { @@ -92,12 +95,12 @@ public static CountJooq use(DSLContext dsl, PageQuery page) { } /** - * 分页查询 + * Page query by jooq * * @param dsl dsl - * @param page 页 - * @param total service层缓存的count计数 - * @return 结果 + * @param page query info + * @param total the count cached in service level + * @return step */ @NotNull public static CountJooq use(DSLContext dsl, PageQuery page, int total) { @@ -124,7 +127,7 @@ private static class ContextJooq { private int total = -1; /** - * 以 PageQuery.sort为主,以bys作为映射,以dft作为default为辅 + * `PageQuery.sort` as the primary, `bys` as the mapping, and `dft` as the default */ private void orderBy(Map> bys, OrderField... dft) { final List srt = PageUtil.sort(page.getSort()); @@ -235,7 +238,7 @@ public FetchJooq orderNone() { } /** - * 指定字段或排序语句,等效于field:field的map关系 + * Specify a field or sort statement that is equivalent to the field to field mapping. */ public FetchJooq order(OrderField... bys) { context.orderBy(Collections.emptyMap(), bys); @@ -243,7 +246,7 @@ public FetchJooq order(OrderField... bys) { } /** - * 根据alias:filed的map关系,到PageQuery的sort中匹配排序 + * Based on the mapping of alias to filed, use PageQuery's sort to match the ordering */ public FetchJooq order(Map> bys, OrderField... dft) { context.orderBy(bys, dft); diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/AutoQualifyFieldListener.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/AutoQualifyFieldListener.java index bc9573a0e..7bd70fa60 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/AutoQualifyFieldListener.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/AutoQualifyFieldListener.java @@ -11,7 +11,8 @@ import org.jooq.impl.TableImpl; /** - * visit可能触发多次,任何需要render的地方,如toString, getSQL0 + * `visit` may be triggered multiple times, anywhere a render is needed, e.g. toString, getSQL0 + * * @author trydofor * @since 2021-01-14 */ diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/JournalDeleteListener.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/JournalDeleteListener.java index 205076037..6aff07fe6 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/JournalDeleteListener.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/JournalDeleteListener.java @@ -17,10 +17,12 @@ import java.util.regex.Pattern; /** - * 仅支持单表执行,不支持batch处理 - *

+ *

+ * Only supports single table execution, does not support batch processing
+ *
  * delete from `tst_sharding` where (`id` = ? and `commit_id` = ?)
  * commit_id = :commit_id and `id` = ?
+ * 
* * @author trydofor * @since 2021-01-14 @@ -47,7 +49,7 @@ public void renderEnd(ExecuteContext ctx) { if (!params.isEmpty()) { for (Param value : params.values()) { ParamType type = value.getParamType(); - // 只处理indexed的 + // only handle indexed if (!(type == ParamType.INDEXED || type == ParamType.FORCE_INDEXED)) { return; } @@ -64,7 +66,7 @@ public void renderEnd(ExecuteContext ctx) { ctx.dsl().execute(updateSql); } else { - // 保证顺序 + // make sure the order Object[] pms = new Object[params.size()]; int i = 0; for (Param pm : params.values()) { diff --git a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/SlowSqlListener.java b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/SlowSqlListener.java index b5f80565a..2494c0b54 100644 --- a/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/SlowSqlListener.java +++ b/wings/faceless-jooq/src/main/java/pro/fessional/wings/faceless/database/jooq/listener/SlowSqlListener.java @@ -11,31 +11,29 @@ import java.util.function.BiConsumer; /** - * 慢查询日志 + * Log the Slow Sql * * @author trydofor * @since 2021-01-14 */ - +@Getter @Setter public class SlowSqlListener implements ExecuteListener { public enum ContextKey { EXECUTING_STOP_WATCH } + + private String token = "SlowSqlListener"; + /** - * slow阈值的毫秒数,-1表示关闭此功能 + * threshold of slow in mills, `-1` means disable */ - @Getter @Setter private long thresholdMillis = -1; - @Getter @Setter - private String token = "SlowSqlListener"; - /** - * 取代日志,自行处理耗时与SQL + * Handle time-consuming and SQL instead of logger */ - @Getter @Setter private BiConsumer costAndSqlConsumer = (c, s) -> Watches.log.warn("SLOW-SQL cost={}ms, sql={}", c, s); @Override 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/database/jooq/WingsJooqDaoAliasImplTest.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoAliasImplTest.java index 4497e3289..5a1ee8ee6 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoAliasImplTest.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/database/jooq/WingsJooqDaoAliasImplTest.java @@ -65,29 +65,29 @@ public void test0DropAndInit() { @Test public void test1BatchLoadSeeLog() { if (WingsJooqEnv.daoBatchMysql) { - testcaseNotice("跳过低效的SQL,使用mysql replace into 语法,见 batchMerge"); + testcaseNotice("Skip the inefficient SQL and use mysql `replace into` syntax, see batchMerge"); return; } val rds = Arrays.asList( - new TstShardingRecord(301L, now, now, now, 9L, "批量加载301", "", ZH_CN), - new TstShardingRecord(302L, now, now, now, 9L, "批量加载302", "", ZH_CN), - new TstShardingRecord(303L, now, now, now, 9L, "批量加载303", "", ZH_CN) + new TstShardingRecord(301L, now, now, now, 9L, "batch load 301", "", ZH_CN), + new TstShardingRecord(302L, now, now, now, 9L, "batch load 302", "", ZH_CN), + new TstShardingRecord(303L, now, now, now, 9L, "batch load 303", "", ZH_CN) ); - testcaseNotice("批量Load,查看日志,ignore, 301-303,使用了from dual where exists先查再插"); + testcaseNotice("batch load, check log, ignore, 301-303, use `from dual where exists` check, then insert"); dao.batchLoad(rds, true); - testcaseNotice("批量Load,查看日志,replace, 301-303,使用了on duplicate key update"); + testcaseNotice("batch load, check log, replace, 301-303, use on duplicate key update"); dao.batchLoad(rds, false); } @Test public void test2BatchInsertSeeLog() { val rds = Arrays.asList( - new TstShardingRecord(304L, now, now, now, 9L, "批量加载304", "", ZH_CN), - new TstShardingRecord(305L, now, now, now, 9L, "批量加载305", "", ZH_CN), - new TstShardingRecord(306L, now, now, now, 9L, "批量加载306", "", ZH_CN) + new TstShardingRecord(304L, now, now, now, 9L, "batch load 304", "", ZH_CN), + new TstShardingRecord(305L, now, now, now, 9L, "batch load 305", "", ZH_CN), + new TstShardingRecord(306L, now, now, now, 9L, "batch load 306", "", ZH_CN) ); - testcaseNotice("批量Insert,查看日志, 304-306,分2批插入"); + testcaseNotice("batch Insert, check log, 304-306, in 2 batch"); val rs = dao.batchInsert(rds, 2); assertArrayEquals(new int[]{1, 1, 1}, rs); } @@ -95,19 +95,19 @@ public void test2BatchInsertSeeLog() { @Test public void test3BatchMergeSeeLog() { val rds = Arrays.asList( - new TstShardingRecord(307L, now, now, now, 9L, "批量加载307", "", ZH_CN), - new TstShardingRecord(308L, now, now, now, 9L, "批量加载308", "", ZH_CN), - new TstShardingRecord(309L, now, now, now, 9L, "批量加载309", "", ZH_CN) + new TstShardingRecord(307L, now, now, now, 9L, "batch load 307", "", ZH_CN), + new TstShardingRecord(308L, now, now, now, 9L, "batch load 308", "", ZH_CN), + new TstShardingRecord(309L, now, now, now, 9L, "batch load 309", "", ZH_CN) ); - testcaseNotice("批量Insert,查看日志,ignore, 307-309,分2批次, insert ignore"); + testcaseNotice("batch Insert, check log, ignore, 307-309, in 2 batch, insert ignore"); val rs1 = dao.batchInsert(rds, 2, true); assertArrayEquals(new int[]{1, 1, 1}, rs1); - testcaseNotice("批量Insert,查看日志,replace, 307-309,分2批,replace into", "BUG https://github.com/apache/shardingsphere/issues/8226\n"); + testcaseNotice("batch Insert, check log, replace, 307-309, in 2 batch, replace into", "BUG https://github.com/apache/shardingsphere/issues/8226\n"); val rs2 = dao.batchInsert(rds, 2, false); assertArrayEquals(new int[]{1, 1, 1}, rs2); - testcaseNotice("批量Merge,查看日志,on dupkey, 307-309,分2批,duplicate"); + testcaseNotice("batch Merge, check log, on dupkey, 307-309, in 2 batch, duplicate"); testcaseNotice("insert into `tst_sharding` (`id`, .., `other_info`) values (?,..., ?) on duplicate key update `login_info` = ?, `other_info` = ?"); val rs3 = dao.batchMerge(tbl, rds, 2, tbl.LoginInfo, tbl.OtherInfo); assertArrayEquals(new int[]{1, 1, 1}, rs3); @@ -116,11 +116,11 @@ public void test3BatchMergeSeeLog() { @Test public void test4BatchStoreSeeLog() { val rds = Arrays.asList( - new TstShardingRecord(310L, now, now, now, 9L, "批量加载310", "", ZH_CN), - new TstShardingRecord(311L, now, now, now, 9L, "批量加载311", "", ZH_CN), - new TstShardingRecord(312L, now, now, now, 9L, "批量加载312", "merge", ZH_CN) + new TstShardingRecord(310L, now, now, now, 9L, "batch load 310", "", ZH_CN), + new TstShardingRecord(311L, now, now, now, 9L, "batch load 311", "", ZH_CN), + new TstShardingRecord(312L, now, now, now, 9L, "batch load 312", "merge", ZH_CN) ); - testcaseNotice("批量Insert,查看日志,ignore, 307-309,分2批插入"); + testcaseNotice("batch Insert, check log, ignore, 307-309, in 2 batch"); val rs = dao.batchStore(rds, 2); assertArrayEquals(new int[]{1, 1, 1}, rs); } @@ -128,11 +128,11 @@ public void test4BatchStoreSeeLog() { @Test public void test5BatchUpdateSeeLog() { val rds = Arrays.asList( - new TstShardingRecord(309L, now, now, now, 9L, "批量加载309", "update", ZH_CN), - new TstShardingRecord(310L, now, now, now, 9L, "批量加载310", "update", ZH_CN), - new TstShardingRecord(311L, now, now, now, 9L, "批量加载311", "update", ZH_CN) + new TstShardingRecord(309L, now, now, now, 9L, "batch load 309", "update", ZH_CN), + new TstShardingRecord(310L, now, now, now, 9L, "batch load 310", "update", ZH_CN), + new TstShardingRecord(311L, now, now, now, 9L, "batch load 311", "update", ZH_CN) ); - testcaseNotice("批量Update,查看日志 307-309,分2批更新"); + testcaseNotice("batch Update, check log, 307-309, in 2 batch"); val rs1 = dao.batchUpdate(rds, 2); assertArrayEquals(new int[]{1, 1, 1}, rs1); @@ -143,7 +143,7 @@ public void test5BatchUpdateSeeLog() { @Test public void test6SingleMergeSeeLog() { testcaseNotice("insert into `tst_sharding` (`id`, .., `other_info`) values (?,..., ?) on duplicate key update `login_info` = ?, `other_info` = ?"); - TstSharding pojo = new TstSharding(312L, now, now, now, 9L, "批量加载312", "update-bymerge", ZH_CN); + TstSharding pojo = new TstSharding(312L, now, now, now, 9L, "batch load 312", "update-bymerge", ZH_CN); val rs = dao.mergeInto(tbl, pojo, tbl.LoginInfo, tbl.OtherInfo); assertEquals(2, rs); } @@ -151,9 +151,9 @@ public void test6SingleMergeSeeLog() { @Test public void test7BatchMergeSeeLog() { val rds = Arrays.asList( - new TstShardingRecord(313L, now, now, now, 9L, "批量合并313-merge", "update-merge", ZH_CN), - new TstShardingRecord(310L, now, now, now, 9L, "批量合并310-merge", "update-merge", ZH_CN), - new TstShardingRecord(311L, now, now, now, 9L, "批量合并311-merge", "update-merge", ZH_CN) + new TstShardingRecord(313L, now, now, now, 9L, "batch 313-merge", "update-merge", ZH_CN), + new TstShardingRecord(310L, now, now, now, 9L, "batch 310-merge", "update-merge", ZH_CN), + new TstShardingRecord(311L, now, now, now, 9L, "batch 311-merge", "update-merge", ZH_CN) ); testcaseNotice("313 insert, 310,311 update"); val rs = dao.batchMerge(tbl, new Field[]{tbl.Id}, rds, 2, tbl.LoginInfo, tbl.OtherInfo); diff --git a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtilTest.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtilTest.java index 5f75ba15e..fd37b536a 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtilTest.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/database/jooq/WingsJooqUtilTest.java @@ -55,7 +55,7 @@ public void builderNormal() { .build(); assertEquals(c0.toString(), c1.toString()); - // 省略结尾 + // omit the end Condition c2 = WingsJooqUtil.condBuilder() .and(d1).and() .grp() @@ -65,7 +65,7 @@ public void builderNormal() { .build(); assertEquals(c0.toString(), c2.toString()); - // 多个操作 + // multiple opr Condition c3 = WingsJooqUtil.condBuilder() .and(d1).and().or() .grp().and() diff --git a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqDeleteListenerTest.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqDeleteListenerTest.java index 40984cfb0..c53d05987 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqDeleteListenerTest.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqDeleteListenerTest.java @@ -67,7 +67,7 @@ public void test2HelperSeeLog() { JournalJooqHelper.deleteByIds(dsl, TstShardingTable.TstSharding, 12L, 1L, 2L); JournalJooqHelper.deleteByIds(tmpl, "`tst_sharding`", 34L, 3L, 4L); testcaseNotice( - "检查日志,在delete前update,如下", + "check logs, update before delete, as follows", "UPDATE `tst_sharding` SET commit_id=34, delete_dt=NOW(3) WHERE id IN (3,4)", "DELETE FROM `tst_sharding` WHERE id IN (3,4)" ); @@ -75,7 +75,7 @@ public void test2HelperSeeLog() { @Test public void test3JooqDslSeeLog() { - // 有效 + // handle dsl.execute("DELETE FROM `tst_sharding` WHERE ID =5 AND COMMIT_ID = 5"); dsl.execute("DELETE FROM `tst_sharding` WHERE commit_id = 6 AND id = 6"); dsl.execute("DELETE FROM `tst_sharding` WHERE commit_id = 7 AND id = ?", 7L); @@ -83,12 +83,12 @@ public void test3JooqDslSeeLog() { TstShardingTable t = TstShardingTable.TstSharding; dsl.deleteFrom(t).where(t.Id.eq(8L).and(t.CommitId.eq(8L))).execute(); testcaseNotice( - "检查日志,id 等于 (5,6,7,8)的sql,先delete,再update,如下", + "check logs, id = (5,6,7,8) sql, and delete first, then update as follows", "DELETE FROM `tst_sharding` WHERE ID =5 AND COMMIT_ID = 5", "UPDATE `tst_sharding` SET COMMIT_ID = 5 ,delete_dt = NOW(3) WHERE ID =5" ); - // 无效 + // can not handle LocalDateTime now = LocalDateTime.now(); dsl.batchDelete( new TstShardingRecord(9L, now, DATE_TIME, DATE_TIME, 9L, "", "", ZH_CN) @@ -104,7 +104,7 @@ public void test3JooqDslSeeLog() { int[] rs = batch.execute(); log.info(Arrays.toString(rs)); testcaseNotice( - "检查日志,id >= 9的sql,只有delete,如下", + "check logs, id >= 9 sql, only delete as follow", "delete from `tst_sharding` where `id` = ?" ); } diff --git a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqMapperCompatibleTest.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqMapperCompatibleTest.java index 2c4a5883d..b4dc49285 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqMapperCompatibleTest.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/jooq/JooqMapperCompatibleTest.java @@ -23,11 +23,6 @@ import static pro.fessional.wings.faceless.WingsTestHelper.testcaseNotice; /** - * SimpleFlatMapper 比较不错,但有intoArray的bug - * https://github.com/arnaudroger/SimpleFlatMapper/issues/764 - *

- * SimpleFlatMapper 不支持 int.class, 仅Integer.class - * * @author trydofor * @since 2020-08-14 */ @@ -59,7 +54,6 @@ public void test1Exist() { Assertions.assertFalse(b); } - // 同名 @Data public static class SameName { private Long id; @@ -72,7 +66,7 @@ public void test1Lower() { TstShardingTable t = dao.getTable(); Condition c = t.Id.gt(1L).and(t.Id.le(105L)); - testcaseNotice("采用区分大小写的别名,jooq不支持,sfm支持"); + testcaseNotice("Case-sensitive alias, not supported by jooq, supported by sfm"); SameName vo1 = ctx.select(t.Id, t.LoginInfo.as("logininfo")) .from(t) .where(c) @@ -80,7 +74,7 @@ public void test1Lower() { .fetchOneInto(SameName.class); Assertions.assertNotNull(vo1); - Assertions.assertNull(vo1.getLoginInfo(), "Jooq区分大小写"); + Assertions.assertNull(vo1.getLoginInfo(), "Jooq is case-sensitive"); } @Test @@ -89,7 +83,7 @@ public void test1Snake() { TstShardingTable t = dao.getTable(); Condition c = t.Id.gt(1L).and(t.Id.le(105L)); - testcaseNotice("采用下划线的别名,jooq和sfm都支持"); + testcaseNotice("Underscore alias, supported by both jooq and sfm"); SameName vo2 = ctx.select(t.Id, t.LoginInfo.as("login_info")) .from(t) .where(c) @@ -103,7 +97,7 @@ public void test1Snake() { @Test public void test1Array() { - testcaseNotice("sfm有bug"); + testcaseNotice("sfm has bug"); SameName vo = new SameName(); vo.setId(101L); vo.setLoginInfo("login-info test"); 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..042e80307 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 @@ -95,17 +95,17 @@ public void test1Create() { pojo.setLoginInfo("login-info-301"); pojo.setOtherInfo("other-info-301"); - testcaseNotice("单个插入 normal"); + testcaseNotice("single insert normal"); assertCud(false, Cud.Create, singletonList(singletonList(301L)), () -> testDao.insert(pojo), "insert into"); - testcaseNotice("单个插入 ignore"); + testcaseNotice("single insert ignore"); assertCud(false, Cud.Create, singletonList(singletonList(301L)), () -> testDao.insertInto(pojo, true), "insert ignore into"); - testcaseNotice("单个插入 replace"); - assertCud(false, Cud.Create, singletonList(singletonList(301L)), () -> testDao.insertInto(pojo, false), + testcaseNotice("single insert replace"); + assertCud(false, Cud.Update, singletonList(singletonList(301L)), () -> testDao.insertInto(pojo, false), "duplicate key update"); final TstShardingTable t = testDao.getTable(); @@ -118,15 +118,15 @@ public void test1Create() { new TstShardingRecord(304L, now, now, now, 9L, "login-info-304", "", ZH_CN) ); - testcaseNotice("批量插入 normal"); + testcaseNotice("batch insert normal"); assertCud(false, Cud.Create, Arrays.asList(singletonList(302L), singletonList(303L), singletonList(304L)), () -> testDao.batchInsert(rds, 10), "insert into"); - testcaseNotice("批量插入 ignore"); + testcaseNotice("batch insert ignore"); assertCud(false, null, Collections.emptyList(), () -> testDao.batchInsert(rds, 10, true), "insert ignore into"); - testcaseNotice("批量插入 replace"); + testcaseNotice("batch insert replace"); assertCud(false, null, Collections.emptyList(), () -> testDao.batchInsert(rds, 10, false), "duplicate key update"); @@ -143,7 +143,7 @@ public void test2Update() { pojo.setId(301L); pojo.setCommitId(-301L); - testcaseNotice("单个更新"); + testcaseNotice("single update"); assertCud(true, Cud.Update, singletonList(singletonList(301L)), () -> testDao.update(pojo, true), "update"); @@ -151,7 +151,7 @@ public void test2Update() { Assertions.assertTrue(StringUtils.containsIgnoreCase(LastSql.get(), "select count")); Assertions.assertEquals(1L, c1); - testcaseNotice("批量更新"); + testcaseNotice("batch update"); assertCud(false, Cud.Update, singletonList(Arrays.asList(302L, 303L, 302L, 304L)), () -> testDao .ctx() .update(t) @@ -167,7 +167,7 @@ public void test2Update() { @Test public void test4Delete() { final TstShardingTable t = testDao.getTable(); - testcaseNotice("单个删除"); + testcaseNotice("single delete"); assertCud(false, Cud.Delete, singletonList(singletonList(301L)), () -> testDao .ctx() .delete(t) @@ -176,7 +176,7 @@ public void test4Delete() { "delete from" ); - testcaseNotice("范围删除"); + testcaseNotice("batch delete"); assertCud(false, Cud.Delete, singletonList(Arrays.asList(302L, 304L)), () -> testDao .ctx() .delete(t) diff --git a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqDslAndDaoSample.java b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqDslAndDaoSample.java index 06ab8991b..99bfafae7 100644 --- a/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqDslAndDaoSample.java +++ b/wings/faceless-jooq/src/test/java/pro/fessional/wings/faceless/sample/JooqDslAndDaoSample.java @@ -65,7 +65,7 @@ public void test0Init() { @Test public void test1Dao() { - testcaseNotice("使用alias"); + testcaseNotice("Use alias"); val a = dao.getAlias(); val c = a.Id.gt(1L).and(a.CommitId.lt(200L)); @@ -81,7 +81,7 @@ public void test1Dao() { log.info("============count {}, ft2'size={}", i, ft2.size()); // table - testcaseNotice("使用table"); + testcaseNotice("Use table"); val t = dao.getTable(); val setter = new HashMap<>(); setter.put(t.LoginInfo, "info"); @@ -100,7 +100,7 @@ public void test1Dao() { @Test public void test2Dsl() { - testcaseNotice("通过dao.ctx()获得dsl能力"); + testcaseNotice("Get Dsl by dao.ctx()"); Condition nullCond = null; Field nullField = null; // val nullOrder: OrderField? = null @@ -122,7 +122,7 @@ public void test2Dsl() { @Test public void test3Journal() { - testcaseNotice("日志功能"); + testcaseNotice("Journal Feature"); val now = LocalDateTime.now(); val journal = new Journal(1L, now, "", "", "", ""); @@ -155,7 +155,7 @@ public void test3Journal() { @Test public void test4DeleteDt() { - testcaseNotice("逻辑删除"); + testcaseNotice("Logic delete"); val c1 = dao.count(); log.info("count1={}", c1); val c2 = dao.count(TstShardingTable::getOnlyDied); @@ -164,7 +164,7 @@ public void test4DeleteDt() { @Test public void test4Shadow() { - testcaseNotice("影子表"); + testcaseNotice("Shadow table"); TstShardingTable upd = dao.newTable("", "_postfix"); val c1 = dao.count(upd, null); log.info("count1={}", c1); @@ -172,7 +172,7 @@ public void test4Shadow() { @Test public void test5DiffDao() { - testcaseNotice("差分数据Dao"); + testcaseNotice("Diff Dao"); TstSharding po = new TstSharding(); final long id = 20221024L; final TstShardingTable t = dao.getTable(); @@ -219,7 +219,7 @@ public void test5DiffDao() { final JournalDiff d3 = dao.diffDelete(t, t.Id.ge(id)); log.warn("diffDelete3={}", d3); - JournalDiffHelper.tidy(d3, t.Language); // 默认withDefault + JournalDiffHelper.tidy(d3, t.Language); // withDefault Assertions.assertNotNull(d3); Assertions.assertEquals(2, d3.getCount()); Assertions.assertEquals(t.getName(), d3.getTable()); @@ -233,7 +233,7 @@ public void test5DiffDao() { @Test public void test5DiffDsl() { - testcaseNotice("差分数据Dsl"); + testcaseNotice("Diff Dsl"); TstSharding po = new TstSharding(); final long id = 20221024L; final TstShardingTable t = dao.getTable(); @@ -282,7 +282,7 @@ public void test5DiffDsl() { final JournalDiff d3 = JournalDiffHelper.diffDelete(t, query, () -> dsl.delete(t).where(t.Id.ge(id))); log.warn("diffDelete3={}", d3); - JournalDiffHelper.tidy(d3, t.Language); // 默认withDefault + JournalDiffHelper.tidy(d3, t.Language); // withDefault Assertions.assertNotNull(d3); Assertions.assertEquals(1, d3.getCount()); Assertions.assertEquals(t.getName(), d3.getTable()); 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..f2876179a 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,6 +4,8 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import lombok.val; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jooq.Condition; import org.jooq.DSLContext; import org.jooq.DataType; @@ -59,9 +61,11 @@ import static pro.fessional.wings.faceless.WingsTestHelper.testcaseNotice; /** - * Jooq的编程能力十分强大,远高于 mybatis系列(mybatis plus) - * https://www.jooq.org/doc/latest/manual/sql-execution/fetching/ - * https://www.jooq.org/doc/3.12/manual/sql-building/plain-sql-templating/ + *

+ * Jooq programming is more powerful than the mybatis series (mybatis plus), see
+ * fetching and
+ * plain-sql-templating
+ * 
* * @author trydofor * @since 2020-08-14 @@ -92,34 +96,34 @@ public void test1SelectOnDemand() { TstShardingTable t = dao.getTable(); Condition c = t.Id.gt(1L).and(t.Id.le(105L)); - testcaseNotice("1个字段,到List"); + testcaseNotice("1 field to List"); List ones = ctx.select(t.Id) .from(t) .where(c) .fetch() .into(Long.class); - testcaseNotice("1个字段,到List"); + testcaseNotice("1 field to object"); Long one = ctx.select(t.Id) .from(t) .where(t.Id.lt(0L)) .fetchOneInto(Long.class); Assertions.assertNull(one); - testcaseNotice("2个字段,到Map"); + testcaseNotice("2 fields to Map"); Map maps = ctx.select(t.Id, t.LoginInfo) .from(t) .where(c) .fetch() .intoMap(t.Id, t.LoginInfo); - testcaseNotice("分组Pojo到Map"); + testcaseNotice("group Pojo to Map"); Map> grps = ctx.selectFrom(t) - .where(c) - .fetch() - .intoGroups(t.Id, dao.mapper()); + .where(c) + .fetch() + .intoGroups(t.Id, dao.mapper()); - testcaseNotice("多个字段到2维数组"); + testcaseNotice("2 fields to 2D array"); Object[][] arrs = ctx.select(t.Id, t.LoginInfo) .from(t) .where(c) @@ -128,14 +132,12 @@ public void test1SelectOnDemand() { } - // 同名 @Data public static class SameName { private Long id; private String loginInfo; } - // 不同名 @Data public static class DiffName { private Long uid; @@ -143,37 +145,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 @@ -182,28 +174,28 @@ public void test2InsertPojo() { TstShardingTable t = dao.getTable(); Condition c = t.Id.gt(1L).and(t.Id.le(105L)); - testcaseNotice("多个字段(同名子集)到List *推荐使用*"); + testcaseNotice("Multiple fields (subsets of the same name) to List *Recommended*"); List sames = ctx.select(t.Id, t.LoginInfo) .from(t) .where(c) .fetch() .into(SameName.class); - testcaseNotice("多个字段(不同名,使用字段别名)到List *推荐使用*"); + testcaseNotice("Multiple fields (with different names, using field aliases) to List *Recommended*"); List alias = ctx.select(t.Id.as("uid"), t.LoginInfo.as("str")) .from(t) .where(c) .fetch() .into(DiffName.class); - testcaseNotice("多个字段(同名子集)到List,使用Mapstruct"); + testcaseNotice("Multiple fields (subsets of the same name) to List, use Mapstruct"); List diffs = ctx.select(t.Id, t.LoginInfo) .from(t) .where(c) .fetch() .map(Record2ToDiffName::into); - testcaseNotice("多个字段(同名子集)到List,使用 lambda"); + testcaseNotice("Multiple fields (subsets of the same name) to List, use lambda"); List lambs = ctx.select(t.Id, t.LoginInfo) .from(t) .where(c) @@ -221,8 +213,8 @@ public void test2InsertPojo() { @Test public void test3MixingSql() { - //////////////////////// 说明部分 //////////////////////// - testcaseNotice("其中的 {0}是,0-base的,直接字符串替换的。使用不当会构成sql注入"); + //////////////////////// description //////////////////////// + testcaseNotice("where {0} is, for 0-base, a direct string replacement. If used incorrectly, this can lead to SQL Injection."); Param count = DSL.val(3); Param string = DSL.val("abc"); DSL.field("replace(substr(quote(zeroblob(({0} + 1) / 2)), 3, {0}), '0', {1})", String.class, count, string); @@ -231,7 +223,7 @@ public void test3MixingSql() { // argument "count" is repeated twice: \------------------+----------|---------------------/ | // argument "string" is used only once: \-----------------------------/ - testcaseNotice("模板中支持,java和sql注释,placeholder和variable-binding"); + testcaseNotice("the template support java and sql comment, placeholder and variable-binding"); DSL.query( "SELECT /* In a comment, this is not a placeholder: {0}. And this is not a bind variable: ? */ title AS `title {1} ?` " + "-- Another comment without placeholders: {2} nor bind variables: ?" + @@ -239,7 +231,7 @@ public void test3MixingSql() { "WHERE title = 'In a string literal, this is not a placeholder: {3}. And this is not a bind variable: ?'" ); - //////////////////////// 执行部分 //////////////////////// + //////////////////////// execution //////////////////////// DSLContext ctx = dao.ctx(); TstShardingTable t = dao.getTable(); @@ -276,7 +268,7 @@ public void test3MixingSql() { public void test3BindSql() { DSLContext ctx = dao.ctx(); - testcaseNotice("按map绑定,或者通过 jackson pojo to map"); + testcaseNotice("Binding by map or jackson pojo to map"); Map bd1 = new HashMap<>(); bd1.put("idMin", 3L); bd1.put("idMax", 105L); @@ -291,10 +283,10 @@ public void test3BindSql() { WingsJooqUtil.bindNamed(bd1)) .into(SameName.class); - // 按数组绑定 + // Binding by array // SELECT id, login_info FROM tst_sharding WHERE id >=? AND id <=? ORDER BY login_info DESC,id LIMIT ?, ? // SELECT id, login_info FROM tst_sharding WHERE id >=4 AND id <=105 ORDER BY login_info DESC,id LIMIT 0, 10 - testcaseNotice("按数组绑定"); + testcaseNotice("Binding by array"); Object[] bd2 = {4L, 105L, 0, 10}; List bv2 = ctx.fetch(""" SELECT id, login_info @@ -304,13 +296,13 @@ public void test3BindSql() { LIMIT {2}, {3}""", bd2) .into(SameName.class); - // 按pojo绑定 + // Binding by pojo SameName bd3 = new SameName(); bd3.setId(5L); bd3.setLoginInfo("LOGIN_INFO-05"); - // 通过record转一下,必须字段同名 - testcaseNotice("按pojo绑定, 通过record转一下,必须字段同名"); + // Convert by record, Must have fields with the same name + testcaseNotice("Binding by pojo, Convert by record, Must have fields with the same name"); TstShardingRecord rc = dao.newRecord(bd3); rc.from(bd3); List bv3 = ctx.fetch(""" @@ -325,10 +317,10 @@ public void test3BindSql() { @Test public void test3DynamicSql() { - // 条件构造,多参加 condition和cond*方法 + // condition and `cond*` TstShardingTable t = dao.getTable(); - // 通过builder建立,对null友好 + // by builder, null friendly Condition d1 = WingsJooqUtil.condition("1=1"); Condition d2 = WingsJooqUtil.condition("2=2"); Condition d3 = WingsJooqUtil.condition("3=3"); @@ -352,7 +344,7 @@ public void test3DynamicSql() { assertEquals(c0.toString(), c1.toString()); - testcaseNotice("通过页面过来的pojo构造and条码"); + testcaseNotice("by passed pojo and `AND`"); SameName bd1 = new SameName(); bd1.setId(105L); bd1.setLoginInfo("LOGIN_INFO-05"); @@ -360,32 +352,32 @@ public void test3DynamicSql() { // where (`id` = ? and `login_info` = ?) // (`id` = 105 and `login_info` = 'LOGIN_INFO-05') - testcaseNotice("通过Record和condChain AND"); + testcaseNotice("by Record and condChain `AND`"); Condition cd1 = WingsJooqUtil.condChain(AND, rc1); List rs1 = dao.fetch(dao.getTable(), cd1); - // 通过页面过来的pojo构造Or条码 + // SameName bd2 = new SameName(); bd2.setId(105L); bd2.setLoginInfo("LOGIN_INFO-06"); TstShardingRecord rc2 = dao.newRecord(bd2); // where (`id` = ? or `login_info` = ?) // where (`id` = 105 or `login_info` = 'LOGIN_INFO-06') - testcaseNotice("通过Record和condChain OR"); + testcaseNotice("by Record and condChain `OR`"); Condition cd2 = WingsJooqUtil.condChain(OR, rc2); List rs2 = dao.fetch(dao.getTable(), cd2); - // 只取id + // only id // where `id` = ? // where `id` = 105 - testcaseNotice("通过Record和condChain 单字段"); + testcaseNotice("by Record and condChain single field"); List cds = WingsJooqUtil.condField(rc2, t.Id); List rs3 = dao.fetch(t, DSL.condition(OR, cds)); - // 通过字符串map构造条件,如用户数据隔离 - testcaseNotice("通过字符串map构造条件,如用户数据隔离"); + // + testcaseNotice("by string-value map"); Map map = new HashMap<>(); - map.put("id", Collections.singletonList(105L));// 当做 in ()处理 + map.put("id", Collections.singletonList(105L));// use `in()` // map.put("id", 105L); map.put("login_info", "LOGIN_INFO-05"); @@ -394,12 +386,12 @@ public void test3DynamicSql() { List rc4 = dao.fetch(t, cd4); // from `tst_sharding` as `y8` where (`y8`.`login_info` = ? and `y8`.`id` = ?) - testcaseNotice("通过字符串map构造条件,别名"); + testcaseNotice("by string-value map, and alias"); TstShardingTable a = dao.getAlias(); Condition cd5 = WingsJooqUtil.condChain(map, true, a); List rc5 = dao.fetch(a, cd5); - // 更新字段,可以直接使用dao.update() + // use dao.update() to update log.info(""); } @@ -409,7 +401,7 @@ public void test3DynamicSql() { @Test public void test4JdbcTemplate() { - // 单字段查询 + // single field select Integer cnt = jdbcTemplate.queryForObject( "SELECT COUNT(1) FROM tst_sharding WHERE id > ?", Integer.class, 1); @@ -442,67 +434,66 @@ public void test5PaginateJooq() { TstShardingTable t2 = dao.getAlias("t2"); // - testcaseNotice("使用helperJooq正常", + testcaseNotice("use helperJooq, normal", "select count(*) from `tst_sharding` as `t1` where `t1`.`id` >= ?", "select `t1`.* from `tst_sharding` as `t1` where `t1`.`id` >= ? order by `id` asc limit ?"); PageQuery page = new PageQuery().setSize(5).setPage(1).setSort("d"); 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; - }); - - testcaseNotice("使用helperJooq简化", - "缓存的total,使页面不执行count操作", + .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("use helperJooq, simple", + "cached total to ignore `select count` in db", "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包装", + testcaseNotice("use helperJooq wrap", "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 - testcaseNotice("包装count", + testcaseNotice("wrap count", "select count(*) as `c` from (select `id` from `tst_sharding` where `id` > ?) as `q`", "select `id` from `tst_sharding` where `id` > ?"); SelectConditionStep> qry1 = dsl.select(t.Id).from(t).where(t.Id.gt(1L)); @@ -511,8 +502,7 @@ public void test5PaginateJooq() { int cnt00 = dsl.fetchCount(qry1); List lst0 = qry.fetch().into(TstSharding.class); - // 单表count - testcaseNotice("单表count", + testcaseNotice("single table count", "select count(*) from `tst_sharding` where `id` > ?"); Integer cnt1 = dsl.selectCount() .from(t) @@ -520,18 +510,17 @@ 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()); - // 联表count // DSL.countDistinct() - testcaseNotice("内联count", + testcaseNotice("joined table count", "select count(`t1`.`id`) from `tst_sharding` as `t1`, `tst_sharding` as `t2` where (`t1`.`id` = `t2`.`id` and `t1`.`id` > ?)"); int cnt2 = dsl.select(DSL.count(t1.Id)) .from(t1, t2) @@ -540,7 +529,7 @@ public void test5PaginateJooq() { .orElse(0); log.info("cnt2={}", cnt2); - testcaseNotice("左联查询", + testcaseNotice("left join", "select count(`t1`.`id`) from `tst_sharding` as `t1` left outer join `tst_sharding` as `t2` on `t1`.`id` = `t2`.`id` where `t1`.`id` > ?"); TableOnConditionStep jt = t1.leftJoin(t2).on(t1.Id.eq(t2.Id)); int cnt3 = dsl.select(DSL.count(t1.Id)) @@ -554,7 +543,7 @@ public void test5PaginateJooq() { @Test public void test5PaginateJdbc() { // - testcaseNotice("使用helperJdbc包装", + testcaseNotice("use helperJdbc wrap", "SELECT count(*) FROM (select `t1`.* from `tst_sharding` as `t1` where `t1`.`id` >= ?) WINGS_WRAP", "select `t1`.* from `tst_sharding` as `t1` where `t1`.`id` >= ? order by t1.Id ASC limit 5"); @@ -562,29 +551,29 @@ 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()); - testcaseNotice("使用helperJdbc正常", + testcaseNotice("use helperJdbc normal", "SELECT count(*) from `tst_sharding` where id >= ?", "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()); } - // 同名,自动转换 + // Same name, auto convert @Data public static class EnumDto { private Long id; @@ -603,7 +592,7 @@ public void test6MapperEnum() { .into(EnumDto.class); log.info("sn={}", sn); - // 全局注入的 + // Global injected final List sn2 = dao.ctx() .select(t.Id, t.Language) .from(t) @@ -614,7 +603,7 @@ public void test6MapperEnum() { @Test public void test7Function() { - testcaseNotice("通过DSL,获取特定函数,DSL特别大,各种方言函数都有的", + testcaseNotice("by DSL, get function of dialect", "select `id` from `tst_sharding` where (`modify_dt` > date_add(?, interval ? day) and substring(`other_info`, ?, ?) like ?)"); final TstShardingTable t = dao.getTable(); @@ -627,7 +616,7 @@ public void test7Function() { .getSQL(); log.info("sql1={}", sql1); - testcaseNotice("通过DSL,元组条件查询 https://www.jooq.org/doc/3.14/manual/sql-building/column-expressions/row-value-expressions/", + testcaseNotice("by DSL, query tuple https://www.jooq.org/doc/3.14/manual/sql-building/column-expressions/row-value-expressions/", "select `id` from `tst_sharding` where (`id`, `login_info`) in ((?, ?), (?, ?))"); final List> rw2 = new ArrayList<>(); 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/faceless-jooq/src/test/resources/wings-conf/wings-conf-block-list.cnf b/wings/faceless-jooq/src/test/resources/wings-conf/wings-conf-block-list.cnf index 55ce8a8a2..fb4edc935 100644 --- a/wings/faceless-jooq/src/test/resources/wings-conf/wings-conf-block-list.cnf +++ b/wings/faceless-jooq/src/test/resources/wings-conf/wings-conf-block-list.cnf @@ -1,2 +1,2 @@ -#测试 +## test shardingsphere-sharding-block.properties diff --git a/wings/faceless-jooq/src/test/resources/wings-conf/wings-jooq-cud.properties b/wings/faceless-jooq/src/test/resources/wings-conf/wings-jooq-cud.properties index 7ca159935..d37c8ec13 100644 --- a/wings/faceless-jooq/src/test/resources/wings-conf/wings-jooq-cud.properties +++ b/wings/faceless-jooq/src/test/resources/wings-conf/wings-jooq-cud.properties @@ -1,5 +1,5 @@ spring.wings.faceless.jooq.enabled.listen-table-cud=true -# cud 关系的表及字段,区分大小写 +## cud table and fields, case-insensitive wings.faceless.jooq.cud.table[win_user_basis]=id wings.faceless.jooq.cud.table[win_user_authn]=id wings.faceless.jooq.cud.table[tst_sharding]=id,login_info diff --git a/wings/faceless-jooq/src/test/resources/wings-conf/wings-test-module.properties b/wings/faceless-jooq/src/test/resources/wings-conf/wings-test-module.properties deleted file mode 100644 index d44b54c3b..000000000 --- a/wings/faceless-jooq/src/test/resources/wings-conf/wings-test-module.properties +++ /dev/null @@ -1 +0,0 @@ -wings.test.module=虚空假面 \ No newline at end of file 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-shard/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessShardingsphereConfiguration.java b/wings/faceless-shard/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessShardingsphereConfiguration.java index 5fd21125a..632c62afe 100644 --- a/wings/faceless-shard/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessShardingsphereConfiguration.java +++ b/wings/faceless-shard/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessShardingsphereConfiguration.java @@ -20,8 +20,7 @@ /** - * 依照 shardingsphere-jdbc-core-spring-boot-starter 配置,构造数据源, - * 如果有多个数据源,使用sharding数据源,同时expose原始出来,可以独立使用。 + * Config sharding datasource to DataSourceContext * * @author trydofor */ diff --git a/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/flywave/FlywaveShardingTest.java b/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/flywave/FlywaveShardingTest.java index d7fa8fd0e..2e7949b58 100644 --- a/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/flywave/FlywaveShardingTest.java +++ b/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/flywave/FlywaveShardingTest.java @@ -53,7 +53,7 @@ public void test1Single() { assertEquals(20, countRecords("writer", "tst_sharding")); assertEquals(0, countRecords("reader", "tst_sharding")); - testcaseNotice("在writer强制插入数据,用SQL查询,只有writer有数据,reader上没有"); + testcaseNotice("Force to insert data in the writer. Select by SQL, only the writer has the data, not the reader."); } @Test @@ -62,7 +62,7 @@ public void test2Sharding() { wingsTestHelper.assertHas(WingsTestHelper.Type.Table, "sys_schema_journal_0", "sys_schema_journal_1"); schemaShardingManager.publishShard("sys_schema_journal", 0); wingsTestHelper.assertNot(WingsTestHelper.Type.Table, "sys_schema_journal_0", "sys_schema_journal_1"); - testcaseNotice("writer 和reader上,同时多出2个sys_schema_journal_[0-1]表"); + testcaseNotice("2 sys_schema_journal_[0-1] tables on writer and reader at the same time"); } @Test @@ -76,18 +76,18 @@ public void test3ShardMove() { "tst_sharding_4"); assertEquals(20, countRecords("writer", "tst_sharding")); schemaShardingManager.shardingData("tst_sharding", true); - // 主表移除 - assertEquals(0, countRecords("writer", "tst_sharding"), "如果失败,单独运行整个类,消除分表干扰"); - // 分表平分 + // delete from main table + assertEquals(0, countRecords("writer", "tst_sharding"), "If it fails, run the entire class individually to avoid interference."); + // insert into the sharding by hash assertEquals(4, countRecords("writer", "tst_sharding_0")); assertEquals(4, countRecords("writer", "tst_sharding_1")); assertEquals(4, countRecords("writer", "tst_sharding_2")); assertEquals(4, countRecords("writer", "tst_sharding_3")); assertEquals(4, countRecords("writer", "tst_sharding_4")); - // 5.x起,shardingsphere 会 SELECT count(*) FROM tst_sharding_0 UNION ALL SELECT count(*) FROM tst_sharding_# + // form 5.x, shardingsphere will SELECT count(*) FROM tst_sharding_0 UNION ALL SELECT count(*) FROM tst_sharding_# Integer cnt = shardingJdbcTemplate.queryForObject("SELECT count(*) FROM tst_sharding", Integer.class); -// testcaseNotice("writer和reader实际未配置同步,所以从库读取为0"); +// testcaseNotice("The writer and reader are not actually configured to synchronize, so reading from db is 0"); assertEquals(20, cnt); } diff --git a/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java b/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java index f0b390dbf..a46380aff 100644 --- a/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java +++ b/wings/faceless-shard/src/test/java/pro/fessional/wings/faceless/jooq/JooqShardingTest.java @@ -88,11 +88,11 @@ public void test4InsertSeeLog() { dao.insert(rd); testcaseNotice( - "==== 检查 sql 日志 ====", + "==== check sql log ====", "[OK] insert into `tst_sharding_0` (`ID`, `CREATE_DT`, `MODIFY_DT`, `COMMIT_ID`, `LOGIN_INFO`, `OTHER_INFO`) values (?, ?, ?, ?, ?, ?)", "[NG] insert into `tst_sharding` as `t1` (`ID`, `CREATE_DT`, `MODIFY_DT`, `COMMIT_ID`, `LOGIN_INFO`, `OTHER_INFO`) values (?, ?, ?, ?, ?, ?)" ); -// dsl.newRecord(TstShardingTable.TST_中文也分表, rd).insert() +// dsl.newRecord(TstShardingTable.TstSharding, rd).insert() } @Test @@ -127,10 +127,10 @@ public void test5UpdateSeeLog() { testcaseNotice( - "==== 检查 sql 日志 ====", + "==== check sql log ====", "[OK] update `tst_sharding` set `MODIFY_DT` = ?, `LOGIN_INFO` = ? where `ID` <= ?", "[OK] update `tst_sharding` as `t1` set `t1`.`MODIFY_DT` = ?, `t1`.`LOGIN_INFO` = ? where `t1`.`ID` <= ?", - "[NG] update `tst_sharding` set `TST_中文也分表`.`MODIFY_DT` = ?, `TST_中文也分表`.`LOGIN_INFO` = ? where `TST_中文也分表`.`ID` <= ?" + "[NG] update `tst_sharding` set `tst_sharding`.`MODIFY_DT` = ?, `tst_sharding`.`LOGIN_INFO` = ? where `tst_sharding`.`ID` <= ?" ); } @@ -164,7 +164,7 @@ public void test6SelectSeeLog() { testcaseNotice("select `y8`.`id`, `y8`.`create_dt`, ... from `tst_sharding` as `y8` where `y8`.`id` = ?"); testcaseNotice( - "==== 检查 sql 日志 ====", + "==== check sql log ====", "[OK] select `ID` from `tst_sharding` where `ID` <= ? limit ?", "[OK] select `t1`.`ID` from `tst_sharding` as `t1` where `t1`.`ID` <= ? limit ?", "[NG] select `tst_sharding`.`ID` from `tst_sharding` where `tst_sharding`.`ID` <= ? limit ?" @@ -189,7 +189,7 @@ public void test7DeleteSeeLog() { testcaseNotice("delete from `tst_sharding_3` where `id` = ? "); testcaseNotice( - "==== 检查 sql 日志 ====", + "==== check sql log ====", "[OK] delete from `tst_sharding` where `ID` <= ?", "[NG] delete from `tst_sharding` where `tst_sharding`.`ID` <= ?", "[NG] delete `t1` from `tst_sharding` as `t1` where `t1`.`ID` <= ?" @@ -202,19 +202,19 @@ public void test7DeleteSeeLog() { @Test public void test8BatchSeeLog() { val rds = Arrays.asList( - new TstShardingRecord(119L, now, now, now, 9L, "批量合并119", "test8", ZH_CN.getId()), - new TstShardingRecord(308L, now, now, now, 9L, "批量合并308", "test8", ZH_CN.getId()), - new TstShardingRecord(309L, now, now, now, 9L, "批量合并309", "test8", ZH_CN.getId()) + new TstShardingRecord(119L, now, now, now, 9L, "Batch merge 119", "test8", ZH_CN.getId()), + new TstShardingRecord(308L, now, now, now, 9L, "Batch merge 308", "test8", ZH_CN.getId()), + new TstShardingRecord(309L, now, now, now, 9L, "Batch merge 309", "test8", ZH_CN.getId()) ); - testcaseNotice("批量Insert,查看日志,ignore, 分2批次, 119 ignore; 308,309 insert"); + testcaseNotice("Batch Insert, check log, ignore in 2 batch, 119 ignore; 308, 309 insert"); val rs1 = dao.batchInsert(rds, 2, true); Assertions.assertArrayEquals(new int[]{1, 1, 1}, rs1); - testcaseNotice("先select在insert 310,或update 308,309"); + testcaseNotice("select first, then insert 310, or update 308, 309"); val rs3 = dao.batchMerge(tbl, new Field[]{tbl.Id}, Arrays.asList( - new TstShardingRecord(310L, now, now, now, 9L, "批量合并310", "其他310", ZH_CN.getId()), - new TstShardingRecord(308L, now, now, now, 9L, "批量合并308", "其他308", ZH_CN.getId()), - new TstShardingRecord(309L, now, now, now, 9L, "批量合并309", "其他309", ZH_CN.getId()) + new TstShardingRecord(310L, now, now, now, 9L, "Batch merge 310", "Other 310", ZH_CN.getId()), + new TstShardingRecord(308L, now, now, now, 9L, "Batch merge 308", "Other 308", ZH_CN.getId()), + new TstShardingRecord(309L, now, now, now, 9L, "Batch merge 309", "Other 309", ZH_CN.getId()) ), 2, tbl.LoginInfo, tbl.OtherInfo); Assertions.assertArrayEquals(new int[]{1, 1, 1}, rs3); } @@ -222,35 +222,35 @@ public void test8BatchSeeLog() { @Test public void test9BatchSeeLog() { val rds = Arrays.asList( - new TstShardingRecord(119L, now, now, now, 9L, "批量加载307", "test9", ZH_CN.getId()), - new TstShardingRecord(318L, now, now, now, 9L, "批量加载318", "test9", ZH_CN.getId()), - new TstShardingRecord(319L, now, now, now, 9L, "批量加载319", "test9", ZH_CN.getId()) + new TstShardingRecord(119L, now, now, now, 9L, "Batch load 307", "test9", ZH_CN.getId()), + new TstShardingRecord(318L, now, now, now, 9L, "Batch load 318", "test9", ZH_CN.getId()), + new TstShardingRecord(319L, now, now, now, 9L, "Batch load 319", "test9", ZH_CN.getId()) ); - testcaseNotice("批量Insert,查看日志, replace 119, new318,319,分2批,replace into"); + testcaseNotice("Batch Insert, check log, replace 119, new318,319, in 2 batch, replace into"); try { val rs2 = dao.batchInsert(rds, 2, false); log.info(Arrays.toString(rs2)); Assertions.assertArrayEquals(new int[]{2, 1, 1}, rs2); } catch (Exception e) { - testcaseNotice("Sharding 不支持,replace into https://github.com/apache/shardingsphere/issues/5330"); + testcaseNotice("Sharding unsupported, replace into https://github.com/apache/shardingsphere/issues/5330"); e.printStackTrace(); } - testcaseNotice("批量Merge,查看日志, new 320, on dupkey 318,319,分2批,duplicate"); + testcaseNotice("Batch Merge, check log, new 320, on dupkey 318,319, in 2 batch, duplicate"); testcaseNotice("insert into `tst_sharding` (`id`, .., `other_info`) values (?,..., ?) on duplicate key update `login_info` = ?, `other_info` = ?"); try { val rs3 = dao.batchMerge(tbl, Arrays.asList( - new TstShardingRecord(320L, now, now, now, 9L, "批量合并320", "其他320", ZH_CN.getId()), - new TstShardingRecord(318L, now, now, now, 9L, "批量合并318", "其他318", ZH_CN.getId()), - new TstShardingRecord(319L, now, now, now, 9L, "批量合并319", "其他319", ZH_CN.getId()) + new TstShardingRecord(320L, now, now, now, 9L, "Batch merge 320", "Other 320", ZH_CN.getId()), + new TstShardingRecord(318L, now, now, now, 9L, "Batch merge 318", "Other 318", ZH_CN.getId()), + new TstShardingRecord(319L, now, now, now, 9L, "Batch merge 319", "Other 319", ZH_CN.getId()) ), 2, tbl.LoginInfo, tbl.OtherInfo); log.info(Arrays.toString(rs3)); Assertions.assertArrayEquals(new int[]{1, 2, 2}, rs3); } catch (Exception e) { - testcaseNotice("Sharding 不支持,on duplicate key update https://github.com/apache/shardingsphere/issues/5210"); - testcaseNotice("Sharding 不支持,https://github.com/apache/shardingsphere/pull/5423"); + testcaseNotice("Sharding unsupported, on duplicate key update https://github.com/apache/shardingsphere/issues/5210"); + testcaseNotice("Sharding unsupported, https://github.com/apache/shardingsphere/pull/5423"); e.printStackTrace(); } } diff --git a/wings/faceless-shard/src/test/resources/wings-conf/shardingsphere-datasource.properties b/wings/faceless-shard/src/test/resources/wings-conf/shardingsphere-datasource.properties index 2ad0b6338..298c7c4ee 100644 --- a/wings/faceless-shard/src/test/resources/wings-conf/shardingsphere-datasource.properties +++ b/wings/faceless-shard/src/test/resources/wings-conf/shardingsphere-datasource.properties @@ -1,5 +1,5 @@ -# https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/yaml-config/jdbc-driver/spring-boot/ -# 配置 DataSource Driver +## https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/yaml-config/jdbc-driver/spring-boot/ +## set DataSource Driver spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver -# 指定 YAML 配置文件 +## set YAML config spring.datasource.url=jdbc:shardingsphere:classpath:extra-conf/shardingsphere-standalone.yml diff --git a/wings/faceless-shard/src/test/resources/wings-conf/wings-test-module.properties b/wings/faceless-shard/src/test/resources/wings-conf/wings-test-module.properties deleted file mode 100644 index d44b54c3b..000000000 --- a/wings/faceless-shard/src/test/resources/wings-conf/wings-test-module.properties +++ /dev/null @@ -1 +0,0 @@ -wings.test.module=虚空假面 \ No newline at end of file diff --git a/wings/faceless-test/src/main/java/pro/fessional/wings/faceless/WingsTestHelper.java b/wings/faceless-test/src/main/java/pro/fessional/wings/faceless/WingsTestHelper.java index 07afe47ca..a479adeb1 100644 --- a/wings/faceless-test/src/main/java/pro/fessional/wings/faceless/WingsTestHelper.java +++ b/wings/faceless-test/src/main/java/pro/fessional/wings/faceless/WingsTestHelper.java @@ -81,16 +81,16 @@ public void assertSame(Type type, String... str) { fetchAllColumn1(type.sql).forEach((k, aSet) -> { Diff.S diff = Diff.of(aSet, bSet); if (!diff.bNotA.isEmpty()) { - testcaseNotice(k + " 数据库少:" + type + ":" + String.join(",", diff.bNotA)); + testcaseNotice(k + " less in db " + type + ":" + String.join(",", diff.bNotA)); good.set(false); } if (!diff.aNotB.isEmpty()) { - testcaseNotice(k + " 数据库多:" + type + ":" + String.join(",", diff.aNotB)); + testcaseNotice(k + " more in db " + type + ":" + String.join(",", diff.aNotB)); good.set(false); } }); - Assertions.assertTrue(good.get(), type.name() + "不一致,查看日志,"); + Assertions.assertTrue(good.get(), type.name() + " difference, check the logs."); } public void assertHas(Type type, String... str) { @@ -99,12 +99,12 @@ public void assertHas(Type type, String... str) { fetchAllColumn1(type.sql).forEach((k, aSet) -> { Diff.S diff = Diff.of(aSet, bSet); if (!diff.bNotA.isEmpty()) { - testcaseNotice(k + " 数据库少:" + type + ":" + String.join(",", diff.bNotA)); + testcaseNotice(k + " less in db " + type + ":" + String.join(",", diff.bNotA)); good.set(false); } }); - Assertions.assertTrue(good.get(), type.name() + "不一致,查看日志,"); + Assertions.assertTrue(good.get(), type.name() + " difference, check the logs."); } public void assertNot(Type type, String... str) { @@ -113,12 +113,12 @@ public void assertNot(Type type, String... str) { fetchAllColumn1(type.sql).forEach((k, aSet) -> { Diff.S diff = Diff.of(aSet, bSet); if (diff.bNotA.size() != bSet.size()) { - testcaseNotice(k + " 数据库不能有:" + type + ": " + String.join(",", diff.bNotA)); + testcaseNotice(k + " cant in db " + type + ":" + String.join(",", diff.bNotA)); good.set(false); } }); - Assertions.assertTrue(good.get(), type.name() + "不一致,查看日志,"); + Assertions.assertTrue(good.get(), type.name() + " difference, check the logs."); } private List lowerCase(String... str) { diff --git a/wings/faceless-test/src/main/resources/wings-conf/spring-wings-enabled.properties b/wings/faceless-test/src/main/resources/wings-conf/spring-wings-enabled.properties index e6677aa80..396822964 100644 --- a/wings/faceless-test/src/main/resources/wings-conf/spring-wings-enabled.properties +++ b/wings/faceless-test/src/main/resources/wings-conf/spring-wings-enabled.properties @@ -1,5 +1,5 @@ -# 是否注入flywave。 +## whether to inject Flywave related beans. spring.wings.faceless.flywave.enabled.module=true -# db执行delete且有commit_id时,先执行update再delete +## when deleting with commit_id, whether to update first and then delete. spring.wings.faceless.jooq.enabled.journal-delete=true diff --git a/wings/faceless-test/src/main/resources/wings-conf/wings-conf-block-list.cnf b/wings/faceless-test/src/main/resources/wings-conf/wings-conf-block-list.cnf index 55ce8a8a2..86a664c50 100644 --- a/wings/faceless-test/src/main/resources/wings-conf/wings-conf-block-list.cnf +++ b/wings/faceless-test/src/main/resources/wings-conf/wings-conf-block-list.cnf @@ -1,2 +1,2 @@ -#测试 +## testing shardingsphere-sharding-block.properties diff --git a/wings/faceless-test/src/main/resources/wings-conf/wings-test-module.properties b/wings/faceless-test/src/main/resources/wings-conf/wings-test-module.properties new file mode 100644 index 000000000..9d487568c --- /dev/null +++ b/wings/faceless-test/src/main/resources/wings-conf/wings-test-module.properties @@ -0,0 +1 @@ +wings.test.module=Faceless \ No newline at end of file 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/faceless/src/main/java/pro/fessional/wings/faceless/concur/DatabaseGlobalLock.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/concur/DatabaseGlobalLock.java index dd6304195..f8ce3bc8d 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/concur/DatabaseGlobalLock.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/concur/DatabaseGlobalLock.java @@ -8,8 +8,8 @@ import java.util.concurrent.locks.Lock; /** - * 基于DataSource的全局锁,用于数据库级锁。 - * 当读写分离或分库时,注意数据源切换。 + * DataSource-based global lock for database-level locking. + * When read/write separation or data sharding, pay attention to data source switching. * * @author trydofor * @since 2021-03-08 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/concur/MysqlServerLock.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/concur/MysqlServerLock.java index f4a191119..78520830a 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/concur/MysqlServerLock.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/concur/MysqlServerLock.java @@ -11,8 +11,7 @@ import java.util.concurrent.locks.Lock; /** - * 基于Mysql IS_FREE_LOCK和GET_LOCK 实现的锁。 - * mysql实例级,跨实例中的数据库。 + * Locks based on the Mysql IS_FREE_LOCK and GET_LOCK at mysql instance level. * * @author trydofor * @since 2021-03-08 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptySugar.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptySugar.java index c2d5b747c..f1143e753 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptySugar.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptySugar.java @@ -7,7 +7,7 @@ import java.util.function.Consumer; /** - * isXxx为精确比较,asXxx为范围比较 + * `isXxx` for exact comparison, `asXxx` for range comparison * * @author trydofor * @since 2019-05-13 @@ -80,7 +80,7 @@ public static boolean asEmptyValue(BigDecimal v) { } /** - * 考虑时区,±24H + * Consider time zone, ±24H */ public static boolean asEmptyValue(LocalDate v) { return v == null || @@ -90,7 +90,7 @@ public static boolean asEmptyValue(LocalDate v) { } /** - * 仅比较时分秒,不考虑秒以下时间 + * Compare hour, minute and second only, without the time below the second */ public static boolean asEmptyValue(LocalTime v) { return v == null || @@ -100,7 +100,7 @@ public static boolean asEmptyValue(LocalTime v) { } /** - * 仅比较日期,不比较时间 + * Compare date only, without time */ public static boolean asEmptyValue(LocalDateTime v) { return v == null || asEmptyValue(v.toLocalDate()); diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptyValue.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptyValue.java index 9b95d43eb..85e1186e1 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptyValue.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/convention/EmptyValue.java @@ -42,7 +42,7 @@ private EmptyValue() { public static final LocalDateTime DATE_TIME_AS_MAX = LocalDateTime.of(DATE_AS_MAX, TIME); - // 可以外部赋值,以改变asEmptyValue的范围 + // Can be assigned externally to change the scope of asEmptyValue public static double DOUBLE_AS_MIN = -0.00001D; public static double DOUBLE_AS_MAX = 0.00001D; public static double FLOAT_AS_MIN = -0.00001F; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/converter/WingsConverter.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/converter/WingsConverter.java index 401f87a60..25e01733b 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/converter/WingsConverter.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/converter/WingsConverter.java @@ -4,7 +4,7 @@ import pro.fessional.mirana.cast.BiConvertor; /** - * Source和Target的双向转换 + * Bidirectional conversion of Source and Target * * @author trydofor * @since 2021-01-17 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/DataSourceContext.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/DataSourceContext.java index 9c13815bd..e702440a2 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/DataSourceContext.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/DataSourceContext.java @@ -55,9 +55,7 @@ public DataSourceContext addBackend(Map map) { } /** - * 获得所有原始数据源 - * - * @return 数据源 + * Get all DataSource and its name */ @NotNull public Map getBackends() { @@ -65,10 +63,7 @@ public Map getBackends() { } /** - * 根据数据源名字获得jdbc url - * - * @param name 名字 - * @return jdbc url + * Get the jdbc-url by the name of datasource */ @NotNull public String backendJdbcUrl(String name) { @@ -76,10 +71,7 @@ public String backendJdbcUrl(String name) { } /** - * 根据数据源获得jdbc url,放入缓存 - * - * @param ds 数据源 - * @return jdbc url + * Obtain the jdbc-url by the name of datasource, and cache it */ @NotNull public String cacheJdbcUrl(DataSource ds) { @@ -87,10 +79,7 @@ public String cacheJdbcUrl(DataSource ds) { } /** - * 提取数据源的jdbc url,不放入缓存 - * - * @param ds 数据源 - * @return jdbc url + * Extract the jdbc-url of the data source, not cached */ @NotNull public static String extractUrl(DataSource ds) { @@ -123,10 +112,7 @@ public String toString() { public interface Customizer { /** - * 修改 context,是否停止其他modifier修改 - * - * @param ctx context - * @return 是否排他 + * Modify the context, return whether to stop other modifiers from modifying. */ boolean customize(DataSourceContext ctx); } diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/DatabaseChecker.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/DatabaseChecker.java index d4420c8a9..fc5754044 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/DatabaseChecker.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/DatabaseChecker.java @@ -24,8 +24,8 @@ import static java.time.ZoneOffset.UTC; /** - * mysql服务器,程序,会话时区 - * time-zone-variables + * check the timezone between mysql server, application, and jdbc session. + * see time-zone-variables * * @author trydofor * @since 2021-04-14 @@ -34,14 +34,14 @@ public class DatabaseChecker { /** - * 自动释放链接 + * Whether in H2database */ public static boolean isH2(DataSource ds) { return extractJdbcUrl(ds).contains(":h2:"); } /** - * 自动释放链接 + * Extract the jdbc-url from the datasource */ @SneakyThrows @NotNull @@ -58,18 +58,21 @@ public static String extractJdbcUrl(DataSource ds) { } /** - * 自动释放链接 + * Check timezone with off=5, fail=true + * + * @see #timezone(DataSource, int, boolean) */ public static void timezone(DataSource ds) { timezone(ds, 5, true); } /** - * 自动释放链接,判断数据库和jvm时差,根据其绝对值的最大值,判断是否终止或log + * Check the database and jvm time difference, + * in the absolute `off` second, throw or log as ERROR if `fail`. * * @param ds datasource - * @param off 时差绝对值的最大值 - * @param fail IllegalStateException还是仅log.ERROR + * @param off max abs time tolerance in second + * @param fail throw IllegalStateException or log.ERROR */ public static void timezone(DataSource ds, int off, boolean fail) { if (isH2(ds)) { @@ -134,7 +137,7 @@ public static void timezone(DataSource ds, int off, boolean fail) { } /** - * 自动释放链接,在日志中输出数据库版本信息 + * output the database version in the log */ public static void version(DataSource ds) { @@ -157,7 +160,8 @@ public static void version(DataSource ds) { } /** - * 手工关闭链接,SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=? AND TABLE_SCHEMA=SCHEMA() + * Whether the table exist, need close the `conn` manually. + * `SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=? AND TABLE_SCHEMA=SCHEMA()` * current-schema-function */ @SneakyThrows diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/JournalJdbcHelper.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/JournalJdbcHelper.java index f3b102ccf..e98dbff35 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/JournalJdbcHelper.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/JournalJdbcHelper.java @@ -17,8 +17,8 @@ import java.util.function.Function; /** - * 对数据库进行journal操作的助手类,表必须有 delete_dt和commit_id 字段。 - * delete前,先更新commit_id=?和delete_dt=NOW(3),然后真正delete + * Helper for journal operations on databases, table must have delete_dt and commit_id columns. + * Before deleting, update commit_id=? and delete_dt=NOW(3), and then actually delete the record. * * @author trydofor * @since 2019-09-28 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/PageJdbcHelper.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/PageJdbcHelper.java index 72abb2102..555f8bada 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/PageJdbcHelper.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/helper/PageJdbcHelper.java @@ -16,15 +16,16 @@ import java.util.Map; /** - * 提供基于jdbc和jooq的分页查询工具。 *
- * total < 0,DB执行count和select
- * total = 0,DB不count,不select
- * total > 0,DB不count,但select
+ * Pagination Util for jdbc and jooq.
+ *
+ * * total < 0 - run count, run select
+ * * total = 0 - no count, no select
+ * * total > 0 - no count, run select
  * 
* * @author trydofor - * @link https://blog.jooq.org/2019/09/19/whats-faster-count-or-count1/ + * @link whats-faster-count-or-count1 * @since 2020-09-30 */ public class PageJdbcHelper { @@ -35,12 +36,11 @@ public static CountJdbc use(JdbcTemplate tpl, PageQuery page) { } /** - * 分页查询 + * Page query by jdbc * - * @param tpl dsl - * @param page 页 - * @param total service层缓存的count计数 - * @return 结果 + * @param tpl jdbc template + * @param page query info + * @param total the count cached in service level */ @NotNull public static CountJdbc use(JdbcTemplate tpl, PageQuery page, int total) { @@ -204,7 +204,7 @@ private static class ContextJdbc { private int total = -1; /** - * 以 PageQuery.sort为主,以bys作为映射,以dft作为default为辅 + * `PageQuery.sort` as the primary, `bys` as the mapping, and `dft` as the default */ private void orderBy(Map bys, String... dft) { final List srt = PageUtil.sort(page.getSort()); @@ -274,7 +274,7 @@ public BindJdbc1 orderNone() { } /** - * 指定字段或排序语句,等效于field:field的map关系 + * Specify a field or sort statement that is equivalent to the field to field mapping. */ public BindJdbc1 order(String bys) { context.orderBy(Collections.emptyMap(), bys); @@ -282,7 +282,7 @@ public BindJdbc1 order(String bys) { } /** - * 根据alias:filed的map关系,到PageQuery的sort中匹配排序 + * Based on the mapping of alias to filed, use PageQuery's sort to match the ordering */ public BindJdbc1 order(Map bys, String... dft) { context.orderBy(bys, dft); diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/CouplingModify.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/CouplingModify.java index 680523f4f..fb81ed4fa 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/CouplingModify.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/CouplingModify.java @@ -7,7 +7,8 @@ import java.lang.annotation.Target; /** - * 耦合变更,多表或关联多表,一般为join查询或子查询,包名以主表命名 + * Coupling modify, multiple tables or associated multiple tables. + * Generally join query or sub-query, package should be named by the main table * * @author trydofor * @since 2023-02-03 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/package-info.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/package-info.java index f122687a1..c2c7f02e1 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/package-info.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/modify/package-info.java @@ -1,7 +1,7 @@ /** - * 增,改,删 + * Create, Update, Delete * * @author trydofor * @since 2019-09-17 */ -package pro.fessional.wings.faceless.database.manual.couple.modify; \ No newline at end of file +package pro.fessional.wings.faceless.database.manual.couple.modify; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/package-info.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/package-info.java index 141eb63a9..152d8b269 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/package-info.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/package-info.java @@ -1,7 +1,7 @@ /** - * 表示多表,一般为join查询或子查询,包名以主表命名 + * Multiple tables. Generally join query or sub-query, package should be named by the main table * * @author trydofor * @since 2019-09-17 */ -package pro.fessional.wings.faceless.database.manual.couple; \ No newline at end of file +package pro.fessional.wings.faceless.database.manual.couple; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/CouplingSelect.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/CouplingSelect.java index 6dea91e30..8f01ac222 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/CouplingSelect.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/CouplingSelect.java @@ -7,7 +7,8 @@ import java.lang.annotation.Target; /** - * 耦合查询,多表或关联多表,一般为join查询或子查询,包名以主表命名 + * Coupling select, multiple tables or associated multiple tables. + * Generally join query or sub-query, package should be named by the main table * * @author trydofor * @since 2023-02-03 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/package-info.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/package-info.java index 5b4100796..2f242f17b 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/package-info.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/couple/select/package-info.java @@ -1,7 +1,7 @@ /** - * 查 + * Select * * @author trydofor * @since 2019-09-17 */ -package pro.fessional.wings.faceless.database.manual.couple.select; \ No newline at end of file +package pro.fessional.wings.faceless.database.manual.couple.select; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/lightsequence/impl/LightSequenceModifyJdbc.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/lightsequence/impl/LightSequenceModifyJdbc.java index 6f2f7ed03..a773ba03b 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/lightsequence/impl/LightSequenceModifyJdbc.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/lightsequence/impl/LightSequenceModifyJdbc.java @@ -49,7 +49,7 @@ public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException ps.setLong(1, obj.getNextVal() + obj.getStepVal()); ps.setInt(2, block); ps.setString(3, obj.getSeqName()); - ps.setLong(4, obj.getLastVal()); + ps.setLong(4, obj.getOldNext()); } @Override diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/package-info.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/package-info.java index c4cb2b6f8..a0389d52d 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/package-info.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/modify/package-info.java @@ -1,7 +1,7 @@ /** - * 增,改,删 + * Create, Update, Delete * * @author trydofor * @since 2019-09-17 */ -package pro.fessional.wings.faceless.database.manual.single.modify; \ No newline at end of file +package pro.fessional.wings.faceless.database.manual.single.modify; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/package-info.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/package-info.java index a1316ae61..8337fd2a9 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/package-info.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/package-info.java @@ -1,7 +1,7 @@ /** - * 表示单表,可含简单的条件子查询,一个包名一个表。 + * Single tables or can contain simple conditional sub-query, one table for one package. * * @author trydofor * @since 2019-09-17 */ -package pro.fessional.wings.faceless.database.manual.single; \ No newline at end of file +package pro.fessional.wings.faceless.database.manual.single; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/LightSequenceSelect.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/LightSequenceSelect.java index cce018afb..5375d80c3 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/LightSequenceSelect.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/LightSequenceSelect.java @@ -4,9 +4,10 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.Optional; /** @@ -17,9 +18,18 @@ public interface LightSequenceSelect { @Data class NextStep { + /** + * current next value + */ private long nextVal; + /** + * suggest step + */ private int stepVal; - private long lastVal; + /** + * next value for update + */ + private long oldNext; public NextStep() { } @@ -28,14 +38,15 @@ public NextStep(long nextVal, int stepVal) { this(nextVal, stepVal, nextVal); } - public NextStep(long nextVal, int stepVal, long lastVal) { + public NextStep(long nextVal, int stepVal, long oldNext) { this.nextVal = nextVal; this.stepVal = stepVal; - this.lastVal = lastVal; + this.oldNext = oldNext; } } - Optional selectOneLock(int block, String name); + @Nullable + NextStep selectOneLock(int block, @NotNull String name); @Data @EqualsAndHashCode(callSuper = true) diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/impl/LightSequenceSelectJdbc.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/impl/LightSequenceSelectJdbc.java index 5da527c6d..fa11b2757 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/impl/LightSequenceSelectJdbc.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/lightsequence/impl/LightSequenceSelectJdbc.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; @@ -12,7 +14,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -33,22 +34,23 @@ public class LightSequenceSelectJdbc implements LightSequenceSelect { NextStep one = new NextStep(); final long nextVal = rs.getLong("next_val"); one.setNextVal(nextVal); - one.setLastVal(nextVal); + one.setOldNext(nextVal); one.setStepVal(rs.getInt("step_val")); return one; }; @Override - public Optional selectOneLock(int block, String name) { + @Nullable + public NextStep selectOneLock(int block, @NotNull String name) { List list = jdbcTemplate.query(selectOne, mapperNextStep, block, name); int size = list.size(); if (size == 0) { - return Optional.empty(); + return null; } else if (size == 1) { final NextStep st = list.get(0); final NameNextStep ad = checkTableAndAdjust(st, name); - return Optional.of(ad == null ? st : ad); + return ad == null ? st : ad; } else { throw new IllegalStateException("find " + size + " records, block=" + block + ", name=" + name); @@ -107,12 +109,7 @@ private NameNextStep checkTableAndAdjust(NextStep step, String name) { }); final long cur = step.getNextVal(); - if (dbMax < cur) { - if (dbMax > 0) { - log.info("LightSequence current, name={}, cur={}, db={}", name, cur, dbMax); - } - return null; - } + if (dbMax < cur) return null; final long nid = AnyIntegerUtil.next64(dbMax, 10); log.warn("LightSequence adjust, name={}, cur={}, db={}, to={}", name, cur, dbMax, nid); @@ -120,8 +117,8 @@ private NameNextStep checkTableAndAdjust(NextStep step, String name) { final NameNextStep nn = new NameNextStep(); nn.setSeqName(name); nn.setNextVal(nid); - nn.setLastVal(step.getLastVal()); nn.setStepVal(step.getStepVal()); + nn.setOldNext(step.getOldNext()); return nn; } diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/package-info.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/package-info.java index 70fefa57a..640beb86a 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/package-info.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/database/manual/single/select/package-info.java @@ -1,7 +1,7 @@ /** - * 查 + * Select * * @author trydofor * @since 2019-09-17 */ -package pro.fessional.wings.faceless.database.manual.single.select; \ No newline at end of file +package pro.fessional.wings.faceless.database.manual.single.select; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnum.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnum.java index 5f25c8515..3011baa91 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnum.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnum.java @@ -3,12 +3,14 @@ import org.jetbrains.annotations.NotNull; /** - * sys_constant_enum 常量枚举,方便编程,要与数据库一致。 - *

- * SUPER,是约定值,代表本组,特征有3个 - * - 名字为SUPER - * - id以00结尾 - * - code为id或code + *

+ * Constant enum for programming convenience, to be consistent with sys_constant_enum in database.
+ *
+ * `SUPER`, a convention value, represents this group and has 3 characteristics
+ * - the name is `SUPER`
+ * - `id` ends with `00`
+ * - `code` is either `id` or `code`
+ * 
* * @author trydofor * @since 2019-09-17 @@ -16,16 +18,14 @@ public interface ConstantEnum { /** - * id:动态9位数起,静态8位以下;建议3-2-2分段(表-组-值);00结尾为SUPER - * - * @return id + * dynamic id has 9+ digits, static id has 8 digits; + * `3-2-2` segmentation recommended (table-group-value); + * `00` ending is SUPER */ int getId(); /** - * enum分组:相同type为同一Enum,自动Pascal命名 - * - * @return type + * Enum grouping: same type for same enum, auto Pascal naming */ @NotNull default String getType() { @@ -38,19 +38,14 @@ default String getInfo() { } /** - * 00 结尾的是组别 - * - * @return 是否00结尾 + * Whether end with `00` */ default boolean isSuper() { return getId() % 100 == 0; } /** - * 是否统一组别 - * - * @param id 其他id - * @return 是否统一组别 + * Whether in same Super (same group) */ default boolean sameSuper(long id) { return id % 100 == getId() % 100; @@ -65,9 +60,7 @@ default int getSuperId() { } /** - * 是否为标致的数据库兼容的x-2-2格式 - * - * @return 是否标准 + * Whether a standard database compatible x-2-2 format */ default boolean isStandard() { return getId() >= 100_00_00; diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnumUtil.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnumUtil.java index d24c432bd..e0f943d50 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnumUtil.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/ConstantEnumUtil.java @@ -219,7 +219,7 @@ public static boolean idIn(Integer id, T... es) { } /** - * 根据el.info中的最后一个`:`的字符串相等分组,如果没有则全字符串相等 + * Group by the string after the last `:` in `el.info`, or the string if no `:` */ @SafeVarargs @NotNull @@ -233,9 +233,9 @@ public static List groupInfo(T el, T... es) { }; /** - * 根据.info分组 + * Group by `el.info` * - * @param fun 如何提前info的字符串 + * @param fun provide the grouping string */ @SafeVarargs @NotNull diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardI18nEnum.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardI18nEnum.java index 175ee0779..bffcd4093 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardI18nEnum.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardI18nEnum.java @@ -4,7 +4,7 @@ import pro.fessional.mirana.data.CodeEnum; /** - * 支持 sys_standard_i18n 的枚举类 + * Enum from sys_standard_i18n table. * * @author trydofor * @since 2019-09-17 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardLanguageEnum.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardLanguageEnum.java index 7cf388dce..c211624d6 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardLanguageEnum.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/enums/StandardLanguageEnum.java @@ -8,7 +8,7 @@ */ public interface StandardLanguageEnum extends ConstantEnum, StandardI18nEnum { /** - * 转变为Locale + * Get or Convert to Locale */ Locale toLocale(); } diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/FlakeIdService.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/FlakeIdService.java index a9d469c63..8cf4872f8 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/FlakeIdService.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/FlakeIdService.java @@ -10,20 +10,16 @@ public interface FlakeIdService { /** - * 按Jooq的Table命名获得,去掉结尾的`Table`后缀,按 小写_小写命名。 - * - * @param table 标记的对象 - * @return id + * Get FlackId by Table name. + * eg. in Jooq, removing the `Table` suffix at the end + * and naming it in lowercase_lowercase. */ default long getId(@NotNull LightIdAware table) { return getId(table.getSeqName()); } /** - * 按名字获得id,不区分大小写,默认全小写。 - * - * @param name 名字 - * @return id + * Get FlackId by name, may be case-insensitive, all lowercase is recommended. */ long getId(@NotNull String name); } diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/impl/FlakeIdLightIdImpl.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/impl/FlakeIdLightIdImpl.java index e689f91a4..af757d6f9 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/impl/FlakeIdLightIdImpl.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/flakeid/impl/FlakeIdLightIdImpl.java @@ -12,8 +12,8 @@ /** *
  * - 41-bit(ms second)
- * - ① 21-bit(sequence), max=2M/ms
- * - ② 10-bit(block) + 12-bit(sequence), max=4K/ms
+ * - (1) 21-bit(sequence), max=2M/ms
+ * - (2) 10-bit(block) + 12-bit(sequence), max=4K/ms
  * 
* * @author trydofor diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalDiff.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalDiff.java index 98d103765..11887e6b2 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalDiff.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalDiff.java @@ -11,13 +11,18 @@ /** *
- * 对数据表的CUD进行diff。存在以下3种状态,value1表示前值,value2表示后值
- * - insert - value1 == empty, value2 != empty
- * - update - value1 != empty, value2 != empty
- * - delete - value1 != empty, value2 == empty
+ * Perform a diff on the CUD of the table. There are 3 states,
+ * `value1` is the old value and `value2` is the new value
  *
- * 此外,column.size * count == value#.size,可表示多条记录。
- * 生成时携带类型,但JSON序列化后会类型丢失,JSON反序列化时,value仅有String和Number类型
+ * * insert - value1 == empty, value2 != empty
+ * * update - value1 != empty, value2 != empty
+ * * delete - value1 != empty, value2 == empty
+ *
+ * In addition, `column.size` x `count` == `value#.size`,
+ * which can represent multiple records.
+ *
+ * Strong type when generated, but type is lost after JSON serialization,
+ * when JSON deserialized, value only has String and Number type
  * 
* * @author trydofor @@ -39,7 +44,7 @@ public class JournalDiff { private transient boolean typed = false; /** - * values是否存在类型,jooq生成存在类型 + * Whether value is typed. jooq-generated code has type */ @Transient public boolean isTyped() { @@ -47,7 +52,7 @@ public boolean isTyped() { } /** - * 是否有记录 + * Whether it has record */ @Transient public boolean hasRecord() { @@ -55,7 +60,7 @@ public boolean hasRecord() { } /** - * 是否有效,Insert,Update,Delete + * Whether valid (Insert,Update, or Delete) */ @Transient public boolean isValid() { @@ -63,7 +68,7 @@ public boolean isValid() { } /** - * 是否为插入 + * Whether an insert */ @Transient public boolean isInsert() { @@ -73,7 +78,7 @@ public boolean isInsert() { } /** - * 是否为删除 + * Whether a delete */ @Transient public boolean isDelete() { @@ -83,7 +88,7 @@ public boolean isDelete() { } /** - * 是否为更新 + * Whether an update */ @Transient public boolean isUpdate() { diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalLock.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalLock.java index 5d76873ca..b4bbb1b6d 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalLock.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalLock.java @@ -3,7 +3,7 @@ import lombok.Data; /** - * 以CommitId作为乐观锁 + * Use CommitId as optimistic lock * * @author trydofor * @since 2019-09-17 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalService.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalService.java index adf72c564..4b3f31796 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalService.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/JournalService.java @@ -13,7 +13,9 @@ import java.util.function.Function; /** - * 提交任务,如果上下文中存在Journal,则复用 + * Submit/Commit the operation with a Journal. + * If a Journal exists in the context, then reuse it, + * otherwise create a new one in the context. * * @author trydofor * @since 2019-06-05 @@ -77,26 +79,26 @@ public long getId() { } /** - * 构建一个日志,并返回任务结果 + * Submit the operation (event) with journal and return some result * - * @param eventName 事件名 - * @param loginInfo 登陆信息,用户id,ip等,自定义 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventName event name + * @param loginInfo login info ,eg. userId, ip + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the result */ @NotNull R submit(@NotNull String eventName, @Nullable String loginInfo, @Nullable String targetKey, @Nullable String otherInfo, @NotNull Function commitSet); /** - * 构建一个日志,返回改日志 + * Commit the operation (event) with journal and return the journal. * - * @param eventName 事件名 - * @param loginInfo 登陆信息,用户id,ip等,自定义 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return Journal + * @param eventName event name + * @param loginInfo login info ,eg. userId, ip + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull String eventName, @Nullable String loginInfo, @Nullable String targetKey, @Nullable String otherInfo, @NotNull Consumer commitSet) { return submit(eventName, loginInfo, targetKey, otherInfo, journal -> { @@ -106,15 +108,15 @@ default Journal commit(@NotNull String eventName, @Nullable String loginInfo, @N } /** - * 构建一个日志 - * 建议Override,通过Json构造targetKey或OtherInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to create targetKey/OtherInfo in Json * - * @param eventClass 事件类,使用类的全路径 - * @param loginInfo 登陆信息,用户id,ip等,自定义 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventClass use Class.getName as eventName + * @param loginInfo login info ,eg. userId, ip + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Class eventClass, @Nullable String loginInfo, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Function commitSet) { @@ -125,15 +127,15 @@ default R submit(@NotNull Class eventClass, @Nullable String loginInfo, @ } /** - * 构建一个日志 - * 建议Override,通过Json构造targetKey或OtherInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to create targetKey/OtherInfo in Json * - * @param eventClass 事件类,使用类的全路径 - * @param loginInfo 登陆信息,用户id,ip等,自定义 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return Journal + * @param eventClass use Class.getName as eventName + * @param loginInfo login info ,eg. userId, ip + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Class eventClass, @Nullable String loginInfo, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Consumer commitSet) { return submit(eventClass, loginInfo, targetKey, otherInfo, journal -> { @@ -143,14 +145,14 @@ default Journal commit(@NotNull Class eventClass, @Nullable String loginInfo, } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventClass 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventClass use Class.getName as eventName + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Class eventClass, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Function commitSet) { @@ -158,27 +160,27 @@ default R submit(@NotNull Class eventClass, @Nullable Object targetKey, @ } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventClass 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return Journal + * @param eventClass use Class.getName as eventName + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Class eventClass, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Consumer commitSet) { return commit(eventClass, null, targetKey, otherInfo, commitSet); } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventClass 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventClass use Class.getName as eventName + * @param targetKey key/id of target + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Class eventClass, @Nullable Object targetKey, @NotNull Function commitSet) { @@ -186,25 +188,25 @@ default R submit(@NotNull Class eventClass, @Nullable Object targetKey, @ } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventClass 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param commitSet 提交任务集 - * @return Journal + * @param eventClass use Class.getName as eventName + * @param targetKey key/id of target + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Class eventClass, @Nullable Object targetKey, @NotNull Consumer commitSet) { return commit(eventClass, null, targetKey, null, commitSet); } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventClass 事件类,使用类的全路径 - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventClass use Class.getName as eventName + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Class eventClass, @NotNull Function commitSet) { @@ -212,27 +214,27 @@ default R submit(@NotNull Class eventClass, @NotNull Function } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventClass 事件类,使用类的全路径 - * @param commitSet 提交任务集 - * @return Journal + * @param eventClass use Class.getName as eventName + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Class eventClass, @NotNull Consumer commitSet) { return commit(eventClass, null, null, null, commitSet); } /** - * 构建一个日志 - * 建议Override,通过Json构造targetKey或OtherInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to create targetKey/OtherInfo in Json * - * @param eventEnum 事件类,使用类的全路径,通常内部类命名为Jane - * @param loginInfo 登陆信息,用户id,ip等,自定义 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventEnum convert enum with EnumConvertor + * @param loginInfo login info ,eg. userId, ip + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Enum eventEnum, @Nullable String loginInfo, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Function commitSet) { @@ -243,15 +245,15 @@ default R submit(@NotNull Enum eventEnum, @Nullable String loginInfo, @Nu } /** - * 构建一个日志 - * 建议Override,通过Json构造targetKey或OtherInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to create targetKey/OtherInfo in Json * - * @param eventEnum 事件类,使用类的全路径,通常内部类命名为Jane - * @param loginInfo 登陆信息,用户id,ip等,自定义 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return Journal + * @param eventEnum convert enum with EnumConvertor + * @param loginInfo login info ,eg. userId, ip + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Enum eventEnum, @Nullable String loginInfo, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Consumer commitSet) { return submit(eventEnum, loginInfo, targetKey, otherInfo, journal -> { @@ -261,14 +263,14 @@ default Journal commit(@NotNull Enum eventEnum, @Nullable String loginInfo, @ } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventEnum 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventEnum convert enum with EnumConvertor + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Enum eventEnum, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Function commitSet) { @@ -276,27 +278,27 @@ default R submit(@NotNull Enum eventEnum, @Nullable Object targetKey, @Nu } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventEnum 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param otherInfo 其他信息 - * @param commitSet 提交任务集 - * @return Journal + * @param eventEnum convert enum with EnumConvertor + * @param targetKey key/id of target + * @param otherInfo other info of operation + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Enum eventEnum, @Nullable Object targetKey, @Nullable Object otherInfo, @NotNull Consumer commitSet) { return commit(eventEnum, null, targetKey, otherInfo, commitSet); } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventEnum 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventEnum convert enum with EnumConvertor + * @param targetKey key/id of target + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Enum eventEnum, @Nullable Object targetKey, @NotNull Function commitSet) { @@ -304,25 +306,25 @@ default R submit(@NotNull Enum eventEnum, @Nullable Object targetKey, @No } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventEnum 事件类,使用类的全路径 - * @param targetKey 目标key或id - * @param commitSet 提交任务集 - * @return Journal + * @param eventEnum convert enum with EnumConvertor + * @param targetKey key/id of target + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Enum eventEnum, @Nullable Object targetKey, @NotNull Consumer commitSet) { return commit(eventEnum, null, targetKey, null, commitSet); } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Submit the operation (event) with journal and return some result. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventEnum 事件类,使用类的全路径 - * @param commitSet 提交任务集 - * @return 任务集结果 + * @param eventEnum convert enum with EnumConvertor + * @param commitSet operations + * @return the result */ @NotNull default R submit(@NotNull Enum eventEnum, @NotNull Function commitSet) { @@ -330,12 +332,12 @@ default R submit(@NotNull Enum eventEnum, @NotNull Function c } /** - * 构建一个日志。 - * 建议Override,通过SecurityContext获得 loginInfo + * Commit the operation (event) with journal and return the journal. + * It is recommended to `Override` to get loginInfo in TerminalContext/SecurityContext * - * @param eventEnum 事件类,使用类的全路径 - * @param commitSet 提交任务集 - * @return Journal + * @param eventEnum convert enum with EnumConvertor + * @param commitSet operations + * @return the journal */ default Journal commit(@NotNull Enum eventEnum, @NotNull Consumer commitSet) { return commit(eventEnum, null, null, null, commitSet); diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/impl/DefaultJournalService.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/impl/DefaultJournalService.java index 92e37bd5b..509c3afff 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/impl/DefaultJournalService.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/journal/impl/DefaultJournalService.java @@ -49,7 +49,7 @@ public R submit(@NotNull String eventName, @Nullable String loginInfo, @Null int rc = journalModify.insert(journal); DaoAssert.assertEq1(rc, "failed to insert Journal={}", journal); - // 谁创建谁销毁,谁分配谁回收 + // Who created, who destroy context.set(journal); try { return commitSet.apply(journal); diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdAware.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdAware.java index 324d5c42e..17c1494ef 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdAware.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdAware.java @@ -3,7 +3,7 @@ import org.jetbrains.annotations.NotNull; /** - * 标记使用lightid的类,建议只标记表。 + * Used to mark Class that support LightId, it is recommended to mark only tables. * * @author trydofor * @since 2019-05-31 diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdService.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdService.java index 479fa5fe8..4c309b1c9 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdService.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/LightIdService.java @@ -10,51 +10,35 @@ public interface LightIdService { /** - * 获得默认的BlockId - * - * @return 默认的BlockId + * Get the default BlockId */ default int geBlockId() { return 0; } /** - * 按名字获得id,不区分大小写,默认全小写。 - * - * @param name 名字 - * @return id + * Get id by name, may be case-insensitive, all lowercase by default. */ default long getId(@NotNull String name) { return getId(name, geBlockId()); } /** - * 按Jooq的Table命名获得,去掉结尾的`Table`后缀,按 小写_小写命名。 - * - * @param table 标记的对象 - * @param block 区块 - * @return id + * Get id by Jooq table name and block. e.g. removing the `Table` suffix and name it in lowercase_lowercase. */ default long getId(@NotNull LightIdAware table, int block) { return getId(table.getSeqName(), block); } /** - * 按Jooq的Table命名获得,去掉结尾的`Table`后缀,按 小写_小写命名。 - * - * @param table 标记的对象 - * @return id + * Get id by Jooq table name. e.g. removing the `Table` suffix and name it in lowercase_lowercase. */ default long getId(@NotNull LightIdAware table) { return getId(table, geBlockId()); } /** - * 按名字获得id,不区分大小写,默认全小写。 - * - * @param name 名字 - * @param block 区块 - * @return id + * Get id by name and block, may be case-insensitive, all lowercase by default. */ long getId(@NotNull String name, int block); } diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/impl/BlockingLightIdProvider.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/impl/BlockingLightIdProvider.java new file mode 100644 index 000000000..4811dcd4b --- /dev/null +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/impl/BlockingLightIdProvider.java @@ -0,0 +1,43 @@ +package pro.fessional.wings.faceless.service.lightid.impl; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import pro.fessional.mirana.id.LightIdProvider; +import pro.fessional.mirana.pain.TimeoutRuntimeException; + +/** + * @author trydofor + * @since 2023-07-17 + */ + +@Slf4j +@RequiredArgsConstructor +public class BlockingLightIdProvider implements LightIdProvider { + + private final LightIdProvider.Loader loader; + + @Setter @Getter + private long timeout = 1000; + + @Override + public long next(@NotNull String name, int block) { + return next(name, block, timeout); + } + + @Override + public long next(@NotNull String name, int block, long timeout) { + final long throwMs = System.currentTimeMillis() + timeout; + + final Segment seg = loader.require(name, block, 1, true); + + final long now = System.currentTimeMillis(); + if (now > throwMs) { + throw new TimeoutRuntimeException("loading timeout=" + (now - throwMs + timeout)); + } + + return seg.getHead(); + } +} diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/impl/LightIdMysqlLoader.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/impl/LightIdMysqlLoader.java index 55cab0b1a..1c6e54eab 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/impl/LightIdMysqlLoader.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/lightid/impl/LightIdMysqlLoader.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; -import java.util.Optional; import static pro.fessional.mirana.id.LightIdProvider.Loader; import static pro.fessional.mirana.id.LightIdProvider.Segment; @@ -37,10 +36,9 @@ public class LightIdMysqlLoader implements Loader { @Override @WriteRouteOnly @Transactional(propagation = Propagation.REQUIRES_NEW) - public Segment require(@NotNull String name, int block, int count) { - Optional one = select.selectOneLock(block, name); - final NextStep vo; - if (one.isEmpty()) { + public Segment require(@NotNull String name, int block, int count, boolean exact) { + NextStep one = select.selectOneLock(block, name); + if (one == null) { if (properties.isAuto()) { log.warn("not found and insert name={}, block={}", name, block); SysLightSequence po = new SysLightSequence(); @@ -51,29 +49,33 @@ public Segment require(@NotNull String name, int block, int count) { po.setComments("Auto insert if Not found"); int cnt = modify.insert(po); if (cnt != 1) { - throw new NoSuchElementException("not found and failed to insert. name=" + name + ",block=" + block); + throw new NoSuchElementException("not found and failed to insert. name=" + name + ", block=" + block); } log.warn("inserted and retry, name={}, block={}", name, block); - vo = new NextStep(properties.getNext(), properties.getStep()); + one = new NextStep(properties.getNext(), properties.getStep()); } else { - throw new NoSuchElementException("not existed name=" + name + ",block=" + block); + throw new NoSuchElementException("not existed name=" + name + ", block=" + block); } } + + final long newNext; + final long curNext = one.getNextVal(); + if (exact) { + newNext = curNext + count; + } else { - vo = one.get(); + long page = (count - 1) / one.getStepVal() + 1; + newNext = curNext + one.getStepVal() * page; } - int page = (count - 1) / vo.getStepVal() + 1; - - long newNext = vo.getNextVal() + (long) vo.getStepVal() * page; - int upd = modify.updateNextVal(newNext, block, name, vo.getLastVal()); + int upd = modify.updateNextVal(newNext, block, name, one.getOldNext()); if (upd != 1) { - throw new IllegalStateException("failed to require, name=" + name + ",block=" + block); + throw new IllegalStateException("failed to require, name=" + name + ", block=" + block); } - return new Segment(name, block, vo.getNextVal(), newNext - 1); + return new Segment(name, block, curNext, newNext - 1); } @NotNull diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/wini18n/StandardI18nService.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/wini18n/StandardI18nService.java index 06e33719e..2198119c1 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/wini18n/StandardI18nService.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/service/wini18n/StandardI18nService.java @@ -14,34 +14,29 @@ public interface StandardI18nService { /** - * 重新加载所有数据 - * - * @return 加载的数量 + * Reload all and return the count. */ int reloadAll(); /** - * 按base重新加载 - * - * @param base base - * @return 加载的数量 + * Reload by base and return the count. */ int reloadBase(String base); /** - * 同数据表 sys_standard_i18n 对应 + * Load i18n message form sys_standard_i18n * - * @param base 基点,表名或java包名 - * @param kind 种类,字段或java类名 - * @param ukey 键值,唯一性值,如id.###|type.code|enum - * @param lang 语言 - * @return 多国语 + * @param base table or package + * @param kind column or java field + * @param ukey unique key eg. id.###, type.code, enum + * @param lang locale + * @return i18n message */ @Nullable String load(String base, String kind, String ukey, Locale lang); /** - * 使用I18nEnum的package,className,name获取I18n + * Load i18n message by I18nEnum (base, kind, ukey) * * @param enu I18nEnum * @param lang locale @@ -54,7 +49,7 @@ default String load(StandardI18nEnum enu, StandardLanguageEnum lang) { } /** - * 使用I18nEnum的base,kind,ukey获取I18n + * Load i18n message by I18nEnum (base, kind, ukey) * * @param enu I18nEnum * @param lang locale @@ -67,7 +62,7 @@ default String load(StandardI18nEnum enu, Locale lang) { } /** - * 使用Enum的package,className,name获取I18n + * Load i18n message by Enum (package, className, name) * * @param enu enum * @param lang locale @@ -80,7 +75,7 @@ default String loadEnum(Enum enu, StandardLanguageEnum lang) { } /** - * 使用Enum的package,className,name获取I18n + * Load i18n message by Enum (package, className, name) * * @param enu enum * @param lang locale @@ -96,7 +91,7 @@ default String loadEnum(Enum enu, Locale lang) { } /** - * 使用CodeEnum的package,className,getCode获取I18n + * Load i18n message by CodeEnum (package, className, getCode) * * @param enu CodeEnum * @param lang locale @@ -109,7 +104,7 @@ default String loadCode(CodeEnum enu, StandardLanguageEnum lang) { } /** - * 使用CodeEnum的package,className,getCode获取I18n + * Load i18n message by CodeEnum (package, className, getCode) * * @param enu CodeEnum * @param lang locale diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessLightIdConfiguration.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessLightIdConfiguration.java index f53e0ef4f..3a515af43 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessLightIdConfiguration.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/bean/FacelessLightIdConfiguration.java @@ -15,21 +15,21 @@ import pro.fessional.mirana.id.LightIdBufferedProvider; import pro.fessional.mirana.id.LightIdProvider; import pro.fessional.mirana.id.LightIdUtil; -import pro.fessional.wings.spring.consts.OrderedFacelessConst; import pro.fessional.wings.faceless.database.manual.single.modify.lightsequence.LightSequenceModify; import pro.fessional.wings.faceless.database.manual.single.modify.lightsequence.impl.LightSequenceModifyJdbc; import pro.fessional.wings.faceless.database.manual.single.select.lightsequence.LightSequenceSelect; import pro.fessional.wings.faceless.database.manual.single.select.lightsequence.impl.LightSequenceSelectJdbc; import pro.fessional.wings.faceless.service.lightid.BlockIdProvider; import pro.fessional.wings.faceless.service.lightid.LightIdService; +import pro.fessional.wings.faceless.service.lightid.impl.BlockingLightIdProvider; import pro.fessional.wings.faceless.service.lightid.impl.DefaultBlockIdProvider; import pro.fessional.wings.faceless.service.lightid.impl.LightIdMysqlLoader; import pro.fessional.wings.faceless.service.lightid.impl.LightIdServiceImpl; import pro.fessional.wings.faceless.spring.prop.FacelessEnabledProp; import pro.fessional.wings.faceless.spring.prop.LightIdInsertProp; import pro.fessional.wings.faceless.spring.prop.LightIdLayoutProp; -import pro.fessional.wings.faceless.spring.prop.LightIdLoaderProp; import pro.fessional.wings.faceless.spring.prop.LightIdProviderProp; +import pro.fessional.wings.spring.consts.OrderedFacelessConst; /** * @author trydofor @@ -55,47 +55,61 @@ public LightSequenceSelect lightSequenceSelect(LightIdProviderProp prop, JdbcTem @Bean @ConditionalOnMissingBean(LightSequenceModify.class) - public LightSequenceModify lightSequenceModify(LightIdProviderProp provider, JdbcTemplate jdbcTemplate) { + public LightSequenceModify lightSequenceModify(LightIdProviderProp providerProp, JdbcTemplate jdbcTemplate) { log.info("Faceless spring-bean lightSequenceModify"); - return new LightSequenceModifyJdbc(jdbcTemplate, provider.getSequenceInsert(), provider.getSequenceUpdate()); + return new LightSequenceModifyJdbc(jdbcTemplate, providerProp.getSequenceInsert(), providerProp.getSequenceUpdate()); } @Bean @ConditionalOnMissingBean(LightIdProvider.Loader.class) public LightIdProvider.Loader lightIdLoader(LightSequenceSelect lightSequenceSelect, LightSequenceModify lightSequenceModify, - LightIdInsertProp properties) { + LightIdInsertProp insertProp) { log.info("Faceless spring-bean lightIdLoader"); - return new LightIdMysqlLoader(lightSequenceSelect, lightSequenceModify, properties); + return new LightIdMysqlLoader(lightSequenceSelect, lightSequenceModify, insertProp); } @Bean @ConditionalOnMissingBean(LightIdProvider.class) public LightIdProvider lightIdProvider(LightIdProvider.Loader lightIdLoader, - LightIdLoaderProp properties, + LightIdProviderProp providerProp, ObjectProvider sequenceHandler) { - log.info("Faceless spring-bean lightIdProvider"); - LightIdBufferedProvider provider = new LightIdBufferedProvider(lightIdLoader); - provider.setTimeout(properties.getTimeout()); - provider.setErrAlive(properties.getErrAlive()); - provider.setMaxError(properties.getMaxError()); - provider.setMaxCount(properties.getMaxCount()); - sequenceHandler.ifAvailable(provider::setSequenceHandler); - return provider; + final String mono = providerProp.getMonotonic(); + log.info("Faceless spring-bean lightIdProvider in " + mono); + if ("jvm".equalsIgnoreCase(mono)) { + // avg=0.039ms + LightIdBufferedProvider provider = new LightIdBufferedProvider(lightIdLoader); + provider.setTimeout(providerProp.getTimeout()); + provider.setErrAlive(providerProp.getErrAlive()); + provider.setMaxError(providerProp.getMaxError()); + provider.setMaxCount(providerProp.getMaxCount()); + sequenceHandler.ifAvailable(provider::setSequenceHandler); + return provider; + } + else if ("db".equalsIgnoreCase(mono)) { + // avg=10.723ms + log.warn("the BlockingLightIdProvider is slow, about 10ms per id"); + final BlockingLightIdProvider provider = new BlockingLightIdProvider(lightIdLoader); + provider.setTimeout(providerProp.getTimeout()); + return provider; + } + else { + throw new IllegalArgumentException("unsupported monotonic type=" + mono); + } } @Bean @ConditionalOnMissingBean(BlockIdProvider.class) @ConditionalOnExpression("!'${" + LightIdProviderProp.Key$blockType + "}'.equals('biz')") - public BlockIdProvider blockProvider(LightIdProviderProp provider, + public BlockIdProvider blockProvider(LightIdProviderProp providerProp, ObjectProvider jdbcTemplate) { - final String blockType = provider.getBlockType(); + final String blockType = providerProp.getBlockType(); log.info("Faceless spring-bean lightIdProvider" + blockType); if ("sql".equalsIgnoreCase(blockType)) { - return new DefaultBlockIdProvider(provider.getBlockPara(), jdbcTemplate.getIfAvailable()); + return new DefaultBlockIdProvider(providerProp.getBlockPara(), jdbcTemplate.getIfAvailable()); } else if ("fix".equalsIgnoreCase(blockType)) { - final int id = Integer.parseInt(provider.getBlockPara()); + final int id = Integer.parseInt(providerProp.getBlockPara()); return () -> id; } else if ("biz".equalsIgnoreCase(blockType)) { diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/prop/LightIdLoaderProp.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/prop/LightIdLoaderProp.java deleted file mode 100644 index 993210981..000000000 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/prop/LightIdLoaderProp.java +++ /dev/null @@ -1,48 +0,0 @@ -package pro.fessional.wings.faceless.spring.prop; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author trydofor - * @see #Key - * @since 2019-05-30 - */ -@Data -@ConfigurationProperties(LightIdLoaderProp.Key) -public class LightIdLoaderProp { - - public static final String Key = "wings.faceless.lightid.loader"; - - /** - * timeout millis of loading. - * - * @see #Key$timeout - */ - private long timeout = 1000; - public static final String Key$timeout = Key + ".timeout"; - - /** - * max error count of loading. - * - * @see #Key$maxError - */ - private int maxError = 5; - public static final String Key$maxError = Key + ".max-error"; - - /** - * max id count of per loading. - * - * @see #Key$maxCount - */ - private int maxCount = 10000; - public static final String Key$maxCount = Key + ".max-count"; - - /** - * no attempt in number of millis if error exists. - * - * @see #Key$errAlive - */ - private long errAlive = 120000; - public static final String Key$errAlive = Key + ".err-alive"; -} diff --git a/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/prop/LightIdProviderProp.java b/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/prop/LightIdProviderProp.java index 1a12afc9a..c53a964ed 100644 --- a/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/prop/LightIdProviderProp.java +++ b/wings/faceless/src/main/java/pro/fessional/wings/faceless/spring/prop/LightIdProviderProp.java @@ -14,6 +14,38 @@ public class LightIdProviderProp { public static final String Key = "wings.faceless.lightid.provider"; + /** + * timeout millis of loading. + * + * @see #Key$timeout + */ + private long timeout = 1000; + public static final String Key$timeout = Key + ".timeout"; + + /** + * max error count of loading. + * + * @see #Key$maxError + */ + private int maxError = 5; + public static final String Key$maxError = Key + ".max-error"; + + /** + * max id count of per loading. + * + * @see #Key$maxCount + */ + private int maxCount = 10000; + public static final String Key$maxCount = Key + ".max-count"; + + /** + * no attempt in number of millis if error exists. + * + * @see #Key$errAlive + */ + private long errAlive = 120000; + public static final String Key$errAlive = Key + ".err-alive"; + /** *
      * method to provide blockId
@@ -100,11 +132,22 @@ public class LightIdProviderProp {
     public static final String Key$sequenceGetAll = Key + ".sequence-get-all";
 
     /**
-     * try to verify and adjust the id in the database to make it correct. Set to `∅` to ignore this feature.
+     * try to verify and adjust the id in the database to make it correct. Set to `empty` to ignore this feature.
      * Enter `table name` (as sequence name), return `table name` and `column name` in the database.
      *
      * @see #Key$sequenceAdjust
      */
     private String sequenceAdjust = "";
     public static final String Key$sequenceAdjust = Key + ".sequence-adjust";
+
+    /**
+     * the LightId monotonic increasing type, jvm|db|hz
+     * - jvm, monotonic in the jvm
+     * - db, monotonic in the database
+     * - hz, monotonic in the hazelcast
+     *
+     * @see #Key$monotonic
+     */
+    private String monotonic = "jvm";
+    public static final String Key$monotonic = Key + ".monotonic";
 }
diff --git a/wings/faceless/src/main/resources/wings-conf/wings-lightid-79.properties b/wings/faceless/src/main/resources/wings-conf/wings-lightid-79.properties
index 0c133e9ae..4d93bf041 100644
--- a/wings/faceless/src/main/resources/wings-conf/wings-lightid-79.properties
+++ b/wings/faceless/src/main/resources/wings-conf/wings-lightid-79.properties
@@ -9,13 +9,13 @@ wings.faceless.lightid.insert.next=1000
 wings.faceless.lightid.insert.step=100
 
 ## timeout millis of loading.
-wings.faceless.lightid.loader.timeout=5000
+wings.faceless.lightid.provider.timeout=5000
 ## max error count of loading.
-wings.faceless.lightid.loader.max-error=5
+wings.faceless.lightid.provider.max-error=5
 ## max id count of per loading.
-wings.faceless.lightid.loader.max-count=10000
+wings.faceless.lightid.provider.max-count=10000
 ## no attempt in number of millis if error exists.
-wings.faceless.lightid.loader.err-alive=120000
+wings.faceless.lightid.provider.err-alive=120000
 
 ## method to provide blockId
 ## - `sql` - query database, return the id
@@ -40,10 +40,16 @@ wings.faceless.lightid.provider.sequence-update=UPDATE sys_light_sequence SET ne
 wings.faceless.lightid.provider.sequence-get-one=SELECT next_val, step_val FROM sys_light_sequence WHERE block_id=? AND seq_name=? FOR UPDATE
 ## fetch all sql for JdbcTemplate.
 wings.faceless.lightid.provider.sequence-get-all=SELECT seq_name, next_val, step_val FROM sys_light_sequence WHERE block_id=? FOR UPDATE
-## try to verify and adjust the id in the database to make it correct. Set to `∅` to ignore this feature.
+## try to verify and adjust the id in the database to make it correct. Set to `empty` to ignore this feature.
 ## Enter `table name` (as sequence name), return `table name` and `column name` in the database.
 wings.faceless.lightid.provider.sequence-adjust=SELECT table_name, column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = SCHEMA() AND UPPER(column_key) = 'PRI' AND UPPER(column_type) like '%INT%' AND table_name = ?
 
+## the LightId monotonic increasing type, jvm|db|hz
+## - jvm, monotonic in the jvm
+## - db, monotonic in the database
+## - hz, monotonic in the hazelcast
+wings.faceless.lightid.provider.monotonic=jvm
+
 ## the number of block bytes, in the range [3,23], empty by default. LightId is 9 by default, so 2^9=512 zones.
 wings.faceless.lightid.layout.block-bits=
 
diff --git a/wings/faceless/src/test/resources/wings-conf/wings-test-module.properties b/wings/faceless/src/test/resources/wings-conf/wings-test-module.properties
index d44b54c3b..9d487568c 100644
--- a/wings/faceless/src/test/resources/wings-conf/wings-test-module.properties
+++ b/wings/faceless/src/test/resources/wings-conf/wings-test-module.properties
@@ -1 +1 @@
-wings.test.module=虚空假面
\ No newline at end of file
+wings.test.module=Faceless
\ 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/lombok.config b/wings/lombok.config
index 5399dbd59..012da51eb 100644
--- a/wings/lombok.config
+++ b/wings/lombok.config
@@ -1,7 +1,7 @@
 lombok.log.fieldName=log
 lombok.addNullAnnotations=jetbrains
-# DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES
-# 影响json序列化和反序列化,不要打开
+## DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES
+## Do not open, it affects JSON serialization and deserialization.
 #lombok.anyConstructor.addConstructorProperties=true
 #
 lombok.var.flagUsage=error
diff --git a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/datetime/Epochs.java b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/datetime/Epochs.java
index 56bca58ba..da9923611 100644
--- a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/datetime/Epochs.java
+++ b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/datetime/Epochs.java
@@ -1,20 +1,20 @@
 package pro.fessional.wings.silencer.datetime;
 
 /**
- * 一些时间纪元,相比于1970-01-01
+ * Some time epochs, compared to 1970-01-01
  *
  * @author trydofor
  * @since 2022-12-02
  */
 public class Epochs {
     /**
-     * 口罩元年
+     * The Year of the Mask
      */
     //public static final ZonedDateTime DateCovid19 = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC+8"));
     public static final long Covid19 = 1577808000000L; // 2022-01-01 UTC+8
 
     /**
-     * FlakeId元年
+     * The Year of the FlakeId
      */
     //public static final ZonedDateTime DateFlakeId = ZonedDateTime.of(2022, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC"));
     public static final long FlakeId = 1640995200000L; // 2022-01-01 UTC
diff --git a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/encrypt/SecretProvider.java b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/encrypt/SecretProvider.java
index c16c07355..0bdf2a5fa 100644
--- a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/encrypt/SecretProvider.java
+++ b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/encrypt/SecretProvider.java
@@ -7,7 +7,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * 系统密码提供者,默认256bit,32字符
+ * System password provider, default 256bit, 32 characters
  *
  * @author trydofor
  * @since 2022-12-05
@@ -15,32 +15,32 @@
 public class SecretProvider {
 
     /**
-     * 默认密码的字符串长度
+     * Default password length
      */
     public static final int Length = 32;
 
     /**
-     * 系统默认,每次系统启动时随机生成,停机后消失。
+     * System default, randomly generated each time the system starts, disappears after downtime.
      */
     public static final String System = "system";
 
     /**
-     * 用于Api Ticket,建议集群内统一
+     * Used for Api Tickets, should be the same within the cluster
      */
     public static final String Ticket = "ticket";
 
     /**
-     * 用于 Http Cookie,建议集群内统一
+     * Used for Http Cookie, should be the same within the cluster
      */
     public static final String Cookie = "cookie";
     /**
-     * 用于 配置文件中敏感数据,建议固定
+     * Used for sensitive data in profiles, it is recommended to fix value
      */
     public static final String Config = "config";
 
 
     /**
-     * 生成len长度的字母大小写和数字符号密码
+     * Generate `len` length passwords of alphabetic, case-sensitive and numeric
      *
      * @see RandCode#strong(int)
      */
@@ -50,7 +50,8 @@ public static String strong(int len) {
     }
 
     /**
-     * 生成len长度的字母大小写和数字可读性好的密码。 供32个英数,去掉了30个(0oO,1il,cC,j,kK,mM,nN,pP,sS,uU,vV,wW,xX,y,zZ)
+     * Generate `len` length passwords with good readability of case-sensitive letters and numbers.
+     * total 32 Letter and Numbers, removing 30 (0oO,1il,cC,j,kK,mM,nN,pP,sS,uU,vV,wW,xX,y,zZ)
      *
      * @see RandCode#human(int)
      */
@@ -60,19 +61,15 @@ public static String human(int len) {
     }
 
     /**
-     * 获取name对应的secret,若没有则生成并保存一个默认Length的密码
+     * Get the secret by name, if not found, generate a default length strong password and cache it.
      */
     @NotNull
     public static String get(String name) {
-        if (name == null || name.isEmpty()) {
-            name = System;
-        }
-
-        return Secrets.computeIfAbsent(name, k -> RandCode.strong(Length));
+        return get(name, true);
     }
 
     /**
-     * 获取name对应的secret,指定非空时,若没有则生成并保存一个默认Length的密码
+     * Get the secret by name, if not found and computeIfAbsent, generate a default length strong password and cache it.
      */
     @Contract("_,true->!null")
     public static String get(String name, boolean computeIfAbsent) {
@@ -92,7 +89,7 @@ public static String get(String name, boolean computeIfAbsent) {
     protected static final ConcurrentHashMap Secrets = new ConcurrentHashMap<>();
 
     /**
-     * 初始一个name的secret
+     * put the secret by name
      */
     protected static void put(@NotNull String name, @NotNull String secret, boolean replace) {
         if (replace) {
diff --git a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/modulate/ApiMode.java b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/modulate/ApiMode.java
index ea4c2a8bf..13d394057 100644
--- a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/modulate/ApiMode.java
+++ b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/modulate/ApiMode.java
@@ -6,19 +6,19 @@
  */
 public enum ApiMode {
     /**
-     * alive 正式
+     * Alive, production
      */
     Online,
     /**
-     * 测试,沙盒
+     * Sandbox, testing
      */
     Sandbox,
     /**
-     * 跑通测试,最后rollback
+     * Dry running, rollback at the end
      */
     Dryrun,
     /**
-     * 未定义,不执行任何动作
+     * Undefined, no action is performed
      */
     Nothing
 }
diff --git a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerAutoLogConfiguration.java b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerAutoLogConfiguration.java
index 8f6f7a0fa..8a729378c 100644
--- a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerAutoLogConfiguration.java
+++ b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/spring/bean/SilencerAutoLogConfiguration.java
@@ -35,7 +35,7 @@ public class SilencerAutoLogConfiguration {
     private static final Log log = LogFactory.getLog(SilencerAutoLogConfiguration.class);
 
     /**
-     * 配置结束,服务开始之前,切换日志
+     * Configuration is complete and the log is switched before the service starts
      */
     @Bean
     @ConditionalOnClass(ConsoleAppender.class)
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..c060404ed 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");
-        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);
-        });
+        log.info("SilencerCurse spring-auto autowireLogbackTweak, init TtlMDC");
+        TtlMDCAdapter.initMdc();// init as early as possible
+
+        if (prop.isMdcThreshold()) {
+            log.info("SilencerCurse spring-conf autowireLogbackTweak WingsMdcThresholdFilter");
+            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+            lc.getTurboFilterList().add(0, TweakLogger.MdcThresholdFilter);
+        }
+        // init as late as possible
+        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..44ab706c0 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);
@@ -152,16 +157,17 @@ public static LogLevel globalLevel(@NotNull String name) {
     // thread
 
     /**
-     * value为Level的文字明,大写
+     * The string value of Level, capitalized
      */
     public static final String LevelKey = "WINGS_DEBUG_LEVEL";
     /**
-     * value为logger名,区分大小写。比较逻辑为互相contains即可。
+     * the string value of logger name, case-insensitive.
+     * Comparison logic is A.contains(B) or B.contains(A)
      */
     public static final String LoggerKey = "WINGS_DEBUG_LOGGER";
 
     /**
-     * 根据MDC中的logger name和 level过滤
+     * Filter by logger name and level in the MDC
      *
      * @see DynamicThresholdFilter
      */
@@ -186,15 +192,15 @@ public FilterReply decide(Marker marker, Logger logger, Level level, String form
     };
 
     /**
-     * level为null或OFF时,为reset
+     * Set the new log level for root, but if level is null or OFF, reset to the original level.
      */
     public static void tweakThread(@Nullable LogLevel level) {
         tweakThread(ROOT_LOGGER_NAME, level);
     }
 
     /**
-     * level为null或OFF时,为reset。
-     * name为ROOT或空为全局
+     * Set the new log level for logger, but if level is null or OFF, reset to the original level.
+     * tweak root level if the name is ROOT or empty.
      */
     public static void tweakThread(@NotNull String name, @Nullable LogLevel level) {
         if (level == null || level == LogLevel.OFF) {
diff --git a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/watch/Watches.java b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/watch/Watches.java
index 52c673ff1..bfd5b4e48 100644
--- a/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/watch/Watches.java
+++ b/wings/silencer-curse/src/main/java/pro/fessional/wings/silencer/watch/Watches.java
@@ -21,7 +21,7 @@ public class Watches {
     private static final ThreadLocal StopWatches = new TransmittableThreadLocal<>();
 
     /**
-     * 以try-close方式使用acquire-release
+     * acquire and release with try-close style
      */
     @NotNull
     public static StopWatch acquire() {
@@ -39,7 +39,7 @@ public static StopWatch.Watch acquire(String name) {
     }
 
     /**
-     * 获取当前线程的StopWatch,建议通过Watch.owner获取
+     * Get the StopWatch at the current thread.
      */
     @Nullable
     public static StopWatch current() {
@@ -47,7 +47,7 @@ public static StopWatch current() {
     }
 
     /**
-     * 获取当前线程的StopWatch,建议通过Watch.owner获取
+     * Get the StopWatch at the current thread.
      */
     @Nullable
     public static StopWatch.Watch current(String name) {
@@ -56,8 +56,8 @@ public static StopWatch.Watch current(String name) {
     }
 
     /**
-     * 释放当前计时,并返回全部计时是否都已结束。
-     * 若全部计时结束时,是否清空记录(clean),是否写入日志(token != null)。
+     * Release the current timer and returns whether all timers have finished.
+     * When all timings are finished, clear the log if clean, write to log if token != null.
      */
     public static boolean release(boolean clean, String token) {
         StopWatch watch = StopWatches.get();
@@ -74,7 +74,7 @@ public static boolean release(boolean clean, String token) {
     }
 
     /**
-     * 使用Warn级别日志输出计时记录
+     * output info to the log with token at Warn level
      */
     public static void logging(String token, StopWatch watch) {
         log.warn("Watching {} {}", token, watch);
diff --git a/wings/silencer-curse/src/main/resources/wings-conf/spring-logging-79.properties b/wings/silencer-curse/src/main/resources/wings-conf/spring-logging-79.properties
index 6d55a3581..2e88b3101 100644
--- a/wings/silencer-curse/src/main/resources/wings-conf/spring-logging-79.properties
+++ b/wings/silencer-curse/src/main/resources/wings-conf/spring-logging-79.properties
@@ -26,7 +26,7 @@ logging.level.root=INFO
 ## Turn on and off console output
 #logging.pattern.console=
 #logging.pattern.file=
-## SpringBoot3 change to [yyyy-MM-dd’T’HH:mm:ss.SSSXXX] 2023-02-04T11:09:32.692+08:00
+## SpringBoot3 change to [yyyy-MM-dd'T'HH:mm:ss.SSSXXX] 2023-02-04T11:09:32.692+08:00
 logging.pattern.dateformat=yyyy-MM-dd HH:mm:ss.SSS
 
 ## Using an external configuration file, see DefaultLogbackConfiguration
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..bcb89403f 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
@@ -35,7 +39,7 @@ public class SpringOrderConfiguration implements InitializingBean {
     private static final Log log = LogFactory.getLog(SpringOrderConfiguration.class);
 
     public SpringOrderConfiguration(SilencerEnabledProp prop) {
-        log.info(">>>>> constructor 可自动注入参数 AutoLog=" + prop.isAutoLog());
+        log.info(">>>>> constructor can inject parameter AutoLog=" + prop.isAutoLog());
     }
 
 
@@ -46,18 +50,18 @@ public CollectionInjectTest.Dto dto2() {
 
     @Bean
     public CommandLineRunner testBean1(SilencerEnabledProp prop) {
-        log.info(">>>>> testBean1 可自动注入参数 AutoLog=" + prop.isAutoLog());
+        log.info(">>>>> testBean1 can inject parameter AutoLog=" + prop.isAutoLog());
         return ignored -> log.info(">>>>> CommandLineRunner1 " + prop.isAutoLog());
     }
 
     @PostConstruct
     public void postConstruct1() {
-        log.info(">>>>> postConstruct1 不可注入参数");
+        log.info(">>>>> postConstruct1 can NOT inject parameter");
     }
 
     @Autowired
     public void testAutowired1(SilencerEnabledProp prop) {
-        log.info(">>>>> testAutowired1 可自动注入参数 AutoLog=" + prop.isAutoLog());
+        log.info(">>>>> testAutowired1 can inject parameter AutoLog=" + prop.isAutoLog());
     }
 
     @PostConstruct
@@ -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-curse/src/test/resources/wings-conf/spring-logging-70.properties b/wings/silencer-curse/src/test/resources/wings-conf/spring-logging-70.properties
index 7bb95006e..064afce70 100644
--- a/wings/silencer-curse/src/test/resources/wings-conf/spring-logging-70.properties
+++ b/wings/silencer-curse/src/test/resources/wings-conf/spring-logging-70.properties
@@ -1,2 +1,2 @@
-# boot3表更为[yyyy-MM-dd’T’HH:mm:ss.SSSXXX] 2023-02-04T11:09:32.692+08:00
+## SpringBoot3 change to [yyyy-MM-dd'T'HH:mm:ss.SSSXXX] 2023-02-04T11:09:32.692+08:00
 logging.pattern.dateformat=
diff --git a/wings/silencer-jaxb/src/main/java/pro/fessional/wings/silencer/jaxb/StringMapXmlWriter.java b/wings/silencer-jaxb/src/main/java/pro/fessional/wings/silencer/jaxb/StringMapXmlWriter.java
index 9bb48460f..714ac37dd 100644
--- a/wings/silencer-jaxb/src/main/java/pro/fessional/wings/silencer/jaxb/StringMapXmlWriter.java
+++ b/wings/silencer-jaxb/src/main/java/pro/fessional/wings/silencer/jaxb/StringMapXmlWriter.java
@@ -7,7 +7,7 @@
 import java.util.TreeMap;
 
 /**
- * 只把顶层元素变成key-value的map,用来做参数签名
+ * Turn only top-level elements into key-value maps for parameter signatures
  *
  * @author trydofor
  * @since 2019-12-31
@@ -18,18 +18,14 @@ public class StringMapXmlWriter implements XMLStreamWriter {
     private String currentKey;
 
     /**
-     * 按key的ascii(unicode)的值排序
-     *
-     * @return key值排序
+     * Order key by ascii (unicode) code
      */
     public static StringMapXmlWriter treeMap() {
         return new StringMapXmlWriter(new TreeMap<>());
     }
 
     /**
-     * 按key的顺序排序
-     *
-     * @return key顺序排序
+     * Sort key by its insertion
      */
     public static StringMapXmlWriter linkMap() {
         return new StringMapXmlWriter(new LinkedHashMap<>());
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/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/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..8747235fd 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
@@ -190,7 +190,7 @@ private static class AutoConf {
         private String promo = "wings-prop-promotion.cnf";
     }
 
-    private void processWingsConf(ConfigurableEnvironment environment) {
+    public void processWingsConf(ConfigurableEnvironment environment) {
 
         final MutablePropertySources propertySources = environment.getPropertySources();
         final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@@ -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) {
@@ -371,7 +371,7 @@ private List profileBlockSort(LinkedHashSet confReso
         Comparator sorter = (r1, r2) -> {
             if (r1.profile.isEmpty() && !r2.profile.isEmpty()) return 1;
             if (!r1.profile.isEmpty() && r2.profile.isEmpty()) return -1;
-            final int p0 = r2.profile.compareTo(r1.profile); // spring 后者优先
+            final int p0 = r2.profile.compareTo(r1.profile); // spring the latter takes precedence.
             if (p0 != 0) return p0;
 
             final int n0 = Integer.compare(r1.nameSeq, r2.nameSeq);
@@ -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);
                 }
@@ -449,7 +449,7 @@ else if (path.startsWith("file:") || path.startsWith("classpath*:")) {
         return confResources;
     }
 
-    public AutoConf processWingsAuto(PathMatchingResourcePatternResolver resolver) {
+    private AutoConf processWingsAuto(PathMatchingResourcePatternResolver resolver) {
         final Resource resource = resolver.getResource(WINGS_AUTO);
         AutoConf autoConf = new AutoConf();
         if (resource.isReadable()) {
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/silencer/src/main/java/pro/fessional/wings/silencer/spring/help/ApplicationContextHelper.java b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/help/ApplicationContextHelper.java
index 68e0571f4..3a7013753 100644
--- a/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/help/ApplicationContextHelper.java
+++ b/wings/silencer/src/main/java/pro/fessional/wings/silencer/spring/help/ApplicationContextHelper.java
@@ -10,6 +10,7 @@
 import org.springframework.core.env.EnumerablePropertySource;
 import org.springframework.core.env.PropertySource;
 import org.springframework.core.io.Resource;
+import pro.fessional.mirana.best.Param;
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -20,7 +21,7 @@
 import java.util.Objects;
 
 /**
- * init on ApplicationPreparedEvent
+ * Init on ApplicationPreparedEvent
  *
  * @author trydofor
  * @since 2022-08-12
@@ -36,7 +37,7 @@ protected ApplicationContextHelper(ConfigurableApplicationContext ctx) {
     }
 
     /**
-     * 获取spring.application.name属性 或 context#getApplicationName
+     * Get the value of spring.application.name (if not empty) or context#getApplicationName by default.
      */
     @NotNull
     public static String getApplicationName() {
@@ -66,7 +67,7 @@ public static  T getProperties(String name, Class type) {
     }
 
     /**
-     * 获取所有key和有效值,按key的出现顺序显示
+     * Get all keys and valid values in order of appearance.
      */
     @NotNull
     public static Map listProperties() {
@@ -89,7 +90,7 @@ public static Map listProperties() {
 
 
     /**
-     * 显示keys及对应的层叠后的来源
+     * Show keys and its properties
      */
     @NotNull
     public static Map listPropertiesKeys() {
@@ -106,7 +107,7 @@ public static Map listPropertiesKeys() {
     }
 
     /**
-     * 获取PropertySources及其内keys,keys可重复,因文件可层叠
+     * Get the PropertySources and the keys within them, the keys can be duplicated as the files can be cascaded.
      */
     @NotNull
     public static Map> listPropertySource() {
@@ -125,9 +126,9 @@ public static Map> listPropertySource() {
     public static final String PropertySourceDelimiter = ":";
 
     /**
-     * 用于调查source和key的关系,返回 source:source2#key - key的map
+     * Use to investigate the relationship between source and key, return source:source2#key to key mapping
      */
-    public static void walkPropertySource(String root, Map> srcKey, PropertySource src) {
+    public static void walkPropertySource(String root, @Param.Out Map> srcKey, PropertySource src) {
         // EnvironmentEndpoint
         if (ConfigurationPropertySources.isAttachedConfigurationPropertySource(src)) return;
         String prefix = root == null || root.isEmpty() ? "" : root + PropertySourceDelimiter;
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..ebb19dfbb
--- /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("Silencer", 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/info/InfoPrintTest.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/info/InfoPrintTest.java
index 04103b9c9..eadd3e2e1 100644
--- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/info/InfoPrintTest.java
+++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/info/InfoPrintTest.java
@@ -14,7 +14,7 @@
  * @since 2021-06-02
  */
 @SpringBootTest
-@Disabled("仅显示BuildProperties, GitProperties")
+@Disabled("Only display BuildProperties, GitProperties")
 @Slf4j
 public class InfoPrintTest {
 
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/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerMergeTest.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerMergeTest.java
index fe7fce051..ca5c91435 100644
--- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerMergeTest.java
+++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerMergeTest.java
@@ -24,7 +24,7 @@ public class WingsSilencerMergeTest {
 
     @Test
     public void merge() {
-        // 替换
+        // replace
         Assertions.assertEquals(List.of("a"), mergingProp.getLst1());
         Assertions.assertEquals(List.of("a"), mergingProp.getLst2());
         Assertions.assertEquals(Set.of("a"), mergingProp.getSet1());
@@ -34,10 +34,10 @@ public void merge() {
         Assertions.assertArrayEquals(new String[]{"a"}, mergingProp.getArr1());
         Assertions.assertArrayEquals(new String[]{"a"}, mergingProp.getArr2());
 
-        // 合并
+        // merge
         Assertions.assertEquals(Map.of("a","a","b","b"), mergingProp.getMap2());
         Assertions.assertEquals(Map.of("a","a","b","b"), mergingProp.getMap2());
-        // 合并
+        // merge
         MergingProp.Pojo po = new MergingProp.Pojo();
         po.setStr1("a");
         po.setStr2("b");
diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile0Test.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile0Test.java
index 9ebfd5bad..b4ac2acd6 100644
--- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile0Test.java
+++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile0Test.java
@@ -33,7 +33,7 @@ public class WingsSilencerProfile0Test {
 
     @Test
     public void profile() {
-        assertEquals("沉默术士", module);
+        assertEquals("Silencer", module);
         assertEquals("wings-silencer", name);
         assertEquals("empty", moduleTest);
         assertEquals("empty", nameTest);
diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile1Test.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile1Test.java
index d7b58e837..8369a0758 100644
--- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile1Test.java
+++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile1Test.java
@@ -35,9 +35,9 @@ public class WingsSilencerProfile1Test {
 
     @Test
     public void profile() {
-        assertEquals("沉默术士-dev", module);
+        assertEquals("Silencer-dev", module);
         assertEquals("wings-silencer-dev", name);
-        assertEquals("沉默术士-dev", moduleDev);
+        assertEquals("Silencer-dev", moduleDev);
         assertEquals("wings-silencer-dev", nameDev);
         assertEquals("empty", moduleTest);
         assertEquals("empty", nameTest);
diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile2Test.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile2Test.java
index 72cddade6..bdc044690 100644
--- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile2Test.java
+++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/boot/WingsSilencerProfile2Test.java
@@ -35,12 +35,12 @@ public class WingsSilencerProfile2Test {
 
     @Test
     public void profile() {
-        assertEquals("沉默术士-test30", moduleTest);
+        assertEquals("Silencer-test30", moduleTest);
         assertEquals("wings-silencer-test", nameTest);
-        assertEquals("沉默术士-dev", moduleDev);
+        assertEquals("Silencer-dev", moduleDev);
         assertEquals("wings-silencer-dev", nameDev);
-        assertEquals("wings-silencer-test", name); // spring 字母顺序,后者优先
-        assertEquals("沉默术士-test30", module); // 序号顺序覆盖
+        assertEquals("wings-silencer-test", name); // spring Alphabetical order, latter taking precedence
+        assertEquals("Silencer-test30", module); // Seq override
         assertEquals("wings-silencer-empty", nameEmpty);
     }
 }
diff --git a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/help/CombinableMessageSourceTest.java b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/help/CombinableMessageSourceTest.java
index 0336caf8c..46c5ce791 100644
--- a/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/help/CombinableMessageSourceTest.java
+++ b/wings/silencer/src/test/java/pro/fessional/wings/silencer/spring/help/CombinableMessageSourceTest.java
@@ -28,16 +28,16 @@ public class CombinableMessageSourceTest {
     public void combine() {
 
         Object[] args = {};
-        String m1 = messageSource.getMessage("test.我的测试", args, Locale.CHINA);
-        combinableMessageSource.addMessage("test.我的测试", Locale.CHINA, "啥都好用");
-        String m2 = messageSource.getMessage("test.我的测试", args, Locale.CHINA);
+        String m1 = messageSource.getMessage("test.MyTest", args, Locale.CHINA);
+        combinableMessageSource.addMessage("test.MyTest", Locale.CHINA, "啥都好用");
+        String m2 = messageSource.getMessage("test.MyTest", args, Locale.CHINA);
 
         StaticMessageSource sms = new StaticMessageSource();
-        sms.addMessage("test.mytest", Locale.CHINA, "又一个测试");
+        sms.addMessage("test.my-test", Locale.CHINA, "又一个测试");
         combinableMessageSource.addMessages(sms, 1);
-        String m3 = messageSource.getMessage("test.mytest", args, Locale.CHINA);
+        String m3 = messageSource.getMessage("test.my-test", args, Locale.CHINA);
 
-        assertEquals("test.我的测试", m1);// code
+        assertEquals("test.MyTest", m1);// code
         assertEquals("啥都好用", m2);
         assertEquals("又一个测试", m3);
     }
diff --git a/wings/silencer/src/test/resources/wings-conf/application-30@test.properties b/wings/silencer/src/test/resources/wings-conf/application-30@test.properties
index 2db2a71ce..337c7a478 100644
--- a/wings/silencer/src/test/resources/wings-conf/application-30@test.properties
+++ b/wings/silencer/src/test/resources/wings-conf/application-30@test.properties
@@ -1,3 +1,3 @@
-# wings扫描并处理,profile test,等同于application-test.properties,但优先级 30
+## wings scan, wings handle, profile test, equal application-test.properties, seq is 30
 spring.application.name=wings-silencer-wings-test30
 spring.application.name-test=wings-silencer-wings-test30
diff --git a/wings/silencer/src/test/resources/wings-conf/application-test.properties b/wings/silencer/src/test/resources/wings-conf/application-test.properties
index 3fda02360..6276a35dc 100644
--- a/wings/silencer/src/test/resources/wings-conf/application-test.properties
+++ b/wings/silencer/src/test/resources/wings-conf/application-test.properties
@@ -1,3 +1,3 @@
-# wings扫描,spring处理,profile test,等同于application-test.properties,但优先级低
+## wings scan, spring handle, profile test, equal application-test.properties, low priority
 spring.application.name=wings-silencer-wings-test
 spring.application.name-test=wings-silencer-wings-test
diff --git a/wings/silencer/src/test/resources/wings-conf/application.properties b/wings/silencer/src/test/resources/wings-conf/application.properties
index 63d341d53..969b202d7 100644
--- a/wings/silencer/src/test/resources/wings-conf/application.properties
+++ b/wings/silencer/src/test/resources/wings-conf/application.properties
@@ -1,2 +1,2 @@
-# wings扫描,spring处理,优先级低于spring
+## wings scan, spring handle, low priority then spring
 spring.application.name=wings-silencer-wings
diff --git a/wings/silencer/src/test/resources/wings-conf/application@test.properties b/wings/silencer/src/test/resources/wings-conf/application@test.properties
index abc9d5e8e..58dea234c 100644
--- a/wings/silencer/src/test/resources/wings-conf/application@test.properties
+++ b/wings/silencer/src/test/resources/wings-conf/application@test.properties
@@ -1,3 +1,3 @@
-# wings扫描并处理,profile test,等同于application-test.properties,但优先级低
+## wings scan, wings handle, profile test, equal application-test.properties, low priority
 spring.application.name=wings-silencer-wings-test70
 spring.application.name-test=wings-silencer-wings-test70
diff --git a/wings/silencer/src/test/resources/wings-conf/wings-test-module-30@test.properties b/wings/silencer/src/test/resources/wings-conf/wings-test-module-30@test.properties
index 0cf3be6f6..97864b09a 100644
--- a/wings/silencer/src/test/resources/wings-conf/wings-test-module-30@test.properties
+++ b/wings/silencer/src/test/resources/wings-conf/wings-test-module-30@test.properties
@@ -1,2 +1,2 @@
-wings.test.module=沉默术士-test30
-wings.test.module-test=沉默术士-test30
+wings.test.module=Silencer-test30
+wings.test.module-test=Silencer-test30
diff --git a/wings/silencer/src/test/resources/wings-conf/wings-test-module.properties b/wings/silencer/src/test/resources/wings-conf/wings-test-module.properties
index b0e360ed4..25456094e 100644
--- a/wings/silencer/src/test/resources/wings-conf/wings-test-module.properties
+++ b/wings/silencer/src/test/resources/wings-conf/wings-test-module.properties
@@ -1 +1 @@
-wings.test.module=沉默术士
\ No newline at end of file
+wings.test.module=Silencer
\ No newline at end of file
diff --git a/wings/silencer/src/test/resources/wings-conf/wings-test-module@dev.properties b/wings/silencer/src/test/resources/wings-conf/wings-test-module@dev.properties
index e1eebcde0..45fa292dd 100644
--- a/wings/silencer/src/test/resources/wings-conf/wings-test-module@dev.properties
+++ b/wings/silencer/src/test/resources/wings-conf/wings-test-module@dev.properties
@@ -1,2 +1,2 @@
-wings.test.module=沉默术士-dev
-wings.test.module-dev=沉默术士-dev
+wings.test.module=Silencer-dev
+wings.test.module-dev=Silencer-dev
diff --git a/wings/silencer/src/test/resources/wings-conf/wings-test-module@test.properties b/wings/silencer/src/test/resources/wings-conf/wings-test-module@test.properties
index d3beaf4dc..67b05cccd 100644
--- a/wings/silencer/src/test/resources/wings-conf/wings-test-module@test.properties
+++ b/wings/silencer/src/test/resources/wings-conf/wings-test-module@test.properties
@@ -1,2 +1,2 @@
-wings.test.module=沉默术士-test
-wings.test.module-test=沉默术士-test
+wings.test.module=Silencer-test
+wings.test.module-test=Silencer-test
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-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcast.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcast.java
deleted file mode 100644
index f44a9332c..000000000
--- a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcast.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package pro.fessional.wings.slardar.cache.hazelcast;
-
-import com.hazelcast.config.InvalidConfigurationException;
-import com.hazelcast.config.MapConfig;
-import com.hazelcast.core.DistributedObject;
-import com.hazelcast.core.HazelcastInstance;
-import com.hazelcast.map.IMap;
-import com.hazelcast.spring.cache.HazelcastCacheManager;
-import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.NotNull;
-import pro.fessional.wings.slardar.cache.WingsCache;
-import pro.fessional.wings.slardar.cache.spring.NullsCache;
-import pro.fessional.wings.slardar.spring.prop.SlardarCacheProp;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static pro.fessional.wings.slardar.spring.prop.SlardarCacheProp.wildcard;
-
-/**
- * @author trydofor
- * @since 2021-02-12
- */
-@Slf4j
-public class WingsHazelcast {
-
-    /**
-     * v4.0.3: Dynamically Adding Data Structure Configuration on a Cluster
-     * v4.0.3: Configuration Pattern Matcher
-     * v5.1 Dynamic Configuration with Programmatic APIs (Java)
-     * v5.1 Using Wildcards
-     */
-    public static class Manager extends HazelcastCacheManager implements WingsCache.State {
-        private final SlardarCacheProp slardarCacheProp;
-        private final ConcurrentHashMap nullsCache = new ConcurrentHashMap<>();
-
-        public Manager(SlardarCacheProp config, HazelcastInstance hazelcastInstance) {
-            super(hazelcastInstance);
-            this.slardarCacheProp = config;
-            checkWingsLevelPattern();
-        }
-
-        @Override
-        public void setHazelcastInstance(HazelcastInstance hazelcastInstance) {
-            super.setHazelcastInstance(hazelcastInstance);
-            checkWingsLevelPattern();
-        }
-
-        @Override
-        public org.springframework.cache.Cache getCache(@NotNull String name) {
-            final int size = slardarCacheProp.getNullSize();
-            if (size < 0) {
-                return super.getCache(name);
-            }
-            else {
-                return nullsCache.computeIfAbsent(name, k -> new NullsCache(super.getCache(k), size, slardarCacheProp.getNullLive()));
-            }
-        }
-
-        @Override
-        @NotNull
-        public Map statsCacheSize() {
-            Collection dst = getHazelcastInstance().getDistributedObjects();
-            final Map stats = new TreeMap<>();
-            for (DistributedObject distributedObject : dst) {
-                if (distributedObject instanceof IMap map) {
-                    stats.put(map.getName(), map.size());
-                }
-            }
-            return stats;
-        }
-
-        @Override
-        @NotNull
-        @SuppressWarnings("unchecked")
-        public Set statsCacheKeys(String name) {
-            Collection dst = getHazelcastInstance().getDistributedObjects();
-            for (DistributedObject distributedObject : dst) {
-                if (distributedObject instanceof IMap map) {
-                    if (map.getName().equals(name)) {
-                        return (Set) map.keySet();
-                    }
-                }
-            }
-            return Collections.emptySet();
-        }
-
-        private void checkWingsLevelPattern() {
-            final com.hazelcast.config.Config config = getHazelcastInstance().getConfig();
-            final Map mapCnf = config.getMapConfigs();
-
-            /*
-            final SlardarCacheProp.Conf common = slardarCacheProp.getCommon();
-            checkMapConf(config, mapCnf, "default", common.getMaxLive(),
-                    common.getMaxIdle(), common.getMaxSize());
-            */
-
-            // check level
-            for (Map.Entry entry : slardarCacheProp.getLevel().entrySet()) {
-                // 前缀同
-                final SlardarCacheProp.Conf lvl = entry.getValue();
-                final String name = wildcard(entry.getKey());
-                checkMapConf(config, mapCnf, name, lvl.getMaxLive(), lvl.getMaxIdle(), lvl.getMaxSize());
-            }
-        }
-
-        private void checkMapConf(com.hazelcast.config.Config config, Map mapCnf, String name, int ttl, int tti, int max) {
-            // check default
-            MapConfig mc = mapCnf.get(name);
-
-            try {
-                if (mc == null) {
-                    mc = new MapConfig(name);
-                    mc.setTimeToLiveSeconds(ttl);
-                    mc.setMaxIdleSeconds(tti);
-                    mc.getEvictionConfig().setSize(max);
-                    log.info("Wings hazelcast addMapConfig name={}, ttl={}, tti={}, size={}", name, ttl, tti, max);
-                    config.addMapConfig(mc);
-                }
-                else {
-                    boolean diff = false;
-                    final int ttl0 = mc.getTimeToLiveSeconds();
-                    if (ttl0 != ttl) {
-                        diff = true;
-                        mc.setTimeToLiveSeconds(ttl);
-                        log.warn("Wings hazelcast exist TimeToLiveSeconds of name={}, from {} to {}", name, ttl0, ttl);
-                    }
-                    final int tti0 = mc.getMaxIdleSeconds();
-                    if (tti0 != tti) {
-                        diff = true;
-                        mc.setMaxIdleSeconds(tti);
-                        log.warn("Wings hazelcast exist MaxIdleSeconds of name={}, from {} to {}", name, tti0, tti);
-                    }
-
-                    int max0 = mc.getEvictionConfig().getSize();
-                    if (max0 != max) {
-                        diff = true;
-                        mc.getEvictionConfig().setSize(max);
-                        log.warn("Wings hazelcast default Eviction-max0 of name={}, from {} to {}", name, max0, max);
-                    }
-
-                    if (diff) {
-                        log.warn("Wings hazelcast dynamically change may has conflict. \nsee https://docs.hazelcast.com/hazelcast/5.1/configuration/dynamic-config-programmatic-api");
-                    }
-                }
-            }
-            catch (InvalidConfigurationException e) {
-                log.error("failed to change MapConfig, name=" + name, e);
-            }
-        }
-    }
-}
diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcastCacheCustomizer.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcastCacheCustomizer.java
new file mode 100644
index 000000000..8915c09d1
--- /dev/null
+++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcastCacheCustomizer.java
@@ -0,0 +1,88 @@
+package pro.fessional.wings.slardar.cache.hazelcast;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.InvalidConfigurationException;
+import com.hazelcast.config.MapConfig;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.hazelcast.HazelcastConfigCustomizer;
+import pro.fessional.wings.slardar.spring.prop.SlardarCacheProp;
+
+import java.util.Map;
+
+import static pro.fessional.wings.slardar.spring.prop.SlardarCacheProp.wildcard;
+
+/**
+ * @author trydofor
+ * @since 2023-07-18
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class WingsHazelcastCacheCustomizer implements HazelcastConfigCustomizer {
+
+    private final SlardarCacheProp cacheProp;
+
+    @Override
+    public void customize(Config config) {
+        final Map mapCnf = config.getMapConfigs();
+
+        /*
+        final SlardarCacheProp.Conf common = slardarCacheProp.getCommon();
+        checkMapConf(config, mapCnf, "default", common.getMaxLive(),
+                common.getMaxIdle(), common.getMaxSize());
+        */
+
+        // check level
+        for (Map.Entry entry : cacheProp.getLevel().entrySet()) {
+            final SlardarCacheProp.Conf lvl = entry.getValue();
+            final String name = wildcard(entry.getKey());
+            checkMapConf(config, mapCnf, name, lvl.getMaxLive(), lvl.getMaxIdle(), lvl.getMaxSize());
+        }
+    }
+
+
+    private void checkMapConf(Config config, Map mapCnf, String name, int ttl, int tti, int max) {
+        // check default
+        MapConfig mc = mapCnf.get(name);
+
+        try {
+            if (mc == null) {
+                mc = new MapConfig(name);
+                mc.setTimeToLiveSeconds(ttl);
+                mc.setMaxIdleSeconds(tti);
+                mc.getEvictionConfig().setSize(max);
+                log.info("Wings hazelcast addMapConfig name={}, ttl={}, tti={}, size={}", name, ttl, tti, max);
+                config.addMapConfig(mc);
+            }
+            else {
+                boolean diff = false;
+                final int ttl0 = mc.getTimeToLiveSeconds();
+                if (ttl0 != ttl) {
+                    diff = true;
+                    mc.setTimeToLiveSeconds(ttl);
+                    log.warn("Wings hazelcast exist TimeToLiveSeconds of name={}, from {} to {}", name, ttl0, ttl);
+                }
+                final int tti0 = mc.getMaxIdleSeconds();
+                if (tti0 != tti) {
+                    diff = true;
+                    mc.setMaxIdleSeconds(tti);
+                    log.warn("Wings hazelcast exist MaxIdleSeconds of name={}, from {} to {}", name, tti0, tti);
+                }
+
+                int max0 = mc.getEvictionConfig().getSize();
+                if (max0 != max) {
+                    diff = true;
+                    mc.getEvictionConfig().setSize(max);
+                    log.warn("Wings hazelcast default Eviction-max0 of name={}, from {} to {}", name, max0, max);
+                }
+
+                if (diff) {
+                    log.warn("Wings hazelcast dynamically change may has conflict. \nsee https://docs.hazelcast.com/hazelcast/5.1/configuration/dynamic-config-programmatic-api");
+                }
+            }
+        }
+        catch (InvalidConfigurationException e) {
+            log.error("failed to change MapConfig, name=" + name, e);
+        }
+    }
+}
diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcastCacheManager.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcastCacheManager.java
new file mode 100644
index 000000000..5d7974a19
--- /dev/null
+++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/cache/hazelcast/WingsHazelcastCacheManager.java
@@ -0,0 +1,82 @@
+package pro.fessional.wings.slardar.cache.hazelcast;
+
+import com.hazelcast.core.DistributedObject;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.map.IMap;
+import com.hazelcast.spring.cache.HazelcastCacheManager;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+import pro.fessional.wings.slardar.cache.WingsCache;
+import pro.fessional.wings.slardar.cache.spring.NullsCache;
+import pro.fessional.wings.slardar.spring.prop.SlardarCacheProp;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * v4.0.3: Dynamically Adding Data Structure Configuration on a Cluster
+ * v4.0.3: Configuration Pattern Matcher
+ * v5.1 Dynamic Configuration with Programmatic APIs (Java)
+ * v5.1 Using Wildcards
+ *
+ * @author trydofor
+ * @since 2023-07-18
+ */
+@Slf4j
+public class WingsHazelcastCacheManager extends HazelcastCacheManager implements WingsCache.State {
+    private final SlardarCacheProp slardarCacheProp;
+    private final ConcurrentHashMap nullsCache = new ConcurrentHashMap<>();
+
+    public WingsHazelcastCacheManager(SlardarCacheProp cacheProp, HazelcastInstance hazelcastInstance) {
+        super(hazelcastInstance);
+        this.slardarCacheProp = cacheProp;
+    }
+
+    @Override
+    public void setHazelcastInstance(HazelcastInstance hazelcastInstance) {
+        super.setHazelcastInstance(hazelcastInstance);
+    }
+
+    @Override
+    public org.springframework.cache.Cache getCache(@NotNull String name) {
+        final int size = slardarCacheProp.getNullSize();
+        if (size < 0) {
+            return super.getCache(name);
+        }
+        else {
+            return nullsCache.computeIfAbsent(name, k -> new NullsCache(super.getCache(k), size, slardarCacheProp.getNullLive()));
+        }
+    }
+
+    @Override
+    @NotNull
+    public Map statsCacheSize() {
+        Collection dst = getHazelcastInstance().getDistributedObjects();
+        final Map stats = new TreeMap<>();
+        for (DistributedObject distributedObject : dst) {
+            if (distributedObject instanceof IMap map) {
+                stats.put(map.getName(), map.size());
+            }
+        }
+        return stats;
+    }
+
+    @Override
+    @NotNull
+    @SuppressWarnings("unchecked")
+    public Set statsCacheKeys(String name) {
+        Collection dst = getHazelcastInstance().getDistributedObjects();
+        for (DistributedObject distributedObject : dst) {
+            if (distributedObject instanceof IMap map) {
+                if (map.getName().equals(name)) {
+                    return (Set) map.keySet();
+                }
+            }
+        }
+        return Collections.emptySet();
+    }
+}
diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/HazelcastGlobalLock.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/HazelcastGlobalLock.java
index e59d91821..1a40b6cd7 100644
--- a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/HazelcastGlobalLock.java
+++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/HazelcastGlobalLock.java
@@ -1,50 +1,42 @@
 package pro.fessional.wings.slardar.concur;
 
 import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.map.IMap;
 import org.jetbrains.annotations.NotNull;
 import pro.fessional.mirana.lock.GlobalLock;
-import pro.fessional.wings.slardar.concur.impl.HazelcastIMapLock;
+import pro.fessional.wings.slardar.concur.impl.HazelcastMapLock;
 
 import java.util.concurrent.locks.Lock;
 
+import static pro.fessional.wings.slardar.constants.HazelcastConst.MapGlobalLock;
+
 /**
- * 默认使用IMap.lock实现,可配置使用CPSubsystem实现锁。
- * 当CpSubsystem可用时(CPMemberCount>0),可选用Raft的lock锁
+ * 
+ * Pessimistic Locking IMap.lock/unlock
+ * The lock will automatically be collected by the garbage collector when the lock is released and no other waiting conditions exist on the lock.
+ *
+ * FencedLock -
+ * Locks are not automatically removed. If a lock is not used anymore,
+ * Hazelcast does not automatically perform garbage collection in the lock.
+ * This can lead to an OutOfMemoryError.
+ * If you create locks on the fly, make sure they are destroyed.
+ * 
* * @author trydofor + * @see pro.fessional.wings.slardar.constants.HazelcastConst#MapGlobalLock * @since 2021-03-08 */ public class HazelcastGlobalLock implements GlobalLock { - private static final String IMapKey = "wings:global:lock"; - private final HazelcastInstance hazelcastInstance; - private final boolean useCpIfSafe; + private final IMap hazelcastMap; - /** - * 默认使用Imap实现 - * - * @param hazelcastInstance 实例 - */ public HazelcastGlobalLock(HazelcastInstance hazelcastInstance) { - this.hazelcastInstance = hazelcastInstance; - this.useCpIfSafe = false; - } - - /** - * @param hazelcastInstance 实例 - * @param useCpIfSafe 当CPMemberCount>0时,使用CP - */ - public HazelcastGlobalLock(HazelcastInstance hazelcastInstance, boolean useCpIfSafe) { - this.hazelcastInstance = hazelcastInstance; - this.useCpIfSafe = useCpIfSafe && hazelcastInstance.getConfig().getCPSubsystemConfig().getCPMemberCount() > 0; + hazelcastMap = hazelcastInstance.getMap(MapGlobalLock); } @Override - public @NotNull Lock getLock(@NotNull String name) { - if (useCpIfSafe) { - return hazelcastInstance.getCPSubsystem().getLock(name); - } else { - return new HazelcastIMapLock(hazelcastInstance.getMap(IMapKey), name); - } + @NotNull + public Lock getLock(@NotNull String name) { + return new HazelcastMapLock(hazelcastMap, name); } } diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/impl/HazelcastIMapLock.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/impl/HazelcastMapLock.java similarity index 95% rename from wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/impl/HazelcastIMapLock.java rename to wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/impl/HazelcastMapLock.java index 13838548f..dd268c5e3 100644 --- a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/impl/HazelcastIMapLock.java +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/concur/impl/HazelcastMapLock.java @@ -14,7 +14,7 @@ * @since 2021-03-09 */ @RequiredArgsConstructor -public class HazelcastIMapLock implements Lock { +public class HazelcastMapLock implements Lock { private final IMap imap; private final String name; diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/constants/HazelcastConst.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/constants/HazelcastConst.java new file mode 100644 index 000000000..a38205b06 --- /dev/null +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/constants/HazelcastConst.java @@ -0,0 +1,13 @@ +package pro.fessional.wings.slardar.constants; + +/** + * @author trydofor + * @since 2023-07-18 + */ +public interface HazelcastConst { + + String MapGlobalLock = "wings:global:lock"; + String MapLightId = "wings:lightid"; + String TopicApplicationEvent = "SlardarApplicationEvent"; + +} 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..81f747891 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,33 +4,34 @@ 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; +import static pro.fessional.wings.slardar.constants.HazelcastConst.TopicApplicationEvent; + /** - * ApplicationEventPublisher辅助类。一般用于非事务Event处理,主要功能: - * ①异步发布。 - * ②IDE提示导航。 - * ③hazelcast的topic(#HazelcastTopic)按SpringEvent模式。 + * ApplicationEventPublisher is a helper. Generally used for non-transactional Event processing, with the following main functions: + * (1) Asynchronous publishing. + * (2) IDE prompt navigation. + * (3) Hazelcast topic (#HazelcastTopic) in the SpringEvent pattern. * * @author trydofor - * @see #HazelcastTopic + * @see pro.fessional.wings.slardar.constants.HazelcastConst#TopicApplicationEvent * @since 2021-06-07 */ - +@Slf4j public class HazelcastSyncPublisher implements ApplicationEventPublisher, MessageListener { - public static final String HazelcastTopic = "SlardarApplicationEvent"; - private final ApplicationEventPublisher publisher; private final ITopic topic; private final UUID uuid; public HazelcastSyncPublisher(@NotNull HazelcastInstance instance, @NotNull ApplicationEventPublisher publisher) { this.publisher = publisher; - topic = instance.getTopic(HazelcastTopic); + topic = instance.getTopic(TopicApplicationEvent); uuid = topic.addMessageListener(this); } @@ -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-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/HazelcastLightIdProvider.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/HazelcastLightIdProvider.java new file mode 100644 index 000000000..a4be6b1f4 --- /dev/null +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/HazelcastLightIdProvider.java @@ -0,0 +1,80 @@ +package pro.fessional.wings.slardar.service.lightid; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import pro.fessional.mirana.id.LightIdProvider; +import pro.fessional.mirana.pain.TimeoutRuntimeException; +import pro.fessional.wings.slardar.service.lightid.ser.LidKey; +import pro.fessional.wings.slardar.service.lightid.ser.LidSeg; + +import java.util.concurrent.TimeUnit; + +import static pro.fessional.wings.slardar.constants.HazelcastConst.MapLightId; + +/** + * @author trydofor + * @since 2023-07-17 + */ + +@Slf4j +public class HazelcastLightIdProvider implements LightIdProvider { + + private final Loader loader; + private final IMap mapper; + + @Setter @Getter + private long timeout = 1000; + + public HazelcastLightIdProvider(Loader loader, HazelcastInstance hazelcastInstance) { + this.loader = loader; + this.mapper = hazelcastInstance.getMap(MapLightId); + } + + @Override + public long next(@NotNull String name, int block) { + return next(name, block, timeout); + } + + @Override + public long next(@NotNull String name, int block, long timeout) { + final long throwMs = System.currentTimeMillis() + timeout; + + final LidKey key = new LidKey(name, block); + final long next, foot; + mapper.lock(key, timeout * 2, TimeUnit.MILLISECONDS); + try { + final LidSeg seg = mapper.get(key); + if (seg == null) { + final Segment sg = loader.require(name, block, 1, false); + next = sg.getHead(); + foot = sg.getFoot(); + mapper.put(key, new LidSeg(next + 1, foot)); + } + else { + next = seg.getNext(); + foot = seg.getFoot(); + if (next == foot) { + final Segment sg = loader.require(name, block, 1, false); + mapper.put(key, new LidSeg(sg.getHead(), sg.getFoot())); + } + else { + mapper.put(key, new LidSeg(next + 1, foot)); + } + } + } + finally { + mapper.unlock(key); + } + + final long now = System.currentTimeMillis(); + if (now > throwMs) { + throw new TimeoutRuntimeException("loading timeout=" + (now - throwMs + timeout)); + } + + return next; + } +} diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/ser/LidKey.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/ser/LidKey.java new file mode 100644 index 000000000..9f1f996d1 --- /dev/null +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/ser/LidKey.java @@ -0,0 +1,34 @@ +package pro.fessional.wings.slardar.service.lightid.ser; + +import com.hazelcast.nio.ObjectDataInput; +import com.hazelcast.nio.ObjectDataOutput; +import com.hazelcast.nio.serialization.DataSerializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.IOException; + +/** + * @author trydofor + * @since 2023-07-18 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LidKey implements DataSerializable { + + private String name; + private int block; + @Override + public void writeData(ObjectDataOutput out) throws IOException { + out.writeString(name); + out.write(block); + } + + @Override + public void readData(ObjectDataInput in) throws IOException { + name = in.readString(); + block = in.readInt(); + } +} diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/ser/LidSeg.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/ser/LidSeg.java new file mode 100644 index 000000000..c7af71d2c --- /dev/null +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/service/lightid/ser/LidSeg.java @@ -0,0 +1,35 @@ +package pro.fessional.wings.slardar.service.lightid.ser; + +import com.hazelcast.nio.ObjectDataInput; +import com.hazelcast.nio.ObjectDataOutput; +import com.hazelcast.nio.serialization.DataSerializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.IOException; + +/** + * @author trydofor + * @since 2023-07-18 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LidSeg implements DataSerializable { + + private long next; + private long foot; + + @Override + public void writeData(ObjectDataOutput out) throws IOException { + out.writeLong(next); + out.writeLong(foot); + } + + @Override + public void readData(ObjectDataInput in) throws IOException { + next = in.readLong(); + foot = in.readLong(); + } +} diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastCacheConfiguration.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastCacheConfiguration.java index 634258810..f537fa0fb 100644 --- a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastCacheConfiguration.java +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastCacheConfiguration.java @@ -7,12 +7,14 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConfigCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; -import pro.fessional.wings.slardar.cache.hazelcast.WingsHazelcast; +import pro.fessional.wings.slardar.cache.hazelcast.WingsHazelcastCacheCustomizer; +import pro.fessional.wings.slardar.cache.hazelcast.WingsHazelcastCacheManager; import pro.fessional.wings.slardar.spring.prop.SlardarCacheProp; import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; import static pro.fessional.wings.slardar.cache.WingsCache.Manager; @@ -23,14 +25,20 @@ @Configuration(proxyBeanMethods = false) @AutoConfigureBefore(SlardarCacheConfiguration.class) @AutoConfigureOrder(OrderedSlardarConst.HazelcastCacheConfiguration) +@ConditionalOnProperty(name = SlardarEnabledProp.Key$caching, havingValue = "true") public class HazelcastCacheConfiguration { private static final Log log = LogFactory.getLog(HazelcastCacheConfiguration.class); + @Bean + public HazelcastConfigCustomizer wingsHazelcastCacheCustomizer(SlardarCacheProp conf) { + log.info("SlardarHazelCaching spring-bean wingsHazelcastCacheCustomizer"); + return new WingsHazelcastCacheCustomizer(conf); + } + @Bean(Manager.Server) - @ConditionalOnProperty(name = SlardarEnabledProp.Key$caching, havingValue = "true") public HazelcastCacheManager hazelcastCacheManager(SlardarCacheProp conf, HazelcastInstance instance) { log.info("SlardarHazelCaching spring-bean hazelcast as " + Manager.Server); - return new WingsHazelcast.Manager(conf, instance); + return new WingsHazelcastCacheManager(conf, instance); } } diff --git a/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastLightIdConfiguration.java b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastLightIdConfiguration.java new file mode 100644 index 000000000..b131ddf9b --- /dev/null +++ b/wings/slardar-hazel-caching/src/main/java/pro/fessional/wings/slardar/spring/bean/HazelcastLightIdConfiguration.java @@ -0,0 +1,45 @@ +package pro.fessional.wings.slardar.spring.bean; + +import com.hazelcast.core.HazelcastInstance; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import pro.fessional.mirana.id.LightIdProvider; +import pro.fessional.wings.faceless.service.flakeid.FlakeIdService; +import pro.fessional.wings.faceless.spring.bean.FacelessLightIdConfiguration; +import pro.fessional.wings.faceless.spring.prop.LightIdProviderProp; +import pro.fessional.wings.slardar.service.lightid.HazelcastLightIdProvider; + +/** + * @author trydofor + * @since 2019-12-03 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(FlakeIdService.class) +@AutoConfigureBefore(FacelessLightIdConfiguration.class) +public class HazelcastLightIdConfiguration { + + private static final Log log = LogFactory.getLog(HazelcastLightIdConfiguration.class); + + @Bean + @ConditionalOnMissingBean(LightIdProvider.class) + @ConditionalOnProperty(name = LightIdProviderProp.Key$monotonic, havingValue = "hz") + public LightIdProvider lightIdProvider(LightIdProvider.Loader lightIdLoader, LightIdProviderProp providerProp, HazelcastInstance hazelcastInstance) { + final String mono = providerProp.getMonotonic(); + log.info("Faceless spring-bean lightIdProvider via Hazelcast"); + if ("hz".equalsIgnoreCase(mono)) { + // avg=1.065ms + HazelcastLightIdProvider provider = new HazelcastLightIdProvider(lightIdLoader, hazelcastInstance); + provider.setTimeout(providerProp.getTimeout()); + return provider; + } + else { + throw new IllegalArgumentException("unsupported monotonic type=" + mono); + } + } +} diff --git a/wings/slardar-hazel-caching/src/main/resources/extra-conf/hazelcast-server.xml b/wings/slardar-hazel-caching/src/main/resources/extra-conf/hazelcast-server.xml index e1a66eddd..72af9ca95 100644 --- a/wings/slardar-hazel-caching/src/main/resources/extra-conf/hazelcast-server.xml +++ b/wings/slardar-hazel-caching/src/main/resources/extra-conf/hazelcast-server.xml @@ -9,7 +9,7 @@ ${wings.slardar.hazelcast.cluster-name} - + 5701 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-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/slardar-hazel-session/src/test/http/hazelcast.http b/wings/slardar-hazel-session/src/test/http/hazelcast.http index 8635fdb3b..c886050b2 100644 --- a/wings/slardar-hazel-session/src/test/http/hazelcast.http +++ b/wings/slardar-hazel-session/src/test/http/hazelcast.http @@ -1,10 +1,10 @@ ### login-page GET {{host}}/test/hazelcast-get.json?key=trydofor -# wings-slardar-pass 的 复制一次,然后MD5,即md5(pass+pass) +## repeat wings-slardar-pass and then MD5, that is md5(pass+pass) # ### login-proc POST {{host}}/test/hazelcast-put.json Content-Type: application/x-www-form-urlencoded;charset=UTF-8 -key=trydofor&value=东北程序猿 +key=trydofor&value=Northeastern Code Monkey diff --git a/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfigurationTest.java b/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfigurationTest.java index 47372a3bc..da44258d4 100644 --- a/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfigurationTest.java +++ b/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/SlardarCacheConfigurationTest.java @@ -42,7 +42,7 @@ public void cacheCall() { } @Test - @Disabled("模拟慢处理 ttl=20") + @Disabled("Mock slow handling ttl=20") public void testTtl() throws InterruptedException { int c1 = cacheService.cacheMemory("cacheCall"); assertEquals(1, c1); diff --git a/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java b/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java index d1a6e42e8..3530a65a5 100644 --- a/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java +++ b/wings/slardar-hazel-session/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java @@ -13,7 +13,7 @@ /** - * WingsSessionTest 使用 + * Used by WingsSessionTest * * @author trydofor * @since 2019-12-01 @@ -66,8 +66,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/authed/*").authenticated() ) .formLogin(conf -> conf - .loginPage("/user/login.json") // 无权限时返回的页面, - .loginProcessingUrl("/user/login-proc.json") // filter处理,不需要controller + .loginPage("/user/login.json") // 401 page + .loginProcessingUrl("/user/login-proc.json") // handle by filter, no controller .usernameParameter("username") .passwordParameter("password") .successHandler(testLoginHandler.loginSuccess) diff --git a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarBootAdminConfiguration.java b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarBootAdminConfiguration.java index aeb4b138f..d5844104a 100644 --- a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarBootAdminConfiguration.java +++ b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarBootAdminConfiguration.java @@ -29,12 +29,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; import pro.fessional.wings.slardar.monitor.WarnMetric; import pro.fessional.wings.slardar.monitor.report.DingTalkReport; import pro.fessional.wings.slardar.security.pass.BasicPasswordEncoder; import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp; import pro.fessional.wings.slardar.spring.prop.SlardarPasscoderProp; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; import reactor.core.publisher.Mono; import java.util.ArrayList; @@ -57,10 +57,13 @@ public class SlardarBootAdminConfiguration { @ConditionalOnClass(BlockingRegistrationClient.class) @ConditionalOnExpression("${" + SlardarEnabledProp.Key$bootAdmin + ":false} && ${spring.boot.admin.client.enabled:false}") public static class ClientConfiguration { - /* + /** + *
          * org.apache.http.client.protocol.ResponseProcessCookies : Invalid cookie header: "Set-Cookie: ...".
          * Invalid 'expires' attribute: Sat, 19 Mar 2022 06:03:21 GMT
-         * 因其默认采用 'EEE, dd-MMM-yy HH:mm:ss z'格式验证cookie,导致不能保持session
+          *
+         * As it use 'EEE, dd-MMM-yyy HH:mm:ss z' format to validate cookie, cause faile
+         * 
*/ @Bean @Conditional(SpringBootAdminClientEnabledCondition.class) diff --git a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSecurityConfiguration.java b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSecurityConfiguration.java index bd6a3a6f3..2f5f16b40 100644 --- a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSecurityConfiguration.java +++ b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSecurityConfiguration.java @@ -42,16 +42,18 @@ public class SlardarSecurityConfiguration { private final SlardarPasscoderProp slardarPasscoderProp; /** + *
      * #@Async
      * #spring.security.strategy=MODE_INHERITABLETHREADLOCAL
-     * 

+ * * #{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG * #{noop}password * #{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc * #{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= - *

- * # 在 2019 年,我建议你以后不要使用 PBKDF2 或 BCrypt,并强烈建议将 Argon2(最好是 Argon2id)用于最新系统。 - * # BScrypt 是当 Argon2 不可用时的不二选择,但要记住,它在侧信道泄露方面也存在相同的问题。 + * + * # strongly recommend Argon2 (preferably Argon2id) for up-to-date systems. + * # BScrypt is good choice when Argon2 is not available + *

* * @return PasswordEncoder */ @@ -95,9 +97,6 @@ else if ("md5".equalsIgnoreCase(encoder)) { return new DefaultPasssaltEncoder(md); } - /** - * 使用wings配置,提到spring默认配置 - */ @Bean public WingsSecBeanInitConfigurer wingsSecBeanInitConfigurer(ApplicationContext context) { log.info("SlardarSprint spring-bean wingsSecBeanInitConfigurer"); @@ -118,7 +117,7 @@ public TerminalContext.Listener LocaleContextHolderTerminalContextListener() { } /** - * 与TerminalContext同步处理Locale和TimeZone + * Sync Locale and TimeZone with TerminalContext */ @Bean public CommandLineRunnerOrdered runnerTerminalContextListener(Map listeners) { diff --git a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsBindLoginConfigurer.java b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsBindLoginConfigurer.java index 7a6189804..186ab1a9e 100644 --- a/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsBindLoginConfigurer.java +++ b/wings/slardar-sprint/src/main/java/pro/fessional/wings/slardar/spring/conf/WingsBindLoginConfigurer.java @@ -25,7 +25,7 @@ import java.util.Set; /** - * 同 FormLoginConfigurer(final无法继承) + * Same as FormLoginConfigurer (for can not extend `final`) * * @author trydofor * @since 2021-02-07 diff --git a/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java b/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java index a4f561b9e..142c62d56 100644 --- a/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java +++ b/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/spring/bean/TestSecurityConfiguration.java @@ -44,8 +44,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .permitTest() ) .bindLogin(conf -> conf - .loginPage("/user/login.json") // 无权限时返回的页面, - .loginProcessingUrl("/*/login-proc.json") // filter处理,不需要controller + .loginPage("/user/login.json") + .loginProcessingUrl("/*/login-proc.json") .usernameParameter("username") .passwordParameter("password") .successHandler((request, response, authentication) -> log.info("successHandler")) @@ -57,8 +57,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/authed/*").authenticated() ) // .formLogin(conf -> conf -// .loginPage("/user/login.json") // 无权限时返回的页面, -// .loginProcessingUrl("/user/login-proc.json") // filter处理,不需要controller +// .loginPage("/user/login.json") +// .loginProcessingUrl("/user/login-proc.json") // .usernameParameter("username") // .passwordParameter("password") // .successHandler(testLoginHandler.loginSuccess) diff --git a/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/webmvc/RighterControllerTest.java b/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/webmvc/RighterControllerTest.java index 1e5356b83..682a8b2e5 100644 --- a/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/webmvc/RighterControllerTest.java +++ b/wings/slardar-sprint/src/test/java/pro/fessional/wings/slardar/webmvc/RighterControllerTest.java @@ -60,7 +60,7 @@ public void righter() throws Exception { .andReturn(); final String allow = result.getResponse().getHeader(prop.getHeader()); - // 通过 + // pass log.info("righter .... right"); mvc.perform(post("/test/righter.json") .contentType(MediaType.APPLICATION_JSON) @@ -68,7 +68,7 @@ public void righter() throws Exception { .andDo(print()) .andExpect(content().json("{\"uid\":1,\"perms\":[\"a\",\"b\"]}")); - // 篡改,失败 + // forgery, fail log.info("righter .... failed"); mvc.perform(post("/test/righter.json") .contentType(MediaType.APPLICATION_JSON) diff --git a/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginResultEnum.java b/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginResultEnum.java index 038d9ae38..a1615e3d2 100644 --- a/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginResultEnum.java +++ b/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginResultEnum.java @@ -8,8 +8,8 @@ * @since 2021-02-01 */ public enum LoginResultEnum implements CodeEnum { - Success("200", "登录成功"), - Failure("401", "登录失败"); + Success("200", "Login Success"), + Failure("401", "Login Failure"); private final String code; private final String hint; diff --git a/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginTypeEnum.java b/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginTypeEnum.java index 1ac14b622..d38f539ec 100644 --- a/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginTypeEnum.java +++ b/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/enums/LoginTypeEnum.java @@ -8,8 +8,8 @@ * @since 2021-02-01 */ public enum LoginTypeEnum implements CodeEnum { - User("200", "用户名密码"), - Sms("401", "短信验证码"); + User("200", "Password"), + Sms("401", "Sms Code"); private final String code; private final String hint; diff --git a/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/service/TestWingsUserDetailsService.java b/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/service/TestWingsUserDetailsService.java index 66383befd..4661cdb24 100644 --- a/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/service/TestWingsUserDetailsService.java +++ b/wings/slardar-test/src/main/java/pro/fessional/wings/slardar/security/service/TestWingsUserDetailsService.java @@ -27,7 +27,7 @@ @Slf4j public class TestWingsUserDetailsService implements WingsUserDetailsService { - public final String origPassword = "大翅挺好吃"; + public final String origPassword = "WingsBoot is Good"; //F9EC9CF4EA9EEEE69FC01AA44638087F public final String sendPassword = Md5.sum(origPassword + ":" + origPassword); private final String hashPassword = "{bcrypt}" + new BCryptPasswordEncoder().encode(sendPassword); @@ -57,7 +57,7 @@ public class TestWingsUserDetailsService implements WingsUserDetailsService { } public static void main(String[] args) { - final String origPassword = "大翅挺好吃"; + final String origPassword = "WingsBoot is Good"; // final String sendPassword = Md5.sum(origPassword + ":" + origPassword); log.info("password={}", sendPassword); diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Debounce.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Debounce.java index b8bfd437b..f78e7b76d 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Debounce.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Debounce.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * 先调用后等待的防抖,默认同一session防抖时间内只能被执行一次。 + * Execute first and debounce later, the same session can have only one executing call in the debouncing time. * * @author trydofor * @since 2022-05-29 @@ -16,52 +16,38 @@ public @interface Debounce { /** - * 是否复用之前的请求,还是直接返回。 - * - * @return 是否复用 + * Whether to wait and reuse the previous request result or return it directly. */ boolean reuse() default false; /** - * 防抖的等待间隔,毫秒 - * - * @return 防抖间隔 + * Interval of debounce waiting in ms */ long waiting() default 500; /** - * 组合key中是否包含sessionId - * - * @return 是否包含 + * Whether the combination key contains the sessionId */ boolean session() default true; /** - * 组合key中是否包含method - * - * @return 是否包含 + * Whether the combination key contains method */ boolean method() default true; /** - * 组合key中是否包含querystring - * - * @return 是否包含 + * Whether the combination key contains querystring */ boolean query() default true; /** - * 组合key中包含的header name - * - * @return header数组 + * Header names contained in the combination key */ String[] header() default {}; /** - * 组合key中包含的body的md5sum或length。 - * 如果request支wings流复用,则采用md5,否则取length - * - * @return 是否包含 + * Whether the combination key contains the md5sum or length of the body. + * If request support wings reuse stream, then use md5, otherwise take length */ boolean body() default false; } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/FirstBlood.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/FirstBlood.java index 6312654e7..687dab029 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/FirstBlood.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/FirstBlood.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * 用来注解需要验证码支持的RequestMapping + * Annotate RequestMapping that requires CAPTCHA support * * @author trydofor * @since 2021-03-10 @@ -16,32 +16,24 @@ public @interface FirstBlood { /** - * 两次请求触发验证的间隔秒数,默认0,表示每次都验证。 - * - * @return 计数秒数 + * The seconds between two requests triggering CAPTCHA, default 0, means CAPTCHA every time. */ int first() default 0; /** - * 验证或禁用的持续秒数。以10秒为一个阶梯,建议不超过1天。 - * - * @return 掉血秒数 + * The seconds of CAPTCHA or disable the duration. In increments of 10s, no more than 1 day is recommended. */ int blood() default 300; /** - * 试错的次数,超过时重发验证码或禁用 - * - * @return 试错的次数 + * Number of CAPTCHA retry, resend CAPTCHA when exceeded */ int retry() default 1; /** - * 验证场景,会传递给interceptor - * - * @return 验证场景 + * CAPTCHA scenarios, which are passed to the interceptor */ String scene() default ""; } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Righter.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Righter.java index 640dc449a..0629bde35 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Righter.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/Righter.java @@ -14,9 +14,9 @@ public @interface Righter { /** - * 提交方法建议true,无header时审查失败。读取方法建议false,以提供审查数据 - * - * @return 是否强制验证 + * Whether to force audit. + * Write method suggests true, audit fails if header is missing. + * Read method suggests false to provide data. */ boolean value() default true; } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/WingsCaptchaHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/WingsCaptchaHelper.java index da1db9d24..171d9d4c7 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/WingsCaptchaHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/WingsCaptchaHelper.java @@ -27,41 +27,29 @@ public class WingsCaptchaHelper { private static final ThreadLocal Kaptcha = ThreadLocal.withInitial(WingsCaptchaHelper::kaptcha); /** - * 从23456789ABCDEFGHJKLMPQRSTUWXY中,生成6位,200x60 - * - * @return 验证码 + * Generate 6 char code from `23456789abdefghqrtABCDEFGHJKLMPQRSTUWXY`, 200x60 pix image * @see Config */ public static Producer kaptcha() { Properties properties = new Properties(); - // 文本集合,验证码值从此集合中获取, 默认为abcde2345678gfynmnpwx properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_STRING, "23456789abdefghqrtABCDEFGHJKLMPQRSTUWXY"); - // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "200"); - // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "70"); - // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "48"); - // 验证码文本字符间距 默认为2 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "2"); - // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, String.valueOf(CODE_LEN)); - // 是否有边框 默认为true,yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); - // 边框颜色 默认为Color.BLACK, RGB // properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); - // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "red"); - // 默认KAPTCHA_SESSION_KEY,没啥用 // properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); - // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) // properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); - // 验证码噪点颜色 默认为Color.BLACK // properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); - // 干扰实现类,默认DefaultNoise // properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); - // 图片样式 默认WaterRipple,水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + // Image style, WaterRipple default. + // com.google.code.kaptcha.impl.WaterRipple + // com.google.code.kaptcha.impl.FishEyeGimpy + // com.google.code.kaptcha.impl.ShadowGimpy // properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.FishEyeGimpy"); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(new Config(properties)); diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodHandler.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodHandler.java index 1fcec125e..0e239340a 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodHandler.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodHandler.java @@ -15,20 +15,12 @@ public interface FirstBloodHandler extends Ordered { /** - * 是否能够处理当前请求,如果accept就必须处理 - * - * @param request HttpServletRequest - * @param anno FirstBlood - * @return true 如果可以 + * Whether the request can be handled, if accept it must be handled. */ boolean accept(@NotNull HttpServletRequest request, @NotNull FirstBlood anno); /** - * 处理请求,告知通过验证 - * - * @param request request - * @param response response - * @return 是否通过验证 + * Handle the request and response, return whether the Captcha is successful */ boolean handle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodImageHandler.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodImageHandler.java index ca6ce5c8e..7c103c25a 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodImageHandler.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/FirstBloodImageHandler.java @@ -26,9 +26,11 @@ /** - * 接受scene为空或以image开始的验证, - * - 发行时,同时设置header和coolie。 - * - 取码和鉴别时,通知支持header和parameter + *
+ * Accepts captcha where scene is `empty` or starts with `image`.
+ * - When need captcha, set both header and coolie.
+ * - Support for header and parameter when fetching code and check
+ * 
* * @author trydofor * @since 2021-03-11 @@ -79,7 +81,7 @@ public boolean handle(@NotNull HttpServletRequest request, tkn = (Tkn) cache.computeIfAbsent(key, k -> new Tkn(now)); } - // 获取验证图,或验证 + // Get the image Captcha, or check Captcha final String ck = getKeyCode(request, questCaptchaKey); if (!ck.isEmpty()) { if (tkn.check(ck, caseIgnore, false)) { @@ -93,13 +95,13 @@ public boolean handle(@NotNull HttpServletRequest request, return false; } - // 检查验证码 + // check Captcha String vk = getKeyCode(request, checkCaptchaKey); if (!vk.isEmpty() && tkn.check(vk, caseIgnore, true)) { return true; } - // 3秒外,非连击,不用验证 + // more than 3 seconds, not double request, no verification needed final int fst = anno.first(); final long rct = tkn.recent; if (fst > 3 && (rct == now || rct + fst * 1000L < now)) { @@ -107,17 +109,17 @@ public boolean handle(@NotNull HttpServletRequest request, return true; } - // 通知需要验证码,在header和cookie中设置uniqueKey + // CAPTCHA is required, set uniqueKey in header and cookie needCaptcha(request, response, key.clientCode); return false; } /** - * 告知client需要身份验证 + * Response the client that authentication is required * * @param response response - * @param token 身份标记 + * @param token captcha token */ protected void needCaptcha(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, String token) { ResponseHelper.bothHeadCookie(response, clientTicketKey, token, 600); @@ -125,21 +127,21 @@ protected void needCaptcha(@NotNull HttpServletRequest request, @NotNull HttpSer } /** - * 显示验证码 + * Show Captcha image * * @param response response - * @param code 验证码 - * @param fmt 模板,以{b64}为占位符 + * @param code Captcha code + * @param fmt template, `{b64}` is placeholder */ protected void showCaptcha(@NotNull HttpServletResponse response, String code, String fmt) { ResponseHelper.showCaptcha(response, code, fmt); } /** - * 制作client身份标记 + * Make client ticket * * @param request request - * @return 身份标记 + * @return ticket */ @NotNull protected String makeClientTicket(HttpServletRequest request) { @@ -160,10 +162,10 @@ protected String makeClientTicket(HttpServletRequest request) { } /** - * 为client发送身份标记 + * Response captcha token to the client * * @param response response - * @param token 身份标记 + * @param token captcha token */ protected void sendClientTicket(@NotNull HttpServletResponse response, String token) { ResponseHelper.bothHeadCookie(response, clientTicketKey, token, 600); @@ -196,7 +198,7 @@ public static class Key { /** - * 鉴别内容,线程同步方法。 + * The content to check, sync method in thread */ public static class Tkn { private volatile long recent; @@ -227,7 +229,7 @@ public boolean check(String tkn, boolean ci, boolean once) { public String fresh(int max, Supplier supplier) { final String code = supplier.get(); synchronized (retry) { - if (retry.get() <= 0) { // 初始或超过 + if (retry.get() <= 0) { // init or over retry.set(max); } token = code; diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterContext.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterContext.java index 7f9d09d1e..25d967327 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterContext.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterContext.java @@ -6,9 +6,11 @@ import java.util.function.Consumer; /** - * 在Controller层通过interceptor,防止数据权限提升。 - * 原则是谁杀谁埋,埋点的数据会以header形式response - * 请求编辑时,设置allow,提交修改时,检查audit + *
+ * In the Controller layer through the interceptor to prevent data privilege elevation.
+ * The principle is who create and who destroy, audit data will respond in the form of header.
+ * When requesting edits, set allow, and when submitting changes, check and audit.
+ * 
* * @author trydofor * @since 2021-03-27 @@ -21,7 +23,7 @@ public class RighterContext { private static final ThreadLocal ReqAudit = new ThreadLocal<>(); /** - * 设置允许项,在response前由controller使用 + * Set the `allow` items. Used in the controller, before response */ public static void setAllow(Object obj) { final Consumer fun = ResAllow.get(); @@ -32,21 +34,21 @@ public static void setAllow(Object obj) { } /** - * 获得允许项,在拦截器内部使用 + * Set the `allow` items. Used in the interceptor. */ public static void funAllow(Consumer fun) { ResAllow.set(fun); } /** - * 移除允许项,在拦截器内部使用 + * Delete the `allow` items. Used in the interceptor. */ public static void delAllow() { ResAllow.remove(); } /** - * 获得审查项,在controller中,业务前使用 + * Get the `audit` items. Used in the controller, before business */ @SuppressWarnings("unchecked") @Contract("true ->!null") @@ -59,14 +61,14 @@ public static R getAudit(boolean nonnull) { } /** - * 设置审查项,在拦截器中内部使用 + * Get the `audit` items. Used in the interceptor. */ public static void setAudit(Object json) { ReqAudit.set(json); } /** - * 移除审查项,在拦截器中内部使用 + * Delete the `audit` items. Used in the interceptor. */ public static void delAudit() { ReqAudit.remove(); diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterInterceptor.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterInterceptor.java index b0dc0b3b7..b2f8b3c46 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterInterceptor.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/concur/impl/RighterInterceptor.java @@ -42,13 +42,13 @@ public class RighterInterceptor implements AutoRegisterInterceptor { private int order = OrderedSlardarConst.MvcRighterInterceptor; /** - * 根据 HttpSession 获得用户加密的密码 + * Get the encryption password by HttpSession */ @Setter @Getter private SecretProvider secretProvider = null; /** - * 若返回null,则使用 RighterInterceptor.Secret + * Use RighterInterceptor.Secret if return `null` */ public interface SecretProvider extends Function { } @@ -63,7 +63,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, final Righter anno = ((HandlerMethod) handler).getMethod().getAnnotation(Righter.class); if (anno == null) return true; - // 使用前清空 + // Delete before use RighterContext.delAudit(); final HttpSession session = request.getSession(false); @@ -87,11 +87,12 @@ public boolean preHandle(@NotNull HttpServletRequest request, } } - // 一般只有登录用户才有权限修改,使用用户slat作为密码 + // Generally only the login user has permission to change it, + // using the user slat as the password if (session == null) return true; - // 检查签名 + // check signature final String key = getKey(session); byte[] bytes = decodeAudit(key, audit); if (bytes == null) { @@ -100,7 +101,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, return false; } - // 反序列化对象 + // deserialize object try { final Object obj = KryoSimple.readClassAndObject(bytes); RighterContext.setAudit(obj); @@ -157,9 +158,9 @@ private Aes genAesKey(String k) { } private String encodeAllow(String key, Object obj) { - // 序列化对象 + // serialize final byte[] bytes = KryoSimple.writeClassAndObject(obj); - // 加密 + // encrypt final Aes aes = genAesKey(key); final String b64 = Base64.encode(aes.encode(bytes)); final String sum = MdHelp.sha1.sum(b64 + key); // 40c diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/SecurityContextUtil.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/SecurityContextUtil.java index 2bb00d3c8..1956ba42b 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/SecurityContextUtil.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/SecurityContextUtil.java @@ -15,13 +15,15 @@ import java.util.Collections; /** + *
  * Wrapper fo SecurityContextHolder.
- * 

- * 尽量在controller层使用,当异步时,context会失效。 - * 因为spring的threadlocal仅支持手动inherit。 - *

- * wings中Authentication为UsernamePasswordAuthenticationToken类型; - * details为WingsAuthDetails类型;principal为WingsUserDetails类型; + * + * Try to use it in controller layer, if in async thread, the context will fail. + * Because spring's threadlocal only supports manual inheritance. + * + * In wings, the Authentication is UsernamePasswordAuthenticationToken; + * details is WingsAuthDetails; principal is WingsUserDetails + *

* * @author trydofor * @since 2019-07-09 @@ -94,7 +96,7 @@ public static T getAuthDetails(Class claz, Authentication atn) { } /** - * wings中,登录前为用户名,登录成功后为WingsUserDetails + * In wings, it is the username before login and WingsUserDetails after successful login. */ @NotNull public static T getPrincipal() { @@ -113,9 +115,9 @@ public static T getPrincipal(boolean notnull) { } /** - * 一般为 UserDetailsService 放入的 UserDetails + * Generally, it is UserDetails provided by UserDetailsService * - * @param UserDetails 类型 + * @param UserDetails type * @return UserDetails */ @Nullable diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/TerminalInterceptor.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/TerminalInterceptor.java index 17cabbf76..d8615b487 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/TerminalInterceptor.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/TerminalInterceptor.java @@ -47,7 +47,7 @@ public Builder buildTerminal(@NotNull HttpServletRequest request) { } /** - * 登录terminal,必须和logoutTerminal 以 try-finally形式出现。 + * Login terminal, which must appear as a try-finally with logoutTerminal. */ public Context loginTerminal(@NotNull HttpServletRequest request, @NotNull Builder builder) { try { @@ -64,7 +64,7 @@ public Context loginTerminal(@NotNull HttpServletRequest request, @NotNull Build } /** - * 登出,返回之前是否成功login + * Logout terminal, and return whether the previous login was successful */ public boolean logoutTerminal(@NotNull HttpServletRequest request) { if (request.getAttribute(AttrTerminalLogin) == Boolean.TRUE) { diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/TerminalSecurityAttribute.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/TerminalSecurityAttribute.java new file mode 100644 index 000000000..63ecf781d --- /dev/null +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/context/TerminalSecurityAttribute.java @@ -0,0 +1,16 @@ +package pro.fessional.wings.slardar.context; + + +import pro.fessional.mirana.best.TypedKey; +import pro.fessional.wings.slardar.security.WingsAuthDetails; +import pro.fessional.wings.slardar.security.WingsUserDetails; + +/** + * @author trydofor + * @since 2023-07-15 + */ +public interface TerminalSecurityAttribute extends TerminalAttribute { + // + TypedKey UserDetails = new TypedKey<>() {}; + TypedKey AuthDetails = new TypedKey<>() {}; +} diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/domainx/DomainRequestMatcher.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/domainx/DomainRequestMatcher.java index dad158643..dbaa50e5b 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/domainx/DomainRequestMatcher.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/domainx/DomainRequestMatcher.java @@ -9,11 +9,11 @@ public interface DomainRequestMatcher { /** - * 尝试wrap request, + * Try wrap request if matches * - * @param request 原始请求 - * @param domain 匹配的domain - * @return 原始request或wrap后 + * @param request original request + * @param domain domain to match + * @return original request or wrapped request if matches */ HttpServletRequest match(HttpServletRequest request, String domain); } 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..d5825d16c --- /dev/null +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/enums/errcode/AuthnErrorEnum.java @@ -0,0 +1,39 @@ +package pro.fessional.wings.slardar.enums.errcode; + +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("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; + 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/httprest/RestTemplateHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/httprest/RestTemplateHelper.java index de07034c2..a955158f6 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/httprest/RestTemplateHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/httprest/RestTemplateHelper.java @@ -23,7 +23,7 @@ import java.io.InputStream; /** - * 你搜到的大部分 post file的资料,都不标准,或不对。 + * Do NOT use CSDN or Baidu to search for technical knowledge. * * @author trydofor * @since 2020-06-02 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/monitor/viewer/LogViewer.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/monitor/viewer/LogViewer.java index 2adde8699..599bc7ff6 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/monitor/viewer/LogViewer.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/monitor/viewer/LogViewer.java @@ -52,13 +52,13 @@ public LogViewer(LogConf conf) { this.cache = WingsCache2k.builder(LogViewer.class, "cache", 2_000, conf.getAlive(), null, String.class, String.class).build(); } - @Operation(summary = "开启自身监控时,配合警报通知,可查看警报日志", description = """ + @Operation(summary = "Alarm logs can be viewed in conjunction with alarm notifications when self-monitoring is enabled.", description = """ # Usage - alias优先于perms检测,check失败时会自动登出logout。 + Pass the log id to view the log. ## Params - * @param id - 日志id,最多缓存2k个,36H + * @param id - log id, max 2k caches in 36H ## Returns - * @return {200 | string} 对应的日志信息或empty""") + * @return {200 | string} log context or empty""") @GetMapping(value = "${" + LogConf.Key$mapping + "}") public void view(@RequestParam("id") String id, HttpServletResponse res) throws IOException { if (id == null) return; @@ -134,7 +134,7 @@ private boolean canIgnoreHead(String out) { continue; } // - max -= line.length(); // 不精确计算 + max -= line.length(); // loose calculation tol++; for (String s : ign) { if (line.contains(s)) { diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/PasssaltEncoder.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/PasssaltEncoder.java index 92106b41e..0f040a110 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/PasssaltEncoder.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/PasssaltEncoder.java @@ -1,17 +1,19 @@ package pro.fessional.wings.slardar.security; /** + * Password salting + * * @author trydofor * @since 2021-02-25 */ public interface PasssaltEncoder { /** - * 根据算法组合salt和pass + * encode pass with salt * - * @param pass 原始密码 + * @param pass plain password * @param salt salt - * @return 新密码 + * @return new password with salt */ String salt(String pass, String salt); } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthCheckService.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthCheckService.java index 967c22f8e..c29bc0253 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthCheckService.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthCheckService.java @@ -9,11 +9,11 @@ public interface WingsAuthCheckService { /** - * 在用户信息及权限加载完毕后,进行后置检查。 + * Perform a post-check after the user information and permissions are loaded. * * @param userDetails user detail - * @param authentication token - * @return ok + * @param authentication auth token + * @return ok or not */ boolean check(WingsUserDetails userDetails, WingsBindAuthToken authentication); } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetails.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetails.java index 4ebd45ca0..28d428626 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetails.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetails.java @@ -10,18 +10,19 @@ * @since 2022-01-18 */ public interface WingsAuthDetails { + /** - * 获取验证的元信息,辅助验证,如request内参数 + * Get meta-info to help with validation, such as the parameters within a request. * - * @return 可变map,非线程安全 + * @return mutable, not thread-safe Map */ @NotNull Map getMetaData(); /** - * 获取build后的实体信息 + * Get the detail data after build * - * @return 实体信息,如Oauth + * @return detail data, eg. Oauth */ @Nullable Object getRealData(); diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetailsSource.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetailsSource.java index 1bbf68ded..ed4b31e74 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetailsSource.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthDetailsSource.java @@ -8,7 +8,7 @@ import pro.fessional.mirana.data.Null; /** - * 构造用于进一步验证的details + * Construct details for further validation * * @author trydofor * @see Authentication#getDetails() diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthPageHandler.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthPageHandler.java index e274f4bc1..1dbd2997f 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthPageHandler.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthPageHandler.java @@ -7,20 +7,17 @@ import org.springframework.http.ResponseEntity; /** - * 处理login-page的get或post请求,如发送短信,oauth重定向 - * * @author trydofor * @since 2021-02-17 */ public interface WingsAuthPageHandler { /** - * 处理login-page的get或post请求,如发送短信,oauth重定向。 - * 如果ResponseEntity.status == HttpStatus.OK,表示由调用者自行处理status。 - * 如果是request的forward则HttpStatus.UNAUTHORIZED,否则HttpStatus.OK + * handle GET/POST request of login-page. eg. send SMS, Oauth redirect + * HttpStatus.UNAUTHORIZED if it is a forward of request, otherwise HttpStatus.OK * - * @param authType 登录类型 - * @param mediaType 指定内容,null是,自动根据判断 + * @param authType auth type + * @param mediaType null means auto-detect * @param request request * @param response response * @see org.springframework.http.MediaType diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeParser.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeParser.java index 401421fc0..bcb4795d7 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeParser.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeParser.java @@ -1,6 +1,7 @@ package pro.fessional.wings.slardar.security; import org.jetbrains.annotations.NotNull; +import pro.fessional.mirana.data.Null; import java.util.Map; @@ -11,26 +12,27 @@ public interface WingsAuthTypeParser { /** - * 获取通过字符串别名,解析成enum类,null返回Null.Enm + * Convert the string alias to enum, return Null.Enm instead of null * * @param authType authType - * @return 枚举类 + * @return strong type of authType + * @see Null#Enm */ @NotNull Enum parse(String authType); /** - * 把enum类,变成字符串别名,无法转换时PC异常 + * Convert the enum to string alias, throws if fail * * @param authType authType - * @return 字符串型 - * @throws IllegalArgumentException 无法转换时 + * @return string alias + * @throws IllegalArgumentException if fail */ @NotNull String parse(Enum authType); /** - * 获取全部映射关系 + * Get all the string alias and enum one-to-one mapping */ @NotNull Map> types(); diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeSource.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeSource.java index 05252ce58..014217029 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeSource.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsAuthTypeSource.java @@ -9,7 +9,7 @@ */ public interface WingsAuthTypeSource { /** - * 获取 auth type + * Build the AuthType from the request * * @param request HttpServletRequest * @return auth type diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetails.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetails.java index 2bfc19fd4..f1576aa22 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetails.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetails.java @@ -6,54 +6,44 @@ import java.util.Locale; /** - * username 用于验证,nickname用于显示 - * * @author trydofor * @since 2019-11-27 */ public interface WingsUserDetails extends UserDetails { /** - * 获得用户id - * - * @return id + * user id */ long getUserId(); /** - * 获得用户区域 - * - * @return 区域 + * locale for i18n */ Locale getLocale(); /** - * 获得用户时区 - * - * @return 时区 + * zoneid for time */ ZoneId getZoneId(); /** - * 验证类型 - * - * @return 类型 + * auth type for auth */ Enum getAuthType(); /** - * 获得用户昵称 + * nickname to show, unlike username for auth * - * @return 昵称,默认使用username + * @return use username by default */ default String getNickname() { return getUsername(); } /** - * 获取密码加盐,构造 password+salt的密码 + * password salting * - * @return 盐,默认空 + * @return empty by default. */ default String getPasssalt() { return ""; diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetailsService.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetailsService.java index 2a38a40fa..edc489b13 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetailsService.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/WingsUserDetailsService.java @@ -9,7 +9,8 @@ import pro.fessional.mirana.data.Null; /** - * 支持验证类型的,当load*出现UsernameNotFoundException时,尝试saveUserByDetail + * UserDetailsService with AuthType support, And + * try `saveUserByDetail` if `load*` throws UsernameNotFoundException * * @author trydofor * @since 2021-02-05 @@ -17,10 +18,8 @@ public interface WingsUserDetailsService extends UserDetailsService { /** - * 默认调用 loadUserByUsername(null, username) + * invoke loadUserByUsername(null, username) by default * - * @param username 同上 - * @return UserDetails * @throws UsernameNotFoundException UsernameNotFound */ @Override @@ -30,10 +29,10 @@ default UserDetails loadUserByUsername(String username) throws UsernameNotFoundE } /** - * 按登录类型加载 UserDetails + * load UserDetails by username and authType * - * @param username type下身份唯一辨识,用户名,手机号,邮箱,userId等 - * @param authType 验证类型,Null.Enm + * @param username Unique Key under authType. eg. username, email, userId, etc. + * @param authType auth type, Null.Enm equals null * @param authDetail Authentication.getDetails * @return UserDetails * @throws UsernameNotFoundException UsernameNotFound diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthFilter.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthFilter.java index d9757cee1..384dad14a 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthFilter.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthFilter.java @@ -36,7 +36,7 @@ public void afterPropertiesSet() { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { - // 同时支持post和get(主要是第三方redirect) + // support both POST and GET (Oauth redirect) String username = obtainUsername(request); username = (username != null) ? username.trim() : ""; 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..c0626c153 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; @@ -27,9 +29,15 @@ import pro.fessional.wings.slardar.security.pass.DefaultPasssaltEncoder; /** - * 兼容DaoAuthenticationProvider,可替换之。如果设置onlyWingsBindAuthnToken=true,则只处理 WingsBindAuthnToken。 - * 需要注意的是,WingsBindAuthnToken 继承UsernamePasswordAuthenticationToken,可能会被其他Provider处理。 - * 不能继承DaoAuthenticationProvider,因为final retrieveUser,但mitigateAgainstTimingAttack很好,全部复制。 + *
+ * Compatible with DaoAuthenticationProvider and can be replaced.
+ * If onlyWingsBindAuthnToken=true, only WingsBindAuthnToken is processed.
+ *
+ * Note that WingsBindAuthnToken inherits UsernamePasswordAuthenticationToken
+ * and may be processed by other Providers.
+ * Can't inherit DaoAuthenticationProvider because final `retrieveUser`,
+ * but `mitigateAgainstTimingAttack` is fine, copy all.
+ * 
* * @author trydofor * @since 2021-02-08 @@ -38,9 +46,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 +73,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 +156,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) { + // salt + 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 +192,6 @@ protected void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken a // setter & getter - public PasssaltEncoder getPasssaltEncoder() { return this.passsaltEncoder; } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthToken.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthToken.java index 6a0cac1ea..6165dbe4b 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthToken.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/bind/WingsBindAuthToken.java @@ -7,7 +7,7 @@ import java.util.Collection; /** - * 认证用token,中间状态 + * UsernamePassword Token with AuthType * * @author trydofor * @since 2021-02-07 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/AbstractAuthPermCheckCombo.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/AbstractAuthPermCheckCombo.java index 01c1622a5..43fd8bd3a 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/AbstractAuthPermCheckCombo.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/AbstractAuthPermCheckCombo.java @@ -11,7 +11,7 @@ import java.util.stream.Collectors; /** - * 区分大小写,检查权限 + * Case-sensitive permission checking * * @author trydofor * @since 2022-01-19 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..03ba4b015 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); } @@ -86,7 +102,7 @@ protected void buildMetaData(@NotNull Enum authType, @NotNull HttpServletRequ public interface Combo extends Ordered { /** - * 不接受或无法构造返回null + * Return null if not accepted or cant be constructed * * @param authType authType * @param request request diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthPageHandler.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthPageHandler.java index 1d18fe64c..094a90705 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthPageHandler.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsAuthPageHandler.java @@ -18,7 +18,8 @@ import java.util.List; /** - * 如果是 forward,建议使用UNAUTHORIZED,直接访问建议OK + * If forward, it is recommended to response UNAUTHORIZED, + * if direct request, response OK * * @author trydofor * @since 2021-02-17 @@ -66,11 +67,11 @@ public interface Combo extends Ordered { /** * @param authType authType - * @param mediaType 内容类型 + * @param mediaType application/json * @param request request * @param response response - * @param status 建议的status - * @return null 如果不能处理 + * @param status Http status should be response + * @return null if not handled */ ResponseEntity response(@NotNull Enum authType, @Nullable MediaType mediaType, @NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull HttpStatus status); } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsUserDetailsService.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsUserDetailsService.java index d130922dd..fed43d5b6 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsUserDetailsService.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/ComboWingsUserDetailsService.java @@ -71,10 +71,11 @@ public void addAll(Collection> source) { public interface Combo extends Ordered { /** - * 不接受或无法构造返回null,非null时,表示加载成功,直接返回用去验证。 + * Do not accept or can not be constructed to return null. + * Non-null means the loading is successful, can be used to verify. * - * @param username type下身份唯一辨识,用户名,手机号,邮箱,userId等 - * @param authType 验证类型,默认null + * @param username Unique Key under authType. eg. username, email, userId, etc. + * @param authType null by default * @param authDetail Authentication.getDetails * @return UserDetails * @throws UsernameNotFoundException UsernameNotFound @@ -86,13 +87,13 @@ default T loadOrNull(String username, @NotNull Enum authType, @Nullable Wings } /** - * 对加载的useDetail后置审查,返回null等同于load失败 + * Post-audit the loaded useDetail, returning null equals `loadOrNull` null * - * @param useDetail 已加载的useDetail - * @param username 原始 username - * @param authType 原始 authType - * @param authDetail 原始 authDetail - * @return 加工后的useDetail + * @param useDetail loaded useDetail + * @param username original username + * @param authType original authType + * @param authDetail original authDetail + * @return audited useDetail */ @Nullable default UserDetails postAudit(@NotNull UserDetails useDetail, String username, @NotNull Enum authType, @Nullable WingsAuthDetails authDetail) { diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeParser.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeParser.java index 5572b19af..cb4fb6a8b 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeParser.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeParser.java @@ -9,7 +9,7 @@ import java.util.Map; /** - * 使用HashMap构建,除default值外,一对一映射。 + * Construct the one-to-one (except for the default value) mapping with HashMap. * * @author trydofor * @since 2021-02-22 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeSource.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeSource.java index e4eb373c0..ef1a8ae09 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeSource.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/impl/DefaultWingsAuthTypeSource.java @@ -12,7 +12,7 @@ import java.util.Map; /** - * 支持 header,param,AntPath (/login/*.json) + * Parse AuthType form header, param and path (PathPattern) * * @author trydofor * @see org.springframework.web.util.pattern.PathPattern @@ -26,10 +26,8 @@ public class DefaultWingsAuthTypeSource implements WingsAuthTypeSource { private final boolean hasZonePath; /** - * 如果不支持对应的类型,设置为null,antPath中,支持authType路径参数 - * - * @param pathPattern 路径参数,参考PathPattern - * @param typeParser enums.name + * @param pathPattern Path variable in PathPattern + * @param typeParser AuthType Parser */ public DefaultWingsAuthTypeSource(String pathPattern, WingsAuthTypeParser typeParser) { this.pathPattern = pathPattern; diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/BasicPasswordEncoder.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/BasicPasswordEncoder.java index 37bf12cf5..4b04002af 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/BasicPasswordEncoder.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/BasicPasswordEncoder.java @@ -5,10 +5,13 @@ import pro.fessional.mirana.time.ThreadNow; /** - * 折中BasicAuthentication与DigestAuthentication,仅避免明文传递密码。 *
- * 使用密码对时间戳Md5,以时间戳和md5值代替密码传送。要求时间戳与服务器相差在正负3分钟内。
- * timestamp - 1970 ms
+ * A compromise between BasicAuthentication and DigestAuthentication  to avoid sending plaintext password.
+ * Use the password to md5sum the current timestamp, then send the timestamp and md5sum instead of a password.
+ *
+ * Requires the timestamp to be within 3 minutes of the server.
+ *
+ * timestamp - form 1970 in ms
  * password - user password
  * md5_hash = md5($timestamp + "#" + $password)
  * token =  $timestamp + "#" + $md5_hash
diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/HashPasswordEncoder.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/HashPasswordEncoder.java
index 2132768f3..607339e48 100644
--- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/HashPasswordEncoder.java
+++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/HashPasswordEncoder.java
@@ -4,8 +4,12 @@
 import pro.fessional.mirana.bits.MdHelp;
 
 /**
- * 使用noop时,以密码的hash取代明文传递,进而后端对hash值加班noop比较。
+ * 
+ * When using `noop`, the hash of the password is passed instead of plaintext,
+ * and the hash value is compared in `noop` way
+ *
  * {noop-md5}PLAIN_TEXT = {noop}md5(PLAIN_TEXT)
+ * 
* * @author trydofor * @since 2021-03-02 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/MysqlPasswordEncoder.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/MysqlPasswordEncoder.java new file mode 100644 index 000000000..a84046e2b --- /dev/null +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/MysqlPasswordEncoder.java @@ -0,0 +1,33 @@ +package pro.fessional.wings.slardar.security.pass; + +import org.springframework.security.crypto.password.PasswordEncoder; +import pro.fessional.mirana.bits.MdHelp; + +import java.nio.charset.StandardCharsets; + +/** + * mysql5 `password` function compatible encoder + * + * @author trydofor + * @since 2021-02-27 + */ +public class MysqlPasswordEncoder implements PasswordEncoder { + + @Override + public String encode(CharSequence rawPassword) { + final String pass = rawPassword.toString(); + final byte[] bytes = pass.getBytes(StandardCharsets.UTF_8); + final byte[] sha1 = MdHelp.sha1.digest(bytes); + return MdHelp.sha1.sum(sha1, true); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + int off = 0; + if (encodedPassword.startsWith("*")) { + off = 1; + } + final String code = encode(rawPassword); + return encodedPassword.regionMatches(true, off, code, 0, code.length()); + } +} diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/NeverPasswordEncoder.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/NeverPasswordEncoder.java index 68883388e..e54a7df75 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/NeverPasswordEncoder.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/NeverPasswordEncoder.java @@ -3,7 +3,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; /** - * always not match + * never match, return false always. * * @author trydofor * @since 2021-02-27 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/PasswordEncoders.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/PasswordEncoders.java index 58e160f9f..db10497b5 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/PasswordEncoders.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/security/pass/PasswordEncoders.java @@ -15,8 +15,8 @@ /** * @author trydofor - * @since 2022-03-12 * @see PasswordEncoderFactories + * @since 2022-03-12 */ public class PasswordEncoders { @@ -39,6 +39,7 @@ public class PasswordEncoders { public static final String Sha256 = "SHA-256"; public static final String Argon2V58 = "argon2@SpringSecurity_v5_8"; public static final String Sha256s = "sha256"; + public static final String Mysql = "mysql"; private static final Map encoderMap = new HashMap<>(); @@ -53,11 +54,11 @@ public static PasswordEncoder getEncoder(String encoder) { } /** - * 使用encoder加密password + * Encrypt the plain password into a hash * - * @param encoder 算法Id - * @param password 明文密码 - * @return 加密后字符串 + * @param encoder encoder id + * @param password plain password + * @return crypt password */ @Nullable public static String encode(String encoder, CharSequence password) { @@ -66,11 +67,11 @@ public static String encode(String encoder, CharSequence password) { } /** - * 以DelegatingPasswordEncoder格式加密,如 {noop-md5}password + * Use DelegatingPasswordEncoder to encode the password and output it in `{noop-md5}password` style. * - * @param encoder 算法Id - * @param password 明文密码 - * @return {算法名}加密后字符串 + * @param encoder encoder id + * @param password plain password + * @return `{id}hash` */ @Nullable public static String delegating(String encoder, CharSequence password) { @@ -87,11 +88,13 @@ public static String delegating(String encoder, CharSequence password) { } /** - * 检查密码是delegating格式,否则使用encoder加密,若失败在使用noop-md5算法 + * Check that the password is in delegating({id}hash`) format, + * otherwise use the given encoder to encrypt it, + * if it fails use the noop-md5 encoder by default. * - * @param password 密码 - * @param encoder 备选算法 - * @return delegating的密码 + * @param password delegated or plain password + * @param encoder encoder id + * @return `{id}hash` */ @Contract("!null, _ -> !null") public static String delegated(String password, String encoder) { @@ -125,6 +128,7 @@ public static Map initEncoders(long basicMs) { encoderMap.put(Sha256, new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256")); encoderMap.put(Sha256s, new org.springframework.security.crypto.password.StandardPasswordEncoder()); encoderMap.put(Argon2V58, Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()); + encoderMap.put(Mysql, new MysqlPasswordEncoder()); return encoderMap; } } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/ContentTypeHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/ContentTypeHelper.java index d095adedd..1ed85ce46 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/ContentTypeHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/ContentTypeHelper.java @@ -7,8 +7,8 @@ import java.util.HashMap; /** - * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type - * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + * Content-Type + * Common_types * * @author trydofor * @since 2021-03-20 @@ -207,7 +207,7 @@ public class ContentTypeHelper { } /** - * 通过截断文件名中的小写扩展名获得类型。 + * Get the type by truncating the lowercase extension in the filename. * * @param fileName `file.txt` * @return ContentType @@ -219,10 +219,10 @@ public static String findByFileName(@Nullable String fileName) { } /** - * 通过截断文件名中的小写扩展名获得类型。 + * Get the type by truncating the lowercase extension in the filename. * * @param fileName `file.txt` - * @param elseType 默认值,未找到时返回 + * @param elseType else value if not found. * @return ContentType */ @NotNull diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/WingsCookieInterceptor.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/WingsCookieInterceptor.java index 1db11ac1b..2b11e0003 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/WingsCookieInterceptor.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/WingsCookieInterceptor.java @@ -15,25 +15,23 @@ enum Coder { } /** - * 是否对cookie有Intercept,true时短路处理,可关闭拦截功能 - * - * @return 是否 + * Whether no Intercept for cookies, true when short-circuit processing, can turn off the intercept function */ boolean notIntercept(); /** - * 读取时转换一个cookie,并转换成新的,返回null表示丢弃 + * Convert to new cookie on read, return `null` means discard it * - * @param cookie 原始cookie - * @return 新 + * @param cookie original cookie + * @return new / original / null */ Cookie read(Cookie cookie); /** - * 写入时一个cookie,并转换成新的,返回null表示丢弃 + * Convert to new cookie on write, return `null` means discard it * - * @param cookie 原始cookie - * @return 新 + * @param cookie original cookie + * @return new / original / null */ Cookie write(Cookie cookie); } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/impl/WingsCookieInterceptorImpl.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/impl/WingsCookieInterceptorImpl.java index 688211f75..17c981c08 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/impl/WingsCookieInterceptorImpl.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/cookie/impl/WingsCookieInterceptorImpl.java @@ -20,7 +20,7 @@ import static pro.fessional.wings.silencer.spring.help.CommonPropHelper.notValue; /** - * 设计目的为非运行时调整,故不提供写保护 + * Designed for non-runtime tuning, so no write protection is provided. * * @author trydofor * @since 2021-10-08 @@ -65,20 +65,20 @@ public WingsCookieInterceptorImpl(String aesKey) { boolean did = false; String name = cookie.getName(); - // name 脱前缀 + // handle prefix of name if (prefix != null && name.startsWith(prefix)) { name = name.substring(prefix.length()); did = true; } - // name 脱别名 + // handle alias of name final String n = aliasDec.get(name); if (n != null) { name = n; did = true; } - // value 解码 + // decode value String value = cookie.getValue(); if (codeAes.contains(name)) { value = aes.decode64(value); @@ -111,7 +111,7 @@ else if (coder == Coder.B64) { boolean did = false; String name = cookie.getName(); - // value 编码 + // decode value String value = cookie.getValue(); if (codeAes.contains(name)) { value = aes.encode64(value); @@ -135,7 +135,7 @@ else if (coder == Coder.B64) { } } - // 处理属性 + // handle attrs final Boolean ho = httpOnly.get(name); if (ho != null) { cookie.setHttpOnly(ho); @@ -153,14 +153,14 @@ else if (coder == Coder.B64) { cookie.setPath(ph); } - // name 设别名 + // handle alias of name final String n = aliasEnc.get(name); if (n != null) { name = n; did = true; } - // name 设前缀 + // handle prefix of name if (prefix != null) { name = prefix + name; did = true; diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/filter/WingsOverloadFilter.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/filter/WingsOverloadFilter.java index 9251d6d33..64ca5d0fc 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/filter/WingsOverloadFilter.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/filter/WingsOverloadFilter.java @@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicLong; /** - * 不建议使用,因实现过于简单,未考虑复杂情况。 + * Not recommended. Implementation is too simple for complexity. * * @author trydofor * @since 2019-11-14 @@ -94,13 +94,13 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha return; } - // 只能处理http的,目前的情况 + // Only handle http final long now = ThreadNow.millis(); final CalmDown calmDown = letCalmDown(httpReq); - // 快请求,累积后清零 + // fast request, reset after calmdown if (calmDown != null) { - final int rqs = calmDown.heardRequest.incrementAndGet(); // 等待处理的请求 + final int rqs = calmDown.heardRequest.incrementAndGet(); // request waiting to handle final boolean isCnt = rqs >= config.requestCalmdown; final boolean isFst = now - calmDown.firstRequest.get() < config.requestInterval; if (isCnt && isFst) { @@ -111,7 +111,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha log.warn("wings-clam-request, now={}, ip={}, uri={}", rqs, calmDown.ip, httpReq.getRequestURI()); lastWarnSlow.put(calmDown.ip, now); } - return; // 直接返回 + return; // } else { if (!isFst) calmDown.firstRequest.set(now); @@ -201,13 +201,13 @@ public interface FallBack { // private CalmDown letCalmDown(HttpServletRequest httpReq) { - if (spiderCache == null) return null; // 不需要处理ip问题 + if (spiderCache == null) return null; // ip without handle final String ip = terminalResolver.resolveRemoteIp(httpReq); for (String p : config.requestPermit.values()) { if (ip.startsWith(p) && !p.isEmpty()) { - return null; // 白名单,不需要处理。 + return null; // allow list } } @@ -223,12 +223,12 @@ private int initCapacity(Config config) { } private void checkAndStats(HttpServletRequest request, ServletResponse response, long bgn, long end) { - // 只处理成功的,其他的忽略。 + // Only handle response OK, ignore others if (!(response instanceof HttpServletResponse res)) return; if (res.getStatus() != 200) return; - // 慢响应 + // slow response final long cost = end - bgn; final long warnSlow = config.responseWarnSlow; if (log.isWarnEnabled() && warnSlow > 0 && cost > warnSlow) { @@ -241,7 +241,7 @@ private void checkAndStats(HttpServletRequest request, ServletResponse response, } } - // 统计,已完成的请求,忽略并发误差。 + // Statistics, completed requests, ignoring concurrency errors. if (responseCost.length > 0) { int idx = (int) (cost % costStep); if (idx >= responseCost.length) { diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/RequestHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/RequestHelper.java index 269a17a9b..8fa4800cf 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/RequestHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/request/RequestHelper.java @@ -28,7 +28,7 @@ import java.util.Set; /** - * 类型安全的获得request中的值。 + * Type-safe to get the value in the request. * * @author trydofor * @since 2019-07-03 @@ -36,10 +36,10 @@ public class RequestHelper { /** - * 把所有错误信息构造成`\n`分隔的(error=)?message格式 + * Construct all error messages in `\n` delimited `(error=)?message` format. * - * @param error 错误信息 - * @return null 无错误 + * @param error error message + * @return null if no error */ public static String allErrors(@NotNull BindingResult error) { if (!error.hasErrors()) return null; @@ -134,11 +134,11 @@ public static String getRemoteIp(HttpServletRequest request, String... header) { } /** - * 带有contextpath进行URI的忽略大小写的全匹配,支持wildcard + * Case-insensitive matching of URI with path, support for wildcard * - * @param req 请求 - * @param path 路径,null或空为false - * @return 是否匹配 + * @param req the request + * @param path path pattern, return false if null or empty + * @return whether matches */ public static boolean matchIgnoreCase(HttpServletRequest req, String path) { if (path == null || path.isEmpty()) return false; @@ -153,11 +153,11 @@ public static boolean matchIgnoreCase(HttpServletRequest req, String path) { } /** - * 带有contextpath进行URI的忽略大小写的全匹配,支持wildcard + * Case-insensitive matching of URI with any of path, support for wildcard * - * @param req 请求 - * @param path 路径,null或空为false - * @return 是否匹配 + * @param req the request + * @param path path patterns, return false if null or empty + * @return whether matches any */ public static boolean matchIgnoreCase(HttpServletRequest req, String... path) { if (path == null) return false; @@ -220,8 +220,9 @@ public static Map getParameter(Map param) { } /** - * 有些获得 headers中的Bearer然后 Parameter的access_token - * `Bearer`和`access_token` 不区分大小写,如果有多个token,取最后一个 + * First get `Bearer` in Header, if not found, then get `access_token` in Parameter + * `Bearer` and `access_token` are case-insensitive, + * if there is more than one token, take the last one. */ @Nullable public static String getAccessToken(HttpServletRequest request) { 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..1752adcc7 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` - * ② query string `locale`, `zoneid` - * ③ http header `Accept-Language`,`Zone-Id` - * ④ cookie `WINGS_LOCALE`, `WINGS_ZONEID` - * ⑤ 登录用户的SecurityContext中获得wings设置 + * get current Locale and ZoneId in the following order: + * (1) request `WINGS.I18N_CONTEXT` + * (2) query string `locale`, `zoneid` + * (3) http header `Accept-Language`,`Zone-Id` + * (4) cookie `WINGS_LOCALE`, `WINGS_ZONEID` + * (5) login user's SecurityContext to get Wings settings + * (6) system default value * * @author trydofor * @since 2019-06-30 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsRemoteResolver.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsRemoteResolver.java index 9fe7d2ac5..11d60364f 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsRemoteResolver.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/resolver/WingsRemoteResolver.java @@ -35,7 +35,7 @@ public void addIpHeader(Collection keys) { } /** - * 组合remote的ip,agent,及header,构造唯一key + * Construct a unique key by remote ip, agent and header */ @NotNull public String resolveRemoteKey(HttpServletRequest request, String... header) { diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/ResponseHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/ResponseHelper.java index 6fa1ced14..3fb7e2a82 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/ResponseHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/ResponseHelper.java @@ -25,6 +25,7 @@ import pro.fessional.wings.slardar.servlet.ContentTypeHelper; import pro.fessional.wings.slardar.servlet.stream.ReuseStreamResponseWrapper; +import javax.annotation.WillClose; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; @@ -32,6 +33,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -50,10 +52,10 @@ public class ResponseHelper { /** - * 为下载文件 fileName,获得正确的ContentType + * Get the correct ContentType for the download filename. * - * @param fileName 文件名 - * @return ContentType,默认 APPLICATION_OCTET_STREAM_VALUE + * @param fileName File name prompted during download + * @return ContentType, default APPLICATION_OCTET_STREAM_VALUE */ @NotNull public static String getDownloadContentType(String fileName) { @@ -61,10 +63,10 @@ public static String getDownloadContentType(String fileName) { } /** - * 为下载文件 fileName,获得正确的ContentDisposition - * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition + * Get the correct ContentDisposition for the download filename. + * see Content-Disposition * - * @param fileName 文件名 + * @param fileName File name prompted during download * @return ContentDisposition */ @NotNull @@ -82,37 +84,37 @@ public static String getDownloadContentDisposition(@Nullable String fileName) { } /** - * 为下载文件的Response设置ContentType + * Set the correct ContentType to Response for the download filename. * * @param response HttpServletResponse - * @param fileName fileName + * @param fileName File name prompted during download */ public static void setDownloadContentType(@NotNull HttpServletResponse response, @Nullable String fileName) { response.setContentType(getDownloadContentType(fileName)); } /** - * 为下载文件的Response设置Content-Disposition + * Set the correct Content-Disposition to Response for the download filename. * * @param response HttpServletResponse - * @param fileName fileName + * @param fileName File name prompted during download */ public static void setDownloadContentDisposition(@NotNull HttpServletResponse response, @Nullable String fileName) { response.setHeader("Content-Disposition", getDownloadContentDisposition(fileName)); } /** - * 直接以HttpServletResponse下载名为fileName的流,并关闭流 + * Directly download file with filename by HttpServletResponse, + * use stream as the content and close the stream. * * @param response HttpServletResponse - * @param fileName 下载时提示的文件名 - * @param stream 流 + * @param fileName File name prompted during download + * @param stream input stream */ - public static void downloadFile(@NotNull HttpServletResponse response, @Nullable String fileName, @NotNull InputStream stream) { - setDownloadContentType(response, fileName); - setDownloadContentDisposition(response, fileName); + public static void downloadFile(@NotNull HttpServletResponse response, @Nullable String fileName, @NotNull @WillClose InputStream stream) { try { - IOUtils.copy(stream, response.getOutputStream(), 1024); + OutputStream outputStream = downloadFile(response, fileName); + IOUtils.copy(stream, outputStream, 1024); } catch (IOException e) { throw new IORuntimeException(e); @@ -123,17 +125,30 @@ public static void downloadFile(@NotNull HttpServletResponse response, @Nullable } /** - * 直接以HttpServletResponse下载文件 + * Set the download filename to HttpServletResponse and return the output stream for user to write the content. * * @param response HttpServletResponse - * @param file 下载的文件 + * @param fileName File name prompted during download + */ + public static OutputStream downloadFile(@NotNull HttpServletResponse response, @Nullable String fileName) throws IOException { + setDownloadContentType(response, fileName); + setDownloadContentDisposition(response, fileName); + return response.getOutputStream(); + } + + + /** + * Directly download the file with its filename by HttpServletResponse + * + * @param response HttpServletResponse + * @param file file and filename to download */ @SneakyThrows public static void downloadFile(@NotNull HttpServletResponse response, @NotNull File file) { downloadFile(response, file.getName(), new FileInputStream(file)); } - public static void downloadFileWithZip(@NotNull HttpServletResponse response, @NotNull Map files, @Nullable String fileName) { + public static void downloadFileWithZip(@NotNull HttpServletResponse response, @NotNull @WillClose Map files, @Nullable String fileName) { if (fileName == null) fileName = "download.zip"; try { @@ -153,14 +168,14 @@ public static void downloadFileWithZip(@NotNull HttpServletResponse response, @N /** - * 直接以HttpServletResponse预览PDF文件 + * Directly preview the PDF by HttpServletResponse * * @param response HttpServletResponse - * @param fileName 要预览的PDF文件名 - * @param stream 流 + * @param fileName the PDF filename to preview + * @param stream input stream */ @SneakyThrows - public static void previewPDF(@NotNull HttpServletResponse response, @Nullable String fileName, @NotNull InputStream stream) { + public static void previewPDF(@NotNull HttpServletResponse response, @Nullable String fileName, @NotNull @WillClose InputStream stream) { final String contentType = getDownloadContentType(fileName); if (!APPLICATION_PDF_VALUE.equals(contentType)) { throw new IllegalArgumentException("The parameter 'fileName' must be a pdf file"); @@ -188,9 +203,9 @@ public static void previewPDF(@NotNull HttpServletResponse response, @Nullable S /** - * 输出图片验证码 + * Output the image/jpeg captcha * - * @param code 文本,6字 + * @param code captcha * @param response response */ public static void showCaptcha(HttpServletResponse response, String code) { @@ -211,10 +226,10 @@ public static void showCaptcha(HttpServletResponse response, String code) { } /** - * 输出图片验证码 + * Output the base64 image captcha in text/plain. * - * @param code 文本,6字 - * @param fmt 模板,以{base64}为占位符 + * @param code captcha + * @param fmt format, `{base64}` is placeholder * @param response response */ public static void showCaptcha(HttpServletResponse response, String code, String fmt) { @@ -290,10 +305,14 @@ public static void renderModelAndView(ModelAndView mav, HttpServletResponse res, } /** - * 那以下顺序执行,并返回3种形式,Accept头严格匹配,因浏览器发送多头 - * ① 200 xml, accept=application/xml - * ② 200 json, accept=application/json 或 uri=null 或③未命中 - * ③ 302 uri, uri != null + *
+     * Execute in the following order and return 3 forms, the Accept header strictly matches,
+     * as the browser sends multiple headers.
+     *
+     * (1) 200 xml,  accept=application/xml
+     * (2) 200 json, accept=application/json or uri=null or (3) if not match
+     * (3) 302 uri, uri != null
+     * 
*/ @SneakyThrows @NotNull diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/view/OnlyView.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/view/OnlyView.java index b37f34713..728e65902 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/view/OnlyView.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/response/view/OnlyView.java @@ -9,7 +9,7 @@ import java.util.Map; /** - * 忽略Model,直接render view + * Ignore Model, directly render view * * @author trydofor * @since 2021-03-10 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/AbstractRequestResponseLogging.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/AbstractRequestResponseLogging.java index 370483cae..547442c36 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/AbstractRequestResponseLogging.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/AbstractRequestResponseLogging.java @@ -17,8 +17,10 @@ import java.util.function.Predicate; /** - * Prefix - 使用特征字符,比如jvmId等。 - * Body - 需要开启流复用或缓存功能。 + * + * Prefix - feature token, such as jvmId. + * Body - Must enable reuse Stream or caching. + * * * @author trydofor * @since 2022-06-07 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/CircleServletInputStream.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/CircleServletInputStream.java index 21c25fa08..75dc65079 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/CircleServletInputStream.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/CircleServletInputStream.java @@ -9,7 +9,7 @@ import java.io.IOException; /** - * 可以循环读的stream + * Stream (reuse) that can be read recursively * * @author trydofor * @since 2020-09-25 diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/RequestResponseLogging.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/RequestResponseLogging.java index 7d688c772..9c06a2904 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/RequestResponseLogging.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/servlet/stream/RequestResponseLogging.java @@ -21,10 +21,7 @@ class Conf { } /** - * 日志配置,null为不记录 - * - * @param req 请求 - * @return 日志配置 + * Get the config of request logging, `null` means no log */ @Nullable default Conf loggingConfig(@NotNull ReuseStreamRequestWrapper req) { @@ -32,20 +29,20 @@ default Conf loggingConfig(@NotNull ReuseStreamRequestWrapper req) { } /** - * 在doFilter之前,未执行dispatch + * handle log before doFilter, that do Not run `dispatch` * - * @param cnf 配置 - * @param req 请求 + * @param cnf the config + * @param req the request wrapper */ default void beforeRequest(@NotNull Conf cnf, @NotNull ReuseStreamRequestWrapper req) { } /** - * 在doFilter之后,完成response,未输出到客户端 + * handle log after doFilter, that completed response, not output to client * - * @param cnf 配置 - * @param req 请求 - * @param res 回复 + * @param cnf the config + * @param req the request wrapper + * @param res the response wrapper */ default void afterResponse(@NotNull Conf cnf, @NotNull ReuseStreamRequestWrapper req, @NotNull ReuseStreamResponseWrapper res) { } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionHelper.java index b4bf84f91..7905dd933 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionHelper.java @@ -25,7 +25,7 @@ public interface WingsSessionHelper { String ExpiredKey = "WingsSession.EXPIRED"; /** - * 返回登录用户的UserId或者DefaultUserId.Unknown + * Get the login UserId or DefaultUserId.Unknown */ default long getUserId(@NotNull Session session) { final Long uid = session.getAttribute(UserIdKey); @@ -43,7 +43,7 @@ default long getUserId(@NotNull Session session) { } /** - * 默认判断ExpiredKey是否存在bool或string类型的true + * Determine if ExpiredKey exists `true` value in `bool` or `String` type by default. */ default boolean isExpired(@NotNull Session session) { final Object obj = session.getAttribute(ExpiredKey); @@ -61,7 +61,7 @@ else if (obj instanceof String) { } /** - * 默认获取 SPRING_SECURITY_CONTEXT_KEY内的Spring SecurityContext + * Get the Spring SecurityContext within SPRING_SECURITY_CONTEXT_KEY by default. */ @Nullable default SecurityContext getSecurityContext(@NotNull Session session) { @@ -69,13 +69,13 @@ default SecurityContext getSecurityContext(@NotNull Session session) { } /** - * 获得一个用户id下的所有用户 + * Get all session of userId */ @NotNull List findByUserId(Long userId); /** - * 移除session + * Drop the session by sessionId */ boolean dropSession(String sessionId); } diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionIdResolver.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionIdResolver.java index ce920240f..50a8f285c 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionIdResolver.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/session/WingsSessionIdResolver.java @@ -14,7 +14,7 @@ import java.util.List; /** - * 组合的HttpSessionIdResolver + * Combined HttpSessionIdResolver * * @author trydofor * @since 2021-02-03 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-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java index 76394f348..7a2a78c0b 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarJacksonWebConfiguration.java @@ -49,6 +49,7 @@ import pro.fessional.wings.spring.consts.OrderedSlardarConst; import java.math.BigDecimal; +import java.math.BigInteger; import java.text.DecimalFormat; import java.time.LocalDate; import java.time.LocalDateTime; @@ -81,10 +82,10 @@ public class SlardarJacksonWebConfiguration { private final MessageSource messageSource; /** - * The context’s Jackson2ObjectMapperBuilder can be customized by one or more + * The context's Jackson2ObjectMapperBuilder can be customized by one or more * Jackson2ObjectMapperBuilderCustomizer beans. Such customizer beans can be ordered - * (Boot’s own customizer has an order of 0), letting additional - * customization be applied both before and after Boot’s customization. + * (Boot's own customizer has an order of 0), letting additional + * customization be applied both before and after Boot's customization. *

* If you provide any @Beans of type MappingJackson2HttpMessageConverter, * they replace the default value in the MVC configuration. Also, @@ -203,8 +204,9 @@ public Jackson2ObjectMapperBuilderCustomizer customizerObjectMapperNumber() { final SlardarNumberProp.Nf decimal = slardarNumberProp.getDecimal(); if (decimal.isEnable()) { final DecimalFormat df = decimal.getWellFormat(); - log.info("SlardarWebmvc conf Jackson2ObjectMapperBuilderCustomizer BigDecimal serializer"); + log.info("SlardarWebmvc conf Jackson2ObjectMapperBuilderCustomizer BigDecimal/BigInteger serializer"); builder.serializerByType(BigDecimal.class, new FormatNumberSerializer(BigDecimal.class, df, decimal.getDigital())); + builder.serializerByType(BigInteger.class, new FormatNumberSerializer(BigInteger.class, df, decimal.getDigital())); } }; } 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/SlardarOverloadConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarOverloadConfiguration.java index 5adddda85..ddb896f05 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarOverloadConfiguration.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarOverloadConfiguration.java @@ -26,8 +26,8 @@ import java.io.PrintWriter; /** - * 自动计算单线程和全局请求数。 - * 收到TERM信号时,阻止所有请求。 + * Auto count the number of single-threaded and global requests. + * Block all requests when TERM signal is received. * * @author trydofor * @since 2019-07-23 @@ -55,7 +55,7 @@ public void onApplicationEvent(@NotNull ContextClosedEvent event) { log.warn("SlardarWebmvc shutting down, deny new request, current=" + overloadFilter.getRequestProcess()); for (long breaks = 60 * 1000, step = 30; overloadFilter.getRequestProcess() > 0 && breaks > 0; ) { try { - Thread.sleep(step); // 忙等 + Thread.sleep(step); // busy wait breaks -= step; } catch (InterruptedException e) { diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java index d5e45a353..be94392d3 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java @@ -13,7 +13,11 @@ import org.springframework.boot.web.servlet.server.Session.Cookie; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.session.web.http.*; +import org.springframework.session.web.http.CookieHttpSessionIdResolver; +import org.springframework.session.web.http.CookieSerializer; +import org.springframework.session.web.http.DefaultCookieSerializer; +import org.springframework.session.web.http.HeaderHttpSessionIdResolver; +import org.springframework.session.web.http.HttpSessionIdResolver; import org.springframework.util.StringUtils; import pro.fessional.mirana.best.AssertArgs; import pro.fessional.wings.slardar.session.WingsSessionIdResolver; @@ -25,7 +29,7 @@ import java.util.List; /** - * 通过 session-hazelcast.xml 配置好 spring session用的map,主要是index和serial + * Configure the IMap for spring session via session-hazelcast.xml, mainly index and serial. * Spring Session * spring-security * spring-session-hazelcast 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..4cfbf47c8 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 @@ -10,17 +10,18 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.i18n.TimeZoneAwareLocaleContext; +import org.springframework.security.core.Authentication; 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; import pro.fessional.wings.slardar.context.TerminalInterceptor; +import pro.fessional.wings.slardar.context.TerminalSecurityAttribute; import pro.fessional.wings.slardar.security.WingsUserDetails; import pro.fessional.wings.slardar.servlet.resolver.WingsLocaleResolver; 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; @@ -54,7 +55,8 @@ public TerminalInterceptor.TerminalBuilder remoteTerminalBuilder(WingsRemoteReso public TerminalInterceptor.TerminalBuilder securityTerminalBuilder(WingsLocaleResolver resolver) { log.info("SlardarWebmvc spring-bean securityTerminalBuilder"); return (builder, request) -> { - final WingsUserDetails details = SecurityContextUtil.getUserDetails(false); + final Authentication authn = SecurityContextUtil.getAuthentication(false); + final WingsUserDetails details = SecurityContextUtil.getUserDetails(authn); if (details == null) { final Long userId = (Long) request.getAttribute(SlardarServletConst.AttrUserId); if (userId != null) { @@ -64,22 +66,24 @@ public TerminalInterceptor.TerminalBuilder securityTerminalBuilder(WingsLocaleRe .user(userId); } else { - // 默认值的优先级最低 + // The default value has the lowest priority TimeZoneAwareLocaleContext locale = resolver.resolveI18nContext(request, null); builder.localeIfAbsent(locale.getLocale()) .timeZoneIfAbsent(locale.getTimeZone()) .guest(); } - } else { builder.locale(details.getLocale()) .timeZone(details.getZoneId()) .user(details.getUserId()) .authType(details.getAuthType()) + .username(details.getUsername()) .authPerm(details.getAuthorities().stream() .map(GrantedAuthority::getAuthority) - .collect(Collectors.toSet())); + .collect(Collectors.toSet())) + .terminal(TerminalSecurityAttribute.UserDetails, details) + .terminal(TerminalSecurityAttribute.AuthDetails, SecurityContextUtil.getAuthDetails(authn)); } }; } @@ -88,7 +92,7 @@ public TerminalInterceptor.TerminalBuilder securityTerminalBuilder(WingsLocaleRe @ConditionalOnMissingBean(TerminalInterceptor.class) public TerminalInterceptor terminalInterceptor(SlardarTerminalProp prop, ObjectProvider builders) { log.info("SlardarWebmvc spring-bean terminalInterceptor"); - TerminalContext.initActive(true); + final TerminalInterceptor bean = new TerminalInterceptor(); builders.orderedStream().forEach(bean::addTerminalBuilder); // diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/AutoRegisterInterceptor.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/AutoRegisterInterceptor.java index 1cb04c013..02a509d55 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/AutoRegisterInterceptor.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/AutoRegisterInterceptor.java @@ -9,7 +9,8 @@ import java.util.List; /** - * 标记接口,实现此接口的Bean,会被按order自动注册mvc环境 + * The marker interface, the bean that implements it, + * will be auto registered with the mvc environment by its order. * * @author trydofor * @see MappedInterceptor diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/PageQueryArgumentResolver.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/PageQueryArgumentResolver.java index 6cd806392..6d9ad83f9 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/PageQueryArgumentResolver.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/PageQueryArgumentResolver.java @@ -27,7 +27,7 @@ public class PageQueryArgumentResolver implements HandlerMethodArgumentResolver private String[] sortAlias = Null.StrArr; /** - * 精确匹配PageQuery,避免污染子类 + * Exactly match PageQuery to avoid pollution of subclasses * * @param parameter MethodParameter * @return supports diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java index 747d724a8..0ab00cd4d 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/RequestMappingHelper.java @@ -20,8 +20,8 @@ import java.util.stream.Collectors; /** - * 列出所有RequestMapping标记的URL, - * 注意,不是容器内所有 mapping + * Lists all RequestMapping annotated URLs. + * Note, not all mapping in the container * * @see DispatcherServlet#getHandlerMappings() */ @@ -81,10 +81,7 @@ public static class Info { private final String javaMethod; /** - * 是否包含以下 http method,空表示包含。 - * - * @param method 指定 - * @return 包含true,否则false + * Whether contains the specified http method, empty method means true */ public boolean hasMethod(RequestMethod method) { if (method == null || httpMethod.isEmpty()) return true; diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/SlowResponseInterceptor.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/SlowResponseInterceptor.java index b81bf7027..c9fd9eaa1 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/SlowResponseInterceptor.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/SlowResponseInterceptor.java @@ -21,13 +21,13 @@ public class SlowResponseInterceptor implements AutoRegisterInterceptor { /** - * slow阈值的毫秒数,-1表示关闭此功能 + * The slow threshold in ms, `-1` means disable */ @Getter @Setter private long thresholdMillis = -1; /** - * 取代日志,自行处理耗时与SQL + * Instead of logging, handle time-consuming and SQL yourself */ @Getter @Setter private BiConsumer costAndReqConsumer = (c, r) -> log.warn("SLOW-RES cost={}ms, uri={}", c, r.getRequestURI()); diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/WingsExceptionResolver.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/WingsExceptionResolver.java index 59034870e..4bab19d0d 100644 --- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/WingsExceptionResolver.java +++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/webmvc/WingsExceptionResolver.java @@ -51,10 +51,10 @@ protected ModelAndView doResolveException( } /** - * 解析异常 + * Resolve Exception * - * @param ex 当前异常 - * @return null 如果不支持 + * @param ex current exception + * @return null if not support */ protected abstract SimpleResponse resolve(T ex); } diff --git a/wings/slardar-webmvc/src/main/resources/wings-conf/spring-servlet-server-79.properties b/wings/slardar-webmvc/src/main/resources/wings-conf/spring-servlet-server-79.properties index 548f1d416..7095cbffa 100644 --- a/wings/slardar-webmvc/src/main/resources/wings-conf/spring-servlet-server-79.properties +++ b/wings/slardar-webmvc/src/main/resources/wings-conf/spring-servlet-server-79.properties @@ -22,7 +22,7 @@ server.undertow.threads.io= server.undertow.threads.worker= server.undertow.direct-buffers=true -## tcp_mem:low, pressure, high +## tcp_mem: low, pressure, high ## net.ipv4.tcp_wmem = 4096 87380 4161536 ## net.ipv4.tcp_rmem = 4096 87380 4161536 ## net.ipv4.tcp_mem = 786432 2097152 3145728 diff --git a/wings/slardar-webmvc/src/main/resources/wings-conf/wings-swagger-79.properties b/wings/slardar-webmvc/src/main/resources/wings-conf/wings-swagger-79.properties index 622ee07d0..782f2404f 100644 --- a/wings/slardar-webmvc/src/main/resources/wings-conf/wings-swagger-79.properties +++ b/wings/slardar-webmvc/src/main/resources/wings-conf/wings-swagger-79.properties @@ -8,13 +8,13 @@ wings.slardar.swagger.version=${build.version:-} ${build.time:-} wings.slardar.swagger.param[headLanguage].enable=false wings.slardar.swagger.param[headLanguage].name=Accept-Language wings.slardar.swagger.param[headLanguage].in=header -wings.slardar.swagger.param[headLanguage].description=用户语言 +wings.slardar.swagger.param[headLanguage].description=user language wings.slardar.swagger.param[headLanguage].example=zh-CN wings.slardar.swagger.param[headZoneId].enable=true wings.slardar.swagger.param[headZoneId].name=Zone-Id wings.slardar.swagger.param[headZoneId].in=header -wings.slardar.swagger.param[headZoneId].description=用户时区 +wings.slardar.swagger.param[headZoneId].description=user timezone wings.slardar.swagger.param[headZoneId].example=Asia/Shanghai ## copy Accept/MediaType to make multiple requests. 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..0c1ba10a8 --- /dev/null +++ b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error.properties @@ -0,0 +1,8 @@ +# AbstractUserDetailsAuthenticationProvider +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 new file mode 100644 index 000000000..a2a277d6e --- /dev/null +++ b/wings/slardar-webmvc/src/main/resources/wings-i18n/authn-error_zh.properties @@ -0,0 +1,7 @@ +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-webmvc/src/test/http/auth.http b/wings/slardar-webmvc/src/test/http/auth.http index 9c6d5bc93..b5d361aa4 100644 --- a/wings/slardar-webmvc/src/test/http/auth.http +++ b/wings/slardar-webmvc/src/test/http/auth.http @@ -1,7 +1,7 @@ ### login-page GET {{host}}/user/login.json -# wings-slardar-pass 的 复制一次,然后MD5,即md5(pass+pass) +## repeat wings-slardar-pass and then MD5, that is md5(pass+pass) # ### login-proc POST {{host}}/user/login-proc.json diff --git a/wings/slardar-webmvc/src/test/http/captcha.http b/wings/slardar-webmvc/src/test/http/captcha.http index ef4213b63..ea815d9c4 100644 --- a/wings/slardar-webmvc/src/test/http/captcha.http +++ b/wings/slardar-webmvc/src/test/http/captcha.http @@ -6,17 +6,17 @@ # * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body; # * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data); -# 输出 code=null +# Output code=null GET {{host}}/test/captcha.json -# 输出 captcha +# Output captcha GET {{host}}/test/captcha.json?quest-captcha-image=12345678 Client-Ticket:127.0.0.1 -### 验证码 -# 输出 captcha +### captcha +# Output captcha GET {{host}}/test/captcha.json?check-captcha-image= Client-Ticket:127.0.0.1 -# 输出 code=null +# Output code=null GET {{host}}/test/captcha-30.json 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..67dc7ee3f 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; @@ -28,7 +31,7 @@ public static class Dec { private String str = "string"; } - // 继承并override + // extends and override public static class Sub extends Dec { @JsonFormat(pattern = "#,###.##", shape = JsonFormat.Shape.STRING) @Override @@ -38,33 +41,53 @@ 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 + // Control yourself, split into different views @Data 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/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/controller/TestPageQueryController.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestPageQueryController.java index de377896b..e32967ee7 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestPageQueryController.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestPageQueryController.java @@ -49,14 +49,14 @@ public static class Ins extends PageQuery{ @RequestMapping({"/test/page-request-3.html"}) @ResponseBody - // 不能享有 别名加持,仅Jackson转换 + // no alias enchantment, Jackson conversion only. public Ins pageQuery3(@RequestBody Ins ins) { return ins; } @RequestMapping({"/test/page-request-4.html"}) @ResponseBody - // MessageConvertor无法处理 @PageDefault + // MessageConvertor can NOT handle @PageDefault public Ins pageQuery4(@RequestBody @PageDefault(size = 22, page = 2) Ins ins) { return ins; } diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestReqresLogController.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestReqresLogController.java index 779819ce6..dde51a5ab 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestReqresLogController.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/controller/TestReqresLogController.java @@ -12,7 +12,7 @@ import java.nio.charset.StandardCharsets; /** - * 手动看日志,reqres-log.http,或swagger + * Manually check the log, reqres-log.http, or swagger * * @author trydofor * @since 2021-02-01 diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/httprest/RetrofitCaller.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/httprest/RetrofitCaller.java index 61a58357c..08344e462 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/httprest/RetrofitCaller.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/httprest/RetrofitCaller.java @@ -29,7 +29,7 @@ class Ins { @Data class Bad { - @JSONField(name = "sstr") // 增加注解,使fastjson和jackson兼容 + @JSONField(name = "sstr") // annotation to make fastjson and jackson compatible private String sStr; // bad naming private String ssStr; } @@ -46,12 +46,12 @@ class Bad { @POST("/test/rest-template-helper-file.htm") Call upload(@Part MultipartBody.Part up); - // 官方issue,没有提供固定name的动态文件名的强类型方案, - // MultipartBody.Part 属于动态name,破坏了retrofit2的注解约定 + // Official issue, there is no strongly typed scheme for dynamic filenames with fixed names. + // MultipartBody.Part is a dynamic name, breaking the annotation convention of retrofit2 // https://github.com/square/retrofit/issues/1063 // https://github.com/square/retrofit/issues/1140 // https://github.com/square/retrofit/pull/1188 - // RequestFactory 构造header的不推荐的hack技巧 + // RequestFactory, not recommended to use hack trick for constructing headers @Multipart @POST("/test/rest-template-helper-file.htm") Call upload(@Part("up\"; filename=\"test.txt") RequestBody up); diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/DecimalFormatTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/DecimalFormatTest.java index 59b530bf5..6ed7d43ba 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/DecimalFormatTest.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/DecimalFormatTest.java @@ -16,6 +16,7 @@ import org.springframework.web.client.RestTemplate; import java.math.BigDecimal; +import java.math.BigInteger; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -85,6 +86,7 @@ public static class DecStr { private Double doubleObj = doubleVal; private BigDecimal decimalObj = new BigDecimal("123456.789"); + private BigInteger integerObj = new BigInteger("123456789"); } @Data @@ -109,6 +111,10 @@ public static class DecFmt { private BigDecimal decimalObj = new BigDecimal("123456.789"); @JsonFormat(pattern = "¥,####.0", shape = JsonFormat.Shape.STRING) private BigDecimal decimalShp = new BigDecimal("123456.789"); + @JsonFormat(pattern = "¥,####.0") + private BigInteger integerObj = new BigInteger("123456789"); + @JsonFormat(pattern = "¥,####.0", shape = JsonFormat.Shape.STRING) + private BigInteger integerShp = new BigInteger("123456789"); } @Data @@ -131,6 +137,8 @@ public static class DecRaw { private Double doubleObj = doubleVal; @JsonRawValue() private BigDecimal decimalObj = new BigDecimal("123456.789"); + @JsonRawValue() + private BigInteger integerObj = new BigInteger("123456789"); } @Test @@ -145,7 +153,8 @@ public void testDecStr() throws JsonProcessingException { + "\"floatObj\":\"123456.78\"," + "\"doubleVal\":\"123456.78\"," + "\"doubleObj\":\"123456.78\"," - + "\"decimalObj\":\"123456.78\"}" + + "\"decimalObj\":\"123456.78\"," + + "\"integerObj\":\"123456789.00\"}" , decStr); } @@ -161,7 +170,8 @@ public void testDecRaw() throws JsonProcessingException { + "\"floatObj\":123456.79," + "\"doubleVal\":123456.789," + "\"doubleObj\":123456.789," - + "\"decimalObj\":123456.789}" + + "\"decimalObj\":123456.789," + + "\"integerObj\":123456789}" , decRaw); } @@ -179,7 +189,9 @@ public void testDecFmt() throws JsonProcessingException { + "\"doubleVal\":\"12,3456.7\"," + "\"doubleObj\":\"12,3456.7\"," + "\"decimalObj\":\"¥12_3456.7\"," - + "\"decimalShp\":\"¥12,3456.7\"}" + + "\"decimalShp\":\"¥12,3456.7\"," + + "\"integerObj\":\"¥1_2345_6789.0\"," + + "\"integerShp\":\"¥1,2345,6789.0\"}" , decFmt); } diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/WingsJacksonMapperTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/WingsJacksonMapperTest.java index 2c3ad70fa..040ac93b3 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/WingsJacksonMapperTest.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/json/WingsJacksonMapperTest.java @@ -180,7 +180,7 @@ public static class JsonIt { @JsonFormat(pattern = DateTimePattern.PTN_FULL_23Z) private ZonedDateTime zonedDateTimeValZ = zonedDateTimeVal.withZoneSameInstant(userTz.toZoneId()); private Instant instantVal = Instant.parse("2020-06-01T12:34:46.000Z"); - private List listVal = Arrays.asList("字符串", "列表"); + private List listVal = Arrays.asList("String", "List"); private Map mapVal = new HashMap<>() {{put("Map", 1L);}}; // empty private LocalDateTime localDateTimeEmpty = LocalDateTime.parse("1970-01-01T00:00:00"); @@ -235,20 +235,20 @@ public void testI18nString() throws IOException { @Data @XmlRootElement public static class I18nJson { - @AutoI18nString // 有效 + @AutoI18nString // valid private String codeManual = "base.not-empty"; private String codeIgnore = "base.not-empty"; - // 自动 + // auto private I18nString textAuto = new I18nString("base.not-empty", "", "textAuto"); - @AutoI18nString(false) //禁用 + @AutoI18nString(false) // disable private I18nString textDisabled = new I18nString("base.not-empty", "", "textDisabled"); - @AutoI18nString // 无效 + @AutoI18nString // invalid private Long longIgnore = 0L; - @AutoI18nString // 无效 + @AutoI18nString // invalid private Map mapIgnore = Collections.singletonMap("ikey", "ival"); - @AutoI18nString(false) //禁用 + @AutoI18nString(false) //disable private Map mapDisabled = Collections.singletonMap("i18n", textDisabled); - // 自动 + // auto private Map mapAuto = Collections.singletonMap("i18n", textAuto); } @@ -257,12 +257,12 @@ public static class I18nJson { public void testI18nResult() throws IOException { ObjectWriter jackson = objectMapper.writerWithDefaultPrettyPrinter(); - R r1 = R.ok("这是一个消息", new I18nJson()); + R r1 = R.ok("This is a message", new I18nJson()); String j1 = jackson.writeValueAsString(r1); assertEquals(""" { "success" : true, - "message" : "这是一个消息", + "message" : "This is a message", "data" : { "codeManual" : "{0} can not be empty", "codeIgnore" : "base.not-empty", @@ -289,12 +289,12 @@ public void testI18nResult() throws IOException { } }""", j1); - R r2 = r1.setI18nMessage("base.not-empty", "第一个参数"); + R r2 = r1.setI18nMessage("base.not-empty", "Param1"); String j2 = jackson.writeValueAsString(r2); assertEquals(""" { "success" : true, - "message" : "第一个参数 can not be empty", + "message" : "Param1 can not be empty", "data" : { "codeManual" : "{0} can not be empty", "codeIgnore" : "base.not-empty", @@ -375,8 +375,8 @@ public void testXml() throws IOException { 2020-06-01 13:34:46.000 +0900 2020-06-01T12:34:46Z - 字符串 - 列表 + String + List 1 @@ -399,7 +399,7 @@ public void testTreeMapGenerator() throws IOException { assertEquals("{code=base.not-empty, codeIgnore=base.not-empty, codeManual={0} can not be empty, hint=, i18n=textAuto can not be empty, ikey=ival, longIgnore=0, textAuto=textAuto can not be empty}", t1.getResultTree() .toString() .trim()); - assertEquals("{intVal=2147483646, longVal=9223372036854775806, floatVal=1.1, doubleVal=2.2, decimalVal=3.3, localDateTimeVal=2020-06-01 12:34:46, localDateVal=2020-06-01, localTimeVal=12:34:46, zonedDateTimeVal=2020-06-01 13:34:46 Asia/Tokyo, zonedDateTimeValV=2020-06-01 13:34:46.000 Asia/Tokyo, zonedDateTimeValZ=2020-06-01 13:34:46.000 +0900, instantVal=2020-06-01T12:34:46Z, listVal=列表, Map=1, bool-val=false}", t2.getResultTree().toString().trim()); + assertEquals("{intVal=2147483646, longVal=9223372036854775806, floatVal=1.1, doubleVal=2.2, decimalVal=3.3, localDateTimeVal=2020-06-01 12:34:46, localDateVal=2020-06-01, localTimeVal=12:34:46, zonedDateTimeVal=2020-06-01 13:34:46 Asia/Tokyo, zonedDateTimeValV=2020-06-01 13:34:46.000 Asia/Tokyo, zonedDateTimeValZ=2020-06-01 13:34:46.000 +0900, instantVal=2020-06-01T12:34:46Z, listVal=List, Map=1, bool-val=false}", t2.getResultTree().toString().trim()); } @Test @@ -413,9 +413,9 @@ public void testHelper() { Map x2 = StringMapHelper.jaxb(jsonIt); assertEquals("{code=base.not-empty, codeIgnore=base.not-empty, codeManual={0} can not be empty, hint=, i18n=textAuto can not be empty, ikey=ival, longIgnore=0, textAuto=textAuto can not be empty}", j1.toString()); - assertEquals("{Map=1, bool-val=false, decimalVal=3.3, doubleVal=2.2, floatVal=1.1, instantVal=2020-06-01T12:34:46Z, intVal=2147483646, listVal=列表, localDateTimeVal=2020-06-01 12:34:46, localDateVal=2020-06-01, localTimeVal=12:34:46, longVal=9223372036854775806, zonedDateTimeVal=2020-06-01 13:34:46 Asia/Tokyo, zonedDateTimeValV=2020-06-01 13:34:46.000 Asia/Tokyo, zonedDateTimeValZ=2020-06-01 13:34:46.000 +0900}", j2.toString()); + assertEquals("{Map=1, bool-val=false, decimalVal=3.3, doubleVal=2.2, floatVal=1.1, instantVal=2020-06-01T12:34:46Z, intVal=2147483646, listVal=List, localDateTimeVal=2020-06-01 12:34:46, localDateVal=2020-06-01, localTimeVal=12:34:46, longVal=9223372036854775806, zonedDateTimeVal=2020-06-01 13:34:46 Asia/Tokyo, zonedDateTimeValV=2020-06-01 13:34:46.000 Asia/Tokyo, zonedDateTimeValZ=2020-06-01 13:34:46.000 +0900}", j2.toString()); assertEquals("{codeIgnore=base.not-empty, codeManual=base.not-empty, key=ikey, longIgnore=0, value=ival}", x1.toString()); - assertEquals("{boolVal=false, decimalVal=3.3, doubleVal=2.2, floatVal=1.1, intVal=2147483646, key=Map, listVal=列表, longVal=9223372036854775806, value=1}", x2.toString()); + assertEquals("{boolVal=false, decimalVal=3.3, doubleVal=2.2, floatVal=1.1, intVal=2147483646, key=Map, listVal=List, longVal=9223372036854775806, value=1}", x2.toString()); } // 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..77c382cf6 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 @@ -22,7 +22,7 @@ "wings.slardar.ding-notice.default.access-token=${DING_TALK_TOKEN:}", "wings.slardar.ding-notice.default.notice-mobiles.god9=155XXXX1991", }) -@Disabled("钉钉通知,避免频繁调用") +@Disabled("Avoid frequent calls") class DingTalkReportTest { @Setter(onMethod_ = {@Autowired}) @@ -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 Title", "## Test context\n\n- **List** Text"); + conf.setMsgType(DingTalkConf.MsgText); + dingTalkNotice.post(conf, "Text Title", "## Test context\n\n- **List** Text"); } } diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/security/pass/DefaultPasssaltEncoderTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/security/pass/DefaultPasssaltEncoderTest.java index 1eb9b0ee4..ddb4ee1fa 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/security/pass/DefaultPasssaltEncoderTest.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/security/pass/DefaultPasssaltEncoderTest.java @@ -11,6 +11,7 @@ import pro.fessional.mirana.code.RandCode; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -25,6 +26,7 @@ class DefaultPasssaltEncoderTest { private final PasswordEncoder scrypt = SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1(); private final PasswordEncoder argon2 = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2(); private final DefaultPasssaltEncoder sha256 = new DefaultPasssaltEncoder(MdHelp.sha256); + private final MysqlPasswordEncoder mysql = new MysqlPasswordEncoder(); /** * BCryptPasswordEncoder ms/100 =7745 @@ -68,4 +70,24 @@ void testSalt() { log.info(p1); assertEquals(p1, p2); } + + /** + * SELECT password("wingsboot"); -- *470398BC6EA62F5FB5BFE1EA5FAD13A28EE432DE + * SELECT password("milioncircle"); -- *48E834F2D5A6762230DF2DC976FD1712793ED6D8 + */ + @Test + void testMysql() { + String text1 = "wingsboot"; + String text2 = "milioncircle"; + String code1 = "*470398BC6EA62F5FB5BFE1EA5FAD13A28EE432DE"; + String code2 = "*48E834F2D5A6762230DF2DC976FD1712793ED6D8"; + + final String pass1 = mysql.encode(text1); + final String pass2 = mysql.encode(text2); + + assertEquals("*" + pass1, code1); + assertEquals("*" + pass2, code2); + assertTrue(mysql.matches(text1, code1)); + assertTrue(mysql.matches(text2, code2)); + } } diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/spring/bean/LoggingRequestConfiguration.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/spring/bean/LoggingRequestConfiguration.java index 73413e22a..2f77a3a95 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/spring/bean/LoggingRequestConfiguration.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/spring/bean/LoggingRequestConfiguration.java @@ -15,7 +15,7 @@ import java.util.Collection; /** - * 由 reqres-log.http 测试,人工识别。 + * Test by reqres-log.http, check manually * * @author trydofor * @since 2019-12-03 diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/DateTimeConverterTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/DateTimeConverterTest.java index 66aeb298a..59e039e18 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/DateTimeConverterTest.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/DateTimeConverterTest.java @@ -30,7 +30,7 @@ public class DateTimeConverterTest { private MockMvc mockMvc; /** - * 测试格式及补全 + * Test for Format and Auto Completion */ @Test public void testFmtDate() throws Exception { @@ -46,7 +46,7 @@ private void assertFmtDate(String d, String v) throws Exception { } /** - * 测试格式及补全 + * Test for Format and Auto Completion */ @Test public void testFullDate() throws Exception { @@ -66,7 +66,7 @@ private void assertFullDate(String d, String v) throws Exception { } /** - * 测试格式及补全 + * Test for Format and Auto Completion */ @Test public void testLocalDate() throws Exception { @@ -90,7 +90,7 @@ private void assertLocalDate(String d, String v) throws Exception { } /** - * 测试格式及补全 + * Test for Format and Auto Completion */ @Test public void testLocalTime() throws Exception { @@ -108,8 +108,8 @@ private void assertLocalTime(String d, String v) throws Exception { /** - * 用户时区GMT+9,系统时区GMT+8,使用@RequestParam输入LocalDateTime,并作为系统时时间, - * 希望json输出时,把系统时区自动变为用户时区,+1小时。 + * User in GMT+9, System in GMT+8, use @RequestParam get input LocalDateTime, and treat as system local time. + * when response the json, auto convert system timezone to user timezone, that is +1 hour. * * @see pro.fessional.wings.slardar.json.WingsJacksonMapperTest */ @@ -124,10 +124,10 @@ public void testLdtZdt() throws Exception { } /** - * @param udt 用户的输入时间 - * @param cdt 收到的输入时间 - * @param zdt 把udt作为系统时间,输出为用户时间 - * @param utz 用户时区 + * @param udt input date time + * @param cdt receive date time + * @param zdt treat udt as system local time, output user time + * @param utz user time zone */ private void testLdtZdt(String udt, String cdt, String zdt, String utz) throws Exception { final MockHttpServletRequestBuilder builder = post("/test/ldt-zdt.json?d=" + udt) @@ -142,8 +142,8 @@ private void testLdtZdt(String udt, String cdt, String zdt, String utz) throws E } /** - * 用户时区GMT+9,系统时区GMT+8,使用@RequestBody输入LocalDateTime,并作为系统时时间, - * 希望json输出时,把系统时区自动变为用户时区,+1小时。 + * User in GMT+9, System in GMT+8, use @RequestBody get input LocalDateTime, and treat as system local time. + * when response the json, auto convert system timezone to user timezone, that is +1 hour. */ @Test public void testLdtZdtBody() throws Exception { @@ -156,10 +156,10 @@ public void testLdtZdtBody() throws Exception { } /** - * @param udt 用户的输入时间 - * @param cdt 收到的输入时间 - * @param zdt 把udt作为系统时间,输出为用户时间 - * @param utz 用户时区 + * @param udt input date time + * @param cdt receive date time + * @param zdt treat udt as system local time, output user time + * @param utz user time zone */ private void testLdtZdtBody(String udt, String cdt, String zdt, String utz) throws Exception { final MockHttpServletRequestBuilder builder = post("/test/ldt-zdt-body.json") @@ -176,8 +176,9 @@ private void testLdtZdtBody(String udt, String cdt, String zdt, String utz) thro } /** - * 用户时区GMT+9,系统时区GMT+8,使用@RequestParam输入ZonedDateTime,并转换一次,变为系统时区。 - * 输出时,自动变为用户时区。(不要使用有夏令时的时区测试,以免刚好切换) + * User in GMT+9, System in GMT+8, use @RequestParam get input ZonedDateTime, and auto convert to system timezone. + * when response the json, auto convert system timezone to user timezone. + * Do not test with a time zone that has daylight saving time to avoid switching. */ @Test public void testZdtLdt() throws Exception { @@ -188,10 +189,10 @@ public void testZdtLdt() throws Exception { } /** - * @param udt 用户的输入时间,以ZonedDateTime接受,会变成用户时区 - * @param zdt 收到的输入时间,自动转为系统时间,并输出时再次转为用户时区 - * @param cdt 收到的输入时间,自动转为系统时间,以LocalDateTime显示 - * @param utz 用户时区 + * @param udt input date time, in ZonedDateTime type, will auto convert to user timezone + * @param zdt receive date time, auto convert to system timezone, and to user timezone when response + * @param cdt receive date time, auto convert to system timezone, output in LocalDateTime + * @param utz user time zone */ private void testZdtLdt(String udt, String zdt, String cdt, String utz) throws Exception { final MockHttpServletRequestBuilder builder = get("/test/zdt-ldt.json?d=" + udt) @@ -206,8 +207,9 @@ private void testZdtLdt(String udt, String zdt, String cdt, String utz) throws E } /** - * 用户时区GMT+9,系统时区GMT+8,使用@RequestBody输入ZonedDateTime,并转换一次,变为系统时区。 - * 输出时,自动变为用户时区。(不要使用有夏令时的时区测试,以免刚好切换) + * User in GMT+9, System in GMT+8, use @RequestBody get input ZonedDateTime, and auto convert to system timezone. + * when response the json, auto convert system timezone to user timezone. + * Do not test with a time zone that has daylight saving time to avoid switching. */ @Test public void testZdtLdtBody() throws Exception { @@ -218,10 +220,10 @@ public void testZdtLdtBody() throws Exception { } /** - * @param udt 用户的输入时间,以ZonedDateTime接受,会变成用户时区 - * @param zdt 收到的输入时间,自动转为系统时间,并输出时再次转为用户时区 - * @param cdt 收到的输入时间,自动转为系统时间,以LocalDateTime显示 - * @param utz 用户时区 + * @param udt input date time, in ZonedDateTime type, will auto convert to user timezone + * @param zdt receive date time, auto convert to system timezone, and to user timezone when response + * @param cdt receive date time, auto convert to system timezone, output in LocalDateTime + * @param utz user time zone */ private void testZdtLdtBody(String udt, String zdt, String cdt, String utz) throws Exception { final MockHttpServletRequestBuilder builder = get("/test/zdt-ldt-body.json") @@ -238,8 +240,8 @@ private void testZdtLdtBody(String udt, String zdt, String cdt, String utz) thro } /** - * 用户时区GMT+9,系统时区GMT+8,用@RequestParam接收LocalDateTime输入,按系统时区处理。 - * 希望json输出时,把系统时区自动变为用户时区,+1小时。 + * User in GMT+9, System in GMT+8, use @RequestParam get input LocalDateTime, and treat as system local time. + * when response the json, auto convert system timezone to user timezone, that is +1 hour. */ @Test public void testLdtOdt() throws Exception { @@ -252,11 +254,11 @@ public void testLdtOdt() throws Exception { } /** - * @param udt 用户的输入时间,以LocalDateTime接受 - * @param ldt 收到的输入时间 - * @param odt 以收到的udt作为系统时间,以OffsetDateTime输出 - * @param utz 用户时区 - * @param otz 输出的Offset + * @param udt input date time, use LocalDateTime type + * @param ldt receive date time + * @param odt treat udt as system local time, output in OffsetDateTime + * @param utz user time zone + * @param otz output Offset */ private void testLdtOdt(String udt, String ldt, String odt, String utz, String otz) throws Exception { final MockHttpServletRequestBuilder builder = post("/test/ldt-odt.json?d=" + udt) @@ -271,8 +273,8 @@ private void testLdtOdt(String udt, String ldt, String odt, String utz, String o } /** - * 用户时区GMT+9,系统时区GMT+8,用@RequestBody接收LocalDateTime输入,按系统时区处理。 - * 希望json输出时,把系统时区自动变为用户时区,+1小时。 + * User in GMT+9, System in GMT+8, use @RequestBody get input LocalDateTime, and treat as system local time. + * when response the json, auto convert system timezone to user timezone, that is +1 hour. */ @Test @@ -286,11 +288,11 @@ public void testLdtOdtBody() throws Exception { } /** - * @param udt 用户的输入时间,以LocalDateTime接受 - * @param ldt 收到的输入时间 - * @param odt 以收到的udt作为系统时间,以OffsetDateTime输出 - * @param utz 用户时区 - * @param otz 输出的Offset + * @param udt input date time, use LocalDateTime type + * @param ldt receive date time + * @param odt treat udt as system local time, output in OffsetDateTime + * @param utz user time zone + * @param otz output Offset */ private void testLdtOdtBody(String udt, String ldt, String odt, String utz, String otz) throws Exception { final MockHttpServletRequestBuilder builder = post("/test/ldt-odt-body.json") @@ -307,8 +309,9 @@ private void testLdtOdtBody(String udt, String ldt, String odt, String utz, Stri } /** - * 用户时区GMT+9,系统时区GMT+8,以@RequestParam接收OffsetDateTime输入,自动转换,并作为系统时间。 - * 输出时,自动变为用户时区。(不要使用有夏令时的时区测试,以免刚好切换) + * User in GMT+9, System in GMT+8, use @RequestParam get input OffsetDateTime, and auto convert to system timezone. + * when response the json, auto convert system timezone to user timezone. + * Do not test with a time zone that has daylight saving time to avoid switching. */ @Test public void testOdtLdt() throws Exception { @@ -319,11 +322,11 @@ public void testOdtLdt() throws Exception { } /** - * @param udt 用户的输入时间,以OffsetDateTime接受,自动转为用户时区,在转为系统时间 - * @param odt 以收到的udt,再转为系统时间 - * @param cdt 以收到的udt,再转为系统时间,以Ldt显示 - * @param utz 用户时区 - * @param otz 输出的Offset + * @param udt input date time, use OffsetDateTime type, auto convert from user timezone to system timezone + * @param odt receive date time, convert to system timezone + * @param cdt receive date time, convert to system timezone, output in LocalDateTime + * @param utz user time zone + * @param otz output Offset */ private void testOdtLdt(String udt, String odt, String cdt, String utz, String otz) throws Exception { final MockHttpServletRequestBuilder builder = get("/test/odt-ldt.json?d=" + udt) @@ -338,8 +341,9 @@ private void testOdtLdt(String udt, String odt, String cdt, String utz, String o } /** - * 用户时区GMT+9,系统时区GMT+8,以@RequestBody接收OffsetDateTime输入,自动转换,并作为系统时间。 - * 输出时,自动变为用户时区。(不要使用有夏令时的时区测试,以免刚好切换) + * User in GMT+9, System in GMT+8, use @RequestBody get input OffsetDateTime, and auto convert to system timezone. + * when response the json, auto convert system timezone to user timezone. + * Do not test with a time zone that has daylight saving time to avoid switching. */ @Test public void testOdtLdtBody() throws Exception { @@ -350,11 +354,11 @@ public void testOdtLdtBody() throws Exception { } /** - * @param udt 用户的输入时间,以OffsetDateTime接受,自动转为用户时区,在转为系统时间 - * @param odt 以收到的udt,再转为系统时间 - * @param cdt 以收到的udt,再转为系统时间,以Ldt显示 - * @param utz 用户时区 - * @param otz 输出的Offset + * @param udt input date time, use OffsetDateTime type, auto convert from user timezone to system timezone + * @param odt receive date time, convert to system timezone + * @param cdt receive date time, convert to system timezone, output in LocalDateTime + * @param utz user time zone + * @param otz output Offset */ private void testOdtLdtBody(String udt, String odt, String cdt, String utz, String otz) throws Exception { final MockHttpServletRequestBuilder builder = get("/test/odt-ldt-body.json") @@ -409,7 +413,7 @@ private void testLtLtBody(String d, String v) throws Exception { @Test public void testLdxAuto1() throws Exception { final String utz = "Asia/Tokyo"; - // 用户+9的12点,变为系统+8的11点 + // User +9 zone, 12 o'clock, convert to system +8 zone, 11 o'clock final MockHttpServletRequestBuilder b1q = post("/test/ldx-body-req.json") .header("Zone-Id", utz) .contentType(MediaType.APPLICATION_JSON) @@ -418,7 +422,7 @@ public void testLdxAuto1() throws Exception { .andDo(print()) .andExpect(content().string("2022-10-03T11:34:56")); - // 系统+8的12点,变为用户+9的13点 + // system +8 zone, 12 o'clock, convert to user +9 zone, 13 o'clock final MockHttpServletRequestBuilder b1s = post("/test/ldx-body-res.json?d=2022-10-03T12:34:56") .header("Zone-Id", utz); mockMvc.perform(b1s) @@ -430,14 +434,16 @@ public void testLdxAuto1() throws Exception { public void testLdxAuto2() throws Exception { final String utz = "Asia/Tokyo"; - // 用户+9的12点,变为系统+8的11点 + // Use +9 zone, 12 o'clock, convert to system +8 zone, 11 o'clock final MockHttpServletRequestBuilder b2q = post("/test/ldt-para-req.json?d=2022-10-03T12:34:56") .header("Zone-Id", utz); mockMvc.perform(b2q) .andDo(print()) .andExpect(content().string("2022-10-03T11:34:56")); - // NOTE 注意,json无法直接获取在return上的注解,需要动用BodyAdvice,比较重。此用法不常见,因此不支持此种场景 + // NOTE json can't get the annotation on return directly, + // need to use BodyAdvice, which is heavy. + // This usage is not common, so this scenario is not supported. final MockHttpServletRequestBuilder b2s = post("/test/ldt-para-res.json?d=2022-10-03T12:34:56") .header("Zone-Id", utz); mockMvc.perform(b2s) 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)); + } +} diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/WingsCookieTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/WingsCookieTest.java index 01197198d..009ed2ff2 100644 --- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/WingsCookieTest.java +++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/WingsCookieTest.java @@ -86,9 +86,9 @@ public void test1Cookie() throws Exception { @Test public void test2Cookie() throws Exception { - // 必须用 client,mock不执行 + // Must use client, do NOT mock httpClient("/test/cookie-forward.json"); - // mock mvc 并不真正执行 mapping方法 + // mock mvc do NOT really execute the mapping method // mockMvcCookie("/test/cookie-forward.json"); } diff --git a/wings/slardar-webmvc/src/test/resources/wings-conf/wings-domain-extend.properties b/wings/slardar-webmvc/src/test/resources/wings-conf/wings-domain-extend.properties index 644c0d6e8..eba589daa 100644 --- a/wings/slardar-webmvc/src/test/resources/wings-conf/wings-domain-extend.properties +++ b/wings/slardar-webmvc/src/test/resources/wings-conf/wings-domain-extend.properties @@ -1,8 +1,7 @@ -# host映射关系,FilenameUtils.wildcardMatch +## host-ext mapping, FilenameUtils.wildcardMatch wings.slardar.domain-extend.host[a]=127.0.0.1 wings.slardar.domain-extend.host[b]=localhost -# 默认情况下,自动解析HandlerMappings。 -# 当非Mapping URL时,手动指定url,支持ant风格 +## wings.slardar.domain-extend.other-url=/a/b/c.html,\ /c/d.json 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/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java index 05b63e294..211ac84e0 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java @@ -42,7 +42,7 @@ public static ThreadPoolTaskScheduler Heavy() { } /** - * 获取Light或Heavy + * Get Light Scheduler if fast, otherwise Heavy. */ @NotNull public static ThreadPoolTaskScheduler referScheduler(boolean fast) { @@ -50,7 +50,7 @@ public static ThreadPoolTaskScheduler referScheduler(boolean fast) { } /** - * 异步立即执行一个任务,fast表示此任务很快会结束,如10s + * Execute an async task immediately, `fast` means that the task will be finished soon, e.g. 10s. * * @see ThreadPoolTaskScheduler#execute(Runnable) */ @@ -59,7 +59,7 @@ public static void execute(boolean fast, @NotNull Runnable task) { } /** - * 在delayMs毫秒(ThreadNow)后,异步执行一个任务,fast表示此任务很快会结束,如10s + * Execute an async task after delayMs millis (ThreadNow), `fast` means that the task will be finished soon, e.g. 10s. * * @see ThreadPoolTaskScheduler#schedule(Runnable, Instant) */ @@ -68,7 +68,7 @@ public static ScheduledFuture execute(boolean fast, long delayMs, @NotNull Ru } /** - * 在指定时间(fastTime构建,系统默认时区),异步执行一个任务,fast表示此任务很快会结束,如10s + * Execute an async task at specified instant, `fast` means that the task will be finished soon, e.g. 10s. * * @see ThreadPoolTaskScheduler#schedule(Runnable, Instant) */ @@ -77,8 +77,8 @@ public static ScheduledFuture execute(boolean fast, Instant start, @NotNull R } /** - * 指定trigger,异步执行一个任务,fast表示此任务很快会结束,如10s, - * errorHandler不同于其他方法,不识别DelegatingErrorHandlingRunnable + * Execute an async task by given trigger, `fast` means that the task will be finished soon, e.g. 10s. + * Note, errorHandler, unlike other methods, does not handle DelegatingErrorHandlingRunnable. * * @see ThreadPoolTaskScheduler#schedule(Runnable, Trigger) */ diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java index 7c4ef62f1..29844a921 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java @@ -19,7 +19,7 @@ import java.util.concurrent.ThreadFactory; /** - * 使用Ttl包装 + * use Ttl ThreadPool * * @author trydofor * @since 2022-12-03 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autodto/AutoI18nString.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autodto/AutoI18nString.java index 5a931c37b..3c0198c2e 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autodto/AutoI18nString.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autodto/AutoI18nString.java @@ -6,9 +6,11 @@ import java.lang.annotation.Target; /** - * 对I18nString和CharSequence进行国际化转换 - * `I18nString`类型会自动转换,使用@JsonI18nString关闭 - * `CharSequence`如果内部是i18nCode,使用@JsonI18nString开启。 + *

+ * Auto I18n Convert for I18nString and CharSequence
+ * `I18nString` - auto convert, use @JsonI18nString to disable.
+ * `CharSequence` - If contains i18nCode, use @JsonI18nString to enable.
+ * 
* * @author trydofor * @since 2019-09-19 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoTimeZone.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoTimeZone.java index 0e41d0b25..5bc57190b 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoTimeZone.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoTimeZone.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * 标记到LocalDateTime,ZonedDatetime,OffsetDateTime,明确其自动转换的行为 + * How to auto convert LocalDateTime, ZonedDatetime and OffsetDateTime. * * @author trydofor * @since 2021-03-22 @@ -16,9 +16,9 @@ public @interface AutoTimeZone { /** - * 自动转换的目标Zone + * Auto convert to target TimeZone * - * @return 目标Zone + * @return target TimeZone */ AutoZoneType value() default AutoZoneType.Auto; } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneAware.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneAware.java index 30b7eb450..708f6f43d 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneAware.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneAware.java @@ -9,8 +9,8 @@ import java.time.temporal.TemporalAccessor; /** - * Request时,自动把用户时间调至系统时区。 - * Response时,自动把系统时间调至用户时区。 + * When request, auto convert timezone from client to system. + * When response, auto convert timezone from system to client * * @author trydofor * @since 2022-10-02 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneType.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneType.java index e5c799ff1..c43c546c3 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneType.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneType.java @@ -2,9 +2,9 @@ /** *
- * 转换在Controller层发生
- * ① request时,从string到DateTime,从CLient到System,spring或jackson完成
- * ② response时,从DateTime到string,从System到Client,jackson完成。
+ * The conversion should occur at the Controller level
+ * (1) when request, form string to DateTime, form Client to System, by spring or jackson
+ * (2) when response, from DateTime to string, from System to Client, by jackson
  * 
* * @author trydofor @@ -12,19 +12,19 @@ */ public enum AutoZoneType { /** - * 转到系统时区 + * To System timezone */ System, /** - * 转到客户时区,仅限于Controller层 + * to Client timezone, should convert at Controller level */ Client, /** - * 按request和response自动转换到目的地时区 + * Auto convert to target timezone by request and response */ Auto, /** - * 关闭转换功能 + * disable convert */ Off, diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneUtil.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneUtil.java index 9ac70885b..b12564e27 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneUtil.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/autozone/AutoZoneUtil.java @@ -31,10 +31,10 @@ public static LocalDateTime autoLocalRequest(@NotNull TemporalAccessor dateTime, @NotNull public static ZonedDateTime autoZonedRequest(@NotNull TemporalAccessor dateTime, @NotNull AutoZoneType autoType, @NotNull Supplier client) { - // ① tma是用户发出,先调整为Client时间 + // (1) tma is sent by the user, first adjusted to Client timezone final ZonedDateTime zdt = DateParser.parseZoned(dateTime, client.get()); - // ② 变为System时区 + // (2) convert to System timezone if (autoType == AutoZoneType.Auto || autoType == AutoZoneType.System) { return zdt.withZoneSameInstant(ThreadNow.sysZoneId()); } @@ -51,7 +51,7 @@ public static OffsetDateTime autoOffsetRequest(@NotNull TemporalAccessor dateTim // response : DateTime to String, Default System to Client @NotNull public static LocalDateTime autoLocalResponse(@NotNull LocalDateTime dateTime, @NotNull AutoZoneType autoType, @NotNull Supplier client) { - // 假设LocalDateTime都是系统时区 + // Assuming that LocalDateTime are all system timezone if (autoType == AutoZoneType.Auto || autoType == AutoZoneType.Client) { return DateLocaling.useLdt(dateTime, client.get()); } 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/cache/WingsCacheHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/WingsCacheHelper.java index f460848fd..20bcfb769 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/WingsCacheHelper.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/WingsCacheHelper.java @@ -10,7 +10,11 @@ import pro.fessional.mirana.data.Null; import java.lang.reflect.Method; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static java.util.Collections.emptyMap; @@ -65,7 +69,7 @@ public static Cache getServerCache(String name) { } /** - * 设置 CacheManager 名字及Resolver对应关系 + * Set CacheManager name and its Resolver */ public static void putManagers(Map mngs) { managers.putAll(mngs); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/Cache2kSlot.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/Cache2kSlot.java index b6ac1f7a9..0ea80dc68 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/Cache2kSlot.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/Cache2kSlot.java @@ -19,21 +19,21 @@ public class Cache2kSlot { private final int max; /** - * 以ConcurrentHashMap构造一个按ttl分片的缓存 + * Construct a cache slot by ttl with ConcurrentHashMap * - * @param ttl 最大ttl秒 - * @param step 分片步长秒 + * @param ttl max ttl in second + * @param step slot step in second */ public Cache2kSlot(int ttl, int step) { this(new ConcurrentHashMap<>(), ttl, step); } /** - * 构造一个按ttl分片的缓存 + * Construct a cache slot by ttl with specified Map * - * @param slot slot - * @param ttl 最大ttl秒 - * @param step 分片步长秒 + * @param slot slot map + * @param ttl max ttl in second + * @param step slot step in second */ public Cache2kSlot(Map> slot, int ttl, int step) { this.slot = slot; @@ -42,10 +42,10 @@ public Cache2kSlot(Map> slot, int ttl, int step) } /** - * 根据秒数,获得最大24小时,误差step秒的无界缓存 + * Get an unbounded cache with a max ttl of 24 hours and a precision of `step` seconds, based on `second`. * * @param second ttl - * @return 缓存 + * @return the cache */ @NotNull public Cache getCache(int second) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/WingsCache2k.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/WingsCache2k.java index 9026f9926..1753c34fe 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/WingsCache2k.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/cache2k/WingsCache2k.java @@ -113,7 +113,7 @@ public void enlist(@NotNull CacheBuildContext ctx) { final Cache2kBuilder bld = ctx.getConfig().builder(); for (Map.Entry entry : slardarCacheProp.getLevel().entrySet()) { - // 前缀同 + // same prefix final String key = entry.getKey(); if (inLevel(name, key)) { final SlardarCacheProp.Conf level = entry.getValue(); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/CacheEvictMultiKeys.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/CacheEvictMultiKeys.java index 29092f21c..6acfc229f 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/CacheEvictMultiKeys.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/CacheEvictMultiKeys.java @@ -2,6 +2,7 @@ import lombok.Getter; import lombok.Setter; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.springframework.cache.interceptor.SimpleKeyGenerator; @@ -10,7 +11,8 @@ import java.util.List; /** - * 可evict单key,多key,全部,或跳过。写入时非线程安全 + * Can evict single key, multiple keys, all keys, or skip. + * Non-thread-safe on write * * @author trydofor * @see SimpleKeyGenerator#generateKey(Object...) @@ -39,7 +41,7 @@ public CacheEvictMultiKeys(boolean all, @NotNull List keys) { } /** - * 兼容非wings版,获得key + * get key, compatible with non-wings style */ public Object getKey() { if (wingsSupport) { @@ -50,6 +52,7 @@ public Object getKey() { } } + @Contract("_->this") public CacheEvictMultiKeys addKey(Object... arg) { if (evictKey.isEmpty()) { evictKey = new LinkedList<>(); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/NullsCache.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/NullsCache.java index f8eadda79..b8ce9a858 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/NullsCache.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/NullsCache.java @@ -7,8 +7,12 @@ import java.util.concurrent.Callable; /** - * 对nulls进行统一处理的缓存,支持weak及skip处理。 - * 正数:缓存大小,默认ttl=3600s; 0:不缓存null;其他值则不统一处理 + *
+ * Unified processing cache for nulls, supports weak and skip.
+ * * >0 : cache size, default ttl=3600s;
+ * * 0 : no nulls cached;
+ * * other values are not handled uniformly.
+ * 
* * @author trydofor * @since 2022-03-13 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/WingsCacheResolver.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/WingsCacheResolver.java index eddcf2d82..9d8a18b6c 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/WingsCacheResolver.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/cache/spring/WingsCacheResolver.java @@ -13,7 +13,7 @@ import java.util.Set; /** - * 支持自动展开为实现类 + * Support for auto-expansion to implementation class * * @author trydofor * @see SimpleCacheResolver diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/DoubleKill.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/DoubleKill.java index e4c733026..93abbc199 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/DoubleKill.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/DoubleKill.java @@ -6,11 +6,17 @@ import java.lang.annotation.Target; /** - * 注意:异步执行时且同@Cacheable等AOP功能注解一起使用时,要保证DK最先被执行。 - * 否则异步执行的结果,不能被正确处理。若是不能保证最先执行,不要同时使用。 - *

- * JVM内重复执行拦截,以抛出无栈异常终止执行,需要调用者自行catch。 - * - DoubleKillException 除同步执行中的调用,其他调用都throw。 + *

+ * Note: When executing async and using with AOP annotations such as @Cacheable,
+ * make sure that DK is executed first. Otherwise, the result of the async execution
+ * cannot be processed correctly. If you can not guarantee the first execution,
+ * do not use at the same time.
+ *
+ * Repeated interception within the JVM will throw a no-stack exception to stop the execution,
+ * the caller needs to `catch` their own.
+ * - DoubleKillException will `throw` except those in sync execution.
+ *
+ * 
* * @author trydofor * @see DoubleKillException @@ -22,15 +28,14 @@ public @interface DoubleKill { /** - * static key,保证方法内唯一即可。优先级高于expression key - * - * @return key + * static key, just make sure it is unique within the method. higher priority than `expression` key */ String value() default ""; /** - * 使用方法同`@Cacheable`的`key`,默认空,使用全部参数。当有static-key时,expression无效。 - * 可以使用`@beanName`获得Bean + * Used in the same way as `key` of `@Cacheable`, empty by default, with all arguments. + * If a static-key exists, the expression is omitted. + * Beans can be obtained using `@beanName`. *

* Spring Expression Language (SpEL) expression for computing the key dynamically *

    @@ -48,25 +53,19 @@ String expression() default ""; /** - * 是否使用spring SecurityContextHolder.context.authentication.principal参与鉴别 - * - * @return 是否参与 + * Whether to use spring SecurityContextHolder.context.authentication.principal in keys */ boolean principal() default true; /** - * 是否异步执行该方法,默认同步。 - * 异步执行时,执行中都以ReturnOrException任务进度。 - * 默认使用spring @Async线程池 - * - * @return 是否异步 + * Whether to execute the method async, default sync. + * If async, the execution progresses with the ReturnOrException. + * Use spring @Async thread pool by default. */ boolean async() default false; /** - * 执行信息在 ProgressContext 中存活的秒数 - * - * @return 默认300秒 + * The seconds of the execution message remain in the ProgressContext. default 300s */ int progress() default 300; } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/ProgressContext.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/ProgressContext.java index 7aea5ebbd..7be96fa0c 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/ProgressContext.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/ProgressContext.java @@ -7,7 +7,6 @@ import pro.fessional.mirana.code.LeapCode; import pro.fessional.mirana.time.ThreadNow; - import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; @@ -23,10 +22,7 @@ public class ProgressContext { private static final LeapCode leapCode = new LeapCode(); /** - * 通过内建的key,获得Bar - * - * @param key 内建key - * @return bar + * Get the progress bar by inside key */ @Nullable public static Bar get(String key) { @@ -39,10 +35,7 @@ public static Bar get(String key) { } /** - * 通过外部的key,获得Bar - * - * @param key 外部key - * @return bar + * Get the progress bar by outside key and ttl second. */ @Nullable public static Bar get(Object key, int second) { @@ -59,28 +52,23 @@ public static Bar get(Object key, int second) { } /** - * 通过 ttl获得对应的Cache - * - * @param second ttl - * @return cache + * Get cache by ttl second */ @NotNull public static Cache get(int second) { return H24M5.getCache(second); } + /** + * Generate a progress bar by outside key and ttl second + */ @NotNull public static Bar gen(Object key, int second) { return gen(key, ThreadNow.millis(), second); } /** - * 生成个Bar - * - * @param key 外部Key,可以用来获取 Bar - * @param started 开始毫秒数 - * @param second ttl秒数 - * @return Bar + * Generate a progress bar by outside key, started time and ttl second */ @NotNull public static Bar gen(Object key, long started, int second) { @@ -94,11 +82,7 @@ public static Bar gen(Object key, long started, int second) { } /** - * 内建key - * - * @param started 开始毫秒数 - * @param second ttl秒数 - * @return 内建key + * build an inside key by started time and ttl second */ @NotNull public static String key(long started, int second) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java index 1234b849c..1524eac72 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java @@ -23,12 +23,12 @@ import pro.fessional.mirana.lock.ArrayKey; import pro.fessional.mirana.lock.JvmStaticGlobalLock; import pro.fessional.mirana.time.ThreadNow; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; import pro.fessional.wings.slardar.concur.DoubleKill; import pro.fessional.wings.slardar.concur.DoubleKillException; import pro.fessional.wings.slardar.concur.ProgressContext; import pro.fessional.wings.slardar.context.TerminalContext; import pro.fessional.wings.slardar.security.DefaultUserId; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; import java.lang.reflect.Method; import java.util.Map; @@ -38,7 +38,7 @@ import java.util.concurrent.locks.Lock; /** - * 单JVM内,key无序列化反序列化时使用 + * Use in single JVM and the key without serialization/deserialization * * @author trydofor * @since 2021-03-09 @@ -119,7 +119,7 @@ public Object doubleKill(ProceedingJoinPoint joinPoint) throws Throwable { else { final ProgressContext.Bar bar = ProgressContext.get(arrKey, ttl); if (bar == null) { - throw new DoubleKillException("", 0); // 不会到这,防御性写法 + throw new DoubleKillException("", 0); // Never here, Defensive } else { throw new DoubleKillException(bar.getKey(), bar.getStarted(), now); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/GlobalAttributeHolder.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/GlobalAttributeHolder.java index f8b2ab0dd..b8be538fe 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/GlobalAttributeHolder.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/GlobalAttributeHolder.java @@ -19,7 +19,8 @@ import java.util.stream.Collectors; /** - * 应用程序级Atl=12H无界缓存,需手动注册和移除。 + * App level, ttl=12H, unbounded cache. + * Need manually register and remove. * * @author trydofor * @since 2021-03-30 @@ -33,25 +34,25 @@ public class GlobalAttributeHolder { private static final ConcurrentHashMap, Function> LOADER = new ConcurrentHashMap<>(); /** - * 注册一个属性及其加载器 + * Registering a typed key-value and its loader * - * @param reg 类型 - * @param loader 加载器,返回null时不被缓存,每次都会调用,因此建议返回`空值` - * @param key类型 - * @param value类型 + * @param reg Type to register + * @param loader returns `null` is not cached and is called every time, so it is recommended to return `nonnull`. + * @param key type + * @param value type */ public static void regLoader(@NotNull TypedReg reg, @NotNull Function, V> loader) { LOADER.put(reg, loader); } /** - * 放入一个type的值,对loader的补充,如生效前 + * Put an attribute value to the typed key. * - * @param reg 类型 - * @param key 唯一key,如userId - * @param value 值 - * @param key类型 - * @param value类型 + * @param reg Type to register + * @param key unique key, e.g. userId + * @param value value + * @param key type + * @param value type */ public static void putAttr(@NotNull TypedReg reg, @NotNull K key, @NotNull V value) { Key k = new Key<>(reg, key); @@ -59,12 +60,12 @@ public static void putAttr(@NotNull TypedReg reg, @NotNull K key, @ } /** - * 放入一个type的值,对loader的补充,如生效前 + * Put all attribute value from map to the typed key. * - * @param reg 类型 - * @param map 唯一key,如userId - * @param key类型 - * @param value类型 + * @param reg Type to register + * @param map map of attribute + * @param key type + * @param value type */ public static void putAttr(@NotNull TypedReg reg, @NotNull Map map) { Map, V> kvs = new HashMap<>(map.size()); @@ -75,14 +76,13 @@ public static void putAttr(@NotNull TypedReg reg, @NotNull Map key类型 - * @param value类型 - * @return 返回值 + * @param reg Type to register + * @param key unique key, e.g. userId + * @param elze return `elze` if result is null + * @param key type + * @param value type */ @Contract("_,_,!null->!null") public static V tryAttr(@NotNull TypedReg reg, @NotNull K key, V elze) { @@ -91,13 +91,12 @@ public static V tryAttr(@NotNull TypedReg reg, @NotNull K key, V el } /** - * 根据一个type获取属性,尝试Loader加载,如果不存在,null时抛NPE异常 + * Try to get an attribute by typed key, load it if not found, throw NPE if the result is null. * - * @param reg 类型 - * @param key 唯一key,如userId - * @param key类型 - * @param value类型 - * @return 返回值 + * @param reg Type to register + * @param key unique key, e.g. userId + * @param key type + * @param value type */ @NotNull public static V tryAttr(@NotNull TypedReg reg, @NotNull K key) { @@ -105,14 +104,13 @@ public static V tryAttr(@NotNull TypedReg reg, @NotNull K key) { } /** - * 根据一个type获取属性,尝试Loader加载,如果不存在,选择null或异常 + * Try to get an attribute by typed key, load it if not found, throw NPE if notnull and the result is null. * - * @param reg 类型 - * @param key 唯一key,如userId - * @param notnull 是否 notnull - * @param key类型 - * @param value类型 - * @return 返回值 + * @param reg Type to register + * @param key unique key, e.g. userId + * @param notnull whether notnull + * @param key type + * @param value type */ @SuppressWarnings({"unchecked", "rawtypes"}) @Contract("_,_,true ->!null") @@ -134,13 +132,12 @@ public static V tryAttr(@NotNull TypedReg reg, @NotNull K key, bool } /** - * 获取当前缓存的type属性,不会调用Loader,如果不存在,返回null + * Get an attribute by typed key, and NOT load if not found. * - * @param reg 类型 - * @param key 唯一key,如userId - * @param key类型 - * @param value类型 - * @return 返回值 + * @param reg Type to register + * @param key unique key, e.g. userId + * @param key type + * @param value type */ @SuppressWarnings("unchecked") public static V getAttr(@NotNull TypedReg reg, @NotNull K key) { @@ -150,22 +147,22 @@ public static V getAttr(@NotNull TypedReg reg, @NotNull K key) { } /** - * 去掉一个缓存 + * remove an attribute by key * - * @param reg 类型 - * @param key 唯一key,如userId - * @param key类型 + * @param reg Type to register + * @param key unique key, e.g. userId + * @param key type */ public static void ridAttr(TypedReg reg, K key) { CACHE.remove(new Key<>(reg, key)); } /** - * 去掉一个缓存 + * remove all attribute by keys * - * @param reg 类型 - * @param key 唯一key,如userId - * @param key类型 + * @param reg Type to register + * @param key unique key, e.g. userId + * @param key type */ @SafeVarargs public static void ridAttr(TypedReg reg, K... key) { @@ -174,11 +171,11 @@ public static void ridAttr(TypedReg reg, K... key) { } /** - * 去掉一个缓存 + * remove all attribute by keys * - * @param key类型 - * @param reg 类型 - * @param key 唯一key,如userId + * @param reg Type to register + * @param key unique key, e.g. userId + * @param key type */ public static void ridAttrs(TypedReg reg, Collection key) { if (key == null || key.isEmpty()) return; @@ -190,9 +187,9 @@ public static void ridAttrs(TypedReg reg, Collection key) } /** - * 移除所有缓存 + * remove all attribute of type * - * @param reg 类型 + * @param reg Type to register */ public static void ridAttrAll(TypedReg... reg) { if (reg == null || reg.length == 0) return; @@ -200,9 +197,9 @@ public static void ridAttrAll(TypedReg... reg) { } /** - * 移除所有缓存 + * remove all attribute of type * - * @param reg 类型 + * @param reg Type to register */ @SuppressWarnings({"unchecked", "rawtypes"}) public static void ridAttrAll(Collection> reg) { @@ -225,9 +222,9 @@ public static void ridAttrAll(Collection> reg) { } /** - * 移除属性及其已存在缓存 + * remove all attribute of type and its loader * - * @param reg 注册类型 + * @param reg Type to register */ public static void ridLoader(TypedReg... reg) { if (reg == null || reg.length == 0) return; @@ -235,9 +232,9 @@ public static void ridLoader(TypedReg... reg) { } /** - * 移除属性及其已存在缓存 + * remove all attribute of type and its loader * - * @param reg 注册类型 + * @param reg Type to register */ public static void ridLoader(Collection> reg) { if (reg == null || reg.isEmpty()) return; 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..6ff9a34e3 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,29 @@ 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 +43,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..38b2d22d8 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 @@ -27,12 +27,19 @@ * Note: No WeakReference Leak due to static and Interceptor cleanup. * * @author trydofor - * @see 框架中间件集成ttl传递 + * @see Framework/Middleware integration to TTL transmittance * @since 2019-11-25 */ 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 @@ -41,26 +48,12 @@ public class TerminalContext { private static final ConcurrentHashMap ContextListeners = new ConcurrentHashMap<>(); private static volatile boolean Active; + @NotNull private static volatile TimeZone DefaultTimeZone = ThreadNow.sysTimeZone(); @NotNull private static volatile Locale DefaultLocale = Locale.getDefault(); - /** - * whether context is active and can be used correctly. - */ - public static boolean isActive() { - return Active; - } - - /** - * active context, default is false - */ - - public static void initActive(boolean b) { - Active = b; - } - /** * init default zoneId */ @@ -167,37 +160,42 @@ public static Context get() { */ @NotNull 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"); + Context ctx = null; + if (Active) { + ctx = ContextLocal.get(); + } + if (ctx == null) { + ctx = Null; + } + if (onlyLogin && ctx.isNull()) { + throw new NullPointerException("find null context, must be user or guest"); } return ctx; } /** - * login if ctx is not null, else logout + * login if ctx is not null/Null, else logout */ - public static void login(Context ctx) { - if (ctx == null || ctx == Null) { + public static void login(@Nullable Context ctx) { + if (ctx == null || ctx.isNull()) { final Context old = ContextLocal.get(); - ContextLocal.remove(); - fireContextChange(true, old); + if (old != null) { + ContextLocal.remove(); + fireContextChange(true, old); + } } else { + Active = true; ContextLocal.set(ctx); fireContextChange(false, ctx); } - Active = true; } public static void logout() { login(Null); } - private static void fireContextChange(boolean del, Context ctx) { - if (ContextListeners.isEmpty()) return; - + private static void fireContextChange(boolean del, @NotNull Context ctx) { for (Listener listener : ContextListeners.values()) { try { listener.onChange(del, ctx); @@ -213,10 +211,9 @@ public interface Listener { * set new value or delete old value, new value is NotNull, old value maybe Null * * @param del whether to delete, else set value - * @param ctx Nullable when del, else NotNull + * @param ctx new to set, or old to delete */ - @Contract("false,!null->_") - void onChange(boolean del, Context ctx); + void onChange(boolean del, @NotNull Context ctx); } public static class Context { @@ -225,16 +222,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,13 +251,6 @@ public boolean isGuest() { return userId == DefaultUserId.Guest; } - /** - *
    userId >= DefaultUserId#Guest
    - */ - public boolean asLogin() { - return userId >= DefaultUserId.Guest; - } - public long getUserId() { return userId; } @@ -283,6 +275,11 @@ public Enum getAuthType() { return authType; } + @NotNull + public String getUsername() { + return username; + } + @NotNull public Set getAuthPerm() { return authPerm; @@ -307,11 +304,35 @@ public boolean allAuthPerm(Collection auths) { return authPerm.containsAll(auths); } + /** + * key must be defined by TerminalAttribute or its subclasses + * + * @see TerminalAttribute + */ @Nullable public T getTerminal(@NotNull TypedKey key) { return key.get(terminal); } + /** + * key must be defined by TerminalAttribute or its subclasses + * + * @see TerminalAttribute + */ + @Contract("_,true->!null") + public T getTerminal(@NotNull TypedKey key, boolean notnull) { + final T t = key.get(terminal); + if (t == null && notnull) { + throw new NullPointerException("Terminal Key " + key + " returned null"); + } + return t; + } + + /** + * key must be defined by TerminalAttribute or its subclasses + * + * @see TerminalAttribute + */ @Contract("_,!null->!null") public T tryTerminal(@NotNull TypedKey key, T elze) { return key.tryOr(terminal, elze); @@ -332,13 +353,13 @@ public int hashCode() { return Objects.hash(userId, locale, timeZone, terminal); } - @Override - public String toString() { + @Override public String toString() { return "Context{" + "userId=" + userId + ", locale=" + locale + ", timeZone=" + timeZone + - ", terminal=" + terminal + + ", authType=" + authType + + ", username='" + username + '\'' + '}'; } } @@ -348,6 +369,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 +414,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 +468,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/context/TerminalContextAware.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/TerminalContextAware.java index 07947046c..3bf50dc20 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/TerminalContextAware.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/context/TerminalContextAware.java @@ -3,7 +3,7 @@ import org.jetbrains.annotations.NotNull; /** - * 用于Service层的标记型接口,用于提醒方法副作用,依赖于上下文 + * At the Service layer, indicate the class has side effects, is context-dependent. * * @author trydofor * @since 2022-11-21 @@ -15,8 +15,4 @@ public interface TerminalContextAware { default TerminalContext.Context getTerminalContext() { return TerminalContext.get(); } - - default boolean isTerminalActive() { - return TerminalContext.isActive(); - } } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/EventPublishHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/EventPublishHelper.java index 54a9f29e1..62f1d8155 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/EventPublishHelper.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/EventPublishHelper.java @@ -11,12 +11,13 @@ /** *
    - * ApplicationEventPublisher辅助类。一般用于非事务Event处理,主要功能:
    - * ①异步发布。
    - * ②IDE提示导航。
    - * ③hazelcast的topic(#HazelcastTopic)按SpringEvent模式。
    + * ApplicationEventPublisher helper. Generally used for non-transactional Event, the main function:
    + * (1) async publish event
    + * (2) IDE hit and navigation
    + * (3) wrap hazelcast topic(#HazelcastTopic) to SpringEvent
      *
    - * 注意,不要为ApplicationEventMulticaster变为异步,或处理异常,会破坏Spring默认的同步机制。
    + * Note, do NOT use ApplicationEventMulticaster in async, or handle exception,
    + * that would break Spring's default synchronization mechanism.
      * 
    * * @author trydofor @@ -30,18 +31,19 @@ public class EventPublishHelper { /** - * ApplicationEventPublisher 的封装,默认sync,建议不要修改 + * Wrapper of ApplicationEventPublisher, default sync, Recommended not to modify. */ public static final ApplicationEventPublisher SyncSpring = new SyncPub(); /** - * 使用Executor(默认SLARDAR_EVENT_EXECUTOR)包装的异步无序ApplicationEventPublisher + * Async and unordered ApplicationEventPublisher wrapped with Executor (default SLARDAR_EVENT_EXECUTOR) */ public static final ApplicationEventPublisher AsyncSpring = new AsyncPub(); /** - * 包装Hazelcast的(HazelcastTopic)topic转成SpringEvent。 - * 默认异步无序, fire and forget。若需要有序,自行设置globalOrderEnabled=true + * Wrap Hazelcast (HazelcastTopic)topic to SpringEvent. + * fire and forget, async and unordered by default. + * If the event needs to be ordered, set globalOrderEnabled=true. * * @see #hasAsyncGlobal */ diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/TweakEventListener.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/TweakEventListener.java index b0267577d..0fc8b28d9 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/TweakEventListener.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/TweakEventListener.java @@ -1,5 +1,6 @@ package pro.fessional.wings.slardar.event; +import org.jetbrains.annotations.NotNull; import org.springframework.boot.logging.LogLevel; import org.springframework.context.event.EventListener; import pro.fessional.mirana.time.ThreadNow; @@ -73,8 +74,8 @@ public void tweakClock(TweakClockEvent event) { } @Override - public void onChange(boolean del, TerminalContext.Context ctx) { - final Conf cur = debugs.getOrDefault(ctx.getUserId(), Null); // 当前用户 + public void onChange(boolean del, TerminalContext.@NotNull Context ctx) { + final Conf cur = debugs.getOrDefault(ctx.getUserId(), Null); // current user final Clock cc = cur.clock; final LogLevel cl = cur.logger; diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakClockEvent.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakClockEvent.java index 6fdd1a40f..053fbcc8f 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakClockEvent.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakClockEvent.java @@ -10,14 +10,14 @@ public class TweakClockEvent { /** - * userId为Long.MAX_VALUE时,为全部用户 + * Long.MAX_VALUE means all user */ private long userId; /** - * 判断条件,mills在未来3650天(315360000000),约1980前 - * ①与系统时钟相差的毫秒数 - * ②固定时间(1970-01-01) - * ③0表示reset + * Condition, mills in the next 3650 days (315360000000), before 1980 + * (1) milliseconds difference from the system clock + * (2) fixed time (from 1970-01-01, after 1980) + * (3) 0 means reset setting, restores the original system settings. */ private long mills; diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakLoggerEvent.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakLoggerEvent.java index 23e5181f4..5f6c0c3e9 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakLoggerEvent.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakLoggerEvent.java @@ -10,12 +10,12 @@ @Data public class TweakLoggerEvent { /** - * userId为Long.MAX_VALUE时,为全部用户 + * Long.MAX_VALUE means all user */ private long userId; /** - * OFF为reset设定,恢复系统原设定 - * FATAL等同于ERROR(slf4j无fatal级别) + * OFF means reset setting, restores the original system settings. + * FATAL is equivalent to ERROR (slf4j has no fatal level) */ private LogLevel level; } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakStackEvent.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakStackEvent.java index 6cd061000..767c423f3 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakStackEvent.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/event/tweak/TweakStackEvent.java @@ -9,11 +9,11 @@ @Data public class TweakStackEvent { /** - * userId为Long.MAX_VALUE时,为全部用户 + * Long.MAX_VALUE means all user */ private long userId; /** - * null表示reset + * `null` means reset setting, restores the original system settings. */ private Boolean stack; } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonFilters.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonFilters.java index 5dfd4a377..9f79abac8 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonFilters.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonFilters.java @@ -16,9 +16,8 @@ public class FastJsonFilters { /** - * Number类型全以String输出。 - * 注意:序列化时支持JSONField.format作为DecimalFormat。 - * 但是发序列化时,不支持format + * Number types are all output as String. + * Note: JSONField.format is supported as DecimalFormat for serialization, but not for deserialization. */ public static final ValueFilter NumberAsString = (object, name, value) -> { if (value instanceof BigDecimal) { @@ -32,9 +31,8 @@ else if (value instanceof Number) { }; /** - * Number类型全以String输出。 - * 注意:序列化时支持JSONField.format作为DecimalFormat。 - * 但是反序列化时,不支持format + * Number types are all output as String. + * Note: JSONField.format is supported as DecimalFormat for serialization, but not for deserialization. */ public static final ContextValueFilter NumberFormatString = (context, object, name, value) -> { final JSONField anno = context.getAnnotation(JSONField.class); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonHelper.java index 4d09b5c81..9e648cb23 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonHelper.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonHelper.java @@ -1,6 +1,10 @@ package pro.fessional.wings.slardar.fastjson; -import com.alibaba.fastjson2.*; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONFactory; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.TypeReference; import com.alibaba.fastjson2.filter.Filter; import com.alibaba.fastjson2.reader.ObjectReaderProvider; import org.jetbrains.annotations.Contract; @@ -16,8 +20,9 @@ import java.util.concurrent.ConcurrentHashMap; /** - * FastJson的工具类,不推荐在复杂类型中使用,兼容性问题较大,推荐是用JacksonHelper + * FastJson Util, not recommended for use in complex types. * + * @see pro.fessional.wings.slardar.jackson.JacksonHelper * @author trydofor * @since 2022-04-22 */ @@ -26,9 +31,7 @@ public class FastJsonHelper { private static final ConcurrentHashMap Inited = new ConcurrentHashMap<>(); /** - * 初始或移除全局的默认配置 - * - * @param init 初始还是移除 + * init or remove the global default setting */ public static void initGlobal(boolean init) { final ObjectReaderProvider readerProvider = JSONFactory.getDefaultObjectReaderProvider(); @@ -56,7 +59,7 @@ public static void initGlobal(boolean init) { ); /** - * 添加或移除默认的Feature + * enable/disable the Feature */ public void enableFeature(@NotNull JSONReader.Feature f, boolean enable) { synchronized (ReaderEnum) { @@ -90,7 +93,7 @@ public static JSONReader.Feature[] DefaultReader() { ); /** - * 添加或移除默认的Feature + * enable/disable the Feature */ public void enableFeature(@NotNull JSONWriter.Feature f, boolean enable) { synchronized (WriterEnum) { @@ -125,10 +128,10 @@ public static JSONWriter.Feature[] DefaultWriter() { private static Filter[] FilterCache = null; /** - * 添加或删除默认的Filter,按添加顺序排序。 + * enable/disable the Filter by name, sort by adding order * - * @param name 名字 - * @param filter null时remove,否则put + * @param name filter name + * @param filter `null` to disable, otherwise to enable */ public static void enableFilter(@NotNull String name, @Nullable Filter filter) { synchronized (FilterList) { @@ -155,7 +158,7 @@ public static Filter[] DefaultFilter() { //// /** - * 采用wings约定反序列化 + * Deserialization with the wings convention */ @Contract("!null,_->!null") public static T object(@Nullable String json, @NotNull ResolvableType targetType) { @@ -164,7 +167,7 @@ public static T object(@Nullable String json, @NotNull ResolvableType target } /** - * 采用wings约定反序列化 + * Deserialization with the wings convention */ @Contract("!null,_->!null") public static T object(@Nullable String json, @NotNull TypeDescriptor targetType) { @@ -173,7 +176,7 @@ public static T object(@Nullable String json, @NotNull TypeDescriptor target } /** - * 采用wings约定反序列化 + * Deserialization with the wings convention */ @Contract("!null,_->!null") public static T object(@Nullable String json, @NotNull TypeReference targetType) { @@ -182,7 +185,7 @@ public static T object(@Nullable String json, @NotNull TypeReference targ } /** - * 采用wings约定反序列化 + * Deserialization with the wings convention */ @Contract("!null,_->!null") public static T object(@Nullable String json, @NotNull Type targetType) { @@ -191,7 +194,7 @@ public static T object(@Nullable String json, @NotNull Type targetType) { } /** - * 采用wings约定反序列化 + * Deserialization with the wings convention */ @Contract("!null,_->!null") public static T object(@Nullable String json, @NotNull Class targetType) { @@ -200,7 +203,9 @@ public static T object(@Nullable String json, @NotNull Class targetType) } /** - * 采用wings约定序列化,尽可能以字符串输出,保证数据精度,但不影响Java类型反解析 + * Serialization using the wings convention, + * output as string wherever possible to ensure data precision, + * but not affecting Java type inverse parsing */ @Contract("!null->!null") public static String string(@Nullable Object obj) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonReaders.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonReaders.java index 45a01df69..38cd2fa1d 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonReaders.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/fastjson/FastJsonReaders.java @@ -7,8 +7,8 @@ import java.time.OffsetDateTime; /** - * 预设值的 fastjson2 Reader - * https://alibaba.github.io/fastjson2/register_custom_reader_writer_cn + * Preset fastjson2 Reader + * register_custom_reader_writer_cn * * @author trydofor * @since 2022-10-25 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientBuilder.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientBuilder.java index 07dbce526..e7510b008 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientBuilder.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpClientBuilder.java @@ -16,7 +16,7 @@ private static final class DefaultBuilderHolder { } /** - * 静态全局的默认初始化的 + * Static global default builder */ @NotNull public static Builder staticBuilder() { @@ -26,21 +26,21 @@ public static Builder staticBuilder() { protected static Builder SpringBuilder; /** - * 注入的Spring Bean + * Spring injected Bean */ @NotNull public static Builder springBuilder() { return SpringBuilder != null ? SpringBuilder : staticBuilder(); } + @SuppressWarnings("KotlinInternalInJava") public static void sslTrustAll(Builder builder) { builder.sslSocketFactory(SslTrustAll.SSL_SOCKET_FACTORY, SslTrustAll.X509_TRUST_MANAGER) .hostnameVerifier(SslTrustAll.HOSTNAME_VERIFIER); } - + + @SuppressWarnings("KotlinInternalInJava") public static void cookieHost(Builder builder) { builder.cookieJar(new OkHttpHostCookie()); } - - } 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/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpHostCookie.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpHostCookie.java index f01f5edae..a7efd4e01 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpHostCookie.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpHostCookie.java @@ -23,12 +23,12 @@ public class OkHttpHostCookie implements CookieJar { /** - * 默认的一个host下cookie数量 + * default max cookie stored at host */ - public static final int MaxCookiePerHost = 16; + public static final int MaxCookiePerHost = 64; // ups, fedex has many cookies.... - private int maxCookiePerHost = 64; + private int maxCookiePerHost = MaxCookiePerHost; public int getMaxCookiePerHost() { return maxCookiePerHost; @@ -68,7 +68,8 @@ public void save(@NotNull List cks) { try { final long now = ThreadNow.millis(); if (cookies.size() >= maxCookies) { - // 没有Expires或Max-Age的默认是9999-12-31,等于Client关闭时过期。 + // The default without Expires or Max-Age is 9999-12-31, + // which equals expiration when the Client closes. cookies.entrySet().removeIf(it -> it.getValue().expiresAt() < now); } for (Cookie ck : cks) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpRedirectNopInterceptor.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpRedirectNopInterceptor.java index 0b483dc2f..abddfaf86 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpRedirectNopInterceptor.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpRedirectNopInterceptor.java @@ -9,10 +9,10 @@ import java.io.IOException; /** - * 当followRedirects时,根据request的header设置,把Location头改为Nop-Location, - * 从而阻断此请求的Redirect + * When followingRedirects, change the Location header to Nop-Location according to the request's header setting, + * thus can stop the Redirects for this request. *

    - * 参考 RetryAndFollowUpInterceptor#followUpRequest + * see RetryAndFollowUpInterceptor#followUpRequest * * @author trydofor * @since 2022-11-01 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenClient.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenClient.java index 50733c49e..2ec505dce 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenClient.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenClient.java @@ -6,10 +6,9 @@ import org.jetbrains.annotations.NotNull; /** - * 自动完成基于Header Token验证功能的Client,比如Oauth2 - *

    - * 采用继承的方式实现,因为Interceptor及Authenticator方式不够轻便 - * okhttp-how-to-refresh-access-token-efficiently + * Clients that auto send Header Token-based authentication, such as Oauth2. + * Implements Call.Factory, because the Interceptor and Authenticator are not lightweight enough. + * See okhttp-how-to-refresh-access-token-efficiently * * @author trydofor * @since 2022-11-25 @@ -78,18 +77,18 @@ public static boolean syncInitToken(@NotNull final Tokenize tokenize, @NotNull C public interface Tokenize { /** - * 是否需要设置token + * Whether to need token */ boolean needToken(@NotNull Request request); /** - * 设置有效的token,一般在header。 - * false表示token无效,应该尝试init + * Set a valid token, usually in the header. + * `false` means the token is not valid, should try init */ boolean fillToken(Request.Builder builder); /** - * 尝试获取有效token + * try init token */ boolean initToken(@NotNull Call.Factory callFactory); } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeLogin.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeLogin.java index e4820cad6..35be1a3cc 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeLogin.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeLogin.java @@ -15,7 +15,7 @@ import pro.fessional.wings.slardar.jackson.JacksonHelper; /** - * 传统的Form登录 + * Traditional Post-Form Login * * @author trydofor * @since 2022-11-26 @@ -25,39 +25,40 @@ public class OkHttpTokenizeLogin implements OkHttpTokenClient.Tokenize { /** - * 登录用户的参数名 + * Parameter name of username. */ private String keyUsername = "username"; /** - * 登录密码的参数名 + * Parameter name of password. */ private String keyPassword = "password"; /** - * 解析token时,使用的key,默认等于headerAuth + * The key used when parsing the token, which by default is equal to headerAuth */ private String keyToken; /** - * 登录网址 + * Url to login */ private String loginUrl; /** - * 登录的用户名 + * login username */ private String username; /** - * 登录的密码 + * login password */ private String password; /** - * 设置token的header名。注意不是cookie,cookie会按cookie自动完成 + * Set the header name of token. Note, it's not a cookie, cookie will be auto-completed */ private String headerAuth; /** - * 是否为自动的Cookie模式,默认false。 - * true时,仅401进行login,而其他动作交给cookie机制处理 + * Whether to use auto cookie mode, default false. + * If true, do auto login only if only response is 401, + * other actions are handled by the cookie mechanism. */ private boolean cookieAuto = false; diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeOauth.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeOauth.java index 5bc7a724f..cdb6d4ded 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeOauth.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTokenizeOauth.java @@ -46,7 +46,7 @@ public class OkHttpTokenizeOauth implements OkHttpTokenClient.Tokenize { private String headerUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"; /** - * 仅支持 AuthorizationCode ClientCredentials + * Only support AuthorizationCode ClientCredentials */ private String keyGrantType = "grant_type"; private String valRefreshToken = RefreshToken; @@ -66,7 +66,7 @@ public class OkHttpTokenizeOauth implements OkHttpTokenClient.Tokenize { private String keyExpiresIn = "expires_in"; /** - * 过期的缓冲ms,以便提前获取,避免过期 + * expiration (ms) of buffer, should avoid expiration */ private int expireBuff = 30_000; private transient Token token; @@ -281,7 +281,7 @@ protected Token parseToken(String str) { } @Data - private static class Token { + public static class Token { private final long expired; private final String access; private final String refresh; diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTweakLogInterceptor.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTweakLogInterceptor.java index 0d8d85afd..5625f4362 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTweakLogInterceptor.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/httprest/okhttp/OkHttpTweakLogInterceptor.java @@ -24,10 +24,12 @@ import static org.springframework.boot.logging.LogLevel.WARN; /** - * 关联日志级别 + *

    + * Default mapping of Log-level and data-level
      * DEBUG- : BODY
      * INFO : BASIC
      * WARN+ : NONE
    + * 
    * * @author trydofor * @since 2022-11-01 @@ -48,10 +50,7 @@ public OkHttpTweakLogInterceptor() { } /** - * 改变映射关系,如 DEBUG - BODY - * - * @param lg 日志级别 - * @param ok 请求日志级别 + * Change the mapping to Log-level and data-level, e.g. DEBUG - BODY */ public void levelMapping(@NotNull LogLevel lg, @NotNull Level ok) { final HttpLoggingInterceptor.Logger okl; @@ -71,10 +70,7 @@ else if (lg == INFO) { } /** - * 重置映射关系,默认关系 - * DEBUG- : BODY - * INFO : BASIC - * WARN+ : NONE + * reset to the default mapping */ public void resetMapping() { HttpLoggingInterceptor none = new HttpLoggingInterceptor(LoggerWarn); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/AesString.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/AesString.java index 9a25196fc..22e253bd5 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/AesString.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/AesString.java @@ -21,28 +21,28 @@ @JsonDeserialize(using = AesStringDeserializer.class) public @interface AesString { /** - * 使用Aes的name,默认的系统的Aes + * The name of the Aes to use, default `system` Aes. */ @NotNull String value() default ""; /** - * 当value的key找不到时的策略,默认报错 + * Misfire policy, default Error */ @NotNull Misfire misfire() default Misfire.Error; enum Misfire { /** - * 报错 + * throw exception */ Error, /** - * 置空 + * set empty */ Empty, /** - * 以ValueMask'*****'代替 + * set ValueMask ('*****') */ Masks, } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/EmptyValuePropertyFilter.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/EmptyValuePropertyFilter.java index 74e12c6f1..ab472ef25 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/EmptyValuePropertyFilter.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/EmptyValuePropertyFilter.java @@ -20,8 +20,10 @@ import java.util.Map; /** - * 当LocalDate,LocalDateTime,ZonedDateTime,OffsetDateTime为empty时不输出 - * 当Array, Collection,Map为empty时,不输出 + *
    + * no output if LocalDate, LocalDateTime, ZonedDateTime or OffsetDateTime is `empty`.
    + * no output if Array, Collection or Map is `empty`.
    + * 
    * * @author trydofor * @since 2021-10-28 @@ -141,7 +143,7 @@ private boolean emptyDateTime(Date dt) { return emptyDateTime(ldt); } - // 考虑时区,相差在 offset 小时内 + // Considering timezone, the difference is considered equal within `offset` hours. private boolean emptyDateTime(LocalDateTime dt) { if (emptyDate.equals(dt.toLocalDate())) return true; if (emptyDateMin == null || emptyDateMax == null) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/FormatNumberSerializer.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/FormatNumberSerializer.java index 6059a754d..bbbe62140 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/FormatNumberSerializer.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/FormatNumberSerializer.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.math.BigDecimal; +import java.math.BigInteger; import java.text.DecimalFormat; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -18,7 +19,7 @@ import static com.fasterxml.jackson.annotation.JsonFormat.Shape.ANY; /** - * 直接JsonFormat和DecimalFormat来格式化数字 + * Directly use JsonFormat and DecimalFormat to format numbers * * @author trydofor * @since 2021-07-06 @@ -39,10 +40,6 @@ public enum Digital { private final Map poolsAuto = new ConcurrentHashMap<>(); private final Map poolsNoop = new ConcurrentHashMap<>(); - /** - * @param rawType 类型 - * @since 2.5 - */ public FormatNumberSerializer(Class rawType, DecimalFormat format, Digital digital) { super(rawType); this.format = format; @@ -84,7 +81,7 @@ public void serialize(Number value, JsonGenerator g, SerializerProvider provider if (format != null || digital != Digital.False || value instanceof Long || value instanceof Integer || value instanceof Float || value instanceof Double - || value instanceof BigDecimal) { + || value instanceof BigDecimal || value instanceof BigInteger) { final String str = format == null ? String.valueOf(value) : this.format.format(value); if (digital == Digital.True) { g.writeRawValue(str); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/I18nStringSerializer.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/I18nStringSerializer.java index b526f43e2..3e08d3716 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/I18nStringSerializer.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/I18nStringSerializer.java @@ -71,7 +71,7 @@ public JsonSerializer createContextual(SerializerProvider provider, BeanPrope if (ann == null || ann.value() == enabled) return this; I18nStringSerializer that = oppositeOne.get(); - // 不需要同步,不影响结果 + // No sync required, no impact on results if (that == null) { that = new I18nStringSerializer(messageSource, !enabled); oppositeOne.set(that); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/JacksonHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/JacksonHelper.java index a88c3fa05..e24274302 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/JacksonHelper.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/JacksonHelper.java @@ -12,10 +12,12 @@ import pro.fessional.mirana.text.WhiteUtil; /** - * [XML limitation](https://github.com/FasterXML/jackson-dataformat-xml#known-limitations) - * 常见的有: - * ①单个元素的节点,XML不能区分是单值还是数组中只有一个值,除非嵌套了wrap。 - * ②Xml无法识别数据类型,而Json有string,number,boolean,object,array + *
    + * XML limitation
    + * The common uses are:
    + * (1) single element node, XML can not distinguish between a single value or only one value in the array, unless nested wrap.
    + * (2) Xml can not recognize the data type, while Json has string, number, boolean, object, array
    + * 
    * * @author trydofor * @since 2022-11-05 @@ -29,10 +31,10 @@ public class JacksonHelper { private static XmlMapper XmlWings = XmlPlain; /** - * 初始化Wings配置的ObjectMapper + * Init the ObjectMapper for Wings configuration * - * @param jsonMapper 负责json - * @param xmlMapper 负责xml + * @param jsonMapper handle json + * @param xmlMapper handle xml */ public static void initGlobal(ObjectMapper jsonMapper, XmlMapper xmlMapper) { if (jsonMapper != null) { @@ -61,7 +63,7 @@ public static ObjectMapper wings(boolean json) { } /** - * 根据text是否有xml特征,自动选择Wings读取Xml/Json + * Auto read text to object, if text asXml, read as xml, otherwise as json */ @SneakyThrows @Contract("!null,_->!null") @@ -75,7 +77,7 @@ public static T object(@Nullable String text, @NotNull Class targetType) } /** - * 根据text是否有xml特征,自动选择Wings读取Xml/Json + * Auto read text to object, if text asXml, read as xml, otherwise as json */ @SneakyThrows @Contract("!null,_->!null") @@ -89,7 +91,7 @@ public static T object(@Nullable String text, @NotNull JavaType targetType) } /** - * 根据text是否有xml特征,自动选择Wings读取Xml/Json + * Auto read text to object, if text asXml, read as xml, otherwise as json */ @SneakyThrows @Contract("!null,_->!null") @@ -103,7 +105,7 @@ public static T object(@Nullable String text, @NotNull TypeReference targ } /** - * 根据text是否有xml特征,自动选择Wings读取Xml/Json + * Auto read text to object, if text asXml, read as xml, otherwise as json */ @SneakyThrows @Contract("!null->!null") @@ -117,7 +119,7 @@ public static JsonNode object(@Nullable String text) { } /** - * str是否具有xml特征,即,首尾的字符是否为尖角括号 + * whether `str` has xml characteristics, i.e. the first and last characters are angle brackets or not */ public static boolean asXml(@Nullable String str) { if (str == null) return false; @@ -152,7 +154,8 @@ public static boolean asXml(@Nullable String str) { } /** - * 采用wings约定序列化(json),尽可能以字符串输出 + * Serialization (json) using the wings convention, + * output as string wherever possible to ensure data precision */ @SneakyThrows @Contract("!null->!null") @@ -161,7 +164,8 @@ public static String string(@Nullable Object obj) { } /** - * 采用wings约定序列化(json或xml),尽可能以字符串输出 + * Serialization (json or xml) using the wings convention, + * output as string wherever possible to ensure data precision */ @SneakyThrows @Contract("!null,_->!null") diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapGenerator.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapGenerator.java index 907918fed..9012ec43a 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapGenerator.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapGenerator.java @@ -21,7 +21,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; /** - * 只把顶层元素变成key-value的map,用来做参数签名 + * Turn only top-level elements into key-value maps for parameter signatures * * @author trydofor * @since 2019-12-30 @@ -37,37 +37,28 @@ public class StringMapGenerator extends JsonGenerator { private String currentKey; /** - * 按key的ascii(unicode)的值排序 - * - * @return key值排序 + * Use TreeMap to sort key by ascii (unicode) order. */ public static StringMapGenerator treeMap() { return new StringMapGenerator(new TreeMap<>()); } /** - * 按key的顺序排序 - * - * @return key顺序排序 + * Use LinkedHashMap to sort key by insertion order. */ public static StringMapGenerator linkMap() { return new StringMapGenerator(new LinkedHashMap<>()); } /** - * 无序 - * - * @return 无序 + * Use HashMap without order */ public static StringMapGenerator hashMap() { return new StringMapGenerator(new HashMap<>()); } /** - * 用户字定义 - * - * @param map 自定义map - * @return 字定义 + * Use specified map */ public static StringMapGenerator userMap(Map map) { return new StringMapGenerator(map); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapHelper.java index 670648f30..9b706797f 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapHelper.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/jackson/StringMapHelper.java @@ -10,7 +10,7 @@ import java.util.Map; /** - * 辅助生成签名 + * Signature generation help * * @author trydofor * @since 2019-12-31 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnFilter.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnFilter.java index aba0736d5..3469532ff 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnFilter.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnFilter.java @@ -10,9 +10,7 @@ public interface WarnFilter { /** - * 修改warns - * - * @param warns 内容 + * filter warns */ void filter(Map> warns); } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnMetric.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnMetric.java index 71994e9c3..051453243 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnMetric.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnMetric.java @@ -12,9 +12,7 @@ public interface WarnMetric { /** - * 获得配置所在的key - * - * @return key + * Get the Key of Config */ @NotNull String getKey(); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnReport.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnReport.java index 044e816b1..032003174 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnReport.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/WarnReport.java @@ -16,12 +16,12 @@ enum Sts { } /** - * 发送报告 + * Send a report * - * @param appName 当前app标识 - * @param jvmName 当前jvm标识 - * @param warn 警告内容 - * @return 报告结果 + * @param appName current app name + * @param jvmName current jvm name + * @param warn wain details + * @return the result of report */ Sts report(String appName, String jvmName, Map> warn); } diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/metric/LogMetric.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/metric/LogMetric.java index 3a9d107ef..c055d4757 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/metric/LogMetric.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/metric/LogMetric.java @@ -83,7 +83,7 @@ public String getKey() { Warn warn = new Warn(); warn.setKey(keyKeyword); warn.setType(Type.File); - // 转换keyword,避免记入日志,触发监控 + // Convert keyword to avoid logging and triggering monitoring warn.setRule(maskKeyword()); warn.setWarn(stat.getPathOut()); result.add(warn); @@ -136,7 +136,7 @@ private String maskKeyword() { sb.append(","); sb.append(rule.maskKey(k)); } - return sb.length() == 0 ? "" : sb.substring(1); + return sb.isEmpty() ? "" : sb.substring(1); } private void check(List result, String key, DataSize ruleValue, long warnValue, boolean less) { @@ -231,7 +231,7 @@ public static class Rule { public static final String Key$clean = Key + ".clean"; /** - * 脱外层单引号,及是否处理后续空白 + * Remove the outer single quotes, and whether to handle subsequent whitespace */ public String trimKey(String kw, boolean white) { final int kl = kw.length(); @@ -239,7 +239,7 @@ public String trimKey(String kw, boolean white) { final int il = kl - 1; if (kw.charAt(0) == '\'' && kw.charAt(il) == '\'') { kw = kw.substring(1, il); - // 不用记录高级别日志,否则每次都会警报 + // No need a high-level log or alert every time! log.trace("trim quoted doubl-quote={} to key={}", kw, kw); } } @@ -254,7 +254,7 @@ public String trimKey(String kw, boolean white) { } /** - * 避免日志中记录key,引发扫描报警 + * Do NOT log the key. This will trigger scan alarms. */ public String maskKey(String kw) { final String s = trimKey(kw, true); @@ -262,9 +262,7 @@ public String maskKey(String kw) { } /** - * 会自动trim掉一组成对的收尾双引号,按charset构造bytes - * - * @return 按字符集构造的byte + * Auto remove a pair of quotes, construct bytes by charset */ @SneakyThrows public List getRuntimeKeys() { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/report/DingTalkReport.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/report/DingTalkReport.java index 606aae275..e12b0b086 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/report/DingTalkReport.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/monitor/report/DingTalkReport.java @@ -14,7 +14,7 @@ import java.util.function.Consumer; /** - * 钉钉机器人 https://developers.dingtalk.com/document/app/custom-robot-access + * Dingtalk robot * * @author trydofor * @since 2021-07-14 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..26ccf76dc 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 @@ -107,7 +109,7 @@ public boolean send(DingTalkConf config, String subject, String content) { -H 'Content-Type: application/json' \ -d ' {"msgtype":"markdown","markdown":{ - "title":"杭州天气", + "title":"HangZhou", "text": " \n" },"at":{"isAtAll":true}}' */ @@ -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 { @@ -171,7 +173,7 @@ public void afterPropertiesSet() { /** * { * "text": { - * "content":"我就是我, @XXX 是不一样的烟火" + * "content":"I am who I am, @XXX a different kind of firework." * }, * "msgtype":"text", * "at": { @@ -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)) ); } @@ -198,8 +197,8 @@ public String buildText(DingTalkConf conf, String subject, String content) { * { * "msgtype": "markdown", * "markdown": { - * "title":"杭州天气", - * "text": "#### 杭州天气 @150XXXXXXXX \n > 9度,西北风1级,空气良89,相对温度73%\n > ![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png)\n > ###### 10点20分发布 [天气](https://www.dingtalk.com) \n" + * "title":"Hangzhou Weather", + * "text": "#### Hangzhou Weather @150XXXXXXXX \n > northwest wind force 1\n > ![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png)\n > ###### 10:20 [weather](https://www.dingtalk.com) \n" * } * } * @@ -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.isEmpty() ? main : buff.toString(); } private void buildNotice(DingTalkConf conf, JsonTemplate.Obj obj) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/security/DefaultUserId.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/security/DefaultUserId.java index 049ab71a9..bd97085bb 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/security/DefaultUserId.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/security/DefaultUserId.java @@ -1,10 +1,18 @@ package pro.fessional.wings.slardar.security; /** - * wings约定的UserId区间,正数为登录用户,否则为不可登录用户。 - * ①正数,大正数-业务用户;小正数-内置用户;0-99为wings保留用户 - * ②负数,特殊用户。 - * ③零无任何权限,root有最高权限,建议不可登录。 + *
    + * The UserId range in wings convention,
    + * a positive number is a login user,
    + * otherwise it is a non-login user.
    + *
    + * (1) Big positive numbers are business users;
    + *     small positive numbers are built-in users;
    + *     0-99 are reserved Wings users.
    + * (2) Negative numbers are special users.
    + * (3) Zero has no privileges, root has the highest privileges,
    + *     it is recommended not to login.
    + * 
    * * @author trydofor * @since 2021-02-20 @@ -12,38 +20,38 @@ public interface DefaultUserId { /** - * Null用户 + * Null User */ long Null = Long.MIN_VALUE; /** - * 非登录用户的统称。 + * Non-login User */ long Guest = -1; /** - * 无权用户,-2:Unprivileged User + * No privileges User */ long Nobody = 0; /** - * 超级用户,0:System Administrator + * Super User */ long Root = 1; /** - * 守护进程,1:System Services + * Daemon User */ long Daemon = 2; /** - * 是否等于Guest (eq -1) + * Whether is Guest (eq -1) */ static boolean isGuest(long uid) { return uid == Guest; } /** - * 是否视为Guest (null | le -1) + * Whether as Guest (null or le -1) */ static boolean asGuest(Long uid) { return uid == null || uid <= Guest; diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/JsonConversion.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/JsonConversion.java index 13f5017f0..8a2d10f02 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/JsonConversion.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/JsonConversion.java @@ -7,8 +7,11 @@ import pro.fessional.wings.slardar.fastjson.FastJsonHelper; /** - * 底层实现为FastJson,仅限于信任服务的简单类型,在非信任领域可能存在安全问题。 - * 对于有泛型的复杂类型,fastjson正确解析,此外有点注意js中number的精度问题。 + * The underlying implementation is FastJson, + * which is limited to simple types of trusted services, + * and may have security issues in non-trusted domains. + * For complex types with generalizations, fastjson parses them correctly, + * in addition to paying more attention to the precision of number in js. * * @author trydofor * @since 2022-03-09 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoConversion.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoConversion.java index 40ca8581e..979956b11 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoConversion.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoConversion.java @@ -7,7 +7,7 @@ import pro.fessional.mirana.bits.Base64; /** - * 数据精度高于Json,但数据结构可能不准,比如TreeMap|HashMap等 + * Data accuracy is higher than Json, but the data structure may not, such as TreeMap or HashMap. * * @author trydofor * @since 2022-03-09 diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoSimple.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoSimple.java index 3cf02198e..d9829afaa 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoSimple.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/serialize/KryoSimple.java @@ -59,9 +59,9 @@ public Output initValue() { } /** - * 增加 用户Serializer,大部分Kryo自己实现了 + * Add User Serializer, mostly implemented by Kryo itself * - * @param kryo 需要注册的类型 + * @param kryo type to register * @see DefaultArraySerializers */ public static void register(Kryo kryo) { diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java index 62a379c97..b7b94858d 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java @@ -76,7 +76,7 @@ public AsyncTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) { return new ConcurrentTaskExecutor(ttlExecutor); } - //不可以使用@Primary,否则@Async线程池被覆盖 + // Do NOT use @Primary to avoid overwriting the @Async thread pool. @Bean(name = DEFAULT_TASK_SCHEDULER_BEAN_NAME) public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) { final TtlThreadPoolTaskScheduler scheduler = new TtlThreadPoolTaskScheduler(); 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..e2c9d0fb4 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 + // Dynamic register 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/bean/SlardarDateTimeConfiguration.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarDateTimeConfiguration.java index 8d553f869..a430ee489 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarDateTimeConfiguration.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarDateTimeConfiguration.java @@ -9,7 +9,6 @@ import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; import pro.fessional.wings.slardar.autozone.spring.LocalDate2StringConverter; import pro.fessional.wings.slardar.autozone.spring.LocalDateTime2StringConverter; import pro.fessional.wings.slardar.autozone.spring.LocalTime2StringConverter; @@ -22,12 +21,13 @@ import pro.fessional.wings.slardar.autozone.spring.ZonedDateTime2StringConverter; import pro.fessional.wings.slardar.spring.prop.SlardarDatetimeProp; import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; import java.time.format.DateTimeFormatter; import java.util.stream.Collectors; /** - * 通过 ApplicationConversionService#addBeans 自动注入 + * Auto inject by ApplicationConversionService#addBeans * * @author trydofor * @see ApplicationConversionService#addBeans diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java index 31082dd2e..47608cd2c 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarMonitorConfiguration.java @@ -21,7 +21,6 @@ import org.springframework.core.env.Environment; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.util.unit.DataSize; -import pro.fessional.wings.spring.consts.OrderedSlardarConst; import pro.fessional.wings.slardar.monitor.MonitorTask; import pro.fessional.wings.slardar.monitor.WarnMetric; import pro.fessional.wings.slardar.monitor.metric.JvmMetric; @@ -30,6 +29,7 @@ import pro.fessional.wings.slardar.notice.DingTalkNotice; import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp; import pro.fessional.wings.slardar.spring.prop.SlardarMonitorProp; +import pro.fessional.wings.spring.consts.OrderedSlardarConst; import java.io.File; import java.util.Map; @@ -75,7 +75,7 @@ public MonitorTask monitorTask() { return bean; } - // 动态注册Bean,LogMetric + // Dynamic register Bean LogMetric @Configuration(proxyBeanMethods = false) @ConditionalOnExpression("${" + SlardarEnabledProp.Key$monitor + ":false} && ${" + SlardarEnabledProp.Key$monitorLog + ":false}") @ComponentScan(basePackageClasses = MonitorTask.class) 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/main/java/pro/fessional/wings/slardar/spring/prop/SlardarNumberProp.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarNumberProp.java index 75cedc8a0..d7c52d7b5 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarNumberProp.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarNumberProp.java @@ -32,12 +32,12 @@ public class SlardarNumberProp { private Nf integer = new Nf(); /** - * Float,Double 类型 + * Float, Double */ private Nf floats = new Nf(); /** - * BigDecimal 类型 + * BigDecimal, BigInteger */ private Nf decimal = new Nf(); diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/validate/Validator.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/validate/Validator.java index 6384df7ba..a8d72cc12 100644 --- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/validate/Validator.java +++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/validate/Validator.java @@ -4,7 +4,7 @@ import org.hibernate.validator.internal.util.DomainNameUtil; /** - * 是对javax.validation工具的静态化提取 + * static use of the `javax.validation` * * @author trydofor * @since 2021-06-22 diff --git a/wings/slardar/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/wings/slardar/src/main/resources/META-INF/additional-spring-configuration-metadata.json index d7d353a01..957344b81 100644 --- a/wings/slardar/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/wings/slardar/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -30,7 +30,7 @@ }, { "value": "session", - "description": "session,10 minutes." + "description": "session, 10 minutes." } ] }, diff --git a/wings/slardar/src/main/resources/wings-conf/spring-jackson-79.properties b/wings/slardar/src/main/resources/wings-conf/spring-jackson-79.properties index 670e9e2e1..4480acb1a 100644 --- a/wings/slardar/src/main/resources/wings-conf/spring-jackson-79.properties +++ b/wings/slardar/src/main/resources/wings-conf/spring-jackson-79.properties @@ -20,7 +20,7 @@ spring.jackson.generator.WRITE_NUMBERS_AS_STRINGS=true ## com.fasterxml.jackson.databind.MapperFeature ## ignore transient properties. jackson default false spring.jackson.mapper.PROPAGATE_TRANSIENT_MARKER=true -## no view annotations are included in JSON serialization. jackson default true,springboot default false +## no view annotations are included in JSON serialization. jackson default true, springboot default false spring.jackson.mapper.DEFAULT_VIEW_INCLUSION=true ## case-insensitive. jackson default false #spring.jackson.mapper.ACCEPT_CASE_INSENSITIVE_PROPERTIES=true diff --git a/wings/slardar/src/main/resources/wings-conf/wings-monitor-79.properties b/wings/slardar/src/main/resources/wings-conf/wings-monitor-79.properties index 7e785e6c3..bbf9e86fb 100644 --- a/wings/slardar/src/main/resources/wings-conf/wings-monitor-79.properties +++ b/wings/slardar/src/main/resources/wings-conf/wings-monitor-79.properties @@ -77,7 +77,7 @@ wings.slardar.monitor.view.ignore[CP-Subsystem]=CP Subsystem is not enabled wings.slardar.monitor.view.ignore[Swagger-DataTypeClass]=dataTypeClass: class java.lang.Void wings.slardar.monitor.view.ignore[AutoLog-Switch]=Auto Switch the following Appender Level to wings.slardar.monitor.view.ignore[No-MessageSource]=not found for MessageSource -## PersistenceProviderResolverHolder,Using jooq can logging.level.javax.persistence.spi=ERROR +## PersistenceProviderResolverHolder, Using jooq can logging.level.javax.persistence.spi=ERROR wings.slardar.monitor.view.ignore[Jpa-Persistence]=javax.persistence.spi::No valid providers found ## UT005071: Undertow request failed HttpServerExchange{ CONNECT ; CONNECT wings.slardar.monitor.view.ignore[UT005071-CONNECT]=UT005071: Undertow request failed HttpServerExchange{ CONNECT diff --git a/wings/slardar/src/main/resources/wings-conf/wings-number-79.properties b/wings/slardar/src/main/resources/wings-conf/wings-number-79.properties index a0072f0bb..265a33cd3 100644 --- a/wings/slardar/src/main/resources/wings-conf/wings-number-79.properties +++ b/wings/slardar/src/main/resources/wings-conf/wings-number-79.properties @@ -30,7 +30,7 @@ wings.slardar.number.floats.separator=, ## force string, avoid loss of precision. see wings.slardar.number.integer.digital wings.slardar.number.floats.digital=false -## format of BigDecimal. `empty` means disable. +## format of BigDecimal, BigInteger. `empty` means disable. #wings.slardar.number.decimal.format=#.00 ## RoundingMode.FLOOR wings.slardar.number.decimal.round=FLOOR diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/ScheduleService.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/ScheduleService.java index 025ac6516..966970823 100644 --- a/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/ScheduleService.java +++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/ScheduleService.java @@ -15,7 +15,7 @@ public class ScheduleService { @Scheduled(fixedRate = 1000) public void scheduleRate() { - final TerminalContext.Context ctx = TerminalContext.get(); + final TerminalContext.Context ctx = TerminalContext.get(false); log.info("ScheduleService userId={}", ctx.getUserId()); } } diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java index 232e784a4..6772deb61 100644 --- a/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java +++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java @@ -45,8 +45,8 @@ void testTask() throws Exception { final AtomicInteger cnt1 = new AtomicInteger(0); final AtomicInteger eqs1 = new AtomicInteger(0); Thread.sleep(500); - // 若非TtlThreadPoolTaskScheduler设置,却用了ttlExecutor, - // 则仅一个线程能会TTL成功,其会失败, + // If a non-TtlThreadPoolTaskScheduler is set up, but a ttlExecutor is used. + // then only one thread will succeed in TTL, others will fail final ScheduledFuture task1 = threadPoolTaskScheduler.scheduleWithFixedDelay(() -> delayUid("TaskSchedulerTest Default", userId, cnt1, eqs1), Duration.ofMillis(1_000)); Thread.sleep(5_000); task1.cancel(false); 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..12d281dd3 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 @@ -36,16 +36,16 @@ class AutoDtoHelperTest { public static final ZoneId ZONE_JP = ZoneId.of("Asia/Tokyo"); /** - * 自动转换语言和时间,JP到CN + * Auto convert lange and time, JP to CN */ @Test void autoRequest() { TerminalContext.Builder builder = new TerminalContext.Builder() - .locale(Locale.CHINA) + .locale(Locale.ENGLISH) .timeZone(ZONE_JP) .terminal(TerminalAddr, "localhost") .terminal(TerminalAgent, "SpringTest") - .user(-1); + .user(1); TerminalContext.login(builder.build()); I18nItem it = initI18nItem(ZONE_JP); @@ -69,10 +69,10 @@ void autoRequest() { final List in1 = it.i18nStringList; Assertions.assertSame(in, in1); - Assertions.assertEquals("{0} 不能为空", it1.i18nCode); - Assertions.assertEquals("{0} 不能为空", it1.i18nCodeList.get(0)); - Assertions.assertEquals("User 不能为空", it1.i18nString.toString()); - Assertions.assertEquals("Name 不能为空", it1.i18nStringList.get(0).toString()); + Assertions.assertEquals("{0} can not be empty", it1.i18nCode); + Assertions.assertEquals("{0} can not be empty", it1.i18nCodeList.get(0)); + Assertions.assertEquals("User can not be empty", it1.i18nString.toString()); + Assertions.assertEquals("Name can not be empty", it1.i18nStringList.get(0).toString()); final LocalDateTime sldt = LocalDateTime.of(2022, 10, 10, 11, 34, 56); Assertions.assertEquals(sldt, it.getLocalDateTime()); @@ -83,7 +83,7 @@ void autoRequest() { } /** - * 自动转换语言和时间,CN到JP + * Auto convert lange and time, CN to JP */ @Test void autoResponse() { @@ -92,7 +92,7 @@ void autoResponse() { .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/slardar/src/test/java/pro/fessional/wings/slardar/fastjson/FastJsonHelperTest.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/fastjson/FastJsonHelperTest.java index 49db31611..0aeb1be37 100644 --- a/wings/slardar/src/test/java/pro/fessional/wings/slardar/fastjson/FastJsonHelperTest.java +++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/fastjson/FastJsonHelperTest.java @@ -91,9 +91,9 @@ public void testSingle() { Assertions.assertEquals("\"123\"", JSON.toJSONString(Integer.valueOf("123"), Feature.WriteNonStringValueAsString)); Assertions.assertEquals("\"3.14\"", JSON.toJSONString(3.14, Feature.WriteNonStringValueAsString)); Assertions.assertEquals("\"3.14\"", JSON.toJSONString(Double.valueOf("3.14"), Feature.WriteNonStringValueAsString)); - // BUG 期望是同Integer一致,得到`"3"`,而不是`3` Fixed 2.0.34 + // BUG Fixed 2.0.34 Assertions.assertEquals("\"3\"", JSON.toJSONString(new BigDecimal("3"), Feature.WriteNonStringValueAsString)); - // BUG 期望是同Double一致,得到`"3.14"`,而不是`3.14` + // BUG Fixed 2.0.34 Assertions.assertEquals("\"3.14\"", JSON.toJSONString(new BigDecimal("3.14"), Feature.WriteNonStringValueAsString)); } diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/json/JsonConversionTest.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/json/JsonConversionTest.java index e9d462114..eab7ee567 100644 --- a/wings/slardar/src/test/java/pro/fessional/wings/slardar/json/JsonConversionTest.java +++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/json/JsonConversionTest.java @@ -61,8 +61,8 @@ public static class Dto { void convert() { Dto dto = new Dto(); Map map = new HashMap<>(); - map.put("one", "一"); - map.put("tow", "二"); + map.put("one", "1"); + map.put("two", "2"); List lst = new ArrayList<>(); lst.add("Mon"); lst.add("Tur"); 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..701cd8437 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 @@ -7,7 +7,7 @@ import java.util.Map; /** - * 支持ConversionService和Json解析配置 + * Support for ConversionService and Json parsing configuration * * @author trydofor * @since 2022-03-09 @@ -118,21 +118,21 @@ default T getObject(Enum key, TypeDescriptor type) { } /** - * 按类型读取配置项 + * Read the value of config by type * * @param key key - * @param type 类型描述 - * @param 类型 - * @return 配置 + * @param type type descriptor + * @param Type of value + * @return value */ T getObject(String key, TypeDescriptor type); /** - * 写入配置项 + * set value of config * * @param key key - * @param value 配置 + * @param value config */ void setObject(String key, Object value); @@ -145,13 +145,13 @@ default void setObject(Enum key, Object value) { } /** - * 新建一个配置项 + * create new config * - * @param key key - * @param value 初始值 - * @param comment 注释 - * @param handler 处理器 - * @return 是否被处理 + * @param key config key + * @param value config value + * @param comment config comment + * @param handler type handler name + * @return whether handled */ boolean newObject(String key, Object value, String comment, String handler); @@ -164,20 +164,20 @@ default boolean newObject(Enum key, Object value, String comment, String hand } /** - * 新建一个配置项,自动选择handler,一定成功,否异常。 + * create new config with auto selected handler, success or throw an error. * - * @param key key - * @param value 初始值 - * @param comment 注释 + * @param key config key + * @param value config value + * @param comment config 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/main/java/pro/fessional/wings/warlock/service/grant/WarlockGrantService.java b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/grant/WarlockGrantService.java index d983fd7b9..8c23713d1 100644 --- a/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/grant/WarlockGrantService.java +++ b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/service/grant/WarlockGrantService.java @@ -8,11 +8,13 @@ import java.util.Map; /** - * 授权与撤销,涉及到权限提升,必须结合GrantChecker使用 - * - role拥有的perm - * - role拥有的role - * - user拥有的perm - * - user拥有的role + *
    + * Authorization and revocation, which involves elevation of privilege, must be used with GrantChecker
    + * - Role-owned perm
    + * - Role-owned role
    + * - User-owned perm
    + * - user-owned role
    + * 
    * * @author trydofor * @since 2021-03-05 @@ -25,10 +27,10 @@ enum Jane { } /** - * 把grant赋予refer,使refer拥有grant权限 + * Grant role/perm to role to give the `grant` privilege. * * @param roleId roleId - * @param type 类型 + * @param type grant type * @param grant RoleId or PermId */ void grantRole(long roleId, @NotNull GrantType type, @NotNull Collection grant); @@ -42,10 +44,10 @@ default void grantRole(long refer, @NotNull GrantType type, @NotNull Long... gra /** - * 撤销refer的grant,使refer不在拥有grant权限 + * Revoke role/perm to role to remove the `grant` privilege. * * @param roleId roleId - * @param type 类型 + * @param type grant type * @param grant RoleId or PermId */ void purgeRole(long roleId, @NotNull GrantType type, @NotNull Collection grant); @@ -58,10 +60,10 @@ default void purgeRole(long roleId, @NotNull GrantType type, @NotNull Long... gr } /** - * 把grant赋予refer,使refer拥有grant权限 + * Grant role/perm to user to give the `grant` privilege. * * @param userId userId - * @param type 类型 + * @param type grant type * @param grant RoleId or PermId */ void grantUser(long userId, @NotNull GrantType type, @NotNull Collection grant); @@ -74,10 +76,10 @@ default void grantUser(long userId, @NotNull GrantType type, @NotNull Long... gr } /** - * 撤销refer的grant,使refer不在拥有grant权限 + * Revoke role/perm to user to remove the `grant` privilege. * * @param userId userId - * @param type 类型 + * @param type grant type * @param grant RoleId or PermId */ void purgeUser(long userId, @NotNull GrantType type, @NotNull Collection grant); @@ -90,9 +92,9 @@ default void purgeUser(long userId, @NotNull GrantType type, @NotNull Long... gr } /** - * 获取用户的授权,返回key=授权,value=userId + * Get user granted privileges. return the map of RoleId/PermId to UserId * - * @param type 类型 + * @param type grant type * @param userId userId * @return RoleId/PermId - UserId */ @@ -106,9 +108,9 @@ default Map entryUser(@NotNull GrantType type, @NotNull Long... user } /** - * 获取角色的授权,返回key=授权,value=roleId + * Get role granted privileges. return the map of RoleId/PermId to UserId * - * @param type 类型 + * @param type grant type * @param roleId roleId * @return RoleId/PermId - roleId */ diff --git a/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockAwesomeConfiguration.java b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockAwesomeConfiguration.java index 6749f0939..6aceca7a4 100644 --- a/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockAwesomeConfiguration.java +++ b/wings/warlock-awesome/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockAwesomeConfiguration.java @@ -51,7 +51,10 @@ public RuntimeConfService runtimeConfService(ObjectProvider c } - @Bean // 数据库值覆盖工程配置 + /** + * Database values override project config + */ + @Bean public CommandLineRunnerOrdered runnerRegisterRuntimeMode(ObjectProvider provider) { log.info("Warlock spring-runs runnerRegisterRuntimeMode"); return new CommandLineRunnerOrdered(OrderedWarlockConst.RunnerRegisterRuntimeMode, ignored -> { 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..d0059a828 --- /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; + +/** + * Need init database via 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..b8b8cc5bf 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; @@ -22,12 +23,14 @@ import java.util.Set; /** - * 需要先初始化数据库 Warlock1SchemaCreator#init0Schema + * Need init database via Warlock1SchemaCreator#init0Schema * * @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}) @@ -37,7 +40,7 @@ class RuntimeConfServiceTest { void testSimple() { assertSimple(BigDecimal.class, new BigDecimal("10.00")); assertSimple(String.class, "string"); - // 注意,丢失精度 SSS + // Note, lost precision, SSS final LocalDateTime ldt = LocalDateTime.of(2022, 2, 1, 12, 34, 56); assertSimple(LocalDateTime.class, ldt); assertSimple(ZonedDateTime.class, ZonedDateTime.of(ldt, ZoneId.of("Asia/Shanghai"))); @@ -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); @@ -87,7 +90,8 @@ public static class Dto { @Test void testJson() { Dto dto = new Dto(); - runtimeConfService.newObject(Dto.class, dto, "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + runtimeConfService.newObject(Dto.class, dto, "Need init database via 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/controller/admin/AdminAuthnController.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminAuthnController.java index 07210d566..d8d685400 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,26 +30,42 @@ 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 = """ # Usage - 根据userId设时钟志级别,stack==null时,为关闭线程设定,复原系统原设置。 + set/unset user danger status and failed count ## Params * @param userId - the user * @param danger - set danger or unset + * @param authType - auth type to reset ## Returns - * @return {401} 权限不够时 - * @return {200} 直接访问或redirect时 + * @return {401} if not authed + * @return {200} ok or redirect """) @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..5a9ad6957 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); @@ -147,8 +168,8 @@ public void onFailure(@NotNull Enum authType, String username) { } journalService.commit(WarlockAuthnService.Jane.Failure, uid, "failed login auth-id=" + aid, commit -> { - // 锁账号 - if (cnt >= max) { + // lock user + 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/perm/impl/WarlockPermServiceImpl.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/perm/impl/WarlockPermServiceImpl.java index 576fe5fb9..38ff740ca 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,14 +1,18 @@ package pro.fessional.wings.warlock.service.perm.impl; +import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jooq.Record3; 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 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 +25,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 +39,126 @@ * @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; + } + + /** + * Async evict all cache, event can be null + */ + @EventListener + @CacheEvict(allEntries = true, condition = "#result") + public boolean evictPermAllCache(@Nullable 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..f548d816c 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,137 @@ * @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; + } + + /** + * Async evict all cache, event can be 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/service/user/impl/WarlockUserAuthnServiceImpl.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserAuthnServiceImpl.java index f6aaa76b6..b273501c0 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) { @@ -103,7 +110,7 @@ public long create(long userId, @NotNull Authn authn) { } catch (Exception e) { log.error("failed to insert authn " + authn, e); - // 可能唯一约束或字段超长 + // Possibly unique key or value is oversize throw new CodeException(e, CommonErrorEnum.DataExisted); } return id; @@ -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/service/user/impl/WarlockUserBasisServiceImpl.java b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserBasisServiceImpl.java index 8ccc65e26..be99f5ff6 100644 --- a/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserBasisServiceImpl.java +++ b/wings/warlock-bond/src/main/java/pro/fessional/wings/warlock/service/user/impl/WarlockUserBasisServiceImpl.java @@ -119,7 +119,7 @@ public void modify(long userId, @NotNull Basis user) { setter.put(tu.Zoneid, user.getZoneId()); setter.put(tu.Remark, user.getRemark()); setter.put(tu.Status, user.getStatus()); - // 一定会更新,除非不存在 + // Must update, unless not found setter.put(tu.CommitId, commit.getCommitId()); setter.put(tu.ModifyDt, commit.getCommitDt()); return winUserBasisDao.update(tu, setter, tu.Id.eq(userId), true); 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..19b5ae412 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,7 +10,12 @@ 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.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; @@ -22,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; @@ -37,8 +44,23 @@ 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 + @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() { @@ -49,23 +71,38 @@ public DefaultDaoAuthnCombo defaultDaoAuthnCombo() { @Bean @ConditionalOnMissingBean(WarlockGrantService.class) public WarlockGrantService warlockGrantService() { - // 存在子类,则不需要此bean,如JustAuthUserAuthnAutoReg + // not needed if subclass bean exists e.g. JustAuthUserAuthnAutoReg log.info("WarlockBond spring-bean 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/security/NoncePermLoginTest.java b/wings/warlock-bond/src/test/java/pro/fessional/wings/warlock/security/NoncePermLoginTest.java index 1896d1dae..f9a365d65 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; /** @@ -44,12 +46,12 @@ public void testRootLogin() { final Response r1 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/console-nonce.json?username=root"), false); String nonce = OkHttpClientHelper.extractString(r1, false); log.warn("get nonce for root, nonce=" + nonce); - Assertions.assertEquals(200, r1.code(), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertEquals(200, r1.code(), "Need init database via Warlock1SchemaCreator#init0Schema"); final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=root&password=" + nonce), false); String login = OkHttpClientHelper.extractString(r2, false); log.warn("get login res = " + login); - Assertions.assertTrue(login.contains("true"), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(login.contains("true"), "Need init database via Warlock1SchemaCreator#init0Schema"); final Response r3 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/list-auth.json"), false); String auths = OkHttpClientHelper.extractString(r3, false); @@ -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("false"), "Need init database via Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(login.contains("账号已锁定"), "check i18n config"); } OkHttpClientHelper.postJson(okHttpClient, host + DangerUrl, "{\"userId\":1,\"danger\":false}"); @@ -72,30 +76,36 @@ public void testDanger() { final Response r1 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/console-nonce.json?username=root"), false); String nonce = OkHttpClientHelper.extractString(r1, false); log.warn("testDanger-b get nonce for root, nonce=" + nonce); - Assertions.assertEquals(200, r1.code(), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertEquals(200, r1.code(), "Need init database via Warlock1SchemaCreator#init0Schema"); final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=root&password=" + nonce), false); String login = OkHttpClientHelper.extractString(r2, false); log.warn("testDanger-b get login res = " + login); - Assertions.assertTrue(login.contains("true"), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(login.contains("true"), "Need init database via Warlock1SchemaCreator#init0Schema"); } 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"), "Need init database via Warlock1SchemaCreator#init0Schema"); + if (i > 2) { + Assertions.assertTrue(login.contains("error.authn.failureWaiting"), "Need init database via Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(login.contains("retry after"), "check i18n config"); + } } + 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); String nonce = OkHttpClientHelper.extractString(r1, false); log.warn("testDanger-d get nonce for root, nonce=" + nonce); - Assertions.assertEquals(200, r1.code(), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertEquals(200, r1.code(), "Need init database via Warlock1SchemaCreator#init0Schema"); final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=root&password=" + nonce), false); String login = OkHttpClientHelper.extractString(r2, false); log.warn("testDanger-d get login res = " + login); - Assertions.assertTrue(login.contains("true"), "需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(login.contains("true"), "Need init database via Warlock1SchemaCreator#init0Schema"); } } } 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-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock1SchemaManager.java b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock1SchemaManager.java index 510712200..64b000444 100644 --- a/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock1SchemaManager.java +++ b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock1SchemaManager.java @@ -8,8 +8,7 @@ import java.util.function.Consumer; /** - * 按常见的版本管理场景提供便捷方式。 - *

    + * Provides a convenient version management of common scenarios. * spring.wings.faceless.flywave.enabled.module=true * * @author trydofor diff --git a/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock2EnumGenerator.java b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock2EnumGenerator.java index 397592822..c4421f167 100644 --- a/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock2EnumGenerator.java +++ b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock2EnumGenerator.java @@ -6,7 +6,7 @@ import java.util.function.Consumer; /** - * idea中,main函数执行和spring执行,workdir不同 + * In IDEA, run from `main` and spring test, they are different in workdir * * @author trydofor * @since 2021-02-20 diff --git a/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock3JooqGenerator.java b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock3JooqGenerator.java index 2eed32e7f..ffd38d8eb 100644 --- a/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock3JooqGenerator.java +++ b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock3JooqGenerator.java @@ -9,7 +9,7 @@ import java.util.function.Consumer; /** - * idea中,main函数执行和spring执行,workdir不同 + * In IDEA, run from `main` and spring test, they are different in workdir * * @author trydofor * @since 2021-02-20 @@ -25,7 +25,7 @@ public Warlock3JooqGenerator() { /// public static Consumer includeWarlockBase(boolean append) { return builder -> builder - // 支持 pattern的注释写法 + // support Pattern comment .databaseIncludes(append, warlockBase); } @@ -37,7 +37,7 @@ public static Consumer includeWarlockBase(boolean append) { public static Consumer includeWarlockBond(boolean append) { return builder -> builder - // 支持 pattern的注释写法 + // support Pattern comment .databaseIncludes(append, warlockBond) .forcedIntConsEnum(UserGender.class, ".*\\.gender") .forcedIntConsEnum(UserStatus.class, "win_user_basis\\.status") diff --git a/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock4AuthGenerator.java b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock4AuthGenerator.java index 0154c3bc0..e3831727f 100644 --- a/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock4AuthGenerator.java +++ b/wings/warlock-codegen/src/main/java/pro/fessional/wings/warlock/project/Warlock4AuthGenerator.java @@ -3,7 +3,7 @@ import pro.fessional.wings.faceless.project.ProjectAuthGenerator; /** - * idea中,main函数执行和spring执行,workdir不同 + * In IDEA, run from `main` and spring test, they are different in workdir * * @author trydofor * @since 2021-02-20 diff --git a/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock1SchemaManagerTest.java b/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock1SchemaManagerTest.java index 795edf70f..a0bf2a8d4 100644 --- a/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock1SchemaManagerTest.java +++ b/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock1SchemaManagerTest.java @@ -15,7 +15,7 @@ * @author trydofor * @since 2021-02-22 */ -@Disabled("初始化数据库,工程模板,已有devs统一管理") +@Disabled("Init database, project template, managed by devops") @SpringBootTest(properties = { "spring.datasource.url=" + Warlock0CodegenConstant.JDBC, "spring.datasource.username=" + Warlock0CodegenConstant.USER, diff --git a/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock2CodeGeneratorTest.java b/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock2CodeGeneratorTest.java index f1c3731d6..0103f8a43 100644 --- a/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock2CodeGeneratorTest.java +++ b/wings/warlock-codegen/src/test/java/pro/fessional/wings/warlock/project/Warlock2CodeGeneratorTest.java @@ -13,7 +13,7 @@ * @author trydofor * @since 2021-02-22 */ -@Disabled("生成代码,已有devs统一管理") +@Disabled("Code generation, managed by devops") class Warlock2CodeGeneratorTest { @Test diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminTweakController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminTweakController.java index 7f49cbdc9..b321e6c8a 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminTweakController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/admin/AdminTweakController.java @@ -23,15 +23,12 @@ @ConditionalOnProperty(name = WarlockEnabledProp.Key$controllerTweak, havingValue = "true") public class AdminTweakController { - @Operation(summary = "线程级设置日志级别", description = """ + @Operation(summary = "Tweak the logging level at the thread level", description = """ # Usage - 根据userId设置日志级别,level==OFF时,为关闭线程设定,复原系统原设置。 + set log level by userId, reset to the original setting if level==OFF. ## Params - * @param userId - 必填,用户id,MAX_VALUE为全部用户 - * @param level - 选填,日志级别,TRACE, DEBUG, INFO, WARN, ERROR和OFF - ## Returns - * @return {401} 权限不够时 - * @return {200} 直接访问或redirect时 + * @param userId - required, MAX_VALUE for all user + * @param level - optional, e.g. TRACE, DEBUG, INFO, WARN, ERROR and OFF """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$adminTweakLogger + "}") @ResponseBody @@ -40,19 +37,16 @@ public R adminTweakLogger(@RequestBody TweakLoggerEvent ev) { return R.OK; } - @Operation(summary = "线程级设置时钟级别", description = """ + @Operation(summary = "Tweak the clock at the thread level", description = """ # Usage - 根据userId设时钟志级别,mills==OFF时,为关闭线程设定,复原系统原设置。 - 判断条件,mills在未来3650天(315360000000),约1980前 - * ①与系统时钟相差的毫秒数 - * ②固定时间(1970-01-01) - * ③0表示reset + Set Clock by userId, reset to the original setting if mills==0 + Condition, mills in the next 3650 days (315360000000), before 1980 + (1) milliseconds difference from the system clock + (2) fixed time (from 1970-01-01, after 1980) + (3) 0 means reset setting, restores the original system settings. ## Params - * @param userId - 必填,用户id,MAX_VALUE为全部用户 - * @param mills - 必填,毫秒数 - ## Returns - * @return {401} 权限不够时 - * @return {200} 直接访问或redirect时 + * @param userId - required, MAX_VALUE for all user + * @param mills - required, millisecond """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$adminTweakClock + "}") @ResponseBody @@ -61,15 +55,12 @@ public R adminTweakClock(@RequestBody TweakClockEvent ev) { return R.OK; } - @Operation(summary = "线程级设置时钟级别", description = """ + @Operation(summary = "Tweak ExceptionStack at the thread level", description = """ # Usage - 根据userId设时钟志级别,stack==null时,为关闭线程设定,复原系统原设置。 + Tweak Stack of Exception by userId, reset to the original setting if stack==null ## Params - * @param userId - 必填,用户id,MAX_VALUE为全部用户 - * @param stack - 选填,是否有堆栈 - ## Returns - * @return {401} 权限不够时 - * @return {200} 直接访问或redirect时 + * @param userId - required, MAX_VALUE for all user + * @param stack - optional, whether have stack """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$adminTweakStack + "}") @ResponseBody diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/api/AbstractApiAuthController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/api/AbstractApiAuthController.java index b5f1053f6..625d11d3e 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/api/AbstractApiAuthController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/api/AbstractApiAuthController.java @@ -45,7 +45,7 @@ import static pro.fessional.wings.warlock.controller.api.AbstractApiAuthController.ApiError.DigestBodyInvalid; /** - * 完成消息签名验证,Terminal登录登出 + * Message signature verification, Terminal login and logout * * @author trydofor * @since 2022-11-09 @@ -59,7 +59,7 @@ public abstract class AbstractApiAuthController { protected final Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass()); /** - * 是否兼容直传clientId,还是仅支持ticket体系 + * Whether it is compatible mode (send clientId directly), or only the ticket mode. */ @Setter @Getter private boolean compatible = true; @@ -74,7 +74,7 @@ public abstract class AbstractApiAuthController { protected TerminalInterceptor terminalInterceptor; /** - * 需要子类Override,以便进行RequestMapping + * To annotate `@RequestMapping`, Need subclass Override */ public void requestMapping(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) { // @@ -85,7 +85,7 @@ public void requestMapping(@NotNull HttpServletRequest request, @NotNull HttpSer if (term != null) { pass = ticketService.findPass(term.getClientId()); } - else { // 无效token或compatible模式 + else { // invalid token or compatible mode if (compatible) { pass = ticketService.findPass(cid); } @@ -161,7 +161,7 @@ protected void responseBody(@NotNull HttpServletResponse response, @NotNull ApiE else { // response file final int size = entity.resFile.available(); - int sumLen = 0; // 指纹算法 + int sumLen = 0; // Digest Algorithm if (size < apiAuthProp.getDigestMax().toBytes()) { for (Map.Entry en : entity.reqPara.entrySet()) { if (en.getKey().endsWith(".sum")) { @@ -263,11 +263,11 @@ else if (vls.length == 1) { final Collection pts = request.getContentType().contains(MULTIPART_FORM_DATA_VALUE) ? request.getParts() : Collections.emptyList(); - // json 模式 + // json mode if (pts.isEmpty()) { entity.reqBody = InputStreams.readText(request.getInputStream()); } - // file 模式 + // file mode else { final HashMap prt = new HashMap<>(); final String jbn = apiAuthProp.getFileJsonBody(); @@ -291,7 +291,8 @@ else if (vls.length == 1) { } /** - * 验签一个Api请求,若未失败(成功或未验证),则返回Entity,否则返回null + * Validate an Api request and returns Entity if it does not fail (successful or unvalidated), + * otherwise it returns null. */ @SneakyThrows @NotNull @@ -303,10 +304,10 @@ public ApiEntity validate(@NotNull HttpServletRequest request, @NotNull String s final String para = FormatUtil.sortParam(entity.reqPara); // final String data; - if (entity.reqFile.isEmpty()) { // json 模式 + if (entity.reqFile.isEmpty()) { // json mode data = para + entity.reqBody + secret + entity.timestamp; } - else { // file 模式 + else { // file mode for (Map.Entry en : entity.reqFile.entrySet()) { final String name = en.getKey(); final Part pt = en.getValue(); @@ -331,7 +332,7 @@ else if (cpt != pt) { } } - // 验证签名 + // validate signature final String sign = signature(data, entity.signature.length(), secret); if (mustSign && !sign.equalsIgnoreCase(entity.signature)) { entity.error = ApiError.SignatureInvalid; @@ -401,8 +402,9 @@ else if (len == HMAC_LEN) { } /** - * 通过validate后,由此方法进行业务处理。 - * true表示已被处理,可以应答,false表示未被处理。 + * After passing validate, this method performs business logic. + * `true` means it has been processed and can response, + * `false` means it has not been processed. */ public abstract boolean handle(@NotNull HttpServletRequest request, @NotNull ApiEntity entity) throws Exception; @@ -417,36 +419,36 @@ public enum ApiError { public static class ApiEntity { /** - * 请求Header中的timestamp + * Timestamp in the Request Header */ @NotNull private String timestamp = Null.Str; /** - * 请求Header中的signature + * Signature in the Request Header */ @NotNull private String signature = Null.Str; /** - * 请求Header中的digest + * Digest in the Request Header */ @NotNull private String digest = Null.Str; /** - * 用户信息,通过validate时一定NotNull + * Terminal info, NotNull if pass the validation */ @NotNull private Context terminal = TerminalContext.Null; /** - * 业务参数,当value为多值时,直接拼接多值为一个value + * Request Param. if key with multiple values, then join them to one * * @see HttpServletRequest#getParameterMap() */ @NotNull private Map reqPara = Collections.emptyMap(); /** - * 业务主体,如json,UTF8。除了RequestBody外,也可由文件取得 + * Request Body, e.g. json, UTF8. can be get by @RequestBody or file * * @see WarlockApiAuthProp#getFileJsonBody() * @see HttpServletRequest#getInputStream() @@ -454,7 +456,7 @@ public static class ApiEntity { @NotNull private String reqBody = Null.Str; /** - * 请求文件,文件名为name + * Request File, filename in `name` * * @see HttpServletRequest#getParts() * @see Part#getName() @@ -464,24 +466,24 @@ public static class ApiEntity { private Map reqFile = Collections.emptyMap(); /** - * 以错误的形式回复 + * Response An Error */ private ApiError error = null; /** - * resFile=null时为应答文本,否则为文件名 + * Response Body if resFile=null, otherwise the filename */ @NotNull private String resText = Null.Str; /** - * 应答文件, + * Response File */ @Nullable private InputStream resFile = null; /** - * 应答头 + * Response header */ @NotNull private Map resHead = Collections.emptyMap(); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginPageController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginPageController.java index 4089a157d..f99f45086 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginPageController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginPageController.java @@ -51,15 +51,16 @@ public class LoginPageController { private HttpSessionIdResolver httpSessionIdResolver; @SuppressWarnings("MVCPathVariableInspection") - @Operation(summary = "集成登录默认页,默认返回支持的type类表", description = """ + @Operation(summary = "Default integrated login page, return list of supported types", description = """ # Usage - 列出支持的登录方式。具体恢复内容,以根据extName和request.ContentType推测的MediaType确定 - 比如`html`和`json`扩展名,默认实现中,结果都以json形式返回 + Lists the supported logon type. The response content is determined by the MediaType + inferred from extName and request.ContentType. e.g. for `html` and `json` extensions, + the default implementation returns in json. ## Params - * @param extName - PathVariable,扩展名,如html,json + * @param extName - PathVariable, extName (.html, .json) ## Returns - * @return {401} 当鉴权失败,有系统forward时 - * @return {200} 直接访问或redirect时 + * @return {401} auth failed and forward + * @return {200} OK or redirect """) @RequestMapping(value = "${" + WarlockUrlmapProp.Key$authLoginList + "}", method = {RequestMethod.POST, RequestMethod.GET}) public ResponseEntity loginList(@PathVariable(WingsAuthHelper.ExtName) String extName, @@ -71,10 +72,10 @@ public ResponseEntity loginList(@PathVariable(WingsAuthHelper.ExtName) String } @SuppressWarnings("MVCPathVariableInspection") - @Operation(summary = "具体验证登录默认页,根据content-type及extName规则做相应的处理", description = """ + @Operation(summary = "The specific login page, according to content-type and extName", description = """ # Usage - 一般用于构造访问入口,如Oauth2登录的第三方路径和参数;获取反扒登录的验证码 - 需要注意state是数组,是spring支持的http协议的参数数组,如`a=1&a=2&a=3` + Generally used to construct the login entry, such as the 3rd path and param of the Oauth2 login; + Note, `state` is an array, is spring supported http param array, such as `a=1&a=2&a=3` ```bash curl -X POST 'http://localhost:8084/auth/login-page.json' \\ --data 'authType=github&state=/order-list&state=http://localhost:8080&state=&host=localhost:8080' @@ -82,14 +83,14 @@ public ResponseEntity loginList(@PathVariable(WingsAuthHelper.ExtName) String ?authType=github&host=localhost:8080&state=/order-list&state=http://localhost%3A8080&state=" ``` ## Params - * @param extName - PathVariable 辅助构造返回数据 - * @param authType - PathVariable 验证类型,系统配置项,可由【集成登录】查看,比如email,github - * @param authZone - 辅助验证参数,可关联权限等 - * @param {string[]} state - 构造Oauth2的state,MessageFormat格式,state[0]作为Format的key,state整体是Format的参数; - * @param host - 构造Oauth2的重定向host,以减少跨域 + * @param extName - PathVariable extName (.html, .json) + * @param authType - PathVariable auth type in the config (email, github) + * @param authZone - help to grant perm + * @param {string[]} state - Oauth2 state in MessageFormat: `state[0]` is Format's key, all `state` are Format's args; + * @param host - Oauth2 redirect host to avoid CORS ## Returns - * @return {401} 当鉴权失败,有系统forward时 - * @return {200} 直接访问或redirect时 + * @return {401} auth failed and forward + * @return {200} OK or redirect """) @RequestMapping(value = "${" + WarlockUrlmapProp.Key$authLoginPage + "}", method = {RequestMethod.POST, RequestMethod.GET}) public ResponseEntity LoginPage(@PathVariable(WingsAuthHelper.ExtName) String extName, @@ -106,9 +107,9 @@ public ResponseEntity LoginPage(@PathVariable(WingsAuthHelper.ExtName) String } @SuppressWarnings("MVCPathVariableInspection") - @Operation(summary = "具体验证登录默认页,参考" + WarlockUrlmapProp.Key$authLoginPage, description = + @Operation(summary = "The specific login page, see " + WarlockUrlmapProp.Key$authLoginPage, description = "# Usage \n" - + "把" + WingsAuthHelper.AuthType + "参数从PathVariable变为RequestParam\n" + + "change " + WingsAuthHelper.AuthType + "from PathVariable to RequestParam\n" + "") @RequestMapping(value = "${" + WarlockUrlmapProp.Key$authLoginPage2 + "}", method = {RequestMethod.POST, RequestMethod.GET}) public ResponseEntity LoginPage2(@PathVariable(WingsAuthHelper.ExtName) String extName, @@ -125,16 +126,16 @@ public ResponseEntity LoginPage2(@PathVariable(WingsAuthHelper.ExtName) Strin } @SuppressWarnings("UastIncorrectHttpHeaderInspection") - @Operation(summary = "验证一次性token是否有效", description = """ + @Operation(summary = "Verify that the one-time token is valid", description = """ # Usage - Oauth2使用state作为token,要求和发行client具有相同ip,agent等header信息 - 验证成功后,在header中,可同样获取login时的session和cookie + Use Oauth2 state as the token and require the same ip, agent and other header as the original client. + After successful verification, the session and cookie are in the header as a normal login ## Params - * @param token - RequestHeader Oauth2使用state作为token + * @param token - RequestHeader Oauth2 state as token ## Returns - * @return {401} 无|过期|失败 - * @return {200 | Result(false, message='authing')} 验证进行中 - * @return {200 | Result(true, data=sessionId)} 验证成功 + * @return {401} token is not-found, expired, or failed + * @return {200 | Result(false, message='authing')} in verifying + * @return {200 | Result(true, data=sessionId)} success """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$authNonceCheck + "}") public ResponseEntity> nonceCheck(@RequestHeader("token") String token, HttpServletRequest request, HttpServletResponse response) { @@ -142,7 +143,7 @@ public ResponseEntity> nonceCheck(@RequestHeader("token") String token, Htt if (sid == null) { return ResponseEntity .status(HttpStatus.UNAUTHORIZED) - .body(R.ng()); + .body(R.NG); } else { final R r; diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginProcController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginProcController.java index 4ffbce202..7f31ed2c0 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginProcController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/LoginProcController.java @@ -24,13 +24,11 @@ @ConditionalOnProperty(name = WarlockEnabledProp.Key$controllerProc, havingValue = "true") public class LoginProcController { - @Operation(summary = "登出接口,有filter处理,仅做文档", description = """ + @Operation(summary = "Logout entry, handled by filter, used for document only", description = """ # Usage - 默认失效Session,参考wings.warlock.security.logout-url - ## Params - * @param token - Oauth2使用state作为token + Invalid all Session, see wings.warlock.security.logout-url ## Returns - * @return {200} 任何时候 + * @return {200} always """) @GetMapping(value = "${" + WarlockSecurityProp.Key$logoutUrl + "}") public R logout() { @@ -39,18 +37,16 @@ public R logout() { @SuppressWarnings("MVCPathVariableInspection") - @Operation(summary = "登录接口,有filter处理,仅做文档", description = """ + @Operation(summary = "Login entry, handled by filter, used for document only", description = """ # Usage - 根据类型自动处理,参考 wings.warlock.security.login-proc-url - username和password可变,参考 参考 wings.warlock.security.username-para - 登录成功后,可在header中获得token和session + Auto handle by authType, see wings.warlock.security.login-proc-url + username and password can be changed, see wings.warlock.security.username-para + After successfully login, the token and session can get in header ## Params - * @param authType - PathVariable 验证类型,系统配置项,可由【集成登录】查看,比如email,github - * @param authZone - 辅助验证参数,可关联权限等,支持path和param传参 - * @param username - Oauth2使用state作为token - * @param password - Oauth2使用state作为token - ## Returns - * @return {200} 登录成功 + * @param authType - PathVariable auth type in the config (email, github) + * @param authZone - help to grant perm, support in `path` and `param` + * @param username - Oauth2 use state as token + * @param password - Oauth2 use state as token """) @PostMapping(value = "${" + WarlockSecurityProp.Key$loginProcUrl + "}") public R login(@PathVariable(WingsAuthHelper.AuthType) String authType, diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/SimpleOauthController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/SimpleOauthController.java index e2ae8a69c..16b681849 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/SimpleOauthController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/auth/SimpleOauthController.java @@ -20,7 +20,7 @@ import pro.fessional.wings.warlock.spring.prop.WarlockUrlmapProp; /** - * 简单的模仿Oauth验证,方便测试和API使用, + * Simple Oauth validation for testing and API use. * github authorizing-oauth-apps * authorization/the-authorization-response * @@ -35,20 +35,20 @@ public class SimpleOauthController { @Setter(onMethod_ = {@Autowired}) protected WarlockOauthService warlockOauthService; - @Operation(summary = "简单模拟Oauth2的AuthorizationCode授权", description = """ + @Operation(summary = "Simple simulation of Oauth2 AuthorizationCode", description = """ # Usage - 参考 https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps - 参考 https://www.oauth.com/oauth2-servers/authorization/the-authorization-response - * 默认为标准的302重定向 - * 当Accept: application/json返回json - * 当Accept: application/xml返回xml - * 错误时,以error,error_description为key返回 + see https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps + see https://www.oauth.com/oauth2-servers/authorization/the-authorization-response + * Default standard 302 redirect + * return json if Accept: application/json + * return xml if Accept: application/xml + * return error, error_description if error ## Params * @param client_id - Required. The client ID - * @param redirect_uri - 重定向或直接返回json格式 - * @param scope - 空格分隔的字符串 - * @param state - 防SCRF攻击,原值返回数据 - * @header Accept - application/json返回json,application/xml返回xml + * @param redirect_uri - redirect_uri if 302, or in json + * @param scope - scope seperated by space + * @param state - anti SCRF, return the raw value + * @header Accept - help to content type ## Returns * @return {302} redirect to redirect_uri * @return {200} json/xml @@ -67,22 +67,22 @@ public ResponseEntity authorize(@RequestParam(WarlockOauthService.Client return ResponseHelper.flatResponse(data, accept, redirectUri); } - @Operation(summary = "简单模拟Oauth2,仅支持authorization-code和client-credentials模式", description = """ + @Operation(summary = "Simple simulation of Oauth2 authorization-code and client-credentials", description = """ # Usage - 参考 https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps - 参考 https://www.oauth.com/oauth2-servers/access-tokens/authorization-code-request/ - 参考 https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/ - * 默认为标准的302重定向 - * 当Accept: application/json返回json - * 当Accept: application/xml返回xml - * 错误时,以error,error_description为key返回 - * 有效期内(默认1小时),可以使用access_token作为code再次刷新code + see https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps + see https://www.oauth.com/oauth2-servers/access-tokens/authorization-code-request/ + see https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/ + * Default standard 302 redirect + * return json if Accept: application/json + * return xml if Accept: application/xml + * return error, error_description if error + * during the validity period (defaut 1H), can refresh the code by passing access_token as the code. ## Params * @param client_id - Required. The client ID * @param client_secret - Required. The client secret - * @param code - 有值时为authorization_code, 否则为client_credentials - * @param redirect_uri - 重定向或直接返回数据 - * @header Accept - application/json返回json,application/xml返回xml + * @param code - authorization_code if not empty, otherwise client_credentials + * @param redirect_uri - redirect or in json + * @header Accept - help to content type ## Returns * @return {302} redirect to redirect_uri * @return {200} json/xml @@ -98,16 +98,16 @@ public ResponseEntity accessToken(@RequestParam(WarlockOauthService.ClientId) return ResponseHelper.flatResponse(data, accept, redirectUri); } - @Operation(summary = "吊销AuthorizationCode或AccessToken授权,应付Token外泄情况", description = """ + @Operation(summary = "Revoke AuthorizationCode or AccessToken in case of Token leakage", description = """ # Usage - * 默认为标准的302重定向 - * 当Accept: application/json返回json - * 当Accept: application/xml返回xml - * 错误时,以error,error_description为key返回 + * Default standard 302 redirect + * return json if Accept: application/json + * return xml if Accept: application/xml + * return error, error_description if error ## Params - * @param code - Required. 有效的AuthorizationCode或AccessToken - * @param redirect_uri - 重定向或直接返回json格式 - * @header Accept - application/json返回json,application/xml返回xml + * @param code - Required. valid AuthorizationCode or AccessToken + * @param redirect_uri - redirect or in json + * @header Accept - help to content type ## Returns * @return {302} redirect to redirect_uri * @return {200} json/xml diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/mock/MockSampleController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/mock/MockSampleController.java index 722ed5708..e252325b5 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/mock/MockSampleController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/mock/MockSampleController.java @@ -35,16 +35,16 @@ @ConditionalOnProperty(name = WarlockEnabledProp.Key$controllerMock, havingValue = "true") public class MockSampleController { - @Operation(summary = "验证码,获得图片,有interceptor处理", description = """ + @Operation(summary = "Get captcha image, handle by interceptor", description = """ # Usage - 参考POST说明,GET方法主要用来获取及刷新验证码图片。 - 若Accept中含有base64时,则返回base64格式的图片。 - 参数参考,FirstBloodImageHandler + The GET is mainly used to get and refresh the CAPTCHA image. + If Accept contains `base64`, it returns the image in base64 format. + see mockCaptchaPost (POST method) and FirstBloodImageHandler ## Params - * @param quest-captcha-image - 验证码,用来获取新的验证图,或检查当前验证码 + * @param quest-captcha-image - Captcha, to check captcha or get a new captcha ## Returns - * @return {200} 验证码不匹配时,新的图片流或base64图片 - * @return {200} 验证码匹配时,空body + * @return {200} captcha not matched, new image stream or base64 string + * @return {200} captcha matched, empty body """) @GetMapping(value = "${" + WarlockUrlmapProp.Key$mockCaptcha + "}") @ResponseBody @@ -54,20 +54,20 @@ public R mockCaptchaGet(@RequestParam(value = "quest-captcha-image") Str return R.ok("should NOT return this, Please use POST. Quest=" + quest + ", Accept=" + accept); } - @Operation(summary = "验证码,获得结果,有interceptor处理", description = """ + @Operation(summary = "Get captcha image, handle by interceptor", description = """ # Usage - 客户端正常访问此URL,验证图片由interceptor处理 - ①服务器需要验证码时,以406(Not Acceptable)返回提示json - ②客户端在header和cookie中获得Client-Ticket的token,并每次都发送 - ③客户端在URL后增加quest-captcha-image={vcode}获取验证码图片(可直接使用) - ④客户端在URL后增加check-captcha-image={vcode}提交验证码 - ⑤服务器端自动校验Client-Ticket和check-captcha-image,完成验证或放行 + The client accesses this URL normally, and the captcha image is handled by the interceptor + (1) Server returns json with 406(Not Acceptable) if CAPTCHA is required + (2) Client gets Client-Ticket token in header and cookie and sends it every time + (3) Client adds quest-captcha-image={vcode} after the URL to get the CAPTCHA image (can be used directly) + (4) Client adds check-captcha-image={vcode} after the URL to submit the CAPTCHA + (5) Server auto checks Client-Ticket and check-captcha-image ## Params - * @param data - 测试数据,验证通过时,原路返回 - * @param check-captcha-image - 提交的验证码,用来验证 + * @param data - test data, return if pass + * @param check-captcha-image - submit captcha to check ## Returns - * @return {200} 验证码通过时,执行被保护的URL结果 - * @return {406} 触发了验证码机制,默认{"success":false,"message":"need a verify code"} + * @return {200} pass captcha, response by the protected URL + * @return {406} trigger captcha, return `{"success":false,"message":"need a verify code"}` """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$mockCaptcha + "}") @ResponseBody @@ -78,15 +78,15 @@ public R mockCaptchaPost(@RequestParam(value = "data", required = false) return R.ok("check=" + check, data); } - @Operation(summary = "防连击,需要2次请求", description = """ + @Operation(summary = "Avoid double click, need 2 fast requests", description = """ # Usage - ①首次执行,会等待sleep秒数后完成。 - ②在①执行过程中再次执行,会返回202(Accepted) + (1) 1st request, set `sleep` second, and waiting for response + (2) 2nd request during (1), will response 202(Accepted) ## Params - * @param sleep - sleep秒数,模拟慢响应 + * @param sleep - sleep to simulate slow operation ## Returns - * @return {200 | Result(sleep)} 返回sleep秒数 - * @return {202 | Result(false, data)} 执行期间再次请求,直接任务id + * @return {200 | Result(sleep)} return the sleep + * @return {202 | Result(false, data)} in 2nd request, return task id """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$mockDoubler + "}") @ResponseBody @@ -97,14 +97,14 @@ public R mockDoubler(@RequestParam(value = "sleep", required = false) I return R.okData(sleep); } - @Operation(summary = "防篡改,GET获得编辑header(Right-Editor)", description = """ + @Operation(summary = "Tamper-proof, GET edit header (Right-Editor)", description = """ # Usage - ①GET 情况获得编辑header,默认key为Right-Editor - ②参加POST请求的文档 + (1) GET the edit header, default key is `Right-Editor` + (2) see mockRighterSave (POST method) ## Params - * @param data - 防篡改的特征数据 + * @param data - data to audit ## Returns - * @return {200 | Result(data)} 返回data参数 + * @return {200 | Result(data)} data """) @GetMapping(value = "${" + WarlockUrlmapProp.Key$mockRighter + "}") @ResponseBody @@ -115,15 +115,15 @@ public R mockRighterView(@RequestParam("data") String data) { } @SuppressWarnings("UastIncorrectHttpHeaderInspection") - @Operation(summary = "防篡改,提交数据及编辑header(Right-Editor)", description = """ + @Operation(summary = "Tamper-proof, Submit changed data with Edit Header (Right-Editor)", description = """ # Usage - ①参考GET - ②提交时,携带编辑Header + (1) see GET + (2) submit changed data with the Edit Header ## Params - * @param Right-Editor - 编辑header,从GET响应中获得 + * @param Right-Editor - Edit header, from the GET response ## Returns - * @return {200 | Result(data)} 返回GET时的data数据 - * @return {409 | Result(false)} Right-Editor验证失败 + * @return {200 | Result(data)} return the data send in GET + * @return {409 | Result(false)} Right-Editor if audit fails """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$mockRighter + "}") @ResponseBody @@ -133,18 +133,18 @@ public R mockRighterSave(@RequestHeader("Right-Editor") String hd) { return R.ok(hd, data); } - @Operation(summary = "回声测试,输入啥返回啥。", description = """ + @Operation(summary = "Echo test, output what you input", description = """ # Usage - 按输入返回status,header, cookie和RequestBody + Response the input status, header, cookie and RequestBody ## Params - * @param [status=200] - http status 默认200 - * @param [header] - http header k1=v1等号分隔 - * @param [cookie] - http cookie k1=v1等号分隔 - * @param [httponly] - httponly的cookie名,如k1 - * @param [secure] - https的cookie名,如k1 + * @param [status=200] - http status, default 200 + * @param [header] - http header k1=v1, `=` seperated + * @param [cookie] - http cookie k1=v1, `=` seperated + * @param [httponly] - cookie name that is httponly, e.g. k1 + * @param [secure] - cookie name that is https, e.g. k1 * @param - request body ## Returns - * @return {200 | Result(data)} 返回GET时的data数据 + * @return {200 | Result(data)} response what input """) @PostMapping(value = "${" + WarlockUrlmapProp.Key$mockEcho0o0 + "}") public void mockEcho( diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/test/TestEnvsController.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/test/TestEnvsController.java index dc9e09546..25cebd776 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/test/TestEnvsController.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/controller/test/TestEnvsController.java @@ -21,9 +21,9 @@ @ConditionalOnProperty(name = WarlockEnabledProp.Key$controllerTest, havingValue = "true") public class TestEnvsController { - @Operation(summary = "获取 RunMode", description = """ + @Operation(summary = "Get RunMode", description = """ # Usage - 无参数Get取得 Product, Test, Develop, Local + Return Product, Test, Develop, Local """) @RequestMapping(value = "${" + WarlockUrlmapProp.Key$testRunMode + "}", method = {RequestMethod.POST, RequestMethod.GET}) @ResponseBody @@ -32,9 +32,9 @@ public R testRunMode() { return R.okData(rm.name()); } - @Operation(summary = "获取系统 Timestamp", description = """ + @Operation(summary = "Get system Timestamp", description = """ # Usage - 无参数Get取得 1970毫秒数的Timestamp + Get the Timestamp from 1970 in mills """) @RequestMapping(value = "${" + WarlockUrlmapProp.Key$testSystemMills + "}", method = {RequestMethod.POST, RequestMethod.GET}) @ResponseBody @@ -43,9 +43,9 @@ public R testSystemMills() { return R.okData(ms); } - @Operation(summary = "获取线程 Timestamp", description = """ + @Operation(summary = "Get thread Timestamp", description = """ # Usage - 无参数Get取得 1970毫秒数的Timestamp + Get the Timestamp from 1970 in mills """) @RequestMapping(value = "${" + WarlockUrlmapProp.Key$testThreadMills + "}", method = {RequestMethod.POST, RequestMethod.GET}) @ResponseBody 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..a0a19625c 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 @@ -35,7 +35,7 @@ import java.util.stream.Collectors; /** - * 需要配置权限,通过filter配置或方法级的@PreAuthorize("isAuthenticated()") + * Permissions need to be configured by filter config or method-level `@PreAuthorize("isAuthenticated()")` * * @author trydofor * @since 2021-02-16 @@ -52,36 +52,36 @@ public class AuthedUserController { @Setter(onMethod_ = {@Autowired(required = false)}) private SessionTokenEncoder sessionTokenEncoder; - @Schema(description = "登录用户基本信息") + @Schema(description = "Basic info of login user") @Data public static class Dto { - @Schema(description = "昵称", example = "trydofor") + @Schema(description = "nickname", example = "trydofor") private String nickname; - @Schema(description = "语言,参考java.util.Locale", example = "zh-CN") + @Schema(description = "username", example = "trydofor") + private String username; + @Schema(description = "language, see java.util.Locale", example = "zh-CN") private String locale; - @Schema(description = "时区,参考java.time.ZoneId", example = "Asia/Shanghai") + @Schema(description = "timezone, see java.time.ZoneId", example = "Asia/Shanghai") private String zoneid; - @Schema(description = "秒差,与UTC相差的秒数", example = "28800") + @Schema(description = "time offset in second to UTD", example = "28800") private int offset; - @Schema(description = "验证类型,此session的登录类型", example = "EMAIL") + @Schema(description = "auth type of current session", example = "EMAIL") private String authtype; - @Schema(description = "验证凭证,此session的登录凭证", example = "fd7a5475-bd3b-4086-96b0-b95d11cf1d3c") + @Schema(description = "auth token of current session", example = "fd7a5475-bd3b-4086-96b0-b95d11cf1d3c") private String token; } - @Operation(summary = "获得登录用户的基本信息", description = """ + @Operation(summary = "Get authed info of current user", description = """ # Usage - 只有登录用户才有信息 - ## Params - 无 + Only logined user ## Returns - * @return {200 | Result(Dto)} 登录用户,成功返回用户基本信息; - * @return {200 | Result(false)} 未登录用户,且无URL权限; - * @return {401} 若设置了URL访问权限且用户未登录;""") + * @return {200 | Result(Dto)} logined user and basis info + * @return {200 | Result(false)} not logined and the URL without perm + * @return {401} logined and no perm to the URL""") @PostMapping(value = "${" + WarlockUrlmapProp.Key$userAuthedUser + "}") public R authedUser(HttpServletRequest request) { final WingsUserDetails wd = SecurityContextUtil.getUserDetails(false); - if (wd == null) return R.ng(); + if (wd == null) return R.NG(); Dto dto = new Dto(); fillDetail(wd, dto); @@ -102,6 +102,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()); @@ -110,32 +111,32 @@ private void fillDetail(WingsUserDetails wd, Dto dto) { @Data - @Schema(description = "精查登录用户角色权限") + @Schema(description = "Check the perm/role of login user") public static class Ins { - @Schema(description = "别名项,本名为key,别名为value", example = "{\"ROLE_SYSTEM\":\"OLD_SYSTEM\"}") + @Schema(description = "original as key, alias as value", example = "{\"ROLE_SYSTEM\":\"OLD_SYSTEM\"}") private Map alias; - @Schema(description = "权限项", example = "[\"ROLE_ADMIN\",\"ROLE_SYSTEM\"]") + @Schema(description = "set of perm/role", example = "[\"ROLE_ADMIN\",\"ROLE_SYSTEM\"]") private Set perms; - @Schema(description = "检查项,若检查项未全满足,则invalidate session", example = "[\"ROLE_ADMIN\"]") + @Schema(description = "perm/role to check, if not fully contain, then invalidate session", example = "[\"ROLE_ADMIN\"]") private Set check; } - @Operation(summary = "检查登录用户的权限,不区分大小写比较,返回存在的权限;", description = """ + @Operation(summary = "Check the perm/role (case-insensitive) of the current user and returns the existing", description = """ # Usage - alias优先于perms检测,check失败时会自动登出logout。 + alias takes precedence over perm, and auto logout if check fails. ## Params - * @param ins.alias - 以本名为key,别名为value,返回别名,以兼容历史遗留 - * @param ins.perms - 权限或角色的本名 - * @param ins.check - 需要检查的权限或角色的本名 + * @param ins.alias - alias as map value for historical legacy + * @param ins.perms - perm/role original name + * @param ins.check - perm/role to check ## Returns - * @return {200 | Result(string[])} 登录用户,成功返回用户基本信息; - * @return {200 | Result(false)} 未登录用户,且无URL权限; - * @return {200 | Result(false,string[])} check失败,返回失败的权限且invalidate session; - * @return {401} 若设置了URL访问权限且用户未登录;""") + * @return {200 | Result(string[])} logined and perms + * @return {200 | Result(false)} not logined and the URL without perm + * @return {200 | Result(false,string[])} check fail, return failed perm and invalidate session + * @return {401} logined and no perm to the URL""") @PostMapping(value = "${" + WarlockUrlmapProp.Key$userAuthedPerm + "}") public R> authedPerm(HttpServletRequest request, @RequestBody Ins ins) { final WingsUserDetails wd = SecurityContextUtil.getUserDetails(false); - if (wd == null) return R.ng(); + if (wd == null) return R.NG(); final Set ck = ins.getCheck(); final Set pm = wd.getAuthorities().stream() @@ -162,10 +163,10 @@ public R> authedPerm(HttpServletRequest request, @RequestBody Ins in if (alias == null) alias = Collections.emptyMap(); if (perms.isEmpty() && alias.isEmpty()) { - return R.ok(); + return R.OK(); } - // alias 优先于perms + // alias over perms Map ci = new HashMap<>(); for (String p : perms) { ci.put(p.toLowerCase(), p); @@ -185,28 +186,26 @@ public R> authedPerm(HttpServletRequest request, @RequestBody Ins in return R.okData(res); } - @Schema(description = "登录用户会话信息") + @Schema(description = "Session info of logined user") @Data @EqualsAndHashCode(callSuper = true) public static class Ses extends Dto { - @Schema(description = "是否过期", example = "true") + @Schema(description = "Whether expired", example = "true") private boolean expired; - @Schema(description = "最新访问时间", example = "true") + @Schema(description = "Latest access time", example = "true") private ZonedDateTime lastAccess; } - @Operation(summary = "获得登录用户的所有会话", description = """ + @Operation(summary = "List all session of current user", description = """ # Usage - 只有登录用户才有信息 - ## Params - 无 + Only the logined user ## Returns - * @return {200 | Result(Dto)} 登录用户,成功返回用户会话信息; - * @return {200 | Result(false)} 未登录用户,且无URL权限; - * @return {401} 若设置了URL访问权限且用户未登录;""") + * @return {200 | Result(Dto)} logined and basis info + * @return {200 | Result(false)} not logined and the URL without perm + * @return {401} logined and no perm to the URL""") @PostMapping(value = "${" + WarlockUrlmapProp.Key$userListSession + "}") public R> listSession() { final WingsUserDetails details = SecurityContextUtil.getUserDetails(false); - if (details == null) return R.ng(); + if (details == null) return R.NG(); final List sessions = wingsSessionHelper.findByUserId(details.getUserId()); final List sess = sessions.stream().map(it -> { @@ -232,15 +231,15 @@ public static class Sid { private String sid; } - @Operation(summary = "踢掉一个登录用户的会话", description = """ + @Operation(summary = "drop the session of current user by id", description = """ # Usage - 只有登录用户才有信息 + Only the logined user ## Params - * @param sid - 要踢掉的会话Id/token + * @param sid - sessionId/token to drop ## Returns - * @return {200 | Result} 登录用户,成功返回用户会话信息; - * @return {200 | Result(false)} 未登录用户,且无URL权限; - * @return {401} 若设置了URL访问权限且用户未登录;""") + * @return {200 | Result(Dto)} logined + * @return {200 | Result(false)} not logined and the URL without perm + * @return {401} logined and no perm to the URL""") @PostMapping(value = "${" + WarlockUrlmapProp.Key$userDropSession + "}") public R dropSession(@RequestBody Sid sid) { final boolean b = wingsSessionHelper.dropSession(sid.sid); 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/SafeHttpHelper.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/SafeHttpHelper.java index a1fd1b2c2..247af3c4b 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/SafeHttpHelper.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/SafeHttpHelper.java @@ -12,7 +12,7 @@ public class SafeHttpHelper { /** - * 区分大小写,确认uri的host是否在hosts中 + * Whether the host of uri is in the `hosts`, case-sensitive */ public static boolean isSafeRedirect(@NotNull String uri, Set hosts) { if (hosts == null || hosts.isEmpty()) return true; @@ -31,9 +31,9 @@ public static boolean isSafeRedirect(@NotNull String uri, Set hosts) { /** *

    -     * 仅支持http,https的解析,解析host和port
    +     * Only parse http/https, get host and port
          *
    -     * https://www.rfc-editor.org/rfc/rfc3986
    +     * rfc3986
          *
          * The authority component is preceded by a double slash ("//") and is
          * terminated by the next slash ("/"), question mark ("?"), or number
    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/handler/LoginSuccessHandler.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/LoginSuccessHandler.java
    index f53ef7d25..414d78323 100644
    --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/LoginSuccessHandler.java
    +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/LoginSuccessHandler.java
    @@ -20,8 +20,8 @@
     import java.io.IOException;
     
     /**
    - * cookie和header返回sid,
    - * 默认情况下 cookie是sid的base64字符串
    + * Return SessionId in cookie and header,
    + * In Spring default, the sessionId in cookie is base64 encoded
      *
      * @author trydofor
      * @since 2021-02-17
    diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/NonceLoginSuccessHandler.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/NonceLoginSuccessHandler.java
    index 3052c481a..0d059a2cb 100644
    --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/NonceLoginSuccessHandler.java
    +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/handler/NonceLoginSuccessHandler.java
    @@ -18,7 +18,7 @@
     import java.io.IOException;
     
     /**
    - * 实现了一次性token换session,需要自行覆盖onResponse
    + * UUse the one-time token to obtain the session, `onResponse` need to be Override
      *
      * @author trydofor
      * @see SavedRequestAwareAuthenticationSuccessHandler
    diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/AuthStateBuilder.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/AuthStateBuilder.java
    index 599c22015..2235965d2 100644
    --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/AuthStateBuilder.java
    +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/justauth/AuthStateBuilder.java
    @@ -24,7 +24,7 @@
     
     
     /**
    - * 用于构造和解析有意义的state
    + * Construct and parse meaningful state
      *
      * @author trydofor
      * @since 2021-07-11
    @@ -63,7 +63,7 @@ public String buildState(HttpServletRequest request) {
     
             buildParaMap(request, paraMap);
     
    -        // 167823d90c46cd70e3961b3f070a871c 32 非性能优先
    +        // 167823d90c46cd70e3961b3f070a871c 32 non-performance first
             String uuid = RandCode.numlet(RAND_LEN);
             if (paraMap.isEmpty()) {
                 return uuid;
    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..6d8f97564 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
    @@ -9,6 +9,7 @@
     import org.springframework.security.core.Authentication;
     import org.springframework.security.core.GrantedAuthority;
     import pro.fessional.wings.slardar.context.TerminalContext;
    +import pro.fessional.wings.slardar.context.TerminalSecurityAttribute;
     import pro.fessional.wings.slardar.fastjson.FastJsonHelper;
     import pro.fessional.wings.slardar.security.WingsAuthDetails;
     import pro.fessional.wings.slardar.security.WingsAuthHelper;
    @@ -38,38 +39,42 @@ public void onApplicationEvent(AuthenticationSuccessEvent event) {
             if (!(source instanceof final Authentication authn)) return;
     
             final Object principal = authn.getPrincipal();
    -        if (!(principal instanceof final WingsUserDetails ud)) {
    +        if (!(principal instanceof final WingsUserDetails userDetails)) {
                 log.debug("skip non-WingsUserDetails, type={}", source.getClass().getName());
                 return;
             }
     
    -        Enum authType = ud.getAuthType();
    -        long userId = ud.getUserId();
    +        Enum authType = userDetails.getAuthType();
    +        long userId = userDetails.getUserId();
             if (authType == null) {
                 log.warn("authType should NOT null, userId={}", userId);
                 return;
             }
             final Map dtlMap = new HashMap<>();
             dtlMap.put("authType", authType.name());
    -        dtlMap.put("locale", ud.getLocale());
    -        dtlMap.put("zoneid", ud.getZoneId());
    -        dtlMap.put("nickname", ud.getNickname());
    -        dtlMap.put("username", ud.getUsername());
    +        dtlMap.put("locale", userDetails.getLocale());
    +        dtlMap.put("zoneid", userDetails.getZoneId());
    +        dtlMap.put("nickname", userDetails.getNickname());
    +        dtlMap.put("username", userDetails.getUsername());
     
             final Object dtl = authn.getDetails();
             if (dtl instanceof WingsAuthDetails authDetails) {
                 final Map meta = authDetails.getMetaData();
                 dtlMap.putAll(meta);
                 TerminalContext.Builder builder = new TerminalContext.Builder()
    -                    .locale(ud.getLocale())
    -                    .timeZone(ud.getZoneId())
    +                    .locale(userDetails.getLocale())
    +                    .timeZone(userDetails.getZoneId())
                         .terminal(TerminalAddr, meta.get(WingsAuthHelper.AuthAddr))
                         .terminal(TerminalAgent, meta.get(WingsAuthHelper.AuthAgent))
                         .user(userId)
                         .authType(authType)
    -                    .authPerm(ud.getAuthorities().stream()
    -                                .map(GrantedAuthority::getAuthority)
    -                                .collect(Collectors.toSet()));
    +                    .username(userDetails.getUsername())
    +                    .authPerm(userDetails.getAuthorities().stream()
    +                                         .map(GrantedAuthority::getAuthority)
    +                                         .collect(Collectors.toSet()))
    +                    .terminal(TerminalSecurityAttribute.UserDetails, userDetails)
    +                    .terminal(TerminalSecurityAttribute.AuthDetails, authDetails);
    +
                 TerminalContext.login(builder.build());
             }
     
    diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/session/NonceTokenSessionHelper.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/session/NonceTokenSessionHelper.java
    index 4a5b4f9aa..b726aa4f9 100644
    --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/session/NonceTokenSessionHelper.java
    +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/session/NonceTokenSessionHelper.java
    @@ -3,15 +3,18 @@
     
     import org.cache2k.Cache;
     import org.cache2k.Cache2kBuilder;
    +import org.jetbrains.annotations.Nullable;
     import pro.fessional.mirana.data.Null;
     
     import java.util.concurrent.TimeUnit;
     
     
     /**
    - * 提供5分钟内有效的一次性token关联验证。
    - * ①initNonce:发型一次性token
    - * ②bindNonceSid:登录成功后,通过uid,绑定token和sessionId
    + * 
    + * Provides a one-time token that valid for 5 minutes to authn.
    + * (1) initNonce: Init one-time token
    + * (2) bindNonceSid: after successful login, bind token and sessionId by uid.
    + * 
    * * @author trydofor * @since 2021-07-01 @@ -30,7 +33,7 @@ private static class Sf { } /** - * 初始化一次性token + * Init one-time token */ public static void initNonce(String token, String ip) { if (token == null) return; @@ -40,7 +43,7 @@ public static void initNonce(String token, String ip) { } /** - * 绑定token和sid + * bind token to sessionId */ public static void bindNonceSid(String token, String sid) { final Sf s = cache.get(token); @@ -50,20 +53,23 @@ public static void bindNonceSid(String token, String sid) { } /** - * 无效掉token + * invalid the token */ public static void invalidNonce(String token) { cache.remove(token); } /** - * null-为不存在验证 - * empty-验证进行中 - * sid-验证成功(如果成功,则自动移除,仅返回一次) + *
    +     * null - authn not exist
    +     * empty - authn in action
    +     * sid - authn success, (auto remove and return only once)
    +     * 
    * - * @param token 一次性token + * @param token one-time token * @return null|empty|sid */ + @Nullable public static String authNonce(String token, String ip) { if (token == null || token.isEmpty()) return null; diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/JustAuthUserDetailsCombo.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/JustAuthUserDetailsCombo.java index 702b09ed9..5b59a0905 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/JustAuthUserDetailsCombo.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/JustAuthUserDetailsCombo.java @@ -10,7 +10,7 @@ import pro.fessional.wings.warlock.service.auth.impl.DefaultUserDetailsCombo; /** - * JustAuth UserDetailsService,不存在用户时,自动创建 + * JustAuth UserDetailsService, auto create use if not exists * * @author trydofor * @since 2021-02-22 diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/MemoryUserDetailsCombo.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/MemoryUserDetailsCombo.java index 68238b1ce..c98d113be 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/MemoryUserDetailsCombo.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/MemoryUserDetailsCombo.java @@ -18,11 +18,13 @@ import java.util.concurrent.CopyOnWriteArrayList; /** - * 通过配置等,在内存中一直有效的预设用户验证信息。 - * 如果autoEncode且password不是以'{'和'}'的代理模式,会自动加密。 - * 注意事项: - * ①自行确保username与userid对应关系,此处不做验证。 - * ②验证时,无盐,即便db中存在passslat。 + *
    + * Predefined user auth info is loaded into memory by config, etc.
    + * If autoEncode and password is not in `{` and `}` proxy mode, it is auto encrypted.
    + * Notes:
    + * (1) Make sure the mapping of username and userid, no verification here.
    + * (2) Verify without salt, even if `passslat` exists in db.
    + * 
    * * @author trydofor * @since 2021-06-03 @@ -30,13 +32,13 @@ @Slf4j public class MemoryUserDetailsCombo extends DefaultUserDetailsCombo { - // 以 username+authType去重 + // distinct by username and authType private final Map> typedUser = new ConcurrentHashMap<>(); /** - * 添加一个内存用户, AuthType=null表示所有类型 + * Add a memory user, AuthType=null for all types * - * @param dtl 会被改变,自动加密密码 + * @param dtl can be modified, e.g. auto encrypt password */ public void addUser(@NotNull Details dtl) { final List
    set = typedUser.computeIfAbsent(dtl.getUsername(), k -> new CopyOnWriteArrayList<>()); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/NonceUserDetailsCombo.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/NonceUserDetailsCombo.java index 85c72d023..4493c1f9d 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/NonceUserDetailsCombo.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/security/userdetails/NonceUserDetailsCombo.java @@ -26,7 +26,8 @@ import java.util.Set; /** - * 一次性凭证登录,使用后立即失效,不论验证通过与否。 + * A one-time credential login that expires immediately after use, + * regardless of whether authentication passes or fails. * * @author trydofor * @see WarlockNonceSendEvent diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthType.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthType.java index 3519a7343..93d33348e 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthType.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthType.java @@ -6,15 +6,15 @@ */ public enum WarlockAuthType { /** - * 通常的用户名登录 + * login by username, generally. */ USERNAME, /** - * 以手机为用户名 + * use mobile number as username */ MOBILE, /** - * 以邮件为用户名 + * use email as username */ EMAIL, } 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..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 @@ -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); @@ -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/WarlockAuthzService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthzService.java index f6b85c50c..75296af85 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthzService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockAuthzService.java @@ -3,7 +3,7 @@ import pro.fessional.wings.slardar.security.impl.DefaultWingsUserDetails; /** - * 授权 + * Authorization (Authz) * * @author trydofor * @since 2021-02-23 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/WarlockOauthService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockOauthService.java index d01f470b4..779048cd2 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockOauthService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockOauthService.java @@ -23,13 +23,13 @@ public interface WarlockOauthService { String AccessToken = "access_token"; /** - * 需要检查scope和redirectUri,session 第三方用户的sessionId + * Need to check scope, redirectUri and session (3rd user's sessionId) */ @NotNull OAuth authorizeCode(@NotNull String clientId, String scope, String redirectUri, String session); /** - * token为empty时,为client_credentials模式,否则为authorization_code + * client_credentials if token is empty, otherwise authorization_code */ @NotNull OAuth accessToken(@NotNull String clientId, @NotNull String clientSecret, String token); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockTicketService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockTicketService.java index e1642f8ea..66b61c227 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockTicketService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/WarlockTicketService.java @@ -27,33 +27,36 @@ public interface WarlockTicketService { String encode(@NotNull Term term, @NotNull Duration ttl); /** - * 获取ClientId对应的信息,null为不存在 + * Find Pass Info by ClientId, null if not exist */ @Nullable Pass findPass(@NotNull String clientId); /** - * 获取用户下的发行序号,分为accessToken和code + * Get the next serial number under the user by type + * + * @see Term#TypeAuthorizeCode + * @see Term#TypeAccessToken */ int nextSeq(long uid, int type); /** - * 吊销用户下所有Code或Token + * revoke the code and token under the user */ void revokeAll(long uid); /** - * 获取凭证的过期时间戳 + * Calculate the expired timestamp * - * @param ttl 存活时间 + * @param ttl time to live */ default long calcDue(@NotNull Duration ttl) { return Now.millis() / 1000 + ttl.toSeconds(); } /** - * 当前凭证的序号是否有效,默认不检查,返回true + * Whether the serial number is valid. */ default boolean checkSeq(long uid, int type, int seq) { return seq >= 0; @@ -66,7 +69,7 @@ class Pass { protected String secret = Null.Str; protected Set scopes = Collections.emptySet(); /** - * 302的主机名,不要使用ipv6 + * the hostname of http status code=302, should not use ipv6 */ protected Set hosts = Collections.emptySet(); } @@ -78,14 +81,14 @@ interface Term { int TypeAccessToken = 2; /** - * 能否精确解析,完全匹配 + * Whether it can decode and match exactly. */ default boolean decode(String str) { return decode(str, true); } /** - * 能否成功的以term解析字符串,并赋值 + * Whether the string can decode into Term and its value */ default boolean decode(String str, boolean exactly) { final int size = getSize(); @@ -102,6 +105,9 @@ default boolean decode(String str, boolean exactly) { } + /** + * encode term to string + */ static String encode(Term term) { BarString buff = new BarString(); buff.append(term.getType()); @@ -113,57 +119,64 @@ static String encode(Term term) { } /** - * 包含的字段数 + * Number of field to encode and decode. + * + * @see #encode(Term) + * @see #decode(String) */ - int getSize(); + default int getSize() { + return 5; // type,userId,scope,clientId,sessionId + } /** - * 类别,AuthCode或AccessToken,非enum以备扩展 + * @see #TypeAccessToken + * @see #TypeAuthorizeCode */ int getType(); /** - * 类别,AuthCode或AccessToken,非enum以备扩展 + * @see #TypeAccessToken + * @see #TypeAuthorizeCode */ void setType(int type); /** - * 资源访问者对应的user + * the userid of the resource visitor */ long getUserId(); /** - * 资源访问者对应的user + * the userid of the resource visitor */ void setUserId(long userId); /** - * 资源对应的scope,空格分割,对应于权限 + * the scope of resource, space seperated (corresponds to the permissions) */ String getScopes(); /** - * 资源对应的scope,空格分割,对应于权限 + * the scope of resource, space seperated (corresponds to the permissions) */ void setScopes(String scopes); /** - * 资源访问者的client id,支持一对多的场景 + * the clientId of the resource visitor, Support for one-to-many scenarios */ String getClientId(); /** - * 资源访问者的client id,支持一对多的场景 + * the clientId of the resource visitor, Support for one-to-many scenarios */ void setClientId(String clientId); /** - * 资源拥有者的session,api不需要session + * the sessionId of the resource owner, no session in api */ String getSessionId(); /** - * 资源拥有者的session,api不需要session + * the sessionId of the resource owner, no session in api */ void setSessionId(String sessionId); } @@ -175,10 +188,5 @@ class SimpleTerm implements Term { protected String scopes = Null.Str; protected String clientId = Null.Str; protected String sessionId = Null.Str; - - @Override - public int getSize() { - return 5; - } } } 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/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthAppPermChecker.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthAppPermChecker.java index 3502c9a24..92f0712c2 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthAppPermChecker.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthAppPermChecker.java @@ -11,7 +11,8 @@ import java.util.Set; /** - * 通过 spring.application.name 检查权限。通常用于本机验证 + * Check Permission via spring.application.name. + * Typically used for native authentication * * @author trydofor * @since 2022-01-18 diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthZonePermChecker.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthZonePermChecker.java index 3f93472eb..8b9af8217 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthZonePermChecker.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/AuthZonePermChecker.java @@ -14,7 +14,8 @@ import java.util.Set; /** - * 通过 authZone 参数做权限检查,可做中心检验 + * Check Permission via param `authZone`. + * Can be used for center authentication * * @author trydofor * @since 2022-01-18 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..8ac180268 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; @@ -130,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; @@ -176,13 +145,13 @@ 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); } // ///// public interface AutoReg extends Ordered { /** - * 不需要事务,在外层事务内调用 + * No transaction required, called within an outer transaction. */ Details create(@NotNull Enum authType, String username, WingsAuthDetails details); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthzService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthzService.java index 5898cf715..f0dcd3edf 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthzService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/ComboWarlockAuthzService.java @@ -123,7 +123,7 @@ else if (ro instanceof String) { else if (ro instanceof final GrantedAuthority gt) { final String au = permNormalizer.role(gt.getAuthority()); log.debug("add role by aut={}", au); - auth.put(au, gt); // 以存在值优先 + auth.put(au, gt); // the existed value has high priority roleStr.add(au); } else { @@ -143,17 +143,17 @@ else if (roleStr.contains(str)) { } } - // 递归前移除 + // remove before recursion auth.keySet().removeAll(denyStr); - // 递归找到所有授权Role,N+1操作,直到size不增加 + // recursively find all authed Role, N+1 loop until no size change Set sub = new HashSet<>(roleIds); while (true) { sub = warlockGrantService.entryRole(ROLE, sub).keySet(); int bs = roleIds.size(); roleIds.addAll(sub); if (bs == roleIds.size()) { - // size无变化,说明全遍历 + // no size change, loop done roleIds.removeAll(excIds); break; } @@ -193,7 +193,7 @@ else if (po instanceof String pm) { } else if (po instanceof final GrantedAuthority gt) { final String au = gt.getAuthority(); - auth.put(au, gt); // 以存在值优先 + auth.put(au, gt); // existed value has high priority permStr.add(au); } else { @@ -211,11 +211,11 @@ else if (po instanceof final GrantedAuthority gt) { } } - // 子扩展 + // sub-extension for (String str : permStr) { final Set ps = PermGrantHelper.inheritPerm(str, permAll); for (String s : ps) { - // 去掉`*`权限 + // remove `*` perm if (!s.contains(PermGrantHelper.ALL) && !denyStr.contains(s)) { auth.putIfAbsent(s, new SimpleGrantedAuthority(s)); } @@ -226,26 +226,27 @@ else if (po instanceof final GrantedAuthority gt) { } /** - * 对用户进行组合授权,可直接修改 details 或 增加role,perm,以便统一处理递归。 - * 统一处理包括,①递归载入,扁平化处理,②移除排除项(以`-`开头) + * Apply combo auth to user. e.g. change the details directly or add roles/perm before th unified processing. + * Unified processing includes (1) recursive loading, flattening (2) removing exclusions (starting with `-`). */ public interface Combo extends Ordered { /** - * 为统一处理role和perm准备数据,初步增加或移除。 - * 返回true,为独断式处理,停止后续的其他Combo,如直接指定权限,不需要后续补充,需要主要Order顺序。 + * Prepare data for unified processing of roles and perm, e.g. modify role and perm. + * Return `true` for solo processing, stopping any subsequent Combo (sort by Order), + * such as directly specify the permissions, no subsequent additions, * * @param details details with/without GrantedAuthority * @param role id(Long), name(String) or Auth(GrantedAuthority) * @param perm id(Long), name(String) or Auth(GrantedAuthority) - * @return 是否中断后继续处理,默认false + * @return Whether to stop any subsequent Combo, default false. */ boolean preAuth(@NotNull DefaultWingsUserDetails details, @NotNull HashSet role, @NotNull HashSet perm); /** - * 对统一处理后的权限进行过滤,最终的添加或移除 + * Filter, add or remove auths after Unified processing * * @param details details with/without GrantedAuthority - * @param auths 已经扁平化及排除项的权限 + * @param auths Flattened and exclusions removed permissions */ default void postAuth(@NotNull DefaultWingsUserDetails details, @NotNull HashMap auths) { } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultPermRoleCombo.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultPermRoleCombo.java index 2b9b23206..49717fe62 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultPermRoleCombo.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultPermRoleCombo.java @@ -5,8 +5,8 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; -import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.slardar.security.impl.DefaultWingsUserDetails; +import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.warlock.enums.autogen.GrantType; import pro.fessional.wings.warlock.service.grant.WarlockGrantService; @@ -14,7 +14,7 @@ import java.util.Map; /** - * 通过user和permit的map关系构造 GrantedAuthority + * Create GrantedAuthority by the mapping of user and permit * * @author trydofor * @since 2021-03-05 diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserAuthnAutoReg.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserAuthnAutoReg.java index 61eeee1f4..ca80c13c4 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserAuthnAutoReg.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserAuthnAutoReg.java @@ -8,10 +8,10 @@ import org.springframework.transaction.annotation.Transactional; import pro.fessional.mirana.code.RandCode; import pro.fessional.wings.faceless.service.journal.JournalService; -import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.slardar.context.GlobalAttributeHolder; import pro.fessional.wings.slardar.context.TerminalContext; import pro.fessional.wings.slardar.security.WingsAuthDetails; +import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.warlock.constants.WarlockGlobalAttribute; import pro.fessional.wings.warlock.enums.autogen.UserGender; import pro.fessional.wings.warlock.enums.autogen.UserStatus; @@ -75,7 +75,7 @@ public Details create(@NotNull Enum authType, String username, WingsAuthDetai authn.setFailedCnt(0); authn.setFailedMax(warlockSecurityProp.getAutoregMaxFailed()); - // 明文,有WarlockUserAuthnService加密 + // Plain text, encrypt in WarlockUserAuthnService later. authn.setPassword(RandCode.human(16)); beforeSave(authn, username, details, uid); diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserDetailsCombo.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserDetailsCombo.java index 86176893d..67532fc77 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserDetailsCombo.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/DefaultUserDetailsCombo.java @@ -20,8 +20,6 @@ import java.util.Set; /** - * JustAuth UserDetailsService,不存在用户时,自动创建 - * * @author trydofor * @since 2021-02-22 */ @@ -66,7 +64,7 @@ public final DefaultWingsUserDetails loadOrNull(String username, @NotNull Enum authType, @Nullable } /** - * 是否验证过,默认false - * - * @param authType 类型 - * @return false + * Whether pass the auth, default false */ public boolean authed(Enum authType) { return false; } /** - * 加载信息 + * Load details */ @Nullable public Details doLoad(String username, @NotNull Enum authType, @Nullable WingsAuthDetails authDetail) { diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/MemoryTypedAuthzCombo.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/MemoryTypedAuthzCombo.java index c4d270b1e..9ded0e694 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/MemoryTypedAuthzCombo.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/MemoryTypedAuthzCombo.java @@ -6,8 +6,8 @@ import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import pro.fessional.mirana.data.Null; -import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.slardar.security.impl.DefaultWingsUserDetails; +import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.warlock.service.perm.WarlockPermNormalizer; import java.util.Arrays; @@ -19,8 +19,8 @@ import java.util.concurrent.CopyOnWriteArraySet; /** - * 根据username和authType等对授权进行增减。 - * Role和Perm不进行区分,需要自行指定Role前缀。 + * Add/remove auth by username and authType. + * Role and Perm are not distinguished, and the Role need a fixed prefix. * * @author trydofor * @since 2021-03-05 @@ -39,20 +39,20 @@ public class MemoryTypedAuthzCombo implements ComboWarlockAuthzService.Combo { private final Map, Set>> typedAuthz = new ConcurrentHashMap<>(); /** - * 按userId,授予权限和角色 + * Add Perm/Role to userId * - * @param userId 用户id - * @param authz 权限和角色 + * @param userId user id + * @param authz Perm/Role */ public void addAuthz(long userId, @NotNull String... authz) { addAuthz(userId, Arrays.asList(authz)); } /** - * 按userId,授予权限和角色 + * Add Perm/Role to userId * - * @param userId 用户id - * @param authz 权限和角色 + * @param userId user id + * @param authz Perm/Role */ public void addAuthz(long userId, @NotNull Collection authz) { final Set set = userAuthz.computeIfAbsent(userId, k -> new CopyOnWriteArraySet<>()); @@ -60,42 +60,42 @@ public void addAuthz(long userId, @NotNull Collection authz) { } /** - * 按登录名,授予权限和角色 + * Add Perm/Role to user by username * - * @param username 登录名 - * @param authz 权限和角色 + * @param username login username + * @param authz Perm/Role */ public void addAuthz(@NotNull String username, @NotNull String... authz) { addAuthz(username, Arrays.asList(authz)); } /** - * 按登录名,授予权限和角色 + * Add Perm/Role to user by username * - * @param username 登录名 - * @param authz 权限和角色 + * @param username login username + * @param authz Perm/Role */ public void addAuthz(@NotNull String username, @NotNull Collection authz) { addAuthz(username, null, authz); } /** - * 按登录名和类型,授予权限和角色 + * Add Perm/Role to user by username and authType * - * @param username 登录名 - * @param authType 类型, null时,仅按登录名 - * @param authz 权限和角色 + * @param username login username + * @param authType only username if null + * @param authz Perm/Role */ public void addAuthz(@NotNull String username, Enum authType, @NotNull String... authz) { addAuthz(username, authType, Arrays.asList(authz)); } /** - * 按登录名和类型,授予权限和角色 + * Add Perm/Role to user by username and authType * - * @param username 登录名 - * @param authType 类型, null时,仅按登录名 - * @param authz 权限和角色 + * @param username login username + * @param authType only username if null + * @param authz Perm/Role */ public void addAuthz(@NotNull String username, Enum authType, @NotNull Collection authz) { if (authType == null || authType == Null.Enm) { @@ -110,19 +110,17 @@ public void addAuthz(@NotNull String username, Enum authType, @NotNull Collec } /** - * 按userId,删除所有授权 - * - * @param userId 用户id + * delete all Perm/Role of userId */ public void delAuthz(long userId) { userAuthz.remove(userId); } /** - * 按userId,删除指定授权 + * delete given Perm/Role of userId * - * @param userId 用户id - * @param authz 指定类型 + * @param userId userid + * @param authz given Perm/Role */ public void delAuthz(long userId, @NotNull Collection authz) { final Set set = userAuthz.get(userId); @@ -132,17 +130,17 @@ public void delAuthz(long userId, @NotNull Collection authz) { } /** - * 按登录名,删除所有授权 + * delete all Perm/Role of user by login username */ public void delAuthz(@NotNull String username) { namedAuthz.remove(username); } /** - * 按登录名,删除指定授权 + * delete given Perm/Role of user by login username * - * @param username 登录名 - * @param authz 指定类型 + * @param username login username + * @param authz given Perm/Role */ public void delAuthz(@NotNull String username, @NotNull Collection authz) { final Set set = namedAuthz.get(username); @@ -152,10 +150,10 @@ public void delAuthz(@NotNull String username, @NotNull Collection authz } /** - * 按登录名和类型,删除所有授权 + * delete all Perm/Role of user by login username and authType * - * @param username 登录名 - * @param authType 登录类型, null时,按登录名授权 + * @param username login username + * @param authType only username if null */ public void delAuthz(@NotNull String username, Enum authType) { if (authType == null) { @@ -170,11 +168,11 @@ public void delAuthz(@NotNull String username, Enum authType) { } /** - * 按登录名和类型,删除指定授权 + * delete given Perm/Role of user by login username and authType * - * @param username 登录名 - * @param authType 登录类型, null时,按登录名授权 - * @param authz 指定类型 + * @param username login username + * @param authType only username if null + * @param authz given Perm/Role */ public void delAuthz(@NotNull String username, Enum authType, @NotNull Collection authz) { if (authType == null) { diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/SimpleTicketServiceImpl.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/SimpleTicketServiceImpl.java index 839a23f34..d61340ab1 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/SimpleTicketServiceImpl.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/SimpleTicketServiceImpl.java @@ -14,7 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * 简单实现,实际业务中,建议基于数据库和Hazelcast构建 + * Simple Ticket implement, in product, it is recommended to use database or Hazelcast * * @author trydofor * @since 2022-11-05 @@ -80,7 +80,8 @@ public int nextSeq(long uid, int type) { @Override public boolean checkSeq(long uid, int type, int seq) { if (seq < 0) return false; - // 不存在时,可能应用重启,token未过期,以当前验证通过的合法值设置 + // If it does not exist, the application may have been restarted. + // If the token has not expired, set the current validated value. final int cur = getSeqMap(type) .computeIfAbsent(uid, k -> new AtomicInteger(seq)).get(); 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/auth/impl/WarlockOauthServiceImpl.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/WarlockOauthServiceImpl.java index 96cf4a9ed..d2685398b 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/WarlockOauthServiceImpl.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/auth/impl/WarlockOauthServiceImpl.java @@ -71,7 +71,7 @@ public OAuth authorizeCode(@NotNull String clientId, String scope, String redire private final Pattern scopeSplitter = Pattern.compile("[ ,;]+"); /** - * 检查 scope是否合法 + * Whether the scope is valid */ protected boolean checkScope(Set scopes, String scope) { if (scopes.isEmpty()) return true; diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermNormalizer.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermNormalizer.java index 40cca880f..3efba8e7f 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermNormalizer.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermNormalizer.java @@ -4,7 +4,7 @@ import lombok.Setter; /** - * 加工role,增加prefix + * Normalize role, add some prefix * * @author trydofor * @since 2021-06-08 @@ -17,7 +17,10 @@ public class WarlockPermNormalizer { private String rolePrefix = "ROLE_"; /** - * 标准化role,增加`负项`或`前缀` + * Normalize role, add `DenyPrefix` or `rolePrefix` + * + * @see #DenyPrefix + * @see #getRolePrefix() */ public String role(String name) { int p = name.startsWith(DenyPrefix) ? DenyPrefix.length() : 0; @@ -36,20 +39,20 @@ public String role(String name) { } /** - * 计算`负项`的index, -1表示非排除项 + * Calc the index of `DenyPrefix` role. * - * @param name 角色字符串 - * @return -1表示非排除项。 + * @param name role string + * @return `-1` means no DenyPrefix */ public int indexDenyPrefix(String name) { return name.startsWith(DenyPrefix) ? DenyPrefix.length() : -1; } /** - * 计算`前缀`index + * Cale index of `rolePrefix` * - * @param name 角色字符串 - * @return -1表示无前缀排除项。 + * @param name role string + * @return `-1` means no rolePrefix */ public int indexRolePrefix(String name) { int p = name.startsWith(DenyPrefix) ? DenyPrefix.length() : 0; diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermService.java index 6c6ee0c34..8d59a06bf 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockPermService.java @@ -19,9 +19,9 @@ enum Jane { } /** - * 一次性获得所有id和权限码(scopes + '.' + action) + * Load all Perm id and code (scopes + `.` + action) on time * - * @return 权限码 + * @return map of id and code */ Map loadPermAll(); @@ -32,10 +32,10 @@ class Act { } /** - * 级联创建多个权限 + * Create multiple perm at scopes from actions * - * @param scopes 范围 - * @param acts 动作 + * @param scopes scopes + * @param acts action */ void create(@NotNull String scopes, @NotNull Collection acts); @@ -51,10 +51,10 @@ default void create(@NotNull String scopes, @NotNull String action, @NotNull Str } /** - * 修改权限码备注 + * Modify the remark/comment of Perm * * @param permId id - * @param remark 备注 + * @param remark comment */ void modify(long permId, @NotNull String remark); } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockRoleService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockRoleService.java index be1ba1e22..7bf89ee94 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockRoleService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/perm/WarlockRoleService.java @@ -16,27 +16,26 @@ enum Jane { } /** - * 一次性获得所有id和角色码name,并进行变准化处理 + * Load all Role id and its Normalized code * - * @return 角色码 + * @return map of id and code */ Map loadRoleAll(); /** - * 创建role,如果同名存在则失败 + * Create role, fail if exist the same name * - * @param name 名字 - * @param remark 备注 + * @param name name + * @param remark comment * @return id */ long create(@NotNull String name, String remark); /** - * 修改备注 - * 备注不能同时为空 + * Modify the remark/comment of ROle * * @param roleId id - * @param remark 备注 + * @param remark comment */ void modify(long roleId, String remark); } 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/WarlockUserBasisService.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserBasisService.java index 5dd937740..1723b7de0 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserBasisService.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/service/user/WarlockUserBasisService.java @@ -10,7 +10,7 @@ import java.util.Locale; /** - * 创建和修改基本用户 + * Create or modify basis user * * @author trydofor * @since 2021-02-23 @@ -34,21 +34,15 @@ class Basis { } /** - * 插入用户,并返回Uid。 - * 对应自动为null时,系统设置默认值 - * - * @param user user - * @return userId + * Create a user and return the userId. */ long create(@NotNull Basis user); /** - * 修改用户,只修改不为null的字段。 - * userId不存在未修改失败 + * Modify the user, only change non-null value. + * fail if userId not found. * - * @param userId 对应的userId - * @param user 需要调整的值 - * @throws CodeException 数据不存在 + * @throws CodeException data not exist */ void modify(long userId, @NotNull Basis user); 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..52fd3e1f8 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 @@ -8,7 +8,7 @@ import java.time.LocalDateTime; /** - * 用户登录验证 + * User login and record auth * * @author trydofor * @since 2021-03-25 @@ -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; @@ -25,11 +26,11 @@ class Item { } /** - * 列出用户所有登录信息 + * List all login info of user * - * @param userId 用户 - * @param query 分页 - * @return 登录信息 + * @param userId user id + * @param query page query + * @return login info */ @NotNull PageResult list(long userId, PageQuery query); @@ -38,15 +39,16 @@ class Item { @Data class Auth { private Enum authType; + private String username; private long userId; private String details; private boolean failed; } /** - * 记录登录验证情况 + * Record the auth info * - * @param auth 验证 + * @param auth auth record */ void auth(Auth auth); } 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/WarlockHazelcastConfiguration.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockHazelcastConfiguration.java index 3852b2b07..f5eedfed4 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockHazelcastConfiguration.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockHazelcastConfiguration.java @@ -12,7 +12,6 @@ import pro.fessional.wings.slardar.concur.HazelcastGlobalLock; import pro.fessional.wings.spring.consts.OrderedWarlockConst; import pro.fessional.wings.warlock.spring.prop.WarlockEnabledProp; -import pro.fessional.wings.warlock.spring.prop.WarlockLockProp; /** @@ -29,9 +28,8 @@ public class WarlockHazelcastConfiguration { @Bean @ConditionalOnMissingBean(HazelcastGlobalLock.class) @ConditionalOnProperty(name = WarlockEnabledProp.Key$globalLock, havingValue = "true") - public HazelcastGlobalLock hazelcastGlobalLock(HazelcastInstance hazelcastInstance, WarlockLockProp warlockLockProp) { - final boolean hcp = warlockLockProp.isHazelcastCp(); - log.info("WarlockShadow spring-bean hazelcastGlobalLock, useCpIfSafe=" + hcp); - return new HazelcastGlobalLock(hazelcastInstance, hcp); + public HazelcastGlobalLock hazelcastGlobalLock(HazelcastInstance hazelcastInstance) { + log.info("WarlockShadow spring-bean hazelcastGlobalLock"); + return new HazelcastGlobalLock(hazelcastInstance); } } diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockJustAuthConfiguration.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockJustAuthConfiguration.java index c12e27989..3379a09a0 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockJustAuthConfiguration.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockJustAuthConfiguration.java @@ -82,7 +82,7 @@ public JustAuthRequestBuilder justAuthRequestBuilder(AuthStateCache cache, AuthS log.info("WarlockShadow conf justAuthRequestFactory auth-type " + k + ", proxy=" + hc.getProxyType()); } - // 处理动态redirect-uri + // handle dynamic redirect-uri map.put(em, AuthConfigWrapper.tryWrap(ac, justAuthProp.getSafeHost())); } 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..bba1c4958 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; @@ -190,7 +191,6 @@ public JustAuthUserAuthnAutoReg justAuthUserAuthnAutoReg() { @Bean @ConditionalOnMissingBean(DefaultUserAuthnAutoReg.class) public DefaultUserAuthnAutoReg defaultUserAuthnAutoReg() { - // 存在子类,则不需要此bean,如JustAuthUserAuthnAutoReg log.info("WarlockShadow spring-bean defaultUserAuthnAutoReg"); return new DefaultUserAuthnAutoReg(); } @@ -198,7 +198,6 @@ public DefaultUserAuthnAutoReg defaultUserAuthnAutoReg() { @Bean @ConditionalOnMissingBean(WarlockGrantService.class) public WarlockGrantService warlockGrantService() { - // 存在子类,则不需要此bean,如JustAuthUserAuthnAutoReg log.info("WarlockShadow spring-bean WarlockGrantServiceDummy"); return new WarlockGrantServiceDummy(); } @@ -382,7 +381,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 +397,7 @@ public WingsAuthDetailsSource wingsAuthDetailsSource(ObjectProvider { - conf.loginPage(securityProp.getLoginPage()) // 初始authenticationEntryPoint,无权限时返回的页面。 - // 初始filter.RequestMatcher - .loginProcessingUrl(securityProp.getLoginProcUrl(), securityProp.getLoginProcMethod()) // filter处理,不需要controller - .loginForward(securityProp.isLoginForward()) // 无权限时返回的页面, + conf.loginPage(securityProp.getLoginPage()) // init authenticationEntryPoint, 401 page + // init filter.RequestMatcher + .loginProcessingUrl(securityProp.getLoginProcUrl(), securityProp.getLoginProcMethod()) // by filter,no controller + .loginForward(securityProp.isLoginForward()) // forward or redirect .usernameParameter(securityProp.getUsernamePara()) .passwordParameter(securityProp.getPasswordPara()) .authenticationDetailsSource(authDetailSource) diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockDangerProp.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockDangerProp.java new file mode 100644 index 000000000..a8fc7b776 --- /dev/null +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockDangerProp.java @@ -0,0 +1,53 @@ +package pro.fessional.wings.warlock.spring.prop; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.time.Duration; + +/** + * wings-warlock-ticket-77.properties + * + * @author trydofor + * @see #Key + * @since 2021-02-17 + */ +@Data +@ConfigurationProperties(WarlockDangerProp.Key) +public class WarlockDangerProp { + + public static final String Key = "wings.warlock.danger"; + + /** + * Whether to switch the account status to danger when the maximum failure is reached. + * + * @see #Key$maxFailure + */ + private boolean maxFailure = true; + public static final String Key$maxFailure = Key + ".max-failure"; + + /** + * Retry interval when bad badCredentials. + * + * @see #Key$retryStep + */ + private Duration retryStep = Duration.ofSeconds(5); + public static final String Key$retryStep = Key + ".retry-step"; + + /** + * cache size for danger + * + * @see #Key$cacheSize + */ + private int cacheSize = 10_000; + public static final String Key$cacheSize = Key + ".cache-size"; + + /** + * cache ttl for danger + * + * @see #Key$cacheTtl + */ + private Duration cacheTtl = Duration.ofSeconds(300); + public static final String Key$cacheTtl = Key + ".cache-ttl"; + +} diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockJustAuthProp.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockJustAuthProp.java index 034929330..e697a8f0f 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockJustAuthProp.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockJustAuthProp.java @@ -1,20 +1,3 @@ -/* - * Copyright (c) 2019-2029, xkcoding & Yangkai.Shen & 沈扬凯 (237497819@qq.com & xkcoding.com). - *

    - * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

    - * http://www.gnu.org/licenses/lgpl.html - *

    - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - package pro.fessional.wings.warlock.spring.prop; import lombok.Data; diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockLockProp.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockLockProp.java deleted file mode 100644 index 2165b98dd..000000000 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockLockProp.java +++ /dev/null @@ -1,26 +0,0 @@ -package pro.fessional.wings.warlock.spring.prop; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * wings-warlock-lock-77.properties - * - * @author trydofor - * @see #Key - * @since 2021-02-17 - */ -@Data -@ConfigurationProperties(WarlockLockProp.Key) -public class WarlockLockProp { - - public static final String Key = "wings.warlock.lock"; - - /** - * whether to use useCpIfSafe in hazelcast GlobalLock. - * - * @see #Key$hazelcastCp - */ - private boolean hazelcastCp = true; - public static final String Key$hazelcastCp = Key + ".hazelcast-cp"; -} diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java index 807a3c17e..617e9a5d6 100644 --- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java +++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/prop/WarlockSecurityProp.java @@ -193,7 +193,7 @@ public class WarlockSecurityProp { public static final String Key$rolePrefix = Key + ".role-prefix"; /** - * ①ignored items, antMatcher, no need of SecurityFilter, such as static resources. + * (1) ignored items, antMatcher, no need of SecurityFilter, such as static resources. * * @see #Key$webIgnore */ @@ -201,7 +201,7 @@ public class WarlockSecurityProp { public static final String Key$webIgnore = Key + ".web-ignore"; /** - * ②allow all, `Map`, antMatcher. + * (2) allow all, `Map`, antMatcher. * * @see #Key$permitAll */ @@ -209,7 +209,7 @@ public class WarlockSecurityProp { public static final String Key$permitAll = Key + ".permit-all"; /** - * ③authed only, antMatcher. + * (3) authed only, antMatcher. * * @see #Key$authenticated */ @@ -217,7 +217,7 @@ public class WarlockSecurityProp { public static final String Key$authenticated = Key + ".authenticated"; /** - * ④has authority, antMatcher. + * (4) has authority, antMatcher. * merge authority by URL grouping, and finally set the URL in reverse ASCII order, * i.e., the English number comes before the `*`, and the loose rule comes after. * @@ -228,7 +228,7 @@ public class WarlockSecurityProp { /** *

    -     * ⑤defaults, `String`, support the followings.
    +     * (5) defaults, `String`, support the followings.
          * - `permitAll`|`authenticated`|`anonymous`|`fullyAuthenticated`
          * - any non-empty, non-above string, considered as `Authority`, use `comma` or `blank` to separate multiple ones.
          * 
    @@ -329,7 +329,7 @@ public class WarlockSecurityProp { *
          * Configure memory user, usually used for special user login.
          * - key is the description, override if duplicate, suggest `username`+(`/`+`auth-type`)?
    -     * - auth-type=`∅`, to match all auth-type.
    +     * - `auth-type=`, to match all auth-type.
          * - For other settings, see WarlockAuthnService.Details and its defaults.
          * 
    * diff --git a/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-danger-77.properties b/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-danger-77.properties new file mode 100644 index 000000000..6c7ac118d --- /dev/null +++ b/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-danger-77.properties @@ -0,0 +1,11 @@ +## Authn Danger Strategy +## Whether to switch the account status to danger when the maximum failure is reached. +wings.warlock.danger.max-failure=true +## Retry interval when bad badCredentials. +wings.warlock.danger.retry-step=5s + +## cache size for danger +wings.warlock.danger.block-size=10000 +## cache ttl for danger +wings.warlock.danger.block-ttl=300s + diff --git a/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-lock-77.properties b/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-lock-77.properties deleted file mode 100644 index 17fcdb47d..000000000 --- a/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-lock-77.properties +++ /dev/null @@ -1,2 +0,0 @@ -## whether to use useCpIfSafe in hazelcast GlobalLock. -wings.warlock.lock.hazelcast-cp=true diff --git a/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties b/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties index 91f08c2f5..e481c2aab 100644 --- a/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties +++ b/wings/warlock-shadow/src/main/resources/wings-conf/wings-warlock-security-77.properties @@ -53,29 +53,29 @@ wings.warlock.security.role-prefix=ROLE_ ## `webIgnore` > `PermitAll` > `Authenticated` > `Authority` > `AnyRequest` at the end. ## if value is `-` or `empty`, means ignore this key. -## ①ignored items, antMatcher, no need of SecurityFilter, such as static resources. +## (1) ignored items, antMatcher, no need of SecurityFilter, such as static resources. ## https://github.com/spring-projects/spring-security/issues/10938 wings.warlock.security.web-ignore[assets]=/assets/** wings.warlock.security.web-ignore[webjars]=/webjars/** wings.warlock.security.web-ignore[swagger-ui]=/swagger-ui/** wings.warlock.security.web-ignore[swagger-api]=/v3/api-docs/** -## ②allow all, antMatcher. +## (2) allow all, antMatcher. wings.warlock.security.permit-all[error]=/error wings.warlock.security.permit-all[auth]=/auth/** wings.warlock.security.permit-all[oauth]=/oauth/** wings.warlock.security.permit-all[api]=/api/** wings.warlock.security.permit-all[test]=/test/** -## ③authed only, antMatcher. +## (3) authed only, antMatcher. wings.warlock.security.authenticated[user]=/user/** -## ④has authority, antMatcher. +## (4) has authority, antMatcher. ## merge authority by URL grouping, and finally set the URL in reverse ASCII order, ## i.e., the English number comes before the `*`, and the loose rule comes after. wings.warlock.security.authority[ROLE_ACTUATOR]=/actuator/** -## ⑤defaults, `String`, support the followings. +## (5) defaults, `String`, support the followings. ## * `permitAll`|`authenticated`|`anonymous`|`fullyAuthenticated` ## * any non-empty, non-above string, considered as `Authority`, use `comma` or `blank` to separate multiple ones. wings.warlock.security.any-request= @@ -116,7 +116,7 @@ wings.warlock.security.autoreg-expired=3652D ## Configure memory user, usually used for special user login. ## * key is the description, override if duplicate, suggest `username`+(`/`+`auth-type`)? -## * auth-type=`∅`, to match all auth-type. +## * `auth-type=`, to match all auth-type. ## * For other settings, see WarlockAuthnService.Details and its defaults. #wings.warlock.security.mem-user[root].auth-type= #wings.warlock.security.mem-user[root].username= diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/ApiAuthControllerTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/ApiAuthControllerTest.java index 3d8a11029..aca81aef1 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/ApiAuthControllerTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/api/ApiAuthControllerTest.java @@ -77,7 +77,7 @@ class ApiAuthControllerTest { private final String client = "wings-trydofor"; private final String secret = "wings-trydofor-secret"; - // 编码后的值 %7B%22try%22%3A%20%22dofor%22%7D + // after encode %7B%22try%22%3A%20%22dofor%22%7D private final String jsonBody = "{\"try\": \"dofor\"}"; private final String fileKey = "file1"; private final String fileSum = fileKey + ".sum"; 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/SimpleOauthControllerTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/auth/SimpleOauthControllerTest.java index c6275fa36..dc905d875 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/auth/SimpleOauthControllerTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/controller/auth/SimpleOauthControllerTest.java @@ -62,7 +62,7 @@ void authorizationCode() throws Exception { Assertions.assertEquals(state, json0.getString(WarlockOauthService.State)); final String code = json0.getString(WarlockOauthService.Code); - // scopes 是否正确 + // Whether scopes is correct Assertions.assertNotNull(code); final String code1 = accessToken(clientId, clientSecret, code, null); 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..dd65b61c4 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; @@ -68,10 +70,12 @@ public Set listAllHold() { } /** - * 需要设置 @Parameter(hidden = true) + * Need to set @Parameter(hidden = true) */ @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..158d1a450 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"); @@ -75,6 +93,6 @@ public R watching() throws InterruptedException { watchingService.asyncAwait(); watchingService.normalFetch(); watchingService.errorFetch(); - return R.ok(); + return R.OK; } } diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Md5HmacSha256Test.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Md5HmacSha256Test.java index 2e5a6ebaf..66becc76e 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Md5HmacSha256Test.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/other/Md5HmacSha256Test.java @@ -19,8 +19,8 @@ public class Md5HmacSha256Test { @Test public void postJson() { final TreeMap queryString = new TreeMap<>(); - queryString.put("query", "string"); // 普通参数 - queryString.put("null", null); // 忽略null + queryString.put("query", "string"); // normal param + queryString.put("null", null); // ignore null final String para = queryString .entrySet().stream() @@ -28,14 +28,14 @@ public void postJson() { .map(e -> e.getKey() + '=' + e.getValue()) .reduce((s1, s2) -> s1 + '&' + s2) .orElse(""); - // 字典序,忽略null + // ascii order, ignore null assertEquals("query=string", para); final String secret = "高密级"; final String body = "{\"try\":\"dofor\"}"; - final long timestamp = 1668167709172L; // 时间戳,有则签名 + final long timestamp = 1668167709172L; // signature with timestamp if exists - // 直接拼接字符串 + // concat string final String signData = para + body + secret + timestamp; assertEquals("query=string{\"try\":\"dofor\"}高密级1668167709172", signData); // echo -n 'query=string{"try":"dofor"}高密级1668167709172' > trydofor.txt @@ -60,9 +60,9 @@ public void postJson() { @Test public void postFile() { final TreeMap queryString = new TreeMap<>(); - queryString.put("query", "string"); // 普通参数 - queryString.put("null", null); // 忽略null - queryString.put("file1.sum", "EE048AF1B8AB675654DDB522F6575909"); // 文件签名 + queryString.put("query", "string"); // normal param + queryString.put("null", null); // ignore null + queryString.put("file1.sum", "EE048AF1B8AB675654DDB522F6575909"); // file signature final String para = queryString .entrySet().stream() @@ -70,13 +70,13 @@ public void postFile() { .map(e -> e.getKey() + '=' + e.getValue()) .reduce((s1, s2) -> s1 + '&' + s2) .orElse(""); - // 字典序,忽略null + // ascii order, ignore null assertEquals("file1.sum=EE048AF1B8AB675654DDB522F6575909&query=string", para); final String secret = "高密级"; - final long timestamp = 1668167709172L; // 时间戳,有则签名 + final long timestamp = 1668167709172L; // signature with timestamp if exists - // 直接拼接字符串 + // concat string final String signData = para + secret + timestamp; assertEquals("file1.sum=EE048AF1B8AB675654DDB522F6575909&query=string高密级1668167709172", signData); // echo -n 'file1.sum=EE048AF1B8AB675654DDB522F6575909&query=string高密级1668167709172' > goodman.txt 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..21de87295 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; - /** - * 查看日志输出 + * Check the log */ @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()); @@ -52,6 +50,6 @@ public void testWatching() { Assertions.assertTrue(del); // async in async task pool Assertions.assertTrue(2 <= WatchingService.AsyncWatch.size()); - Assertions.assertTrue(WatchingService.WatchOwner.getWatches().isEmpty(),"需要先初始化数据库 Warlock1SchemaCreator#init0Schema"); + Assertions.assertTrue(WatchingService.WatchOwner.getWatches().isEmpty(),"Need init database by Warlock1SchemaCreator#init0Schema"); } } 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\"")); } } diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/NonceLoginTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/NonceLoginTest.java index 7e9371f4c..a46e187b7 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/NonceLoginTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/security/NonceLoginTest.java @@ -35,12 +35,12 @@ public void testTestNyLogin() { final Response r1 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/console-nonce.json?username=test_ny"), false); String nonce = OkHttpClientHelper.extractString(r1, false); log.warn("get nonce for test_ny, nonce=" + nonce); - Assertions.assertEquals(200, r1.code(), "如果失败,单独执行,排除Event干扰"); + Assertions.assertEquals(200, r1.code(), "run it separately to exclude Event interference"); final Response r2 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/username/login.json?username=test_ny&password=" + nonce), false); String login = OkHttpClientHelper.extractString(r2, false); log.warn("get login res = " + login); - Assertions.assertTrue(login.contains("true"), "如果失败,单独执行,排除Event干扰"); + Assertions.assertTrue(login.contains("true"), "run it separately to exclude Event interference"); final Response r4 = OkHttpClientHelper.execute(okHttpClient, new Request.Builder().url(host + "/auth/list-zoneid.json"), false); String zones = OkHttpClientHelper.extractString(r4, false); diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/auth/PasswordEncoderTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/auth/PasswordEncoderTest.java index 04fc1a533..b51be1855 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/auth/PasswordEncoderTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/auth/PasswordEncoderTest.java @@ -26,7 +26,7 @@ class PasswordEncoderTest { private PasssaltEncoder passsaltEncoder; @Test - @Disabled("手动设置密码,控制台输出密文") + @Disabled("Output password to the log") void printPassword() { final String md5h = Md5.sum("moilioncircle"); log.info("md5={}", md5h); diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/AllLightIdProviderPerformTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/AllLightIdProviderPerformTest.java new file mode 100644 index 000000000..a65db73c0 --- /dev/null +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/AllLightIdProviderPerformTest.java @@ -0,0 +1,71 @@ +package pro.fessional.wings.warlock.service.lightid.impl; + +import com.hazelcast.core.HazelcastInstance; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import pro.fessional.mirana.id.LightIdBufferedProvider; +import pro.fessional.mirana.id.LightIdProvider; +import pro.fessional.wings.faceless.service.lightid.impl.BlockingLightIdProvider; +import pro.fessional.wings.slardar.service.lightid.HazelcastLightIdProvider; + +/** + * @author trydofor + * @since 2023-07-18 + */ +@SpringBootTest(properties = { + "debug=false", + "logging.level.root=WARN" +}) +@Slf4j +@Disabled("benchmark") +public class AllLightIdProviderPerformTest { + + @Setter(onMethod_ = {@Autowired}) + private LightIdProvider.Loader lightIdLoader; + @Setter(onMethod_ = {@Autowired}) + private HazelcastInstance hazelcastInstance; + + @Test + public void testJvm() { + // avg=0.039ms + test(new LightIdBufferedProvider(lightIdLoader), 1000); + } + + @Test + public void testHz() { + // avg=1.065ms + test(new HazelcastLightIdProvider(lightIdLoader, hazelcastInstance), 1000); + } + + @Test + public void testDb() { + // avg=10.723ms, 5ms per sql + test(new BlockingLightIdProvider(lightIdLoader), 1000); + } + + public void test(LightIdProvider provider, int count) { + final String key = "sys_commit_journal"; + final int blk = 0; + long pre = provider.next(key, blk); + + final long now = System.nanoTime(); + for (int i = 0; i < count; i++) { + final long id = provider.next(key, blk, 2000); + Assertions.assertEquals(pre + 1, id, "ID's are not continuous increment"); + pre = id; + } + long cost = System.nanoTime() - now; + + final String str = String.format("provider=%s, count=%,d, cost=%,.3fms, avg=%,.3fms", + provider.getClass().getSimpleName(), + count, + cost * 1D / 1_000_000, + cost * 1D / (count * 1_000_000)); + log.warn(str); + } +} diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/DbLightIdProviderTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/DbLightIdProviderTest.java new file mode 100644 index 000000000..2d1dce93d --- /dev/null +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/DbLightIdProviderTest.java @@ -0,0 +1,27 @@ +package pro.fessional.wings.warlock.service.lightid.impl; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +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.id.LightIdProvider; +import pro.fessional.wings.faceless.service.lightid.impl.BlockingLightIdProvider; + +/** + * @author trydofor + * @since 2023-07-18 + */ +@SpringBootTest(properties = "wings.faceless.lightid.provider.monotonic=db") +@Slf4j +public class DbLightIdProviderTest { + + @Setter(onMethod_ = {@Autowired}) + protected LightIdProvider lightIdProvider; + + @Test + public void test() { + Assertions.assertInstanceOf(BlockingLightIdProvider.class, lightIdProvider); + } +} diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/HzLightIdProviderTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/HzLightIdProviderTest.java new file mode 100644 index 000000000..7144b699d --- /dev/null +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/HzLightIdProviderTest.java @@ -0,0 +1,26 @@ +package pro.fessional.wings.warlock.service.lightid.impl; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +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.id.LightIdProvider; +import pro.fessional.wings.slardar.service.lightid.HazelcastLightIdProvider; + +/** + * @author trydofor + * @since 2023-07-18 + */ +@SpringBootTest(properties = "wings.faceless.lightid.provider.monotonic=hz") +@Slf4j +public class HzLightIdProviderTest { + @Setter(onMethod_ = {@Autowired}) + protected LightIdProvider lightIdProvider; + + @Test + public void test() { + Assertions.assertInstanceOf(HazelcastLightIdProvider.class, lightIdProvider); + } +} diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/JvmLightIdProviderTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/JvmLightIdProviderTest.java new file mode 100644 index 000000000..7c89da9fe --- /dev/null +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/lightid/impl/JvmLightIdProviderTest.java @@ -0,0 +1,27 @@ +package pro.fessional.wings.warlock.service.lightid.impl; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +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.id.LightIdBufferedProvider; +import pro.fessional.mirana.id.LightIdProvider; + +/** + * @author trydofor + * @since 2023-07-18 + */ +@SpringBootTest(properties = "wings.faceless.lightid.provider.monotonic=jvm") +@Slf4j +public class JvmLightIdProviderTest { + + @Setter(onMethod_ = {@Autowired}) + protected LightIdProvider lightIdProvider; + + @Test + public void test() { + Assertions.assertInstanceOf(LightIdBufferedProvider.class, lightIdProvider); + } +} diff --git a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermCacheListenerTest.java b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermCacheListenerTest.java index 6c685cb46..07dfec4e0 100644 --- a/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermCacheListenerTest.java +++ b/wings/warlock-shadow/src/test/java/pro/fessional/wings/warlock/service/perm/WarlockPermCacheListenerTest.java @@ -21,23 +21,23 @@ class WarlockPermCacheListenerTest { private WarlockRoleService warlockRoleService; @Test - @Disabled("模拟慢处理,观察缓存变化") + @Disabled("Simulate slow processing and observe cache changes") void cleanCache() throws InterruptedException { - log.warn("无缓存,从数据库加载Perm"); + log.warn("No cache, select Perm from Db"); warlockPermServer.loadPermAll(); - log.warn("无缓存,从数据库加载Role"); + log.warn("No cache, select Role from Db"); warlockRoleService.loadRoleAll(); - log.warn("有缓存,无任何数据库"); + log.warn("Cache, no Db select"); warlockPermServer.loadPermAll(); warlockRoleService.loadRoleAll(); - log.warn("修改Perm=1,触发jooq CUD事件"); + log.warn("Modify Perm=1, trigger jooq CUD event"); warlockPermServer.modify(1, "test cleanCache"); - log.info("睡眠3秒,等待async事件"); + log.info("Sleep 3s, Wait async event"); Thread.sleep(3000); - log.warn("无缓存,从数据库加载Perm"); + log.warn("No cache, select Perm from Db"); warlockPermServer.loadPermAll(); - log.warn("有缓存,不从数据库加载Role"); + log.warn("Cache, no Db select"); warlockRoleService.loadRoleAll(); - log.warn("查看日志"); + log.warn("Check the log"); } } 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..70ff177cb 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,8 +68,8 @@ public void asyncWatch() { log.warn("AsyncWatch={}", WatchOwner); asyncLatch.countDown(); asyncLatch = new CountDownLatch(1); - try (Watch w0 = stopWatch.start("AsyncWatch.fetchLatch")) { - fetchLatch.await(); // 等待sql执行,交叉时间线 + try (Watch ignored = stopWatch.start("AsyncWatch.fetchLatch")) { + fetchLatch.await(); // wait for sql executing, cross timeline } catch (InterruptedException e) { DummyBlock.ignore(e); diff --git a/wings/warlock-test/src/main/resources/application.properties b/wings/warlock-test/src/main/resources/application.properties index 739107d8a..861d343d3 100644 --- a/wings/warlock-test/src/main/resources/application.properties +++ b/wings/warlock-test/src/main/resources/application.properties @@ -3,11 +3,8 @@ spring.application.name=wings-warlock debug=true #logging.level.root=debug -# 需要任意权限的路径,antMatcher,逗号分隔,斜杠换行 wings.warlock.security.authority[ROLE_ADMIN]=/admin/** -# 需要登录的路径,antMatcher,逗号分隔,斜杠换行 wings.warlock.security.authenticated[user]=/user/** -# 支持自动注册用户的验证类型 wings.warlock.security.autoreg-auth-type=github,weibo diff --git a/wings/warlock-test/src/main/resources/wings-conf/wings-warlock-user-77.properties b/wings/warlock-test/src/main/resources/wings-conf/wings-warlock-user-77.properties index fac2791f6..bee2638f6 100644 --- a/wings/warlock-test/src/main/resources/wings-conf/wings-warlock-user-77.properties +++ b/wings/warlock-test/src/main/resources/wings-conf/wings-warlock-user-77.properties @@ -1,6 +1,6 @@ -# 内存用户,key用户说明,重复时覆盖,建议为`username`+[`/`+`auth-type`] -# auth-type=""时,为匹配全部auth-type -# 其他设置,参考WarlockAuthnService.Details 的类型及默认值 +## memory user, key is user description, replace if same, recommend `username`+[`/`+`auth-type`] +## auth-type="" to match any auth-type +## see WarlockAuthnService.Details for type and default value wings.warlock.security.mem-user[trydofor/username].auth-type=username wings.warlock.security.mem-user[trydofor/username].username=trydofor @@ -19,16 +19,15 @@ wings.warlock.security.mem-user[test_ny/username].user-id=99 wings.warlock.security.mem-user[test_ny/username].locale=en_US wings.warlock.security.mem-user[test_ny/username].zoneId=America/New_York -# 内存用户权限,key授权说明,重复时覆盖,建议以类型和用途 wings.warlock.security.mem-auth[trydofor/uid].user-id=79 wings.warlock.security.mem-auth[trydofor/uid].auth-role=SYSTEM,ADMIN wings.warlock.security.mem-auth[trydofor/uid].auth-perm=user-perm -# 增加角色 +# add role wings.warlock.security.mem-auth[role_admin/test_ny].user-id=99 wings.warlock.security.mem-auth[role_admin/test_ny].auth-role=ADMIN -# 以email登录,减少和增加 +# email login, add/remove perm wings.warlock.security.mem-auth[trydofor/email].auth-type=email wings.warlock.security.mem-auth[trydofor/email].username=trydofor@qq.com wings.warlock.security.mem-auth[trydofor/email].auth-role=-SYSTEM 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 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..c9e919fcc 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 @@ -8,7 +8,7 @@ import static java.util.Collections.singletonList; /** - * 统一注册和管理缓存的信息,包括类和方法 + * Unified registration and management of cached information, including classes and methods * * @author trydofor * @since 2022-04-20 @@ -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/WarlockCallbackEvent.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/WarlockCallbackEvent.java index 8c445a3be..6a441001a 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/WarlockCallbackEvent.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/WarlockCallbackEvent.java @@ -1,7 +1,7 @@ package pro.fessional.wings.warlock.event; /** - * 事件中包括了callback的引用,不一定可序列化 + * The event includes a reference to the callback, which may not serializable * * @author trydofor * @since 2021-02-27 diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/WarlockMetadataEvent.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/WarlockMetadataEvent.java index b7e751346..4452bae89 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/WarlockMetadataEvent.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/event/WarlockMetadataEvent.java @@ -3,7 +3,7 @@ import java.io.Serializable; /** - * 纯数据事件,可以被序列化 + * Plain data events that can be serialized * * @author trydofor * @since 2021-02-27 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/TableChangePublisher.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/event/TableChangePublisher.java index 70f0efb54..0923c4f7e 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/event/TableChangePublisher.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/event/TableChangePublisher.java @@ -12,10 +12,12 @@ import java.util.Map; import static java.util.Collections.singletonMap; -import static pro.fessional.wings.warlock.event.cache.TableChangeEvent.*; +import static pro.fessional.wings.warlock.event.cache.TableChangeEvent.DELETE; +import static pro.fessional.wings.warlock.event.cache.TableChangeEvent.INSERT; +import static pro.fessional.wings.warlock.event.cache.TableChangeEvent.UPDATE; /** - * 发送表记录变更(insert, update, delete)事件(默认并建议异步) + * Publish table record change (insert, update, delete) events (default and recommended asynchronous) * * @author trydofor * @since 2021-06-10 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); } } } diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/grant/PermGrantHelper.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/grant/PermGrantHelper.java index 00ab6359d..b610b2c93 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/grant/PermGrantHelper.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/grant/PermGrantHelper.java @@ -10,9 +10,11 @@ import java.util.stream.Collectors; /** - * 根权限是空,这样unite之后是 `.*`。 - * inherit指,使用`.`分隔的scope,存在隐式的继承关系。 - * grant值,绑定授权。 + *
    + * Root permission is empty, so the unite result is followed by `. *`.
    + * `inherit` means the scopes separated by `.` is an implicit inheritance relationship.
    + * `grant` means binding authorization.
    + * 
    * * @author trydofor * @since 2021-03-07 @@ -41,25 +43,23 @@ else if (p == 0) { } /** - * top 是否包括了 sub. - * `*` 包括所有,同名的action作比较 + * Whether the `top` include `sub` (sub inherit top, case-insensitive) + * `*` means all, Compare actions with the same name * - * @param top system.* - * @param sub system.menu.read - * @return 包含 + * @param top e.g. `system.*` + * @param sub e.g. `system.menu.read` */ public static boolean canInherit(String top, String sub) { return canInherit(top, sub, SPL); } /** - * 不区分大小写,top 是否包括了 sub. - * `*` 包括所有,同名的action作比较 + * Whether the `top` include `sub` (sub inherit top, case-insensitive) + * `*` means all, Compare actions with the same name * - * @param top system.* - * @param sub system.menu.read - * @param spl 分隔符,默认`.` - * @return 包含 + * @param top e.g. `system.*` + * @param sub e.g. `system.menu.read` + * @param spl separator, default `.` */ public static boolean canInherit(String top, String sub, String spl) { if (top == null || sub == null) return false; @@ -93,11 +93,11 @@ public static boolean canInherit(String top, String sub, String spl) { } /** - * 通过id,获得继承的权限码,system.menu 继承于 system + * Get the inherited permission by code. e.g. `system.menu` inherits from `system` * - * @param permCode code - * @param permAll 所有权限id-code - * @return 包括当前id的继承权限码 + * @param permCode top perm code + * @param permAll all perms id-code map + * @return inherited perms */ @NotNull public static Set inheritPerm(String permCode, Map permAll) { @@ -109,11 +109,11 @@ public static Set inheritPerm(String permCode, Map permAll } /** - * 通过id,获得继承的权限码,system.menu 继承于 system + * Get the inherited permission by id. e.g. `system.menu` inherits from `system` * - * @param permId id - * @param permAll 所有权限id-code - * @return 包括当前id的继承权限码 + * @param permId top perm id + * @param permAll all perms id-code map + * @return inherited perms */ @NotNull public static Set inheritPerm(long permId, Map permAll) { @@ -121,12 +121,12 @@ public static Set inheritPerm(long permId, Map permAll) { } /** - * 通过id,获得授权的角色 + * Get authorized roles by id * - * @param roleId role - * @param roleAll 所有角色id-name - * @param roleGrant 拥有权限继承id-Set[id] - * @return 包括当前id的继承角色码 + * @param roleId top role id + * @param roleAll all roles id-name map + * @param roleGrant id-Set[id] map with inheritance + * @return inherited roles */ @NotNull public static Set grantRole(long roleId, Map roleAll, Map> roleGrant) { diff --git a/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/other/TerminalJournalService.java b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/other/TerminalJournalService.java index 64801310f..6f9358a97 100644 --- a/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/other/TerminalJournalService.java +++ b/wings/warlock/src/main/java/pro/fessional/wings/warlock/service/other/TerminalJournalService.java @@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import pro.fessional.mirana.text.JsonTemplate; import pro.fessional.wings.faceless.database.manual.single.modify.commitjournal.CommitJournalModify; import pro.fessional.wings.faceless.service.journal.impl.DefaultJournalService; import pro.fessional.wings.faceless.service.lightid.BlockIdProvider; @@ -27,8 +28,14 @@ public TerminalJournalService(LightIdService ids, BlockIdProvider bid, CommitJou public @NotNull R submit(@NotNull String eventName, @Nullable String loginInfo, @Nullable String targetKey, @Nullable String otherInfo, @NotNull Function commitSet) { if (loginInfo == null || loginInfo.isEmpty()) { final TerminalContext.Context ctx = TerminalContext.get(false); - if (ctx.asLogin()) { - loginInfo = ctx.toString(); + if (!ctx.isNull()) { + loginInfo = JsonTemplate.obj(obj -> { + obj.putVal("userId", ctx.getUserId()); + obj.putVal("locale", ctx.getLocale().toLanguageTag()); + obj.putVal("zoneid", ctx.getZoneId().getId()); + obj.putVal("authType", ctx.getAuthType().name()); + obj.putVal("username", ctx.getUsername()); + }); } } return super.submit(eventName, loginInfo, targetKey, otherInfo, commitSet); diff --git a/wings/warlock/src/main/resources/wings-i18n/common-error.properties b/wings/warlock/src/main/resources/wings-i18n/common-error.properties index b30512cb8..6d863e410 100644 --- a/wings/warlock/src/main/resources/wings-i18n/common-error.properties +++ b/wings/warlock/src/main/resources/wings-i18n/common-error.properties @@ -2,8 +2,8 @@ error.common.assert.empty={0} should NOT empty error.common.assert.format={0} bad format error.common.assert.notfound={0} not found error.common.assert.existed={0} existed -error.common.assert.state=bad state,name={0}, value={1} -error.common.assert.param=bad parameter,name={0}, value={1} +error.common.assert.state=bad state, name={0}, value={1} +error.common.assert.param=bad parameter, name={0}, value={1} error.common.data.notfound=data not found error.common.data.existed=data existed 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..5987dee66 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 @@ -26,8 +26,8 @@ void to() { JooqJournalDiffConverter c = new JooqJournalDiffConverter(); 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")); + // Type missing + d0.setValue1(List.of("10086", "trydofor", "3.14", "2022-10-24 12:34:56")); final JournalDiff d1 = c.from(s); Assertions.assertEquals(d0, d1); } diff --git a/wings/warlock/src/test/resources/wings-conf/wings-warlock-cud-77.properties b/wings/warlock/src/test/resources/wings-conf/wings-warlock-cud-77.properties index 99268b6cc..9e7fdbd92 100644 --- a/wings/warlock/src/test/resources/wings-conf/wings-warlock-cud-77.properties +++ b/wings/warlock/src/test/resources/wings-conf/wings-warlock-cud-77.properties @@ -1,2 +1,2 @@ -# cud 关系的表及字段,区分大小写 +## cud mapping wings.faceless.jooq.cud.table[win_user_login]=id 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 @@