{"id":21378141,"url":"https://github.com/honhimw/i-spring-enhance","last_synced_at":"2026-05-08T11:33:16.394Z","repository":{"id":251189903,"uuid":"836671913","full_name":"honhimW/i-spring-enhance","owner":"honhimW","description":"My Spring-Boot application extended configuration","archived":false,"fork":false,"pushed_at":"2025-01-22T07:11:11.000Z","size":596,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-22T08:22:31.266Z","etag":null,"topics":["access-control-list","event-driven","finite-state-machine","jpa-hibernate","multi-release","no-aop","redis","spring-boot","utilities","webflux"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/honhimW.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-01T10:13:32.000Z","updated_at":"2025-01-22T07:11:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"ab1d21fb-7027-4a61-a35a-07763bd2eafb","html_url":"https://github.com/honhimW/i-spring-enhance","commit_stats":null,"previous_names":["honhimw/i-spring-enhance"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/honhimW%2Fi-spring-enhance","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/honhimW%2Fi-spring-enhance/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/honhimW%2Fi-spring-enhance/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/honhimW%2Fi-spring-enhance/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/honhimW","download_url":"https://codeload.github.com/honhimW/i-spring-enhance/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243852505,"owners_count":20358272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["access-control-list","event-driven","finite-state-machine","jpa-hibernate","multi-release","no-aop","redis","spring-boot","utilities","webflux"],"created_at":"2024-11-22T09:28:57.581Z","updated_at":"2026-05-08T11:33:16.381Z","avatar_url":"https://github.com/honhimW.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spring-Boot-Enhancement configuration\n\n## Web configuration for MVC/Webflux\n\n### Http delegating log\n\nHttp raw data logging by delegating the very IOStream of Request/Response. Only work on the data is read/written(e.g.\nHttpBody haven't be used by the server, the server may not read the data from the IOStream thus the data will not be\nlogged).\n\n- MVC: io.github.honhimw.spring.web.mvc.MvcHttpLogFilter\n- Webflux: io.github.honhimw.spring.web.reactive.ReactiveHttpLogHandler\n\n### Exception handling\n\n```java\n/**\n * @see io.github.honhimw.spring.web.common.ExceptionWrapper\n * Declare a bean that can be scan\n */\n@Component\npublic class SomeExceptionWrapper implements ExceptionWrapper {\n    @Override\n    public boolean support(@Nonnull Throwable e) {\n        return e instanceof A_Exception\n               || e instanceof B_Exception\n               || (StringUtils.startsWith(e.getClass().getPackage().getName(), \"org.example.exce\"))\n            ;\n    }\n\n    @Nonnull\n    @Override\n    public Object wrap(@Nonnull Throwable e) {\n        Result\u003cVoid\u003e result = Result.empty();\n        result.code(\"500\");\n        result.msg(e.getMessage());\n        return result;\n    }\n}\n```\n\n### Json response properties fetcher\n\nFetch only selected properties, make the result message more simple.\n\n```http request\nPOST http://localhost:80/hello/world/page\nFETCHER-ONLY-INCLUDE: /code;/data/*/content;/msg;/data/1/id\n```\n\n```json\n{\n  \"code\": 0,\n  \"msg\": \"success\",\n  \"data\": [\n    {\n      \"content\": \"hello\"\n    },\n    {\n      \"id\": 1,\n      \"content\": \"world\"\n    }\n  ]\n}\n```\n\nOr fetch only non excluded properties.\n\n```http request\nPOST http://localhost:80/hello/world/page\nFETCH-NON-EXCLUDE: /code;/data/*;\n```\n\n```json\n{\n  \"msg\": \"success\",\n  \"data\": [\n  ]\n}\n```\n\n### TextParam/FormDataParam resolver\n\n- TextParam: Aggregate those text-type param(e.g. RquestParameter/form-data-url-encoded/Json/pathVariable) to an single\n  entity.\n- FormDataParam: `multipart/form-data` is included.\n\n\u003e JacksonNodeReactiveCustomizer:\n\u003e * CsvJacksonNodeReactiveCustomizer(with `@CsvField`, application/csv)\n\u003e * YamlJacksonNodeReactiveCustomizer(application/yaml)\n\u003e\n\u003e JacksonNodeCustomizer\n\u003e * CsvJacksonNodeCustomizer(with `@CsvField`, application/csv)\n\u003e * YamlJacksonNodeCustomizer(application/yaml)\n\n### PartParam/FileReturn resolver and result handler\n\n- PartParam: accept a named part of `multipart/form-data` part, currently support CSV resolve.\n- FileReturn: return handler that set the `Content-Disposition` header with a default filename, and specific a file content charset(bom if unicode).\n\n\u003e Json response properties fetcher also supported.\n\n**Usage**\n```java\n@Controller\n@EnableCsvConverter     // Enable CSV Converter Features\n@SpringBootApplication\npublic class WebApp {\n  public static void main(String[] args) { SpringApplication.run(WebApp.class, args); }\n\n  @RequestMapping(value = \"/concat\", produces = {\"text/csv\"})\n  @FileReturn(value = \"csvTran.csv\", encoding = FileReturn.Encoding.UTF_8_BOM)\n  public List\u003cEntity\u003e conCSV(@PartParam(\"file\") List\u003cEntity\u003e list, @PartParam(\"file2\") List\u003cEntity\u003e list2) {\n//    DispositionHelper.attachment(\"ct.csv\"); you may override the filename by setting 'Content-Disposition' manually\n    List\u003cEntity\u003e concat = new ArrayList\u003c\u003e();\n    concat.addAll(list);\n    concat.addAll(list2);\n    return concat;\n  }\n}\n```\n\n### Validation\n\n- PhoneNumber: CN phoneNumber format validation.\n\n---\n\n## Cache configuration\n\n### TTLCache\n\nAn interface that extends Map\u003cK, V\u003e defines a way to set a separate Time-To-Live for each key(common redis usage).\n\n### RefreshableCache\n\nAn interface that defines cache data that refreshable by specific-event.\n\n### Redis extendsion\n\n- RedisUtils/R2edisUtils: Provide a Generic supported usage(Multi-RedisTemplate);\n- RedisKeySpaceEvent: support cluster-mode redisEvent;\n- RedisTTLCache: An implementation of TTLCache using RedisTemplate;\n\n### Memory\n\n- InMemoryTTLCache: An implementation of TTLCache using ConcurrentHashMap and ScheduledExecutorService;\n\n## Reactor Thread Local Accessor HttpHandler/WebHandler\n\nInject context parameters into thread local in reactive(project reactor)\n\n```groovy\n// required\nimplementation 'io.micrometer:context-propagation:1.1.1'\n```\n\n- AbstractThreadLocalHttpHandler\n- AbstractThreadLocalWebFilter\n\n## Spring Cloud Configuration\n\n### Development Load Balancer\n\nA Better load balancer under development.\n- Avoid developers affecting each other by using the same host instance as preferred.\n- Use the test server for the default service instance, so that developers don’t have to run the dependent services themselves.\n\n```java\n@SpringBootApplication\n@EnableDevLoadBalancer({\n        @Config(profile = \"develop\", servers = {\n                @TestServer(serviceId = \"geo-service\", host = \"geo-test.internal\", port = 8080)\n        }),\n        @Config(profile = \"test\", servers = {\n                @TestServer(serviceId = \"geo-service\", host = \"geo-uat.cloud\", port = 443, secure = true),\n        }, preferHost = \"geo-test.internal\")\n})\n@EnableFeignClients\npublic class WebApp {\n  public static void main(String[] args) { SpringApplication.run(WebApp.class, args); }\n}\n```\n\n## Data\n\n### Common\n\n- Access control list: Acl/Ace/ResourceMod etc.\n- Data operation event\n\n### Jpa implementation\n\n#### Event\n\n**Let Your Entity Extends AbstractAR**, and override DomainEntity#eventBuilder\n```java\n@Setter\n@Getter\n@Entity\n@Table(value = \"table_name\")\npublic class YourEntity extends AbstractAR\u003cYourEntity, String\u003e {\n\n    @Id\n    private String id;\n\n    @Override\n    public Function\u003cDaoAction, ? extends DomainEvent\u003cYourEntity, String\u003e\u003e eventBuilder() {\n         return daoAction -\u003e new YourEntityEvent(daoAction).id(getId()).entity(this);\n    }\n}\n```\n\n**Subscribe Entity Event with Spring Event**\n```java\n@Component\npublic class YourListener extends DomainListenerSupports {\n    @EventListener\n    public void appEvent(YourEvent event) {\n         afterInsert(event)\n             .ifPresent(e -\u003e {\n                 String id = e.getId();\n                 YourEntity entity = e.getEntity();\n                 // do something\n             });\n    }\n}\n```\n\n#### Acl\n\n**Create an AclExecutor**\n```java\npublic class CustomAclExecutorImpl\u003cT\u003e extends AbstractAclExecutor\u003cT\u003e {\n\n    public CustomAclExecutorImpl(\n        @Nonnull ResourceMod defaultMod,\n        @Nonnull JpaEntityInformation\u003cT, ?\u003e ei,\n        @Nonnull EntityManager em, @Nonnull String dataDomain) {\n        super(defaultMod, ei, em, dataDomain);\n    }\n\n    @Override\n    protected boolean guard() {\n        if (SudoSupports.isSudo()) {\n            log.warn(\"Sudo mode, executed without ACL guard.\");\n            return false;\n        }\n        return SecurityUtils.getAuthorizedUser().isPresent();\n    }\n\n    @Override\n    protected boolean isRoot() {\n        return SecurityUtils.getAuthorizedUser()\n            .map(AuthorizedUser::isRoot)\n            .orElse(false);\n    }\n\n    @Nonnull\n    @Override\n    protected Map\u003cString, Object\u003e getAttributes() {\n        return SecurityUtils.getAuthorizedUser()\n            .map(AuthorizedUser::getAttributes)\n            .orElseGet(HashMap::new);\n    }\n\n    @Nonnull\n    @Override\n    protected List\u003c? extends Ace\u003e getAcl() {\n        return SecurityUtils.getAuthorizedUser()\n            .map(AuthorizedUser::getAcl)\n            .orElseGet(ArrayList::new);\n    }\n}\n```\n\n**Register AclExecutor**\n\n```java\n@Component\npublic class CustomAclProvider implements AclProvider {\n    @Nonnull\n    @Override\n    public \u003cT\u003e AclExecutor\u003cT\u003e getExecutor(\n        JpaEntityInformation\u003cT, ?\u003e jpaEntityInformation,\n        EntityManager entityManager, String dataDomain,\n        ResourceMod resourceMod) {\n        return new CustomAclExecutorImpl\u003c\u003e(resourceMod, jpaEntityInformation, entityManager, dataDomain);\n    }\n}\n```\n\n**Enable Repositories**\n```java\n@Configuration\n@EnableJpaRepositories(basePackages = \"repositories.package.to.scan\", repositoryFactoryBeanClass = AclJpaRepositoryFactoryBean.class)\npublic class CustomConfiguration {}\n```\n\n**Naming Domain**\n```java\n@AclDataDomain(DatabaseObjectClass.TABLE_NAME)\n@Entity\n@Table(name = DatabaseObjectClass.TABLE_NAME)\n@Generated(value = DatabaseObjectClass.TABLE_NAME)\npublic class DatabaseObjectClass\u003cID\u003e extends AbstractAggregateRoot\u003cID\u003e {\n    @Id\n    private ID id;\n}\n```\n\n### Jimmer implementation\n\n#### Event\n\nDefine an intermediate layer interface `PlayerDomain` extends DomainEntity and override `DomainEntity#eventBuilder`.\n**Let Your Entity Extends PlayerDomain**\n```java\n// intermediate layer interface\npublic interface PlayerDomain extends DomainEntity {\n  @Override\n  default Function\u003cDaoAction, ? extends DomainEvent\u003c?, ?\u003e\u003e eventBuilder() {\n    if (this instanceof Player player) {\n      return daoAction -\u003e new PlayerEvent(daoAction, player);\n    }\n    return DomainEntity.super.eventBuilder();\n  }\n}\n\n@Entity\n@Table(name = \"player\")\npublic interface Player extends BaseAR, PlayerDomain {\n  @Id\n  String id();\n}\n```\n\n**Subscribe Entity Event with Spring Event**\n```java\n@Component\npublic class YourListener extends DomainListenerSupports {\n    @EventListener\n    public void appEvent(PlayerEvent event) {\n         afterInsert(event)\n             .ifPresent(e -\u003e {\n                 String id = e.getId();\n                 YourEntity entity = e.getEntity();\n                 // do something\n             });\n    }\n}\n```\n\n#### Acl\n\n**Create an AclExecutor**\n```java\npublic class CustomAclExecutorImpl\u003cT\u003e extends AbstractAclExecutor\u003cT\u003e {\n\n    public CustomAclExecutorImpl(\n        JSqlClientImplementor sqlClient,\n        TableProxy\u003cT\u003e tableProxy,\n        String dataDomain,\n        ResourceMod defaultMod) {\n      super(sqlClient, tableProxy, dataDomain, defaultMod);\n    }\n\n    @Override\n    protected boolean guard() {\n        if (SudoSupports.isSudo()) {\n            log.warn(\"Sudo mode, executed without ACL guard.\");\n            return false;\n        }\n        return SecurityUtils.getAuthorizedUser().isPresent();\n    }\n\n    @Override\n    protected boolean isRoot() {\n        return SecurityUtils.getAuthorizedUser()\n            .map(AuthorizedUser::isRoot)\n            .orElse(false);\n    }\n\n    @Nonnull\n    @Override\n    protected Map\u003cString, Object\u003e getAttributes() {\n        return SecurityUtils.getAuthorizedUser()\n            .map(AuthorizedUser::getAttributes)\n            .orElseGet(HashMap::new);\n    }\n\n    @Nonnull\n    @Override\n    protected List\u003c? extends Ace\u003e getAcl() {\n        return SecurityUtils.getAuthorizedUser()\n            .map(AuthorizedUser::getAcl)\n            .orElseGet(ArrayList::new);\n    }\n}\n```\n\n**Register AclExecutor**\n\n```java\n@Component\npublic class CustomAclProvider implements AclProvider {\n  @Override\n  @Nonnull\n  public AclExecutor getExecutor(\n          JSqlClientImplementor sqlClient,\n          TableProxy\u003c?\u003e tableProxy,\n          String dataDomain,\n          ResourceMod defaultMod) {\n    return new CustomAclExecutorImpl\u003c\u003e(sqlClient, tableProxy, dataDomain, defaultMod);\n  }\n}\n```\n\n**Enable Jimmer Repositories**\n```java\n@Configuration\n@EnableJimmerRepositories(basePackages = \"repositories.package.to.scan\", repositoryFactoryBeanClass = AclJimmerRepositoryFactoryBean.class)\npublic class CustomConfiguration {}\n```\n\n**Naming Domain**\n```java\n@AclDataDomain(DatabaseObjectClass.TABLE_NAME)\n@Entity\n@Table(name = DatabaseObjectClass.TABLE_NAME)\npublic interface DatabaseObjectClass extends BaseAR {\n    String TABLE_NAME = \"table_name\";\n    \n    @Id\n    private ID id;\n}\n```\n\n### Query\n\n\u003e Suppose you have the following data object relationship structure as follows:\n```json\n{\n  \"id\": 1,\n  \"name\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\"\n  },\n  \"parent\": {\n    \"id\": 2,\n    \"name\": {\n      \"first_name\": \"xxx\",\n      \"last_name\": \"xxx\"\n    }\n  }\n}\n```\n\n**Pattern Matching**, limited to Match as Equal, but easier to use\n```java\n// select * from person where first_name = 'xxx' and parent_id = '2' limit 10;\npublic void patternMatching() {\n  IPageRequest\u003cPerson\u003e iPageRequest = IPageRequest.of(1, 10);\n  Person condition = new Person();\n  Name name = new Name();\n  name.setFirstName(\"xxx\");\n  condition.setName(name);\n  Person parent = new Person();\n  parent.setId(\"2\");\n  condition.setParent(parent);\n  iPageRequest.setCondition(condition);\n  Page\u003cPerson\u003e paging = PageUtils.paging(personRepository, iPageRequest);\n  PageInfoVO\u003cPerson\u003e personPageInfoVO = PageUtils.pageInfoVO(paging, person -\u003e person);\n}\n```\n\n**Query Builder**, flexible\n```java\n// select * from person where first_name like '%xxx%' and parent_id is null limit 10;\npublic void queryBuilder() {\n  IPageRequest\u003cPerson\u003e iPageRequest = IPageRequest.of(1, 10);\n  List\u003cConditionColumn\u003e conditions = new ArrayList\u003c\u003e();\n  conditions.add(ConditionColumn.of(\"name.firstName\", \"xxx\", MatchingType.CONTAINING));\n  conditions.add(ConditionColumn.of(\"parent.id\", \"2\", MatchingType.NULL));\n  iPageRequest.setConditions(conditions);\n  Page\u003cPerson\u003e paging = PageUtils.paging(personRepository, iPageRequest);\n  PageInfoVO\u003cPerson\u003e personPageInfoVO = PageUtils.pageInfoVO(paging, person -\u003e person);\n}\n```\n\n**Ordering**\n```java\n// select * from person order by id desc limit 10;\npublic void ordering() {\n  IPageRequest\u003cPerson\u003e iPageRequest = IPageRequest.of(1, 10);\n  List\u003cOrderColumn\u003e orders = new ArrayList\u003c\u003e();\n  orders.add(OrderColumn.of(\"id\", true));\n  iPageRequest.setOrders(orders);\n  Page\u003cPerson\u003e paging = PageUtils.paging(personRepository, iPageRequest);\n  PageInfoVO\u003cPerson\u003e personPageInfoVO = PageUtils.pageInfoVO(paging, person -\u003e person);\n}\n```\n\n**Example**\n\u003e Just like GraphQL, you can query based on the data structure you see.\n```java\n@PostMapping(\"/list\")\npublic IResult\u003cPageInfoVO\u003cPersonVO\u003e\u003e list(@TextParam IPageRequest\u003cPersonVO\u003e iPageRequest) {\n  iPageRequest = PageUtils.convertRequest(iPageRequest, person -\u003e person.toDO());\n  Page\u003cPerson\u003e paging = PageUtils.paging(personRepository, iPageRequest);\n  PageInfoVO\u003cPersonVO\u003e personPageInfoVO = PageUtils.pageInfoVO(paging, person -\u003e person.toVO());\n  return IResult.ok(personPageInfoVO);\n}\n```\n\n```http request\nPOST /list\nHost: localhost:8080\nContent-Type: application/json\n\n{\n  \"page\": 1,\n  \"pageSize\": 10,\n  \"condition\": {\n    \"name\": {\n      \"firstName\": \"xxx\"\n    },\n    \"parent\": {\n      \"id\": \"2\"\n    }\n  },\n  \"orders\": [\n    {\n      \"name\": \"id\",\n      \"desc\": true\n    }\n  ]\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhonhimw%2Fi-spring-enhance","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhonhimw%2Fi-spring-enhance","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhonhimw%2Fi-spring-enhance/lists"}