Repository: alibaba/COLA Branch: master Commit: d948f204d58f Files: 749 Total size: 912.1 KB Directory structure: gitextract_s6zpyxns/ ├── .editorconfig ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yaml │ └── ci_by_multiply_java_versions.yaml ├── .gitignore ├── .gitmodules ├── .mvn/ │ └── wrapper/ │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── cola-archetypes/ │ ├── cola-archetype-light/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── maven/ │ │ │ │ └── archetype-metadata.xml │ │ │ └── archetype-resources/ │ │ │ ├── README.md │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ ├── Application.java │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ └── ChargeController.java │ │ │ │ │ ├── application/ │ │ │ │ │ │ ├── ChargeServiceI.java │ │ │ │ │ │ ├── ChargeServiceImpl.java │ │ │ │ │ │ └── dto/ │ │ │ │ │ │ ├── BeginSessionRequest.java │ │ │ │ │ │ ├── ChargeRecordDto.java │ │ │ │ │ │ ├── ChargeRequest.java │ │ │ │ │ │ ├── EndSessionRequest.java │ │ │ │ │ │ ├── MultiResponse.java │ │ │ │ │ │ ├── Response.java │ │ │ │ │ │ └── SingleResponse.java │ │ │ │ │ ├── domain/ │ │ │ │ │ │ ├── ApplicationContextHelper.java │ │ │ │ │ │ ├── BizException.java │ │ │ │ │ │ ├── DomainFactory.java │ │ │ │ │ │ ├── Entity.java │ │ │ │ │ │ ├── account/ │ │ │ │ │ │ │ ├── Account.java │ │ │ │ │ │ │ └── AccountDomainService.java │ │ │ │ │ │ ├── charge/ │ │ │ │ │ │ │ ├── CallType.java │ │ │ │ │ │ │ ├── ChargeContext.java │ │ │ │ │ │ │ ├── ChargeRecord.java │ │ │ │ │ │ │ ├── Money.java │ │ │ │ │ │ │ ├── MoneyConverter.java │ │ │ │ │ │ │ ├── Session.java │ │ │ │ │ │ │ ├── chargeplan/ │ │ │ │ │ │ │ │ ├── BasicChargePlan.java │ │ │ │ │ │ │ │ ├── ChargePlan.java │ │ │ │ │ │ │ │ ├── ChargePlanType.java │ │ │ │ │ │ │ │ ├── FamilyChargePlan.java │ │ │ │ │ │ │ │ ├── FixedTimeChangePlan.java │ │ │ │ │ │ │ │ └── Resource.java │ │ │ │ │ │ │ └── chargerule/ │ │ │ │ │ │ │ ├── AbstractChargeRule.java │ │ │ │ │ │ │ ├── BasicChargeRule.java │ │ │ │ │ │ │ ├── ChargeRule.java │ │ │ │ │ │ │ ├── ChargeRuleFactory.java │ │ │ │ │ │ │ ├── CompositeChargeRule.java │ │ │ │ │ │ │ ├── FamilyChargeRule.java │ │ │ │ │ │ │ └── FixedTimeChargeRule.java │ │ │ │ │ │ └── gateway/ │ │ │ │ │ │ ├── AccountGateway.java │ │ │ │ │ │ ├── ChargeGateway.java │ │ │ │ │ │ └── SessionGateway.java │ │ │ │ │ └── infrastructure/ │ │ │ │ │ ├── AccountGatewayImpl.java │ │ │ │ │ ├── RestClientBean.java │ │ │ │ │ └── SessionGatewayImpl.java │ │ │ │ └── resources/ │ │ │ │ ├── application.yml │ │ │ │ └── logback.xml │ │ │ └── test/ │ │ │ ├── charge.http │ │ │ ├── java/ │ │ │ │ ├── CleanArchTest.java │ │ │ │ ├── TestsContainerBoot.java │ │ │ │ ├── application/ │ │ │ │ │ └── ChargeServiceTest.java │ │ │ │ ├── domain/ │ │ │ │ │ ├── ChargeRecordPlanTest.java │ │ │ │ │ ├── ChargeRecordRuleTest.java │ │ │ │ │ └── CompositeChargeRuleTestRecord.java │ │ │ │ └── infrastructure/ │ │ │ │ ├── AccountGatewayTest.java │ │ │ │ ├── ChargeRecordRepoTest.java │ │ │ │ ├── FixtureLoader.java │ │ │ │ ├── JSONTest.java │ │ │ │ ├── SpingBootConfTest.java │ │ │ │ ├── WireMockBasicTest.java │ │ │ │ └── WireMockRegister.java │ │ │ └── resources/ │ │ │ ├── application-test.yml │ │ │ ├── application.yml │ │ │ ├── fixture/ │ │ │ │ └── wiremock/ │ │ │ │ ├── stub_account.json │ │ │ │ ├── stub_insufficient_account.json │ │ │ │ └── stub_wire_mock_basic.json │ │ │ └── logback-test.xml │ │ └── test/ │ │ └── resources/ │ │ └── projects/ │ │ └── basic/ │ │ ├── archetype.properties │ │ └── goal.txt │ ├── cola-archetype-service/ │ │ ├── .gitignore │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── maven/ │ │ │ │ └── archetype-metadata.xml │ │ │ └── archetype-resources/ │ │ │ ├── __gitignore__ │ │ │ ├── __rootArtifactId__-app/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── java/ │ │ │ │ │ ├── customer/ │ │ │ │ │ │ ├── CustomerServiceImpl.java │ │ │ │ │ │ └── executor/ │ │ │ │ │ │ ├── CustomerAddCmdExe.java │ │ │ │ │ │ └── query/ │ │ │ │ │ │ └── CustomerListByNameQryExe.java │ │ │ │ │ └── order/ │ │ │ │ │ └── OrderServiceImpl.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── app/ │ │ │ │ ├── CustomerConvertorTest.java │ │ │ │ └── CustomerValidatorTest.java │ │ │ ├── __rootArtifactId__-client/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ ├── api/ │ │ │ │ │ └── CustomerServiceI.java │ │ │ │ └── dto/ │ │ │ │ ├── CustomerAddCmd.java │ │ │ │ ├── CustomerListByNameQry.java │ │ │ │ ├── data/ │ │ │ │ │ ├── CustomerDTO.java │ │ │ │ │ └── ErrorCode.java │ │ │ │ └── event/ │ │ │ │ ├── CustomerCreatedEvent.java │ │ │ │ └── DomainEventConstant.java │ │ │ ├── __rootArtifactId__-domain/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── java/ │ │ │ │ │ └── domain/ │ │ │ │ │ ├── customer/ │ │ │ │ │ │ ├── CompanyType.java │ │ │ │ │ │ ├── Credit.java │ │ │ │ │ │ ├── Customer.java │ │ │ │ │ │ ├── CustomerType.java │ │ │ │ │ │ ├── SourceType.java │ │ │ │ │ │ ├── domainservice/ │ │ │ │ │ │ │ └── CreditChecker.java │ │ │ │ │ │ └── gateway/ │ │ │ │ │ │ ├── CreditGateway.java │ │ │ │ │ │ └── CustomerGateway.java │ │ │ │ │ ├── order/ │ │ │ │ │ │ └── Order.java │ │ │ │ │ └── package-info.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── domain/ │ │ │ │ └── CustomerEntityTest.java │ │ │ ├── __rootArtifactId__-infrastructure/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── java/ │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── DiamondConfig.java │ │ │ │ │ │ ├── customer/ │ │ │ │ │ │ │ ├── CreditGatewayImpl.java │ │ │ │ │ │ │ ├── CustomerDO.java │ │ │ │ │ │ │ ├── CustomerGatewayImpl.java │ │ │ │ │ │ │ └── CustomerMapper.java │ │ │ │ │ │ └── order/ │ │ │ │ │ │ └── OrderGatewayImpl.java │ │ │ │ │ └── resources/ │ │ │ │ │ ├── logback-spring.xml │ │ │ │ │ └── mybatis/ │ │ │ │ │ ├── customer-mapper.xml │ │ │ │ │ └── mybatis-config.xml │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ └── repository/ │ │ │ │ │ └── CustomerMapperTest.java │ │ │ │ └── resources/ │ │ │ │ └── sample.properties │ │ │ ├── pom.xml │ │ │ └── start/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── Application.java │ │ │ │ └── resources/ │ │ │ │ ├── application.properties │ │ │ │ └── logback-spring.xml │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ ├── TestApplication.java │ │ │ │ └── test/ │ │ │ │ └── CustomerServiceTest.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ └── test.properties │ │ └── test/ │ │ └── resources/ │ │ └── projects/ │ │ └── basic/ │ │ ├── archetype.properties │ │ └── goal.txt │ ├── cola-archetype-web/ │ │ ├── .gitignore │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── maven/ │ │ │ │ └── archetype-metadata.xml │ │ │ └── archetype-resources/ │ │ │ ├── __gitignore__ │ │ │ ├── __rootArtifactId__-adapter/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ ├── mobile/ │ │ │ │ │ └── CustomerMobileAdaptor.java │ │ │ │ ├── wap/ │ │ │ │ │ └── CustomerWapAdaptor.java │ │ │ │ └── web/ │ │ │ │ └── CustomerController.java │ │ │ ├── __rootArtifactId__-app/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── java/ │ │ │ │ │ ├── customer/ │ │ │ │ │ │ ├── CustomerServiceImpl.java │ │ │ │ │ │ └── executor/ │ │ │ │ │ │ ├── CustomerAddCmdExe.java │ │ │ │ │ │ └── query/ │ │ │ │ │ │ └── CustomerListByNameQryExe.java │ │ │ │ │ └── order/ │ │ │ │ │ └── OrderServiceImpl.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── app/ │ │ │ │ ├── CustomerConvertorTest.java │ │ │ │ └── CustomerValidatorTest.java │ │ │ ├── __rootArtifactId__-client/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ ├── api/ │ │ │ │ │ └── CustomerServiceI.java │ │ │ │ └── dto/ │ │ │ │ ├── CustomerAddCmd.java │ │ │ │ ├── CustomerListByNameQry.java │ │ │ │ ├── data/ │ │ │ │ │ ├── CustomerDTO.java │ │ │ │ │ └── ErrorCode.java │ │ │ │ └── event/ │ │ │ │ ├── CustomerCreatedEvent.java │ │ │ │ └── DomainEventConstant.java │ │ │ ├── __rootArtifactId__-domain/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── java/ │ │ │ │ │ └── domain/ │ │ │ │ │ ├── customer/ │ │ │ │ │ │ ├── CompanyType.java │ │ │ │ │ │ ├── Credit.java │ │ │ │ │ │ ├── Customer.java │ │ │ │ │ │ ├── CustomerType.java │ │ │ │ │ │ ├── SourceType.java │ │ │ │ │ │ ├── domainservice/ │ │ │ │ │ │ │ └── CreditChecker.java │ │ │ │ │ │ └── gateway/ │ │ │ │ │ │ ├── CreditGateway.java │ │ │ │ │ │ └── CustomerGateway.java │ │ │ │ │ ├── order/ │ │ │ │ │ │ └── Order.java │ │ │ │ │ └── package-info.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── domain/ │ │ │ │ └── CustomerEntityTest.java │ │ │ ├── __rootArtifactId__-infrastructure/ │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── java/ │ │ │ │ │ │ ├── config/ │ │ │ │ │ │ │ └── DiamondConfig.java │ │ │ │ │ │ ├── customer/ │ │ │ │ │ │ │ ├── CreditGatewayImpl.java │ │ │ │ │ │ │ ├── CustomerDO.java │ │ │ │ │ │ │ ├── CustomerGatewayImpl.java │ │ │ │ │ │ │ └── CustomerMapper.java │ │ │ │ │ │ └── order/ │ │ │ │ │ │ └── OrderGatewayImpl.java │ │ │ │ │ └── resources/ │ │ │ │ │ ├── logback-spring.xml │ │ │ │ │ └── mybatis/ │ │ │ │ │ ├── customer-mapper.xml │ │ │ │ │ └── mybatis-config.xml │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ └── repository/ │ │ │ │ │ └── CustomerMapperTest.java │ │ │ │ └── resources/ │ │ │ │ └── sample.properties │ │ │ ├── pom.xml │ │ │ └── start/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── Application.java │ │ │ │ └── resources/ │ │ │ │ ├── application.properties │ │ │ │ └── logback-spring.xml │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ ├── TestApplication.java │ │ │ │ └── test/ │ │ │ │ └── CustomerServiceTest.java │ │ │ └── resources/ │ │ │ ├── logback-test.xml │ │ │ └── test.properties │ │ └── test/ │ │ └── resources/ │ │ └── projects/ │ │ └── basic/ │ │ ├── archetype.properties │ │ └── goal.txt │ └── pom.xml ├── cola-components/ │ ├── cola-component-catchlog-starter/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cola/ │ │ │ │ └── catchlog/ │ │ │ │ ├── ApplicationContextHelper.java │ │ │ │ ├── CatchAndLog.java │ │ │ │ ├── CatchLogAspect.java │ │ │ │ ├── CatchLogAutoConfiguration.java │ │ │ │ ├── DefaultResponseHandler.java │ │ │ │ ├── ResponseHandlerFactory.java │ │ │ │ └── ResponseHandlerI.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── catchlog/ │ │ │ └── test/ │ │ │ ├── Application.java │ │ │ ├── CatchLogTest.java │ │ │ ├── CustomResponseHandler.java │ │ │ └── Demo.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback-test.xml │ ├── cola-component-domain-starter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cola/ │ │ │ │ └── domain/ │ │ │ │ ├── ApplicationContextHelper.java │ │ │ │ ├── DomainAutoConfiguration.java │ │ │ │ ├── DomainFactory.java │ │ │ │ └── Entity.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── domain/ │ │ │ ├── Application.java │ │ │ ├── Customer.java │ │ │ └── PurchasePowerGateway.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback-test.xml │ ├── cola-component-dto/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ ├── dto/ │ │ │ │ ├── ClientObject.java │ │ │ │ ├── Command.java │ │ │ │ ├── DTO.java │ │ │ │ ├── MultiResponse.java │ │ │ │ ├── PageQuery.java │ │ │ │ ├── PageResponse.java │ │ │ │ ├── Query.java │ │ │ │ ├── Response.java │ │ │ │ ├── Scope.java │ │ │ │ └── SingleResponse.java │ │ │ └── extension/ │ │ │ └── BizScenario.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── Test.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── cola-component-exception/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── exception/ │ │ │ ├── Assert.java │ │ │ ├── BaseException.java │ │ │ ├── BizException.java │ │ │ ├── ExceptionFactory.java │ │ │ └── SysException.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── exception/ │ │ │ └── Test.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── cola-component-extension-starter/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cola/ │ │ │ │ └── extension/ │ │ │ │ ├── Extension.java │ │ │ │ ├── ExtensionAutoConfiguration.java │ │ │ │ ├── ExtensionCoordinate.java │ │ │ │ ├── ExtensionException.java │ │ │ │ ├── ExtensionExecutor.java │ │ │ │ ├── ExtensionPointI.java │ │ │ │ ├── ExtensionRepository.java │ │ │ │ ├── Extensions.java │ │ │ │ └── register/ │ │ │ │ ├── AbstractComponentExecutor.java │ │ │ │ ├── ExtensionBootstrap.java │ │ │ │ └── ExtensionRegister.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── extension/ │ │ │ ├── Application.java │ │ │ ├── ExtensionTest.java │ │ │ ├── MultiCoordinateTests.java │ │ │ ├── customer/ │ │ │ │ ├── app/ │ │ │ │ │ ├── AddCustomerCmdExe.java │ │ │ │ │ ├── CustomerCreatedEventHandler.java │ │ │ │ │ ├── CustomerServiceImpl.java │ │ │ │ │ ├── GetOneCustomerQryExe.java │ │ │ │ │ ├── extension/ │ │ │ │ │ │ ├── AddCustomerBiz1UseCase1Scenario1Validator.java │ │ │ │ │ │ ├── AddCustomerBiz1UseCase1Validator.java │ │ │ │ │ │ ├── AddCustomerBizOneValidator.java │ │ │ │ │ │ ├── AddCustomerBizTwoValidator.java │ │ │ │ │ │ ├── CustomerBizOneConvertorExt.java │ │ │ │ │ │ ├── CustomerBizTwoConvertorExt.java │ │ │ │ │ │ ├── CustomerConvertor.java │ │ │ │ │ │ └── StatusNameConvertorExt.java │ │ │ │ │ └── extensionpoint/ │ │ │ │ │ ├── AddCustomerValidatorExtPt.java │ │ │ │ │ ├── CustomerConvertorExtPt.java │ │ │ │ │ └── StatusNameConvertorExtPt.java │ │ │ │ ├── client/ │ │ │ │ │ ├── AddCustomerCmd.java │ │ │ │ │ ├── Constants.java │ │ │ │ │ ├── CustomerCreatedEvent.java │ │ │ │ │ ├── CustomerDTO.java │ │ │ │ │ ├── CustomerServiceI.java │ │ │ │ │ └── GetOneCustomerQry.java │ │ │ │ ├── domain/ │ │ │ │ │ ├── CustomerEntity.java │ │ │ │ │ ├── CustomerType.java │ │ │ │ │ ├── SourceType.java │ │ │ │ │ └── rule/ │ │ │ │ │ ├── CustomerBizOneRuleExt.java │ │ │ │ │ ├── CustomerBizTwoRuleExt.java │ │ │ │ │ └── CustomerRuleExtPt.java │ │ │ │ └── infrastructure/ │ │ │ │ ├── CustomerDO.java │ │ │ │ ├── CustomerRepository.java │ │ │ │ └── DomainEventPublisher.java │ │ │ └── register/ │ │ │ ├── CglibProxyFactory.java │ │ │ ├── ExtensionRegisterTest.java │ │ │ ├── SomeExtPt.java │ │ │ ├── SomeExtensionA.java │ │ │ └── SomeExtensionB.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback-test.xml │ ├── cola-component-job/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── cola/ │ │ │ │ └── job/ │ │ │ │ ├── BatchJobLauncher.java │ │ │ │ ├── ExecutionContext.java │ │ │ │ ├── JobBuilderFactory.java │ │ │ │ ├── JobException.java │ │ │ │ ├── JobLauncher.java │ │ │ │ ├── UuidGenerator.java │ │ │ │ ├── config/ │ │ │ │ │ ├── DBAutoConfiguration.java │ │ │ │ │ ├── EnableColaJob.java │ │ │ │ │ ├── EnableJobConfiguration.java │ │ │ │ │ ├── JobProperties.java │ │ │ │ │ └── RedisConfig.java │ │ │ │ ├── model/ │ │ │ │ │ ├── AbstractStep.java │ │ │ │ │ ├── BatchJob.java │ │ │ │ │ ├── BatchJobExecution.java │ │ │ │ │ ├── ExecutionStatus.java │ │ │ │ │ ├── Job.java │ │ │ │ │ ├── JobExecution.java │ │ │ │ │ ├── JobInstance.java │ │ │ │ │ ├── Step.java │ │ │ │ │ └── StepExecution.java │ │ │ │ └── repository/ │ │ │ │ ├── AbstractJobRepository.java │ │ │ │ ├── JobRepository.java │ │ │ │ ├── JsonUtil.java │ │ │ │ ├── RepositoryType.java │ │ │ │ ├── db/ │ │ │ │ │ ├── BatchJobExecutionRepository.java │ │ │ │ │ ├── DataBaseJobRepository.java │ │ │ │ │ ├── JobExecutionRepository.java │ │ │ │ │ └── StepExecutionRepository.java │ │ │ │ ├── memory/ │ │ │ │ │ └── MemoryJobRepository.java │ │ │ │ └── redis/ │ │ │ │ └── RedisJobRepository.java │ │ │ └── resources/ │ │ │ └── schema-mysql.sql │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── job/ │ │ │ └── test/ │ │ │ ├── AbstractBaseJobTest.java │ │ │ ├── MemoryDBJobTest.java │ │ │ ├── MemoryJobTest.java │ │ │ ├── MySQLJobTest.java │ │ │ ├── RedisJobTest.java │ │ │ ├── TestApplication.java │ │ │ ├── TestsContainerBoot.java │ │ │ └── steps/ │ │ │ ├── FailedStep.java │ │ │ ├── LongTimeStep.java │ │ │ ├── MyStep1.java │ │ │ ├── MyStep2.java │ │ │ ├── MyStep3.java │ │ │ ├── MyStep4.java │ │ │ ├── MyStep5.java │ │ │ └── SwitchStep.java │ │ └── resources/ │ │ ├── application-h2-test.yml │ │ ├── application-mysql-test.yml │ │ ├── application-redis-test.yml │ │ └── logback-test.xml │ ├── cola-component-ruleengine/ │ │ ├── README.md │ │ ├── gitignore.txt │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── ruleengine/ │ │ │ ├── api/ │ │ │ │ ├── Action.java │ │ │ │ ├── Condition.java │ │ │ │ ├── Fact.java │ │ │ │ ├── Facts.java │ │ │ │ ├── Rule.java │ │ │ │ └── RuleEngine.java │ │ │ └── core/ │ │ │ ├── AbstractRule.java │ │ │ ├── AllRules.java │ │ │ ├── AnyRules.java │ │ │ ├── CompositeRule.java │ │ │ ├── DefaultRule.java │ │ │ ├── DefaultRuleEngine.java │ │ │ ├── NaturalRules.java │ │ │ └── RuleBuilder.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── ruleengine/ │ │ │ ├── CompositeRuleTest.java │ │ │ ├── FactsTest.java │ │ │ ├── FizzBuzz.java │ │ │ ├── HelloWorld.java │ │ │ ├── PriorityTest.java │ │ │ ├── RuleBuilderTest.java │ │ │ ├── RuleEngineTest.java │ │ │ └── fizzbuzz/ │ │ │ ├── FizzBuzzTest.java │ │ │ ├── v1/ │ │ │ │ └── FizzBuzz.java │ │ │ └── v2/ │ │ │ ├── Action.java │ │ │ ├── Condition.java │ │ │ ├── FizzBuzz.java │ │ │ ├── Rule.java │ │ │ ├── SimpleRuleEngine.java │ │ │ └── TimesCondition.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── cola-component-statemachine/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── statemachine/ │ │ │ ├── Action.java │ │ │ ├── Condition.java │ │ │ ├── State.java │ │ │ ├── StateContext.java │ │ │ ├── StateMachine.java │ │ │ ├── StateMachineFactory.java │ │ │ ├── Transition.java │ │ │ ├── Visitable.java │ │ │ ├── Visitor.java │ │ │ ├── builder/ │ │ │ │ ├── AbstractParallelTransitionBuilder.java │ │ │ │ ├── AbstractTransitionBuilder.java │ │ │ │ ├── AlertFailCallback.java │ │ │ │ ├── ExternalParallelTransitionBuilder.java │ │ │ │ ├── ExternalTransitionBuilder.java │ │ │ │ ├── ExternalTransitionsBuilder.java │ │ │ │ ├── FailCallback.java │ │ │ │ ├── From.java │ │ │ │ ├── InternalTransitionBuilder.java │ │ │ │ ├── NumbFailCallback.java │ │ │ │ ├── On.java │ │ │ │ ├── ParallelFrom.java │ │ │ │ ├── ParallelTransitionBuilderImpl.java │ │ │ │ ├── StateMachineBuilder.java │ │ │ │ ├── StateMachineBuilderFactory.java │ │ │ │ ├── StateMachineBuilderImpl.java │ │ │ │ ├── To.java │ │ │ │ ├── TransitionBuilderImpl.java │ │ │ │ ├── TransitionsBuilderImpl.java │ │ │ │ ├── When.java │ │ │ │ └── package-info.java │ │ │ ├── exception/ │ │ │ │ └── TransitionFailException.java │ │ │ └── impl/ │ │ │ ├── Debugger.java │ │ │ ├── EventTransitions.java │ │ │ ├── PlantUMLVisitor.java │ │ │ ├── StateHelper.java │ │ │ ├── StateImpl.java │ │ │ ├── StateMachineException.java │ │ │ ├── StateMachineImpl.java │ │ │ ├── SysOutVisitor.java │ │ │ ├── TransitionImpl.java │ │ │ └── TransitionType.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── cola/ │ │ └── test/ │ │ ├── StateMachineChoiceTest.java │ │ ├── StateMachinePlantUMLTest.java │ │ ├── StateMachineTest.java │ │ └── StateMachineUnNormalTest.java │ ├── cola-component-test-container/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── test/ │ │ │ ├── BeanMetaUtils.java │ │ │ ├── TestExecutor.java │ │ │ ├── TestsContainer.java │ │ │ └── command/ │ │ │ ├── AbstractCommand.java │ │ │ ├── CommandEnum.java │ │ │ ├── GuideCmd.java │ │ │ ├── TestClassRunCmd.java │ │ │ └── TestMethodRunCmd.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── test/ │ │ │ ├── Demo.java │ │ │ ├── DemoWithExtension.java │ │ │ ├── SpringBootConfig.java │ │ │ ├── SpringConfig.java │ │ │ └── TestsContainerTest.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── cola-component-unittest/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── unittest/ │ │ │ ├── FixtureLoader.java │ │ │ ├── kafka/ │ │ │ │ ├── KafkaExtension.java │ │ │ │ ├── MessageData.java │ │ │ │ └── ProduceMessage.java │ │ │ ├── redis/ │ │ │ │ ├── ExpectRedis.java │ │ │ │ ├── RedisData.java │ │ │ │ ├── RedisExtension.java │ │ │ │ └── SetupRedis.java │ │ │ └── wiremock/ │ │ │ └── WireMockRegister.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── cola/ │ │ │ └── unittest/ │ │ │ ├── Application.java │ │ │ ├── TestsContainerBoot.java │ │ │ ├── db/ │ │ │ │ ├── DBSetupTest.java │ │ │ │ ├── Person.java │ │ │ │ └── PersonRepository.java │ │ │ ├── kafka/ │ │ │ │ ├── KafkaConsumer.java │ │ │ │ └── KafkaExtensionTest.java │ │ │ ├── redis/ │ │ │ │ └── RedisExtensionTest.java │ │ │ └── wiremock/ │ │ │ ├── Account.java │ │ │ └── WireMockBasicTest.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── fixture/ │ │ │ ├── db/ │ │ │ │ └── sample-data.xml │ │ │ ├── kafka/ │ │ │ │ └── produce-message.json │ │ │ ├── redis/ │ │ │ │ ├── array-setup.json │ │ │ │ ├── hash-setup.json │ │ │ │ ├── string-expect.json │ │ │ │ └── string-setup.json │ │ │ └── wiremock/ │ │ │ ├── stub-account.json │ │ │ └── stub-wire-mock-basic.json │ │ └── logback-test.xml │ ├── cola-components-bom/ │ │ └── pom.xml │ ├── dev-util-archetypes/ │ │ ├── README.md │ │ ├── cola-normal-component-archetype/ │ │ │ ├── .gitignore │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── resources/ │ │ │ │ ├── META-INF/ │ │ │ │ │ └── maven/ │ │ │ │ │ └── archetype-metadata.xml │ │ │ │ └── archetype-resources/ │ │ │ │ ├── gitignore.txt │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── java/ │ │ │ │ │ └── Dummy.java │ │ │ │ └── test/ │ │ │ │ └── resources/ │ │ │ │ └── logback-test.xml │ │ │ └── test/ │ │ │ └── resources/ │ │ │ └── projects/ │ │ │ └── basic/ │ │ │ ├── archetype.properties │ │ │ └── goal.txt │ │ ├── cola-starter-component-archetype/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── resources/ │ │ │ │ ├── META-INF/ │ │ │ │ │ └── maven/ │ │ │ │ │ └── archetype-metadata.xml │ │ │ │ └── archetype-resources/ │ │ │ │ ├── README.md │ │ │ │ ├── gitignore.txt │ │ │ │ ├── pom.xml │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── java/ │ │ │ │ │ │ ├── CatchAndLog.java │ │ │ │ │ │ ├── CatchLogAspect.java │ │ │ │ │ │ └── CatchLogAutoConfiguration.java │ │ │ │ │ └── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── spring.factories │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ └── test/ │ │ │ │ │ └── Application.java │ │ │ │ └── resources/ │ │ │ │ ├── application.properties │ │ │ │ └── logback-test.xml │ │ │ └── test/ │ │ │ └── resources/ │ │ │ └── projects/ │ │ │ └── basic/ │ │ │ ├── archetype.properties │ │ │ └── goal.txt │ │ ├── new-cola-normal-component.sh │ │ └── new-cola-starter-component.sh │ └── pom.xml ├── cola-samples/ │ ├── charge/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── huawei/ │ │ │ │ └── charging/ │ │ │ │ ├── Application.java │ │ │ │ ├── adapter/ │ │ │ │ │ └── ChargeController.java │ │ │ │ ├── application/ │ │ │ │ │ ├── ChargeServiceI.java │ │ │ │ │ ├── ChargeServiceImpl.java │ │ │ │ │ └── dto/ │ │ │ │ │ ├── BeginSessionRequest.java │ │ │ │ │ ├── ChargeRecordDto.java │ │ │ │ │ ├── ChargeRequest.java │ │ │ │ │ ├── EndSessionRequest.java │ │ │ │ │ ├── MultiResponse.java │ │ │ │ │ ├── Response.java │ │ │ │ │ └── SingleResponse.java │ │ │ │ ├── domain/ │ │ │ │ │ ├── ApplicationContextHelper.java │ │ │ │ │ ├── BizException.java │ │ │ │ │ ├── DomainFactory.java │ │ │ │ │ ├── Entity.java │ │ │ │ │ ├── account/ │ │ │ │ │ │ ├── Account.java │ │ │ │ │ │ └── AccountDomainService.java │ │ │ │ │ ├── charge/ │ │ │ │ │ │ ├── CallType.java │ │ │ │ │ │ ├── ChargeContext.java │ │ │ │ │ │ ├── ChargeRecord.java │ │ │ │ │ │ ├── Money.java │ │ │ │ │ │ ├── MoneyConverter.java │ │ │ │ │ │ ├── Session.java │ │ │ │ │ │ ├── chargeplan/ │ │ │ │ │ │ │ ├── BasicChargePlan.java │ │ │ │ │ │ │ ├── ChargePlan.java │ │ │ │ │ │ │ ├── ChargePlanType.java │ │ │ │ │ │ │ ├── FamilyChargePlan.java │ │ │ │ │ │ │ ├── FixedTimeChangePlan.java │ │ │ │ │ │ │ └── Resource.java │ │ │ │ │ │ └── chargerule/ │ │ │ │ │ │ ├── AbstractChargeRule.java │ │ │ │ │ │ ├── BasicChargeRule.java │ │ │ │ │ │ ├── ChargeRule.java │ │ │ │ │ │ ├── ChargeRuleFactory.java │ │ │ │ │ │ ├── CompositeChargeRule.java │ │ │ │ │ │ ├── FamilyChargeRule.java │ │ │ │ │ │ └── FixedTimeChargeRule.java │ │ │ │ │ └── gateway/ │ │ │ │ │ ├── AccountGateway.java │ │ │ │ │ ├── ChargeGateway.java │ │ │ │ │ └── SessionGateway.java │ │ │ │ └── infrastructure/ │ │ │ │ ├── AccountGatewayImpl.java │ │ │ │ ├── RestClientBean.java │ │ │ │ └── SessionGatewayImpl.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── logback.xml │ │ └── test/ │ │ ├── charge.http │ │ ├── java/ │ │ │ └── com/ │ │ │ └── huawei/ │ │ │ └── charging/ │ │ │ ├── CleanArchTest.java │ │ │ ├── TestsContainerBoot.java │ │ │ ├── application/ │ │ │ │ └── ChargeServiceTest.java │ │ │ ├── domain/ │ │ │ │ ├── ChargeRecordPlanTest.java │ │ │ │ ├── ChargeRecordRuleTest.java │ │ │ │ └── CompositeChargeRuleTestRecord.java │ │ │ └── infrastructure/ │ │ │ ├── AccountGatewayTest.java │ │ │ ├── ChargeRecordRepoTest.java │ │ │ ├── FixtureLoader.java │ │ │ ├── JSONTest.java │ │ │ ├── SpingBootConfTest.java │ │ │ ├── WireMockBasicTest.java │ │ │ └── WireMockRegister.java │ │ └── resources/ │ │ ├── application-test.yml │ │ ├── application.yml │ │ ├── fixture/ │ │ │ └── wiremock/ │ │ │ ├── stub_account.json │ │ │ ├── stub_insufficient_account.json │ │ │ └── stub_wire_mock_basic.json │ │ └── logback-test.xml │ └── craftsman/ │ ├── craftsman-adapter/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── craftsman/ │ │ └── web/ │ │ └── MetricsController.java │ ├── craftsman-app/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── craftsman/ │ │ │ ├── command/ │ │ │ │ ├── ATAMetricAddCmdExe.java │ │ │ │ ├── CodeReviewMetricAddCmdExe.java │ │ │ │ ├── MetricDeleteCmdExe.java │ │ │ │ ├── MiscMetricAddCmdExe.java │ │ │ │ ├── PaperMetricAddCmdExe.java │ │ │ │ ├── PatentMetricAddCmdExe.java │ │ │ │ ├── RefactoringMetricAddCmdExe.java │ │ │ │ ├── RefreshScoreCmdExe.java │ │ │ │ ├── SharingMetricAddCmdExe.java │ │ │ │ ├── UserProfileAddCmdExe.java │ │ │ │ ├── UserProfileUpdateCmdExe.java │ │ │ │ ├── package-info.java │ │ │ │ └── query/ │ │ │ │ ├── ATAMetricQryExe.java │ │ │ │ ├── UserProfileGetQryExe.java │ │ │ │ ├── UserProfileListQryExe.java │ │ │ │ └── package-info.java │ │ │ ├── event/ │ │ │ │ └── handler/ │ │ │ │ └── MetricItemCreatedHandler.java │ │ │ └── service/ │ │ │ ├── MetricsServiceImpl.java │ │ │ ├── UserProfileServiceImpl.java │ │ │ └── package-info.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── craftsman/ │ │ │ └── app/ │ │ │ └── ContextInterceptorTest.java │ │ └── resources/ │ │ └── logback-test.xml │ ├── craftsman-client/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── craftsman/ │ │ ├── api/ │ │ │ ├── MetricsServiceI.java │ │ │ └── UserProfileServiceI.java │ │ ├── context/ │ │ │ └── UserContext.java │ │ └── dto/ │ │ ├── ATAMetricAddCmd.java │ │ ├── ATAMetricQry.java │ │ ├── CodeReviewMetricAddCmd.java │ │ ├── CommonCommand.java │ │ ├── MetricDeleteCmd.java │ │ ├── MiscMetricAddCmd.java │ │ ├── PaperMetricAddCmd.java │ │ ├── PatentMetricAddCmd.java │ │ ├── RefactoringMetricAddCmd.java │ │ ├── RefreshScoreCmd.java │ │ ├── SharingMetricAddCmd.java │ │ ├── UserProfileAddCmd.java │ │ ├── UserProfileGetQry.java │ │ ├── UserProfileListQry.java │ │ ├── UserProfileUpdateCmd.java │ │ ├── clientobject/ │ │ │ ├── ATAMetricCO.java │ │ │ ├── AbstractMetricCO.java │ │ │ ├── MiscMetricCO.java │ │ │ ├── PaperMetricCO.java │ │ │ ├── PatentMetricCO.java │ │ │ ├── RefactoringMetricCO.java │ │ │ ├── SharingMetricCO.java │ │ │ └── UserProfileCO.java │ │ └── domainevent/ │ │ ├── CustomerCreatedEvent.java │ │ └── MetricItemCreatedEvent.java │ ├── craftsman-domain/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── craftsman/ │ │ │ └── domain/ │ │ │ ├── DomainFactory.java │ │ │ ├── gateway/ │ │ │ │ ├── MetricGateway.java │ │ │ │ └── UserProfileGateway.java │ │ │ ├── metrics/ │ │ │ │ ├── JSONPropertyFilter.java │ │ │ │ ├── MainMetric.java │ │ │ │ ├── MainMetricType.java │ │ │ │ ├── Measurable.java │ │ │ │ ├── Metric.java │ │ │ │ ├── MetricItem.java │ │ │ │ ├── SubMetric.java │ │ │ │ ├── SubMetricType.java │ │ │ │ ├── appquality/ │ │ │ │ │ ├── AppMetric.java │ │ │ │ │ ├── AppMetricItem.java │ │ │ │ │ └── AppQualityMetric.java │ │ │ │ ├── devquality/ │ │ │ │ │ ├── BugMetric.java │ │ │ │ │ ├── BugMetricItem.java │ │ │ │ │ └── DevQualityMetric.java │ │ │ │ ├── techcontribution/ │ │ │ │ │ ├── CodeReviewMetric.java │ │ │ │ │ ├── CodeReviewMetricItem.java │ │ │ │ │ ├── ContributionMetric.java │ │ │ │ │ ├── MiscMetric.java │ │ │ │ │ ├── MiscMetricItem.java │ │ │ │ │ ├── RefactoringLevel.java │ │ │ │ │ ├── RefactoringMetric.java │ │ │ │ │ └── RefactoringMetricItem.java │ │ │ │ ├── techinfluence/ │ │ │ │ │ ├── ATAMetric.java │ │ │ │ │ ├── ATAMetricItem.java │ │ │ │ │ ├── AuthorType.java │ │ │ │ │ ├── InfluenceMetric.java │ │ │ │ │ ├── PaperMetric.java │ │ │ │ │ ├── PaperMetricItem.java │ │ │ │ │ ├── PatentMetric.java │ │ │ │ │ ├── PatentMetricItem.java │ │ │ │ │ ├── SharingMetric.java │ │ │ │ │ ├── SharingMetricItem.java │ │ │ │ │ └── SharingScope.java │ │ │ │ └── weight/ │ │ │ │ ├── DevWeight.java │ │ │ │ ├── OtherWeight.java │ │ │ │ ├── QAWeight.java │ │ │ │ ├── Weight.java │ │ │ │ └── WeightFactory.java │ │ │ ├── package-info.java │ │ │ └── user/ │ │ │ ├── Role.java │ │ │ └── UserProfile.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── craftsman/ │ │ └── domain/ │ │ ├── ATAMetricTest.java │ │ ├── AppMetricTest.java │ │ ├── BugMetricTest.java │ │ ├── InfluenceMetricTest.java │ │ ├── PatentMetricTest.java │ │ ├── SharingMetricTest.java │ │ └── UserProfileTest.java │ ├── craftsman-infrastructure/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── craftsman/ │ │ │ │ ├── common/ │ │ │ │ │ ├── BizCode.java │ │ │ │ │ ├── event/ │ │ │ │ │ │ └── DomainEventPublisher.java │ │ │ │ │ └── exception/ │ │ │ │ │ └── ErrorCode.java │ │ │ │ ├── config/ │ │ │ │ │ └── CraftsmanConfig.java │ │ │ │ ├── convertor/ │ │ │ │ │ ├── MetricConvertor.java │ │ │ │ │ └── UserProfileConvertor.java │ │ │ │ └── gatewayimpl/ │ │ │ │ ├── MetricGatewayImpl.java │ │ │ │ ├── UserProfileGatewayImpl.java │ │ │ │ ├── database/ │ │ │ │ │ ├── MetricMapper.java │ │ │ │ │ ├── UserProfileMapper.java │ │ │ │ │ └── dataobject/ │ │ │ │ │ ├── BaseDO.java │ │ │ │ │ ├── MetricDO.java │ │ │ │ │ └── UserProfileDO.java │ │ │ │ └── rpc/ │ │ │ │ ├── AppMetricMapper.java │ │ │ │ ├── BugMetricMapper.java │ │ │ │ └── dataobject/ │ │ │ │ ├── AppMetricDO.java │ │ │ │ └── BugMetricDO.java │ │ │ └── resources/ │ │ │ ├── TableCreationDDL.sql │ │ │ ├── mybatis/ │ │ │ │ ├── MetricMapper.xml │ │ │ │ └── UserProfileMapper.xml │ │ │ └── mybatis-config.xml │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── craftsman/ │ │ │ └── gatewayimpl/ │ │ │ ├── Mybatis3Utils.java │ │ │ └── MybatisTest.java │ │ └── resources/ │ │ ├── logback-test.xml │ │ └── mybatis-config-test.xml │ ├── pom.xml │ └── start/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── craftsman/ │ │ │ └── Application.java │ │ └── resources/ │ │ ├── application.properties │ │ └── logback-spring.xml │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── craftsman/ │ │ ├── TestApplication.java │ │ └── gatewayimpl/ │ │ ├── MetricTunnelTest.java │ │ └── UserProfileTunnelTest.java │ ├── resources/ │ │ ├── logback-test.xml │ │ ├── mockfile/ │ │ │ ├── com.alibaba.craftsman.app.ATAMetricAddCmdExeTest_testATAMetricAddSuccess │ │ │ ├── com.alibaba.craftsman.app.ATAMetricAddCmdExeTest_testATAMetricAddSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.CodeReviewMetricAddCmdExeTest_testSuccess │ │ │ ├── com.alibaba.craftsman.app.CodeReviewMetricAddCmdExeTest_testSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.MetricDeleteCmdExeTest_testSuccess │ │ │ ├── com.alibaba.craftsman.app.MetricDeleteCmdExeTest_testSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.MiscMetricAddCmdExeTest_testSuccess │ │ │ ├── com.alibaba.craftsman.app.MiscMetricAddCmdExeTest_testSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.PaperMetricAddCmdExeTest_testPaperMetricAddSuccess │ │ │ ├── com.alibaba.craftsman.app.PaperMetricAddCmdExeTest_testPaperMetricAddSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.PatentMetricAddCmdExeTest_testPatentMetricAddSuccess │ │ │ ├── com.alibaba.craftsman.app.PatentMetricAddCmdExeTest_testPatentMetricAddSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.RefactoringMetricAddCmdExeTest_testSuccess │ │ │ ├── com.alibaba.craftsman.app.RefactoringMetricAddCmdExeTest_testSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.ScoreRecalculateTest_testDevSuccess │ │ │ ├── com.alibaba.craftsman.app.ScoreRecalculateTest_testDevSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.SharingMetricAddCmdExeTest_testSharingMetricAddSuccess │ │ │ ├── com.alibaba.craftsman.app.SharingMetricAddCmdExeTest_testSharingMetricAddSuccess_inputParams │ │ │ ├── com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessAdd │ │ │ ├── com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessAdd_inputParams │ │ │ ├── com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessUpdate │ │ │ ├── com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessUpdate_inputParams │ │ │ └── service.list │ │ └── spring-mock-test.xml │ ├── testAddCmd.http │ └── testQry.http ├── mvnw ├── mvnw.cmd ├── pom.xml └── scripts/ ├── bump_cola_version ├── integration_test └── maven-deploy.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true tab_width = 4 indent_style = space trim_trailing_whitespace = true [*.xml] indent_size = 4 [*.{yml,yaml}] indent_size = 2 [*.{md,mkd,markdown}] indent_size = 4 trim_trailing_whitespace = false ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "maven" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" open-pull-requests-limit: 32 ================================================ FILE: .github/workflows/ci.yaml ================================================ # Quickstart for GitHub Actions # https://docs.github.com/en/actions/quickstart name: Fast CI on: [ push, pull_request, workflow_dispatch ] jobs: test: runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: matrix: os: [ ubuntu-latest, windows-latest ] java: [ 17 ] fail-fast: false max-parallel: 64 name: fast test on Java ${{ matrix.java }} OS ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: java-version: ${{ matrix.java }} distribution: zulu cache: maven - name: Build with Maven run: ./mvnw -V --no-transfer-progress clean install - name: remove cola self maven install files for OS *nix run: rm -rf $HOME/.m2/repository/com/alibaba/{cola,craftsman} # https://docs.github.com/en/actions/learn-github-actions/variables#detecting-the-operating-system # https://docs.github.com/en/actions/learn-github-actions/expressions if: runner.os != 'Windows' - name: remove cola self maven install files for OS Windows run: | Remove-Item -Recurse -Force $home/.m2/repository/com/alibaba/cola Remove-Item -Recurse -Force $home/.m2/repository/com/alibaba/craftsman if: runner.os == 'Windows' ================================================ FILE: .github/workflows/ci_by_multiply_java_versions.yaml ================================================ # Quickstart for GitHub Actions # https://docs.github.com/en/actions/quickstart name: Strong CI with multiply java versions on: [ push, pull_request, workflow_dispatch ] jobs: test: # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-github-hosted-runners runs-on: ubuntu-latest timeout-minutes: 20 name: test by multiply java versions steps: - uses: actions/checkout@v3 with: submodules: recursive - name: Setup Java uses: actions/setup-java@v4 with: # https://github.com/actions/setup-java?tab=readme-ov-file#install-multiple-jdks java-version: | 17 21 22 distribution: zulu cache: maven - run: scripts/integration_test env: JAVA17_HOME: ${{ env.JAVA_HOME_17_X64 }} JAVA21_HOME: ${{ env.JAVA_HOME_21_X64 }} JAVA22_HOME: ${{ env.JAVA_HOME_22_X64 }} - name: remove cola self maven install files run: rm -rf $HOME/.m2/repository/com/alibaba/{cola,craftsman} ================================================ FILE: .gitignore ================================================ target/ !.mvn/wrapper/maven-wrapper.jar ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### BUILD ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ target/ out/ .DS_Store .vscode/settings.json ${project.build.directory} ================================================ FILE: .gitmodules ================================================ [submodule "scripts/bash-buddy"] path = scripts/bash-buddy url = https://github.com/foldright/bash-buddy.git ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. wrapperVersion=3.3.2 distributionType=only-script distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip ================================================ FILE: LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ================================================ FILE: README.md ================================================ # 🥤 COLA v5 [![Fast CI](https://img.shields.io/github/actions/workflow/status/alibaba/cola/ci.yaml?branch=master&logo=github&logoColor=white&label=fast%20ci)](https://github.com/alibaba/cola/actions/workflows/ci.yaml) [![Multiply Java versions CI](https://img.shields.io/github/actions/workflow/status/alibaba/cola/ci_by_multiply_java_versions.yaml?branch=master&logo=github&logoColor=white&label=strong%20ci)](https://github.com/alibaba/cola/actions/workflows/ci_by_multiply_java_versions.yaml) [![License](https://img.shields.io/badge/license-LGPL--2.1-4EB1BA.svg?color=4D7A97&logo=apache)](LICENSE) [![Java support](https://img.shields.io/badge/Java-17+-339933?logo=OpenJDK&logoColor=white)](https://openjdk.java.net/) [![Maven Central](https://img.shields.io/maven-central/v/com.alibaba.cola/cola-component-dto.svg?logo=apache-maven&label=maven%20central)](https://central.sonatype.com/namespace/com.alibaba.cola) [![GitHub Releases](https://img.shields.io/github/release/alibaba/COLA.svg)](https://github.com/alibaba/COLA/releases) [![GitHub Stars](https://img.shields.io/github/stars/alibaba/COLA?style=flat)](https://github.com/alibaba/COLA/stargazers) [![GitHub Forks](https://img.shields.io/github/forks/alibaba/COLA?style=flat)](https://github.com/alibaba/COLA/fork) [![user repos](https://badgen.net/github/dependents-repo/alibaba/COLA?label=user%20repos)](https://github.com/alibaba/COLA/network/dependents) [![GitHub issues](https://img.shields.io/github/issues/alibaba/COLA.svg)](https://github.com/alibaba/COLA/issues) [![GitHub Contributors](https://img.shields.io/github/contributors/alibaba/COLA)](https://github.com/alibaba/COLA/graphs/contributors) [![gitpod: Ready to Code](https://img.shields.io/badge/Gitpod-ready%20to%20code-339933?label=gitpod&logo=gitpod&logoColor=white)](https://gitpod.io/#https://github.com/alibaba/COLA) COLA 是 Clean Object-Oriented and Layered Architecture的缩写,代表“整洁面向对象分层架构”。 目前COLA已经发展到[COLA v5](#版本迭代)。 > - 想了解更多COLA信息,请关注微信公众号: > qrcode > - 想了解更多COLA背后的故事,请支持我的新书[《程序员的底层思维》](https://item.jd.com/13652002.html) COLA分为两个部分,COLA架构和COLA组件。 # 一、COLA架构 ## COLA 概述 **架构**的**意义** 就是 要素结构: - 要素 是 组成架构的重要元素; - 结构 是 要素之间的关系。 而 **应用架构**的**意义** 就在于 - 定义一套良好的结构; - 治理应用复杂度,降低系统熵值; - 从随心所欲的混乱状态,走向井井有条的有序状态。 arch why COLA架构就是为此而生,其核心职责就是定义良好的应用结构,提供最佳应用架构的最佳实践。通过不断探索,我们发现良好的分层结构,良好的包结构定义,可以帮助我们治理混乱不堪的业务应用系统。 cure 经过多次迭代,我们定义出了相对稳定、可靠的应用架构: cola arch ## COLA Archetypes 好的应用架构,都遵循一些共同模式,不管是六边形架构、洋葱圈架构、整洁架构、还是COLA架构,**都提倡以业务为核心,解耦外部依赖,分离业务复杂度和技术复杂度等**。 COLA架构区别于这些架构的地方,在于除了思想之外,我们还提供了可落地的工具和实践指导。 为了能够快速创建满足COLA架构的应用,我们提供了两个`archetype`,位于[`cola-archetypes`目录](cola-archetypes)下: 1. `cola-archetype-service`:用来创建纯后端服务的`archetype`。 2. `cola-archetype-web`:用来创建`adapter`和后端服务一体的`web`应用`archetype`。 # 二、COLA组件 此外,我们还提供了一些非常有用的通用组件,这些组件可以帮助我们提升研发效率。 这些功能组件被收拢在[`cola-components`目录](cola-components)下面。到目前为止,我们已经沉淀了以下组件: 组件名称 | 功能 | 依赖 ------ | ---- | ---- `cola-component-dto` | 定义了`DTO`格式,包括分页 |无 `cola-component-exception` | 定义了异常格式,
主要有`BizException`和`SysException` |无 `cola-component-statemachine` | 状态机组件 | 无 `cola-component-domain-starter` | `Spring`托管的领域实体组件 | 无 `cola-component-catchlog-starter` | 异常处理和日志组件 | `exception`、`dto`组件 `cola-component-extension-starter` | 扩展点组件 | 无 `cola-component-test-container` | 测试容器组件 | 无 # 三、如何使用COLA ## 1. 创建应用 执行以下命令: ```bash mvn archetype:generate \ -DgroupId=com.alibaba.cola.demo.web \ -DartifactId=demo-web \ -Dversion=1.0.0-SNAPSHOT \ -Dpackage=com.alibaba.demo \ -DarchetypeArtifactId=cola-framework-archetype-web \ -DarchetypeGroupId=com.alibaba.cola \ -DarchetypeVersion=5.0.0 ``` 命令执行成功的话,会看到如下的应用代码结构: demo struture ## 2. 运行应用 - 在`项目`目录下运行`mvn install`(如果不想运行测试,可以加上`-DskipTests`参数)。 - 进入`start`目录,执行`mvn spring-boot:run`。 运行成功的话,可以看到`SpringBoot`启动成功的界面。 - 生成的应用中,已经实现了一个简单的`Rest`请求,可以在浏览器中输入 http://localhost:8080/helloworld 进行测试。 如果要生成不是`web`工程而是`service`工程也类似,执行的是下面的命令: ```bash mvn archetype:generate \ -DgroupId=com.alibaba.cola.demo.service \ -DartifactId=demo-service \ -Dversion=1.0.0-SNAPSHOT \ -Dpackage=com.alibaba.demo \ -DarchetypeArtifactId=cola-framework-archetype-service \ -DarchetypeGroupId=com.alibaba.cola \ -DarchetypeVersion=5.0.0 ``` # 版本迭代 ## 5.0.0 版本 1. 支持jdk17和SpringBoot 3.x 2. 增加cola-archetype-light,支持新的基于package轻量级分层架构 3. 增加cola-component-unittest组件,支持[新的单元测试](https://blog.csdn.net/significantfrank/article/details/137495244) 4. 增强cola-component-test-container组件,支持Junit5的Extension ## 4.0.0 版本 https://blog.csdn.net/significantfrank/article/details/110934799 ## 3.1.0 版本 https://blog.csdn.net/significantfrank/article/details/109529311 1. 进一步简化了`cola-core`,只保留了扩展能力。 2. 将`exception`从`cola-core`移入到`cola-common`。 3. 对`archetype`中的分包逻辑进行重构,改成按照`domain`做划分。 4. 将`cola-archetype-web`中的`controller`改名为`adapter`,为了呼应六边形架构的命名。 ## 3.0.0 版本 https://blog.csdn.net/significantfrank/article/details/106976804 ## 2.0.0 版本 https://blog.csdn.net/significantfrank/article/details/100074716 ## 1.0.0 版本 https://blog.csdn.net/significantfrank/article/details/85785565 ================================================ FILE: cola-archetypes/cola-archetype-light/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-framework-archetypes-parent 5.x-SNAPSHOT cola-archetype-light maven-archetype ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/META-INF/maven/archetype-metadata.xml ================================================ src/main/java **/*.java src/main/resources **/*.xml src/main/resources **/*.yml src/test/java **/*.java src/test/resources **/*.xml src/test **/*.http src/test/resources **/*.json **/*.yml .idea **/*.xml .idea **/*.gitignore img_1.png charge-parent.iml charge.iml img.png charging-system.iml README.md ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/README.md ================================================ # 运营商计费系统 计费系统是一个典型的复杂问题场景,比较适合采用COLA架构,且发挥Domain层的价值。故将其作为COLA的另一个Sample。 运营商计费系统的需求如下: 运营商向用户提供电话服务,支持用户拨打/接听电话,并对通话收取费用。 如:主动拨打电话收取 0.5 元/分钟的通话费用;接听电话收取 0.4 元/分钟的通话费用。 运营商为了吸引客户,定义了若干电话套餐,总共有三种类型的套餐。我们要设计一个**计费系统**用于套餐计费规则的执行,保存计费记录,并通知**账户系统**扣减费用。 _注意:在一次通话过程中,通话控制系统可能会调用多次计费系统进行计费。_ - 基础套餐 1. 主叫收费 0.5 元/分钟 2. 被叫收费 0.4 元/分钟 - 固定时长套餐 1. 套餐月固定费 100 元,包含:200 分钟主叫通话时间+200 分钟被叫接听时间 2. 套餐外部分不再参与打折优惠,主叫 0.5 元/分钟,被叫 0.4 元/分钟 - 家庭套餐 1. 套餐月固定费 20 元 2. 用户可以指定 N 个号码作为自己的亲情号 3. 用户接听/拨打亲情号均不收费 4. 与亲情号之外的号码通话,主叫 0.5 元/分钟,被叫 0.4 元/分钟 # 系统设计 ## 统一语言 我经常说,建模就是在分析语言,对于任何问题域,熟悉领域知识、理清概念、统一语言都是非常必要且重要的事情。 对于这个系统也不例外。 ![img_1.png](img_1.png) ## 计费系统和周边系统的关系 ![img.png](img.png) ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/pom.xml ================================================ 4.0.0 ${groupId} ${artifactId} ${version} 17 17 17 UTF-8 3.0.0 1.3.0 3.2.0 org.springframework.boot spring-boot-dependencies ${spring.boot.version} pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-webflux org.springframework.boot spring-boot-starter-test test org.projectlombok lombok 1.18.22 org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java 8.0.33 com.alibaba.cola cola-component-test-container 4.4.0-SNAPSHOT com.h2database h2 test org.wiremock wiremock-standalone 3.5.4 test ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/Application.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/adapter/ChargeController.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.adapter; import ${package}.application.ChargeServiceI; import ${package}.application.dto.*; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @RestController @Slf4j public class ChargeController { @Resource private ChargeServiceI chargeService; @PostMapping("session/{sessionId}/begin") public Response begin(@PathVariable(name = "sessionId") String sessionId, @RequestParam("callingPhoneNo") String callingPhoneNo, @RequestParam("calledPhoneNo") String calledPhoneNo) { log.debug(sessionId + " " + callingPhoneNo + " " + calledPhoneNo); BeginSessionRequest request = new BeginSessionRequest(sessionId, Long.valueOf(callingPhoneNo), Long.valueOf(calledPhoneNo)); return chargeService.begin(request); } @PostMapping("session/{sessionId}/charge") public Response charge(@PathVariable(name = "sessionId") String sessionId, @RequestParam int duration) { log.debug(sessionId + " " + duration); ChargeRequest request = new ChargeRequest(sessionId, duration); return chargeService.charge(request); } @PostMapping("session/{sessionId}/end") public Response end(@PathVariable(name = "sessionId") String sessionId, @RequestParam int duration) { log.debug(sessionId + " " + duration); EndSessionRequest request = new EndSessionRequest(sessionId, duration); return chargeService.end(request); } @GetMapping("{sessionId}/chargeRecords") public MultiResponse getChargeRecord(@PathVariable(name = "sessionId") String sessionId) { return chargeService.listChargeRecords(sessionId); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/ChargeServiceI.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application; import ${package}.application.dto.*; public interface ChargeServiceI { Response begin(BeginSessionRequest request); Response charge(ChargeRequest request); Response end(EndSessionRequest request); MultiResponse listChargeRecords(String sessionId); } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/ChargeServiceImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application; import ${package}.application.dto.*; import ${package}.domain.account.Account; import ${package}.domain.account.AccountDomainService; import ${package}.domain.charge.CallType; import ${package}.domain.charge.ChargeContext; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.Session; import ${package}.domain.gateway.AccountGateway; import ${package}.domain.gateway.ChargeGateway; import ${package}.domain.gateway.SessionGateway; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service @Slf4j public class ChargeServiceImpl implements ChargeServiceI { @Resource private SessionGateway sessionGateway; @Resource private AccountGateway accountGateway; @Resource private AccountDomainService accountDomainService; @Resource private ChargeGateway chargeGateway; @Override public Response begin(BeginSessionRequest request) { Session session = request.toSession(); accountDomainService.canSessionStart(session); sessionGateway.create(session); log.debug("Session created successfully :" + session); return Response.buildSuccess(); } @Override public Response charge(ChargeRequest request) { log.debug("Do charge : " + request); Session session = sessionGateway.get(request.getSessionId()); int durationToCharge = request.getDuration() - session.getChargedDuration(); List chargeRecordList = new ArrayList<>(); chargeCalling(session, durationToCharge, chargeRecordList); chargeCalled(session, durationToCharge, chargeRecordList); chargeGateway.saveAll(chargeRecordList); session.setChargedDuration(request.getDuration()); return Response.buildSuccess(); } private void chargeCalling(Session session, int durationToCharge, List chargeRecordList) { Account callingAccount = accountGateway.getAccount(session.getCallingPhoneNo()); ChargeContext callingCtx = new ChargeContext(CallType.CALLING, session.getCallingPhoneNo(), session.getCalledPhoneNo(), durationToCharge); callingCtx.session = session; callingCtx.account = callingAccount; chargeRecordList.addAll(callingAccount.charge(callingCtx)); } private void chargeCalled(Session session, int durationToCharge, List chargeRecordList) { Account calledAccount = accountGateway.getAccount(session.getCalledPhoneNo()); ChargeContext calledCtx = new ChargeContext(CallType.CALLED, session.getCalledPhoneNo(), session.getCallingPhoneNo(), durationToCharge); calledCtx.session = session; calledCtx.account = calledAccount; chargeRecordList.addAll(calledAccount.charge(calledCtx)); } @Override public Response end(EndSessionRequest request) { charge(request.toChargeRequest()); sessionGateway.end(request.getSessionId()); return Response.buildSuccess(); } @Override public MultiResponse listChargeRecords(String sessionId) { List chargeRecordList = chargeGateway.findBySessionId(sessionId); List chargeRecordDtoList = new ArrayList<>(); for (ChargeRecord chargeRecord : chargeRecordList) { chargeRecordDtoList.add(ChargeRecordDto.fromEntity(chargeRecord)); } return MultiResponse.of(chargeRecordDtoList); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/dto/BeginSessionRequest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application.dto; import ${package}.domain.charge.Session; import lombok.Data; @Data public class BeginSessionRequest { /** * 本次通话的UUID */ private String sessionId; /** * 主叫电话号码 */ private long callingPhoneNo; /** * 被叫电话号码 */ private long calledPhoneNo; public Session toSession(){ return new Session(sessionId, callingPhoneNo, calledPhoneNo); } public BeginSessionRequest() { } public BeginSessionRequest(String sessionId, long callingPhoneNo, long calledPhoneNo) { this.sessionId = sessionId; this.callingPhoneNo = callingPhoneNo; this.calledPhoneNo = calledPhoneNo; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/dto/ChargeRecordDto.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application.dto; import ${package}.domain.charge.CallType; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.chargeplan.ChargePlanType; public class ChargeRecordDto { public Long id; public String sessionId; public long phoneNo; public int chargeDuration; public long cost; public CallType callType; public ChargePlanType chargePlanType; public static ChargeRecordDto fromEntity(ChargeRecord chargeRecord){ ChargeRecordDto dto = new ChargeRecordDto(); dto.id = chargeRecord.getId(); dto.sessionId = chargeRecord.getSessionId(); dto.phoneNo = chargeRecord.getPhoneNo(); dto.chargeDuration = chargeRecord.getChargeDuration(); dto.cost = chargeRecord.getCost().getAmount(); dto.callType = chargeRecord.getCallType(); dto.chargePlanType = chargeRecord.getChargePlanType(); return dto; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/dto/ChargeRequest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application.dto; import lombok.Data; @Data public class ChargeRequest { private String sessionId; /** * 当前通话,截止目前的累计时间 */ private int duration; public ChargeRequest() { } public ChargeRequest(String sessionId, int duration) { this.sessionId = sessionId; this.duration = duration; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/dto/EndSessionRequest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application.dto; import lombok.Data; @Data public class EndSessionRequest { private String sessionId; /** * 当前通话,截止目前的累计时间 */ private int duration; public ChargeRequest toChargeRequest() { ChargeRequest chargeRequest = new ChargeRequest(); chargeRequest.setSessionId(sessionId); chargeRequest.setDuration(duration); return chargeRequest; } public EndSessionRequest() { } public EndSessionRequest(String sessionId, int duration) { this.sessionId = sessionId; this.duration = duration; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/dto/MultiResponse.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application.dto; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; public class MultiResponse extends Response { private static final long serialVersionUID = 1L; private Collection data; public List getData() { if (null == data) { return Collections.emptyList(); } if (data instanceof List) { return (List) data; } return new ArrayList<>(data); } public void setData(Collection data) { this.data = data; } public boolean isEmpty() { return data == null || data.isEmpty(); } public boolean isNotEmpty() { return !isEmpty(); } public static MultiResponse buildSuccess() { MultiResponse response = new MultiResponse(); response.setSuccess(true); return response; } public static MultiResponse buildFailure(String errCode, String errMessage) { MultiResponse response = new MultiResponse(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } public static MultiResponse of(Collection data) { MultiResponse response = new MultiResponse<>(); response.setSuccess(true); response.setData(data); return response; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/dto/Response.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application.dto; public class Response { private boolean success; private String errCode; private String errMessage; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getErrCode() { return errCode; } public void setErrCode(String errCode) { this.errCode = errCode; } public String getErrMessage() { return errMessage; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } @Override public String toString() { return "Response [success=" + success + ", errCode=" + errCode + ", errMessage=" + errMessage + "]"; } public static Response buildSuccess() { Response response = new Response(); response.setSuccess(true); return response; } public static Response buildFailure(String errCode, String errMessage) { Response response = new Response(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/application/dto/SingleResponse.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application.dto; public class SingleResponse extends Response { private static final long serialVersionUID = 1L; private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } public static SingleResponse buildSuccess() { SingleResponse response = new SingleResponse(); response.setSuccess(true); return response; } public static SingleResponse buildFailure(String errCode, String errMessage) { SingleResponse response = new SingleResponse(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } public static SingleResponse of(T data) { SingleResponse response = new SingleResponse<>(); response.setSuccess(true); response.setData(data); return response; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/ApplicationContextHelper.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextHelper implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHelper.applicationContext = applicationContext; } public static T getBean(Class targetClz) { T beanInstance = null; //优先按type查 try { beanInstance = (T)applicationContext.getBean(targetClz); } catch (Exception e) { } //按name查 if (beanInstance == null) { String simpleName = targetClz.getSimpleName(); //首字母小写 simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); beanInstance = (T)applicationContext.getBean(simpleName); } if (beanInstance == null) { throw new RuntimeException("Component " + targetClz + " can not be found in Spring Container"); } return beanInstance; } public static Object getBean(String claz) { return ApplicationContextHelper.applicationContext.getBean(claz); } public static T getBean(String name, Class requiredType) { return ApplicationContextHelper.applicationContext.getBean(name, requiredType); } public static T getBean(Class requiredType, Object... params) { return ApplicationContextHelper.applicationContext.getBean(requiredType, params); } public static ApplicationContext getApplicationContext() { return applicationContext; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/BizException.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; public class BizException extends RuntimeException{ public BizException(String errMessage) { super(errMessage); } public static BizException of(String errMessage){ return new BizException(errMessage); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/DomainFactory.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; public class DomainFactory { public static T get(Class entityClz){ return ApplicationContextHelper.getBean(entityClz); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/Entity.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.lang.annotation.*; @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public @interface Entity { } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/account/Account.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.account; import com.fasterxml.jackson.annotation.JsonIgnore; import ${package}.domain.BizException; import ${package}.domain.DomainFactory; import ${package}.domain.Entity; import ${package}.domain.charge.*; import ${package}.domain.charge.chargeplan.BasicChargePlan; import ${package}.domain.charge.chargeplan.ChargePlan; import ${package}.domain.charge.chargerule.ChargeRuleFactory; import ${package}.domain.charge.chargerule.CompositeChargeRule; import ${package}.domain.gateway.AccountGateway; import jakarta.annotation.Resource; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; @Data @Entity @Slf4j public class Account { /** * 用户号码 */ private long phoneNo; /** * 账户余额 */ private Money remaining; /** * 账户所拥有的套餐 */ @JsonIgnore private List chargePlanList = new ArrayList<>();; @Resource private AccountGateway accountGateway; private String name; public Account(){ } public Account(long phoneNo, Money amount, List chargePlanList){ this.phoneNo = phoneNo; this.remaining = amount; this.chargePlanList = chargePlanList; } public static Account valueOf(long phoneNo, Money amount) { Account account = DomainFactory.get(Account.class); account.setPhoneNo(phoneNo); account.setRemaining(amount); account.chargePlanList.add(new BasicChargePlan()); return account; } /** * 检查账户余额是否足够 */ public void checkRemaining() { if (remaining.isLessThan(Money.of(0))) { throw BizException.of(this.phoneNo + " has insufficient amount"); } } public List charge(ChargeContext ctx) { CompositeChargeRule compositeChargeRule = ChargeRuleFactory.get(chargePlanList); List chargeRecords = compositeChargeRule.doCharge(ctx); log.debug("Charges: "+ chargeRecords); //跟新账户系统 accountGateway.sync(phoneNo, chargeRecords); return chargeRecords; } @Override public String toString() { return "Account{" + "phoneNo=" + phoneNo + ", remaining=" + remaining + ", chargePlanList=" + chargePlanList + ", name=" + name + '}'; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/account/AccountDomainService.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.account; import ${package}.domain.charge.Session; import ${package}.domain.gateway.AccountGateway; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; @Component public class AccountDomainService { @Resource private AccountGateway accountGateway; public void canSessionStart(Session session){ Account callingAccount = accountGateway.getAccount(session.getCallingPhoneNo()); Account calledAccount = accountGateway.getAccount(session.getCalledPhoneNo()); callingAccount.checkRemaining(); calledAccount.checkRemaining(); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/CallType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge; public enum CallType { /** * 主叫 */ CALLING, /** * 被叫 */ CALLED } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/ChargeContext.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge; import ${package}.domain.account.Account; import lombok.Data; @Data public class ChargeContext { /** * 本次通话的Session */ public Session session; /** * 呼叫类型 */ public CallType callType; /** * 账号号码 */ public long phoneNo; /** * 通话另一端号码 */ public long otherSidePhoneNo; /** * 当前需要被扣费的时长 */ public int durationToCharge; /** * 被Charge的账号 */ public Account account; public ChargeContext(CallType callType, long phoneNo, long otherSidePhoneNo, int durationToCharge) { this.callType = callType; this.phoneNo = phoneNo; this.otherSidePhoneNo = otherSidePhoneNo; this.durationToCharge = durationToCharge; } public boolean needCharge(){ return durationToCharge >0; } public boolean isCalling(){ return CallType.CALLING == this.callType; } public boolean isCalled(){ return CallType.CALLED == this.callType; } @Override public String toString() { return "ChargeContext{" + "callType=" + callType + ", phoneNo=" + phoneNo + ", otherSidePhoneNo=" + otherSidePhoneNo + ", durationToCharge=" + durationToCharge + ", account=" + account + '}'; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/ChargeRecord.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge; import ${package}.domain.charge.chargeplan.ChargePlanType; import lombok.Data; import jakarta.persistence.*; import java.util.Date; @Entity @Table(name = "charge_record") @Data public class ChargeRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long Id; private String sessionId; private long phoneNo; /** * 呼叫类型 */ @Enumerated(EnumType.STRING) private CallType callType; /** * 计费记录所对应的呼叫时长 */ private int chargeDuration; /** * 所属计费套餐 */ @Enumerated(EnumType.STRING) private ChargePlanType chargePlanType; private Money cost; @Temporal(TemporalType.TIMESTAMP) public Date createTime; @Temporal(TemporalType.TIMESTAMP) public Date updateTime; public ChargeRecord() { } public ChargeRecord(long phoneNo, CallType callType, int chargeDuration, ChargePlanType chargePlanType, Money cost) { this.phoneNo = phoneNo; this.callType = callType; this.chargeDuration = chargeDuration; this.chargePlanType = chargePlanType; this.cost = cost; } @Override public String toString() { return "Charge{" + "phoneNo=" + phoneNo + ", callType=" + callType + ", chargeDuration=" + chargeDuration + ", chargePlanType=" + chargePlanType + ", cost=" + cost + '}'; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/Money.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge; import lombok.Data; import java.util.Objects; /** * 这个Money是简化版的,真实场景应该用BigDecimal */ @Data public class Money { /** * 单位是角,1代表0.1元, 10代表1元 */ private int amount; public Money(int amount) { this.amount = amount; } public static Money of(int amount){ return new Money(amount); } public boolean isLessThan(Money money){ return this.amount <= money.getAmount(); } public void minus(Money money){ this.amount = this.amount - money.getAmount(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Money money = (Money) o; return amount == money.amount; } @Override public int hashCode() { return Objects.hash(amount); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/MoneyConverter.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; @Converter(autoApply = true) public class MoneyConverter implements AttributeConverter { @Override public Long convertToDatabaseColumn(Money entityData) { return Long.valueOf(entityData.getAmount()); } @Override public Money convertToEntityAttribute(Long dbData) { return Money.of(dbData.intValue()); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/Session.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class Session { private String sessionId; /** * 主叫电话号码 */ private long callingPhoneNo; /** * 被叫电话号码 */ private long calledPhoneNo; /** * 当前通话已扣费的时长 * */ private int chargedDuration; /** * 当前通话产生的Charge记录 */ private List chargeRecordList = new ArrayList<>(); public Session(String sessionId, long callingPhoneNo, long calledPhoneNo) { this.sessionId = sessionId; this.callingPhoneNo = callingPhoneNo; this.calledPhoneNo = calledPhoneNo; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargeplan/BasicChargePlan.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargeplan; public class BasicChargePlan extends ChargePlan{ public BasicChargePlan(){ this.priority = 0; } @Override public BasicChargeFee getResource() { return new BasicChargeFee(); } @Override public ChargePlanType getType() { return ChargePlanType.BASIC; } public static class BasicChargeFee implements Resource{ /** * 主叫单价。单位是角,5表示0.5元每分钟 */ public final int CALLING_PRICE = 5; /** * 主叫单价。单位是角,4表示0.4元每分钟 */ public final int CALLED_PRICE = 4; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargeplan/ChargePlan.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargeplan; public abstract class ChargePlan implements Comparable{ protected int priority; public abstract T getResource(); public abstract ChargePlanType getType(); public ChargePlan(){ } /** * 不同套餐之间的优先级关系 * @param other the object to be compared. * @return */ @Override public int compareTo(ChargePlan other) { return other.priority - this.priority; } @Override public String toString() { return "ChargePlan{chargeType=" + getType()+ ", priority=" + priority + '}'; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargeplan/ChargePlanType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargeplan; public enum ChargePlanType { /** * 基础套餐 */ BASIC, /** * 固定时常套餐 */ FIXED_TIME, /** * 家庭套餐 */ FAMILY } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargeplan/FamilyChargePlan.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargeplan; import java.util.HashSet; import java.util.Set; public class FamilyChargePlan extends ChargePlan { public FamilyChargePlan() { this.priority = 2; } @Override public FamilyMember getResource() { return new FamilyMember(); } @Override public ChargePlanType getType() { return ChargePlanType.FAMILY; } public static class FamilyMember implements Resource{ private Set familyMembers = new HashSet<>(); /** * Mock here, 真实场景,情亲号码肯定也是从外系统获取的 */ public FamilyMember() { familyMembers.add(13681874561L); familyMembers.add(15921582125L); } public boolean isMember(long phoneNo) { return familyMembers.contains(phoneNo); } } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargeplan/FixedTimeChangePlan.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargeplan; public class FixedTimeChangePlan extends ChargePlan{ public FixedTimeChangePlan() { this.priority=1; } @Override public FreeCallTime getResource() { return new FreeCallTime(); } @Override public ChargePlanType getType() { return ChargePlanType.FIXED_TIME; } public static class FreeCallTime implements Resource{ public static int FREE_CALLING_TIME = 200; public static int FREE_CALLED_TIME = 200; public boolean isCallingTimeRemaining(){ return FREE_CALLING_TIME > 0; } /** * 扣减固定时长套餐的费用 * @param duration 扣减时长 * @return 剩余还需要扣减的时长 */ public int chargeFreeCallingTime(int duration){ if(duration > FREE_CALLING_TIME){ int durationToCharge = duration - FREE_CALLING_TIME; FREE_CALLING_TIME = 0; return durationToCharge; } else{ FREE_CALLING_TIME = FREE_CALLING_TIME - duration; return 0; } } public boolean isCalledTimeRemaining(){ return FREE_CALLED_TIME > 0; } /** * 扣减固定时长套餐的费用 * @param duration 扣减时长 * @return 剩余还需要扣减的时长 */ public int chargeFreeCalledTime(int duration){ if(duration > FREE_CALLED_TIME){ int durationToCharge = duration - FREE_CALLED_TIME; FREE_CALLED_TIME = 0; return durationToCharge; } else{ FREE_CALLED_TIME = FREE_CALLED_TIME - duration; return 0; } } } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargeplan/Resource.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargeplan; /** * 套餐背后所绑定的资源 */ public interface Resource { } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargerule/AbstractChargeRule.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargerule; import ${package}.domain.charge.chargeplan.ChargePlan; public abstract class AbstractChargeRule implements ChargeRule{ protected ChargePlan chargePlan; @Override public void belongsTo(ChargePlan chargePlan){ this.chargePlan = chargePlan; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargerule/BasicChargeRule.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargerule; import ${package}.domain.charge.*; import ${package}.domain.charge.chargeplan.BasicChargePlan; import ${package}.domain.charge.chargeplan.ChargePlanType; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component @Slf4j public class BasicChargeRule extends AbstractChargeRule{ @Override public ChargeRecord doCharge(ChargeContext ctx) { if(!ctx.needCharge()){ log.debug("No need charge for : "+ctx); return null; } BasicChargePlan basicChargePlan = (BasicChargePlan)chargePlan; BasicChargePlan.BasicChargeFee chargeFee = basicChargePlan.getResource(); Money cost; int duration = ctx.durationToCharge; if (ctx.callType == CallType.CALLING) { cost = Money.of(duration * chargeFee.CALLING_PRICE); } else { cost = Money.of(duration * chargeFee.CALLED_PRICE); } ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, duration, ChargePlanType.BASIC, cost); //在账号上扣减费用 ctx.account.getRemaining().minus(cost); ctx.setDurationToCharge(0); return chargeRecord; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargerule/ChargeRule.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargerule; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.ChargeContext; import ${package}.domain.charge.chargeplan.ChargePlan; public interface ChargeRule { ChargeRecord doCharge(ChargeContext ctx); void belongsTo(ChargePlan chargePlan); } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargerule/ChargeRuleFactory.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargerule; import ${package}.domain.ApplicationContextHelper; import ${package}.domain.charge.chargeplan.ChargePlan; import ${package}.domain.charge.chargeplan.ChargePlanType; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ChargeRuleFactory { public static CompositeChargeRule get(List chargePlanList) { //按套餐的优先级进行排序 Collections.sort(chargePlanList); List chargeRules = new ArrayList<>(); for (ChargePlan chargePlan : chargePlanList) { ChargeRule chargeRule; if (chargePlan.getType() == ChargePlanType.FAMILY) { chargeRule = ApplicationContextHelper.getBean(FamilyChargeRule.class); } else if (chargePlan.getType() == ChargePlanType.FIXED_TIME) { chargeRule = ApplicationContextHelper.getBean(FixedTimeChargeRule.class); } else { chargeRule = ApplicationContextHelper.getBean(BasicChargeRule.class); } chargeRule.belongsTo(chargePlan); chargeRules.add(chargeRule); } CompositeChargeRule compositeChargeRule = new CompositeChargeRule(); compositeChargeRule.chargeRules = chargeRules; return compositeChargeRule; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargerule/CompositeChargeRule.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargerule; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.ChargeContext; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 为了应对套餐组合 * 组合模式(Composite pattern) */ public class CompositeChargeRule { public List chargeRules; public List doCharge(ChargeContext chargeContext){ List chargeRecords = new ArrayList<>(); for(ChargeRule chargeRule : chargeRules){ ChargeRecord chargeRecord = chargeRule.doCharge(chargeContext); if(chargeRecord != null){ chargeRecord.setSessionId(chargeContext.getSession().getSessionId()); //fill fields for persistence needs chargeRecord.setCreateTime(new Date()); chargeRecord.setUpdateTime(new Date()); chargeRecords.add(chargeRecord); } } return chargeRecords; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargerule/FamilyChargeRule.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargerule; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.ChargeContext; import ${package}.domain.charge.Money; import ${package}.domain.charge.chargeplan.ChargePlanType; import ${package}.domain.charge.chargeplan.FamilyChargePlan; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component @Slf4j public class FamilyChargeRule extends AbstractChargeRule { @Override public ChargeRecord doCharge(ChargeContext ctx) { FamilyChargePlan familyChargePlan = (FamilyChargePlan) chargePlan; FamilyChargePlan.FamilyMember familyMember = familyChargePlan.getResource(); if (familyMember.isMember(ctx.otherSidePhoneNo)) { log.debug("Family Charge plan for Account : " + ctx.account); ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, ctx.durationToCharge, ChargePlanType.FAMILY, Money.of(0)); ctx.setDurationToCharge(0); return chargeRecord; } return null; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/charge/chargerule/FixedTimeChargeRule.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.charge.chargerule; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.ChargeContext; import ${package}.domain.charge.Money; import ${package}.domain.charge.chargeplan.ChargePlanType; import ${package}.domain.charge.chargeplan.FixedTimeChangePlan; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component @Slf4j public class FixedTimeChargeRule extends AbstractChargeRule { @Override public ChargeRecord doCharge(ChargeContext ctx) { if(!ctx.needCharge()){ log.debug("No need charge for : "+ctx); return null; } FixedTimeChangePlan fixedTimeChangePlan = (FixedTimeChangePlan) chargePlan; FixedTimeChangePlan.FreeCallTime freeCallTime = fixedTimeChangePlan.getResource(); if (ctx.isCalling() && freeCallTime.isCallingTimeRemaining()) { int leftDuration = freeCallTime.chargeFreeCallingTime(ctx.durationToCharge); log.debug("Calling Left Duration after FixedTimeCharge : " + leftDuration); ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, ctx.durationToCharge - leftDuration, ChargePlanType.FIXED_TIME, Money.of(0)); ctx.setDurationToCharge(leftDuration); return chargeRecord; } if (ctx.isCalled() && freeCallTime.isCalledTimeRemaining()) { int leftDuration = freeCallTime.chargeFreeCalledTime(ctx.durationToCharge); log.debug("Called Left Duration after FixedTimeCharge : " + leftDuration); ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, ctx.durationToCharge - leftDuration, ChargePlanType.FIXED_TIME, Money.of(0)); ctx.setDurationToCharge(leftDuration); return chargeRecord; } return null; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/gateway/AccountGateway.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.gateway; import ${package}.domain.account.Account; import ${package}.domain.charge.ChargeRecord; import java.util.List; /** * 跟账户系统交互的网关(Gateway) * * @version 1.0 */ public interface AccountGateway { /** * 根据用户号码获取账户信息(含计费项余额等信息) * * @param phoneNo 电话号码 * @return 账户信息 */ Account getAccount(long phoneNo); /** * 将扣费记录同步到账户中 * * @param phoneNo 电话号码 * @param records 扣费记录 */ void sync(long phoneNo, List records); } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/gateway/ChargeGateway.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.gateway; import ${package}.domain.charge.ChargeRecord; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface ChargeGateway extends JpaRepository { public List findBySessionId(String sessionId); public ChargeRecord getBySessionId(String sessionId); public List findByPhoneNo(long phoneNo); } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/domain/gateway/SessionGateway.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.gateway; import ${package}.domain.charge.Session; public interface SessionGateway { void create(Session session); Session get(String sessionId); void end(String sessionId); } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/infrastructure/AccountGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import ${package}.domain.account.Account; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.Money; import ${package}.domain.gateway.AccountGateway; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; import java.util.HashMap; import java.util.List; import java.util.Map; @Component @Slf4j public class AccountGatewayImpl implements AccountGateway { private static final String GET_ACCOUNT_PATH = "/v1/api/account/{account}"; private static final String SYNC_ACCOUNT_PATH = "/v1/api/account/account/{account}/sync"; private Map accountMap = new HashMap<>(); @Autowired private RestClient restClient; @Override public Account getAccount(long phoneNo) { Account account = restClient.get() .uri(GET_ACCOUNT_PATH, phoneNo) .accept(MediaType.APPLICATION_JSON) .retrieve() .body(Account.class); return account; } @Override public void sync(long phoneNo, List records) { // 更新账户系统 log.info("sync account info, to be implemented"); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/infrastructure/RestClientBean.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestClient; @Configuration public class RestClientBean { @Value("${symbol_dollar}{REMOTE_BASE_URI:http://localhost:8080}") String baseURI; @Bean RestClient restClient() { return RestClient.create(baseURI); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/java/infrastructure/SessionGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import ${package}.domain.BizException; import ${package}.domain.charge.Session; import ${package}.domain.gateway.SessionGateway; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class SessionGatewayImpl implements SessionGateway { private Map sessionMap = new HashMap<>(); @Override public void create(Session session) { sessionMap.put(session.getSessionId(), session); } @Override public Session get(String sessionId) { return sessionMap.get(sessionId); } @Override public void end(String sessionId) { //真实场景是逻辑删除,比如把session的状态标记为“已结束”。 sessionMap.remove(sessionId); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/resources/application.yml ================================================ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${MYSQL_SERVER:localhost}:${MYSQL_PORT:3306}/${MYSQL_DB_NAME:chargeDB}?serverTimezone=UTC #如果运行出错,可以把连接写成下面的路径进行测试 #url: jdbc:mysql://localhost:3306/blogDB?useUnicode=true&characterEncoding=utf-8 username: ${MYSQL_USER_TEST:root} password: ${MYSQL_PASSWORD_TEST:root} jpa: hibernate: ddl-auto: update show-sql: true server: port: 8081 my-name: default my-age: default ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/main/resources/logback.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/charge.http ================================================ ### list charge records by sessionId GET http://localhost:8080/123145/chargeRecords Accept: application/json ### end session POST http://localhost:8080/session/123145/end?duration=10 Content-Type: application/x-www-form-urlencoded duration=10 ### do charge POST http://localhost:8080/session/123145/charge?duration=10 ### begin Session POST http://localhost:8080/session/123145/begin?callingPhoneNo=13681874561&calledPhoneNo=15921252125 <> 2022-11-03T150743.200.json ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/CleanArchTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import org.junit.jupiter.api.Test; public class CleanArchTest { @Test public void protect_clean_arch() { // JavaClasses classes = new ClassFileImporter() // .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) // .importPackages("${package}"); // // layeredArchitecture() // .consideringOnlyDependenciesInLayers() // .layer("adapter").definedBy("${package}.adapter") // .layer("application").definedBy("${package}.application") // .layer("domain").definedBy("${package}.domain") // .layer("infrastructure").definedBy("${package}.infrastructure") // .whereLayer("adapter").mayNotBeAccessedByAnyLayer() // //.whereLayer("domain").mayOnlyBeAccessedByLayers("application", "infrastructure") // .as("The layer dependencies must be respected") // .because("we must follow the Clean Architecture principle") // .check(classes); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/TestsContainerBoot.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import com.alibaba.cola.test.TestsContainer; public class TestsContainerBoot { public static void main(String[] args) { TestsContainer.start(); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/application/ChargeServiceTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.application; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import ${package}.Application; import ${package}.application.dto.BeginSessionRequest; import ${package}.domain.BizException; import ${package}.domain.gateway.AccountGateway; import ${package}.domain.gateway.SessionGateway; import ${package}.infrastructure.WireMockRegister; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; @SpringBootTest @ContextConfiguration(classes = Application.class) @WireMockTest(httpPort = 8080) public class ChargeServiceTest { @Autowired private ChargeServiceI chargeService; @Autowired private SessionGateway sessionGateway; @Autowired private AccountGateway accountGateway; @Test public void test_session_create(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_account.json"); BeginSessionRequest request = new BeginSessionRequest(); String sessionId = "00002"; request.setSessionId(sessionId); request.setCallingPhoneNo(13681874563L); request.setCalledPhoneNo(15921582125L); chargeService.begin(request); Assertions.assertEquals(sessionId, sessionGateway.get(sessionId).getSessionId()); } @Test public void test_remaining_insufficient(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_insufficient_account.json"); BeginSessionRequest request = new BeginSessionRequest(); String sessionId = "00003"; request.setSessionId(sessionId); request.setCallingPhoneNo(13681874561L); request.setCalledPhoneNo(15921582125L); Exception exception = Assertions.assertThrows(BizException.class, () -> { chargeService.begin(request); }); String expectedMsg = "has insufficient amount"; String actualMsg = exception.getMessage(); Assertions.assertTrue(actualMsg.contains(expectedMsg)); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/domain/ChargeRecordPlanTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; import ${package}.domain.charge.chargeplan.BasicChargePlan; import ${package}.domain.charge.chargeplan.ChargePlan; import ${package}.domain.charge.chargeplan.ChargePlanType; import ${package}.domain.charge.chargeplan.FamilyChargePlan; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ChargeRecordPlanTest { @Test public void test_priority(){ ChargePlan basicChargePlan = new BasicChargePlan(); ChargePlan familyChargePlan = new FamilyChargePlan(); ChargePlan fixedTimeChargePlan = new FamilyChargePlan(); List chargePlanList = new ArrayList<>(); chargePlanList.add(basicChargePlan); chargePlanList.add(familyChargePlan); chargePlanList.add(fixedTimeChargePlan); Collections.sort(chargePlanList); System.out.println(chargePlanList.get(0)); Assertions.assertEquals(ChargePlanType.FAMILY, chargePlanList.get(0).getType()); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/domain/ChargeRecordRuleTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; import ${package}.domain.account.Account; import ${package}.domain.charge.CallType; import ${package}.domain.charge.ChargeContext; import ${package}.domain.charge.Money; import ${package}.domain.charge.chargeplan.BasicChargePlan; import ${package}.domain.charge.chargeplan.ChargePlan; import ${package}.domain.charge.chargeplan.FamilyChargePlan; import ${package}.domain.charge.chargeplan.FixedTimeChangePlan; import ${package}.domain.charge.chargerule.BasicChargeRule; import ${package}.domain.charge.chargerule.FamilyChargeRule; import ${package}.domain.charge.chargerule.FixedTimeChargeRule; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.Collections; public class ChargeRecordRuleTest { @Test public void test_basic_charge_rule(){ //prepare ChargePlan chargePlan = new BasicChargePlan(); Account account = new Account(13681874561L, Money.of(200), Collections.singletonList(chargePlan)); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 20); ctx.account = account; System.out.println("Account before charge: "+ account); //do BasicChargeRule basicChargeRule = new BasicChargeRule(); basicChargeRule.belongsTo(chargePlan); basicChargeRule.doCharge(ctx); //check System.out.println("Account after charge: "+ account); Assertions.assertEquals( Money.of(100), ctx.account.getRemaining()); Assertions.assertEquals( 0, ctx.getDurationToCharge()); } @Test public void test_family_charge_rule(){ //prepare FamilyChargePlan chargePlan = new FamilyChargePlan(); Account account = new Account(13681874561L, Money.of(200), Collections.singletonList(chargePlan)); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 20); ctx.account = account; System.out.println("Account before charge: "+ account); //do FamilyChargeRule familyChargeRule = new FamilyChargeRule(); familyChargeRule.belongsTo(chargePlan); familyChargeRule.doCharge(ctx); //check System.out.println("Account after charge: "+ account); Assertions.assertEquals( Money.of(200), ctx.account.getRemaining()); Assertions.assertEquals( 0, ctx.getDurationToCharge()); } @Test public void test_fixed_time_charge_rule(){ //prepare FixedTimeChangePlan chargePlan = new FixedTimeChangePlan(); Account account = new Account(13681874561L, Money.of(200), Collections.singletonList(chargePlan)); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 180); ctx.account = account; System.out.println("Account before charge: "+ account); //do FixedTimeChargeRule fixedTimeChargeRule = new FixedTimeChargeRule(); fixedTimeChargeRule.belongsTo(chargePlan); fixedTimeChargeRule.doCharge(ctx); //check System.out.println("Account after charge: "+ account); Assertions.assertEquals( true, chargePlan.getResource().isCallingTimeRemaining()); Assertions.assertEquals( 0, ctx.getDurationToCharge()); // come a new charge ChargeContext ctx2 = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 40); ctx2.account = account; fixedTimeChargeRule.doCharge(ctx2); Assertions.assertEquals( false, chargePlan.getResource().isCallingTimeRemaining()); Assertions.assertEquals( 20, ctx2.getDurationToCharge()); //reset fixed time FixedTimeChangePlan.FreeCallTime.FREE_CALLED_TIME = 200; FixedTimeChangePlan.FreeCallTime.FREE_CALLING_TIME = 200; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/domain/CompositeChargeRuleTestRecord.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; import ${package}.Application; import ${package}.domain.account.Account; import ${package}.domain.charge.*; import ${package}.domain.charge.chargeplan.BasicChargePlan; import ${package}.domain.charge.chargeplan.ChargePlan; import ${package}.domain.charge.chargeplan.FamilyChargePlan; import ${package}.domain.charge.chargeplan.FixedTimeChangePlan; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import java.util.ArrayList; import java.util.List; import java.util.UUID; @SpringBootTest @ContextConfiguration(classes = Application.class) public class CompositeChargeRuleTestRecord { private long callingPhoneNo = 13681874561L; private long calledPhoneNo = 15921582125L; @Test public void test_basic_and_fixedTime_charge_rule(){ // prepare List chargePlanList = new ArrayList<>(); chargePlanList.add(new BasicChargePlan()); chargePlanList.add(new FixedTimeChangePlan()); Account account = Account.valueOf(13681874561L, Money.of(200)); // for spring bean account.setChargePlanList(chargePlanList); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 220); String sessionId = UUID.randomUUID().toString(); Session session = new Session(sessionId, callingPhoneNo, calledPhoneNo); ctx.setSession(session); ctx.account = account; // do List chargeRecords = account.charge(ctx); System.out.println("Account after charge: "+ account); // check Assertions.assertEquals(2, chargeRecords.size()); } @Test public void test_basic_and_family_charge_rule(){ // prepare List chargePlanList = new ArrayList<>(); chargePlanList.add(new BasicChargePlan()); chargePlanList.add(new FamilyChargePlan()); Account account = Account.valueOf(13681874561L, Money.of(200)); // for spring bean account.setChargePlanList(chargePlanList); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 220); String sessionId = UUID.randomUUID().toString(); Session session = new Session(sessionId, callingPhoneNo, calledPhoneNo); ctx.setSession(session); ctx.account = account; // do List chargeRecords = account.charge(ctx); System.out.println("Account after charge: "+ account); // check Assertions.assertEquals(1, chargeRecords.size()); } @Test public void test_all_charge_rule(){ // prepare List chargePlanList = new ArrayList<>(); chargePlanList.add(new BasicChargePlan()); chargePlanList.add(new FamilyChargePlan()); chargePlanList.add(new FixedTimeChangePlan()); Account account = Account.valueOf(13681874561L, Money.of(200)); // for spring bean account.setChargePlanList(chargePlanList); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 220); String sessionId = UUID.randomUUID().toString(); Session session = new Session(sessionId, callingPhoneNo, calledPhoneNo); ctx.setSession(session); ctx.account = account; // do List chargeRecords = account.charge(ctx); System.out.println("Account after charge: "+ account); // check Assertions.assertEquals(1, chargeRecords.size()); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/infrastructure/AccountGatewayTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import ${package}.Application; import ${package}.domain.account.Account; import ${package}.domain.charge.Money; import ${package}.domain.gateway.AccountGateway; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; @SpringBootTest @ContextConfiguration(classes = Application.class) @WireMockTest(httpPort = 8080) public class AccountGatewayTest { @Autowired AccountGateway accountGateway; @Test public void testGetAccount(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_account.json"); Account account = accountGateway.getAccount(15921582125L); System.out.println("account : " + account); Assertions.assertEquals(account.getPhoneNo(), 15921582125L); Assertions.assertEquals(account.getRemaining(), Money.of(400)); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/infrastructure/ChargeRecordRepoTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import ${package}.domain.charge.CallType; import ${package}.domain.charge.ChargeRecord; import ${package}.domain.charge.Money; import ${package}.domain.charge.chargeplan.ChargePlanType; import ${package}.domain.gateway.ChargeGateway; import jakarta.annotation.Resource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; @SpringBootTest public class ChargeRecordRepoTest { @Resource private ChargeGateway chargeGateway; private String sessionId; @BeforeEach public void setup(){ sessionId = UUID.randomUUID().toString(); } @Test public void testSave(){ ChargeRecord chargeRecord = new ChargeRecord(13681874561L, CallType.CALLED, 10, ChargePlanType.FAMILY, Money.of(123)); chargeRecord.setSessionId(sessionId); chargeRecord.setCreateTime(new Date()); chargeRecord.setUpdateTime(new Date()); chargeGateway.save(chargeRecord); chargeRecord = chargeGateway.getBySessionId(sessionId); Assertions.assertEquals(chargeRecord.getSessionId(), sessionId); } @Test public void testSaveList(){ List chargeRecordList = new ArrayList<>(); ChargeRecord chargeRecord1 = new ChargeRecord(13681874561L, CallType.CALLED, 10, ChargePlanType.FAMILY, Money.of(123)); chargeRecord1.setSessionId(UUID.randomUUID().toString()); ChargeRecord chargeRecord2 = new ChargeRecord(13681874561L, CallType.CALLING, 10, ChargePlanType.FAMILY, Money.of(123)); chargeRecord2.setSessionId(UUID.randomUUID().toString()); chargeRecordList.add(chargeRecord1); chargeRecordList.add(chargeRecord2); chargeGateway.saveAll(chargeRecordList); List result = chargeGateway.findByPhoneNo(13681874561L); Assertions.assertEquals(result.size(), 2); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/infrastructure/FixtureLoader.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StreamUtils; public class FixtureLoader { public static String loadResource(String resourcePath) { // 创建一个 ClassPathResource 对象 ClassPathResource resource = new ClassPathResource(resourcePath); // 使用 withResource 来自动关闭输入流 String content = ""; try (InputStream inputStream = resource.getInputStream()) { content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return content; } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/infrastructure/JSONTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import com.fasterxml.jackson.databind.ObjectMapper; import ${package}.domain.account.Account; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class JSONTest { @Test public void testJsonBind() { // this will throw exception since account not recognized String badJson = "{${symbol_escape}"account${symbol_escape}":{${symbol_escape}"name${symbol_escape}":${symbol_escape}"frank${symbol_escape}",${symbol_escape}"phoneNo${symbol_escape}":${symbol_escape}"15921582125${symbol_escape}",${symbol_escape}"remaining${symbol_escape}":${symbol_escape}"400${symbol_escape}",${symbol_escape}"chargePlanList${symbol_escape}":[{${symbol_escape}"priority${symbol_escape}":${symbol_escape}"2${symbol_escape}",${symbol_escape}"type${symbol_escape}":${symbol_escape}"fixedTime${symbol_escape}"},{${symbol_escape}"priority${symbol_escape}":${symbol_escape}"1${symbol_escape}",${symbol_escape}"type${symbol_escape}":${symbol_escape}"familyMember${symbol_escape}"}]}}"; // this is good String goodJson = "{${symbol_escape}"name${symbol_escape}":${symbol_escape}"frank${symbol_escape}",${symbol_escape}"phoneNo${symbol_escape}":${symbol_escape}"15921582125${symbol_escape}",${symbol_escape}"remaining${symbol_escape}":${symbol_escape}"400${symbol_escape}",${symbol_escape}"chargePlanList${symbol_escape}":[{${symbol_escape}"priority${symbol_escape}":${symbol_escape}"2${symbol_escape}",${symbol_escape}"type${symbol_escape}":${symbol_escape}"fixedTime${symbol_escape}"},{${symbol_escape}"priority${symbol_escape}":${symbol_escape}"1${symbol_escape}",${symbol_escape}"type${symbol_escape}":${symbol_escape}"familyMember${symbol_escape}"}]}"; try { ObjectMapper objectMapper = new ObjectMapper(); Account account = objectMapper.readValue(goodJson, Account.class); Assertions.assertEquals(account.getPhoneNo(), 15921582125L); System.out.println(account); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/infrastructure/SpingBootConfTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @SpringBootTest //use test profile, this will merge application.yml and application-test.yaml. //but if you put an application.yml under test/resources, it will replace the project application.yml. //the advantage of profile, is that it can inherit and override. @ActiveProfiles("test") public class SpingBootConfTest { @Value("${symbol_dollar}{spring.jpa.show-sql}") private String showSql; @Value("${symbol_dollar}{spring.jpa.hibernate.ddl-auto}") private String ddlAuto; @Value("${symbol_dollar}{my-name}") private String myName; @Value("${symbol_dollar}{my-age}") private String myAge; @Value("${symbol_dollar}{my-age-test}") private String myAgeTest; @Test public void test() { System.out.println("spring.jpa.show-sql : " + showSql); System.out.println("spring.jpa.hibernate.ddl-auto : " + ddlAuto); System.out.println("myName : " + myName); System.out.println("myAge : " + myAge); System.out.println("myAgeTest : " + myAgeTest); Assertions.assertEquals("30", myAge); Assertions.assertEquals("40", myAgeTest); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/infrastructure/WireMockBasicTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import ${package}.Application; import ${package}.domain.account.Account; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import static com.github.tomakehurst.wiremock.client.WireMock.*; @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @WireMockTest(httpPort = 8080) @Slf4j public class WireMockBasicTest { @Autowired protected WebTestClient webClient; @Test public void testWireMockBasic() { // The static DSL will be automatically configured for you stubFor(get("/static-dsl").willReturn(ok())); webClient.get() .uri("http://localhost:8080/static-dsl") .exchange() .expectStatus() .isEqualTo(200); } @Test public void testWireMockStub(WireMockRuntimeInfo wmRuntimeInfo) { WireMock wireMock = wmRuntimeInfo.getWireMock(); WireMockRegister.registerStub(wireMock, "/fixture/wiremock/stub_wire_mock_basic.json"); webClient.get() .uri("http://localhost:8080/v1/wiremock/basic") .exchange() .expectStatus() .isEqualTo(200) .expectHeader() .contentType(MediaType.APPLICATION_JSON); System.out.println("wire mock serer port : " + wmRuntimeInfo.getHttpPort()); } @Test public void testWireMockAccount(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_account.json"); long phoneNo = 123456789; webClient.get() .uri("http://localhost:8080/v1/api/account/"+phoneNo) .exchange() .expectStatus() .isEqualTo(200) .expectHeader() .contentType(MediaType.APPLICATION_JSON) .returnResult(Account.class) .getResponseBody() .map(account -> { log.info(account.toString()); Assertions.assertEquals("frank", account.getName()); Assertions.assertEquals(phoneNo, account.getPhoneNo()); return account; }) .subscribe(); log.info("wire mock serer port : " + wmRuntimeInfo.getHttpPort()); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/java/infrastructure/WireMockRegister.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.infrastructure; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.stubbing.StubMapping; public class WireMockRegister { public static void registerStub(WireMock wireMock, String resourcePath){ StubMapping stubMapping = StubMapping.buildFrom(FixtureLoader.loadResource(resourcePath)); wireMock.register(stubMapping); } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/resources/application-test.yml ================================================ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # hard code for test purpose url: jdbc:mysql://localhost:3306/chargeDB?serverTimezone=UTC username: root password: root jpa: hibernate: ddl-auto: update show-sql: true # this will override config in test/resources/application.yml and resources/application.yml my-age: 30 my-age-test: 40 ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/resources/application.yml ================================================ spring: datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=1 username: sa password: jpa: hibernate: ddl-auto: update show-sql: true server: port: 8081 my-name: frank my-age: 35 REMOTE_BASE_URI: http://localhost:8080 ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/resources/fixture/wiremock/stub_account.json ================================================ { "request": { "urlPathPattern": "/v1/api/account/[0-9]+", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "transformers": [ "response-template" ], "jsonBody": { "name": "frank", "phoneNo": "{{request.path.[3]}}", "remaining": "400", "chargePlanList": [ { "priority": "2", "type": "fixedTime" }, { "priority": "1", "type": "familyMember" } ] } } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/resources/fixture/wiremock/stub_insufficient_account.json ================================================ { "request": { "urlPathPattern": "/v1/api/account/[0-9]+", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "transformers": [ "response-template" ], "jsonBody": { "name": "frank", "phoneNo": "{{request.path.[3]}}", "remaining": "0", "chargePlanList": [ { "priority": "2", "type": "fixedTime" }, { "priority": "1", "type": "familyMember" } ] } } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/resources/fixture/wiremock/stub_wire_mock_basic.json ================================================ { "request": { "urlPathPattern": "/v1/wiremock/basic", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": { "request_id": "f7f9e747-f073-4ea8-8360-b42fc754a049", "hyper_switch": { "id": "switch1", "name": "1520-001", "device_model": "1520", "role": "tor", "mgmt_ip": "192.168.0.1", "rack_code": "kw14b2-1k-01-08", "sn": "21980119523GP8000745", "node_id": "node1", "xpod_id": "pod1", "op_status": "online", "created_at": "2024-02-04 15:11:13", "updated_at": "2020-02-04 15:11:13", "type": "l1" } } } } ================================================ FILE: cola-archetypes/cola-archetype-light/src/main/resources/archetype-resources/src/test/resources/logback-test.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) %date{HH:mm:ss} %highlight(%-5level) [%blue(%t)] %yellow(%C{35}): %msg%n%throwable utf8 ================================================ FILE: cola-archetypes/cola-archetype-light/src/test/resources/projects/basic/archetype.properties ================================================ #Sun May 12 20:30:31 CST 2024 package=it.pkg groupId=archetype.it artifactId=basic version=0.1-SNAPSHOT ================================================ FILE: cola-archetypes/cola-archetype-light/src/test/resources/projects/basic/goal.txt ================================================ ================================================ FILE: cola-archetypes/cola-archetype-service/.gitignore ================================================ target/ ### eclipse ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-archetypes/cola-archetype-service/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-framework-archetypes-parent 5.x-SNAPSHOT cola-framework-archetype-service maven-archetype ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/META-INF/maven/archetype-metadata.xml ================================================ .gitignore __gitignore__ src/main/java **/*.java src/main/java **/*.java src/test/java **/*.java src/main/java **/*.java src/test/java **/*.java src/main/java **/*.java src/main/resources **/*.xml src/test/java **/*.java src/test/resources **/*.properties src/main/java **/*.java src/main/resources **/*.xml **/*.properties src/test/java **/*.java src/test/resources **/*.xml **/*.properties ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__gitignore__ ================================================ target/ ### eclipse ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-app/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-client ${groupId} ${rootArtifactId}-infrastructure com.alibaba.cola cola-component-catchlog-starter org.hibernate.validator hibernate-validator javax.el javax.el-api org.glassfish jakarta.el ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/customer/CustomerServiceImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import com.alibaba.cola.catchlog.CatchAndLog; import ${package}.api.CustomerServiceI; import ${package}.dto.CustomerAddCmd; import ${package}.dto.CustomerListByNameQry; import ${package}.dto.data.CustomerDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ${package}.customer.executor.CustomerAddCmdExe; import ${package}.customer.executor.query.CustomerListByNameQryExe; import javax.annotation.Resource; @Service @CatchAndLog public class CustomerServiceImpl implements CustomerServiceI { @Resource private CustomerAddCmdExe customerAddCmdExe; @Resource private CustomerListByNameQryExe customerListByNameQryExe; public Response addCustomer(CustomerAddCmd customerAddCmd) { return customerAddCmdExe.execute(customerAddCmd); } @Override public MultiResponse listByName(CustomerListByNameQry customerListByNameQry) { return customerListByNameQryExe.execute(customerListByNameQry); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/customer/executor/CustomerAddCmdExe.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer.executor; import com.alibaba.cola.dto.Response; import com.alibaba.cola.exception.BizException; import ${package}.dto.CustomerAddCmd; import ${package}.dto.data.ErrorCode; import org.springframework.stereotype.Component; @Component public class CustomerAddCmdExe{ public Response execute(CustomerAddCmd cmd) { //The flow of usecase is defined here. //The core ablility should be implemented in Domain. or sink to Domian gradually if(cmd.getCustomerDTO().getCompanyName().equals("ConflictCompanyName")){ throw new BizException(ErrorCode.B_CUSTOMER_companyNameConflict, "公司名冲突"); } return Response.buildSuccess(); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/customer/executor/query/CustomerListByNameQryExe.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer.executor.query; import com.alibaba.cola.dto.MultiResponse; import ${package}.dto.CustomerListByNameQry; import ${package}.dto.data.CustomerDTO; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Component; @Component public class CustomerListByNameQryExe{ public MultiResponse execute(CustomerListByNameQry cmd) { List customerDTOList = new ArrayList<>(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCustomerName("Frank"); customerDTOList.add(customerDTO); return MultiResponse.of(customerDTOList); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/order/OrderServiceImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.order; //package by domain, not by duty public class OrderServiceImpl{ } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-app/src/test/java/app/CustomerConvertorTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.app; public class CustomerConvertorTest { } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-app/src/test/java/app/CustomerValidatorTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.app; import org.junit.Test; public class CustomerValidatorTest { @Test public void testValidation(){ } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${parentArtifactId}-client com.alibaba.cola cola-component-dto javax.validation validation-api ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/api/CustomerServiceI.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.api; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import ${package}.dto.CustomerAddCmd; import ${package}.dto.CustomerListByNameQry; import ${package}.dto.data.CustomerDTO; public interface CustomerServiceI { Response addCustomer(CustomerAddCmd customerAddCmd); MultiResponse listByName(CustomerListByNameQry customerListByNameQry); } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/CustomerAddCmd.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto; import ${package}.dto.data.CustomerDTO; import lombok.Data; @Data public class CustomerAddCmd{ private CustomerDTO customerDTO; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/CustomerListByNameQry.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto; import com.alibaba.cola.dto.Query; import lombok.Data; @Data public class CustomerListByNameQry extends Query{ private String name; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/data/CustomerDTO.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.data; import lombok.Data; import javax.validation.constraints.NotEmpty; @Data public class CustomerDTO{ private String customerId; private String memberId; private String customerName; private String customerType; @NotEmpty private String companyName; @NotEmpty private String source; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/data/ErrorCode.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.data; public class ErrorCode { public static final String B_CUSTOMER_companyNameConflict = "B_CUSTOMER_companyNameConflict"; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/event/CustomerCreatedEvent.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.event; import static ${package}.dto.event.DomainEventConstant.CUSTOMER_CREATED_TOPIC; /** * CustomerCreatedEvent * * @author Frank Zhang * @date 2019-01-04 10:32 AM */ public class CustomerCreatedEvent{ private String customerId; public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/event/DomainEventConstant.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.event; /** * @author niexiaolong * @date 2019/4/16 */ public class DomainEventConstant { public static final String CUSTOMER_CREATED_TOPIC = "CRM_CUSTOMER_CREATED_DOMAIN_EVENT_TOPIC"; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-client com.alibaba.cola cola-component-domain-starter com.alibaba.cola cola-component-exception ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/CompanyType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; /** * CompanyType * * @author Frank Zhang * @date 2018-01-08 11:02 AM */ public enum CompanyType { POTENTIAL, INTENTIONAL, IMPORTANT, VIP; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/Credit.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; import com.alibaba.cola.domain.Entity; import lombok.Data; @Data @Entity public class Credit{ } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/Customer.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; import com.alibaba.cola.domain.Entity; import com.alibaba.cola.exception.BizException; import lombok.Data; //Domain Entity can choose to extends the domain model which is used for DTO @Data @Entity public class Customer{ private String customerId; private String memberId; private String globalId; private long registeredCapital; private String companyName; private SourceType sourceType; private CompanyType companyType; public Customer() { } public boolean isBigCompany() { return registeredCapital > 10000000; //注册资金大于1000万的是大企业 } public boolean isSME() { return registeredCapital > 10000 && registeredCapital < 1000000; //注册资金大于10万小于100万的为中小企业 } public void checkConflict(){ //Per different biz, the check policy could be different, if so, use ExtensionPoint if("ConflictCompanyName".equals(this.companyName)){ throw new BizException(this.companyName+" has already existed, you can not add it"); } } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/CustomerType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; /** * CustomerType * * @author Frank Zhang * @date 2018-01-08 8:51 AM */ public enum CustomerType { POTENTIAL, INTENTIONAL, IMPORTANT, VIP; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/SourceType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; /** * SourceType * * @author Frank Zhang * @date 2018-01-08 11:09 AM */ public enum SourceType { BIZ_ONE, //From biz one BIZ_TWO; //From biz two } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/domainservice/CreditChecker.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer.domainservice; //The domain's ablility can also be placed here public class CreditChecker{ } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/gateway/CreditGateway.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer.gateway; import ${package}.domain.customer.Customer; import ${package}.domain.customer.Credit; //Assume that the credit info is in antoher distributed Service public interface CreditGateway { Credit getCredit(String customerId); } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/gateway/CustomerGateway.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer.gateway; import ${package}.domain.customer.Customer; public interface CustomerGateway { Customer getByById(String customerId); } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/order/Order.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.order; public class Order{ } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/package-info.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) /** * This is domain module, the core business logic is implemented here. * * @author fulan.zjf */ package ${package}.domain; ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/test/java/domain/CustomerEntityTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; public class CustomerEntityTest { public void testCustomerConflict() { System.out.println("Please mock gatewayimpl, test pure Domain Knowledge"); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-domain org.mybatis.spring.boot mybatis-spring-boot-starter mysql mysql-connector-java com.alibaba fastjson org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/config/DiamondConfig.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.config; public class DiamondConfig { public final static String DummyConfig = "DummyConfig"; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CreditGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import ${package}.domain.customer.Credit; import ${package}.domain.customer.gateway.CreditGateway; public class CreditGatewayImpl implements CreditGateway { public Credit getCredit(String customerId){ return null; } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CustomerDO.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import lombok.Data; @Data public class CustomerDO{ private String customerId; private String memberId; private String globalId; private long registeredCapital; private String companyName; } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CustomerGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import ${package}.domain.customer.Customer; import ${package}.domain.customer.gateway.CustomerGateway; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CustomerGatewayImpl implements CustomerGateway { @Autowired private CustomerMapper customerMapper; public Customer getByById(String customerId){ CustomerDO customerDO = customerMapper.getById(customerId); //Convert to Customer return null; } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CustomerMapper.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import org.apache.ibatis.annotations.Mapper; @Mapper public interface CustomerMapper{ CustomerDO getById(String customerId); } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/order/OrderGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.order; public class OrderGatewayImpl{ } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/resources/logback-spring.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ${symbol_dollar}{LOG_FILE} ${symbol_dollar}{FILE_LOG_PATTERN} ${symbol_dollar}{LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB ${symbol_dollar}{CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/resources/mybatis/customer-mapper.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/resources/mybatis/mybatis-config.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/test/java/repository/CustomerMapperTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.repository; public class CustomerMapperTest { public void testFindByID() { System.out.println("Write your test here"); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/test/resources/sample.properties ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/pom.xml ================================================ 4.0.0 ${groupId} ${artifactId}-parent ${version} pom ${artifactId} 1.8 ${maven.compiler.source} UTF-8 UTF-8 true 5.x-SNAPSHOT 2.7.2 2.2.2 ${rootArtifactId}-client ${rootArtifactId}-app ${rootArtifactId}-domain ${rootArtifactId}-infrastructure start org.projectlombok lombok provided junit junit test ${groupId} ${rootArtifactId}-client ${project.version} ${groupId} ${rootArtifactId}-app ${project.version} ${groupId} ${rootArtifactId}-domain ${project.version} ${groupId} ${rootArtifactId}-infrastructure ${project.version} com.alibaba.cola cola-components-bom ${cola.components.version} pom import org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis-starter.version} javax.el javax.el-api 3.0.0 com.alibaba fastjson 1.2.83 maven-resources-plugin 3.3.0 maven-compiler-plugin 3.10.1 maven-source-plugin 3.3.1 maven-javadoc-plugin 3.4.0 maven-deploy-plugin 3.0.0 org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-app org.mybatis.spring.boot mybatis-spring-boot-starter org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/src/main/java/Application.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Spring Boot Starter * * * @author Frank Zhang */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/src/main/resources/application.properties ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) project.name=${artifactId} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/src/main/resources/logback-spring.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ${symbol_dollar}{LOG_FILE} ${symbol_dollar}{FILE_LOG_PATTERN} ${symbol_dollar}{LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB ${symbol_dollar}{CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/src/test/java/TestApplication.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; public class TestApplication { public static void main(String[] args) { //这里填的是TestApplication ApplicationContext context = SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/src/test/java/test/CustomerServiceTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.test; import com.alibaba.cola.dto.Response; import ${package}.api.CustomerServiceI; import ${package}.dto.CustomerAddCmd; import ${package}.dto.data.CustomerDTO; import ${package}.dto.data.ErrorCode; import ${package}.Application; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * This is for integration test. * * Created by fulan.zjf on 2017/11/29. */ @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class CustomerServiceTest { @Autowired private CustomerServiceI customerService; @Before public void setUp() { } @Test public void testCustomerAddSuccess(){ //1.prepare CustomerAddCmd customerAddCmd = new CustomerAddCmd(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCompanyName("NormalName"); customerAddCmd.setCustomerDTO(customerDTO); //2.execute Response response = customerService.addCustomer(customerAddCmd); //3.assert Assert.assertTrue(response.isSuccess()); } @Test public void testCustomerAddCompanyNameConflict(){ //1.prepare CustomerAddCmd customerAddCmd = new CustomerAddCmd(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCompanyName("ConflictCompanyName"); customerAddCmd.setCustomerDTO(customerDTO); //2.execute Response response = customerService.addCustomer(customerAddCmd); //3.Exception Assert.assertEquals(ErrorCode.B_CUSTOMER_companyNameConflict, response.getErrCode()); } } ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/src/test/resources/logback-test.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/start/src/test/resources/test.properties ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ${symbol_pound}tddl spring.tddl.app=TDDL6_APP spring.tddl.sharding=false mybatis.config-location=classpath:mybatis/mybatis-config.xml ${symbol_pound}app spring.hsf.group=HSF spring.hsf.version=1.0.0.DAILY spring.hsf.timeout=2000 ${symbol_pound}tair spring.tair.config-id=mdbcomm-daily spring.tair.dynamic-config=true ${symbol_pound}notify spring.notify.subscriber.group-id=S-pb-test-subscriber1 spring.notify.subscriber.messageListener=messageListener spring.notify.subscriber.binding.key=type spring.notify.subscriber.binding.topic=pandora-boot-test-topic spring.notify.publisher.group-id=P-pb-test spring.notify.publisher.topics=pandora-boot-test-topic2, pandora-boot-test-topic ================================================ FILE: cola-archetypes/cola-archetype-service/src/test/resources/projects/basic/archetype.properties ================================================ #Tue Apr 16 18:52:28 CST 2019 package=it.pkg version=0.1-SNAPSHOT groupId=archetype.it artifactId=basic gitignore=.gitignore ================================================ FILE: cola-archetypes/cola-archetype-service/src/test/resources/projects/basic/goal.txt ================================================ ================================================ FILE: cola-archetypes/cola-archetype-web/.gitignore ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-archetypes/cola-archetype-web/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-framework-archetypes-parent 5.x-SNAPSHOT cola-framework-archetype-web maven-archetype ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/META-INF/maven/archetype-metadata.xml ================================================ .gitignore __gitignore__ src/main/java **/*.java src/main/java **/*.java src/main/java **/*.java src/test/java **/*.java src/main/java **/*.java src/test/java **/*.java src/main/java **/*.java src/main/resources **/*.xml src/test/java **/*.java src/test/resources **/*.properties src/main/java **/*.java src/main/resources **/*.xml **/*.properties src/test/java **/*.java src/test/resources **/*.xml **/*.properties ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__gitignore__ ================================================ target/ ### eclipse ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-adapter/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-app org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-adapter/src/main/java/mobile/CustomerMobileAdaptor.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.mobile; /** * Customer Mobile Adaptor * * * @author Frank Zhang * @date 2020-10-27 8:04 PM */ public class CustomerMobileAdaptor { } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-adapter/src/main/java/wap/CustomerWapAdaptor.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.wap; /** * Customer Wap Adaptor * * WAP : Wireless Application Protocol) * * @author Frank Zhang * @date 2020-10-27 8:03 PM */ public class CustomerWapAdaptor { } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-adapter/src/main/java/web/CustomerController.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.web; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import ${package}.api.CustomerServiceI; import ${package}.dto.CustomerAddCmd; import ${package}.dto.CustomerListByNameQry; import ${package}.dto.data.CustomerDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController public class CustomerController { @Autowired private CustomerServiceI customerService; @GetMapping(value = "/helloworld") public String helloWorld(){ return "Hello, welcome to COLA world!"; } @GetMapping(value = "/customer") public MultiResponse listCustomerByName(@RequestParam(required = false) String name){ CustomerListByNameQry customerListByNameQry = new CustomerListByNameQry(); customerListByNameQry.setName(name); return customerService.listByName(customerListByNameQry); } @PostMapping(value = "/customer") public Response addCustomer(@RequestBody CustomerAddCmd customerAddCmd){ return customerService.addCustomer(customerAddCmd); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-app/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-client ${groupId} ${rootArtifactId}-infrastructure com.alibaba.cola cola-component-catchlog-starter org.hibernate.validator hibernate-validator javax.el javax.el-api org.glassfish jakarta.el ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/customer/CustomerServiceImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import com.alibaba.cola.catchlog.CatchAndLog; import ${package}.api.CustomerServiceI; import ${package}.dto.CustomerAddCmd; import ${package}.dto.CustomerListByNameQry; import ${package}.dto.data.CustomerDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ${package}.customer.executor.CustomerAddCmdExe; import ${package}.customer.executor.query.CustomerListByNameQryExe; import javax.annotation.Resource; @Service @CatchAndLog public class CustomerServiceImpl implements CustomerServiceI { @Resource private CustomerAddCmdExe customerAddCmdExe; @Resource private CustomerListByNameQryExe customerListByNameQryExe; public Response addCustomer(CustomerAddCmd customerAddCmd) { return customerAddCmdExe.execute(customerAddCmd); } @Override public MultiResponse listByName(CustomerListByNameQry customerListByNameQry) { return customerListByNameQryExe.execute(customerListByNameQry); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/customer/executor/CustomerAddCmdExe.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer.executor; import com.alibaba.cola.dto.Response; import com.alibaba.cola.exception.BizException; import ${package}.dto.CustomerAddCmd; import ${package}.dto.data.ErrorCode; import org.springframework.stereotype.Component; @Component public class CustomerAddCmdExe{ public Response execute(CustomerAddCmd cmd) { //The flow of usecase is defined here. //The core ablility should be implemented in Domain. or sink to Domian gradually if(cmd.getCustomerDTO().getCompanyName().equals("ConflictCompanyName")){ throw new BizException(ErrorCode.B_CUSTOMER_companyNameConflict.getErrCode(), "公司名冲突"); } return Response.buildSuccess(); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/customer/executor/query/CustomerListByNameQryExe.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer.executor.query; import com.alibaba.cola.dto.MultiResponse; import ${package}.dto.CustomerListByNameQry; import ${package}.dto.data.CustomerDTO; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Component; @Component public class CustomerListByNameQryExe{ public MultiResponse execute(CustomerListByNameQry cmd) { List customerDTOList = new ArrayList<>(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCustomerName("Frank"); customerDTOList.add(customerDTO); return MultiResponse.of(customerDTOList); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-app/src/main/java/order/OrderServiceImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.order; //package by domain, not by duty public class OrderServiceImpl{ } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-app/src/test/java/app/CustomerConvertorTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.app; public class CustomerConvertorTest { } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-app/src/test/java/app/CustomerValidatorTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.app; import org.junit.Test; public class CustomerValidatorTest { @Test public void testValidation(){ } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${parentArtifactId}-client com.alibaba.cola cola-component-dto javax.validation validation-api ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/api/CustomerServiceI.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.api; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import ${package}.dto.CustomerAddCmd; import ${package}.dto.CustomerListByNameQry; import ${package}.dto.data.CustomerDTO; public interface CustomerServiceI { Response addCustomer(CustomerAddCmd customerAddCmd); MultiResponse listByName(CustomerListByNameQry customerListByNameQry); } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/CustomerAddCmd.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto; import ${package}.dto.data.CustomerDTO; import lombok.Data; @Data public class CustomerAddCmd{ private CustomerDTO customerDTO; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/CustomerListByNameQry.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto; import com.alibaba.cola.dto.Query; import lombok.Data; @Data public class CustomerListByNameQry extends Query{ private String name; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/data/CustomerDTO.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.data; import lombok.Data; import javax.validation.constraints.NotEmpty; @Data public class CustomerDTO{ private String customerId; private String memberId; private String customerName; private String customerType; @NotEmpty private String companyName; @NotEmpty private String source; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/data/ErrorCode.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.data; public enum ErrorCode{ B_CUSTOMER_companyNameConflict("B_CUSTOMER_companyNameConflict", "客户公司名冲突"); private final String errCode; private final String errDesc; private ErrorCode(String errCode, String errDesc) { this.errCode = errCode; this.errDesc = errDesc; } public String getErrCode() { return errCode; } public String getErrDesc() { return errDesc; } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/event/CustomerCreatedEvent.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.event; import static ${package}.dto.event.DomainEventConstant.CUSTOMER_CREATED_TOPIC; /** * CustomerCreatedEvent * * @author Frank Zhang * @date 2019-01-04 10:32 AM */ public class CustomerCreatedEvent{ private String customerId; public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-client/src/main/java/dto/event/DomainEventConstant.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.dto.event; /** * @author niexiaolong * @date 2019/4/16 */ public class DomainEventConstant { public static final String CUSTOMER_CREATED_TOPIC = "CRM_CUSTOMER_CREATED_DOMAIN_EVENT_TOPIC"; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-client com.alibaba.cola cola-component-domain-starter com.alibaba.cola cola-component-exception ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/CompanyType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; /** * CompanyType * * @author Frank Zhang * @date 2018-01-08 11:02 AM */ public enum CompanyType { POTENTIAL, INTENTIONAL, IMPORTANT, VIP; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/Credit.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; import com.alibaba.cola.domain.Entity; import lombok.Data; @Data @Entity public class Credit{ } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/Customer.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; import com.alibaba.cola.domain.Entity; import com.alibaba.cola.exception.BizException; import lombok.Data; //Domain Entity can choose to extend the domain model which is used for DTO @Data @Entity public class Customer{ private String customerId; private String memberId; private String globalId; private long registeredCapital; private String companyName; private SourceType sourceType; private CompanyType companyType; public Customer() { } public boolean isBigCompany() { return registeredCapital > 10000000; //注册资金大于1000万的是大企业 } public boolean isSME() { return registeredCapital > 10000 && registeredCapital < 1000000; //注册资金大于10万小于100万的为中小企业 } public void checkConflict(){ //Per different biz, the check policy could be different, if so, use ExtensionPoint if("ConflictCompanyName".equals(this.companyName)){ throw new BizException(this.companyName+" has already existed, you can not add it"); } } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/CustomerType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; /** * CustomerType * * @author Frank Zhang * @date 2018-01-08 8:51 AM */ public enum CustomerType { POTENTIAL, INTENTIONAL, IMPORTANT, VIP; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/SourceType.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer; /** * SourceType * * @author Frank Zhang * @date 2018-01-08 11:09 AM */ public enum SourceType { BIZ_ONE, //From biz one BIZ_TWO; //From biz two } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/domainservice/CreditChecker.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer.domainservice; //The domain's ability can also be placed here public class CreditChecker{ } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/gateway/CreditGateway.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer.gateway; import ${package}.domain.customer.Credit; //Assume that the credit info is in another distributed Service public interface CreditGateway { Credit getCredit(String customerId); } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/customer/gateway/CustomerGateway.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.customer.gateway; import ${package}.domain.customer.Customer; public interface CustomerGateway { Customer getByById(String customerId); } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/order/Order.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain.order; public class Order{ } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/main/java/domain/package-info.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) /** * This is domain module, the core business logic is implemented here. * * @author fulan.zjf */ package ${package}.domain; ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-domain/src/test/java/domain/CustomerEntityTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.domain; public class CustomerEntityTest { public void testCustomerConflict() { System.out.println("Please mock gatewayimpl, test pure Domain Knowledge"); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-domain org.mybatis.spring.boot mybatis-spring-boot-starter mysql mysql-connector-java com.alibaba fastjson org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/config/DiamondConfig.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.config; public class DiamondConfig { public final static String DummyConfig = "DummyConfig"; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CreditGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import ${package}.domain.customer.Credit; import ${package}.domain.customer.gateway.CreditGateway; public class CreditGatewayImpl implements CreditGateway { public Credit getCredit(String customerId){ return null; } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CustomerDO.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import lombok.Data; @Data public class CustomerDO{ private String customerId; private String memberId; private String globalId; private long registeredCapital; private String companyName; } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CustomerGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import ${package}.domain.customer.Customer; import ${package}.domain.customer.gateway.CustomerGateway; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CustomerGatewayImpl implements CustomerGateway { @Autowired private CustomerMapper customerMapper; public Customer getByById(String customerId){ CustomerDO customerDO = customerMapper.getById(customerId); //Convert to Customer return null; } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/customer/CustomerMapper.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.customer; import org.apache.ibatis.annotations.Mapper; @Mapper public interface CustomerMapper{ CustomerDO getById(String customerId); } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/java/order/OrderGatewayImpl.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.order; public class OrderGatewayImpl{ } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/resources/logback-spring.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ${symbol_dollar}{LOG_FILE} ${symbol_dollar}{FILE_LOG_PATTERN} ${symbol_dollar}{LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB ${symbol_dollar}{CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/resources/mybatis/customer-mapper.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/main/resources/mybatis/mybatis-config.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/test/java/repository/CustomerMapperTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.repository; public class CustomerMapperTest { public void testFindByID() { System.out.println("Write your test here"); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/__rootArtifactId__-infrastructure/src/test/resources/sample.properties ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/pom.xml ================================================ 4.0.0 ${groupId} ${artifactId}-parent ${version} pom ${artifactId} 1.8 ${maven.compiler.source} UTF-8 UTF-8 true 5.x-SNAPSHOT 2.7.2 2.2.2 ${rootArtifactId}-client ${rootArtifactId}-adapter ${rootArtifactId}-app ${rootArtifactId}-domain ${rootArtifactId}-infrastructure start org.projectlombok lombok provided junit junit test ${groupId} ${rootArtifactId}-adapter ${project.version} ${groupId} ${rootArtifactId}-client ${project.version} ${groupId} ${rootArtifactId}-app ${project.version} ${groupId} ${rootArtifactId}-domain ${project.version} ${groupId} ${rootArtifactId}-infrastructure ${project.version} com.alibaba.cola cola-components-bom ${cola.components.version} pom import org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis-starter.version} javax.el javax.el-api 3.0.0 com.alibaba fastjson 1.2.83 maven-resources-plugin 3.3.0 maven-compiler-plugin 3.10.1 maven-source-plugin 3.3.1 maven-javadoc-plugin 3.4.0 maven-deploy-plugin 3.0.0 org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/pom.xml ================================================ 4.0.0 ${groupId} ${rootArtifactId}-parent ${version} ../pom.xml ${artifactId} jar ${artifactId} ${groupId} ${rootArtifactId}-adapter org.mybatis.spring.boot mybatis-spring-boot-starter org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/src/main/java/Application.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Spring Boot Starter * * @author Frank Zhang */ @SpringBootApplication(scanBasePackages = {"${package}", "com.alibaba.cola"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/src/main/resources/application.properties ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) project.name=${artifactId} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/src/main/resources/logback-spring.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ${symbol_dollar}{LOG_FILE} ${symbol_dollar}{FILE_LOG_PATTERN} ${symbol_dollar}{LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB ${symbol_dollar}{CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/src/test/java/TestApplication.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; public class TestApplication { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/src/test/java/test/CustomerServiceTest.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.test; import com.alibaba.cola.dto.Response; import ${package}.api.CustomerServiceI; import ${package}.dto.CustomerAddCmd; import ${package}.dto.data.CustomerDTO; import ${package}.dto.data.ErrorCode; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * This is for integration test. * * Created by fulan.zjf on 2017/11/29. */ @RunWith(SpringRunner.class) @SpringBootTest public class CustomerServiceTest { @Autowired private CustomerServiceI customerService; @Before public void setUp() { } @Test public void testCustomerAddSuccess(){ //1.prepare CustomerAddCmd customerAddCmd = new CustomerAddCmd(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCompanyName("NormalName"); customerAddCmd.setCustomerDTO(customerDTO); //2.execute Response response = customerService.addCustomer(customerAddCmd); //3.assert Assert.assertTrue(response.isSuccess()); } @Test public void testCustomerAddCompanyNameConflict(){ //1.prepare CustomerAddCmd customerAddCmd = new CustomerAddCmd(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCompanyName("ConflictCompanyName"); customerAddCmd.setCustomerDTO(customerDTO); //2.execute Response response = customerService.addCustomer(customerAddCmd); //3.assert error Assert.assertEquals(ErrorCode.B_CUSTOMER_companyNameConflict.getErrCode(), response.getErrCode()); } } ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/src/test/resources/logback-test.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ${symbol_dollar}{LOG_FILE} ${symbol_dollar}{FILE_LOG_PATTERN} ${symbol_dollar}{LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB ${symbol_dollar}{CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/start/src/test/resources/test.properties ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ${symbol_pound}tddl spring.tddl.app=TDDL6_APP spring.tddl.sharding=false mybatis.config-location=classpath:mybatis/mybatis-config.xml ${symbol_pound}app spring.hsf.group=HSF spring.hsf.version=1.0.0.DAILY spring.hsf.timeout=2000 ${symbol_pound}tair spring.tair.config-id=mdbcomm-daily spring.tair.dynamic-config=true ${symbol_pound}notify spring.notify.subscriber.group-id=S-pb-test-subscriber1 spring.notify.subscriber.messageListener=messageListener spring.notify.subscriber.binding.key=type spring.notify.subscriber.binding.topic=pandora-boot-test-topic spring.notify.publisher.group-id=P-pb-test spring.notify.publisher.topics=pandora-boot-test-topic2, pandora-boot-test-topic ================================================ FILE: cola-archetypes/cola-archetype-web/src/test/resources/projects/basic/archetype.properties ================================================ #Tue Apr 16 18:52:28 CST 2019 package=it.pkg version=0.1-SNAPSHOT groupId=archetype.it artifactId=basic gitignore=.gitignore ================================================ FILE: cola-archetypes/cola-archetype-web/src/test/resources/projects/basic/goal.txt ================================================ ================================================ FILE: cola-archetypes/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-framework-archetypes-parent 5.x-SNAPSHOT pom ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee cola-archetype-light cola-archetype-service cola-archetype-web 1.8 ${maven.compiler.source} UTF-8 org.apache.maven.archetype archetype-packaging 3.2.1 maven-archetype-plugin 3.2.1 maven-resources-plugin 3.3.1 maven-compiler-plugin 3.13.0 maven-source-plugin 3.3.1 maven-javadoc-plugin 3.7.0 maven-gpg-plugin 3.1.0 maven-jar-plugin 3.3.0 maven-surefire-plugin 3.2.5 maven-deploy-plugin 3.1.1 org.sonatype.plugins nexus-staging-maven-plugin 1.6.13 org.jacoco jacoco-maven-plugin 0.8.12 pl.project13.maven git-commit-id-plugin 4.9.10 ossrh https://oss.sonatype.org/content/repositories/snapshots gen-java-src performRelease true maven-source-plugin gen-java-doc performRelease true maven-javadoc-plugin attach-javadoc jar 8 protected UTF-8 UTF-8 UTF-8 -quiet -J-Duser.language=en -J-Duser.country=US -Xdoclint:none gen-sign performRelease true maven-gpg-plugin sign-artifacts verify sign gen-git-properties performRelease true pl.project13.maven git-commit-id-plugin get-the-git-infos revision validate-the-git-infos validateRevision validating git dirty ${git.dirty} false true ${project.build.outputDirectory}/META-INF/scm/${project.groupId}/${project.artifactId}/git.properties force-jdk11-when-release performRelease true maven-enforcer-plugin enforce-jdk-versions enforce 11 deploy-settings performRelease true org.sonatype.plugins nexus-staging-maven-plugin true ossrh https://oss.sonatype.org/ true ================================================ FILE: cola-components/cola-component-catchlog-starter/.gitignore ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/cola-component-catchlog-starter/README.md ================================================ ## 原理 通过注解AOP,提供service级别的logging和exception处理。 通过Spring Boot的autoConfig机制进行加载,无需手动配置,只需要添加如下依赖即可: ```xml com.alibaba.cola cola-component-catchlog-starter ``` 兼容普通的HSF service和Mtop service,具体做法可以查看代码`ResponseHandler` ```java public class ResponseHandler { public static Object handle(Class returnType, String errCode, String errMsg){ if (isColaResponse(returnType)){ return handleColaResponse(returnType, errCode, errMsg); } if(isMtopResponse(returnType)){ return handleMtopResponse(returnType, errCode, errMsg); } return null; } ... } ``` ## 使用介绍 1、在需要处理的Service类上面加上@CatchAndLog注解 ```java @CatchAndLog public class GrouponServiceImpl implements GrouponService ``` 2、logback-test.xml为组件开启DEGUG level的日志输出 ```xml ``` 3、如果在控制台看到如下的日志输出,说明CatchAndLog已经在做AOP拦截 ```xml DEBUG c.a.c.catchlog.CatchLogAspect - Start processing: GrouponServiceImpl.queryGrouponItemDetail(..) DEBUG c.a.c.catchlog.CatchLogAspect - REQUEST : 257 DEBUG c.a.c.catchlog.CatchLogAspect - RESPONSE : {"errCode":"UNKNOWN_ERROR"...} DEBUG c.a.c.catchlog.CatchLogAspect - COST : 1329ms ``` ================================================ FILE: cola-components/cola-component-catchlog-starter/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-catchlog-starter jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee org.springframework.boot spring-boot-autoconfigure provided org.springframework.boot spring-boot-configuration-processor provided org.springframework.boot spring-boot-starter-aop com.alibaba fastjson provided com.alibaba.cola cola-component-dto ${project.version} com.alibaba.cola cola-component-exception ${project.version} org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/java/com/alibaba/cola/catchlog/ApplicationContextHelper.java ================================================ package com.alibaba.cola.catchlog; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * ApplicationContextHelper * * @author Frank Zhang * @date 2020-11-14 1:58 PM */ @Component("colaCatchLogApplicationContextHelper") @Slf4j public class ApplicationContextHelper implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHelper.applicationContext = applicationContext; } public static T getBean(Class targetClz) { T beanInstance = null; //优先按type查 try { beanInstance = (T)applicationContext.getBean(targetClz); } catch (Exception e) { } //按name查 try { if (beanInstance == null) { String simpleName = targetClz.getSimpleName(); //首字母小写 simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); beanInstance = (T) applicationContext.getBean(simpleName); } } catch (Exception e) { log.warn("No bean found for " + targetClz.getCanonicalName()); } return beanInstance; } public static Object getBean(String claz) { return ApplicationContextHelper.applicationContext.getBean(claz); } public static T getBean(String name, Class requiredType) { return ApplicationContextHelper.applicationContext.getBean(name, requiredType); } public static T getBean(Class requiredType, Object... params) { return ApplicationContextHelper.applicationContext.getBean(requiredType, params); } public static ApplicationContext getApplicationContext() { return applicationContext; } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/java/com/alibaba/cola/catchlog/CatchAndLog.java ================================================ package com.alibaba.cola.catchlog; /** * CatchAndLog * * @author Frank Zhang * @date 2020-11-10 10:48 AM */ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CatchAndLog { } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/java/com/alibaba/cola/catchlog/CatchLogAspect.java ================================================ package com.alibaba.cola.catchlog; import com.alibaba.cola.exception.BizException; import com.alibaba.cola.exception.SysException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; /** * @Description : Catching and Logging * @author : Frank Zhang * @CreateDate : 2020/11/09 * @version : 1.0 */ @Aspect @Slf4j @Order(1) public class CatchLogAspect { /** * The syntax of pointcut */ @Pointcut("@within(CatchAndLog) && execution(public * *(..))") public void pointcut() { } @Around(value = "pointcut()") public Object around(ProceedingJoinPoint joinPoint) { long startTime = System.currentTimeMillis(); logRequest(joinPoint); Object response = null; try { response = joinPoint.proceed(); } catch (Throwable e) { response = handleException(joinPoint, e); } finally { logResponse(startTime, response); } return response; } private Object handleException(ProceedingJoinPoint joinPoint, Throwable e) { MethodSignature ms = (MethodSignature) joinPoint.getSignature(); Class returnType = ms.getReturnType(); if (e instanceof BizException) { log.warn("BIZ EXCEPTION : {}", e.getMessage()); // 在Debug的时候,对于BizException也打印堆栈 if (log.isDebugEnabled()) { log.error(e.getMessage(), e); } return ResponseHandlerFactory.get().handle(returnType, ((BizException) e).getErrCode(), e.getMessage()); } else if (e instanceof SysException) { log.error("SYS EXCEPTION: {}",e.getMessage(), e); return ResponseHandlerFactory.get().handle(returnType, ((SysException) e).getErrCode(), e.getMessage()); } log.error("UNKNOWN EXCEPTION: {}", e.getMessage(), e); return ResponseHandlerFactory.get().handle(returnType, "UNKNOWN_ERROR", e.getMessage()); } private void logResponse(long startTime, Object response) { try { long endTime = System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("RESPONSE: {}", JSON.toJSONString(response)); log.debug("COST: {}ms", (endTime - startTime)); } } catch (Exception e) { //swallow it log.error("logResponse error: {}", e.getMessage() , e); } } private void logRequest(ProceedingJoinPoint joinPoint) { if (!log.isDebugEnabled()) { return; } try { log.debug("START PROCESSING: {}", joinPoint.getSignature().toShortString()); Object[] args = joinPoint.getArgs(); for (Object arg : args) { log.debug("REQUEST: {}", JSON.toJSONString(arg, SerializerFeature.IgnoreErrorGetter)); } } catch (Exception e) { //swallow it log.error("logReqeust error: {}", e.getMessage() , e); } } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/java/com/alibaba/cola/catchlog/CatchLogAutoConfiguration.java ================================================ package com.alibaba.cola.catchlog; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @ Description : * @ Author : Frank Zhang * @ CreateDate : 2020/11/09 * @ Version : 1.0 */ @Configuration @EnableAspectJAutoProxy public class CatchLogAutoConfiguration { @Bean @ConditionalOnMissingBean(CatchLogAspect.class) public CatchLogAspect catchLogAspect() { return new CatchLogAspect(); } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/java/com/alibaba/cola/catchlog/DefaultResponseHandler.java ================================================ package com.alibaba.cola.catchlog; import com.alibaba.cola.dto.Response; import com.alibaba.cola.exception.BaseException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * ResponseHandler * * @author Frank Zhang * @date 2020-11-10 3:18 PM */ @Slf4j public class DefaultResponseHandler implements ResponseHandlerI{ @Override public Object handle(Class returnType, String errCode, String errMsg){ if (isColaResponse(returnType)){ return handleColaResponse(returnType, errCode, errMsg); } return null; } public Object handle(Class returnType, BaseException e){ return handle(returnType, e.getErrCode(), e.getMessage()); } private static Object handleColaResponse(Class returnType, String errCode, String errMsg) { try { Response response = (Response)returnType.newInstance(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMsg); return response; } catch (Exception ex){ log.error(ex.getMessage(), ex); return null; } } private static boolean isColaResponse(Class returnType) { return returnType == Response.class || returnType.getGenericSuperclass() == Response.class; } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/java/com/alibaba/cola/catchlog/ResponseHandlerFactory.java ================================================ package com.alibaba.cola.catchlog; import org.springframework.context.annotation.Bean; public class ResponseHandlerFactory { public static ResponseHandlerI get(){ if(ApplicationContextHelper.getBean(ResponseHandlerI.class) != null){ return ApplicationContextHelper.getBean(ResponseHandlerI.class); } return new DefaultResponseHandler(); } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/java/com/alibaba/cola/catchlog/ResponseHandlerI.java ================================================ package com.alibaba.cola.catchlog; import com.alibaba.cola.exception.BaseException; public interface ResponseHandlerI { public Object handle(Class returnType, String errCode, String errMsg); } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.alibaba.cola.catchlog.CatchLogAutoConfiguration ================================================ FILE: cola-components/cola-component-catchlog-starter/src/test/java/com/alibaba/cola/catchlog/test/Application.java ================================================ package com.alibaba.cola.catchlog.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Application * * @author Frank Zhang * @date 2020-11-10 3:58 PM */ @SpringBootApplication(scanBasePackages = {"com.alibaba.cola.catchlog"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/test/java/com/alibaba/cola/catchlog/test/CatchLogTest.java ================================================ package com.alibaba.cola.catchlog.test; import com.alibaba.cola.catchlog.CatchLogAspect; import com.alibaba.cola.catchlog.CatchLogAutoConfiguration; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; /** * */ @SpringBootTest(classes = {CatchLogAutoConfiguration.class, Demo.class, CatchLogAspect.class, Application.class}) public class CatchLogTest { @Resource private Demo demo; @Test public void testAspect() { demo.doSomething(); } @Test public void testCatchAndLog() { Demo.Request request = new Demo.Request(); request.name = "Frank"; request.age = 18; demo.execute(request); } @Test public void testExecuteWithResponse(){ Demo.Request request = new Demo.Request(); request.name = "Frank"; request.age = 18; demo.executeWithResponse(request); } @Test public void testExecuteWithVoid(){ demo.executeWithVoid(); } @Test public void testExecuteWithExceptionAndVoid(){ demo.executeWithExceptionAndVoid(); } @Test public void testExecuteWithExceptionAndDemoResponse(){ demo.executeWithExceptionAndDemoResponse(); } @Test public void testExecuteWithBizExceptionAndResponse(){ demo.executeWithBizExceptionAndResponse(); } @Test public void testExecuteWithSysExceptionAndResponse(){ demo.executeWithSysExceptionAndResponse(); } @Test public void testExecuteWithExceptionAndResponse(){ demo.executeWithExceptionAndResponse(); } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/test/java/com/alibaba/cola/catchlog/test/CustomResponseHandler.java ================================================ package com.alibaba.cola.catchlog.test; import com.alibaba.cola.catchlog.ResponseHandlerI; import org.springframework.stereotype.Component; @Component public class CustomResponseHandler implements ResponseHandlerI{ @Override public Object handle(Class returnType, String errCode, String errMsg) { System.out.println("==== This is Customized Response handler"); Demo.DemoResponse response = new Demo.DemoResponse(); response.setSuccess(false); return response; } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/test/java/com/alibaba/cola/catchlog/test/Demo.java ================================================ package com.alibaba.cola.catchlog.test; import com.alibaba.cola.catchlog.CatchAndLog; import com.alibaba.cola.catchlog.CatchLogAspect; import com.alibaba.cola.dto.Response; import com.alibaba.cola.dto.SingleResponse; import com.alibaba.cola.exception.BizException; import com.alibaba.cola.exception.SysException; import lombok.AllArgsConstructor; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; /** * Demo * * @author Frank Zhang * @date 2020-11-10 11:19 AM */ @Configuration @CatchAndLog public class Demo implements ApplicationContextAware { static ApplicationContext applicationContext; public void doSomething(){ System.out.println("Doing something"); CatchLogAspect catchAndLog = applicationContext.getBean(CatchLogAspect.class); System.out.println(catchAndLog); doSomethingInner(); } private void doSomethingInner(){ System.out.println("doSomethingInner"); } public DemoResponse execute(Request request){ System.out.println("executing request"); return new DemoResponse(request.name, true); } public DemoResponse executeWithExceptionAndDemoResponse(){ if(true){ throw new RuntimeException("executeWithExceptionAndDemoResponse"); } return null; } public Response executeWithResponse(Request request){ DemoResponse demoResponse = new DemoResponse("Jack Ma", true); return SingleResponse.of(demoResponse); } public Response executeWithExceptionAndResponse(){ if(true){ throw new RuntimeException("execute With Exception And Response"); } return null; } public void executeWithVoid(){ System.out.println("execute with void"); } public void executeWithExceptionAndVoid(){ if(true){ throw new BizException("execute With Exception And Void"); } } public Response executeWithBizExceptionAndResponse(){ if(true){ throw new BizException("execute With BizException And Response"); } return null; } public Response executeWithSysExceptionAndResponse(){ if(true){ throw new SysException("execute With SysException And Response"); } return null; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public static class Request { public String name; public int age; } @AllArgsConstructor public static class DemoResponse extends Response{ public String name; public boolean isSuccess; public DemoResponse(){ } } } ================================================ FILE: cola-components/cola-component-catchlog-starter/src/test/resources/application.properties ================================================ ================================================ FILE: cola-components/cola-component-catchlog-starter/src/test/resources/logback-test.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/cola-component-domain-starter/README.md ================================================ ## 作用 主要提供了@Entity注解,该注解将Spring的Bean的scope定义为prototype,因为Domain Entity是有状态的,不能 进行多线程共享,所以必须是多实例的。 另外提供了DomainFactory辅助类,帮助应用创建Domain Entity。 ================================================ FILE: cola-components/cola-component-domain-starter/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-domain-starter jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee org.springframework.boot spring-boot-autoconfigure provided org.springframework.boot spring-boot-configuration-processor provided org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-components/cola-component-domain-starter/src/main/java/com/alibaba/cola/domain/ApplicationContextHelper.java ================================================ package com.alibaba.cola.domain; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * ApplicationContextHelper * * @author Frank Zhang * @date 2020-11-14 1:58 PM */ @Component("colaDomainApplicationContextHelper") public class ApplicationContextHelper implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHelper.applicationContext = applicationContext; } public static T getBean(Class targetClz) { T beanInstance = null; //优先按type查 try { beanInstance = (T)applicationContext.getBean(targetClz); } catch (Exception e) { } //按name查 if (beanInstance == null) { String simpleName = targetClz.getSimpleName(); //首字母小写 simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); beanInstance = (T)applicationContext.getBean(simpleName); } if (beanInstance == null) { throw new RuntimeException("Component " + targetClz + " can not be found in Spring Container"); } return beanInstance; } public static Object getBean(String claz) { return ApplicationContextHelper.applicationContext.getBean(claz); } public static T getBean(String name, Class requiredType) { return ApplicationContextHelper.applicationContext.getBean(name, requiredType); } public static T getBean(Class requiredType, Object... params) { return ApplicationContextHelper.applicationContext.getBean(requiredType, params); } public static ApplicationContext getApplicationContext() { return applicationContext; } } ================================================ FILE: cola-components/cola-component-domain-starter/src/main/java/com/alibaba/cola/domain/DomainAutoConfiguration.java ================================================ package com.alibaba.cola.domain; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @ Description : * @ Author : Frank Zhang * @ CreateDate : 2020/11/09 * @ Version : 1.0 */ @Configuration public class DomainAutoConfiguration { @Bean @ConditionalOnMissingBean(ApplicationContextHelper.class) public ApplicationContextHelper applicationContextHelper() { return new ApplicationContextHelper(); } } ================================================ FILE: cola-components/cola-component-domain-starter/src/main/java/com/alibaba/cola/domain/DomainFactory.java ================================================ package com.alibaba.cola.domain; /** * DomainFactory * * @author Frank Zhang * @date 2019-01-03 2:41 PM */ public class DomainFactory { public static T create(Class entityClz){ return ApplicationContextHelper.getBean(entityClz); } } ================================================ FILE: cola-components/cola-component-domain-starter/src/main/java/com/alibaba/cola/domain/Entity.java ================================================ package com.alibaba.cola.domain; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.lang.annotation.*; /** * Entity, Entity Object is prototype and is not thread-safe * * @author Frank Zhang * @date 2019-01-03 2:53 PM */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public @interface Entity { } ================================================ FILE: cola-components/cola-component-domain-starter/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.alibaba.cola.domain.DomainAutoConfiguration ================================================ FILE: cola-components/cola-component-domain-starter/src/test/java/com/alibaba/cola/domain/Application.java ================================================ package com.alibaba.cola.domain; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Application * * @author Frank Zhang * @date 2020-11-10 3:58 PM */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); Customer customer = DomainFactory.create(Customer.class); System.out.println("Customer purchase power score : " + customer.getPurchasePowerScore()); } } ================================================ FILE: cola-components/cola-component-domain-starter/src/test/java/com/alibaba/cola/domain/Customer.java ================================================ package com.alibaba.cola.domain; import com.alibaba.cola.domain.Entity; import jakarta.annotation.Resource; /** * Customer * * @author Frank Zhang * @date 2020-11-14 2:43 PM */ @Entity public class Customer { private String name; private Integer age; @Resource private PurchasePowerGateway purchasePowerGateway; public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getPurchasePowerScore(){ return purchasePowerGateway.getScore(); } } ================================================ FILE: cola-components/cola-component-domain-starter/src/test/java/com/alibaba/cola/domain/PurchasePowerGateway.java ================================================ package com.alibaba.cola.domain; import org.springframework.stereotype.Component; /** * PurchasePowerGateway * * @author Frank Zhang * @date 2020-11-14 2:45 PM */ @Component public class PurchasePowerGateway { public Long getScore(){ return 96L; } } ================================================ FILE: cola-components/cola-component-domain-starter/src/test/resources/application.properties ================================================ ================================================ FILE: cola-components/cola-component-domain-starter/src/test/resources/logback-test.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/cola-component-dto/.gitignore ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/cola-component-dto/README.md ================================================ ## 作用 制定了DTO的相关规范,一是,为了复用;二是,使得应用层面的Logging和异常处理AOP成为可能。 关于Logging和异常处理的更多信息,请参考cola-component-catchlog-starter。 ================================================ FILE: cola-components/cola-component-dto/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-dto jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/ClientObject.java ================================================ package com.alibaba.cola.dto; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * This is the object communicate with Client. * The clients could be view layer or other HSF Consumers * @author fulan.zjf 2017-10-27 PM 12:19:15 */ public abstract class ClientObject implements Serializable{ private static final long serialVersionUID = 1L; /** * This is for extended values */ protected Map extValues = new HashMap(); public Object getExtField(String key){ if(extValues != null){ return extValues.get(key); } return null; } public void putExtField(String fieldName, Object value){ this.extValues.put(fieldName, value); } public Map getExtValues() { return extValues; } public void setExtValues(Map extValues) { this.extValues = extValues; } } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/Command.java ================================================ package com.alibaba.cola.dto; /** * Command request from Client. * * @author Frank Zhang 2020.11.13 * */ public abstract class Command extends DTO { private static final long serialVersionUID = 1L; } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/DTO.java ================================================ package com.alibaba.cola.dto; import java.io.Serializable; /** * Data Transfer object, including Command, Query and Response, * * Command and Query is CQRS concept. * * @author Frank Zhang 2020.11.13 * */ public abstract class DTO implements Serializable { private static final long serialVersionUID = 1L; } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/MultiResponse.java ================================================ package com.alibaba.cola.dto; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * Response with batch record to return, * usually use in conditional query *

* Created by Danny.Lee on 2017/11/1. */ public class MultiResponse extends Response { private static final long serialVersionUID = 1L; private Collection data; public List getData() { if (null == data) { return Collections.emptyList(); } if (data instanceof List) { return (List) data; } return new ArrayList<>(data); } public void setData(Collection data) { this.data = data; } public boolean isEmpty() { return data == null || data.isEmpty(); } public boolean isNotEmpty() { return !isEmpty(); } public static MultiResponse buildSuccess() { MultiResponse response = new MultiResponse(); response.setSuccess(true); return response; } public static MultiResponse buildFailure(String errCode, String errMessage) { MultiResponse response = new MultiResponse(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } public static MultiResponse of(Collection data) { MultiResponse response = new MultiResponse<>(); response.setSuccess(true); response.setData(data); return response; } } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/PageQuery.java ================================================ package com.alibaba.cola.dto; /** * Page Query Param * * @author jacky */ public abstract class PageQuery extends Query { private static final long serialVersionUID = 1L; public static final String ASC = "ASC"; public static final String DESC = "DESC"; private static final int DEFAULT_PAGE_SIZE = 10; private int pageSize = DEFAULT_PAGE_SIZE; private int pageIndex = 1; private String orderBy; private String orderDirection = DESC; private String groupBy; private boolean needTotalCount = true; public int getPageIndex() { if (pageIndex < 1) { return 1; } return pageIndex; } public PageQuery setPageIndex(int pageIndex) { this.pageIndex = pageIndex; return this; } public int getPageSize() { if (pageSize < 1) { pageSize = DEFAULT_PAGE_SIZE; } return pageSize; } public PageQuery setPageSize(int pageSize) { if (pageSize < 1) { pageSize = DEFAULT_PAGE_SIZE; } this.pageSize = pageSize; return this; } public int getOffset() { return (getPageIndex() - 1) * getPageSize(); } public String getOrderBy() { return orderBy; } public PageQuery setOrderBy(String orderBy) { this.orderBy = orderBy; return this; } public String getOrderDirection() { return orderDirection; } public PageQuery setOrderDirection(String orderDirection) { if (ASC.equalsIgnoreCase(orderDirection) || DESC.equalsIgnoreCase(orderDirection)) { this.orderDirection = orderDirection; } return this; } public String getGroupBy() { return groupBy; } public void setGroupBy(String groupBy) { this.groupBy = groupBy; } public boolean isNeedTotalCount() { return needTotalCount; } public void setNeedTotalCount(boolean needTotalCount) { this.needTotalCount = needTotalCount; } } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/PageResponse.java ================================================ package com.alibaba.cola.dto; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * Response with batch page record to return, * usually use in page query *

* Created by xiaochu.lbj on 2020/06/30. */ public class PageResponse extends Response { private static final long serialVersionUID = 1L; private int totalCount = 0; private int pageSize = 1; private int pageIndex = 1; private Collection data; public int getTotalCount() { return totalCount; } public void setTotalCount(int totalCount) { this.totalCount = totalCount; } public int getPageSize() { if (pageSize < 1) { return 1; } return pageSize; } public void setPageSize(int pageSize) { if (pageSize < 1) { this.pageSize = 1; } else { this.pageSize = pageSize; } } public int getPageIndex() { if (pageIndex < 1) { return 1; } return pageIndex; } public void setPageIndex(int pageIndex) { if (pageIndex < 1) { this.pageIndex = 1; } else { this.pageIndex = pageIndex; } } public List getData() { if (null == data) { return Collections.emptyList(); } if (data instanceof List) { return (List) data; } return new ArrayList<>(data); } public void setData(Collection data) { this.data = data; } public int getTotalPages() { return this.totalCount % this.pageSize == 0 ? this.totalCount / this.pageSize : (this.totalCount / this.pageSize) + 1; } public boolean isEmpty() { return data == null || data.isEmpty(); } public boolean isNotEmpty() { return !isEmpty(); } public static PageResponse buildSuccess() { PageResponse response = new PageResponse(); response.setSuccess(true); return response; } public static PageResponse buildFailure(String errCode, String errMessage) { PageResponse response = new PageResponse(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } public static PageResponse of(int pageSize, int pageIndex) { PageResponse response = new PageResponse<>(); response.setSuccess(true); response.setData(Collections.emptyList()); response.setTotalCount(0); response.setPageSize(pageSize); response.setPageIndex(pageIndex); return response; } public static PageResponse of(Collection data, int totalCount, int pageSize, int pageIndex) { PageResponse response = new PageResponse<>(); response.setSuccess(true); response.setData(data); response.setTotalCount(totalCount); response.setPageSize(pageSize); response.setPageIndex(pageIndex); return response; } } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/Query.java ================================================ package com.alibaba.cola.dto; /** * Query request from Client. * * @author Frank Zhang 2020.11.13 * */ public abstract class Query extends Command { private static final long serialVersionUID = 1L; } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/Response.java ================================================ package com.alibaba.cola.dto; /** * Response to caller * * @author fulan.zjf 2017年10月21日 下午8:53:17 */ public class Response extends DTO { private static final long serialVersionUID = 1L; private boolean success; private String errCode; private String errMessage; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getErrCode() { return errCode; } public void setErrCode(String errCode) { this.errCode = errCode; } public String getErrMessage() { return errMessage; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } @Override public String toString() { return "Response [success=" + success + ", errCode=" + errCode + ", errMessage=" + errMessage + "]"; } public static Response buildSuccess() { Response response = new Response(); response.setSuccess(true); return response; } public static Response buildFailure(String errCode, String errMessage) { Response response = new Response(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/Scope.java ================================================ package com.alibaba.cola.dto; /** * 结果范围控制 * * @author xiaochu.lbj */ public abstract class Scope extends DTO { private static final long serialVersionUID = 1L; } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/dto/SingleResponse.java ================================================ package com.alibaba.cola.dto; /** * Response with single record to return *

* Created by Danny.Lee on 2017/11/1. */ public class SingleResponse extends Response { private static final long serialVersionUID = 1L; private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } public static SingleResponse buildSuccess() { SingleResponse response = new SingleResponse(); response.setSuccess(true); return response; } public static SingleResponse buildFailure(String errCode, String errMessage) { SingleResponse response = new SingleResponse(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } public static SingleResponse of(T data) { SingleResponse response = new SingleResponse<>(); response.setSuccess(true); response.setData(data); return response; } } ================================================ FILE: cola-components/cola-component-dto/src/main/java/com/alibaba/cola/extension/BizScenario.java ================================================ package com.alibaba.cola.extension; /** * BizScenario(业务场景)= bizId + useCase + scenario, which can uniquely identify a user scenario. * * @author Frank Zhang * @date 2019-08-20 12:07 */ public class BizScenario { public final static String DEFAULT_BIZ_ID = "#defaultBizId#"; public final static String DEFAULT_USE_CASE = "#defaultUseCase#"; public final static String DEFAULT_SCENARIO = "#defaultScenario#"; private final static String DOT_SEPARATOR = "."; /** * bizId is used to identify a business, such as "tmall", it's nullable if there is only one biz */ private String bizId = DEFAULT_BIZ_ID; /** * useCase is used to identify a use case, such as "placeOrder", can not be null */ private String useCase = DEFAULT_USE_CASE; /** * scenario is used to identify a use case, such as "88vip","normal", can not be null */ private String scenario = DEFAULT_SCENARIO; /** * For above case, the BizScenario will be "tmall.placeOrder.88vip", * with this code, we can provide extension processing other than "tmall.placeOrder.normal" scenario. * * @return */ public String getUniqueIdentity(){ return bizId + DOT_SEPARATOR + useCase + DOT_SEPARATOR + scenario; } public static BizScenario valueOf(String bizId, String useCase, String scenario){ BizScenario bizScenario = new BizScenario(); bizScenario.bizId = bizId; bizScenario.useCase = useCase; bizScenario.scenario = scenario; return bizScenario; } public static BizScenario valueOf(String bizId, String useCase){ return BizScenario.valueOf(bizId, useCase, DEFAULT_SCENARIO); } public static BizScenario valueOf(String bizId){ return BizScenario.valueOf(bizId, DEFAULT_USE_CASE, DEFAULT_SCENARIO); } public static BizScenario newDefault(){ return BizScenario.valueOf(DEFAULT_BIZ_ID, DEFAULT_USE_CASE, DEFAULT_SCENARIO); } public String getIdentityWithDefaultScenario(){ return bizId + DOT_SEPARATOR + useCase + DOT_SEPARATOR + DEFAULT_SCENARIO; } public String getIdentityWithDefaultUseCase(){ return bizId + DOT_SEPARATOR + DEFAULT_USE_CASE + DOT_SEPARATOR + DEFAULT_SCENARIO; } } ================================================ FILE: cola-components/cola-component-dto/src/test/java/com/alibaba/cola/Test.java ================================================ package com.alibaba.cola; public class Test { } ================================================ FILE: cola-components/cola-component-dto/src/test/resources/logback-test.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/cola-component-exception/.gitignore ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/cola-component-exception/README.md ================================================ ## 作用 制定了Exception的相关规范,一是,为了复用;二是,使得应用层面的Logging和异常处理AOP成为可能。 实际上,对于应用系统而言,只有三种类型的异常: 1. BizException:业务异常,有明确的业务语义,不需要记录Error日志,不需要Retry 2. SysException:已知的系统异常,需要记录Error日志,可以Retry 3. Exception:未知的其它异常,需要完整的Error Stack日志,可以Retry ================================================ FILE: cola-components/cola-component-exception/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-exception jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee ================================================ FILE: cola-components/cola-component-exception/src/main/java/com/alibaba/cola/exception/Assert.java ================================================ package com.alibaba.cola.exception; import java.util.Collection; import java.util.Map; /** * Assertion utility class that assists in validating arguments. * *

Useful for identifying programmer errors early and clearly at runtime. * *

For example, if the contract of a public method states it does not * allow {@code null} arguments, {@code Assert} can be used to validate that * contract. * * For example: * *

 * Assert.notNull(clazz, "The class must not be null");
 * Assert.isTrue(i > 0, "The value must be greater than zero");
* * This class is empowered by {@link org.springframework.util.Assert} * * @author Frank Zhang * @date 2019-01-13 11:49 AM */ public abstract class Assert { /** * Assert a boolean expression, throwing {@code BizException} * * for example * *
Assert.isTrue(i != 0, errorCode.B_ORDER_illegalNumber, "The order number can not be zero");
* * @param expression a boolean expression * @param errorCode * @param errMessage the exception message to use if the assertion fails * @throws BizException if expression is {@code false} */ public static void isTrue(boolean expression, String errorCode, String errMessage) { if (!expression) { throw new BizException(errorCode, errMessage); } } /** * Assert a boolean expression, if expression is true, throwing {@code BizException} * * for example * *
Assert.isFalse(i == 0, errorCode.B_ORDER_illegalNumber, "The order number can not be zero");
* * This is more intuitive than isTure. */ public static void isFalse(boolean expression, String errorCode, String errMessage) { if (expression) { throw new BizException(errorCode, errMessage); } } public static void isTrue(boolean expression, String errMessage) { if (!expression) { throw new BizException(errMessage); } } public static void isFalse(boolean expression, String errMessage) { if (expression) { throw new BizException(errMessage); } } public static void isTrue(boolean expression) { isTrue(expression, "[Assertion failed] Must be true"); } public static void isFalse(boolean expression) { isFalse(expression, "[Assertion failed] Must be false"); } public static void notNull(Object object, String errorCode, String errMessage) { if (object == null) { throw new BizException(errorCode, errMessage); } } public static void notNull(Object object, String errMessage) { if (object == null) { throw new BizException(errMessage); } } public static void notNull(Object object) { notNull(object, "[Assertion failed] Must not null"); } public static void notEmpty(Collection collection, String errorCode, String errMessage) { if (collection == null || collection.isEmpty()) { throw new BizException(errorCode, errMessage); } } public static void notEmpty(Collection collection, String errMessage) { if (collection == null || collection.isEmpty()) { throw new BizException(errMessage); } } public static void notEmpty(Collection collection) { notEmpty(collection, "[Assertion failed] Collection must not be empty: it must contain at least 1 element"); } public static void notEmpty(Map map, String errorCode, String errMessage) { if (map == null || map.isEmpty()) { throw new BizException(errorCode, errMessage); } } public static void notEmpty(Map map, String errMessage) { if (map == null || map.isEmpty()) { throw new BizException(errMessage); } } public static void notEmpty(Map map) { notEmpty(map, "[Assertion failed] Map must not be empty: it must contain at least one entry"); } } ================================================ FILE: cola-components/cola-component-exception/src/main/java/com/alibaba/cola/exception/BaseException.java ================================================ package com.alibaba.cola.exception; /** * Base Exception is the parent of all exceptions * * @author fulan.zjf 2017年10月22日 上午12:00:39 */ public abstract class BaseException extends RuntimeException { private static final long serialVersionUID = 1L; private String errCode; public BaseException(String errMessage) { super(errMessage); } public BaseException(String errCode, String errMessage) { super(errMessage); this.errCode = errCode; } public BaseException(String errMessage, Throwable e) { super(errMessage, e); } public BaseException(String errCode, String errMessage, Throwable e) { super(errMessage, e); this.errCode = errCode; } public String getErrCode() { return errCode; } public void setErrCode(String errCode) { this.errCode = errCode; } } ================================================ FILE: cola-components/cola-component-exception/src/main/java/com/alibaba/cola/exception/BizException.java ================================================ package com.alibaba.cola.exception; /** * BizException is known Exception, no need retry * * @author Frank Zhang */ public class BizException extends BaseException { private static final long serialVersionUID = 1L; private static final String DEFAULT_ERR_CODE = "BIZ_ERROR"; public BizException(String errMessage) { super(DEFAULT_ERR_CODE, errMessage); } public BizException(String errCode, String errMessage) { super(errCode, errMessage); } public BizException(String errMessage, Throwable e) { super(DEFAULT_ERR_CODE, errMessage, e); } public BizException(String errorCode, String errMessage, Throwable e) { super(errorCode, errMessage, e); } } ================================================ FILE: cola-components/cola-component-exception/src/main/java/com/alibaba/cola/exception/ExceptionFactory.java ================================================ package com.alibaba.cola.exception; /** * @ Description : 异常工厂实现 * @ Author : da.xue * @ CreateDate : 2020/04/11 * @ Version : 1.0 */ public class ExceptionFactory { public static BizException bizException(String errorMessage) { return new BizException(errorMessage); } public static BizException bizException(String errorCode, String errorMessage) { return new BizException(errorCode, errorMessage); } public static SysException sysException(String errorMessage) { return new SysException(errorMessage); } public static SysException sysException(String errorCode, String errorMessage) { return new SysException(errorCode, errorMessage); } public static SysException sysException(String errorMessage, Throwable e) { return new SysException(errorMessage, e); } public static SysException sysException(String errorCode, String errorMessage, Throwable e) { return new SysException(errorCode, errorMessage, e); } } ================================================ FILE: cola-components/cola-component-exception/src/main/java/com/alibaba/cola/exception/SysException.java ================================================ package com.alibaba.cola.exception; /** * System Exception is unexpected Exception, retry might work again * * @author Danny.Lee 2018/1/27 */ public class SysException extends BaseException { private static final long serialVersionUID = 1L; private static final String DEFAULT_ERR_CODE = "SYS_ERROR"; public SysException(String errMessage) { super(DEFAULT_ERR_CODE, errMessage); } public SysException(String errCode, String errMessage) { super(errCode, errMessage); } public SysException(String errMessage, Throwable e) { super(DEFAULT_ERR_CODE, errMessage, e); } public SysException(String errorCode, String errMessage, Throwable e) { super(errorCode, errMessage, e); } } ================================================ FILE: cola-components/cola-component-exception/src/test/java/com/alibaba/cola/exception/Test.java ================================================ package com.alibaba.cola.exception; public class Test { } ================================================ FILE: cola-components/cola-component-exception/src/test/resources/logback-test.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/cola-component-extension-starter/README.md ================================================ ## 作用 该组件继承了老Cola Framework中的扩展点功能,旨在通过统一的扩展形式来支撑业务的变化。 ## 原理 https://blog.csdn.net/significantfrank/article/details/100074716 ## 使用介绍 参看测试代码`com.alibaba.cola.extension.ExtensionTest` ================================================ FILE: cola-components/cola-component-extension-starter/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-extension-starter jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee org.springframework.boot spring-boot-autoconfigure provided org.springframework.boot spring-boot-configuration-processor provided org.springframework.boot spring-boot-starter-aop provided com.alibaba fastjson provided org.slf4j slf4j-api provided com.alibaba.cola cola-component-dto ${project.version} com.alibaba.cola cola-component-exception ${project.version} test com.alibaba.cola cola-component-domain-starter ${project.version} test org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/Extension.java ================================================ /* * Copyright 2017 Alibaba.com All right reserved. This software is the * confidential and proprietary information of Alibaba.com ("Confidential * Information"). You shall not disclose such Confidential Information and shall * use it only in accordance with the terms of the license agreement you entered * into with Alibaba.com. */ package com.alibaba.cola.extension; import org.springframework.stereotype.Component; import java.lang.annotation.*; /** * Extension * @author fulan.zjf 2017-11-05 */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Component public @interface Extension { String bizId() default BizScenario.DEFAULT_BIZ_ID; String useCase() default BizScenario.DEFAULT_USE_CASE; String scenario() default BizScenario.DEFAULT_SCENARIO; } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/ExtensionAutoConfiguration.java ================================================ package com.alibaba.cola.extension; import com.alibaba.cola.extension.register.ExtensionBootstrap; import com.alibaba.cola.extension.register.ExtensionRegister; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * @ Description : * @ Author : Frank Zhang * @ CreateDate : 2020/11/09 * @ Version : 1.0 */ @Configuration public class ExtensionAutoConfiguration { @Bean(initMethod = "init") @ConditionalOnMissingBean(ExtensionBootstrap.class) public ExtensionBootstrap bootstrap() { return new ExtensionBootstrap(); } @Bean @ConditionalOnMissingBean(ExtensionRepository.class) public ExtensionRepository repository() { return new ExtensionRepository(); } @Bean @ConditionalOnMissingBean(ExtensionExecutor.class) public ExtensionExecutor executor() { return new ExtensionExecutor(); } @Bean @ConditionalOnMissingBean(ExtensionRegister.class) public ExtensionRegister register() { return new ExtensionRegister(); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/ExtensionCoordinate.java ================================================ /* * Copyright 2017 Alibaba.com All right reserved. This software is the * confidential and proprietary information of Alibaba.com ("Confidential * Information"). You shall not disclose such Confidential Information and shall * use it only in accordance with the terms of the license agreement you entered * into with Alibaba.com. */ package com.alibaba.cola.extension; /** * Extension Coordinate(扩展坐标) is used to uniquely position an Extension * @author fulan.zjf 2017-11-05 */ public class ExtensionCoordinate { private final String extensionPointName; private final String bizScenarioUniqueIdentity; /** * Wrapper */ private Class extensionPointClass; private BizScenario bizScenario; public Class getExtensionPointClass() { return extensionPointClass; } public BizScenario getBizScenario() { return bizScenario; } public static ExtensionCoordinate valueOf(Class extPtClass, BizScenario bizScenario){ return new ExtensionCoordinate(extPtClass, bizScenario); } public ExtensionCoordinate(Class extPtClass, BizScenario bizScenario){ this.extensionPointClass = extPtClass; this.extensionPointName = extPtClass.getName(); this.bizScenario = bizScenario; this.bizScenarioUniqueIdentity = bizScenario.getUniqueIdentity(); } public ExtensionCoordinate(String extensionPoint, String bizScenario){ this.extensionPointName = extensionPoint; this.bizScenarioUniqueIdentity = bizScenario; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((bizScenarioUniqueIdentity == null) ? 0 : bizScenarioUniqueIdentity.hashCode()); result = prime * result + ((extensionPointName == null) ? 0 : extensionPointName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ExtensionCoordinate other = (ExtensionCoordinate) obj; if (bizScenarioUniqueIdentity == null) { if (other.bizScenarioUniqueIdentity != null) { return false; } } else if (!bizScenarioUniqueIdentity.equals(other.bizScenarioUniqueIdentity)) { return false; } if (extensionPointName == null) { return other.extensionPointName == null; } else { return extensionPointName.equals(other.extensionPointName); } } @Override public String toString() { return "ExtensionCoordinate [extensionPointName=" + extensionPointName + ", bizScenarioUniqueIdentity=" + bizScenarioUniqueIdentity + "]"; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/ExtensionException.java ================================================ package com.alibaba.cola.extension; /** * 扩展点初始化或者查找失败时,使用次异常 *

* 扩展点初始化或者查找失败时,使用次异常 *

* * @author ***flying@126.com * @version 1.0.0 * @since 1.0.0 2022/9/26 */ public class ExtensionException extends RuntimeException { private String errCode; public ExtensionException(String errMessage) { super(errMessage); } public ExtensionException(String errCode, String errMessage) { super(errMessage); this.errCode = errCode; } public ExtensionException(String errMessage, Throwable e) { super(errMessage, e); } public ExtensionException(String errCode, String errMessage, Throwable e) { super(errMessage, e); this.errCode = errCode; } public String getErrCode() { return errCode; } public void setErrCode(String errCode) { this.errCode = errCode; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/ExtensionExecutor.java ================================================ /* * Copyright 2017 Alibaba.com All right reserved. This software is the * confidential and proprietary information of Alibaba.com ("Confidential * Information"). You shall not disclose such Confidential Information and shall * use it only in accordance with the terms of the license agreement you entered * into with Alibaba.com. */ package com.alibaba.cola.extension; import com.alibaba.cola.extension.register.AbstractComponentExecutor; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * ExtensionExecutor * * @author fulan.zjf 2017-11-05 */ @Component public class ExtensionExecutor extends AbstractComponentExecutor { private static final String EXTENSION_NOT_FOUND = "extension_not_found"; private Logger logger = LoggerFactory.getLogger(ExtensionExecutor.class); @Resource private ExtensionRepository extensionRepository; @Override protected C locateComponent(Class targetClz, BizScenario bizScenario) { C extension = locateExtension(targetClz, bizScenario); logger.debug("[Located Extension]: " + extension.getClass().getSimpleName()); return extension; } /** * if the bizScenarioUniqueIdentity is "ali.tmall.supermarket" *

* the search path is as below: * 1、first try to get extension by "ali.tmall.supermarket", if get, return it. * 2、loop try to get extension by "ali.tmall", if get, return it. * 3、loop try to get extension by "ali", if get, return it. * 4、if not found, try the default extension * * @param targetClz */ protected Ext locateExtension(Class targetClz, BizScenario bizScenario) { checkNull(bizScenario); Ext extension; logger.debug("BizScenario in locateExtension is : " + bizScenario.getUniqueIdentity()); // first try with full namespace extension = firstTry(targetClz, bizScenario); if (extension != null) { return extension; } // second try with default scenario extension = secondTry(targetClz, bizScenario); if (extension != null) { return extension; } // third try with default use case + default scenario extension = defaultUseCaseTry(targetClz, bizScenario); if (extension != null) { return extension; } String errMessage = "Can not find extension with ExtensionPoint: " + targetClz + " BizScenario:" + bizScenario.getUniqueIdentity(); throw new ExtensionException(EXTENSION_NOT_FOUND, errMessage); } /** * first try with full namespace *

* example: biz1.useCase1.scenario1 */ private Ext firstTry(Class targetClz, BizScenario bizScenario) { logger.debug("First trying with " + bizScenario.getUniqueIdentity()); return locate(targetClz.getName(), bizScenario.getUniqueIdentity()); } /** * second try with default scenario *

* example: biz1.useCase1.#defaultScenario# */ private Ext secondTry(Class targetClz, BizScenario bizScenario) { logger.debug("Second trying with " + bizScenario.getIdentityWithDefaultScenario()); return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultScenario()); } /** * third try with default use case + default scenario *

* example: biz1.#defaultUseCase#.#defaultScenario# */ private Ext defaultUseCaseTry(Class targetClz, BizScenario bizScenario) { logger.debug("Third trying with " + bizScenario.getIdentityWithDefaultUseCase()); return locate(targetClz.getName(), bizScenario.getIdentityWithDefaultUseCase()); } private Ext locate(String name, String uniqueIdentity) { final Ext ext = (Ext) extensionRepository.getExtensionRepo(). get(new ExtensionCoordinate(name, uniqueIdentity)); return ext; } private void checkNull(BizScenario bizScenario) { if (bizScenario == null) { throw new IllegalArgumentException("BizScenario can not be null for extension"); } } } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/ExtensionPointI.java ================================================ package com.alibaba.cola.extension; /** * ExtensionPointI is the parent interface of all ExtensionPoints * 扩展点表示一块逻辑在不同的业务有不同的实现,使用扩展点做接口申明,然后用Extension(扩展)去实现扩展点。 * @author fulan.zjf 2017-10-22 */ public interface ExtensionPointI { } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/ExtensionRepository.java ================================================ /* * Copyright 2017 Alibaba.com All right reserved. This software is the * confidential and proprietary information of Alibaba.com ("Confidential * Information"). You shall not disclose such Confidential Information and shall * use it only in accordance with the terms of the license agreement you entered * into with Alibaba.com. */ package com.alibaba.cola.extension; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * ExtensionRepository * @author fulan.zjf 2017-11-05 */ @Component public class ExtensionRepository { public Map getExtensionRepo() { return extensionRepo; } private Map extensionRepo = new HashMap<>(); } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/Extensions.java ================================================ package com.alibaba.cola.extension; import org.springframework.stereotype.Component; import java.lang.annotation.*; /** * because {@link Extension} only supports single coordinates, * this annotation is a supplement to {@link Extension} and supports multiple coordinates * * @author wangguoqiang wrote on 2022/10/10 12:19 * @version 1.0 * @see Extension */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Component public @interface Extensions { String[] bizId() default BizScenario.DEFAULT_BIZ_ID; String[] useCase() default BizScenario.DEFAULT_USE_CASE; String[] scenario() default BizScenario.DEFAULT_SCENARIO; } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/register/AbstractComponentExecutor.java ================================================ package com.alibaba.cola.extension.register; import com.alibaba.cola.extension.BizScenario; import com.alibaba.cola.extension.ExtensionCoordinate; import java.util.function.Consumer; import java.util.function.Function; /** * @author fulan.zjf * @date 2017/12/21 */ public abstract class AbstractComponentExecutor { /** * Execute extension with Response * * @param targetClz * @param bizScenario * @param exeFunction * @param Response Type * @param Parameter Type * @return */ public R execute(Class targetClz, BizScenario bizScenario, Function exeFunction) { T component = locateComponent(targetClz, bizScenario); return exeFunction.apply(component); } public R execute(ExtensionCoordinate extensionCoordinate, Function exeFunction){ return execute((Class) extensionCoordinate.getExtensionPointClass(), extensionCoordinate.getBizScenario(), exeFunction); } /** * Execute extension without Response * * @param targetClz * @param context * @param exeFunction * @param Parameter Type */ public void executeVoid(Class targetClz, BizScenario context, Consumer exeFunction) { T component = locateComponent(targetClz, context); exeFunction.accept(component); } public void executeVoid(ExtensionCoordinate extensionCoordinate, Consumer exeFunction){ executeVoid(extensionCoordinate.getExtensionPointClass(), extensionCoordinate.getBizScenario(), exeFunction); } protected abstract C locateComponent(Class targetClz, BizScenario context); } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/register/ExtensionBootstrap.java ================================================ package com.alibaba.cola.extension.register; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.ExtensionPointI; import com.alibaba.cola.extension.Extensions; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.Map; /** * ExtensionBootstrap * * @author Frank Zhang * @date 2020-06-18 7:55 PM */ @Slf4j @Component public class ExtensionBootstrap implements ApplicationContextAware { @Resource private ExtensionRegister extensionRegister; private ApplicationContext applicationContext; @PostConstruct public void init(){ Map extMap = applicationContext.getBeansOfType(ExtensionPointI.class); for (ExtensionPointI ext : extMap.values()) { if (ext.getClass().isAnnotationPresent(Extension.class)) { extensionRegister.doRegistration(ext); }else if (ext.getClass().isAnnotationPresent(Extensions.class)){ extensionRegister.doRegistrationExtensions(ext); }else { log.error("There is no annotation for @Extension or @Extension on this extension class:{}" , ext.getClass()); } } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/java/com/alibaba/cola/extension/register/ExtensionRegister.java ================================================ /* * Copyright 2017 Alibaba.com All right reserved. This software is the * confidential and proprietary information of Alibaba.com ("Confidential * Information"). You shall not disclose such Confidential Information and shall * use it only in accordance with the terms of the license agreement you entered * into with Alibaba.com. */ package com.alibaba.cola.extension.register; import com.alibaba.cola.extension.*; import jakarta.annotation.Resource; import org.springframework.aop.support.AopUtils; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** * ExtensionRegister * * @author fulan.zjf 2017-11-05 */ @Component public class ExtensionRegister { /** * 扩展点接口名称不合法 */ private static final String EXTENSION_INTERFACE_NAME_ILLEGAL = "extension_interface_name_illegal"; /** * 扩展点不合法 */ private static final String EXTENSION_ILLEGAL = "extension_illegal"; /** * 扩展点定义重复 */ private static final String EXTENSION_DEFINE_DUPLICATE = "extension_define_duplicate"; @Resource private ExtensionRepository extensionRepository; public final static String EXTENSION_EXTPT_NAMING = "ExtPt"; public void doRegistration(ExtensionPointI extensionObject) { Class extensionClz = extensionObject.getClass(); if (AopUtils.isAopProxy(extensionObject)) { extensionClz = AopUtils.getTargetClass(extensionObject); } Extension extensionAnn = AnnotatedElementUtils.findMergedAnnotation(extensionClz, Extension.class); BizScenario bizScenario = BizScenario.valueOf(extensionAnn.bizId(), extensionAnn.useCase(), extensionAnn.scenario()); ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity()); ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject); if (preVal != null) { String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate; throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage); } } public void doRegistrationExtensions(ExtensionPointI extensionObject){ Class extensionClz = extensionObject.getClass(); if (AopUtils.isAopProxy(extensionObject)) { extensionClz = ClassUtils.getUserClass(extensionObject); } Extensions extensionsAnnotation = AnnotationUtils.findAnnotation(extensionClz, Extensions.class); //Support multiple extensions registration String[] bizIds = extensionsAnnotation.bizId(); String[] useCases = extensionsAnnotation.useCase(); String[] scenarios = extensionsAnnotation.scenario(); for (String bizId : bizIds) { for (String useCase : useCases) { for (String scenario : scenarios) { BizScenario bizScenario = BizScenario.valueOf(bizId, useCase, scenario); ExtensionCoordinate extensionCoordinate = new ExtensionCoordinate(calculateExtensionPoint(extensionClz), bizScenario.getUniqueIdentity()); ExtensionPointI preVal = extensionRepository.getExtensionRepo().put(extensionCoordinate, extensionObject); if (preVal != null) { String errMessage = "Duplicate registration is not allowed for :" + extensionCoordinate; throw new ExtensionException(EXTENSION_DEFINE_DUPLICATE, errMessage); } } } } } /** * @param targetClz * @return */ private String calculateExtensionPoint(Class targetClz) { Class[] interfaces = ClassUtils.getAllInterfacesForClass(targetClz); if (interfaces == null || interfaces.length == 0) { throw new ExtensionException(EXTENSION_ILLEGAL, "Please assign a extension point interface for " + targetClz); } for (Class intf : interfaces) { String extensionPoint = intf.getSimpleName(); if (extensionPoint.contains(EXTENSION_EXTPT_NAMING)) { return intf.getName(); } } String errMessage = "Your name of ExtensionPoint for " + targetClz + " is not valid, must be end of " + EXTENSION_EXTPT_NAMING; throw new ExtensionException(EXTENSION_INTERFACE_NAME_ILLEGAL, errMessage); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.alibaba.cola.extension.ExtensionAutoConfiguration ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/Application.java ================================================ package com.alibaba.cola.extension; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; /** * Application * * @author Frank Zhang * @date 2020-11-10 3:58 PM */ @SpringBootApplication @ComponentScan(basePackages = "com.alibaba.cola") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/ExtensionTest.java ================================================ package com.alibaba.cola.extension; import com.alibaba.cola.dto.Response; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.client.CustomerDTO; import com.alibaba.cola.extension.customer.client.CustomerServiceI; import com.alibaba.cola.extension.customer.domain.CustomerType; import jakarta.annotation.Resource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; /** * ExtensionTest * * @author Frank Zhang * @date 2020-11-14 2:55 PM */ @SpringBootTest(classes = Application.class) public class ExtensionTest { @Resource private CustomerServiceI customerService; @Test public void testBiz1UseCase1Scenario1AddCustomerSuccess(){ //1. Prepare AddCustomerCmd addCustomerCmd = new AddCustomerCmd(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCompanyName("alibaba"); customerDTO.setSource(Constants.SOURCE_RFQ); customerDTO.setCustomerType(CustomerType.IMPORTANT); addCustomerCmd.setCustomerDTO(customerDTO); BizScenario scenario = BizScenario.valueOf(Constants.BIZ_1, Constants.USE_CASE_1, Constants.SCENARIO_1); addCustomerCmd.setBizScenario(scenario); //2. Execute Response response = customerService.addCustomer(addCustomerCmd); //3. Expect Success Assertions.assertTrue(response.isSuccess()); } @Test public void testBiz1UseCase1AddCustomerSuccess(){ //1. Prepare AddCustomerCmd addCustomerCmd = new AddCustomerCmd(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCompanyName("alibaba"); customerDTO.setSource(Constants.SOURCE_RFQ); customerDTO.setCustomerType(CustomerType.IMPORTANT); addCustomerCmd.setCustomerDTO(customerDTO); BizScenario scenario = BizScenario.valueOf(Constants.BIZ_1, Constants.USE_CASE_1); addCustomerCmd.setBizScenario(scenario); //2. Execute Response response = customerService.addCustomer(addCustomerCmd); //3. Expect Success Assertions.assertTrue(response.isSuccess()); } @Test public void testBiz1AddCustomerSuccess(){ //1. Prepare AddCustomerCmd addCustomerCmd = new AddCustomerCmd(); CustomerDTO customerDTO = new CustomerDTO(); customerDTO.setCompanyName("jingdong"); customerDTO.setSource(Constants.SOURCE_RFQ); customerDTO.setCustomerType(CustomerType.IMPORTANT); addCustomerCmd.setCustomerDTO(customerDTO); BizScenario scenario = BizScenario.valueOf(Constants.BIZ_1); addCustomerCmd.setBizScenario(scenario); //2. Execute Response response = customerService.addCustomer(addCustomerCmd); //3. Expect Success Assertions.assertTrue(response.isSuccess()); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/MultiCoordinateTests.java ================================================ package com.alibaba.cola.extension; import com.alibaba.cola.extension.customer.app.extensionpoint.StatusNameConvertorExtPt; import jakarta.annotation.Resource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; /** * 多坐标测试 * * @author wangguoqiang wrote on 2022/10/10 14:54 * @version 1.0 */ @SpringBootTest(classes = Application.class) public class MultiCoordinateTests { @Resource private ExtensionExecutor extensionExecutor; @Test public void testMultiCoordinate() { BizScenario bizScenario1 = BizScenario.valueOf("Samsung", "order", "scenario1"); BizScenario bizScenario2 = BizScenario.valueOf("Samsung", "order", "scenario2"); BizScenario bizScenario3 = BizScenario.valueOf("Samsung", "parts", "scenario1"); BizScenario bizScenario4 = BizScenario.valueOf("Samsung", "parts", "scenario2"); BizScenario bizScenario5 = BizScenario.valueOf("Motorola", "order", "scenario1"); BizScenario bizScenario6 = BizScenario.valueOf("Motorola", "order", "scenario2"); BizScenario bizScenario7 = BizScenario.valueOf("Motorola", "parts", "scenario1"); BizScenario bizScenario8 = BizScenario.valueOf("Motorola", "parts", "scenario2"); String name1 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario1, pt -> pt.statusNameConvertor(1)); String name2 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario2, pt -> pt.statusNameConvertor(2)); String name3 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario3, pt -> pt.statusNameConvertor(3)); String name4 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario4, pt -> pt.statusNameConvertor(4)); String name5 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario5, pt -> pt.statusNameConvertor(5)); String name6 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario6, pt -> pt.statusNameConvertor(6)); String name7 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario7, pt -> pt.statusNameConvertor(7)); String name8 = extensionExecutor.execute(StatusNameConvertorExtPt.class, bizScenario8, pt -> pt.statusNameConvertor(8)); Assertions.assertEquals("one",name1); Assertions.assertEquals("two",name2); Assertions.assertEquals("three",name3); Assertions.assertEquals("four",name4); Assertions.assertEquals("five",name5); Assertions.assertEquals("six",name6); Assertions.assertEquals("seven",name7); Assertions.assertEquals("eight",name8); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/AddCustomerCmdExe.java ================================================ package com.alibaba.cola.extension.customer.app; import com.alibaba.cola.dto.Response; import com.alibaba.cola.extension.ExtensionExecutor; import com.alibaba.cola.extension.customer.app.extensionpoint.AddCustomerValidatorExtPt; import com.alibaba.cola.extension.customer.app.extensionpoint.CustomerConvertorExtPt; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.domain.CustomerEntity; import com.alibaba.cola.extension.customer.infrastructure.DomainEventPublisher; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * AddCustomerCmdExe * * @author Frank Zhang 2018-01-06 7:48 PM */ @Component public class AddCustomerCmdExe { private Logger logger = LoggerFactory.getLogger(AddCustomerCmd.class); @Resource private ExtensionExecutor extensionExecutor; @Resource private DomainEventPublisher domainEventPublisher; public Response execute(AddCustomerCmd cmd) { logger.info("Start processing command:" + cmd); //validation extensionExecutor.executeVoid(AddCustomerValidatorExtPt.class, cmd.getBizScenario(), extension -> extension.validate(cmd)); //Convert CO to Entity CustomerEntity customerEntity = extensionExecutor.execute(CustomerConvertorExtPt.class, cmd.getBizScenario(), extension -> extension.clientToEntity(cmd)); //Call Domain Entity for business logic processing logger.info("Call Domain Entity for business logic processing..."+customerEntity); customerEntity.addNewCustomer(); //domainEventPublisher.publish(new CustomerCreatedEvent()); logger.info("End processing command:" + cmd); return Response.buildSuccess(); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/CustomerCreatedEventHandler.java ================================================ package com.alibaba.cola.extension.customer.app; import com.alibaba.cola.dto.Response; import com.alibaba.cola.extension.customer.client.CustomerCreatedEvent; /** * CustomerCreatedEventHandler * * @author Frank Zhang * @date 2020-06-22 7:00 PM */ public class CustomerCreatedEventHandler { public Response execute(CustomerCreatedEvent customerCreatedEvent) { System.out.println("customerCreatedEvent processed"); return null; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/CustomerServiceImpl.java ================================================ package com.alibaba.cola.extension.customer.app; import com.alibaba.cola.dto.Response; import com.alibaba.cola.dto.SingleResponse; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.CustomerDTO; import com.alibaba.cola.extension.customer.client.CustomerServiceI; import com.alibaba.cola.extension.customer.client.GetOneCustomerQry; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; /** * CustomerServiceImpl * * @author Frank Zhang 2018-01-06 7:40 PM */ @Service public class CustomerServiceImpl implements CustomerServiceI { @Resource private AddCustomerCmdExe addCustomerCmdExe; @Resource private GetOneCustomerQryExe getOneCustomerQryExe; @Override public Response addCustomer(AddCustomerCmd addCustomerCmd) { return addCustomerCmdExe.execute(addCustomerCmd); } @Override public SingleResponse getCustomer(GetOneCustomerQry getOneCustomerQry) { return getOneCustomerQryExe.execute(getOneCustomerQry); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/GetOneCustomerQryExe.java ================================================ package com.alibaba.cola.extension.customer.app; import com.alibaba.cola.dto.SingleResponse; import com.alibaba.cola.extension.customer.client.GetOneCustomerQry; import org.springframework.stereotype.Component; /** * GetOneCustomerQryExe * * @author Frank Zhang * @date 2020-06-22 5:12 PM */ @Component public class GetOneCustomerQryExe { public SingleResponse execute(GetOneCustomerQry getOneCustomerQry){ return null; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/AddCustomerBiz1UseCase1Scenario1Validator.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.app.extensionpoint.AddCustomerValidatorExtPt; /** * AddCustomerBiz1UseCase1Scenario1Validator * * @author Frank Zhang * @date 2020-08-20 1:01 PM */ @Extension(bizId = Constants.BIZ_1, useCase = Constants.USE_CASE_1, scenario = Constants.SCENARIO_1) public class AddCustomerBiz1UseCase1Scenario1Validator implements AddCustomerValidatorExtPt { public void validate(AddCustomerCmd addCustomerCmd) { System.out.println("Do validation for Biz_One's Use_Case_One's Scenario_One"); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/AddCustomerBiz1UseCase1Validator.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.app.extensionpoint.AddCustomerValidatorExtPt; /** * AddCustomerBiz1UseCase1Validator * * @author Frank Zhang * @date 2020-08-20 12:58 PM */ @Extension(bizId = Constants.BIZ_1, useCase = Constants.USE_CASE_1) public class AddCustomerBiz1UseCase1Validator implements AddCustomerValidatorExtPt { public void validate(AddCustomerCmd addCustomerCmd) { System.out.println("Do validation for Biz_One's Use_Case_One"); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/AddCustomerBizOneValidator.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.exception.BizException; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.domain.CustomerType; import com.alibaba.cola.extension.customer.app.extensionpoint.AddCustomerValidatorExtPt; /** * AddCustomerBizOneValidator * * @author Frank Zhang * @date 2018-01-07 1:31 AM */ @Extension(bizId = Constants.BIZ_1) public class AddCustomerBizOneValidator implements AddCustomerValidatorExtPt { public void validate(AddCustomerCmd addCustomerCmd) { //For BIZ TWO CustomerTYpe could not be VIP if(CustomerType.VIP == addCustomerCmd.getCustomerDTO().getCustomerType()) throw new BizException("Customer Type could not be VIP for Biz One"); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/AddCustomerBizTwoValidator.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.exception.BizException; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.app.extensionpoint.AddCustomerValidatorExtPt; /** * AddCustomerBizTwoValidator * * @author Frank Zhang * @date 2018-01-07 1:31 AM */ @Extension(bizId = Constants.BIZ_2) public class AddCustomerBizTwoValidator implements AddCustomerValidatorExtPt { public void validate(AddCustomerCmd addCustomerCmd) { //For BIZ TWO CustomerTYpe could not be null if (addCustomerCmd.getCustomerDTO().getCustomerType() == null) throw new BizException("CustomerType could not be null"); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/CustomerBizOneConvertorExt.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.app.extensionpoint.CustomerConvertorExtPt; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.client.CustomerDTO; import com.alibaba.cola.extension.customer.domain.CustomerEntity; import com.alibaba.cola.extension.customer.domain.SourceType; import org.springframework.beans.factory.annotation.Autowired; /** * CustomerBizOneConvertorExt * * @author Frank Zhang * @date 2018-01-07 3:05 AM */ @Extension(bizId = Constants.BIZ_1) public class CustomerBizOneConvertorExt implements CustomerConvertorExtPt { @Autowired private CustomerConvertor customerConvertor;//Composite basic convertor to do basic conversion @Override public CustomerEntity clientToEntity(AddCustomerCmd addCustomerCmd){ CustomerEntity customerEntity = customerConvertor.clientToEntity(addCustomerCmd); CustomerDTO customerDTO =addCustomerCmd.getCustomerDTO(); //In this business, AD and RFQ are regarded as different source if(Constants.SOURCE_AD.equals(customerDTO.getSource())) { customerEntity.setSourceType(SourceType.AD); } if (Constants.SOURCE_RFQ.equals(customerDTO.getSource())){ customerEntity.setSourceType(SourceType.RFQ); } return customerEntity; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/CustomerBizTwoConvertorExt.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.app.extensionpoint.CustomerConvertorExtPt; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.domain.CustomerEntity; import com.alibaba.cola.extension.customer.domain.SourceType; import org.springframework.beans.factory.annotation.Autowired; /** * CustomerBizTwoConvertorExt * * @author Frank Zhang * @date 2018-01-07 3:05 AM */ @Extension(bizId = Constants.BIZ_2) public class CustomerBizTwoConvertorExt implements CustomerConvertorExtPt { @Autowired private CustomerConvertor customerConvertor;//Composite basic convertor to do basic conversion @Override public CustomerEntity clientToEntity(AddCustomerCmd addCustomerCmd){ CustomerEntity customerEntity = customerConvertor.clientToEntity(addCustomerCmd); //In this business, if customers from RFQ and Advertisement are both regarded as Advertisement if(Constants.SOURCE_AD.equals(addCustomerCmd.getCustomerDTO().getSource()) || Constants.SOURCE_RFQ.equals(addCustomerCmd.getCustomerDTO().getSource())) { customerEntity.setSourceType(SourceType.AD); } return customerEntity; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/CustomerConvertor.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.domain.ApplicationContextHelper; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.client.CustomerDTO; import com.alibaba.cola.extension.customer.domain.CustomerEntity; import org.springframework.stereotype.Component; /** * CustomerConvertor * * @author Frank Zhang * @date 2018-01-07 3:08 AM */ @Component public class CustomerConvertor{ public CustomerEntity clientToEntity(Object clientObject){ AddCustomerCmd addCustomerCmd = (AddCustomerCmd)clientObject; CustomerDTO customerDTO =addCustomerCmd.getCustomerDTO(); CustomerEntity customerEntity = (CustomerEntity) ApplicationContextHelper.getBean(CustomerEntity.class); customerEntity.setCompanyName(customerDTO.getCompanyName()); customerEntity.setCustomerType(customerDTO.getCustomerType()); customerEntity.setBizScenario(addCustomerCmd.getBizScenario()); return customerEntity; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extension/StatusNameConvertorExt.java ================================================ package com.alibaba.cola.extension.customer.app.extension; import com.alibaba.cola.extension.Extensions; import com.alibaba.cola.extension.customer.app.extensionpoint.StatusNameConvertorExtPt; import java.util.HashMap; /** * @author wangguoqiang wrote on 2022/10/10 14:39 * @version 1.0 */ @Extensions(bizId = {"Samsung", "Motorola"}, useCase = {"order", "parts"}, scenario = {"scenario1", "scenario2"}) public class StatusNameConvertorExt implements StatusNameConvertorExtPt { /** * In real business scenarios, the business status is usually represented by numbers. * In some places, the number needs to be mapped to the real status name. For ease of understanding here, * the status name is the number in English */ HashMap map = new HashMap() {{ put(1, "one"); put(2, "two"); put(3, "three"); put(4, "four"); put(5, "five"); put(6, "six"); put(7, "seven"); put(8, "eight"); }}; @Override public String statusNameConvertor(Integer statusCode) { return map.getOrDefault(statusCode, "unknow"); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extensionpoint/AddCustomerValidatorExtPt.java ================================================ package com.alibaba.cola.extension.customer.app.extensionpoint; import com.alibaba.cola.extension.ExtensionPointI; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; /** * AddCustomerValidatorExtPt * * @author Frank Zhang * @date 2018-01-07 1:27 AM */ public interface AddCustomerValidatorExtPt extends ExtensionPointI { public void validate(AddCustomerCmd addCustomerCmd); } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extensionpoint/CustomerConvertorExtPt.java ================================================ package com.alibaba.cola.extension.customer.app.extensionpoint; import com.alibaba.cola.extension.ExtensionPointI; import com.alibaba.cola.extension.customer.client.AddCustomerCmd; import com.alibaba.cola.extension.customer.domain.CustomerEntity; /** * CustomerConvertorExtPt * * @author Frank Zhang * @date 2018-01-07 2:37 AM */ public interface CustomerConvertorExtPt extends ExtensionPointI { public CustomerEntity clientToEntity(AddCustomerCmd addCustomerCmd); } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/app/extensionpoint/StatusNameConvertorExtPt.java ================================================ package com.alibaba.cola.extension.customer.app.extensionpoint; import com.alibaba.cola.extension.ExtensionPointI; /** * This extension point supports state transition operations of multiple manufacturers and different business lines * * @author wangguoqiang wrote on 2022/10/10 14:37 * @version 1.0 */ public interface StatusNameConvertorExtPt extends ExtensionPointI { String statusNameConvertor(Integer statusCode); } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/client/AddCustomerCmd.java ================================================ package com.alibaba.cola.extension.customer.client; import com.alibaba.cola.dto.Command; import com.alibaba.cola.extension.BizScenario; import lombok.Data; /** * AddCustomerCmd * * @author Frank Zhang 2018-01-06 7:28 PM */ @Data public class AddCustomerCmd extends Command { private CustomerDTO customerDTO; private String biz; private BizScenario bizScenario; } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/client/Constants.java ================================================ package com.alibaba.cola.extension.customer.client; /** * Constants * * @author Frank Zhang * @date 2018-01-07 1:23 AM */ public class Constants { public static final String BIZ_1 = "BIZ_ONE"; public static final String USE_CASE_1 = "USE_CASE_ONE"; public static final String SCENARIO_1 = "SCENARIO_ONE"; public static final String BIZ_2 = "BIZ_TWO"; public static final String SOURCE_AD = "Advertisement"; public static final String SOURCE_WB = "Web site"; public static final String SOURCE_RFQ = "Request For Quota"; public static final String SOURCE_MARKETING = "Marketing"; public static final String SOURCE_OFFLINE = "Off Line"; } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/client/CustomerCreatedEvent.java ================================================ package com.alibaba.cola.extension.customer.client; /** * CustomerCreatedEvent * * @author Frank Zhang * @date 2020-06-22 6:59 PM */ public class CustomerCreatedEvent { } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/client/CustomerDTO.java ================================================ package com.alibaba.cola.extension.customer.client; import com.alibaba.cola.dto.DTO; import com.alibaba.cola.extension.customer.domain.CustomerType; /** * CustomerDTO * * @author Frank Zhang 2018-01-06 7:30 PM */ public class CustomerDTO extends DTO { private String companyName; private String source; //advertisement, p4p, RFQ, ATM private CustomerType customerType; //potential, intentional, important, vip public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public CustomerType getCustomerType() { return customerType; } public void setCustomerType(CustomerType customerType) { this.customerType = customerType; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/client/CustomerServiceI.java ================================================ package com.alibaba.cola.extension.customer.client; import com.alibaba.cola.dto.Response; import com.alibaba.cola.dto.SingleResponse; /** * CustomerServiceI * * @author Frank Zhang 2018-01-06 7:24 PM */ public interface CustomerServiceI { public Response addCustomer(AddCustomerCmd addCustomerCmd); public SingleResponse getCustomer(GetOneCustomerQry getOneCustomerQry); } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/client/GetOneCustomerQry.java ================================================ package com.alibaba.cola.extension.customer.client; import com.alibaba.cola.dto.Query; /** * GetOneCustomerQry * * @author Frank Zhang 2018-01-06 7:38 PM */ public class GetOneCustomerQry extends Query{ private long customerId; private String companyName; public long getCustomerId() { return customerId; } public void setCustomerId(long customerId) { this.customerId = customerId; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/domain/CustomerEntity.java ================================================ package com.alibaba.cola.extension.customer.domain; import com.alibaba.cola.domain.Entity; import com.alibaba.cola.extension.BizScenario; import com.alibaba.cola.extension.ExtensionExecutor; import com.alibaba.cola.extension.customer.domain.rule.CustomerRuleExtPt; import com.alibaba.cola.extension.customer.infrastructure.CustomerRepository; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; /** * Customer Entity * * @author Frank Zhang * @date 2018-01-07 2:38 AM */ @Entity @Data public class CustomerEntity { private String companyName; private SourceType sourceType; private CustomerType customerType; private BizScenario bizScenario; @Autowired private CustomerRepository customerRepository; @Autowired private ExtensionExecutor extensionExecutor; public CustomerEntity() { } public void addNewCustomer() { //Add customer policy extensionExecutor.execute(CustomerRuleExtPt.class,this.getBizScenario(), extension -> extension.addCustomerCheck(this)); //Persist customer customerRepository.persist(this); } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } public SourceType getSourceType() { return sourceType; } public void setSourceType(SourceType sourceType) { this.sourceType = sourceType; } public CustomerType getCustomerType() { return customerType; } public void setCustomerType(CustomerType customerType) { this.customerType = customerType; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/domain/CustomerType.java ================================================ package com.alibaba.cola.extension.customer.domain; /** * CustomerType * * @author Frank Zhang 2018-01-06 7:35 PM */ public enum CustomerType { POTENTIAL, INTENTIONAL, IMPORTANT, VIP; } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/domain/SourceType.java ================================================ package com.alibaba.cola.extension.customer.domain; /** * SourceType * * @author Frank Zhang * @date 2018-01-07 3:02 AM */ public enum SourceType { AD, //Advertisement 广告 WB, // Web site 网站 RFQ; // Request For Quota 询盘 } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/domain/rule/CustomerBizOneRuleExt.java ================================================ package com.alibaba.cola.extension.customer.domain.rule; import com.alibaba.cola.exception.BizException; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.domain.CustomerEntity; import com.alibaba.cola.extension.customer.domain.SourceType; /** * CustomerBizOneRuleExt * * @author Frank Zhang * @date 2018-01-07 12:10 PM */ @Extension(bizId = Constants.BIZ_1) public class CustomerBizOneRuleExt implements CustomerRuleExtPt{ @Override public boolean addCustomerCheck(CustomerEntity customerEntity) { if(SourceType.AD == customerEntity.getSourceType()){ throw new BizException("Sorry, Customer from advertisement can not be added in this period"); } return true; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/domain/rule/CustomerBizTwoRuleExt.java ================================================ package com.alibaba.cola.extension.customer.domain.rule; import com.alibaba.cola.extension.Extension; import com.alibaba.cola.extension.customer.client.Constants; import com.alibaba.cola.extension.customer.domain.CustomerEntity; /** * CustomerBizTwoRuleExt * * @author Frank Zhang * @date 2018-01-07 12:10 PM */ @Extension(bizId = Constants.BIZ_2) public class CustomerBizTwoRuleExt implements CustomerRuleExtPt{ @Override public boolean addCustomerCheck(CustomerEntity customerEntity) { //Any Customer can be added return true; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/domain/rule/CustomerRuleExtPt.java ================================================ package com.alibaba.cola.extension.customer.domain.rule; import com.alibaba.cola.extension.ExtensionPointI; import com.alibaba.cola.extension.customer.domain.CustomerEntity; /** * CustomerRuleExtPt * * @author Frank Zhang * @date 2018-01-07 12:03 PM */ public interface CustomerRuleExtPt extends ExtensionPointI { //Different business check for different biz public boolean addCustomerCheck(CustomerEntity customerEntity); //Different upgrade policy for different biz default public void customerUpgradePolicy(CustomerEntity customerEntity){ //Nothing special } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/infrastructure/CustomerDO.java ================================================ package com.alibaba.cola.extension.customer.infrastructure; /** * CustomerDO * * @author Frank Zhang * @date 2018-01-08 1:45 PM */ public class CustomerDO implements java.io.Serializable { private String customerId; private String memberId; private String globalId; private String companyName; private String source; private String companyType; public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } public String getMemberId() { return memberId; } public void setMemberId(String memberId) { this.memberId = memberId; } public String getGlobalId() { return globalId; } public void setGlobalId(String globalId) { this.globalId = globalId; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public String getCompanyType() { return companyType; } public void setCompanyType(String companyType) { this.companyType = companyType; } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/infrastructure/CustomerRepository.java ================================================ package com.alibaba.cola.extension.customer.infrastructure; import com.alibaba.cola.extension.customer.domain.CustomerEntity; import org.springframework.stereotype.Repository; /** * CustomerRepository * * @author Frank Zhang * @date 2018-01-07 11:59 AM */ @Repository public class CustomerRepository { public void persist(CustomerEntity customerEntity){ System.out.println("Persist customer to DB : "+ customerEntity); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/customer/infrastructure/DomainEventPublisher.java ================================================ package com.alibaba.cola.extension.customer.infrastructure; import org.springframework.stereotype.Component; /** * DomainEventPublisher * * @author Frank Zhang * @date 2020-06-22 7:04 PM */ @Component public class DomainEventPublisher { /* @Resource private EventBusI eventBus; public void publish(DomainEventI domainEvent) { eventBus.fire(domainEvent); }*/ } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/register/CglibProxyFactory.java ================================================ package com.alibaba.cola.extension.register; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; public class CglibProxyFactory { public static T createProxy(T object) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(object.getClass()); enhancer.setCallback(new ProxyCallback(object)); return (T) enhancer.create(); } public static class ProxyCallback implements MethodInterceptor { private Object target; public ProxyCallback(Object target) { this.target = target; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("ProxyObject::before"); Object object = proxy.invoke(target, args); System.out.println("ProxyObject::after"); return object; } } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/register/ExtensionRegisterTest.java ================================================ package com.alibaba.cola.extension.register; import com.alibaba.cola.extension.BizScenario; import com.alibaba.cola.extension.ExtensionException; import com.alibaba.cola.extension.ExtensionExecutor; import com.alibaba.cola.extension.Application; import jakarta.annotation.Resource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = Application.class) public class ExtensionRegisterTest { @Resource private ExtensionRegister register; @Resource private ExtensionExecutor executor; @Test public void testDuplicateRegistration() { // expect: //Duplicate registration is not allowed for :ExtensionCoordinate // [extensionPointName=com.alibaba.cola.extension.register.SomeExtPt, bizScenarioUniqueIdentity=A.#defaultUseCase#.#defaultScenario#] Assertions.assertThrows(ExtensionException.class, ()->{ SomeExtPt extA = new SomeExtensionA(); register.doRegistration(extA); executor.executeVoid(SomeExtPt.class, BizScenario.valueOf("A"), SomeExtPt::doSomeThing); }); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/register/SomeExtPt.java ================================================ package com.alibaba.cola.extension.register; import com.alibaba.cola.extension.ExtensionPointI; public interface SomeExtPt extends ExtensionPointI { public void doSomeThing(); } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/register/SomeExtensionA.java ================================================ package com.alibaba.cola.extension.register; import com.alibaba.cola.extension.Extension; import org.springframework.stereotype.Component; @Extension(bizId = "A") @Component public class SomeExtensionA implements SomeExtPt { @Override public void doSomeThing() { System.out.println("SomeExtensionA::doSomething"); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/java/com/alibaba/cola/extension/register/SomeExtensionB.java ================================================ package com.alibaba.cola.extension.register; import com.alibaba.cola.extension.Extension; import org.springframework.stereotype.Component; @Extension(bizId = "B") @Component public class SomeExtensionB implements SomeExtPt { @Override public void doSomeThing() { System.out.println("SomeExtensionB::doSomething"); } } ================================================ FILE: cola-components/cola-component-extension-starter/src/test/resources/application.properties ================================================ ================================================ FILE: cola-components/cola-component-extension-starter/src/test/resources/logback-test.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/cola-component-job/README.md ================================================ # 主要功能 可持久化、分布式任务管理组件: 1. 可同步,可异步 2. 可持久化(redis,数据库) 3. Job可重复执行,Step失败可回滚,Step可断点继续 4. 支持批量任务 # 核心领域概念 6. JobLauncher:是任务启动器,通过它来启动任务,可以看做是程序的入口。 7. BatchJobLauncher: 批量任务的处理入口 8. BatchJob:批量任务,包含了多个JobInstance,一个JobInstance包含一个Job单例和一个ExecutionContext对象 9. ExecutionContext: 任务执行的上下文,主要是用来承载用户传递的参数,以及不同Job、step之间传递参数 10. Job: 代表着一个具体的任务。 11. JobExecution: 代表一次具体的任务执行,可持久化,会管理任务执行状态。 12. Step: 代表着一个具体的步骤,一个Job可以包含多个Step。在实际业务场景中,可能一个任务很复杂,这个时候可以将任务 拆分成多个step,分别对这些step 进行管理(将一个复杂任务简单化)。 13. StepExecution: 代表一个具体的执行步骤,可持久化,会管理步骤的执行状态。 14. JobRepository:批处理框架执行过程中的上下文(元数据),有三种实现,1)通过内存来管理,2)通过Redis持久化,3)通过数据库持久化。 # 详细设计解析 https://blog.csdn.net/significantfrank/article/details/145314072 # 如何使用 1. 引入依赖cola-component-job依赖。注意:为了减少冲突,依赖的存储redis和database均为provided,需要使用方自己提供,SpringBoot版本最好在3.2.0以上 2. 在SpringBoot的启动类上添加@EnableColaJob注解,该注解会扫描注册需要的bean,以及负责初始化JobRepository 3. 默认的数据库初始化脚本是schema-mysql.sql。如果使用其他数据库,可通过application.yml配置文件中的cola.job.database.ddl-location属性来指定脚本路径 ================================================ FILE: cola-components/cola-component-job/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-component-job jar ${project.artifactId}:${project.version} ${project.artifactId} 1.0.0-SNAPSHOT 17 17 UTF-8 3.2.9 org.projectlombok lombok 1.18.34 provided org.springframework.boot spring-boot-starter-data-redis ${spring.boot.version} provided org.springframework.boot spring-boot-starter-web ${spring.boot.version} provided org.springframework.boot spring-boot-starter-data-jpa ${spring.boot.version} mysql mysql-connector-java 8.0.33 test org.junit.jupiter junit-jupiter-engine 5.10.2 test com.alibaba.cola cola-component-unittest 5.0.0 org.springframework.kafka spring-kafka org.springframework.kafka spring-kafka-test org.postgresql postgresql test com.alibaba.cola cola-component-test-container 5.0.0 test org.apache.maven.plugins maven-compiler-plugin 3.8.1 17 17 ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/BatchJobLauncher.java ================================================ package com.alibaba.cola.job; import com.alibaba.cola.job.model.*; import com.alibaba.cola.job.repository.JobRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; import java.time.LocalDateTime; import java.util.Set; /** * 批量任务只支持单个Job的异步执行 */ @Slf4j public class BatchJobLauncher { public static BatchJobExecution execute(BatchJob batchJob) { JobRepository jobRepository = batchJob.getJobRepository(); BatchJobExecution batchJobExecution = new BatchJobExecution(UuidGenerator.nextBatchJobId()); for (JobInstance jobInstance : batchJob.getJobInstances()) { jobInstance.getExecutionContext().setBatchJobId(batchJobExecution.getBatchJobId()); String jobExecutionId = JobLauncher.executeAsync(jobInstance.getJob(), jobInstance.getExecutionContext(), batchJob.getExecutorService()); batchJobExecution.addJobExecution(jobExecutionId); } batchJobExecution.setStartTime(LocalDateTime.now()); batchJobExecution.setStatus(ExecutionStatus.STARTED); batchJobExecution.setJobType(batchJob.getJobType()); jobRepository.saveBatchJobExecution(batchJobExecution); log.info("[BatchJob] started: {}", batchJobExecution); return batchJobExecution; } public static boolean checkAndRefresh(BatchJob batchJob, String batchJobId) { JobRepository jobRepository = batchJob.getJobRepository(); BatchJobExecution batchJobExecution = jobRepository.getBatchJobExecutionByBatchJobId(batchJobId); Set jobIds = batchJobExecution.getJobExecutionResults().keySet(); if (CollectionUtils.isEmpty(jobIds)) { return false; } // 如果已经完成,就不用费时查看每个job的状态了 if (batchJobExecution.isBatchJobCompleted()) { return true; } for (String jobId : jobIds) { JobExecution jobExecution = jobRepository.getJobExecutionByJobId(jobId); batchJobExecution.put(jobExecution.getJobId(), jobExecution.getExecutionStatus()); } boolean isChildrenCompleted = batchJobExecution.isChildrenJobsCompleted(); if (isChildrenCompleted) { // 所有子任务都已完成 batchJobExecution.setStatus(ExecutionStatus.COMPLETED); batchJobExecution.setEndTime(LocalDateTime.now()); } else if (batchJobExecution.isFailed()) { batchJobExecution.setStatus(ExecutionStatus.FAILED); } else { batchJobExecution.setStatus(ExecutionStatus.UNKNOWN); } jobRepository.updateBatchJobExecution(batchJobExecution); return isChildrenCompleted; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/ExecutionContext.java ================================================ package com.alibaba.cola.job; import com.alibaba.cola.job.repository.JsonUtil; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.ToString; import org.springframework.lang.Nullable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @ToString public class ExecutionContext { /** * 这是任务执行的主参数,通常是client自定义的类 */ private T param; @JsonIgnore private Object rawParam; /** * 为了灵活性,主参数之外的数据,放在extensions */ @JsonProperty("extensions") private final Map extensions; /** * 这里是暂存数据到context的,不会进行持久化 */ @JsonIgnore private final Map temp; /** * 当执行批量任务的时候,需要传入batchJobId */ @JsonIgnore private String batchJobId; /** * 当重复执行一个Job的时候,需要传入上一次执行的jobExecutionId */ @JsonIgnore private String jobId; public T getParam() { return (T) param; } @SuppressWarnings("unchecked") public T getParam(Class tClass) { if (rawParam != null) { return (T) rawParam; } if (param.getClass().equals(tClass)) { rawParam = param; } rawParam = JsonUtil.decode(JsonUtil.encode(param), tClass); return (T) rawParam; } public void setParam(T param) { this.param = param; } public String getJobId() { return jobId; } public void setJobId(String jobId) { this.jobId = jobId; } public String getBatchJobId() { return batchJobId; } public void setBatchJobId(String batchJobId) { this.batchJobId = batchJobId; } public ExecutionContext() { this.extensions = new ConcurrentHashMap(); this.temp = new ConcurrentHashMap<>(); } public ExecutionContext(String jobId) { this(); this.jobId = jobId; } public ExecutionContext(Map extensions) { this.extensions = new ConcurrentHashMap(extensions); this.temp = new ConcurrentHashMap<>(); } public void putString(String key, @Nullable String value) { this.put(key, value); } public void putTemp(String key, Object value){ this.temp.put(key, value); } public Object getTemp(String key){ return this.temp.get(key); } public String getString(String key) { return (String) this.get(key, String.class); } public String getString(String key, String defaultString) { return !this.containsKey(key) ? defaultString : this.getString(key); } public void put(String key, @Nullable Object value) { if (value != null) { this.extensions.put(key, value); } else { this.extensions.remove(key); } } @Nullable public Object get(String key) { return this.extensions.get(key); } @Nullable public V get(String key, Class type) { Object value = this.extensions.get(key); return value == null ? null : this.get(key, type, null); } @Nullable public V get(String key, Class type, @Nullable V defaultValue) { Object value = this.extensions.get(key); if (value == null) { return defaultValue; } else if (!type.isInstance(value)) { throw new ClassCastException( "Value for key=[" + key + "] is not of type: [" + type + "], it is [(" + value.getClass() + ")" + value + "]"); } else { return type.cast(value); } } public boolean containsKey(String key) { return this.extensions.containsKey(key); } @Nullable public Object remove(String key) { return this.extensions.remove(key); } public boolean containsValue(Object value) { return this.extensions.containsValue(value); } public ExecutionContext fromJsonString(String jsonString) { return JsonUtil.decode(jsonString, ExecutionContext.class); } @Override public String toString() { return "ExecutionContext{" + "param=" + param + ", extensions=" + extensions + '}'; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/JobBuilderFactory.java ================================================ package com.alibaba.cola.job; import com.alibaba.cola.job.model.Job; import com.alibaba.cola.job.model.Step; import com.alibaba.cola.job.repository.JobRepository; public class JobBuilderFactory { public static JobBuilder create() { return new JobBuilder(); } public static class JobBuilder { private final Job job = new Job(); public JobBuilder addStep(Step step) { step.setJob(job); job.getSteps().add(step); return this; } public JobBuilder name(String name) { job.setName(name); return this; } public JobBuilder needRollback(boolean needRollback) { job.setNeedRollback(needRollback); return this; } public JobBuilder isAsync(boolean isAsync) { job.setAsync(isAsync); return this; } public JobBuilder jobRepository(JobRepository jobRepository) { job.setJobRepository(jobRepository); return this; } public Job build(String name, JobRepository jobRepository) { job.setName(name); job.setJobRepository(jobRepository); return job; } public Job build() { return job; } } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/JobException.java ================================================ package com.alibaba.cola.job; public class JobException extends RuntimeException{ public JobException(String message){ super(message); } public JobException(Exception e){ super(e); } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/JobLauncher.java ================================================ package com.alibaba.cola.job; import com.alibaba.cola.job.model.ExecutionStatus; import com.alibaba.cola.job.model.Job; import com.alibaba.cola.job.model.JobExecution; import com.alibaba.cola.job.repository.JobRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Slf4j public class JobLauncher { private static final ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool(); /** * Execute job in sync mode * * @param job * @return */ public static JobExecution executeSync(Job job) { return executeSync(job, new ExecutionContext()); } /** * Execute job in sync mode * * @param job , the job to be executed * @param executionContext, the context to be used in execution runtime * @return */ public static JobExecution executeSync(Job job, ExecutionContext executionContext) { // 1. 创建任务 JobExecution jobExecution = createJobExecution(job, executionContext); // 2. 执行任务 job.execute(jobExecution); return jobExecution; } /** * Execute job in Async mode * * @param job * @return jobExecutionId instantly */ public static String executeAsync(Job job) { return executeAsync(job, new ExecutionContext(), null); } public static String executeAsync(Job job, ExecutorService executorService) { return executeAsync(job, new ExecutionContext(), executorService); } public static String executeAsync(Job job, ExecutionContext executionContext) { return executeAsync(job, executionContext, null); } public static String executeAsync(Job job, ExecutionContext executionContext, ExecutorService executorService) { // 1. 创建任务 final JobExecution jobExecution = createJobExecution(job, executionContext); // 2. 执行任务 if (executorService != null) { // 使用自定义的线程池执行任务 executorService.submit(() -> { job.execute(jobExecution); }); } else { // 使用默认的线程池执行任务 DEFAULT_EXECUTOR_SERVICE.submit(() -> { job.execute(jobExecution); }); } // 3. 立即返回任务执行id,任务异步执行 return jobExecution.getJobId(); } private static JobExecution createJobExecution(Job job, ExecutionContext executionContext) { JobRepository jobRepository = job.getJobRepository(); JobExecution jobExecution = null; if (StringUtils.hasLength(executionContext.getJobId())) { jobExecution = jobRepository.getJobExecutionByJobId(executionContext.getJobId()); } if (jobExecution == null) { // 全新的任务 jobExecution = new JobExecution(); jobExecution.setJobId(UuidGenerator.nextJobId()); jobExecution.setJobDef(job); jobExecution.setExecutionStatus(ExecutionStatus.CREATED); jobExecution.setExecutionContext(executionContext); jobExecution.setBatchJobId(executionContext.getBatchJobId()); log.info("[Job] created: {}", jobExecution); jobRepository.saveJobExecution(jobExecution); } else { // 已经存在的任务 jobExecution.setJobDef(job); jobExecution.setStepExecutionList(jobRepository.listStepExecutions(jobExecution.getJobId())); } return jobExecution; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/UuidGenerator.java ================================================ package com.alibaba.cola.job; import java.security.SecureRandom; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Random; public class UuidGenerator { private static final char[] CHILD_ALPHABET = "abcdefghijklmnopqrstuvwxyz".toCharArray(); private static final char[] PARENT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss-"); private static int CUSTOM_LENGTH = 8; /** * job id, 时间+随机数,样例:20250317T141750-dgkyfjlm * @return */ public static String nextJobId() { LocalDateTime currentDateTime = LocalDateTime.now(); String formattedDateTime = currentDateTime.format(FORMATTER); return formattedDateTime + NanoIdUtils.randomNanoId(NanoIdUtils.DEFAULT_NUMBER_GENERATOR, CHILD_ALPHABET, CUSTOM_LENGTH); } /** * job id, 时间+随机数+jobName,样例:20250317T141750-dgkyfjlm-myJobName * @return */ public static String nextJobId(String jobName){ return nextJobId()+"-"+jobName; } /** * batch job id, 时间+大写之母,样例: 20250317T141750-ADBAMLAU * @return */ public static String nextBatchJobId() { LocalDateTime currentDateTime = LocalDateTime.now(); String formattedDateTime = currentDateTime.format(FORMATTER); return formattedDateTime + NanoIdUtils.randomNanoId(NanoIdUtils.DEFAULT_NUMBER_GENERATOR, PARENT_ALPHABET, CUSTOM_LENGTH); } /** * 生成随机的NanoId工具内部类 */ public final class NanoIdUtils { public static final SecureRandom DEFAULT_NUMBER_GENERATOR = new SecureRandom(); public static final char[] DEFAULT_ALPHABET = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); public static final int DEFAULT_SIZE = 21; private NanoIdUtils() { } public static String randomNanoId() { return randomNanoId(DEFAULT_NUMBER_GENERATOR, DEFAULT_ALPHABET, 21); } public static String randomNanoId(Random random, char[] alphabet, int size) { if (random == null) { throw new IllegalArgumentException("random cannot be null."); } else if (alphabet == null) { throw new IllegalArgumentException("alphabet cannot be null."); } else if (alphabet.length != 0 && alphabet.length < 256) { if (size <= 0) { throw new IllegalArgumentException("size must be greater than zero."); } else { int mask = (2 << (int)Math.floor(Math.log((double)(alphabet.length - 1)) / Math.log(2.0D))) - 1; int step = (int)Math.ceil(1.6D * (double)mask * (double)size / (double)alphabet.length); StringBuilder idBuilder = new StringBuilder(); while(true) { byte[] bytes = new byte[step]; random.nextBytes(bytes); for(int i = 0; i < step; ++i) { int alphabetIndex = bytes[i] & mask; if (alphabetIndex < alphabet.length) { idBuilder.append(alphabet[alphabetIndex]); if (idBuilder.length() == size) { return idBuilder.toString(); } } } } } } else { throw new IllegalArgumentException("alphabet must contain between 1 and 255 symbols."); } } } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/config/DBAutoConfiguration.java ================================================ package com.alibaba.cola.job.config; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.jdbc.datasource.init.DatabasePopulator; import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import javax.sql.DataSource; import java.util.Objects; @Slf4j @Configuration @EnableJpaRepositories(basePackages = { "com.alibaba.cola.job.repository.db" }) @EntityScan(basePackages = "com.alibaba.cola.job.model") @ConditionalOnProperty(value = "cola.job.repository-type", havingValue = "DB") public class DBAutoConfiguration { @Resource private DataSource dataSource; @Resource private JobProperties jobProperties; @PostConstruct public void init() { log.info("DBAutoConfiguration jobDatabaseProperties: {}", jobProperties.getDatabase()); if(jobProperties == null || jobProperties.getDatabase() == null) { log.warn("DBAutoConfiguration jobProperties or jobProperties.getDatabase() is null, skip init sql"); return; } if (jobProperties.getDatabase().getAutoDdl()) { log.info("DBAutoConfiguration start init sql"); String schema = "schema-mysql.sql"; // 可以通过ddl-location覆盖defaultSchema String customizedSchema = jobProperties.getDatabase().getDdlLocation(); if (Objects.nonNull(customizedSchema) && !customizedSchema.isEmpty()) { schema = customizedSchema; } DatabasePopulator databasePopulator = new ResourceDatabasePopulator( new ClassPathResource(schema)); DatabasePopulatorUtils.execute(databasePopulator, dataSource); } } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/config/EnableColaJob.java ================================================ package com.alibaba.cola.job.config; import org.springframework.context.annotation.Import; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(EnableJobConfiguration.class) @Documented public @interface EnableColaJob { } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/config/EnableJobConfiguration.java ================================================ package com.alibaba.cola.job.config; import com.alibaba.cola.job.JobException; import com.alibaba.cola.job.repository.JobRepository; import com.alibaba.cola.job.repository.JsonUtil; import com.alibaba.cola.job.repository.RepositoryType; import com.alibaba.cola.job.repository.db.DataBaseJobRepository; import com.alibaba.cola.job.repository.memory.MemoryJobRepository; import com.alibaba.cola.job.repository.redis.RedisJobRepository; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; @Slf4j @Import( {JobProperties.class, DBAutoConfiguration.class, RedisConfig.class, JsonUtil.class}) @ComponentScan("com.alibaba.cola.job") public class EnableJobConfiguration { @Resource private JobProperties jobProperties; @Bean public JobRepository jobRepository() { if (jobProperties.getRepositoryType() == null) { jobProperties.setRepositoryType(RepositoryType.MEMORY); } JobRepository jobRepository = switch (jobProperties.getRepositoryType()) { case DB -> new DataBaseJobRepository(); case REDIS -> new RedisJobRepository(); case MEMORY -> new MemoryJobRepository(); default -> throw new JobException("not support repositoryType: " + jobProperties.getRepositoryType()); }; log.info("[cola-job]create JobRepository: " + jobRepository.getClass().getSimpleName()); return jobRepository; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/config/JobProperties.java ================================================ package com.alibaba.cola.job.config; import com.alibaba.cola.job.repository.RepositoryType; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Data @Configuration @ConfigurationProperties(prefix = "cola.job") public class JobProperties { private RepositoryType repositoryType; private DatabaseProperties database; // database配置,数据库配置会自动映射到 cola.job.database 路径下 @Data public static class DatabaseProperties { private Boolean autoDdl; private String ddlLocation; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/config/RedisConfig.java ================================================ package com.alibaba.cola.job.config; import com.alibaba.cola.job.repository.JsonUtil; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @AutoConfigureAfter(EnableJobConfiguration.class) public class RedisConfig { @Bean("jobRedisTemplate") public RedisTemplate jobRedisTemplate(RedisConnectionFactory redisConnectionFactory) { return buildTemplate(redisConnectionFactory, JsonUtil.ObjectMapperFactory.getObjectMapper()); } @Bean("stepRedisTemplate") public RedisTemplate stepRedisTemplate(RedisConnectionFactory redisConnectionFactory) { return buildTemplate(redisConnectionFactory, JsonUtil.ObjectMapperFactory.getObjectMapper()); } @Bean("batchJobRedisTemplate") public RedisTemplate batchJobRedisTemplate(RedisConnectionFactory redisConnectionFactory) { return buildTemplate(redisConnectionFactory, JsonUtil.ObjectMapperFactory.getObjectMapper()); } private RedisTemplate buildTemplate(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>( objectMapper, Object.class); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); return redisTemplate; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/AbstractStep.java ================================================ package com.alibaba.cola.job.model; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; @Data @Slf4j public abstract class AbstractStep implements Step { private Job job; @Override public void setJob(Job job) { this.job = job; } @Override public Job getJob() { return this.job; } @Override public void execute(StepExecution stepExecution) { try { logInfo(stepExecution, "start"); this.doExecute(stepExecution); stepExecution.setExecutionStatus(ExecutionStatus.COMPLETED); } catch (Exception e) { log.error("[job - {}][Step - {}] need rollback: {},execute error: {}", stepExecution.getJobExecution().getJobId(), stepExecution.getStepName(), this.needRollBack(stepExecution), e.getMessage()); stepExecution.setMessage("[execute error]: " + e.getMessage() + "; "); if (this.needRollBack(stepExecution)) { this.internalRollback(stepExecution); } else { stepExecution.setExecutionStatus(ExecutionStatus.FAILED); } throw e; } finally { updateStepExecution(stepExecution); } } @Override public void rollback(StepExecution stepExecution) { try { internalRollback(stepExecution); } finally { updateStepExecution(stepExecution); } } private void updateStepExecution(StepExecution stepExecution) { logInfo(stepExecution, "end, status is [" + stepExecution.getExecutionStatus()+"]"); stepExecution.setEndTime(LocalDateTime.now()); stepExecution.getRepository().updateStepExecution(stepExecution); } private void internalRollback(StepExecution stepExecution) { try { logInfo(stepExecution, "rollback start"); stepExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_STARTED); this.doRollback(stepExecution); stepExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_COMPLETED); logInfo(stepExecution, "rollback end"); } catch (Exception e) { String errMsg = String.format("[Job - %s][Step - %s]Rollback with error: %s", stepExecution.getJobExecution().getJobId(), stepExecution.getStepName(), e.getMessage()); log.error(errMsg, e); stepExecution.setMessage(stepExecution.getMessage() + "[rollback error]: " + e.getMessage()); stepExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_FAILED); throw e; } } public abstract void doExecute(StepExecution stepExecution); public void doRollback(StepExecution stepExecution) { log.info("[job - {}][Step - {}] The rollback content is empty.", stepExecution.getJobExecution().getJobId(), stepExecution.getStepName()); } @Override public boolean needRollBack(StepExecution stepExecution) { return false; } private void logInfo(StepExecution stepExecution, String message) { log.info("[Job - {}][Step - {}] {}", stepExecution.getJobExecution().getJobId(), stepExecution.getStepName(), message); } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/BatchJob.java ================================================ package com.alibaba.cola.job.model; import com.alibaba.cola.job.repository.JobRepository; import lombok.Getter; import lombok.Setter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; /** * 一个批量任务,包含了多个JobInstance */ public class BatchJob { @Getter private final List jobInstances; @Getter private JobRepository jobRepository; @Getter @Setter private String jobType; @Getter private ExecutorService executorService; public BatchJob() { jobInstances = new ArrayList<>(); } public BatchJob jobRepository(JobRepository jobRepository) { this.jobRepository = jobRepository; return this; } public BatchJob executorService(ExecutorService executorService) { this.executorService = executorService; return this; } public BatchJob add(JobInstance jobInstance) { jobInstances.add(jobInstance); return this; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/BatchJobExecution.java ================================================ package com.alibaba.cola.job.model; import com.alibaba.cola.job.repository.JsonUtil; import com.fasterxml.jackson.core.type.TypeReference; import jakarta.persistence.Access; import jakarta.persistence.AccessType; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; import lombok.Data; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @Data @Entity @Access(AccessType.FIELD) public class BatchJobExecution { @Id @Column(name = "batch_job_id") private String batchJobId; @Column(name = "job_type") private String jobType; @Column(name = "execution_status") @Enumerated(EnumType.STRING) private ExecutionStatus status; @Column(name = "job_execution_results") @Convert(converter = ResultsConverter.class) // key: jobExecutionId, value: 执行的结果状态 private Map jobExecutionResults = new HashMap<>(); @Column(name = "start_time") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime startTime; @Column(name = "end_time") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime endTime; public BatchJobExecution() { } public BatchJobExecution(String batchJobId) { this.batchJobId = batchJobId; } public void addJobExecution(String jobExecutionId) { jobExecutionResults.put(jobExecutionId, ExecutionStatus.STARTED); } public void put(String jobExecutionId, ExecutionStatus executionStatus) { jobExecutionResults.put(jobExecutionId, executionStatus); } public boolean isCompleted() { if (status == ExecutionStatus.COMPLETED) { return true; } return isAllJobsCompleted(); } public boolean isBatchJobCompleted() { return status == ExecutionStatus.COMPLETED; } public boolean isChildrenJobsCompleted() { for (ExecutionStatus jobStatus : jobExecutionResults.values()) { if (jobStatus != ExecutionStatus.COMPLETED) { return false; } } return true; } public boolean isAllJobsCompleted() { for (ExecutionStatus jobStatus : jobExecutionResults.values()) { if (jobStatus != ExecutionStatus.COMPLETED) { return false; } } return true; } /** * 检查batchJob中是否所有的job都已经达到终态 * 终态的定义是:Completed,Failed or Rollback * @return */ public boolean isTerminated(){ for (ExecutionStatus jobStatus : jobExecutionResults.values()) { if (!ExecutionStatus.isTerminated(jobStatus)) { return false; } } return true; } public boolean isFailed() { for (ExecutionStatus jobStatus : jobExecutionResults.values()) { if (jobStatus == ExecutionStatus.FAILED) { return true; } } return false; } } class ResultsConverter implements AttributeConverter, String> { @Override public String convertToDatabaseColumn(Map attribute) { return JsonUtil.encode(attribute); } @Override public Map convertToEntityAttribute(String dbData) { return JsonUtil.decode(dbData, new TypeReference<>() { }); } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/ExecutionStatus.java ================================================ package com.alibaba.cola.job.model; import com.alibaba.cola.job.JobException; import com.fasterxml.jackson.annotation.JsonCreator; import java.util.List; public enum ExecutionStatus { CREATED, STARTED, COMPLETED, FAILED, RUNNING, ROLLBACK_STARTED, ROLLBACK_COMPLETED, ROLLBACK_FAILED, UNKNOWN; public final static List ROLLBACK_STATUS = List.of(ROLLBACK_STARTED, ROLLBACK_FAILED, ROLLBACK_COMPLETED); @JsonCreator public static ExecutionStatus fromValue(String value) { for (ExecutionStatus s : ExecutionStatus.values()) { if (s.name().equals(value)) { return s; } } throw new JobException("Unexpected value '" + value + "'"); } public static boolean isRollback(ExecutionStatus executionStatus) { return executionStatus != null && ROLLBACK_STATUS.contains(executionStatus); } public static boolean isTerminated(ExecutionStatus executionStatus) { return executionStatus == ExecutionStatus.COMPLETED || executionStatus == ExecutionStatus.FAILED || isRollback(executionStatus); } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/Job.java ================================================ package com.alibaba.cola.job.model; import com.alibaba.cola.job.repository.JobRepository; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * 任务的定义,主要定义了任务所包含的steps,以及相关metadata,比如任务形态(同步,异步),是否需要回滚。 */ @Slf4j @Data public class Job { private String name; private List steps = new ArrayList<>(); private JobRepository jobRepository; // 任务是否需要回滚 private boolean needRollback; // 任务是否是异步任务,主要影响任务的状态设置 private boolean isAsync; public void execute(JobExecution jobExecution) { if (ExecutionStatus.isRollback(jobExecution.getExecutionStatus())) { rollback(jobExecution); } else { run(jobExecution); } } private void run(JobExecution jobExecution) { jobExecution.setExecutionStatus(ExecutionStatus.STARTED); jobExecution.setStartTime(LocalDateTime.now()); log.info("[Job - {}] started: {}", jobExecution.getJobId(), jobExecution); this.jobRepository.updateJobExecution(jobExecution); try { for (Step step : this.getSteps()) { runStep(step, jobExecution); jobExecution.getCompletedSteps().add(step); } if (isAsync) { // 对于异步任务,触发完所有的steps,状态仍然是running jobExecution.setExecutionStatus(ExecutionStatus.RUNNING); } else { // 对于同步任务,执行完所有的steps,不出问题的话,任务就Completed了 jobExecution.setEndTime(LocalDateTime.now()); jobExecution.setExecutionStatus(ExecutionStatus.COMPLETED); } } catch (Exception e) { if (this.isNeedRollback()) { onFailToRollback(e, jobExecution); } else { onException(e, jobExecution, ExecutionStatus.FAILED); } } finally { onFinally(jobExecution); } } private void onFailToRollback(Exception e, JobExecution jobExecution) { log.error("[Job - {}] execute error, need to rollback, error: {}", jobExecution.getJobId(), e.getMessage(), e); jobExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_STARTED); try { List rollbackSteps = jobExecution.getRollbackSteps(); log.info("[Job - {}] start rollback: {}", jobExecution.getJobId(), rollbackSteps.stream().map(s -> s.getClass().getSimpleName()).collect(Collectors.toList())); rollbackSteps.forEach(s -> rollbackStep(s, jobExecution)); } catch (Exception ee) { log.error("[Job - {}] rollback error: ", jobExecution.getJobId(), ee); jobExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_FAILED); return; } jobExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_COMPLETED); } private void rollbackStep(Step step, JobExecution jobExecution) { StepExecution stepExecution = this.jobRepository.getStepExecution(jobExecution.getJobId(), step.getClass().getSimpleName()); if (stepExecution.isRollbacked()) { log.info("[Job - {}][Step - {}] rollback skip", jobExecution.getJobId(), stepExecution.getStepName()); } else { if (step.needRollBack(stepExecution)) { // 因为stepExecution是持久层取出的,所以要重新设置step和jobExecution stepExecution.setStep(step); stepExecution.setJobExecution(jobExecution); step.rollback(stepExecution); } else { log.info("[Job - {}][Step - {}] no need rollback", jobExecution.getJobId(), stepExecution.getStepName()); } } } private void runStep(Step step, JobExecution jobExecution) { StepExecution stepExecution = this.jobRepository.getStepExecution(jobExecution.getJobId(), step.getClass().getSimpleName()); // 全新的step if (stepExecution == null) { stepExecution = new StepExecution(step); stepExecution.setJobExecution(jobExecution); stepExecution.setExecutionStatus(ExecutionStatus.STARTED); stepExecution.setStartTime(LocalDateTime.now()); this.jobRepository.saveStepExecution(stepExecution); step.execute(stepExecution); return; } // step已经执行成功,跳过 if (stepExecution.isCompleted()) { log.info("[Job - {}][Step - {}] skip", jobExecution.getJobId(), stepExecution.getStepName()); } else { // 因为stepExecution是持久层取出的,所以要重新设置step和jobExecution stepExecution.setStep(step); stepExecution.setJobExecution(jobExecution); stepExecution.setExecutionStatus(ExecutionStatus.STARTED); this.jobRepository.updateStepExecution(stepExecution); step.execute(stepExecution); } } private void rollback(JobExecution jobExecution) { jobExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_STARTED); jobExecution.setStartTime(LocalDateTime.now()); log.info("[Job - {}] rollback started: {}", jobExecution.getJobId(), jobExecution); this.jobRepository.updateJobExecution(jobExecution); try { for (Step step : getReverseSteps()) { StepExecution stepExecution = this.jobRepository.getStepExecution(jobExecution.getJobId(), step.getClass().getSimpleName()); if (stepExecution == null) { continue; } // step已经执行成功,跳过 if (stepExecution.isRollbacked()) { log.info("[Job - {}][Step - {}] rollback skip", jobExecution.getJobId(), stepExecution.getStepName()); } else { stepExecution.setStep(step); stepExecution.setJobExecution(jobExecution); stepExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_STARTED); this.jobRepository.updateStepExecution(stepExecution); step.rollback(stepExecution); } } jobExecution.setExecutionStatus(ExecutionStatus.ROLLBACK_COMPLETED); } catch (Exception e) { onException(e, jobExecution, ExecutionStatus.ROLLBACK_FAILED); } finally { onFinally(jobExecution); } } public List getReverseSteps() { List reverseSteps = new ArrayList<>(this.steps); Collections.reverse(reverseSteps); return reverseSteps; } private void onException(Exception e, JobExecution jobExecution, ExecutionStatus status) { log.error("[Job - {}] execute error: {}", jobExecution.getJobId(), e.getMessage(), e); jobExecution.setMessage(e.getMessage()); jobExecution.setEndTime(LocalDateTime.now()); jobExecution.setExecutionStatus(status); } private void onFinally(JobExecution jobExecution) { jobExecution.setUpdateTime(LocalDateTime.now()); log.info("[Job - {}] end, status is [{}]", jobExecution.getJobId(), jobExecution.getExecutionStatus()); this.jobRepository.updateJobExecution(jobExecution); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Job)) { return false; } Job job = (Job) o; if (isNeedRollback() != job.isNeedRollback()) { return false; } return getName() != null ? getName().equals(job.getName()) : job.getName() == null; } @Override public int hashCode() { int result = getName() != null ? getName().hashCode() : 0; result = 31 * result + (isNeedRollback() ? 1 : 0); return result; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/JobExecution.java ================================================ package com.alibaba.cola.job.model; import com.alibaba.cola.job.ExecutionContext; import com.alibaba.cola.job.repository.JsonUtil; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.type.TypeReference; import jakarta.persistence.Access; import jakarta.persistence.AccessType; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; import jakarta.persistence.Transient; import lombok.Data; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; @Data @Entity @Access(AccessType.FIELD) public class JobExecution { @Id @Column(name = "job_id") private String jobId; // 用来关联批量任务的batchJobId @Column(name = "batch_job_id") private String batchJobId; @Column(name = "start_time") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime startTime; @Column(name = "update_time") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime updateTime; @Column(name = "end_time") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime endTime; @Column(name = "execution_context") @Convert(converter = ContextConverter.class) private ExecutionContext executionContext; @Column(name = "execution_status") @Enumerated(EnumType.STRING) private ExecutionStatus executionStatus; @Column(name = "message") private String message; @JsonIgnore @Transient private List stepExecutions; // JobExecution所从属的job的定义definition @JsonIgnore @Transient private Job jobDef; // 已经执行的任务,用于回滚 @JsonIgnore @Transient private List completedSteps = new ArrayList<>(); public void setStepExecutionList(List stepExecutions) { if (stepExecutions == null) { return; } this.stepExecutions = stepExecutions; for (StepExecution stepExecution : this.stepExecutions) { stepExecution.setJobExecution(this); } } public boolean isCompleted() { return ExecutionStatus.COMPLETED == this.executionStatus; } public boolean isFailOrRollback() { return ExecutionStatus.FAILED == this.executionStatus || ExecutionStatus.isRollback(this.executionStatus); } @Override public String toString() { return "JobExecution{" + "jobId='" + jobId + '\'' + ", startTime=" + startTime + ", endTime=" + endTime + ", executionStatus=" + executionStatus + '}'; } @JsonIgnore public List getRollbackSteps() { List rollbackSteps = new ArrayList<>(completedSteps); Collections.reverse(rollbackSteps); return rollbackSteps; } } class ContextConverter implements AttributeConverter { @Override public String convertToDatabaseColumn(ExecutionContext attribute) { return JsonUtil.encode(attribute); } @Override public ExecutionContext convertToEntityAttribute(String dbData) { return JsonUtil.decode(dbData, new TypeReference<>() { }); } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/JobInstance.java ================================================ package com.alibaba.cola.job.model; import com.alibaba.cola.job.ExecutionContext; import lombok.AllArgsConstructor; import lombok.Data; /** * Job实例:包含了Job的定义,以及运行job的runtime信息,也就是ExecutionContext * 一个BatchJob会包含多个JobInstance,是为了BatchJob创建的模型。 */ @Data @AllArgsConstructor public class JobInstance { private Job job; private ExecutionContext executionContext; } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/Step.java ================================================ package com.alibaba.cola.job.model; public interface Step { void execute(StepExecution stepExecution); void rollback(StepExecution stepExecution); boolean needRollBack(StepExecution stepExecution); void setJob(Job job); Job getJob(); } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/model/StepExecution.java ================================================ package com.alibaba.cola.job.model; import com.alibaba.cola.job.ExecutionContext; import com.alibaba.cola.job.repository.JobRepository; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.Access; import jakarta.persistence.AccessType; import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; import jakarta.persistence.Transient; import lombok.Data; import org.springframework.util.Assert; import java.time.LocalDateTime; @Data @Entity @Access(AccessType.FIELD) public class StepExecution { @Id @Column(name = "step_id") // stepId = jobId + "-" +stepName private String stepId; // StepExecution的Step定义 @JsonIgnore @Transient private Step step; // 该stepExecution所归属的jobExecution @JsonIgnore @Transient private JobExecution jobExecution; @Column(name = "job_id") private String jobId; @Column(name = "step_name") private String stepName; @Column(name = "start_time") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime startTime; @Column(name = "end_time") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime endTime; @Column(name = "execution_status") @Enumerated(EnumType.STRING) private ExecutionStatus executionStatus; @Column(name = "execution_context") @Convert(converter = ContextConverter.class) private ExecutionContext executionContext; @Column(name = "message") private String message; public boolean isCompleted() { return this.executionStatus == ExecutionStatus.COMPLETED; } public boolean isRollbacked() { return this.executionStatus == ExecutionStatus.ROLLBACK_COMPLETED; } public StepExecution(){ } public StepExecution(Step step) { this.step = step; this.stepName = step.getClass().getSimpleName(); } public void setJobExecution(JobExecution jobExecution) { Assert.notNull(jobExecution, "jobExecution cannot be null"); this.jobExecution = jobExecution; this.jobId = jobExecution.getJobId(); this.stepId = this.jobId + "-" + this.stepName; } public ExecutionContext getExecutionContext() { if (executionContext == null) { executionContext = this.jobExecution.getExecutionContext(); } return executionContext; } @JsonIgnore public JobRepository getRepository() { Assert.notNull(jobExecution, "jobExecution cannot be null"); Assert.notNull(jobExecution.getJobDef(), "job must be attached to jobExecution"); return this.jobExecution.getJobDef().getJobRepository(); } @Override public String toString() { return "StepExecution{" + "stepExecutionId='" + stepId + '\'' + ", executionStatus=" + executionStatus + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof StepExecution)) { return false; } StepExecution that = (StepExecution) o; return stepId != null ? stepId.equals(that.getStepId()) : that.getStepId() == null; } @Override public int hashCode() { return getJobExecution() != null ? stepId.hashCode() : 0; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/AbstractJobRepository.java ================================================ package com.alibaba.cola.job.repository; import com.alibaba.cola.job.model.BatchJobExecution; import java.time.LocalDateTime; import java.util.List; public abstract class AbstractJobRepository implements JobRepository { @Override public List findNotCompletedBatchJobsOlderThan(LocalDateTime thresholdTime) { return null; } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/JobRepository.java ================================================ package com.alibaba.cola.job.repository; import com.alibaba.cola.job.model.BatchJobExecution; import com.alibaba.cola.job.model.JobExecution; import com.alibaba.cola.job.model.StepExecution; import java.time.LocalDateTime; import java.util.List; public interface JobRepository { BatchJobExecution saveBatchJobExecution(BatchJobExecution batchJobExecution); void updateBatchJobExecution(BatchJobExecution batchJobExecution); BatchJobExecution getBatchJobExecutionByBatchJobId(String batchJobId); JobExecution saveJobExecution(JobExecution jobExecution); void updateJobExecution(JobExecution jobExecution); JobExecution getJobExecutionByJobId(String jobId); void saveStepExecution(StepExecution stepExecution); void updateStepExecution(StepExecution stepExecution); StepExecution getStepExecution(String jobExecutionId, String stepName); List listStepExecutions(String jobExecutionId); List findNotCompletedBatchJobsOlderThan(LocalDateTime thresholdTime); } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/JsonUtil.java ================================================ package com.alibaba.cola.job.repository; import com.alibaba.cola.job.JobException; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import com.fasterxml.jackson.core.type.TypeReference; import java.util.concurrent.Callable; public final class JsonUtil { private static ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); public static String encode(Object object) { return invoke(() -> OBJECT_MAPPER.writeValueAsString(object)); } public static T decode(String source, Class valueType) { return invoke(() -> OBJECT_MAPPER.readValue(source, valueType)); } public static T decode(String src, TypeReference valueTypeRef) { return invoke(() -> OBJECT_MAPPER.readValue(src, valueTypeRef)); } private static T invoke(Callable callable) { try { return callable.call(); } catch (Exception e) { throw new JobException(e); } } public class ObjectMapperFactory { public static ObjectMapper OBJECT_MAPPER; static { OBJECT_MAPPER = Jackson2ObjectMapperBuilder.json().build(); OBJECT_MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); OBJECT_MAPPER.findAndRegisterModules(); } public static ObjectMapper getObjectMapper() { return OBJECT_MAPPER; } } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/RepositoryType.java ================================================ package com.alibaba.cola.job.repository; public enum RepositoryType { REDIS, DB, MEMORY } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/db/BatchJobExecutionRepository.java ================================================ package com.alibaba.cola.job.repository.db; import com.alibaba.cola.job.model.BatchJobExecution; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.List; @Repository public interface BatchJobExecutionRepository extends JpaRepository { BatchJobExecution getByBatchJobId(String batchJobId); @Query("SELECT b FROM BatchJobExecution b " + "WHERE b.status <> 'COMPLETED' " + "AND b.startTime < :thresholdTime") List findNotCompletedBatchJobsOlderThan( @Param("thresholdTime") LocalDateTime thresholdTime); } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/db/DataBaseJobRepository.java ================================================ package com.alibaba.cola.job.repository.db; import com.alibaba.cola.job.model.BatchJobExecution; import com.alibaba.cola.job.model.JobExecution; import com.alibaba.cola.job.model.StepExecution; import com.alibaba.cola.job.repository.AbstractJobRepository; import jakarta.annotation.Resource; import java.time.LocalDateTime; import java.util.List; public class DataBaseJobRepository extends AbstractJobRepository { @Resource private BatchJobExecutionRepository batchJobExecutionRepository; @Resource private JobExecutionRepository jobExecutionRepository; @Resource private StepExecutionRepository stepExecutionRepository; @Override public JobExecution getJobExecutionByJobId(String jobId) { return jobExecutionRepository.getByJobId(jobId); } @Override public BatchJobExecution saveBatchJobExecution(BatchJobExecution batchJobExecution) { return batchJobExecutionRepository.save(batchJobExecution); } @Override public void updateBatchJobExecution(BatchJobExecution batchJobExecution) { batchJobExecutionRepository.save(batchJobExecution); } @Override public BatchJobExecution getBatchJobExecutionByBatchJobId(String batchJobId) { return batchJobExecutionRepository.getByBatchJobId(batchJobId); } @Override public JobExecution saveJobExecution(JobExecution jobExecution) { return jobExecutionRepository.save(jobExecution); } @Override public void updateJobExecution(JobExecution jobExecution) { jobExecutionRepository.save(jobExecution); } @Override public void saveStepExecution(StepExecution stepExecution) { stepExecutionRepository.save(stepExecution); } @Override public void updateStepExecution(StepExecution stepExecution) { stepExecutionRepository.save(stepExecution); } @Override public StepExecution getStepExecution(String jobId, String stepName) { return stepExecutionRepository.getByJobIdAndStepName(jobId, stepName); } @Override public List listStepExecutions(String jobId) { return stepExecutionRepository.findByJobId(jobId); } @Override public List findNotCompletedBatchJobsOlderThan(LocalDateTime thresholdTime) { return batchJobExecutionRepository.findNotCompletedBatchJobsOlderThan(thresholdTime); } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/db/JobExecutionRepository.java ================================================ package com.alibaba.cola.job.repository.db; import com.alibaba.cola.job.model.JobExecution; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface JobExecutionRepository extends JpaRepository { JobExecution getByJobId(String jobId); } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/db/StepExecutionRepository.java ================================================ package com.alibaba.cola.job.repository.db; import com.alibaba.cola.job.model.StepExecution; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface StepExecutionRepository extends JpaRepository { StepExecution getByStepId(String stepId); StepExecution getByJobIdAndStepName(String jobId, String stepName); List findByJobId(String jobId); } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/memory/MemoryJobRepository.java ================================================ package com.alibaba.cola.job.repository.memory; import com.alibaba.cola.job.JobException; import com.alibaba.cola.job.repository.AbstractJobRepository; import com.alibaba.cola.job.model.BatchJobExecution; import com.alibaba.cola.job.model.JobExecution; import com.alibaba.cola.job.model.StepExecution; import org.springframework.beans.BeanUtils; import org.springframework.util.Assert; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * 内存任务仓储,没有落盘持久化,适合Demo和一次性的任务 */ public class MemoryJobRepository extends AbstractJobRepository { // key是batchJobId private Map batchJobMap = new ConcurrentHashMap<>(); // key是jobExecutionId private Map jobMap = new ConcurrentHashMap<>(); // key是jobExecutionId+stepName private Map stepMap = new ConcurrentHashMap<>(); // key是jobExecutionId private Map> stepSetMap = new ConcurrentHashMap<>(); @Override public JobExecution getJobExecutionByJobId(String jobId) { return jobMap.get(jobId); } @Override public BatchJobExecution saveBatchJobExecution(BatchJobExecution batchJobExecution) { batchJobMap.put(batchJobExecution.getBatchJobId(), batchJobExecution); return batchJobExecution; } @Override public void updateBatchJobExecution(BatchJobExecution batchJobExecution) { batchJobMap.put(batchJobExecution.getBatchJobId(), batchJobExecution); } @Override public BatchJobExecution getBatchJobExecutionByBatchJobId(String batchJobId) { return batchJobMap.get(batchJobId); } @Override public JobExecution saveJobExecution(JobExecution jobExecution) { Assert.notNull(jobExecution, "jobExecution cannot be null"); Assert.notNull(jobExecution.getJobDef(), "job cannot be null."); Assert.notNull(jobExecution.getJobId(), "jobExecutionId cannot be null."); jobMap.put(jobExecution.getJobId(), jobExecution); return jobExecution; } @Override public void updateJobExecution(JobExecution jobExecution) { Assert.notNull(jobExecution, "jobExecution cannot be null"); String executionId = jobExecution.getJobId(); Assert.notNull(executionId, "jobExecutionId cannot be null."); JobExecution oldJobExecution = jobMap.get(executionId); if (oldJobExecution == null) { throw new JobException("No jobExecution with id:" + executionId + " found"); } jobMap.put(executionId, jobExecution); } @Override public void saveStepExecution(StepExecution stepExecution) { Assert.notNull(stepExecution.getJobExecution(), "jobExecution cannot be null"); String jobExecutionId = stepExecution.getJobExecution().getJobId(); String stepName = stepExecution.getStepName(); stepMap.put(jobExecutionId + stepName, stepExecution); Set stepSet = stepSetMap.get(jobExecutionId); if (stepSet == null) { stepSet = new HashSet<>(); stepSetMap.put(jobExecutionId, stepSet); } stepSet.add(stepExecution); } @Override public void updateStepExecution(StepExecution stepExecution) { Assert.notNull(stepExecution.getJobExecution(), "jobExecution cannot be null"); String jobExecutionId = stepExecution.getJobExecution().getJobId(); String stepName = stepExecution.getStepName(); stepMap.put(jobExecutionId + stepName, stepExecution); Set stepSet = stepSetMap.get(jobExecutionId); for (StepExecution target : stepSet) { if (target.equals(stepExecution)) { BeanUtils.copyProperties(stepExecution, target); } } } @Override public StepExecution getStepExecution(String jobExecutionId, String stepName) { return stepMap.get(jobExecutionId + stepName); } @Override public List listStepExecutions(String jobExecutionId) { return stepSetMap.get(jobExecutionId).stream().toList(); } } ================================================ FILE: cola-components/cola-component-job/src/main/java/com/alibaba/cola/job/repository/redis/RedisJobRepository.java ================================================ package com.alibaba.cola.job.repository.redis; import com.alibaba.cola.job.model.BatchJobExecution; import com.alibaba.cola.job.model.JobExecution; import com.alibaba.cola.job.model.StepExecution; import com.alibaba.cola.job.repository.AbstractJobRepository; import com.alibaba.cola.job.repository.JsonUtil; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; import java.util.ArrayList; import java.util.List; public class RedisJobRepository extends AbstractJobRepository { @Resource private RedisTemplate jobRedisTemplate; @Resource private RedisTemplate stepRedisTemplate; @Resource private RedisTemplate batchJobRedisTemplate; private ObjectMapper objectMapper; @PostConstruct public void init() { objectMapper = JsonUtil.ObjectMapperFactory.getObjectMapper(); } public static final String JOB_PREFIX = "job:"; public static final String BATCH_JOB_PREFIX = "batchJob:"; @Override public BatchJobExecution saveBatchJobExecution(BatchJobExecution batchJobExecution) { String key = BATCH_JOB_PREFIX + batchJobExecution.getBatchJobId(); batchJobRedisTemplate.opsForValue().set(key, batchJobExecution); return batchJobExecution; } @Override public void updateBatchJobExecution(BatchJobExecution batchJobExecution) { String key = BATCH_JOB_PREFIX + batchJobExecution.getBatchJobId(); batchJobRedisTemplate.opsForValue().set(key, batchJobExecution); } @Override public BatchJobExecution getBatchJobExecutionByBatchJobId(String batchJobId) { String key = BATCH_JOB_PREFIX + batchJobId; return objectMapper.convertValue(batchJobRedisTemplate.opsForValue().get(key), new TypeReference() { }); } @Override public JobExecution saveJobExecution(JobExecution jobExecution) { String key = JOB_PREFIX + jobExecution.getJobId(); jobRedisTemplate.opsForValue().set(key, jobExecution); return jobExecution; } @Override public void updateJobExecution(JobExecution jobExecution) { String key = JOB_PREFIX + jobExecution.getJobId(); jobRedisTemplate.opsForValue().set(key, jobExecution); } @Override public JobExecution getJobExecutionByJobId(String jobId) { String key = JOB_PREFIX + jobId; return objectMapper.convertValue(jobRedisTemplate.opsForValue().get(key), new TypeReference() { }); } @Override public void saveStepExecution(StepExecution stepExecution) { String key = JOB_PREFIX + stepExecution.getJobExecution().getJobId() + ":" + stepExecution.getStepName(); stepRedisTemplate.opsForValue().set(key, stepExecution); } @Override public void updateStepExecution(StepExecution stepExecution) { String key = JOB_PREFIX + stepExecution.getJobExecution().getJobId() + ":" + stepExecution.getStepName(); stepRedisTemplate.opsForValue().set(key, stepExecution); } @Override public StepExecution getStepExecution(String jobId, String stepName) { String key = JOB_PREFIX + jobId + ":" + stepName; return objectMapper.convertValue(stepRedisTemplate.opsForValue().get(key), new TypeReference() { }); } @Override public List listStepExecutions(String jobExecutionId) { List stepExecutions = new ArrayList<>(); ScanOptions options = ScanOptions.scanOptions().match(JOB_PREFIX + jobExecutionId + ":*").count(10) // 每次返回的数量 .build(); Cursor cursor = jobRedisTemplate.scan(options); while (cursor.hasNext()) { StepExecution stepExecution = objectMapper.convertValue(stepRedisTemplate.opsForValue().get(cursor.next()), new TypeReference() { }); stepExecutions.add(stepExecution); } return stepExecutions; } } ================================================ FILE: cola-components/cola-component-job/src/main/resources/schema-mysql.sql ================================================ CREATE TABLE IF NOT EXISTS BATCH_JOB_EXECUTION ( BATCH_JOB_ID VARCHAR(128) NOT NULL PRIMARY KEY, JOB_TYPE VARCHAR(128), JOB_EXECUTION_RESULTS TEXT, EXECUTION_STATUS VARCHAR(20), START_TIME DATETIME DEFAULT NULL, END_TIME DATETIME DEFAULT NULL ); CREATE TABLE IF NOT EXISTS JOB_EXECUTION ( JOB_ID VARCHAR(256) NOT NULL PRIMARY KEY, BATCH_JOB_ID VARCHAR(128), START_TIME DATETIME DEFAULT NULL, UPDATE_TIME DATETIME DEFAULT NULL, END_TIME DATETIME DEFAULT NULL, EXECUTION_STATUS VARCHAR(20), EXECUTION_CONTEXT TEXT, MESSAGE TEXT ); CREATE TABLE IF NOT EXISTS STEP_EXECUTION ( STEP_ID VARCHAR(512) NOT NULL PRIMARY KEY, STEP_NAME VARCHAR(50) NOT NULL, JOB_ID VARCHAR(128) NOT NULL, START_TIME DATETIME DEFAULT NULL, END_TIME DATETIME DEFAULT NULL, EXECUTION_STATUS VARCHAR(20), EXECUTION_CONTEXT TEXT, MESSAGE TEXT, constraint JOB_EXEC_STEP_FK foreign key (JOB_ID) references JOB_EXECUTION (JOB_ID) ); ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/AbstractBaseJobTest.java ================================================ package com.alibaba.cola.job.test; import com.alibaba.cola.job.BatchJobLauncher; import com.alibaba.cola.job.ExecutionContext; import com.alibaba.cola.job.JobBuilderFactory; import com.alibaba.cola.job.JobLauncher; import com.alibaba.cola.job.model.*; import com.alibaba.cola.job.repository.JobRepository; import com.alibaba.cola.job.test.steps.*; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import static org.awaitility.Awaitility.await; @Slf4j public abstract class AbstractBaseJobTest { public abstract JobRepository getJobRepository(); public abstract void testBatchJobCompleted(); public void batchJobCompleted() { Job job = JobBuilderFactory.create() .addStep(new MyStep1()) .addStep(new MyStep2()) .addStep(new LongTimeStep()) .build("batchJob", getJobRepository()); ExecutionContext context1 = new ExecutionContext(); context1.putString("hostId", "111"); JobInstance jobInstance1 = new JobInstance(job, context1); ExecutionContext context2 = new ExecutionContext(); context2.putString("hostId", "222"); JobInstance jobInstance2 = new JobInstance(job, context2); ExecutionContext context3 = new ExecutionContext(); context3.putString("hostId", "333"); JobInstance jobInstance3 = new JobInstance(job, context3); BatchJob batchJob = new BatchJob(); batchJob.add(jobInstance1) .add(jobInstance2) .add(jobInstance3) .jobRepository(getJobRepository()) .executorService(buildExecutorService()); BatchJobExecution batchJobExecution = BatchJobLauncher.execute(batchJob); // 最长等待5秒,每1秒钟检查一下,看batchJob状态是不是COMPLETED。超过5秒,报错 await().atMost(7, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> { boolean isCompleted = BatchJobLauncher.checkAndRefresh(batchJob, batchJobExecution.getBatchJobId()); return isCompleted; }); } public ExecutorService buildExecutorService() { // 创建自定义的 ThreadFactory ThreadFactory namedThreadFactory = new ThreadFactory() { private final AtomicInteger count = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("CustomThread-" + count.getAndIncrement()); return thread; } }; // 使用自定义的 ThreadFactory 创建 ExecutorService ExecutorService executorService = new ThreadPoolExecutor(2, // corePoolSize 5, // maximumPoolSize 60L, // keepAliveTime TimeUnit.SECONDS, // timeUnit new LinkedBlockingQueue<>(), // workQueue namedThreadFactory // 使用自定义的 ThreadFactory ); return executorService; } public abstract void testBatchJobFailed(); public void batchJobFailed() { Job job = JobBuilderFactory.create() .addStep(new MyStep1()) .addStep(new MyStep2()) .addStep(new FailedStep()) .build("batchJob", getJobRepository()); ExecutionContext context1 = new ExecutionContext(); context1.putString("hostId", "111"); JobInstance jobInstance1 = new JobInstance(job, context1); ExecutionContext context2 = new ExecutionContext(); context2.putString("hostId", "222"); JobInstance jobInstance2 = new JobInstance(job, context2); ExecutionContext context3 = new ExecutionContext(); context3.putString("hostId", "333"); JobInstance jobInstance3 = new JobInstance(job, context3); BatchJob batchJob = new BatchJob(); batchJob.add(jobInstance1).add(jobInstance2).add(jobInstance3).jobRepository(getJobRepository()); BatchJobExecution batchJobExecution = BatchJobLauncher.execute(batchJob); // 最长等待5秒,每300ms钟检查一下,看batchJob状态是不是Failed await().atMost(5, TimeUnit.SECONDS).pollInterval(300, TimeUnit.MILLISECONDS).until(() -> { BatchJobLauncher.checkAndRefresh(batchJob, batchJobExecution.getBatchJobId()); BatchJobExecution result = getJobRepository().getBatchJobExecutionByBatchJobId( batchJobExecution.getBatchJobId()); return result.isFailed(); }); } public abstract void testJobCompleted(); public void jobCompleted() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new MyStep2()); Job job = jobBuilder.build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.COMPLETED, jobExecution.getExecutionStatus()); } public void jobRunning() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new MyStep2()); jobBuilder.isAsync(true); Job job = jobBuilder.build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.RUNNING, jobExecution.getExecutionStatus()); } public abstract void testJobWithCustomizedParam(); public void jobWithCustomizedParam() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep2()); Job job = jobBuilder.build("myJob", getJobRepository()); // ExecutionContext executionContext = new ExecutionContext<>(); // executionContext.setParam(JobRequest.mock()); ExecutionContext executionContext = new ExecutionContext(); JobExecution jobExecution = JobLauncher.executeSync(job, executionContext); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.COMPLETED, jobExecution.getExecutionStatus()); } public abstract void testJobRerunSkipNext(); public void jobRerunSkipNext() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new MyStep2()); Job job = jobBuilder.build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); ExecutionContext executionContext = new ExecutionContext(jobExecution.getJobId()); JobLauncher.executeSync(job, executionContext); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.COMPLETED, jobExecution.getExecutionStatus()); } public abstract void testJobRerunFixPreviousFail(); public void jobRerunFixPreviousFail() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new SwitchStep()); Job job = jobBuilder.build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.FAILED, jobExecution.getExecutionStatus()); ExecutionContext executionContext = new ExecutionContext(jobExecution.getJobId()); JobLauncher.executeSync(job, executionContext); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.COMPLETED, jobExecution.getExecutionStatus()); } public abstract void testStepRollBack(); public void stepRollBack() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new MyStep2()); jobBuilder.addStep(new MyStep3()); Job job = jobBuilder.build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); StepExecution stepExecution = getJobRepository().getStepExecution(jobExecution.getJobId(), MyStep3.class.getSimpleName()); Assertions.assertEquals(ExecutionStatus.ROLLBACK_COMPLETED, stepExecution.getExecutionStatus()); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.FAILED, jobExecution.getExecutionStatus()); } public abstract void testStepRollbackFailed(); public void stepRollbackFailed() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new MyStep2()); jobBuilder.addStep(new MyStep4()); Job job = jobBuilder.build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); StepExecution stepExecution = getJobRepository().getStepExecution(jobExecution.getJobId(), MyStep4.class.getSimpleName()); Assertions.assertEquals(ExecutionStatus.ROLLBACK_FAILED, stepExecution.getExecutionStatus()); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.FAILED, jobExecution.getExecutionStatus()); } public void jobRollBack() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new MyStep2()); jobBuilder.addStep(new MyStep3()); Job job = jobBuilder.needRollback(true).build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); for (Step step : job.getSteps()) { StepExecution stepExecution = getJobRepository().getStepExecution(jobExecution.getJobId(), step.getClass().getSimpleName()); Assertions.assertEquals(ExecutionStatus.ROLLBACK_COMPLETED, stepExecution.getExecutionStatus()); } jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.ROLLBACK_COMPLETED, jobExecution.getExecutionStatus()); } public void jobRollBackFailed() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); MyStep5 rollbackErrorStep = new MyStep5(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(rollbackErrorStep); jobBuilder.addStep(new MyStep3()); Job job = jobBuilder.needRollback(true).build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); expectedStatus(jobExecution.getJobId(), Map.of("MyStep1", ExecutionStatus.COMPLETED, "MyStep5", ExecutionStatus.ROLLBACK_FAILED, "MyStep3", ExecutionStatus.ROLLBACK_COMPLETED)); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.ROLLBACK_FAILED, jobExecution.getExecutionStatus()); // now step status = COMPLETED, ROLLBACK_FAILED ,ROLLBACK_COMPLETED // continue rollback will skip third, rollback second and first log.info("start continue rollback"); rollbackErrorStep.setRollbackThrow(false); jobExecution.getExecutionContext().setJobId(jobExecution.getJobId()); jobExecution = JobLauncher.executeSync(job, jobExecution.getExecutionContext()); // 这里需要补充全部的状态检查 expectedStatus(jobExecution.getJobId(), Map.of("MyStep1", ExecutionStatus.ROLLBACK_COMPLETED, "MyStep5", ExecutionStatus.ROLLBACK_COMPLETED, "MyStep3", ExecutionStatus.ROLLBACK_COMPLETED)); } public void expectedStatus(String executionId, Map ExecutionStatus) { for (var entry : ExecutionStatus.entrySet()) { StepExecution stepExecution = getJobRepository().getStepExecution(executionId, entry.getKey()); Assertions.assertEquals(entry.getValue(), stepExecution.getExecutionStatus()); } } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/MemoryDBJobTest.java ================================================ package com.alibaba.cola.job.test; import com.alibaba.cola.job.config.EnableColaJob; import com.alibaba.cola.job.repository.JobRepository; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @Slf4j @SpringBootTest(classes = TestApplication.class) @ActiveProfiles("h2-test") public class MemoryDBJobTest extends AbstractBaseJobTest { @Resource JobRepository jobRepository; @Override public JobRepository getJobRepository() { return jobRepository; } @Test public void testBatchJobCompleted() { super.batchJobCompleted(); } @Test public void testBatchJobFailed() { super.batchJobFailed(); } @Test public void testJobCompleted() { super.jobCompleted(); } @Test public void testJobWithCustomizedParam() { super.jobWithCustomizedParam(); } @Test public void testJobRerunSkipNext() { super.jobRerunSkipNext(); } @Test public void testJobRerunFixPreviousFail() { super.jobRerunFixPreviousFail(); } @Test public void testStepRollBack() { super.stepRollBack(); } @Test public void testStepRollbackFailed() { super.stepRollbackFailed(); } @Test public void testJobRollback() { super.jobRollBack(); } @Test public void test() { super.jobRollBackFailed(); } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/MemoryJobTest.java ================================================ package com.alibaba.cola.job.test; import com.alibaba.cola.job.repository.JobRepository; import com.alibaba.cola.job.repository.memory.MemoryJobRepository; import org.junit.jupiter.api.Test; public class MemoryJobTest extends AbstractBaseJobTest { private static JobRepository jobRepository = new MemoryJobRepository(); @Override public JobRepository getJobRepository() { return jobRepository; } @Test public void testBatchJobCompleted() { super.batchJobCompleted(); } @Test public void testBatchJobFailed() { super.batchJobFailed(); } @Test public void testJobCompleted() { super.jobCompleted(); } @Test public void testJobWithCustomizedParam() { super.jobWithCustomizedParam(); } @Test public void testJobRerunSkipNext() { super.jobRerunSkipNext(); } @Test public void testJobRerunFixPreviousFail() { super.jobRerunFixPreviousFail(); } @Test public void testStepRollBack() { super.stepRollBack(); } @Test public void testStepRollbackFailed() { super.stepRollbackFailed(); } @Test public void testJobRollback() { super.jobRollBack(); } @Test public void testJobRollbackFailed() { super.jobRollBackFailed(); } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/MySQLJobTest.java ================================================ package com.alibaba.cola.job.test; import com.alibaba.cola.job.repository.JobRepository; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @Slf4j @SpringBootTest(classes = TestApplication.class) @ActiveProfiles("mysql-test") @Disabled // MySQL tests are disabled by default, enable them manually if needed public class MySQLJobTest extends AbstractBaseJobTest { @Resource JobRepository jobRepository; @Override public JobRepository getJobRepository() { return jobRepository; } @Test public void testBatchJobCompleted() { super.batchJobCompleted(); } @Test public void testBatchJobFailed() { super.batchJobFailed(); } @Test public void testJobCompleted() { super.jobCompleted(); } @Test public void testJobWithCustomizedParam() { super.jobWithCustomizedParam(); } @Test public void testJobRerunSkipNext() { super.jobRerunSkipNext(); } @Test public void testJobRerunFixPreviousFail() { super.jobRerunFixPreviousFail(); } @Test public void testStepRollBack() { super.stepRollBack(); } @Test public void testStepRollbackFailed() { super.stepRollbackFailed(); } @Test public void testJobRollback() { super.jobRollBack(); } @Test public void test() { super.jobRollBackFailed(); } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/RedisJobTest.java ================================================ package com.alibaba.cola.job.test; import com.alibaba.cola.job.JobBuilderFactory; import com.alibaba.cola.job.JobLauncher; import com.alibaba.cola.job.model.ExecutionStatus; import com.alibaba.cola.job.model.Job; import com.alibaba.cola.job.model.JobExecution; import com.alibaba.cola.job.repository.JobRepository; import com.alibaba.cola.job.test.steps.FailedStep; import com.alibaba.cola.job.test.steps.MyStep1; import com.alibaba.cola.unittest.redis.RedisExtension; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @Slf4j @SpringBootTest(classes = TestApplication.class) @ExtendWith(RedisExtension.class) @ActiveProfiles("redis-test") public class RedisJobTest extends AbstractBaseJobTest { @Resource JobRepository jobRepository; @Override public JobRepository getJobRepository() { return jobRepository; } @Test public void testBatchJobCompleted() { super.batchJobCompleted(); } @Test public void testBatchJobFailed() { super.batchJobFailed(); } @Test public void testJobCompleted() { super.jobCompleted(); } @Test public void testJobRunning(){ super.jobRunning(); } @Test public void testJobFailed() { JobBuilderFactory.JobBuilder jobBuilder = JobBuilderFactory.create(); jobBuilder.addStep(new MyStep1()); jobBuilder.addStep(new FailedStep()); Job job = jobBuilder.build("myJob", getJobRepository()); JobExecution jobExecution = JobLauncher.executeSync(job); jobExecution = getJobRepository().getJobExecutionByJobId(jobExecution.getJobId()); Assertions.assertEquals(ExecutionStatus.FAILED, jobExecution.getExecutionStatus()); } @Test public void testJobWithCustomizedParam() { super.jobWithCustomizedParam(); } @Test public void testJobRerunSkipNext() { super.jobRerunSkipNext(); } @Test public void testJobRerunFixPreviousFail() { super.jobRerunFixPreviousFail(); } @Test public void testStepRollBack() { super.stepRollBack(); } @Test public void testStepRollbackFailed() { super.stepRollbackFailed(); } @Test public void testJobRollback() { super.jobRollBack(); } @Test public void testJobRollBackFailed() { super.jobRollBackFailed(); } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/TestApplication.java ================================================ package com.alibaba.cola.job.test; import com.alibaba.cola.job.config.EnableColaJob; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EnableColaJob public class TestApplication { } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/TestsContainerBoot.java ================================================ package com.alibaba.cola.job.test; import com.alibaba.cola.test.TestsContainer; public class TestsContainerBoot { public static void main(String[] args) { TestsContainer.start(); } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/FailedStep.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; // This class is used to simulate a step that fails during execution public class FailedStep extends AbstractStep { @Override public void doExecute(StepExecution stepExecution) { System.out.println("this is FailedStep"); throw new RuntimeException("something wrong happened"); } @Override public void doRollback(StepExecution stepExecution) { } @Override public boolean needRollBack(StepExecution stepExecution) { return false; } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/LongTimeStep.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; // This class represents a step that simulates a long execution time of 2 seconds. public class LongTimeStep extends AbstractStep { @Override public void doExecute(StepExecution stepExecution) { try { System.out.println("This is long time executing step, will cost 2 seconds"); Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void doRollback(StepExecution stepExecution) { } @Override public boolean needRollBack(StepExecution stepExecution) { return false; } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/MyStep1.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; public class MyStep1 extends AbstractStep { @Override public void doRollback(StepExecution stepExecution) { } @Override public void doExecute(StepExecution stepExecution) { System.out.println("this is step1"); stepExecution.getExecutionContext().putString("step1", "something need pass to step2"); } @Override public boolean needRollBack(StepExecution stepExecution) { return true; } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/MyStep2.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; public class MyStep2 extends AbstractStep { @Override public void doRollback(StepExecution stepExecution) { } @Override public void doExecute(StepExecution stepExecution) { System.out.println("this is step2, information from step1: " + stepExecution.getExecutionContext().getString("step1")); stepExecution.getExecutionContext().putString("step2", "valuable information to next"); } @Override public boolean needRollBack(StepExecution stepExecution) { return true; } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/MyStep3.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; public class MyStep3 extends AbstractStep { @Override public void doRollback(StepExecution stepExecution) { System.out.println("this is step3 rollback"); } @Override public void doExecute(StepExecution stepExecution) { System.out.println("this is step3"); throw new RuntimeException("something wrong happened"); } @Override public boolean needRollBack(StepExecution stepExecution) { return true; } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/MyStep4.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; public class MyStep4 extends AbstractStep { @Override public void doRollback(StepExecution stepExecution) { System.out.println("this is step4 rollback"); throw new RuntimeException("oops, rollback failed"); } @Override public void doExecute(StepExecution stepExecution) { System.out.println("this is step4"); throw new RuntimeException("something wrong happened"); } @Override public boolean needRollBack(StepExecution stepExecution) { return true; } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/MyStep5.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; import lombok.Setter; public class MyStep5 extends AbstractStep { @Setter private boolean isRollbackThrow = true; @Override public void doRollback(StepExecution stepExecution) { System.out.println("this is step5 rollback"); if(isRollbackThrow){ throw new RuntimeException("oops, rollback failed"); } } @Override public void doExecute(StepExecution stepExecution) { System.out.println("this is step5"); } @Override public boolean needRollBack(StepExecution stepExecution) { return true; } } ================================================ FILE: cola-components/cola-component-job/src/test/java/com/alibaba/cola/job/test/steps/SwitchStep.java ================================================ package com.alibaba.cola.job.test.steps; import com.alibaba.cola.job.model.AbstractStep; import com.alibaba.cola.job.model.StepExecution; import lombok.extern.slf4j.Slf4j; @Slf4j public class SwitchStep extends AbstractStep { private static boolean switcher = true; @Override public void doExecute(StepExecution stepExecution) { if(switcher){ switcher = false; throw new RuntimeException("something wrong, rerun will fix it"); } log.info("flipped back to normal"); switcher = true; } @Override public void doRollback(StepExecution stepExecution) { } @Override public boolean needRollBack(StepExecution stepExecution) { return false; } } ================================================ FILE: cola-components/cola-component-job/src/test/resources/application-h2-test.yml ================================================ spring: datasource: url: jdbc:h2:mem:testdb username: sa password: driver-class-name: org.h2.Driver jpa: database-platform: org.hibernate.dialect.H2Dialect properties: hibernate: format_sql: true cola: job: repository-type: db database: auto-ddl: true ================================================ FILE: cola-components/cola-component-job/src/test/resources/application-mysql-test.yml ================================================ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # hard code for test purpose url: jdbc:mysql://localhost:3306/colaJob?serverTimezone=UTC username: root password: root jpa: database-platform: org.hibernate.dialect.H2Dialect properties: hibernate: format_sql: true cola: job: repository-type: db database: auto-ddl: true ddl-location: schema-mysql.sql ================================================ FILE: cola-components/cola-component-job/src/test/resources/application-redis-test.yml ================================================ spring: data: redis: host: localhost port: 6379 cola: job: repository-type: redis ================================================ FILE: cola-components/cola-component-job/src/test/resources/logback-test.xml ================================================ %date{HH:mm:ss.SSS} %highlight(%-5level) [%blue(%t)] %yellow(%C{45}): %msg%n%throwable utf8 ================================================ FILE: cola-components/cola-component-ruleengine/README.md ================================================ ## 介绍 这是COLA规则引擎 ## 使用 hello world 案例: ```java RuleEngine ruleEngine = new DefaultRuleEngine(); Rule rule = new RuleBuilder() .name("hello world rule") .description("always say hello world") .priority(1) .when(facts -> true) .then(facts -> System.out.println("hello world")) .build(); Rules rules = new Rules(); rules.register(rule); ruleEngine.fire(rules, null); ``` ================================================ FILE: cola-components/cola-component-ruleengine/gitignore.txt ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/cola-component-ruleengine/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-ruleengine jar ${project.artifactId}:${project.version} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee org.slf4j slf4j-api org.slf4j slf4j-simple test ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Action.java ================================================ package com.alibaba.cola.ruleengine.api; @FunctionalInterface public interface Action { void execute(Facts facts); } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Condition.java ================================================ package com.alibaba.cola.ruleengine.api; import java.util.Objects; @FunctionalInterface public interface Condition { boolean evaluate(Facts facts); //谓词and逻辑,参考Predicate default Condition and(Condition other) { Objects.requireNonNull(other); return (facts) -> { return this.evaluate(facts) && other.evaluate(facts); }; } //谓词or逻辑,参考Predicate default Condition or(Condition other) { Objects.requireNonNull(other); return (facts) -> { return this.evaluate(facts) || other.evaluate(facts); }; } /** * A NoOp {@link Condition} that always returns false. */ Condition FALSE = facts -> false; /** * A NoOp {@link Condition} that always returns true. */ Condition TRUE = facts -> true; } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Fact.java ================================================ package com.alibaba.cola.ruleengine.api; import java.util.Objects; public class Fact { private final String name; private final T value; /** * Create a new fact. * @param name of the fact * @param value of the fact */ public Fact(String name, T value) { Objects.requireNonNull(name, "name must not be null"); Objects.requireNonNull(value, "value must not be null"); this.name = name; this.value = value; } /** * Get the fact name. * @return fact name */ public String getName() { return name; } /** * Get the fact value. * @return fact value */ public T getValue() { return value; } @Override public String toString() { return "Fact{" + "name='" + name + '\'' + ", value=" + value + '}'; } /* * The Facts API represents a namespace for facts where each fact has a unique name. * Hence, equals/hashcode are deliberately calculated only on the fact name. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Fact fact = (Fact) o; return name.equals(fact.name); } @Override public int hashCode() { return Objects.hash(name); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Facts.java ================================================ package com.alibaba.cola.ruleengine.api; import java.util.*; public class Facts implements Iterable> { private final Set> facts = new HashSet<>(); /** * Add a fact, replacing any fact with the same name. * * @param name of the fact to add, must not be null * @param value of the fact to add, must not be null */ public void put(String name, T value) { Objects.requireNonNull(name, "fact name must not be null"); Objects.requireNonNull(value, "fact value must not be null"); Fact retrievedFact = getFact(name); if (retrievedFact != null) { remove(retrievedFact); } add(new Fact<>(name, value)); } /** * Add a fact, replacing any fact with the same name. * * @param fact to add, must not be null */ public void add(Fact fact) { Objects.requireNonNull(fact, "fact must not be null"); Fact retrievedFact = getFact(fact.getName()); if (retrievedFact != null) { remove(retrievedFact); } facts.add(fact); } /** * Remove a fact by name. * * @param factName name of the fact to remove, must not be null */ public void remove(String factName) { Objects.requireNonNull(factName, "fact name must not be null"); Fact fact = getFact(factName); if (fact != null) { remove(fact); } } /** * Remove a fact. * * @param fact to remove, must not be null */ public void remove(Fact fact) { Objects.requireNonNull(fact, "fact must not be null"); facts.remove(fact); } /** * Get the value of a fact by its name. This is a convenience method provided * as a short version of {@code getFact(factName).getValue()}. * * @param factName name of the fact, must not be null * @param type of the fact's value * @return the value of the fact having the given name, or null if there is * no fact with the given name */ @SuppressWarnings("unchecked") public T get(String factName) { Objects.requireNonNull(factName, "fact name must not be null"); Fact fact = getFact(factName); if (fact != null) { return (T) fact.getValue(); } return null; } /** * Get a fact by name. * * @param factName name of the fact, must not be null * @return the fact having the given name, or null if there is no fact with the given name */ public Fact getFact(String factName) { Objects.requireNonNull(factName, "fact name must not be null"); return facts.stream() .filter(fact -> fact.getName().equals(factName)) .findFirst() .orElse(null); } public boolean contains(String factName){ return getFact(factName) != null; } public boolean contains(Fact fact){ if(fact == null){ return false; } return getFact(fact.getName()) != null; } public int size(){ return facts.size(); } /** * Return a copy of the facts as a map. It is not intended to manipulate * facts outside of the rules engine (aka other than manipulating them through rules). * * @return a copy of the current facts as a {@link HashMap} */ public Map asMap() { Map map = new HashMap<>(); for (Fact fact : facts) { map.put(fact.getName(), fact.getValue()); } return map; } /** * Return an iterator on the set of facts. It is not intended to remove * facts using this iterator outside of the rules engine (aka other than doing it through rules) * * @return an iterator on the set of facts */ @Override public Iterator> iterator() { return facts.iterator(); } /** * Clear facts. */ public void clear() { facts.clear(); } @Override public String toString() { Iterator> iterator = facts.iterator(); StringBuilder stringBuilder = new StringBuilder("["); while (iterator.hasNext()) { stringBuilder.append(iterator.next().toString()); if (iterator.hasNext()) { stringBuilder.append(","); } } stringBuilder.append("]"); return stringBuilder.toString(); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Rule.java ================================================ package com.alibaba.cola.ruleengine.api; public interface Rule extends Comparable { /** * Default rule name. */ String DEFAULT_NAME = "rule"; /** * Default rule description. */ String DEFAULT_DESCRIPTION = "description"; /** * Default rule priority. */ int DEFAULT_PRIORITY = Integer.MAX_VALUE - 1; /** * Getter for rule name. * @return the rule name */ default String getName() { return DEFAULT_NAME; } /** * Getter for rule description. * @return rule description */ default String getDescription() { return DEFAULT_DESCRIPTION; } /** * Getter for rule priority. * @return rule priority */ default int getPriority() { return DEFAULT_PRIORITY; } /** * This method implements the rule's condition(s). * Implementations should handle any runtime exception and return true/false accordingly * * @return true if the rule should be applied given the provided facts, false otherwise */ boolean evaluate(Facts facts); /** * This method implements the rule's action(s). * @throws Exception thrown if an exception occurs when performing action(s) */ void execute(Facts facts); /** * This method apply facts to the rule, which is the combination of evaluation and execution * @param facts * @return true if this rule is applied successfully, false otherwise */ boolean apply(Facts facts); } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/RuleEngine.java ================================================ package com.alibaba.cola.ruleengine.api; public interface RuleEngine { /** * Fire rule on given facts. */ void fire(Rule rule, Facts facts); } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/AbstractRule.java ================================================ /* * The MIT License * * Copyright (c) 2021, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import java.util.Objects; /** * Basic rule implementation class that provides common methods. *

* You can extend this class and override {@link AbstractRule#evaluate(Facts)} and {@link AbstractRule#execute(Facts)} to provide rule * conditions and actions logic. * * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) */ public abstract class AbstractRule implements Rule { /** * Rule name. */ protected String name; /** * Rule description. */ protected String description; /** * Rule priority. */ protected int priority; /** * Create a new {@link AbstractRule}. */ public AbstractRule() { this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY); } /** * Create a new {@link AbstractRule}. * * @param name rule name */ public AbstractRule(final String name) { this(name, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY); } public AbstractRule(final int priority) { this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, priority); } /** * Create a new {@link AbstractRule}. * * @param name rule name * @param description rule description */ public AbstractRule(final String name, final String description) { this(name, description, Rule.DEFAULT_PRIORITY); } /** * Create a new {@link AbstractRule}. * * @param name rule name * @param description rule description * @param priority rule priority */ public AbstractRule(final String name, final String description, final int priority) { this.name = name; this.description = description; this.priority = priority; } public abstract boolean evaluate(Facts facts); public abstract void execute(Facts facts); public abstract boolean apply(Facts facts); public String getName() { return name; } public String getDescription() { return description; } public void setDescription(final String description) { this.description = description; } public int getPriority() { return priority; } public void setPriority(final int priority) { this.priority = priority; } /* * Rules are unique according to their names within a rules engine registry. */ @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AbstractRule basicRule = (AbstractRule) o; if (priority != basicRule.priority) return false; if (!name.equals(basicRule.name)) return false; return Objects.equals(description, basicRule.description); } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + (description != null ? description.hashCode() : 0); result = 31 * result + priority; return result; } @Override public String toString() { return name + ":" + priority; } @Override public int compareTo(final Rule rule) { if (getPriority() < rule.getPriority()) { return -1; } else if (getPriority() > rule.getPriority()) { return 1; } return 0; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/AllRules.java ================================================ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; public class AllRules extends CompositeRule{ private static final Logger LOGGER = LoggerFactory.getLogger(AllRules.class); public static CompositeRule allOf(Rule... rules) { CompositeRule instance = new AllRules(); Collections.addAll(instance.rules, rules); return instance; } @Override public boolean evaluate(Facts facts) { if (rules.stream().allMatch(rule -> rule.evaluate(facts))) return true; return false; } @Override public void execute(Facts facts) { for (Rule rule : rules) { rule.execute(facts); } } @Override protected boolean doApply(Facts facts) { LOGGER.debug("start AND composite rule apply"); if (evaluate(facts)) { for (Rule rule : rules) { //所有的rules都执行 rule.execute(facts); } return true; } return false; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/AnyRules.java ================================================ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import java.util.Collections; public class AnyRules extends CompositeRule{ public static CompositeRule anyOf(Rule... rules) { CompositeRule instance = new AnyRules(); Collections.addAll(instance.rules, rules); return instance; } @Override public boolean evaluate(Facts facts) { if (rules.stream().anyMatch(rule -> rule.evaluate(facts))) return true; return false; } @Override public void execute(Facts facts) { //不支持OR relation throw new RuntimeException("execute not supported for OR relation composite"); } @Override protected boolean doApply(Facts facts) { for (Rule rule : rules) { //短路操作,只要第一个rule成功执行,其它就不执行了 if (rule.apply(facts)) { return true; } } return false; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/CompositeRule.java ================================================ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; public abstract class CompositeRule extends AbstractRule { private static final Logger LOGGER = LoggerFactory.getLogger(CompositeRule.class); protected List rules = new ArrayList<>(); private boolean isSorted=false; public CompositeRule priority(int priority) { this.priority = priority; return this; } public CompositeRule name(String name){ this.name = name; return this; } public CompositeRule() { } @Override public boolean apply(Facts facts) { sort(); return doApply(facts); } protected abstract boolean doApply(Facts facts); protected void sort(){ if(!isSorted){ LOGGER.debug(this.name+" before sort:" + rules); Collections.sort(rules); LOGGER.debug(this.name+" after sort:" + rules); isSorted = true; } } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRule.java ================================================ /* * The MIT License * * Copyright (c) 2021, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Action; import com.alibaba.cola.ruleengine.api.Condition; import com.alibaba.cola.ruleengine.api.Facts; import java.util.ArrayList; import java.util.List; public class DefaultRule extends AbstractRule { private final Condition condition; private final List actions; public DefaultRule(Condition condition, Action action){ this.condition = condition; this.actions = new ArrayList<>(); this.actions.add(action); } public DefaultRule(Condition condition, List actions){ this.condition = condition; this.actions = actions; } public DefaultRule(String name, String description, int priority, Condition condition, List actions) { super(name, description, priority); this.condition = condition; this.actions = actions; } @Override public boolean evaluate(Facts facts) { return condition.evaluate(facts); } @Override public void execute(Facts facts) { for (Action action : actions) { action.execute(facts); } } @Override public boolean apply(Facts facts){ if (condition.evaluate(facts)){ for (Action action : actions) { action.execute(facts); } return true; } return false; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRuleEngine.java ================================================ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import com.alibaba.cola.ruleengine.api.RuleEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DefaultRuleEngine implements RuleEngine { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRuleEngine.class); @Override public void fire(Rule rule, Facts facts) { if (rule == null) { LOGGER.error("Rules is null! Nothing to apply"); return; } rule.apply(facts); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/NaturalRules.java ================================================ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; /** * This is Natural Rules */ public class NaturalRules extends CompositeRule{ private static final Logger LOGGER = LoggerFactory.getLogger(NaturalRules.class); public static CompositeRule of(Rule... rules) { CompositeRule instance = new NaturalRules(); Collections.addAll(instance.rules, rules); return instance; } @Override public boolean evaluate(Facts facts) { //不支持, which means Natural Rules can not be the children of other rules throw new RuntimeException("evaluate not supported for natural composite"); } @Override public void execute(Facts facts) { //不支持, which means Natural Rules can not be the children of other rules throw new RuntimeException("execute not supported for natural composite"); } @Override protected boolean doApply(Facts facts) { LOGGER.debug("start Natural composite rule apply"); for (Rule rule : rules) { rule.apply(facts); } return true; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/RuleBuilder.java ================================================ package com.alibaba.cola.ruleengine.core; import com.alibaba.cola.ruleengine.api.Action; import com.alibaba.cola.ruleengine.api.Condition; import com.alibaba.cola.ruleengine.api.Rule; import java.util.ArrayList; import java.util.List; public class RuleBuilder { private String name = Rule.DEFAULT_NAME; private String description = Rule.DEFAULT_DESCRIPTION; private int priority = Rule.DEFAULT_PRIORITY; private Condition condition = Condition.FALSE; private final List actions = new ArrayList<>(); /** * Set rule name. * * @param name of the rule * @return the builder instance */ public RuleBuilder name(String name) { this.name = name; return this; } /** * Set rule description. * * @param description of the rule * @return the builder instance */ public RuleBuilder description(String description) { this.description = description; return this; } /** * Set rule priority. * * @param priority of the rule * @return the builder instance */ public RuleBuilder priority(int priority) { this.priority = priority; return this; } /** * Set rule condition. * * @param condition of the rule * @return the builder instance */ public RuleBuilder when(Condition condition) { this.condition = condition; return this; } /** * Add an action to the rule. * * @param action to add * @return the builder instance */ public RuleBuilder then(Action action) { this.actions.add(action); return this; } /** * Create a new {@link Rule}. * * @return a new rule instance */ public Rule build() { return new DefaultRule(name, description, priority, condition, actions); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/CompositeRuleTest.java ================================================ package com.alibaba.cola.ruleengine; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import com.alibaba.cola.ruleengine.api.RuleEngine; import com.alibaba.cola.ruleengine.core.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class CompositeRuleTest { RuleEngine fizzBuzzEngine; @BeforeEach public void setUp(){ fizzBuzzEngine = new DefaultRuleEngine(); } @Test public void test_fizz_first(){ Facts facts = new Facts(); facts.put("number", 15); Rule rule = assembleRules(1,2,3); fizzBuzzEngine.fire(rule, facts); assertEquals(facts.getFact("fizz").getValue(), "Fizz"); } @Test public void test_buzz_first(){ Facts facts = new Facts(); facts.put("number", 15); Rule rule = assembleRules(2,1,3); fizzBuzzEngine.fire(rule, facts); assertEquals(facts.getFact("buzz").getValue(), "Buzz"); } @Test public void test_fizzBuzz_first(){ Facts facts = new Facts(); facts.put("number", 15); Rule rule = assembleRules(2,3,1); fizzBuzzEngine.fire(rule, facts); assertEquals(facts.getFact("fizz").getValue(), "Fizz"); assertEquals(facts.getFact("buzz").getValue(), "Buzz"); } private Rule assembleRules(int fizzPriority, int BuzzPriority, int FizzBuzzPriority){ // create rules Rule fizzRule = new RuleBuilder() .name("fizzRule") .description("fizz rule when input times 3, output is Fizz") .priority(fizzPriority) .when(facts -> (int) facts.get("number") % 3 == 0) .then(facts -> facts.put("fizz","Fizz")) .build(); Rule buzzRule = new RuleBuilder() .name("buzzRule") .description("buzz rule when input times 5, output is buzz") .priority(BuzzPriority) .when(facts -> (int) facts.get("number") % 5 == 0) .then(facts -> facts.put("buzz","Buzz")) .build(); Rule fizzBuzzRule = AllRules.allOf(fizzRule, buzzRule) .name("fizzBuzzRule") .priority(FizzBuzzPriority); Rule defaultRule = new RuleBuilder() .name("defaultRule") .description("default rule, output number") .priority(40) .when(facts -> true) .then(facts -> System.out.print((int) facts.get("number"))) .build(); Rule rule = AnyRules.anyOf(fizzBuzzRule, fizzRule, buzzRule, defaultRule) .name("anyRule"); return rule; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/FactsTest.java ================================================ package com.alibaba.cola.ruleengine; import com.alibaba.cola.ruleengine.api.Fact; import com.alibaba.cola.ruleengine.api.Facts; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import java.util.Map; public class FactsTest { private final Facts facts = new Facts(); @Test public void factsMustHaveUniqueName() { facts.add(new Fact<>("foo", 1)); facts.add(new Fact<>("foo", 2)); Fact fact = facts.getFact("foo"); assertEquals(fact.getValue(),2); } @Test public void testAdd() { Fact fact1 = new Fact<>("foo", 1); Fact fact2 = new Fact<>("bar", 2); facts.add(fact1); facts.add(fact2); assertTrue(facts.contains(fact1)); assertTrue(facts.contains(fact2)); } @Test public void testPut() { facts.put("foo", 1); facts.put("bar", 2); assertTrue(facts.contains(new Fact<>("foo", 1))); assertTrue(facts.contains(new Fact<>("bar", 2))); } @Test public void testRemove() { Fact foo = new Fact<>("foo", 1); facts.add(foo); facts.remove(foo); assertTrue(facts.size() == 0); } @Test public void testRemoveByName() { Fact foo = new Fact<>("foo", 1); facts.add(foo); facts.remove("foo"); assertTrue(facts.size() == 0); } @Test public void testGet() { Fact fact = new Fact<>("foo", 1); facts.add(fact); Integer value = facts.get("foo"); assertEquals(value, 1); } @Test public void testGetFact() { Fact fact = new Fact<>("foo", 1); facts.add(fact); Fact retrievedFact = facts.getFact("foo"); assertEquals(retrievedFact, fact); } @Test public void testAsMap() { Fact fact1 = new Fact<>("foo", 1); Fact fact2 = new Fact<>("bar", 2); facts.add(fact1); facts.add(fact2); Map map = facts.asMap(); assertTrue(map.containsKey("foo")); assertTrue(map.containsKey("bar")); } @Test public void testClear() { Facts facts = new Facts(); facts.add(new Fact<>("foo", 1)); facts.clear(); assertTrue(facts.size() == 0); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/FizzBuzz.java ================================================ package com.alibaba.cola.ruleengine; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import com.alibaba.cola.ruleengine.api.RuleEngine; import com.alibaba.cola.ruleengine.core.AllRules; import com.alibaba.cola.ruleengine.core.AnyRules; import com.alibaba.cola.ruleengine.core.DefaultRuleEngine; import com.alibaba.cola.ruleengine.core.RuleBuilder; public class FizzBuzz { public static void main(String[] args) { Rule fizzBuzzRule = assembleFizzBuzzRule(); RuleEngine ruleEngine = new DefaultRuleEngine(); for (int i = 0; i <= 15; i++) { Facts facts = new Facts(); facts.put("number", i); ruleEngine.fire(fizzBuzzRule, facts); System.out.println(); } } public static Rule assembleFizzBuzzRule() { Rule fizzRule = new RuleBuilder() .name("fizzRule") .description("fizz rule when input times 3, output is Fizz") .priority(10) .when(facts -> (int) facts.get("number") % 3 == 0) .then(facts -> System.out.print("Fizz")) .build(); Rule buzzRule = new RuleBuilder() .name("buzzRule") .description("buzz rule when input times 5, output is buzz") .priority(20) .when(facts -> (int) facts.get("number") % 5 == 0) .then(facts -> System.out.print("Buzz")) .build(); Rule fizzBuzzRule = AllRules.allOf(fizzRule, buzzRule) .name("fizzBuzzRule") .priority(1); Rule defaultRule = new RuleBuilder() .name("defaultRule") .description("default rule, output number") .priority(40) .when(facts -> true) .then(facts -> System.out.print((int) facts.get("number"))) .build(); Rule rule = AnyRules.anyOf(fizzBuzzRule, fizzRule, buzzRule, defaultRule) .name("anyRule"); return rule; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/HelloWorld.java ================================================ package com.alibaba.cola.ruleengine; import com.alibaba.cola.ruleengine.api.Rule; import com.alibaba.cola.ruleengine.api.RuleEngine; import com.alibaba.cola.ruleengine.core.DefaultRuleEngine; import com.alibaba.cola.ruleengine.core.RuleBuilder; public class HelloWorld { public static void main(String[] args) { RuleEngine ruleEngine = new DefaultRuleEngine(); Rule rule = new RuleBuilder() .name("hello world rule") .description("always say hello world") .priority(1) .when(facts -> true) .then(facts -> System.out.println("hello world")) .build(); ruleEngine.fire(rule, null); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/PriorityTest.java ================================================ package com.alibaba.cola.ruleengine; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import com.alibaba.cola.ruleengine.api.RuleEngine; import com.alibaba.cola.ruleengine.core.DefaultRule; import com.alibaba.cola.ruleengine.core.DefaultRuleEngine; import com.alibaba.cola.ruleengine.core.NaturalRules; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.ArrayList; public class PriorityTest { RuleEngine ruleEngine; @BeforeEach public void setUp() { ruleEngine = new DefaultRuleEngine(); } @Test public void testNoPriority() { DummyRule r1 = new DummyRule(1); DummyRule r2 = new DummyRule(2); DummyRule r3 = new DummyRule(3); // assertThat(rules).startsWith(r1).endsWith(r3); } @Test public void testPriority() { DummyRule r1 = new DummyRule(10); DummyRule r2 = new DummyRule(3); DummyRule r3 = new DummyRule(1); // assertThat(rules).startsWith(r3).endsWith(r1); } @Test public void test_natural_rule() { DummyRule r1 = new DummyRule(10); DummyRule r2 = new DummyRule(3); DummyRule r3 = new DummyRule(1); Facts facts = new Facts(); facts.put("number", 15); Rule naturalRules = NaturalRules.of(r1, r2, r3); ruleEngine.fire(naturalRules, facts); } static class DummyRule extends DefaultRule { public DummyRule(int priority) { super("rule" + priority, null, priority, facts -> true, new ArrayList<>()); } @Override public boolean evaluate(Facts facts) { return true; } @Override public void execute(Facts facts) { System.out.println(facts.getFact("number").getValue()); } @Override public boolean apply(Facts facts) { System.out.println(name + ": " + facts.getFact("number").getValue()); return true; } } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleBuilderTest.java ================================================ package com.alibaba.cola.ruleengine; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import com.alibaba.cola.ruleengine.api.RuleEngine; import com.alibaba.cola.ruleengine.core.*; import org.junit.jupiter.api.Test; public class RuleBuilderTest { @Test public void testFizzBuzz() { RuleEngine fizzBuzzEngine = new DefaultRuleEngine(); // create rules Rule fizzRule = new RuleBuilder() .name("fizzRule") .description("fizz rule when input times 3, output is Fizz") .priority(10) .when(facts -> (int) facts.get("number") % 3 == 0) .then(facts -> System.out.print("Fizz")) .build(); Rule buzzRule = new RuleBuilder() .name("buzzRule") .description("buzz rule when input times 5, output is buzz") .priority(1) .when(facts -> (int) facts.get("number") % 5 == 0) .then(facts -> System.out.print("Buzz")) .build(); Rule fizzBuzzRule = AllRules.allOf(fizzRule, buzzRule) .name("fizzBuzzRule") .priority(30); Rule defaultRule = new RuleBuilder() .name("defaultRule") .description("default rule, output number") .priority(40) .when(facts -> true) .then(facts -> System.out.print((int) facts.get("number"))) .build(); Rule rule = AnyRules.anyOf(fizzBuzzRule, fizzRule, buzzRule, defaultRule) .name("anyRule"); // fire rules Facts facts = new Facts(); facts.put("number", 15); fizzBuzzEngine.fire(rule, facts); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleEngineTest.java ================================================ package com.alibaba.cola.ruleengine; import com.alibaba.cola.ruleengine.api.Facts; import com.alibaba.cola.ruleengine.api.Rule; import com.alibaba.cola.ruleengine.api.RuleEngine; import com.alibaba.cola.ruleengine.core.*; import org.junit.jupiter.api.Test; public class RuleEngineTest { @Test public void testRuleEngine() { RuleEngine ruleEngine = new DefaultRuleEngine(); ruleEngine.fire(null, null); } @Test public void testFizzBuzz() { RuleEngine fizzBuzzEngine = new DefaultRuleEngine(); // create rules Rule fizzRule = new DefaultRule(facts -> (int) facts.get("number") % 3 == 0, facts -> System.out.print("Fizz")); Rule buzzRule = new DefaultRule(facts -> (int) facts.get("number") % 5 == 0, facts -> System.out.print("Buzz")); Rule fizzBuzzRule = AllRules.allOf(fizzRule, buzzRule); Rule NonFizzBuzzRule = new DefaultRule(facts -> true, facts -> System.out.print((int) facts.get("number"))); Rule rule = AnyRules.anyOf(fizzBuzzRule, fizzRule, buzzRule, NonFizzBuzzRule); // fire rules Facts facts = new Facts(); facts.put("number", 15); fizzBuzzEngine.fire(rule, facts); // for (int i = 1; i <= 10; i++) { // facts.put("number", i); // fizzBuzzEngine.fire(rules, facts); // System.out.println(); // } } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/FizzBuzzTest.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz; //import com.alibaba.cola.ruleengine.fizzbuzz.v1.FizzBuzz; import com.alibaba.cola.ruleengine.fizzbuzz.v2.FizzBuzz; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class FizzBuzzTest { @Test public void num_given_1() { //given int input = 1; //when String result = FizzBuzz.count(input); //then assertEquals(result, "1"); } @Test public void fizz_given_3() { //given int input = 3; //when String result = FizzBuzz.count(input); //then assertEquals(result, "Fizz"); } @Test public void buzz_given_5() { //given int input = 5; //when String result = FizzBuzz.count(input); //then assertEquals(result, "Buzz"); } @Test public void fizz_buzz_given_15() { //given int input = 15; //when String result = FizzBuzz.count(input); //then assertEquals(result, "FizzBuzz"); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v1/FizzBuzz.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz.v1; public class FizzBuzz { public static String count(int n){ if (((n % 3) == 0) && ((n % 5) == 0)) return "FizzBuzz"; if ((n % 3) == 0) return "Fizz"; if ((n % 5) == 0) return "Buzz"; return String.valueOf(n); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Action.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz.v2; @FunctionalInterface public interface Action { String execute(int n); } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Condition.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz.v2; import java.util.Objects; @FunctionalInterface public interface Condition { boolean evaluate(int n); //谓词and逻辑,参考Predicate default Condition and(Condition other) { Objects.requireNonNull(other); return (n) -> { return this.evaluate(n) && other.evaluate(n); }; } //谓词or逻辑,参考Predicate default Condition or(Condition other) { Objects.requireNonNull(other); return (n) -> { return this.evaluate(n) || other.evaluate(n); }; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/FizzBuzz.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz.v2; import static com.alibaba.cola.ruleengine.fizzbuzz.v2.SimpleRuleEngine.*; import static com.alibaba.cola.ruleengine.fizzbuzz.v2.TimesCondition.times; /** * 用简易规则引擎重构后的FizzBuzz实现 */ public class FizzBuzz { public static String count(int i){ //Composite condition Rule fizzBuzzRule = atom(times(3).and(times(5)), n -> "FizzBuzz"); Rule fizzRule = atom(times(3) , n -> "Fizz"); Rule buzzRule = atom(times(5), n -> "Buzz"); //Composite rule Rule compositeFizzBuzzRule = allOf(fizzRule, buzzRule); Rule defaultRule = atom(n -> true, n -> String.valueOf(n)); Rule rule = anyOf(compositeFizzBuzzRule, fizzRule, buzzRule, defaultRule); return rule.apply(i); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Rule.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz.v2; @FunctionalInterface public interface Rule { String apply(int n); } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/SimpleRuleEngine.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz.v2; import java.util.Arrays; import java.util.stream.Collectors; import java.util.stream.Stream; /** * 通过原子atom rule,以及atom rule之间的组合解决FizzBuzz问题 * 这里为了简单使用Rule的组合模式代替了RuleEngine实体 * 注意:这个SimpleRuleEngine只能解决输入为n,输出为String的FizzBuzz问题 * 完全不具备通用性 */ public class SimpleRuleEngine { public static Rule atom(Condition condition, Action action){ return n -> condition.evaluate(n) ? action.execute(n) : ""; } public static Rule anyOf(Rule... rules){ return n -> stringStream(n, rules).filter(s -> !s.isEmpty()).findFirst().get(); } public static Rule allOf(Rule... rules){ return n -> stringStream(n, rules).collect(Collectors.joining()); } public static Stream stringStream(int n, Rule[] rules){ return Arrays.stream(rules).map(r -> r.apply(n)); } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/TimesCondition.java ================================================ package com.alibaba.cola.ruleengine.fizzbuzz.v2; /** * 计算倍数关系的谓词逻辑 */ public class TimesCondition { public static Condition times(int i){ return n -> n % i == 0; } } ================================================ FILE: cola-components/cola-component-ruleengine/src/test/resources/logback-test.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/cola-component-statemachine/.gitignore ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/cola-component-statemachine/README.md ================================================ ## 作用 简单、轻量、性能极高的状态机DSL实现,解决业务中的状态流转问题。 ## 原理 https://blog.csdn.net/significantfrank/article/details/104996419 ================================================ FILE: cola-components/cola-component-statemachine/pom.xml ================================================ 4.0.0 org.junit.jupiter junit-jupiter RELEASE test com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-statemachine jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/Action.java ================================================ package com.alibaba.cola.statemachine; /** * Generic strategy interface used by a state machine to respond * events by executing an {@code Action} with a {@link StateContext}. * * @author Frank Zhang * @date 2020-02-07 2:51 PM */ public interface Action { // /** // * Execute action with a {@link StateContext}. // * // * @param context the state context // */ // void execute(StateContext context); public void execute(S from, S to, E event, C context); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/Condition.java ================================================ package com.alibaba.cola.statemachine; /** * Condition * * @author Frank Zhang * @date 2020-02-07 2:50 PM */ public interface Condition { /** * @param context context object * @return whether the context satisfied current condition */ boolean isSatisfied(C context); default String name(){ return this.getClass().getSimpleName(); } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/State.java ================================================ package com.alibaba.cola.statemachine; import com.alibaba.cola.statemachine.impl.TransitionType; import java.util.Collection; import java.util.List; import java.util.Optional; /** * State * * @param the type of state * @param the type of event * * @author Frank Zhang * @date 2020-02-07 2:12 PM */ public interface State extends Visitable{ /** * Gets the state identifier. * * @return the state identifiers */ S getId(); /** * Add transition to the state * @param event the event of the Transition * @param target the target of the transition * @return */ Transition addTransition(E event, State target, TransitionType transitionType); List> addTransitions(E event, List> targets, TransitionType transitionType); List> getEventTransitions(E event); Collection> getAllTransitions(); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/StateContext.java ================================================ package com.alibaba.cola.statemachine; /** * StateContext * * @author Frank Zhang * @date 2020-02-07 2:49 PM */ public interface StateContext { /** * Gets the transition. * * @return the transition */ Transition getTransition(); /** * Gets the state machine. * * @return the state machine */ StateMachine getStateMachine(); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/StateMachine.java ================================================ package com.alibaba.cola.statemachine; import java.util.List; /** * StateMachine * * @author Frank Zhang * * @param the type of state * @param the type of event * @param the user defined context * @date 2020-02-07 2:13 PM */ public interface StateMachine extends Visitable{ /** * Verify if an event {@code E} can be fired from current state {@code S} * @param sourceStateId * @param event * @return */ boolean verify(S sourceStateId,E event); /** * Send an event {@code E} to the state machine. * * @param sourceState the source state * @param event the event to send * @param ctx the user defined context * @return the target state */ S fireEvent(S sourceState, E event, C ctx); List fireParallelEvent(S sourceState, E event, C ctx); /** * MachineId is the identifier for a State Machine * @return */ String getMachineId(); /** * Use visitor pattern to display the structure of the state machine */ void showStateMachine(); String generatePlantUML(); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/StateMachineFactory.java ================================================ package com.alibaba.cola.statemachine; import com.alibaba.cola.statemachine.impl.StateMachineException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * StateMachineFactory * * @author Frank Zhang * @date 2020-02-08 10:21 PM */ public class StateMachineFactory { static Map stateMachineMap = new ConcurrentHashMap<>(); public static void register(StateMachine stateMachine){ String machineId = stateMachine.getMachineId(); if(stateMachineMap.get(machineId) != null){ throw new StateMachineException("The state machine with id ["+machineId+"] is already built, no need to build again"); } stateMachineMap.put(stateMachine.getMachineId(), stateMachine); } public static StateMachine get(String machineId){ StateMachine stateMachine = stateMachineMap.get(machineId); if(stateMachine == null){ throw new StateMachineException("There is no stateMachine instance for "+machineId+", please build it first"); } return stateMachine; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/Transition.java ================================================ package com.alibaba.cola.statemachine; import com.alibaba.cola.statemachine.impl.TransitionType; /** * {@code Transition} is something what a state machine associates with a state * changes. * * @author Frank Zhang * * @param the type of state * @param the type of event * @param the type of user defined context, which is used to hold application data * * @date 2020-02-07 2:20 PM */ public interface Transition{ /** * Gets the source state of this transition. * * @return the source state */ State getSource(); void setSource(State state); E getEvent(); void setEvent(E event); void setType(TransitionType type); /** * Gets the target state of this transition. * * @return the target state */ State getTarget(); void setTarget(State state); /** * Gets the guard of this transition. * * @return the guard */ Condition getCondition(); void setCondition(Condition condition); Action getAction(); void setAction(Action action); /** * Do transition from source state to target state. * * @return the target state */ State transit(C ctx, boolean checkCondition); /** * Verify transition correctness */ void verify(); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/Visitable.java ================================================ package com.alibaba.cola.statemachine; /** * Visitable * * @author Frank Zhang * @date 2020-02-08 8:41 PM */ public interface Visitable { String accept(final Visitor visitor); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/Visitor.java ================================================ package com.alibaba.cola.statemachine; /** * Visitor * * @author Frank Zhang * @date 2020-02-08 8:41 PM */ public interface Visitor { char LF = '\n'; /** * @param visitable the element to be visited. * @return */ String visitOnEntry(StateMachine visitable); /** * @param visitable the element to be visited. * @return */ String visitOnExit(StateMachine visitable); /** * @param visitable the element to be visited. * @return */ String visitOnEntry(State visitable); /** * @param visitable the element to be visited. * @return */ String visitOnExit(State visitable); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/AbstractParallelTransitionBuilder.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.impl.StateHelper; import com.alibaba.cola.statemachine.impl.TransitionType; import java.util.List; import java.util.Map; abstract class AbstractParallelTransitionBuilder implements ParallelFrom,On,To{ final Map> stateMap; protected List> targets; final TransitionType transitionType; public AbstractParallelTransitionBuilder(Map> stateMap, TransitionType transitionType) { this.stateMap = stateMap; this.transitionType = transitionType; } @Override public To toAmong(S ... stateIds) { targets = StateHelper.getStates(stateMap, stateIds); return this; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/AbstractTransitionBuilder.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.impl.StateHelper; import com.alibaba.cola.statemachine.impl.TransitionType; import java.util.Map; /** * Take TransitionBuilderImpl and TransitionsBuilderImpl sharing variables * and methods to that abstract class, which acts as their parent, * instead of having TransitionsBuilderImpl inherit from * TransitionsBuilderImpl. I think that the multi-flow * builder(TransitionsBuilderImpl) and single-flow * builder(TransitionBuilderImpl) are equal and not supposed to be * parent-child relationship, they from, when, and perform methods * are not the same, and although it looks like just a set of loops * but logically should not be inherited over Override. * ( Just as there was no relationship, why should we talk to each other, * say a we are not suitable). With the abstract class, multi-flow and single-flow * only use to focus on their respective functions are single-flow, * or multi-flow. Conform to a single duty. * @author welliem * @date 2023-07-14 12:13 */ abstract class AbstractTransitionBuilder implements From,On,To{ final Map> stateMap; protected State target; final TransitionType transitionType; public AbstractTransitionBuilder(Map> stateMap, TransitionType transitionType) { this.stateMap = stateMap; this.transitionType = transitionType; } @Override public To to(S stateId) { target = StateHelper.getState(stateMap, stateId); return this; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/AlertFailCallback.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.exception.TransitionFailException; /** * Alert fail callback, throw an {@code TransitionFailException} * * @author 龙也 * @date 2022/9/15 12:02 PM */ public class AlertFailCallback implements FailCallback { @Override public void onFail(S sourceState, E event, C context) { throw new TransitionFailException( "Cannot fire event [" + event + "] on current state [" + sourceState + "] with context [" + context + "]" ); } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/ExternalParallelTransitionBuilder.java ================================================ package com.alibaba.cola.statemachine.builder; public interface ExternalParallelTransitionBuilder { /** * Build transition source state. * @param stateId id of state * @return from clause builder */ ParallelFrom from(S stateId); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/ExternalTransitionBuilder.java ================================================ package com.alibaba.cola.statemachine.builder; /** * ExternalTransitionBuilder * * @author Frank Zhang * @date 2020-02-07 6:11 PM */ public interface ExternalTransitionBuilder { /** * Build transition source state. * @param stateId id of state * @return from clause builder */ From from(S stateId); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/ExternalTransitionsBuilder.java ================================================ package com.alibaba.cola.statemachine.builder; /** * ExternalTransitionsBuilder * * This builder is for multiple transitions, currently only support multiple sources <----> one target * * @author Frank Zhang * @date 2020-02-08 7:41 PM */ public interface ExternalTransitionsBuilder { From fromAmong(S... stateIds); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/FailCallback.java ================================================ package com.alibaba.cola.statemachine.builder; /** * FailCallback * * @author 龙也 * @date 2022/9/15 12:02 PM */ @FunctionalInterface public interface FailCallback { /** * Callback function to execute if failed to trigger an Event * * @param sourceState * @param event * @param context */ void onFail(S sourceState, E event, C context); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/From.java ================================================ package com.alibaba.cola.statemachine.builder; /** * From * * @author Frank Zhang * @date 2020-02-07 6:13 PM */ public interface From { /** * Build transition target state and return to clause builder * @param stateId id of state * @return To clause builder */ To to(S stateId); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/InternalTransitionBuilder.java ================================================ package com.alibaba.cola.statemachine.builder; /** * InternalTransitionBuilder * * @author Frank Zhang * @date 2020-02-07 9:39 PM */ public interface InternalTransitionBuilder { /** * Build a internal transition * @param stateId id of transition * @return To clause builder */ To within(S stateId); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/NumbFailCallback.java ================================================ package com.alibaba.cola.statemachine.builder; /** * Default fail callback, do nothing. * * @author 龙也 * @date 2022/9/15 12:02 PM */ public class NumbFailCallback implements FailCallback { @Override public void onFail(S sourceState, E event, C context) { //do nothing } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/On.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.Condition; /** * On * * @author Frank Zhang * @date 2020-02-07 6:14 PM */ public interface On extends When{ /** * Add condition for the transition * @param condition transition condition * @return When clause builder */ When when(Condition condition); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/ParallelFrom.java ================================================ package com.alibaba.cola.statemachine.builder; public interface ParallelFrom { /** * Build transition target state and return to clause builder * @param stateIds id of state * @return To clause builder */ To toAmong(S ... stateIds); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/ParallelTransitionBuilderImpl.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.Transition; import com.alibaba.cola.statemachine.impl.StateHelper; import com.alibaba.cola.statemachine.impl.TransitionType; import java.util.List; import java.util.Map; class ParallelTransitionBuilderImpl extends AbstractParallelTransitionBuilder implements ExternalParallelTransitionBuilder { private State source; private List> transitions; public ParallelTransitionBuilderImpl(Map> stateMap, TransitionType transitionType) { super(stateMap, transitionType); } @Override public ParallelFrom from(S stateId) { source = StateHelper.getState(stateMap, stateId); return this; } @Override public When when(Condition condition) { for (Transition transition : transitions) { transition.setCondition(condition); } return this; } @Override public On on(E event) { transitions = source.addTransitions(event, targets, transitionType); return this; } @Override public void perform(Action action) { for (Transition transition : transitions) { transition.setAction(action); } } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/StateMachineBuilder.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.StateMachine; /** * StateMachineBuilder * * @author Frank Zhang * @date 2020-02-07 5:32 PM */ public interface StateMachineBuilder { /** * Builder for one transition * * @return External transition builder */ ExternalTransitionBuilder externalTransition(); /** * Builder for multiple transitions * * @return External transition builder */ ExternalTransitionsBuilder externalTransitions(); /** * Builder for parallel transitions * * @return External transition builder */ ExternalParallelTransitionBuilder externalParallelTransition(); /** * Start to build internal transition * * @return Internal transition builder */ InternalTransitionBuilder internalTransition(); /** * set up fail callback, default do nothing {@code NumbFailCallbackImpl} * * @param callback */ void setFailCallback(FailCallback callback); StateMachine build(String machineId); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/StateMachineBuilderFactory.java ================================================ package com.alibaba.cola.statemachine.builder; /** * StateMachineBuilderFactory * * @author Frank Zhang * @date 2020-02-08 12:33 PM */ public class StateMachineBuilderFactory { public static StateMachineBuilder create(){ return new StateMachineBuilderImpl<>(); } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/StateMachineBuilderImpl.java ================================================ package com.alibaba.cola.statemachine.builder; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.StateMachineFactory; import com.alibaba.cola.statemachine.impl.StateMachineImpl; import com.alibaba.cola.statemachine.impl.TransitionType; /** * StateMachineBuilderImpl * * @author Frank Zhang * @date 2020-02-07 9:40 PM */ public class StateMachineBuilderImpl implements StateMachineBuilder { /** * StateMap is the same with stateMachine, as the core of state machine is holding reference to states. */ private final Map> stateMap = new ConcurrentHashMap<>(); private final StateMachineImpl stateMachine = new StateMachineImpl<>(stateMap); private FailCallback failCallback = new NumbFailCallback<>(); @Override public ExternalTransitionBuilder externalTransition() { return new TransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL); } @Override public ExternalTransitionsBuilder externalTransitions() { return new TransitionsBuilderImpl<>(stateMap, TransitionType.EXTERNAL); } @Override public ExternalParallelTransitionBuilder externalParallelTransition() { return new ParallelTransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL); } @Override public InternalTransitionBuilder internalTransition() { return new TransitionBuilderImpl<>(stateMap, TransitionType.INTERNAL); } @Override public void setFailCallback(FailCallback callback) { this.failCallback = callback; } @Override public StateMachine build(String machineId) { stateMachine.setMachineId(machineId); stateMachine.setReady(true); stateMachine.setFailCallback(failCallback); StateMachineFactory.register(stateMachine); return stateMachine; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/To.java ================================================ package com.alibaba.cola.statemachine.builder; /** * To * * @author Frank Zhang * @date 2020-02-07 6:14 PM */ public interface To { /** * Build transition event * @param event transition event * @return On clause builder */ On on(E event); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/TransitionBuilderImpl.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.Transition; import com.alibaba.cola.statemachine.impl.StateHelper; import com.alibaba.cola.statemachine.impl.TransitionType; import java.util.Map; /** * TransitionBuilderImpl * * @author Frank Zhang * @date 2020-02-07 10:20 PM */ class TransitionBuilderImpl extends AbstractTransitionBuilder implements ExternalTransitionBuilder, InternalTransitionBuilder { private State source; private Transition transition; public TransitionBuilderImpl(Map> stateMap, TransitionType transitionType) { super(stateMap, transitionType); } @Override public From from(S stateId) { source = StateHelper.getState(stateMap, stateId); return this; } @Override public To within(S stateId) { source = target = StateHelper.getState(stateMap, stateId); return this; } @Override public When when(Condition condition) { transition.setCondition(condition); return this; } @Override public On on(E event) { transition = source.addTransition(event, target, transitionType); return this; } @Override public void perform(Action action) { transition.setAction(action); } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/TransitionsBuilderImpl.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.Transition; import com.alibaba.cola.statemachine.impl.StateHelper; import com.alibaba.cola.statemachine.impl.TransitionType; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * TransitionsBuilderImpl * * @author Frank Zhang * @date 2020-02-08 7:43 PM */ public class TransitionsBuilderImpl extends AbstractTransitionBuilder implements ExternalTransitionsBuilder { /** * This is for fromAmong where multiple sources can be configured to point to one target */ private List> sources = new ArrayList<>(); private List> transitions = new ArrayList<>(); public TransitionsBuilderImpl(Map> stateMap, TransitionType transitionType) { super(stateMap, transitionType); } @Override public From fromAmong(S... stateIds) { for(S stateId : stateIds) { sources.add(StateHelper.getState(super.stateMap, stateId)); } return this; } @Override public On on(E event) { for(State source : sources) { Transition transition = source.addTransition(event, super.target, super.transitionType); transitions.add(transition); } return this; } @Override public When when(Condition condition) { for(Transition transition : transitions){ transition.setCondition(condition); } return this; } @Override public void perform(Action action) { for(Transition transition : transitions){ transition.setAction(action); } } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/When.java ================================================ package com.alibaba.cola.statemachine.builder; import com.alibaba.cola.statemachine.Action; /** * When * * @author Frank Zhang * @date 2020-02-07 9:33 PM */ public interface When{ /** * Define action to be performed during transition * * @param action performed action */ void perform(Action action); } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/builder/package-info.java ================================================ /** * The builder is to provide fluent interfaces for statemachine, which is a classic Internal DSL implementing skill. * * For more information, please check Martin Fowler's Article: https://martinfowler.com/bliki/FluentInterface.html * @author Frank Zhang */ package com.alibaba.cola.statemachine.builder; ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/exception/TransitionFailException.java ================================================ package com.alibaba.cola.statemachine.exception; /** * @author 龙也 * @date 2022/9/15 12:08 PM */ public class TransitionFailException extends RuntimeException { public TransitionFailException(String errMsg) { super(errMsg); } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/Debugger.java ================================================ package com.alibaba.cola.statemachine.impl; /** * Debugger, This is used to decouple Logging framework dependency * * @author Frank Zhang * @date 2020-02-11 11:08 AM */ public class Debugger { private static boolean isDebugOn = false; public static void debug(String message){ if(isDebugOn){ System.out.println(message); } } public static void enableDebug(){ isDebugOn = true; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/EventTransitions.java ================================================ package com.alibaba.cola.statemachine.impl; import com.alibaba.cola.statemachine.Transition; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * EventTransitions * * 同一个Event可以触发多个Transitions,https://github.com/alibaba/COLA/pull/158 * * @author Frank Zhang * @date 2021-05-28 5:17 PM */ public class EventTransitions { private HashMap>> eventTransitions; public EventTransitions(){ eventTransitions = new HashMap<>(); } public void put(E event, Transition transition){ if(eventTransitions.get(event) == null){ List> transitions = new ArrayList<>(); transitions.add(transition); eventTransitions.put(event, transitions); } else{ List existingTransitions = eventTransitions.get(event); verify(existingTransitions, transition); existingTransitions.add(transition); } } /** * Per one source and target state, there is only one transition is allowed * @param existingTransitions * @param newTransition */ private void verify(List> existingTransitions, Transition newTransition) { for (Transition transition : existingTransitions) { if (transition.equals(newTransition)) { throw new StateMachineException(transition + " already Exist, you can not add another one"); } } } public List> get(E event){ return eventTransitions.get(event); } public List> allTransitions(){ List> allTransitions = new ArrayList<>(); for(List> transitions : eventTransitions.values()){ allTransitions.addAll(transitions); } return allTransitions; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/PlantUMLVisitor.java ================================================ package com.alibaba.cola.statemachine.impl; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.Transition; import com.alibaba.cola.statemachine.Visitor; /** * PlantUMLVisitor * * @author Frank Zhang * @date 2020-02-09 7:47 PM */ public class PlantUMLVisitor implements Visitor { /** * Since the state machine is stateless, there is no initial state. * * You have to add "[*] -> initialState" to mark it as a state machine diagram. * otherwise it will be recognized as a sequence diagram. * * @param visitable the element to be visited. * @return */ @Override public String visitOnEntry(StateMachine visitable) { return "@startuml" + LF; } @Override public String visitOnExit(StateMachine visitable) { return "@enduml"; } @Override public String visitOnEntry(State state) { StringBuilder sb = new StringBuilder(); for(Transition transition: state.getAllTransitions()){ sb.append(transition.getSource().getId()) .append(" --> ") .append(transition.getTarget().getId()) .append(" : ") .append(transition.getEvent()) .append(LF); } return sb.toString(); } @Override public String visitOnExit(State state) { return ""; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/StateHelper.java ================================================ package com.alibaba.cola.statemachine.impl; import com.alibaba.cola.statemachine.State; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * StateHelper * * @author Frank Zhang * @date 2020-02-08 4:23 PM */ public class StateHelper { public static State getState(Map> stateMap, S stateId){ State state = stateMap.get(stateId); if (state == null) { state = new StateImpl<>(stateId); stateMap.put(stateId, state); } return state; } public static List> getStates(Map> stateMap, S ... stateIds) { List> result = new ArrayList<>(); for (S stateId : stateIds) { State state = getState(stateMap, stateId); result.add(state); } return result; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/StateImpl.java ================================================ package com.alibaba.cola.statemachine.impl; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.Transition; import com.alibaba.cola.statemachine.Visitor; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * StateImpl * * @author Frank Zhang * @date 2020-02-07 11:19 PM */ public class StateImpl implements State { protected final S stateId; private EventTransitions eventTransitions = new EventTransitions(); StateImpl(S stateId){ this.stateId = stateId; } @Override public Transition addTransition(E event, State target, TransitionType transitionType) { Transition newTransition = new TransitionImpl<>(); newTransition.setSource(this); newTransition.setTarget(target); newTransition.setEvent(event); newTransition.setType(transitionType); Debugger.debug("Begin to add new transition: "+ newTransition); eventTransitions.put(event, newTransition); return newTransition; } @Override public List> addTransitions(E event, List> targets, TransitionType transitionType) { List> result = new ArrayList<>(); for (State target : targets) { Transition secTransition = addTransition(event, target, transitionType); result.add(secTransition); } return result; } @Override public List> getEventTransitions(E event) { return eventTransitions.get(event); } @Override public Collection> getAllTransitions() { return eventTransitions.allTransitions(); } @Override public S getId() { return stateId; } @Override public String accept(Visitor visitor) { String entry = visitor.visitOnEntry(this); String exit = visitor.visitOnExit(this); return entry + exit; } @Override public boolean equals(Object anObject){ if(anObject instanceof State){ State other = (State)anObject; if(this.stateId.equals(other.getId())) return true; } return false; } @Override public String toString(){ return stateId.toString(); } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/StateMachineException.java ================================================ package com.alibaba.cola.statemachine.impl; /** * StateMachineException * * @author Frank Zhang * @date 2020-02-08 5:28 PM */ public class StateMachineException extends RuntimeException{ public StateMachineException(String message){ super(message); } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/StateMachineImpl.java ================================================ package com.alibaba.cola.statemachine.impl; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.Transition; import com.alibaba.cola.statemachine.Visitor; import com.alibaba.cola.statemachine.builder.FailCallback; /** * For performance consideration, * The state machine is made "stateless" on purpose. * Once it's built, it can be shared by multi-thread *

* One side effect is since the state machine is stateless, we can not get current state from State Machine. * * @author Frank Zhang * @date 2020-02-07 5:40 PM */ public class StateMachineImpl implements StateMachine { private String machineId; private final Map> stateMap; private boolean ready; private FailCallback failCallback; public StateMachineImpl(Map> stateMap) { this.stateMap = stateMap; } @Override public boolean verify(S sourceStateId, E event) { isReady(); State sourceState = getState(sourceStateId); List> transitions = sourceState.getEventTransitions(event); return transitions != null && transitions.size() != 0; } @Override public S fireEvent(S sourceStateId, E event, C ctx) { isReady(); Transition transition = routeTransition(sourceStateId, event, ctx); if (transition == null) { Debugger.debug("There is no Transition for " + event); failCallback.onFail(sourceStateId, event, ctx); return sourceStateId; } return transition.transit(ctx, false).getId(); } @Override public List fireParallelEvent(S sourceState, E event, C context) { isReady(); List> transitions = routeTransitions(sourceState, event, context); List result = new ArrayList<>(); if (transitions == null||transitions.isEmpty()) { Debugger.debug("There is no Transition for " + event); failCallback.onFail(sourceState, event, context); result.add(sourceState); return result; } for (Transition transition : transitions) { S id = transition.transit(context, false).getId(); result.add(id); } return result; } private Transition routeTransition(S sourceStateId, E event, C ctx) { State sourceState = getState(sourceStateId); List> transitions = sourceState.getEventTransitions(event); if (transitions == null || transitions.size() == 0) { return null; } Transition transit = null; for (Transition transition : transitions) { if (transition.getCondition() == null) { transit = transition; } else if (transition.getCondition().isSatisfied(ctx)) { transit = transition; break; } } return transit; } private List> routeTransitions(S sourceStateId, E event, C context) { State sourceState = getState(sourceStateId); List> result = new ArrayList<>(); List> transitions = sourceState.getEventTransitions(event); if (transitions == null || transitions.size() == 0) { return null; } for (Transition transition : transitions) { Transition transit = null; if (transition.getCondition() == null) { transit = transition; } else if (transition.getCondition().isSatisfied(context)) { transit = transition; } result.add(transit); } return result; } private State getState(S currentStateId) { State state = StateHelper.getState(stateMap, currentStateId); if (state == null) { showStateMachine(); throw new StateMachineException(currentStateId + " is not found, please check state machine"); } return state; } private void isReady() { if (!ready) { throw new StateMachineException("State machine is not built yet, can not work"); } } @Override public String accept(Visitor visitor) { StringBuilder sb = new StringBuilder(); sb.append(visitor.visitOnEntry(this)); for (State state : stateMap.values()) { sb.append(state.accept(visitor)); } sb.append(visitor.visitOnExit(this)); return sb.toString(); } @Override public void showStateMachine() { SysOutVisitor sysOutVisitor = new SysOutVisitor(); accept(sysOutVisitor); } @Override public String generatePlantUML() { PlantUMLVisitor plantUMLVisitor = new PlantUMLVisitor(); return accept(plantUMLVisitor); } @Override public String getMachineId() { return machineId; } public void setMachineId(String machineId) { this.machineId = machineId; } public void setReady(boolean ready) { this.ready = ready; } public void setFailCallback(FailCallback failCallback) { this.failCallback = failCallback; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/SysOutVisitor.java ================================================ package com.alibaba.cola.statemachine.impl; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.Transition; import com.alibaba.cola.statemachine.Visitor; /** * SysOutVisitor * * @author Frank Zhang * @date 2020-02-08 8:48 PM */ public class SysOutVisitor implements Visitor { @Override public String visitOnEntry(StateMachine stateMachine) { String entry = "-----StateMachine:"+stateMachine.getMachineId()+"-------"; System.out.println(entry); return entry; } @Override public String visitOnExit(StateMachine stateMachine) { String exit = "------------------------"; System.out.println(exit); return exit; } @Override public String visitOnEntry(State state) { StringBuilder sb = new StringBuilder(); String stateStr = "State:"+state.getId(); sb.append(stateStr).append(LF); System.out.println(stateStr); for(Transition transition: state.getAllTransitions()){ String transitionStr = " Transition:"+transition; sb.append(transitionStr).append(LF); System.out.println(transitionStr); } return sb.toString(); } @Override public String visitOnExit(State visitable) { return ""; } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/TransitionImpl.java ================================================ package com.alibaba.cola.statemachine.impl; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.State; import com.alibaba.cola.statemachine.Transition; /** * TransitionImpl。 * * This should be designed to be immutable, so that there is no thread-safe risk * * @author Frank Zhang * @date 2020-02-07 10:32 PM */ public class TransitionImpl implements Transition { private State source; private State target; private E event; private Condition condition; private Action action; private TransitionType type = TransitionType.EXTERNAL; @Override public State getSource() { return source; } @Override public void setSource(State state) { this.source = state; } @Override public E getEvent() { return this.event; } @Override public void setEvent(E event) { this.event = event; } @Override public void setType(TransitionType type) { this.type = type; } @Override public State getTarget() { return this.target; } @Override public void setTarget(State target) { this.target = target; } @Override public Condition getCondition() { return this.condition; } @Override public void setCondition(Condition condition) { this.condition = condition; } @Override public Action getAction() { return this.action; } @Override public void setAction(Action action) { this.action = action; } @Override public State transit(C ctx, boolean checkCondition) { Debugger.debug("Do transition: "+this); this.verify(); if (!checkCondition || condition == null || condition.isSatisfied(ctx)) { if(action != null){ action.execute(source.getId(), target.getId(), event, ctx); } return target; } Debugger.debug("Condition is not satisfied, stay at the "+source+" state "); return source; } @Override public final String toString() { return source + "-[" + event.toString() +", "+type+"]->" + target; } @Override public boolean equals(Object anObject){ if(anObject instanceof Transition){ Transition other = (Transition)anObject; if(this.event.equals(other.getEvent()) && this.source.equals(other.getSource()) && this.target.equals(other.getTarget())){ return true; } } return false; } @Override public void verify() { if(type== TransitionType.INTERNAL && source != target) { throw new StateMachineException(String.format("Internal transition source state '%s' " + "and target state '%s' must be same.", source, target)); } } } ================================================ FILE: cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/impl/TransitionType.java ================================================ package com.alibaba.cola.statemachine.impl; /** * TransitionType * * @author Frank Zhang * @date 2020-02-07 10:23 PM */ public enum TransitionType { /** * Implies that the Transition, if triggered, occurs without exiting or entering the source State * (i.e., it does not cause a state change). This means that the entry or exit condition of the source * State will not be invoked. An internal Transition can be taken even if the SateMachine is in one or * more Regions nested within the associated State. */ INTERNAL, /** * Implies that the Transition, if triggered, will not exit the composite (source) State, but it * will exit and re-enter any state within the composite State that is in the current state configuration. */ LOCAL, /** * Implies that the Transition, if triggered, will exit the composite (source) State. */ EXTERNAL } ================================================ FILE: cola-components/cola-component-statemachine/src/test/java/com/alibaba/cola/test/StateMachineChoiceTest.java ================================================ package com.alibaba.cola.test; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.builder.StateMachineBuilder; import com.alibaba.cola.statemachine.builder.StateMachineBuilderFactory; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * @author dingchenchen * @since 2021/1/6 */ public class StateMachineChoiceTest { static class Context{ private String condition; public Context(String condition){ this.condition = condition; } public String getCondition() { return condition; } } /** * 测试选择分支,针对同一个事件:EVENT1 * if condition == "1", STATE1 --> STATE1 * if condition == "2" , STATE1 --> STATE2 * if condition == "3" , STATE1 --> STATE3 */ @Test public void testChoice(){ StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.internalTransition() .within(StateMachineTest.States.STATE1) .on(StateMachineTest.Events.EVENT1) .when(checkCondition1()) .perform(doAction()); builder.externalTransition() .from(StateMachineTest.States.STATE1) .to(StateMachineTest.States.STATE2) .on(StateMachineTest.Events.EVENT1) .when(checkCondition2()) .perform(doAction()); builder.externalTransition() .from(StateMachineTest.States.STATE1) .to(StateMachineTest.States.STATE3) .on(StateMachineTest.Events.EVENT1) .when(checkCondition3()) .perform(doAction()); StateMachine stateMachine = builder.build("ChoiceConditionMachine"); StateMachineTest.States target1 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("1")); Assertions.assertEquals(StateMachineTest.States.STATE1,target1); StateMachineTest.States target2 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("2")); Assertions.assertEquals(StateMachineTest.States.STATE2,target2); StateMachineTest.States target3 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("3")); Assertions.assertEquals(StateMachineTest.States.STATE3,target3); } private Condition checkCondition1() { return (ctx) -> "1".equals(ctx.getCondition()); } private Condition checkCondition2() { return (ctx) -> "2".equals(ctx.getCondition()); } private Condition checkCondition3() { return (ctx) -> "3".equals(ctx.getCondition()); } private Action doAction() { return (from, to, event, ctx)->{ System.out.println("from:"+from+" to:"+to+" on:"+event+" condition:" + ctx.getCondition()); }; } } ================================================ FILE: cola-components/cola-component-statemachine/src/test/java/com/alibaba/cola/test/StateMachinePlantUMLTest.java ================================================ package com.alibaba.cola.test; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.builder.StateMachineBuilder; import com.alibaba.cola.statemachine.builder.StateMachineBuilderFactory; import com.alibaba.cola.statemachine.impl.Debugger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.stream.Stream; import static com.alibaba.cola.test.StateMachinePlantUMLTest.PriceAdjustmentTaskEventEnum.*; import static com.alibaba.cola.test.StateMachinePlantUMLTest.PriceAdjustmentTaskStatusEnum.*; /** * StateMachinePlantUMLTest * * @author Frank Zhang * @date 2020-02-09 7:53 PM */ public class StateMachinePlantUMLTest { static enum PriceAdjustmentTaskStatusEnum { /** * 开始状态 */ None, /** * 待商家处理 */ Supplier_Processing, /** * 待控商小二处理 */ Supplier_Manager_Processing, /** * 待价格管控小二处理 */ Price_Manager_Processing, /** * 退出 */ Closed } static enum PriceAdjustmentTaskEventEnum { // 系统事件 Create, Normal_Update, /** * 合理价变更 */ P0_Changed, /** * 页面价变合理 */ Page_Price_changed, // 商家事件 Supplier_Reject, Supplier_Agree, Supplier_Timeout, // 控商小二事件 Apply_Over_P0_Sell, // 价格小二事件 Agree_Over_P0_Sell, Reject_Over_P0_Sell; public boolean isSupplierTimeout() { return this == Supplier_Timeout; } public boolean isSystemEvent(){ return this == Create || this == Normal_Update || this == P0_Changed || this == Page_Price_changed; } } @BeforeEach public void init(){ Debugger.enableDebug(); } @Test public void testPlantUML(){ StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(None) .to(Supplier_Processing) .on(Create) .when(checkCondition()) .perform(doAction()); // 商家调价 Stream.of(Supplier_Processing, Supplier_Manager_Processing, Price_Manager_Processing) .forEach(status -> builder.externalTransition() .from(status) .to(Closed) .on(Supplier_Agree) .when(checkCondition()) .perform(doAction()) ); // 商家 -上升至-> 控商小二 builder.externalTransition() .from(Supplier_Processing) .to(Supplier_Manager_Processing) .on(Supplier_Reject) .when(checkCondition()) .perform(doAction()); builder.externalTransition() .from(Supplier_Processing) .to(Supplier_Manager_Processing) .on(Supplier_Timeout) .when(checkCondition()) .perform(doAction()); // 申请申请高于P0售卖 builder.externalTransition() .from(Supplier_Manager_Processing) .to(Price_Manager_Processing) .on(Apply_Over_P0_Sell) .when(checkCondition()) .perform(doAction()); // 同意高于P0价售卖 builder.externalTransition() .from(Price_Manager_Processing) .to(Closed) .on(Agree_Over_P0_Sell) .when(checkCondition()) .perform(doAction()); // 拒绝高于P0价售卖 builder.externalTransition() .from(Price_Manager_Processing) .to(Supplier_Manager_Processing) .on(Reject_Over_P0_Sell) .when(checkCondition()) .perform(doAction()); // 普通字段更新事件 Stream.of(Supplier_Processing, Supplier_Manager_Processing, Price_Manager_Processing) .forEach(status -> builder .internalTransition() .within(status) .on(Normal_Update) .when(checkCondition()) .perform(doAction()) ); // P0价变更事件、页面价高于合理价事件 Stream.of(P0_Changed, Page_Price_changed) .forEach(event -> builder.externalTransitions() .fromAmong(Supplier_Processing, Supplier_Manager_Processing, Price_Manager_Processing) .to(Closed) .on(event) .when(checkCondition()) .perform(doAction())); StateMachine stateMachine = builder.build("AdjustPriceTask"); String plantUML = stateMachine.generatePlantUML(); System.out.println(plantUML); } private Condition checkCondition() { return (ctx) -> {return true;}; } private Action doAction() { return (from, to, event, ctx)->{ System.out.println(ctx.operator+" is operating "+ctx.entityId+" from:"+from+" to:"+to+" on:"+event); }; } } ================================================ FILE: cola-components/cola-component-statemachine/src/test/java/com/alibaba/cola/test/StateMachineTest.java ================================================ package com.alibaba.cola.test; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.StateMachineFactory; import com.alibaba.cola.statemachine.builder.AlertFailCallback; import com.alibaba.cola.statemachine.builder.StateMachineBuilder; import com.alibaba.cola.statemachine.builder.StateMachineBuilderFactory; import com.alibaba.cola.statemachine.exception.TransitionFailException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.List; /** * StateMachineTest * * @author Frank Zhang * @date 2020-02-08 12:19 PM */ public class StateMachineTest { static String MACHINE_ID = "TestStateMachine"; static enum States { STATE1, STATE2, STATE3, STATE4 } static enum Events { EVENT1, EVENT2, EVENT3, EVENT4, INTERNAL_EVENT } static class Context { String operator = "frank"; String entityId = "123465"; } @Test public void testExternalNormal() { StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(States.STATE1) .to(States.STATE2) .on(Events.EVENT1) .when(checkCondition()) .perform(doAction()); StateMachine stateMachine = builder.build(MACHINE_ID); States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context()); Assertions.assertEquals(States.STATE2, target); } @Test public void testFail() { StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(States.STATE1) .to(States.STATE2) .on(Events.EVENT1) .when(checkCondition()) .perform(doAction()); builder.setFailCallback(new AlertFailCallback<>()); StateMachine stateMachine = builder.build(MACHINE_ID + "-testFail"); Assertions.assertThrows(TransitionFailException.class, () -> stateMachine.fireEvent(States.STATE2, Events.EVENT1, new Context())); } @Test public void testVerify() { StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(States.STATE1) .to(States.STATE2) .on(Events.EVENT1) .when(checkCondition()) .perform(doAction()); StateMachine stateMachine = builder.build(MACHINE_ID + "-testVerify"); Assertions.assertTrue(stateMachine.verify(States.STATE1, Events.EVENT1)); Assertions.assertFalse(stateMachine.verify(States.STATE1, Events.EVENT2)); } @Test public void testExternalTransitionsNormal() { StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransitions() .fromAmong(States.STATE1, States.STATE2, States.STATE3) .to(States.STATE4) .on(Events.EVENT1) .when(checkCondition()) .perform(doAction()); StateMachine stateMachine = builder.build(MACHINE_ID + "1"); States target = stateMachine.fireEvent(States.STATE2, Events.EVENT1, new Context()); Assertions.assertEquals(States.STATE4, target); } @Test public void testInternalNormal() { StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.internalTransition() .within(States.STATE1) .on(Events.INTERNAL_EVENT) .when(checkCondition()) .perform(doAction()); StateMachine stateMachine = builder.build(MACHINE_ID + "2"); stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context()); States target = stateMachine.fireEvent(States.STATE1, Events.INTERNAL_EVENT, new Context()); Assertions.assertEquals(States.STATE1, target); } @Test public void testExternalInternalNormal() { StateMachine stateMachine = buildStateMachine("testExternalInternalNormal"); Context context = new Context(); States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, context); Assertions.assertEquals(States.STATE2, target); target = stateMachine.fireEvent(States.STATE2, Events.INTERNAL_EVENT, context); Assertions.assertEquals(States.STATE2, target); target = stateMachine.fireEvent(States.STATE2, Events.EVENT2, context); Assertions.assertEquals(States.STATE1, target); target = stateMachine.fireEvent(States.STATE1, Events.EVENT3, context); Assertions.assertEquals(States.STATE3, target); } private StateMachine buildStateMachine(String machineId) { StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(States.STATE1) .to(States.STATE2) .on(Events.EVENT1) .when(checkCondition()) .perform(doAction()); builder.internalTransition() .within(States.STATE2) .on(Events.INTERNAL_EVENT) .when(checkCondition()) .perform(doAction()); builder.externalTransition() .from(States.STATE2) .to(States.STATE1) .on(Events.EVENT2) .when(checkCondition()) .perform(doAction()); builder.externalTransition() .from(States.STATE1) .to(States.STATE3) .on(Events.EVENT3) .when(checkCondition()) .perform(doAction()); builder.externalTransitions() .fromAmong(States.STATE1, States.STATE2, States.STATE3) .to(States.STATE4) .on(Events.EVENT4) .when(checkCondition()) .perform(doAction()); builder.build(machineId); StateMachine stateMachine = StateMachineFactory.get(machineId); stateMachine.showStateMachine(); return stateMachine; } @Test public void testMultiThread() { buildStateMachine("testMultiThread"); for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { StateMachine stateMachine = StateMachineFactory.get("testMultiThread"); States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context()); Assertions.assertEquals(States.STATE2, target); }); thread.start(); } for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { StateMachine stateMachine = StateMachineFactory.get("testMultiThread"); States target = stateMachine.fireEvent(States.STATE1, Events.EVENT4, new Context()); Assertions.assertEquals(States.STATE4, target); }); thread.start(); } for (int i = 0; i < 10; i++) { Thread thread = new Thread(() -> { StateMachine stateMachine = StateMachineFactory.get("testMultiThread"); States target = stateMachine.fireEvent(States.STATE1, Events.EVENT3, new Context()); Assertions.assertEquals(States.STATE3, target); }); thread.start(); } } @Test public void testParallel(){ StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalParallelTransition() .from(States.STATE1) .toAmong(States.STATE2,States.STATE3) .on(StateMachineTest.Events.EVENT1) .when(checkCondition()) .perform(doAction()); builder.externalTransitions() .fromAmong(StateMachineTest.States.STATE2,StateMachineTest.States.STATE3) .to(StateMachineTest.States.STATE4) .on(StateMachineTest.Events.EVENT2) .when(checkCondition()) .perform(doAction()); StateMachine stateMachine = builder.build("ParallelMachine"); System.out.println(stateMachine.generatePlantUML()); List states = stateMachine.fireParallelEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context()); for (StateMachineTest.States state : states) { System.out.println(state); } States target2 = stateMachine.fireEvent(StateMachineTest.States.STATE2, StateMachineTest.Events.EVENT2, new Context()); Assertions.assertEquals(States.STATE4,target2); States target3 = stateMachine.fireEvent(StateMachineTest.States.STATE3, StateMachineTest.Events.EVENT2, new Context()); Assertions.assertEquals(States.STATE4,target3); } private Condition checkCondition() { return new Condition() { @Override public boolean isSatisfied(Context context) { System.out.println("Check condition : " + context); return true; } }; } private Action doAction() { return (from, to, event, ctx) -> { System.out.println( ctx.operator + " is operating " + ctx.entityId + " from:" + from + " to:" + to + " on:" + event); }; } } ================================================ FILE: cola-components/cola-component-statemachine/src/test/java/com/alibaba/cola/test/StateMachineUnNormalTest.java ================================================ package com.alibaba.cola.test; import com.alibaba.cola.statemachine.Action; import com.alibaba.cola.statemachine.Condition; import com.alibaba.cola.statemachine.StateMachine; import com.alibaba.cola.statemachine.builder.StateMachineBuilder; import com.alibaba.cola.statemachine.builder.StateMachineBuilderFactory; import com.alibaba.cola.statemachine.impl.StateMachineException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * StateMachineUnNormalTest * * @author Frank Zhang * @date 2020-02-08 5:52 PM */ public class StateMachineUnNormalTest { @Test public void testConditionNotMeet(){ StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(StateMachineTest.States.STATE1) .to(StateMachineTest.States.STATE2) .on(StateMachineTest.Events.EVENT1) .when(checkConditionFalse()) .perform(doAction()); StateMachine stateMachine = builder.build("NotMeetConditionMachine"); StateMachineTest.States target = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new StateMachineTest.Context()); Assertions.assertEquals(StateMachineTest.States.STATE1,target); } @Test public void testDuplicatedTransition(){ Assertions.assertThrows(StateMachineException.class, ()->{ StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(StateMachineTest.States.STATE1) .to(StateMachineTest.States.STATE2) .on(StateMachineTest.Events.EVENT1) .when(checkCondition()) .perform(doAction()); builder.externalTransition() .from(StateMachineTest.States.STATE1) .to(StateMachineTest.States.STATE2) .on(StateMachineTest.Events.EVENT1) .when(checkCondition()) .perform(doAction()); }); } @Test public void testDuplicateMachine(){ Assertions.assertThrows(StateMachineException.class, ()-> { StateMachineBuilder builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(StateMachineTest.States.STATE1) .to(StateMachineTest.States.STATE2) .on(StateMachineTest.Events.EVENT1) .when(checkCondition()) .perform(doAction()); builder.build("DuplicatedMachine"); builder.build("DuplicatedMachine"); }); } private Condition checkCondition() { return (ctx) -> {return true;}; } private Condition checkConditionFalse() { return (ctx) -> {return false;}; } private Action doAction() { return (from, to, event, ctx)->{ System.out.println(ctx.operator+" is operating "+ctx.entityId+"from:"+from+" to:"+to+" on:"+event); }; } } ================================================ FILE: cola-components/cola-component-test-container/README.md ================================================ ## 作用 测试工具,当容器启动比较耗时的时候,这个工具特别有用,用法摘要: 启动TestsContainer 1. 运行测试类,在命令行中输入类全称:com.alibaba.cola.test.Demo 2. 运行单个方法,在命令行中输入方法引用:com.alibaba.cola.test.Demo#testTwo 3. 重复运行,输入r ================================================ FILE: cola-components/cola-component-test-container/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-test-container jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test junit junit commons-cli commons-cli org.junit.platform junit-platform-launcher 1.9.3 org.junit.jupiter junit-jupiter-engine ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/BeanMetaUtils.java ================================================ package com.alibaba.cola.test; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /** * BeanMetaUtils * * @author Frank Zhang * @date 2020-11-17 4:49 PM */ public class BeanMetaUtils { public static Method findMethod(Class clazz, Class annotationType){ Method[] allMethods = clazz.getMethods(); for (Method method : allMethods){ Annotation[] annotations = method.getAnnotations(); for(Annotation item : annotations){ if(item.annotationType().equals(annotationType)){ return method; } } } return null; } } ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/TestExecutor.java ================================================ package com.alibaba.cola.test; import com.alibaba.cola.test.command.TestClassRunCmd; import com.alibaba.cola.test.command.TestMethodRunCmd; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; /** * TestExecutor * * @author Frank Zhang * @date 2020-11-17 3:42 PM */ public class TestExecutor { private Launcher launcher; public TestExecutor(Launcher launcher) { this.launcher = launcher; } public void execute(TestClassRunCmd cmd) throws Exception { Class testClz = Class.forName(cmd.getClassName()); runClassTest(cmd, testClz); } public void execute(TestMethodRunCmd cmd) throws Exception { Class testClz = Class.forName(cmd.getClassName()); runMethodTest(cmd, testClz, cmd.getMethodName()); } private void runMethodTest(TestMethodRunCmd cmd, Class testClz, String methodName) throws Exception { // 获取测试类的方法参数类型name,只支持单参数 String paramTypeName = extractParamTypeName(testClz, methodName); // 创建测试方法 LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder .request() .selectors(selectMethod(testClz, methodName, paramTypeName)) .build(); // 运行测试方法 launcher.execute(request, new MyTestExecutionListener()); } private String extractParamTypeName(Class testClz, String methodName) { for (Method method : testClz.getMethods()) { if(methodName.equals(method.getName())){ for (Parameter parameter : method.getParameters()) { return parameter.getType().getName(); } } } return ""; } private void runClassTest(TestClassRunCmd cmd, Class testClz) { // 创建测试类 LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder .request() .selectors(selectClass(testClz)) .build(); // 运行测试方法 launcher.execute(request, new MyTestExecutionListener()); } static class MyTestExecutionListener implements TestExecutionListener { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { if (testExecutionResult.getStatus() == TestExecutionResult.Status.FAILED) { // 处理测试失败的情况,例如记录日志或发送通知 System.err.println("Test failed: " + testIdentifier.getDisplayName()); testExecutionResult.getThrowable().get().printStackTrace(); } } } } ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/TestsContainer.java ================================================ package com.alibaba.cola.test; import com.alibaba.cola.test.command.AbstractCommand; import com.alibaba.cola.test.command.GuideCmd; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.core.LauncherFactory; import org.springframework.context.ApplicationContext; import org.springframework.util.ObjectUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.concurrent.atomic.AtomicBoolean; /** * TestsContainer * * 这是一个轻量级的TDD测试工具,可以敏捷的在开发过程中,运行测试,功能如下: * 1.测试单个方法,请在控制台输入方法全称 * 例如:com.alibaba.framework.sales.service.test.CustomerServiceTest.testCheckConflict() * 2.测试整个测试类,请在控制台输入类全称 * 例如:com.alibaba.framework.sales.service.test.CustomerServiceTest * 3.重复上一次测试,只需在控制台输入字母 - ‘r’ * * @author Frank Zhang * @date 2020-11-17 3:35 PM */ public class TestsContainer { private static ApplicationContext context; private static Launcher launcher; private static TestExecutor testExecutor; private static AtomicBoolean initFlag = new AtomicBoolean(false); /** * 如果要用到Junit5的Extension功能,需要显示的提供Launcher * * @param context ApplicationContext to be provided * @param launcher 运行Junit5测试用例的Launcher, 如果不提供,默认会自己创建一个 */ public static void start(ApplicationContext context, Launcher launcher) { TestsContainer.context = context; if (launcher != null) { TestsContainer.launcher = launcher; } else { TestsContainer.launcher = LauncherFactory.create(); } testExecutor = new TestExecutor(TestsContainer.launcher); monitorConsole(); } /** * 使用Junit5的launcher之后,不再需要ApplicationContext,框架会自己处理Spring的依赖关系 * @param launcher */ public static void start(Launcher launcher) { start(null, launcher); } /** * TestsContainer is optional to be in Spring Container * * @param context ApplicationContext to be provided */ public static void start(ApplicationContext context) { start(context, null); } /** * TestsContainer without Spring Container */ public static void start() { start(null, null); } public static void execute(String input) { if (ObjectUtils.isEmpty(input)) { return; } input = input.trim(); AbstractCommand command = AbstractCommand.createCmd(input); if (command == null) { System.err.println("Your input is not a valid qualified name"); return; } command.execute(); } public static TestExecutor getTestExecutor() { return testExecutor; } private static void monitorConsole() { BufferedReader bufferRead = new BufferedReader(new InputStreamReader( System.in)); String input = GuideCmd.GUIDE_HELP; while (true) { try { execute(input); } catch (Exception e) { e.printStackTrace(); } catch (Error e) { e.printStackTrace(); break; } try { input = bufferRead.readLine(); } catch (IOException e) { e.printStackTrace(); return; } } } } ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/command/AbstractCommand.java ================================================ package com.alibaba.cola.test.command; import org.apache.commons.cli.*; import org.springframework.util.ObjectUtils; import java.util.HashMap; import java.util.Map; /** * AbstractCommand * * @author Frank Zhang * @date 2020-11-17 4:33 PM */ public abstract class AbstractCommand { private static final CommandLineParser parser = new DefaultParser(); protected static AbstractCommand curCmd; protected static AbstractCommand preCmd; protected String cmdRaw; private Map params; private Options options; private CommandLine commandLine; private final String SPACE = " "; private final String EMPTY = ""; public AbstractCommand(String cmdRaw){ this.cmdRaw = cmdRaw.replaceAll(" +", SPACE); params = new HashMap<>(); options = new Options(); initParser(options); commandLine = parse(); } public void execute(){ System.out.println("===Run start==== "+cmdRaw); action(); System.out.println("===Run end====\n"); } /** * 清理当前命令的上下文 */ protected void cleanContext(){} protected void initParser(Options options){}; protected abstract void action(); public CommandLine parse(){ try { return parser.parse(options, cmdRaw.split(SPACE)); } catch (ParseException e) { e.printStackTrace(); } return null; } public boolean isEclipseMethod(String input) { return input.indexOf("(") > 0 ; } public boolean isIdeaMethod(String input) { return input.indexOf("#") > 0 ; } public CommandLine getCommandLine() { return commandLine; } public static AbstractCommand createCmd(String cmdRaw){ if(ObjectUtils.isEmpty(cmdRaw)){ return null; } AbstractCommand command = null; if(cmdRaw.matches(CommandEnum.TestMethodRunCmd.getDesc())){ command = new TestMethodRunCmd(cmdRaw); } else if(cmdRaw.matches(CommandEnum.TestClassRunCmd.getDesc())){ command = new TestClassRunCmd(cmdRaw); }else if(cmdRaw.matches(CommandEnum.GuideCmd.getDesc())){ command = new GuideCmd(cmdRaw); } if(command != null){ preCmd = curCmd; curCmd = command; } if(preCmd != null){ preCmd.cleanContext(); } return command; } } ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/command/CommandEnum.java ================================================ package com.alibaba.cola.test.command; /** * CommandEnum * * @author Frank Zhang * @date 2020-11-17 4:39 PM */ public enum CommandEnum { TestMethodRunCmd("", "[^\\s]*[\\(#].*"), TestClassRunCmd("", "(\\w+\\.\\w+){1,}"), GuideCmd("", "^[rhq]$"), ; private String cmd; private String desc; CommandEnum(String cmd, String desc){ this.cmd = cmd; this.desc = desc; } public String getCmd() { return cmd; } public void setCmd(String cmd) { this.cmd = cmd; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } } ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/command/GuideCmd.java ================================================ package com.alibaba.cola.test.command; import com.alibaba.cola.test.TestsContainer; /** * GuideCmd * * @author Frank Zhang * @date 2020-11-17 4:41 PM */ public class GuideCmd extends AbstractCommand { public static final String GUIDE_HELP = "h"; public static final String GUIDE_REPEAT = "r"; public static final String GUIDE_QUIT = "q"; public GuideCmd(String cmdRaw) { super(cmdRaw); } @Override public void execute(){ action(); } @Override protected void action() { if(cmdRaw.equals(GUIDE_HELP)){ System.out.println("************** 欢迎使用轻量级TDD测试工具 ***************************"); System.out.println("**** 1.测试单个方法,请在控制台输入方法全称"); System.out.println("**** 例如:com.alibaba.framework.sales.service.test.CustomerServiceTest.testCheckConflict()"); System.out.println("**** 2.测试整个测试类,请在控制台输入类全称"); System.out.println("**** 例如:com.alibaba.framework.sales.service.test.CustomerServiceTest"); System.out.println("**** 3.重复上一次测试,只需在控制台输入字母 - ‘r’"); System.out.println("**** 4.自动生成ColaTest测试类,请输入‘new 方法全称 参数1 参数2 ...’"); System.out.println("**** 例如:new com.alibaba.crm.sales.domain.customer.entity.CustomerE#addContact"); System.out.println("***********************************************************************************"); }else if(cmdRaw.equals(GUIDE_REPEAT)){ TestsContainer.execute(preCmd.cmdRaw); }else if(cmdRaw.equals(GUIDE_QUIT)){ System.exit(0); throw new Error("强制退出"); } } } ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/command/TestClassRunCmd.java ================================================ package com.alibaba.cola.test.command; import com.alibaba.cola.test.TestsContainer; /** * TestClassRunCmd * * @author Frank Zhang * @date 2020-11-17 4:42 PM */ public class TestClassRunCmd extends AbstractCommand { private String className; public TestClassRunCmd(String cmdRaw) { super(cmdRaw); this.className = cmdRaw; } @Override protected void action() { try { TestsContainer.getTestExecutor().execute(this); } catch (Exception e) { e.printStackTrace(); } } public String getClassName() { return className; } } ================================================ FILE: cola-components/cola-component-test-container/src/main/java/com/alibaba/cola/test/command/TestMethodRunCmd.java ================================================ package com.alibaba.cola.test.command; import com.alibaba.cola.test.TestsContainer; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.springframework.core.type.filter.RegexPatternTypeFilter; import java.util.ArrayList; import java.util.List; /** * TestMethodRunCmd * * @author Frank Zhang * @date 2020-11-17 4:43 PM */ public class TestMethodRunCmd extends AbstractCommand { private static final String RE_RECORD = "rr"; public final static String DOT = "."; public final static String NOTE_SYMBOL = "#"; private String methodName; private String className; /** 是否片段录制*/ private boolean segmentRecord = false; List recordFilters = new ArrayList<>(); public TestMethodRunCmd(String cmdRaw) { super(cmdRaw); parseCommand(); } @Override protected void action() { try { TestsContainer.getTestExecutor().execute(this); } catch (Exception e) { e.printStackTrace(); } } @Override protected void initParser(Options options) { Option point = Option.builder(RE_RECORD) .hasArgs() .argName("p1,p2...") .valueSeparator(',') .desc("A directories list with ',' separate to handle its child files") .build(); options.addOption(point); } public String getMethodName() { return methodName; } public String getClassName() { return className; } public boolean isSegmentRecord() { return segmentRecord; } private void parseCommand(){ String cmd = getCommandLine().getArgs()[0]; if (isEclipseMethod(cmd)) { methodName = cmd.substring(cmd.lastIndexOf(DOT)+1, cmd.indexOf("(")); className = cmd.substring(0, cmd.lastIndexOf(DOT)); } if (isIdeaMethod(cmd)) { methodName = cmd.substring(cmd.lastIndexOf(NOTE_SYMBOL)+1, cmd.length()); className = cmd.substring(0, cmd.lastIndexOf(NOTE_SYMBOL)); } } } ================================================ FILE: cola-components/cola-component-test-container/src/test/java/com/alibaba/cola/test/Demo.java ================================================ package com.alibaba.cola.test; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @Slf4j public class Demo { @BeforeEach public void before(){ System.out.println("before action"); } @Test public void testOne(){ System.out.println("test one"); Assertions.assertEquals(1,1); System.out.println("test one end"); } @Test public void testTwo(){ System.out.println("test two"); } @Test void testThree(){ System.out.println("test three"); } @Test public void testParam(String param){ System.out.println("hello param"); } @AfterEach public void after(){ System.out.println("after action"); } } ================================================ FILE: cola-components/cola-component-test-container/src/test/java/com/alibaba/cola/test/DemoWithExtension.java ================================================ package com.alibaba.cola.test; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest(classes = SpringBootConfig.class) @ExtendWith(LoggingExtension.class) public class DemoWithExtension { @Autowired private Demo demo; @BeforeEach public void before() { System.out.println("=====before"); } @Test public void testParam(String param) { System.out.println("hello : " + param); } @Test public void testMethod1() { System.out.println("Begin testMethod1"); demo.testOne(); System.out.println("End testMethod1"); } @Test public void testMethod2() { System.out.println("Begin testMethod2"); demo.testTwo(); System.out.println("End testMethod2"); } @AfterEach public void after() { System.out.println("=====after"); } } class LoggingExtension implements BeforeEachCallback, ParameterResolver { @Override public void beforeEach(ExtensionContext context) throws Exception { System.out.println("Executing test method: " + context.getRequiredTestMethod().getName()); } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return true; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { System.out.println("resolveParameter: " + parameterContext); return null; } } ================================================ FILE: cola-components/cola-component-test-container/src/test/java/com/alibaba/cola/test/SpringBootConfig.java ================================================ package com.alibaba.cola.test; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootConfig { } ================================================ FILE: cola-components/cola-component-test-container/src/test/java/com/alibaba/cola/test/SpringConfig.java ================================================ package com.alibaba.cola.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * SpringConfig * * @author Frank Zhang * @date 2020-11-17 5:11 PM */ @Configuration @ComponentScan public class SpringConfig { @Bean("demo") public Demo generateDemo(){ return new Demo(); } } ================================================ FILE: cola-components/cola-component-test-container/src/test/java/com/alibaba/cola/test/TestsContainerTest.java ================================================ package com.alibaba.cola.test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * TestsContainerTest * * @author Frank Zhang * @date 2020-11-17 4:55 PM */ public class TestsContainerTest { public static void main(String[] args) { TestsContainer.start(); } } ================================================ FILE: cola-components/cola-component-test-container/src/test/resources/logback-test.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/cola-component-unittest/.gitignore ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/cola-component-unittest/README.md ================================================ ## 原理 通过注解AOP,提供service级别的logging和exception处理。 通过Spring Boot的autoConfig机制进行加载,无需手动配置,只需要添加如下依赖即可: ```xml com.alibaba.lst.tech.shared catch-log-starter ``` 兼容普通的HSF service和Mtop service,具体做法可以查看代码`ResponseHandler` ```java public class ResponseHandler { public static Object handle(Class returnType, String errCode, String errMsg){ if (isColaResponse(returnType)){ return handleColaResponse(returnType, errCode, errMsg); } if(isMtopResponse(returnType)){ return handleMtopResponse(returnType, errCode, errMsg); } return null; } ... } ``` ## 使用介绍 1、在需要处理的Service类上面加上@CatchAndLog注解 ```java @CatchAndLog public class GrouponServiceImpl implements GrouponService ``` 2、logback-test.xml为组件开启DEGUG level的日志输出 ```xml ``` 3、如果在控制台看到如下的日志输出,说明CatchAndLog已经在做AOP拦截 ```xml DEBUG c.a.l.t.s.catchlog.CatchLogAspect - Start processing: GrouponServiceImpl.queryGrouponItemDetail(..) DEBUG c.a.l.t.s.catchlog.CatchLogAspect - REQUEST : 257 DEBUG c.a.l.t.s.catchlog.CatchLogAspect - RESPONSE : {"errCode":"UNKNOWN_ERROR"...} DEBUG c.a.l.t.s.catchlog.CatchLogAspect - COST : 1329ms ``` ================================================ FILE: cola-components/cola-component-unittest/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT cola-component-unittest jar ${project.artifactId}:${project.version} ${project.artifactId} https://github.com/alibaba/COLA true org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test it.ozimov embedded-redis 0.7.3 redis.clients jedis 5.1.0 org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 com.github.ppodgorsek spring-test-dbunit-core 5.2.0 org.dbunit dbunit 2.7.0 org.springframework.kafka spring-kafka org.springframework.kafka spring-kafka-test org.wiremock wiremock-standalone 3.0.3 org.springframework.boot spring-boot-starter-webflux org.projectlombok lombok com.alibaba.cola cola-component-test-container 5.x-SNAPSHOT test org.awaitility awaitility ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/FixtureLoader.java ================================================ package com.alibaba.cola.unittest; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StreamUtils; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @Slf4j public class FixtureLoader { public static String loadResource(String resourcePath) { log.info("Fixture resource location: " + resourcePath); // 创建一个 ClassPathResource 对象 ClassPathResource resource = new ClassPathResource(resourcePath); // 使用 withResource 来自动关闭输入流 String content = ""; try (InputStream inputStream = resource.getInputStream()) { content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return content; } } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/kafka/KafkaExtension.java ================================================ package com.alibaba.cola.unittest.kafka; import com.alibaba.cola.unittest.FixtureLoader; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.common.serialization.Serializer; import org.apache.kafka.common.serialization.StringSerializer; import org.junit.jupiter.api.extension.*; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.support.serializer.JsonSerializer; import org.springframework.kafka.test.EmbeddedKafkaBroker; import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.util.List; import java.util.Map; import java.util.Objects; @Slf4j public class KafkaExtension implements BeforeAllCallback, BeforeEachCallback { private EmbeddedKafkaBroker embeddedKafkaBroker; private ObjectMapper objectMapper = new ObjectMapper(); @Override public void beforeAll(ExtensionContext context) throws Exception { try { EmbeddedKafkaBroker embeddedKafkaBroker = (EmbeddedKafkaBroker) SpringExtension.getApplicationContext(context).getBean(EmbeddedKafkaBroker.class); log.debug("embeddedKafkaBroker:" + embeddedKafkaBroker); this.embeddedKafkaBroker = embeddedKafkaBroker; } catch (NoSuchBeanDefinitionException e) { log.error("Please add @EmbeddedKafka for your test", e); throw e; } } @Override public void beforeEach(ExtensionContext context) throws Exception { ProduceMessage produceMessage = context.getElement().get().getAnnotation(ProduceMessage.class); if (Objects.nonNull(produceMessage)) { log.info("begin produce message for kafka"); String location = produceMessage.value(); MessageData messageData = objectMapper.readValue(FixtureLoader.loadResource(location), MessageData.class); log.debug("messageData: " + messageData); //get producer KafkaTemplate producer = this.createProducer(); List messages = messageData.getMessages(); int count = 0; for (ObjectNode message : messages) { // TODO:add key support later // Optional recordKey = Optional.ofNullable(record.remove("$KEY$")).map(JsonNode::asText); producer.send(messageData.getTopic(), message); log.info("produce message[{}:{}]: {}", new Object[]{messageData.getTopic(), ++count, message}); } } } public KafkaTemplate createProducer(Serializer keySerializer, Serializer valueSerializer) { Map props = KafkaTestUtils.producerProps(this.embeddedKafkaBroker); return new KafkaTemplate(new DefaultKafkaProducerFactory(props, keySerializer, valueSerializer)); } public KafkaTemplate createProducer() { return this.createProducer(new StringSerializer(), new JsonSerializer()); } } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/kafka/MessageData.java ================================================ package com.alibaba.cola.unittest.kafka; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import java.util.List; @Data @EqualsAndHashCode @ToString public class MessageData { private String topic; private List messages; } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/kafka/ProduceMessage.java ================================================ package com.alibaba.cola.unittest.kafka; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target( {ElementType.TYPE, ElementType.METHOD}) public @interface ProduceMessage { String value(); } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/redis/ExpectRedis.java ================================================ package com.alibaba.cola.unittest.redis; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target( {ElementType.TYPE, ElementType.METHOD}) public @interface ExpectRedis { /** * 测试校验数据路径, 通常放在测试夹具(fixture)下面 */ String value(); /** * 重试等待key生效的间隔(ms) */ long interval() default 200L; /** * 验证超时时间(ms) */ long timeout() default 3000L; } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/redis/RedisData.java ================================================ package com.alibaba.cola.unittest.redis; import com.fasterxml.jackson.databind.JsonNode; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Map; /** * 使用jackson:https://zhuanlan.zhihu.com/p/646744855 */ @Data @AllArgsConstructor @NoArgsConstructor public class RedisData { // Map private Map records; } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/redis/RedisExtension.java ================================================ package com.alibaba.cola.unittest.redis; import com.alibaba.cola.unittest.FixtureLoader; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.awaitility.Awaitility; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.*; import redis.clients.jedis.Jedis; import redis.embedded.RedisServer; import java.time.Duration; import java.util.*; @Slf4j public class RedisExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { ObjectMapper objectMapper = new ObjectMapper(); //default port is 6397 private static RedisServer redisServer; public static Jedis jedis; private static boolean isStarted; @Override public void afterEach(ExtensionContext context) throws Exception { ExpectRedis expectRedis = context.getElement().get().getAnnotation(ExpectRedis.class); if (Objects.nonNull(expectRedis)) { log.info("after each, check redis result"); String location = expectRedis.value(); long interval = expectRedis.interval(); long timeout = expectRedis.timeout(); RedisData redisData = objectMapper.readValue(FixtureLoader.loadResource(location), RedisData.class); redisData.getRecords().forEach((key, content) -> { await(interval, timeout, key); String expect = content.textValue(); String actual = jedis.get(key); log.debug("expect: " + expect); log.debug("actual: " + actual); Assertions.assertEquals(expect, actual); }); } } private void await(long interval, long timeout, String key) { Awaitility.await().pollInterval(Duration.ofMillis(interval)).atMost(Duration.ofMillis(timeout)) .until(() -> jedis.exists(key)); } @Override public void beforeEach(ExtensionContext context) throws Exception { SetupRedis setupRedis = context.getElement().get().getAnnotation(SetupRedis.class); if (Objects.nonNull(setupRedis)) { log.info("before each, setup redis"); String location = setupRedis.value(); RedisData redisData = objectMapper.readValue(FixtureLoader.loadResource(location), RedisData.class); log.debug("redisData: " + redisData); redisData.getRecords().forEach((key, jsonNode) -> { processJsonNode(key, jsonNode); }); } } private void processJsonNode(String key, JsonNode jsonNode) { JsonNodeType nodeType = jsonNode.getNodeType(); log.debug("set redis record: TYPE--> {} , KEY--> {}, VALUE--> {}", nodeType, key, jsonNode); if (nodeType == JsonNodeType.STRING) { jedis.set(key, jsonNode.textValue()); return; } if (nodeType == JsonNodeType.ARRAY) { List elements = new ArrayList<>(); jsonNode.forEach(item -> { String itemStr = item.isValueNode() ? item.asText() : item.toString(); elements.add(itemStr); }); jedis.sadd(key, elements.toArray(new String[0])); return; } if (nodeType == JsonNodeType.OBJECT) { Map map = new HashMap<>(); ObjectNode objectNode = (ObjectNode) jsonNode; objectNode.fields() .forEachRemaining( field -> { String value = field.getValue().isValueNode() ? field.getValue().asText() : field.getValue().toString(); map.put(field.getKey(), value); }); jedis.hmset(key, map); } } @Override public void beforeAll(ExtensionContext context) { try { if (redisServer == null && !isStarted) { redisServer = new RedisServer(); //default port is 6379 redisServer.start(); log.debug("Redis server started"); } } catch (Exception e) { isStarted = true; log.warn("Redis Server may already started, just ignore this exception:" + e.getMessage()); } if (jedis == null) { jedis = new Jedis("localhost", 6379); } } } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/redis/SetupRedis.java ================================================ package com.alibaba.cola.unittest.redis; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 测试启动时注入redis记录 */ @Retention(RetentionPolicy.RUNTIME) @Target( {ElementType.TYPE, ElementType.METHOD}) public @interface SetupRedis { /** * 测试准备数据路径, 通常放在测试夹具(fixture)下面,比如:/fixture/job.json */ String value(); } ================================================ FILE: cola-components/cola-component-unittest/src/main/java/com/alibaba/cola/unittest/wiremock/WireMockRegister.java ================================================ package com.alibaba.cola.unittest.wiremock; import com.alibaba.cola.unittest.FixtureLoader; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.stubbing.StubMapping; public class WireMockRegister { public static void registerStub(WireMock wireMock, String resourcePath){ StubMapping stubMapping = StubMapping.buildFrom(FixtureLoader.loadResource(resourcePath)); wireMock.register(stubMapping); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/Application.java ================================================ package com.alibaba.cola.unittest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Application * * @author Frank Zhang * @date 2020-11-10 3:58 PM */ @SpringBootApplication(scanBasePackages = {"com.alibaba.cola.unittest"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/TestsContainerBoot.java ================================================ package com.alibaba.cola.unittest; import com.alibaba.cola.test.TestsContainer; public class TestsContainerBoot { public static void main(String[] args) { TestsContainer.start(); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/db/DBSetupTest.java ================================================ package com.alibaba.cola.unittest.db; import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; // 关于TestExecutionListener: https://www.baeldung.com/spring-testexecutionlistener @SpringBootTest @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DbUnitTestExecutionListener.class }) public class DBSetupTest { @Autowired private PersonRepository personRepository; @Test @DatabaseSetup("/fixture/db/sample-data.xml") public void testFind() throws Exception { List personList = personRepository.find("hil"); System.out.println(personList); assertEquals(1, personList.size()); assertEquals("Phillip", personList.get(0).getFirstName()); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/db/Person.java ================================================ package com.alibaba.cola.unittest.db; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.NamedQueries; import jakarta.persistence.NamedQuery; @Entity @NamedQueries({ @NamedQuery(name = "Person.find", query = "SELECT p from Person p where p.firstName like :name " + "or p.lastName like :name") }) public class Person { @Id private int id; private String title; private String firstName; private String lastName; public int getId() { return id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @Override public String toString() { return "Person{" + "id=" + id + ", title='" + title + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}'; } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/db/PersonRepository.java ================================================ package com.alibaba.cola.unittest.db; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; import jakarta.transaction.Transactional; import org.springframework.stereotype.Repository; import java.util.List; @Repository @Transactional public class PersonRepository { @PersistenceContext private EntityManager entityManager; @SuppressWarnings("unchecked") public List find(String name) { Query query = entityManager.createNamedQuery("Person.find"); query.setParameter("name", "%" + name + "%"); return query.getResultList(); } public void remove(int personId) { Person person = entityManager.find(Person.class, personId); entityManager.remove(person); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/kafka/KafkaConsumer.java ================================================ package com.alibaba.cola.unittest.kafka; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; @Slf4j @Component public class KafkaConsumer { private String payload; boolean isFinished; @KafkaListener(topics = "${test.topic}", groupId = "testGroup") public void receive(ConsumerRecord consumerRecord) { log.info("received payload='{}'", consumerRecord.toString()); payload = consumerRecord.toString(); processBiz(); isFinished = true; } private void processBiz() { try { Thread.sleep(300); } catch (InterruptedException e) { throw new RuntimeException(e); } } public String getPayload() { return payload; } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/kafka/KafkaExtensionTest.java ================================================ package com.alibaba.cola.unittest.kafka; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.kafka.test.context.EmbeddedKafka; import java.util.concurrent.TimeUnit; import static org.awaitility.Awaitility.await; @Slf4j @SpringBootTest @EmbeddedKafka(partitions = 1, brokerProperties = { "listeners=PLAINTEXT://localhost:9092", "port=9092" }) @ExtendWith(KafkaExtension.class) public class KafkaExtensionTest { @Autowired private KafkaConsumer consumer; @Test @ProduceMessage("/fixture/kafka/produce-message.json") public void testProduceMessage(){ log.info("test produce message"); // 等待消息业务处理,每100毫秒poll一下,最长等待10秒 await().atMost(10, TimeUnit.SECONDS).pollInterval(100, TimeUnit.MILLISECONDS) .until(() -> consumer.isFinished); log.info("consume message finished"); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/redis/RedisExtensionTest.java ================================================ package com.alibaba.cola.unittest.redis; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.util.Map; import java.util.Set; @ExtendWith(RedisExtension.class) public class RedisExtensionTest { /** * json String 的内容,注意如果是String的话,需要用转义符号\,转义双引号 * { * "records": { * "topo:child_job:222": "{\"id\":\"12345678-1234-1234-1234-childJob0000\",\"parent_job_id\":\"12345678-1234-1234-1234-noParentJob0\",\"resource_id\":\"1.1.1.4\",\"status\":\"success\"}", * "topo:child_job:333": "{\"id\":\"12345678-1234-1234-1234-childJob0000\"}" * } * } */ @Test @SetupRedis("/fixture/redis/string-setup.json") public void testString() { System.out.println("test String SetupRedis"); } /** * json array 的内容: * { * "records": { * "xlink:10.0.0.11:port": [ * "30000000-0000-0000-0000-000000000001", * "30000000-0000-0000-0000-000000000002" * ] * } * } */ @Test @SetupRedis("/fixture/redis/array-setup.json") public void testArray() { System.out.println("test array SetupRedis"); Set result = RedisExtension.jedis.smembers("test:array"); System.out.println("test result : " + result); Assertions.assertEquals(2, result.size()); } /** * json 的object在redis里面是用hash存储,内容如下: * { * "records": { * "xlink:hyper_cluster_port:30000000-0000-0000-0000-000000000001": { * "version": 1, * "json": { * "id": "30000000-0000-0000-0000-000000000001", * "name": "port-01", * "project_id": "7a9941d34fc1497d8d0797429ecfd354", * "provisioning_status": "active", * "created_at": "2024-01-01T12:00:00Z", * "updated_at": "2024-01-01T12:00:00Z" * } * } * } * } */ @Test @SetupRedis("/fixture/redis/hash-setup.json") public void testHash() { System.out.println("test hash SetupRedis"); Map result = RedisExtension.jedis.hgetAll("test:hash"); System.out.println("test result : " + result); Assertions.assertEquals("1", result.get("version")); } @Test @SetupRedis("/fixture/redis/string-setup.json") @ExpectRedis("/fixture/redis/string-expect.json") public void testStringExpect() { System.out.println("test ExpectRedis"); } @Test public void testVoid() { System.out.println("test without SetupRedis"); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/wiremock/Account.java ================================================ package com.alibaba.cola.unittest.wiremock; import lombok.Data; @Data public class Account { /** * 用户号码 */ private long phoneNo; /** * 账户余额 */ private String remaining; private String name; } ================================================ FILE: cola-components/cola-component-unittest/src/test/java/com/alibaba/cola/unittest/wiremock/WireMockBasicTest.java ================================================ package com.alibaba.cola.unittest.wiremock; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import static com.github.tomakehurst.wiremock.client.WireMock.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @WireMockTest(httpPort = 8080) @Slf4j public class WireMockBasicTest { @Autowired protected WebTestClient webClient; @Test public void testWireMockBasic() { // The static DSL will be automatically configured for you stubFor(get("/static-dsl").willReturn(ok())); webClient.get() .uri("http://localhost:8080/static-dsl") .exchange() .expectStatus() .isEqualTo(200); } @Test public void testWireMockStub(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub-wire-mock-basic.json"); webClient.get() .uri("http://localhost:8080/v1/wiremock/basic") .exchange() .expectStatus() .isEqualTo(200) .expectHeader() .contentType(MediaType.APPLICATION_JSON); System.out.println("wire mock serer port : " + wmRuntimeInfo.getHttpPort()); } @Test public void testWireMockAccount(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub-account.json"); long phoneNo = 123456789; webClient.get() .uri("http://localhost:8080/v1/api/account/"+phoneNo) .exchange() .expectStatus() .isEqualTo(200) .expectHeader() .contentType(MediaType.APPLICATION_JSON) .returnResult(Account.class) .getResponseBody() .map(account -> { log.info(account.toString()); Assertions.assertEquals("frank", account.getName()); Assertions.assertEquals(phoneNo, account.getPhoneNo()); return account; }) .subscribe(); log.info("wire mock serer port : " + wmRuntimeInfo.getHttpPort()); } } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/application.properties ================================================ test.topic=embedded-test-topic ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/db/sample-data.xml ================================================ ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/kafka/produce-message.json ================================================ { "topic": "embedded-test-topic", "messages": [ { "job_id": "10000000-0000-0000-0000-000000000001", "version": "v1", "action": "create", "resource_type": "test_resource", "request": [ { "id": "30000000-0000-0000-0000-000000000001", "name": "test-01", "project_id": "7a9941d34fc1497d8d0797429ecfd354" } ] } , { "job_id": "10000000-0000-0000-0000-000000000002", "version": "v1", "action": "create", "resource_type": "test_resource", "request": [ { "id": "30000000-0000-0000-0000-000000000002", "name": "test-02", "project_id": "7a9941d34fc1497d8d0797429ecfd354" } ] } ] } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/redis/array-setup.json ================================================ { "records": { "test:array": [ "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002" ] } } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/redis/hash-setup.json ================================================ { "records": { "test:hash": { "version": 1, "json": { "id": "30000000-0000-0000-0000-000000000001", "name": "port-01", "project_id": "7a9941d34fc1497d8d0797429ecfd354", "provisioning_status": "active", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-01T12:00:00Z" } } } } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/redis/string-expect.json ================================================ { "records": { "topo:child_job:222": "{\"id\":\"12345678-1234-1234-1234-childJob0000\",\"parent_job_id\":\"12345678-1234-1234-1234-noParentJob0\",\"resource_id\":\"1.1.1.4\",\"status\":\"success\"}", "topo:child_job:333": "{\"id\":\"12345678-1234-1234-1234-childJob0000\"}" } } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/redis/string-setup.json ================================================ { "records": { "topo:child_job:222": "{\"id\":\"12345678-1234-1234-1234-childJob0000\",\"parent_job_id\":\"12345678-1234-1234-1234-noParentJob0\",\"resource_id\":\"1.1.1.4\",\"status\":\"success\"}", "topo:child_job:333": "{\"id\":\"12345678-1234-1234-1234-childJob0000\"}" } } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/wiremock/stub-account.json ================================================ { "request": { "urlPathPattern": "/v1/api/account/[0-9]+", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "transformers": [ "response-template" ], "jsonBody": { "name": "frank", "phoneNo": "{{request.path.[3]}}", "remaining": "400", "chargePlanList": [ { "priority": "2", "type": "fixedTime" }, { "priority": "1", "type": "familyMember" } ] } } } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/fixture/wiremock/stub-wire-mock-basic.json ================================================ { "request": { "urlPathPattern": "/v1/wiremock/basic", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": { "request_id": "f7f9e747-f073-4ea8-8360-b42fc754a049", "test_resource": { "name": "1520-001", "created_at": "2024-02-04 15:11:13", "updated_at": "2020-02-04 15:11:13", "type": "l1" } } } } ================================================ FILE: cola-components/cola-component-unittest/src/test/resources/logback-test.xml ================================================ %date{HH:mm:ss} %highlight(%-5level) [%blue(%t)] %yellow(%C{35}): %msg%n%throwable utf8 ================================================ FILE: cola-components/cola-components-bom/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-bom 5.x-SNAPSHOT pom ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee com.alibaba.cola cola-component-dto ${project.version} com.alibaba.cola cola-component-exception ${project.version} com.alibaba.cola cola-component-statemachine ${project.version} com.alibaba.cola cola-component-domain-starter ${project.version} com.alibaba.cola cola-component-extension-starter ${project.version} com.alibaba.cola cola-component-catchlog-starter ${project.version} com.alibaba.cola cola-component-test-container ${project.version} ossrh https://oss.sonatype.org/content/repositories/snapshots maven-source-plugin 3.3.1 maven-javadoc-plugin 3.7.0 maven-deploy-plugin 3.1.1 gen-sign performRelease true maven-gpg-plugin 3.1.0 sign-artifacts verify sign deploy-settings performRelease true org.sonatype.plugins nexus-staging-maven-plugin 1.6.13 true ossrh https://oss.sonatype.org/ true ================================================ FILE: cola-components/dev-util-archetypes/README.md ================================================ # COLA Dev Util Archetypes 用于开发时快速生成`COLA Components`工程的Archetypes,即方便COLA自身开发的工具工程。 提供了脚本,调用`Util Archetypes`生成`COLA Components`工程: - [`new-cola-normal-component.sh`](new-cola-normal-component.sh) - [`new-cola-starter-component.sh`](new-cola-starter-component.sh) ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/.gitignore ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-normal-component-archetype 1.0.0-SNAPSHOT jar cola-normal-component-archetype COLA normal component archetype org.apache.maven.archetype archetype-packaging 3.0.1 maven-source-plugin 3.3.1 maven-archetype-plugin 3.0.1 maven-resources-plugin 3.0.1 false ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml ================================================ gitignore.txt src/main/java **/*.java src/main/resources **/*.xml **/*.properties src/test/java **/*.java src/test/resources **/*.xml **/*.properties ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/src/main/resources/archetype-resources/gitignore.txt ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/src/main/resources/archetype-resources/pom.xml ================================================ 4.0.0 ${groupId} cola-components-parent ${version} ${artifactId} jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee org.slf4j slf4j-api provided ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/src/main/resources/archetype-resources/src/main/java/Dummy.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; /** * * Dummy class * * @author Frank Zhang */ public abstract class Dummy{ private static final long serialVersionUID = 1L; } ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/src/main/resources/archetype-resources/src/test/resources/logback-test.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/src/test/resources/projects/basic/archetype.properties ================================================ #Fri Nov 13 12:26:43 CST 2020 package=it.pkg version=0.1-SNAPSHOT groupId=archetype.it artifactId=basic ================================================ FILE: cola-components/dev-util-archetypes/cola-normal-component-archetype/src/test/resources/projects/basic/goal.txt ================================================ ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-starter-component-archetype 1.0.0-SNAPSHOT jar cola-starter-component-archetype org.apache.maven.archetype archetype-packaging 3.2.0 maven-archetype-plugin 3.2.0 ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml ================================================ gitignore.txt README.md src/main/java **/*.java src/main/resources **/*.factories src/test/java **/*.java src/test/resources **/*.xml **/*.properties ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/README.md ================================================ ## 原理 通过注解AOP,提供service级别的logging和exception处理。 通过Spring Boot的autoConfig机制进行加载,无需手动配置,只需要添加如下依赖即可: ```xml com.alibaba.lst.tech.shared catch-log-starter ``` 兼容普通的HSF service和Mtop service,具体做法可以查看代码`ResponseHandler` ```java public class ResponseHandler { public static Object handle(Class returnType, String errCode, String errMsg){ if (isColaResponse(returnType)){ return handleColaResponse(returnType, errCode, errMsg); } if(isMtopResponse(returnType)){ return handleMtopResponse(returnType, errCode, errMsg); } return null; } ... } ``` ## 使用介绍 1、在需要处理的Service类上面加上@CatchAndLog注解 ```java @CatchAndLog public class GrouponServiceImpl implements GrouponService ``` 2、logback-test.xml为组件开启DEGUG level的日志输出 ```xml ``` 3、如果在控制台看到如下的日志输出,说明CatchAndLog已经在做AOP拦截 ```xml DEBUG c.a.l.t.s.catchlog.CatchLogAspect - Start processing: GrouponServiceImpl.queryGrouponItemDetail(..) DEBUG c.a.l.t.s.catchlog.CatchLogAspect - REQUEST : 257 DEBUG c.a.l.t.s.catchlog.CatchLogAspect - RESPONSE : {"errCode":"UNKNOWN_ERROR"...} DEBUG c.a.l.t.s.catchlog.CatchLogAspect - COST : 1329ms ``` ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/gitignore.txt ================================================ target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr out/ ### NetBeans ### nbproject/private/ build/ nbbuild/ dist/ nbdist/ bin/ doc/ .DS_Store ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/pom.xml ================================================ 4.0.0 ${groupId} cola-components-parent ${version} ${artifactId} jar ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee org.springframework.boot spring-boot-autoconfigure provided org.springframework.boot spring-boot-configuration-processor provided org.springframework.boot spring-boot-starter-aop provided com.alibaba fastjson provided org.slf4j slf4j-api org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/src/main/java/CatchAndLog.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; /** * CatchAndLog * * @author Frank Zhang * @date 2020-11-10 10:48 AM */ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CatchAndLog { } ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/src/main/java/CatchLogAspect.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; /** * @ Description : Catching and Logging * @ Author : Frank Zhang * @ CreateDate : 2020/11/09 * @ Version : 1.0 */ @Aspect @Slf4j public class CatchLogAspect { /** * The syntax of pointcut : https://blog.csdn.net/zhengchao1991/article/details/53391244 */ @Pointcut("@within(CatchAndLog) && execution(public * *(..))") public void pointcut(){ } @Around(value = "pointcut()") public Object around(ProceedingJoinPoint joinPoint ) { long startTime = System.currentTimeMillis(); logRequest(joinPoint); Object response = null; try { response = joinPoint.proceed(); } catch (Throwable e){ response = handleException(joinPoint, e); } finally { logResponse(startTime, response); } return response ; } private Object handleException(ProceedingJoinPoint joinPoint, Throwable e) { MethodSignature ms = (MethodSignature)joinPoint.getSignature(); Class returnType = ms.getReturnType(); //Dummy implementation return null; } private void logResponse(long startTime, Object response) { try{ long endTime = System.currentTimeMillis(); log.debug("RESPONSE : "+ JSON.toJSONString(response) ); log.debug("COST : " + (endTime - startTime) + "ms"); } catch (Exception e){ //swallow it log.error("logResponse error : " + e); } } private void logRequest(ProceedingJoinPoint joinPoint) { try { log.debug("START PROCESSING: " + joinPoint.getSignature().toShortString()); Object[] args = joinPoint.getArgs(); for (Object arg : args) { log.debug("REQUEST : " + JSON.toJSONString(arg)); } } catch (Exception e){ //swallow it log.error("logReqeust error : " + e); } } } ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/src/main/java/CatchLogAutoConfiguration.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @ Description : * @ Author : Frank Zhang * @ CreateDate : 2020/11/09 * @ Version : 1.0 */ @Configuration @EnableAspectJAutoProxy public class CatchLogAutoConfiguration { @Bean @ConditionalOnMissingBean(CatchLogAspect.class) public CatchLogAspect initCatchLogAspect() { return new CatchLogAspect(); } } ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring.factories ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) org.springframework.boot.autoconfigure.EnableAutoConfiguration = ${package}.CatchLogAutoConfiguration ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/src/test/java/test/Application.java ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) package ${package}.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Application * * @author Frank Zhang * @date 2020-11-10 3:58 PM */ @SpringBootApplication(scanBasePackages = {"${package}"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/main/resources/archetype-resources/src/test/resources/logback-test.xml ================================================ #set( $symbol_pound = '#' ) #set( $symbol_dollar = '$' ) #set( $symbol_escape = '\' ) %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/test/resources/projects/basic/archetype.properties ================================================ #Fri Nov 13 16:34:48 CST 2020 package=it.pkg version=0.1-SNAPSHOT groupId=archetype.it artifactId=basic ================================================ FILE: cola-components/dev-util-archetypes/cola-starter-component-archetype/src/test/resources/projects/basic/goal.txt ================================================ ================================================ FILE: cola-components/dev-util-archetypes/new-cola-normal-component.sh ================================================ #!/bin/bash set -eEuo pipefail # adjust current dir to script dir cd "$(dirname "$(readlink -f "$0")")" source ../../scripts/common.sh source ../../scripts/common_build.sh # shellcheck disable=SC2154 [ $# -ne 1 ] && die "need only 1 argument for component name!$nl${nl}usage:$nl $0 hello" readonly component_name="$1" readonly archetype_dir=cola-normal-component-archetype ( cd "$archetype_dir" MVN_WITH_BASIC_OPTIONS install ) groupId=$(extractFirstElementValueFromPom groupId ../pom.xml) component_version=$(extractFirstElementValueFromPom version ../pom.xml) archetypeGroupId=$(extractFirstElementValueFromPom groupId "$archetype_dir/pom.xml") archetypeArtifactId=$(extractFirstElementValueFromPom artifactId "$archetype_dir/pom.xml") archetypeVersion=$(extractFirstElementValueFromPom version "$archetype_dir/pom.xml") cd .. MVN_WITH_BASIC_OPTIONS archetype:generate \ -DgroupId="$groupId" \ -DartifactId="cola-component-$component_name" \ -Dversion="$component_version" \ -Dpackage="com.alibaba.cola.$component_name" \ -DarchetypeGroupId="$archetypeGroupId" \ -DarchetypeArtifactId="$archetypeArtifactId" \ -DarchetypeVersion="$archetypeVersion" \ -DinteractiveMode=false \ -DarchetypeCatalog=local ================================================ FILE: cola-components/dev-util-archetypes/new-cola-starter-component.sh ================================================ #!/bin/bash set -eEuo pipefail # adjust current dir to script dir cd "$(dirname "$(readlink -f "$0")")" source ../../scripts/common.sh source ../../scripts/common_build.sh # shellcheck disable=SC2154 [ $# -ne 1 ] && die "need only 1 argument for component name!$nl${nl}usage:$nl $0 hello" readonly component_name="$1" readonly archetype_dir=cola-starter-component-archetype ( cd "$archetype_dir" MVN_WITH_BASIC_OPTIONS install ) groupId=$(extractFirstElementValueFromPom groupId ../pom.xml) component_version=$(extractFirstElementValueFromPom version ../pom.xml) archetypeGroupId=$(extractFirstElementValueFromPom groupId "$archetype_dir/pom.xml") archetypeArtifactId=$(extractFirstElementValueFromPom artifactId "$archetype_dir/pom.xml") archetypeVersion=$(extractFirstElementValueFromPom version "$archetype_dir/pom.xml") cd .. MVN_WITH_BASIC_OPTIONS archetype:generate \ -DgroupId="$groupId" \ -DartifactId="cola-component-$component_name-starter" \ -Dversion="$component_version" \ -Dpackage="com.alibaba.cola.$component_name" \ -DarchetypeGroupId="$archetypeGroupId" \ -DarchetypeArtifactId="$archetypeArtifactId" \ -DarchetypeVersion="$archetypeVersion" \ -DinteractiveMode=false \ -DarchetypeCatalog=local ================================================ FILE: cola-components/pom.xml ================================================ 4.0.0 com.alibaba.cola cola-components-parent 5.x-SNAPSHOT pom ${project.artifactId} ${project.artifactId} https://github.com/alibaba/COLA GNU Lesser General Public License v2.1 https://github.com/alibaba/COLA/blob/master/LICENSE repo scm:git:https://github.com/alibaba/COLA.git scm:git:https://github.com/alibaba/COLA.git https://github.com/alibaba/COLA https://github.com/alibaba/COLA/issues GitHub Issues significantfrank Frank Zhang 25216348(at)qq.com Developer Architect +8 https://github.com/significantfrank oldratlee Jerry Lee oldratlee(at)gmail.com Developer CI/SCM Engineer +8 https://github.com/oldratlee cola-component-dto cola-component-exception cola-component-statemachine cola-component-domain-starter cola-component-extension-starter cola-component-catchlog-starter cola-component-test-container cola-components-bom cola-component-ruleengine cola-component-unittest 17 17 17 UTF-8 3.3.0 org.projectlombok lombok provided org.junit.jupiter junit-jupiter test org.springframework.boot spring-boot-dependencies ${spring.boot.version} pom import com.alibaba fastjson 1.2.83 provided commons-cli commons-cli 1.8.0 maven-enforcer-plugin enforce 3.3.9 maven-resources-plugin 3.3.1 maven-compiler-plugin 3.13.0 maven-source-plugin 3.3.1 maven-javadoc-plugin 3.7.0 maven-gpg-plugin 3.1.0 maven-enforcer-plugin 3.4.1 maven-deploy-plugin 3.1.1 org.sonatype.plugins nexus-staging-maven-plugin 1.6.13 org.jacoco jacoco-maven-plugin 0.8.12 pl.project13.maven git-commit-id-plugin 4.9.10 org.apache.maven.plugins maven-surefire-plugin 3.2.5 ossrh https://oss.sonatype.org/content/repositories/snapshots gen-java-src performRelease true maven-source-plugin gen-java-doc performRelease true maven-javadoc-plugin attach-javadoc jar 17 protected UTF-8 UTF-8 UTF-8 -quiet -J-Duser.language=en -J-Duser.country=US -Xdoclint:none gen-code-cov env.TRAVIS true org.jacoco jacoco-maven-plugin prepare-agent report test report gen-sign performRelease true maven-gpg-plugin sign-artifacts verify sign gen-git-properties performRelease true pl.project13.maven git-commit-id-plugin get-the-git-infos revision validate-the-git-infos validateRevision validating git dirty ${git.dirty} false true ${project.build.outputDirectory}/META-INF/scm/${project.groupId}/${project.artifactId}/git.properties force-jdk11-when-release performRelease true maven-enforcer-plugin enforce 17 deploy-settings performRelease true org.sonatype.plugins nexus-staging-maven-plugin true ossrh https://oss.sonatype.org/ true ================================================ FILE: cola-samples/charge/README.md ================================================ # 运营商计费系统 计费系统是一个典型的复杂问题场景,比较适合采用COLA架构,且发挥Domain层的价值。故将其作为COLA的另一个Sample。 运营商计费系统的需求如下: 运营商向用户提供电话服务,支持用户拨打/接听电话,并对通话收取费用。 如:主动拨打电话收取 0.5 元/分钟的通话费用;接听电话收取 0.4 元/分钟的通话费用。 运营商为了吸引客户,定义了若干电话套餐,总共有三种类型的套餐。我们要设计一个**计费系统**用于套餐计费规则的执行,保存计费记录,并通知**账户系统**扣减费用。 _注意:在一次通话过程中,通话控制系统可能会调用多次计费系统进行计费。_ - 基础套餐 1. 主叫收费 0.5 元/分钟 2. 被叫收费 0.4 元/分钟 - 固定时长套餐 1. 套餐月固定费 100 元,包含:200 分钟主叫通话时间+200 分钟被叫接听时间 2. 套餐外部分不再参与打折优惠,主叫 0.5 元/分钟,被叫 0.4 元/分钟 - 家庭套餐 1. 套餐月固定费 20 元 2. 用户可以指定 N 个号码作为自己的亲情号 3. 用户接听/拨打亲情号均不收费 4. 与亲情号之外的号码通话,主叫 0.5 元/分钟,被叫 0.4 元/分钟 # 系统设计 ## 统一语言 我经常说,建模就是在分析语言,对于任何问题域,熟悉领域知识、理清概念、统一语言都是非常必要且重要的事情。 对于这个系统也不例外。 ![img_1.png](img_1.png) ## 计费系统和周边系统的关系 ![img.png](img.png) ================================================ FILE: cola-samples/charge/pom.xml ================================================ 4.0.0 com.huawei charging-system 1.0.0-SNAPSHOT 17 17 17 UTF-8 3.0.0 1.3.0 3.2.0 org.springframework.boot spring-boot-dependencies ${spring.boot.version} pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-webflux org.springframework.boot spring-boot-starter-test test org.projectlombok lombok 1.18.22 org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java 8.0.33 com.alibaba.cola cola-component-test-container 4.4.0-SNAPSHOT com.h2database h2 test org.wiremock wiremock-standalone 3.5.4 test ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/Application.java ================================================ package com.huawei.charging; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/adapter/ChargeController.java ================================================ package com.huawei.charging.adapter; import com.huawei.charging.application.ChargeServiceI; import com.huawei.charging.application.dto.*; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @RestController @Slf4j public class ChargeController { @Resource private ChargeServiceI chargeService; @PostMapping("session/{sessionId}/begin") public Response begin(@PathVariable(name = "sessionId") String sessionId, @RequestParam("callingPhoneNo") String callingPhoneNo, @RequestParam("calledPhoneNo") String calledPhoneNo) { log.debug(sessionId + " " + callingPhoneNo + " " + calledPhoneNo); BeginSessionRequest request = new BeginSessionRequest(sessionId, Long.valueOf(callingPhoneNo), Long.valueOf(calledPhoneNo)); return chargeService.begin(request); } @PostMapping("session/{sessionId}/charge") public Response charge(@PathVariable(name = "sessionId") String sessionId, @RequestParam int duration) { log.debug(sessionId + " " + duration); ChargeRequest request = new ChargeRequest(sessionId, duration); return chargeService.charge(request); } @PostMapping("session/{sessionId}/end") public Response end(@PathVariable(name = "sessionId") String sessionId, @RequestParam int duration) { log.debug(sessionId + " " + duration); EndSessionRequest request = new EndSessionRequest(sessionId, duration); return chargeService.end(request); } @GetMapping("{sessionId}/chargeRecords") public MultiResponse getChargeRecord(@PathVariable(name = "sessionId") String sessionId) { return chargeService.listChargeRecords(sessionId); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/ChargeServiceI.java ================================================ package com.huawei.charging.application; import com.huawei.charging.application.dto.*; public interface ChargeServiceI { Response begin(BeginSessionRequest request); Response charge(ChargeRequest request); Response end(EndSessionRequest request); MultiResponse listChargeRecords(String sessionId); } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/ChargeServiceImpl.java ================================================ package com.huawei.charging.application; import com.huawei.charging.application.dto.*; import com.huawei.charging.domain.account.Account; import com.huawei.charging.domain.account.AccountDomainService; import com.huawei.charging.domain.charge.CallType; import com.huawei.charging.domain.charge.ChargeContext; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.Session; import com.huawei.charging.domain.gateway.AccountGateway; import com.huawei.charging.domain.gateway.ChargeGateway; import com.huawei.charging.domain.gateway.SessionGateway; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service @Slf4j public class ChargeServiceImpl implements ChargeServiceI { @Resource private SessionGateway sessionGateway; @Resource private AccountGateway accountGateway; @Resource private AccountDomainService accountDomainService; @Resource private ChargeGateway chargeGateway; @Override public Response begin(BeginSessionRequest request) { Session session = request.toSession(); accountDomainService.canSessionStart(session); sessionGateway.create(session); log.debug("Session created successfully :" + session); return Response.buildSuccess(); } @Override public Response charge(ChargeRequest request) { log.debug("Do charge : " + request); Session session = sessionGateway.get(request.getSessionId()); int durationToCharge = request.getDuration() - session.getChargedDuration(); List chargeRecordList = new ArrayList<>(); chargeCalling(session, durationToCharge, chargeRecordList); chargeCalled(session, durationToCharge, chargeRecordList); chargeGateway.saveAll(chargeRecordList); session.setChargedDuration(request.getDuration()); return Response.buildSuccess(); } private void chargeCalling(Session session, int durationToCharge, List chargeRecordList) { Account callingAccount = accountGateway.getAccount(session.getCallingPhoneNo()); ChargeContext callingCtx = new ChargeContext(CallType.CALLING, session.getCallingPhoneNo(), session.getCalledPhoneNo(), durationToCharge); callingCtx.session = session; callingCtx.account = callingAccount; chargeRecordList.addAll(callingAccount.charge(callingCtx)); } private void chargeCalled(Session session, int durationToCharge, List chargeRecordList) { Account calledAccount = accountGateway.getAccount(session.getCalledPhoneNo()); ChargeContext calledCtx = new ChargeContext(CallType.CALLED, session.getCalledPhoneNo(), session.getCallingPhoneNo(), durationToCharge); calledCtx.session = session; calledCtx.account = calledAccount; chargeRecordList.addAll(calledAccount.charge(calledCtx)); } @Override public Response end(EndSessionRequest request) { charge(request.toChargeRequest()); sessionGateway.end(request.getSessionId()); return Response.buildSuccess(); } @Override public MultiResponse listChargeRecords(String sessionId) { List chargeRecordList = chargeGateway.findBySessionId(sessionId); List chargeRecordDtoList = new ArrayList<>(); for (ChargeRecord chargeRecord : chargeRecordList) { chargeRecordDtoList.add(ChargeRecordDto.fromEntity(chargeRecord)); } return MultiResponse.of(chargeRecordDtoList); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/dto/BeginSessionRequest.java ================================================ package com.huawei.charging.application.dto; import com.huawei.charging.domain.charge.Session; import lombok.Data; @Data public class BeginSessionRequest { /** * 本次通话的UUID */ private String sessionId; /** * 主叫电话号码 */ private long callingPhoneNo; /** * 被叫电话号码 */ private long calledPhoneNo; public Session toSession(){ return new Session(sessionId, callingPhoneNo, calledPhoneNo); } public BeginSessionRequest() { } public BeginSessionRequest(String sessionId, long callingPhoneNo, long calledPhoneNo) { this.sessionId = sessionId; this.callingPhoneNo = callingPhoneNo; this.calledPhoneNo = calledPhoneNo; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/dto/ChargeRecordDto.java ================================================ package com.huawei.charging.application.dto; import com.huawei.charging.domain.charge.CallType; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; public class ChargeRecordDto { public Long id; public String sessionId; public long phoneNo; public int chargeDuration; public long cost; public CallType callType; public ChargePlanType chargePlanType; public static ChargeRecordDto fromEntity(ChargeRecord chargeRecord){ ChargeRecordDto dto = new ChargeRecordDto(); dto.id = chargeRecord.getId(); dto.sessionId = chargeRecord.getSessionId(); dto.phoneNo = chargeRecord.getPhoneNo(); dto.chargeDuration = chargeRecord.getChargeDuration(); dto.cost = chargeRecord.getCost().getAmount(); dto.callType = chargeRecord.getCallType(); dto.chargePlanType = chargeRecord.getChargePlanType(); return dto; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/dto/ChargeRequest.java ================================================ package com.huawei.charging.application.dto; import lombok.Data; @Data public class ChargeRequest { private String sessionId; /** * 当前通话,截止目前的累计时间 */ private int duration; public ChargeRequest() { } public ChargeRequest(String sessionId, int duration) { this.sessionId = sessionId; this.duration = duration; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/dto/EndSessionRequest.java ================================================ package com.huawei.charging.application.dto; import lombok.Data; @Data public class EndSessionRequest { private String sessionId; /** * 当前通话,截止目前的累计时间 */ private int duration; public ChargeRequest toChargeRequest() { ChargeRequest chargeRequest = new ChargeRequest(); chargeRequest.setSessionId(sessionId); chargeRequest.setDuration(duration); return chargeRequest; } public EndSessionRequest() { } public EndSessionRequest(String sessionId, int duration) { this.sessionId = sessionId; this.duration = duration; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/dto/MultiResponse.java ================================================ package com.huawei.charging.application.dto; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; public class MultiResponse extends Response { private static final long serialVersionUID = 1L; private Collection data; public List getData() { if (null == data) { return Collections.emptyList(); } if (data instanceof List) { return (List) data; } return new ArrayList<>(data); } public void setData(Collection data) { this.data = data; } public boolean isEmpty() { return data == null || data.isEmpty(); } public boolean isNotEmpty() { return !isEmpty(); } public static MultiResponse buildSuccess() { MultiResponse response = new MultiResponse(); response.setSuccess(true); return response; } public static MultiResponse buildFailure(String errCode, String errMessage) { MultiResponse response = new MultiResponse(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } public static MultiResponse of(Collection data) { MultiResponse response = new MultiResponse<>(); response.setSuccess(true); response.setData(data); return response; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/dto/Response.java ================================================ package com.huawei.charging.application.dto; public class Response { private boolean success; private String errCode; private String errMessage; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getErrCode() { return errCode; } public void setErrCode(String errCode) { this.errCode = errCode; } public String getErrMessage() { return errMessage; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } @Override public String toString() { return "Response [success=" + success + ", errCode=" + errCode + ", errMessage=" + errMessage + "]"; } public static Response buildSuccess() { Response response = new Response(); response.setSuccess(true); return response; } public static Response buildFailure(String errCode, String errMessage) { Response response = new Response(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/application/dto/SingleResponse.java ================================================ package com.huawei.charging.application.dto; public class SingleResponse extends Response { private static final long serialVersionUID = 1L; private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } public static SingleResponse buildSuccess() { SingleResponse response = new SingleResponse(); response.setSuccess(true); return response; } public static SingleResponse buildFailure(String errCode, String errMessage) { SingleResponse response = new SingleResponse(); response.setSuccess(false); response.setErrCode(errCode); response.setErrMessage(errMessage); return response; } public static SingleResponse of(T data) { SingleResponse response = new SingleResponse<>(); response.setSuccess(true); response.setData(data); return response; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/ApplicationContextHelper.java ================================================ package com.huawei.charging.domain; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationContextHelper implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHelper.applicationContext = applicationContext; } public static T getBean(Class targetClz) { T beanInstance = null; //优先按type查 try { beanInstance = (T)applicationContext.getBean(targetClz); } catch (Exception e) { } //按name查 if (beanInstance == null) { String simpleName = targetClz.getSimpleName(); //首字母小写 simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); beanInstance = (T)applicationContext.getBean(simpleName); } if (beanInstance == null) { throw new RuntimeException("Component " + targetClz + " can not be found in Spring Container"); } return beanInstance; } public static Object getBean(String claz) { return ApplicationContextHelper.applicationContext.getBean(claz); } public static T getBean(String name, Class requiredType) { return ApplicationContextHelper.applicationContext.getBean(name, requiredType); } public static T getBean(Class requiredType, Object... params) { return ApplicationContextHelper.applicationContext.getBean(requiredType, params); } public static ApplicationContext getApplicationContext() { return applicationContext; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/BizException.java ================================================ package com.huawei.charging.domain; public class BizException extends RuntimeException{ public BizException(String errMessage) { super(errMessage); } public static BizException of(String errMessage){ return new BizException(errMessage); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/DomainFactory.java ================================================ package com.huawei.charging.domain; public class DomainFactory { public static T get(Class entityClz){ return ApplicationContextHelper.getBean(entityClz); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/Entity.java ================================================ package com.huawei.charging.domain; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.lang.annotation.*; @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public @interface Entity { } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/account/Account.java ================================================ package com.huawei.charging.domain.account; import com.fasterxml.jackson.annotation.JsonIgnore; import com.huawei.charging.domain.BizException; import com.huawei.charging.domain.DomainFactory; import com.huawei.charging.domain.Entity; import com.huawei.charging.domain.charge.*; import com.huawei.charging.domain.charge.chargeplan.BasicChargePlan; import com.huawei.charging.domain.charge.chargeplan.ChargePlan; import com.huawei.charging.domain.charge.chargerule.ChargeRuleFactory; import com.huawei.charging.domain.charge.chargerule.CompositeChargeRule; import com.huawei.charging.domain.gateway.AccountGateway; import jakarta.annotation.Resource; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; @Data @Entity @Slf4j public class Account { /** * 用户号码 */ private long phoneNo; /** * 账户余额 */ private Money remaining; /** * 账户所拥有的套餐 */ @JsonIgnore private List chargePlanList = new ArrayList<>();; @Resource private AccountGateway accountGateway; private String name; public Account(){ } public Account(long phoneNo, Money amount, List chargePlanList){ this.phoneNo = phoneNo; this.remaining = amount; this.chargePlanList = chargePlanList; } public static Account valueOf(long phoneNo, Money amount) { Account account = DomainFactory.get(Account.class); account.setPhoneNo(phoneNo); account.setRemaining(amount); account.chargePlanList.add(new BasicChargePlan()); return account; } /** * 检查账户余额是否足够 */ public void checkRemaining() { if (remaining.isLessThan(Money.of(0))) { throw BizException.of(this.phoneNo + " has insufficient amount"); } } public List charge(ChargeContext ctx) { CompositeChargeRule compositeChargeRule = ChargeRuleFactory.get(chargePlanList); List chargeRecords = compositeChargeRule.doCharge(ctx); log.debug("Charges: "+ chargeRecords); //跟新账户系统 accountGateway.sync(phoneNo, chargeRecords); return chargeRecords; } @Override public String toString() { return "Account{" + "phoneNo=" + phoneNo + ", remaining=" + remaining + ", chargePlanList=" + chargePlanList + ", name=" + name + '}'; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/account/AccountDomainService.java ================================================ package com.huawei.charging.domain.account; import com.huawei.charging.domain.charge.Session; import com.huawei.charging.domain.gateway.AccountGateway; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; @Component public class AccountDomainService { @Resource private AccountGateway accountGateway; public void canSessionStart(Session session){ Account callingAccount = accountGateway.getAccount(session.getCallingPhoneNo()); Account calledAccount = accountGateway.getAccount(session.getCalledPhoneNo()); callingAccount.checkRemaining(); calledAccount.checkRemaining(); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/CallType.java ================================================ package com.huawei.charging.domain.charge; public enum CallType { /** * 主叫 */ CALLING, /** * 被叫 */ CALLED } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/ChargeContext.java ================================================ package com.huawei.charging.domain.charge; import com.huawei.charging.domain.account.Account; import lombok.Data; @Data public class ChargeContext { /** * 本次通话的Session */ public Session session; /** * 呼叫类型 */ public CallType callType; /** * 账号号码 */ public long phoneNo; /** * 通话另一端号码 */ public long otherSidePhoneNo; /** * 当前需要被扣费的时长 */ public int durationToCharge; /** * 被Charge的账号 */ public Account account; public ChargeContext(CallType callType, long phoneNo, long otherSidePhoneNo, int durationToCharge) { this.callType = callType; this.phoneNo = phoneNo; this.otherSidePhoneNo = otherSidePhoneNo; this.durationToCharge = durationToCharge; } public boolean needCharge(){ return durationToCharge >0; } public boolean isCalling(){ return CallType.CALLING == this.callType; } public boolean isCalled(){ return CallType.CALLED == this.callType; } @Override public String toString() { return "ChargeContext{" + "callType=" + callType + ", phoneNo=" + phoneNo + ", otherSidePhoneNo=" + otherSidePhoneNo + ", durationToCharge=" + durationToCharge + ", account=" + account + '}'; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/ChargeRecord.java ================================================ package com.huawei.charging.domain.charge; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; import lombok.Data; import jakarta.persistence.*; import java.util.Date; @Entity @Table(name = "charge_record") @Data public class ChargeRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long Id; private String sessionId; private long phoneNo; /** * 呼叫类型 */ @Enumerated(EnumType.STRING) private CallType callType; /** * 计费记录所对应的呼叫时长 */ private int chargeDuration; /** * 所属计费套餐 */ @Enumerated(EnumType.STRING) private ChargePlanType chargePlanType; private Money cost; @Temporal(TemporalType.TIMESTAMP) public Date createTime; @Temporal(TemporalType.TIMESTAMP) public Date updateTime; public ChargeRecord() { } public ChargeRecord(long phoneNo, CallType callType, int chargeDuration, ChargePlanType chargePlanType, Money cost) { this.phoneNo = phoneNo; this.callType = callType; this.chargeDuration = chargeDuration; this.chargePlanType = chargePlanType; this.cost = cost; } @Override public String toString() { return "Charge{" + "phoneNo=" + phoneNo + ", callType=" + callType + ", chargeDuration=" + chargeDuration + ", chargePlanType=" + chargePlanType + ", cost=" + cost + '}'; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/Money.java ================================================ package com.huawei.charging.domain.charge; import lombok.Data; import java.util.Objects; /** * 这个Money是简化版的,真实场景应该用BigDecimal */ @Data public class Money { /** * 单位是角,1代表0.1元, 10代表1元 */ private int amount; public Money(int amount) { this.amount = amount; } public static Money of(int amount){ return new Money(amount); } public boolean isLessThan(Money money){ return this.amount <= money.getAmount(); } public void minus(Money money){ this.amount = this.amount - money.getAmount(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Money money = (Money) o; return amount == money.amount; } @Override public int hashCode() { return Objects.hash(amount); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/MoneyConverter.java ================================================ package com.huawei.charging.domain.charge; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; @Converter(autoApply = true) public class MoneyConverter implements AttributeConverter { @Override public Long convertToDatabaseColumn(Money entityData) { return Long.valueOf(entityData.getAmount()); } @Override public Money convertToEntityAttribute(Long dbData) { return Money.of(dbData.intValue()); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/Session.java ================================================ package com.huawei.charging.domain.charge; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class Session { private String sessionId; /** * 主叫电话号码 */ private long callingPhoneNo; /** * 被叫电话号码 */ private long calledPhoneNo; /** * 当前通话已扣费的时长 * */ private int chargedDuration; /** * 当前通话产生的Charge记录 */ private List chargeRecordList = new ArrayList<>(); public Session(String sessionId, long callingPhoneNo, long calledPhoneNo) { this.sessionId = sessionId; this.callingPhoneNo = callingPhoneNo; this.calledPhoneNo = calledPhoneNo; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargeplan/BasicChargePlan.java ================================================ package com.huawei.charging.domain.charge.chargeplan; public class BasicChargePlan extends ChargePlan{ public BasicChargePlan(){ this.priority = 0; } @Override public BasicChargeFee getResource() { return new BasicChargeFee(); } @Override public ChargePlanType getType() { return ChargePlanType.BASIC; } public static class BasicChargeFee implements Resource{ /** * 主叫单价。单位是角,5表示0.5元每分钟 */ public final int CALLING_PRICE = 5; /** * 主叫单价。单位是角,4表示0.4元每分钟 */ public final int CALLED_PRICE = 4; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargeplan/ChargePlan.java ================================================ package com.huawei.charging.domain.charge.chargeplan; public abstract class ChargePlan implements Comparable{ protected int priority; public abstract T getResource(); public abstract ChargePlanType getType(); public ChargePlan(){ } /** * 不同套餐之间的优先级关系 * @param other the object to be compared. * @return */ @Override public int compareTo(ChargePlan other) { return other.priority - this.priority; } @Override public String toString() { return "ChargePlan{chargeType=" + getType()+ ", priority=" + priority + '}'; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargeplan/ChargePlanType.java ================================================ package com.huawei.charging.domain.charge.chargeplan; public enum ChargePlanType { /** * 基础套餐 */ BASIC, /** * 固定时常套餐 */ FIXED_TIME, /** * 家庭套餐 */ FAMILY } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargeplan/FamilyChargePlan.java ================================================ package com.huawei.charging.domain.charge.chargeplan; import java.util.HashSet; import java.util.Set; public class FamilyChargePlan extends ChargePlan { public FamilyChargePlan() { this.priority = 2; } @Override public FamilyMember getResource() { return new FamilyMember(); } @Override public ChargePlanType getType() { return ChargePlanType.FAMILY; } public static class FamilyMember implements Resource{ private Set familyMembers = new HashSet<>(); /** * Mock here, 真实场景,情亲号码肯定也是从外系统获取的 */ public FamilyMember() { familyMembers.add(13681874561L); familyMembers.add(15921582125L); } public boolean isMember(long phoneNo) { return familyMembers.contains(phoneNo); } } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargeplan/FixedTimeChangePlan.java ================================================ package com.huawei.charging.domain.charge.chargeplan; public class FixedTimeChangePlan extends ChargePlan{ public FixedTimeChangePlan() { this.priority=1; } @Override public FreeCallTime getResource() { return new FreeCallTime(); } @Override public ChargePlanType getType() { return ChargePlanType.FIXED_TIME; } public static class FreeCallTime implements Resource{ public static int FREE_CALLING_TIME = 200; public static int FREE_CALLED_TIME = 200; public boolean isCallingTimeRemaining(){ return FREE_CALLING_TIME > 0; } /** * 扣减固定时长套餐的费用 * @param duration 扣减时长 * @return 剩余还需要扣减的时长 */ public int chargeFreeCallingTime(int duration){ if(duration > FREE_CALLING_TIME){ int durationToCharge = duration - FREE_CALLING_TIME; FREE_CALLING_TIME = 0; return durationToCharge; } else{ FREE_CALLING_TIME = FREE_CALLING_TIME - duration; return 0; } } public boolean isCalledTimeRemaining(){ return FREE_CALLED_TIME > 0; } /** * 扣减固定时长套餐的费用 * @param duration 扣减时长 * @return 剩余还需要扣减的时长 */ public int chargeFreeCalledTime(int duration){ if(duration > FREE_CALLED_TIME){ int durationToCharge = duration - FREE_CALLED_TIME; FREE_CALLED_TIME = 0; return durationToCharge; } else{ FREE_CALLED_TIME = FREE_CALLED_TIME - duration; return 0; } } } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargeplan/Resource.java ================================================ package com.huawei.charging.domain.charge.chargeplan; /** * 套餐背后所绑定的资源 */ public interface Resource { } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargerule/AbstractChargeRule.java ================================================ package com.huawei.charging.domain.charge.chargerule; import com.huawei.charging.domain.charge.chargeplan.ChargePlan; public abstract class AbstractChargeRule implements ChargeRule{ protected ChargePlan chargePlan; @Override public void belongsTo(ChargePlan chargePlan){ this.chargePlan = chargePlan; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargerule/BasicChargeRule.java ================================================ package com.huawei.charging.domain.charge.chargerule; import com.huawei.charging.domain.charge.*; import com.huawei.charging.domain.charge.chargeplan.BasicChargePlan; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component @Slf4j public class BasicChargeRule extends AbstractChargeRule{ @Override public ChargeRecord doCharge(ChargeContext ctx) { if(!ctx.needCharge()){ log.debug("No need charge for : "+ctx); return null; } BasicChargePlan basicChargePlan = (BasicChargePlan)chargePlan; BasicChargePlan.BasicChargeFee chargeFee = basicChargePlan.getResource(); Money cost; int duration = ctx.durationToCharge; if (ctx.callType == CallType.CALLING) { cost = Money.of(duration * chargeFee.CALLING_PRICE); } else { cost = Money.of(duration * chargeFee.CALLED_PRICE); } ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, duration, ChargePlanType.BASIC, cost); //在账号上扣减费用 ctx.account.getRemaining().minus(cost); ctx.setDurationToCharge(0); return chargeRecord; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargerule/ChargeRule.java ================================================ package com.huawei.charging.domain.charge.chargerule; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.ChargeContext; import com.huawei.charging.domain.charge.chargeplan.ChargePlan; public interface ChargeRule { ChargeRecord doCharge(ChargeContext ctx); void belongsTo(ChargePlan chargePlan); } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargerule/ChargeRuleFactory.java ================================================ package com.huawei.charging.domain.charge.chargerule; import com.huawei.charging.domain.ApplicationContextHelper; import com.huawei.charging.domain.charge.chargeplan.ChargePlan; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ChargeRuleFactory { public static CompositeChargeRule get(List chargePlanList) { //按套餐的优先级进行排序 Collections.sort(chargePlanList); List chargeRules = new ArrayList<>(); for (ChargePlan chargePlan : chargePlanList) { ChargeRule chargeRule; if (chargePlan.getType() == ChargePlanType.FAMILY) { chargeRule = ApplicationContextHelper.getBean(FamilyChargeRule.class); } else if (chargePlan.getType() == ChargePlanType.FIXED_TIME) { chargeRule = ApplicationContextHelper.getBean(FixedTimeChargeRule.class); } else { chargeRule = ApplicationContextHelper.getBean(BasicChargeRule.class); } chargeRule.belongsTo(chargePlan); chargeRules.add(chargeRule); } CompositeChargeRule compositeChargeRule = new CompositeChargeRule(); compositeChargeRule.chargeRules = chargeRules; return compositeChargeRule; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargerule/CompositeChargeRule.java ================================================ package com.huawei.charging.domain.charge.chargerule; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.ChargeContext; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 为了应对套餐组合 * 组合模式(Composite pattern) */ public class CompositeChargeRule { public List chargeRules; public List doCharge(ChargeContext chargeContext){ List chargeRecords = new ArrayList<>(); for(ChargeRule chargeRule : chargeRules){ ChargeRecord chargeRecord = chargeRule.doCharge(chargeContext); if(chargeRecord != null){ chargeRecord.setSessionId(chargeContext.getSession().getSessionId()); //fill fields for persistence needs chargeRecord.setCreateTime(new Date()); chargeRecord.setUpdateTime(new Date()); chargeRecords.add(chargeRecord); } } return chargeRecords; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargerule/FamilyChargeRule.java ================================================ package com.huawei.charging.domain.charge.chargerule; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.ChargeContext; import com.huawei.charging.domain.charge.Money; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; import com.huawei.charging.domain.charge.chargeplan.FamilyChargePlan; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component @Slf4j public class FamilyChargeRule extends AbstractChargeRule { @Override public ChargeRecord doCharge(ChargeContext ctx) { FamilyChargePlan familyChargePlan = (FamilyChargePlan) chargePlan; FamilyChargePlan.FamilyMember familyMember = familyChargePlan.getResource(); if (familyMember.isMember(ctx.otherSidePhoneNo)) { log.debug("Family Charge plan for Account : " + ctx.account); ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, ctx.durationToCharge, ChargePlanType.FAMILY, Money.of(0)); ctx.setDurationToCharge(0); return chargeRecord; } return null; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/charge/chargerule/FixedTimeChargeRule.java ================================================ package com.huawei.charging.domain.charge.chargerule; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.ChargeContext; import com.huawei.charging.domain.charge.Money; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; import com.huawei.charging.domain.charge.chargeplan.FixedTimeChangePlan; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Component @Slf4j public class FixedTimeChargeRule extends AbstractChargeRule { @Override public ChargeRecord doCharge(ChargeContext ctx) { if(!ctx.needCharge()){ log.debug("No need charge for : "+ctx); return null; } FixedTimeChangePlan fixedTimeChangePlan = (FixedTimeChangePlan) chargePlan; FixedTimeChangePlan.FreeCallTime freeCallTime = fixedTimeChangePlan.getResource(); if (ctx.isCalling() && freeCallTime.isCallingTimeRemaining()) { int leftDuration = freeCallTime.chargeFreeCallingTime(ctx.durationToCharge); log.debug("Calling Left Duration after FixedTimeCharge : " + leftDuration); ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, ctx.durationToCharge - leftDuration, ChargePlanType.FIXED_TIME, Money.of(0)); ctx.setDurationToCharge(leftDuration); return chargeRecord; } if (ctx.isCalled() && freeCallTime.isCalledTimeRemaining()) { int leftDuration = freeCallTime.chargeFreeCalledTime(ctx.durationToCharge); log.debug("Called Left Duration after FixedTimeCharge : " + leftDuration); ChargeRecord chargeRecord = new ChargeRecord(ctx.phoneNo, ctx.callType, ctx.durationToCharge - leftDuration, ChargePlanType.FIXED_TIME, Money.of(0)); ctx.setDurationToCharge(leftDuration); return chargeRecord; } return null; } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/gateway/AccountGateway.java ================================================ package com.huawei.charging.domain.gateway; import com.huawei.charging.domain.account.Account; import com.huawei.charging.domain.charge.ChargeRecord; import java.util.List; /** * 跟账户系统交互的网关(Gateway) * * @version 1.0 */ public interface AccountGateway { /** * 根据用户号码获取账户信息(含计费项余额等信息) * * @param phoneNo 电话号码 * @return 账户信息 */ Account getAccount(long phoneNo); /** * 将扣费记录同步到账户中 * * @param phoneNo 电话号码 * @param records 扣费记录 */ void sync(long phoneNo, List records); } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/gateway/ChargeGateway.java ================================================ package com.huawei.charging.domain.gateway; import com.huawei.charging.domain.charge.ChargeRecord; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface ChargeGateway extends JpaRepository { public List findBySessionId(String sessionId); public ChargeRecord getBySessionId(String sessionId); public List findByPhoneNo(long phoneNo); } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/domain/gateway/SessionGateway.java ================================================ package com.huawei.charging.domain.gateway; import com.huawei.charging.domain.charge.Session; public interface SessionGateway { void create(Session session); Session get(String sessionId); void end(String sessionId); } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/infrastructure/AccountGatewayImpl.java ================================================ package com.huawei.charging.infrastructure; import com.huawei.charging.domain.account.Account; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.Money; import com.huawei.charging.domain.gateway.AccountGateway; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; import java.util.HashMap; import java.util.List; import java.util.Map; @Component @Slf4j public class AccountGatewayImpl implements AccountGateway { private static final String GET_ACCOUNT_PATH = "/v1/api/account/{account}"; private static final String SYNC_ACCOUNT_PATH = "/v1/api/account/account/{account}/sync"; private Map accountMap = new HashMap<>(); @Autowired private RestClient restClient; @Override public Account getAccount(long phoneNo) { Account account = restClient.get() .uri(GET_ACCOUNT_PATH, phoneNo) .accept(MediaType.APPLICATION_JSON) .retrieve() .body(Account.class); return account; } @Override public void sync(long phoneNo, List records) { // 更新账户系统 log.info("sync account info, to be implemented"); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/infrastructure/RestClientBean.java ================================================ package com.huawei.charging.infrastructure; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestClient; @Configuration public class RestClientBean { @Value("${REMOTE_BASE_URI:http://localhost:8080}") String baseURI; @Bean RestClient restClient() { return RestClient.create(baseURI); } } ================================================ FILE: cola-samples/charge/src/main/java/com/huawei/charging/infrastructure/SessionGatewayImpl.java ================================================ package com.huawei.charging.infrastructure; import com.huawei.charging.domain.BizException; import com.huawei.charging.domain.charge.Session; import com.huawei.charging.domain.gateway.SessionGateway; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class SessionGatewayImpl implements SessionGateway { private Map sessionMap = new HashMap<>(); @Override public void create(Session session) { sessionMap.put(session.getSessionId(), session); } @Override public Session get(String sessionId) { return sessionMap.get(sessionId); } @Override public void end(String sessionId) { //真实场景是逻辑删除,比如把session的状态标记为“已结束”。 sessionMap.remove(sessionId); } } ================================================ FILE: cola-samples/charge/src/main/resources/application.yml ================================================ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${MYSQL_SERVER:localhost}:${MYSQL_PORT:3306}/${MYSQL_DB_NAME:chargeDB}?serverTimezone=UTC #如果运行出错,可以把连接写成下面的路径进行测试 #url: jdbc:mysql://localhost:3306/blogDB?useUnicode=true&characterEncoding=utf-8 username: ${MYSQL_USER_TEST:root} password: ${MYSQL_PASSWORD_TEST:root} jpa: hibernate: ddl-auto: update show-sql: true server: port: 8081 my-name: default my-age: default ================================================ FILE: cola-samples/charge/src/main/resources/logback.xml ================================================ %-4relative [%thread] %-5level %logger{35} - %msg%n utf8 ================================================ FILE: cola-samples/charge/src/test/charge.http ================================================ ### list charge records by sessionId GET http://localhost:8080/123145/chargeRecords Accept: application/json ### end session POST http://localhost:8080/session/123145/end?duration=10 Content-Type: application/x-www-form-urlencoded duration=10 ### do charge POST http://localhost:8080/session/123145/charge?duration=10 ### begin Session POST http://localhost:8080/session/123145/begin?callingPhoneNo=13681874561&calledPhoneNo=15921252125 <> 2022-11-03T150743.200.json ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/CleanArchTest.java ================================================ package com.huawei.charging; import org.junit.jupiter.api.Test; public class CleanArchTest { @Test public void protect_clean_arch() { // JavaClasses classes = new ClassFileImporter() // .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) // .importPackages("com.huawei.charging"); // // layeredArchitecture() // .consideringOnlyDependenciesInLayers() // .layer("adapter").definedBy("com.huawei.charging.adapter") // .layer("application").definedBy("com.huawei.charging.application") // .layer("domain").definedBy("com.huawei.charging.domain") // .layer("infrastructure").definedBy("com.huawei.charging.infrastructure") // .whereLayer("adapter").mayNotBeAccessedByAnyLayer() // //.whereLayer("domain").mayOnlyBeAccessedByLayers("application", "infrastructure") // .as("The layer dependencies must be respected") // .because("we must follow the Clean Architecture principle") // .check(classes); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/TestsContainerBoot.java ================================================ package com.huawei.charging; import com.alibaba.cola.test.TestsContainer; public class TestsContainerBoot { public static void main(String[] args) { TestsContainer.start(); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/application/ChargeServiceTest.java ================================================ package com.huawei.charging.application; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import com.huawei.charging.Application; import com.huawei.charging.application.dto.BeginSessionRequest; import com.huawei.charging.domain.BizException; import com.huawei.charging.domain.gateway.AccountGateway; import com.huawei.charging.domain.gateway.SessionGateway; import com.huawei.charging.infrastructure.WireMockRegister; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; @SpringBootTest @ContextConfiguration(classes = Application.class) @WireMockTest(httpPort = 8080) public class ChargeServiceTest { @Autowired private ChargeServiceI chargeService; @Autowired private SessionGateway sessionGateway; @Autowired private AccountGateway accountGateway; @Test public void test_session_create(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_account.json"); BeginSessionRequest request = new BeginSessionRequest(); String sessionId = "00002"; request.setSessionId(sessionId); request.setCallingPhoneNo(13681874563L); request.setCalledPhoneNo(15921582125L); chargeService.begin(request); Assertions.assertEquals(sessionId, sessionGateway.get(sessionId).getSessionId()); } @Test public void test_remaining_insufficient(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_insufficient_account.json"); BeginSessionRequest request = new BeginSessionRequest(); String sessionId = "00003"; request.setSessionId(sessionId); request.setCallingPhoneNo(13681874561L); request.setCalledPhoneNo(15921582125L); Exception exception = Assertions.assertThrows(BizException.class, () -> { chargeService.begin(request); }); String expectedMsg = "has insufficient amount"; String actualMsg = exception.getMessage(); Assertions.assertTrue(actualMsg.contains(expectedMsg)); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/domain/ChargeRecordPlanTest.java ================================================ package com.huawei.charging.domain; import com.huawei.charging.domain.charge.chargeplan.BasicChargePlan; import com.huawei.charging.domain.charge.chargeplan.ChargePlan; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; import com.huawei.charging.domain.charge.chargeplan.FamilyChargePlan; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ChargeRecordPlanTest { @Test public void test_priority(){ ChargePlan basicChargePlan = new BasicChargePlan(); ChargePlan familyChargePlan = new FamilyChargePlan(); ChargePlan fixedTimeChargePlan = new FamilyChargePlan(); List chargePlanList = new ArrayList<>(); chargePlanList.add(basicChargePlan); chargePlanList.add(familyChargePlan); chargePlanList.add(fixedTimeChargePlan); Collections.sort(chargePlanList); System.out.println(chargePlanList.get(0)); Assertions.assertEquals(ChargePlanType.FAMILY, chargePlanList.get(0).getType()); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/domain/ChargeRecordRuleTest.java ================================================ package com.huawei.charging.domain; import com.huawei.charging.domain.account.Account; import com.huawei.charging.domain.charge.CallType; import com.huawei.charging.domain.charge.ChargeContext; import com.huawei.charging.domain.charge.Money; import com.huawei.charging.domain.charge.chargeplan.BasicChargePlan; import com.huawei.charging.domain.charge.chargeplan.ChargePlan; import com.huawei.charging.domain.charge.chargeplan.FamilyChargePlan; import com.huawei.charging.domain.charge.chargeplan.FixedTimeChangePlan; import com.huawei.charging.domain.charge.chargerule.BasicChargeRule; import com.huawei.charging.domain.charge.chargerule.FamilyChargeRule; import com.huawei.charging.domain.charge.chargerule.FixedTimeChargeRule; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.Collections; public class ChargeRecordRuleTest { @Test public void test_basic_charge_rule(){ //prepare ChargePlan chargePlan = new BasicChargePlan(); Account account = new Account(13681874561L, Money.of(200), Collections.singletonList(chargePlan)); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 20); ctx.account = account; System.out.println("Account before charge: "+ account); //do BasicChargeRule basicChargeRule = new BasicChargeRule(); basicChargeRule.belongsTo(chargePlan); basicChargeRule.doCharge(ctx); //check System.out.println("Account after charge: "+ account); Assertions.assertEquals( Money.of(100), ctx.account.getRemaining()); Assertions.assertEquals( 0, ctx.getDurationToCharge()); } @Test public void test_family_charge_rule(){ //prepare FamilyChargePlan chargePlan = new FamilyChargePlan(); Account account = new Account(13681874561L, Money.of(200), Collections.singletonList(chargePlan)); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 20); ctx.account = account; System.out.println("Account before charge: "+ account); //do FamilyChargeRule familyChargeRule = new FamilyChargeRule(); familyChargeRule.belongsTo(chargePlan); familyChargeRule.doCharge(ctx); //check System.out.println("Account after charge: "+ account); Assertions.assertEquals( Money.of(200), ctx.account.getRemaining()); Assertions.assertEquals( 0, ctx.getDurationToCharge()); } @Test public void test_fixed_time_charge_rule(){ //prepare FixedTimeChangePlan chargePlan = new FixedTimeChangePlan(); Account account = new Account(13681874561L, Money.of(200), Collections.singletonList(chargePlan)); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 180); ctx.account = account; System.out.println("Account before charge: "+ account); //do FixedTimeChargeRule fixedTimeChargeRule = new FixedTimeChargeRule(); fixedTimeChargeRule.belongsTo(chargePlan); fixedTimeChargeRule.doCharge(ctx); //check System.out.println("Account after charge: "+ account); Assertions.assertEquals( true, chargePlan.getResource().isCallingTimeRemaining()); Assertions.assertEquals( 0, ctx.getDurationToCharge()); // come a new charge ChargeContext ctx2 = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 40); ctx2.account = account; fixedTimeChargeRule.doCharge(ctx2); Assertions.assertEquals( false, chargePlan.getResource().isCallingTimeRemaining()); Assertions.assertEquals( 20, ctx2.getDurationToCharge()); //reset fixed time FixedTimeChangePlan.FreeCallTime.FREE_CALLED_TIME = 200; FixedTimeChangePlan.FreeCallTime.FREE_CALLING_TIME = 200; } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/domain/CompositeChargeRuleTestRecord.java ================================================ package com.huawei.charging.domain; import com.huawei.charging.Application; import com.huawei.charging.domain.account.Account; import com.huawei.charging.domain.charge.*; import com.huawei.charging.domain.charge.chargeplan.BasicChargePlan; import com.huawei.charging.domain.charge.chargeplan.ChargePlan; import com.huawei.charging.domain.charge.chargeplan.FamilyChargePlan; import com.huawei.charging.domain.charge.chargeplan.FixedTimeChangePlan; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import java.util.ArrayList; import java.util.List; import java.util.UUID; @SpringBootTest @ContextConfiguration(classes = Application.class) public class CompositeChargeRuleTestRecord { private long callingPhoneNo = 13681874561L; private long calledPhoneNo = 15921582125L; @Test public void test_basic_and_fixedTime_charge_rule(){ // prepare List chargePlanList = new ArrayList<>(); chargePlanList.add(new BasicChargePlan()); chargePlanList.add(new FixedTimeChangePlan()); Account account = Account.valueOf(13681874561L, Money.of(200)); // for spring bean account.setChargePlanList(chargePlanList); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 220); String sessionId = UUID.randomUUID().toString(); Session session = new Session(sessionId, callingPhoneNo, calledPhoneNo); ctx.setSession(session); ctx.account = account; // do List chargeRecords = account.charge(ctx); System.out.println("Account after charge: "+ account); // check Assertions.assertEquals(2, chargeRecords.size()); } @Test public void test_basic_and_family_charge_rule(){ // prepare List chargePlanList = new ArrayList<>(); chargePlanList.add(new BasicChargePlan()); chargePlanList.add(new FamilyChargePlan()); Account account = Account.valueOf(13681874561L, Money.of(200)); // for spring bean account.setChargePlanList(chargePlanList); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 220); String sessionId = UUID.randomUUID().toString(); Session session = new Session(sessionId, callingPhoneNo, calledPhoneNo); ctx.setSession(session); ctx.account = account; // do List chargeRecords = account.charge(ctx); System.out.println("Account after charge: "+ account); // check Assertions.assertEquals(1, chargeRecords.size()); } @Test public void test_all_charge_rule(){ // prepare List chargePlanList = new ArrayList<>(); chargePlanList.add(new BasicChargePlan()); chargePlanList.add(new FamilyChargePlan()); chargePlanList.add(new FixedTimeChangePlan()); Account account = Account.valueOf(13681874561L, Money.of(200)); // for spring bean account.setChargePlanList(chargePlanList); ChargeContext ctx = new ChargeContext(CallType.CALLING, 13681874561L, 15921582125L, 220); String sessionId = UUID.randomUUID().toString(); Session session = new Session(sessionId, callingPhoneNo, calledPhoneNo); ctx.setSession(session); ctx.account = account; // do List chargeRecords = account.charge(ctx); System.out.println("Account after charge: "+ account); // check Assertions.assertEquals(1, chargeRecords.size()); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/infrastructure/AccountGatewayTest.java ================================================ package com.huawei.charging.infrastructure; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import com.huawei.charging.Application; import com.huawei.charging.domain.account.Account; import com.huawei.charging.domain.charge.Money; import com.huawei.charging.domain.gateway.AccountGateway; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; @SpringBootTest @ContextConfiguration(classes = Application.class) @WireMockTest(httpPort = 8080) public class AccountGatewayTest { @Autowired AccountGateway accountGateway; @Test public void testGetAccount(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_account.json"); Account account = accountGateway.getAccount(15921582125L); System.out.println("account : " + account); Assertions.assertEquals(account.getPhoneNo(), 15921582125L); Assertions.assertEquals(account.getRemaining(), Money.of(400)); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/infrastructure/ChargeRecordRepoTest.java ================================================ package com.huawei.charging.infrastructure; import com.huawei.charging.domain.charge.CallType; import com.huawei.charging.domain.charge.ChargeRecord; import com.huawei.charging.domain.charge.Money; import com.huawei.charging.domain.charge.chargeplan.ChargePlanType; import com.huawei.charging.domain.gateway.ChargeGateway; import jakarta.annotation.Resource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; @SpringBootTest public class ChargeRecordRepoTest { @Resource private ChargeGateway chargeGateway; private String sessionId; @BeforeEach public void setup(){ sessionId = UUID.randomUUID().toString(); } @Test public void testSave(){ ChargeRecord chargeRecord = new ChargeRecord(13681874561L, CallType.CALLED, 10, ChargePlanType.FAMILY, Money.of(123)); chargeRecord.setSessionId(sessionId); chargeRecord.setCreateTime(new Date()); chargeRecord.setUpdateTime(new Date()); chargeGateway.save(chargeRecord); chargeRecord = chargeGateway.getBySessionId(sessionId); Assertions.assertEquals(chargeRecord.getSessionId(), sessionId); } @Test public void testSaveList(){ List chargeRecordList = new ArrayList<>(); ChargeRecord chargeRecord1 = new ChargeRecord(13681874561L, CallType.CALLED, 10, ChargePlanType.FAMILY, Money.of(123)); chargeRecord1.setSessionId(UUID.randomUUID().toString()); ChargeRecord chargeRecord2 = new ChargeRecord(13681874561L, CallType.CALLING, 10, ChargePlanType.FAMILY, Money.of(123)); chargeRecord2.setSessionId(UUID.randomUUID().toString()); chargeRecordList.add(chargeRecord1); chargeRecordList.add(chargeRecord2); chargeGateway.saveAll(chargeRecordList); List result = chargeGateway.findByPhoneNo(13681874561L); Assertions.assertEquals(result.size(), 2); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/infrastructure/FixtureLoader.java ================================================ package com.huawei.charging.infrastructure; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import org.springframework.core.io.ClassPathResource; import org.springframework.util.StreamUtils; public class FixtureLoader { public static String loadResource(String resourcePath) { // 创建一个 ClassPathResource 对象 ClassPathResource resource = new ClassPathResource(resourcePath); // 使用 withResource 来自动关闭输入流 String content = ""; try (InputStream inputStream = resource.getInputStream()) { content = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return content; } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/infrastructure/JSONTest.java ================================================ package com.huawei.charging.infrastructure; import com.fasterxml.jackson.databind.ObjectMapper; import com.huawei.charging.domain.account.Account; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class JSONTest { @Test public void testJsonBind() { // this will throw exception since account not recognized String badJson = "{\"account\":{\"name\":\"frank\",\"phoneNo\":\"15921582125\",\"remaining\":\"400\",\"chargePlanList\":[{\"priority\":\"2\",\"type\":\"fixedTime\"},{\"priority\":\"1\",\"type\":\"familyMember\"}]}}"; // this is good String goodJson = "{\"name\":\"frank\",\"phoneNo\":\"15921582125\",\"remaining\":\"400\",\"chargePlanList\":[{\"priority\":\"2\",\"type\":\"fixedTime\"},{\"priority\":\"1\",\"type\":\"familyMember\"}]}"; try { ObjectMapper objectMapper = new ObjectMapper(); Account account = objectMapper.readValue(goodJson, Account.class); Assertions.assertEquals(account.getPhoneNo(), 15921582125L); System.out.println(account); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/infrastructure/SpingBootConfTest.java ================================================ package com.huawei.charging.infrastructure; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @SpringBootTest //use test profile, this will merge application.yml and application-test.yaml. //but if you put an application.yml under test/resources, it will replace the project application.yml. //the advantage of profile, is that it can inherit and override. @ActiveProfiles("test") public class SpingBootConfTest { @Value("${spring.jpa.show-sql}") private String showSql; @Value("${spring.jpa.hibernate.ddl-auto}") private String ddlAuto; @Value("${my-name}") private String myName; @Value("${my-age}") private String myAge; @Value("${my-age-test}") private String myAgeTest; @Test public void test() { System.out.println("spring.jpa.show-sql : " + showSql); System.out.println("spring.jpa.hibernate.ddl-auto : " + ddlAuto); System.out.println("myName : " + myName); System.out.println("myAge : " + myAge); System.out.println("myAgeTest : " + myAgeTest); Assertions.assertEquals("30", myAge); Assertions.assertEquals("40", myAgeTest); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/infrastructure/WireMockBasicTest.java ================================================ package com.huawei.charging.infrastructure; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import com.huawei.charging.Application; import com.huawei.charging.domain.account.Account; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import static com.github.tomakehurst.wiremock.client.WireMock.*; @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @WireMockTest(httpPort = 8080) @Slf4j public class WireMockBasicTest { @Autowired protected WebTestClient webClient; @Test public void testWireMockBasic() { // The static DSL will be automatically configured for you stubFor(get("/static-dsl").willReturn(ok())); webClient.get() .uri("http://localhost:8080/static-dsl") .exchange() .expectStatus() .isEqualTo(200); } @Test public void testWireMockStub(WireMockRuntimeInfo wmRuntimeInfo) { WireMock wireMock = wmRuntimeInfo.getWireMock(); WireMockRegister.registerStub(wireMock, "/fixture/wiremock/stub_wire_mock_basic.json"); webClient.get() .uri("http://localhost:8080/v1/wiremock/basic") .exchange() .expectStatus() .isEqualTo(200) .expectHeader() .contentType(MediaType.APPLICATION_JSON); System.out.println("wire mock serer port : " + wmRuntimeInfo.getHttpPort()); } @Test public void testWireMockAccount(WireMockRuntimeInfo wmRuntimeInfo) { WireMockRegister.registerStub(wmRuntimeInfo.getWireMock(), "/fixture/wiremock/stub_account.json"); long phoneNo = 123456789; webClient.get() .uri("http://localhost:8080/v1/api/account/"+phoneNo) .exchange() .expectStatus() .isEqualTo(200) .expectHeader() .contentType(MediaType.APPLICATION_JSON) .returnResult(Account.class) .getResponseBody() .map(account -> { log.info(account.toString()); Assertions.assertEquals("frank", account.getName()); Assertions.assertEquals(phoneNo, account.getPhoneNo()); return account; }) .subscribe(); log.info("wire mock serer port : " + wmRuntimeInfo.getHttpPort()); } } ================================================ FILE: cola-samples/charge/src/test/java/com/huawei/charging/infrastructure/WireMockRegister.java ================================================ package com.huawei.charging.infrastructure; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.stubbing.StubMapping; public class WireMockRegister { public static void registerStub(WireMock wireMock, String resourcePath){ StubMapping stubMapping = StubMapping.buildFrom(FixtureLoader.loadResource(resourcePath)); wireMock.register(stubMapping); } } ================================================ FILE: cola-samples/charge/src/test/resources/application-test.yml ================================================ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # hard code for test purpose url: jdbc:mysql://localhost:3306/chargeDB?serverTimezone=UTC username: root password: root jpa: hibernate: ddl-auto: update show-sql: true # this will override config in test/resources/application.yml and resources/application.yml my-age: 30 my-age-test: 40 ================================================ FILE: cola-samples/charge/src/test/resources/application.yml ================================================ spring: datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=1 username: sa password: jpa: hibernate: ddl-auto: update show-sql: true server: port: 8081 my-name: frank my-age: 35 REMOTE_BASE_URI: http://localhost:8080 ================================================ FILE: cola-samples/charge/src/test/resources/fixture/wiremock/stub_account.json ================================================ { "request": { "urlPathPattern": "/v1/api/account/[0-9]+", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "transformers": [ "response-template" ], "jsonBody": { "name": "frank", "phoneNo": "{{request.path.[3]}}", "remaining": "400", "chargePlanList": [ { "priority": "2", "type": "fixedTime" }, { "priority": "1", "type": "familyMember" } ] } } } ================================================ FILE: cola-samples/charge/src/test/resources/fixture/wiremock/stub_insufficient_account.json ================================================ { "request": { "urlPathPattern": "/v1/api/account/[0-9]+", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "transformers": [ "response-template" ], "jsonBody": { "name": "frank", "phoneNo": "{{request.path.[3]}}", "remaining": "0", "chargePlanList": [ { "priority": "2", "type": "fixedTime" }, { "priority": "1", "type": "familyMember" } ] } } } ================================================ FILE: cola-samples/charge/src/test/resources/fixture/wiremock/stub_wire_mock_basic.json ================================================ { "request": { "urlPathPattern": "/v1/wiremock/basic", "method": "GET" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": { "request_id": "f7f9e747-f073-4ea8-8360-b42fc754a049", "hyper_switch": { "id": "switch1", "name": "1520-001", "device_model": "1520", "role": "tor", "mgmt_ip": "192.168.0.1", "rack_code": "kw14b2-1k-01-08", "sn": "21980119523GP8000745", "node_id": "node1", "xpod_id": "pod1", "op_status": "online", "created_at": "2024-02-04 15:11:13", "updated_at": "2020-02-04 15:11:13", "type": "l1" } } } } ================================================ FILE: cola-samples/charge/src/test/resources/logback-test.xml ================================================ %date{HH:mm:ss} %highlight(%-5level) [%blue(%t)] %yellow(%C{35}): %msg%n%throwable utf8 ================================================ FILE: cola-samples/craftsman/craftsman-adapter/pom.xml ================================================ 4.0.0 com.alibaba.craftsman craftsman.all 1.0.0-SNAPSHOT ../pom.xml craftsman-adapter jar craftsman-adapter com.alibaba.craftsman craftsman-app org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-samples/craftsman/craftsman-adapter/src/main/java/com/alibaba/craftsman/web/MetricsController.java ================================================ package com.alibaba.craftsman.web; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.api.MetricsServiceI; import com.alibaba.craftsman.dto.ATAMetricAddCmd; import com.alibaba.craftsman.dto.ATAMetricQry; import com.alibaba.craftsman.dto.clientobject.ATAMetricCO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController public class MetricsController { @Autowired private MetricsServiceI metricsService; @GetMapping(value = "/metrics/ata") public MultiResponse listATAMetrics(@RequestParam String ownerId){ ATAMetricQry ataMetricQry = new ATAMetricQry(); ataMetricQry.setOwnerId(ownerId); return metricsService.listATAMetrics(ataMetricQry); } @PostMapping(value = "/metrics/ata") public Response addATAMetric(@RequestBody ATAMetricAddCmd ataMetricAddCmd){ return metricsService.addATAMetric(ataMetricAddCmd); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/pom.xml ================================================ 4.0.0 com.alibaba.craftsman craftsman.all 1.0.0-SNAPSHOT ../pom.xml craftsman-app jar craftsman-app com.alibaba.craftsman craftsman-client com.alibaba.craftsman craftsman-infrastructure com.alibaba.cola cola-component-catchlog-starter org.hibernate.validator hibernate-validator javax.el javax.el-api org.glassfish jakarta.el ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/ATAMetricAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.metrics.techinfluence.ATAMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.ATAMetricItem; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.ATAMetricAddCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * ATAMetricAddCmdExe * * @author Frank Zhang * @date 2019-03-01 11:42 AM */ @Component public class ATAMetricAddCmdExe{ @Autowired private MetricGateway metricGateway; public Response execute(ATAMetricAddCmd cmd) { ATAMetricItem ataMetricItem = new ATAMetricItem(); BeanUtils.copyProperties(cmd.getAtaMetricCO(), ataMetricItem); ataMetricItem.setSubMetric(new ATAMetric(new InfluenceMetric(new UserProfile(cmd.getAtaMetricCO().getOwnerId())))); metricGateway.save(ataMetricItem); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/CodeReviewMetricAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.metrics.techcontribution.CodeReviewMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.CodeReviewMetricItem; import com.alibaba.craftsman.domain.metrics.techcontribution.ContributionMetric; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.CodeReviewMetricAddCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * CodeReviewMetricAddCmdExe * * @author Frank Zhang * @date 2019-03-04 11:14 AM */ @Component public class CodeReviewMetricAddCmdExe{ @Autowired private MetricGateway metricGateway; public Response execute(CodeReviewMetricAddCmd cmd) { CodeReviewMetricItem codeReviewMetricItem = new CodeReviewMetricItem(); BeanUtils.copyProperties(cmd, codeReviewMetricItem); codeReviewMetricItem.setSubMetric(new CodeReviewMetric(new ContributionMetric(new UserProfile(cmd.getOwnerId())))); metricGateway.save(codeReviewMetricItem); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/MetricDeleteCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.gateway.MetricGateway; import com.alibaba.craftsman.dto.MetricDeleteCmd; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * MetricDeleteCmdExe * * @author Frank Zhang * @date 2019-03-04 3:01 PM */ @Component public class MetricDeleteCmdExe{ @Resource private MetricGateway metricGateway; public Response execute(MetricDeleteCmd cmd) { metricGateway.delete(cmd.getMetricId(), cmd.getOperater()); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/MiscMetricAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.metrics.techcontribution.ContributionMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.MiscMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.MiscMetricItem; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.MiscMetricAddCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * MiscMetricAddCmdExe * * @author Frank Zhang * @date 2019-03-04 11:15 AM */ @Component public class MiscMetricAddCmdExe{ @Resource private MetricGateway metricGateway; public Response execute(MiscMetricAddCmd cmd) { MiscMetricItem miscMetricItem = new MiscMetricItem(); BeanUtils.copyProperties(cmd.getMiscMetricCO(), miscMetricItem); miscMetricItem.setSubMetric(new MiscMetric(new ContributionMetric(new UserProfile(cmd.getMiscMetricCO().getOwnerId())))); metricGateway.save(miscMetricItem); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/PaperMetricAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.PaperMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.PaperMetricItem; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.PaperMetricAddCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * PaperMetricAddCmdExe * * @author Frank Zhang * @date 2019-03-03 11:41 AM */ @Component public class PaperMetricAddCmdExe { @Autowired private MetricGateway metricGateway; public Response execute(PaperMetricAddCmd cmd) { PaperMetricItem paperMetricItem = new PaperMetricItem(); BeanUtils.copyProperties(cmd.getPaperMetricCO(), paperMetricItem); paperMetricItem.setSubMetric(new PaperMetric(new InfluenceMetric(new UserProfile(cmd.getPaperMetricCO().getOwnerId())))); paperMetricItem.setMetricOwner(new UserProfile(cmd.getPaperMetricCO().getOwnerId())); metricGateway.save(paperMetricItem); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/PatentMetricAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.metrics.techinfluence.AuthorType; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.PatentMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.PatentMetricItem; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.PatentMetricAddCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * PatentMetricAddCmdExe * * @author Frank Zhang * @date 2019-03-03 11:41 AM */ @Component public class PatentMetricAddCmdExe{ @Resource private MetricGateway metricGateway; public Response execute(PatentMetricAddCmd cmd) { PatentMetricItem patentMetricItem = new PatentMetricItem(); BeanUtils.copyProperties(cmd.getPatentMetricCO(), patentMetricItem); patentMetricItem.setSubMetric(new PatentMetric(new InfluenceMetric(new UserProfile(cmd.getPatentMetricCO().getOwnerId())))); patentMetricItem.setAuthorType(AuthorType.valueOf(cmd.getPatentMetricCO().getAuthorType())); metricGateway.save(patentMetricItem); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/RefactoringMetricAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.metrics.techcontribution.ContributionMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.RefactoringLevel; import com.alibaba.craftsman.domain.metrics.techcontribution.RefactoringMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.RefactoringMetricItem; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.RefactoringMetricAddCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * RefactoringMetricAddCmdExe * * @author Frank Zhang * @date 2019-03-04 11:15 AM */ @Component public class RefactoringMetricAddCmdExe{ @Autowired private MetricGateway metricGateway; public Response execute(RefactoringMetricAddCmd cmd) { RefactoringMetricItem refactoringMetricItem = new RefactoringMetricItem(); BeanUtils.copyProperties(cmd.getRefactoringMetricCO(), refactoringMetricItem); refactoringMetricItem.setSubMetric(new RefactoringMetric(new ContributionMetric(new UserProfile(cmd.getRefactoringMetricCO().getOwnerId())))); refactoringMetricItem.setRefactoringLevel(RefactoringLevel.valueOf(cmd.getRefactoringMetricCO().getRefactoringLevel())); metricGateway.save(refactoringMetricItem); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/RefreshScoreCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.cola.exception.Assert; import com.alibaba.craftsman.domain.metrics.SubMetric; import com.alibaba.craftsman.domain.metrics.appquality.AppMetric; import com.alibaba.craftsman.domain.metrics.appquality.AppQualityMetric; import com.alibaba.craftsman.domain.metrics.devquality.BugMetric; import com.alibaba.craftsman.domain.metrics.devquality.DevQualityMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.ContributionMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.RefreshScoreCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import com.alibaba.craftsman.domain.gateway.UserProfileGateway; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; @Component public class RefreshScoreCmdExe{ @Resource private UserProfileGateway userProfileGateway; @Resource private MetricGateway metricGateway; public Response execute(RefreshScoreCmd cmd) { UserProfile userProfile = getUserProfile(cmd); calculateScore(userProfile); update(userProfile); return Response.buildSuccess(); } private UserProfile getUserProfile(RefreshScoreCmd cmd) { UserProfile userProfile = userProfileGateway.getByUserId(cmd.getUserId()); Assert.notNull(userProfile, "There is no User Profile for "+cmd.getUserId()+" to update"); return userProfile; } private void calculateScore(UserProfile userProfile) { loadInfluenceMetric(userProfile); loadContributionMetrics(userProfile); loadDevQualityMetrics(userProfile); loadAppQualityMetrics(userProfile); userProfile.calculateScore(); } private void loadAppQualityMetrics(UserProfile userProfile) { AppQualityMetric appQualityMetric = new AppQualityMetric(userProfile); AppMetric appMetric = metricGateway.getAppMetric(userProfile.getUserId()); appMetric.setParent(appQualityMetric); } private void loadDevQualityMetrics(UserProfile userProfile) { DevQualityMetric devQualityMetric = new DevQualityMetric(userProfile); BugMetric bugMetric = metricGateway.getBugMetric(userProfile.getUserId()); bugMetric.setParent(devQualityMetric); } private void loadContributionMetrics(UserProfile userProfile) { ContributionMetric contributionMetric = new ContributionMetric(userProfile); List subMetricList = metricGateway.listByTechContribution(userProfile.getUserId()); subMetricList.forEach(subMetric -> subMetric.setParent(contributionMetric)); } private void loadInfluenceMetric(UserProfile userProfile) { InfluenceMetric influenceMetric = new InfluenceMetric(userProfile); List subMetricList = metricGateway.listByTechInfluence(userProfile.getUserId()); subMetricList.forEach(subMetric -> subMetric.setParent(influenceMetric)); } private void update(UserProfile userProfile) { userProfileGateway.update(userProfile); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/SharingMetricAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.SharingMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.SharingMetricItem; import com.alibaba.craftsman.domain.metrics.techinfluence.SharingScope; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.SharingMetricAddCmd; import com.alibaba.craftsman.domain.gateway.MetricGateway; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * SharingMetricAddCmdExe * * @author Frank Zhang * @date 2019-03-02 5:00 PM */ @Component public class SharingMetricAddCmdExe{ @Resource private MetricGateway metricGateway; public Response execute(SharingMetricAddCmd cmd) { SharingMetricItem sharingMetricItem = new SharingMetricItem(); BeanUtils.copyProperties(cmd.getSharingMetricCO(), sharingMetricItem); sharingMetricItem.setSubMetric(new SharingMetric(new InfluenceMetric(new UserProfile(cmd.getSharingMetricCO().getOwnerId())))); sharingMetricItem.setSharingScope(SharingScope.valueOf(cmd.getSharingMetricCO().getSharingScope())); metricGateway.save(sharingMetricItem); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/UserProfileAddCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.convertor.UserProfileConvertor; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.UserProfileAddCmd; import com.alibaba.craftsman.domain.gateway.UserProfileGateway; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * UserProfileAddCmdExe * * @author Frank Zhang * @date 2019-02-28 6:25 PM */ @Component public class UserProfileAddCmdExe{ @Resource private UserProfileGateway userProfileGateway; public Response execute(UserProfileAddCmd cmd) { UserProfile userProfile = UserProfileConvertor.toEntity(cmd.getUserProfileCO()); userProfileGateway.create(userProfile); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/UserProfileUpdateCmdExe.java ================================================ package com.alibaba.craftsman.command; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.convertor.UserProfileConvertor; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.UserProfileUpdateCmd; import com.alibaba.craftsman.domain.gateway.UserProfileGateway; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class UserProfileUpdateCmdExe{ @Resource private UserProfileGateway userProfileGateway; public Response execute(UserProfileUpdateCmd cmd) { UserProfile userProfile = UserProfileConvertor.toEntity(cmd.getUserProfileCO()); userProfileGateway.update(userProfile); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/package-info.java ================================================ /** * This package contains CommandExecutors which are used to process Command Request. * * @author fulan.zjf */ package com.alibaba.craftsman.command; ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/query/ATAMetricQryExe.java ================================================ package com.alibaba.craftsman.command.query; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.craftsman.domain.metrics.SubMetricType; import com.alibaba.craftsman.dto.ATAMetricQry; import com.alibaba.craftsman.dto.clientobject.ATAMetricCO; import com.alibaba.craftsman.gatewayimpl.database.MetricMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO; import com.alibaba.fastjson.JSON; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Component public class ATAMetricQryExe{ @Resource private MetricMapper metricMapper; public MultiResponse execute(ATAMetricQry cmd) { List metricDOList = metricMapper.listBySubMetric(cmd.getOwnerId(), SubMetricType.ATA.getMetricSubTypeCode()); List ataMetricCOList = new ArrayList<>(); metricDOList.forEach(metricDO -> { ATAMetricCO ataMetricCO = JSON.parseObject(metricDO.getMetricItem(), ATAMetricCO.class); ataMetricCO.setOwnerId(metricDO.getUserId()); ataMetricCOList.add(ataMetricCO); }); return MultiResponse.of(ataMetricCOList); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/query/UserProfileGetQryExe.java ================================================ package com.alibaba.craftsman.command.query; import com.alibaba.cola.dto.SingleResponse; import com.alibaba.craftsman.dto.UserProfileGetQry; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; import com.alibaba.craftsman.gatewayimpl.database.UserProfileMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class UserProfileGetQryExe { @Resource private UserProfileMapper userProfileMapper; public SingleResponse execute(UserProfileGetQry qry) { UserProfileDO userProfileDO = userProfileMapper.getByUserId(qry.getUserId()); UserProfileCO userProfileCO = new UserProfileCO(); BeanUtils.copyProperties(userProfileDO, userProfileCO); return SingleResponse.of(userProfileCO); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/query/UserProfileListQryExe.java ================================================ package com.alibaba.craftsman.command.query; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.craftsman.dto.UserProfileListQry; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; import com.alibaba.craftsman.gatewayimpl.database.UserProfileMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Component public class UserProfileListQryExe{ @Resource private UserProfileMapper userProfileMapper; public MultiResponse execute(UserProfileListQry qry) { List userProfileDOList = userProfileMapper.listByDep(qry.getDep()); List userProfileCOList = new ArrayList<>(); userProfileDOList.forEach(userDO -> { UserProfileCO userProfileCO = new UserProfileCO(); BeanUtils.copyProperties(userDO, userProfileCO); userProfileCOList.add(userProfileCO); }); return MultiResponse.of(userProfileCOList); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/command/query/package-info.java ================================================ /** * This package contains QueryExecutors which are used to process Query Request. * * @author fulan.zjf */ package com.alibaba.craftsman.command.query; ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/event/handler/MetricItemCreatedHandler.java ================================================ package com.alibaba.craftsman.event.handler; import com.alibaba.cola.catchlog.CatchAndLog; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.api.UserProfileServiceI; import com.alibaba.craftsman.dto.RefreshScoreCmd; import com.alibaba.craftsman.dto.domainevent.MetricItemCreatedEvent; import org.springframework.beans.factory.annotation.Autowired; @CatchAndLog public class MetricItemCreatedHandler { @Autowired private UserProfileServiceI userProfileService; public Response execute(MetricItemCreatedEvent event) { RefreshScoreCmd cmd = new RefreshScoreCmd(event.getUserId()); userProfileService.refreshScore(cmd); return Response.buildSuccess(); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/service/MetricsServiceImpl.java ================================================ package com.alibaba.craftsman.service; import com.alibaba.cola.catchlog.CatchAndLog; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.api.MetricsServiceI; import com.alibaba.craftsman.command.*; import com.alibaba.craftsman.command.query.ATAMetricQryExe; import com.alibaba.craftsman.dto.*; import com.alibaba.craftsman.dto.clientobject.ATAMetricCO; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * MetricsServiceImpl * * @author Frank Zhang * @date 2019-03-01 11:41 AM */ @Service @CatchAndLog public class MetricsServiceImpl implements MetricsServiceI{ @Resource private ATAMetricAddCmdExe ataMetricAddCmdExe; @Resource private SharingMetricAddCmdExe sharingMetricAddCmdExe; @Resource private PatentMetricAddCmdExe patentMetricAddCmdExe; @Resource private PaperMetricAddCmdExe paperMetricAddCmdExe; @Resource private RefactoringMetricAddCmdExe refactoringMetricAddCmdExe; @Resource private MiscMetricAddCmdExe miscMetricAddCmdExe; @Resource private CodeReviewMetricAddCmdExe codeReviewMetricAddCmdExe; @Resource private MetricDeleteCmdExe metricDeleteCmdExe; @Resource private ATAMetricQryExe ataMetricQryExe; @Override public Response addATAMetric(ATAMetricAddCmd cmd) { return ataMetricAddCmdExe.execute(cmd); } @Override public Response addSharingMetric(SharingMetricAddCmd cmd) { return sharingMetricAddCmdExe.execute(cmd); } @Override public Response addPatentMetric(PatentMetricAddCmd cmd) { return patentMetricAddCmdExe.execute(cmd); } @Override public Response addPaperMetric(PaperMetricAddCmd cmd) { return paperMetricAddCmdExe.execute(cmd); } @Override public Response addRefactoringMetric(RefactoringMetricAddCmd cmd) { return refactoringMetricAddCmdExe.execute(cmd); } @Override public Response addMiscMetric(MiscMetricAddCmd cmd) { return miscMetricAddCmdExe.execute(cmd); } @Override public Response addCodeReviewMetric(CodeReviewMetricAddCmd cmd) { return codeReviewMetricAddCmdExe.execute(cmd); } @Override public Response deleteMetric(MetricDeleteCmd cmd) { return metricDeleteCmdExe.execute(cmd); } @Override public MultiResponse listATAMetrics(ATAMetricQry ataMetricQry) { return ataMetricQryExe.execute(ataMetricQry); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/service/UserProfileServiceImpl.java ================================================ package com.alibaba.craftsman.service; import com.alibaba.cola.catchlog.CatchAndLog; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import com.alibaba.cola.dto.SingleResponse; import com.alibaba.craftsman.api.UserProfileServiceI; import com.alibaba.craftsman.command.RefreshScoreCmdExe; import com.alibaba.craftsman.command.UserProfileAddCmdExe; import com.alibaba.craftsman.command.UserProfileUpdateCmdExe; import com.alibaba.craftsman.command.query.UserProfileGetQryExe; import com.alibaba.craftsman.command.query.UserProfileListQryExe; import com.alibaba.craftsman.dto.*; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * UserProfileServiceImpl * * @author Frank Zhang * @date 2019-02-28 6:22 PM */ @Service @CatchAndLog public class UserProfileServiceImpl implements UserProfileServiceI{ @Resource private UserProfileAddCmdExe userProfileAddCmdExe; @Resource private UserProfileUpdateCmdExe userProfileUpdateCmdExe; @Resource private RefreshScoreCmdExe refreshScoreCmdExe; @Resource private UserProfileGetQryExe userProfileGetQryExe; @Resource private UserProfileListQryExe userProfileListQryExe; @Override public Response addUserProfile(UserProfileAddCmd userProfileAddCmd) { return userProfileAddCmdExe.execute(userProfileAddCmd); } @Override public Response updateUserProfile(UserProfileUpdateCmd cmd) { return userProfileUpdateCmdExe.execute(cmd); } @Override public Response refreshScore(RefreshScoreCmd cmd) { return refreshScoreCmdExe.execute(cmd); } @Override public SingleResponse getUserProfileBy(UserProfileGetQry qry) { return userProfileGetQryExe.execute(qry); } @Override public MultiResponse listUserProfileBy(UserProfileListQry qry) { return userProfileListQryExe.execute(qry); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/main/java/com/alibaba/craftsman/service/package-info.java ================================================ /** * Service is the facade of application API * * @author fulan.zjf */ package com.alibaba.craftsman.service; ================================================ FILE: cola-samples/craftsman/craftsman-app/src/test/java/com/alibaba/craftsman/app/ContextInterceptorTest.java ================================================ package com.alibaba.craftsman.app; import com.alibaba.craftsman.dto.UserProfileAddCmd; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; import org.junit.Test; /** * ContextInterceptorTest 单元测试 * * @author Frank Zhang * @date 2019-03-01 9:38 AM */ public class ContextInterceptorTest { @Test public void testNoOperatorContext(){ UserProfileAddCmd userProfileAddCmd = new UserProfileAddCmd(); userProfileAddCmd.setUserProfileCO(new UserProfileCO()); // ContextInterceptor contextInterceptor = new ContextInterceptor(); // contextInterceptor.preIntercept(userProfileAddCmd); } @Test public void testOperatorContext(){ UserProfileAddCmd userProfileAddCmd = new UserProfileAddCmd(); userProfileAddCmd.setUserProfileCO(new UserProfileCO()); userProfileAddCmd.setOperater("Frank"); // ContextInterceptor contextInterceptor = new ContextInterceptor(); // contextInterceptor.preIntercept(userProfileAddCmd); } } ================================================ FILE: cola-samples/craftsman/craftsman-app/src/test/resources/logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n utf8 ================================================ FILE: cola-samples/craftsman/craftsman-client/pom.xml ================================================ 4.0.0 com.alibaba.craftsman craftsman.all 1.0.0-SNAPSHOT ../pom.xml craftsman-client jar craftsman-client com.alibaba.cola cola-component-dto javax.validation validation-api ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/api/MetricsServiceI.java ================================================ package com.alibaba.craftsman.api; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import com.alibaba.craftsman.dto.*; import com.alibaba.craftsman.dto.clientobject.ATAMetricCO; /** * MetricsServiceI * * @author Frank Zhang * @date 2019-03-01 10:06 AM */ public interface MetricsServiceI { Response addATAMetric(ATAMetricAddCmd cmd); Response addSharingMetric(SharingMetricAddCmd cmd); Response addPatentMetric(PatentMetricAddCmd cmd); Response addPaperMetric(PaperMetricAddCmd cmd); Response addRefactoringMetric(RefactoringMetricAddCmd cmd); Response addMiscMetric(MiscMetricAddCmd cmd); Response addCodeReviewMetric(CodeReviewMetricAddCmd cmd); Response deleteMetric(MetricDeleteCmd cmd); MultiResponse listATAMetrics(ATAMetricQry ataMetricQry); } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/api/UserProfileServiceI.java ================================================ package com.alibaba.craftsman.api; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; import com.alibaba.cola.dto.SingleResponse; import com.alibaba.craftsman.dto.*; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; /** * UserProfileServiceI * * @author Frank Zhang * @date 2019-02-28 6:15 PM */ public interface UserProfileServiceI { Response addUserProfile(UserProfileAddCmd cmd); Response updateUserProfile(UserProfileUpdateCmd cmd); Response refreshScore(RefreshScoreCmd cmd); SingleResponse getUserProfileBy(UserProfileGetQry qry); MultiResponse listUserProfileBy(UserProfileListQry qry); } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/context/UserContext.java ================================================ package com.alibaba.craftsman.context; import lombok.Data; /** * UserContext * * @author Frank Zhang * @date 2019-02-28 7:08 PM */ @Data public class UserContext { private String operator; private String loginUserId; private String loginUserName; private String loginUserRole; private String loginUserPrivilege; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/ATAMetricAddCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.ATAMetricCO; import lombok.Data; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; /** * ATAMetricAddCmd * * @author Frank Zhang * @date 2019-03-01 10:12 AM */ @Data public class ATAMetricAddCmd extends CommonCommand{ @NotNull private ATAMetricCO ataMetricCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/ATAMetricQry.java ================================================ package com.alibaba.craftsman.dto; import lombok.Data; @Data public class ATAMetricQry extends CommonCommand { public String ownerId; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/CodeReviewMetricAddCmd.java ================================================ package com.alibaba.craftsman.dto; import lombok.Data; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Positive; /** * CodeReviewMetricAddCmd * * @author Frank Zhang * @date 2019-03-01 10:09 AM */ @Data public class CodeReviewMetricAddCmd extends CommonCommand{ @NotEmpty private String ownerId; @NotEmpty private String reviewId; /** * 评论数 */ @Positive private int noteCount; /** * 文档链接 */ private String reviewDocLink; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/CommonCommand.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.cola.dto.Command; import lombok.Data; /** * 整个应用通用的Command * * @author Frank Zhang * @date 2019-02-28 7:18 PM */ public class CommonCommand extends Command{ private String operater; private boolean needsOperator; public String getOperater() { return this.operater; } public void setOperater(String operater) { this.operater = operater; needsOperator = true; } public boolean isNeedsOperator(){ return needsOperator; } } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/MetricDeleteCmd.java ================================================ package com.alibaba.craftsman.dto; import lombok.Data; /** * MetricDeleteCmd * * @author Frank Zhang * @date 2019-03-01 10:11 AM */ @Data public class MetricDeleteCmd extends CommonCommand{ /** * Metric ID */ private String metricId; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/MiscMetricAddCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.MiscMetricCO; import com.alibaba.craftsman.dto.clientobject.PatentMetricCO; import lombok.Data; import javax.validation.constraints.NotNull; /** * MiscMetricAddCmd * * @author Frank Zhang * @date 2019-03-04 11:04 AM */ @Data public class MiscMetricAddCmd extends CommonCommand{ @NotNull private MiscMetricCO miscMetricCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/PaperMetricAddCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.PaperMetricCO; import com.alibaba.craftsman.dto.clientobject.PatentMetricCO; import lombok.Data; import javax.validation.constraints.NotNull; /** * PaperMetricAddCmd * * @author Frank Zhang * @date 2019-03-03 11:38 AM */ @Data public class PaperMetricAddCmd extends CommonCommand{ @NotNull private PaperMetricCO paperMetricCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/PatentMetricAddCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.ATAMetricCO; import com.alibaba.craftsman.dto.clientobject.PatentMetricCO; import lombok.Data; import javax.validation.constraints.NotNull; /** * PatentMetricAddCmd * * @author Frank Zhang * @date 2019-03-03 11:37 AM */ @Data public class PatentMetricAddCmd extends CommonCommand{ @NotNull private PatentMetricCO patentMetricCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/RefactoringMetricAddCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.MiscMetricCO; import com.alibaba.craftsman.dto.clientobject.RefactoringMetricCO; import lombok.Data; import javax.validation.constraints.NotNull; /** * RefactoringMetricAddCmd * * @author Frank Zhang * @date 2019-03-04 11:04 AM */ @Data public class RefactoringMetricAddCmd extends CommonCommand{ @NotNull private RefactoringMetricCO refactoringMetricCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/RefreshScoreCmd.java ================================================ package com.alibaba.craftsman.dto; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class RefreshScoreCmd extends CommonCommand{ private String userId; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/SharingMetricAddCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.SharingMetricCO; import lombok.Data; import javax.validation.constraints.NotNull; /** * SharingMetricAddCmd * * @author Frank Zhang * @date 2019-03-01 10:12 AM */ @Data public class SharingMetricAddCmd extends CommonCommand{ @NotNull private SharingMetricCO sharingMetricCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/UserProfileAddCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; import lombok.Data; import javax.validation.constraints.NotNull; /** * UserProfileAddCmd * * @author Frank Zhang * @date 2019-02-28 6:20 PM */ @Data public class UserProfileAddCmd extends CommonCommand { @NotNull private UserProfileCO userProfileCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/UserProfileGetQry.java ================================================ package com.alibaba.craftsman.dto; import lombok.Data; @Data public class UserProfileGetQry extends CommonCommand { private String userId; private String id; public UserProfileGetQry(){ } } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/UserProfileListQry.java ================================================ package com.alibaba.craftsman.dto; import lombok.Data; @Data public class UserProfileListQry extends CommonCommand { private String dep; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/UserProfileUpdateCmd.java ================================================ package com.alibaba.craftsman.dto; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; import lombok.Data; import javax.validation.constraints.NotNull; @Data public class UserProfileUpdateCmd extends CommonCommand { @NotNull private UserProfileCO userProfileCO; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/ATAMetricCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import com.alibaba.cola.dto.ClientObject; import lombok.Data; import javax.validation.constraints.NotEmpty; /** * ATAMetricCO * * @author Frank Zhang * @date 2019-03-01 5:39 PM */ @Data public class ATAMetricCO extends AbstractMetricCO { @NotEmpty private String title;//文章标题 private String url;//文章链接 private long thumbsUpCount;//点赞数 private long hitCount;//点击数 private long commentCount;//评论数 private long favoriteCount;//收藏数 } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/AbstractMetricCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import com.alibaba.cola.dto.ClientObject; import lombok.Data; import javax.validation.constraints.NotEmpty; /** * AbstractMetricCO * * @author Frank Zhang * @date 2019-03-04 11:32 AM */ @Data public abstract class AbstractMetricCO extends ClientObject{ /** * The ownerId of this Metric Item */ @NotEmpty private String ownerId; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/MiscMetricCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import com.alibaba.cola.dto.ClientObject; import lombok.Data; import javax.validation.constraints.NotEmpty; /** * MiscMetricCO * * @author Frank Zhang * @date 2019-03-04 10:58 AM */ @Data public class MiscMetricCO extends AbstractMetricCO { /** * 名称 */ @NotEmpty private String name; /** * 内容 */ @NotEmpty private String content; /** * 文档链接 */ private String docUrl; /** * 代码链接 */ private String codeUrl; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/PaperMetricCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import lombok.Data; import javax.validation.constraints.NotEmpty; /** * PaperMetricCO * * @author Frank Zhang * @date 2019-03-03 11:16 AM */ @Data public class PaperMetricCO extends AbstractMetricCO{ @NotEmpty private String paperName; private String paperDesc; private String magazine; private String paperLink; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/PatentMetricCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import com.alibaba.cola.dto.ClientObject; import lombok.Data; import javax.validation.constraints.NotEmpty; /** * PatentMetricCO * * @author Frank Zhang * @date 2019-03-03 11:16 AM */ @Data public class PatentMetricCO extends AbstractMetricCO { public static final String FIRST_AUTHOR_TYPE = "FIRST_AUTHOR"; public static final String OTHER_AUTHOR_TYPE = "OTHER_AUTHOR"; @NotEmpty private String patentName; private String patentDesc; private String patentNo; private String patentUrl; private String authorType; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/RefactoringMetricCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import com.alibaba.cola.dto.ClientObject; import lombok.Data; /** * RefactoringMetricCO * * @author Frank Zhang * @date 2019-03-04 10:58 AM */ @Data public class RefactoringMetricCO extends AbstractMetricCO { public static final String METHOD_LEVEL = "METHOD"; public static final String MODULE_LEVEL = "MODULE"; public static final String PROJECT_LEVEL = "PROJECT"; /** * 名称 */ private String name; /** * 内容 */ private String content; /** * 文档链接 */ private String docUrl; /** * 代码链接 */ private String codeUrl; /** * 重构的范围 */ private String refactoringLevel; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/SharingMetricCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import com.alibaba.cola.dto.ClientObject; import lombok.Data; /** * SharingMetricCO * * @author Frank Zhang * @date 2019-03-02 4:55 PM */ @Data public class SharingMetricCO extends AbstractMetricCO { public final static String TEAM_SCOPE = "TEAM"; public final static String BU_SCOPE = "BU"; public final static String ALIBABA_SCOPE = "ALIBABA"; public final static String COMMUNITY_SCOPE = "COMMUNITY"; /** * 分享标题 */ private String sharingName; /** * 分享范围 */ private String sharingScope; /** * 分享日期 */ private String sharingDate; /** * 分享文档链接 */ private String sharingLink; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/clientobject/UserProfileCO.java ================================================ package com.alibaba.craftsman.dto.clientobject; import com.alibaba.cola.dto.ClientObject; import lombok.Data; import javax.validation.constraints.NotEmpty; @Data public class UserProfileCO extends ClientObject { public final static String IS_MANAGER = "y"; public final static String IS_NOT_MANAGER = "n"; public final static String DEV_ROLE = "DEV"; public final static String QA_ROLE = "QA"; public final static String OTHER_ROLE = "OTHER"; @NotEmpty private String userId; private String userName; private String dep; private String role; private String isManager; /** * 综合得分 */ private double totalScore; /** * 代码质量分 */ private double appQualityScore; /** * 技术影响力分 */ private double techInfluenceScore; /** * 技术贡献分 */ private double techContributionScore; /** * 开发质量分 */ private double devQualityScore; /** * checkin代码量 */ private double checkinCodeQuantity; } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/domainevent/CustomerCreatedEvent.java ================================================ package com.alibaba.craftsman.dto.domainevent; /** * CustomerCreatedEvent * * @author Frank Zhang * @date 2019-01-04 10:32 AM */ public class CustomerCreatedEvent { private String customerId; public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } } ================================================ FILE: cola-samples/craftsman/craftsman-client/src/main/java/com/alibaba/craftsman/dto/domainevent/MetricItemCreatedEvent.java ================================================ package com.alibaba.craftsman.dto.domainevent; import lombok.Data; @Data public class MetricItemCreatedEvent { private String id; private String userId; private String mainMetricType; } ================================================ FILE: cola-samples/craftsman/craftsman-domain/pom.xml ================================================ 4.0.0 com.alibaba.craftsman craftsman.all 1.0.0-SNAPSHOT ../pom.xml craftsman-domain jar craftsman-domain com.alibaba.cola cola-component-dto com.alibaba.cola cola-component-exception com.alibaba.cola cola-component-domain-starter org.slf4j slf4j-api com.alibaba fastjson ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/DomainFactory.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.craftsman.domain.user.UserProfile; public class DomainFactory { public static UserProfile getUserProfile(){ return new UserProfile(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/gateway/MetricGateway.java ================================================ package com.alibaba.craftsman.domain.gateway; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.craftsman.domain.metrics.SubMetric; import com.alibaba.craftsman.domain.metrics.appquality.AppMetric; import com.alibaba.craftsman.domain.metrics.devquality.BugMetric; import java.util.List; /** * MetricGateway * * @author Frank Zhang * @date 2020-07-02 12:16 PM */ public interface MetricGateway { void save(MetricItem metricItem); List listByTechContribution(String userId); List listByTechInfluence(String userId); BugMetric getBugMetric(String userId); AppMetric getAppMetric(String userId); void delete(String id, String operator); } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/gateway/UserProfileGateway.java ================================================ package com.alibaba.craftsman.domain.gateway; import com.alibaba.craftsman.domain.user.UserProfile; /** * UserProfileGateway * * @author Frank Zhang * @date 2020-07-02 12:16 PM */ public interface UserProfileGateway { void create(UserProfile userProfile); void update(UserProfile userProfile); UserProfile getByUserId(String userId); } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/JSONPropertyFilter.java ================================================ package com.alibaba.craftsman.domain.metrics; import com.alibaba.fastjson.serializer.PropertyFilter; /** * JSONPropertyFilter * * @author Frank Zhang * @date 2019-03-02 10:53 PM */ public class JSONPropertyFilter implements PropertyFilter { public static JSONPropertyFilter singleton = new JSONPropertyFilter(); @Override public boolean apply(Object object, String name, Object value) { if(name.equalsIgnoreCase("context")){ return false; } if(name.equalsIgnoreCase("extValues")){ return false; } return true; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/MainMetric.java ================================================ package com.alibaba.craftsman.domain.metrics; import lombok.Data; import java.util.ArrayList; import java.util.List; /** * Main Metric, 一个主度量(MainMetric)是由多个子度量(SubMetric)组成的。 * * @author Frank Zhang * @date 2018-08-28 2:00 PM */ @Data public abstract class MainMetric extends Metric{ protected MainMetricType metricMainType; protected List subMetrics = new ArrayList<>(); public MainMetric(){ } public void addSubMetric(SubMetric metric){ subMetrics.add(metric); } @Override public String getName() { return metricMainType.getMetricName(); } @Override public String getCode(){ return metricMainType.getMetricCode(); } @Override public double calculateScore() { double mainMetricScore = 0; for (Metric subMetric : subMetrics) { mainMetricScore = mainMetricScore + subMetric.calculateScore() * subMetric.getWeight(); } return mainMetricScore; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/MainMetricType.java ================================================ package com.alibaba.craftsman.domain.metrics; public enum MainMetricType { APP_QUALITY("app-quality","应用质量"), TECH_INFLUENCE("tech-influence","技术影响力"), TECH_CONTRIBUTION("tech-contribution","技术贡献"), DEV_QUALITY("dev-quality","开发质量"); private String metricCode; private String metricName; private MainMetricType(String metricCode, String metricName){ this.metricCode = metricCode; this.metricName = metricName; } public String getMetricCode() { return metricCode; } public String getMetricName() { return metricName; } public static MainMetricType of(String metricCode){ if(metricCode == null){ return null; } for (MainMetricType metricMainType : MainMetricType.values()) { if(metricCode.equals(metricMainType.metricCode)){ return metricMainType; } } return null; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/Measurable.java ================================================ package com.alibaba.craftsman.domain.metrics; import java.io.Serializable; /** * Measurable * 可度量的 * @author Frank Zhang * @date 2018-07-04 1:32 PM */ public interface Measurable extends Serializable{ /** * 计算分数 * @return */ public double calculateScore(); } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/Metric.java ================================================ package com.alibaba.craftsman.domain.metrics; import com.alibaba.cola.domain.Entity; import com.alibaba.craftsman.domain.user.UserProfile; import lombok.Getter; import lombok.Setter; /** * Metric * 指标 * @author Frank Zhang * @date 2018-07-04 1:23 PM */ @Entity public abstract class Metric implements Measurable{ private double score; @Getter @Setter protected UserProfile metricOwner; public Metric(){ } public Metric(UserProfile metricOwner){ this.metricOwner = metricOwner; } /** * 度量名称,用于UI显示 * @return */ abstract public String getName(); /** * 度量Code,用于数据库存储 * @return */ abstract public String getCode(); abstract public double getWeight(); @Override public String toString(){ return this.getName() + " " + this.score; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/MetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics; import com.alibaba.cola.domain.Entity; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.annotation.JSONField; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * MetricItem * 指标项,多个指标项可以构成一个指标 * @author Frank Zhang * @date 2018-07-04 1:23 PM */ @Data @Entity @Slf4j public abstract class MetricItem implements Measurable{ /** * The metric this MetricItem belongs to */ @JSONField(serialize = false) private SubMetric subMetric; /** * The owner of this MetricItem */ @JSONField(serialize = false) private UserProfile metricOwner; public void setSubMetric(SubMetric subMetric){ this.subMetric = subMetric; this.metricOwner = subMetric.getMetricOwner(); } /** * 将度量项的转成JSON * @return */ public String toJsonString() { String jsonStr = JSON.toJSONString(this, JSONPropertyFilter.singleton); log.debug("\n From : " + this + " \n To: " + jsonStr); return jsonStr; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/SubMetric.java ================================================ package com.alibaba.craftsman.domain.metrics; import com.alibaba.craftsman.domain.user.UserProfile; import lombok.Data; import lombok.Getter; import lombok.Setter; import java.util.ArrayList; import java.util.List; /** * Sub Metric, 一个主度量(MainMetric)可以有一个或者多个子度量(SubMetric)组成的。 * * @author Frank Zhang * @date 2018-08-27 6:44 PM */ @Data public abstract class SubMetric extends Metric { protected SubMetricType subMetricType; protected MainMetric parent; @Getter private List metricItemList = new ArrayList<>(); public SubMetric(){ } public void setParent(MainMetric parent){ this.parent = parent; this.metricOwner = parent.metricOwner; parent.addSubMetric(this); } /** * 添加度量项 * @param metricItem */ public void addMetricItem(MetricItem metricItem){ metricItemList.add(metricItem); } @Override public String getName() { return subMetricType.getMetricSubTypeName(); } @Override public String getCode(){ return subMetricType.getMetricSubTypeCode(); } @Override public double calculateScore() { double subMetricScore = 0; for (MetricItem metricItem : metricItemList) { subMetricScore = subMetricScore + metricItem.calculateScore(); } return subMetricScore; } @Override public UserProfile getMetricOwner(){ return parent.getMetricOwner(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/SubMetricType.java ================================================ package com.alibaba.craftsman.domain.metrics; /** * SubMetricType * * @author Frank Zhang * @date 2018-08-27 4:54 PM */ public enum SubMetricType { LongMethod(MainMetricType.APP_QUALITY, "LongMethod","超长方法"), Cyclomatic(MainMetricType.APP_QUALITY, "Cyclomatic","圈复杂度"), Duplication(MainMetricType.APP_QUALITY, "Duplication","代码重复度"), App(MainMetricType.APP_QUALITY, "App","App应用"), ATA(MainMetricType.TECH_INFLUENCE, "ATA", "ATA文章"), Sharing(MainMetricType.TECH_INFLUENCE, "Sharing", "技术分享"), Patent(MainMetricType.TECH_INFLUENCE, "Patent", "专利"), Paper(MainMetricType.TECH_INFLUENCE, "Paper", "论文"), CodeReview(MainMetricType.TECH_CONTRIBUTION, "CodeReview", "Code Review"), Refactoring(MainMetricType.TECH_CONTRIBUTION, "Refactoring", "重构"), Misc(MainMetricType.TECH_CONTRIBUTION, "Misc", "其他贡献"), Bug(MainMetricType.DEV_QUALITY, "Bug", "提测Bug"), Fault(MainMetricType.DEV_QUALITY, "Fault", "故障"), ; //度量类型 private MainMetricType parentType; //度量项Code private String metricSubTypeCode; //度量项中文名称 private String metricSubTypeName; private SubMetricType(MainMetricType parentType, String metricSubTypeCode, String metricSubTypeName){ this.parentType = parentType; this.metricSubTypeCode = metricSubTypeCode; this.metricSubTypeName = metricSubTypeName; } public MainMetricType getParentType() { return parentType; } public String getMetricSubTypeCode() { return metricSubTypeCode; } public String getMetricSubTypeName() { return metricSubTypeName; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/appquality/AppMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.appquality; import com.alibaba.craftsman.domain.metrics.SubMetric; import com.alibaba.craftsman.domain.metrics.SubMetricType; public class AppMetric extends SubMetric { public AppMetric(){ this.subMetricType = SubMetricType.App; } @Override public double getWeight() { return metricOwner.getWeight().getUnanimousWeight(); } @Override public double calculateScore() { int appCount = super.getMetricItemList().size(); if (appCount == 0){ return 0; } double sumScore = super.calculateScore(); return sumScore/appCount; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/appquality/AppMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.appquality; import com.alibaba.craftsman.domain.metrics.MetricItem; import lombok.Data; import lombok.extern.slf4j.Slf4j; @Data @Slf4j public class AppMetricItem extends MetricItem { private String appName;//应用名称 private int cyclomaticComplexityCount;//圈复杂度超标的数目 private int duplicatedMethodCount;//重复代码的数目 private int longMethodCount;//长方法的数目 private int blockedCodeConductCount;//不符合编码标准的数目 private static final int FULL_SCORE=100; private static final int STEP_SIZE=10; private static final int STEP_MINUS_SCORE = 1; @Override public double calculateScore() { double score = FULL_SCORE; score = duductScore(score, cyclomaticComplexityCount); score = duductScore(score, duplicatedMethodCount); score = duductScore(score, longMethodCount); score = duductScore(score, blockedCodeConductCount); log.debug("Calculated App score is "+score ); return score; } private double duductScore(double score, int count) { for (int counter = STEP_SIZE; counter <= count; counter = counter + STEP_SIZE) { score = score - STEP_MINUS_SCORE; } return score; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/appquality/AppQualityMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.appquality; import com.alibaba.craftsman.domain.metrics.MainMetric; import com.alibaba.craftsman.domain.metrics.MainMetricType; import com.alibaba.craftsman.domain.metrics.devquality.BugMetric; import com.alibaba.craftsman.domain.user.UserProfile; public class AppQualityMetric extends MainMetric { private AppMetric appMetric; public AppQualityMetric(UserProfile metricOwner){ this.metricOwner = metricOwner; metricOwner.setAppQualityMetric(this); this.metricMainType = MainMetricType.APP_QUALITY; } @Override public double getWeight() { return metricOwner.getWeight().getAppQualityWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/devquality/BugMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.devquality; import com.alibaba.craftsman.domain.metrics.*; import com.alibaba.craftsman.domain.user.Role; /** * BUG数指标 */ public class BugMetric extends SubMetric { public BugMetric(){ this.subMetricType = SubMetricType.Bug; } @Override public double getWeight() { return metricOwner.getWeight().getUnanimousWeight(); } @Override public double calculateScore() { if(metricOwner.getRole() == Role.OTHER){ return 0; } return super.calculateScore(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/devquality/BugMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.devquality; import com.alibaba.craftsman.domain.metrics.MetricItem; import lombok.Data; @Data public class BugMetricItem extends MetricItem{ private int bugCount; //缺陷数量 private long checkInCodeCount; //check in的代码量 private static int DEFAULT_SCORE = 100; private static double STEP_SIZE = 1; private static double STEP_MINUS_SCORE = 5; public BugMetricItem(int bugCount, long checkInCodeCount){ this.bugCount = bugCount; this.checkInCodeCount = checkInCodeCount; } @Override public double calculateScore() { double score = DEFAULT_SCORE; //千行代码缺陷数 double bugPerThousandLinesCode = bugCount * 1000 / checkInCodeCount; for(double counter = STEP_SIZE; counter <= bugPerThousandLinesCode; counter = counter + STEP_SIZE){ score = score - STEP_MINUS_SCORE; } return score; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/devquality/DevQualityMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.devquality; import com.alibaba.craftsman.domain.metrics.MainMetric; import com.alibaba.craftsman.domain.metrics.MainMetricType; import com.alibaba.craftsman.domain.user.UserProfile; import lombok.Data; @Data public class DevQualityMetric extends MainMetric { private BugMetric bugMetric; public DevQualityMetric(UserProfile metricOwner){ this.metricOwner = metricOwner; metricOwner.setDevQualityMetric(this); this.metricMainType = MainMetricType.DEV_QUALITY; } @Override public double getWeight() { return metricOwner.getWeight().getDevQualityWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/CodeReviewMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import com.alibaba.craftsman.domain.metrics.*; /** * CodeReview指标 * @author xueliang.sxl, alisa.hsh, xiangning.lxn */ public class CodeReviewMetric extends SubMetric { public CodeReviewMetric(){ this.subMetricType = SubMetricType.CodeReview; } public CodeReviewMetric(MainMetric parent) { this.parent = parent; parent.addSubMetric(this); this.subMetricType = SubMetricType.CodeReview; } @Override public double getWeight() { return metricOwner.getWeight().getUnanimousWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/CodeReviewMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.fastjson.JSON; import lombok.Data; import java.util.HashMap; import java.util.Map; /** * CodeReview指标项 * @author xueliang.sxl, alisa.hsh, xiangning.lxn */ @Data public class CodeReviewMetricItem extends MetricItem { /** * 评审id */ private String reviewId; /** * 评论数量 */ private int noteCount; /** * 文档链接 */ private String reviewDocLink; /** * 每条评论0.1分 */ private static double CODE_REVIEW_SCORE = 0.1; public CodeReviewMetricItem(){ } public static CodeReviewMetricItem valueOf(String json){ return JSON.parseObject(json, CodeReviewMetricItem.class); } /** * 计算当前度量项分数 * @return */ @Override public double calculateScore() { return noteCount * CODE_REVIEW_SCORE; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/ContributionMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import com.alibaba.craftsman.domain.metrics.MainMetric; import com.alibaba.craftsman.domain.metrics.MainMetricType; import com.alibaba.craftsman.domain.user.UserProfile; import lombok.Data; /** * ContributionMetric * * @author Frank Zhang * @date 2018-08-27 7:06 PM */ @Data public class ContributionMetric extends MainMetric { private CodeReviewMetric codeReviewMetric; private RefactoringMetric refactoringMetric; private MiscMetric miscMetric; public ContributionMetric(UserProfile metricOwner){ this.metricOwner = metricOwner; metricOwner.setContributionMetric(this); this.metricMainType = MainMetricType.TECH_CONTRIBUTION; } @Override public double getWeight() { return metricOwner.getWeight().getTechContributionWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/MiscMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import com.alibaba.craftsman.domain.metrics.*; /** * Miscellaneous,其他度量,任何的技术亮点都可以添加 * @author frankzhang */ public class MiscMetric extends SubMetric { public MiscMetric(){ this.subMetricType = SubMetricType.Misc; } public MiscMetric(MainMetric parent) { this.parent = parent; parent.addSubMetric(this); this.subMetricType = SubMetricType.Misc; } @Override public double getWeight() { return metricOwner.getWeight().getUnanimousWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/MiscMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.Data; import java.util.HashMap; import java.util.Map; /** * 设计指标度量项 * @author xueliang.sxl, alisa.hsh, xiangning.lxn */ @Data public class MiscMetricItem extends MetricItem { /** * 名称 */ private String name; /** * 内容 */ private String content; /** * 文档链接 */ private String docUrl; /** * 代码链接 */ private String codeUrl; private static double OUTSTANDING_CONTRIBUTION_SCORE = 20; public MiscMetricItem(){ } public MiscMetricItem(String name, String content, String docUrl, String codeUrl){ this.name = name; this.codeUrl = codeUrl; this.content = content; this.docUrl = docUrl; } public static MiscMetricItem valueOf(String json){ return JSON.parseObject(json, MiscMetricItem.class); } /** * 计算当前度量项分数 * @return */ @Override public double calculateScore() { return OUTSTANDING_CONTRIBUTION_SCORE; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/RefactoringLevel.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import lombok.Getter; /** * RefactoringLevel * * @author Frank Zhang * @date 2018-09-20 3:37 PM */ public enum RefactoringLevel { METHOD(2, "方法级别的重构"), MODULE( 4, "模块级别的重构(多个方法和类的重构)"), PROJECT(10, "项目级别的重构(超过3个人日的重构项目)"); @Getter private double score; private String desc; RefactoringLevel(double score, String desc) { this.score = score; this.desc = desc; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/RefactoringMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import com.alibaba.craftsman.domain.metrics.*; /** * 重构指标 * @author xueliang.sxl, alisa.hsh, xiangning.lxn */ public class RefactoringMetric extends SubMetric { public RefactoringMetric(){ this.subMetricType = SubMetricType.Refactoring; } public RefactoringMetric(MainMetric parent) { this.parent = parent; parent.addSubMetric(this); this.subMetricType = SubMetricType.Refactoring; } @Override public double getWeight() { return metricOwner.getWeight().getUnanimousWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techcontribution/RefactoringMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.techcontribution; import com.alibaba.cola.exception.Assert; import com.alibaba.cola.exception.BizException; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.craftsman.domain.metrics.techinfluence.ATAMetricItem; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.Data; import java.util.HashMap; import java.util.Map; /** * 重构指标度量项 * @author xueliang.sxl, alisa.hsh, xiangning.lxn */ @Data public class RefactoringMetricItem extends MetricItem { /** * 名称 */ private String name; /** * 内容 */ private String content; /** * 文档链接 */ private String docUrl; /** * 代码链接 */ private String codeUrl; /** * 重构的范围 */ private RefactoringLevel refactoringLevel; public RefactoringMetricItem(){ } public static RefactoringMetricItem valueOf(String json){ return JSON.parseObject(json, RefactoringMetricItem.class); } /** * 计算当前度量项分数 * @return */ @Override public double calculateScore() { Assert.notNull(refactoringLevel, "Refactoring Level can not be null"); return refactoringLevel.getScore(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/ATAMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.*; /** * ATAMetric * ATA文章指标 * @author Frank Zhang * @date 2018-07-04 1:24 PM */ public class ATAMetric extends SubMetric { public ATAMetric(){ this.subMetricType = SubMetricType.ATA; } public ATAMetric(MainMetric parent) { this.parent = parent; parent.addSubMetric(this); this.subMetricType = SubMetricType.ATA; } @Override public double getWeight() { return parent.getMetricOwner().getWeight().getUnanimousWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/ATAMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.fastjson.JSON; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * ATAMetricItem * 线上分享,ATA文章指标项 * @author Frank Zhang * @date 2018-07-04 3:20 PM */ @Data @Slf4j public class ATAMetricItem extends MetricItem { private String authorId;//作者 private String title;//文章标题 private String url;//文章链接 private long thumbsUpCount;//点赞数 private long hitCount;//点击数 private long commentCount;//评论数 private long favoriteCount;//收藏数 private static int HIT_STEP_SIZE = 100; private static int THUMB_UPS_STEP_SIZE = 20; private static int FAVORITE_STEP_SIZE = 15; private static int COMMENT_STEP_SIZE = 3; private static double STEP_SCORE = 0.25; private static double BASIC_SCORE = 0.5; public ATAMetricItem(){ } public ATAMetricItem(String title, long thumbsUpCount, long hitCount, long favoriteCount, long commentCount) { this.title = title; this.thumbsUpCount = thumbsUpCount; this.hitCount = hitCount; this.favoriteCount = favoriteCount; this.commentCount = commentCount; } public static ATAMetricItem valueOf(String json){ return JSON.parseObject(json, ATAMetricItem.class); } @Override public double calculateScore() { log.debug("calculate score for : " + this); double score = BASIC_SCORE; score = addScoreByHitCount(score); score = addScoreByThumbsupCount(score); score = addScoreByFavoriteCount(score); score = addScoreByCommentCount(score); log.debug("calculated score is : " + score); return score; } private double addScoreByHitCount(double score) { for(int counter = HIT_STEP_SIZE; counter <= hitCount; counter = counter + HIT_STEP_SIZE){ score = score + STEP_SCORE; } return score; } private double addScoreByThumbsupCount(double score){ for(int counter = THUMB_UPS_STEP_SIZE; counter <= thumbsUpCount; counter = counter + THUMB_UPS_STEP_SIZE){ score = score + STEP_SCORE; } return score; } private double addScoreByFavoriteCount(double score){ for(int counter = FAVORITE_STEP_SIZE; counter <= favoriteCount; counter = counter + FAVORITE_STEP_SIZE){ score = score + STEP_SCORE; } return score; } private double addScoreByCommentCount(double score){ for(int counter = COMMENT_STEP_SIZE; counter <= commentCount; counter = counter + COMMENT_STEP_SIZE){ score = score + STEP_SCORE; } return score; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/AuthorType.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import lombok.Getter; /** * 论文专利作者类型枚举 * * @author Frank Zhang * @date 2018-09-20 3:28 PM */ public enum AuthorType { FIRST_AUTHOR(AuthorType.FIRST_AUTHOR_PATENT_SCORE, "专利或者论文的第一作者"), OTHER_AUTHOR(AuthorType.OTHER_AUTHOR_PATENT_SCORE, "专利或论文的其他作者"); private static final double FIRST_AUTHOR_PATENT_SCORE = 20; private static final double OTHER_AUTHOR_PATENT_SCORE = 5; @Getter private double score; private String desc; private AuthorType(double score, String desc) { this.score = score; this.desc = desc; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/InfluenceMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.MainMetric; import com.alibaba.craftsman.domain.metrics.MainMetricType; import com.alibaba.craftsman.domain.user.UserProfile; import lombok.Data; /** * InfluenceMetric * 影响力指标 * @author Frank Zhang * @date 2018-07-04 1:24 PM */ @Data public class InfluenceMetric extends MainMetric { private ATAMetric ataMetric; private PatentMetric patentMetric; private SharingMetric sharingMetric; private PaperMetric paperMetric; public InfluenceMetric(UserProfile metricOwner){ this.metricOwner = metricOwner; metricOwner.setInfluenceMetric(this); this.metricMainType = MainMetricType.TECH_INFLUENCE; } @Override public double getWeight() { return metricOwner.getWeight().getTechInfluenceWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/PaperMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.*; /** * 论文子度量 * PaperMetric * * @author Frank Zhang * @date 2018-09-20 3:26 PM */ public class PaperMetric extends SubMetric { public PaperMetric(){ this.subMetricType = SubMetricType.Paper; } public PaperMetric(MainMetric parent) { this.parent = parent; parent.addSubMetric(this); this.subMetricType = SubMetricType.Paper; } @Override public double getWeight() { return parent.getMetricOwner().getWeight().getUnanimousWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/PaperMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.Data; import java.util.HashMap; import java.util.Map; /** * PaperMetricItem * * @author Frank Zhang * @date 2018-09-20 3:26 PM */ @Data public class PaperMetricItem extends MetricItem { private String paperName; private String paperDesc; private String magazine; private String paperLink; private static final double PAPER_SCORE = 10; public PaperMetricItem(){ } public PaperMetricItem(String paperName, String paperDesc, String magazine, String paperLink) { this.paperName = paperName; this.paperDesc = paperDesc; this.magazine = magazine; this.paperLink = paperLink; } public static PaperMetricItem valueOf(String json){ return JSON.parseObject(json, PaperMetricItem.class); } @Override public double calculateScore() { return PAPER_SCORE; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/PatentMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.*; /** * 技术专利指标 * @author xueliang.sxl */ public class PatentMetric extends SubMetric { public PatentMetric(){ this.subMetricType = SubMetricType.Patent; } public PatentMetric(MainMetric parent) { this.parent = parent; parent.addSubMetric(this); this.subMetricType = SubMetricType.Patent; } @Override public double getWeight() { return parent.getMetricOwner().getWeight().getUnanimousWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/PatentMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.Data; /** * 技术创新指标项 * @author xueliang.sxl */ @Data public class PatentMetricItem extends MetricItem { private String patentName; private String patentDesc; private String patentNo; private String patentUrl; private AuthorType authorType; public PatentMetricItem(){ } public PatentMetricItem(String patentName, String patentDesc, String patentNo, String patentUrl, AuthorType authorType){ this.patentName = patentName; this.patentDesc = patentDesc; this.patentNo = patentNo; this.patentUrl = patentUrl; this.authorType = authorType; } public static PatentMetricItem valueOf(String json){ return JSON.parseObject(json, PatentMetricItem.class); } @Override public double calculateScore() { return authorType.getScore(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/SharingMetric.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.*; /** * SharingMetric * 线下技术分享指标 * @author Frank Zhang * @date 2018-07-04 1:25 PM */ public class SharingMetric extends SubMetric { public SharingMetric(){ this.subMetricType = SubMetricType.Sharing; } public SharingMetric(MainMetric parent) { this.parent = parent; parent.addSubMetric(this); this.subMetricType = SubMetricType.Sharing; } @Override public double getWeight() { return parent.getMetricOwner().getWeight().getUnanimousWeight(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/SharingMetricItem.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.fastjson.JSON; import lombok.Data; import java.util.Date; /** * SharingMetricItem * 技术线下分享指标项 * @author Frank Zhang * @date 2018-07-04 3:20 PM */ @Data public class SharingMetricItem extends MetricItem { private String sharingName; private SharingScope sharingScope; private Date sharingDate; private String sharingLink; public SharingMetricItem(){ } public SharingMetricItem(String sharingName, SharingScope sharingScope, Date sharingDate, String url) { this.sharingName = sharingName; this.sharingScope = sharingScope; this.sharingDate = sharingDate; this.sharingLink = url; } public static SharingMetricItem valueOf(String json){ return JSON.parseObject(json, SharingMetricItem.class); } @Override public double calculateScore() { return sharingScope.getScore(); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/techinfluence/SharingScope.java ================================================ package com.alibaba.craftsman.domain.metrics.techinfluence; import lombok.Getter; /** * SharingScope * 线下分享的范围,范围不同,分值不同 * @author Frank Zhang * @date 2018-07-04 3:25 PM */ public enum SharingScope { TEAM(2, "团队内分享"), BU(20, "BU内部分享"), ALIBABA(30, "集团内部分享"), COMMUNITY(40, "公众外部分享"); @Getter private double score; private String desc; private SharingScope(double score, String desc) { this.score = score; this.desc = desc; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/weight/DevWeight.java ================================================ package com.alibaba.craftsman.domain.metrics.weight; /** * 开发的总分计算权重 */ public class DevWeight extends Weight{ public static DevWeight singleton= new DevWeight(); @Override public double getAppQualityWeight() { return 20 / WEIGHT_PERCENTAGE; } @Override public double getTechInfluenceWeight() { return 30 / WEIGHT_PERCENTAGE; } @Override public double getTechContributionWeight() { return 30 / WEIGHT_PERCENTAGE; } @Override public double getDevQualityWeight() { return 20 / WEIGHT_PERCENTAGE; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/weight/OtherWeight.java ================================================ package com.alibaba.craftsman.domain.metrics.weight; /** * 非技术人员不需要考核 */ public class OtherWeight extends Weight{ public static OtherWeight singleton= new OtherWeight(); @Override public double getAppQualityWeight() { return 0; } @Override public double getTechInfluenceWeight() { return 0; } @Override public double getTechContributionWeight() { return 0; } @Override public double getDevQualityWeight() { return 0; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/weight/QAWeight.java ================================================ package com.alibaba.craftsman.domain.metrics.weight; /** * 测试的分数权重占比 */ public class QAWeight extends Weight{ public static QAWeight singleton= new QAWeight(); @Override public double getAppQualityWeight() { return 10 / WEIGHT_PERCENTAGE; } @Override public double getTechInfluenceWeight() { return 60 / WEIGHT_PERCENTAGE; } @Override public double getTechContributionWeight() { return 20 / WEIGHT_PERCENTAGE; } @Override public double getDevQualityWeight() { return 10 / WEIGHT_PERCENTAGE; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/weight/Weight.java ================================================ package com.alibaba.craftsman.domain.metrics.weight; /** * Weight 权重 * * @author Frank Zhang * @date 2018-09-13 12:13 PM */ public abstract class Weight { public static double WEIGHT_PERCENTAGE = 100; public abstract double getAppQualityWeight(); public abstract double getTechInfluenceWeight(); public abstract double getTechContributionWeight(); public abstract double getDevQualityWeight(); public double getUnanimousWeight(){ return 100/WEIGHT_PERCENTAGE; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/metrics/weight/WeightFactory.java ================================================ package com.alibaba.craftsman.domain.metrics.weight; import com.alibaba.craftsman.domain.user.Role; public class WeightFactory { public static Weight get(Role role){ if(role == Role.DEV){ return DevWeight.singleton; } if(role == Role.QA){ return QAWeight.singleton; } return OtherWeight.singleton; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/package-info.java ================================================ /** * * This is the core Domain business which should be pure and clean, have no dependency over other layers. * * @author fulan.zjf */ package com.alibaba.craftsman.domain; ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/user/Role.java ================================================ package com.alibaba.craftsman.domain.user; /** * Role Enumeration * * @author Frank Zhang * @date 2018-09-13 12:25 PM */ public enum Role { DEV("开发"), QA( "测试"), OTHER("非技术岗"); public String desc; Role(String desc){ this.desc = desc; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/main/java/com/alibaba/craftsman/domain/user/UserProfile.java ================================================ package com.alibaba.craftsman.domain.user; import com.alibaba.cola.domain.Entity; import com.alibaba.cola.exception.Assert; import com.alibaba.craftsman.domain.metrics.appquality.AppQualityMetric; import com.alibaba.craftsman.domain.metrics.devquality.DevQualityMetric; import com.alibaba.craftsman.domain.metrics.weight.Weight; import com.alibaba.craftsman.domain.metrics.techcontribution.ContributionMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; /** * 员工档案 * @author frankzhang */ @Data @NoArgsConstructor @Entity @Slf4j public class UserProfile { private String id; private String userId; private String userName; private String dep; private Role role; private String isManager; private Weight weight; private double totalScore; private double appQualityScore; private double techInfluenceScore; private double techContributionScore; private double devQualityScore; private double checkinCodeQuantity; private AppQualityMetric appQualityMetric; private InfluenceMetric influenceMetric; private ContributionMetric contributionMetric; private DevQualityMetric devQualityMetric; private static final double MAXIMUM_SCORE = 100; private static final double MINIMUM_SCORE = 0; public void calculateScore(){ calculateTechInfluenceScore(); calculateTechContributionScore(); calculateDevQualityMetric(); calculateAppQualityMetric(); calculateTotalScore(); } private void calculateAppQualityMetric() { Assert.notNull(appQualityMetric, "appQualityMetric is null, initialize it before calculating"); appQualityScore = appQualityMetric.calculateScore(); } private void calculateDevQualityMetric(){ Assert.notNull(devQualityMetric, "devQualityMetric is null, initialize it before calculating"); devQualityScore = devQualityMetric.calculateScore(); } private void calculateTechInfluenceScore(){ Assert.notNull(influenceMetric, "influenceMetric is null, initialize it before calculating"); techInfluenceScore = influenceMetric.calculateScore(); } private void calculateTechContributionScore(){ Assert.notNull(contributionMetric, "contributionMetric is null, initialize it before calculating"); techContributionScore = contributionMetric.calculateScore(); } private void calculateTotalScore(){ totalScore = round(this.techInfluenceScore) * influenceMetric.getWeight() + round(this.techContributionScore) * contributionMetric.getWeight() + round(this.devQualityScore) * devQualityMetric.getWeight() + round(this.appQualityScore) * appQualityMetric.getWeight(); } private double round(double score){ if(score > MAXIMUM_SCORE){ score = MAXIMUM_SCORE; }else if(score < MINIMUM_SCORE){ score = MINIMUM_SCORE; } return score; } public UserProfile(String userId){ this.userId = userId; } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/test/java/com/alibaba/craftsman/domain/ATAMetricTest.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.craftsman.domain.metrics.techinfluence.ATAMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.ATAMetricItem; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.user.UserProfile; import org.junit.Assert; import org.junit.Test; /** * ATAMetricTest * * @author Frank Zhang * @date 2019-02-26 2:07 PM */ public class ATAMetricTest { @Test public void testBasicScore(){ ATAMetricItem ataMetricItem = new ATAMetricItem("article",19,99,14,2) ; Assert.assertEquals(0.5, ataMetricItem.calculateScore(), 0.01); } @Test public void testNormalScore(){ ATAMetricItem ataMetricItem = new ATAMetricItem("article",20,100,15,3) ; Assert.assertEquals(1.5, ataMetricItem.calculateScore(), 0.01); } @Test public void testPopularScore(){ ATAMetricItem ataMetricItem = new ATAMetricItem("article",100, 500, 75, 15) ; Assert.assertEquals(5.5, ataMetricItem.calculateScore(), 0.01); } @Test public void testJSON(){ ATAMetricItem ataMetricItem = new ATAMetricItem(); ataMetricItem.setTitle("title"); ataMetricItem.setUrl("sharingLink"); ataMetricItem.setFavoriteCount(1000); ataMetricItem.setCommentCount(203); ataMetricItem.setSubMetric(new ATAMetric(new InfluenceMetric(new UserProfile("78492")))); String jsonStr = ataMetricItem.toJsonString(); ATAMetricItem jsonObject = ATAMetricItem.valueOf(jsonStr); Assert.assertEquals(ataMetricItem.getTitle(), jsonObject.getTitle()); Assert.assertEquals(ataMetricItem.getUrl(), jsonObject.getUrl()); Assert.assertEquals(ataMetricItem.getFavoriteCount(), jsonObject.getFavoriteCount()); Assert.assertEquals(ataMetricItem.getCommentCount(), jsonObject.getCommentCount()); Assert.assertEquals(ataMetricItem.getHitCount(), jsonObject.getHitCount()); Assert.assertEquals(ataMetricItem, jsonObject); } @Test public void testATAMetric(){ ATAMetric ataMetric = new ATAMetric(new InfluenceMetric(new UserProfile())); ataMetric.addMetricItem( new ATAMetricItem("article",19,99,14,2)); ataMetric.addMetricItem( new ATAMetricItem("article",20,100,15,3) ); ataMetric.addMetricItem( new ATAMetricItem("article",100, 500, 75, 15) ); Assert.assertEquals(7.5, ataMetric.calculateScore(), 0.01); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/test/java/com/alibaba/craftsman/domain/AppMetricTest.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.craftsman.domain.metrics.appquality.AppMetric; import com.alibaba.craftsman.domain.metrics.appquality.AppMetricItem; import org.junit.Assert; import org.junit.Test; public class AppMetricTest { @Test public void testAppMetricItem(){ AppMetricItem appMetricItem = new AppMetricItem(); appMetricItem.setAppName("app1"); appMetricItem.setCyclomaticComplexityCount(200); appMetricItem.setDuplicatedMethodCount(80); appMetricItem.setLongMethodCount(70); appMetricItem.setBlockedCodeConductCount(20); Assert.assertEquals(63, appMetricItem.calculateScore(), 0.01); } @Test public void testAppMetric(){ AppMetricItem appMetricItem1 = new AppMetricItem(); appMetricItem1.setAppName("app1"); appMetricItem1.setCyclomaticComplexityCount(200); appMetricItem1.setDuplicatedMethodCount(80); appMetricItem1.setLongMethodCount(70); appMetricItem1.setBlockedCodeConductCount(20); appMetricItem1.calculateScore(); AppMetricItem appMetricItem2 = new AppMetricItem(); appMetricItem2.setAppName("app2"); appMetricItem2.setCyclomaticComplexityCount(20); appMetricItem2.setDuplicatedMethodCount(30); appMetricItem2.setLongMethodCount(7); appMetricItem2.setBlockedCodeConductCount(5); appMetricItem2.calculateScore(); AppMetric appMetric = new AppMetric(); appMetric.addMetricItem(appMetricItem1); appMetric.addMetricItem(appMetricItem2); Assert.assertEquals(79, appMetric.calculateScore(), 0.01); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/test/java/com/alibaba/craftsman/domain/BugMetricTest.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.craftsman.domain.metrics.devquality.BugMetricItem; import org.junit.Assert; import org.junit.Test; public class BugMetricTest { @Test public void test5BugsPer1000LinesCode(){ BugMetricItem bugMetricItem = new BugMetricItem(5, 1000); Assert.assertEquals(75, bugMetricItem.calculateScore(), 0.01); } @Test public void test2BugsPer1000LinesCode(){ BugMetricItem bugMetricItem = new BugMetricItem(2, 1000); Assert.assertEquals(90, bugMetricItem.calculateScore(), 0.01); } @Test public void test5BugsPer10000LinesCode(){ BugMetricItem bugMetricItem = new BugMetricItem(5, 10000); Assert.assertEquals(100, bugMetricItem.calculateScore(), 0.01); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/test/java/com/alibaba/craftsman/domain/InfluenceMetricTest.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.craftsman.domain.metrics.techinfluence.*; import com.alibaba.craftsman.domain.metrics.weight.DevWeight; import com.alibaba.craftsman.domain.metrics.weight.QAWeight; import com.alibaba.craftsman.domain.user.UserProfile; import org.junit.Assert; import org.junit.Test; import java.util.Date; public class InfluenceMetricTest { @Test public void testDevScore(){ UserProfile userProfile = new UserProfile(); userProfile.setWeight(new DevWeight()); InfluenceMetric influenceMetric = new InfluenceMetric(userProfile); prepareSubMetrics(influenceMetric); Assert.assertEquals(124.5, influenceMetric.calculateScore(), 0.01); } @Test public void testQAScore(){ UserProfile userProfile = new UserProfile(); userProfile.setWeight(new QAWeight()); InfluenceMetric influenceMetric = new InfluenceMetric(userProfile); prepareSubMetrics(influenceMetric); Assert.assertEquals(124.5, influenceMetric.calculateScore(), 0.01); } public static void prepareSubMetrics(InfluenceMetric influenceMetric) { ATAMetric ataMetric = new ATAMetric(influenceMetric); ataMetric.addMetricItem( new ATAMetricItem("article",19,99,14,2)); ataMetric.addMetricItem( new ATAMetricItem("article",20,100,15,3) ); ataMetric.addMetricItem( new ATAMetricItem("article",100, 500, 75, 15) ); PatentMetric patentMetric = new PatentMetric(influenceMetric); patentMetric.addMetricItem(new PatentMetricItem("patentName","patentDesc","patentNo","sharingLink", AuthorType.FIRST_AUTHOR)); patentMetric.addMetricItem(new PatentMetricItem("patentName","patentDesc","patentNo","sharingLink", AuthorType.OTHER_AUTHOR)); SharingMetric sharingMetric = new SharingMetric(influenceMetric); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.TEAM, new Date(), "sharingLink")); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.BU, new Date(), "sharingLink")); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.ALIBABA, new Date(), "sharingLink")); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.COMMUNITY, new Date(), "sharingLink")); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/test/java/com/alibaba/craftsman/domain/PatentMetricTest.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.craftsman.domain.metrics.techinfluence.*; import com.alibaba.craftsman.domain.user.UserProfile; import org.junit.Assert; import org.junit.Test; /** * PatentMetricTest * * @author Frank Zhang * @date 2019-02-26 4:20 PM */ public class PatentMetricTest { @Test public void testPatentMetric(){ PatentMetric patentMetric = new PatentMetric(new InfluenceMetric(new UserProfile())); patentMetric.addMetricItem(new PatentMetricItem("patentName","patentDesc","patentNo","sharingLink", AuthorType.FIRST_AUTHOR)); patentMetric.addMetricItem(new PatentMetricItem("patentName","patentDesc","patentNo","sharingLink", AuthorType.OTHER_AUTHOR)); Assert.assertEquals(25, patentMetric.calculateScore(), 0.01); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/test/java/com/alibaba/craftsman/domain/SharingMetricTest.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.SharingMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.SharingMetricItem; import com.alibaba.craftsman.domain.metrics.techinfluence.SharingScope; import com.alibaba.craftsman.domain.user.UserProfile; import org.junit.Assert; import org.junit.Test; import java.util.Date; /** * SharingMetricTest * * @author Frank Zhang * @date 2019-02-26 4:14 PM */ public class SharingMetricTest { @Test public void testSharingMetric(){ SharingMetric sharingMetric = new SharingMetric(new InfluenceMetric(new UserProfile())); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.TEAM, new Date(), "sharingLink")); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.BU, new Date(), "sharingLink")); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.ALIBABA, new Date(), "sharingLink")); sharingMetric.addMetricItem(new SharingMetricItem("title", SharingScope.COMMUNITY, new Date(), "sharingLink")); Assert.assertEquals(92, sharingMetric.calculateScore(), 0.01); } } ================================================ FILE: cola-samples/craftsman/craftsman-domain/src/test/java/com/alibaba/craftsman/domain/UserProfileTest.java ================================================ package com.alibaba.craftsman.domain; import com.alibaba.cola.exception.BizException; import com.alibaba.craftsman.domain.metrics.appquality.AppMetric; import com.alibaba.craftsman.domain.metrics.appquality.AppMetricItem; import com.alibaba.craftsman.domain.metrics.appquality.AppQualityMetric; import com.alibaba.craftsman.domain.metrics.devquality.BugMetric; import com.alibaba.craftsman.domain.metrics.devquality.BugMetricItem; import com.alibaba.craftsman.domain.metrics.devquality.DevQualityMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.CodeReviewMetric; import com.alibaba.craftsman.domain.metrics.techcontribution.CodeReviewMetricItem; import com.alibaba.craftsman.domain.metrics.techcontribution.ContributionMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.ATAMetric; import com.alibaba.craftsman.domain.metrics.techinfluence.ATAMetricItem; import com.alibaba.craftsman.domain.metrics.techinfluence.InfluenceMetric; import com.alibaba.craftsman.domain.metrics.weight.DevWeight; import com.alibaba.craftsman.domain.metrics.weight.QAWeight; import com.alibaba.craftsman.domain.user.UserProfile; import org.junit.Assert; import org.junit.Test; /** * UserProfileTest * * @author Frank Zhang * @date 2020-08-28 2:03 PM */ public class UserProfileTest { @Test public void testCalculateScore(){ UserProfile userProfile = new UserProfile(); userProfile.setWeight(new DevWeight()); //App Quality Metric AppMetricItem appMetricItem1 = new AppMetricItem(); appMetricItem1.setAppName("app1"); appMetricItem1.setCyclomaticComplexityCount(200); appMetricItem1.setDuplicatedMethodCount(80); appMetricItem1.setLongMethodCount(70); appMetricItem1.setBlockedCodeConductCount(20); appMetricItem1.calculateScore(); AppMetricItem appMetricItem2 = new AppMetricItem(); appMetricItem2.setAppName("app2"); appMetricItem2.setCyclomaticComplexityCount(20); appMetricItem2.setDuplicatedMethodCount(30); appMetricItem2.setLongMethodCount(7); appMetricItem2.setBlockedCodeConductCount(5); appMetricItem2.calculateScore(); AppMetric appMetric = new AppMetric(); appMetric.addMetricItem(appMetricItem1); appMetric.addMetricItem(appMetricItem2); AppQualityMetric appQualityMetric = new AppQualityMetric(userProfile); appMetric.setParent(appQualityMetric); //influence Metric InfluenceMetric influenceMetric = new InfluenceMetric(userProfile); InfluenceMetricTest.prepareSubMetrics(influenceMetric); //techContribution Metric CodeReviewMetric codeReviewMetric = new CodeReviewMetric(); CodeReviewMetricItem codeReviewMetricItem = new CodeReviewMetricItem(); codeReviewMetricItem.setNoteCount(4); codeReviewMetricItem.setReviewId("12234455"); codeReviewMetric.addMetricItem(codeReviewMetricItem); ContributionMetric contributionMetric = new ContributionMetric(userProfile); //dev quality metric DevQualityMetric devQualityMetric = new DevQualityMetric(userProfile); BugMetric bugMetric = new BugMetric(); BugMetricItem bugMetricItem = new BugMetricItem(2, 1000); bugMetric.addMetricItem(bugMetricItem); devQualityMetric.setBugMetric(bugMetric); //Execution userProfile.calculateScore(); //Assertion Assert.assertEquals(45.8, userProfile.getTotalScore(), 0.01); } @Test(expected = BizException.class) public void testNPE(){ UserProfile userProfile = new UserProfile(); userProfile.setWeight(new DevWeight()); userProfile.calculateScore(); } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/pom.xml ================================================ 4.0.0 com.alibaba.craftsman craftsman.all 1.0.0-SNAPSHOT ../pom.xml craftsman-infrastructure jar craftsman-infrastructure com.alibaba.craftsman craftsman-domain com.alibaba.craftsman craftsman-client org.mybatis.spring.boot mybatis-spring-boot-starter mysql mysql-connector-java com.alibaba fastjson org.springframework.boot spring-boot-starter-test test ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/common/BizCode.java ================================================ package com.alibaba.craftsman.common; public class BizCode { public final static String BIZ_ONE = "ali.cola.demo.bizOne"; //biz one public final static String BIZ_TWO = "ali.cola.demo.bizTwo"; //biz two } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/common/event/DomainEventPublisher.java ================================================ package com.alibaba.craftsman.common.event; import org.springframework.stereotype.Component; /** * DomainEventPublisher, this is for demo purpose, the Event is sent to EventBus * * Normally DomainEvent should be sent to Messaging Middleware * * @author Frank Zhang * @date 2019-01-04 11:05 AM */ @Component public class DomainEventPublisher{ public void publish(Object domainEvent) { //eventBus.fire(domainEvent); } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/common/exception/ErrorCode.java ================================================ package com.alibaba.craftsman.common.exception; /** * ErrorCode * * @author Frank Zhang * @date 2019-01-04 11:00 AM */ public enum ErrorCode { B_CUSTOMER_companyNameConflict("B_CUSTOMER_companyNameConflict", "客户公司名冲突"); private final String errCode; private final String errDesc; private ErrorCode(String errCode, String errDesc) { this.errCode = errCode; this.errDesc = errDesc; } public String getErrCode() { return errCode; } public String getErrDesc() { return errDesc; } public static ErrorCode statOf(String ecode) { for (ErrorCode errorCode : values()){ if (errorCode.getErrCode().equals(ecode)) return errorCode; } return null; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/config/CraftsmanConfig.java ================================================ package com.alibaba.craftsman.config; /** * Configuration for infrastructure */ public class CraftsmanConfig { } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/convertor/MetricConvertor.java ================================================ package com.alibaba.craftsman.convertor; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO; /** * @author frankzhang */ public class MetricConvertor{ public static MetricDO toDataObject(MetricItem metricItem){ MetricDO metricDO = new MetricDO(); metricDO.setUserId(metricItem.getMetricOwner().getUserId()); metricDO.setMainMetric(metricItem.getSubMetric().getParent().getCode()); metricDO.setSubMetric(metricItem.getSubMetric().getCode()); metricDO.setMetricItem(metricItem.toJsonString()); metricDO.setCreator("test"); metricDO.setModifier("test"); return metricDO; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/convertor/UserProfileConvertor.java ================================================ package com.alibaba.craftsman.convertor; import com.alibaba.craftsman.domain.user.Role; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.dto.clientobject.UserProfileCO; import com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO; import org.springframework.beans.BeanUtils; public class UserProfileConvertor{ public static UserProfile toEntity(UserProfileCO userProfileCO){ UserProfile userProfile = new UserProfile(); BeanUtils.copyProperties(userProfileCO, userProfile); userProfile.setRole(Role.valueOf(userProfileCO.getRole())); return userProfile; } public static UserProfileDO toDataObject(UserProfile userProfile){ UserProfileDO userProfileDO = new UserProfileDO(); BeanUtils.copyProperties(userProfile, userProfileDO); userProfileDO.setRole(userProfile.getRole().name()); return userProfileDO; } public static UserProfileDO toDataObjectForCreate(UserProfile userProfile){ UserProfileDO userProfileDO = toDataObject(userProfile); return userProfileDO; } public static UserProfileDO toDataObjectForUpdate(UserProfile userProfile){ UserProfileDO userProfileDO = toDataObject(userProfile); return userProfileDO; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/MetricGatewayImpl.java ================================================ package com.alibaba.craftsman.gatewayimpl; import com.alibaba.craftsman.common.event.DomainEventPublisher; import com.alibaba.craftsman.convertor.MetricConvertor; import com.alibaba.craftsman.domain.gateway.MetricGateway; import com.alibaba.craftsman.domain.metrics.MainMetricType; import com.alibaba.craftsman.domain.metrics.MetricItem; import com.alibaba.craftsman.domain.metrics.SubMetric; import com.alibaba.craftsman.domain.metrics.SubMetricType; import com.alibaba.craftsman.domain.metrics.appquality.AppMetric; import com.alibaba.craftsman.domain.metrics.appquality.AppMetricItem; import com.alibaba.craftsman.domain.metrics.devquality.BugMetric; import com.alibaba.craftsman.domain.metrics.devquality.BugMetricItem; import com.alibaba.craftsman.domain.metrics.techcontribution.*; import com.alibaba.craftsman.domain.metrics.techinfluence.*; import com.alibaba.craftsman.dto.domainevent.MetricItemCreatedEvent; import com.alibaba.craftsman.gatewayimpl.database.MetricMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO; import com.alibaba.craftsman.gatewayimpl.rpc.AppMetricMapper; import com.alibaba.craftsman.gatewayimpl.rpc.BugMetricMapper; import com.alibaba.craftsman.gatewayimpl.rpc.dataobject.AppMetricDO; import com.alibaba.craftsman.gatewayimpl.rpc.dataobject.BugMetricDO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; /** * MetricGatewayImpl * * @author Frank Zhang * @date 2020-07-02 12:20 PM */ @Component @Slf4j public class MetricGatewayImpl implements MetricGateway { @Resource private MetricMapper metricMapper; @Resource private BugMetricMapper bugMetricMapper; @Resource private AppMetricMapper appMetricMapper; @Resource private DomainEventPublisher domainEventPublisher; @Override public void save(MetricItem metricItem){ MetricDO metricDO = MetricConvertor.toDataObject(metricItem); metricMapper.create(metricDO); log.debug("AutoGeneratedId: "+metricDO.getId()); MetricItemCreatedEvent metricItemCreatedEvent = new MetricItemCreatedEvent(); metricItemCreatedEvent.setId(metricDO.getId()); metricItemCreatedEvent.setUserId(metricDO.getUserId()); metricItemCreatedEvent.setMainMetricType(metricDO.getMainMetric()); domainEventPublisher.publish(metricItemCreatedEvent); } @Override public List listByTechContribution(String userId){ List metricDOList = metricMapper.listByMainMetric(userId, MainMetricType.TECH_CONTRIBUTION.getMetricCode()); RefactoringMetric refactoringMetric = new RefactoringMetric(); MiscMetric miscMetric = new MiscMetric(); CodeReviewMetric codeReviewMetric = new CodeReviewMetric(); List subMetricList = new ArrayList<>(); subMetricList.add(refactoringMetric); subMetricList.add(miscMetric); subMetricList.add(codeReviewMetric); metricDOList.forEach(metricDO -> { String json = metricDO.getMetricItem(); switch (SubMetricType.valueOf(metricDO.getSubMetric())){ case Refactoring: refactoringMetric.addMetricItem(RefactoringMetricItem.valueOf(json)); break; case Misc: miscMetric.addMetricItem(MiscMetricItem.valueOf(json)); break; case CodeReview: codeReviewMetric.addMetricItem(CodeReviewMetricItem.valueOf(json)); break; default: log.error("illegal SubMetric type: " + metricDO.getSubMetric()); } }); return subMetricList; } @Override public List listByTechInfluence(String userId){ List metricDOList = metricMapper.listByMainMetric(userId, MainMetricType.TECH_INFLUENCE.getMetricCode()); ATAMetric ataMetric = new ATAMetric(); SharingMetric sharingMetric = new SharingMetric(); PatentMetric patentMetric = new PatentMetric(); PaperMetric paperMetric = new PaperMetric(); List subMetricList = new ArrayList<>(); subMetricList.add(ataMetric); subMetricList.add(sharingMetric); subMetricList.add(patentMetric); subMetricList.add(paperMetric); metricDOList.forEach(metricDO -> { String json = metricDO.getMetricItem(); switch (SubMetricType.valueOf(metricDO.getSubMetric())){ case ATA: ataMetric.addMetricItem(ATAMetricItem.valueOf(json)); break; case Sharing: sharingMetric.addMetricItem(SharingMetricItem.valueOf(json)); break; case Patent: patentMetric.addMetricItem(PatentMetricItem.valueOf(json)); break; case Paper: paperMetric.addMetricItem(PaperMetricItem.valueOf(json)); default: log.error("illegal SubMetric type: " + metricDO.getSubMetric()); } }); return subMetricList; } @Override public BugMetric getBugMetric(String userId){ BugMetricDO bugMetricDO = bugMetricMapper.getByUserId(userId); BugMetricItem bugMetricItem = new BugMetricItem(bugMetricDO.getBugCount(), bugMetricDO.getCheckInCodeCount()); BugMetric bugMetric = new BugMetric(); bugMetric.addMetricItem(bugMetricItem); return bugMetric; } @Override public AppMetric getAppMetric(String userId){ List appMetricDOList = appMetricMapper.listByUserId(userId); AppMetric appMetric = new AppMetric(); appMetricDOList.forEach(appMetricDO -> { AppMetricItem appMetricItem = new AppMetricItem(); BeanUtils.copyProperties(appMetricDO, appMetricItem); appMetric.addMetricItem(appMetricItem); }); return appMetric; } @Override public void delete(String id, String operator) { metricMapper.delete(id, operator); } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/UserProfileGatewayImpl.java ================================================ package com.alibaba.craftsman.gatewayimpl; import com.alibaba.craftsman.convertor.UserProfileConvertor; import com.alibaba.craftsman.domain.DomainFactory; import com.alibaba.craftsman.domain.gateway.UserProfileGateway; import com.alibaba.craftsman.domain.metrics.weight.WeightFactory; import com.alibaba.craftsman.domain.user.Role; import com.alibaba.craftsman.domain.user.UserProfile; import com.alibaba.craftsman.gatewayimpl.database.UserProfileMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * UserProfileGatewayImpl * * @author Frank Zhang * @date 2020-07-02 12:32 PM */ @Component @Slf4j public class UserProfileGatewayImpl implements UserProfileGateway { @Resource private UserProfileMapper userProfileMapper; @Override public void create(UserProfile userProfile) { userProfileMapper.create(UserProfileConvertor.toDataObjectForCreate(userProfile)); } @Override public void update(UserProfile userProfile) { userProfileMapper.update(UserProfileConvertor.toDataObjectForUpdate(userProfile)); } @Override public UserProfile getByUserId(String userId) { UserProfileDO userProfileDO = userProfileMapper.getByUserId(userId); if (userProfileDO == null) { log.warn("There is no UserProfile for : " + userId); return null; } UserProfile userProfile = DomainFactory.getUserProfile(); BeanUtils.copyProperties(userProfileDO, userProfile); Role role = Role.valueOf(userProfileDO.getRole()); userProfile.setRole(role); userProfile.setWeight(WeightFactory.get(role)); return userProfile; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/database/MetricMapper.java ================================================ package com.alibaba.craftsman.gatewayimpl.database; import com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface MetricMapper { int create(MetricDO dataObject); List listByUserId(@Param("userId") String userId); List listByMainMetric(@Param("userId") String userId, @Param("mainMetric") String mainMetric); List listBySubMetric(@Param("userId") String userId, @Param("subMetric") String subMetric); int delete(@Param("id") String id, @Param("modifier") String modifier); MetricDO getById(@Param("id") String id); } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/database/UserProfileMapper.java ================================================ package com.alibaba.craftsman.gatewayimpl.database; import com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; /** * UserProfileMapper * * @author Frank Zhang * @date 2019-02-27 5:03 PM */ @Mapper public interface UserProfileMapper { int create(UserProfileDO userProfileDO); int update(UserProfileDO userProfileDO); int delete(@Param("userId") String userId); UserProfileDO getByUserId(@Param("userId") String userId); List listByDep(@Param("dep") String dep); } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/database/dataobject/BaseDO.java ================================================ package com.alibaba.craftsman.gatewayimpl.database.dataobject; /** * BaseDO * * @author Frank Zhang * @date 2020-07-02 10:03 AM */ public class BaseDO { private String creator; private String modifier; public String getCreator() { return creator; } public void setCreator(String creator) { this.creator = creator; } public String getModifier() { return modifier; } public void setModifier(String modifier) { this.modifier = modifier; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/database/dataobject/MetricDO.java ================================================ package com.alibaba.craftsman.gatewayimpl.database.dataobject; public class MetricDO extends BaseDO{ private String id; /** * 域账号 */ private String userId; /** * 主度量 */ private String mainMetric; /** * 子度量 */ private String subMetric; /** * 度量条目 */ private String metricItem; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getMainMetric() { return mainMetric; } public void setMainMetric(String mainMetric) { this.mainMetric = mainMetric; } public String getSubMetric() { return subMetric; } public void setSubMetric(String subMetric) { this.subMetric = subMetric; } public String getMetricItem() { return metricItem; } public void setMetricItem(String metricItem) { this.metricItem = metricItem; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/database/dataobject/UserProfileDO.java ================================================ package com.alibaba.craftsman.gatewayimpl.database.dataobject; /** * UserProfileDO * * @author Frank Zhang * @date 2019-02-27 5:00 PM */ public class UserProfileDO extends BaseDO{ /** * 域账号 */ private String userId; /** * 姓名 */ private String userName; /** * 部门 */ private String dep; /** * 角色 */ private String role; /** * 是否主管 */ private String isManager; /** * 综合得分 */ private double totalScore; /** * 代码质量分 */ private double appQualityScore; /** * 技术影响力分 */ private double techInfluenceScore; /** * 技术贡献分 */ private double techContributionScore; /** * 开发质量分 */ private double devQualityScore; /** * checkin代码量 */ private double checkinCodeQuantity; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getDep() { return dep; } public void setDep(String dep) { this.dep = dep; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public String getIsManager() { return isManager; } public void setIsManager(String isManager) { this.isManager = isManager; } public double getTotalScore() { return totalScore; } public void setTotalScore(double totalScore) { this.totalScore = totalScore; } public double getAppQualityScore() { return appQualityScore; } public void setAppQualityScore(double appQualityScore) { this.appQualityScore = appQualityScore; } public double getTechInfluenceScore() { return techInfluenceScore; } public void setTechInfluenceScore(double techInfluenceScore) { this.techInfluenceScore = techInfluenceScore; } public double getTechContributionScore() { return techContributionScore; } public void setTechContributionScore(double techContributionScore) { this.techContributionScore = techContributionScore; } public double getDevQualityScore() { return devQualityScore; } public void setDevQualityScore(double devQualityScore) { this.devQualityScore = devQualityScore; } public double getCheckinCodeQuantity() { return checkinCodeQuantity; } public void setCheckinCodeQuantity(double checkinCodeQuantity) { this.checkinCodeQuantity = checkinCodeQuantity; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/rpc/AppMetricMapper.java ================================================ package com.alibaba.craftsman.gatewayimpl.rpc; import com.alibaba.craftsman.gatewayimpl.rpc.dataobject.AppMetricDO; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Component public class AppMetricMapper { /** * Dummy RPC Call */ public List listByUserId(String userId){ List appMetricDOList = new ArrayList<>(); AppMetricDO appMetricDO1 = new AppMetricDO(); appMetricDO1.setAppName("app1"); appMetricDO1.setCyclomaticComplexityCount(200); appMetricDO1.setDuplicatedMethodCount(80); appMetricDO1.setLongMethodCount(70); appMetricDO1.setBlockedCodeConductCount(20); appMetricDOList.add(appMetricDO1); AppMetricDO appMetricDO2 = new AppMetricDO(); appMetricDO2.setAppName("app2"); appMetricDO2.setCyclomaticComplexityCount(20); appMetricDO2.setDuplicatedMethodCount(30); appMetricDO2.setLongMethodCount(7); appMetricDO2.setBlockedCodeConductCount(5); appMetricDOList.add(appMetricDO2); return appMetricDOList; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/rpc/BugMetricMapper.java ================================================ package com.alibaba.craftsman.gatewayimpl.rpc; import com.alibaba.craftsman.gatewayimpl.rpc.dataobject.BugMetricDO; import org.springframework.stereotype.Component; /** * 模拟一个RPC的Tunnel,也是日常业务中非常常见的场景。 * * 假设Bug数和代码checkin数再另外一个系统中,没有存在本地,需要通过RPC调用才能获取。 * */ @Component public class BugMetricMapper { /** * Dummy RPC Call */ public BugMetricDO getByUserId(String userId){ BugMetricDO bugMetricDO = new BugMetricDO(); bugMetricDO.setBugCount(3); bugMetricDO.setCheckInCodeCount(1500); return bugMetricDO; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/rpc/dataobject/AppMetricDO.java ================================================ package com.alibaba.craftsman.gatewayimpl.rpc.dataobject; public class AppMetricDO { private String appName;//应用名称 private int cyclomaticComplexityCount;//圈复杂度超标的数目 private int duplicatedMethodCount;//重复代码的数目 private int longMethodCount;//长方法的数目 private int blockedCodeConductCount;//不符合编码标准的数目 public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public int getCyclomaticComplexityCount() { return cyclomaticComplexityCount; } public void setCyclomaticComplexityCount(int cyclomaticComplexityCount) { this.cyclomaticComplexityCount = cyclomaticComplexityCount; } public int getDuplicatedMethodCount() { return duplicatedMethodCount; } public void setDuplicatedMethodCount(int duplicatedMethodCount) { this.duplicatedMethodCount = duplicatedMethodCount; } public int getLongMethodCount() { return longMethodCount; } public void setLongMethodCount(int longMethodCount) { this.longMethodCount = longMethodCount; } public int getBlockedCodeConductCount() { return blockedCodeConductCount; } public void setBlockedCodeConductCount(int blockedCodeConductCount) { this.blockedCodeConductCount = blockedCodeConductCount; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/java/com/alibaba/craftsman/gatewayimpl/rpc/dataobject/BugMetricDO.java ================================================ package com.alibaba.craftsman.gatewayimpl.rpc.dataobject; public class BugMetricDO { private int bugCount; //缺陷数量 private long checkInCodeCount; //check in的代码量 public int getBugCount() { return bugCount; } public void setBugCount(int bugCount) { this.bugCount = bugCount; } public long getCheckInCodeCount() { return checkInCodeCount; } public void setCheckInCodeCount(long checkInCodeCount) { this.checkInCodeCount = checkInCodeCount; } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/resources/TableCreationDDL.sql ================================================ CREATE TABLE `metric` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` varchar(64) NOT NULL COMMENT '域账号', `main_metric` varchar(64) NOT NULL COMMENT '主度量', `sub_metric` varchar(64) NOT NULL COMMENT '度量项', `metric_item` json DEFAULT NULL COMMENT '度量项内容', `creator` varchar(64) NOT NULL COMMENT '创建人', `modifier` varchar(64) NOT NULL COMMENT '修改人', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `is_deleted` char(1) NOT NULL DEFAULT 'n' COMMENT '逻辑删除', PRIMARY KEY (`id`), KEY `idx_username` (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8mb4 COMMENT='度量表'; CREATE TABLE `user_profile` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` varchar(64) NOT NULL COMMENT '工号', `user_name` varchar(64) NOT NULL COMMENT '名字', `dep` varchar(128) NOT NULL COMMENT '部门', `role` varchar(6) NOT NULL COMMENT '角色', `total_score` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '综合得分', `app_quality_score` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '代码质量分', `tech_influence_score` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '技术影响力分', `tech_contribution_score` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '技术贡献分', `dev_quality_score` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '开发质量分', `checkin_code_quantity` decimal(9,2) NOT NULL DEFAULT '0.00' COMMENT 'checkin代码量', `is_manager` char(1) DEFAULT NULL COMMENT '是否主管', `creator` varchar(64) NOT NULL COMMENT '创建人', `modifier` varchar(64) NOT NULL COMMENT '修改人', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `is_deleted` char(1) NOT NULL DEFAULT 'n' COMMENT '逻辑删除', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8mb4 COMMENT='用户Profile表'; ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/resources/mybatis/MetricMapper.xml ================================================ id, user_id, main_metric, sub_metric, metric_item, creator, modifier, gmt_create, gmt_modified, is_deleted SELECT FROM metric INSERT INTO metric( ) VALUES ( null, #{userId}, #{mainMetric}, #{subMetric}, #{metricItem}, #{creator}, #{modifier}, now(), now(), 'n' ) UPDATE metric SET is_deleted='y', modifier = #{modifier} WHERE id = #{id} and IS_DELETED = 'n' ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/resources/mybatis/UserProfileMapper.xml ================================================ id, user_id, user_name, is_manager, dep, role, total_score, app_quality_score, tech_influence_score, tech_contribution_score, dev_quality_score, checkin_code_quantity, creator, modifier, gmt_create, gmt_modified, is_deleted SELECT FROM user_profile INSERT INTO user_profile( ) VALUES ( null, #{userId}, #{userName}, #{isManager}, #{dep}, #{role}, #{totalScore}, #{appQualityScore}, #{techInfluenceScore}, #{techContributionScore}, #{devQualityScore}, #{checkinCodeQuantity}, #{creator}, #{modifier}, now(), now(), 'n' ) UPDATE user_profile SET is_deleted='y' WHERE user_id = #{userId} and IS_DELETED = 'n' UPDATE user_profile SET GMT_MODIFIED = now() ,user_name = #{userName} ,dep = #{dep} ,role = #{role} ,is_manager = #{isManager} ,total_score = #{totalScore} ,app_quality_score = #{appQualityScore} ,tech_influence_score = #{techInfluenceScore} ,tech_contribution_score = #{techContributionScore} ,dev_quality_score = #{devQualityScore} ,checkin_code_quantity = #{checkinCodeQuantity} ,modifier = #{modifier} where user_id = #{userId} and is_deleted='n' ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/main/resources/mybatis-config.xml ================================================ ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/test/java/com/alibaba/craftsman/gatewayimpl/Mybatis3Utils.java ================================================ package com.alibaba.craftsman.gatewayimpl; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.Reader; import java.util.Objects; /** * Mybatis3Utils * * @author Frank Zhang * @date 2019-02-27 4:38 PM */ public class Mybatis3Utils { public static final SqlSessionFactory sqlSessionFactory; public static final ThreadLocal sessionThread = new ThreadLocal<>(); static { try { Reader reader = Resources.getResourceAsReader("mybatis-config-test.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (IOException e) { throw new RuntimeException(e); } } public static SqlSession getCurrentSqlSession() { SqlSession sqlSession = sessionThread.get(); if (Objects.isNull(sqlSession)) { sqlSession = sqlSessionFactory.openSession(); sessionThread.set(sqlSession); } return sqlSession; } public static void closeCurrentSession() { SqlSession sqlSession = sessionThread.get(); if (Objects.nonNull(sqlSession)) { sqlSession.close(); } sessionThread.set(null); } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/test/java/com/alibaba/craftsman/gatewayimpl/MybatisTest.java ================================================ package com.alibaba.craftsman.gatewayimpl; import com.alibaba.craftsman.gatewayimpl.database.MetricMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO; import org.apache.ibatis.session.SqlSession; import org.junit.After; import org.junit.Before; /** * MybatisTest * * @author Frank Zhang * @date 2019-02-27 4:38 PM */ public class MybatisTest { SqlSession sqlSession; MetricMapper metricMapper; @Before public void before() { sqlSession = Mybatis3Utils.getCurrentSqlSession(); metricMapper = sqlSession.getMapper(MetricMapper.class); } @After public void after() { Mybatis3Utils.closeCurrentSession(); } //@Test public void insert() { MetricDO metricDO = new MetricDO(); metricDO.setMainMetric("mainTest"); metricDO.setSubMetric("subTest"); metricDO.setUserId("12345"); metricDO.setCreator("Frank"); metricDO.setModifier("Frank"); metricDO.setMetricItem("{\"patentName\": \"Leads重构\", \"level\": \"PROJECT\"}"); metricMapper.create(metricDO); sqlSession.commit(); } } ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/test/resources/logback-test.xml ================================================ ${LOG_FILE} ${FILE_LOG_PATTERN} ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n utf8 ================================================ FILE: cola-samples/craftsman/craftsman-infrastructure/src/test/resources/mybatis-config-test.xml ================================================ ================================================ FILE: cola-samples/craftsman/pom.xml ================================================ 4.0.0 com.alibaba.craftsman craftsman.all pom 1.0.0-SNAPSHOT craftsman.all 1.8 ${maven.compiler.source} UTF-8 UTF-8 true 5.x-SNAPSHOT 2.7.5 2.2.2 craftsman-client craftsman-adapter craftsman-app craftsman-domain craftsman-infrastructure start org.projectlombok lombok provided junit junit test com.alibaba.craftsman craftsman-adapter ${project.version} com.alibaba.craftsman craftsman-client ${project.version} com.alibaba.craftsman craftsman-app ${project.version} com.alibaba.craftsman craftsman-domain ${project.version} com.alibaba.craftsman craftsman-infrastructure ${project.version} com.alibaba.cola cola-components-bom ${cola.components.version} pom import org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis-starter.version} javax.el javax.el-api 3.0.0 com.alibaba fastjson 1.2.83 maven-resources-plugin 3.3.1 maven-compiler-plugin 3.13.0 maven-source-plugin 3.3.1 maven-javadoc-plugin 3.7.0 maven-deploy-plugin 3.1.1 org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} gen-java-src performRelease true maven-source-plugin gen-java-doc performRelease true maven-javadoc-plugin attach-javadoc jar 8 protected UTF-8 UTF-8 UTF-8 -quiet -J-Duser.language=en -J-Duser.country=US -Xdoclint:none ================================================ FILE: cola-samples/craftsman/start/pom.xml ================================================ 4.0.0 com.alibaba.craftsman craftsman.all 1.0.0-SNAPSHOT ../pom.xml start jar start com.alibaba.craftsman craftsman-adapter org.mybatis.spring.boot mybatis-spring-boot-starter org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-test test ================================================ FILE: cola-samples/craftsman/start/src/main/java/com/alibaba/craftsman/Application.java ================================================ package com.alibaba.craftsman; import com.alibaba.craftsman.config.CraftsmanConfig; import lombok.extern.slf4j.Slf4j; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Spring Boot Starter * * COLA framework initialization is configured in {@link CraftsmanConfig} * * @author Frank Zhang */ @SpringBootApplication(scanBasePackages = {"com.alibaba.craftsman","com.alibaba.cola"}) @MapperScan("com.alibaba.craftsman.gatewayimpl.database") @Slf4j public class Application { public static void main(String[] args) { log.info("Begin to start Spring Boot Application"); long startTime = System.currentTimeMillis(); SpringApplication.run(Application.class, args); long endTime = System.currentTimeMillis(); log.info("End starting Spring Boot Application, Time used: "+ (endTime - startTime) ); } } ================================================ FILE: cola-samples/craftsman/start/src/main/resources/application.properties ================================================ project.name=start # mysql configruation spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/craftsman?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull spring.datasource.username=root spring.datasource.password=1983Root_2 #mybatis mybatis.config-location=classpath:mybatis-config.xml ================================================ FILE: cola-samples/craftsman/start/src/main/resources/logback-spring.xml ================================================ ${LOG_FILE} ${FILE_LOG_PATTERN} ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB ${CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: cola-samples/craftsman/start/src/test/java/com/alibaba/craftsman/TestApplication.java ================================================ package com.alibaba.craftsman; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {Application.class})}) @SpringBootApplication(scanBasePackages = {"com.alibaba.craftsman"}) public class TestApplication { public static void main(String[] args) { //这里填的是TestApplication ApplicationContext context = SpringApplication.run(Application.class, args); } } ================================================ FILE: cola-samples/craftsman/start/src/test/java/com/alibaba/craftsman/gatewayimpl/MetricTunnelTest.java ================================================ package com.alibaba.craftsman.gatewayimpl; import com.alibaba.craftsman.domain.metrics.MainMetricType; import com.alibaba.craftsman.domain.metrics.SubMetricType; import com.alibaba.craftsman.gatewayimpl.database.MetricMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO; import org.junit.Assert; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * This is Tunnel Test, no need to mock, and no regression needed as well * * @author Frank Zhang * @date 2019-02-27 2:33 PM */ public class MetricTunnelTest { @Autowired private MetricMapper metricMapper; public void testCRUD(){ String userId = "MetricTunnelTest" + Math.random(); MetricDO metricDO = new MetricDO(); metricDO.setMainMetric(MainMetricType.TECH_INFLUENCE.getMetricCode()); metricDO.setSubMetric(SubMetricType.Refactoring.getMetricSubTypeCode()); metricDO.setUserId(userId); metricDO.setMetricItem("{\"patentName\": \"Leads重构\", \"level\": \"PROJECT\"}"); metricMapper.create(metricDO); List metricDOS = metricMapper.listByUserId(userId); Assert.assertEquals(1, metricDOS.size()); metricMapper.delete(metricDOS.get(0).getId(),"MetricTunnelTest"); Assert.assertEquals(0, metricMapper.listByUserId(userId).size()); } } ================================================ FILE: cola-samples/craftsman/start/src/test/java/com/alibaba/craftsman/gatewayimpl/UserProfileTunnelTest.java ================================================ package com.alibaba.craftsman.gatewayimpl; import com.alibaba.craftsman.gatewayimpl.database.UserProfileMapper; import com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO; import org.junit.Assert; import org.springframework.beans.factory.annotation.Autowired; /** * This is Tunnel Test, no need to mock, and no regression needed as well * * @author Frank Zhang * @date 2019-02-27 5:31 PM */ public class UserProfileTunnelTest { @Autowired private UserProfileMapper userProfileMapper; public void testCRUD(){ String userId = Math.random()+"UserProfileTunnelTest"; UserProfileDO userProfileDO = new UserProfileDO(); userProfileDO.setUserId(userId); userProfileDO.setDep("alibaba"); userProfileDO.setIsManager("n"); userProfileDO.setUserName("Frank"); userProfileDO.setRole("DEV"); userProfileMapper.create(userProfileDO); userProfileDO = userProfileMapper.getByUserId(userId); Assert.assertEquals(userId, userProfileDO.getUserId()); userProfileMapper.delete(userId); Assert.assertNull(userProfileMapper.getByUserId(userId)); } } ================================================ FILE: cola-samples/craftsman/start/src/test/resources/logback-test.xml ================================================ ${LOG_FILE} ${FILE_LOG_PATTERN} ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log 7 50MB 20GB %d{HH:mm:ss.SSS} [%thread] %-5level %logger{30} - %msg%n utf8 ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.ATAMetricAddCmdExeTest_testATAMetricAddSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.ATAMetricAddCmdExeTest_testATAMetricAddSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1073", "mainMetric":"tech-influence", "metricItem":"{\"commentCount\":14,\"favoriteCount\":49,\"hitCount\":299,\"thumbsUpCount\":89,\"title\":\"testATAMetricAddSuccess\",\"url\":\"sharingLink\"}", "modifier":"System", "subMetric":"ATA", "userId":"ATAMetricAddCmdExeTest1552554148803" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.CodeReviewMetricAddCmdExeTest_testSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.CodeReviewMetricAddCmdExeTest_testSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1074", "mainMetric":"tech-contribution", "metricItem":"{\"noteCount\":8,\"reviewDocLink\":\"http://www.alibaba.com \",\"reviewId\":\"72376263\"}", "modifier":"System", "subMetric":"CodeReview", "userId":"CodeReviewMetricAddCmdExeTest_098873" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.MetricDeleteCmdExeTest_testSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_delete --------------------------- [ 0 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.MetricDeleteCmdExeTest_testSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_delete =================method_params_separator================ [ [ "1013", "MetricDeleteCmdExeTest" ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.MiscMetricAddCmdExeTest_testSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.MiscMetricAddCmdExeTest_testSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1075", "mainMetric":"tech-contribution", "metricItem":"{\"codeUrl\":\"codeUrl\",\"content\":\"Highlight content\",\"docUrl\":\"docUrl\",\"name\":\"Tech highlight\"}", "modifier":"System", "subMetric":"Misc", "userId":"MiscMetricAddCmdExeTest_09888" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.PaperMetricAddCmdExeTest_testPaperMetricAddSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.PaperMetricAddCmdExeTest_testPaperMetricAddSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1076", "mainMetric":"tech-influence", "metricItem":"{\"magazine\":\"IEEE\",\"paperDesc\":\"paper Description\",\"paperLink\":\"http://www.alibaba.com\",\"paperName\":\"paperName\"}", "modifier":"System", "subMetric":"Paper", "userId":"PaperMetricAddCmdExeTest_098872" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.PatentMetricAddCmdExeTest_testPatentMetricAddSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.PatentMetricAddCmdExeTest_testPatentMetricAddSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"jack ma", "id":"1077", "mainMetric":"tech-influence", "metricItem":"{\"authorType\":\"FIRST_AUTHOR\",\"patentDesc\":\"This is a very valuable patent\",\"patentName\":\"patentName\",\"patentNo\":\"73499992323\",\"patentUrl\":\"http://www.alibaba.com\"}", "modifier":"jack ma", "subMetric":"Patent", "userId":"PatentMetricAddCmdExeTest_627327" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.RefactoringMetricAddCmdExeTest_testSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.RefactoringMetricAddCmdExeTest_testSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"Lucy", "id":"1078", "mainMetric":"tech-contribution", "metricItem":"{\"codeUrl\":\"codeUrl\",\"content\":\"Refactor Content\",\"docUrl\":\"docUrl\",\"name\":\"Refactor Leads code\",\"refactoringLevel\":\"MODULE\"}", "modifier":"Lucy", "subMetric":"Refactoring", "userId":"RefactoringMetricAddCmdExeTest_09098" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.ScoreRecalculateTest_testDevSuccess ================================================ {"initialized":false,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_create --------------------------- [ 1 ] =========================== com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1, 1, 1 ] =========================== com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_getByUserId --------------------------- [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":0.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":0.0D, "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1067", "isManager":"y", "modifier":"testSuccessAdd", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":0.0D, "userId":"ScoreRecalculateTest1552554289036", "userName":"Frank" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":90.0D, "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1067", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":3.75D, "totalScore":34.93D, "userId":"ScoreRecalculateTest1552554289036", "userName":"Frank" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":90.0D, "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1067", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":7.5D, "totalScore":36.05D, "userId":"ScoreRecalculateTest1552554289036", "userName":"Frank" } ] =========================== com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_listByMainMetric --------------------------- [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1080", "mainMetric":"tech-influence", "metricItem":"{\"url\": \"sharingLink\", \"title\": \"testATAMetricAddSuccess\", \"hitCount\": 299, \"commentCount\": 14, \"favoriteCount\": 49, \"thumbsUpCount\": 89}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" } ], [], [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1080", "mainMetric":"tech-influence", "metricItem":"{\"url\": \"sharingLink\", \"title\": \"testATAMetricAddSuccess\", \"hitCount\": 299, \"commentCount\": 14, \"favoriteCount\": 49, \"thumbsUpCount\": 89}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1081", "mainMetric":"tech-influence", "metricItem":"{\"url\": \"sharingLink\", \"title\": \"testATAMetricAddSuccess\", \"hitCount\": 299, \"commentCount\": 14, \"favoriteCount\": 49, \"thumbsUpCount\": 89}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" } ], [], [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1080", "mainMetric":"tech-influence", "metricItem":"{\"url\": \"sharingLink\", \"title\": \"testATAMetricAddSuccess\", \"hitCount\": 299, \"commentCount\": 14, \"favoriteCount\": 49, \"thumbsUpCount\": 89}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1081", "mainMetric":"tech-influence", "metricItem":"{\"url\": \"sharingLink\", \"title\": \"testATAMetricAddSuccess\", \"hitCount\": 299, \"commentCount\": 14, \"favoriteCount\": 49, \"thumbsUpCount\": 89}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "gmtCreate":1552601089000, "gmtModified":1552601089000, "id":"1082", "mainMetric":"tech-influence", "metricItem":"{\"url\": \"sharingLink\", \"title\": \"testATAMetricAddSuccess\", \"hitCount\": 299, \"commentCount\": 14, \"favoriteCount\": 49, \"thumbsUpCount\": 89}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" } ], [] ] =========================== com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_update --------------------------- [ 1, 1, 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.ScoreRecalculateTest_testDevSuccess_inputParams ================================================ {"initialized":false,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":0.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":0.0D, "isManager":"y", "modifier":"testSuccessAdd", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":0.0D, "userId":"ScoreRecalculateTest1552554289036", "userName":"Frank" } ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1080", "mainMetric":"tech-influence", "metricItem":"{\"commentCount\":14,\"favoriteCount\":49,\"hitCount\":299,\"thumbsUpCount\":89,\"title\":\"testATAMetricAddSuccess\",\"url\":\"sharingLink\"}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" } ], [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1081", "mainMetric":"tech-influence", "metricItem":"{\"commentCount\":14,\"favoriteCount\":49,\"hitCount\":299,\"thumbsUpCount\":89,\"title\":\"testATAMetricAddSuccess\",\"url\":\"sharingLink\"}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" } ], [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1082", "mainMetric":"tech-influence", "metricItem":"{\"commentCount\":14,\"favoriteCount\":49,\"hitCount\":299,\"thumbsUpCount\":89,\"title\":\"testATAMetricAddSuccess\",\"url\":\"sharingLink\"}", "modifier":"System", "subMetric":"ATA", "userId":"ScoreRecalculateTest1552554289036" } ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_getByUserId =================method_params_separator================ [ [ "ScoreRecalculateTest1552554289036" ], [ "ScoreRecalculateTest1552554289036" ], [ "ScoreRecalculateTest1552554289036" ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_listByMainMetric =================method_params_separator================ [ [ "ScoreRecalculateTest1552554289036", "tech-influence" ], [ "ScoreRecalculateTest1552554289036", "tech-contribution" ], [ "ScoreRecalculateTest1552554289036", "tech-influence" ], [ "ScoreRecalculateTest1552554289036", "tech-contribution" ], [ "ScoreRecalculateTest1552554289036", "tech-influence" ], [ "ScoreRecalculateTest1552554289036", "tech-contribution" ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_update =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "dep":"ICBU", "devQualityScore":90.0D, "id":"1067", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":3.75D, "totalScore":34.925D, "userId":"ScoreRecalculateTest1552554289036", "userName":"Frank" } ], [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "dep":"ICBU", "devQualityScore":90.0D, "id":"1067", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":7.5D, "totalScore":36.05D, "userId":"ScoreRecalculateTest1552554289036", "userName":"Frank" } ], [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "dep":"ICBU", "devQualityScore":90.0D, "id":"1067", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":11.25D, "totalScore":37.175D, "userId":"ScoreRecalculateTest1552554289036", "userName":"Frank" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.SharingMetricAddCmdExeTest_testSharingMetricAddSuccess ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.SharingMetricAddCmdExeTest_testSharingMetricAddSuccess_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.MetricDO", "creator":"System", "id":"1079", "mainMetric":"tech-influence", "metricItem":"{\"sharingLink\":\"sharing.com\",\"sharingName\":\"Structured thinking\",\"sharingScope\":\"TEAM\"}", "modifier":"System", "subMetric":"Sharing", "userId":"testSharingMetricAddSuccess_089765" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessAdd ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_create --------------------------- [ 1 ] =========================== com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_getByUserId --------------------------- [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":0.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":0.0D, "gmtCreate":1552601073000, "gmtModified":1552601073000, "id":"1065", "isManager":"y", "modifier":"testSuccessAdd", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":0.0D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":90.0D, "gmtCreate":1552601073000, "gmtModified":1552601073000, "id":"1065", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":33.8D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" } ] =========================== com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_listByMainMetric --------------------------- [ [], [] ] =========================== com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_update --------------------------- [ 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessAdd_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":0.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":0.0D, "isManager":"y", "modifier":"testSuccessAdd", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":0.0D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" } ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_getByUserId =================method_params_separator================ [ [ "UserProfileCmdExeTest1000" ], [ "UserProfileCmdExeTest1000" ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_listByMainMetric =================method_params_separator================ [ [ "UserProfileCmdExeTest1000", "tech-influence" ], [ "UserProfileCmdExeTest1000", "tech-contribution" ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_update =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "dep":"ICBU", "devQualityScore":90.0D, "id":"1065", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":33.8D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessUpdate ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_create --------------------------- [ 1 ] =========================== com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_getByUserId --------------------------- [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":0.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":0.0D, "gmtCreate":1552601079000, "gmtModified":1552601079000, "id":"1066", "isManager":"y", "modifier":"testSuccessAdd", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":0.0D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":90.0D, "gmtCreate":1552601079000, "gmtModified":1552601079000, "id":"1066", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":33.8D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" }, { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":90.0D, "gmtCreate":1552601079000, "gmtModified":1552601079000, "id":"1066", "isManager":"y", "modifier":"testSuccessUpdate", "role":"QA", "techContributionScore":100.0D, "techInfluenceScore":200.0D, "totalScore":33.8D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" } ] =========================== com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_listByMainMetric --------------------------- [ [], [] ] =========================== com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_update --------------------------- [ 1, 1 ] =========================== ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/com.alibaba.craftsman.app.UserProfileCmdExeTest_testSuccessUpdate_inputParams ================================================ {"initialized":true,"version":"1.0.0"} com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_create =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":0.0D, "checkinCodeQuantity":0.0D, "creator":"testSuccessAdd", "dep":"ICBU", "devQualityScore":0.0D, "isManager":"y", "modifier":"testSuccessAdd", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":0.0D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" } ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_getByUserId =================method_params_separator================ [ [ "UserProfileCmdExeTest1000" ], [ "UserProfileCmdExeTest1000" ], [ "UserProfileCmdExeTest1000" ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.MetricTunnel_listByMainMetric =================method_params_separator================ [ [ "UserProfileCmdExeTest1000", "tech-influence" ], [ "UserProfileCmdExeTest1000", "tech-contribution" ] ] =======================different_method_separator============================= com.alibaba.craftsman.gatewayimpl.database.UserProfileTunnel_update =================method_params_separator================ [ [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":79.0D, "checkinCodeQuantity":0.0D, "dep":"ICBU", "devQualityScore":90.0D, "id":"1066", "isManager":"y", "modifier":"System", "role":"DEV", "techContributionScore":0.0D, "techInfluenceScore":0.0D, "totalScore":33.8D, "userId":"UserProfileCmdExeTest1000", "userName":"Frank" } ], [ { "@type":"com.alibaba.craftsman.gatewayimpl.database.dataobject.UserProfileDO", "appQualityScore":0.0D, "checkinCodeQuantity":0.0D, "devQualityScore":0.0D, "modifier":"testSuccessUpdate", "role":"QA", "techContributionScore":100.0D, "techInfluenceScore":200.0D, "totalScore":0.0D, "userId":"UserProfileCmdExeTest1000" } ] ] =======================different_method_separator============================= ================================================ FILE: cola-samples/craftsman/start/src/test/resources/mockfile/service.list ================================================ ATAMetricAddCmdExe_@_com.alibaba.craftsman.command.ATAMetricAddCmdExe ATAMetricQryExe_@_com.alibaba.craftsman.command.query.ATAMetricQryExe appMetricTunnel_@_com.alibaba.craftsman.tunnel.rpc.AppMetricTunnel applicationContextHelper_@_com.alibaba.cola.common.ApplicationContextHelper applicationTaskExecutor_@_org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor application_@_com.alibaba.craftsman.Application basicErrorController_@_org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController beanNameHandlerMapping_@_org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping beanNameViewResolver_@_org.springframework.web.servlet.view.BeanNameViewResolver bootstrap_@_com.alibaba.cola.boot.SpringBootstrap bugMetricTunnel_@_com.alibaba.craftsman.tunnel.rpc.BugMetricTunnel characterEncodingFilter_@_org.springframework.web.filter.CharacterEncodingFilter codeReviewMetricAddCmdExe_@_com.alibaba.craftsman.command.CodeReviewMetricAddCmdExe colaConfig_@_com.alibaba.craftsman.config.ColaConfig conventionErrorViewResolver_@_org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver dataSource_@_com.zaxxer.hikari.HikariDataSource defaultServletHandlerMapping_@_org.springframework.web.servlet.HandlerMapping defaultValidator_@_org.springframework.validation.beanvalidation.LocalValidatorFactoryBean defaultViewResolver_@_org.springframework.web.servlet.view.InternalResourceViewResolver dispatcherServletRegistration_@_org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean dispatcherServlet_@_org.springframework.web.servlet.DispatcherServlet domainEventPublisher_@_com.alibaba.craftsman.common.util.DomainEventPublisher errorAttributes_@_org.springframework.boot.web.servlet.error.DefaultErrorAttributes errorPageCustomizer_@_org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$ErrorPageCustomizer error_@_org.springframework.web.servlet.View eventBus_@_com.alibaba.cola.event.EventBus eventHub_@_com.alibaba.cola.event.EventHub eventRegister_@_com.alibaba.cola.boot.EventRegister extensionExecutor_@_com.alibaba.cola.extension.ExtensionExecutor extensionRegister_@_com.alibaba.cola.boot.ExtensionRegister extensionRepository_@_com.alibaba.cola.extension.ExtensionRepository faviconHandlerMapping_@_org.springframework.web.servlet.handler.SimpleUrlHandlerMapping faviconRequestHandler_@_org.springframework.web.servlet.resource.ResourceHttpRequestHandler formContentFilter_@_org.springframework.boot.web.servlet.filter.OrderedFormContentFilter groovyMarkupConfigurer_@_org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer groovyMarkupViewResolver_@_org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver gsonBuilder_@_com.google.gson.GsonBuilder gson_@_com.google.gson.Gson handlerExceptionResolver_@_org.springframework.web.servlet.HandlerExceptionResolver hiddenHttpMethodFilter_@_org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter hikariPoolDataSourceMetadataProvider_@_org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider httpRequestHandlerAdapter_@_org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter jacksonCodecCustomizer_@_org.springframework.boot.web.codec.CodecCustomizer jacksonObjectMapperBuilder_@_org.springframework.http.converter.json.Jackson2ObjectMapperBuilder jacksonObjectMapper_@_com.fasterxml.jackson.databind.ObjectMapper jdbcTemplate_@_org.springframework.jdbc.core.JdbcTemplate jsonComponentModule_@_org.springframework.boot.jackson.JsonComponentModule localeCharsetMappingsCustomizer_@_org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration$LocaleCharsetMappingsCustomizer loggingCodecCustomizer_@_org.springframework.boot.web.codec.CodecCustomizer mappingJackson2HttpMessageConverter_@_org.springframework.http.converter.json.MappingJackson2HttpMessageConverter mbeanExporter_@_org.springframework.jmx.export.annotation.AnnotationMBeanExporter mbeanServer_@_javax.management.MBeanServer messageConverters_@_org.springframework.boot.autoconfigure.http.HttpMessageConverters metricDeleteCmdExe_@_com.alibaba.craftsman.command.MetricDeleteCmdExe metricItemCreatedHandler_@_com.alibaba.craftsman.event.handler.MetricItemCreatedHandler metricRepository_@_com.alibaba.craftsman.repository.MetricRepository metricTunnel_@_com.alibaba.craftsman.tunnel.database.MetricTunnel metricsController_@_com.alibaba.craftsman.controller.MetricsController metricsServiceImpl_@_com.alibaba.craftsman.service.MetricsServiceImpl miscMetricAddCmdExe_@_com.alibaba.craftsman.command.MiscMetricAddCmdExe multipartConfigElement_@_javax.servlet.MultipartConfigElement multipartResolver_@_org.springframework.web.multipart.support.StandardServletMultipartResolver mvcContentNegotiationManager_@_org.springframework.web.accept.ContentNegotiationManager mvcConversionService_@_org.springframework.format.support.FormattingConversionService mvcHandlerMappingIntrospector_@_org.springframework.web.servlet.handler.HandlerMappingIntrospector mvcPathMatcher_@_org.springframework.util.PathMatcher mvcResourceUrlProvider_@_org.springframework.web.servlet.resource.ResourceUrlProvider mvcUriComponentsContributor_@_org.springframework.web.method.support.CompositeUriComponentsContributor mvcUrlPathHelper_@_org.springframework.web.util.UrlPathHelper mvcValidator_@_org.springframework.validation.Validator mvcViewResolver_@_org.springframework.web.servlet.ViewResolver mybatis-org.mybatis.spring.boot.autoconfigure.MybatisProperties_@_org.mybatis.spring.boot.autoconfigure.MybatisProperties namedParameterJdbcTemplate_@_org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate objectNamingStrategy_@_org.springframework.boot.autoconfigure.jmx.ParentAwareNamingStrategy org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration_@_org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration org.springframework.boot.autoconfigure.AutoConfigurationPackages_@_org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration_@_org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration_@_org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration_@_org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration$GroovyMarkupConfiguration_@_org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration$GroovyMarkupConfiguration org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration$GroovyWebConfiguration_@_org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration$GroovyWebConfiguration org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration_@_org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration_@_org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration_@_org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration_@_org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration_@_org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration_@_org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration_@_org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$JacksonCodecConfiguration_@_org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$JacksonCodecConfiguration org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$LoggingCodecConfiguration_@_org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$LoggingCodecConfiguration org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration_@_org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration_@_org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration_@_org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration_@_org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration_@_org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration_@_org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration_@_org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$PooledDataSourceConfiguration_@_org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$PooledDataSourceConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration_@_org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari_@_org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration_@_org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker_@_org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari_@_org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration_@_org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration$DataSourceTransactionManagerConfiguration_@_org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration$DataSourceTransactionManagerConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration_@_org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration$JdbcTemplateConfiguration_@_org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration$JdbcTemplateConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration$NamedParameterJdbcTemplateConfiguration_@_org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration$NamedParameterJdbcTemplateConfiguration org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration_@_org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration_@_org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration_@_org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration_@_org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration_@_org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration_@_org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration_@_org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration_@_org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$TransactionTemplateConfiguration_@_org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$TransactionTemplateConfiguration org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration_@_org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration_@_org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration_@_org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration_@_org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration_@_org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat_@_org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter_@_org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration_@_org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration_@_org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration_@_org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration_@_org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration org.springframework.transaction.config.internalTransactionAdvisor_@_org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor paperMetricAddCmdExe_@_com.alibaba.craftsman.command.PaperMetricAddCmdExe parameterNamesModule_@_com.fasterxml.jackson.module.paramnames.ParameterNamesModule patentMetricAddCmdExe_@_com.alibaba.craftsman.command.PatentMetricAddCmdExe platformTransactionManagerCustomizers_@_org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers refactoringMetricAddCmdExe_@_com.alibaba.craftsman.command.RefactoringMetricAddCmdExe refreshScoreCmdExe_@_com.alibaba.craftsman.command.RefreshScoreCmdExe requestContextFilter_@_org.springframework.web.filter.RequestContextFilter requestMappingHandlerAdapter_@_org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter requestMappingHandlerMapping_@_org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping resourceHandlerMapping_@_org.springframework.web.servlet.HandlerMapping restTemplateBuilder_@_org.springframework.boot.web.client.RestTemplateBuilder server-org.springframework.boot.autoconfigure.web.ServerProperties_@_org.springframework.boot.autoconfigure.web.ServerProperties servletWebServerFactoryCustomizer_@_org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer sharingMetricAddCmdExe_@_com.alibaba.craftsman.command.SharingMetricAddCmdExe simpleControllerHandlerAdapter_@_org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties_@_org.springframework.boot.autoconfigure.jdbc.DataSourceProperties spring.groovy.template-org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateProperties_@_org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateProperties spring.gson-org.springframework.boot.autoconfigure.gson.GsonProperties_@_org.springframework.boot.autoconfigure.gson.GsonProperties spring.http-org.springframework.boot.autoconfigure.http.HttpProperties_@_org.springframework.boot.autoconfigure.http.HttpProperties spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties_@_org.springframework.boot.autoconfigure.info.ProjectInfoProperties spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties_@_org.springframework.boot.autoconfigure.jackson.JacksonProperties spring.jdbc-org.springframework.boot.autoconfigure.jdbc.JdbcProperties_@_org.springframework.boot.autoconfigure.jdbc.JdbcProperties spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties_@_org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties_@_org.springframework.boot.autoconfigure.web.ResourceProperties spring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartProperties_@_org.springframework.boot.autoconfigure.web.servlet.MultipartProperties spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties_@_org.springframework.boot.autoconfigure.task.TaskExecutionProperties spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties_@_org.springframework.boot.autoconfigure.task.TaskSchedulingProperties spring.transaction-org.springframework.boot.autoconfigure.transaction.TransactionProperties_@_org.springframework.boot.autoconfigure.transaction.TransactionProperties sqlSessionFactory_@_org.apache.ibatis.session.SqlSessionFactory sqlSessionTemplate_@_org.mybatis.spring.SqlSessionTemplate standardGsonBuilderCustomizer_@_org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration$StandardGsonBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer_@_org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration$StandardJackson2ObjectMapperBuilderCustomizer stringHttpMessageConverter_@_org.springframework.http.converter.StringHttpMessageConverter taskExecutorBuilder_@_org.springframework.boot.task.TaskExecutorBuilder taskSchedulerBuilder_@_org.springframework.boot.task.TaskSchedulerBuilder testsContainer_@_com.alibaba.cola.container.TestsContainer tomcatServletWebServerFactoryCustomizer_@_org.springframework.boot.autoconfigure.web.servlet.TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactory_@_org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory tomcatWebServerFactoryCustomizer_@_org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer transactionAttributeSource_@_org.springframework.transaction.interceptor.TransactionAttributeSource transactionInterceptor_@_org.springframework.transaction.interceptor.TransactionInterceptor transactionManager_@_org.springframework.jdbc.datasource.DataSourceTransactionManager transactionTemplate_@_org.springframework.transaction.support.TransactionTemplate userProfileAddCmdExe_@_com.alibaba.craftsman.command.UserProfileAddCmdExe userProfileGetQryExe_@_com.alibaba.craftsman.command.query.UserProfileGetQryExe userProfileListQryExe_@_com.alibaba.craftsman.command.query.UserProfileListQryExe userProfileRepository_@_com.alibaba.craftsman.repository.UserProfileRepository userProfileServiceImpl_@_com.alibaba.craftsman.service.UserProfileServiceImpl userProfileTunnel_@_com.alibaba.craftsman.tunnel.database.UserProfileTunnel userProfileUpdateCmdExe_@_com.alibaba.craftsman.command.UserProfileUpdateCmdExe viewControllerHandlerMapping_@_org.springframework.web.servlet.HandlerMapping viewResolver_@_org.springframework.web.servlet.view.ContentNegotiatingViewResolver websocketServletWebServerCustomizer_@_org.springframework.boot.autoconfigure.websocket.servlet.TomcatWebSocketServletWebServerCustomizer welcomePageHandlerMapping_@_org.springframework.boot.autoconfigure.web.servlet.WelcomePageHandlerMapping ================================================ FILE: cola-samples/craftsman/start/src/test/resources/spring-mock-test.xml ================================================ ================================================ FILE: cola-samples/craftsman/start/src/test/testAddCmd.http ================================================ POST http://localhost:8080/metrics/ata Content-Type: application/json Cache-Control: no-cache { "ataMetricCO": { "ownerId": 12345, "title": "testAdd", "url": "testAdd", "thumbsUpCount": 100, "hitCount": 10, "commentCount": 10, "favoriteCount": 10 }, "needsOperator": true, "operater": "system" } ================================================ FILE: cola-samples/craftsman/start/src/test/testQry.http ================================================ GET http://localhost:8080/metrics/ata?ownerId=12345 Content-Type: application/json Cache-Control: no-cache ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- # JAVA_HOME - location of a JDK home dir, required when download maven via java source # MVNW_REPOURL - repo url base for downloading maven distribution # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- set -euf [ "${MVNW_VERBOSE-}" != debug ] || set -x # OS specific support. native_path() { printf %s\\n "$1"; } case "$(uname)" in CYGWIN* | MINGW*) [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" native_path() { cygpath --path --windows "$1"; } ;; esac # set JAVACMD and JAVACCMD set_java_home() { # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched if [ -n "${JAVA_HOME-}" ]; then if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" JAVACCMD="$JAVA_HOME/bin/javac" if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 return 1 fi fi else JAVACMD="$( 'set' +e 'unset' -f command 2>/dev/null 'command' -v java )" || : JAVACCMD="$( 'set' +e 'unset' -f command 2>/dev/null 'command' -v javac )" || : if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 return 1 fi fi } # hash string like Java String::hashCode hash_string() { str="${1:-}" h=0 while [ -n "$str" ]; do char="${str%"${str#?}"}" h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) str="${str#?}" done printf %x\\n $h } verbose() { :; } [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } die() { printf %s\\n "$1" >&2 exit 1 } trim() { # MWRAPPER-139: # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. # Needed for removing poorly interpreted newline sequences when running in more # exotic environments such as mingw bash on Windows. printf "%s" "${1}" | tr -d '[:space:]' } # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; :Linux*x86_64*) distributionPlatform=linux-amd64 ;; *) echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 distributionPlatform=linux-amd64 ;; esac distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" distributionUrlName="${distributionUrl##*/}" distributionUrlNameMain="${distributionUrlName%.*}" distributionUrlNameMain="${distributionUrlNameMain%-bin}" MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" exec_maven() { unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } if [ -d "$MAVEN_HOME" ]; then verbose "found existing MAVEN_HOME at $MAVEN_HOME" exec_maven "$@" fi case "${distributionUrl-}" in *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; esac # prepare tmp dir if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } trap clean HUP INT TERM EXIT else die "cannot create temp dir" fi mkdir -p -- "${MAVEN_HOME%/*}" # Download and Install Apache Maven verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." verbose "Downloading from: $distributionUrl" verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" # select .zip or .tar.gz if ! command -v unzip >/dev/null; then distributionUrl="${distributionUrl%.zip}.tar.gz" distributionUrlName="${distributionUrl##*/}" fi # verbose opt __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v # normalize http auth case "${MVNW_PASSWORD:+has-password}" in '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; esac if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then verbose "Found wget ... using wget" wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then verbose "Found curl ... using curl" curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" elif set_java_home; then verbose "Falling back to use Java to download" javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" cat >"$javaSource" <<-END public class Downloader extends java.net.Authenticator { protected java.net.PasswordAuthentication getPasswordAuthentication() { return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); } public static void main( String[] args ) throws Exception { setDefault( new Downloader() ); java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); } } END # For Cygwin/MinGW, switch paths to Windows format before running javac and java verbose " - Compiling Downloader.java ..." "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" verbose " - Running Downloader.java ..." "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" fi # If specified, validate the SHA-256 sum of the Maven distribution zip file if [ -n "${distributionSha256Sum-}" ]; then distributionSha256Result=false if [ "$MVN_CMD" = mvnd.sh ]; then echo "Checksum validation is not supported for maven-mvnd." >&2 echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then distributionSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $distributionSha256Result = false ]; then echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi # unzip and move if command -v unzip >/dev/null; then unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" ================================================ FILE: mvnw.cmd ================================================ <# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) @SET __MVNW_CMD__= @SET __MVNW_ERROR__= @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% @SET PSModulePath= @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% @SET __MVNW_PSMODULEP_SAVE= @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> $ErrorActionPreference = "Stop" if ($env:MVNW_VERBOSE -eq "true") { $VerbosePreference = "Continue" } # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl if (!$distributionUrl) { Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" } switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { "maven-mvnd-*" { $USE_MVND = $true $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" $MVN_CMD = "mvnd.cmd" break } default { $USE_MVND = $false $MVN_CMD = $script -replace '^mvnw','mvn' break } } # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" if ($env:MAVEN_USER_HOME) { $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" } $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" exit $? } if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" } # prepare tmp dir $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null trap { if ($TMP_DOWNLOAD_DIR.Exists) { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } } New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null # Download and Install Apache Maven Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." Write-Verbose "Downloading from: $distributionUrl" Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" $webclient = New-Object System.Net.WebClient if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null # If specified, validate the SHA-256 sum of the Maven distribution zip file $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum if ($distributionSha256Sum) { if ($USE_MVND) { Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." } Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." } } # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { Write-Error "fail to move MAVEN_HOME" } } finally { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" ================================================ FILE: pom.xml ================================================ 4.0.0 com.alibaba.cola cola-dummy-aggregation-parent dummy-SNAPSHOT pom cola-components cola-archetypes cola-samples/craftsman ================================================ FILE: scripts/bump_cola_version ================================================ #!/bin/bash set -eEuo pipefail # adjust current dir to script dir cd "$(dirname "$(readlink -f "$0")")" source bash-buddy/lib/trap_error_info.sh source bash-buddy/lib/common_utils.sh source bash-buddy/lib/java_build_utils.sh readonly nl=$'\n' # new line # shellcheck disable=SC2154 [ $# -ne 1 ] && cu::die "need only 1 argument for version!$nl${nl}usage:$nl $0 4.x.y" readonly bump_version="$1" ( cu::head_line_echo "bump cola version of cola-components to $bump_version" cd ../cola-components/ jvb::mvn_cmd \ org.codehaus.mojo:versions-maven-plugin:2.16.2:set \ -DgenerateBackupPoms=false \ -DprocessAllModules=true \ -DnewVersion="$bump_version" ) ( cu::head_line_echo "bump cola version of cola-archetypes to $bump_version" cd ../cola-archetypes/ jvb::mvn_cmd \ org.codehaus.mojo:versions-maven-plugin:2.16.2:set \ -DgenerateBackupPoms=false \ -DnewVersion="$bump_version" cu::log_then_run -s \ sed -ri 's~()(.*)()~\1'"$bump_version"'\3~' \ cola-archetype-service/src/main/resources/archetype-resources/pom.xml \ cola-archetype-web/src/main/resources/archetype-resources/pom.xml ) ( cu::head_line_echo "bump cola version of samples to $bump_version" cd ../cola-samples/ cu::log_then_run -s \ sed -ri 's~()(.*)()~\1'"$bump_version"'\3~' \ craftsman/pom.xml ) ================================================ FILE: scripts/integration_test ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" BASH_BUDDY_ROOT="$(readlink -f bash-buddy)" readonly BASH_BUDDY_ROOT source "$BASH_BUDDY_ROOT/lib/trap_error_info.sh" source "$BASH_BUDDY_ROOT/lib/common_utils.sh" source "$BASH_BUDDY_ROOT/lib/java_build_utils.sh" ################################################################################ # ci build logic ################################################################################ readonly default_build_jdk_version=17 # shellcheck disable=SC2034 readonly JDK_VERSIONS=( "$default_build_jdk_version" ) readonly default_jh_var_name="JAVA${default_build_jdk_version}_HOME" # Here use `-D performRelease` intendedly to check release operations. # # De-activate a maven profile from command line # https://stackoverflow.com/questions/25201430 # # shellcheck disable=SC2034 JVB_MVN_OPTS=( "${JVB_DEFAULT_MVN_OPTS[@]}" -DperformRelease -P'!gen-sign' ${CI_MORE_MVN_OPTS:+${CI_MORE_MVN_OPTS}} ) ################################################################################ # ci build logic ################################################################################ cd .. extractFirstElementValueFromPom() { (($# == 2)) || die "${FUNCNAME[0]} need only 2 arguments, actual arguments: $*" local element=$1 local pom_file=$2 grep \<"$element"'>.* "$pom_file" | awk -F' 'NR==1 {print $2}' } test_cola_archetype() { ( JVB_MVN_OPTS=("${JVB_DEFAULT_MVN_OPTS[@]}") readonly archetype_name=cola-framework-archetype-service cu::head_line_echo "test archetype:generate by $archetype_name" # NOTE: DO NOT declare archetypeVersion var as readonly, its value is supplied by subshell. archetypeVersion=$(extractFirstElementValueFromPom version cola-archetypes/cola-archetype-service/pom.xml) # shellcheck disable=SC2030 readonly demo_dir="cola-archetypes/target/$archetype_name-demo" rm -rf "$demo_dir" mkdir -p "$demo_dir" cd "$demo_dir" # shellcheck disable=SC2030 readonly artifactId=demo-service jvb::mvn_cmd archetype:generate \ -DgroupId=com.alibaba.cola.demo.archetype-service \ -DartifactId="$artifactId" \ -Dversion=1.0.0-SNAPSHOT \ -Dpackage=com.alibaba.cola.demo.service \ -DarchetypeGroupId=com.alibaba.cola \ -DarchetypeArtifactId=$archetype_name \ -DarchetypeVersion="$archetypeVersion" \ -DinteractiveMode=false \ -DarchetypeCatalog=local cd "$artifactId" jvb::mvn_cmd install ) ( JVB_MVN_OPTS=("${JVB_DEFAULT_MVN_OPTS[@]}") readonly archetype_name=cola-framework-archetype-web cu::head_line_echo "test archetype:generate by $archetype_name" # NOTE: DO NOT declare archetypeVersion var as readonly, its value is supplied by subshell. archetypeVersion=$(extractFirstElementValueFromPom version cola-archetypes/cola-archetype-web/pom.xml) # shellcheck disable=SC2031 readonly demo_dir="cola-archetypes/target/$archetype_name-demo" rm -rf "$demo_dir" mkdir -p "$demo_dir" cd "$demo_dir" # shellcheck disable=SC2031 readonly artifactId=demo-web jvb::mvn_cmd archetype:generate \ -DgroupId=com.alibaba.cola.demo.archetype-web \ -DartifactId="$artifactId" \ -Dversion=1.0.0-SNAPSHOT \ -Dpackage=com.alibaba.cola.demo.web \ -DarchetypeGroupId=com.alibaba.cola \ -DarchetypeArtifactId=$archetype_name \ -DarchetypeVersion="$archetypeVersion" \ -DinteractiveMode=false \ -DarchetypeCatalog=local cd "$artifactId" jvb::mvn_cmd install ) } ######################################## # default jdk 11, do build and test ######################################## [ -d "${!default_jh_var_name:-}" ] || cu::die "\$${default_jh_var_name}(${!default_jh_var_name:-}) dir is not existed!" export JAVA_HOME="${!default_jh_var_name}" cu::head_line_echo "build and test with Java $default_build_jdk_version: $JAVA_HOME" jvb::mvn_cmd clean install test_cola_archetype ######################################## # test by multiply version jdks ######################################## for jdk_version in "${JDK_VERSIONS[@]}"; do # skip default jdk, already tested above [ "$jdk_version" = "$default_build_jdk_version" ] && continue jh_var_name="JAVA${jdk_version}_HOME" [ -d "${!jh_var_name:-}" ] || cu::die "\$${jh_var_name}(${!jh_var_name:-}) dir is not existed!" export JAVA_HOME="${!jh_var_name}" cu::head_line_echo "test with Java $jdk_version: $JAVA_HOME" # just test without build jvb::mvn_cmd surefire:test test_cola_archetype done ================================================ FILE: scripts/maven-deploy.md ================================================ # COLA发布操作说明 COLA发布到`Maven`中央库操作过程/CheckList。 ## 0. 前置准备与配置 在Maven的`setting.xml`中配置`oss.sonatype.org`账号: ```xml ossrh __YOUR_USERNAME__ __YOUR_PASSWORD__ ``` 更多发布操作说明(如用于`GPG`签名的`GPG`安装与配置),参见: - OSSRH Guide https://central.sonatype.org/pages/ossrh-guide.html - Deploying to OSSRH with Apache Maven - Introduction https://central.sonatype.org/pages/apache-maven.html 发布过程与发布文件的查看地址: - sonatype的发布控制台 https://oss.sonatype.org/index.html - Maven中央库的文件查看 https://repo1.maven.org/maven2/com/alibaba/cola/ 发布使用`JDK 11`,为了生成`Javadoc`更现代。 TODO:这个约束应该要去掉。使用`JDK 8`能发布挺好 :") ## 1. 发布 COLA Components 先确认版本号,去掉`SNAPSHOT`,如`4.x.y`。 更新版本操作可以通过脚本[`bump_cola_version`](bump_cola_version)来统一完成。 在[COLA Components的根目录](../cola-components),执行发布 ```bash ./mvnw clean && ./mvnw deploy -DperformRelease ``` ## 2. 发布 COLA Archetype 先确认版本号,去掉`SNAPSHOT`,如`4.x.y`: - 更新 Archetype工程的POM文件的工程版本号: - [`cola-archetypes/pom.xml`](../cola-archetypes/pom.xml) - [`cola-archetype-service/pom.xml`](../cola-archetypes/cola-archetype-service/pom.xml) - [`cola-archetype-web/pom.xml`](../cola-archetypes/cola-archetype-web/pom.xml) - 更新 Archetype模板中的POM文件的`cola.components.version`: - [`cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/pom.xml`](../cola-archetypes/cola-archetype-service/src/main/resources/archetype-resources/pom.xml) - [`cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/pom.xml`](../cola-archetypes/cola-archetype-web/src/main/resources/archetype-resources/pom.xml) 更新版本操作可以通过脚本[`bump_cola_version`](bump_cola_version)来统一完成。 在[COLA Archetype的根目录](../cola-archetypes),执行发布 ```bash ./mvnw clean && ./mvnw deploy -DperformRelease ``` ## 3. 使用发布版本的COLA Archetype重新生成Sample 在[Samples目录](../samples)执行: ```bash rm -rf craftsman ./mvnw archetype:generate \ -DgroupId=com.alibaba.craftsman \ -DartifactId=craftsman \ -Dversion=1.0.0-SNAPSHOT \ -Dpackage=com.alibaba.craftsman \ -DarchetypeGroupId=com.alibaba.cola \ -DarchetypeArtifactId=cola-framework-archetype-web \ -DarchetypeVersion=4.x.y \ -DinteractiveMode=false ``` 然后`git`提交Sample。