From 1326784f87d7901e32eada1f3e9a096b572903ea Mon Sep 17 00:00:00 2001 From: xuzhen97 Date: Wed, 6 Dec 2023 13:40:11 +0000 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E9=80=80=20'Pull=20Request=20!1=20:?= =?UTF-8?q?=200.30.0'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 +- changelog.md | 9 - .../JointBlockAutoConfiguration.java | 127 ++++- .../JointBlockWebAutoConfiguration.java | 20 +- jointblock-spring-boot-parent/pom.xml | 17 +- .../adpter/api/ArticleRestController.java | 7 +- .../jointblock/manager/UserBaseDto.java | 2 + ...iclePageQry.java => ArticlePagingQry.java} | 4 +- .../service/dto/data/ArticlePagingDto.java | 35 ++ .../service/executor/ArticlePagingQryExe.java | 10 +- ...Test.java => ArticlePagingQryExeTest.java} | 29 +- .../core/AuditMetaObjectHandler.java | 6 +- .../jointblock/core/CloudUserHolder.java | 19 + .../easycode/jointblock/core/Condition.java | 14 + .../easycode/jointblock/core/Constant.java | 40 ++ .../jointblock/core/DeleteResultConvert.java | 15 + .../jointblock/core/DynamicOperate.java | 457 ++++++++++++++++-- .../jointblock/core/EnumeratorSerializer.java | 9 +- .../core/ExecutorDataFillInterceptor.java | 24 + .../core/ExecutorDataFillPointcut.java | 47 ++ .../jointblock/core/InsertCallback.java | 16 + .../core/InsertOrUpdateBatchMethod.java | 78 --- .../jointblock/core/InsertResultConvert.java | 15 + .../jointblock/core/JointBlockMapper.java | 22 - .../jointblock/core/LogUpdateWrapper.java | 23 + .../jointblock/core/OperateSymbol.java | 74 --- .../fun/easycode/jointblock/core/PageDto.java | 71 ++- .../core/{PageQry.java => PagingQry.java} | 2 +- .../jointblock/core/QueryDslParser.java | 47 -- .../QueryHandlerMethodArgumentResolver.java | 37 -- .../easycode/jointblock/core/QueryParam.java | 22 - .../jointblock/core/QueryWrapperBuilder.java | 120 ----- .../{util => core}/RequestHolder.java | 2 +- .../jointblock/core/SearchCriteria.java | 25 - .../jointblock/core/UpdateCallback.java | 19 + .../jointblock/core/UpdateResultConvert.java | 15 + .../easycode/jointblock/core/UserHolder.java | 39 +- .../jointblock/datafill/DataAlias.java | 16 + .../jointblock/datafill/DataCopy.java | 244 ++++++++++ .../jointblock/datafill/DataFill.java | 56 +++ .../jointblock/datafill/DataFillActuator.java | 49 -- .../jointblock/datafill/DataFillExecutor.java | 193 ++++++++ .../jointblock/datafill/DataFillId.java | 9 + .../jointblock/datafill/DataFillMetadata.java | 35 ++ .../jointblock/datafill/DataFillProcess.java | 38 -- .../datafill/DataFillProcessBuilder.java | 107 ---- .../jointblock/datafill/DataFillStrategy.java | 110 +++++ .../datafill/DataFillStrategyContext.java | 38 ++ .../jointblock/datafill/DataFillTask.java | 51 +- ...or.java => DataFillThreadPoolManager.java} | 49 +- .../jointblock/datafill/DataGenerate.java | 42 ++ .../jointblock/datafill/DataParam.java | 21 + .../jointblock/datafill/EnableDataFill.java | 13 + .../datafill/FeignDataFillStrategy.java | 130 +++++ .../easycode/jointblock/datafill/FeignId.java | 13 + .../jointblock/datafill/IDGenerateMode.java | 18 + .../datafill/MyBatisPlusDataFillStrategy.java | 90 ++++ .../{core => exception}/CheckException.java | 68 ++- .../generator/JointBlockGenerator.java | 8 +- .../{core => mybatisplus}/BatchMapper.java | 2 +- .../JointBlockSqlInjector.java | 3 +- .../MyBatisPlusSQLLog.java | 2 +- .../ReplaceBatchSomeColumn.java | 2 +- .../{core => mybatisplus}/StreamMapper.java | 2 +- .../StreamQueryAbstractMethod.java | 4 +- .../StreamQuerySqlAbstractMethod.java | 6 +- .../easycode/jointblock/util/BatchUtil.java | 63 --- .../jointblock/util/CamelUnderUtil.java | 38 -- .../fun/easycode/jointblock/util/Either.java | 77 --- .../jointblock/util/ExceptionUtil.java | 13 - .../fun/easycode/jointblock/util/LogUtil.java | 119 +++++ .../fun/easycode/jointblock/util/URLUtil.java | 1 + .../jointblock/validator/IValidate.java | 3 +- pom.xml | 2 +- 74 files changed, 2218 insertions(+), 1058 deletions(-) rename jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/{ArticlePageQry.java => ArticlePagingQry.java} (59%) rename jointblock-spring-boot-test/src/test/java/fun/easycode/jointblock/service/executor/{ArticlePageQryExeTest.java => ArticlePagingQryExeTest.java} (39%) create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/CloudUserHolder.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Condition.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Constant.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DeleteResultConvert.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillInterceptor.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillPointcut.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertCallback.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertOrUpdateBatchMethod.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertResultConvert.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/JointBlockMapper.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/LogUpdateWrapper.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/OperateSymbol.java rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/{PageQry.java => PagingQry.java} (90%) delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryDslParser.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryHandlerMethodArgumentResolver.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryParam.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryWrapperBuilder.java rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{util => core}/RequestHolder.java (99%) delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/SearchCriteria.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateCallback.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateResultConvert.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataAlias.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataCopy.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFill.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillActuator.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillExecutor.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillId.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillMetadata.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcess.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcessBuilder.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategy.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategyContext.java rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/{DataFillThreadExecutor.java => DataFillThreadPoolManager.java} (49%) create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataGenerate.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataParam.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/EnableDataFill.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignDataFillStrategy.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignId.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/IDGenerateMode.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/MyBatisPlusDataFillStrategy.java rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{core => exception}/CheckException.java (35%) rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{core => mybatisplus}/BatchMapper.java (90%) rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{core => mybatisplus}/JointBlockSqlInjector.java (90%) rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{util => mybatisplus}/MyBatisPlusSQLLog.java (95%) rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{core => mybatisplus}/ReplaceBatchSomeColumn.java (98%) rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{core => mybatisplus}/StreamMapper.java (94%) rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{core => mybatisplus}/StreamQueryAbstractMethod.java (91%) rename jointblock-spring-boot/src/main/java/fun/easycode/jointblock/{core => mybatisplus}/StreamQuerySqlAbstractMethod.java (88%) delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/BatchUtil.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/CamelUnderUtil.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/Either.java delete mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/ExceptionUtil.java create mode 100644 jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/LogUtil.java diff --git a/README.md b/README.md index 72231ea..a110940 100644 --- a/README.md +++ b/README.md @@ -18,22 +18,11 @@ GeneratorConfig generatorConfig = GeneratorConfig.builder() JointBlockGenerator.generator(generatorConfig); ``` -DataFill填充框架使用demo +DataFill填充框架使用必须要引入aop -```java -// 数据填充执行器 -DataFillActuator actuator = new DataFillActuator<>(billDtos); -// 中心填充处理过程 -actuator.addProcess(DataFillProcessBuilder.builder() - .keyGetFun(billDto -> billDto.getCenter().getLmnId()) - .fillDataGetFun(lmnIds -> baseDataOrgFeignClient.queryOrgPageList(DataFillProcessBuilder.request(() -> { - OrgPageQry qry = new OrgPageQry(); - qry.setLmnIdIn(lmnIds.stream().map(String::valueOf).collect(Collectors.toList())); - qry.setPageSize((long)lmnIds.size()); - return qry; - })).getRows()) - .fillKeyGetFun(OrgDto::getLmnId) - .fillFun((billDto, orgDto) -> BeanUtil.copyProperties(orgDto, billDto.getCenter())).build()); -// 执行填充 -actuator.execute(); +```xml + + org.springframework.boot + spring-boot-starter-aop + ``` \ No newline at end of file diff --git a/changelog.md b/changelog.md index 15407da..ade0bd5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,12 +1,3 @@ -# 0.30.0 -feature: -- flexible查询增加,特殊情况下动态查询`totalChapter[NotIn]=(6|7|8)` -refactor: -- 重构DataFill,不在使用注解,使用编码的方式增强代码可读性。 -- 大幅度精简框架,去除大量无用代码。 -fix: -- 修复代码生成器对审计字段的支持。 - # 0.29.0 feature: - batch mapper功能支持,支持批量插入,批量replace。 diff --git a/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockAutoConfiguration.java b/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockAutoConfiguration.java index 32bab01..6253a77 100644 --- a/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockAutoConfiguration.java +++ b/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockAutoConfiguration.java @@ -3,17 +3,22 @@ package fun.easycode.joinblock.autoconfigure; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import fun.easycode.jointblock.core.AuditMetaObjectHandler; import fun.easycode.jointblock.core.ExecutorContext; -import fun.easycode.jointblock.core.JointBlockSqlInjector; -import fun.easycode.jointblock.core.UserHolder; -import fun.easycode.jointblock.util.BatchUtil; +import fun.easycode.jointblock.core.ExecutorDataFillInterceptor; +import fun.easycode.jointblock.core.ExecutorDataFillPointcut; +import fun.easycode.jointblock.datafill.*; +import fun.easycode.jointblock.mybatisplus.JointBlockSqlInjector; import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.session.SqlSessionFactory; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.aop.Advisor; +import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -27,23 +32,9 @@ import org.springframework.context.annotation.Configuration; @EnableConfigurationProperties(JointBlockProperties.class) @Configuration @Slf4j -public class JointBlockAutoConfiguration { +public class JointBlockAutoConfiguration implements BeanFactoryAware{ - @Bean - @ConditionalOnBean(SqlSessionFactory.class) - public BatchUtil batchUtil(SqlSessionFactory factory){ - return new BatchUtil(factory); - } - - /** - * 配置用户信息获取器,如果用户自己配置则默认配置失效 - * @return UserHolder - */ - @Bean - @ConditionalOnMissingBean(UserHolder.class) - public UserHolder userHolder(){ - return new UserHolder.DefaultUserHolder(); - } + private BeanFactory beanFactory; /** * 配置mybatis plus插件, 如果用户自己配置则默认配置失效 @@ -74,6 +65,11 @@ public class JointBlockAutoConfiguration { return new JointBlockSqlInjector(); } + @Bean + public DataCopy dataCopy(){ + return new DataCopy(); + } + /** * mybatis plus审计字段处理 * @return AuditMetaObjectHandler @@ -84,6 +80,41 @@ public class JointBlockAutoConfiguration { return new AuditMetaObjectHandler(); } + /** + * 代码填充织入执行器 + * @return DefaultBeanFactoryPointcutAdvisor + */ + @ConditionalOnClass(Advisor.class) + @Bean + public DefaultBeanFactoryPointcutAdvisor executorDataFillAdvisor() { + // 自动填充织入到Executor.execute方法 + DefaultBeanFactoryPointcutAdvisor advisor = new DefaultBeanFactoryPointcutAdvisor(); + advisor.setPointcut(executorDataFillPointcut()); + advisor.setAdvice(executorDataFillInterceptor()); + advisor.setBeanFactory(beanFactory); + return advisor; + } + + /** + * 执行器识别定义的切点 + * @return ExecutorDataFillPointcut + */ + @Bean + @ConditionalOnClass(Advisor.class) + public ExecutorDataFillPointcut executorDataFillPointcut(){ + return new ExecutorDataFillPointcut(); + } + + /** + * 执行器数据填充具体的切入逻辑 + * @return ExecutorDataFillInterceptor + */ + @Bean + @ConditionalOnClass(Advisor.class) + public ExecutorDataFillInterceptor executorDataFillInterceptor(){ + return new ExecutorDataFillInterceptor(); + } + /** * 执行器上下文,用于直接获取执行器进行调用 * @return ExecutorContext @@ -92,4 +123,58 @@ public class JointBlockAutoConfiguration { public ExecutorContext executorContext(){ return new ExecutorContext(); } + + // 下方是DataFill的配置 + + /** + * mybatis plus填充策略支持 + * @return MyBatisPlusDataFillStrategy + */ + @Bean + @ConditionalOnClass({BaseMapper.class}) + public MyBatisPlusDataFillStrategy myBatisPlusDataFillStrategy(){ + return new MyBatisPlusDataFillStrategy(); + } + + /** + * feign远程调用策略支持 + * @return FeignDataFillStrategy + */ + @Bean + @ConditionalOnClass(name = "org.springframework.cloud.openfeign.FeignContext") + public FeignDataFillStrategy feignDataFillStrategy(){ + return new FeignDataFillStrategy(); + } + + /** + * 数据填充具体执行器, 这里与业务执行器没有联系 + * @return DataFillExecutor + */ + @Bean + public DataFillExecutor dataFillExecutor(){ + return new DataFillExecutor(); + } + + /** + * 数据填充策略Context, 用于获取填充策略 + * @return DataFillStrategyContext + */ + @Bean + public DataFillStrategyContext dataFillStrategyContext(){ + return new DataFillStrategyContext(); + } + + /** + * 数据填充线程池管理 + * @return DataFillThreadPoolManager + */ + @Bean + public DataFillThreadPoolManager dataFillThreadPoolManager(){ + return new DataFillThreadPoolManager(); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } } diff --git a/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockWebAutoConfiguration.java b/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockWebAutoConfiguration.java index 8c8aae1..9194a76 100644 --- a/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockWebAutoConfiguration.java +++ b/jointblock-spring-boot-autoconfigure/src/main/java/fun/easycode/joinblock/autoconfigure/JointBlockWebAutoConfiguration.java @@ -2,17 +2,17 @@ package fun.easycode.joinblock.autoconfigure; import fun.easycode.jointblock.core.*; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.time.LocalDateTime; -import java.util.List; /** * web应用才生效 @@ -23,11 +23,6 @@ import java.util.List; @Slf4j public class JointBlockWebAutoConfiguration implements WebMvcConfigurer, Jackson2ObjectMapperBuilderCustomizer{ - @Override - public void addArgumentResolvers(List resolvers) { - resolvers.add(new QueryHandlerMethodArgumentResolver()); - } - /** * mvc枚举转换器 * @return EnumeratorConvertFactory @@ -62,4 +57,15 @@ public class JointBlockWebAutoConfiguration implements WebMvcConfigurer, Jackson jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer()); jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer()); } + + /** + * Cloud UserHolder 实例 + * @return UserHolder + */ + @ConditionalOnMissingClass({"org.springframework.security.core.Authentication"}) + @ConditionalOnMissingBean(UserHolder.class) + @Bean + public UserHolder cloudUserHolder(){ + return new CloudUserHolder(); + } } diff --git a/jointblock-spring-boot-parent/pom.xml b/jointblock-spring-boot-parent/pom.xml index c22bfec..7c6ee2b 100644 --- a/jointblock-spring-boot-parent/pom.xml +++ b/jointblock-spring-boot-parent/pom.xml @@ -27,7 +27,7 @@ 10.10.1 2.1.5.RELEASE 20.0 - 0.30.0 + 0.29.0 2.14.2 @@ -64,11 +64,6 @@ mybatis-plus-boot-starter ${mybatis-plus.version} - - com.baomidou - mybatis-plus - ${mybatis-plus.version} - com.baomidou mybatis-plus-generator @@ -79,11 +74,6 @@ jointblock-spring-boot-starter ${jointblock.version} - - fun.easycode - jointblock-spring-boot - ${jointblock.version} - com.github.xiaoymin knife4j-spring-boot-starter @@ -147,11 +137,6 @@ lombok-mapstruct-binding 0.2.0 - - org.springframework.boot - spring-boot-configuration-processor - ${spring-boot.version} - diff --git a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/adpter/api/ArticleRestController.java b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/adpter/api/ArticleRestController.java index 869bfaf..8b8a717 100644 --- a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/adpter/api/ArticleRestController.java +++ b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/adpter/api/ArticleRestController.java @@ -1,9 +1,10 @@ package fun.easycode.jointblock.adpter.api; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import fun.easycode.jointblock.core.Constant; import fun.easycode.jointblock.core.ExecutorContext; import fun.easycode.jointblock.core.PageDto; -import fun.easycode.jointblock.service.dto.ArticlePageQry; +import fun.easycode.jointblock.service.dto.ArticlePagingQry; import fun.easycode.jointblock.service.dto.data.ArticlePagingDto; import fun.easycode.jointblock.service.executor.ArticlePagingQryExe; import io.swagger.annotations.ApiImplicitParam; @@ -21,10 +22,10 @@ public class ArticleRestController { + "查看别人的能看机审通过、到达发布时间、并且不是草稿、不是驳回的阅读文章") @GetMapping("/articles") @ApiImplicitParams({ -// @ApiImplicitParam(name = Constant.CURRENT_USER_ID_HEADER, paramType = "header"), + @ApiImplicitParam(name = Constant.CURRENT_USER_ID_HEADER, paramType = "header"), @ApiImplicitParam(value = "用户ID", name = "userId") }) - public ResponseEntity> myReadList(ArticlePageQry qry) { + public ResponseEntity> myReadList(ArticlePagingQry qry) { return ResponseEntity.ok(ExecutorContext.get(ArticlePagingQryExe.class).execute(qry)); } } diff --git a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/manager/UserBaseDto.java b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/manager/UserBaseDto.java index 5671601..7ed3436 100644 --- a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/manager/UserBaseDto.java +++ b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/manager/UserBaseDto.java @@ -1,6 +1,7 @@ package fun.easycode.jointblock.manager; +import fun.easycode.jointblock.datafill.FeignId; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -14,6 +15,7 @@ import java.io.Serializable; @NoArgsConstructor public class UserBaseDto implements Serializable { + @FeignId private String userId; private String nickname; private String avatar; diff --git a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/ArticlePageQry.java b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/ArticlePagingQry.java similarity index 59% rename from jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/ArticlePageQry.java rename to jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/ArticlePagingQry.java index c1cc688..8792403 100644 --- a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/ArticlePageQry.java +++ b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/ArticlePagingQry.java @@ -1,11 +1,11 @@ package fun.easycode.jointblock.service.dto; -import fun.easycode.jointblock.core.PageQry; +import fun.easycode.jointblock.core.PagingQry; import fun.easycode.jointblock.validator.IValidate; import lombok.Data; @Data -public class ArticlePageQry extends PageQry implements IValidate { +public class ArticlePagingQry extends PagingQry implements IValidate { // @NotBlank private String userId; } diff --git a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/data/ArticlePagingDto.java b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/data/ArticlePagingDto.java index e4d8f46..40b4155 100644 --- a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/data/ArticlePagingDto.java +++ b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/dto/data/ArticlePagingDto.java @@ -1,11 +1,15 @@ package fun.easycode.jointblock.service.dto.data; +import fun.easycode.jointblock.datafill.DataFill; +import fun.easycode.jointblock.datafill.DataParam; +import fun.easycode.jointblock.datafill.EnableDataFill; import fun.easycode.jointblock.manager.*; import lombok.Data; import java.util.List; @Data +@EnableDataFill public class ArticlePagingDto { private String id; private String title; @@ -15,14 +19,45 @@ public class ArticlePagingDto { private List img; private List coverImgSmall; // , methodName = "getUserByIds" + @DataFill(source = UserClient.class, value = "userId") private UserBaseDto user; + @DataFill(source = CollegeClient.class) private CollegeDto college; private Long createTime; + @DataFill(source = PraiseCountClient.class) private int praiseCount; + @DataFill(source = CommentCountClient.class, params = { + @DataParam( + name = "topicIds", + value = "#ids" + ) + }) private int commentCount; + @DataFill(source = BroweCountClient.class + , spEl = "#source.playAmount + #source.virtualPlayAmount") private int browseCount; + @DataFill(source = PraiseClient.class, params = { + @DataParam( + name = "userId", + value = "T(fun.easycode.jointblock.common.util.SecurityUtil).getCurrentUserId()" + ), + @DataParam( + name = "topicIds", + value = "#ids" + ) + }) private Boolean isPraise = false; private Boolean isAttention = false; + @DataFill(source = FavoriteClient.class, params = { + @DataParam( + name = "userId", + value = "T(fun.easycode.jointblock.common.util.SecurityUtil).getCurrentUserId()" + ), + @DataParam( + name = "topicIds", + value = "#ids" + ) + }) private Boolean isCollection = false; private String topicType = TopicType.ARTICLE.getValue(); private String type = WorkType.TEXT.getValue(); diff --git a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/executor/ArticlePagingQryExe.java b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/executor/ArticlePagingQryExe.java index 89402be..b040375 100644 --- a/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/executor/ArticlePagingQryExe.java +++ b/jointblock-spring-boot-test/src/main/java/fun/easycode/jointblock/service/executor/ArticlePagingQryExe.java @@ -6,8 +6,9 @@ import fun.easycode.jointblock.core.PageDto; import fun.easycode.jointblock.manager.ArticleClientDto; import fun.easycode.jointblock.manager.ArticleClientQuery; import fun.easycode.jointblock.manager.ArticleServiceClient; -import fun.easycode.jointblock.service.dto.ArticlePageQry; +import fun.easycode.jointblock.service.dto.ArticlePagingQry; import fun.easycode.jointblock.service.dto.data.ArticlePagingDto; +import fun.easycode.jointblock.service.dto.data.ArticlePagingDtoAssembler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -19,7 +20,7 @@ import java.util.Objects; * @author xuzhe */ @Service -public class ArticlePagingQryExe implements Executor> { +public class ArticlePagingQryExe implements Executor> { private final ArticleServiceClient articleServiceClient; @@ -29,7 +30,7 @@ public class ArticlePagingQryExe implements Executor execute(ArticlePageQry qry) { + public PageDto execute(ArticlePagingQry qry) { qry.validate(); @@ -48,8 +49,7 @@ public class ArticlePagingQryExe implements Executor pageDto = ExecutorContext.get(ArticlePagingQryExe.class).execute(qry); System.out.println(pageDto.getTotalSize()); } - - public static void main(String[] args) { - Pattern pattern = Pattern.compile("(\\w+?)(\\[.+?\\]=)([a-zA-Z0-9\\\\u4e00-\\\\u9fa5()|]+?),"); - Matcher matcher = pattern.matcher("lmnid[Eq]=164044345999532823,status[Eq]=1,"); - - Pattern operatorPattern = Pattern.compile("(?<=\\[).*(?=\\])"); -// Pattern operatorPattern = Pattern.compile("(?<=\\[).*(?=\\])"); - - - while (matcher.find()) { - System.out.print(matcher.group(1) + " "); - System.out.print(matcher.group(2)+ " "); - - Matcher operatorMatcher = operatorPattern.matcher(matcher.group(2)); - - System.err.print(operatorMatcher.find() ? operatorMatcher.group() : ""); - System.out.println(matcher.group(3) + " "); - } - - } } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/AuditMetaObjectHandler.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/AuditMetaObjectHandler.java index 29f4d1e..c6a462c 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/AuditMetaObjectHandler.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/AuditMetaObjectHandler.java @@ -24,9 +24,9 @@ public class AuditMetaObjectHandler implements MetaObjectHandler { public void insertFill(MetaObject metaObject) { LocalDateTime now = LocalDateTime.now(); this.strictInsertFill(metaObject, "createTime", ()-> now, LocalDateTime.class); - this.strictInsertFill(metaObject, "createBy",UserHolder.getInstance().getLoginUser()::getId, String.class); + this.strictInsertFill(metaObject, "createBy", UserHolder.getInstance()::getUserId, String.class); this.strictInsertFill(metaObject, "updateTime", ()-> now, LocalDateTime.class); - this.strictInsertFill(metaObject, "updateBy", UserHolder.getInstance().getLoginUser()::getId, String.class); + this.strictInsertFill(metaObject, "updateBy", UserHolder.getInstance()::getUserId, String.class); // 兼容date Date nowDate = new Date(); @@ -38,7 +38,7 @@ public class AuditMetaObjectHandler implements MetaObjectHandler { public void updateFill(MetaObject metaObject) { LocalDateTime now = LocalDateTime.now(); this.strictUpdateFill(metaObject, "updateTime", () -> now, LocalDateTime.class); - this.strictUpdateFill(metaObject, "updateBy", UserHolder.getInstance().getLoginUser()::getId, String.class); + this.strictUpdateFill(metaObject, "updateBy", UserHolder.getInstance()::getUserId, String.class); // 兼容date Date nowDate = new Date(); diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/CloudUserHolder.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/CloudUserHolder.java new file mode 100644 index 0000000..3e3361b --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/CloudUserHolder.java @@ -0,0 +1,19 @@ +package fun.easycode.jointblock.core; + +import fun.easycode.jointblock.exception.CheckException; +import org.springframework.util.StringUtils; + +/** + * 微服务UserHolder实例 + * @author xuzhe + */ +public class CloudUserHolder extends UserHolder{ + @Override + public String getUserId() { + String currUserId = RequestHolder.getRequest().getHeader("x-currUserId"); + if(StringUtils.isEmpty(currUserId)){ + throw new CheckException("x-currUserId header未传入!"); + } + return currUserId; + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Condition.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Condition.java new file mode 100644 index 0000000..ad50f41 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Condition.java @@ -0,0 +1,14 @@ +package fun.easycode.jointblock.core; + +import java.lang.annotation.*; + +/** + * 条件注解,在修改的时候标识这是条件,不是set + * @author xuzhe + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Condition { + +} \ No newline at end of file diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Constant.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Constant.java new file mode 100644 index 0000000..04b0f87 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/Constant.java @@ -0,0 +1,40 @@ +package fun.easycode.jointblock.core; + +/** + * 框架常量 + * @author xuzhe + */ +public interface Constant { + + /** + * 当前页 + */ + int CURRENT = 1; + /** + * 页大小 + */ + int SIZE = 10; + + /** + * 总条数 header key + */ + String TOTAL_SIZE_HEADER = "x-pagination-count"; + + /** + * 总页数 header key + */ + String TOTAL_PAGE_HEADER = "x-pagination-pages"; + /** + * 页大小 header key + */ + String SIZE_HEADER = "x-pagination-size"; + /** + * 当前页 header key + */ + String CURRENT_HEADER = "x-pagination-number"; + + /** + * 当前操作人 header key + */ + String CURRENT_USER_ID_HEADER = "x-currUserId"; +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DeleteResultConvert.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DeleteResultConvert.java new file mode 100644 index 0000000..7b94781 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DeleteResultConvert.java @@ -0,0 +1,15 @@ +package fun.easycode.jointblock.core; + +/** + * 删除返回结果转换 + * @author xuzhe + */ +@FunctionalInterface +public interface DeleteResultConvert { + /** + * 数据库实体T转换为返回结果R + * @param entity 数据库实体 + * @return R + */ + R to(T entity); +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DynamicOperate.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DynamicOperate.java index 3e1ff19..73c4804 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DynamicOperate.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/DynamicOperate.java @@ -1,20 +1,21 @@ package fun.easycode.jointblock.core; import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import fun.easycode.jointblock.exception.CheckException; +import fun.easycode.jointblock.util.LogUtil; import lombok.extern.slf4j.Slf4j; -import static fun.easycode.jointblock.util.CamelUnderUtil.*; import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.time.LocalDateTime; +import java.util.*; import java.util.stream.Collectors; /** @@ -23,6 +24,59 @@ import java.util.stream.Collectors; */ @Slf4j public final class DynamicOperate { + + /** + * 等于 + */ + public static final String EQ = "Eq"; + /** + * 模糊查询 + */ + public static final String LIKE = "Like"; + /** + * 模糊查询左 + */ + public static final String LIKE_LEFT = "LikeLeft"; + /** + * 模糊查询右 + */ + public static final String LIKE_RIGHT = "LikeRight"; + /** + * 大于 + */ + public static final String GT = "Gt"; + /** + * 小于 + */ + public static final String LT = "Lt"; + /** + * 大于等于 + */ + public static final String GE = "Ge"; + /** + * 小于等于 + */ + public static final String LE = "Le"; + + /** + * in 集合查询 + */ + public static final String IN = "In"; + + /** + * not in 集合查询 + */ + public static final String NOT_IN = "NotIn"; + + /** + * is null 查询 + */ + public static final String IS_NULL = "IsNull"; + + /** + * 排序 + */ + public static final String SORT = "Sort"; /** * 跳过字段, queryWrapper中跳过 */ @@ -56,7 +110,7 @@ public final class DynamicOperate { * @param 查询指令 * @param 转换的返回结果 */ - public static PageDto page(K qry + public static PageDto page(K qry , BaseMapper mapper , QueryResultConvert resultConvert){ QueryWrapper queryWrapper = queryWrapper(qry); @@ -64,6 +118,230 @@ public final class DynamicOperate { return PageDto.toPageDto(page, resultConvert::to); } + /** + * 修改工具方法,注意此方法cmd中的值必须传入,如果未传,数据库会被设置为Null + * @param cmd 修改指令实体 + * @param mapper mybatis mapper + * @param callback 修改回调,用于自定义修改逻辑 + * @param resultConvert 修改实体返回转换 + * @param skipFields 跳过字段,用于那些需要自定义逻辑的字段配合修改回调 + * @return 返回修改后的实体 + * @param 数据库实体 + * @param 修改指令 + * @param 修改后的实体 + */ + public static Optional update(K cmd, BaseMapper mapper + , UpdateCallback callback + , UpdateResultConvert resultConvert + , String... skipFields) { + return update(cmd, mapper, callback, resultConvert, false, false, skipFields); + } + + /** + * 修改工具方法,注意此方法cmd中的值必须传入,如果未传,数据库会被设置为Null + * 此方法进行审计信息的填充update_by、update_time + * 单个对象修改,根据条件必须唯一锁定一条数据 + * + * @param cmd 修改指令实体 + * @param mapper mybatis mapper + * @param callback 修改回调,用于自定义修改逻辑 + * @param resultConvert 修改实体返回转换 + * @param skipFields 跳过字段,用于那些需要自定义逻辑的字段配合修改回调 + * @param 数据库实体 + * @param 修改指令 + * @param 修改后的实体 + * @return 返回修改后的实体 + */ + public static Optional updateNotAudit(K cmd, BaseMapper mapper + , UpdateCallback callback + , UpdateResultConvert resultConvert + , String... skipFields) { + return update(cmd, mapper, callback, resultConvert, false, true, skipFields); + } + + /** + * 修改工具方法 + * + * @param cmd 修改指令实体 + * @param mapper mybatis mapper + * @param callback 修改回调,用于自定义修改逻辑 + * @param resultConvert 修改实体返回转换 + * @param skipEmpty 是否跳过cmd的empty字段,如果要跳过,那么不传入的字段,或者是""这种都不进行修改 + * @param skipAudit 是否跳过审计字段update_by和update_time就不自动传参 + * @param skipFields 跳过字段,用于那些需要自定义逻辑的字段配合修改回调 + * @param 数据库实体 + * @param 修改指令 + * @param 修改后的实体 + * @return 返回修改后的实体 + */ + private static Optional update(K cmd + , BaseMapper mapper + , UpdateCallback callback + , UpdateResultConvert resultConvert + , boolean skipEmpty + , boolean skipAudit + , String... skipFields) { + + Map fieldMap = ReflectUtil.getFieldMap(cmd.getClass()); + List skipFieldList = Arrays.asList(skipFields); + // 生成修改id, 用来记录修改的具体情况,用于打印日志 + String updateId = IdUtil.getSnowflakeNextIdStr(); + UpdateWrapper wrapper = new LogUpdateWrapper<>(updateId); + Queue updateQueue = new LinkedList<>(); + + for (Map.Entry entry : fieldMap.entrySet()) { + // 如果字段要跳过则方法对这个字段不进行任何处理 + if (skipFieldList.stream().anyMatch(skipField -> ObjectUtil.equal(skipField, entry.getKey()))) { + continue; + } + // 在修改的过程中如果被标记条件则称为修改条件 + if (AnnotationUtil.hasAnnotation(entry.getValue(), Condition.class)) { + wrapper(cmd, entry.getKey(), entry.getValue(), wrapper); + } else { + // 如果没有被标记为条件则进行修改 + String fieldName = getDatabaseFieldName(-1, entry.getKey(), entry.getValue()); + Object fieldValue = ReflectUtil.getFieldValue(cmd, entry.getValue()); + if (ObjectUtil.isNotEmpty(fieldValue)) { + updateQueue.offer(fieldName); + updateQueue.offer(fieldValue); + } else if (!skipEmpty) { + // 如果设置跳过empty则为empty的时候不进行修改 + updateQueue.offer(fieldName); + updateQueue.offer(null); + } + } + } + T entity = mapper.selectOne(wrapper); + if (entity != null) { + if (callback != null) { + callback.callback(cmd, wrapper); + } + while (!updateQueue.isEmpty()) { + String updateFieldName = (String) updateQueue.remove(); + Object updateFieldValue = updateQueue.remove(); + wrapper.set(updateFieldName, updateFieldValue); + } + + // 当skipAudit == true, 跳过审计填充 + if (!skipAudit) { + // 审计信息支持 + wrapper.set(camel2under("lastModifiedDate"), LocalDateTime.now()); + wrapper.set(camel2under("lastModifiedBy"), UserHolder.getInstance().getUserId()); + } + mapper.update(null, wrapper); + log.info("update {}: op {} info {}." + , entity.getClass().getSimpleName(), UserHolder.getInstance().getUserId() + , LogUtil.getFieldUpdateLog(updateId, entity)); + if (resultConvert == null) { + return Optional.empty(); + } else { + return Optional.of(resultConvert.to(entity)); + } + } + throw new CheckException("要修改的实体不存在!"); + } + + /** + * 批量修改,自动填充审计lastModifiedDate lastModifiedBy + * 如果值为null会被设置成null + * + * @param cmd + * @param mapper + * @param skipFields + * @param + * @param + */ + public static void updateBatch(K cmd + , BaseMapper mapper + , UpdateCallback callback + , String... skipFields) { + UpdateWrapper updateWrapper = updateWrapper(cmd, false, false, false, skipFields); + updateWrapper = callback.callback(cmd, updateWrapper); + String whereSqlSegment = getWhereSqlSegment(updateWrapper); + String setSql = getSetSqlSegment(updateWrapper); + mapper.update(null, updateWrapper); + log.info("update {} : condition {}, set {}, op {}.", mapper.getClass().getSimpleName() + , whereSqlSegment + , setSql + , UserHolder.getInstance().getUserId()); + } + + /** + * 批量修改,自动填充审计lastModifiedDate lastModifiedBy + * 跳过空值,null不会往数据库设置 + * + * @param cmd + * @param mapper + * @param skipFields + * @param + * @param + */ + public static void updateBatchSkipEmpty(K cmd + , BaseMapper mapper + , UpdateCallback callback + , String... skipFields) { + UpdateWrapper updateWrapper = updateWrapper(cmd, false, false, false, skipFields); + updateWrapper = callback.callback(cmd, updateWrapper); + String whereSqlSegment = getWhereSqlSegment(updateWrapper); + String setSql = getSetSqlSegment(updateWrapper); + mapper.update(null, updateWrapper); + log.info("update {} : condition {}, set {}, op {}.", mapper.getClass().getSimpleName() + , whereSqlSegment + , setSql + , UserHolder.getInstance().getUserId()); + } + + /** + * 批量修改跳过审计,但是不跳过空值,值为null数据库为null + * lastModifiedDate lastModifiedBy + * + * @param cmd + * @param mapper + * @param skipFields + * @param + * @param + */ + public static void updateBatchNotAudit(K cmd + , BaseMapper mapper + , UpdateCallback callback + , String... skipFields) { + UpdateWrapper updateWrapper = updateWrapper(cmd, false, false, true, skipFields); + updateWrapper = callback.callback(cmd, updateWrapper); + String whereSqlSegment = getWhereSqlSegment(updateWrapper); + String setSql = getSetSqlSegment(updateWrapper); + mapper.update(null, updateWrapper); + log.info("update {} : condition {}, set {}, op {}.", mapper.getClass().getSimpleName() + , whereSqlSegment + , setSql + , UserHolder.getInstance().getUserId()); + } + + /** + * 批量修改跳过审计 + * lastModifiedDate lastModifiedBy + * 跳过空值null set + * + * @param cmd + * @param mapper + * @param skipFields + * @param + * @param + */ + public static void updateBatchNotAuditSkipEmpty(K cmd + , BaseMapper mapper + , UpdateCallback callback + , String... skipFields) { + UpdateWrapper updateWrapper = updateWrapper(cmd, false, true, true, skipFields); + updateWrapper = callback.callback(cmd, updateWrapper); + String whereSqlSegment = getWhereSqlSegment(updateWrapper); + String setSql = getSetSqlSegment(updateWrapper); + mapper.update(null, updateWrapper); + log.info("update {} : condition {}, set {}, op {}.", mapper.getClass().getSimpleName() + , whereSqlSegment + , setSql + , UserHolder.getInstance().getUserId()); + } + /** * 根据实体命令生成QueryWrapper条件 * @@ -119,6 +397,94 @@ public final class DynamicOperate { return queryWrapper; } + /** + * 根据指令生成UpdateWrapper + * 如果skipSet=true, 则skipEmpty设置无效 + * + * @param cmd 对象指令 + * @param skipSet 是否跳过set, 跳过set仅仅生成用于查询的wrapper + * @param skipEmpty 是否跳过empty + * , 如果跳过当值为Null或者size=0 以及""这种的时候不进行处理,反之设置为null + * @param skipFields 要跳过的字段,跳过的字段方法就不会处理这种数据 + * @param 实体类型 + * @return UpdateWrapper + */ + public static UpdateWrapper updateWrapper(Object cmd + , boolean skipSet + , boolean skipEmpty + , boolean skipAudit + , String... skipFields) { + + Map fieldMap = ReflectUtil.getFieldMap(cmd.getClass()); + List skipFieldList = Arrays.asList(skipFields); + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + boolean isCondition = false; + for (Map.Entry entry : fieldMap.entrySet()) { + // 如果字段要跳过则方法对这个字段不进行任何处理 + if (skipFieldList.stream().anyMatch(skipField -> ObjectUtil.equal(skipField, entry.getKey()))) { + continue; + } + // 在修改的过程中如果被标记条件则称为修改条件 + if (AnnotationUtil.hasAnnotation(entry.getValue(), Condition.class)) { + wrapper(cmd, entry.getKey(), entry.getValue(), updateWrapper); + isCondition = true; + } else if (!skipSet) { + // 如果没有被标记为条件则进行修改 + String fieldName = getDatabaseFieldName(-1, entry.getKey(), entry.getValue()); + Object fieldValue = ReflectUtil.getFieldValue(cmd, entry.getValue()); + if (ObjectUtil.isNotEmpty(fieldValue)) { + updateWrapper.set(fieldName, fieldValue); + } else if (!skipEmpty) { + updateWrapper.set(fieldName, null); + } + // 是否跳过审计信息支持 + if (!skipAudit) { + // 审计信息支持 + updateWrapper.set(camel2under("lastModifiedDate"), LocalDateTime.now()); + updateWrapper.set(camel2under("lastModifiedBy"), UserHolder.getInstance().getUserId()); + } + } + } + if (!isCondition) { + throw new CheckException("updateWrapper方法调用必须存在条件@Condition!"); + } + return updateWrapper; + } + + /** + * 获取where sql段用于打印 + * @param wrapper UpdateWrapper + * @return sql + * @param T + */ + private static String getWhereSqlSegment(UpdateWrapper wrapper){ + String sqlSegment = wrapper.getSqlSegment(); + + for(Map.Entry entry : wrapper.getParamNameValuePairs().entrySet()){ + String repKey = "#{ew.paramNameValuePairs."+entry.getKey()+"}"; + sqlSegment = sqlSegment.replace(repKey, String.valueOf(entry.getValue())); + } + return sqlSegment; + } + + /** + * 获取where set sql段用于打印 + * + * @param wrapper UpdateWrapper + * @param T + * @return sql + */ + private static String getSetSqlSegment(UpdateWrapper wrapper) { + String sqlSet = wrapper.getSqlSet(); + + for (Map.Entry entry : wrapper.getParamNameValuePairs().entrySet()) { + String repKey = "#{ew.paramNameValuePairs." + entry.getKey() + "}"; + sqlSet = sqlSet.replace(repKey, String.valueOf(entry.getValue())); + } + return sqlSet; + } + /** * 给wrapper增加条件 * @param o 对象例如qry cmd @@ -128,64 +494,64 @@ public final class DynamicOperate { */ private static void wrapper(Object o, String key, Field field, AbstractWrapper wrapper) { - if (isConditional(key, OperateSymbol.EQ.getValue())) { - int index = lastIndexOf(key, OperateSymbol.EQ.getValue()); + if (isConditional(key, EQ)) { + int index = lastIndexOf(key, EQ); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.eq(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.LIKE.getValue())) { - int index = lastIndexOf(key, OperateSymbol.LIKE.getValue()); + } else if (isConditional(key, LIKE)) { + int index = lastIndexOf(key, LIKE); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, key); if (ObjectUtil.isNotEmpty(value)) { wrapper.like(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.LIKE_LEFT.getValue())) { - int index = lastIndexOf(key, OperateSymbol.LIKE_LEFT.getValue()); + } else if (isConditional(key, LIKE_LEFT)) { + int index = lastIndexOf(key, LIKE_LEFT); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.likeLeft(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.LIKE_RIGHT.getValue())) { - int index = lastIndexOf(key, OperateSymbol.LIKE_RIGHT.getValue()); + } else if (isConditional(key, LIKE_RIGHT)) { + int index = lastIndexOf(key, LIKE_RIGHT); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.likeRight(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.GT.getValue())) { - int index = lastIndexOf(key, OperateSymbol.GT.getValue()); + } else if (isConditional(key, GT)) { + int index = lastIndexOf(key, GT); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.gt(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.LT.getValue())) { - int index = lastIndexOf(key, OperateSymbol.LT.getValue()); + } else if (isConditional(key, LT)) { + int index = lastIndexOf(key, LT); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.lt(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.GE.getValue())) { - int index = lastIndexOf(key, OperateSymbol.GE.getValue()); + } else if (isConditional(key, GE)) { + int index = lastIndexOf(key, GE); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.ge(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.LE.getValue())) { - int index = lastIndexOf(key, OperateSymbol.LE.getValue()); + } else if (isConditional(key, LE)) { + int index = lastIndexOf(key, LE); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.le(fieldName, transformValue(value)); } - } else if (isConditional(key, OperateSymbol.IS_NULL.getValue())) { - int index = lastIndexOf(key, OperateSymbol.IS_NULL.getValue()); + } else if (isConditional(key, IS_NULL)) { + int index = lastIndexOf(key, IS_NULL); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (value != null && !(value instanceof Boolean)) { @@ -198,22 +564,22 @@ public final class DynamicOperate { wrapper.isNotNull(fieldName); } } - } else if (isConditional(key, OperateSymbol.NOT_IN.getValue())) { - int index = lastIndexOf(key, OperateSymbol.NOT_IN.getValue()); + } else if (isConditional(key, NOT_IN)) { + int index = lastIndexOf(key, NOT_IN); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.notIn(fieldName, (Collection) value); } - } else if (isConditional(key, OperateSymbol.IN.getValue())) { - int index = lastIndexOf(key, OperateSymbol.IN.getValue()); + } else if (isConditional(key, IN)) { + int index = lastIndexOf(key, IN); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); if (ObjectUtil.isNotEmpty(value)) { wrapper.in(fieldName, (Collection) value); } - } else if (isConditional(key, OperateSymbol.SORT.getValue())) { - int index = lastIndexOf(key, OperateSymbol.SORT.getValue()); + } else if (isConditional(key, SORT)) { + int index = lastIndexOf(key, SORT); String fieldName = getDatabaseFieldName(index, key, field); Object value = ReflectUtil.getFieldValue(o, field); // 必须传入值,不是DESC其他值均为ASC @@ -285,4 +651,35 @@ public final class DynamicOperate { return camel2under(original); } + /** + * 功能:驼峰命名转下划线命名 + * 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写 + */ + public static String camel2under(String c) + { + String separator = "_"; + c = c.replaceAll("([a-z])([A-Z])", "$1"+separator+"$2").toLowerCase(); + return c; + } + + /** + * 功能:下划线命名转驼峰命名 + * 将下划线替换为空格,将字符串根据空格分割成数组,再将每个单词首字母大写 + * @param s + * @return + */ + public static String under2camel(String s) + { + String separator = "_"; + String under=""; + s = s.toLowerCase().replace(separator, " "); + String sarr[]=s.split(" "); + for(int i=0;i> { @Override public void serialize(Enumerator enumerator, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { -// jsonGenerator.writeStartObject(); -// jsonGenerator.writeObjectField("value", enumerator.getValue()); -// jsonGenerator.writeStringField("desc", enumerator.getDesc()); -// jsonGenerator.writeEndObject(); - jsonGenerator.writeObject(enumerator.getValue()); + jsonGenerator.writeStartObject(); + jsonGenerator.writeObjectField("value", enumerator.getValue()); + jsonGenerator.writeStringField("desc", enumerator.getDesc()); + jsonGenerator.writeEndObject(); } } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillInterceptor.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillInterceptor.java new file mode 100644 index 0000000..2fb1208 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillInterceptor.java @@ -0,0 +1,24 @@ +package fun.easycode.jointblock.core; + +import fun.easycode.jointblock.datafill.DataFillExecutor; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import javax.annotation.Resource; + +/** + * interceptor 用于数据填充 + * @author xuzhe + */ +public class ExecutorDataFillInterceptor implements MethodInterceptor { + + @Resource + private DataFillExecutor dataFillExecutor; + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + Object result = methodInvocation.proceed(); + dataFillExecutor.execute(result); + return result; + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillPointcut.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillPointcut.java new file mode 100644 index 0000000..b46e1fc --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ExecutorDataFillPointcut.java @@ -0,0 +1,47 @@ +package fun.easycode.jointblock.core; + +import cn.hutool.core.util.ClassUtil; +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; + +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * 自定义aop切点 + * 当是Executor时切入,execute时执行逻辑 + * @author xuzhe + */ +public class ExecutorDataFillPointcut implements Pointcut { + @Override + public ClassFilter getClassFilter() { + return new ClassFilter() { + @Override + public boolean matches(Class aClass) { + + return ClassUtil.isAssignable(Executor.class, aClass); + } + }; + } + + @Override + public MethodMatcher getMethodMatcher() { + return new MethodMatcher() { + @Override + public boolean matches(Method method, Class aClass) { + return Objects.equals("execute", method.getName()); + } + + @Override + public boolean isRuntime() { + return false; + } + + @Override + public boolean matches(Method method, Class aClass, Object... objects) { + return false; + } + }; + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertCallback.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertCallback.java new file mode 100644 index 0000000..b7cb3cb --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertCallback.java @@ -0,0 +1,16 @@ +package fun.easycode.jointblock.core; + +/** + * 插入自定义逻辑回调 + * @author xuzhe + */ +@FunctionalInterface +public interface InsertCallback { + /** + * 用户拿指令K和数据库实体T可以进行自己自定义的逻辑 + * @param cmd 指令 + * @param entity 数据库实体 + * @return 数据库实体 + */ + T callback(K cmd, T entity); +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertOrUpdateBatchMethod.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertOrUpdateBatchMethod.java deleted file mode 100644 index 5daafb1..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertOrUpdateBatchMethod.java +++ /dev/null @@ -1,78 +0,0 @@ -package fun.easycode.jointblock.core; - -import com.baomidou.mybatisplus.core.injector.AbstractMethod; -import com.baomidou.mybatisplus.core.metadata.TableInfo; -import com.baomidou.mybatisplus.core.toolkit.StringUtils; -import org.apache.ibatis.executor.keygen.NoKeyGenerator; -import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.mapping.SqlSource; - -/** - * 批量插入或更新 - * insert into table (id,name) values (1,'xuzhen'),(2,'xuzhen2') ON DUPLICATE KEY UPDATE name=VALUES(name) - * @author xuzhen97 - */ -public class InsertOrUpdateBatchMethod extends AbstractMethod { - @Override - public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { - final String sql = ""; - final String tableName = tableInfo.getTableName(); - final String filedSql = prepareFieldSql(tableInfo); - final String modelValuesSql = prepareModelValuesSql(tableInfo); - final String duplicateKeySql =prepareDuplicateKeySql(tableInfo); - final String sqlResult = String.format(sql, tableName, filedSql, modelValuesSql,duplicateKeySql); - //System.out.println("savaorupdatesqlsql="+sqlResult); - SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); - return this.addInsertMappedStatement(mapperClass, modelClass, "insertOrUpdateBatch", sqlSource, new NoKeyGenerator(), null, null); - } - - /** - * 准备ON DUPLICATE KEY UPDATE sql - * @param tableInfo - * @return - */ - private String prepareDuplicateKeySql(TableInfo tableInfo) { - final StringBuilder duplicateKeySql = new StringBuilder(); - if(!StringUtils.isEmpty(tableInfo.getKeyColumn())) { - duplicateKeySql.append(tableInfo.getKeyColumn()).append("=values(").append(tableInfo.getKeyColumn()).append("),"); - } - - tableInfo.getFieldList().forEach(x -> { - duplicateKeySql.append(x.getColumn()) - .append("=values(") - .append(x.getColumn()) - .append("),"); - }); - duplicateKeySql.delete(duplicateKeySql.length() - 1, duplicateKeySql.length()); - return duplicateKeySql.toString(); - } - - /** - * 准备属性名 - * @param tableInfo - * @return - */ - private String prepareFieldSql(TableInfo tableInfo) { - StringBuilder fieldSql = new StringBuilder(); - fieldSql.append(tableInfo.getKeyColumn()).append(","); - tableInfo.getFieldList().forEach(x -> { - fieldSql.append(x.getColumn()).append(","); - }); - fieldSql.delete(fieldSql.length() - 1, fieldSql.length()); - fieldSql.insert(0, "("); - fieldSql.append(")"); - return fieldSql.toString(); - } - - private String prepareModelValuesSql(TableInfo tableInfo){ - final StringBuilder valueSql = new StringBuilder(); - valueSql.append(""); - if(!StringUtils.isEmpty(tableInfo.getKeyProperty())) { - valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},"); - } - tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},")); - valueSql.delete(valueSql.length() - 1, valueSql.length()); - valueSql.append(""); - return valueSql.toString(); - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertResultConvert.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertResultConvert.java new file mode 100644 index 0000000..c71fced --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/InsertResultConvert.java @@ -0,0 +1,15 @@ +package fun.easycode.jointblock.core; + +/** + * 插入成功返回结果转换 + * @author xuzhe + */ +@FunctionalInterface +public interface InsertResultConvert { + /** + * 数据库实体T转换成自定义返回结果R + * @param entity 数据库实体 + * @return R + */ + R to(T entity); +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/JointBlockMapper.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/JointBlockMapper.java deleted file mode 100644 index 614d49d..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/JointBlockMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -package fun.easycode.jointblock.core; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Param; - -import java.util.List; - -/** - * 通用mapper - * 在原版的mapper上增加了批量操作和流式查询 - * @param - */ -public interface JointBlockMapper extends BaseMapper, StreamMapper, BatchMapper{ - - /** - * 批量插入或更新 - * insert into table (id, name) values (1, 'a'), (2, 'b') on duplicate key update name = values(name) - * @param list 数据集 - * @return 影响行数 - */ - int insertOrUpdateBatch(@Param("list") List list); -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/LogUpdateWrapper.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/LogUpdateWrapper.java new file mode 100644 index 0000000..d9e6af5 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/LogUpdateWrapper.java @@ -0,0 +1,23 @@ +package fun.easycode.jointblock.core; + +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import fun.easycode.jointblock.util.LogUtil; + +/** + * 扩展UpdateWrapper用于收集插入的字段修改详情 + * @author xuzhe + */ +public class LogUpdateWrapper extends UpdateWrapper { + + private final String updateId; + + public LogUpdateWrapper(String updateId){ + this.updateId = updateId; + } + + @Override + public UpdateWrapper set(String column, Object val) { + LogUtil.setUpdateField(updateId, column, val); + return super.set(column, val); + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/OperateSymbol.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/OperateSymbol.java deleted file mode 100644 index 4c373b4..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/OperateSymbol.java +++ /dev/null @@ -1,74 +0,0 @@ -package fun.easycode.jointblock.core; - -/** - * 操作符枚举 - * @author xuzhen97 - */ -public enum OperateSymbol implements Enumerator{ - /** - * 等于 - */ - EQ("Eq", "等于"), - /** - * 模糊查询 - */ - LIKE("Like", "模糊查询"), - /** - * 模糊查询左 - */ - LIKE_LEFT("LikeLeft", "模糊查询左"), - /** - * 模糊查询右 - */ - LIKE_RIGHT("LikeRight", "模糊查询右"), - /** - * 大于 - */ - GT("Gt", "大于"), - /** - * 小于 - */ - LT("Lt", "小于"), - /** - * 大于等于 - */ - GE("Ge", "大于等于"), - /** - * 小于等于 - */ - LE("Le", "小于等于"), - /** - * in 集合查询 - */ - IN("In", "in 集合查询"), - /** - * not in 集合查询 - */ - NOT_IN("NotIn", "not in 集合查询"), - /** - * is null 查询 - */ - IS_NULL("IsNull", "is null 查询"), - /** - * 排序 - */ - SORT("Sort", "排序"); - private final String value; - private final String desc; - - OperateSymbol(String value, String desc) { - this.value = value; - this.desc = desc; - } - - @Override - public String getValue() { - return value; - } - - @Override - public String getDesc() { - return desc; - } - -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PageDto.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PageDto.java index 8134221..0ba210b 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PageDto.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PageDto.java @@ -3,8 +3,12 @@ package fun.easycode.jointblock.core; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.Builder; import lombok.Data; +import org.springframework.http.ResponseEntity; +import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -28,9 +32,9 @@ public class PageDto { * @param 实体类型 * @return PageDto */ - public static PageDto toPageDto(Page page, QueryResultConvert convert){ + public static PageDto toPageDto(Page page, Convert convert){ - List data = page.getRecords().stream().map(convert::to) + List data = page.getRecords().stream().map(convert::convert) .collect(Collectors.toList()); return PageDto.builder() .current(page.getCurrent()) @@ -41,6 +45,69 @@ public class PageDto { .build(); } + /** + * 根据ResponseEntity 返回值以及qry生成PageDto + * @param qry + * @param entity + * @param convert + * @return + * @param + * @param + * @param + */ + public static > PageDto toPageDto(PagingQry qry, ResponseEntity entity, Convert convert){ + + List data = Objects.requireNonNull(entity.getBody()) + .stream().map(convert::convert).collect(Collectors.toList()); + + return PageDto.builder() + .current(Long.valueOf(qry.getCurrent())) + .totalPage(getHeaderValue(Constant.TOTAL_PAGE_HEADER, entity, 0L, Long.class)) + .totalSize(getHeaderValue(Constant.TOTAL_SIZE_HEADER, entity, 0L, Long.class)) + .size(Long.valueOf(qry.getSize())) + .data(data) + .build(); + } + + /** + * 根据ResponseEntity 返回值生成PageDto + * @param entity + * @param convert + * @return + * @param + * @param + * @param + */ + public static > PageDto toPageDto(ResponseEntity entity, Convert convert){ + + List data = Objects.requireNonNull(entity.getBody()) + .stream().map(convert::convert).collect(Collectors.toList()); + + return PageDto.builder() + .current(getHeaderValue(Constant.CURRENT_HEADER, entity, 1L, Long.class)) + .totalPage(getHeaderValue(Constant.TOTAL_PAGE_HEADER, entity, 0L, Long.class)) + .totalSize(getHeaderValue(Constant.TOTAL_SIZE_HEADER, entity, 0L, Long.class)) + .size(getHeaderValue(Constant.SIZE_HEADER, entity, 10L, Long.class)) + .data(data) + .build(); + } + + /** + * 获取header通用封装方法 + * @param header + * @param responseEntity + * @param defaultValue + * @param type + * @return + * @param + */ + private static T getHeaderValue(String header, ResponseEntity responseEntity, T defaultValue, Class type){ + + return Optional.ofNullable(responseEntity.getHeaders().get(header)) + .map(headers-> cn.hutool.core.convert.Convert.convert(type, headers.get(0))) + .orElse(defaultValue); + } + /** * 转换,这是mybatis plus page类型和dto类型转换的声明 * @param diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PageQry.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PagingQry.java similarity index 90% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PageQry.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PagingQry.java index b2865ff..66df9d4 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PageQry.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/PagingQry.java @@ -9,7 +9,7 @@ import javax.validation.constraints.NotNull; * @author xuzhe */ @Data -public class PageQry { +public class PagingQry { @NotNull private Integer size = 10; @NotNull diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryDslParser.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryDslParser.java deleted file mode 100644 index f4a7436..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryDslParser.java +++ /dev/null @@ -1,47 +0,0 @@ -package fun.easycode.jointblock.core; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 查询表达式解析器 - * @author xuzhen97 - */ -public class QueryDslParser { - - private static final Pattern QUERY_PATTERN = Pattern.compile("(\\w+?)(\\[.+?\\]=)([a-zA-Z0-9\\u4e00-\\u9fa5()|\\s\\-:]+?),"); - private static final Pattern OPERATOR_PATTERN = Pattern.compile("(?<=\\[).*(?=\\])"); - /** - * 解析查询表达式 - * @param value 查询表达式 - * @param tClass 实体类 - * @param 实体类 - * @return 查询构造器 - */ - public static QueryWrapperBuilder parser(String value, Class tClass) { - QueryWrapperBuilder builder; - if(tClass == null){ - builder = new QueryWrapperBuilder<>(); - }else{ - builder = new QueryWrapperBuilder<>(tClass); - } - // 整体表达式正则 - Matcher matcher = QUERY_PATTERN.matcher(value + ","); - // 操作符匹配正则 - while (matcher.find()) { - Matcher operatorMatcher = OPERATOR_PATTERN.matcher( matcher.group(2)); - builder.with(matcher.group(1), operatorMatcher.find() ? operatorMatcher.group() : "", matcher.group(3)); - } - return builder; - } - - /** - * 解析查询表达式 - * @param value 查询表达式 - * @param 实体类 - * @return 查询构造器 - */ - public static QueryWrapperBuilder parser(String value) { - return parser(value, null); - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryHandlerMethodArgumentResolver.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryHandlerMethodArgumentResolver.java deleted file mode 100644 index 8d72c75..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryHandlerMethodArgumentResolver.java +++ /dev/null @@ -1,37 +0,0 @@ -package fun.easycode.jointblock.core; - -import org.springframework.core.MethodParameter; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.ModelAndViewContainer; - -/** - * 查询参数解析器 - * @author xuzhen97 - */ -public class QueryHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { - @Override - public boolean supportsParameter(MethodParameter methodParameter) { - return methodParameter.hasParameterAnnotation(QueryParam.class); - } - - @Override - public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { - QueryParam queryParam = methodParameter.getParameterAnnotation(QueryParam.class); - - String name = "".equalsIgnoreCase(queryParam.name()) ? queryParam.value() : queryParam.name(); - - if ("".equalsIgnoreCase(name)) { - name = methodParameter.getParameter().getName(); - } - - String value = nativeWebRequest.getParameter(name); - - if (value == null) { - return null; - } - - return QueryDslParser.parser(value, queryParam.root()).build(); - } -} \ No newline at end of file diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryParam.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryParam.java deleted file mode 100644 index fe9c037..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryParam.java +++ /dev/null @@ -1,22 +0,0 @@ -package fun.easycode.jointblock.core; - -import org.springframework.core.annotation.AliasFor; - -import java.lang.annotation.*; - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface QueryParam { - /** - * Alias for {@link #name}. - */ - @AliasFor("name") String value() default ""; - /** - * The name of the request parameter to bind to. - * - * @since 4.2 - */ - @AliasFor("value") String name() default ""; - Class root(); -} \ No newline at end of file diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryWrapperBuilder.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryWrapperBuilder.java deleted file mode 100644 index 8e43ecb..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/QueryWrapperBuilder.java +++ /dev/null @@ -1,120 +0,0 @@ -package fun.easycode.jointblock.core; - -import cn.hutool.core.util.ObjectUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.LambdaUtils; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class QueryWrapperBuilder { - - private final List params; - private final Class tClass; - private final Map columnMap; - - public QueryWrapperBuilder(Class tClass){ - params = new ArrayList<>(); - this.tClass = tClass; - columnMap = LambdaUtils.getColumnMap(tClass); - } - - public QueryWrapperBuilder(){ - params = new ArrayList<>(); - this.tClass = null; - this.columnMap = null; - } - - public QueryWrapperBuilder with(String key, String operation, Object value){ - params.add(new SearchCriteria(key, operation, value)); - return this; - } - - public QueryWrapper build(){ - if(params.size() == 0){ - return null; - } - QueryWrapper queryWrapper = Wrappers.query(); - - for(SearchCriteria criteria : params){ - String tableField = getTableField(criteria.getKey()); - // 解析查询条件 - analysis(tableField, criteria.getOperation(), criteria.getValue(), queryWrapper); - } - return queryWrapper; - } - - private String getTableField(String key){ - if(tClass != null) { - String columnKey = LambdaUtils.formatKey(key); - return columnMap.get(columnKey).getColumnSelect(); - }else{ - return key; - } - } - - /** - * 解析查询条件 - * @param tableField 字段名 - * @param operation 操作符 - * @param value 值 - * @param wrapper 条件构造器 - * @param 实体类 - */ - private static void analysis(String tableField, String operation, Object value, QueryWrapper wrapper) { - - // 如果是空值,不处理 - if (!ObjectUtil.isNotEmpty(value)) { - return; - } - - if (Objects.equals(OperateSymbol.EQ.getValue(), operation)) { - wrapper.eq(tableField, value); - } else if (Objects.equals(OperateSymbol.LIKE.getValue(), operation)) { - wrapper.like(tableField, value); - } else if (Objects.equals(OperateSymbol.LIKE_LEFT.getValue(), operation)) { - wrapper.likeLeft(tableField, value); - } else if (Objects.equals(OperateSymbol.LIKE_RIGHT.getValue(), operation)) { - wrapper.likeRight(tableField, value); - } else if (Objects.equals(OperateSymbol.GT.getValue(), operation)) { - wrapper.gt(tableField, value); - } else if (Objects.equals(OperateSymbol.LT.getValue(), operation)) { - wrapper.lt(tableField, value); - } else if (Objects.equals(OperateSymbol.GE.getValue(), operation)) { - wrapper.ge(tableField, value); - } else if (Objects.equals(OperateSymbol.LE.getValue(), operation)) { - wrapper.le(tableField, value); - } else if (Objects.equals(OperateSymbol.IS_NULL.getValue(), operation)) { - if ((Boolean) value) { - wrapper.isNull(tableField); - } else { - wrapper.isNotNull(tableField); - } - } else if (Objects.equals(OperateSymbol.NOT_IN.getValue(), operation)) { - wrapper.notIn(tableField, getValueCollection(value)); - } else if (Objects.equals(OperateSymbol.IN.getValue(), operation)) { - wrapper.in(tableField, getValueCollection(value)); - } else if (Objects.equals(OperateSymbol.SORT.getValue(), operation)) { - if (ObjectUtil.equal(String.valueOf(value).toUpperCase(), "DESC")) { - wrapper.orderByDesc(tableField); - } else { - wrapper.orderByAsc(tableField); - } - } else { - throw new CheckException("不支持的查询条件!"); - } - } - - private static List getValueCollection(Object value) { - Pattern valuePattern = Pattern.compile("(?<=\\().*(?=\\))"); - Matcher matcher = valuePattern.matcher(value.toString()); - if (!matcher.find()) { - throw new CheckException("集合条件value表达式不正确!"); - } - String[] values = matcher.group().split("\\|"); - return Arrays.asList(values); - } -} \ No newline at end of file diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/RequestHolder.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/RequestHolder.java similarity index 99% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/RequestHolder.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/RequestHolder.java index 8bc6344..9f1aaaa 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/RequestHolder.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/RequestHolder.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.util; +package fun.easycode.jointblock.core; import lombok.extern.slf4j.Slf4j; import org.springframework.web.context.request.RequestAttributes; diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/SearchCriteria.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/SearchCriteria.java deleted file mode 100644 index 12e60dd..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/SearchCriteria.java +++ /dev/null @@ -1,25 +0,0 @@ -package fun.easycode.jointblock.core; - -import lombok.AllArgsConstructor; -import lombok.Data; - -/** - * 查询条件 - * @author xuzhen97 - */ -@Data -@AllArgsConstructor -public class SearchCriteria { - /** - * 查询字段 - */ - private String key; - /** - * 查询条件 - */ - private String operation; - /** - * 查询值 - */ - private Object value; -} \ No newline at end of file diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateCallback.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateCallback.java new file mode 100644 index 0000000..62773e9 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateCallback.java @@ -0,0 +1,19 @@ +package fun.easycode.jointblock.core; + +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; + +/** + * 修改回调,用于自定义自己的修改逻辑 + * @author xuzhe + */ +@FunctionalInterface +public interface UpdateCallback { + + /** + * 修改自定义逻辑 + * @param cmd 指令 + * @param wrapper UpdateWrapper + * @return UpdateWrapper + */ + UpdateWrapper callback(K cmd, UpdateWrapper wrapper); +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateResultConvert.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateResultConvert.java new file mode 100644 index 0000000..27d2458 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UpdateResultConvert.java @@ -0,0 +1,15 @@ +package fun.easycode.jointblock.core; + +/** + * 修改结果转换 + * @author xuzhe + */ +@FunctionalInterface +public interface UpdateResultConvert { + /** + * 实体T转换成返回结果R + * @param entity 数据库实体 + * @return 要转换的实体 + */ + R to(T entity); +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UserHolder.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UserHolder.java index 15446ba..dce2334 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UserHolder.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/UserHolder.java @@ -1,5 +1,6 @@ package fun.easycode.jointblock.core; +import fun.easycode.jointblock.exception.CheckException; import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; @@ -10,7 +11,7 @@ import org.springframework.context.ApplicationContextAware; * 用户持有者,用来操作当前用户信息 * @author xuzhe */ -public abstract class UserHolder implements ApplicationContextAware { +public abstract class UserHolder implements ApplicationContextAware { private static ApplicationContext context; @@ -22,14 +23,38 @@ public abstract class UserHolder implements ApplicationConte return context.getBean(UserHolder.class); } + /** + * 获取当前用户id + * @return String + */ + public abstract String getUserId(); + /** * 获取当前登录用户 * @return 登录用户 + * @param 登录用户泛型 */ - public T getLoginUser(){ + public T getUser(){ throw new CheckException("先重写getUser方法!"); } + /** + * 判断是否登录 + * @return 是否 + */ + public boolean isLogin(){ + throw new CheckException("先重写isLogin方法!"); + } + + /** + * 根据用户名强制下线用户 + * @param username 用户名 + */ + public void offline(String username){ + throw new CheckException("先重写offline方法!"); + } + + @Override public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { context = applicationContext; @@ -42,14 +67,4 @@ public abstract class UserHolder implements ApplicationConte protected static ApplicationContext getContext(){ return context; } - - /** - * 默认的UserHolder - */ - public static class DefaultUserHolder extends UserHolder{ - @Override - public UserInfo getLoginUser() { - return () -> "jointblock"; - } - } } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataAlias.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataAlias.java new file mode 100644 index 0000000..d023b0b --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataAlias.java @@ -0,0 +1,16 @@ +package fun.easycode.jointblock.datafill; + +import java.lang.annotation.*; + +/** + * @author xuzhe + */ +@Target({ElementType.FIELD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataAlias { + /** + * 实体的主键名, 默认id + */ + String value(); +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataCopy.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataCopy.java new file mode 100644 index 0000000..ef73b9f --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataCopy.java @@ -0,0 +1,244 @@ +package fun.easycode.jointblock.datafill; + +import cn.hutool.core.util.ReflectUtil; +import fun.easycode.jointblock.core.PageDto; +import fun.easycode.jointblock.exception.CheckException; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.StringUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.*; + +/** + * @author xuzhe + */ +public class DataCopy implements ApplicationContextAware { + + private static ApplicationContext context; + private ExpressionParser expressionParser; + + /** + * 解析spEl表达式, 传输两个对象source 和 target 到上下文 + * @param source + * @param target + * @param spEl + * @param tClass + * @return + * @param + */ + public Optional parserSpEl(Object source, Object target, String spEl, Class tClass){ + if(StringUtils.isEmpty(spEl)){ + return Optional.empty(); + } + EvaluationContext evaluationContext = new StandardEvaluationContext(context); + evaluationContext.setVariable("source", source); + evaluationContext.setVariable("target", target); + Expression expression = expressionParser.parseExpression(spEl); + return Optional.ofNullable(expression.getValue(evaluationContext, tClass)); + } + + /** + * 解析spEl表达式 + * @param spEl + * @param tClass + * @return + * @param + */ + public Optional parserSpEl(String spEl, Class tClass){ + if(StringUtils.isEmpty(spEl)){ + return Optional.empty(); + } + EvaluationContext evaluationContext = new StandardEvaluationContext(context); + Expression expression = expressionParser.parseExpression(spEl); + return Optional.ofNullable(expression.getValue(evaluationContext, tClass)); + } + + /** + * 解析spEl表达式,允许往上下文传输contextProps变量列表,供用户使用 + * @param spEl + * @param tClass + * @param contextProps + * @return + * @param + */ + public Optional parserSpEl(String spEl, Class tClass,final Map contextProps){ + if(StringUtils.isEmpty(spEl)){ + return Optional.empty(); + } + EvaluationContext evaluationContext = new StandardEvaluationContext(context); + if(contextProps != null){ + contextProps.forEach((key,value)->{ + evaluationContext.setVariable(key, value); + }); + } + Expression expression = expressionParser.parseExpression(spEl); + return Optional.ofNullable(expression.getValue(evaluationContext, tClass)); + } + + /** + * 对象属性copy + * 方法支持使用@DataAlias注解进行转义 + * 例如目标中叫做id ,但是在source中叫做userId + * 那么我们可以在目标中增加注解@DataAlias("userId")来进行对接 + * + * @param source 元对象 + * @param target 目标对象 + */ + @SuppressWarnings("all") + public void copy(Object source, Object target, String spEl) { + + Map sourceFieldMap = ReflectUtil.getFieldMap(source.getClass()); + Map targetFieldMap = ReflectUtil.getFieldMap(target.getClass()); + + for (Map.Entry targetEntry : targetFieldMap.entrySet()) { + Field targetField = targetEntry.getValue(); + String targetFieldName; + if (targetField.isAnnotationPresent(DataAlias.class)) { + DataAlias dataAlias = targetField.getAnnotation(DataAlias.class); + targetFieldName = dataAlias.value(); + } else { + targetFieldName = targetEntry.getKey(); + } + + Field sourceField = sourceFieldMap.get(targetFieldName); + + // 如果source和target对象,属性值能直接匹配的话,就直接copy + if (sourceField != null) { + Object value = ReflectUtil.getFieldValue(source, sourceField); + ReflectUtil.setFieldValue(target, targetField, value); + } + // 如果target对象中的属性是一个完整的对象,或者list, 就需要增加DataGenerate处理 + // 要知道如果我们对象很简单数据填充的时候只需要mapstruct将id转换成空对象即可 + // 但是超过两层结构,我们的数据其实还没有加载,那么就需要DataGenerate来进行解决了 + else if (targetField.isAnnotationPresent(DataGenerate.class)) { + DataGenerate dataGenerate = targetField.getAnnotation(DataGenerate.class); + + sourceField = sourceFieldMap.get(dataGenerate.value()); + Object sourceFieldValue = ReflectUtil.getFieldValue(source, sourceField); + + // 如果是id生成的默认形式,会根据我们声明的目标对象类型,生成空对象 + // 然后去source对象中去取指定的属性值填充到空对象内 + // 通俗的讲 就是id属性转换成一个对象,对象中仅仅id属性有值 + if (dataGenerate.mode() == IDGenerateMode.DEFAULT) { + // 根据要生成空对象的Field生成实例 + Object targetFieldValue = ReflectUtil.newInstance(targetField.getType()); + + // 生成实例后将主键信息填写进去 + Field subTargetField = ReflectUtil.getField(targetFieldValue.getClass() + , dataGenerate.targetField()); + + if (targetField != null) { + ReflectUtil.setFieldValue(targetFieldValue, subTargetField, sourceFieldValue); + } + ReflectUtil.setFieldValue(target, targetField, targetFieldValue); + + } else if (dataGenerate.mode() == IDGenerateMode.MULTIPLE + && dataGenerate.listClass() != void.class) { + // 这里主要是处理数据库中一个字段存了多个id类似 aa,bb,cc这种 + // 而我们需要将多个id生成为list集合, 方便填充框架下一步工作 + String idsStr = (String) sourceFieldValue; + // 数据库的主键分割 + List ids = Arrays.asList(idsStr.split(dataGenerate.listSeparator())); + List list = new ArrayList(); + for (String id : ids) { + Object targetFieldValue = ReflectUtil.newInstance(dataGenerate.listClass()); + // 生成实例后将主键信息填写进去 + Field subTargetField = ReflectUtil.getField(targetFieldValue.getClass() + , dataGenerate.targetField()); + + if (subTargetField != null) { + ReflectUtil.setFieldValue(targetFieldValue, targetField, id); + } + list.add(targetFieldValue); + } + ReflectUtil.setFieldValue(target, targetField, list); + } + } + } + // 如果非基本类型定义了el表达式,不会取返回参数,会执行用户自定义的逻辑 + if(!StringUtils.isEmpty(spEl)){ + parserSpEl(source, target, spEl, Void.class); + } + } + + public Object getMapObjById(Object id, Map map){ + return map.get(id); + } + + public Object getPageDtoById(Object id , PageDto result + , Class idAnnotation){ + return getCollectionObjById(id, result.getData(), idAnnotation); + } + + public Object getObjById(Object id, Object container, Class idAnnotation){ + if(container instanceof Map){ + return getMapObjById(id, (Map) container); + }else if (container instanceof Collection){ + return getCollectionObjById(id, (Collection) container, idAnnotation); + }else if(container instanceof PageDto){ + return getPageDtoById(id, (PageDto) container, idAnnotation); + }else{ + throw new CheckException("不支持的容器!"); + } + } + + /** + * 根据id获取List中符合的元素 + * 默认取id的列对比,如果没有叫做id的列则取@TableId的列对比 + * @param id + * @param collection + * @param idAnnotation 如果id不是主键默认名称,应该以什么注解进行判断 + * @return + */ + @SuppressWarnings("all") + public Object getCollectionObjById(Object id, Collection collection, Class idAnnotation){ + + if(collection == null || collection.size() == 0){ + return null; + } + + Class elementClass = collection.stream().findFirst().get().getClass(); + Map fieldMap = ReflectUtil.getFieldMap(elementClass); + + Field field = fieldMap.get("id"); + + if(field == null){ + + Optional first = fieldMap.values() + .stream() + .filter(f -> f.isAnnotationPresent(idAnnotation)) + .findFirst(); + + field = first.isPresent()?first.get():null; + + if(field == null){ + throw new CheckException(elementClass.getName() + "请增加注解" + idAnnotation.getName()+"标记主键!"); + } + } + + final Field conditionField = field; + + Optional first = collection.stream().filter(obj -> Objects.equals(id, ReflectUtil.getFieldValue(obj, conditionField))) + .findFirst(); + + return first.isPresent()?first.get():null; + } + + public static DataCopy getInstance(){ + return context.getBean(DataCopy.class); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + expressionParser = new SpelExpressionParser(); + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFill.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFill.java new file mode 100644 index 0000000..f11ff5a --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFill.java @@ -0,0 +1,56 @@ +package fun.easycode.jointblock.datafill; + +import java.lang.annotation.*; + +/** + * @author xuzhe + */ +@Target({ElementType.FIELD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataFill { + /** + * 实体的主键名, 默认id + * @return id字段名 + */ + String value() default "id"; + + /** + * 注解对应处理器 + * @return 对应的处理器 + */ + Class source(); + + /** + * spring el表达式 + * 如果我们自定义表达式 + * 当DataFill所在基本数据类型的时候spEl表达式的值是最终填充的值 + * 所在属性是对象,或者是list的时候,表达式不取返回值 + * 我们可以在Mapstruct转换器中自定义处理逻辑 + * 固定会传入target对象和source对象,target对象就是当前属性的值 + * List的话就是其中的对象,source是id查出的对应实体 + * @return 我们自定义的逻辑 + */ + String spEl() default ""; + + /** + * 自定义传参 + * DataParam value支持el表达式 + * @return 参数列表 + */ + DataParam[] params() default {}; + + /** + * 自定义调用方法名,目前仅对feign client生效 + * 如果不指定方法名,那么在feign client的指定中,client只能存在一个方法 + * @return 方法名 + */ + String methodName() default ""; + + /** + * 是否自定义mybatis plus id注解 + * @Author: xuzhen97 + * @Date: 2023/03/17 + */ + boolean isCustomMyBatisPlusAnno() default false; +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillActuator.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillActuator.java deleted file mode 100644 index a32b7c5..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillActuator.java +++ /dev/null @@ -1,49 +0,0 @@ -package fun.easycode.jointblock.datafill; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 数据填充执行器 - * @param 数据类型 - */ -public class DataFillActuator { - // 数据 - private final Collection data; - // 填充过程 - private final List> processes = new ArrayList<>(); - - /** - * 构造函数 - * @param data 数据 - */ - public DataFillActuator(Collection data) { - this.data = data; - } - - /** - * 增加填充过程 - * @param process 填充过程 - */ - public DataFillActuator addProcess(DataFillProcess process) { - processes.add(process); - return this; - } - - /** - * 执行 - */ - public void execute() { - List> tasks = processes.stream() - .map(process -> new DataFillTask<>(process, data)) - .collect(Collectors.toList()); - try { - // 执行任务 - DataFillThreadExecutor.execParallelTasks(tasks); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillExecutor.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillExecutor.java new file mode 100644 index 0000000..4a363e7 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillExecutor.java @@ -0,0 +1,193 @@ +package fun.easycode.jointblock.datafill; + +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.HashUtil; +import cn.hutool.core.util.ReflectUtil; +import fun.easycode.jointblock.core.PageDto; +import fun.easycode.jointblock.exception.CheckException; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 数据填充工厂 + * + * @author xuzhe + */ +@Slf4j +public class DataFillExecutor { + + /** + * 填充框架执行 + * + * @param o 填充对象 + */ + public void execute(Object o) { + List metadataList = new ArrayList<>(); + fetch(o, metadataList); + executeMetaDataList(metadataList); + } + + private void executeMetaDataList(List metadataList) { + + executeDataFill(metadataList); + List metadataListSub = new ArrayList<>(); + + for (DataFillMetadata metadata : metadataList) { + fetch(metadata.getTargetObjMap().values(), metadataListSub); + } + if (metadataListSub.size() > 0) { + executeMetaDataList(metadataListSub); + } + } + + private void executeDataFill(List metadataList) { + //异步任务 + List>> tasks = new ArrayList<>(); + + // 现根据sourceClass进行分组 + // 实际上就是BaseMapper或者FeignContract的实现 + // 其它实现不支持,默认不处理 + Map, List> anClassMetadatas = metadataList.stream() + .collect(Collectors.groupingBy(dataFillMetadata -> dataFillMetadata.getDataFill().source())); + + // 根据params hash 进行group分组生成task + anClassMetadatas.forEach((sourceClass, sourceClassMetadataList) -> { + sourceClassMetadataList.stream().collect(Collectors.groupingBy(this::hashParams)).values() + .forEach(hashMetadataList -> { + DataFillStrategyContext.getStrategy(sourceClass).ifPresent(strategy -> { + tasks.add(new DataFillTask<>(strategy, hashMetadataList)); + }); + }); + }); + + try { + DataFillThreadPoolManager.execParallelTasks(tasks); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 填充信息拉取 + * + * @param o 处理对象 + * @param metadataList 元数据集合 + */ + @SuppressWarnings("all") + private void fetch(Object o, List metadataList) { + if (o == null || metadataList == null) { + return; + } + if (o instanceof Collection) { + for (Object o1 : ((Collection) o)) { + fetch(o1, metadataList); + } + } else if (o instanceof Map) { + for (Object value : ((Map) o).values()) { + fetch(value, metadataList); + } + } else if (o instanceof PageDto) { + PageDto pageDto = (PageDto) o; + fetch(pageDto.getData(), metadataList); + } else { + Class oClass = o.getClass(); + // 这里抛出的异常其最终目的就是中断当前集合的遍历, + // 如果集合容器中第一个节点到达这里并判断没有填充标识,则当前容器就不需要在继续遍历下去了 + if (oClass.getAnnotation(EnableDataFill.class) == null) { + return; + } + + // 如果传入操作对象是基本数据类型或者String不进行下面的逻辑 + if (ClassUtil.isBasicType(oClass) + || oClass.isAssignableFrom(String.class)) { + return; + } + + Map fieldMap = ReflectUtil.getFieldMap(oClass); + + for (Map.Entry entry : fieldMap.entrySet()) { + + Field field = entry.getValue(); + + // 非基本类型而且拥有DataFill的注解才代表需要处理 + DataFill dataFill = field.getAnnotation(DataFill.class); + + // 字段上未增加DataFill注解,代表字段不需要处理 + if (dataFill == null) { + continue; + } + + Object fieldValue = ReflectUtil.getFieldValue(o, field); + + if (ClassUtil.isBasicType(field.getType()) + || field.getType().isAssignableFrom(String.class)) { + // 基本类型或者String 给fieldValue复制null, 方便下边的判断 + fieldValue = null; + } else { + if (fieldValue == null) { + continue; + } + } + + DataFillMetadata dataFillMetadata = new DataFillMetadata(); + dataFillMetadata.setDataFill(dataFill); + dataFillMetadata.setOpObj(o); + dataFillMetadata.setOpField(field); + + Map targetObjMap = new HashMap<>(); + + // DataFill可以依附的对象 + // - Collection + // - 普通对象 + // - 基本类型 + if (fieldValue == null) { + // 如果要操作的field是基本数据类型或者String + // 对象或者List都是去对象、List内对象里面的主键属性 + // 但是基本数据类型取的是opObj操作对象的属性 + // 这里是结合上面的判断得出的fieldValue == null 是基本数据类型或者String + Field primaryKeyField = fieldMap.get(dataFill.value()); + Object primaryKeyValue = ReflectUtil.getFieldValue(o, primaryKeyField); + targetObjMap.put(primaryKeyValue, null); + } else if (fieldValue instanceof Collection) { + for (Object targetObj : (Collection) fieldValue) { + Field primaryKeyField = ReflectUtil.getField(targetObj.getClass(), dataFill.value()); + Object primaryKeyValue = ReflectUtil.getFieldValue(targetObj, primaryKeyField); + targetObjMap.put(primaryKeyValue, targetObj); + } + } else { + // 如果是普通对象 + Field primaryKeyField = ReflectUtil.getField(fieldValue.getClass(), dataFill.value()); + Object primaryKeyValue = ReflectUtil.getFieldValue(fieldValue, primaryKeyField); + + targetObjMap.put(primaryKeyValue, fieldValue); + } + + dataFillMetadata.setTargetObjMap(targetObjMap); + + metadataList.add(dataFillMetadata); + } + } + } + + /** + * 根据DataParam[] 参数列表计算一个hash + * 用于分组,不同的条件需要不同的查询 + * + * @param metadata DataFillMetadata + * @return long hash + */ + public long hashParams(DataFillMetadata metadata) { + DataParam[] params = metadata.getDataFill().params(); + Map paramMap = Arrays.stream(params) + .collect(Collectors.toMap(DataParam::name, p -> p)); + + List paramNameList = Arrays.stream(params).map(DataParam::name) + .sorted().collect(Collectors.toList()); + String grouping = paramNameList.stream().map(paramName -> paramName + paramMap.get(paramName)) + .collect(Collectors.joining()); + return HashUtil.mixHash(grouping); + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillId.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillId.java new file mode 100644 index 0000000..4f27c1b --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillId.java @@ -0,0 +1,9 @@ +package fun.easycode.jointblock.datafill; + +import java.lang.annotation.*; + +@Target({ElementType.FIELD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataFillId { +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillMetadata.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillMetadata.java new file mode 100644 index 0000000..5a0ccbf --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillMetadata.java @@ -0,0 +1,35 @@ +package fun.easycode.jointblock.datafill; + +import lombok.Data; + +import java.lang.reflect.Field; +import java.util.Map; + +/** + * 元数据填充类型 + * @author xuzhe + */ +@Data +public class DataFillMetadata { + + /** + * 数据填充操作对象 + */ + private Object opObj; + /** + * 数据填充操作属性 + */ + private Field opField; + + /** + * 数据填充目标对象 + * key = 主键 + * value = 仅有主键属性的空对象 + */ + private Map targetObjMap; + + /** + * 数据填充操作属性上标注的@DataFill注解 + */ + private DataFill dataFill; +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcess.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcess.java deleted file mode 100644 index 92eaae1..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcess.java +++ /dev/null @@ -1,38 +0,0 @@ -package fun.easycode.jointblock.datafill; - -import lombok.Getter; - -import java.util.Collection; -import java.util.function.BiConsumer; -import java.util.function.Function; - -/** - * 数据填充过程 - * @author xuzhen97 - */ -@Getter -public class DataFillProcess { - - // key获取函数 - private final Function keyGetFun; - // 填充数据获取函数 - private final Function, Collection> fillDataGetFun; - // 填充数据key获取函数 - private final Function fillKeyGetFun; - // 填充函数 - private final BiConsumer fillFun; - - /** - * 构造函数 - * @param keyGetFun key获取函数 - * @param fillDataGetFun 填充数据获取函数 - * @param fillKeyGetFun 填充数据key获取函数 - * @param fillFun 填充函数 - */ - protected DataFillProcess(Function keyGetFun, Function, Collection> fillDataGetFun, Function fillKeyGetFun, BiConsumer fillFun) { - this.keyGetFun = keyGetFun; - this.fillDataGetFun = fillDataGetFun; - this.fillKeyGetFun = fillKeyGetFun; - this.fillFun = fillFun; - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcessBuilder.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcessBuilder.java deleted file mode 100644 index 541df79..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillProcessBuilder.java +++ /dev/null @@ -1,107 +0,0 @@ -package fun.easycode.jointblock.datafill; - -import cn.hutool.core.util.ReflectUtil; - -import java.util.Collection; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * 数据填充过程构建器 - * @param 数据类型 - * @param 填充逻辑远程返回的数据类型 - * @author xuzhen97 - */ -public class DataFillProcessBuilder { - - // key获取函数 - private Function keyGetFun; - // 填充数据获取函数 - private Function, Collection> fillDataGetFun; - // 填充数据key获取函数 - private Function fillKeyGetFun; - // 填充函数 - private BiConsumer fillFun; - - /** - * 设置key获取函数 - * @param keyGetFun key获取函数 - * @return this - */ - public DataFillProcessBuilder keyGetFun(Function keyGetFun) { - this.keyGetFun = keyGetFun; - return this; - } - - /** - * 设置填充数据获取函数 - * @param fillDataGetFun 填充数据获取函数 - * @return this - */ - public DataFillProcessBuilder fillDataGetFun(Function, Collection> fillDataGetFun) { - this.fillDataGetFun = fillDataGetFun; - return this; - } - - /** - * 设置填充数据key获取函数 - * @param fillKeyGetFun 填充数据key获取函数 - * @return this - */ - public DataFillProcessBuilder fillKeyGetFun(Function fillKeyGetFun) { - this.fillKeyGetFun = fillKeyGetFun; - return this; - } - - /** - * 设置填充函数 - * @param fillFun 填充函数 - * @return this - */ - public DataFillProcessBuilder fillFun(BiConsumer fillFun) { - this.fillFun = fillFun; - return this; - } - - /** - * 构建 - * @return 数据填充过程 - */ - public DataFillProcess build() { - if (keyGetFun == null) { - throw new IllegalArgumentException("keyGetFun不能为空"); - } - if (fillDataGetFun == null) { - throw new IllegalArgumentException("fillDataGetFun不能为空"); - } - if(fillKeyGetFun == null){ - // 默认使用id字段 - fillKeyGetFun = e -> ReflectUtil.getFieldValue(e, "id"); - } - if (fillFun == null) { - throw new IllegalArgumentException("fillFun不能为空"); - } - return new DataFillProcess<>(keyGetFun, fillDataGetFun,fillKeyGetFun, fillFun); - } - - /** - * 获取构建器 - * @param 数据类型 - * @param 填充逻辑远程返回的数据类型 - * @return 构建器 - */ - public static DataFillProcessBuilder builder() { - return new DataFillProcessBuilder<>(); - } - - /** - * 请求, 简化填充写法 - * @param supplier 请求函数 - * @param 返回类型 - * @return 返回值 - */ - public static RE request(Supplier supplier){ - return supplier.get(); - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategy.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategy.java new file mode 100644 index 0000000..0268ee6 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategy.java @@ -0,0 +1,110 @@ +package fun.easycode.jointblock.datafill; + +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ReflectUtil; +import org.springframework.util.StringUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Optional; + +/** + * 数据填充策略 + * + * @author xuzhe + */ +public interface DataFillStrategy { + + /** + * 填充逻辑 + * + * @param metadataList 元数据集合 + */ + void fill(List metadataList); + + /** + * 获取可以处理的类型Class + * + * @return Class + */ + default Class getType() { + return void.class; + } + + /** + * 获取注解,数据源对象上增加注解也可以 + * @return + */ + default Class getAnnotation(){ + return null; + } + + /** + * 基本数据类型set逻辑 + * @param metadata + * @param container + * @param ofAnnoClass + * @param + */ + default void basicTypeSet(DataFillMetadata metadata, Object container , Class ofAnnoClass){ + Object primaryKey = metadata.getTargetObjMap().keySet().stream().findFirst().get(); + Object databaseObj = DataCopy.getInstance().getObjById(primaryKey, container, ofAnnoClass); + + // 查找不到填充对象则本地处理结束 + if(databaseObj == null){ + return; + } + + // 如果返回值是基本数据类型那么就直接就是值 + if(ClassUtil.isBasicType(databaseObj.getClass()) + || databaseObj.getClass().isAssignableFrom(String.class)){ + ReflectUtil.setFieldValue(metadata.getOpObj(), metadata.getOpField(), databaseObj); + }else { + + Object value = null; + + // 如果用户自定义了spEl表达式,那么完全以el表达式的结果作为value + // 如果没有定义spEl表达式则查找同名的属性进行填充 + if (!StringUtils.isEmpty(metadata.getDataFill().spEl())) { + Optional valueOptional = DataCopy.getInstance().parserSpEl( + databaseObj, + null, + metadata.getDataFill().spEl(), + metadata.getOpField().getType()); + value = valueOptional.orElse(null); + } else { + Field databaseObjField = ReflectUtil.getField(databaseObj.getClass() + , metadata.getOpField().getName()); + + if (databaseObjField != null) { + value = ReflectUtil.getFieldValue(databaseObj, databaseObjField); + } + } + ReflectUtil.setFieldValue(metadata.getOpObj(), metadata.getOpField(), value); + } + } + + /** + * 处理返回结构 + * @param metadata 元数据 + * @param container 数据源查询后返回的容器,就是list或者map + * @param idAnno 当返回的数据主键不是id的时候,用什么注解进行的标注 + */ + default void handlerResult(DataFillMetadata metadata, Object container, Class idAnno){ + // 如果目标对象是基本类型,我们直接去找source中有没有重名的属性 + // 有就取值 + if (ClassUtil.isBasicType(metadata.getOpField().getType()) + || metadata.getOpField().getType().isAssignableFrom(String.class)) { + basicTypeSet(metadata, container, idAnno); + }else{ + metadata.getTargetObjMap().forEach((key, value) -> { + Object databaseObj = DataCopy.getInstance().getObjById(key, container, idAnno); + if (databaseObj != null) { + DataCopy.getInstance().copy(databaseObj + , value, metadata.getDataFill().spEl()); + } + }); + } + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategyContext.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategyContext.java new file mode 100644 index 0000000..2ae407f --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillStrategyContext.java @@ -0,0 +1,38 @@ +package fun.easycode.jointblock.datafill; + +import cn.hutool.core.util.ClassUtil; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import java.util.Map; +import java.util.Optional; + +/** + * 填充策略Context + * @author xuzhe + */ +public class DataFillStrategyContext implements ApplicationContextAware { + + // 实际上这里的key无用,因为判断条件需要ClassUtil.isAssignable判断 + // 并不是一个简单的key + private static Map strategies; + + /** + * 获取策略,就是根据DataFill上面的class + * @param type 类型 + * @return 填充策略 + */ + public static Optional getStrategy(Class type){ + // 填充策略寻找规则,注解和type的方式只要有一种即可 + return strategies.values().stream() + .filter(sgy -> (sgy.getAnnotation() != null && type.isAnnotationPresent(sgy.getAnnotation())) + || (sgy.getType() != void.class && ClassUtil.isAssignable(sgy.getType(), type))) + .findFirst(); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + DataFillStrategyContext.strategies = context.getBeansOfType(DataFillStrategy.class); + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillTask.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillTask.java index 6eae579..8aedf6f 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillTask.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillTask.java @@ -2,46 +2,35 @@ package fun.easycode.jointblock.datafill; import lombok.extern.slf4j.Slf4j; -import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.concurrent.Callable; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.stream.Collectors; /** * 数据框架填充任务 - * @author xuzhen97 + * @author xuzhe */ @Slf4j -public class DataFillTask implements Callable { +public class DataFillTask implements Callable> { - private final DataFillProcess process; - private final Collection data; + private final DataFillStrategy strategy; + private final List metadataList; - public DataFillTask(DataFillProcess process, Collection data) { - this.process = process; - this.data = data; - } - @Override - public Void call() throws Exception { - Function keyGetFun = process.getKeyGetFun(); - Function, Collection> fillDataGetFun = process.getFillDataGetFun(); - Function fillKeyGetFun = process.getFillKeyGetFun(); - BiConsumer fillFun = process.getFillFun(); - // 获取keys - List keys = data.stream().map(keyGetFun).collect(Collectors.toList()); - // 获取填充数据 - Collection fillData = fillDataGetFun.apply(keys); - // 处理填充数据成key map 方便查找 - Map idFillDataMap = fillData.stream().collect(Collectors.toMap(fillKeyGetFun, e -> e)); + public DataFillTask(DataFillStrategy handler, List metadataList){ + this.strategy = handler; + this.metadataList = metadataList; + } - // 填充数据 - data.stream().filter(d-> idFillDataMap.containsKey(keyGetFun.apply(d))).forEach(d -> { - E e = idFillDataMap.get(keyGetFun.apply(d)); - fillFun.accept(d, e); - }); - return null; + @Override + public List call() { + try { + strategy.fill(metadataList); + }catch (Exception e){ + if(log.isDebugEnabled()) { + e.printStackTrace(); + } + log.error(e.getMessage()); } + return null; + } + } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillThreadExecutor.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillThreadPoolManager.java similarity index 49% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillThreadExecutor.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillThreadPoolManager.java index 668353c..7d36e6d 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillThreadExecutor.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataFillThreadPoolManager.java @@ -1,6 +1,9 @@ package fun.easycode.jointblock.datafill; import com.alibaba.ttl.TtlCallable; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; import java.util.Collection; import java.util.List; @@ -8,13 +11,13 @@ import java.util.concurrent.*; import java.util.stream.Collectors; /** - * 数据填充线程池 - * @author xuzhen97 + * @author xuzhe */ -public final class DataFillThreadExecutor { +@Slf4j +public class DataFillThreadPoolManager { /** - * 异步任务线程池,最大队列数,无需及时返回的任务,请使用此线程池 + * 异步任务线程池,最大队列数,无需即使性 */ public static final ThreadPoolExecutor executor = new ThreadPoolExecutor(50 @@ -24,6 +27,42 @@ public final class DataFillThreadExecutor { , new LinkedBlockingQueue<>() , new ThreadPoolExecutor.AbortPolicy()); + @ManagedAttribute + public int getCorePoolSize() { + return executor.getCorePoolSize(); + } + + @ManagedAttribute + public int getMaximumPoolSize(){ + return executor.getMaximumPoolSize(); + } + + /** + * 设置线程池初始数量 + * @param corePoolSize 初始数量 + */ + @ManagedAttribute + public void setCorePoolSize(int corePoolSize){ + executor.setCorePoolSize(corePoolSize); + } + + /** + * 设置线程池最大数量 + * @param maximumPoolSize 最大数量 + */ + @ManagedAttribute + public void setMaximumPoolSize(int maximumPoolSize){ + executor.setMaximumPoolSize(maximumPoolSize); + } + + /** + * 打印填充线程池基本信息 + */ + @ManagedOperation + public void printPoolExecutorInfo() { + log.info("填充线程池corePoolSize:{}、maximumPoolSize:{}.", executor.getCorePoolSize(), executor.getMaximumPoolSize()); + } + /** * 执行异步任务,会等待全部执行完成 * @param tasks 任务集合 @@ -36,11 +75,11 @@ public final class DataFillThreadExecutor { // 主要是为了中转用户信息 List> ttlCallables = tasks.stream().map(TtlCallable::get) .collect(Collectors.toList()); - List> futures = executor.invokeAll(ttlCallables); for (Future future : futures) { future.get(); } } + } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataGenerate.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataGenerate.java new file mode 100644 index 0000000..1393257 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataGenerate.java @@ -0,0 +1,42 @@ +package fun.easycode.jointblock.datafill; + +import java.lang.annotation.*; + +/** + * 级联查询的时候对于id生成空对象的策略 + * @author xuzhe + */ +@Target({ElementType.FIELD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataGenerate { + /** + * 实体的主键名, 默认id + * @return id字段名 + */ + String value(); + + /** + * 目标 + * @return + */ + String targetField(); + + /** + * 如果是多对象模式集合下的的属性类型Class + * @return + */ + Class listClass() default void.class; + + /** + * 多对象模式集合下的主键分隔符默认, + * @return + */ + String listSeparator() default ","; + + /** + * id生成的模式 + * @return + */ + IDGenerateMode mode() default IDGenerateMode.DEFAULT; +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataParam.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataParam.java new file mode 100644 index 0000000..158eeef --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/DataParam.java @@ -0,0 +1,21 @@ +package fun.easycode.jointblock.datafill; + +import java.lang.annotation.*; + +/** + * @author xuzhe + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ElementType.ANNOTATION_TYPE}) +public @interface DataParam { + /** + * 方法的参数名称 + */ + String name(); + + /** + * 方法参数传参值,支持spEl表达式 + */ + String value(); +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/EnableDataFill.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/EnableDataFill.java new file mode 100644 index 0000000..feef901 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/EnableDataFill.java @@ -0,0 +1,13 @@ +package fun.easycode.jointblock.datafill; + +import java.lang.annotation.*; + +/** + * 减少不必要的对象扫描 + * @author xuzhe + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface EnableDataFill { +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignDataFillStrategy.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignDataFillStrategy.java new file mode 100644 index 0000000..d614cab --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignDataFillStrategy.java @@ -0,0 +1,130 @@ +package fun.easycode.jointblock.datafill; + +import cn.hutool.core.util.ReflectUtil; +import fun.easycode.jointblock.exception.CheckException; +import fun.easycode.jointblock.validator.IValidate; +import org.springframework.beans.BeansException; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.FeignContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.StringUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * feign填充策略 + * + * @author xuzhe + */ +public class FeignDataFillStrategy implements DataFillStrategy, ApplicationContextAware { + + private ApplicationContext context; + + @Override + public void fill(List metadataList) { + + Map, List> sourceMap = metadataList.stream() + .collect(Collectors.groupingBy(metadata -> metadata.getDataFill().source())); + + + sourceMap.forEach((feignClass, feignClassMetadataList) -> { + + FeignClient feignClient = feignClass.getAnnotation(FeignClient.class); + Object clientObj = getFeignBean(feignClient.name(), feignClass); + // 获取DataFill , 只要被处理器处理过传过来的,元数据集合注解的信息是一致的 + DataFill dataFill = metadataList.stream().findFirst().get().getDataFill(); + + Method method; + + if(StringUtils.isEmpty(dataFill.methodName())){ + Method[] methods = ReflectUtil.getMethods(feignClass); + + if (methods.length != 1) { + throw new CheckException(feignClass.getName() + "用于@DataFill时,如果不指定methodName, 则必须有且仅能有一个方法!"); + } + + method = methods[0]; + }else{ + method = ReflectUtil.getMethodByName(feignClass, dataFill.methodName()); + if(method == null){ + throw new CheckException("@DataFill 指定methodName找不到对应的方法!"); + } + } + + // 获取到client请求的主键信息 + List ids = feignClassMetadataList.stream() + .flatMap(metadata -> metadata.getTargetObjMap().keySet().stream()) + .collect(Collectors.toList()); + + // el表达式可以使用的变量 + Map contextProps = new HashMap<>(); + contextProps.put("ids", ids); + + Map> paramMap = Arrays.stream(dataFill.params()) + .collect(Collectors.toMap(DataParam::name + , param -> DataCopy.getInstance() + .parserSpEl(param.value(), Object.class, contextProps))); + + Object invoke; + // 如果标注@DataParam注解,我们将完全按照DataParam标记传参 + // 如果没有注解标记,我们认为方法有且只有一个参数,并传入ids + if (paramMap.size() == 0) { + invoke = ReflectUtil.invoke(clientObj, method, ids); + } else { + // 处理参数列表 + Parameter[] parameters = method.getParameters(); + Object[] args = new Object[parameters.length]; + + // 如果发现feign调用方法的第一个参数实现了IValidate,说明是类传参 + if(IValidate.class.isAssignableFrom(parameters[0].getType())){ + try { + Object cmd = parameters[0].getType().newInstance(); + paramMap.forEach((key, value) -> { + value.ifPresent(o -> ReflectUtil.setFieldValue(cmd, key, o)); + }); + args[0] = cmd; + } catch (Exception e) { + throw new CheckException(parameters[0].getType().getName() + "缺少无参构造!"); + } + }else{ + // 不是非类传参就是直接传参 + for (int i = 0; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + args[i] = paramMap.get(parameter.getName()) == null + ? null : paramMap.get(parameter.getName()).orElse(null); + } + } + // 调用方法 + invoke = ReflectUtil.invoke(clientObj, method, args); + } + + if(invoke != null){ + feignClassMetadataList.forEach(metadata -> { + handlerResult(metadata, invoke, FeignId.class); + }); + } + }); + + } + + @Override + public Class getAnnotation() { + return FeignClient.class; + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + public T getFeignBean(String beanName, Class tClass) { + FeignContext feignContext = context.getBean("feignContext", FeignContext.class); + System.out.println(feignContext.getContextNames()); + return feignContext.getInstance(beanName, tClass); + } +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignId.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignId.java new file mode 100644 index 0000000..9624286 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/FeignId.java @@ -0,0 +1,13 @@ +package fun.easycode.jointblock.datafill; + +import java.lang.annotation.*; + +/** + * 用来标识feign请求返回对象中哪个字段是id + * @author xuzhe + */ +@Target({ElementType.FIELD,ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface FeignId { +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/IDGenerateMode.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/IDGenerateMode.java new file mode 100644 index 0000000..ba3d44e --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/IDGenerateMode.java @@ -0,0 +1,18 @@ +package fun.easycode.jointblock.datafill; + +/** + * 需要填充的id字段要做的处理 + * 因为有些是,分割的所以就需要处理,正常的1对多就没有问题 + * @author xuzhe + */ +public enum IDGenerateMode { + /** + * 默认不做任何处理 + */ + DEFAULT, + /** + * 多个,认为一个字段中有多个Id是用,号分割 + * 排序也会默认根据id的排序进行排序 + */ + MULTIPLE +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/MyBatisPlusDataFillStrategy.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/MyBatisPlusDataFillStrategy.java new file mode 100644 index 0000000..cc031ce --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/datafill/MyBatisPlusDataFillStrategy.java @@ -0,0 +1,90 @@ +package fun.easycode.jointblock.datafill; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * mybatis-plus填充策略 + * @author xuzhe + */ +public class MyBatisPlusDataFillStrategy implements DataFillStrategy, ApplicationContextAware { + + private ApplicationContext context; + + @SuppressWarnings("all") + @Override + public void fill(List metadataList) { + + Map, List> collect = metadataList.stream() + .collect(Collectors.groupingBy(metadata -> metadata.getDataFill().source())); + + collect.forEach((sourceClass, sourceClassMetadataList) -> { + BaseMapper baseMapper = (BaseMapper) context.getBean(sourceClass); + + List ids = sourceClassMetadataList.stream() + .flatMap(metadata-> metadata.getTargetObjMap().keySet().stream()) + .collect(Collectors.toList()); + + // el表达式可以使用的变量 + Map contextProps = new HashMap<>(); + contextProps.put("ids", ids); + + Map> paramMap = Arrays.stream(sourceClassMetadataList.stream() + .findFirst().get() + .getDataFill().params()) + .collect(Collectors.toMap(DataParam::name + , param -> DataCopy.getInstance() + .parserSpEl(param.value(), Object.class, contextProps))); + + Object invoke; + // 如果标注@DataParam注解,我们将完全按照DataParam标记传参 + // 如果没有注解标记,直接调用BaseMapper的selectBatchIds方法 + if (paramMap.size() == 0) { + invoke = baseMapper.selectBatchIds(ids); + } else { + // 如果传入DataParam列表,将按照参数列表插叙 + // 不要忘记自行塞入 ids, 参考el表达式 + QueryWrapper wrapper = new QueryWrapper<>(); + paramMap.forEach((paramName,paramValue)->{ + if(paramValue.isPresent()){ + // 这里支持Collection参数和单个参数, 用In或者eq + if(paramValue.get() instanceof Collection){ + wrapper.in(paramName, (Collection) paramValue.get()); + }else{ + wrapper.eq(paramName, paramValue.get()); + } + } + }); + invoke = baseMapper.selectList(wrapper); + } + + sourceClassMetadataList.forEach(metadata -> { + // 如果是指定使用自定义注解,我们将使用自定义注解 + if(metadata.getDataFill().isCustomMyBatisPlusAnno()) { + handlerResult(metadata, invoke, DataFillId.class); + }else { + handlerResult(metadata, invoke, TableId.class); + } + }); + }); + } + + @Override + public Class getType() { + return BaseMapper.class; + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/CheckException.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/exception/CheckException.java similarity index 35% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/CheckException.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/exception/CheckException.java index fe677db..9e4ad40 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/CheckException.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/exception/CheckException.java @@ -1,11 +1,14 @@ -package fun.easycode.jointblock.core; +package fun.easycode.jointblock.exception; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; /** * 通用逻辑异常 - * @author xuzhen97 + * @Author: xuzhen97 + * @Date: 2023/03/22 */ @EqualsAndHashCode(callSuper = true) @Data @@ -24,53 +27,46 @@ public class CheckException extends RuntimeException{ * @Date: 2023/03/22 */ private final Integer code; - private final String message; - /** - * 通用逻辑异常 - * @param message 错误信息 + * 错误信息 + * @Author: xuzhen97 + * @Date: 2023/03/22 */ - public CheckException(String message){ - super(message); + private final String msg; + + public CheckException(String msg){ + super(msg); this.code = DEFAULT_CODE; - this.message = message; + this.msg = msg; } - /** - * 通用逻辑异常 - * @param code 错误码 - * @param message 错误信息 - */ - public CheckException(Integer code, String message){ - super(message); + public CheckException(Integer code, String msg){ + super(msg); this.code = code; - this.message = message; + this.msg = msg; } /** - * 返回错误信息 - * @return 错误信息 + * 返回结果 + * @Author: xuzhen97 + * @Date: 2023/03/22 */ - public ErrorDto error(){ - return new ErrorDto(code, message); + public Result result(){ + return new Result(code, msg); } - /** - * 返回错误信息 - * @param code 错误码 - * @param message 错误信息 - * @return 错误信息 - */ - public static ErrorDto error(Integer code, String message){ - return new ErrorDto(code, message); + public static Result toResult(Integer code, String msg){ + return new Result(code, msg); } - /** - * 返回错误信息 - * @param msg 错误信息 - * @return 错误信息 - */ - public static ErrorDto error(String msg){ - return new ErrorDto(DEFAULT_CODE, msg); + public static Result toResult(String msg){ + return new Result(DEFAULT_CODE, msg); + } + + @AllArgsConstructor + @Getter + public static class Result{ + private final Integer code; + private final String msg; } } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/generator/JointBlockGenerator.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/generator/JointBlockGenerator.java index 9f634d1..c9b8edb 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/generator/JointBlockGenerator.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/generator/JointBlockGenerator.java @@ -56,10 +56,10 @@ public final class JointBlockGenerator { .enableRemoveIsPrefix() // 去除boolean类型数据的is_前缀 .enableTableFieldAnnotation() .logicDeleteColumnName("is_deleted") - .addTableFills(new Column("create_by", FieldFill.INSERT) - , new Column("update_by", FieldFill.INSERT_UPDATE) - , new Column("create_time", FieldFill.INSERT) - , new Column("update_time", FieldFill.INSERT_UPDATE)); + .addTableFills(new Column("created_by", FieldFill.INSERT) + , new Column("last_modified_by", FieldFill.INSERT_UPDATE) + , new Column("created_date", FieldFill.INSERT) + , new Column("last_modified_date", FieldFill.INSERT_UPDATE)); // 只有表的前缀有值的时候才进行设置 if(!StringUtils.isEmpty(generatorConfig.getTablePrefix())){ diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/BatchMapper.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/BatchMapper.java similarity index 90% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/BatchMapper.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/BatchMapper.java index 9b45469..36eb6f1 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/BatchMapper.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/BatchMapper.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.core; +package fun.easycode.jointblock.mybatisplus; import java.util.List; diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/JointBlockSqlInjector.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/JointBlockSqlInjector.java similarity index 90% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/JointBlockSqlInjector.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/JointBlockSqlInjector.java index 2dee3c5..fc46482 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/JointBlockSqlInjector.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/JointBlockSqlInjector.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.core; +package fun.easycode.jointblock.mybatisplus; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; @@ -19,7 +19,6 @@ public class JointBlockSqlInjector extends DefaultSqlInjector { methodList.add(new ReplaceBatchSomeColumn()); methodList.add(new StreamQuerySqlAbstractMethod()); methodList.add(new StreamQueryAbstractMethod()); - methodList.add(new InsertOrUpdateBatchMethod()); return methodList; } } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/MyBatisPlusSQLLog.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/MyBatisPlusSQLLog.java similarity index 95% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/MyBatisPlusSQLLog.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/MyBatisPlusSQLLog.java index 0df7763..1009927 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/MyBatisPlusSQLLog.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/MyBatisPlusSQLLog.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.util; +package fun.easycode.jointblock.mybatisplus; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.logging.Log; diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ReplaceBatchSomeColumn.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/ReplaceBatchSomeColumn.java similarity index 98% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ReplaceBatchSomeColumn.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/ReplaceBatchSomeColumn.java index fe1255e..96b19f9 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/ReplaceBatchSomeColumn.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/ReplaceBatchSomeColumn.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.core; +package fun.easycode.jointblock.mybatisplus; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.core.injector.AbstractMethod; diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamMapper.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamMapper.java similarity index 94% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamMapper.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamMapper.java index 204bb41..0c5ecc7 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamMapper.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamMapper.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.core; +package fun.easycode.jointblock.mybatisplus; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.toolkit.Constants; diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamQueryAbstractMethod.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamQueryAbstractMethod.java similarity index 91% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamQueryAbstractMethod.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamQueryAbstractMethod.java index b5c96a4..367374c 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamQueryAbstractMethod.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamQueryAbstractMethod.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.core; +package fun.easycode.jointblock.mybatisplus; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; @@ -20,7 +20,7 @@ public class StreamQueryAbstractMethod extends AbstractMethod { } /* 缓存逻辑处理 */ return builderAssistant.addMappedStatement(METHOD, sqlSource, StatementType.PREPARED, SqlCommandType.SELECT, - Integer.MIN_VALUE, null, null, null, tableInfo.getResultMap(), modelClass, + Integer.MIN_VALUE, null, null, null, null, modelClass, ResultSetType.FORWARD_ONLY, true, true, false, null, null, null, configuration.getDatabaseId(), languageDriver, null); } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamQuerySqlAbstractMethod.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamQuerySqlAbstractMethod.java similarity index 88% rename from jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamQuerySqlAbstractMethod.java rename to jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamQuerySqlAbstractMethod.java index d6d6baf..5b922c0 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/core/StreamQuerySqlAbstractMethod.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/mybatisplus/StreamQuerySqlAbstractMethod.java @@ -1,4 +1,4 @@ -package fun.easycode.jointblock.core; +package fun.easycode.jointblock.mybatisplus; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; @@ -10,6 +10,8 @@ public class StreamQuerySqlAbstractMethod extends AbstractMethod { public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { String sqlFormat = ""; + System.err.println(tableInfo.getAllSqlSelect()); + String sql = String.format(sqlFormat, tableInfo.getAllSqlSelect(), tableInfo.getTableName(), "${customSql}"); @@ -21,7 +23,7 @@ public class StreamQuerySqlAbstractMethod extends AbstractMethod { } /* 缓存逻辑处理 */ return builderAssistant.addMappedStatement(METHOD, sqlSource, StatementType.PREPARED, SqlCommandType.SELECT, - Integer.MIN_VALUE, null, null, null, tableInfo.getResultMap() , modelClass, + Integer.MIN_VALUE, null, null, null, null, modelClass, ResultSetType.FORWARD_ONLY, true, true, false, null, null, null, configuration.getDatabaseId(), languageDriver, null); } diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/BatchUtil.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/BatchUtil.java deleted file mode 100644 index 30e6682..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/BatchUtil.java +++ /dev/null @@ -1,63 +0,0 @@ -package fun.easycode.jointblock.util; - -import fun.easycode.jointblock.core.JointBlockMapper; -import lombok.extern.slf4j.Slf4j; -import org.apache.ibatis.session.ExecutorType; -import org.apache.ibatis.session.SqlSession; -import org.apache.ibatis.session.SqlSessionFactory; - -import java.util.List; - -/** - * 批处理工具类 - * 必须被spring管理才可以使用 - * @author xuzhen97 - */ -@Slf4j -public class BatchUtil { - /** - * 批处理数量 - */ - private static final int BATCH = 1000; - - /** - * MyBatis SqlSessionFactory - */ - private static SqlSessionFactory SQL_SESSION_FACTORY; - - public BatchUtil(SqlSessionFactory sqlSessionFactory) { - SQL_SESSION_FACTORY = sqlSessionFactory; - } - - /** - * 批量插入方法 - * - * @param data 需要被处理的数据 - * @param mapper 实体类的mapper - * @return int 影响的总行数 - */ - public static int saveBatch(List data, JointBlockMapper mapper) { - - if(SQL_SESSION_FACTORY == null){ - throw new RuntimeException("SQL_SESSION_FACTORY is null, 请先使用spring管理BatchUtil"); - } - - int count = 0; - SqlSession batchSqlSession = SQL_SESSION_FACTORY.openSession(ExecutorType.BATCH); - try { - for (int index = 0; index < data.size(); index++) { - count += mapper.insert(data.get(index)); - if (index != 0 && index % BATCH == 0) { - batchSqlSession.flushStatements(); - } - } - batchSqlSession.commit(); - } catch (Exception e) { - batchSqlSession.rollback(); - log.error(e.getMessage(), e); - } finally { - batchSqlSession.close(); - } - return count; - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/CamelUnderUtil.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/CamelUnderUtil.java deleted file mode 100644 index f2dfa69..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/CamelUnderUtil.java +++ /dev/null @@ -1,38 +0,0 @@ -package fun.easycode.jointblock.util; - -/** - * 驼峰下划线转换工具类 - * @author xuzhen97 - */ -public final class CamelUnderUtil { - /** - * 功能:驼峰命名转下划线命名 - * 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写 - */ - public static String camel2under(String c) - { - String separator = "_"; - c = c.replaceAll("([a-z])([A-Z])", "$1"+separator+"$2").toLowerCase(); - return c; - } - - /** - * 功能:下划线命名转驼峰命名 - * 将下划线替换为空格,将字符串根据空格分割成数组,再将每个单词首字母大写 - * @param s - * @return - */ - public static String under2camel(String s) - { - String separator = "_"; - String under=""; - s = s.toLowerCase().replace(separator, " "); - String sarr[]=s.split(" "); - for(int i=0;i { - private final E error; - private final R result; - - private Either(E error, R result) { - this.error = error; - this.result = result; - } - - public static Either error(E error) { - return new Either<>(error, null); - } - - public static Either result(R result) { - return new Either<>(null, result); - } - - public Optional getError() { - return Optional.ofNullable(error); - } - - public Optional getResult() { - return Optional.ofNullable(result); - } - - public boolean isError() { - return error != null; - } - - public boolean isResult() { - return result != null; - } - - public Optional mapError(Function mapper) { - if (isError()) { - return Optional.of(mapper.apply(error)); - } - return Optional.empty(); - } - - public Optional mapResult(Function mapper) { - if (isResult()) { - return Optional.of(mapper.apply(result)); - } - return Optional.empty(); - } - - @Override - public String toString() { - if (isError()) { - return "Error: " + error.toString(); - } - return "Result: " + result.toString(); - } - - /** - * 将一个可能抛出异常的方法转换为Either - * @param function 可能抛出异常的方法 - * @return Either - * @param 参数类型 - * @param 返回值类型 - */ - public static Function> wrap(Function function){ - return t -> { - try { - return Either.result(function.apply(t)); - } catch (Exception e) { - return Either.error(e); - } - }; - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/ExceptionUtil.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/ExceptionUtil.java deleted file mode 100644 index 27f5bce..0000000 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/ExceptionUtil.java +++ /dev/null @@ -1,13 +0,0 @@ -package fun.easycode.jointblock.util; - -import java.io.PrintWriter; -import java.io.StringWriter; - -public class ExceptionUtil { - public static String getStackTrace(Throwable throwable) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw, true); - throwable.printStackTrace(pw); - return sw.getBuffer().toString(); - } -} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/LogUtil.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/LogUtil.java new file mode 100644 index 0000000..257a525 --- /dev/null +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/LogUtil.java @@ -0,0 +1,119 @@ +package fun.easycode.jointblock.util; + +import cn.hutool.core.annotation.AnnotationUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import fun.easycode.jointblock.core.Alias; +import fun.easycode.jointblock.core.DynamicOperate; +import fun.easycode.jointblock.exception.CheckException; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +/** + * entity update log 工具类 + * 用来获取实体的修改日志,哪些字段变更了 + * @author xuzhen97 + */ + +public class LogUtil { + + private static final ThreadLocal> OLD_ENTITY_JSON = ThreadLocal.withInitial(HashMap::new); + private static final ThreadLocal> NEW_ENTITY_JSON = ThreadLocal.withInitial(HashMap::new); + private static final ThreadLocal>> UPDATE_FIELD_QUEUE = ThreadLocal.withInitial(HashMap::new); + + /** + * 设置原先的实体 + * @param key String 唯一id, 实体id + * @param oldEntity 实体 + */ + public static void setOldEntity(String key, Object oldEntity){ + OLD_ENTITY_JSON.get().put(key, JacksonUtil.toJson(oldEntity)); + } + + /** + * 设置修改后的实体 + * @param key String 唯一id, 实体id + * @param oldEntity Object + */ + public static void setNewEntity(String key, Object oldEntity){ + NEW_ENTITY_JSON.get().put(key, JacksonUtil.toJson(oldEntity)); + } + + public static void setUpdateField(String key, String fieldName, Object fieldValue){ + Queue queue = UPDATE_FIELD_QUEUE.get().computeIfAbsent(key, k-> new LinkedList<>()); + queue.offer(fieldName); + queue.offer(fieldValue); + } + + /** + * 获取update log 先调用 + * setOldEntity + * setNewEntity + * 获取后数据会被清除,自行保存字符串 + * @param key String + * @return String + */ + public static String getUpdateLog(String key){ + + String oldJson = OLD_ENTITY_JSON.get().get(key); + String newJson = NEW_ENTITY_JSON.get().get(key); + + OLD_ENTITY_JSON.get().remove(key); + NEW_ENTITY_JSON.get().remove(key); + + return JacksonUtil.diff(oldJson, newJson); + } + + /** + * 获取数据修改日志 + * @param key + * @return + */ + public static String getFieldUpdateLog(String key, T entity){ + Queue queue = UPDATE_FIELD_QUEUE.get().get(key); + + Map entityFieldMap = ReflectUtil + .getFieldMap(entity.getClass()).entrySet().stream().map(entry->{ + // 将field map key转换成小写_ 的形式,如果Alias有值就取 + // 这里其实就是为了转换成数据库filed的形式,为了和mybatis对起来 + String mapKey; + if(AnnotationUtil.hasAnnotation(entry.getValue(), Alias.class)) { + Alias alias = entry.getValue().getAnnotation(Alias.class); + mapKey = alias.value(); + }else{ + mapKey = DynamicOperate.camel2under(entry.getKey()); + } + + return new AbstractMap.SimpleEntry<>(mapKey, entry.getValue()); + }) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + + StringBuilder updateLogBuilder = new StringBuilder(); + + while (!queue.isEmpty()){ + + String updateFieldName = (String) queue.remove(); + Object updateFieldValue = queue.remove(); + + Field entityField = entityFieldMap.get(updateFieldName); + + if(entityField == null){ + throw new CheckException("数据库实体字段不存在!"); + } + + Object entityFieldValue = ReflectUtil.getFieldValue(entity, entityField); + // 只有值真正改变的才会打印日志 + if(ObjectUtil.notEqual(updateFieldValue, entityFieldValue)) { + updateLogBuilder.append("[key=").append(updateFieldName).append(",oldValue=") + .append(entityFieldValue).append(",newValue=").append(updateFieldValue).append("];"); + } + } + UPDATE_FIELD_QUEUE.get().remove(key); + + return updateLogBuilder.toString(); + } + +} diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/URLUtil.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/URLUtil.java index e847bc7..638baaa 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/URLUtil.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/util/URLUtil.java @@ -1,5 +1,6 @@ package fun.easycode.jointblock.util; +import fun.easycode.jointblock.core.RequestHolder; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; diff --git a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/validator/IValidate.java b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/validator/IValidate.java index 2fd0b92..e38b51e 100644 --- a/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/validator/IValidate.java +++ b/jointblock-spring-boot/src/main/java/fun/easycode/jointblock/validator/IValidate.java @@ -1,6 +1,7 @@ package fun.easycode.jointblock.validator; -import fun.easycode.jointblock.core.CheckException; +import fun.easycode.jointblock.exception.CheckException; +import org.springframework.util.StringUtils; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; diff --git a/pom.xml b/pom.xml index 55fa29f..5e6c167 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 8 8 UTF-8 - 0.30.0 + 0.29.0 -- Gitee